1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 """
19 portal-related functionality inspired by twisted.cred.portal
20 """
21
22 from twisted.spread import flavors
23 from twisted.internet import defer
24 from twisted.cred.portal import Portal
25 from twisted.python import reflect
26 from twisted.python.components import registerAdapter
27
28 from flumotion.common import keycards, log, interfaces, errors
29 from flumotion.twisted.pb import _FPortalRoot
30
31 __version__ = "$Rev$"
32
33
35 """
36 I am a portal for an FPB server using a bouncer to decide on FPB client
37 access.
38 """
39
40 logCategory = "BouncerPortal"
41
43 """
44 Create a BouncerPortal to a L{twisted.cred.portal.IRealm}.
45
46 @param realm: an implementor of L{twisted.cred.portal.IRealm}
47 @param bouncer: a bouncer to use for authentication
48 @type bouncer: L{flumotion.component.bouncers.bouncer.Bouncer}
49 """
50 self.realm = realm
51 self.bouncer = bouncer
52 self._adminCounter = 0
53
55 """
56 Return the Keycard interfaces supported by this portal's bouncer.
57
58 @rtype: L{twisted.internet.defer.Deferred} firing list of str
59 """
60 if not self.bouncer:
61
62
63 return []
64 if hasattr(self.bouncer, 'getKeycardClasses'):
65
66 return self.bouncer.getKeycardClasses()
67 else:
68 interfaces = [reflect.qual(k) for k in self.bouncer.keycardClasses]
69 return defer.succeed(interfaces)
70
71 - def login(self, keycard, mind, *ifaces):
72 """
73 Log in the keycard to the portal using the bouncer.
74
75 @param keycard: the keycard used to login
76 @type keycard: L{flumotion.common.keycards.Keycard}
77 @param mind: a reference to the client-side requester
78 @type mind: L{twisted.spread.pb.RemoteReference}
79 @param ifaces: a list of interfaces for the perspective that the
80 mind wishes to attach to
81
82 @returns: a deferred, which will fire a tuple of
83 (interface, avatarAspect, logout) or None.
84 """
85 self.debug("_login(keycard=%r, mind=%r, ifaces=%r)" % (
86 keycard, mind, ifaces))
87
88 if not self.bouncer:
89 self.warning("no bouncer, refusing login")
90 mind.broker.transport.loseConnection()
91 return defer.fail(errors.NotAuthenticatedError(
92 "No bouncer configured, no logins possible"))
93
94 def onErrorCloseConnection(failure):
95 try:
96 host = mind.broker.transport.getHost()
97 remote = '%s:%d' % (host.host, host.port)
98 except:
99 remote = '(unknown)'
100
101 self.warning('failed login -- closing connection to %s',
102 remote)
103 self.debug('failure: %s', log.getFailureMessage(failure))
104 try:
105 mind.broker.transport.loseConnection()
106 except Exception, e:
107 self.info('loseConnection failed: %s',
108 log.getExceptionMessage(e))
109
110 return failure
111
112 def bouncerResponse(result):
113
114
115
116 if not result:
117 self.info("unauthorized login for interfaces %r", ifaces)
118 return defer.fail(errors.NotAuthenticatedError(
119 "Unauthorized login"))
120
121 keycard = result
122 if not keycard.state == keycards.AUTHENTICATED:
123
124 self.log('returning keycard for further authentication')
125 return keycard
126
127
128 self.debug('authenticated login of %r into realm %r', keycard,
129 self.realm)
130
131
132 if interfaces.IAdminMedium in ifaces:
133
134 keycard.avatarId = "admin-%06x" % self._adminCounter
135 self._adminCounter += 1
136
137 self.log(
138 'calling %r.requestAvatar(keycard=%r, mind=%r, ifaces=%r)',
139 self.realm, keycard, mind, ifaces)
140
141 return self.realm.requestAvatar(keycard.avatarId,
142 keycard, mind, *ifaces)
143
144 if hasattr(keycard, 'address'):
145 try:
146 keycard.address = mind.broker.transport.getHost().host
147 except:
148 self.debug("can't get address of remote, setting to None")
149 keycard.address = None
150
151 d = defer.maybeDeferred(self.bouncer.authenticate, keycard)
152 d.addCallback(bouncerResponse)
153 d.addErrback(onErrorCloseConnection)
154 return d
155
156 registerAdapter(_FPortalRoot, BouncerPortal, flavors.IPBRoot)
157