Ticket #482: buildbot-auth.patch

File buildbot-auth.patch, 11.3 KB (added by ipv6guru, 3 years ago)
  • buildbot/ldapauth.py

    diff -Nru buildbot-0.7.2/buildbot/ldapauth.py buildbot-0.7.2-auth/buildbot/ldapauth.py
    old new  
     1#!/usr/bin/env python 
     2 
     3from twisted.internet import reactor, defer 
     4from ldaptor.protocols.ldap import ldapclient, ldapsyntax, ldapconnector, \ 
     5                                   distinguishedname 
     6from ldaptor.protocols import pureldap 
     7 
     8class LDAPAuthenticationSource(object): 
     9    def __init__(self, base_dn, hostname=None, attr='uid', bind_dn='', 
     10                 bind_pw='', filter=pureldap.LDAPFilter_present('cn')): 
     11        self.hostname = hostname 
     12        self.base_dn = base_dn 
     13        self.attr = attr 
     14        self.bind_dn = bind_dn 
     15        self.bind_pw = bind_pw 
     16        self.filter = filter 
     17 
     18    def authenticate(self, username, password): 
     19        """Returns a deferred that will succeed or fail with the auth.""" 
     20        c = ldapconnector.LDAPClientCreator(reactor, ldapclient.LDAPClient) 
     21        return c.connect(self.base_dn 
     22        ).addCallback(self._connected, username, password) 
     23 
     24    def _connected(self, cli, username, password): 
     25        # Bind a first time before issuing our query. 
     26        return cli.bind(self.bind_dn, self.bind_pw 
     27        ).addCallback(self._query_bound, username, password, cli) 
     28 
     29    def _query_bound(self, dummy, username, password, cli): 
     30        # Then issue a search for the uid we want. 
     31        entries = [] 
     32        baseEntry = ldapsyntax.LDAPEntry(cli, self.base_dn) 
     33        return baseEntry.search( 
     34            # This is stupid verbose, but at least it escapes properly. 
     35            filterObject=pureldap.LDAPFilter_and([ 
     36                pureldap.LDAPFilter_equalityMatch( 
     37                    attributeDesc=pureldap.LDAPAttributeDescription(self.attr), 
     38                    assertionValue=pureldap.LDAPAssertionValue(username) 
     39                ), 
     40                self.filter, 
     41            ]), 
     42            callback=entries.append 
     43        ).addCallback(self._search_end, password, entries) 
     44 
     45    def _search_end(self, dummy, password, entries): 
     46        n_entries = len(entries) 
     47        if n_entries == 0: 
     48            raise Exception('No search results!') 
     49        elif n_entries > 1: 
     50            raise Exception('%d search results for unique entry!' % n_entries) 
     51        else: 
     52            # The password matches if we can bind as this DN with it. 
     53            return entries[0].bind(password) 
  • buildbot/status/html.py

    diff -Nru buildbot-0.7.2/buildbot/status/html.py buildbot-0.7.2-auth/buildbot/status/html.py
    old new  
    5858    label = html.escape(label) 
    5959    return ROW_TEMPLATE % {"label": label, "field": field} 
    6060 
     61def authenticate(build_authenticator, request, nextfunc): 
     62    if build_authenticator is None: 
     63        # We assume they're authorized. 
     64        return nextfunc(request) 
     65    else: 
     66        # We actually check. 
     67        name = request.args.get("username", [None])[0] 
     68        password = request.args.get("password", [None])[0] 
     69 
     70        def _success(entry): 
     71            log.msg('Successful authentication as %r' % name) 
     72            return nextfunc(request) 
     73 
     74        def _failure(f): 
     75            log.msg('Failed authentication as %r' % name, f) 
     76            raise Exception('Authentication failed') 
     77 
     78        d = build_authenticator.authenticate(name, password) 
     79        d.addCallbacks(_success, _failure) 
     80        return DeferredResource(d) 
     81 
    6182colormap = { 
    6283    'green': '#72ff75', 
    6384    } 
     
    310331class StatusResourceBuild(HtmlResource): 
    311332    title = "Build" 
    312333 
    313     def __init__(self, status, build, builderControl, buildControl): 
     334    def __init__(self, status, build, builderControl, buildControl, 
     335                 build_authenticator): 
    314336        HtmlResource.__init__(self) 
    315337        self.status = status 
    316338        self.build = build 
    317339        self.builderControl = builderControl 
    318340        self.control = buildControl 
     341        self.build_authenticator = build_authenticator 
    319342 
    320343    def body(self, request): 
    321344        b = self.build 
     
    361384                push the 'Stop' button</p>\n""" % stopURL 
    362385                data += make_row("Your name:", 
    363386                                 "<input type='text' name='username' />") 
     387                if self.build_authenticator is not None: 
     388                    data += make_row("Password:", 
     389                        "<input type='password' name='password' />") 
    364390                data += make_row("Reason for stopping build:", 
    365391                                 "<input type='text' name='comments' />") 
    366392                data += """<input type="submit" value="Stop Builder" /> 
     
    388414                     % rebuildURL) 
    389415            data += make_row("Your name:", 
    390416                             "<input type='text' name='username' />") 
     417            if self.build_authenticator is not None: 
     418                data += make_row("Password:", 
     419                    "<input type='password' name='password' />") 
    391420            data += make_row("Reason for re-running build:", 
    392421                             "<input type='text' name='comments' />") 
    393422            data += '<input type="submit" value="Rebuild" />\n' 
     
    425454        return data 
    426455 
    427456    def stop(self, request): 
     457        return authenticate(self.build_authenticator, request, 
     458                            self._stop_authenticated) 
     459 
     460    def _stop_authenticated(self, request): 
    428461        log.msg("web stopBuild of build %s:%s" % \ 
    429462                (self.build.getBuilder().getName(), 
    430463                 self.build.getNumber())) 
     
    444477        return DeferredResource(d) 
    445478 
    446479    def rebuild(self, request): 
     480        return authenticate(self.build_authenticator, request, 
     481                            self._rebuild_authenticated) 
     482 
     483    def _rebuild_authenticated(self, request): 
    447484        log.msg("web rebuild of build %s:%s" % \ 
    448485                (self.build.getBuilder().getName(), 
    449486                 self.build.getNumber())) 
     
    484521# $builder 
    485522class StatusResourceBuilder(HtmlResource): 
    486523 
    487     def __init__(self, status, builder, control): 
     524    def __init__(self, status, builder, control, build_authenticator): 
    488525        HtmlResource.__init__(self) 
    489526        self.status = status 
    490527        self.title = builder.getName() + " Builder" 
    491528        self.builder = builder 
    492529        self.control = control 
     530        self.build_authenticator = build_authenticator 
    493531 
    494532    def body(self, request): 
    495533        b = self.builder 
     
    522560        if self.control is not None and connected_slaves: 
    523561            forceURL = urllib.quote(request.childLink("force")) 
    524562            data += ( 
    525                 """ 
    526                 <form action='%(forceURL)s' class='command forcebuild'> 
     563                (""" 
     564                <form method='post' action='%(forceURL)s' class='command forcebuild'> 
    527565                <p>To force a build, fill out the following fields and 
    528566                push the 'Force Build' button</p>""" 
    529                 + make_row("Your name:", 
    530                            "<input type='text' name='username' />") 
    531                 + make_row("Reason for build:", 
    532                            "<input type='text' name='comments' />") 
     567                % {"forceURL": forceURL}) 
     568                + make_row("Username:", 
     569                           "<input type='text' name='username' />")) 
     570            if self.build_authenticator is not None: 
     571                data += make_row("Password:", 
     572                                 "<input type='password' name='password' />") 
     573            data += ( 
     574                make_row("Reason for build:", 
     575                         "<input type='text' name='comments' />") 
    533576                + make_row("Branch to build:", 
    534577                           "<input type='text' name='branch' />") 
    535578                + make_row("Revision to build:", 
     
    537580                + """ 
    538581                <input type='submit' value='Force Build' /> 
    539582                </form> 
    540                 """) % {"forceURL": forceURL} 
     583                """) 
    541584        elif self.control is not None: 
    542585            data += """ 
    543586            <p>All buildslaves appear to be offline, so it's not possible 
     
    557600        return data 
    558601 
    559602    def force(self, request): 
     603        return authenticate(self.build_authenticator, request, 
     604                            self._force_authenticated) 
     605 
     606    def _force_authenticated(self, request): 
    560607        name = request.args.get("username", ["<unknown>"])[0] 
    561608        reason = request.args.get("comments", ["<no reason specified>"])[0] 
    562609        branch = request.args.get("branch", [""])[0] 
     
    637684                if self.control: 
    638685                    control = self.control.getBuild(num) 
    639686                return StatusResourceBuild(self.status, build, 
    640                                            self.control, control) 
     687                                           self.control, control, 
     688                                           self.build_authenticator) 
    641689            else: 
    642690                return NoResource("No such build '%d'" % num) 
    643691        return NoResource("really weird URL %s" % path) 
     
    15481596    control = None 
    15491597    favicon = None 
    15501598 
    1551     def __init__(self, status, control, changemaster, categories, css): 
     1599    def __init__(self, status, control, changemaster, categories, css, 
     1600                 build_authenticator): 
    15521601        """ 
    15531602        @type  status:       L{buildbot.status.builder.Status} 
    15541603        @type  control:      L{buildbot.master.Control} 
     
    15601609        self.changemaster = changemaster 
    15611610        self.categories = categories 
    15621611        self.css = css 
     1612        self.build_authenticator = build_authenticator 
    15631613        waterfall = WaterfallStatusResource(self.status, changemaster, 
    15641614                                            categories, css) 
    15651615        self.putChild("", waterfall) 
     
    15831633            control = None 
    15841634            if self.control: 
    15851635                control = self.control.getBuilder(path) 
    1586             return StatusResourceBuilder(self.status, builder, control) 
     1636            return StatusResourceBuilder(self.status, builder, control, 
     1637                                         self.build_authenticator) 
    15871638 
    15881639        return NoResource("No such Builder '%s'" % path) 
    15891640 
     
    16361687                     "categories", "css", "favicon"] 
    16371688 
    16381689    def __init__(self, http_port=None, distrib_port=None, allowForce=True, 
    1639                  categories=None, css=buildbot_css, favicon=buildbot_icon): 
     1690                 categories=None, css=buildbot_css, favicon=buildbot_icon, 
     1691                 build_authenticator=None): 
    16401692        """To have the buildbot run its own web server, pass a port number to 
    16411693        C{http_port}. To have it run a web.distrib server 
    16421694 
     
    17021754        self.categories = categories 
    17031755        self.css = css 
    17041756        self.favicon = favicon 
     1757        self.build_authenticator = build_authenticator 
    17051758 
    17061759    def __repr__(self): 
    17071760        if self.http_port is None: 
     
    17261779            control = None 
    17271780        change_svc = self.parent.change_svc 
    17281781        sr = StatusResource(status, control, change_svc, self.categories, 
    1729                             self.css) 
     1782                            self.css, self.build_authenticator) 
    17301783        sr.favicon = self.favicon 
    17311784        self.site = server.Site(sr) 
    17321785