Package flumotion :: Package component :: Package bouncers :: Module component
[hide private]

Source Code for Module flumotion.component.bouncers.component

  1  # -*- Mode: Python -*- 
  2  # vi:si:et:sw=4:sts=4:ts=4 
  3   
  4  # Flumotion - a streaming media server 
  5  # Copyright (C) 2004,2005,2006,2007,2008,2009 Fluendo, S.L. 
  6  # Copyright (C) 2010,2011 Flumotion Services, S.A. 
  7  # All rights reserved. 
  8  # 
  9  # This file may be distributed and/or modified under the terms of 
 10  # the GNU Lesser General Public License version 2.1 as published by 
 11  # the Free Software Foundation. 
 12  # This file is distributed without any warranty; without even the implied 
 13  # warranty of merchantability or fitness for a particular purpose. 
 14  # See "LICENSE.LGPL" in the source distribution for more information. 
 15  # 
 16  # Headers in this file shall remain intact. 
 17   
 18  """ 
 19  Base class and implementation for bouncer components, who perform 
 20  authentication services for other components. 
 21   
 22  Bouncers receive keycards, defined in L{flumotion.common.keycards}, and 
 23  then authenticate them. 
 24   
 25  Passing a keycard over a PB connection will copy all of the keycard's 
 26  attributes to a remote side, so that bouncer authentication can be 
 27  coupled with PB. Bouncer implementations have to make sure that they 
 28  never store sensitive data as an attribute on a keycard. 
 29   
 30  Keycards have three states: REQUESTING, AUTHENTICATED, and REFUSED. When 
 31  a keycard is first passed to a bouncer, it has the state REQUESTING. 
 32  Bouncers should never read the 'state' attribute on a keycard for any 
 33  authentication-related purpose, since it comes from the remote side. 
 34  Typically, a bouncer will only set the 'state' attribute to 
 35  AUTHENTICATED or REFUSED once it has the information to make such a 
 36  decision. 
 37   
 38  Authentication of keycards is performed in the authenticate() method, 
 39  which takes a keycard as an argument. The Bouncer base class' 
 40  implementation of this method will perform some common checks (e.g., is 
 41  the bouncer enabled, is the keycard of the correct type), and then 
 42  dispatch to the do_authenticate method, which is expected to be 
 43  overridden by subclasses. 
 44   
 45  Implementations of do_authenticate should eventually return a keycard 
 46  with the state AUTHENTICATED or REFUSED. It is acceptable for this 
 47  method to return either a keycard or a deferred that will eventually 
 48  return a keycard. 
 49   
 50  FIXME: Currently, a return value of 'None' is treated as rejecting the 
 51  keycard. This is unintuitive. 
 52   
 53  Challenge-response authentication may be implemented in 
 54  do_authenticate(), by returning a keycard still in the state REQUESTING 
 55  but with extra attributes annotating the keycard. The remote side would 
 56  then be expected to set a response on the card, resubmit, at which point 
 57  authentication could be performed. The exact protocol for this depends 
 58  on the particular keycard class and set of bouncers that can 
 59  authenticate that keycard class. 
 60   
 61  It is expected that a bouncer implementation keeps references on the 
 62  currently active set of authenticated keycards. These keycards can then 
 63  be revoked at any time by the bouncer, which will be effected through an 
 64  'expireKeycard' call. When the code that requested the keycard detects 
 65  that the keycard is no longer necessary, it should notify the bouncer 
 66  via calling 'removeKeycardId'. 
 67   
 68  The above process is leak-prone, however; if for whatever reason, the 
 69  remote side is unable to remove the keycard, the keycard will never be 
 70  removed from the bouncer's state. For that reason there is a more robust 
 71  method: if the keycard has a 'ttl' attribute, then it will be expired 
 72  automatically after 'keycard.ttl' seconds have passed. The remote side 
 73  is then responsible for periodically telling the bouncer which keycards 
 74  are still valid via the 'keepAlive' call, which resets the TTL on the 
 75  given set of keycards. 
 76   
 77  Note that with automatic expiry via the TTL attribute, it is still 
 78  preferred, albeit not strictly necessary, that callers of authenticate() 
 79  call removeKeycardId when the keycard is no longer used. 
 80  """ 
 81   
 82  import random 
 83  import time 
 84   
 85  from twisted.internet import defer, reactor 
 86   
 87  from flumotion.common import keycards, errors, python, poller 
 88  from flumotion.common.componentui import WorkerComponentUIState 
 89   
 90  from flumotion.component import component 
 91  from flumotion.twisted import credentials 
 92   
 93  __all__ = ['Bouncer'] 
 94  __version__ = "$Rev$" 
 95   
 96  # How many keycards to expire in a single synchronous deferred expiration call. 
 97  EXPIRE_BLOCK_SIZE = 100 
 98   
 99   
100 -class BouncerMedium(component.BaseComponentMedium):
101 102 logCategory = 'bouncermedium' 103
104 - def remote_authenticate(self, keycard):
105 """ 106 Authenticates the given keycard. 107 108 @type keycard: L{flumotion.common.keycards.Keycard} 109 """ 110 return self.comp.authenticate(keycard)
111
112 - def remote_keepAlive(self, issuerName, ttl):
113 """ 114 Resets the expiry timeout for keycards issued by issuerName. 115 116 @param issuerName: the issuer for which keycards should be kept 117 alive; that is to say, keycards with the 118 attribute 'issuerName' set to this value will 119 have their ttl values reset. 120 @type issuerName: str 121 @param ttl: the new expiry timeout 122 @type ttl: number 123 """ 124 return self.comp.keepAlive(issuerName, ttl)
125
126 - def remote_removeKeycardId(self, keycardId):
127 try: 128 self.comp.removeKeycardId(keycardId) 129 # FIXME: at least have an exception name please 130 except KeyError: 131 self.warning('Could not remove keycard id %s' % keycardId)
132
133 - def remote_expireKeycardId(self, keycardId):
134 """ 135 Called by bouncer views to expire keycards. 136 """ 137 return self.comp.expireKeycardId(keycardId)
138
139 - def remote_expireKeycardIds(self, keycardIds):
140 """ 141 Called by bouncer views to expire multiple keycards. 142 """ 143 return self.comp.expireKeycardIds(keycardIds)
144
145 - def remote_setEnabled(self, enabled):
146 return self.comp.setEnabled(enabled)
147
148 - def remote_getEnabled(self):
149 return self.comp.getEnabled()
150 151
152 -class Bouncer(component.BaseComponent):
153 """ 154 I am the base class for all bouncer components. 155 156 @cvar keycardClasses: tuple of all classes of keycards this bouncer can 157 authenticate, in order of preference 158 @type keycardClasses: tuple of L{flumotion.common.keycards.Keycard} 159 class objects 160 """ 161 keycardClasses = () 162 componentMediumClass = BouncerMedium 163 logCategory = 'bouncer' 164 165 KEYCARD_EXPIRE_INTERVAL = 2 * 60 # expire every 2 minutes 166
167 - def init(self):
168 self._idCounter = 0 169 self._idFormat = time.strftime('%Y%m%d%H%M%S-%%d') 170 self._keycards = {} # keycard id -> Keycard 171 172 self._expirer = poller.Poller(self._expire, 173 self.KEYCARD_EXPIRE_INTERVAL, 174 start=False) 175 self.enabled = True
176
177 - def setDomain(self, name):
178 self.domain = name
179
180 - def getDomain(self):
181 return self.domain
182
183 - def typeAllowed(self, keycard):
184 """ 185 Verify if the keycard is an instance of a Keycard class specified 186 in the bouncer's keycardClasses variable. 187 """ 188 return isinstance(keycard, self.keycardClasses)
189
190 - def setEnabled(self, enabled):
191 192 def callAndPassthru(result, method, *args): 193 method(*args) 194 return result
195 196 if not enabled and self.enabled: 197 # If we were enabled and are being set to disabled, eject the warp 198 # core^w^w^w^wexpire all existing keycards 199 self.enabled = False 200 self._expirer.stop() 201 d = self.expireAllKeycards() 202 d.addCallback(callAndPassthru, self.on_disabled) 203 return d 204 self.enabled = enabled 205 d = defer.succeed(0) 206 d.addCallback(callAndPassthru, self.on_enabled) 207 return d
208
209 - def getEnabled(self):
210 return self.enabled
211
212 - def do_stop(self):
213 return self.setEnabled(False)
214
215 - def authenticate(self, keycard):
216 if not self.typeAllowed(keycard): 217 self.warning('keycard %r is not an allowed keycard class', keycard) 218 return None 219 220 if not self.do_validate(keycard): 221 self.warning('keycard %r is not a valid keycard instance', keycard) 222 return None 223 224 if self.enabled: 225 if not self._expirer.running and hasattr(keycard, 'ttl'): 226 self.debug('installing keycard timeout poller') 227 self._expirer.start() 228 return defer.maybeDeferred(self.do_authenticate, keycard) 229 else: 230 self.debug("Bouncer disabled, refusing authentication") 231 return None
232
233 - def do_expireKeycards(self, elapsed):
234 """ 235 Override to expire keycards managed by sub-classes. 236 237 @param elapsed: time in second since the last expiration call. 238 @type elapsed: int 239 @returns: if there is more keycard to expire. If False is returned, 240 the expirer poller MAY be stopped. 241 @rtype: bool 242 """ 243 for k in self._keycards.values(): 244 if hasattr(k, 'ttl'): 245 k.ttl -= elapsed 246 if k.ttl <= 0: 247 self.expireKeycardId(k.id) 248 return len(self._keycards) > 0
249
250 - def do_validate(self, keycard):
251 """ 252 Override to check keycards before authentication steps. 253 Should return True if the keycard is valid, False otherwise. 254 #FIXME: This belong to the base bouncer class 255 256 @param keycard: the keycard that should be validated 257 before authentication 258 @type keycard: flumotion.common.keycards.Keycard 259 @returns: True if the keycard is accepted, False otherwise 260 @rtype: bool 261 """ 262 return True
263
264 - def do_authenticate(self, keycard):
265 """ 266 Must be overridden by subclasses. 267 268 Authenticate the given keycard. 269 Return the keycard with state AUTHENTICATED to authenticate, 270 with state REQUESTING to continue the authentication process, 271 or REFUSED to deny the keycard or a deferred which should 272 have the same eventual value. 273 274 FIXME: Currently, a return value of 'None' is treated 275 as rejecting the keycard. This is unintuitive. 276 277 FIXME: in fact, for authentication sessions like challenge/response, 278 returning a keycard with state REFUSED instead of None 279 will not work properly and may enter in an asynchronous infinit loop. 280 """ 281 raise NotImplementedError("authenticate not overridden")
282
283 - def on_keycardAdded(self, keycard):
284 """ 285 Override to update sub-class specific data related to keycards. 286 Called when the base bouncer accepts and references a new keycard. 287 """
288
289 - def on_keycardRemoved(self, keycard):
290 """ 291 Override to cleanup sub-class specific data related to keycards. 292 Called when the base bouncer has cleanup his references to a keycard. 293 """
294
295 - def on_enabled(self):
296 """ 297 Override to initialize sub-class specific data 298 when the bouncer is enabled. 299 """
300
301 - def on_disabled(self):
302 """ 303 Override to cleanup sub-class specific data 304 when the bouncer is disabled. 305 """
306
307 - def hasKeycard(self, keycard):
308 return keycard in self._keycards.values()
309
310 - def generateKeycardId(self):
311 # FIXME: what if it already had one ? 312 # FIXME: deal with wraparound ? 313 keycardId = self._idFormat % self._idCounter 314 self._idCounter += 1 315 return keycardId
316
317 - def addKeycard(self, keycard):
318 """ 319 Adds a keycard to the bouncer. 320 Can be called with the same keycard more than one time. 321 If the keycard has already been added successfully, 322 adding it again will succeed and return True. 323 324 @param keycard: the keycard to add. 325 @return: if the bouncer accepts the keycard. 326 """ 327 # give keycard an id and store it in our hash 328 if keycard.id in self._keycards: 329 # already in there 330 return True 331 332 keycardId = self.generateKeycardId() 333 keycard.id = keycardId 334 335 if hasattr(keycard, 'ttl') and keycard.ttl <= 0: 336 self.log('immediately expiring keycard %r', keycard) 337 return False 338 339 self._addKeycard(keycard) 340 return True
341
342 - def removeKeycard(self, keycard):
343 if not keycard.id in self._keycards: 344 raise KeyError 345 346 del self._keycards[keycard.id] 347 self.on_keycardRemoved(keycard) 348 349 self.info("removed keycard with id %s" % keycard.id)
350
351 - def removeKeycardId(self, keycardId):
352 self.debug("removing keycard with id %s" % keycardId) 353 if not keycardId in self._keycards: 354 raise KeyError 355 356 keycard = self._keycards[keycardId] 357 self.removeKeycard(keycard)
358
359 - def keepAlive(self, issuerName, ttl):
360 for k in self._keycards.itervalues(): 361 if hasattr(k, 'issuerName') and k.issuerName == issuerName: 362 k.ttl = ttl
363
364 - def expireAllKeycards(self):
365 return self.expireKeycardIds(self._keycards.keys())
366
367 - def expireKeycardId(self, keycardId):
368 self.log("expiring keycard with id %r", keycardId) 369 if not keycardId in self._keycards: 370 raise KeyError 371 372 keycard = self._keycards[keycardId] 373 self.removeKeycardId(keycardId) 374 375 if self.medium: 376 return self.medium.callRemote('expireKeycard', 377 keycard.requesterId, keycard.id) 378 else: 379 return defer.succeed(None)
380
381 - def expireKeycardIds(self, keycardIds):
382 self.log("expiring keycards with id %r", keycardIds) 383 d = defer.Deferred() 384 self._expireNextKeycardBlock(0, keycardIds, d) 385 return d
386
387 - def _expireNextKeycardBlock(self, total, keycardIds, finished):
388 # We can't expire all keycards in a single blocking call because 389 # there might be so many that the component goes lost. 390 # This call will trigger expiring all keycards by chunking them 391 # across separate deferreds. 392 393 keycardBlock = keycardIds[:EXPIRE_BLOCK_SIZE] 394 keycardIds = keycardIds[EXPIRE_BLOCK_SIZE:] 395 idByReq = {} 396 397 for keycardId in keycardBlock: 398 if keycardId in self._keycards: 399 keycard = self._keycards[keycardId] 400 requesterId = keycard.requesterId 401 idByReq.setdefault(requesterId, []).append(keycardId) 402 self.removeKeycardId(keycardId) 403 404 if not (idByReq and self.medium): 405 # instead of serializing each block by chaining deferreds, which 406 # can trigger maximum recursion depth, we just callback once 407 # on the passed-in deferred 408 finished.callback(total) 409 return 410 411 defs = [self.medium.callRemote('expireKeycards', rid, ids) 412 for rid, ids in idByReq.items()] 413 dl = defer.DeferredList(defs, consumeErrors=True) 414 415 def countExpirations(results, total): 416 return sum([v for s, v in results if s and v]) + total
417 418 dl.addCallback(countExpirations, total) 419 dl.addCallback(self._expireNextKeycardBlock, keycardIds, finished) 420
421 - def _addKeycard(self, keycard):
422 """ 423 Adds a keycard without checking. 424 Used by sub-class knowing what they do. 425 """ 426 self._keycards[keycard.id] = keycard 427 self.on_keycardAdded(keycard) 428 429 self.debug("added keycard with id %s, ttl %r", keycard.id, 430 getattr(keycard, 'ttl', None))
431
432 - def _expire(self):
433 if not self.do_expireKeycards(self._expirer.timeout): 434 if self._expirer.running: 435 self.debug('no more keycards, removing timeout poller') 436 self._expirer.stop()
437 438
439 -class AuthSessionBouncer(Bouncer):
440 """ 441 I am a bouncer that handle pending authentication sessions. 442 I am storing the last keycard of an authenticating session. 443 """ 444
445 - def init(self):
446 # Keycards pending to be authenticated 447 self._sessions = {} # keycard id -> (ttl, data)
448
449 - def on_disabled(self):
450 # Removing all pending authentication 451 self._sessions.clear()
452
453 - def do_extractKeycardInfo(self, keycard, oldData):
454 """ 455 Extracts session info from a keycard. 456 Used by updateAuthSession to store session info. 457 Must be overridden by subclasses. 458 """ 459 raise NotImplementedError()
460
461 - def hasAuthSession(self, keycard):
462 """ 463 Tells if a keycard is related to a pending authentication session. 464 It basically check if the id of the keycard is known. 465 466 @param keycard: the keycard to check 467 @type keycard: flumotion.common.keycards.Keycard 468 @returns: if a pending authentication session associated 469 with the specified keycard exists. 470 471 @rtype: bool 472 """ 473 return (keycard.id is not None) and (keycard.id in self._sessions)
474
475 - def getAuthSessionInfo(self, keycard):
476 """ 477 @return: the last updated keycard for the authentication session 478 associated with the specified keycard 479 @rtype: flumotion.common.keycards.Keycard or None 480 """ 481 data = keycard.id and self._sessions.get(keycard.id, None) 482 return data and data[1]
483
484 - def startAuthSession(self, keycard):
485 """ 486 Starts an authentication session with a keycard. 487 The keycard id will be generated and set. 488 The session info will be extracted from the keycard 489 by calling the method do_extractKeycardInfo, and can 490 be retrieved by calling getAuthSessionInfo. 491 492 If a the keycard already have and id, and there is 493 an authentication session with this id, the session info 494 is updated from the keycard, and it return True. 495 496 @param keycard: the keycard to update from. 497 @type keycard: flumotion.common.keycards.Keycard 498 @return: if the bouncer accepts the keycard. 499 """ 500 # Check if there is already an authentication session 501 if self.hasAuthSession(keycard): 502 # Updating the authentication session data 503 self._updateInfoFromKeycard(keycard) 504 return True 505 506 if keycard.id: 507 self.warning("keycard %r already has an id, but no " 508 "authentication session", keycard) 509 keycard.state = keycards.REFUSED 510 return False 511 512 if hasattr(keycard, 'ttl') and keycard.ttl <= 0: 513 self.log('immediately expiring keycard %r', keycard) 514 keycard.state = keycards.REFUSED 515 return False 516 517 # Generate an id for the authentication session 518 keycardId = self.generateKeycardId() 519 keycard.id = keycardId 520 521 self._updateInfoFromKeycard(keycard) 522 523 self.debug("started authentication session with with id %s, ttl %r", 524 keycard.id, getattr(keycard, 'ttl', None)) 525 return True
526
527 - def updateAuthSession(self, keycard):
528 """ 529 Updates an authentication session with the last keycard. 530 The session info will be extracted from the keycard 531 by calling the method do_extractKeycardInfo, and can 532 be retrieved by calling getAuthSessionInfo. 533 534 @param keycard: the keycard to update from. 535 @type keycard: flumotion.common.keycards.Keycard 536 """ 537 # Check if there is already an authentication session 538 if self.hasAuthSession(keycard): 539 # Updating the authentication session data 540 self._updateInfoFromKeycard(keycard) 541 else: 542 keycard.state = keycards.REFUSED
543
544 - def cancelAuthSession(self, keycard):
545 """ 546 Cancels the authentication session associated 547 with the specified keycard. 548 Used when doing challenge/response authentication. 549 @raise KeyError: when there is no session associated with the keycard. 550 """ 551 keycard.state = keycards.REFUSED 552 del self._sessions[keycard.id]
553
554 - def confirmAuthSession(self, keycard):
555 """ 556 Confirms the authentication session represented 557 by the specified keycard is authenticated. 558 This will add the specified keycard to the 559 bouncer keycard list like addKeycard would do 560 but without changing the keycard id. 561 The authentication session data is cleaned up. 562 563 If the bouncer already have a keycard with the same id, 564 the authentication is confirmed but the bouncer keycard 565 is NOT updated. FIXME: is it what we want ? ? ? 566 567 @param keycard: the keycard to add to the bouncer list. 568 @type keycard: flumotion.common.keycards.Keycard 569 @return: if the bouncer accepts the keycard. 570 """ 571 keycardId = keycard.id 572 573 if keycardId not in self._sessions: 574 self.warning("unknown authentication session, or pending keycard " 575 "expired for id %s", keycardId) 576 keycard.state = keycards.REFUSED 577 return False 578 579 del self._sessions[keycardId] 580 581 # Check if there already an authenticated keycard with the same id 582 if keycardId in self._keycards: 583 self.debug("confirming an authentication session we already " 584 "know about with id %s", keycardId) 585 keycard.state = keycards.AUTHENTICATED 586 return True 587 588 # check if the keycard already expired 589 if hasattr(keycard, 'ttl') and keycard.ttl <= 0: 590 self.log('immediately expiring keycard %r', keycard) 591 keycard.state = keycards.REFUSED 592 return False 593 594 keycard.state = keycards.AUTHENTICATED 595 self._addKeycard(keycard) 596 return True
597
598 - def updateAuthSessionInfo(self, keycard, data):
599 """ 600 Updates the authentication session data. 601 Can be used bu subclasses to modify the data directly. 602 """ 603 ttl, _oldData = self._sessions.get(keycard.id, (None, None)) 604 if ttl is None: 605 ttl = getattr(keycard, 'ttl', None) 606 self._sessions[keycard.id] = (ttl, data)
607
608 - def do_expireKeycards(self, elapsed):
609 cont = Bouncer.do_expireKeycards(self, elapsed) 610 for sessionId, (ttl, data) in self._sessions.items(): 611 if ttl is not None: 612 ttl -= elapsed 613 self._sessions[sessionId] = (ttl, data) 614 if ttl <= 0: 615 del self._sessions[sessionId] 616 617 return cont and len(self._sessions) > 0
618
619 - def _updateInfoFromKeycard(self, keycard):
620 oldData = self.getAuthSessionInfo(keycard) 621 newData = self.do_extractKeycardInfo(keycard, oldData) 622 self.updateAuthSessionInfo(keycard, newData)
623 624
625 -class TrivialBouncer(Bouncer):
626 """ 627 A very trivial bouncer implementation. 628 629 Useful as a concrete bouncer class for which all users are 630 accepted whenever the bouncer is enabled. 631 """ 632 keycardClasses = (keycards.KeycardGeneric, ) 633
634 - def do_authenticate(self, keycard):
635 if self.addKeycard(keycard): 636 keycard.state = keycards.AUTHENTICATED 637 else: 638 keycard.state = keycards.REFUSED 639 return keycard
640 641
642 -class ChallengeResponseBouncer(AuthSessionBouncer):
643 """ 644 A base class for Challenge-Response bouncers 645 """ 646 647 challengeResponseClasses = () 648
649 - def init(self):
650 self._checker = None 651 self._challenges = {} 652 self._db = {}
653
654 - def setChecker(self, checker):
655 self._checker = checker
656
657 - def addUser(self, user, salt, *args):
658 self._db[user] = salt 659 self._checker.addUser(user, *args)
660
661 - def do_extractKeycardInfo(self, keycard, oldData):
662 return getattr(keycard, 'challenge', None)
663
664 - def _requestAvatarIdCallback(self, PossibleAvatarId, keycard):
665 if not self.hasAuthSession(keycard): 666 # The session expired 667 keycard.state = keycards.REFUSED 668 return None 669 670 # authenticated, so return the keycard with state authenticated 671 if not keycard.avatarId: 672 keycard.avatarId = PossibleAvatarId 673 self.info('authenticated login of "%s"' % keycard.avatarId) 674 self.debug('keycard %r authenticated, id %s, avatarId %s' % ( 675 keycard, keycard.id, keycard.avatarId)) 676 self.confirmAuthSession(keycard) 677 keycard.state = keycards.AUTHENTICATED 678 return keycard
679
680 - def _requestAvatarIdErrback(self, failure, keycard):
681 if not self.hasAuthSession(keycard): 682 # The session expired 683 keycard.state = keycards.REFUSED 684 return None 685 686 failure.trap(errors.NotAuthenticatedError) 687 # FIXME: we want to make sure the "None" we return is returned 688 # as coming from a callback, ie the deferred 689 self.info('keycard %r refused, Unauthorized' % keycard) 690 self.cancelAuthSession(keycard) 691 keycard.state = keycards.REFUSED 692 return None
693
694 - def do_authenticate(self, keycard):
695 if isinstance(keycard, self.challengeResponseClasses): 696 # Check if we need to challenge it 697 if not self.hasAuthSession(keycard): 698 if not self.startAuthSession(keycard): 699 # Keycard refused right away 700 keycard.state = keycards.REFUSED 701 return None 702 self.debug('putting challenge on keycard %r' % keycard) 703 keycard.challenge = credentials.cryptChallenge() 704 if keycard.username in self._db: 705 keycard.salt = self._db[keycard.username] 706 else: 707 # random-ish salt, otherwise it's too obvious 708 string = str(random.randint(pow(10, 10), pow(10, 11))) 709 md = python.md5() 710 md.update(string) 711 keycard.salt = md.hexdigest()[:2] 712 self.debug("user not found, inventing bogus salt") 713 self.debug("salt %s, storing challenge for id %s" 714 % (keycard.salt, keycard.id)) 715 self.updateAuthSession(keycard) 716 return keycard 717 else: 718 # Check if the challenge has been tampered with 719 challenge = self.getAuthSessionInfo(keycard) 720 if challenge != keycard.challenge: 721 self.info('keycard %r refused, challenge tampered with' 722 % keycard) 723 self.cancelAuthSession(keycard) 724 keycard.state = keycards.REFUSED 725 return None 726 else: 727 # Not a challenge/response authentication. 728 # creating a temporary session to have a keycard id 729 if not self.startAuthSession(keycard): 730 # Keycard refused right away 731 keycard.state = keycards.REFUSED 732 return None 733 734 # use the checker 735 self.debug('submitting keycard %r to checker' % keycard) 736 d = self._checker.requestAvatarId(keycard) 737 d.addCallback(self._requestAvatarIdCallback, keycard) 738 d.addErrback(self._requestAvatarIdErrback, keycard) 739 return d
740