Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 2 commits into from Oct 9, 2011
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions master/CREDITS
Expand Up @@ -110,6 +110,7 @@ Nicolás Alvarez
Niklaus Giger
Olivier Bonnet
Olly Betts
Patrick Gansterer
Paul Warren
Paul Winkler
Phil Thompson
Expand Down
19 changes: 19 additions & 0 deletions master/buildbot/status/web/authz.py
Expand Up @@ -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:
Expand All @@ -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):
Expand All @@ -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:
Expand Down
4 changes: 2 additions & 2 deletions master/buildbot/status/web/templates/forms.html
Expand Up @@ -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"/>
Expand Down
51 changes: 51 additions & 0 deletions master/buildbot/test/unit/test_status_web_authz_Authz.py
Expand Up @@ -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):
Expand Down Expand Up @@ -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'
Expand Down
22 changes: 22 additions & 0 deletions master/docs/manual/cfg-statustargets.rst
Expand Up @@ -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
#####################

Expand Down