aboutsummaryrefslogtreecommitdiffstats
path: root/lib/python2.7/site-packages/buildbot-0.8.8-py2.7.egg/buildbot/pbmanager.py
blob: d499c11219c660c0e08e31befe42bc5963f020c2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
# This file is part of Buildbot.  Buildbot is free software: you can
# redistribute it and/or modify it under the terms of the GNU General Public
# License as published by the Free Software Foundation, version 2.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc., 51
# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Copyright Buildbot Team Members

from zope.interface import implements
from twisted.spread import pb
from twisted.python import failure, log
from twisted.internet import defer
from twisted.cred import portal, checkers, credentials, error
from twisted.application import service, strports

debug = False

class PBManager(service.MultiService):
    """
    A centralized manager for PB ports and authentication on them.

    Allows various pieces of code to request a (port, username) combo, along
    with a password and a perspective factory.
    """
    def __init__(self):
        service.MultiService.__init__(self)
        self.setName('pbmanager')
        self.dispatchers = {}

    def register(self, portstr, username, password, pfactory):
        """
        Register a perspective factory PFACTORY to be executed when a PB
        connection arrives on PORTSTR with USERNAME/PASSWORD.  Returns a
        Registration object which can be used to unregister later.
        """
        # do some basic normalization of portstrs
        if type(portstr) == type(0) or ':' not in portstr:
            portstr = "tcp:%s" % portstr

        reg = Registration(self, portstr, username)

        if portstr not in self.dispatchers:
            disp = self.dispatchers[portstr] = Dispatcher(portstr)
            disp.setServiceParent(self)
        else:
            disp = self.dispatchers[portstr]

        disp.register(username, password, pfactory)

        return reg

    def _unregister(self, registration):
        disp = self.dispatchers[registration.portstr]
        disp.unregister(registration.username)
        registration.username = None
        if not disp.users:
            disp = self.dispatchers[registration.portstr]
            del self.dispatchers[registration.portstr]
            return disp.disownServiceParent()
        return defer.succeed(None)

class Registration(object):
    def __init__(self, pbmanager, portstr, username):
        self.portstr = portstr
        "portstr this registration is active on"
        self.username = username
        "username of this registration"

        self.pbmanager = pbmanager

    def __repr__(self):
        return "<pbmanager.Registration for %s on %s>" % \
                            (self.username, self.portstr)

    def unregister(self):
        """
        Unregister this registration, removing the username from the port, and
        closing the port if there are no more users left.  Returns a Deferred.
        """
        return self.pbmanager._unregister(self)

    def getPort(self):
        """
        Helper method for testing; returns the TCP port used for this
        registration, even if it was specified as 0 and thus allocated by the
        OS.
        """
        disp = self.pbmanager.dispatchers[self.portstr]
        return disp.port.getHost().port


class Dispatcher(service.Service):
    implements(portal.IRealm, checkers.ICredentialsChecker)

    credentialInterfaces = [ credentials.IUsernamePassword,
                             credentials.IUsernameHashedPassword ]

    def __init__(self, portstr):
        self.portstr = portstr
        self.users = {}

        # there's lots of stuff to set up for a PB connection!
        self.portal = portal.Portal(self)
        self.portal.registerChecker(self)
        self.serverFactory = pb.PBServerFactory(self.portal)
        self.serverFactory.unsafeTracebacks = True
        self.port = strports.listen(portstr, self.serverFactory)

    def __repr__(self):
        return "<pbmanager.Dispatcher for %s on %s>" % \
                            (", ".join(self.users.keys()), self.portstr)

    def stopService(self):
        # stop listening on the port when shut down
        d = defer.maybeDeferred(self.port.stopListening)
        d.addCallback(lambda _ : service.Service.stopService(self))
        return d

    def register(self, username, password, pfactory):
        if debug:
            log.msg("registering username '%s' on pb port %s: %s"
                % (username, self.portstr, pfactory))
        if username in self.users:
            raise KeyError, ("username '%s' is already registered on PB port %s"
                             % (username, self.portstr))
        self.users[username] = (password, pfactory)

    def unregister(self, username):
        if debug:
            log.msg("unregistering username '%s' on pb port %s"
                    % (username, self.portstr))
        del self.users[username]

    # IRealm

    def requestAvatar(self, username, mind, interface):
        assert interface == pb.IPerspective
        if username not in self.users:
            d = defer.succeed(None) # no perspective
        else:
            _, afactory = self.users.get(username)
            d = defer.maybeDeferred(afactory, mind, username)

        # check that we got a perspective
        def check(persp):
            if not persp:
                raise ValueError("no perspective for '%s'" % username)
            return persp
        d.addCallback(check)

        # call the perspective's attached(mind)
        def call_attached(persp):
            d = defer.maybeDeferred(persp.attached, mind)
            d.addCallback(lambda _ : persp) # keep returning the perspective
            return d
        d.addCallback(call_attached)

        # return the tuple requestAvatar is expected to return
        def done(persp):
            return (pb.IPerspective, persp, lambda: persp.detached(mind))
        d.addCallback(done)

        return d
    
    # ICredentialsChecker

    def requestAvatarId(self, creds):
        if creds.username in self.users:
            password, _ = self.users[creds.username]
            d = defer.maybeDeferred(creds.checkPassword, password)
            def check(matched):
                if not matched:
                    log.msg("invalid login from user '%s'" % creds.username)
                    return failure.Failure(error.UnauthorizedLogin())
                return creds.username
            d.addCallback(check)
            return d
        else:
            log.msg("invalid login from unknown user '%s'" % creds.username)
            return defer.fail(error.UnauthorizedLogin())