From 38c5cf680f552d1613d88c1dfeabb13ccc8d1533 Mon Sep 17 00:00:00 2001
From: Benoit Sigoure <tsuna@lrde.epita.fr>
Date: Thu, 15 Nov 2007 23:23:25 +0100
Subject: [PATCH v4 11/12] Allow authentication to work with .htpasswd-style files.

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

Signed-off-by: Benoit Sigoure <tsuna@lrde.epita.fr>
---
 NEWS                                  |    3 +-
 buildbot/status/web/authentication.py |   40 ++++++++++++++++++++++++++++++++-
 docs/buildbot.texinfo                 |    6 +++++
 3 files changed, 47 insertions(+), 2 deletions(-)

diff --git a/NEWS b/NEWS
index 7fdcd11..2a87f27 100644
--- a/NEWS
+++ b/NEWS
@@ -13,7 +13,8 @@ that start builds on all Builders at once.
 *** Password protected WebStatus
 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.
+argument in pretty much the same way as Try_Userpass does.  The class
+HTPasswdAuth authenticate users with a .htpasswd-style file.
 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 c8c6875..1c36d78 100644
--- a/buildbot/status/web/authentication.py
+++ b/buildbot/status/web/authentication.py
@@ -1,4 +1,4 @@
-
+import os
 from zope.interface import Interface, implements
 
 class IAuth(Interface):
@@ -45,3 +45,41 @@ class BasicAuth(AuthBase):
                 return True
         self.err = "Invalid login or password"
         return False
+
+class HTPasswdAuth(AuthBase):
+    implements(IAuth)
+    """Implement an authentication against an .htpasswd-style file."""
+
+    file = ""
+    """Path to the .htpasswd-style file to use."""
+
+    def __init__(self, file):
+        """C{file} is a path to an .htpasswd-style file."""
+        assert os.path.exists(file)
+        self.file = file
+
+    def authenticate(self, login, password):
+        """Authenticate C{login} and C{password} against a .htpasswd-style
+        file"""
+        if not os.path.exists(self.file):
+            self.err = "No such file: " + self.file
+            return False
+        # Fetch the lines from the .htpasswd file and split them in a
+        # [login, password] array.
+        lines = [l.rstrip().split(':', 1)
+                 for l in file(self.file).readlines()]
+        # Keep only the line for this login
+        lines = [l for l in lines if l[0] == login]
+        if not lines:
+            self.err = "Invalid login"
+            return False
+        # This is the DES-hash of the password.  The first two characters are
+        # the salt used to introduce disorder in the DES algorithm.
+        hash = lines[0][1]
+        from crypt import crypt
+        res = hash == crypt(password, hash[0:2])
+        if res:
+            self.err = ""
+        else:
+            self.err = "Invalid password"
+        return res
diff --git a/docs/buildbot.texinfo b/docs/buildbot.texinfo
index 278cf4a..e16ee40 100644
--- a/docs/buildbot.texinfo
+++ b/docs/buildbot.texinfo
@@ -6127,12 +6127,18 @@ current builds, in that case you can pass an instance of
 @code{status.web.authentication.IAuth} as a @code{auth} keyword argument.
 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
+@file{.htpasswd}-style file.
 
 @example
 from buildbot.status.html import WebStatus
 from buildbot.status.web.authentication import BasicAuth
 users = [('login', 'password'), ('bob', 'secret-pass')]
 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)))
 @end example
 
 
-- 
1.5.3.5.737.gdee1b


