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