~certify-web-dev/twisted/certify-trunk

« back to all changes in this revision

Viewing changes to twisted/spread/pb.py

  • Committer: Bazaar Package Importer
  • Author(s): Matthias Klose
  • Date: 2004-06-21 22:01:11 UTC
  • mto: (2.2.3 sid)
  • mto: This revision was merged to the branch mainline in revision 3.
  • Revision ID: james.westby@ubuntu.com-20040621220111-vkf909euqnyrp3nr
Tags: upstream-1.3.0
ImportĀ upstreamĀ versionĀ 1.3.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
 
 
1
# -*- test-case-name: twisted.test.test_pb -*-
 
2
#
2
3
# Twisted, the Framework of Your Internet
3
4
# Copyright (C) 2001 Matthew W. Lefkowitz
4
5
#
18
19
"""
19
20
Perspective Broker
20
21
 
21
 
    \"This isn\'t a professional opinion, but it's probably got enough
22
 
    internet to kill you.\" --glyph
23
 
 
24
 
 Introduction
25
 
 
26
 
  This is a broker for proxies for and copies of objects.  It provides a
27
 
  translucent interface layer to those proxies.
28
 
 
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.
32
 
 
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.
37
 
 
 
22
\"This isn\'t a professional opinion, but it's probably got enough
 
23
internet to kill you.\" --glyph
 
24
 
 
25
Stability: semi-stable
 
26
 
 
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
 
31
existing protocol.
 
32
 
 
33
 
 
34
Important Changes
 
35
=================
 
36
 
 
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().
 
42
 
 
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.
 
47
 
 
48
The Perspective class is now deprecated, and has been replaced with
 
49
Avatar, which does not rely on the old cred APIs.
 
50
 
 
51
 
 
52
Introduction
 
53
============
 
54
 
 
55
This is a broker for proxies for and copies of objects.  It provides a
 
56
translucent interface layer to those proxies.
 
57
 
 
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.
 
61
 
 
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.
 
66
 
 
67
@author: U{Glyph Lefkowitz<mailto:glyph@twistedmatrix.com>}
38
68
"""
39
69
 
 
70
__version__ = "$Revision: 1.157 $"[11:-2]
 
71
 
 
72
 
40
73
# System Imports
41
 
import traceback
42
 
import cStringIO
43
 
import copy
44
 
import string
 
74
try:
 
75
    import cStringIO as StringIO
 
76
except ImportError:
 
77
    import StringIO
 
78
 
45
79
import sys
46
80
import types
 
81
import warnings
47
82
 
48
83
# Twisted Imports
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
55
90
 
56
91
# Sibling Imports
57
 
import jelly
 
92
from twisted.spread.interfaces import IJellyable, IUnjellyable
 
93
from jelly import jelly, unjelly, globalSecurity
58
94
import banana
59
95
 
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
73
111
 
 
112
MAX_BROKER_REFS = 1024
 
113
 
74
114
portno = 8787
75
115
 
76
116
 
79
119
    This error is raised when an invalid protocol statement is received.
80
120
    """
81
121
 
 
122
class DeadReferenceError(ProtocolError):
 
123
    """
 
124
    This error is raised when a method is called on a dead reference (one whose
 
125
    broker has been disconnected).
 
126
    """
 
127
 
82
128
class Error(Exception):
83
129
    """
84
130
    This error can be raised to generate known error conditions.
89
135
    sent.
90
136
    """
91
137
 
92
 
def print_excFullStack(file=None):
93
 
    """Print exception traceback with the full stack.
94
 
 
95
 
    This is in contrast to traceback.print_exc which only prints
96
 
    the traceback between the frame where the error occoured and
97
 
    the frame where the exception was caught, but not the frames
98
 
    leading up to that one.
99
 
 
100
 
    The need for this function arises from the fact that several PB
101
 
    classes have the peculiar habit of discarding exceptions with
102
 
    bareword \"except:\"s.  This premature exception catching means
103
 
    tracebacks generated here don't tend to show what called upon
104
 
    the PB object.
105
 
    """
106
 
    (eType, eVal, tb) = sys.exc_info()
107
 
    s = (["Traceback (most recent call last):\n"]
108
 
         + traceback.format_stack(tb.tb_frame.f_back)
109
 
         + ["--- <exception caught here> ---\n"]
110
 
         + traceback.format_tb(tb)
111
 
         + traceback.format_exception_only(eType, eVal))
112
 
    del tb
113
 
 
114
 
    if not file:
115
 
        file = sys.stderr
116
 
    file.write(string.join(s,''))
117
 
 
118
 
 
119
138
class RemoteMethod:
120
139
    """This is a translucent reference to a remote message.
121
140
    """
122
141
    def __init__(self, obj, name):
123
 
        """Initialize with a RemoteReference and the name of this message.
 
142
        """Initialize with a L{RemoteReference} and the name of this message.
124
143
        """
125
144
        self.obj = obj
126
145
        self.name = name
143
162
    consectetur, adipisci velit...
144
163
    """
145
164
 
146
 
PB_CONNECTION_LOST = 'Connection Lost'
 
165
class PBConnectionLost(Exception):
 
166
    pass
147
167
 
148
168
def printTraceback(tb):
149
169
    """Print a traceback (string) to the standard log.
152
172
    log.msg('Perspective Broker Traceback:' )
153
173
    log.msg(tb)
154
174
 
155
 
class Perspective(perspective.Perspective):
156
 
    """A perspective on a service.
157
 
 
 
175
class IPerspective(Interface):
 
176
    """
158
177
    per*spec*tive, n. : The relationship of aspects of a subject to each
159
178
    other and to a whole: 'a perspective of history'; 'a need to view
160
179
    the problem in the proper perspective'.
161
180
 
162
 
    A service represents a collection of state, plus a collection of
163
 
    perspectives.  Perspectives are the way that networked clients have
164
 
    a 'view' onto an object, or collection of objects on the server.
165
 
 
166
 
    Although you may have a service onto which there is only one
167
 
    perspective, the common case is that a Perspective will be
168
 
    analagous to (or the same as) a "user"; if you are creating a
169
 
    PB-enabled service, your User (or equivalent) class should subclass
170
 
    Perspective.
171
 
 
172
 
    Initially, a peer requesting a perspective will receive only a
173
 
    RemoteReference to a Perspective.  When a method is called on
174
 
    that RemoteReference, it will translate to a method on the remote
175
 
    perspective named 'perspective_methodname'.  (For more information
176
 
    on invoking methods on other objects, see ViewPoint.)
177
 
    """
 
181
    This is a Perspective Broker-specific wrapper for an avatar. That
 
182
    is to say, a PB-published view on to the business logic for the
 
183
    system's concept of a 'user'.
 
184
 
 
185
    The concept of attached/detached is no longer implemented by the
 
186
    framework. The realm is expected to implement such semantics if
 
187
    needed.
 
188
    """
 
189
 
 
190
    def perspectiveMessageReceived(self, broker, message, args, kwargs):
 
191
        """
 
192
        This method is called when a network message is received.
 
193
 
 
194
        @arg broker: The Perspective Broker.
 
195
 
 
196
        @type message: str
 
197
        @arg message: The name of the method called by the other end.
 
198
 
 
199
        @type args: list in jelly format
 
200
        @arg args: The arguments that were passed by the other end. It
 
201
                   is recommend that you use the `unserialize' method of the
 
202
                   broker to decode this.
 
203
 
 
204
        @type kwargs: dict in jelly format
 
205
        @arg kwargs: The keyword arguments that were passed by the
 
206
                     other end.  It is recommended that you use the
 
207
                     `unserialize' method of the broker to decode this.
 
208
 
 
209
        @rtype: A jelly list.
 
210
        @return: It is recommended that you use the `serialize' method
 
211
                 of the broker on whatever object you need to return to
 
212
                 generate the return value.
 
213
        """
 
214
 
 
215
 
 
216
 
 
217
class Avatar:
 
218
    """A default IPerspective implementor.
 
219
 
 
220
    This class is intended to be subclassed, and a realm should return
 
221
    an instance of such a subclass when IPerspective is requested of
 
222
    it.
 
223
 
 
224
    A peer requesting a perspective will receive only a
 
225
    L{RemoteReference} to a pb.Avatar.  When a method is called on
 
226
    that L{RemoteReference}, it will translate to a method on the
 
227
    remote perspective named 'perspective_methodname'.  (For more
 
228
    information on invoking methods on other objects, see
 
229
    L{flavors.ViewPoint}.)
 
230
    """
 
231
 
 
232
    __implements__ = IPerspective
178
233
 
179
234
    def perspectiveMessageReceived(self, broker, message, args, kw):
180
235
        """This method is called when a network message is received.
181
236
 
182
237
        I will call::
183
238
 
184
 
            self.perspective_%(message)s(*broker.unserialize(args),
185
 
                                         **broker.unserialize(kw))
 
239
          |  self.perspective_%(message)s(*broker.unserialize(args),
 
240
          |                               **broker.unserialize(kw))
186
241
 
187
 
        to handle the method; subclasses of Perspective are expected to
 
242
        to handle the method; subclasses of Avatar are expected to
188
243
        implement methods of this naming convention.
189
244
        """
190
245
 
192
247
        kw = broker.unserialize(kw, self)
193
248
        method = getattr(self, "perspective_%s" % message)
194
249
        try:
195
 
            state = apply(method, args, kw)
 
250
            state = method(*args, **kw)
196
251
        except TypeError:
197
 
            print ("%s didn't accept %s and %s" % (method, args, kw))
 
252
            log.msg("%s didn't accept %s and %s" % (method, args, kw))
198
253
            raise
199
254
        return broker.serialize(state, self, method, args, kw)
200
255
 
201
256
 
 
257
class Perspective(perspective.Perspective, Avatar):
 
258
    """
 
259
    This class is DEPRECATED, because it relies on old cred
 
260
    APIs. Please use L{Avatar}.
 
261
    """
 
262
 
 
263
    def brokerAttached(self, reference, identity, broker):
 
264
        """An intermediary method to override.
 
265
 
 
266
        Normally you will want to use 'attached', as described in
 
267
        L{twisted.cred.perspective.Perspective}.attached; however, this method
 
268
        serves the same purpose, and in some circumstances, you are sure that
 
269
        the protocol that objects will be attaching to your Perspective with is
 
270
        Perspective Broker, and in that case you may wish to get the Broker
 
271
        object they are connecting with, for example, to determine what host
 
272
        they are connecting from.  Bear in mind that when overriding this
 
273
        method, other, non-PB protocols will not notify you of being attached
 
274
        or detached.
 
275
        """
 
276
        warnings.warn("pb.Perspective is deprecated, please use pb.Avatar.", DeprecationWarning, 2)
 
277
        return self.attached(reference, identity)
 
278
 
 
279
    def brokerDetached(self, reference, identity, broker):
 
280
        """See L{brokerAttached}.
 
281
        """
 
282
        return self.detached(reference, identity)
 
283
 
 
284
 
202
285
class Service(service.Service):
203
286
    """A service for Perspective Broker.
204
287
 
 
288
    This class is DEPRECATED, because it relies on old cred APIs.
 
289
 
205
290
    On this Service, the result of a perspective request must be a
206
 
    pb.Perspective rather than a perspective.Perspective.
 
291
    L{pb.Perspective} rather than a L{twisted.cred.perspective.Perspective}.
207
292
    """
 
293
    perspectiveClass = Perspective
208
294
 
209
295
 
210
296
class AsReferenceable(Referenceable):
217
303
        self.remoteMessageReceived = getattr(object, messageType + "MessageReceived")
218
304
 
219
305
 
220
 
local_atom = "local"
221
 
 
222
306
class RemoteReference(Serializable, styles.Ephemeral):
223
307
    """This is a translucent reference to a remote object.
224
308
 
225
 
    I may be a reference to a ViewPoint, a Referenceable, or
226
 
    a Perspective.  From the client's perspective, it is not
227
 
    possible to tell which except by convention.
 
309
    I may be a reference to a L{flavors.ViewPoint}, a
 
310
    L{flavors.Referenceable}, or an L{IPerspective} implementor (e.g.,
 
311
    pb.Avatar).  From the client's perspective, it is not possible to
 
312
    tell which except by convention.
228
313
 
229
 
    I am a "translucent" reference because although no additional
 
314
    I am a \"translucent\" reference because although no additional
230
315
    bookkeeping overhead is given to the application programmer for
231
316
    manipulating a reference, return values are asynchronous.
232
317
 
233
 
    All attributes besides '__double_underscored__' are RemoteMethod
234
 
    instances; these act like methods which return Deferreds. However,
235
 
    this method of calling remote methods is deprecated - use callRemote
236
 
    instead.
 
318
    See also L{twisted.internet.defer}.
237
319
 
238
 
    See also twisted.python.defer.
 
320
    @ivar broker: The broker I am obtained through.
 
321
    @type broker: L{Broker}
239
322
    """
240
323
 
 
324
    __implements__ = IUnjellyable,
 
325
 
241
326
    def __init__(self, perspective, broker, luid, doRefCount):
242
327
        """(internal) Initialize me with a broker and a locally-unique ID.
243
328
 
253
338
    def notifyOnDisconnect(self, callback):
254
339
        """Register a callback to be called if our broker gets disconnected.
255
340
 
256
 
        This callback will be called with one method, this instance.
 
341
        This callback will be called with one argument, this instance.
257
342
        """
258
343
        assert callable(callback)
259
344
        self.disconnectCallbacks.append(callback)
260
 
        if len(self.disconnectCallbacks):
 
345
        if len(self.disconnectCallbacks) == 1:
261
346
            self.broker.notifyOnDisconnect(self._disconnected)
262
347
 
263
348
    def dontNotifyOnDisconnect(self, callback):
264
 
        """Register a callback to be called if our broker gets disconnected."""
 
349
        """Remove a callback that was registered with notifyOnDisconnect."""
265
350
        self.disconnectCallbacks.remove(callback)
266
351
        if not self.disconnectCallbacks:
267
352
            self.broker.dontNotifyOnDisconnect(self._disconnected)
272
357
            callback(self)
273
358
        self.disconnectCallbacks = None
274
359
 
275
 
    def remoteSerialize(self, broker):
 
360
    def jellyFor(self, jellier):
276
361
        """If I am being sent back to where I came from, serialize as a local backreference.
277
362
        """
278
 
        assert self.broker == broker, "Can't send references to brokers other than their own."
279
 
        return local_atom, self.luid
280
 
 
281
 
    def callRemote(self, name, *args, **kw):
 
363
        if jellier.invoker:
 
364
            assert self.broker == jellier.invoker, "Can't send references to brokers other than their own."
 
365
            return "local", self.luid
 
366
        else:
 
367
            return "unpersistable", "References cannot be serialized"
 
368
 
 
369
    def unjellyFor(self, unjellier, unjellyList):
 
370
        self.__init__(unjellier.invoker.unserializingPerspective, unjellier.invoker, unjellyList[1], 1)
 
371
        return self
 
372
 
 
373
    def callRemote(self, _name, *args, **kw):
282
374
        """Asynchronously invoke a remote method.
283
 
        """
284
 
        return self.broker._sendMessage('',self.perspective, self.luid, name, args, kw)
285
 
 
286
 
    def __getattr__(self, key):
287
 
        """(Deprecated) Return a RemoteMethod.
288
 
        """
289
 
        if (key[:2]=='__' and key[-2:]=='__'):
290
 
            raise AttributeError(key)
291
 
        file, lineno, func, nne = traceback.extract_stack()[-2] # caller
292
 
        log.msg("%s:%s %s calls obsolete 'transparent' RemoteMethod %s" % (file, lineno, func, key))
293
 
        return self.remoteMethod(key)
 
375
 
 
376
        @type _name:   C{string}
 
377
        @param _name:  the name of the remote method to invoke
 
378
        @param args: arguments to serialize for the remote function
 
379
        @param kw:  keyword arguments to serialize for the remote function.
 
380
        @rtype:   L{twisted.internet.defer.Deferred}
 
381
        @returns: a Deferred which will be fired when the result of
 
382
                  this remote call is received.
 
383
        """
 
384
        # note that we use '_name' instead of 'name' so the user can call
 
385
        # remote methods with 'name' as a keyword parameter, like this:
 
386
        #  ref.callRemote("getPeopleNamed", count=12, name="Bob")
 
387
 
 
388
        return self.broker._sendMessage('',self.perspective, self.luid,
 
389
                                        _name, args, kw)
294
390
 
295
391
    def remoteMethod(self, key):
296
 
        """Get a RemoteMethod for this key.
 
392
        """Get a L{RemoteMethod} for this key.
297
393
        """
298
394
        return RemoteMethod(self, key)
299
395
 
300
396
    def __cmp__(self,other):
301
 
        """Compare me [to another RemoteReference].
 
397
        """Compare me [to another L{RemoteReference}].
302
398
        """
303
399
        if isinstance(other, RemoteReference):
304
400
            if other.broker == self.broker:
316
412
        if self.doRefCount:
317
413
            self.broker.sendDecRef(self.luid)
318
414
 
319
 
 
 
415
setUnjellyableForClass("remote", RemoteReference)
320
416
 
321
417
class Local:
322
418
    """(internal) A reference to a local object.
323
419
    """
324
420
 
325
 
    def __init__(self, object):
 
421
    def __init__(self, object, perspective=None):
326
422
        """Initialize.
327
423
        """
328
424
        self.object = object
 
425
        self.perspective = perspective
329
426
        self.refcount = 1
330
427
 
 
428
    def __repr__(self):
 
429
        return "<pb.Local %r ref:%s>" % (self.object, self.refcount)
 
430
 
331
431
    def incref(self):
332
432
        """Increment and return my reference count.
333
433
        """
341
441
        return self.refcount
342
442
 
343
443
 
344
 
class _NetJellier(jelly._Jellier):
345
 
    """A Jellier for pb, serializing all serializable flavors.
346
 
    """
347
 
 
348
 
    def __init__(self, broker):
349
 
        """initialize me for a single request.
350
 
        """
351
 
 
352
 
        jelly._Jellier.__init__(self, broker.localSecurity, None)
353
 
        self.broker = broker
354
 
 
355
 
    def _jelly_instance(self, instance):
356
 
        """(internal) replacement method
357
 
        """
358
 
 
359
 
        assert isinstance(instance, Serializable),\
360
 
               'non-serializable %s (%s) for: %s %s %s' % (
361
 
            str(instance.__class__),
362
 
            str(instance),
363
 
            str(self.broker.jellyMethod),
364
 
            str(self.broker.jellyArgs),
365
 
            str(self.broker.jellyKw))
366
 
 
367
 
        sxp = self._prepare(instance)
368
 
        tup = instance.remoteSerialize(self.broker)
369
 
        map(sxp.append, tup)
370
 
        return self._preserve(instance, sxp)
371
 
 
372
444
class _RemoteCacheDummy:
373
445
    """Ignore.
374
446
    """
375
447
 
376
 
class _NetUnjellier(jelly._Unjellier):
377
 
    """An unjellier for PB.
378
 
 
379
 
    This unserializes the various Serializable flavours in PB.
380
 
    """
381
 
 
382
 
    def __init__(self, broker):
383
 
        jelly._Unjellier.__init__(self, broker.localSecurity, None)
384
 
        self.broker = broker
385
 
 
386
 
    def _unjelly_copy(self, rest):
387
 
        """Unserialize a Copyable.
388
 
        """
389
 
        global copyTags
390
 
        inst = copyTags[rest[0]]()
391
 
        inst.setCopyableState(self._unjelly(rest[1]))
392
 
        self.postCallbacks.append(inst.postUnjelly)
393
 
        return inst
394
 
 
395
 
    def _unjelly_cache(self, rest):
396
 
        global copyTags
397
 
        luid = rest[0]
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)
406
 
        if init:
407
 
            init()
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)
419
 
        return cProxy
420
 
 
421
 
    def _unjelly_cached(self, rest):
422
 
        luid = rest[0]
423
 
        cNotProxy = self.broker.cachedLocallyAs(luid)
424
 
        cProxy = _RemoteCacheDummy()
425
 
        cProxy.__class__ = cNotProxy.__class__
426
 
        cProxy.__dict__ = cNotProxy.__dict__
427
 
        return cProxy
428
 
 
429
 
    def _unjelly_lcache(self, rest):
430
 
        luid = rest[0]
431
 
        obj = self.broker.remotelyCachedForLUID(luid)
432
 
        return obj
433
 
 
434
 
    def _unjelly_remote(self, rest):
435
 
        obj = RemoteReference(self.broker.unserializingPerspective, self.broker, rest[0], 1)
436
 
        return obj
437
 
 
438
 
    def _unjelly_local(self, rest):
439
 
        obj = self.broker.localObjectForID(rest[0])
440
 
        return obj
441
 
 
 
448
##
 
449
# Failure
 
450
##
 
451
 
 
452
class CopyableFailure(failure.Failure, Copyable):
 
453
    """
 
454
    A L{flavors.RemoteCopy} and L{flavors.Copyable} version of
 
455
    L{twisted.python.failure.Failure} for serialization.
 
456
    """
 
457
 
 
458
    unsafeTracebacks = 0
 
459
 
 
460
    def getStateToCopy(self):
 
461
        #state = self.__getstate__()
 
462
        state = self.__dict__.copy()
 
463
        state['tb'] = None
 
464
        state['frames'] = []
 
465
        state['stack'] = []
 
466
        if isinstance(self.value, failure.Failure):
 
467
            state['value'] = failure2Copyable(self.value, self.unsafeTracebacks)
 
468
        else:
 
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()
 
475
        else:
 
476
            state['traceback'] = 'Traceback unavailable\n'
 
477
        return state
 
478
 
 
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)
 
484
 
 
485
    printBriefTraceback = printTraceback
 
486
    printDetailedTraceback = printTraceback
 
487
 
 
488
setUnjellyableForClass(CopyableFailure, CopiedFailure)
 
489
 
 
490
def failure2Copyable(fail, unsafeTracebacks=0):
 
491
    f = CopyableFailure()
 
492
    f.__dict__ = fail.__dict__
 
493
    f.unsafeTracebacks = unsafeTracebacks
 
494
    return f
442
495
 
443
496
class Broker(banana.Banana):
444
497
    """I am a broker for objects.
445
498
    """
446
499
 
447
 
    version = 5
 
500
    version = 6
448
501
    username = None
 
502
    factory = None
449
503
 
450
 
    def __init__(self):
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
 
515
        # Some terms:
 
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.
 
525
        self.luids = {}
 
526
        # Dictionary mapping LUIDs to local (remotely cached) objects. Remotely
 
527
        # cached means that they're objects which originate here, and were
 
528
        # copied remotely.
 
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 = {}
 
535
 
 
536
    def resumeProducing(self):
 
537
        """Called when the consumer attached to me runs out of buffer.
 
538
        """
 
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]
 
542
            pager.sendNextPage()
 
543
            if not pager.stillPaging():
 
544
                del self.pageProducers[pageridx]
 
545
        if not self.pageProducers:
 
546
            self.transport.unregisterProducer()
 
547
 
 
548
    # Streaming producer methods; not necessary to implement.
 
549
    def pauseProducing(self):
 
550
        pass
 
551
 
 
552
    def stopProducing(self):
 
553
        pass
 
554
 
 
555
    def registerPageProducer(self, pager):
 
556
        self.pageProducers.append(pager)
 
557
        if len(self.pageProducers) == 1:
 
558
            self.transport.registerProducer(self, 0)
457
559
 
458
560
    def expressionReceived(self, sexp):
459
561
        """Evaluate an expression as it's received.
463
565
            methodName = "proto_%s" % command
464
566
            method = getattr(self, methodName, None)
465
567
            if method:
466
 
                apply(method, sexp[1:])
 
568
                method(*sexp[1:])
467
569
            else:
468
570
                self.sendCall("didNotUnderstand", command)
469
571
        else:
487
589
        self.sendEncoded(exp)
488
590
 
489
591
    def proto_didNotUnderstand(self, command):
490
 
        """Respond to stock 'didNotUnderstand' message.
 
592
        """Respond to stock 'C{didNotUnderstand}' message.
491
593
 
492
594
        Log the command that was not understood and continue. (Note:
493
595
        this will probably be changed to close the connection or raise
494
596
        an exception in the future.)
495
597
        """
496
 
        log.msg("Didn't understand command:", repr(command))
 
598
        log.msg("Didn't understand command: %r" % command)
497
599
 
498
 
    def connectionMade(self):
499
 
        """Initialize.
 
600
    def connectionReady(self):
 
601
        """Initialize. Called after Banana negotiation is done.
500
602
        """
501
 
 
502
 
        # Some terms:
503
 
        #  PUID: process unique ID; return value of id() function.  type "int".
504
 
        #  LUID: locally unique ID; an ID unique to an object mapped over this
505
 
        #        connection. type "int"
506
 
        #  GUID: (not used yet) globally unique ID; an ID for an object which
507
 
        #        may be on a redirected or meta server.  Type as yet undecided.
508
 
        banana.Banana.connectionMade(self)
509
603
        self.sendCall("version", self.version)
510
 
        self.currentRequestID = 0
511
 
        self.currentLocalID = 0
512
 
        # Dictionary mapping LUIDs to local objects.
513
 
        # set above to allow root object to be assigned before connection is made
514
 
        # self.localObjects = {}
515
 
        # Dictionary mapping PUIDs to LUIDs.
516
 
        self.luids = {}
517
 
        # Dictionary mapping LUIDs to local (remotely cached) objects. Remotely
518
 
        # cached means that they're objects which originate here, and were
519
 
        # copied remotely.
520
 
        self.remotelyCachedObjects = {}
521
 
        # Dictionary mapping PUIDs to (cached) LUIDs
522
 
        self.remotelyCachedLUIDs = {}
523
 
        # Dictionary mapping (remote) LUIDs to (locally cached) objects.
524
 
        self.locallyCachedObjects = {}
525
 
        self.waitingForAnswers = {}
526
 
        security = jelly.SecurityOptions()
527
 
        self.remoteSecurity = self.localSecurity = security
528
 
        security.allowBasicTypes()
529
 
        security.allowTypes("copy", "cache", "cached", "local", "remote", "lcache")
530
 
        self.jellier = None
531
 
        self.unjellier = None
532
604
        for notifier in self.connects:
533
605
            try:
534
606
                notifier()
535
607
            except:
536
608
                log.deferr()
 
609
        self.connects = None
 
610
        if self.factory: # in tests we won't have factory
 
611
            self.factory.clientConnectionMade(self)
537
612
 
538
613
    def connectionFailed(self):
 
614
        # XXX should never get called anymore? check!
539
615
        for notifier in self.failures:
540
616
            try:
541
617
                notifier()
542
618
            except:
543
619
                log.deferr()
544
 
 
545
 
    def connectionLost(self):
 
620
        self.failures = None
 
621
 
 
622
    waitingForAnswers = None
 
623
 
 
624
    def connectionLost(self, reason):
546
625
        """The connection was lost.
547
626
        """
548
 
 
549
627
        self.disconnected = 1
550
628
        # nuke potential circular references.
551
629
        self.luids = None
552
 
        for d in self.waitingForAnswers.values():
 
630
        if self.waitingForAnswers:
 
631
            for d in self.waitingForAnswers.values():
 
632
                try:
 
633
                    d.errback(failure.Failure(PBConnectionLost(reason)))
 
634
                except:
 
635
                    log.deferr()
 
636
        # Assure all Cacheable.stoppedObserving are called
 
637
        for lobj in self.remotelyCachedObjects.values():
 
638
            cacheable = lobj.object
 
639
            perspective = lobj.perspective
553
640
            try:
554
 
                d.errback(PB_CONNECTION_LOST)
 
641
                cacheable.stoppedObserving(perspective, RemoteCacheObserver(self, cacheable, perspective))
555
642
            except:
556
 
                print_excFullStack(file=log.logfile)
557
 
        for notifier in self.disconnects:
 
643
                log.deferr()
 
644
        # Loop on a copy to prevent notifiers to mixup
 
645
        # the list by calling dontNotifyOnDisconnect
 
646
        for notifier in self.disconnects[:]:
558
647
            try:
559
648
                notifier()
560
649
            except:
561
 
                print_excFullStack(file=log.logfile)
 
650
                log.deferr()
562
651
        self.disconnects = None
563
652
        self.waitingForAnswers = None
564
653
        self.localSecurity = None
566
655
        self.remotelyCachedObjects = None
567
656
        self.remotelyCachedLUIDs = None
568
657
        self.locallyCachedObjects = None
 
658
        self.localObjects = None
569
659
 
570
660
    def notifyOnDisconnect(self, notifier):
571
661
        """Call the given callback when the Broker disconnects."""
580
670
    def notifyOnConnect(self, notifier):
581
671
        """Call the given callback when the Broker connects."""
582
672
        assert callable(notifier)
583
 
        self.connects.append(notifier)
 
673
        if self.connects is None:
 
674
            try:
 
675
                notifier()
 
676
            except:
 
677
                log.err()
 
678
        else:
 
679
            self.connects.append(notifier)
584
680
 
585
681
    def dontNotifyOnDisconnect(self, notifier):
586
682
        """Remove a callback from list of disconnect callbacks."""
593
689
        """Get a local object for a locally unique ID.
594
690
 
595
691
        I will return an object previously stored with
596
 
        self.registerReference, or None if XXX:Unfinished thought:XXX
 
692
        self.L{registerReference}, or C{None} if XXX:Unfinished thought:XXX
597
693
        """
598
694
 
599
695
        lob = self.localObjects.get(luid)
601
697
            return
602
698
        return lob.object
603
699
 
 
700
    maxBrokerRefsViolations = 0
 
701
 
604
702
    def registerReference(self, object):
605
703
        """Get an ID for a local object.
606
704
 
612
710
        puid = object.processUniqueID()
613
711
        luid = self.luids.get(puid)
614
712
        if luid is None:
 
713
            if len(self.localObjects) > MAX_BROKER_REFS:
 
714
                self.maxBrokerRefsViolations = self.maxBrokerRefsViolations + 1
 
715
                if self.maxBrokerRefsViolations > 3:
 
716
                    self.transport.loseConnection()
 
717
                    raise Error("Maximum PB reference count exceeded.  "
 
718
                                "Goodbye.")
 
719
                raise Error("Maximum PB reference count exceeded.")
 
720
 
615
721
            luid = self.newLocalID()
616
722
            self.localObjects[luid] = Local(object)
617
723
            self.luids[puid] = luid
636
742
        """
637
743
        return RemoteReference(None, self, name, 0)
638
744
 
639
 
    def cachedRemotelyAs(self, instance):
640
 
        """Returns an ID that says what this instance is cached as remotely, or None if it's not.
 
745
    def cachedRemotelyAs(self, instance, incref=0):
 
746
        """Returns an ID that says what this instance is cached as remotely, or C{None} if it's not.
641
747
        """
642
748
 
643
749
        puid = instance.processUniqueID()
644
750
        luid = self.remotelyCachedLUIDs.get(puid)
645
 
        if luid is not None:
 
751
        if (luid is not None) and (incref):
646
752
            self.remotelyCachedObjects[luid].incref()
647
753
        return luid
648
754
 
656
762
        XXX"""
657
763
        puid = instance.processUniqueID()
658
764
        luid = self.newLocalID()
 
765
        if len(self.remotelyCachedObjects) > MAX_BROKER_REFS:
 
766
            self.maxBrokerRefsViolations = self.maxBrokerRefsViolations + 1
 
767
            if self.maxBrokerRefsViolations > 3:
 
768
                self.transport.loseConnection()
 
769
                raise Error("Maximum PB cache count exceeded.  "
 
770
                            "Goodbye.")
 
771
            raise Error("Maximum PB cache count exceeded.")
 
772
 
659
773
        self.remotelyCachedLUIDs[puid] = luid
660
774
        # This table may not be necessary -- for now, it's to make sure that no
661
775
        # monkey business happens with id(instance)
662
 
        self.remotelyCachedObjects[luid] = Local(instance)
 
776
        self.remotelyCachedObjects[luid] = Local(instance, self.serializingPerspective)
663
777
        return luid
664
778
 
665
779
    def cacheLocally(self, cid, instance):
673
787
        instance = self.locallyCachedObjects[cid]
674
788
        return instance
675
789
 
676
 
    def jelly(self, object):
677
 
        return self.jellier.jelly(object)
678
 
 
679
790
    def serialize(self, object, perspective=None, method=None, args=None, kw=None):
680
791
        """Jelly an object according to the remote security rules for this broker.
681
792
        """
696
807
        # from within a getState (this causes concurrency problems anyway so
697
808
        # you really, really shouldn't do it))
698
809
 
699
 
        self.jellier = _NetJellier(self)
 
810
        # self.jellier = _NetJellier(self)
700
811
        self.serializingPerspective = perspective
701
812
        self.jellyMethod = method
702
813
        self.jellyArgs = args
703
814
        self.jellyKw = kw
704
815
        try:
705
 
            return self.jellier.jelly(object)
 
816
            return jelly(object, self.security, None, self)
706
817
        finally:
707
 
            self.jellier = None
708
818
            self.serializingPerspective = None
709
819
            self.jellyMethod = None
710
820
            self.jellyArgs = None
715
825
        """
716
826
 
717
827
        self.unserializingPerspective = perspective
718
 
        self.unjellier = _NetUnjellier(self)
719
828
        try:
720
 
            return self.unjellier.unjelly(sexp)
 
829
            return unjelly(sexp, self.security, None, self)
721
830
        finally:
722
831
            self.unserializingPerspective = None
723
 
            self.unjellier = None
724
832
 
725
833
    def newLocalID(self):
726
834
        """Generate a new LUID.
749
857
            answerRequired = kw['pbanswer']
750
858
            del kw['pbanswer']
751
859
        if self.disconnected:
752
 
            raise ProtocolError("Calling Stale Broker")
753
 
        netArgs = self.serialize(args, perspective=perspective, method=message)
754
 
        netKw = self.serialize(kw, perspective=perspective, method=message)
 
860
            raise DeadReferenceError("Calling Stale Broker")
 
861
        try:
 
862
            netArgs = self.serialize(args, perspective=perspective, method=message)
 
863
            netKw = self.serialize(kw, perspective=perspective, method=message)
 
864
        except:
 
865
            return defer.fail(failure.Failure())
755
866
        requestID = self.newRequestID()
756
867
        if answerRequired:
757
868
            rval = defer.Deferred()
782
893
            netResult = object.remoteMessageReceived(self, message, netArgs, netKw)
783
894
        except Error, e:
784
895
            if answerRequired:
785
 
                self._sendError(str(e), requestID)
 
896
                self._sendError(CopyableFailure(e), requestID)
786
897
        except:
787
898
            if answerRequired:
788
 
                io = cStringIO.StringIO()
789
 
                failure.Failure().printBriefTraceback(file=io)
790
 
                self._sendError(io.getvalue(), requestID)
791
 
                log.msg("Client Received PB Traceback:")
792
 
            else:
793
 
                log.msg("Client Ignored PB Traceback:")
 
899
                log.msg("Peer will receive following PB traceback:", isError=True)
 
900
                f = CopyableFailure()
 
901
                self._sendError(f, requestID)
794
902
            log.deferr()
795
903
        else:
796
904
            if answerRequired:
797
905
                if isinstance(netResult, defer.Deferred):
798
906
                    args = (requestID,)
799
 
                    netResult.addCallbacks(self._sendAnswer, self._sendFormattedFailure,
 
907
                    netResult.addCallbacks(self._sendAnswer, self._sendFailure,
800
908
                                           callbackArgs=args, errbackArgs=args)
801
909
                    # XXX Should this be done somewhere else?
802
 
                    netResult.arm()
803
910
                else:
804
911
                    self._sendAnswer(netResult, requestID)
805
 
 
 
912
    ##
 
913
    # success
 
914
    ##
806
915
 
807
916
    def _sendAnswer(self, netResult, requestID):
808
917
        """(internal) Send an answer to a previously sent message.
809
918
        """
810
919
        self.sendCall("answer", requestID, netResult)
811
920
 
812
 
    def _sendFormattedFailure(self, error, requestID):
813
 
        self._sendError(repr(error), requestID)
814
 
 
815
921
    def proto_answer(self, requestID, netResult):
816
922
        """(internal) Got an answer to a previously sent message.
817
923
 
819
925
        """
820
926
        d = self.waitingForAnswers[requestID]
821
927
        del self.waitingForAnswers[requestID]
822
 
        d.armAndCallback(self.unserialize(netResult))
823
 
 
824
 
    def _sendError(self, descriptiveString, requestID):
 
928
        d.callback(self.unserialize(netResult))
 
929
 
 
930
    ##
 
931
    # failure
 
932
    ##
 
933
 
 
934
    def _sendFailure(self, fail, requestID):
 
935
        """Log error and then send it."""
 
936
        log.msg("Peer will receive following PB traceback:")
 
937
        log.err(fail)
 
938
        self._sendError(fail, requestID)
 
939
 
 
940
    def _sendError(self, fail, requestID):
825
941
        """(internal) Send an error for a previously sent message.
826
942
        """
827
 
        self.sendCall("error", requestID, descriptiveString)
 
943
        if not isinstance(fail, CopyableFailure) and isinstance(fail, failure.Failure):
 
944
            fail = failure2Copyable(fail, self.factory.unsafeTracebacks)
 
945
        if isinstance(fail, CopyableFailure):
 
946
            fail.unsafeTracebacks = self.factory.unsafeTracebacks
 
947
        self.sendCall("error", requestID, self.serialize(fail))
828
948
 
829
 
    def proto_error(self, requestID, descriptiveString):
 
949
    def proto_error(self, requestID, fail):
830
950
        """(internal) Deal with an error.
831
951
        """
832
952
        d = self.waitingForAnswers[requestID]
833
953
        del self.waitingForAnswers[requestID]
834
 
        d.arm()
835
 
        d.errback(descriptiveString)
 
954
        d.errback(self.unserialize(fail))
 
955
 
 
956
    ##
 
957
    # refcounts
 
958
    ##
836
959
 
837
960
    def sendDecRef(self, objectID):
838
961
        """(internal) Send a DECREF directive.
839
962
        """
840
963
        self.sendCall("decref", objectID)
841
964
 
842
 
    def decCacheRef(self, objectID):
843
 
        """(internal) Send a DECACHE directive.
844
 
        """
845
 
        self.sendCall("decache", objectID)
846
 
 
847
965
    def proto_decref(self, objectID):
848
 
        """(internal) Decrement the refernce count of an object.
 
966
        """(internal) Decrement the reference count of an object.
849
967
 
850
968
        If the reference count is zero, it will free the reference to this
851
969
        object.
852
970
        """
853
971
        refs = self.localObjects[objectID].decref()
854
 
        # print "decref for %d #refs: %d" % (objectID, refs)
855
972
        if refs == 0:
856
973
            puid = self.localObjects[objectID].object.processUniqueID()
857
974
            del self.luids[puid]
858
975
            del self.localObjects[objectID]
859
976
 
 
977
    ##
 
978
    # caching
 
979
    ##
 
980
 
 
981
    def decCacheRef(self, objectID):
 
982
        """(internal) Send a DECACHE directive.
 
983
        """
 
984
        self.sendCall("decache", objectID)
 
985
 
860
986
    def proto_decache(self, objectID):
861
987
        """(internal) Decrement the reference count of a cached object.
862
988
 
864
990
        'uncached' directive.
865
991
        """
866
992
        refs = self.remotelyCachedObjects[objectID].decref()
867
 
        # print 'decaching: %s #refs: %s' % (objectID, refs)
 
993
        # log.msg('decaching: %s #refs: %s' % (objectID, refs))
868
994
        if refs == 0:
869
 
            puid = self.remotelyCachedObjects[objectID].object.processUniqueID()
 
995
            lobj = self.remotelyCachedObjects[objectID]
 
996
            cacheable = lobj.object
 
997
            perspective = lobj.perspective
 
998
            # TODO: force_decache needs to be able to force-invalidate a
 
999
            # cacheable reference.
 
1000
            try:
 
1001
                cacheable.stoppedObserving(perspective, RemoteCacheObserver(self, cacheable, perspective))
 
1002
            except:
 
1003
                log.deferr()
 
1004
            puid = cacheable.processUniqueID()
870
1005
            del self.remotelyCachedLUIDs[puid]
871
1006
            del self.remotelyCachedObjects[objectID]
872
1007
            self.sendCall("uncache", objectID)
874
1009
    def proto_uncache(self, objectID):
875
1010
        """(internal) Tell the client it is now OK to uncache an object.
876
1011
        """
877
 
        # print "uncaching %d" % objectID
 
1012
        # log.msg("uncaching locally %d" % objectID)
878
1013
        obj = self.locallyCachedObjects[objectID]
879
 
        def reallyDel(obj=obj):
880
 
            obj.__really_del__()
881
 
        obj.__del__ = reallyDel
 
1014
        obj.broker = None
 
1015
##         def reallyDel(obj=obj):
 
1016
##             obj.__really_del__()
 
1017
##         obj.__del__ = reallyDel
882
1018
        del self.locallyCachedObjects[objectID]
883
1019
 
884
1020
 
885
 
class BrokerFactory(protocol.Factory, styles.Versioned, coil.Configurable):
886
 
    """I am a server for object brokerage.
 
1021
class BrokerFactory(protocol.Factory, styles.Versioned):
 
1022
    """DEPRECATED, use PBServerFactory instead.
 
1023
 
 
1024
    I am a server for object brokerage.
887
1025
    """
 
1026
 
 
1027
    unsafeTracebacks = 0
888
1028
    persistenceVersion = 3
 
1029
 
889
1030
    def __init__(self, objectToBroker):
 
1031
        warnings.warn("This is deprecated. Use PBServerFactory.", DeprecationWarning, 2)
890
1032
        self.objectToBroker = objectToBroker
891
1033
 
892
 
    configTypes = {'objectToBroker': Root}
893
 
    configName = 'PB Broker Factory'
894
 
 
895
 
    def configInit(self, container, name):
896
 
        self.__init__(AuthRoot(container.app))
897
 
 
898
1034
    def config_objectToBroker(self, newObject):
899
1035
        self.objectToBroker = newObject
900
1036
 
901
 
    def getConfiguration(self):
902
 
        return {"objectToBroker": self.objectToBroker}
903
 
 
904
1037
    def upgradeToVersion2(self):
905
1038
        app = self.app
906
1039
        del self.app
909
1042
    def buildProtocol(self, addr):
910
1043
        """Return a Broker attached to me (as the service provider).
911
1044
        """
912
 
        proto = Broker()
 
1045
        proto = Broker(0)
913
1046
        proto.factory = self
914
1047
        proto.setNameForLocal("root",
915
1048
                              self.objectToBroker.rootObject(proto))
916
1049
        return proto
917
1050
 
918
 
coil.registerClass(BrokerFactory)
919
 
 
920
 
### AUTH STUFF
 
1051
    def clientConnectionMade(self, protocol):
 
1052
        pass
 
1053
 
 
1054
 
 
1055
### DEPRECATED AUTH STUFF
921
1056
 
922
1057
class AuthRoot(Root):
923
 
    """I provide AuthServs as root objects to Brokers for a BrokerFactory.
 
1058
    """DEPRECATED.
 
1059
 
 
1060
    I provide AuthServs as root objects to Brokers for a BrokerFactory.
924
1061
    """
925
1062
 
926
 
    def __init__(self, app):
927
 
        self.app = app
 
1063
    def __init__(self, auth):
 
1064
        from twisted.internet.app import Application
 
1065
        if isinstance(auth, Application):
 
1066
            auth = auth.authorizer
 
1067
        self.auth = auth
928
1068
 
929
1069
    def rootObject(self, broker):
930
 
        return AuthServ(self.app, broker)
 
1070
        return AuthServ(self.auth, broker)
931
1071
 
932
1072
class _Detacher:
933
 
    def __init__(self, perspective, remoteRef, identity):
 
1073
    """DEPRECATED."""
 
1074
 
 
1075
    def __init__(self, perspective, remoteRef, identity, broker):
934
1076
        self.perspective = perspective
935
1077
        self.remoteRef = remoteRef
936
1078
        self.identity = identity
 
1079
        self.broker = broker
937
1080
 
938
1081
    def detach(self):
939
 
        self.perspective.detached(self.remoteRef, self.identity)
 
1082
        self.perspective.brokerDetached(self.remoteRef,
 
1083
                                        self.identity,
 
1084
                                        self.broker)
940
1085
 
941
1086
class IdentityWrapper(Referenceable):
942
 
    """I delegate most functionality to a identity.Identity.
 
1087
    """DEPRECATED.
 
1088
 
 
1089
    I delegate most functionality to a L{twisted.cred.identity.Identity}.
943
1090
    """
944
1091
 
945
1092
    def __init__(self, broker, identity):
957
1104
            callbackArgs = [remoteRef])
958
1105
 
959
1106
    def _attached(self, perspective, remoteRef):
960
 
        perspective = perspective.attached(remoteRef, self.identity)
 
1107
        perspective = perspective.brokerAttached(remoteRef,
 
1108
                                                 self.identity,
 
1109
                                                 self.broker)
961
1110
        # Make sure that when connectionLost happens, this perspective
962
1111
        # will be tracked in order that 'detached' will be called.
963
 
        self.broker.notifyOnDisconnect(_Detacher(perspective, remoteRef, self.identity).detach)
 
1112
        self.broker.notifyOnDisconnect(_Detacher(perspective,
 
1113
                                                 remoteRef,
 
1114
                                                 self.identity,
 
1115
                                                 self.broker).detach)
964
1116
        return AsReferenceable(perspective, "perspective")
965
1117
 
966
1118
    # (Possibly?) TODO: Implement 'remote_detach' as well.
967
1119
 
968
1120
 
969
1121
class AuthChallenger(Referenceable):
970
 
    """XXX
 
1122
    """DEPRECATED.
971
1123
 
972
1124
    See also: AuthServ
973
1125
    """
979
1131
 
980
1132
    def remote_respond(self, response):
981
1133
        if self.ident:
982
 
            if self.ident.verifyPassword(self.challenge, response):
983
 
                return IdentityWrapper(self.serv.broker, self.ident)
984
 
 
 
1134
            d = defer.Deferred()
 
1135
            pwrq = self.ident.verifyPassword(self.challenge, response)
 
1136
            pwrq.addCallback(self._authOk, d)
 
1137
            pwrq.addErrback(self._authFail, d)
 
1138
            return d
 
1139
 
 
1140
    def _authOk(self, result, d):
 
1141
        d.callback(IdentityWrapper(self.serv.broker, self.ident))
 
1142
 
 
1143
    def _authFail(self, result, d):
 
1144
        d.callback(None)
985
1145
 
986
1146
class AuthServ(Referenceable):
987
 
    """XXX
 
1147
    """DEPRECATED.
988
1148
 
989
 
    See also: AuthRoot
 
1149
    See also: L{AuthRoot}
990
1150
    """
991
1151
 
992
 
    def __init__(self, app, broker):
993
 
        self.app = app
 
1152
    def __init__(self, auth, broker):
 
1153
        self.auth = auth
994
1154
        self.broker = broker
995
1155
 
996
1156
    def remote_username(self, username):
997
 
        defr = self.app.authorizer.getIdentityRequest(username)
998
 
        defr.addCallbacks(self.mkchallenge, self.mkchallenge)
 
1157
        defr = self.auth.getIdentityRequest(username)
 
1158
        defr.addCallback(self.mkchallenge)
999
1159
        return defr
1000
1160
 
1001
1161
    def mkchallenge(self, ident):
1007
1167
            challenge = ident.challenge()
1008
1168
            return challenge, AuthChallenger(ident, self, challenge)
1009
1169
 
 
1170
 
1010
1171
class _ObjectRetrieval:
1011
 
    """(Internal) Does callbacks for getObjectAt.
 
1172
    """DEPRECATED.
 
1173
 
 
1174
    (Internal) Does callbacks for L{getObjectAt}.
1012
1175
    """
1013
1176
 
1014
1177
    def __init__(self, broker, d):
 
1178
        warnings.warn("This is deprecated. Use PBClientFactory.", DeprecationWarning, 2)
1015
1179
        self.deferred = d
1016
1180
        self.term = 0
1017
1181
        self.broker = broker
1026
1190
        if not self.term:
1027
1191
            self.term = 1
1028
1192
            del self.broker
1029
 
            self.deferred.armAndErrback("connection lost")
 
1193
            self.deferred.errback(error.ConnectionLost())
 
1194
            del self.deferred
 
1195
 
1030
1196
 
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
1035
1201
        self.term = 1
1036
 
        self.deferred.armAndCallback(x)
 
1202
        self.deferred.callback(x)
 
1203
        del self.deferred
1037
1204
 
1038
1205
    def connectionFailed(self):
1039
1206
        if not self.term:
1040
1207
            self.term = 1
1041
1208
            del self.broker
1042
 
            self.deferred.armAndErrback("connection failed")
 
1209
            self.deferred.errback(error.ConnectError(string="Connection failed"))
 
1210
            del self.deferred
 
1211
 
 
1212
 
 
1213
class BrokerClientFactory(protocol.ClientFactory):
 
1214
    noisy = 0
 
1215
    unsafeTracebacks = 0
 
1216
 
 
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
 
1221
 
 
1222
    def buildProtocol(self, addr):
 
1223
        return self.protocol
 
1224
 
 
1225
    def clientConnectionFailed(self, connector, reason):
 
1226
        self.protocol.connectionFailed()
 
1227
 
 
1228
    def clientConnectionMade(self, protocol):
 
1229
        pass
 
1230
 
 
1231
 
 
1232
def getObjectRetriever():
 
1233
    """DEPRECATED.
 
1234
 
 
1235
    Get a factory which retreives a root object from its client
 
1236
 
 
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
 
1239
    """
 
1240
    warnings.warn("This is deprecated. Use PBClientFactory.", DeprecationWarning, 2)
 
1241
    d = defer.Deferred()
 
1242
    b = Broker(1)
 
1243
    bf = BrokerClientFactory(b)
 
1244
    _ObjectRetrieval(b, d)
 
1245
    return bf, d
 
1246
 
1043
1247
 
1044
1248
def getObjectAt(host, port, timeout=None):
1045
 
    """Establishes a PB connection and returns with a RemoteReference.
1046
 
 
1047
 
    Arguments:
1048
 
 
1049
 
      host: the host to connect to
1050
 
 
1051
 
      port: the port number to connect to
1052
 
 
1053
 
      timeout (optional): a value in milliseconds to wait before failing by
1054
 
      default.
1055
 
 
1056
 
    Returns:
1057
 
 
1058
 
      A Deferred which will be passed a remote reference to the root object of
1059
 
      a PB server.x
1060
 
    """
1061
 
    d = defer.Deferred()
1062
 
    b = Broker()
1063
 
    _ObjectRetrieval(b, d)
1064
 
    tcp.Client(host, port, b, timeout)
1065
 
    return d
 
1249
    """DEPRECATED. Establishes a PB connection and returns with a L{RemoteReference}.
 
1250
 
 
1251
    @param host: the host to connect to
 
1252
 
 
1253
    @param port: the port number to connect to
 
1254
 
 
1255
    @param timeout: a value in milliseconds to wait before failing by
 
1256
      default. (OPTIONAL)
 
1257
 
 
1258
    @returns: A Deferred which will be passed a remote reference to the
 
1259
      root object of a PB server.x
 
1260
    """
 
1261
    warnings.warn("This is deprecated. Use PBClientFactory.", DeprecationWarning, 2)
 
1262
    bf = PBClientFactory()
 
1263
    if host == "unix":
 
1264
        # every time you use this, God kills a kitten
 
1265
        reactor.connectUNIX(port, bf, timeout)
 
1266
    else:
 
1267
        reactor.connectTCP(host, port, bf, timeout)
 
1268
    return bf.getRootObject()
 
1269
 
 
1270
def getObjectAtSSL(host, port, timeout=None, contextFactory=None):
 
1271
    """DEPRECATED. Establishes a PB connection over SSL and returns with a RemoteReference.
 
1272
 
 
1273
    @param host: the host to connect to
 
1274
 
 
1275
    @param port: the port number to connect to
 
1276
 
 
1277
    @param timeout: a value in milliseconds to wait before failing by
 
1278
      default. (OPTIONAL)
 
1279
 
 
1280
    @param contextFactory: A factory object for producing SSL.Context
 
1281
      objects.  (OPTIONAL)
 
1282
 
 
1283
    @returns: A Deferred which will be passed a remote reference to the
 
1284
      root object of a PB server.
 
1285
    """
 
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()
1066
1293
 
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.
1070
1297
 
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
1077
1304
 
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
 
1311
 
 
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.
1084
1315
    """
1085
 
    d = defer.Deferred()
1086
 
    getObjectAt(host,port,timeout).addCallbacks(
1087
 
        _connGotRoot, d.armAndErrback,
1088
 
        callbackArgs=[d, client, serviceName,
1089
 
                      username, password, perspectiveName])
1090
 
    return d
 
1316
    warnings.warn("This is deprecated. Use PBClientFactory.", DeprecationWarning, 2)
 
1317
    if timeout == None:
 
1318
        timeout = 30
 
1319
    bf = PBClientFactory()
 
1320
    if host == "unix":
 
1321
        # every time you use this, God kills a kitten
 
1322
        reactor.connectUNIX(port, bf, timeout)
 
1323
    else:
 
1324
        reactor.connectTCP(host, port, bf, timeout)
 
1325
    return bf.getPerspective(username, password, serviceName, perspectiveName, client)
1091
1326
 
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)
1095
1331
 
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.
1099
1335
    """
 
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))
1104
1341
    return d
1105
1342
 
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)
1109
1347
 
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.
1112
1350
    """
 
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))
1118
1357
    return d
1119
1358
 
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))
1125
1365
 
1126
1366
def _cbLogInResponded(identity, d, client, serviceName, perspectiveName):
 
1367
    warnings.warn("This is deprecated. Use PBClientFactory.", DeprecationWarning, 2)
1127
1368
    if identity:
1128
 
        identity.callRemote("attach", serviceName, perspectiveName, client).armAndChain(d)
 
1369
        identity.callRemote("attach", serviceName, perspectiveName, client).chainDeferred(d)
1129
1370
    else:
1130
 
        d.armAndErrback("invalid username or password")
 
1371
        from twisted import cred
 
1372
        d.errback(cred.error.Unauthorized("invalid username or password"))
 
1373
 
 
1374
class IdentityConnector:
 
1375
     """DEPRECATED.
 
1376
 
 
1377
     I support connecting to multiple Perspective Broker services that are
 
1378
     in a service tree.
 
1379
     """
 
1380
     def __init__(self, host, port, identityName, password):
 
1381
         """
 
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
 
1393
                                   the PB server.
 
1394
         """
 
1395
         warnings.warn("This is deprecated. Use PBClientFactory.", DeprecationWarning, 2)
 
1396
         self.host = host
 
1397
         self.port = port
 
1398
         self.identityName = identityName
 
1399
         self.password = password
 
1400
         self._identityWrapper = None
 
1401
         self._connectDeferreds = []
 
1402
         self._requested = 0
 
1403
 
 
1404
     def _cbGotAuthRoot(self, authroot):
 
1405
         authIdentity(authroot, self.identityName,
 
1406
                      self.password).addCallbacks(
 
1407
             self._cbGotIdentity, self._ebGotIdentity)
 
1408
 
 
1409
     def _cbGotIdentity(self, i):
 
1410
         self._identityWrapper = i
 
1411
         if i:
 
1412
             for d in self._connectDeferreds:
 
1413
                 d.callback(i)
 
1414
             self._connectDeferreds[:] = []
 
1415
         else:
 
1416
             from twisted import cred
 
1417
             e = cred.error.Unauthorized("invalid username or password")
 
1418
             self._ebGotIdentity(e)
 
1419
 
 
1420
     def _ebGotIdentity(self, e):
 
1421
         self._requested = 0
 
1422
         for d in self._connectDeferreds:
 
1423
             d.errback(e)
 
1424
         self._connectDeferreds[:] = []
 
1425
 
 
1426
     def requestLogin(self):
 
1427
         """
 
1428
         Attempt to authenticate about the PB server, but don't
 
1429
         request any services, yet.
 
1430
 
 
1431
         @returns:                  L{IdentityWrapper}
 
1432
         @rtype:                    L{twisted.internet.defer.Deferred}
 
1433
         """
 
1434
         if not self._identityWrapper:
 
1435
             d = defer.Deferred()
 
1436
             self._connectDeferreds.append(d)
 
1437
             if not self._requested:
 
1438
                 self._requested = 1
 
1439
                 getObjectAt(self.host, self.port).addCallbacks(
 
1440
                     self._cbGotAuthRoot, self._ebGotIdentity)
 
1441
             return d
 
1442
         else:
 
1443
             return defer.succeed(self._identityWrapper)
 
1444
 
 
1445
     def requestService(self, serviceName, perspectiveName=None,
 
1446
                        client=None):
 
1447
         """
 
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.
 
1451
 
 
1452
         @type serviceName:         C{string}
 
1453
         @param serviceName:        The name of the service to obtain
 
1454
                                    a perspective for.
 
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
 
1460
                                    the perspective.
 
1461
 
 
1462
         @rtype:                    L{twisted.internet.defer.Deferred}
 
1463
         @return:                   A deferred which will receive a callback
 
1464
                                    with the perspective.
 
1465
         """
 
1466
         return self.requestLogin().addCallback(
 
1467
             lambda i, self=self: i.callRemote("attach",
 
1468
                                               serviceName,
 
1469
                                               perspectiveName,
 
1470
                                               client))
 
1471
 
 
1472
     def disconnect(self):
 
1473
         """Lose my connection to the server.
 
1474
 
 
1475
         Useful to free up resources if you've completed requestLogin but
 
1476
         then change your mind.
 
1477
         """
 
1478
         if not self._identityWrapper:
 
1479
             return
 
1480
         else:
 
1481
             self._identityWrapper.broker.transport.loseConnection()
 
1482
 
 
1483
 
 
1484
# this is the new shiny API you should be using:
 
1485
 
 
1486
import md5
 
1487
import random
 
1488
from twisted.cred.credentials import ICredentials, IUsernameHashedPassword
 
1489
 
 
1490
def respond(challenge, password):
 
1491
    """Respond to a challenge.
 
1492
 
 
1493
    This is useful for challenge/response authentication.
 
1494
    """
 
1495
    m = md5.new()
 
1496
    m.update(password)
 
1497
    hashedPassword = m.digest()
 
1498
    m = md5.new()
 
1499
    m.update(hashedPassword)
 
1500
    m.update(challenge)
 
1501
    doubleHashedPassword = m.digest()
 
1502
    return doubleHashedPassword
 
1503
 
 
1504
def challenge():
 
1505
    """I return some random data."""
 
1506
    crap = ''
 
1507
    for x in range(random.randrange(15,25)):
 
1508
        crap = crap + chr(random.randint(65,90))
 
1509
    crap = md5.new(crap).digest()
 
1510
    return crap
 
1511
 
 
1512
 
 
1513
class PBClientFactory(protocol.ClientFactory):
 
1514
    """Client factory for PB brokers.
 
1515
 
 
1516
    As with all client factories, use with reactor.connectTCP/SSL/etc..
 
1517
    getPerspective and getRootObject can be called either before or
 
1518
    after the connect.
 
1519
    """
 
1520
 
 
1521
    protocol = Broker
 
1522
    unsafeTracebacks = 0
 
1523
 
 
1524
    def __init__(self):
 
1525
        self._reset()
 
1526
 
 
1527
    def _reset(self):
 
1528
        self.rootObjectRequests = [] # list of deferred
 
1529
        self._broker = None
 
1530
        self._root = None
 
1531
 
 
1532
    def _failAll(self, reason):
 
1533
        deferreds = self.rootObjectRequests
 
1534
        self._reset()
 
1535
        for d in deferreds:
 
1536
            d.errback(reason)
 
1537
 
 
1538
    def clientConnectionFailed(self, connector, reason):
 
1539
        self._failAll(reason)
 
1540
 
 
1541
    def clientConnectionLost(self, connector, reason, reconnecting=0):
 
1542
        """Reconnecting subclasses should call with reconnecting=1."""
 
1543
        if reconnecting:
 
1544
            # any pending requests will go to next connection attempt
 
1545
            # so we don't fail them.
 
1546
            self._broker = None
 
1547
            self._root = None
 
1548
        else:
 
1549
            self._failAll(reason)
 
1550
 
 
1551
    def clientConnectionMade(self, broker):
 
1552
        self._broker = broker
 
1553
        self._root = broker.remoteForName("root")
 
1554
        ds = self.rootObjectRequests
 
1555
        self.rootObjectRequests = []
 
1556
        for d in ds:
 
1557
            d.callback(self._root)
 
1558
 
 
1559
    def getRootObject(self):
 
1560
        """Get root object of remote PB server.
 
1561
 
 
1562
        @return Deferred of the root object.
 
1563
        """
 
1564
        if self._broker and not self._broker.disconnected:
 
1565
           return defer.succeed(self._root)
 
1566
        d = defer.Deferred()
 
1567
        self.rootObjectRequests.append(d)
 
1568
        return d
 
1569
 
 
1570
    def getPerspective(self, username, password, serviceName,
 
1571
                       perspectiveName=None, client=None):
 
1572
        """Get perspective from remote PB server.
 
1573
 
 
1574
        New systems should use login() instead.
 
1575
 
 
1576
        @return Deferred of RemoteReference to the perspective.
 
1577
        """
 
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)
 
1585
        return d
 
1586
 
 
1587
    def _cbAuthIdentity(self, authServRef, username, password):
 
1588
        return authServRef.callRemote('username', username).addCallback(
 
1589
            self._cbRespondToChallenge, password)
 
1590
 
 
1591
    def _cbRespondToChallenge(self, (challenge, challenger), password):
 
1592
        return challenger.callRemote("respond", respond(challenge, password))
 
1593
 
 
1594
    def _cbGetPerspective(self, identityWrapper, serviceName, perspectiveName, client):
 
1595
        return identityWrapper.callRemote(
 
1596
            "attach", serviceName, perspectiveName, client)
 
1597
 
 
1598
    def disconnect(self):
 
1599
        """If the factory is connected, close the connection.
 
1600
 
 
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
 
1603
        is called.
 
1604
        """
 
1605
        if self._broker:
 
1606
            self._broker.transport.loseConnection()
 
1607
 
 
1608
    def _cbSendUsername(self, root, username, password, client):
 
1609
        return root.callRemote("login", username).addCallback(
 
1610
            self._cbResponse, password, client)
 
1611
 
 
1612
    def _cbResponse(self, (challenge, challenger), password, client):
 
1613
        return challenger.callRemote("respond", respond(challenge, password), client)
 
1614
 
 
1615
    def login(self, credentials, client=None):
 
1616
        """Login and get perspective from remote PB server.
 
1617
 
 
1618
        Currently only credentials implementing IUsernamePassword are
 
1619
        supported.
 
1620
 
 
1621
        @return Deferred of RemoteReference to the perspective.
 
1622
        """
 
1623
        d = self.getRootObject()
 
1624
        d.addCallback(self._cbSendUsername, credentials.username, credentials.password, client)
 
1625
        return d
 
1626
 
 
1627
 
 
1628
class PBServerFactory(protocol.ServerFactory):
 
1629
    """Server factory for perspective broker.
 
1630
 
 
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.
 
1634
 
 
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
 
1637
    server.
 
1638
    """
 
1639
 
 
1640
    unsafeTracebacks = 0
 
1641
 
 
1642
    # object broker factory
 
1643
    protocol = Broker
 
1644
 
 
1645
    def __init__(self, root, unsafeTracebacks=False):
 
1646
        self.root = IPBRoot(root)
 
1647
        self.unsafeTracebacks = unsafeTracebacks
 
1648
 
 
1649
    def buildProtocol(self, addr):
 
1650
        """Return a Broker attached to me (as the service provider).
 
1651
        """
 
1652
        proto = self.protocol(0)
 
1653
        proto.factory = self
 
1654
        proto.setNameForLocal("root", self.root.rootObject(proto))
 
1655
        return proto
 
1656
 
 
1657
    def clientConnectionMade(self, protocol):
 
1658
        pass
 
1659
 
 
1660
 
 
1661
class IUsernameMD5Password(ICredentials):
 
1662
    """I encapsulate a username and a hashed password.
 
1663
 
 
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.
 
1667
 
 
1668
    @type username: C{str} or C{Deferred}
 
1669
    @ivar username: The username associated with these credentials.
 
1670
    """
 
1671
 
 
1672
    def checkPassword(self, password):
 
1673
        """Validate these credentials against the correct password.
 
1674
 
 
1675
        @param password: The correct, plaintext password against which to
 
1676
        @check.
 
1677
 
 
1678
        @return: a deferred which becomes, or a boolean indicating if the
 
1679
        password matches.
 
1680
        """
 
1681
 
 
1682
    def checkMD5Password(self, password):
 
1683
        """Validate these credentials against the correct MD5 digest of password.
 
1684
 
 
1685
        @param password: The correct, plaintext password against which to
 
1686
        @check.
 
1687
 
 
1688
        @return: a deferred which becomes, or a boolean indicating if the
 
1689
        password matches.
 
1690
        """
 
1691
 
 
1692
 
 
1693
 
 
1694
 
 
1695
class _PortalRoot:
 
1696
    """Root object, used to login to portal."""
 
1697
 
 
1698
    __implements__ = IPBRoot,
 
1699
 
 
1700
    def __init__(self, portal):
 
1701
        self.portal = portal
 
1702
 
 
1703
    def rootObject(self, broker):
 
1704
        return _PortalWrapper(self.portal, broker)
 
1705
 
 
1706
registerAdapter(_PortalRoot, Portal, IPBRoot)
 
1707
 
 
1708
 
 
1709
class _PortalWrapper(Referenceable):
 
1710
    """Root Referenceable object, used to login to portal."""
 
1711
 
 
1712
    def __init__(self, portal, broker):
 
1713
        self.portal = portal
 
1714
        self.broker = broker
 
1715
 
 
1716
    def remote_login(self, username):
 
1717
        """Start of username/password login."""
 
1718
        c = challenge()
 
1719
        return c, _PortalAuthChallenger(self, username, c)
 
1720
 
 
1721
 
 
1722
class _PortalAuthChallenger(Referenceable):
 
1723
    """Called with response to password challenge."""
 
1724
 
 
1725
    __implements__ = (Referenceable.__implements__, IUsernameHashedPassword, IUsernameMD5Password)
 
1726
 
 
1727
    def __init__(self, portalWrapper, username, challenge):
 
1728
        self.portalWrapper = portalWrapper
 
1729
        self.username = username
 
1730
        self.challenge = challenge
 
1731
 
 
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)
 
1736
        return d
 
1737
 
 
1738
    def _loggedIn(self, (interface, perspective, logout)):
 
1739
        self.portalWrapper.broker.notifyOnDisconnect(logout)
 
1740
        return AsReferenceable(perspective, "perspective")
 
1741
 
 
1742
    # IUsernameHashedPassword:
 
1743
    def checkPassword(self, password):
 
1744
        return self.checkMD5Password(md5.md5(password).digest())
 
1745
 
 
1746
    # IUsernameMD5Password
 
1747
    def checkMD5Password(self, md5Password):
 
1748
        md = md5.new()
 
1749
        md.update(md5Password)
 
1750
        md.update(self.challenge)
 
1751
        correct = md.digest()
 
1752
        return self.response == correct