[Buildbot-devel] [PATCH 12/12 v3] Implement an LDAP based authentication for the WebStatus.

Benoit Sigoure tsuna at lrde.epita.fr
Wed Nov 28 13:07:52 UTC 2007


	* NEWS: Mention the change.
	* buildbot/status/web/authentication.py (LDAPAuth): New class.
	* docs/buildbot.texinfo (WebStatus Configuration Parameters):
	Document the new feature.

Signed-off-by: Benoit Sigoure <tsuna at lrde.epita.fr>
---
This is a resend with a one-line fix in LDAPAuth.authenticate that adds a
missing `import ldap' statement.

 NEWS                                  |    3 +-
 buildbot/status/web/authentication.py |  101 +++++++++++++++++++++++++++++++++
 docs/buildbot.texinfo                 |    9 +++-
 3 files changed, 111 insertions(+), 2 deletions(-)

diff --git a/NEWS b/NEWS
index 9ef04a4..d687e2a 100644
--- a/NEWS
+++ b/NEWS
@@ -11,7 +11,8 @@ User visible changes in Buildbot.             -*- outline -*-
 The WebStatus constructor can take an instance of IAuth (from
 status.web.authentication).  The class BasicAuth accepts a `userpass' keyword
 argument in pretty much the same way as Try_Userpass does.  The class
-HTPasswdAuth authenticate users with a .htpasswd-style file.
+HTPasswdAuth authenticate users with a .htpasswd-style file.  The class
+LDAPAuth authenticate users with an LDAP directory (requires python-ldap).
 Only users with a valid login/password can then force/stop builds from the
 WebStatus.
 
diff --git a/buildbot/status/web/authentication.py b/buildbot/status/web/authentication.py
index a7e8527..46a122b 100644
--- a/buildbot/status/web/authentication.py
+++ b/buildbot/status/web/authentication.py
@@ -88,3 +88,104 @@ class HTPasswdAuth(AuthBase):
         else:
             self.err = "Invalid password"
         return res
+
+class LDAPAuth(AuthBase):
+    implements(IAuth)
+    """Implement a synchronous authentication with an LDAP directory."""
+
+    basedn = ""
+    """Base DN (Distinguished Name): the root of the LDAP directory tree
+
+    e.g.: ou=people,dc=subdomain,dc=company,dc=com"""
+
+    binddn = ""
+    """The bind DN is the user on the external LDAP server permitted to
+    search the LDAP directory.  You can leave this empty."""
+
+    passwd = ""
+    """Password required to query the LDAP server.  Leave this empty if
+    you can query the server without password."""
+
+    host = ""
+    """Hostname of the LDAP server"""
+
+    search = ""
+    """Template string to use to search the user trying to login in the
+    LDAP directory"""
+
+    def __init__(self, host, basedn, binddn="", passwd="",
+                 search="(uid=%s)"):
+        """Authenticate users against the LDAP server on C{host}.
+
+        The arguments are documented above."""
+        self.host = host
+        self.basedn = basedn
+        self.binddn = binddn
+        self.passwd = passwd
+        self.search = search
+
+        self.search_conn = None
+        self.connect()
+
+    def connect(self):
+        """Setup the connections with the LDAP server."""
+        import ldap
+        # Close existing connections
+        if self.search_conn:
+            self.search_conn.unbind()
+        # Connection used to locate the users in the LDAP DB.
+        self.search_conn = ldap.open(self.host)
+        self.search_conn.bind_s(self.binddn, self.passwd,
+                                ldap.AUTH_SIMPLE)
+
+    def authenticate(self, login, password):
+        """Authenticate the C{login} / C{password} with the LDAP server."""
+        import ldap
+        # Python-LDAP raises all sorts of exceptions to express various
+        # failures, let's catch them all here and assume that if
+        # anything goes wrong, the authentication failed.
+        try:
+            res = self._authenticate(login, password)
+            if res:
+                self.err = ""
+            return res
+        except ldap.LDAPError, e:
+            self.err = "LDAP error: " + str(e)
+            return False
+        except:
+            self.err = "unknown error"
+            return False
+
+    def _authenticate(self, login, password):
+        import ldap
+        # Search the login in the LDAP DB
+        try:
+            result = self.search_conn.search_s(self.basedn,
+                                               ldap.SCOPE_SUBTREE,
+                                               self.search % login,
+                                               ['objectclass'], 1)
+        except ldap.SERVER_DOWN:
+            self.err = "LDAP server seems down"
+            # Try to reconnect...
+            self.connect()
+            # FIXME: Check that this can't lead to an infinite recursion
+            return self.authenticate(login, password)
+
+        # Make sure we found a single user in the LDAP DB
+        if not result or len(result) != 1:
+            self.err = "user not found in the LDAP DB"
+            return False
+
+        # Connection used to authenticate users with the LDAP DB.
+        auth_conn = ldap.open(self.host)
+        # DN associated to this user
+        ldap_dn = result[0][0]
+        #log.msg('using ldap_dn = ' + ldap_dn)
+        # Authenticate the user
+        try:
+            auth_conn.bind_s(ldap_dn, password, ldap.AUTH_SIMPLE)
+        except ldap.INVALID_CREDENTIALS:
+            self.err = "invalid credentials"
+            return False
+        auth_conn.unbind()
+        return True
diff --git a/docs/buildbot.texinfo b/docs/buildbot.texinfo
index 098ab63..301941b 100644
--- a/docs/buildbot.texinfo
+++ b/docs/buildbot.texinfo
@@ -6071,7 +6071,9 @@ current builds, in that case you can pass an instance of
 The class @code{BasicAuth} implements a basic authentication mechanism
 using a list of login/password pairs provided from the configuration file.
 The class @code{HTPasswdAuth} implements an authentication against an
- at file{.htpasswd}-style file.
+ at file{.htpasswd}-style file.  The class @code{LDAPAuth} authenticates
+the users with an LDAP database.  It requires
+ at uref{http://python-ldap.sourceforge.net/, python-ldap}.
 
 @example
 from buildbot.status.html import WebStatus
@@ -6082,6 +6084,11 @@ c['status'].append(WebStatus(http_port=8080, auth=BasicAuth(users)))
 from buildbot.status.web.authentication import HTPasswdAuth
 file = '/path/to/file'
 c['status'].append(WebStatus(http_port=8080, auth=HTPasswdAuth(file)))
+
+from buildbot.status.web.authentication import LDAPAuth
+c['status'].append(WebStatus(http_port=8080,
+                             auth=LDAPAuth('ldap.server.company.com',
+                                           'ou=people,dc=company,dc=com'))
 @end example
 
 
-- 
1.5.3.5.756.gc887a





More information about the devel mailing list