Package turbogears :: Package identity :: Module soprovider

Source Code for Module turbogears.identity.soprovider

  1  import logging 
  2  from datetime import datetime 
  3   
  4  from turbogears import config, identity 
  5  from turbogears.database import PackageHub 
  6  from turbogears.util import load_class 
  7  from turbojson.jsonify import jsonify_sqlobject, jsonify 
  8   
  9  from sqlobject import (SQLObject, SQLObjectNotFound, RelatedJoin, 
 10      DateTimeCol, IntCol, StringCol, UnicodeCol) 
 11  from sqlobject.dberrors import DuplicateEntryError 
 12   
 13   
 14  log = logging.getLogger('turbogears.identity.soprovider') 
 15   
 16  hub = PackageHub('turbogears.identity') 
 17  __connection__ = hub 
18 19 20 -def to_db_encoding(s, encoding):
21 if not isinstance(s, basestring) and hasattr(s, '__unicode__'): 22 s = unicode(s) 23 if isinstance(s, unicode): 24 s = s.encode(encoding) 25 return s
26 27 28 # Global class references -- 29 # these will be set when the provider is initialized. 30 user_class = None 31 group_class = None 32 permission_class = None 33 visit_class = None
34 35 36 -class SqlObjectIdentity(object):
37 """Identity that uses a model from a database (via SQLObject).""" 38
39 - def __init__(self, visit_key=None, user=None):
40 self.visit_key = visit_key 41 if user: 42 self._user = user 43 if visit_key is not None: 44 self.login()
45 46 @property
47 - def user(self):
48 """Get user instance for this identity.""" 49 try: 50 return self._user 51 except AttributeError: 52 # User hasn't already been set 53 pass 54 # Attempt to load the user. After this code executes, there *will* be 55 # a _user attribute, even if the value is None. 56 visit = self.visit_link 57 if visit: 58 try: 59 self._user = user_class.get(visit.user_id) 60 except SQLObjectNotFound: 61 log.warning("No such user with ID: %s", visit.user_id) 62 self._user = None 63 else: 64 self._user = None 65 return self._user
66 67 @property
68 - def user_name(self):
69 """Get user name of this identity.""" 70 if not self.user: 71 return None 72 return self.user.user_name
73 74 @property
75 - def user_id(self):
76 """Get user id of this identity.""" 77 if not self.user: 78 return None 79 return self.user.id
80 81 @property
82 - def anonymous(self):
83 """Return true if not logged in.""" 84 return not self.user
85 86 @property
87 - def permissions(self):
88 """Get set of permission names of this identity.""" 89 try: 90 return self._permissions 91 except AttributeError: 92 # Permissions haven't been computed yet 93 pass 94 if not self.user: 95 self._permissions = frozenset() 96 else: 97 self._permissions = frozenset( 98 p.permission_name for p in self.user.permissions) 99 return self._permissions
100 101 @property
102 - def groups(self):
103 """Get set of group names of this identity.""" 104 try: 105 return self._groups 106 except AttributeError: 107 # Groups haven't been computed yet 108 pass 109 if not self.user: 110 self._groups = frozenset() 111 else: 112 self._groups = frozenset(g.group_name for g in self.user.groups) 113 return self._groups
114 115 @property
116 - def group_ids(self):
117 """Get set of group IDs of this identity.""" 118 try: 119 return self._group_ids 120 except AttributeError: 121 # Groups haven't been computed yet 122 pass 123 if not self.user: 124 self._group_ids = frozenset() 125 else: 126 self._group_ids = frozenset(g.id for g in self.user.groups) 127 return self._group_ids
128 129 @property 138 139 @property
140 - def login_url(self):
141 """Get the URL for the login page.""" 142 return identity.get_failure_url()
143
144 - def login(self):
145 """Set the link between this identity and the visit.""" 146 visit = self.visit_link 147 if visit: 148 visit.user_id = self._user.id 149 else: 150 try: 151 visit = visit_class( 152 visit_key=self.visit_key, user_id=self._user.id) 153 except DuplicateEntryError: 154 visit = self.visit_link 155 if not visit: 156 raise 157 visit.user_id = self._user.id
158
159 - def logout(self):
160 """Remove the link between this identity and the visit.""" 161 visit = self.visit_link 162 if visit: 163 visit.destroySelf() 164 # Clear the current identity 165 identity.set_current_identity(SqlObjectIdentity())
166
167 168 -class SqlObjectIdentityProvider(object):
169 """IdentityProvider that uses a model from a database (via SQLObject).""" 170
171 - def __init__(self):
172 super(SqlObjectIdentityProvider, self).__init__() 173 glob_ns = globals() 174 175 for classname in ('user', 'group', 'permission', 'visit'): 176 default_classname = '.TG_' + (classname == 'visit' 177 and 'VisitIdentity' or classname.capitalize()) 178 class_path = config.get('identity.soprovider.model.%s' % classname, 179 __name__ + default_classname) 180 class_ = load_class(class_path) 181 if class_: 182 log.info('Successfully loaded "%s".', class_path) 183 glob_ns['%s_class' % classname] = class_ 184 else: 185 log.error('Could not load class "%s".' 186 ' Check identity.soprovider.model.%s setting', 187 class_path, classname) 188 try: 189 encoding = glob_ns[ 190 'user_class'].sqlmeta.columns['user_name'].dbEncoding 191 except (KeyError, AttributeError): 192 encoding = None 193 self.user_class_db_encoding = encoding or 'utf-8'
194
195 - def encrypt_password(self, password):
196 # Default encryption algorithm is to use plain text passwords 197 algorithm = config.get('identity.soprovider.encryption_algorithm', None) 198 return identity.encrypt_pw_with_algorithm(algorithm, password)
199
200 - def create_provider_model(self):
201 """Create the database tables if they don't already exist.""" 202 try: 203 hub.begin() 204 user_class.createTable(ifNotExists=True) 205 group_class.createTable(ifNotExists=True) 206 permission_class.createTable(ifNotExists=True) 207 visit_class.createTable(ifNotExists=True) 208 hub.commit() 209 hub.end() 210 except KeyError: 211 log.warning("No database is configured:" 212 " SqlObjectIdentityProvider is disabled.") 213 return
214
215 - def validate_identity(self, user_name, password, visit_key):
216 """Validate the identity represented by user_name using the password. 217 218 Must return either None if the credentials weren't valid or an object 219 with the following properties: 220 user_name: original user name 221 user: a provider dependent object (TG_User or similar) 222 groups: a set of group names 223 permissions: a set of permission names 224 225 """ 226 try: 227 user_name = to_db_encoding(user_name, self.user_class_db_encoding) 228 user = user_class.by_user_name(user_name) 229 if not self.validate_password(user, user_name, password): 230 log.info("Passwords don't match for user: %s", user_name) 231 return None 232 log.info("Associating user (%s) with visit (%s)", 233 user_name, visit_key) 234 return SqlObjectIdentity(visit_key, user) 235 except SQLObjectNotFound: 236 log.warning("No such user: %s", user_name) 237 return None
238
239 - def validate_password(self, user, user_name, password):
240 """Check the user_name and password against existing credentials. 241 242 Note: user_name is not used here, but is required by external 243 password validation schemes that might override this method. 244 If you use SqlObjectIdentityProvider, but want to check the passwords 245 against an external source (i.e. PAM, a password file, Windows domain), 246 subclass SqlObjectIdentityProvider, and override this method. 247 248 """ 249 return user.password == self.encrypt_password(password)
250
251 - def load_identity(self, visit_key):
252 """Lookup the principal represented by user_name. 253 254 Return None if there is no principal for the given user ID. 255 256 Must return an object with the following properties: 257 user_name: original user name 258 user: a provider dependent object (TG_User or similar) 259 groups: a set of group names 260 permissions: a set of permission names 261 262 """ 263 return SqlObjectIdentity(visit_key)
264
265 - def anonymous_identity(self):
266 """Return anonymous identity. 267 268 Must return an object with the following properties: 269 user_name: original user name 270 user: a provider dependent object (TG_User or similar) 271 groups: a set of group names 272 permissions: a set of permission names 273 274 """ 275 return SqlObjectIdentity()
276
277 - def authenticated_identity(self, user):
278 """Constructs Identity object for users with no visit_key.""" 279 return SqlObjectIdentity(user=user)
280
281 282 -class TG_User(SQLObject):
283 """Reasonably basic User definition.""" 284 285 user_name = UnicodeCol(length=16, alternateID=True, 286 alternateMethodName='by_user_name') 287 email_address = UnicodeCol(length=255, alternateID=True, 288 alternateMethodName='by_email_address') 289 display_name = UnicodeCol(length=255) 290 password = UnicodeCol(length=40) 291 created = DateTimeCol(default=datetime.now) 292 293 # groups this user belongs to 294 groups = RelatedJoin('TG_Group', intermediateTable='user_group', 295 joinColumn='user_id', otherColumn='group_id') 296
297 - def _get_permissions(self):
298 perms = set() 299 for g in self.groups: 300 perms = perms | set(g.permissions) 301 return perms
302
303 - def _set_password(self, cleartext_password):
304 """Run cleartext_password through the hash algorithm before saving.""" 305 try: 306 hash = identity.current_provider.encrypt_password(cleartext_password) 307 except identity.exceptions.IdentityManagementNotEnabledException: 308 # Creating identity provider just to encrypt password 309 # (so we don't reimplement the encryption step). 310 ip = SqlObjectIdentityProvider() 311 hash = ip.encrypt_password(cleartext_password) 312 if hash == cleartext_password: 313 log.info("Identity provider not enabled," 314 " and no encryption algorithm specified in config." 315 " Setting password as plaintext.") 316 self._SO_set_password(hash)
317
318 - def set_password_raw(self, password):
319 """Save the password as-is to the database.""" 320 self._SO_set_password(password)
321
322 @jsonify.when('isinstance(obj, TG_User)') 323 -def jsonify_user(obj):
324 """Convert user to JSON.""" 325 result = jsonify_sqlobject(obj) 326 result.pop('password', None) 327 result['groups'] = [g.group_name for g in obj.groups] 328 result['permissions'] = [p.permission_name for p in obj.permissions] 329 return result
330
331 332 -class TG_Group(SQLObject):
333 """An ultra-simple group definition.""" 334 335 group_name = UnicodeCol(length=16, alternateID=True, 336 alternateMethodName='by_group_name') 337 display_name = UnicodeCol(length=255) 338 created = DateTimeCol(default=datetime.now) 339 340 # collection of all users belonging to this group 341 users = RelatedJoin('TG_User', intermediateTable='user_group', 342 joinColumn='group_id', otherColumn='user_id') 343 344 # collection of all permissions for this group 345 permissions = RelatedJoin('TG_Permission', joinColumn='group_id', 346 intermediateTable='group_permission', 347 otherColumn='permission_id')
348
349 @jsonify.when('isinstance(obj, TG_Group)') 350 -def jsonify_group(obj):
351 """Convert group to JSON.""" 352 result = jsonify_sqlobject(obj) 353 result['users'] = [u.user_name for u in obj.users] 354 result['permissions'] = [p.permission_name for p in obj.permissions] 355 return result
356
357 358 -class TG_Permission(SQLObject):
359 """Permissions for a given group.""" 360
361 - class sqlmeta:
362 table = 'permission'
363 364 permission_name = UnicodeCol(length=16, alternateID=True, 365 alternateMethodName='by_permission_name') 366 description = UnicodeCol(length=255) 367 368 groups = RelatedJoin('TG_Group', intermediateTable='group_permission', 369 joinColumn='permission_id', otherColumn='group_id')
370
371 @jsonify.when('isinstance(obj, TG_Permission)') 372 -def jsonify_permission(obj):
373 """Convert permissions to JSON.""" 374 result = jsonify_sqlobject(obj) 375 result['groups'] = [g.group_name for g in obj.groups] 376 return result
377
378 379 -class TG_VisitIdentity(SQLObject):
380 """A visit to your website.""" 381
382 - class sqlmeta:
383 table = 'visit_identity'
384 385 visit_key = StringCol(length=40, alternateID=True, 386 alternateMethodName='by_visit_key') 387 user_id = IntCol()
388
389 390 -def encrypt_password(cleartext_password):
391 """Encrypt given cleartext password.""" 392 try: 393 hash = identity.current_provider.encrypt_password(cleartext_password) 394 except identity.exceptions.RequestRequiredException: 395 # Creating identity provider just to encrypt password 396 # (so we don't reimplement the encryption step). 397 ip = SqlObjectIdentityProvider() 398 hash = ip.encrypt_password(cleartext_password) 399 if hash == cleartext_password: 400 log.info("Identity provider not enabled, and no encryption " 401 "algorithm specified in config. Setting password as plaintext.") 402 return hash
403