1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 from zope.interface import implements
17 from twisted.spread import pb
18 from twisted.python import failure, log
19 from twisted.internet import defer
20 from twisted.cred import portal, checkers, credentials, error
21 from twisted.application import service, strports
22
23 debug = False
24
26 """
27 A centralized manager for PB ports and authentication on them.
28
29 Allows various pieces of code to request a (port, username) combo, along
30 with a password and a perspective factory.
31 """
33 service.MultiService.__init__(self)
34 self.setName('pbmanager')
35 self.dispatchers = {}
36
37 - def register(self, portstr, username, password, pfactory):
38 """
39 Register a perspective factory PFACTORY to be executed when a PB
40 connection arrives on PORTSTR with USERNAME/PASSWORD. Returns a
41 Registration object which can be used to unregister later.
42 """
43
44 if type(portstr) == type(0) or ':' not in portstr:
45 portstr = "tcp:%s" % portstr
46
47 reg = Registration(self, portstr, username)
48
49 if portstr not in self.dispatchers:
50 disp = self.dispatchers[portstr] = Dispatcher(portstr)
51 disp.setServiceParent(self)
52 else:
53 disp = self.dispatchers[portstr]
54
55 disp.register(username, password, pfactory)
56
57 return reg
58
60 disp = self.dispatchers[registration.portstr]
61 disp.unregister(registration.username)
62 registration.username = None
63 if not disp.users:
64 disp = self.dispatchers[registration.portstr]
65 del self.dispatchers[registration.portstr]
66 return disp.disownServiceParent()
67 return defer.succeed(None)
68
69
71 - def __init__(self, pbmanager, portstr, username):
78
80 """
81 Unregister this registration, removing the username from the port, and
82 closing the port if there are no more users left. Returns a Deferred.
83 """
84 return self.pbmanager._unregister(self)
85
87 """
88 Helper method for testing; returns the TCP port used for this
89 registration, even if it was specified as 0 and thus allocated by the
90 OS.
91 """
92 disp = self.pbmanager.dispatchers[self.portstr]
93 return disp.port.getHost().port
94
95
97 implements(portal.IRealm, checkers.ICredentialsChecker)
98
99 credentialInterfaces = [ credentials.IUsernamePassword,
100 credentials.IUsernameHashedPassword ]
101
103 self.portstr = portstr
104 self.users = {}
105
106
107 self.portal = portal.Portal(self)
108 self.portal.registerChecker(self)
109 self.serverFactory = pb.PBServerFactory(self.portal)
110 self.serverFactory.unsafeTracebacks = True
111 self.port = strports.listen(portstr, self.serverFactory)
112
114
115 d = defer.maybeDeferred(self.port.stopListening)
116 d.addCallback(lambda _ : service.Service.stopService(self))
117 return d
118
119 - def register(self, username, password, pfactory):
120 if debug:
121 log.msg("registering username '%s' on pb port %s: %s"
122 % (username, self.portstr, pfactory))
123 if username in self.users:
124 raise KeyError, ("username '%s' is already registered on PB port %s"
125 % (username, self.portstr))
126 self.users[username] = (password, pfactory)
127
133
134
135
137 assert interface == pb.IPerspective
138 if username not in self.users:
139 d = defer.succeed(None)
140 else:
141 _, afactory = self.users.get(username)
142 d = defer.maybeDeferred(afactory, mind, username)
143
144
145 def check(persp):
146 if not persp:
147 raise ValueError("no perspective for '%s'" % username)
148 return persp
149 d.addCallback(check)
150
151
152 def call_attached(persp):
153 d = defer.maybeDeferred(persp.attached, mind)
154 d.addCallback(lambda _ : persp)
155 return d
156 d.addCallback(call_attached)
157
158
159 def done(persp):
160 return (pb.IPerspective, persp, lambda: persp.detached(mind))
161 d.addCallback(done)
162
163 return d
164
165
166
168 if creds.username in self.users:
169 password, _ = self.users[creds.username]
170 d = defer.maybeDeferred(creds.checkPassword, password)
171 def check(matched):
172 if not matched:
173 log.msg("invalid login from user '%s'" % creds.username)
174 return failure.Failure(error.UnauthorizedLogin())
175 return creds.username
176 d.addCallback(check)
177 return d
178 else:
179 log.msg("invalid login from unknown user '%s'" % creds.username)
180 return defer.fail(error.UnauthorizedLogin())
181