summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJeroen van Meeuwen (Kolab Systems) <vanmeeuwen@kolabsys.com>2012-04-19 17:00:20 +0100
committerJeroen van Meeuwen (Kolab Systems) <vanmeeuwen@kolabsys.com>2012-04-19 17:00:20 +0100
commit77d3804884bd6fc13182ec26f8d2d447cda3be61 (patch)
treece79d070bf9629d2067d253372342d893c9b7935
parent1a2d4b809307316a59961811d3f252e0b50477fe (diff)
downloadpykolab-77d3804884bd6fc13182ec26f8d2d447cda3be61.tar.gz
Rebase on specification
-rw-r--r--conf/kolab.conf37
-rw-r--r--pykolab/__init__.py12
-rw-r--r--pykolab/auth/__init__.py145
-rw-r--r--pykolab/auth/ldap/__init__.py2033
-rw-r--r--pykolab/base.py93
-rw-r--r--pykolab/conf/__init__.py10
-rw-r--r--pykolab/conf/defaults.py4
-rw-r--r--pykolab/imap/__init__.py367
-rw-r--r--pykolab/imap/cyrus.py7
-rw-r--r--pykolab/plugins/recipientpolicy/__init__.py4
-rw-r--r--pykolab/wap_client/__init__.py75
11 files changed, 1482 insertions, 1305 deletions
diff --git a/conf/kolab.conf b/conf/kolab.conf
index 2a7888c..fcce25f 100644
--- a/conf/kolab.conf
+++ b/conf/kolab.conf
@@ -2,51 +2,40 @@
primary_domain = kolabsys.com
auth_mechanism = ldap
imap_backend = cyrus-imap
-default_quota = 2097152
-virtual_domains = userid
-quota_attribute = mailquota
-unique_attribute = nsunique
-mailserver_attribute = mailhost
-mail_attributes = mail, alias
-auth_attributes = mail, alias, uid
[ldap]
-ldap_uri = ldap://localhost
+ldap_uri = ldap://localhost:389
base_dn = dc=kolabsys,dc=com
+bind_dn = cn=Directory Manager
+bind_pw = 0B2NFj581H8kZgO
user_base_dn = ou=People,%(base_dn)s
-user_filter = (objectclass=inetorgperson)
user_scope = sub
+user_filter = (objectclass=inetorgperson)
kolab_user_filter = (objectclass=kolabinetorgperson)
group_base_dn = ou=Groups,%(base_dn)s
+group_scope = sub
group_filter = (|(objectclass=groupofuniquenames)(objectclass=groupofurls))
kolab_group_filter = (|(objectclass=kolabgroupofuniquenames)(objectclass=kolabgroupofurls))
-bind_dn = cn=Directory Manager
-bind_pw = 55WX2YOgYmt8EO5
domain_base_dn = cn=kolab,cn=config
domain_filter = (&(associatedDomain=*))
domain_name_attribute = associateddomain
domain_rootdn_attribute = inetdomainbasedn
service_bind_dn = uid=kolab-service,ou=Special Users,dc=kolabsys,dc=com
-service_bind_pw = hAIOAhP8qIUEPbB
+service_bind_pw = wc18bqshFmifGtN
+quota_attribute = mailquota
+unique_attribute = nsuniqueid
+mail_attributes = mail, alias
+auth_attributes = mail, alias, uid
[kolab_smtp_access_policy]
-uri = mysql://user:pass@localhost/kolab
-retention = 3600
+cache_uri = mysql://user:pass@localhost/database
+cache_retention = 30
address_search_attrs = mail, alias
-[kolab_telemetry]
-uri = mysql://user:pass@localhost/kolab
-retention = 30
-
-[kolab_wap]
-sql_uri = mysql://user:pass@localhost/kolab
-skin = default
-admin_auto_fields_rw = false
-
[cyrus-imap]
uri = imaps://localhost:993
admin_login = cyrus-admin
-admin_password = 5xosOaTm_Kg8Ax5
+admin_password = xgbGH1xHSCFxPH2
[cyrus-sasl]
result_attribute = mail
diff --git a/pykolab/__init__.py b/pykolab/__init__.py
index 22bf88e..3f0b520 100644
--- a/pykolab/__init__.py
+++ b/pykolab/__init__.py
@@ -26,6 +26,7 @@
import logging
import shutil
import sys
+import threading
import traceback
from pykolab.logger import Logger
@@ -44,10 +45,11 @@ from pykolab.conf import Conf
conf = Conf()
def getConf():
- return conf
+ _data = threading.local()
+ if hasattr(_data, 'conf'):
+ log.debug(_("Returning thread local configuration"))
+ return _data.conf
-from pykolab.auth import Auth
-auth = Auth()
+ return conf
-from pykolab.imap import IMAP
-imap = IMAP()
+import base
diff --git a/pykolab/auth/__init__.py b/pykolab/auth/__init__.py
index 04fd578..5686e4b 100644
--- a/pykolab/auth/__init__.py
+++ b/pykolab/auth/__init__.py
@@ -21,28 +21,27 @@ import os
import time
import pykolab
+import pykolab.base
from pykolab.translate import _
-conf = pykolab.getConf()
log = pykolab.getLogger('pykolab.auth')
+conf = pykolab.getConf()
-class Auth(object):
+class Auth(pykolab.base.Base):
"""
This is the Authentication and Authorization module for PyKolab.
"""
- def __init__(self):
+ def __init__(self, domain):
"""
Initialize the authentication class.
-
- self._auth is the placeholder for domain-specific authentication
- backends. The keys are the primary domain names for each domain.
"""
+ pykolab.base.Base.__init__(self)
+
self._auth = None
- # Placeholder mapping back to the primary domain name space
- self.secondary_domains = {}
+ self.domain = domain
def authenticate(self, login):
"""
@@ -70,6 +69,8 @@ class Auth(object):
if len(login[0].split('@')) > 1:
domain = login[0].split('@')[1]
+ elif len(login) >= 4:
+ domain = login[3]
else:
domain = conf.get("kolab", "primary_domain")
@@ -77,9 +78,7 @@ class Auth(object):
if len(login) == 4:
domain = login[3]
- self.connect(domain)
-
- retval = self._auth._authenticate(login, domain)
+ retval = self._auth.authenticate(login, domain)
return retval
@@ -89,6 +88,7 @@ class Auth(object):
back to the primary domain specified by the configuration.
"""
+ log.debug(_("Called for domain %r") % (domain))
if not self._auth == None:
return
@@ -118,16 +118,18 @@ class Auth(object):
if conf.get(section, 'auth_mechanism') == 'ldap':
from pykolab.auth import ldap
- self._auth = ldap.LDAP()
+ self._auth = ldap.LDAP(self.domain)
elif conf.get(section, 'auth_mechanism') == 'sql':
from pykolab.auth import sql
- self._auth = sql.SQL()
+ self._auth = sql.SQL(self.domain)
else:
from pykolab.auth import ldap
- self._auth = ldap.LDAP()
+ self._auth = ldap.LDAP(self.domain)
+
+ self._auth.connect()
- def disconnect(self, domain=None):
+ def disconnect(self):
"""
Connect to the domain authentication backend using domain, or fall
back to the primary domain specified by the configuration.
@@ -144,55 +146,16 @@ class Auth(object):
self._auth._disconnect()
- def find_group(self, attr, value, domain=None, **kw):
- self.connect(domain)
-
- if self.secondary_domains.has_key(domain):
- log.debug(
- _("Using primary domain %s instead of secondary domain %s")
- % (
- self.secondary_domains[domain],
- domain
- ),
- level=9
- )
-
- domain = self.secondary_domains[domain]
-
- return self._auth._find_group(attr, value, domain=domain, **kw)
-
- def find_user(self, attr, value, domain=None, **kw):
- self.connect(domain)
-
- if self.secondary_domains.has_key(domain):
- log.debug(
- _("Using primary domain %s instead of secondary domain %s")
- % (
- self.secondary_domains[domain],
- domain
- ),
- level=9
- )
-
- domain = self.secondary_domains[domain]
+ def find_recipient(self, address):
+ """
+ Find one or more entries corresponding to the recipient address.
+ """
+ return self._auth.find_recipient(address)
+ def find_user(self, attr, value, **kw):
return self._auth._find_user(attr, value, domain=domain, **kw)
- def search_users(self, attr, value, domain=None, **kw):
- self.connect(domain)
-
- if self.secondary_domains.has_key(domain):
- log.debug(
- _("Using primary domain %s instead of secondary domain %s")
- % (
- self.secondary_domains[domain],
- domain
- ),
- level=9
- )
-
- domain = self.secondary_domains[domain]
-
+ def search_users(self, attr, value, **kw):
return self._auth._search_users(attr, value, domain=domain, **kw)
def list_domains(self):
@@ -225,76 +188,20 @@ class Auth(object):
return domains
- def list_users(self, primary_domain, secondary_domains=[], callback=None):
- self.connect(domain=primary_domain)
- users = self._auth._list_users(
- primary_domain,
- secondary_domains,
- callback
- )
- self.disconnect(domain=primary_domain)
- return users
-
- def synchronize(self, primary_domain, secondary_domains=[]):
- self.connect(domain=primary_domain)
- self.list_users(
- primary_domain,
- secondary_domains,
- callback=self._auth.sync_user
- )
+ def synchronize(self):
+ self._auth.synchronize()
def domain_default_quota(self, domain):
- self.connect(domain=domain)
-
- if self.secondary_domains.has_key(domain):
- domain = self.secondary_domains[domain]
-
return self._auth._domain_default_quota(domain)
- def domain_section(self, domain):
- self.connect(domain=domain)
-
- if self.secondary_domains.has_key(domain):
- domain = self.secondary_domains[domain]
-
- return self._auth._domain_section(domain)
-
- def get_group_attribute(self, domain, group, attribute):
- self.connect(domain=domain)
-
- if self.secondary_domains.has_key(domain):
- domain = self.secondary_domains[domain]
-
- return self._auth._get_group_attribute(group, attribute)
-
def get_user_attribute(self, domain, user, attribute):
- self.connect(domain=domain)
-
- if self.secondary_domains.has_key(domain):
- domain = self.secondary_domains[domain]
-
return self._auth._get_user_attribute(user, attribute)
def get_user_attributes(self, domain, user, attributes):
- self.connect(domain=domain)
-
- if self.secondary_domains.has_key(domain):
- domain = self.secondary_domains[domain]
-
return self._auth._get_user_attributes(user, attributes)
def search_mail_address(self, domain, mail_address):
- self.connect(domain=domain)
-
- if self.secondary_domains.has_key(domain):
- domain = self.secondary_domains[domain]
-
return self._auth._search_mail_address(domain, mail_address)
def set_user_attribute(self, domain, user, attribute, value):
- self.connect(domain=domain)
-
- if self.secondary_domains.has_key(domain):
- domain = self.secondary_domains[domain]
-
self._auth._set_user_attribute(user, attribute, value)
diff --git a/pykolab/auth/ldap/__init__.py b/pykolab/auth/ldap/__init__.py
index f8d4cdd..17e6533 100644
--- a/pykolab/auth/ldap/__init__.py
+++ b/pykolab/auth/ldap/__init__.py
@@ -16,6 +16,7 @@
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
+import datetime
import _ldap
import ldap
import ldap.async
@@ -24,6 +25,7 @@ import logging
import time
import pykolab
+import pykolab.base
from pykolab import utils
from pykolab.constants import *
@@ -95,22 +97,56 @@ class SimplePagedResultsControl(ldap.controls.SimplePagedResultsControl):
else:
return self.controlValue[0]
-class LDAP(object):
+class LDAP(pykolab.base.Base):
"""
Abstraction layer for the LDAP authentication / authorization backend,
for use with Kolab.
"""
- def __init__(self):
+ def __init__(self, domain=None):
+ """
+ Initialize the LDAP object for domain. If no domain is specified,
+ domain name space configured as 'kolab'.'primary_domain' is used.
+ """
+ pykolab.base.Base.__init__(self)
+
self.ldap = None
self.bind = False
+ if domain == None:
+ self.domain = conf.get('kolab', 'primary_domain')
+ else:
+ self.domain = domain
+
+ def authenticate(self, login, realm):
+ """
+ Find the entry corresponding to login, and attempt a bind.
+
+ login is a tuple with 4 values. In order of appearance;
+
+ [0] - the login username.
+
+ [1] - the password
+
+ [2] - the service (optional)
+
+ [3] - the realm
+
+ Called from pykolab.auth.Auth, the realm parameter is derived, while
+ login[3] preserves the originally specified realm.
+ """
- def _authenticate(self, login, domain):
- log.debug(_("Attempting to authenticate user %s in domain %s")
- % (login, domain), level=8)
+ log.debug(
+ _("Attempting to authenticate user %s in realm %s") % (
+ login,
+ realm
+ ),
+ level=8
+ )
+
+ self.connect()
+
+ user_dn = self._find_user_dn(login[0], realm)
- self._connect()
- user_dn = self._find_dn(login[0], domain)
try:
log.debug(_("Binding with user_dn %s and password %s")
% (user_dn, login[1]))
@@ -129,31 +165,16 @@ class LDAP(object):
return retval
- def _connect(self, domain=None):
+ def connect(self):
"""
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'
- key = 'ldap_uri'
-
- if conf.has_option(domain, 'uri'):
- log.warning(_("Deprecation: Setting 'uri' for LDAP in section %s needs to be updated to 'ldap_uri'") % (domain))
- section = domain
- key = 'uri'
- elif conf.has_option(domain, 'ldap_uri'):
- section = domain
- key = 'ldap_uri'
-
log.debug(_("Connecting to LDAP..."), level=8)
- uri = conf.get(section, key)
+ uri = self.config_get('ldap_uri')
log.debug(_("Attempting to use LDAP URI %s") % (uri), level=8)
@@ -166,14 +187,443 @@ class LDAP(object):
self.ldap.protocol_version = 3
self.ldap.supported_controls = []
+ def entry_dn(self, entry_id):
+ """
+ Get a entry's distinguished name for an entry ID.
+
+ The entry ID may be any of:
+
+ - an entry's value for the configured unique_attribute,
+ - a (syntactically valid) Distinguished Name,
+ - a dictionary such as previously returned as (part of) the result
+ of a search.
+ """
+ entry_dn = None
+
+ if self._entry_dn(entry_id):
+ return entry_id
+
+ if self._entry_dict(entry_id):
+ return entry_id['dn']
+
+ unique_attribute = self.config_get('unique_attribute')
+ base_dn = self.config_get('base_dn')
+
+ _search = self.ldap.search_ext(
+ base_dn,
+ ldap.SCOPE_SUBTREE,
+ '(%s=%s)' % (unique_attribute, entry_id),
+ ['entrydn']
+ )
+
+ (
+ _result_type,
+ _result_data,
+ _result_msgid,
+ _result_controls
+ ) = self.ldap.result3(_search)
+
+ if len(_result_data) >= 1:
+ (entry_dn, entry_attrs) = _result_data[0]
+
+ return entry_dn
+
+ def get_entry_attribute(self, entry_id, attribute):
+ """
+ Get an attribute for an entry.
+
+ Return the attribute value if successful, or None if not.
+ """
+
+ entry_attrs = self.get_entry_attributes(entry_id, [attribute])
+
+ if entry_attrs.has_key(attribute):
+ return entry_attrs[attribute]
+ else:
+ return None
+
+ def get_entry_attributes(self, entry_id, attributes):
+ """
+ Get multiple attributes for an entry.
+ """
+
+ entry_dn = self.entry_dn(entry_id)
+
+ _search = self.ldap.search_ext(
+ entry_dn,
+ ldap.SCOPE_BASE,
+ '(objectclass=*)',
+ [ 'dn' ] + attributes
+ )
+
+ (
+ _result_type,
+ _result_data,
+ _result_msgid,
+ _result_controls
+ ) = self.ldap.result3(_search)
+
+ if len(_result_data) >= 1:
+ (_entry_dn, _entry_attrs) = _result_data[0]
+ else:
+ return None
+
+ return utils.normalize(_entry_attrs)
+
+ def find_recipient(self, address="*", exclude_entry_id=None):
+ """
+ Given an address string or list of addresses, find one or more valid
+ recipients.
+
+ Use this function only to detect whether an address is already in
+ use by any entry in the tree.
+
+ Specify an additional entry_id to exclude to exclude matches against
+ the current entry.
+ """
+
+ if not exclude_entry_id == None:
+ __filter_prefix = "(&"
+ __filter_suffix = "(!(%s=%s)))" % (
+ self.config_get('unique_attribute'),
+ exclude_entry_id
+ )
+
+ else:
+ __filter_prefix = ""
+ __filter_suffix = ""
+
+ kolab_filter = self._kolab_filter()
+ recipient_address_attrs = self.config_get_list("mail_attributes")
+ result_attributes = recipient_address_attrs
+ result_attributes.append(self.config_get('unique_attribute'))
+
+ _filter = "(|"
+
+ for recipient_address_attr in recipient_address_attrs:
+ if isinstance(address, basestring):
+ _filter += "(%s=%s)" % (recipient_address_attr, address)
+ else:
+ for _address in address:
+ _filter += "(%s=%s)" % (recipient_address_attr, _address)
+
+ _filter += ")"
+
+ _filter = "%s%s%s" % (__filter_prefix,_filter,__filter_suffix)
+
+
+ log.debug(_("Finding recipient with filter %r") % (_filter), level=8)
+
+ if len(_filter) <= 6:
+ return None
+
+ _results = self.ldap.search_s(
+ self.config_get('base_dn'),
+ scope=ldap.SCOPE_SUBTREE,
+ filterstr=_filter,
+ attrlist=result_attributes,
+ attrsonly=True
+ )
+
+ _entry_dns = []
+
+ for _result in _results:
+ (_entry_id, _entry_attrs) = _result
+ _entry_dns.append(_entry_id)
+
+ return _entry_dns
+
+ def list_secondary_domains(self):
+ """
+ List alias domain name spaces for the current domain name space.
+ """
+ return [s for s, p in self.secondary_domains.iteritems() if p == self.domain]
+
+ def recipient_policy(self, entry):
+ """
+ Apply a recipient policy, if configured.
+
+ Given an entry, returns the entry's attribute values to be set.
+ """
+
+ entry_dn = self.entry_dn(entry)
+ entry_modifications = {}
+ entry_type = self._entry_type(entry)
+ primary_mail = None
+ primary_mail_attribute = self.config_get_list('mail_attributes')[0]
+ secondary_mail = None
+ secondary_mail_attribute = self.config_get_list('mail_attributes')[1]
+ want_attrs = []
+
+ # See which mail attributes we would want to control.
+ #
+ # 'mail' is considered for primary_mail,
+ # 'alias' and 'mailalternateaddress' are considered for secondary mail.
+ #
+ primary_mail = self.config_get_raw('%s_primary_mail' % (entry_type))
+ if primary_mail == None and entry_type == 'user':
+ primary_mail = self.config_get_raw('primary_mail')
+
+ secondary_mail = self.config_get_raw('%s_secondary_mail' % (entry_type))
+ if secondary_mail == None and entry_type == 'user':
+ secondary_mail = self.config_get_raw('secondary_mail')
+
+ # See if the relevant mail attributes exist
+ _mail_attrs = self.config_get('mail_attributes')
+ for _mail_attr in _mail_attrs:
+ if not entry.has_key(_mail_attr):
+ if _mail_attr == primary_mail_attribute:
+ if not primary_mail == None:
+ want_attrs.append(_mail_attr)
+ elif _mail_attr == secondary_mail_attribute:
+ if not secondary_mail == None:
+ want_attrs.append(_mail_attr)
+
+ # Also append the preferredlanguage or 'native tongue' configured
+ # for the entry.
+ if not entry.has_key('preferredlanguage'):
+ want_attrs.append('preferredlanguage')
+
+ # If we wanted anything, now is the type to get it.
+ if len(want_attrs) > 0:
+ attributes = self.get_entry_attributes(entry_dn, want_attrs)
+
+ for attribute in attributes.keys():
+ entry[attribute] = attributes[attribute]
+
+ # Primary mail address
+ if not primary_mail == None:
+ if not entry.has_key(primary_mail_attribute) or entry[primary_mail_attribute] == None:
+ primary_mail_address = conf.plugins.exec_hook(
+ "set_primary_mail",
+ kw={
+ 'primary_mail': primary_mail,
+ 'entry': entry,
+ 'primary_domain': self.domain
+ }
+ )
+ else:
+ primary_mail_address = entry[primary_mail_attribute]
+
+ i = 1
+ _primary_mail = primary_mail_address
+
+ done = False
+ while not done:
+ results = self.find_recipient(_primary_mail, entry['id'])
+
+ # Length of results should be 0 (no entry found)
+ # or 1 (which should be the entry we're looking at here)
+ if len(results) == 0:
+ log.debug(
+ _("No results for mail address %s found") % (
+ _primary_mail
+ ),
+ level=8
+ )
+
+ done = True
+ continue
+
+ if len(results) == 1:
+ log.debug(
+ _("1 result for address %s found, verifying") % (
+ _primary_mail
+ ),
+ level=8
+ )
+
+ almost_done = True
+ for result in results:
+ if not result == entry_dn:
+ log.debug(
+ _("Too bad, primary email address %s " + \
+ "already in use for %s (we are %s)") % (
+ _primary_mail,
+ result,
+ entry_dn
+ ),
+ level=8
+ )
+
+ almost_done = False
+ else:
+ log.debug(_("Address assigned to us"), level=8)
+
+ if almost_done:
+ done = True
+ continue
+
+ i += 1
+ _primary_mail = "%s%d@%s" % (
+ primary_mail_address.split('@')[0],
+ i,
+ primary_mail_address.split('@')[1]
+ )
+
+ primary_mail_address = _primary_mail
+
+ ###
+ ### FIXME
+ ###
+ if not primary_mail_address == None:
+ if not entry.has_key(primary_mail_attribute):
+ self.set_entry_attribute(entry, primary_mail_attribute, primary_mail_address)
+ entry_modifications[primary_mail_attribute] = primary_mail_address
+ else:
+ if not primary_mail_address == entry[primary_mail_attribute]:
+ self.set_entry_attribute(entry, primary_mail_attribute, primary_mail_address)
+
+ entry_modifications[primary_mail_attribute] = primary_mail_address
+
+ if not secondary_mail == None:
+ # Execute the plugin hook
+ suggested_secondary_mail = conf.plugins.exec_hook(
+ "set_secondary_mail",
+ kw={
+ 'secondary_mail': secondary_mail,
+ 'entry': entry,
+ 'domain': self.domain,
+ 'primary_domain': self.domain,
+ 'secondary_domains': self.list_secondary_domains()
+ }
+ ) # end of conf.plugins.exec_hook() call
+
+ secondary_mail_addresses = []
+
+ for _secondary_mail in suggested_secondary_mail:
+ i = 1
+ __secondary_mail = _secondary_mail
+
+ done = False
+ while not done:
+ results = self.find_recipient(__secondary_mail, entry['id'])
+
+ # Length of results should be 0 (no entry found)
+ # or 1 (which should be the entry we're looking at here)
+ if len(results) == 0:
+ log.debug(
+ _("No results for address %s found") % (
+ __secondary_mail
+ ),
+ level=8
+ )
+
+ done = True
+ continue
+
+ if len(results) == 1:
+ log.debug(
+ _("1 result for address %s found, " + \
+ "verifying...") % (
+ __secondary_mail
+ ),
+ level=8
+ )
+
+ almost_done = True
+ for result in results:
+ if not result == entry_dn:
+ log.debug(
+ _("Too bad, secondary email " + \
+ "address %s already in use for " + \
+ "%s (we are %s)") % (
+ __secondary_mail,
+ result,
+ entry_dn
+ ),
+ level=8
+ )
+
+ almost_done = False
+ else:
+ log.debug(_("Address assigned to us"), level=8)
+
+ if almost_done:
+ done = True
+ continue
+
+ i += 1
+ __secondary_mail = "%s%d@%s" % (
+ _secondary_mail.split('@')[0],
+ i,
+ _secondary_mail.split('@')[1]
+ )
+
+ secondary_mail_addresses.append(__secondary_mail)
+
+ if not secondary_mail_addresses == None:
+ secondary_mail_addresses = list(set(secondary_mail_addresses))
+ # Avoid duplicates
+ while primary_mail in secondary_mail_addresses:
+ secondary_mail_addresses.pop(secondary_mail_addresses.index(primary_mail))
+
+ if not entry.has_key(secondary_mail_attribute):
+ if not len(secondary_mail_addresses) == 0:
+ self.set_entry_attribute(
+ entry,
+ secondary_mail_attribute,
+ secondary_mail_addresses
+ )
+
+ entry_modifications[secondary_mail_attribute] = secondary_mail_addresses
+ else:
+ if not secondary_mail_addresses == entry[secondary_mail_attribute]:
+ self.set_entry_attribute(
+ entry,
+ secondary_mail_attribute,
+ secondary_mail_addresses
+ )
+
+ entry_modifications[secondary_mail_attribute] = secondary_mail_addresses
+
+ return entry_modifications
+
+ def set_entry_attribute(self, entry_id, attribute, value):
+ self.set_entry_attributes(entry_id, { attribute: value })
+
+ def set_entry_attributes(self, entry_id, attributes):
+ self._bind()
+
+ entry_dn = self.entry_dn(entry_id)
+
+ print entry_dn
+
+ attrs = {}
+ for attribute in attributes.keys():
+ attrs[attribute.lower()] = attributes[attribute]
+
+ def synchronize(self):
+ """
+ Synchronize with LDAP
+ """
+ self._bind()
+
+ _filter = self._kolab_filter()
+
+ modified_after = datetime.datetime.utcfromtimestamp(time.time()-18600).strftime("%Y%m%d%H%M%SZ")
+ _filter = "(&%s(modifytimestamp>=%s))" % (_filter,modified_after)
+
+ log.debug(_("Using filter %r") % (_filter), level=8)
+
+ self._search(
+ self.config_get('base_dn'),
+ filterstr=_filter,
+ attrlist=[ '*', self.config_get('unique_attribute') ],
+ callback=self._synchronize_callback,
+ )
+
+ ###
+ ### API depth level increasing!
+ ###
+
def _bind(self):
- # TODO: Implement some mechanism for r/o, r/w and mgmt binding.
- self._connect()
+ if self.ldap == None:
+ self.connect()
if not self.bind:
- # TODO: Use bind credentials for the domain itself.
- bind_dn = conf.get('ldap', 'bind_dn')
- bind_pw = conf.get('ldap', 'bind_pw')
+ bind_dn = self.config_get('bind_dn')
+ bind_pw = self.config_get('bind_pw')
# TODO: Binding errors control
try:
@@ -189,13 +639,350 @@ class LDAP(object):
except ldap.INVALID_CREDENTIALS:
log.error(_("Invalid bind credentials"))
- def _unbind(self):
- self.ldap.unbind()
- self.bind = False
+ def _change_add_user(self, entry, change):
+ """
+ An entry of type user was added.
+ """
+ mailserver_attribute = self.config_get('mailserver_attribute')
+ result_attribute = conf.get('cyrus-sasl', 'result_attribute')
- def _reconnect(self):
- self._disconnect()
- self._connect()
+ if not entry.has_key(mailserver_attribute):
+ entry[mailserver_attribute] = \
+ self.get_entry_attribute(entry, mailserver_attribute)
+
+ if not entry.has_key(result_attribute):
+ entry = self.recipient_policy(entry)
+ return
+
+ if entry[result_attribute] == None:
+ return
+
+ self.imap.connect(domain=self.domain)
+
+ if not self.imap.user_mailbox_exists(entry[result_attribute]):
+ folder = self.imap.user_mailbox_create(
+ entry[result_attribute],
+ entry[mailserver_attribute]
+ )
+ else:
+ folder = "user%s%s" % (self.imap.separator,entry[result_attribute])
+
+ server = self.imap.user_mailbox_server(folder)
+
+ if not entry[mailserver_attribute] == server:
+ self.set_entry_attribute(entry, mailserver_attribute, server)
+
+ def _change_add_group(self, entry, change):
+ """
+ An entry of type group was added.
+
+ The Kolab daemon has little to do for this type of action on this
+ type of entry.
+ """
+ pass
+
+ def _change_add_role(self, entry, change):
+ """
+ An entry of type role was added.
+
+ The Kolab daemon has little to do for this type of action on this
+ type of entry.
+ """
+ pass
+
+ def _change_add_sharedfolder(self, entry, change):
+ """
+ An entry of type sharedfolder was added.
+ """
+ self.imap.connect(domain=self.domain)
+
+ server = None
+ mailserver_attribute = self.config_get('mailserver_attribute')
+
+ if entry.has_key(mailserver_attribute):
+ server = entry['mailserver_attribute']
+
+ if not entry.has_key('kolabtargetfolder'):
+ entry['kolabtargetfolder'] = self.get_entry_attribute(
+ entry['id'],
+ 'kolabtargetfolder'
+ )
+
+ if not entry.has_key('kolabfoldertype'):
+ entry['kolabfoldertype'] = self.get_entry_attribute(
+ entry['id'],
+ 'kolabfoldertype'
+ )
+
+ #if not entry.has_key('kolabmailfolderaclentry'):
+ #entry['kolabmailfolderaclentry'] = self.get_entry_attribute(
+ #entry['id'],
+ #'kolabmailfolderaclentry'
+ #)
+
+ if entry.has_key('kolabtargetfolder') and not entry['kolabtargetfolder'] == None:
+ folder_path = entry['kolabtargetfolder']
+ else:
+ # TODO: What is *the* way to see if we need to create an @domain
+ # shared mailbox?
+ # TODO^2: self.domain, really? Presumes any mail attribute is
+ # set to the primary domain name space...
+ # TODO^3: Test if the cn is already something@domain
+ result_attribute = conf.get('cyrus-sasl', 'result_attribute')
+ if result_attribute in ['mail']:
+ folder_path = "%s@%s" % (entry['cn'], self.domain)
+ else:
+ folder_path = entry['cn']
+
+ if not self.imap.shared_folder_exists(folder_path):
+ self.imap.shared_folder_create(folder_path, server)
+
+ if entry.has_key('kolabfoldertype') and not entry['kolabfoldertype'] == None:
+ self.imap.shared_folder_set_type(folder_path, entry['kolabfoldertype'])
+
+ #if entry.has_key('kolabmailfolderaclentry') and not entry['kolabmailfolderaclentry'] == None:
+ #self.imap._set_kolab_mailfolder_acls(entry['kolabmailfolderaclentry'])
+
+ #if server == None:
+ #self.entry_set_attribute(mailserver_attribute, server)
+
+ def _change_delete_group(self, entry, change):
+ """
+ An entry of type group was deleted.
+ """
+
+ result_attribute = conf.get('cyrus-sasl', 'result_attribute')
+
+ if not entry.has_key(result_attribute):
+ return None
+
+ if entry[result_attribute] == None:
+ return None
+
+ self.imap.cleanup_acls(entry[result_attribute])
+
+
+ def _change_delete_None(self, entry, change):
+ """
+ Redirect to _change_delete_unknown
+ """
+ self._change_delete_unknown(entry, change)
+
+ def _change_delete_sharedfolder(self, entry, change):
+ pass
+
+ def _change_delete_unknown(self, entry, change):
+ """
+ An entry has been deleted, and we do not know of what object type
+ the entry was - user, group, role or sharedfolder.
+ """
+ result_attribute = conf.get('cyrus-sasl', 'result_attribute')
+ if not entry.has_key(result_attribute):
+ return None
+
+ if entry[result_attribute] == None:
+ return None
+
+ success = True
+ for _type in ['user','group','role','sharedfolder']:
+ try:
+ eval("_change_delete_%s(entry, change)" % (_type))
+ except:
+ success = False
+
+ if success:
+ break
+
+ def _change_delete_user(self, entry, change):
+ """
+ An entry of type user was deleted.
+ """
+
+ result_attribute = conf.get('cyrus-sasl', 'result_attribute')
+
+ if not entry.has_key(result_attribute):
+ return None
+
+ if entry[result_attribute] == None:
+ return None
+
+ self.imap.user_mailbox_delete(entry[result_attribute])
+ self.imap.cleanup_acls(entry[result_attribute])
+
+
+ def _change_moddn_group(self, entry, change):
+ # TODO: If the rdn attribute is the same as the result attribute...
+ pass
+
+ def _change_moddn_role(self, entry, change):
+ pass
+
+ def _change_moddn_user(self, entry, change):
+ old_dn = change['previous_dn']
+ new_dn = change['dn']
+
+ import ldap.dn
+ old_rdn = ldap.dn.explode_dn(old_dn)[0].split('=')[0]
+ new_rdn = ldap.dn.explode_dn(new_dn)[0].split('=')[0]
+
+ result_attribute = conf.get('cyrus-sasl', 'result_attribute')
+
+ if conf.changelog.has_key(entry['id']):
+ old_canon_attr = conf.changelog[entry['id']]
+
+ # See if we have to trigger the recipient policy. Only really applies to
+ # situations in which the result_attribute is used in the old or in the
+ # new DN.
+ trigger_recipient_policy = False
+
+ if old_rdn == result_attribute:
+ if new_rdn == result_attribute:
+ if new_rdn == old_rdn:
+ trigger_recipient_policy = True
+ else:
+ if not new_rdn == old_rdn:
+ trigger_recipient_policy = True
+ else:
+ if new_rdn == result_attribute:
+ if not new_rdn == old_rdn:
+ trigger_recipient_policy = True
+
+ if trigger_recipient_policy:
+ entry_changes = self.recipient_policy(entry)
+ # Now look at entry_changes and old_canon_attr, and see if they're the
+ # same value.
+ if entry_changes.has_key(result_attribute):
+ if not entry_changes[result_attribute] == old_canon_attr:
+ self.imap.user_mailbox_rename(old_canon_attr, entry_changes[result_attribute])
+ conf.changelog[entry['id']] = entry_changes[result_attribute]
+
+ def _change_moddn_sharedfolder(self, entry, change):
+ pass
+
+ def _change_modify_group(self, entry, change):
+ pass
+
+ def _change_modify_role(self, entry, change):
+ pass
+
+ def _change_modify_sharedfolder(self, entry, change):
+ pass
+
+ def _change_modify_user(self, entry, change):
+ for entry_key in conf.changelog.keys():
+ log.debug(_("Current changelog entry %s with %s") % (entry_key,conf.changelog[entry_key]), level=8)
+
+ if conf.changelog.has_key(entry['id']):
+ old_canon_attr = conf.changelog[entry['id']]
+
+ entry_changes = self.recipient_policy(entry)
+
+ log.debug(_("Result from recipient policy: %r") % (entry_changes), level=8)
+
+ result_attribute = conf.get('cyrus-sasl','result_attribute')
+ if entry_changes.has_key(result_attribute):
+ if not entry_changes[result_attribute] == old_canon_attr:
+ self.imap.user_mailbox_rename(old_canon_attr, entry_changes[result_attribute])
+ conf.changelog[entry['id']] = entry_changes[result_attribute]
+
+ def _change_none_group(self, entry, change):
+ """
+ A group entry as part of the initial search result set.
+
+ The Kolab daemon has little to do for this type of action on this
+ type of entry.
+ """
+ pass
+
+ def _change_none_role(self, entry, change):
+ """
+ A role entry as part of the initial search result set.
+
+ The Kolab daemon has little to do for this type of action on this
+ type of entry.
+ """
+ pass
+
+ def _change_none_sharedfolder(self, entry, change):
+ """
+ A sharedfolder entry as part of the initial search result set.
+ """
+ self.imap.connect(domain=self.domain)
+
+ server = None
+ mailserver_attribute = self.config_get('mailserver_attribute')
+
+ if entry.has_key(mailserver_attribute):
+ server = entry['mailserver_attribute']
+
+ if not entry.has_key('kolabtargetfolder'):
+ entry['kolabtargetfolder'] = self.get_entry_attribute(
+ entry['id'],
+ 'kolabtargetfolder'
+ )
+
+ if not entry.has_key('kolabfoldertype'):
+ entry['kolabfoldertype'] = self.get_entry_attribute(
+ entry['id'],
+ 'kolabfoldertype'
+ )
+
+ #if not entry.has_key('kolabmailfolderaclentry'):
+ #entry['kolabmailfolderaclentry'] = self.get_entry_attribute(
+ #entry['id'],
+ #'kolabmailfolderaclentry'
+ #)
+
+ if entry.has_key('kolabtargetfolder') and not entry['kolabtargetfolder'] == None:
+ folder_path = entry['kolabtargetfolder']
+ else:
+ # TODO: What is *the* way to see if we need to create an @domain
+ # shared mailbox?
+ # TODO^2: self.domain, really? Presumes any mail attribute is
+ # set to the primary domain name space...
+ # TODO^3: Test if the cn is already something@domain
+ result_attribute = conf.get('cyrus-sasl', 'result_attribute')
+ if result_attribute in ['mail']:
+ folder_path = "%s@%s" % (entry['cn'], self.domain)
+ else:
+ folder_path = entry['cn']
+
+ if not self.imap.shared_folder_exists(folder_path):
+ self.imap.shared_folder_create(folder_path, server)
+
+ if entry.has_key('kolabfoldertype') and not entry['kolabfoldertype'] == None:
+ self.imap.shared_folder_set_type(folder_path, entry['kolabfoldertype'])
+
+ #if entry.has_key('kolabmailfolderaclentry') and not entry['kolabmailfolderaclentry'] == None:
+ #self.imap._set_kolab_mailfolder_acls(entry['kolabmailfolderaclentry'])
+
+ #if server == None:
+ #self.entry_set_attribute(mailserver_attribute, server)
+
+ def _change_none_user(self, entry, change):
+ """
+ A user entry as part of the initial search result set.
+ """
+ result_attribute = conf.get('cyrus-sasl', 'result_attribute')
+
+ test = self.recipient_policy(entry)
+
+ if not entry.has_key(result_attribute):
+ # TODO: Execute plugin-hook
+ return
+
+ if entry[result_attribute] == None:
+ # TODO: Execute plugin-hook
+ return
+
+ if not conf.changelog.has_key(entry['id']):
+ conf.changelog[entry['id']] = entry[result_attribute]
+
+ self.imap.connect(domain=self.domain)
+
+ if not self.imap.user_mailbox_exists(entry[result_attribute]):
+ folder = self.imap.user_mailbox_create(entry[result_attribute])
+ server = self.imap.user_mailbox_server(folder)
def _disconnect(self):
self._unbind()
@@ -203,65 +990,87 @@ class LDAP(object):
self.ldap = None
self.bind = False
- def _find_dn(self, login, domain=None):
+ def _entry_dict(self, value):
"""
- Find the distinguished name (DN) for an entry in LDAP.
+ Tests if 'value' is a valid entry dictionary with a DN contained
+ within key 'dn'.
+ Returns True or False
"""
- self._connect()
- self._bind()
+ if isinstance(value, dict):
+ if value.has_key('dn'):
+ return True
- if domain == None:
- domain = conf.get('kolab', 'primary_domain')
+ return False
- domain_root_dn = self._kolab_domain_root_dn(domain)
+ def _entry_dn(self, value):
+ """
+ Tests if 'value' is a valid DN.
- if conf.has_option(domain, 'base_dn'):
- section = domain
- else:
- section = 'ldap'
+ Returns True or False
+ """
- user_base_dn = conf.get_raw(section, 'base_dn')
+ # Only basestrings can be DNs
+ if not isinstance(value, basestring):
+ return False
- if conf.has_option(domain, 'user_filter'):
- section = domain
- else:
- section = 'ldap'
-
- user_filter = conf.get(section, 'user_filter', quiet=True)
-
- if user_filter == "":
- user_filter = conf.get('ldap', 'user_filter')
-
- if conf.has_option(section, 'auth_attrs'):
- auth_search_attrs = conf.get_list(section, 'auth_attrs')
- elif conf.has_section('kolab_smtp_access_policy'):
- if conf.has_option('kolab_smtp_access_policy', 'address_search_attrs'):
- log.warning(
- _("Deprecation warning: The setting " + \
- "kolab_smtp_access_policy.address_search_attrs " + \
- "is to be replaced with the 'auth_attrs' key in " + \
- "the 'ldap' or '%s' domain section.") % (domain)
- )
+ try:
+ import ldap.dn
+ ldap_dn = ldap.dn.explode_dn(value)
+ except ldap.DECODING_ERROR:
+ # This is not a DN.
+ return False
- auth_search_attrs = conf.get_list(
- 'kolab_smtp_access_policy',
- 'address_search_attrs'
- )
+ return True
- else:
- auth_search_attrs = [ 'uid', 'mail' ]
- else:
- auth_search_attrs = [ 'uid', 'mail' ]
+ def _entry_type(self, entry_id):
+ """
+ Return the type of object for an entry.
+ """
+
+ entry_dn = self.entry_dn(entry_id)
+
+ base_dn = self.config_get('base_dn')
+
+ for _type in ['user', 'group', 'sharedfolder']:
+ __filter = self.config_get('kolab_%s_filter' % (_type))
+ if __filter == None:
+ __filter = self.config_get('%s_filter' % (_type))
+
+ if not __filter == None:
+ try:
+ result = self._regular_search(entry_dn, filterstr=__filter)
+ except:
+ result = self._regular_search(
+ base_dn,
+ filterstr="(%s=%s)" %(
+ self.config_get('unique_attribute'),
+ entry_id['id'])
+ )
+
+ if not result:
+ continue
+ else:
+ return _type
+
+ def _find_user_dn(self, login, realm):
+ """
+ Find the distinguished name (DN) for an entry in LDAP.
+ """
+
+ domain_root_dn = self._kolab_domain_root_dn(self.domain)
- if not 'uid' in auth_search_attrs:
- auth_search_attrs.append('uid')
+ base_dn = self.config_get('user_base_dn')
+ if base_dn == None:
+ base_dn = self.config_get('base_dn')
+
+ auth_attrs = self.config_get_list('auth_attributes')
auth_search_filter = [ '(|' ]
- for auth_search_attr in auth_search_attrs:
- auth_search_filter.append('(%s=%s)' % (auth_search_attr,login))
- auth_search_filter.append('(%s=%s@%s)' % (auth_search_attr,login,domain))
+ for auth_attr in auth_attrs:
+ auth_search_filter.append('(%s=%s)' % (auth_attr,login))
+ auth_search_filter.append('(%s=%s@%s)' % (auth_attr,login,self.domain))
auth_search_filter.append(')')
@@ -302,183 +1111,185 @@ class LDAP(object):
return _user_dn
- def _find_group(self, attr, value, domain=None, additional_filter=None, base_dn=None):
- self._connect()
+ def _kolab_domain_root_dn(self, domain):
self._bind()
- if domain == None:
- domain = conf.get('kolab', 'primary_domain')
+ log.debug(_("Finding domain root dn for domain %s") % (domain), level=8)
- domain_root_dn = self._kolab_domain_root_dn(domain)
+ bind_dn = conf.get('ldap', 'bind_dn')
+ bind_pw = conf.get('ldap', 'bind_pw')
- if conf.has_option(domain, 'group_base_dn'):
- section = domain
- else:
- section = 'ldap'
+ domain_base_dn = conf.get('ldap', 'domain_base_dn', quiet=True)
- if base_dn == None:
- group_base_dn = conf.get_raw(
- section,
- 'group_base_dn'
- ) % ({'base_dn': domain_root_dn})
- else:
- group_base_dn = base_dn
+ if not domain_base_dn == "":
+
+ # If we haven't returned already, let's continue searching
+ domain_name_attribute = conf.get('ldap', 'domain_name_attribute')
- if type(attr) == str:
- search_filter = "(%s=%s)" % (
- attr,
- value
+ _results = self._search(
+ domain_base_dn,
+ ldap.SCOPE_SUBTREE,
+ "(%s=%s)" % (domain_name_attribute,domain),
+ override_search='_regular_search'
)
- elif type(attr) == list:
- search_filter = "(|"
- for _attr in attr:
- search_filter = "%s(%s=%s)" % (search_filter, _attr, value)
- search_filter = "%s)" % (search_filter)
- if additional_filter:
- search_filter = additional_filter % {
- 'search_filter': search_filter
- }
+ domains = []
+ for _domain in _results:
+ (domain_dn, _domain_attrs) = _domain
+ 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]
- log.debug(
- _("Attempting to find the group with search filter: %s") % (
- search_filter
- ),
- level=8
- )
+ else:
+ if conf.has_option('ldap', 'base_dn'):
+ return conf.get('ldap', 'base_dn')
- _results = self.ldap.search_s(
- group_base_dn,
- scope=ldap.SCOPE_SUBTREE,
- filterstr=search_filter,
- attrlist=[ 'dn' ]
- )
+ return utils.standard_root_dn(domain)
- if len(_results) == 1:
- (_group_dn, _group_attrs) = _results[0]
- else:
- return False
+ def _kolab_filter(self):
+ """
+ Compose a filter using the relevant settings from configuration.
+ """
+ _filter = "(|"
+ for _type in ['user', 'group', 'sharedfolder']:
+ __filter = self.config_get('kolab_%s_filter' % (_type))
+ if __filter == None:
+ __filter = self.config_get('%s_filter' % (_type))
- return _group_dn
+ if not __filter == None:
+ _filter = "%s%s" % (_filter,__filter)
- def _find_user(self, attr, value, domain=None, additional_filter=None, base_dn=None):
- self._connect()
- self._bind()
+ _filter = "%s)" % (_filter)
- if domain == None:
- domain = conf.get('kolab', 'primary_domain')
+ return _filter
- domain_root_dn = self._kolab_domain_root_dn(domain)
+ def _list_domains(self):
+ """
+ Find the domains related to this Kolab setup, and return a list of
+ DNS domain names.
- if conf.has_option(domain, 'user_base_dn'):
- section = domain
- else:
- section = 'ldap'
+ Returns a list of tuples, each tuple containing the primary domain
+ name and a list of secondary domain names.
- if base_dn == None:
- user_base_dn = conf.get_raw(
- section,
- 'user_base_dn'
- ) % ({'base_dn': domain_root_dn})
+ This function should only be called by the primary instance of Auth.
+ """
+
+ log.debug(_("Listing domains..."), level=8)
+
+ self.connect()
+
+ bind_dn = conf.get('ldap', 'bind_dn')
+ bind_pw = conf.get('ldap', 'bind_pw')
+
+ 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.
+ # Note that the Auth() base itself handles this case.
+ return []
+
+ # If we haven't returned already, let's continue searching
+ domain_filter = conf.get('ldap', 'kolab_domain_filter')
+ if not domain_filter == None:
+ log.warning(_("ldap/kolab_domain_filter deprecated, use ldap/domain_filter instead."))
else:
- user_base_dn = base_dn
+ domain_filter = conf.get('ldap', 'domain_filter')
+
+ if domain_base_dn == None or domain_filter == None:
+ return []
+
+ # TODO: this function should be wrapped for error handling
+ try:
+ self.ldap.simple_bind_s(bind_dn, bind_pw)
+ except ldap.SERVER_DOWN, e:
+ raise AuthBackendError, _("Authentication database DOWN")
- if type(attr) == str:
- search_filter = "(%s=%s)" % (
- attr,
- value
+ try:
+ _search = self._search(
+ domain_base_dn,
+ ldap.SCOPE_SUBTREE,
+ domain_filter,
+ # TODO: Where we use associateddomain is actually configurable
+ [ 'associateddomain' ],
+ override_search='_regular_search'
)
- elif type(attr) == list:
- search_filter = "(|"
- for _attr in attr:
- search_filter = "%s(%s=%s)" % (search_filter, _attr, value)
- search_filter = "%s)" % (search_filter)
+ except:
+ return []
- if additional_filter:
- search_filter = additional_filter % {
- 'search_filter': search_filter
- }
+ domains = []
- log.debug(
- _("Attempting to find the user with search filter: %s") % (
- search_filter
- ),
- level=8
- )
+ for domain_dn, domain_attrs in _search:
+ primary_domain = None
+ secondary_domains = []
- _results = self.ldap.search_s(
- user_base_dn,
- scope=ldap.SCOPE_SUBTREE,
- filterstr=search_filter,
- attrlist=[ 'dn' ]
- )
+ domain_attrs = utils.normalize(domain_attrs)
- if len(_results) == 1:
- (_user_dn, _user_attrs) = _results[0]
- else:
- return False
+ # 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']
- return _user_dn
+ domains.append((primary_domain,secondary_domains))
- def _search_users(self, attr, value, domain=None, additional_filter=None, base_dn=None):
- self._connect()
- self._bind()
+ return domains
- if domain == None:
- domain = conf.get('kolab', 'primary_domain')
+ def _reconnect(self):
+ """
+ Reconnect to LDAP
+ """
+ self._disconnect()
+ self.connect()
- domain_root_dn = self._kolab_domain_root_dn(domain)
+ def _synchronize_callback(self, *args, **kw):
+ change_type = None
- if conf.has_option(domain, 'user_base_dn'):
- section = domain
- else:
- section = 'ldap'
+ change_dict = {
+ 'change_type': kw['change_type'],
+ 'previous_dn': kw['previous_dn'],
+ 'change_number': kw['change_number'],
+ 'dn': kw['dn']
+ }
- if base_dn == None:
- user_base_dn = conf.get_raw(
- section,
- 'user_base_dn'
- ) % ({'base_dn': domain_root_dn})
- else:
- user_base_dn = base_dn
+ entry = utils.normalize(kw['entry'])
+ entry['dn'] = kw['dn']
- if type(attr) == str:
- search_filter = "(%s=%s)" % (
- attr,
- value
- )
- elif type(attr) == list:
- search_filter = "(|"
- for _attr in attr:
- search_filter = "%s(%s=%s)" % (search_filter, _attr, value)
- search_filter = "%s)" % (search_filter)
+ unique_attr = self.config_get('unique_attribute')
+ entry['id'] = entry[unique_attr]
- if additional_filter:
- search_filter = additional_filter % {
- 'search_filter': search_filter
- }
+ try:
+ entry['type'] = self._entry_type(entry)
+ except:
+ entry['type'] = "unknown"
- log.debug(
- _("Attempting to find entries with search filter: %s") % (
- search_filter
- ),
- level=8
- )
+ log.debug(_("Entry type: %s") % (entry['type']), level=8)
- _results = self.ldap.search_s(
- user_base_dn,
- scope=ldap.SCOPE_SUBTREE,
- filterstr=search_filter,
- attrlist=[ 'dn' ]
- )
+ if change_dict['change_type'] == None:
+ # This entry was in the start result set
+ eval("self._change_none_%s(entry, change_dict)" % (entry['type']))
+ else:
+ change = psearch.CHANGE_TYPES_STR[change_dict['change_type']].lower()
+ eval("self._change_%s_%s(entry, change_dict)" % (change, entry['type']))
- _user_dns = []
+ def _unbind(self):
+ """
+ Discard the current set of bind credentials.
- for _result in _results:
- (_user_dn, _user_attrs) = _result
- _user_dns.append(_user_dn)
+ Virtually disconnects the LDAP connection, and should be followed by
+ a call to _bind() afterwards.
+ """
+
+ self.ldap.unbind()
+ self.bind = False
- return _user_dns
+ ###
+ ### Backend search functions
+ ###
def _persistent_search(self,
base_dn,
@@ -713,8 +1524,8 @@ class LDAP(object):
primary_domain=None,
secondary_domains=[]
):
- self._connect()
- self._bind()
+
+ log.debug(_("Searching with filter %r") % (filterstr), level=8)
_search = self.ldap.search(
base_dn,
@@ -758,8 +1569,9 @@ class LDAP(object):
if len(self.ldap.supported_controls) < 1:
for control_num in SUPPORTED_LDAP_CONTROLS.keys():
log.debug(
- _("Checking for support for %s") % (
- SUPPORTED_LDAP_CONTROLS[control_num]['desc']
+ _("Checking for support for %s on %s") % (
+ SUPPORTED_LDAP_CONTROLS[control_num]['desc'],
+ self.domain
),
level=8
)
@@ -812,776 +1624,3 @@ class LDAP(object):
)
return _results
-
- def _result(self, msgid=ldap.RES_ANY, all=1, timeout=-1):
- return self.ldap.result(msgid, all, timeout)
-
- def _domain_default_quota(self, domain):
- if conf.has_option(domain, 'default_quota'):
- return conf.get(domain, '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):
- if conf.has_section(domain):
- return domain
- else:
- return 'ldap'
-
- def _get_group_attribute(self, group, attribute):
- self._bind()
-
- attribute = attribute.lower()
-
- log.debug(
- _("Getting attribute %s for group %s") % (attribute,group),
- level=8
- )
-
- _result_type = None
-
- _search = self.ldap.search_ext(
- group['dn'],
- ldap.SCOPE_BASE,
- '(objectclass=*)',
- [ 'dn', attribute ]
- )
-
- (
- _result_type,
- _result_data,
- _result_msgid,
- _result_controls
- ) = self.ldap.result3(_search)
-
- if len(_result_data) >= 1:
- (group_dn, group_attrs) = _result_data[0]
- else:
- log.warning(_("Could not get attribute %s for group %s")
- % (attribute,user['dn']))
-
- return None
-
- group_attrs = utils.normalize(group_attrs)
-
- if not group_attrs.has_key(attribute):
- log.debug(
- _("Wanted attribute %s, which does not exist for group " + \
- "%r") % (
- attribute,
- group_dn
- ),
- level=8
- )
-
- group_attrs[attribute] = None
-
- return group_attrs[attribute]
-
- def _get_user_attribute(self, user, attribute):
- self._bind()
-
- attribute = attribute.lower()
-
- log.debug(
- _("Getting attribute %s for user %s") % (attribute,user),
- level=8
- )
-
- _result_type = None
-
- _search = self.ldap.search_ext(
- user['dn'],
- ldap.SCOPE_BASE,
- '(objectclass=*)',
- [ 'dn', attribute ]
- )
-
- try:
- (
- _result_type,
- _result_data,
- _result_msgid,
- _result_controls
- ) = self.ldap.result3(_search)
- except ldap.NO_SUCH_OBJECT, e:
- log.error(_("No such object %r in Auth::LDAP::_get_user_attribute") % (user['dn']))
- return None
-
- if len(_result_data) >= 1:
- (user_dn, user_attrs) = _result_data[0]
- else:
- log.warning(_("Could not get attribute %s for user %s")
- % (attribute,user['dn']))
-
- return None
-
- user_attrs = utils.normalize(user_attrs)
-
- if not user_attrs.has_key(attribute):
- log.debug(
- _("Wanted attribute %s, which does not exist for user " + \
- "%r") % (
- attribute,
- user_dn
- ),
- level=8
- )
-
- user_attrs[attribute] = None
-
- return user_attrs[attribute]
-
- def _get_user_attributes(self, user, attributes):
- _user_attrs = {}
-
- for attribute in attributes:
- _user_attrs[attribute] = self._get_user_attribute(user, attribute)
-
- return _user_attrs
-
- def _search_mail_address(self, domain, mail_address):
- self._bind()
-
- domain_root_dn = self._kolab_domain_root_dn(domain)
-
- return self._search(
- domain_root_dn,
- ldap.SCOPE_SUBTREE,
- # TODO: Configurable
- '(|(mail=%s)(mailalternateaddress=%s))' % (
- mail_address,
- mail_address
- ),
- [ 'mail', 'mailalternateaddress' ],
- override_search='_regular_search'
- )
-
- def _set_user_attribute(self, user, attribute, value):
- self._bind()
-
- if isinstance(user, basestring):
- user = { "dn": user }
-
- attribute = attribute.lower()
-
- if not user.has_key(attribute):
- user[attribute] = self._get_user_attribute(user, attribute)
-
- mode = None
-
- # TODO: This should be a schema check!
- 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']
- )
-
- if user.has_key(attribute) and not user[attribute] == None:
- mode = ldap.MOD_REPLACE
- else:
- mode = ldap.MOD_ADD
-
- try:
- if isinstance(value, int):
- value = (str)(value)
-
- self.ldap.modify(user['dn'], [(mode, attribute, value)])
- except ldap.LDAPError, e:
- log.warning(
- _("LDAP modification of attribute %s for %s to value " + \
- "%s failed: %r") % (attribute,user_dn,value,e.message['info'])
- )
-
- def _list_domains(self):
- """
- Find the domains related to this Kolab setup, and return a list of
- DNS domain names.
-
- Returns a list of tuples, each tuple containing the primary domain
- name and a list of secondary domain names.
- """
-
- log.debug(_("Listing domains..."), level=8)
-
- self._connect()
-
- bind_dn = conf.get('ldap', 'bind_dn')
- bind_pw = conf.get('ldap', 'bind_pw')
-
- 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.
- # Note that the Auth() base itself handles this case.
- return []
-
- # If we haven't returned already, let's continue searching
- kolab_domain_filter = conf.get('ldap', 'kolab_domain_filter')
-
- # TODO: this function should be wrapped for error handling
- try:
- self.ldap.simple_bind_s(bind_dn, bind_pw)
- except ldap.SERVER_DOWN, e:
- raise AuthBackendError, _("Authentication database DOWN")
-
- _search = self._search(
- domain_base_dn,
- ldap.SCOPE_SUBTREE,
- kolab_domain_filter,
- # TODO: Where we use associateddomain is actually configurable
- [ 'associateddomain' ],
- override_search='_regular_search'
- )
-
- domains = []
-
- 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))
-
- return domains
-
- def _kolab_domain_root_dn(self, domain):
- self._bind()
-
- log.debug(_("Finding domain root dn for domain %s") % (domain), level=8)
-
- bind_dn = conf.get('ldap', 'bind_dn')
- bind_pw = conf.get('ldap', 'bind_pw')
-
- domain_base_dn = conf.get('ldap', 'domain_base_dn', quiet=True)
-
- if not domain_base_dn == "":
-
- # If we haven't returned already, let's continue searching
- domain_name_attribute = conf.get('ldap', 'domain_name_attribute')
-
- _results = self._search(
- domain_base_dn,
- ldap.SCOPE_SUBTREE,
- "(%s=%s)" % (domain_name_attribute,domain),
- override_search='_regular_search'
- )
-
- domains = []
- for _domain in _results:
- (domain_dn, _domain_attrs) = _domain
- 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]
-
- else:
- if conf.has_option('ldap', 'base_dn'):
- return conf.get('ldap', 'base_dn')
-
- return utils.standard_root_dn(domain)
-
- def _list_users(self, primary_domain, secondary_domains=[], callback=None):
-
- # Track state for psearch and paged searches.
- self._initial_sync_done = False
-
- 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
- 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')
-
- if conf.has_option(primary_domain, 'user_base_dn'):
- section = primary_domain
- else:
- section = 'ldap'
-
- domain_root_dn = self._kolab_domain_root_dn(primary_domain)
-
- user_base_dn = conf.get_raw(
- section,
- 'user_base_dn'
- ) % ({'base_dn': domain_root_dn})
-
- if conf.has_option(primary_domain, 'kolab_user_filter'):
- section = primary_domain
- else:
- section = 'ldap'
-
- kolab_user_filter = conf.get(section, 'kolab_user_filter', quiet=True)
-
- if conf.has_option(primary_domain, 'kolab_user_scope'):
- section = primary_domain
- else:
- section = 'ldap'
-
- _kolab_user_scope = conf.get(section, 'kolab_user_scope', quiet=True)
-
- if LDAP_SCOPE.has_key(_kolab_user_scope):
- kolab_user_scope = LDAP_SCOPE[_kolab_user_scope]
- else:
- log.warning(
- _("LDAP Search scope %s not found, using 'sub'") % (
- _kolab_user_scope
- )
- )
-
- kolab_user_scope = ldap.SCOPE_SUBTREE
-
- # TODO: Is, perhaps, a domain specific setting
- result_attribute = conf.get(
- 'cyrus-sasl',
- 'result_attribute',
- quiet=True
- )
-
- try:
- self.ldap.simple_bind_s(bind_dn, bind_pw)
- except ldap.SERVER_DOWN, e:
- error = eval("%s" % (e))
- log.error(_("Error binding to LDAP: %s") % (error['desc']))
- # TODO: Exit the fork (if fork!)
- return
-
- # TODO: The quota and alternative address attributes are actually
- # supposed to be settings.
- _search = self._search(
- user_base_dn,
- kolab_user_scope,
- kolab_user_filter,
- attrlist=[
- 'dn',
- result_attribute,
- 'sn',
- 'givenname',
- 'cn',
- 'uid'
- ],
- attrsonly=0,
- callback=callback,
- primary_domain=primary_domain,
- secondary_domains=secondary_domains
- )
-
- if callback == None:
- log.info(_("Found %d users") % (len(_search)))
-
- log.debug(_("Iterating over %d users, making sure we have the " + \
- "necessary attributes...") % (len(_search)), level=6)
-
- users = []
-
- num_users = len(_search)
- num_user = 0
-
- for user_dn, user_attrs in _search:
- num_user += 1
-
- # Placeholder for the user attributes
- user = user_attrs
- user['dn'] = user_dn
-
- user = self._get_user_details(
- user,
- primary_domain,
- secondary_domains
- )
-
- if user:
- users.append(user)
-
- if (num_user % 1000) == 0:
- log.debug(
- _("Done iterating over user %d of %d")
- % (num_user,num_users),
- level=3
- )
-
- return users
-
- def _get_user_details(self, user, primary_domain, secondary_domains=[]):
-
- # TODO: Is, perhaps, a domain specific setting
- result_attribute = conf.get(
- 'cyrus-sasl',
- 'result_attribute',
- quiet=True
- )
-
- if not user.has_key('standard_domain'):
- user['standard_domain'] = (primary_domain, secondary_domains)
-
- user = utils.normalize(user)
-
- _get_attrs = []
- _wanted_attributes = [
- result_attribute,
- 'mail',
- 'mailalternateaddress',
- 'sn',
- 'givenname',
- 'cn',
- 'uid',
- 'preferredLanguage'
- ]
-
- for attribute in _wanted_attributes:
- if not user.has_key(attribute):
- _get_attrs.append(attribute)
- #user[attribute] = self._get_user_attribute(user, attribute)
-
- if len(_get_attrs) > 0:
- _user_attrs = self._get_user_attributes(user, _get_attrs)
- for key in _user_attrs.keys():
- user[key] = _user_attrs[key]
-
- user = utils.normalize(user)
-
- if not user.has_key('preferredlanguage') or user['preferredlanguage'] == None:
- if conf.has_option(primary_domain, 'default_locale'):
- default_locale = conf.get(primary_domain, 'default_locale')
- else:
- default_locale = conf.get('kolab','default_locale')
-
- self._set_user_attribute(user, 'preferredlanguage', default_locale)
- user['preferredlanguage'] = default_locale
-
- # Check to see if we want to apply a primary mail recipient policy
- if conf.has_option(primary_domain, 'primary_mail'):
- primary_mail = conf.plugins.exec_hook(
- "set_primary_mail",
- kw={
- 'primary_mail':
- conf.get_raw(
- primary_domain,
- 'primary_mail'
- ),
- 'user_attrs': user,
- 'primary_domain': primary_domain,
- 'secondary_domains': secondary_domains
- }
- )
-
- i = 1
- _primary_mail = primary_mail
-
- done = False
- while not done:
- results = self._search_mail_address(
- primary_domain,
- _primary_mail
- )
-
- # Length of results should be 0 (no entry found)
- # or 1 (which should be the entry we're looking at here)
- if len(results) == 0:
- log.debug(
- _("No results for mail address %s found") % (
- _primary_mail
- ),
- level=8
- )
-
- done = True
- continue
-
- if len(results) == 1:
- log.debug(
- _("1 result for address %s found, verifying") % (
- _primary_mail
- ),
- level=8
- )
-
- almost_done = True
- for result in results:
- if not result[0] == user['dn']:
- log.debug(
- _("Too bad, primary email address %s " + \
- "already in use for %s (we are %s)") % (
- _primary_mail,
- result[0],
- user['dn']
- ),
- level=8
- )
-
- almost_done = False
-
- if almost_done:
- done = True
- continue
-
- i += 1
- _primary_mail = "%s%d@%s" % (
- primary_mail.split('@')[0],
- i,
- primary_mail.split('@')[1]
- )
-
- primary_mail = _primary_mail
-
- 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']:
- self._set_user_attribute(user, 'mail', primary_mail)
-
- if not user['mail'] == None:
- user['old_mail'] = user['mail']
-
- user['mail'] = primary_mail
-
- # Check to see if we want to apply a secondary mail recipient
- # policy.
- section = None
-
- if conf.has_option(primary_domain, 'secondary_mail'):
- section = primary_domain
- elif conf.has_option('kolab', 'secondary_mail'):
- section = 'kolab'
-
- if not section == None:
- # Execute the plugin hook
- suggested_secondary_mail = conf.plugins.exec_hook(
- "set_secondary_mail",
- kw={
- 'secondary_mail':
- conf.get_raw(
- primary_domain,
- 'secondary_mail'
- ),
- 'user_attrs': user,
- 'primary_domain': primary_domain,
- 'secondary_domains': secondary_domains
- }
- ) # end of conf.plugins.exec_hook() call
-
- secondary_mail = []
-
- for _secondary_mail in suggested_secondary_mail:
- i = 1
- __secondary_mail = _secondary_mail
-
- done = False
- while not done:
- results = self._search_mail_address(
- primary_domain,
- __secondary_mail
- )
-
- # Length of results should be 0 (no entry found)
- # or 1 (which should be the entry we're looking at here)
- if len(results) == 0:
- log.debug(
- _("No results for address %s found") % (
- __secondary_mail
- ),
- level=8
- )
-
- done = True
- continue
-
- if len(results) == 1:
- log.debug(
- _("1 result for address %s found, " + \
- "verifying...") % (
- __secondary_mail
- ),
- level=8
- )
-
- almost_done = True
- for result in results:
- if not result[0] == user['dn']:
- log.debug(
- _("Too bad, secondary email " + \
- "address %s already in use for " + \
- "%s (we are %s)") % (
- __secondary_mail,
- result[0],
- user['dn']
- ),
- level=8
- )
-
- almost_done = False
-
- if almost_done:
- done = True
- continue
-
- i += 1
- __secondary_mail = "%s%d@%s" % (
- _secondary_mail.split('@')[0],
- i,
- _secondary_mail.split('@')[1]
- )
-
- secondary_mail.append(__secondary_mail)
-
- if not secondary_mail == None:
- secondary_mail = list(set(secondary_mail))
- # Avoid duplicates
- while primary_mail in secondary_mail:
- secondary_mail.pop(secondary_mail.index(primary_mail))
-
- if not user.has_key('mailalternateaddress'):
- if not len(secondary_mail) == 0:
- 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
-
- return user
-
- def sync_user(self, *args, **kw):
- # See if kw['dn'] has been set.
-
- if kw.has_key('dn'):
- self.sync_ldap_user(*args, **kw)
- elif kw.has_key('user'):
- for user_dn, user_attrs in kw['user']:
- _user = utils.normalize(user_attrs)
- _user['dn'] = user_dn
- kw['user'] = _user
- self.sync_ldap_user(*args, **kw)
- else:
- # TODO: Not yet implemented
- pass
-
- def sync_ldap_user(self, *args, **kw):
- user = None
-
- done = False
-
- if kw.has_key('change_type'):
- # This is a EntryChangeControl notification
- user = utils.normalize(kw['entry'])
- user['dn'] = kw['dn']
-
- if kw['change_type'] == None:
- # This user has not been changed, but existed already.
- user = self._get_user_details(
- user,
- primary_domain=kw['primary_domain'],
- secondary_domains=kw['secondary_domains']
- )
-
- pykolab.imap.synchronize(
- users=[user],
- primary_domain=kw['primary_domain'],
- secondary_domains=kw['secondary_domains']
- )
-
- done = True
-
- elif kw['change_type'] == 1:
- user = self._get_user_details(
- user,
- primary_domain=kw['primary_domain'],
- secondary_domains=kw['secondary_domains']
- )
-
- pykolab.imap.synchronize(
- users=[user],
- primary_domain=kw['primary_domain'],
- secondary_domains=kw['secondary_domains']
- )
-
- done = True
-
- elif kw['change_type'] == 4:
- # TODO: How do we know what has changed?
- user = self._get_user_details(
- user,
- primary_domain=kw['primary_domain'],
- secondary_domains=kw['secondary_domains']
- )
-
- pykolab.imap.synchronize(
- users=[user],
- primary_domain=kw['primary_domain'],
- secondary_domains=kw['secondary_domains']
- )
-
- done = True
-
- elif kw['change_type'] == 2:
- # TODO: Use Cyrus SASL authorization ID
- folder = 'user/%s' % (user['mail'].lower())
- # TODO: Verify if folder exists
- pykolab.imap.delete_mailfolder(folder)
- done = True
-
- elif kw['change_type'] == 8:
- # Object has had its rdn changed
- user = self._get_user_details(
- user,
- primary_domain=kw['primary_domain'],
- secondary_domains=kw['secondary_domains']
- )
-
- pykolab.imap.synchronize(
- users=[user],
- primary_domain=kw['primary_domain'],
- secondary_domains=kw['secondary_domains']
- )
-
- done = True
-
- if kw.has_key('user'):
- user = kw['user']
-
- if user and not done:
- pykolab.imap.synchronize(
- users=[user],
- primary_domain=kw['primary_domain'],
- secondary_domains=kw['secondary_domains']
- )
diff --git a/pykolab/base.py b/pykolab/base.py
new file mode 100644
index 0000000..ebc1a59
--- /dev/null
+++ b/pykolab/base.py
@@ -0,0 +1,93 @@
+# Copyright 2010-2012 Kolab Systems AG (http://www.kolabsys.com)
+#
+# Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen a kolabsys.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 3 or, at your option, any later version
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Library General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+
+import pykolab
+from pykolab.imap import IMAP
+
+conf = pykolab.getConf()
+
+class Base(object):
+ """
+ Abstraction class for functions commonly shared between auth, imap, etc.
+ """
+ def __init__(self, *args, **kw):
+ self.domain = conf.get('kolab', 'primary_domain')
+
+ # Placeholder primary_domain => [secondary_domains]. Should be updated
+ # on auth backend _connect().
+ self.secondary_domains = {}
+
+ self.imap = IMAP()
+
+ def config_get(self, key1, key2=None):
+ if not key2 == None:
+ return conf.get(key1, key2)
+
+ if conf.has_option(self.domain, key1):
+ return conf.get(self.domain, key1)
+
+ if conf.has_option(self.domain, 'auth_mechanism'):
+ if conf.has_option(conf.get(self.domain, 'auth_mechanism'), key1):
+ return conf.get(conf.get(self.domain, 'auth_mechanism'), key1)
+
+ if conf.has_option(conf.get('kolab', 'auth_mechanism'), key1):
+ return conf.get(conf.get('kolab', 'auth_mechanism'), key1)
+
+ if conf.has_option('kolab', key1):
+ return conf.get('kolab', key1)
+
+ return None
+
+ def config_get_list(self, key1, key2=None):
+ if not key2 == None:
+ return conf.get_list(key1, key2)
+
+ if conf.has_option(self.domain, key1):
+ return conf.get_list(self.domain, key1)
+
+ if conf.has_option(self.domain, 'auth_mechanism'):
+ if conf.has_option(conf.get(self.domain, 'auth_mechanism'), key1):
+ return conf.get_list(conf.get(self.domain, 'auth_mechanism'), key1)
+
+ if conf.has_option(conf.get('kolab', 'auth_mechanism'), key1):
+ return conf.get_list(conf.get('kolab', 'auth_mechanism'), key1)
+
+ if conf.has_option('kolab', key1):
+ return conf.get_list('kolab', key1)
+
+ return None
+
+ def config_get_raw(self, key1, key2=None):
+ if not key2 == None:
+ return conf.get_raw(key1, key2)
+
+ if conf.has_option(self.domain, key1):
+ return conf.get_raw(self.domain, key1)
+
+ if conf.has_option(self.domain, 'auth_mechanism'):
+ if conf.has_option(conf.get(self.domain, 'auth_mechanism'), key1):
+ return conf.get_raw(conf.get(self.domain, 'auth_mechanism'), key1)
+
+ if conf.has_option(conf.get('kolab', 'auth_mechanism'), key1):
+ return conf.get_raw(conf.get('kolab', 'auth_mechanism'), key1)
+
+ if conf.has_option('kolab', key1):
+ return conf.get_raw('kolab', key1)
+
+ return None
+
diff --git a/pykolab/conf/__init__.py b/pykolab/conf/__init__.py
index fdf723f..9092689 100644
--- a/pykolab/conf/__init__.py
+++ b/pykolab/conf/__init__.py
@@ -48,6 +48,8 @@ class Conf(object):
self.entitlement = None
+ self.changelog = {}
+
try:
from pykolab.conf.entitlement import Entitlement
entitlements = True
@@ -496,8 +498,14 @@ class Conf(object):
if not self.cfg_parser:
self.read_config()
+ #log.debug(_("Obtaining value for section %r, key %r") % (section, key), level=8)
+
if self.cfg_parser.has_option(section, key):
- return self.cfg_parser.get(section,key)
+ try:
+ return self.cfg_parser.get(section, key)
+ except:
+ self.read_config()
+ return self.cfg_parser.get(section, key)
if hasattr(self, "get_%s_%s" % (section,key)):
try:
diff --git a/pykolab/conf/defaults.py b/pykolab/conf/defaults.py
index 22e8cc4..9fd6e8b 100644
--- a/pykolab/conf/defaults.py
+++ b/pykolab/conf/defaults.py
@@ -29,6 +29,8 @@ class Defaults(object):
# library should try to retrieve annotations
self.cyrus_annotations_retry_interval = 1
- self.address_search_attrs = "mail, alias"
+ self.address_search_attrs = ['mail', 'alias']
+ self.mail_attributes = ['mail', 'alias']
self.kolab_default_locale = 'en_US'
+ self.ldap_unique_attribute = 'nsuniqueid' \ No newline at end of file
diff --git a/pykolab/imap/__init__.py b/pykolab/imap/__init__.py
index ee7e722..f544beb 100644
--- a/pykolab/imap/__init__.py
+++ b/pykolab/imap/__init__.py
@@ -31,8 +31,6 @@ from pykolab.translate import _
log = pykolab.getLogger('pykolab.imap')
conf = pykolab.getConf()
-auth = pykolab.auth
-
class IMAP(object):
def __init__(self):
# Pool of named IMAP connections, by hostname
@@ -41,10 +39,50 @@ class IMAP(object):
# Place holder for the current IMAP connection
self.imap = None
- self.users = []
- self.inbox_folders = []
+ def cleanup_acls(self, aci_subject):
+ lm_suffix = ""
+
+ log.info(_("Cleaning up ACL entries for %s across all folders") % (aci_subject))
+
+ if len(aci_subject.split('@')) > 1:
+ lm_suffix = "@%s" % (aci_subject.split('@')[1])
+
+
+ shared_folders = self.imap.lm(
+ "shared/*%s" % (lm_suffix)
+ )
- def connect(self, uri=None, domain=None, login=True):
+ user_folders = self.imap.lm(
+ "user/*%s" % (lm_suffix)
+ )
+
+ log.debug(
+ _("Cleaning up ACL entries referring to identifier %s") % (
+ aci_subject
+ ),
+ level=5
+ )
+
+ # For all folders (shared and user), ...
+ folders = user_folders + shared_folders
+
+ log.debug(_("Iterating over %d folders") % (len(folders)), level=5)
+
+ # ... loop through them and ...
+ for folder in folders:
+ # ... list the ACL entries
+ acls = self.imap.lam(folder)
+
+ # For each ACL entry, see if we think it is a current, valid entry
+ for acl_entry in acls.keys():
+ # If the key 'acl_entry' does not exist in the dictionary of valid
+ # ACL entries, this ACL entry has got to go.
+ if acl_entry == aci_subject:
+ # Set the ACL to '' (effectively deleting the ACL entry)
+ log.debug(_("Removing acl %r for subject %r from folder %r") % (acls[acl_entry],acl_entry,folder), level=8)
+ self.imap.sam(folder, acl_entry, '')
+
+ def connect(self, uri=None, server=None, domain=None, login=True):
"""
Connect to the appropriate IMAP backend.
@@ -64,6 +102,7 @@ class IMAP(object):
backend = conf.get('kolab', 'imap_backend')
if not domain == None:
+ self.domain = domain
if conf.has_section(domain) and conf.has_option(domain, 'imap_backend'):
backend = conf.get(domain, 'imap_backend')
@@ -85,6 +124,9 @@ class IMAP(object):
scheme = uri.split(':')[0]
(hostname, port) = uri.split('/')[2].split(':')
+ if not server == None:
+ hostname = server
+
if port == None:
port = 993
@@ -155,117 +197,91 @@ class IMAP(object):
log.warning(_("Called imap.disconnect() on a server that " + \
"we had no connection to"))
+ def create_folder(self, folder_path, server=None):
+ if not server == None:
+ if not self._imap.has_key(server):
+ self.connect(server=server)
+
+ self._imap[server].cm(folder_path)
+ else:
+ self.imap.cm(folder_path)
+
def __getattr__(self, name):
if hasattr(self.imap, name):
return getattr(self.imap, name)
else:
raise AttributeError, _("%r has no attribute %s") % (self,name)
- def has_folder(self, folder):
- folders = self.imap.lm(folder)
- log.debug(_("Looking for folder '%s', we found folders: %r") % (folder,folders), level=8)
- # Greater then one, this folder may have subfolders.
- if len(folders) > 0:
- return True
- else:
- return False
-
- def move_user_folders(self, users=[], domain=None):
- self.connect(domain=domain)
-
- for user in users:
- if type(user) == dict:
- if user.has_key('old_mail'):
- inbox = "user/%s" % (user['mail'])
- old_inbox = "user/%s" % (user['old_mail'])
-
- if self.has_folder(old_inbox):
- log.debug(_("Found old INBOX folder %s") % (old_inbox), level=8)
-
- if not self.has_folder(inbox):
- log.info(_("Renaming INBOX from %s to %s") % (old_inbox,inbox))
- self.imap.rename(old_inbox,inbox)
- self.inbox_folders.append(inbox)
- else:
- log.warning(_("Moving INBOX folder %s won't succeed as target folder %s already exists") % (old_inbox,inbox))
- else:
- log.debug(_("Did not find old folder user/%s to rename") % (user['old_mail']), level=8)
- else:
- log.debug(_("Value for user is not a dictionary"), level=8)
-
- def create_user_folders(self, users, primary_domain, secondary_domains):
- self.connect(domain=primary_domain)
+ def shared_folder_create(self, folder_path, server=None):
+ """
+ Create a shared folder.
+ """
- inbox_folders = []
+ folder_name = "shared%s%s" % (self.imap.separator, folder_path)
+ log.info(_("Creating new shared folder %s") %(folder_path))
+ self.create_folder(folder_name, server)
- domain_section = auth.domain_section(primary_domain)
+ def shared_folder_exists(self, folder_path):
+ """
+ Check if a shared mailbox exists.
+ """
+ return self.has_folder('shared%s%s' % (self.imap.separator, folder_path))
- folders = self.list_user_folders(primary_domain, secondary_domains)
+ def shared_folder_set_type(self, folder_path, folder_type):
+ self.imap._setannotation('shared%s%s' % (self.imap.separator, folder_path), '/vendor/kolab/folder-type', folder_type)
- # See if the folder belongs to any of the users
- _match_attr = conf.get('cyrus-sasl', 'result_attribute')
+ def shared_mailbox_create(self, mailbox_base_name, server=None):
+ """
+ Create a shared folder.
+ """
- if not users:
- users = auth.list_users(primary_domain)
+ folder_name = "shared%s%s" % (self.imap.separator, mailbox_base_name)
+ log.info(_("Creating new shared folder %s") %(mailbox_base_name))
+ self.create_folder(folder_name, server)
- for user in users:
- if type(user) == dict:
- if user.has_key(_match_attr):
- inbox_folders.append(user[_match_attr].lower())
- else:
- # If the user passed on to this function does not have
- # a key for _match_attr, then we have to bail out and
- # continue
- continue
+ def shared_mailbox_exists(self, mailbox_base_name):
+ """
+ Check if a shared mailbox exists.
+ """
+ return self.has_folder('shared%s%s' %(self.imap.separator, mailbox_base_name))
- elif type(user) == str:
- inbox_folders.append(user.lower())
-
- for folder in inbox_folders:
- additional_folders = None
- if not self.has_folder("user%s%s" % (self.imap.separator, folder)):
- # TODO: Perhaps this block is moot
- log.info(_("Creating new INBOX for user (%d): %s")
- % (1,folder))
- try:
- self.imap.cm("user%s%s" % (self.imap.separator, folder))
- except:
- log.warning(
- _("Mailbox already exists: user%s%s") % (
- self.imap.separator,folder
- )
- )
+ def user_mailbox_create(self, mailbox_base_name, server=None):
+ """
+ Create a user mailbox.
- continue
+ Returns the full path to the new mailbox folder.
+ """
+ folder_name = "user%s%s" % (self.imap.separator, mailbox_base_name)
+ log.info(_("Creating new mailbox for user %s") %(mailbox_base_name))
- if conf.get('kolab', 'imap_backend') == 'cyrus-imap':
- self.imap._setquota(
- "user%s%s" % (self.imap.separator, folder),
- 0
- )
+ self.create_folder(folder_name, server)
- if conf.has_option(domain_section, "autocreate_folders"):
+ if not self.domain == None:
+ if conf.has_option(self.domain, "autocreate_folders"):
_additional_folders = conf.get_raw(
- domain_section,
+ self.domain,
"autocreate_folders"
)
additional_folders = conf.plugins.exec_hook(
"create_user_folders",
kw={
- 'folder': folder,
+ 'folder': folder_name,
'additional_folders': _additional_folders
}
)
- if not additional_folders == None:
- self.create_user_additional_folders(folder, additional_folders)
+ if not additional_folders == None:
+ self.user_mailbox_create_additional_folders(mailbox_base_name, additional_folders)
- return inbox_folders
+ return folder_name
+
+ def user_mailbox_create_additional_folders(self, folder, additional_folders):
+ log.debug(_("Creating additional folders for user %s") % (folder), level=8)
- def create_user_additional_folders(self, folder, additional_folders):
for additional_folder in additional_folders.keys():
_add_folder = {}
+
if len(folder.split('@')) > 1:
folder_name = "user%(separator)s%(username)s%(separator)s%(additional_folder_name)s@%(domainname)s"
_add_folder['username'] = folder.split('@')[0]
@@ -283,7 +299,7 @@ class IMAP(object):
try:
self.imap.cm(folder_name)
except:
- log.warning(_("Mailbox already exists: user/%s") % (folder))
+ log.warning(_("Mailbox already exists: %s") % (folder_name))
if additional_folders[additional_folder].has_key("annotations"):
for annotation in additional_folders[additional_folder]["annotations"].keys():
@@ -312,8 +328,10 @@ class IMAP(object):
if len(folder.split('@')) > 1:
domain = folder.split('@')[1]
+ domain_suffix = "@%s" % (domain)
else:
domain = None
+ domain_suffix = ""
if not domain == None:
if conf.has_section(domain) and conf.has_option(domain, 'imap_backend'):
@@ -324,6 +342,7 @@ class IMAP(object):
else:
uri = None
+ log.debug(_("Subscribing user to the additional folders"), level=8)
# Get the credentials
admin_login = conf.get(backend, 'admin_login')
admin_password = conf.get(backend, 'admin_password')
@@ -331,11 +350,122 @@ class IMAP(object):
self.connect(login=False)
self.login_plain(admin_login, admin_password, folder)
- for _folder in self.lm():
+ for _folder in self.lm("%s/*%s" % (folder_name.split('@')[0],domain_suffix)):
self.subscribe(_folder)
self.logout()
+ def user_mailbox_delete(self, mailbox_base_name):
+ """
+ Delete a user mailbox.
+ """
+ folder = "user%s%s" %(self.imap.separator,mailbox_base_name)
+ self.delete_mailfolder(folder)
+ self.cleanup_acls(mailbox_base_name)
+
+ def user_mailbox_exists(self, mailbox_base_name):
+ """
+ Check if a user mailbox exists.
+ """
+ return self.has_folder('user%s%s' %(self.imap.separator, mailbox_base_name))
+
+ def user_mailbox_rename(self, old_name, new_name):
+ old_name = "user%s%s" % (self.imap.separator,old_name)
+ new_name = "user%s%s" % (self.imap.separator,new_name)
+
+ if old_name == new_name:
+ return
+
+ if not self.has_folder(new_name):
+ log.info(_("Renaming INBOX from %s to %s") % (old_name,new_name))
+ try:
+ self.imap.rename(old_name,new_name)
+ except:
+ log.error(_("Could not rename INBOX folder %s to %s") % (oldname,new_name))
+ else:
+ log.warning(_("Moving INBOX folder %s won't succeed as target folder %s already exists") % (old_name,new_name))
+
+ def user_mailbox_server(self, mailbox):
+ self.connect(domain=self.domain)
+ return self.imap.find_mailfolder_server(mailbox)
+
+ def has_folder(self, folder):
+ """
+ Check if the environment has a folder named folder.
+ """
+ self.connect(domain=self.domain)
+
+ folders = self.imap.lm(folder)
+ log.debug(_("Looking for folder '%s', we found folders: %r") % (folder,folders), level=8)
+ # Greater then one, this folder may have subfolders.
+ if len(folders) > 0:
+ return True
+ else:
+ return False
+
+ def _set_kolab_mailfolder_acls(self, acls):
+ if isinstance(acls, basestring):
+ acls = [ acls ]
+
+ for acl in acls:
+ exec("acl = %s" % (acl))
+ folder = acl[0]
+ subject = acl[1]
+ rights = acl[2]
+ if len(acl) == 4:
+ epoch = acl[3]
+ else:
+ epoch = (int)(time.time()) + 3600
+
+ if epoch > (int)(time.time()):
+ log.debug(
+ _("Setting ACL rights %s for subject %s on folder " + \
+ "%s") % (rights,subject,folder), level=8)
+
+ self.imap.sam(
+ folder,
+ "%s" % (subject),
+ "%s" % (rights)
+ )
+
+ else:
+ log.debug(
+ _("Removing ACL rights %s for subject %s on folder " + \
+ "%s") % (rights,subject,folder), level=8)
+
+ self.imap.sam(
+ folder,
+ "%s" % (subject),
+ ""
+ )
+
+ pass
+
+ """ Blah functions """
+
+ def move_user_folders(self, users=[], domain=None):
+ self.connect(domain=domain)
+
+ for user in users:
+ if type(user) == dict:
+ if user.has_key('old_mail'):
+ inbox = "user/%s" % (user['mail'])
+ old_inbox = "user/%s" % (user['old_mail'])
+
+ if self.has_folder(old_inbox):
+ log.debug(_("Found old INBOX folder %s") % (old_inbox), level=8)
+
+ if not self.has_folder(inbox):
+ log.info(_("Renaming INBOX from %s to %s") % (old_inbox,inbox))
+ self.imap.rename(old_inbox,inbox)
+ self.inbox_folders.append(inbox)
+ else:
+ log.warning(_("Moving INBOX folder %s won't succeed as target folder %s already exists") % (old_inbox,inbox))
+ else:
+ log.debug(_("Did not find old folder user/%s to rename") % (user['old_mail']), level=8)
+ else:
+ log.debug(_("Value for user is not a dictionary"), level=8)
+
def set_user_folder_quota(self, users=[], primary_domain=None, secondary_domain=[], folders=[]):
"""
@@ -530,69 +660,6 @@ class IMAP(object):
self.imap.dm(mailfolder_path)
- clean_acls = False
-
- section = False
-
- if mbox_parts['domain']:
- if conf.has_option(mbox_parts['domain'], 'delete_clean_acls'):
- section = mbox_parts['domain']
- elif conf.has_option('kolab', 'delete_clean_acls'):
- section = 'kolab'
- elif conf.has_option('kolab', 'delete_clean_acls'):
- section = 'kolab'
-
- if not section == False:
- clean_acls = conf.get(section, 'delete_clean_acls')
-
- if not clean_acls == False and not clean_acls == 0:
- log.info(_("Cleaning up ACL entries across all folders"))
-
- if mbox_parts['domain']:
- # List the shared and user folders
- shared_folders = self.imap.lm(
- "shared/*@%s" % (mbox_parts['domain'])
- )
-
- user_folders = self.imap.lm(
- "user/*@%s" % (mbox_parts['domain'])
- )
-
- aci_identifier = "%s@%s" % (
- mbox_parts['path_parts'][1],
- mbox_parts['domain']
- )
-
- else:
- shared_folders = self.imap.lm("shared/*")
- user_folders = self.imap.lm("user/*")
- aci_identifier = "%s" % (mbox_parts['path_parts'][1])
-
- log.debug(
- _("Cleaning up ACL entries referring to identifier %s") % (
- aci_identifier
- ),
- level=5
- )
-
- # For all folders (shared and user), ...
- folders = user_folders + shared_folders
-
- log.debug(_("Iterating over %d folders") % (len(folders)), level=5)
-
- # ... loop through them and ...
- for folder in folders:
- # ... list the ACL entries
- acls = self.imap.lam(folder)
-
- # For each ACL entry, see if we think it is a current, valid entry
- for acl_entry in acls.keys():
- # If the key 'acl_entry' does not exist in the dictionary of valid
- # ACL entries, this ACL entry has got to go.
- if acl_entry == aci_identifier:
- # Set the ACL to '' (effectively deleting the ACL entry)
- self.imap.sam(folder, acl_entry, '')
-
def list_user_folders(self, primary_domain=None, secondary_domains=[]):
"""
List the INBOX folders in the IMAP backend. Returns a list of unique
diff --git a/pykolab/imap/cyrus.py b/pykolab/imap/cyrus.py
index 1d5b9b4..1eae6d7 100644
--- a/pykolab/imap/cyrus.py
+++ b/pykolab/imap/cyrus.py
@@ -29,8 +29,6 @@ from pykolab.translate import _
log = pykolab.getLogger('pykolab.imap')
conf = pykolab.getConf()
-imap = pykolab.imap
-
class Cyrus(cyruslib.CYRUS):
"""
Abstraction class for some common actions to do exclusively in Cyrus.
@@ -100,6 +98,7 @@ class Cyrus(cyruslib.CYRUS):
cyruslib.CYRUS.login(self, *args, **kw)
self.separator = self.SEP
+ log.debug(_("Continuing with separator: %r") % (self.separator), level=8)
self.murder = False
for capability in self.m.capabilities:
@@ -113,10 +112,7 @@ class Cyrus(cyruslib.CYRUS):
def find_mailfolder_server(self, mailfolder):
annotations = {}
- #print "mailfolder:", mailfolder
-
_mailfolder = self.parse_mailfolder(mailfolder)
- #print "_mailfolder:", _mailfolder
prefix = _mailfolder['path_parts'].pop(0)
mbox = _mailfolder['path_parts'].pop(0)
@@ -198,7 +194,6 @@ class Cyrus(cyruslib.CYRUS):
Login to the actual backend server, then set annotation.
"""
server = self.find_mailfolder_server(mailfolder)
- imap.connect(self.uri.replace(self.server,server))
log.debug(_("Setting annotation %s on folder %s") % (annotation,mailfolder), level=8)
diff --git a/pykolab/plugins/recipientpolicy/__init__.py b/pykolab/plugins/recipientpolicy/__init__.py
index 6621947..ab72980 100644
--- a/pykolab/plugins/recipientpolicy/__init__.py
+++ b/pykolab/plugins/recipientpolicy/__init__.py
@@ -54,7 +54,7 @@ class KolabRecipientpolicy(object):
Return the new primary mail address
"""
- user_attrs = utils.normalize(kw['user_attrs'])
+ user_attrs = utils.normalize(kw['entry'])
if not user_attrs.has_key('domain'):
user_attrs['domain'] = kw['primary_domain']
@@ -84,7 +84,7 @@ class KolabRecipientpolicy(object):
Return a list of secondary mail addresses
"""
- user_attrs = utils.normalize(kw['user_attrs'])
+ user_attrs = utils.normalize(kw['entry'])
if not user_attrs.has_key('domain'):
user_attrs['domain'] = kw['primary_domain']
diff --git a/pykolab/wap_client/__init__.py b/pykolab/wap_client/__init__.py
index 9c3a245..31cfb57 100644
--- a/pykolab/wap_client/__init__.py
+++ b/pykolab/wap_client/__init__.py
@@ -193,6 +193,9 @@ def request(method, api_uri, params=None, headers={}):
else:
return response_data['result']
+def role_capabilities():
+ return request('GET', 'role.capabilities')
+
def system_capabilities():
return request('GET', 'system.capabilities')
@@ -261,6 +264,78 @@ def user_form_value_generate_mail(params=None):
def form_value_generate_password(*args, **kw):
return request('GET', 'form_value.generate_password')
+def form_value_list_options(attribute_name, *args, **kw):
+ params = json.dumps({'attribute': attribute_name})
+
+ return request('POST', 'form_value.list_options', params)
+
+def form_value_select_options(attribute_name, *args, **kw):
+ params = json.dumps({'attributes': [attribute_name]})
+
+ return request('POST', 'form_value.select_options', params)
+
+def role_find_by_attribute(params=None):
+ if params == None:
+ role_name = utils.ask_question("Role name")
+ else:
+ role_name = params['cn']
+
+ role = request('GET', 'role.find_by_attribute?cn=%s' % (role_name))
+
+ return role
+
+def role_add(params=None):
+ if params == None:
+ role_name = utils.ask_question("Role name")
+ params = {
+ 'cn': role_name
+ }
+
+ params = json.dumps(params)
+
+ return request('POST', 'role.add', params)
+
+def role_delete(params=None):
+ if params == None:
+ role_name = utils.ask_question("Role name")
+ role = role_find_by_attribute({'cn': role_name})
+ params = {
+ 'role': role.keys()[0]
+ }
+
+ if not params.has_key('role'):
+ role = role_find_by_attribute(params)
+ params = {
+ 'role': role.keys()[0]
+ }
+
+ params = json.dumps(params)
+
+ return request('POST', 'role.delete', params)
+
+def role_info(params=None):
+ if params == None:
+ role_name = utils.ask_question("Role name")
+ role = role_find_by_attribute({'cn': role_name})
+ params = {
+ 'role': role
+ }
+
+ if not params.has_key('role'):
+ role = role_find_by_attribute(params)
+ params = {
+ 'role': role
+ }
+
+ print role
+
+ role = request('GET', 'role.info?role=%s' % (params['role'].keys()[0]))
+
+ return role
+
+def roles_list():
+ return request('GET', 'roles.list')
+
def user_form_value_generate_uid(params=None):
if params == None:
params = get_user_input()