Ticket #926: 0001-LDAP-URL-based-on-RFC-2255.patch

File 0001-LDAP-URL-based-on-RFC-2255.patch, 7.4 KB (added by sumpfralle, 10 years ago)
  • mediagoblin/plugins/ldap/README.rst

    From dc6658bae6ec07f1ff71a1d6ed9fdb3425fae043 Mon Sep 17 00:00:00 2001
    From: Lars Kruse <devel@sumpfralle.de>
    Date: Fri, 25 Jul 2014 22:53:04 +0200
    Subject: [PATCH] LDAP URL based on RFC 2255
    
    ---
     mediagoblin/plugins/ldap/README.rst |   17 ++++--
     mediagoblin/plugins/ldap/tools.py   |  101 ++++++++++++++++++++++++++++-------
     2 files changed, 97 insertions(+), 21 deletions(-)
    
    diff --git a/mediagoblin/plugins/ldap/README.rst b/mediagoblin/plugins/ldap/README.rst
    index ea9a34b..a2072ec 100644
    a b under the ldap plugin::  
    5050Make any necessary changes to the above to work with your sever. Make sure
    5151``{username}`` is where the username should be in LDAP_USER_DN_TEMPLATE.
    5252   
     53Starting with MediaGoblin v0.7 the following syntax is allows even more details::
     54
     55    [[mediagoblin.plugins.ldap]]
     56    [[[server1]]]
     57    LDAP_USER_URL = 'ldap://ldap.testathon.net:389/ou=users,dc=testathon,dc=net?uid?sub?(objectClass=*)'
     58    [[[server2]]]
     59    ...
     60
     61`RFC 2255 <http://www.ietf.org/rfc/rfc2255.txt>` describes the above
     62``LDAP_USER_URL`` in more detail.
     63
    5364If you would like to fetch the users email from the ldap server upon account
    54 registration, add ``LDAP_SEARCH_BASE = 'ou=users,dc=testathon,dc=net'`` and
    55 ``EMAIL_SEARCH_FIELD = 'mail'`` under you server configuration in your
    56 MediaGoblin .ini file.
     65registration, add ``LDAP_SEARCH_BASE = 'ou=users,dc=testathon,dc=net'``
     66(only if you use ``LDAP_SERVER_URI``) and ``EMAIL_SEARCH_FIELD = 'mail'``
     67under you server configuration in your MediaGoblin .ini file.
    5768
    5869.. Warning::
    5970   By default, this plugin provides no encryption when communicating with the
  • mediagoblin/plugins/ldap/tools.py

    diff --git a/mediagoblin/plugins/ldap/tools.py b/mediagoblin/plugins/ldap/tools.py
    index 1c43679..1ae5655 100644
    a b  
    1515# along with this program.  If not, see <http://www.gnu.org/licenses/>.
    1616import ldap
    1717import logging
     18import re
     19import urllib2
    1820
    1921from mediagoblin.tools import pluginapi
    2022
    _log = logging.getLogger(__name__)  
    2426class LDAP(object):
    2527    def __init__(self):
    2628        self.ldap_settings = pluginapi.get_config('mediagoblin.plugins.ldap')
     29        self.conn = None
    2730
    28     def _connect(self, server):
    29         _log.info('Connecting to {0}.'.format(server['LDAP_SERVER_URI']))
    30         self.conn = ldap.initialize(server['LDAP_SERVER_URI'])
     31    def _connect(self, server_uri, use_start_tls):
     32        _log.info('Connecting to {0}.'.format(server_uri))
     33        self.conn = ldap.initialize(server_uri)
    3134
    32         if server['LDAP_START_TLS'] == 'true':
     35        if use_start_tls == 'true':
    3336            _log.info('Initiating TLS')
    3437            self.conn.start_tls_s()
    3538
    36     def _get_email(self, server, username):
     39    def _get_entry_value(self, user_dn, field_name):
    3740        try:
    38             results = self.conn.search_s(server['LDAP_SEARCH_BASE'],
    39                                         ldap.SCOPE_SUBTREE, 'uid={0}'
    40                                         .format(username),
    41                                         [server['EMAIL_SEARCH_FIELD']])
    42 
    43             email = results[0][1][server['EMAIL_SEARCH_FIELD']][0]
     41            results = self.conn.search_s(user_dn, ldap.SCOPE_BASE,
     42                                        attrlist=[field_name])
     43            return results[0][1][field_name][0]
    4444        except KeyError:
    45             email = None
     45            return None
    4646
    47         return email
     47    def _parse_ldap_url(self, url):
     48        url_regex = re.compile(r'^([\w+.-]+://[\w.-]+(?:\d+)?)/(.*)$')
     49        match = re.search(url_regex, url)
     50        if match:
     51            server_uri, query = match.groups()
     52            result = {'SERVER_URI': server_uri}
     53            tokens = query.split('?')
     54            result['BASE_DN'] = ''
     55            # 'base' is the default scope
     56            result['SCOPE'] = ldap.SCOPE_BASE
     57            attribute = ''
     58            scope = ''
     59            result['FILTERS'] = '(objectClass=*)'
     60            try:
     61                result['BASE_DN'] = tokens.pop(0)
     62                # 'uid' is the default attribute
     63                result['ATTRIBUTES'] = tokens.pop(0).split(",") or ['uid']
     64                scope = tokens.pop(0)
     65                result['FILTERS'] = tokens.pop(0)
     66            except IndexError:
     67                pass
     68            try:
     69                result['SCOPE'] = {'': ldap.SCOPE_BASE,
     70                                   'base': ldap.SCOPE_BASE,
     71                                   'one': ldap.SCOPE_ONELEVEL,
     72                                   'sub': ldap.SCOPE_SUBTREE}[scope]
     73            except KeyError as err:
     74                _log.info(err)
     75            return result
     76        else:
     77            return None
     78
     79    def _get_dn(self, username, attribute, base_dn, scope, filters):
     80        if filters:
     81            if filters.startswith("("):
     82                # remove surrounding braces - otherwise the filter string fails
     83                filters = filters.lstrip("(").rstrip(")")
     84            filters = "(&({0})({1}={2}))".format(filters, attribute, username)
     85        else:
     86            filters = "{0}={1}".format(attribute, username)
     87        try:
     88            results = self.conn.search_s(base_dn, scope, filters)
     89            return results[0][0]
     90        except KeyError:
     91            return None
    4892
    4993    def login(self, username, password):
    5094        for k, v in self.ldap_settings.iteritems():
     95            _log.debug("Server settings: {1}".format(k, v))
     96            server_uri = None
    5197            try:
    52                 self._connect(v)
    53                 user_dn = v['LDAP_USER_DN_TEMPLATE'].format(username=username)
     98                if 'LDAP_USER_URL' in v:
     99                    login_settings = self._parse_ldap_url(v['LDAP_USER_URL'])
     100                    _log.debug("Parsed LDAP_USER_URL: {0}".format(login_settings))
     101                    server_uri = login_settings["SERVER_URI"]
     102                    self._connect(server_uri, v.get('LDAP_START_TLS', None))
     103                    user_dn = self._get_dn(username,
     104                                           login_settings["ATTRIBUTES"][0],
     105                                           login_settings["BASE_DN"],
     106                                           login_settings["SCOPE"],
     107                                           login_settings["FILTERS"])
     108                else:
     109                    # use LDAP_USER_DN_TEMPLATE and LDAP_SERVER_URI
     110                    server_uri = v["LDAP_SERVER_URI"]
     111                    self._connect(server_uri, v.get('LDAP_START_TLS', None))
     112                    user_dn = v['LDAP_USER_DN_TEMPLATE'].format(username=username)
     113                _log.debug("DN of user: {0}".format(user_dn))
    54114                self.conn.simple_bind_s(user_dn, password.encode('utf8'))
    55                 email = self._get_email(v, username)
     115                if 'EMAIL_SEARCH_FIELD' in v:
     116                    email = self._get_entry_value(user_dn, v['EMAIL_SEARCH_FIELD'])
     117                else:
     118                    email = None
    56119                return username, email
    57120
    58121            except ldap.LDAPError, e:
    59122                _log.info(e)
    60123
    61124            finally:
    62                 _log.info('Unbinding {0}.'.format(v['LDAP_SERVER_URI']))
    63                 self.conn.unbind()
     125                _log.info('Unbinding {0}.'.format(server_uri))
     126                if self.conn:
     127                    self.conn.unbind()
    64128
    65129        return False, None
     130