diff options
author | Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen@kolabsys.com> | 2011-03-23 13:15:27 +0000 |
---|---|---|
committer | Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen@kolabsys.com> | 2011-03-23 13:15:27 +0000 |
commit | fff400379f15390fbf90a75a1748f230bbdf0ee6 (patch) | |
tree | c5f6a4f6c77fb576d31fadcbbf513f63a891a5a6 | |
parent | 50519b624a9e3aeb67cad2131ebe227d1d38f4d1 (diff) | |
download | pykolab-fff400379f15390fbf90a75a1748f230bbdf0ee6.tar.gz |
Enhance logger and config subsystem usage in pykolab.auth
-rw-r--r-- | pykolab/auth/__init__.py | 66 | ||||
-rw-r--r-- | pykolab/auth/ldap/__init__.py | 254 |
2 files changed, 196 insertions, 124 deletions
diff --git a/pykolab/auth/__init__.py b/pykolab/auth/__init__.py index 8154284..17ab4b8 100644 --- a/pykolab/auth/__init__.py +++ b/pykolab/auth/__init__.py @@ -20,33 +20,41 @@ import logging import os import time -from pykolab.conf import Conf +import pykolab + from pykolab.translate import _ +conf = pykolab.getConf() +log = pykolab.getLogger('pykolab.auth') + class Auth(object): """ This is the Authentication and Authorization module for PyKolab. """ - def __init__(self, conf=None): + def __init__(self): """ Initialize the authentication class. - """ - self.conf = conf - self.log = logging.getLogger('pykolab') + self._auth is the placeholder for domain-specific authentication + backends. The keys are the primary domain names for each domain. + """ self._auth = {} def authenticate(self, login): - # Login is a list of authentication credentials: - # 0: username - # 1: password - # 2: service - # 3: realm, optional + """ + Verify login credentials supplied in login against the appropriate + authentication backend. + + Login is a simple list of username, password, service and, + optionally, the realm. + """ if len(login) == 3: - # Realm not set - use_virtual_domains = self.conf.get('imap', 'virtual_domains', quiet=True) + # The realm has not been specified. See if we know whether or not + # to use virtual_domains, as this may be a cause for the realm not + # having been specified seperately. + use_virtual_domains = conf.get('imap', 'virtual_domains') if use_virtual_domains == "userid": print "# Derive domain from login[0]" elif not use_virtual_domains: @@ -58,7 +66,7 @@ class Auth(object): if len(login[0].split('@')) > 1: domain = login[0].split('@')[1] else: - domain = self.conf.get("kolab", "primary_domain") + domain = conf.get("kolab", "primary_domain") # realm overrides domain if len(login) == 4: @@ -78,7 +86,7 @@ class Auth(object): if domain == None: section = 'kolab' - domain = self.conf.get('kolab', 'primary_domain') + domain = conf.get('kolab', 'primary_domain') else: section = domain @@ -87,21 +95,38 @@ class Auth(object): #print "Connecting to Authentication backend for domain %s" %(domain) - if not self.conf.has_section(section): + if not conf.has_section(section): section = 'kolab' - if self.conf.get(section, 'auth_mechanism') == 'ldap': + if conf.get(section, 'auth_mechanism') == 'ldap': from pykolab.auth import ldap - self._auth[domain] = ldap.LDAP(self.conf) - elif self.conf.get(section, 'auth_mechanism') == 'sql': + self._auth[domain] = ldap.LDAP() + elif conf.get(section, 'auth_mechanism') == 'sql': from pykolab.auth import sql - self._auth[domain] = sql.SQL(self.conf) + self._auth[domain] = sql.SQL() #else: ## TODO: Fail more verbose #print "COULD NOT FIND AUTHENTICATION MECHANISM FOR DOMAIN %s" %(domain) #print self._auth + def disconnect(self, domain=None): + """ + Connect to the domain authentication backend using domain, or fall + back to the primary domain specified by the configuration. + """ + + if domain == None: + section = 'kolab' + domain = conf.get('kolab', 'primary_domain') + else: + section = domain + + if not self._auth.has_key(section) or self._auth[section] == None: + return + + self._auth[domain]._disconnect() + def list_domains(self): """ List the domains using the auth_mechanism setting in the kolab @@ -118,7 +143,7 @@ class Auth(object): self.connect() # Find the domains in the authentication backend. - kolab_primary_domain = self.conf.get('kolab', 'primary_domain') + kolab_primary_domain = conf.get('kolab', 'primary_domain') domains = self._auth[kolab_primary_domain]._list_domains() # If no domains are found, the primary domain is used. @@ -130,6 +155,7 @@ class Auth(object): def list_users(self, primary_domain, secondary_domains=[]): self.connect(domain=primary_domain) users = self._auth[primary_domain]._list_users(primary_domain, secondary_domains) + self.disconnect(domain=primary_domain) return users def domain_default_quota(self, domain): diff --git a/pykolab/auth/ldap/__init__.py b/pykolab/auth/ldap/__init__.py index 5ec2d82..c81f7dd 100644 --- a/pykolab/auth/ldap/__init__.py +++ b/pykolab/auth/ldap/__init__.py @@ -24,29 +24,32 @@ import ldap.resiter import logging import time +import pykolab + from pykolab import utils -from pykolab.conf import Conf from pykolab.constants import * +from pykolab.errors import * from pykolab.translate import _ +log = pykolab.getLogger('pykolab.auth') +conf = pykolab.getConf() + class LDAP(object): """ Abstraction layer for the LDAP authentication / authorization backend, for use with Kolab. """ - def __init__(self, conf): - self.conf = conf - self.log = logging.getLogger('pykolab.ldap') + def __init__(self): self.ldap = None self.bind = False def _authenticate(self, login, domain): - print "Authenticating:", login, domain + log.debug(_("Attempting to authenticate user %s in domain %s") %(login, domain), level=8) self._connect() user_dn = self._find_dn(login[0], domain) try: - print "Binding with user_dn %s and password %s" %(user_dn, login[1]) + log.debug(_("Binding with user_dn %s and password %s") %(user_dn, login[1])) # Needs to be synchronous or succeeds and continues setting retval to True!! self.ldap.simple_bind_s(user_dn, login[1]) retval = True @@ -55,20 +58,33 @@ class LDAP(object): return retval def _connect(self, domain=None): + """ + Connect to the LDAP server through the uri configured. + + Pass it a configuration section name to get the ldap options from + that section instead of the default [ldap] section. + """ if not self.ldap == None: return if domain == None: section = 'ldap' - elif not self.conf.has_option(domain, uri): + elif not conf.has_option(domain, uri): section = 'ldap' - self.log.debug(_("Connecting to LDAP...")) + log.debug(_("Connecting to LDAP..."), level=8) + + uri = conf.get(section, 'uri') + + log.debug(_("Attempting to use LDAP URI %s") %(uri), level=8) - uri = self.conf.get(section, 'uri') + trace_level = 0 - self.log.debug(_("Attempting to use LDAP URI %s") %(uri)) - self.ldap = ldap.initialize(uri, trace_level=0) + # TODO: Perhaps we can be smarter then this! + if conf.debuglevel >= 9: + trace_level = 1 + + self.ldap = ldap.initialize(uri, trace_level=trace_level) self.ldap.protocol_version = 3 def _bind(self): @@ -77,11 +93,13 @@ class LDAP(object): if not self.bind: # TODO: Use bind credentials for the domain itself. - bind_dn = self.conf.get('ldap', 'bind_dn') - bind_pw = self.conf.get('ldap', 'bind_pw') + bind_dn = conf.get('ldap', 'bind_dn') + bind_pw = conf.get('ldap', 'bind_pw') + # TODO: Binding errors control try: self.ldap.simple_bind_s(bind_dn, bind_pw) + self.bind = True except ldap.SERVER_DOWN: # Can't contact LDAP server # @@ -89,8 +107,8 @@ class LDAP(object): # - Service faulty # - Firewall pass - - self.bind = True + except ldap.INVALID_CREDENTIALS: + log.error(_("Invalid bind credentials")) def _unbind(self): self.ldap.unbind() @@ -111,32 +129,32 @@ class LDAP(object): self._bind() if domain == None: - domain = self.conf.get('kolab', 'primary_domain') + domain = conf.get('kolab', 'primary_domain') domain_root_dn = self._kolab_domain_root_dn(domain) - if self.conf.has_option(domain_root_dn, 'user_base_dn'): + if conf.has_option(domain_root_dn, 'user_base_dn'): section = domain_root_dn else: section = 'ldap' - user_base_dn = self.conf.get_raw(section, 'user_base_dn') %({'base_dn': domain_root_dn}) + user_base_dn = conf.get_raw(section, 'user_base_dn') %({'base_dn': domain_root_dn}) - print "user_base_dn:", user_base_dn + #print "user_base_dn:", user_base_dn - if self.conf.has_option(domain_root_dn, 'kolab_user_filter'): + if conf.has_option(domain_root_dn, 'kolab_user_filter'): section = domain_root_dn else: section = 'ldap' - kolab_user_filter = self.conf.get(section, 'kolab_user_filter', quiet=True) + kolab_user_filter = conf.get(section, 'kolab_user_filter', quiet=True) if kolab_user_filter == "": - kolab_user_filter = self.conf.get('ldap', 'kolab_user_filter') + kolab_user_filter = conf.get('ldap', 'kolab_user_filter') - search_filter = "(&(%s=%s)%s)" %(self.conf.get('cyrus-sasl', 'result_attribute'),login,kolab_user_filter) + search_filter = "(&(%s=%s)%s)" %(conf.get('cyrus-sasl', 'result_attribute'),login,kolab_user_filter) - print search_filter + #print search_filter _results = self._search(user_base_dn, filterstr=search_filter, attrlist=['dn']) @@ -182,10 +200,14 @@ class LDAP(object): while True: pages += 1 #print "Getting page %d..." %(pages) - (_result_type, _result_data, _result_msgid, _result_controls) = self.ldap.result3(_search) + try: + (_result_type, _result_data, _result_msgid, _result_controls) = self.ldap.result3(_search) + except ldap.NO_SUCH_OBJECT, e: + log.warning(_("Object %s searched no longer exists") %(base_dn)) + break _results.extend(_result_data) if (pages % 2) == 0: - self.log.debug(_("%d results...") %(len(_results))) + log.debug(_("%d results...") %(len(_results))) pctrls = [c for c in _result_controls if c.controlType == ldap.LDAP_CONTROL_PAGE_OID] @@ -217,17 +239,17 @@ class LDAP(object): def _domain_default_quota(self, domain): domain_root_dn = self._kolab_domain_root_dn(domain) - if self.conf.has_option(domain_root_dn, 'default_quota'): - return self.conf.get(domain_root_dn, 'default_quota', quiet=True) - elif self.conf.has_option('ldap', 'default_quota'): - return self.conf.get('ldap', 'default_quota', quiet=True) - elif self.conf.has_option('kolab', 'default_quota'): - return self.conf.get('kolab', 'default_quota', quiet=True) + if conf.has_option(domain_root_dn, 'default_quota'): + return conf.get(domain_root_dn, 'default_quota', quiet=True) + elif conf.has_option('ldap', 'default_quota'): + return conf.get('ldap', 'default_quota', quiet=True) + elif conf.has_option('kolab', 'default_quota'): + return conf.get('kolab', 'default_quota', quiet=True) def _domain_section(self, domain): domain_root_dn = self._kolab_domain_root_dn(domain) - if self.conf.has_section(domain_root_dn): + if conf.has_section(domain_root_dn): return domain_root_dn else: return 'ldap' @@ -235,7 +257,13 @@ class LDAP(object): def _get_user_attribute(self, user, attribute): self._bind() - (user_dn, user_attrs) = self._search(user['dn'], ldap.SCOPE_BASE, '(objectclass=*)', [ attribute ])[0] + result = self._search(user['dn'], ldap.SCOPE_BASE, '(objectclass=*)', [ attribute ]) + + if len(result) >= 1: + (user_dn, user_attrs) = result[0] + else: + log.warning(_("Could not get user attribute %s for %s") %(attribute,user['dn'])) + return None user_attrs = utils.normalize(user_attrs) if not user_attrs.has_key(attribute): @@ -255,6 +283,8 @@ class LDAP(object): if attribute in [ 'mailquota', 'mailalternateaddress' ]: if not user.has_key('objectclass'): user['objectclass'] = self._get_user_attribute(user,'objectclass') + if user['objectclass'] == None: + return if not 'mailrecipient' in user['objectclass']: user['objectclass'].append('mailrecipient') self._set_user_attribute(user, 'objectclass', user['objectclass']) @@ -262,7 +292,7 @@ class LDAP(object): try: self.ldap.modify(user['dn'], [(ldap.MOD_REPLACE, attribute, value)]) except: - self.log.warning(_("LDAP modification of attribute %s" + \ + log.warning(_("LDAP modification of attribute %s" + \ " for %s to value %s failed") %(attribute,user_dn,value)) def _list_domains(self): @@ -274,14 +304,14 @@ class LDAP(object): name and a list of secondary domain names. """ - self.log.info(_("Listing domains...")) + log.info(_("Listing domains...")) self._connect() - bind_dn = self.conf.get('ldap', 'bind_dn') - bind_pw = self.conf.get('ldap', 'bind_pw') + bind_dn = conf.get('ldap', 'bind_dn') + bind_pw = conf.get('ldap', 'bind_pw') - domain_base_dn = self.conf.get('ldap', 'domain_base_dn', quiet=True) + domain_base_dn = conf.get('ldap', 'domain_base_dn', quiet=True) if domain_base_dn == "": # No domains are to be found in LDAP, return an empty list. @@ -289,12 +319,15 @@ class LDAP(object): return [] # If we haven't returned already, let's continue searching - kolab_domain_filter = self.conf.get('ldap', 'kolab_domain_filter') + kolab_domain_filter = conf.get('ldap', 'kolab_domain_filter') # TODO: this function should be wrapped for error handling - self.ldap.simple_bind(bind_dn, bind_pw) + try: + self.ldap.simple_bind_s(bind_dn, bind_pw) + except ldap.SERVER_DOWN, e: + raise AuthBackendError, _("Authentication database DOWN") - _search = self.ldap.search( + _search = self._search( domain_base_dn, ldap.SCOPE_SUBTREE, kolab_domain_filter, @@ -303,51 +336,38 @@ class LDAP(object): ) domains = [] - _result_type = None - while not _result_type == ldap.RES_SEARCH_RESULT: - try: - (_result_type, _domains) = self._result(_search, False, 0) - except AttributeError, e: - if self.ldap == None: - self._bind() - continue - if not _domains == None: - for _domain in _domains: - (domain_dn, _domain_attrs) = _domain - primary_domain = None - secondary_domains = [] - - _domain_attrs = utils.normalize(_domain_attrs) - - # TODO: Where we use associateddomain is actually configurable - if type(_domain_attrs['associateddomain']) == list: - primary_domain = _domain_attrs['associateddomain'].pop(0) - secondary_domains = _domain_attrs['associateddomain'] - else: - primary_domain = _domain_attrs['associateddomain'] + for domain_dn, domain_attrs in _search: + primary_domain = None + secondary_domains = [] + + domain_attrs = utils.normalize(domain_attrs) + + # TODO: Where we use associateddomain is actually configurable + if type(domain_attrs['associateddomain']) == list: + primary_domain = domain_attrs['associateddomain'].pop(0) + secondary_domains = domain_attrs['associateddomain'] + else: + primary_domain = domain_attrs['associateddomain'] - domains.append((primary_domain,secondary_domains)) + domains.append((primary_domain,secondary_domains)) return domains def _kolab_domain_root_dn(self, domain): self._bind() - print "Finding domain root dn for domain %s" %(domain) + log.debug(_("Finding domain root dn for domain %s") %(domain), level=8) - bind_dn = self.conf.get('ldap', 'bind_dn') - bind_pw = self.conf.get('ldap', 'bind_pw') + bind_dn = conf.get('ldap', 'bind_dn') + bind_pw = conf.get('ldap', 'bind_pw') - domain_base_dn = self.conf.get('ldap', 'domain_base_dn', quiet=True) - - print "domain_base_dn:", domain_base_dn + domain_base_dn = conf.get('ldap', 'domain_base_dn', quiet=True) if not domain_base_dn == "": - print "domain_base_dn is not \"\"" # If we haven't returned already, let's continue searching - domain_name_attribute = self.conf.get('ldap', 'domain_name_attribute') + domain_name_attribute = conf.get('ldap', 'domain_name_attribute') _results = self._search( domain_base_dn, @@ -358,46 +378,45 @@ class LDAP(object): domains = [] for _domain in _results: (domain_dn, _domain_attrs) = _domain - domain_rootdn_attribute = self.conf.get('ldap', 'domain_rootdn_attribute') + domain_rootdn_attribute = conf.get('ldap', 'domain_rootdn_attribute') _domain_attrs = utils.normalize(_domain_attrs) if _domain_attrs.has_key(domain_rootdn_attribute): return _domain_attrs[domain_rootdn_attribute] - print "domain_base_dn is \"\"" return utils.standard_root_dn(domain) def _list_users(self, primary_domain, secondary_domains=[]): - self.log.info(_("Listing users for domain %s (and %s)") %(primary_domain, ' '.join(secondary_domains))) + log.info(_("Listing users for domain %s (and %s)") %(primary_domain, ' '.join(secondary_domains))) self._bind() # TODO: Bind with read-only credentials, perhaps even domain-specific, please - bind_dn = self.conf.get('ldap', 'bind_dn') - #bind_dn = self.conf.get('ldap', 'ro_bind_dn') - bind_pw = self.conf.get('ldap', 'bind_pw') - #bind_pw = self.conf.get('ldap', 'ro_bind_pw') + bind_dn = conf.get('ldap', 'bind_dn') + #bind_dn = conf.get('ldap', 'ro_bind_dn') + bind_pw = conf.get('ldap', 'bind_pw') + #bind_pw = conf.get('ldap', 'ro_bind_pw') domain_root_dn = self._kolab_domain_root_dn(primary_domain) - if self.conf.has_option(domain_root_dn, 'user_base_dn'): + if conf.has_option(domain_root_dn, 'user_base_dn'): section = domain_root_dn else: section = 'ldap' - user_base_dn = self.conf.get_raw(section, 'user_base_dn') %({'base_dn': domain_root_dn}) + user_base_dn = conf.get_raw(section, 'user_base_dn') %({'base_dn': domain_root_dn}) - if self.conf.has_option(domain_root_dn, 'kolab_user_filter'): + if conf.has_option(domain_root_dn, 'kolab_user_filter'): section = domain_root_dn else: section = 'ldap' - kolab_user_filter = self.conf.get(section, 'kolab_user_filter', quiet=True) + kolab_user_filter = conf.get(section, 'kolab_user_filter', quiet=True) if kolab_user_filter == "": - kolab_user_filter = self.conf.get('ldap', 'kolab_user_filter') + kolab_user_filter = conf.get('ldap', 'kolab_user_filter') - self.ldap.simple_bind(bind_dn, bind_pw) + self.ldap.simple_bind_s(bind_dn, bind_pw) # TODO: The quota and alternative address attributes are actually # supposed to be settings. @@ -409,14 +428,17 @@ class LDAP(object): attrsonly=0 ) - self.log.debug(_("Iterating over %d users, making sure we have the necessary attributes...") %(len(_search))) + log.debug(_("Iterating over %d users, making sure we have the necessary attributes...") %(len(_search))) #print "SEARCH RESULTS:", _search users = [] - _result_type = None + + num_users = len(_search) + num_user = 0 for user_dn, user_attrs in _search: + num_user += 1 user = {} user['dn'] = user_dn if not user.has_key('standard_domain'): @@ -436,31 +458,55 @@ class LDAP(object): #print "============== EXECING PLUGINS =================" - if self.conf.has_option(domain_root_dn, 'primary_mail'): - primary_mail = self.conf.plugins.exec_hook("set_primary_mail", - kw={'primary_mail': self.conf.get_raw(domain_root_dn, 'primary_mail')}, - args=(user_attrs, primary_domain, secondary_domains) + if conf.has_option(domain_root_dn, 'primary_mail'): + primary_mail = conf.plugins.exec_hook("set_primary_mail", + kw={ + 'primary_mail': conf.get_raw(domain_root_dn, 'primary_mail'), + 'user_attrs': user_attrs, + 'primary_domain': primary_domain, + 'secondary_domains': secondary_domains + } ) - if not primary_mail == None and (not user.has_key('mail') or not primary_mail == user['mail']): - self._set_user_attribute(user, 'mail', primary_mail) - user['mail'] = primary_mail - - if self.conf.has_option(domain_root_dn, 'secondary_mail'): - secondary_mail = self.conf.plugins.exec_hook("set_secondary_mail", - kw={'secondary_mail': self.conf.get_raw(domain_root_dn, 'secondary_mail')}, - args=(user_attrs, primary_domain, secondary_domains) + if not primary_mail == None: + if not user.has_key('mail'): + self._set_user_attribute(user, 'mail', primary_mail) + user['mail'] = primary_mail + else: + if not primary_mail == user['mail']: + user['old_mail'] = user['mail'] + user['mail'] = primary_mail + + section = None + + if conf.has_option(domain_root_dn, 'secondary_mail'): + section = domain_root_dn + elif conf.has_option('kolab', 'secondary_mail'): + section = 'kolab' + + if not section == None: + secondary_mail = conf.plugins.exec_hook("set_secondary_mail", kw={ + 'secondary_mail': conf.get_raw(domain_root_dn, 'secondary_mail'), + 'user_attrs': user_attrs, + 'primary_domain': primary_domain, + 'secondary_domains': secondary_domains + } ) - #print "secondary mail:", secondary_mail, len(secondary_mail) - if not secondary_mail == None: - secondary_mail = list(set(secondary_mail)) + if not secondary_mail == None: + secondary_mail = list(set(secondary_mail)) + if not user.has_key('mailalternateaddress'): + self._set_user_attribute(user, 'mailalternateaddress', secondary_mail) + user['mailalternateaddress'] = secondary_mail + else: if not secondary_mail == user['mailalternateaddress']: - self._set_user_attribute(user, 'mailalternateaddress', secondary_mail) user['mailalternateaddress'] = secondary_mail users.append(user) + if (num_user % 1000) == 0: + log.info(_("Done iterating over user %d of %d") %(num_user,num_users)) + #print "USERS:", users return users |