summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJeroen van Meeuwen (Kolab Systems) <vanmeeuwen@kolabsys.com>2011-03-23 13:15:27 +0000
committerJeroen van Meeuwen (Kolab Systems) <vanmeeuwen@kolabsys.com>2011-03-23 13:15:27 +0000
commitfff400379f15390fbf90a75a1748f230bbdf0ee6 (patch)
treec5f6a4f6c77fb576d31fadcbbf513f63a891a5a6
parent50519b624a9e3aeb67cad2131ebe227d1d38f4d1 (diff)
downloadpykolab-fff400379f15390fbf90a75a1748f230bbdf0ee6.tar.gz
Enhance logger and config subsystem usage in pykolab.auth
-rw-r--r--pykolab/auth/__init__.py66
-rw-r--r--pykolab/auth/ldap/__init__.py254
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