Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

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

Merged
merged 2 commits into from over 2 years ago

2 participants

Patrick Gansterer Dustin J. Mitchell
Patrick Gansterer

This change is a friend of #45.

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

added some commits October 06, 2011
Patrick Gansterer 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
Patrick Gansterer 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
Dustin J. Mitchell djmitche merged commit 31ff088 into from October 09, 2011
Dustin J. Mitchell djmitche closed this October 09, 2011
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Showing 2 unique commits by 1 author.

Oct 06, 2011
Patrick Gansterer 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
Patrick Gansterer 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
This page is out of date. Refresh to see the latest.
1  master/CREDITS
@@ -110,6 +110,7 @@ Nicolás Alvarez
110 110
 Niklaus Giger
111 111
 Olivier Bonnet
112 112
 Olly Betts
  113
+Patrick Gansterer
113 114
 Paul Warren
114 115
 Paul Winkler
115 116
 Phil Thompson
19  master/buildbot/status/web/authz.py
@@ -37,11 +37,14 @@ class Authz(object):
37 37
     def __init__(self,
38 38
             default_action=False,
39 39
             auth=None,
  40
+            useHttpHeader=False,
40 41
             **kwargs):
41 42
         self.auth = auth
42 43
         if auth:
43 44
             assert IAuth.providedBy(auth)
44 45
 
  46
+        self.useHttpHeader = useHttpHeader
  47
+
45 48
         self.config = dict( (a, default_action) for a in self.knownActions )
46 49
         for act in self.knownActions:
47 50
             if act in kwargs:
@@ -52,9 +55,13 @@ def __init__(self,
52 55
             raise ValueError("unknown authorization action(s) " + ", ".join(kwargs.keys()))
53 56
 
54 57
     def getUsername(self, request):
  58
+        if self.useHttpHeader:
  59
+            return request.getUser()
55 60
         return request.args.get("username", ["<unknown>"])[0]
56 61
 
57 62
     def getPassword(self, request):
  63
+        if self.useHttpHeader:
  64
+            return request.getPassword()
58 65
         return request.args.get("passwd", ["<no-password>"])[0]
59 66
 
60 67
     def advertiseAction(self, action):
@@ -75,6 +82,18 @@ def needAuthForm(self, action):
75 82
             return True
76 83
         return False
77 84
 
  85
+    def needUserForm(self, action):
  86
+        """Does this action require an user form?"""
  87
+        if action not in self.knownActions:
  88
+            raise KeyError("unknown action")
  89
+        if self.useHttpHeader:
  90
+            # TODO: show the form when Authorization header is missing?
  91
+            return False
  92
+        cfg = self.config.get(action, False)
  93
+        if cfg:
  94
+            return True
  95
+        return False
  96
+
78 97
     def actionAllowed(self, action, request, *args):
79 98
         """Is this ACTION allowed, given this http REQUEST?"""
80 99
         if action not in self.knownActions:
4  master/buildbot/status/web/templates/forms.html
@@ -4,12 +4,12 @@
4 4
       <span class="label">Your username:</span>
5 5
       <input type="text" name="username"/>
6 6
     </div>
7  
-  
  7
+
8 8
     <div class="row">
9 9
       <span class="label">Your password:</span>
10 10
       <input type="password" name="passwd"/>
11 11
     </div>
12  
-  {% else %}
  12
+  {% elif authz.needUserForm(action) %}
13 13
     <div class="row">
14 14
       <span class="label">Your name:</span>
15 15
       <input type="text" name="username"/>
51  master/buildbot/test/unit/test_status_web_authz_Authz.py
@@ -28,6 +28,25 @@ def __init__(self, username, passwd):
28 28
             'passwd' : [ passwd ],
29 29
         }
30 30
 
  31
+    def getUser(self):
  32
+        return None
  33
+
  34
+    def getPassword(self):
  35
+        return None
  36
+
  37
+class StubHttpAuthRequest(object):
  38
+    # all we need from a request is username/password
  39
+    def __init__(self, username, passwd):
  40
+        self.args = {}
  41
+        self.username = username
  42
+        self.passwd = passwd
  43
+
  44
+    def getUser(self):
  45
+        return self.username
  46
+
  47
+    def getPassword(self):
  48
+        return self.passwd
  49
+
31 50
 class StubAuth(object):
32 51
     implements(IAuth)
33 52
     def __init__(self, user):
@@ -152,9 +171,41 @@ def test_needAuthForm_callable(self):
152 171
         z = Authz(stopAllBuilds = lambda u : False)
153 172
         assert z.needAuthForm('stopAllBuilds')
154 173
 
  174
+    def test_needUserForm_False(self):
  175
+        z = Authz(forceBuild = False)
  176
+        assert not z.needUserForm('forceBuild')
  177
+
  178
+    def test_needUserForm_Ture(self):
  179
+        z = Authz(forceBuild = True)
  180
+        assert z.needUserForm('forceBuild')
  181
+
  182
+    def test_needUserForm_http_False(self):
  183
+        z = Authz(useHttpHeader = True, forceBuild = False)
  184
+        assert not z.needUserForm('forceBuild')
  185
+
  186
+    def test_needUserForm_http_True(self):
  187
+        z = Authz(useHttpHeader = True, forceBuild = True)
  188
+        assert not z.needUserForm('forceBuild')
  189
+
155 190
     def test_constructor_invalidAction(self):
156 191
         self.assertRaises(ValueError, Authz, someRandomAction=3)
157 192
 
  193
+    def test_getUsername_http(self):
  194
+        z = Authz(useHttpHeader = True)
  195
+        assert z.getUsername(StubHttpAuthRequest('foo', 'bar')) == 'foo'
  196
+
  197
+    def test_getPassword_http(self):
  198
+        z = Authz(useHttpHeader = True)
  199
+        assert z.getPassword(StubHttpAuthRequest('foo', 'bar')) == 'bar'
  200
+
  201
+    def test_getUsername_http_missing(self):
  202
+        z = Authz(useHttpHeader = True)
  203
+        assert z.getUsername(StubRequest('foo', 'bar')) == None
  204
+
  205
+    def test_getPassword_http_missing(self):
  206
+        z = Authz(useHttpHeader = True)
  207
+        assert z.getPassword(StubRequest('foo', 'bar')) == None
  208
+
158 209
     def test_getUsername_request(self):
159 210
         z = Authz()
160 211
         assert z.getUsername(StubRequest('foo', 'bar')) == 'foo'
22  master/docs/manual/cfg-statustargets.rst
Source Rendered
@@ -515,6 +515,28 @@ The ``forceBuild`` and ``pingBuilder`` actions both supply a
515 515
 object.  The ``cancelPendingBuild`` action supplies a :class:`BuildRequest`.  The
516 516
 remainder do not supply any extra arguments.
517 517
 
  518
+HTTP-based authentication by frontend server
  519
+############################################
  520
+
  521
+In case if WebStatus is served through reverse proxy that supports HTTP-based
  522
+authentication (like apache, lighttpd), it's possible to to tell WebStatus to
  523
+trust web server and get username from request headers. This allows displaying
  524
+correct usernames in build reason, interrupt messages, etc.
  525
+
  526
+Just set ``useHttpHeader`` to ``True`` in :class:`Authz` constructor. ::
  527
+
  528
+    authz = Authz(useHttpHeader=True) # WebStatus secured by web frontend with HTTP auth
  529
+
  530
+Please note that WebStatus can decode password for HTTP Basic requests only (for
  531
+Digest authentication it's just impossible). Custom :class:`status.web.auth.IAuth`
  532
+subclasses may just ignore password at all since it's already validated by web server.
  533
+
  534
+Administrator must make sure that it's impossible to get access to WebStatus
  535
+using other way than through frontend. Usually this means that WebStatus should
  536
+listen for incoming connections only on localhost (or on some firewall-protected
  537
+port). Frontend must require HTTP authentication to access WebStatus pages
  538
+(using any source for credentials, such as htpasswd, PAM, LDAP).
  539
+
518 540
 Logging configuration
519 541
 #####################
520 542
 
Commit_comment_tip

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.