Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Get username from HTTP request headers when buildbot is served by frontend webserver #266

Merged
merged 2 commits into from

2 participants

@paroga

This change is a friend of #45.

Thanks to dionorgua for the documentation I reused in the first commit.

paroga added some commits
@paroga paroga Add support for getting HTTP username
Sometimes it may be useful to run WebStatus through standalone
webserver that may be configured for HTTP authentication (for
example in corporate network it may use LDAP to authenticate users).
In such cases it's reasonable to trust webserver and instead of
asking for username and password for actions like trigger build,
just get username from HTTP headers.
0ec7159
@paroga paroga Hide "username" field when HTTP authentication is enabled
In case if username is already known from HTTP request headers, there
is no sense to ask user to enter it again. This adds needUserForm
method to Authz class and modifies the authFormIfNeeded template to
hide the username field if not needed.
31ff088
@djmitche djmitche merged commit 31ff088 into from
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Oct 6, 2011
  1. @paroga

    Add support for getting HTTP username

    paroga authored
    Sometimes it may be useful to run WebStatus through standalone
    webserver that may be configured for HTTP authentication (for
    example in corporate network it may use LDAP to authenticate users).
    In such cases it's reasonable to trust webserver and instead of
    asking for username and password for actions like trigger build,
    just get username from HTTP headers.
  2. @paroga

    Hide "username" field when HTTP authentication is enabled

    paroga authored
    In case if username is already known from HTTP request headers, there
    is no sense to ask user to enter it again. This adds needUserForm
    method to Authz class and modifies the authFormIfNeeded template to
    hide the username field if not needed.
This page is out of date. Refresh to see the latest.
View
1  master/CREDITS
@@ -110,6 +110,7 @@ Nicolás Alvarez
Niklaus Giger
Olivier Bonnet
Olly Betts
+Patrick Gansterer
Paul Warren
Paul Winkler
Phil Thompson
View
19 master/buildbot/status/web/authz.py
@@ -37,11 +37,14 @@ class Authz(object):
def __init__(self,
default_action=False,
auth=None,
+ useHttpHeader=False,
**kwargs):
self.auth = auth
if auth:
assert IAuth.providedBy(auth)
+ self.useHttpHeader = useHttpHeader
+
self.config = dict( (a, default_action) for a in self.knownActions )
for act in self.knownActions:
if act in kwargs:
@@ -52,9 +55,13 @@ def __init__(self,
raise ValueError("unknown authorization action(s) " + ", ".join(kwargs.keys()))
def getUsername(self, request):
+ if self.useHttpHeader:
+ return request.getUser()
return request.args.get("username", ["<unknown>"])[0]
def getPassword(self, request):
+ if self.useHttpHeader:
+ return request.getPassword()
return request.args.get("passwd", ["<no-password>"])[0]
def advertiseAction(self, action):
@@ -75,6 +82,18 @@ def needAuthForm(self, action):
return True
return False
+ def needUserForm(self, action):
+ """Does this action require an user form?"""
+ if action not in self.knownActions:
+ raise KeyError("unknown action")
+ if self.useHttpHeader:
+ # TODO: show the form when Authorization header is missing?
+ return False
+ cfg = self.config.get(action, False)
+ if cfg:
+ return True
+ return False
+
def actionAllowed(self, action, request, *args):
"""Is this ACTION allowed, given this http REQUEST?"""
if action not in self.knownActions:
View
4 master/buildbot/status/web/templates/forms.html
@@ -4,12 +4,12 @@
<span class="label">Your username:</span>
<input type="text" name="username"/>
</div>
-
+
<div class="row">
<span class="label">Your password:</span>
<input type="password" name="passwd"/>
</div>
- {% else %}
+ {% elif authz.needUserForm(action) %}
<div class="row">
<span class="label">Your name:</span>
<input type="text" name="username"/>
View
51 master/buildbot/test/unit/test_status_web_authz_Authz.py
@@ -28,6 +28,25 @@ def __init__(self, username, passwd):
'passwd' : [ passwd ],
}
+ def getUser(self):
+ return None
+
+ def getPassword(self):
+ return None
+
+class StubHttpAuthRequest(object):
+ # all we need from a request is username/password
+ def __init__(self, username, passwd):
+ self.args = {}
+ self.username = username
+ self.passwd = passwd
+
+ def getUser(self):
+ return self.username
+
+ def getPassword(self):
+ return self.passwd
+
class StubAuth(object):
implements(IAuth)
def __init__(self, user):
@@ -152,9 +171,41 @@ def test_needAuthForm_callable(self):
z = Authz(stopAllBuilds = lambda u : False)
assert z.needAuthForm('stopAllBuilds')
+ def test_needUserForm_False(self):
+ z = Authz(forceBuild = False)
+ assert not z.needUserForm('forceBuild')
+
+ def test_needUserForm_Ture(self):
+ z = Authz(forceBuild = True)
+ assert z.needUserForm('forceBuild')
+
+ def test_needUserForm_http_False(self):
+ z = Authz(useHttpHeader = True, forceBuild = False)
+ assert not z.needUserForm('forceBuild')
+
+ def test_needUserForm_http_True(self):
+ z = Authz(useHttpHeader = True, forceBuild = True)
+ assert not z.needUserForm('forceBuild')
+
def test_constructor_invalidAction(self):
self.assertRaises(ValueError, Authz, someRandomAction=3)
+ def test_getUsername_http(self):
+ z = Authz(useHttpHeader = True)
+ assert z.getUsername(StubHttpAuthRequest('foo', 'bar')) == 'foo'
+
+ def test_getPassword_http(self):
+ z = Authz(useHttpHeader = True)
+ assert z.getPassword(StubHttpAuthRequest('foo', 'bar')) == 'bar'
+
+ def test_getUsername_http_missing(self):
+ z = Authz(useHttpHeader = True)
+ assert z.getUsername(StubRequest('foo', 'bar')) == None
+
+ def test_getPassword_http_missing(self):
+ z = Authz(useHttpHeader = True)
+ assert z.getPassword(StubRequest('foo', 'bar')) == None
+
def test_getUsername_request(self):
z = Authz()
assert z.getUsername(StubRequest('foo', 'bar')) == 'foo'
View
22 master/docs/manual/cfg-statustargets.rst
@@ -515,6 +515,28 @@ The ``forceBuild`` and ``pingBuilder`` actions both supply a
object. The ``cancelPendingBuild`` action supplies a :class:`BuildRequest`. The
remainder do not supply any extra arguments.
+HTTP-based authentication by frontend server
+############################################
+
+In case if WebStatus is served through reverse proxy that supports HTTP-based
+authentication (like apache, lighttpd), it's possible to to tell WebStatus to
+trust web server and get username from request headers. This allows displaying
+correct usernames in build reason, interrupt messages, etc.
+
+Just set ``useHttpHeader`` to ``True`` in :class:`Authz` constructor. ::
+
+ authz = Authz(useHttpHeader=True) # WebStatus secured by web frontend with HTTP auth
+
+Please note that WebStatus can decode password for HTTP Basic requests only (for
+Digest authentication it's just impossible). Custom :class:`status.web.auth.IAuth`
+subclasses may just ignore password at all since it's already validated by web server.
+
+Administrator must make sure that it's impossible to get access to WebStatus
+using other way than through frontend. Usually this means that WebStatus should
+listen for incoming connections only on localhost (or on some firewall-protected
+port). Frontend must require HTTP authentication to access WebStatus pages
+(using any source for credentials, such as htpasswd, PAM, LDAP).
+
Logging configuration
#####################
Something went wrong with that request. Please try again.