diff -r a8a777074233 MoinMoin/auth/ldap_login.py
--- a/MoinMoin/auth/ldap_login.py	Mon Jul 27 01:55:59 2009 +0200
+++ b/MoinMoin/auth/ldap_login.py	Mon Aug 03 11:13:21 2009 +0200
@@ -41,26 +41,18 @@
     name = 'ldap'
 
     def __init__(self,
-        server_uri='ldap://localhost',  # ldap / active directory server URI
-                                        # use ldaps://server:636 url for ldaps,
-                                        # use  ldap://server for ldap without tls (and set start_tls to 0),
-                                        # use  ldap://server for ldap with tls (and set start_tls to 1 or 2).
-        bind_dn='',  # We can either use some fixed user and password for binding to LDAP.
-                     # Be careful if you need a % char in those strings - as they are used as
-                     # a format string, you have to write %% to get a single % in the end.
-                     #bind_dn = 'binduser@example.org' # (AD)
-                     #bind_dn = 'cn=admin,dc=example,dc=org' # (OpenLDAP)
-                     #bind_pw = 'secret'
-                     # or we can use the username and password we got from the user:
-                     #bind_dn = '%(username)s@example.org' # DN we use for first bind (AD)
-                     #bind_pw = '%(password)s' # password we use for first bind
-                     # or we can bind anonymously (if that is supported by your directory).
-                     # In any case, bind_dn and bind_pw must be defined.
-        bind_pw='',
+        conn_mgr,      # a LDAPConnectionManager object
+        bind_dn=None,  # You can override the conn_mgr parameters here.
+                       # if you do this you won't be able to share connection with LDAPGroups
+                       # Be careful if you need a % char in those strings - as they are used as
+                       # a format string, you have to write %% to get a single % in the end.
+                       #
+                       # You can use the username and password we got from the user:
+                       # bind_dn = '%(username)s@example.org' # DN we use for first bind (AD)
+        bind_pw=None,  # bind_pw = '%(password)s' # password we use for first bind
         base_dn='',  # base DN we use for searching
                      #base_dn = 'ou=SOMEUNIT,dc=example,dc=org'
         scope=ldap.SCOPE_SUBTREE, # scope of the search we do (2 == ldap.SCOPE_SUBTREE)
-        referrals=0, # LDAP REFERRALS (0 needed for AD)
         search_filter='(uid=%(username)s)',  # ldap filter used for searching:
                                              #search_filter = '(sAMAccountName=%(username)s)' # (AD)
                                              #search_filter = '(uid=%(username)s)' # (OpenLDAP)
@@ -73,23 +65,15 @@
         email_attribute=None, # ('mail') ldap attribute we get the email address from
         email_callback=None, # called to make up email address
         name_callback=None, # called to use a Wiki name different from the login name
-        coding='utf-8', # coding used for ldap queries and result values
-        timeout=10, # how long we wait for the ldap server [s]
-        start_tls=0, # 0 = No, 1 = Try, 2 = Required
-        tls_cacertdir=None,
-        tls_cacertfile=None,
-        tls_certfile=None,
-        tls_keyfile=None,
-        tls_require_cert=0, # 0 == ldap.OPT_X_TLS_NEVER (needed for self-signed certs)
         bind_once=False, # set to True to only do one bind - useful if configured to bind as the user on the first attempt
         autocreate=False, # set to True if you want to autocreate user profiles
         ):
-        self.server_uri = server_uri
+        self.conn_mgr = conn_mgr
+
         self.bind_dn = bind_dn
         self.bind_pw = bind_pw
         self.base_dn = base_dn
         self.scope = scope
-        self.referrals = referrals
         self.search_filter = search_filter
 
         self.givenname_attribute = givenname_attribute
@@ -98,16 +82,6 @@
         self.email_attribute = email_attribute
         self.email_callback = email_callback
         self.name_callback = name_callback
-
-        self.coding = coding
-        self.timeout = timeout
-
-        self.start_tls = start_tls
-        self.tls_cacertdir = tls_cacertdir
-        self.tls_cacertfile = tls_cacertfile
-        self.tls_certfile = tls_certfile
-        self.tls_keyfile = tls_keyfile
-        self.tls_require_cert = tls_require_cert
 
         self.bind_once = bind_once
         self.autocreate = autocreate
@@ -127,44 +101,14 @@
             try:
                 u = None
                 dn = None
-                coding = self.coding
-                logging.debug("Setting misc. ldap options...")
-                ldap.set_option(ldap.OPT_PROTOCOL_VERSION, ldap.VERSION3) # ldap v2 is outdated
-                ldap.set_option(ldap.OPT_REFERRALS, self.referrals)
-                ldap.set_option(ldap.OPT_NETWORK_TIMEOUT, self.timeout)
-
-                if hasattr(ldap, 'TLS_AVAIL') and ldap.TLS_AVAIL:
-                    for option, value in (
-                        (ldap.OPT_X_TLS_CACERTDIR, self.tls_cacertdir),
-                        (ldap.OPT_X_TLS_CACERTFILE, self.tls_cacertfile),
-                        (ldap.OPT_X_TLS_CERTFILE, self.tls_certfile),
-                        (ldap.OPT_X_TLS_KEYFILE, self.tls_keyfile),
-                        (ldap.OPT_X_TLS_REQUIRE_CERT, self.tls_require_cert),
-                        (ldap.OPT_X_TLS, self.start_tls),
-                        #(ldap.OPT_X_TLS_ALLOW, 1),
-                    ):
-                        if value is not None:
-                            ldap.set_option(option, value)
-
-                server = self.server_uri
-                logging.debug("Trying to initialize %r." % server)
-                l = ldap.initialize(server)
-                logging.debug("Connected to LDAP server %r." % server)
-
-                if self.start_tls and server.startswith('ldap:'):
-                    logging.debug("Trying to start TLS to %r." % server)
-                    try:
-                        l.start_tls_s()
-                        logging.debug("Using TLS to %r." % server)
-                    except (ldap.SERVER_DOWN, ldap.CONNECT_ERROR), err:
-                        logging.warning("Couldn't establish TLS to %r (err: %s)." % (server, str(err)))
-                        raise
-
+                coding = self.conn_mgr.coding
+                binddn = self.bind_dn
+                bindpw = self.bind_pw
                 # you can use %(username)s and %(password)s here to get the stuff entered in the form:
-                binddn = self.bind_dn % locals()
-                bindpw = self.bind_pw % locals()
-                l.simple_bind_s(binddn.encode(coding), bindpw.encode(coding))
-                logging.debug("Bound with binddn %r" % binddn)
+                if binddn is not None:
+                    binddn = binddn % locals()
+                    bindpw = bindpw % locals()
+                conn = self.conn_mgr.get_connection(binddn, bindpw)
 
                 # you can use %(username)s here to get the stuff entered in the form:
                 filterstr = self.search_filter % locals()
@@ -175,8 +119,8 @@
                                          'surname_attribute',
                                          'givenname_attribute',
                                          ] if getattr(self, attr) is not None]
-                lusers = l.search_st(self.base_dn, self.scope, filterstr.encode(coding),
-                                     attrlist=attrs, timeout=self.timeout)
+                lusers = conn.search_st(self.base_dn, self.scope,
+                                        filterstr.encode(coding), attrlist=attrs)
                 # we remove entries with dn == None to get the real result list:
                 lusers = [(dn, ldap_dict) for dn, ldap_dict in lusers if dn is not None]
                 for dn, ldap_dict in lusers:
@@ -195,7 +139,7 @@
                 dn, ldap_dict = lusers[0]
                 if not self.bind_once:
                     logging.debug("DN found is %r, trying to bind with pw" % dn)
-                    l.simple_bind_s(dn, password.encode(coding))
+                    self.conn_mgr.get_connection(dn, password)
                     logging.debug("Bound with dn %r (username: %r)" % (dn, username))
 
                 if self.email_callback is None:
@@ -229,6 +173,7 @@
                 else:
                     u = user.User(request, auth_username=username, auth_method=self.name, auth_attribs=('name', 'password', 'mailto_author', ))
                 u.name = username
+                u.dn = dn
                 u.aliasname = aliasname
                 u.remember_me = 0 # 0 enforces cookie_lifetime config param
                 logging.debug("creating userprefs with name %r email %r alias %r" % (username, email, aliasname))
diff -r a8a777074233 MoinMoin/datastruct/backends/ldap_groups.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/datastruct/backends/ldap_groups.py	Mon Aug 03 11:13:21 2009 +0200
@@ -0,0 +1,90 @@
+# -*- coding: iso-8859-1 -*-
+"""
+    MoinMoin - ldap group lazy backend.
+
+    The ldap group backend allows one to define groups in an
+    ldap server. Group members must be authenticated via
+    ldap.
+
+    @copyright: 2009 Benoit Peccatte (peck)
+    @license: GPL, see COPYING for details
+"""
+
+from MoinMoin import log
+logging = log.getLogger(__name__)
+
+from MoinMoin.user import User
+from MoinMoin.datastruct.backends import LazyGroup, LazyGroupsBackend
+
+import ldap
+
+class LdapGroup(LazyGroup):
+    pass
+
+
+class LdapGroups(LazyGroupsBackend):
+
+    def __init__(self, request,
+        conn_mgr,                  # LDAPConnectionManager to use for connections
+        base_dn='',                # base ldap dn to use when searching for groups
+        scope=ldap.SCOPE_SUBTREE,  # scope of the search
+        search_filter='',          # filter to use when searching for groups
+        member_attribute='member', # attribute name for group members
+        name_attribute='cn',       # attribute name for group name
+        ):
+
+        super(LdapGroups, self).__init__(request)
+        self.base = base_dn
+        self.scope = scope
+        if search_filter and not search_filter.startswith('('):
+            search_filter = '(%s)' % search_filter
+        self.filter = search_filter
+        self.member_attribute = member_attribute
+        self.name_attribute = name_attribute
+        self.ldap = conn_mgr
+
+    def __contains__(self, group_name):
+        filter = self._getfilter(group_name)
+        res = self.ldap.search_single(self.base, self.scope, filter, 'dn')
+        return res != []
+
+    def __iter__(self):
+        filter = self._getfilter('*')
+        res = self.ldap.search_single(self.base, self.scope, filter, self.name_attribute)
+        # XXX better group name rebuilding ? needs configurability
+        res = ['%sGroup' % x for x in res]
+        return res.__iter__()
+
+    def __getitem__(self, group_name):
+        return LdapGroup(self.request, group_name, self)
+
+    def _iter_group_members(self, group_name):
+        filter = self._getfilter(group_name)
+        return self.ldap.search_single(self.base, self.scope, filter, self.member_attribute)
+
+    def _group_has_member(self, group_name, member):
+        # only check users that are authenticated using ldap
+        u = User(self.request, auth_username=member)
+        if not hasattr(u, 'dn'):
+            return False
+        filter = self._getfilter(group_name, u.dn)
+        res = self.ldap.search_single(self.base, self.scope, filter, 'dn')
+        return res != []
+
+    def _getfilter(self, group_name, member=None):
+        """ Create a filter string for a group.
+            Add a member filter if one is provided."""
+        # extract real group name from the form XxxGroup
+        grp = self.page_group_regex.search(group_name)
+        if grp:
+            group = grp.group(2)
+        else:
+            group = group_name
+
+        filter = '(%s=%s)' % (self.name_attribute, group)
+        if self.filter:
+            filter = '(&%s%s)' % (self.filter, filter)
+        if member:
+            filter = '(&%s(%s=%s))' % (filter, self.member_attribute, member)
+        return filter
+
diff -r a8a777074233 MoinMoin/util/ldap_connection.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/util/ldap_connection.py	Mon Aug 03 11:13:21 2009 +0200
@@ -0,0 +1,133 @@
+# -*- coding: iso-8859-1 -*-
+"""
+    MoinMoin - ldap connection object
+
+    The connexion object holds informations to connect to a single
+    ldap server.
+
+    @copyright: 2006-2009 MoinMoin:ThomasWaldmann,
+                2006 Nick Phillips
+                2009 Benoit Peccatte (peck)
+    @license: GPL, see COPYING for details
+"""
+
+from MoinMoin import log
+logging = log.getLogger(__name__)
+
+try:
+    import ldap
+except ImportError, err:
+    logging.error("You need to have python-ldap installed (%s)." % str(err))
+    raise
+
+class LDAPConnectionManager(object):
+    def __init__(self,
+        server_uri='ldap://localhost',  # ldap / active directory server URI
+                                        # use ldaps://server:636 url for ldaps,
+                                        # use  ldap://server for ldap without tls (and set start_tls to 0),
+                                        # use  ldap://server for ldap with tls (and set start_tls to 1 or 2).
+        bind_dn='', # bind_dn = 'binduser@example.org' # (AD)
+                    # bind_dn = 'cn=admin,dc=example,dc=org' # (OpenLDAP)
+        bind_pw='',
+        referrals=0, # LDAP REFERRALS (0 needed for AD)
+        coding='utf-8', # coding used for ldap queries and result values
+        timeout=10, # how long we wait for the ldap server [s]
+        start_tls=0, # 0 = No, 1 = Try, 2 = Required
+        tls_cacertdir=None,
+        tls_cacertfile=None,
+        tls_certfile=None,
+        tls_keyfile=None,
+        tls_require_cert=0, # 0 == ldap.OPT_X_TLS_NEVER (needed for self-signed certs)
+        ):
+
+        self.server_uri = server_uri
+        self.bind_dn = bind_dn
+        self.bind_pw = bind_pw
+        self.referrals = referrals
+        self.coding = coding
+        self.timeout = timeout
+        self.start_tls = start_tls
+        self.tls_cacertdir = tls_cacertdir
+        self.tls_cacertfile = tls_cacertfile
+        self.tls_certfile = tls_certfile
+        self.tls_keyfile = tls_keyfile
+        self.tls_require_cert = tls_require_cert
+
+        self.ldap = None
+
+    def get_connection(self, bind_dn=None, bind_pw=None):
+        """ Create a connection object and bind.
+            Always return the default connection when bind_dn=None """
+        default = bind_dn is None
+        # only one default connection
+        if default and self.ldap:
+            return self.ldap
+        try:
+            # options
+            logging.debug("Setting misc. ldap options...")
+            ldap.set_option(ldap.OPT_PROTOCOL_VERSION, ldap.VERSION3)
+            ldap.set_option(ldap.OPT_REFERRALS, self.referrals)
+            ldap.set_option(ldap.OPT_NETWORK_TIMEOUT, self.timeout)
+            ldap.set_option(ldap.OPT_TIMEOUT, self.timeout)
+            if hasattr(ldap, 'TLS_AVAIL') and ldap.TLS_AVAIL:
+                for option, value in (
+                    (ldap.OPT_X_TLS_CACERTDIR, self.tls_cacertdir),
+                    (ldap.OPT_X_TLS_CACERTFILE, self.tls_cacertfile),
+                    (ldap.OPT_X_TLS_CERTFILE, self.tls_certfile),
+                    (ldap.OPT_X_TLS_KEYFILE, self.tls_keyfile),
+                    (ldap.OPT_X_TLS_REQUIRE_CERT, self.tls_require_cert),
+                    (ldap.OPT_X_TLS, self.start_tls),
+                    #(ldap.OPT_X_TLS_ALLOW, 1),
+                ):
+                    if value is not None:
+                        ldap.set_option(option, value)
+
+            # connect and bind
+            logging.debug("Trying to initialize %r." % self.server_uri)
+            conn = ldap.initialize(self.server_uri)
+            conn.protocol_version = ldap.VERSION3 # recommended by documentation
+            logging.debug("Connected to LDAP server %r." % self.server_uri)
+            if self.start_tls and server.startswith('ldap:'):
+                logging.debug("Trying to start TLS to %r." % self.server_uri)
+                try:
+                    conn.start_tls_s()
+                    logging.debug("Using TLS to %r." % self.server_uri)
+                except (ldap.SERVER_DOWN, ldap.CONNECT_ERROR), err:
+                    logging.warning("Couldn't establish TLS to %r (err: %s)." % (self.server_uri, str(err)))
+                    raise
+
+            if default:
+                bind_dn = self.bind_dn
+                bind_pw = self.bind_pw
+            conn.simple_bind_s(bind_dn.encode(self.coding), bind_pw.encode(self.coding))
+            logging.debug("Bound with binddn %r" % bind_dn)
+            if default:
+                self.ldap = conn
+            return conn
+        except ldap.LDAPError, error_message:
+            logging.error("LDAP error %r" % error_message)
+            raise
+
+    def search_single(self, base, scope, ldap_filter, attribute):
+        """ Make a search with the default connection
+            Search for a single attribute, returns an array of single values. """
+        conn = self.get_connection()
+        try:
+            result_id = conn.search(base.encode(self.coding),
+                                    scope,
+                                    ldap_filter.encode(self.coding),
+                                    [attribute])
+            result_type, data = conn.result(result_id, self.timeout)
+            if result_type != ldap.RES_SEARCH_RESULT:
+                logging.warning("LDAP result : unknown result_type")
+                return []
+            if(attribute == 'dn'):
+                # data is an array of pair with dn as the first element
+                return [x[0] for x in data]
+            else:
+                # the 2nd elt is a hash of attributes containing an array of values
+                return [x[1][attribute][0] for x in data]
+        except ldap.LDAPError, error_message:
+            logging.error("LDAP search error %s" % error_message)
+        return []
+
