[Buildbot-devel] [PATCH 11/11] Allow authentication to work with .htpasswd-style files.
Benoit Sigoure
tsuna at lrde.epita.fr
Fri Nov 16 17:13:01 UTC 2007
* NEWS: Mention the change.
* buildbot/status/web/baseweb.py (WebStatus.__init__): Adjust to
also accept a string as `userpass' argument.
(WebStatus._auth_htpasswd): New.
(WebStatus.authUser): Use the new method when htpasswd-style
authentication is used.
* docs/buildbot.texinfo (WebStatus Configuration Parameters):
Document the new feature.
Signed-off-by: Benoit Sigoure <tsuna at lrde.epita.fr>
---
NEWS | 3 +-
buildbot/status/web/baseweb.py | 62 +++++++++++++++++++++++++++++++--------
docs/buildbot.texinfo | 3 +-
3 files changed, 53 insertions(+), 15 deletions(-)
diff --git a/NEWS b/NEWS
index d136cf7..7d4496b 100644
--- a/NEWS
+++ b/NEWS
@@ -11,7 +11,8 @@ User visible changes in Buildbot. -*- outline -*-
The WebStatus constructor can take a list of login/password as a (optional)
`userpass' argument in pretty much the same way as Try_Userpass does.
Only users with a valid login/password can then force/stop builds from the
-WebStatus.
+WebStatus. Alternatively, the `userpass' argument can be a string that
+contains a path to a .htpasswd-style file to use to authenticate users.
* Release 0.7.6 (30 Sep 2007)
diff --git a/buildbot/status/web/baseweb.py b/buildbot/status/web/baseweb.py
index 5755140..d8a8448 100644
--- a/buildbot/status/web/baseweb.py
+++ b/buildbot/status/web/baseweb.py
@@ -353,7 +353,7 @@ class WebStatus(service.MultiService):
# all the changes).
def __init__(self, http_port=None, distrib_port=None, allowForce=False,
- userpass=[]):
+ userpass=None):
"""Run a web server that provides Buildbot status.
@type http_port: int or L{twisted.application.strports} string
@@ -388,12 +388,15 @@ class WebStatus(service.MultiService):
the strports parser.
@param allowForce: boolean, if True then the webserver will allow
visitors to trigger and cancel builds
- @rtpw userpass: list of (string, string) tuples
+ @rtpw userpass: list of (string, string) tuples or a string
@param userpass: a list of login/password pairs to restrain access to
the C{allowForce} features. Ignored if C{allowForce}
is not True. If you don't pass a list of
user/passwords, the legacy behavior is used: people
can force/stop builds without auth.
+ If C{userpass} is a string, it is assumed to be a
+ path to a .htpasswd-style file which will be used to
+ authenticate users.
"""
service.MultiService.__init__(self)
@@ -408,17 +411,25 @@ class WebStatus(service.MultiService):
self.distrib_port = distrib_port
self.allowForce = allowForce
if allowForce and userpass:
- for user_pass_pair in userpass:
- assert isinstance(user_pass_pair, tuple)
- login, password = user_pass_pair
- assert isinstance(login, str)
- assert isinstance(password, str)
+ if isinstance(userpass, str):
+ self.auth_type = 'htpasswd'
+ import os
+ assert os.path.exists(userpass)
+ elif isinstance(userpass, list):
+ self.auth_type = 'simple'
+ for user_pass_pair in userpass:
+ assert isinstance(user_pass_pair, tuple)
+ login, password = user_pass_pair
+ assert isinstance(login, str)
+ assert isinstance(password, str)
+ else:
+ raise TypeError("userpass has an invalid type")
self.userpass = userpass
else:
if userpass:
log.msg("warning: discarding the list of user/passwords, you"
"must also set allowForce to True to use it.")
- self.userpass = []
+ self.userpass = None
# this will be replaced once we've been attached to a parent (and
# thus have a basedir and can reference BASEDIR/public_html/)
@@ -527,6 +538,23 @@ class WebStatus(service.MultiService):
return True
return False
+ def _auth_htpasswd(self, login, password):
+ """Authenticate C{login} and C{password} against a .htpasswd-style
+ file"""
+ # 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.userpass).readlines()]
+ # Keep only the line for this login
+ lines = [l for l in lines if l[0] == login]
+ if not lines:
+ 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
+ return hash == crypt(password, hash[0:2])
+
def authUser(self, login, password):
"""Check that login/password is a valid user/password pair and can be
allowed to perform a privileged action. If this WebStatus is not
@@ -534,11 +562,19 @@ class WebStatus(service.MultiService):
approach)."""
if not self.userpass:
return False
- for l, p in self.userpass:
- if login == l and password == p:
- return True
- log.msg("Authentication failed (%s/%s)" % (login, password))
- return False
+
+ res = False
+ if self.auth_type == 'htpasswd':
+ res = self._auth_htpasswd(login, password)
+ else:
+ # Basic authentication against a list of login/password.
+ for l, p in self.userpass:
+ if login == l and password == p:
+ res = True
+
+ if not res:
+ log.msg("Authentication failed (%s/%s)" % (login, password))
+ return res
def getPortnum(self):
# this is for the benefit of unit tests
diff --git a/docs/buildbot.texinfo b/docs/buildbot.texinfo
index 7086f96..5cf9d17 100644
--- a/docs/buildbot.texinfo
+++ b/docs/buildbot.texinfo
@@ -6067,7 +6067,8 @@ installed at the time of the previous build. The default value is False.
You may not wish to allow strangers to cause a build to run or to stop current
builds, in that case you can pass a list of pair of login/password as a
- at code{userpass} argument.
+ at code{userpass} argument. Alternatively, this argument can be a string that
+contains a path to a @file{.htpasswd}-style file to use to authenticate users.
@node Buildbot Web Resources, XMLRPC server, WebStatus Configuration Parameters, WebStatus
--
1.5.3.5.654.gdd5ec
More information about the devel
mailing list