1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 """
23 Base class and implementation for bouncer components, who perform
24 authentication services for other components.
25 """
26
27 import md5
28 import random
29
30 from twisted.python import components
31 from twisted.internet import defer
32 from twisted.cred import error
33
34 from flumotion.common import interfaces, keycards
35 from flumotion.common.componentui import WorkerComponentUIState
36
37 from flumotion.component import component
38 from flumotion.twisted import flavors, credentials
39
40 __all__ = ['Bouncer']
41
43
44 logCategory = 'bouncermedium'
46 """
47 Authenticates the given keycard.
48
49 @type keycard: L{flumotion.common.keycards.Keycard}
50 """
51 return self.comp.authenticate(keycard)
52
54 try:
55 self.comp.removeKeycardId(keycardId)
56
57 except KeyError:
58 self.warning('Could not remove keycard id %s' % keycardId)
59
61 """
62 Called by bouncer views to expire keycards.
63 """
64 return self.comp.expireKeycardId(keycardId)
65
68
69 -class Bouncer(component.BaseComponent):
70 """
71 I am the base class for all bouncers.
72
73 @cvar keycardClasses: tuple of all classes of keycards this bouncer can
74 authenticate, in order of preference
75 @type keycardClasses: tuple of L{flumotion.common.keycards.Keycard}
76 class objects
77 """
78 keycardClasses = ()
79 componentMediumClass = BouncerMedium
80 logCategory = 'bouncer'
81
83 self._idCounter = 0
84 self._keycards = {}
85 self._keycardDatas = {}
86 self.uiState.addListKey('keycards')
87
88 self.enabled = True
89
90 - def setDomain(self, name):
92
93 - def getDomain(self):
95
97 """
98 Verify if the keycard is an instance of a Keycard class specified
99 in the bouncer's keycardClasses variable.
100 """
101 return isinstance(keycard, self.keycardClasses)
102
104 if not enabled and self.enabled:
105
106
107 self.expireAllKeycards()
108
109 self.enabled = enabled
110
121
123 """
124 Must be overridden by subclasses.
125
126 Authenticate the given keycard.
127 Return the keycard with state AUTHENTICATED to authenticate,
128 with state REQUESTING to continue the authentication process,
129 or None to deny the keycard, or a deferred which should have the same
130 eventual value.
131 """
132 raise NotImplementedError("authenticate not overridden")
133
135 return keycard in self._keycards.values()
136
138
139 if self._keycards.has_key(keycard.id):
140
141 return
142
143
144
145 id = "%016x" % self._idCounter
146 self._idCounter += 1
147
148 keycard.id = id
149 self._keycards[id] = keycard
150 data = keycard.getData()
151 self._keycardDatas[id] = data
152
153 self.uiState.append('keycards', data)
154 self.debug("added keycard with id %s" % keycard.id)
155
157 id = keycard.id
158 if not self._keycards.has_key(id):
159 raise KeyError
160
161 del self._keycards[id]
162
163 data = self._keycardDatas[id]
164 self.uiState.remove('keycards', data)
165 del self._keycardDatas[id]
166 self.debug("removed keycard with id %s" % id)
167
169 self.debug("removing keycard with id %s" % id)
170 if not self._keycards.has_key(id):
171 raise KeyError
172
173 keycard = self._keycards[id]
174 self.removeKeycard(keycard)
175
179
181 self.debug("expiring keycard with id %r" % id)
182 if not self._keycards.has_key(id):
183 raise KeyError
184
185 keycard = self._keycards[id]
186
187 d = self.medium.callRemote(
188 'expireKeycard', keycard.requesterId, keycard.id)
189
190
191
192 return d
193
195 """
196 A very trivial bouncer implementation.
197
198 Useful as a concrete bouncer class for which all users are accepted whenever
199 the bouncer is enabled.
200 """
201 keycardClasses = (keycards.KeycardGeneric,)
202
207
209 """
210 A base class for Challenge-Response bouncers
211 """
212
213 challengeResponseClasses = ()
214
216 self._checker = None
217 self._challenges = {}
218 self._db = {}
219
221 self._checker = checker
222
223 - def addUser(self, user, salt, *args):
224 self._db[user] = salt
225 self._checker.addUser(user, *args)
226
238
246
248
249 self.addKeycard(keycard)
250
251
252 if isinstance(keycard, self.challengeResponseClasses):
253
254 if not keycard.challenge:
255 self.debug('putting challenge on keycard %r' % keycard)
256 keycard.challenge = credentials.cryptChallenge()
257 if keycard.username in self._db:
258 keycard.salt = self._db[keycard.username]
259 else:
260
261 string = str(random.randint(pow(10,10), pow(10, 11)))
262 md = md5.new()
263 md.update(string)
264 keycard.salt = md.hexdigest()[:2]
265 self.debug("user not found, inventing bogus salt")
266 self.debug("salt %s, storing challenge for id %s" % (
267 keycard.salt, keycard.id))
268
269 self._challenges[keycard.id] = keycard.challenge
270 return keycard
271
272 if keycard.response:
273
274 if self._challenges[keycard.id] != keycard.challenge:
275 self.removeKeycard(keycard)
276 self.info('keycard %r refused, challenge tampered with' %
277 keycard)
278 return None
279 del self._challenges[keycard.id]
280
281
282 self.debug('submitting keycard %r to checker' % keycard)
283 d = self._checker.requestAvatarId(keycard)
284 d.addCallback(self._requestAvatarIdCallback, keycard)
285 d.addErrback(self._requestAvatarIdErrback, keycard)
286 return d
287