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

Source Code for Module flumotion.component.bouncers.bouncer

  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 Fluendo, S.L. (www.fluendo.com). 
  6  # All rights reserved. 
  7   
  8  # This file may be distributed and/or modified under the terms of 
  9  # the GNU General Public License version 2 as published by 
 10  # the Free Software Foundation. 
 11  # This file is distributed without any warranty; without even the implied 
 12  # warranty of merchantability or fitness for a particular purpose. 
 13  # See "LICENSE.GPL" in the source distribution for more information. 
 14   
 15  # Licensees having purchased or holding a valid Flumotion Advanced 
 16  # Streaming Server license may use this file in accordance with the 
 17  # Flumotion Advanced Streaming Server Commercial License Agreement. 
 18  # See "LICENSE.Flumotion" in the source distribution for more information. 
 19   
 20  # Headers in this file shall remain intact. 
 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   
42 -class BouncerMedium(component.BaseComponentMedium):
43 44 logCategory = 'bouncermedium'
45 - def remote_authenticate(self, keycard):
46 """ 47 Authenticates the given keycard. 48 49 @type keycard: L{flumotion.common.keycards.Keycard} 50 """ 51 return self.comp.authenticate(keycard)
52
53 - def remote_removeKeycardId(self, keycardId):
54 try: 55 self.comp.removeKeycardId(keycardId) 56 # FIXME: at least have an exception name please 57 except KeyError: 58 self.warning('Could not remove keycard id %s' % keycardId)
59
60 - def remote_expireKeycardId(self, keycardId):
61 """ 62 Called by bouncer views to expire keycards. 63 """ 64 return self.comp.expireKeycardId(keycardId)
65
66 - def remote_setEnabled(self, enabled):
67 return self.comp.setEnabled(enabled)
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
82 - def init(self):
83 self._idCounter = 0 84 self._keycards = {} # keycard id -> Keycard 85 self._keycardDatas = {} # keycard id -> data in uiState 86 self.uiState.addListKey('keycards') 87 88 self.enabled = True
89
90 - def setDomain(self, name):
91 self.domain = name
92
93 - def getDomain(self):
94 return self.domain
95
96 - def typeAllowed(self, keycard):
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
103 - def setEnabled(self, enabled):
104 if not enabled and self.enabled: 105 # If we were enabled and are being set to disabled, eject the warp 106 # core^w^w^w^wexpire all existing keycards 107 self.expireAllKeycards() 108 109 self.enabled = enabled
110
111 - def authenticate(self, keycard):
112 if not self.typeAllowed(keycard): 113 self.warning('keycard %r is not an allowed keycard class', keycard) 114 return None 115 116 if self.enabled: 117 return self.do_authenticate(keycard) 118 else: 119 self.debug("Bouncer disabled, refusing authentication") 120 return None
121
122 - def do_authenticate(self, keycard):
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
134 - def hasKeycard(self, keycard):
135 return keycard in self._keycards.values()
136
137 - def addKeycard(self, keycard):
138 # give keycard an id and store it in our hash 139 if self._keycards.has_key(keycard.id): 140 # already in there 141 return 142 143 # FIXME: what if it already had one ? 144 # FIXME: deal with wraparound ? 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
156 - def removeKeycard(self, keycard):
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
168 - def removeKeycardId(self, id):
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
176 - def expireAllKeycards(self):
177 return defer.DeferredList( 178 [self.expireKeycardId(id) for id in self._keycards])
179
180 - def expireKeycardId(self, id):
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 # we don't need to remove the keycard ourselves, since that's done 190 # by the requester when the client is definately gone 191 192 return d
193
194 -class TrivialBouncer(Bouncer):
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
203 - def do_authenticate(self, keycard):
207
208 -class ChallengeResponseBouncer(Bouncer):
209 """ 210 A base class for Challenge-Response bouncers 211 """ 212 213 challengeResponseClasses = () 214
215 - def init(self):
216 self._checker = None 217 self._challenges = {} 218 self._db = {}
219
220 - def setChecker(self, checker):
221 self._checker = checker
222
223 - def addUser(self, user, salt, *args):
224 self._db[user] = salt 225 self._checker.addUser(user, *args)
226
227 - def _requestAvatarIdCallback(self, PossibleAvatarId, keycard):
228 # authenticated, so return the keycard with state authenticated 229 keycard.state = keycards.AUTHENTICATED 230 self.addKeycard(keycard) 231 if not keycard.avatarId: 232 keycard.avatarId = PossibleAvatarId 233 self.info('authenticated login of "%s"' % keycard.avatarId) 234 self.debug('keycard %r authenticated, id %s, avatarId %s' % ( 235 keycard, keycard.id, keycard.avatarId)) 236 237 return keycard
238
239 - def _requestAvatarIdErrback(self, failure, keycard):
240 failure.trap(error.UnauthorizedLogin) 241 # FIXME: we want to make sure the "None" we return is returned 242 # as coming from a callback, ie the deferred 243 self.removeKeycard(keycard) 244 self.info('keycard %r refused, Unauthorized' % keycard) 245 return None
246
247 - def do_authenticate(self, keycard):
248 # at this point we add it so there's an ID for challenge-response 249 self.addKeycard(keycard) 250 251 # check if the keycard is ready for the checker, based on the type 252 if isinstance(keycard, self.challengeResponseClasses): 253 # Check if we need to challenge it 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 # random-ish salt, otherwise it's too obvious 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 # we store the challenge locally to verify against tampering 269 self._challenges[keycard.id] = keycard.challenge 270 return keycard 271 272 if keycard.response: 273 # Check if the challenge has been tampered with 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 # use the checker 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