21
\"This isn\'t a professional opinion, but it's probably got enough
22
internet to kill you.\" --glyph
26
This is a broker for proxies for and copies of objects. It provides a
27
translucent interface layer to those proxies.
29
The protocol is not opaque, because it provides objects which
30
represent the remote proxies and require no context (server
31
references, IDs) to operate on.
33
It is not transparent because it does *not* attempt to make remote
34
objects behave identically, or even similiarly, to local objects.
35
Method calls are invoked asynchronously, and specific rules are
36
applied when serializing arguments.
22
\"This isn\'t a professional opinion, but it's probably got enough
23
internet to kill you.\" --glyph
25
Stability: semi-stable
27
Future Plans: The connection APIs will be extended with support for
28
URLs, that will be able to extend resource location and discovery
29
conversations and specify different authentication mechanisms besides
30
username/password. This should only add to, and not change, the
37
New APIs have been added for serving and connecting. On the client
38
side, use PBClientFactory.getPerspective() instead of connect(), and
39
PBClientFactory.getRootObject() instead of getObjectAt(). Server side
40
should switch to updated cred APIs by using PBServerFactory, at which
41
point clients would switch to PBClientFactory.login().
43
The new cred support means a different method is sent for login,
44
although the protocol is compatible on the binary level. When we
45
switch to pluggable credentials this will introduce another change,
46
although the current change will still be supported.
48
The Perspective class is now deprecated, and has been replaced with
49
Avatar, which does not rely on the old cred APIs.
55
This is a broker for proxies for and copies of objects. It provides a
56
translucent interface layer to those proxies.
58
The protocol is not opaque, because it provides objects which
59
represent the remote proxies and require no context (server
60
references, IDs) to operate on.
62
It is not transparent because it does I{not} attempt to make remote
63
objects behave identically, or even similiarly, to local objects.
64
Method calls are invoked asynchronously, and specific rules are
65
applied when serializing arguments.
67
@author: U{Glyph Lefkowitz<mailto:glyph@twistedmatrix.com>}
70
__version__ = "$Revision: 1.157 $"[11:-2]
75
import cStringIO as StringIO
49
from twisted.python import log, defer, failure
50
from twisted.protocols import protocol
51
from twisted.internet import tcp
84
from twisted.python import log, failure
85
from twisted.internet import reactor, defer, protocol, error
52
86
from twisted.cred import authorizer, service, perspective, identity
87
from twisted.cred.portal import Portal
53
88
from twisted.persisted import styles
54
from twisted.manhole import coil
89
from twisted.python.components import Interface, registerAdapter
92
from twisted.spread.interfaces import IJellyable, IUnjellyable
93
from jelly import jelly, unjelly, globalSecurity
60
96
# Tightly coupled sibling import
61
97
from flavors import Serializable
62
98
from flavors import Referenceable
63
from flavors import Root
99
from flavors import Root, IPBRoot
64
100
from flavors import ViewPoint
65
101
from flavors import Viewable
66
102
from flavors import Copyable
67
103
from flavors import Cacheable
68
104
from flavors import RemoteCopy
69
105
from flavors import RemoteCache
106
from flavors import RemoteCacheObserver
70
107
from flavors import copyTags
71
from flavors import setCopierForClass
108
from flavors import setCopierForClass, setUnjellyableForClass
109
from flavors import setFactoryForClass
72
110
from flavors import setCopierForClassTree
112
MAX_BROKER_REFS = 1024
341
441
return self.refcount
344
class _NetJellier(jelly._Jellier):
345
"""A Jellier for pb, serializing all serializable flavors.
348
def __init__(self, broker):
349
"""initialize me for a single request.
352
jelly._Jellier.__init__(self, broker.localSecurity, None)
355
def _jelly_instance(self, instance):
356
"""(internal) replacement method
359
assert isinstance(instance, Serializable),\
360
'non-serializable %s (%s) for: %s %s %s' % (
361
str(instance.__class__),
363
str(self.broker.jellyMethod),
364
str(self.broker.jellyArgs),
365
str(self.broker.jellyKw))
367
sxp = self._prepare(instance)
368
tup = instance.remoteSerialize(self.broker)
370
return self._preserve(instance, sxp)
372
444
class _RemoteCacheDummy:
376
class _NetUnjellier(jelly._Unjellier):
377
"""An unjellier for PB.
379
This unserializes the various Serializable flavours in PB.
382
def __init__(self, broker):
383
jelly._Unjellier.__init__(self, broker.localSecurity, None)
386
def _unjelly_copy(self, rest):
387
"""Unserialize a Copyable.
390
inst = copyTags[rest[0]]()
391
inst.setCopyableState(self._unjelly(rest[1]))
392
self.postCallbacks.append(inst.postUnjelly)
395
def _unjelly_cache(self, rest):
398
cNotProxy = _RemoteCacheDummy() #copyTags[rest[1]]()
399
cNotProxy.broker = self.broker
400
cNotProxy.luid = luid
401
cNotProxy.__class__ = copyTags[rest[1]]
402
cProxy = _RemoteCacheDummy() # (self.broker, cNotProxy, luid)
403
cProxy.__class__ = cNotProxy.__class__
404
cProxy.__dict__ = cNotProxy.__dict__
405
init = getattr(cProxy, "__init__", None)
408
cProxy.setCopyableState(self._unjelly(rest[2]))
409
# Might have changed due to setCopyableState method; we'll assume that
410
# it's bad form to do so afterwards.
411
cNotProxy.__dict__ = cProxy.__dict__
412
# chomp, chomp -- some existing code uses "self.__dict__ =", some uses
413
# "__dict__.update". This is here in order to handle both cases.
414
cNotProxy.broker = self.broker
415
cNotProxy.luid = luid
416
# Must be done in this order otherwise __hash__ isn't right!
417
self.broker.cacheLocally(luid, cNotProxy)
418
self.postCallbacks.append(cProxy.postUnjelly)
421
def _unjelly_cached(self, rest):
423
cNotProxy = self.broker.cachedLocallyAs(luid)
424
cProxy = _RemoteCacheDummy()
425
cProxy.__class__ = cNotProxy.__class__
426
cProxy.__dict__ = cNotProxy.__dict__
429
def _unjelly_lcache(self, rest):
431
obj = self.broker.remotelyCachedForLUID(luid)
434
def _unjelly_remote(self, rest):
435
obj = RemoteReference(self.broker.unserializingPerspective, self.broker, rest[0], 1)
438
def _unjelly_local(self, rest):
439
obj = self.broker.localObjectForID(rest[0])
452
class CopyableFailure(failure.Failure, Copyable):
454
A L{flavors.RemoteCopy} and L{flavors.Copyable} version of
455
L{twisted.python.failure.Failure} for serialization.
460
def getStateToCopy(self):
461
#state = self.__getstate__()
462
state = self.__dict__.copy()
466
if isinstance(self.value, failure.Failure):
467
state['value'] = failure2Copyable(self.value, self.unsafeTracebacks)
469
state['value'] = str(self.value) # Exception instance
470
state['type'] = str(self.type) # Exception class
471
if self.unsafeTracebacks:
472
io = StringIO.StringIO()
473
self.printTraceback(io)
474
state['traceback'] = io.getvalue()
476
state['traceback'] = 'Traceback unavailable\n'
479
class CopiedFailure(RemoteCopy, failure.Failure):
480
def printTraceback(self, file=None):
481
if not file: file = log.logfile
482
file.write("Traceback from remote host -- ")
483
file.write(self.traceback)
485
printBriefTraceback = printTraceback
486
printDetailedTraceback = printTraceback
488
setUnjellyableForClass(CopyableFailure, CopiedFailure)
490
def failure2Copyable(fail, unsafeTracebacks=0):
491
f = CopyableFailure()
492
f.__dict__ = fail.__dict__
493
f.unsafeTracebacks = unsafeTracebacks
443
496
class Broker(banana.Banana):
444
497
"""I am a broker for objects.
451
banana.Banana.__init__(self)
504
def __init__(self, isClient=1, security=globalSecurity):
505
banana.Banana.__init__(self, isClient)
452
506
self.disconnected = 0
453
507
self.disconnects = []
454
508
self.failures = []
455
509
self.connects = []
456
510
self.localObjects = {}
511
self.security = security
512
self.pageProducers = []
513
self.currentRequestID = 0
514
self.currentLocalID = 0
516
# PUID: process unique ID; return value of id() function. type "int".
517
# LUID: locally unique ID; an ID unique to an object mapped over this
518
# connection. type "int"
519
# GUID: (not used yet) globally unique ID; an ID for an object which
520
# may be on a redirected or meta server. Type as yet undecided.
521
# Dictionary mapping LUIDs to local objects.
522
# set above to allow root object to be assigned before connection is made
523
# self.localObjects = {}
524
# Dictionary mapping PUIDs to LUIDs.
526
# Dictionary mapping LUIDs to local (remotely cached) objects. Remotely
527
# cached means that they're objects which originate here, and were
529
self.remotelyCachedObjects = {}
530
# Dictionary mapping PUIDs to (cached) LUIDs
531
self.remotelyCachedLUIDs = {}
532
# Dictionary mapping (remote) LUIDs to (locally cached) objects.
533
self.locallyCachedObjects = {}
534
self.waitingForAnswers = {}
536
def resumeProducing(self):
537
"""Called when the consumer attached to me runs out of buffer.
539
# Go backwards over the list so we can remove indexes from it as we go
540
for pageridx in xrange(len(self.pageProducers)-1, -1, -1):
541
pager = self.pageProducers[pageridx]
543
if not pager.stillPaging():
544
del self.pageProducers[pageridx]
545
if not self.pageProducers:
546
self.transport.unregisterProducer()
548
# Streaming producer methods; not necessary to implement.
549
def pauseProducing(self):
552
def stopProducing(self):
555
def registerPageProducer(self, pager):
556
self.pageProducers.append(pager)
557
if len(self.pageProducers) == 1:
558
self.transport.registerProducer(self, 0)
458
560
def expressionReceived(self, sexp):
459
561
"""Evaluate an expression as it's received.
1026
1190
if not self.term:
1028
1192
del self.broker
1029
self.deferred.armAndErrback("connection lost")
1193
self.deferred.errback(error.ConnectionLost())
1031
1197
def connectionMade(self):
1032
1198
assert not self.term, "How did this get called?"
1033
1199
x = self.broker.remoteForName("root")
1034
1200
del self.broker
1036
self.deferred.armAndCallback(x)
1202
self.deferred.callback(x)
1038
1205
def connectionFailed(self):
1039
1206
if not self.term:
1041
1208
del self.broker
1042
self.deferred.armAndErrback("connection failed")
1209
self.deferred.errback(error.ConnectError(string="Connection failed"))
1213
class BrokerClientFactory(protocol.ClientFactory):
1215
unsafeTracebacks = 0
1217
def __init__(self, protocol):
1218
warnings.warn("This is deprecated. Use PBClientFactory.", DeprecationWarning, 2)
1219
if not isinstance(protocol,Broker): raise TypeError, "protocol is not an instance of Broker"
1220
self.protocol = protocol
1222
def buildProtocol(self, addr):
1223
return self.protocol
1225
def clientConnectionFailed(self, connector, reason):
1226
self.protocol.connectionFailed()
1228
def clientConnectionMade(self, protocol):
1232
def getObjectRetriever():
1235
Get a factory which retreives a root object from its client
1237
@returns: A pair: A ClientFactory and a Deferred which will be passed a
1238
remote reference to the root object of a PB server.x
1240
warnings.warn("This is deprecated. Use PBClientFactory.", DeprecationWarning, 2)
1241
d = defer.Deferred()
1243
bf = BrokerClientFactory(b)
1244
_ObjectRetrieval(b, d)
1044
1248
def getObjectAt(host, port, timeout=None):
1045
"""Establishes a PB connection and returns with a RemoteReference.
1049
host: the host to connect to
1051
port: the port number to connect to
1053
timeout (optional): a value in milliseconds to wait before failing by
1058
A Deferred which will be passed a remote reference to the root object of
1061
d = defer.Deferred()
1063
_ObjectRetrieval(b, d)
1064
tcp.Client(host, port, b, timeout)
1249
"""DEPRECATED. Establishes a PB connection and returns with a L{RemoteReference}.
1251
@param host: the host to connect to
1253
@param port: the port number to connect to
1255
@param timeout: a value in milliseconds to wait before failing by
1258
@returns: A Deferred which will be passed a remote reference to the
1259
root object of a PB server.x
1261
warnings.warn("This is deprecated. Use PBClientFactory.", DeprecationWarning, 2)
1262
bf = PBClientFactory()
1264
# every time you use this, God kills a kitten
1265
reactor.connectUNIX(port, bf, timeout)
1267
reactor.connectTCP(host, port, bf, timeout)
1268
return bf.getRootObject()
1270
def getObjectAtSSL(host, port, timeout=None, contextFactory=None):
1271
"""DEPRECATED. Establishes a PB connection over SSL and returns with a RemoteReference.
1273
@param host: the host to connect to
1275
@param port: the port number to connect to
1277
@param timeout: a value in milliseconds to wait before failing by
1280
@param contextFactory: A factory object for producing SSL.Context
1283
@returns: A Deferred which will be passed a remote reference to the
1284
root object of a PB server.
1286
warnings.warn("This is deprecated. Use PBClientFactory.", DeprecationWarning, 2)
1287
bf = PBClientFactory()
1288
if contextFactory is None:
1289
from twisted.internet import ssl
1290
contextFactory = ssl.ClientContextFactory()
1291
reactor.connectSSL(host, port, bf, contextFactory, timeout)
1292
return bf.getRootObject()
1067
1294
def connect(host, port, username, password, serviceName,
1068
1295
perspectiveName=None, client=None, timeout=None):
1069
"""Connects and authenticates, then retrieves a PB service.
1296
"""DEPRECATED. Connects and authenticates, then retrieves a PB service.
1071
1298
Required arguments:
1072
host -- the host the service is running on
1073
port -- the port on the host to connect to
1074
username -- the name you will be identified as to the authorizer
1075
password -- the password for this username
1076
serviceName -- name of the service to request
1299
- host -- the host the service is running on
1300
- port -- the port on the host to connect to
1301
- username -- the name you will be identified as to the authorizer
1302
- password -- the password for this username
1303
- serviceName -- name of the service to request
1078
1305
Optional (keyword) arguments:
1079
perspectiveName -- the name of the perspective to request, if
1306
- perspectiveName -- the name of the perspective to request, if
1080
1307
different than the username
1081
client -- XXX the "reference" argument to
1308
- client -- XXX the \"reference\" argument to
1082
1309
perspective.Perspective.attached
1083
timeout -- see twisted.internet.tcp.Client
1310
- timeout -- see twisted.internet.tcp.Client
1312
@returns: A Deferred instance that gets a callback when the final
1313
Perspective is connected, and an errback when an error
1314
occurs at any stage of connecting.
1085
d = defer.Deferred()
1086
getObjectAt(host,port,timeout).addCallbacks(
1087
_connGotRoot, d.armAndErrback,
1088
callbackArgs=[d, client, serviceName,
1089
username, password, perspectiveName])
1316
warnings.warn("This is deprecated. Use PBClientFactory.", DeprecationWarning, 2)
1319
bf = PBClientFactory()
1321
# every time you use this, God kills a kitten
1322
reactor.connectUNIX(port, bf, timeout)
1324
reactor.connectTCP(host, port, bf, timeout)
1325
return bf.getPerspective(username, password, serviceName, perspectiveName, client)
1092
1327
def _connGotRoot(root, d, client, serviceName,
1093
1328
username, password, perspectiveName):
1094
logIn(root, client, serviceName, username, password, perspectiveName).armAndChain(d)
1329
warnings.warn("This is deprecated. Use PBClientFactory.", DeprecationWarning, 2)
1330
logIn(root, client, serviceName, username, password, perspectiveName).chainDeferred(d)
1096
1332
def authIdentity(authServRef, username, password):
1097
"""Return a Deferred which will do the challenge-response dance and
1333
"""DEPRECATED. Return a Deferred which will do the challenge-response dance and
1098
1334
return a remote Identity reference.
1336
warnings.warn("This is deprecated. Use PBClientFactory.", DeprecationWarning, 2)
1100
1337
d = defer.Deferred()
1101
1338
authServRef.callRemote('username', username).addCallbacks(
1102
_cbRespondToChallenge, d.armAndErrback,
1339
_cbRespondToChallenge, d.errback,
1103
1340
callbackArgs=(password,d))
1106
1343
def _cbRespondToChallenge((challenge, challenger), password, d):
1344
warnings.warn("This is deprecated. Use PBClientFactory.", DeprecationWarning, 2)
1107
1345
challenger.callRemote("respond", identity.respond(challenge, password)).addCallbacks(
1108
d.armAndCallback, d.armAndErrback)
1346
d.callback, d.errback)
1110
1348
def logIn(authServRef, client, service, username, password, perspectiveName=None):
1111
"""I return a Deferred which will be called back with a Perspective.
1349
"""DEPRECATED. I return a Deferred which will be called back with a Perspective.
1351
warnings.warn("This is deprecated. Use PBClientFactory.", DeprecationWarning, 2)
1113
1352
d = defer.Deferred()
1114
1353
authServRef.callRemote('username', username).addCallbacks(
1115
_cbLogInRespond, d.armAndErrback,
1354
_cbLogInRespond, d.errback,
1116
1355
callbackArgs=(d, client, service, password,
1117
1356
perspectiveName or username))
1120
1359
def _cbLogInRespond((challenge, challenger), d, client, service, password, perspectiveName):
1360
warnings.warn("This is deprecated. Use PBClientFactory.", DeprecationWarning, 2)
1121
1361
challenger.callRemote('respond',
1122
1362
identity.respond(challenge, password)).addCallbacks(
1123
_cbLogInResponded, d.armAndErrback,
1363
_cbLogInResponded, d.errback,
1124
1364
callbackArgs=(d, client, service, perspectiveName))
1126
1366
def _cbLogInResponded(identity, d, client, serviceName, perspectiveName):
1367
warnings.warn("This is deprecated. Use PBClientFactory.", DeprecationWarning, 2)
1128
identity.callRemote("attach", serviceName, perspectiveName, client).armAndChain(d)
1369
identity.callRemote("attach", serviceName, perspectiveName, client).chainDeferred(d)
1130
d.armAndErrback("invalid username or password")
1371
from twisted import cred
1372
d.errback(cred.error.Unauthorized("invalid username or password"))
1374
class IdentityConnector:
1377
I support connecting to multiple Perspective Broker services that are
1380
def __init__(self, host, port, identityName, password):
1382
@type host: C{string}
1383
@param host: The host to connect to or the PB server.
1384
If this is C{"unix"}, then a UNIX socket
1385
will be used rather than a TCP socket.
1386
@type port: C{integer}
1387
@param port: The port to connect to for the PB server.
1388
@type identityName: C{string}
1389
@param identityName: The name of the identity to use to
1390
autheticate with the PB server.
1391
@type password: C{string}
1392
@param password: The password to use to autheticate with
1395
warnings.warn("This is deprecated. Use PBClientFactory.", DeprecationWarning, 2)
1398
self.identityName = identityName
1399
self.password = password
1400
self._identityWrapper = None
1401
self._connectDeferreds = []
1404
def _cbGotAuthRoot(self, authroot):
1405
authIdentity(authroot, self.identityName,
1406
self.password).addCallbacks(
1407
self._cbGotIdentity, self._ebGotIdentity)
1409
def _cbGotIdentity(self, i):
1410
self._identityWrapper = i
1412
for d in self._connectDeferreds:
1414
self._connectDeferreds[:] = []
1416
from twisted import cred
1417
e = cred.error.Unauthorized("invalid username or password")
1418
self._ebGotIdentity(e)
1420
def _ebGotIdentity(self, e):
1422
for d in self._connectDeferreds:
1424
self._connectDeferreds[:] = []
1426
def requestLogin(self):
1428
Attempt to authenticate about the PB server, but don't
1429
request any services, yet.
1431
@returns: L{IdentityWrapper}
1432
@rtype: L{twisted.internet.defer.Deferred}
1434
if not self._identityWrapper:
1435
d = defer.Deferred()
1436
self._connectDeferreds.append(d)
1437
if not self._requested:
1439
getObjectAt(self.host, self.port).addCallbacks(
1440
self._cbGotAuthRoot, self._ebGotIdentity)
1443
return defer.succeed(self._identityWrapper)
1445
def requestService(self, serviceName, perspectiveName=None,
1448
Request a perspective on the specified service. This will
1449
authenticate against the server as well if L{requestLogin}
1450
hasn't already been called.
1452
@type serviceName: C{string}
1453
@param serviceName: The name of the service to obtain
1455
@type perspectiveName: C{string}
1456
@param perspectiveName: If specified, the name of the
1457
perspective to obtain. Otherwise,
1458
default to the name of the identity.
1459
@param client: The client object to attach to
1462
@rtype: L{twisted.internet.defer.Deferred}
1463
@return: A deferred which will receive a callback
1464
with the perspective.
1466
return self.requestLogin().addCallback(
1467
lambda i, self=self: i.callRemote("attach",
1472
def disconnect(self):
1473
"""Lose my connection to the server.
1475
Useful to free up resources if you've completed requestLogin but
1476
then change your mind.
1478
if not self._identityWrapper:
1481
self._identityWrapper.broker.transport.loseConnection()
1484
# this is the new shiny API you should be using:
1488
from twisted.cred.credentials import ICredentials, IUsernameHashedPassword
1490
def respond(challenge, password):
1491
"""Respond to a challenge.
1493
This is useful for challenge/response authentication.
1497
hashedPassword = m.digest()
1499
m.update(hashedPassword)
1501
doubleHashedPassword = m.digest()
1502
return doubleHashedPassword
1505
"""I return some random data."""
1507
for x in range(random.randrange(15,25)):
1508
crap = crap + chr(random.randint(65,90))
1509
crap = md5.new(crap).digest()
1513
class PBClientFactory(protocol.ClientFactory):
1514
"""Client factory for PB brokers.
1516
As with all client factories, use with reactor.connectTCP/SSL/etc..
1517
getPerspective and getRootObject can be called either before or
1522
unsafeTracebacks = 0
1528
self.rootObjectRequests = [] # list of deferred
1532
def _failAll(self, reason):
1533
deferreds = self.rootObjectRequests
1538
def clientConnectionFailed(self, connector, reason):
1539
self._failAll(reason)
1541
def clientConnectionLost(self, connector, reason, reconnecting=0):
1542
"""Reconnecting subclasses should call with reconnecting=1."""
1544
# any pending requests will go to next connection attempt
1545
# so we don't fail them.
1549
self._failAll(reason)
1551
def clientConnectionMade(self, broker):
1552
self._broker = broker
1553
self._root = broker.remoteForName("root")
1554
ds = self.rootObjectRequests
1555
self.rootObjectRequests = []
1557
d.callback(self._root)
1559
def getRootObject(self):
1560
"""Get root object of remote PB server.
1562
@return Deferred of the root object.
1564
if self._broker and not self._broker.disconnected:
1565
return defer.succeed(self._root)
1566
d = defer.Deferred()
1567
self.rootObjectRequests.append(d)
1570
def getPerspective(self, username, password, serviceName,
1571
perspectiveName=None, client=None):
1572
"""Get perspective from remote PB server.
1574
New systems should use login() instead.
1576
@return Deferred of RemoteReference to the perspective.
1578
warnings.warn("Update your backend to use PBServerFactory, and then use login().",
1579
DeprecationWarning, 2)
1580
if perspectiveName == None:
1581
perspectiveName = username
1582
d = self.getRootObject()
1583
d.addCallback(self._cbAuthIdentity, username, password)
1584
d.addCallback(self._cbGetPerspective, serviceName, perspectiveName, client)
1587
def _cbAuthIdentity(self, authServRef, username, password):
1588
return authServRef.callRemote('username', username).addCallback(
1589
self._cbRespondToChallenge, password)
1591
def _cbRespondToChallenge(self, (challenge, challenger), password):
1592
return challenger.callRemote("respond", respond(challenge, password))
1594
def _cbGetPerspective(self, identityWrapper, serviceName, perspectiveName, client):
1595
return identityWrapper.callRemote(
1596
"attach", serviceName, perspectiveName, client)
1598
def disconnect(self):
1599
"""If the factory is connected, close the connection.
1601
Note that if you set up the factory to reconnect, you will need to
1602
implement extra logic to prevent automatic reconnection after this
1606
self._broker.transport.loseConnection()
1608
def _cbSendUsername(self, root, username, password, client):
1609
return root.callRemote("login", username).addCallback(
1610
self._cbResponse, password, client)
1612
def _cbResponse(self, (challenge, challenger), password, client):
1613
return challenger.callRemote("respond", respond(challenge, password), client)
1615
def login(self, credentials, client=None):
1616
"""Login and get perspective from remote PB server.
1618
Currently only credentials implementing IUsernamePassword are
1621
@return Deferred of RemoteReference to the perspective.
1623
d = self.getRootObject()
1624
d.addCallback(self._cbSendUsername, credentials.username, credentials.password, client)
1628
class PBServerFactory(protocol.ServerFactory):
1629
"""Server factory for perspective broker.
1631
Login is done using a Portal object, whose realm is expected to return
1632
avatars implementing IPerspective. The credential checkers in the portal
1633
should accept IUsernameHashedPassword or IUsernameMD5Password.
1635
Alternatively, any object implementing or adaptable to IPBRoot can
1636
be used instead of a portal to provide the root object of the PB
1640
unsafeTracebacks = 0
1642
# object broker factory
1645
def __init__(self, root, unsafeTracebacks=False):
1646
self.root = IPBRoot(root)
1647
self.unsafeTracebacks = unsafeTracebacks
1649
def buildProtocol(self, addr):
1650
"""Return a Broker attached to me (as the service provider).
1652
proto = self.protocol(0)
1653
proto.factory = self
1654
proto.setNameForLocal("root", self.root.rootObject(proto))
1657
def clientConnectionMade(self, protocol):
1661
class IUsernameMD5Password(ICredentials):
1662
"""I encapsulate a username and a hashed password.
1664
This credential is used for username/password over
1665
PB. CredentialCheckers which check this kind of credential must
1666
store the passwords in plaintext form or as a MD5 digest.
1668
@type username: C{str} or C{Deferred}
1669
@ivar username: The username associated with these credentials.
1672
def checkPassword(self, password):
1673
"""Validate these credentials against the correct password.
1675
@param password: The correct, plaintext password against which to
1678
@return: a deferred which becomes, or a boolean indicating if the
1682
def checkMD5Password(self, password):
1683
"""Validate these credentials against the correct MD5 digest of password.
1685
@param password: The correct, plaintext password against which to
1688
@return: a deferred which becomes, or a boolean indicating if the
1696
"""Root object, used to login to portal."""
1698
__implements__ = IPBRoot,
1700
def __init__(self, portal):
1701
self.portal = portal
1703
def rootObject(self, broker):
1704
return _PortalWrapper(self.portal, broker)
1706
registerAdapter(_PortalRoot, Portal, IPBRoot)
1709
class _PortalWrapper(Referenceable):
1710
"""Root Referenceable object, used to login to portal."""
1712
def __init__(self, portal, broker):
1713
self.portal = portal
1714
self.broker = broker
1716
def remote_login(self, username):
1717
"""Start of username/password login."""
1719
return c, _PortalAuthChallenger(self, username, c)
1722
class _PortalAuthChallenger(Referenceable):
1723
"""Called with response to password challenge."""
1725
__implements__ = (Referenceable.__implements__, IUsernameHashedPassword, IUsernameMD5Password)
1727
def __init__(self, portalWrapper, username, challenge):
1728
self.portalWrapper = portalWrapper
1729
self.username = username
1730
self.challenge = challenge
1732
def remote_respond(self, response, mind):
1733
self.response = response
1734
d = self.portalWrapper.portal.login(self, mind, IPerspective)
1735
d.addCallback(self._loggedIn)
1738
def _loggedIn(self, (interface, perspective, logout)):
1739
self.portalWrapper.broker.notifyOnDisconnect(logout)
1740
return AsReferenceable(perspective, "perspective")
1742
# IUsernameHashedPassword:
1743
def checkPassword(self, password):
1744
return self.checkMD5Password(md5.md5(password).digest())
1746
# IUsernameMD5Password
1747
def checkMD5Password(self, md5Password):
1749
md.update(md5Password)
1750
md.update(self.challenge)
1751
correct = md.digest()
1752
return self.response == correct