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_VisitIdentity(SQLObject):
283 """A visit to your website.""" 284
285 - class sqlmeta:
286 table = 'visit_identity'
287 288 visit_key = StringCol(length=40, alternateID=True, 289 alternateMethodName='by_visit_key') 290 user_id = IntCol()
291
292 293 -class TG_Group(SQLObject):
294 """An ultra-simple group definition.""" 295 296 group_name = UnicodeCol(length=16, alternateID=True, 297 alternateMethodName='by_group_name') 298 display_name = UnicodeCol(length=255) 299 created = DateTimeCol(default=datetime.now) 300 301 # collection of all users belonging to this group 302 users = RelatedJoin('TG_User', intermediateTable='user_group', 303 joinColumn='group_id', otherColumn='user_id') 304 305 # collection of all permissions for this group 306 permissions = RelatedJoin('TG_Permission', joinColumn='group_id', 307 intermediateTable='group_permission', 308 otherColumn='permission_id')
309
310 @jsonify.when('isinstance(obj, TG_Group)') 311 -def jsonify_group(obj):
312 """Convert group to JSON.""" 313 result = jsonify_sqlobject(obj) 314 result['users'] = [u.user_name for u in obj.users] 315 result['permissions'] = [p.permission_name for p in obj.permissions] 316 return result
317
318 319 -class TG_User(SQLObject):
320 """Reasonably basic User definition.""" 321 322 user_name = UnicodeCol(length=16, alternateID=True, 323 alternateMethodName='by_user_name') 324 email_address = UnicodeCol(length=255, alternateID=True, 325 alternateMethodName='by_email_address') 326 display_name = UnicodeCol(length=255) 327 password = UnicodeCol(length=40) 328 created = DateTimeCol(default=datetime.now) 329 330 # groups this user belongs to 331 groups = RelatedJoin('TG_Group', intermediateTable='user_group', 332 joinColumn='user_id', otherColumn='group_id') 333
334 - def _get_permissions(self):
335 perms = set() 336 for g in self.groups: 337 perms = perms | set(g.permissions) 338 return perms
339
340 - def _set_password(self, cleartext_password):
341 """Run cleartext_password through the hash algorithm before saving.""" 342 try: 343 hash = identity.current_provider.encrypt_password(cleartext_password) 344 except identity.exceptions.IdentityManagementNotEnabledException: 345 # Creating identity provider just to encrypt password 346 # (so we don't reimplement the encryption step). 347 ip = SqlObjectIdentityProvider() 348 hash = ip.encrypt_password(cleartext_password) 349 if hash == cleartext_password: 350 log.info("Identity provider not enabled," 351 " and no encryption algorithm specified in config." 352 " Setting password as plaintext.") 353 self._SO_set_password(hash)
354
355 - def set_password_raw(self, password):
356 """Save the password as-is to the database.""" 357 self._SO_set_password(password)
358
359 @jsonify.when('isinstance(obj, TG_User)') 360 -def jsonify_user(obj):
361 """Convert user to JSON.""" 362 result = jsonify_sqlobject(obj) 363 del result['password'] 364 result['groups'] = [g.group_name for g in obj.groups] 365 result['permissions'] = [p.permission_name for p in obj.permissions] 366 return result
367
368 369 -class TG_Permission(SQLObject):
370 """Permissions for a given group.""" 371
372 - class sqlmeta:
373 table = 'permission'
374 375 permission_name = UnicodeCol(length=16, alternateID=True, 376 alternateMethodName='by_permission_name') 377 description = UnicodeCol(length=255) 378 379 groups = RelatedJoin('TG_Group', intermediateTable='group_permission', 380 joinColumn='permission_id', otherColumn='group_id')
381
382 @jsonify.when('isinstance(obj, TG_Permission)') 383 -def jsonify_permission(obj):
384 """Convert permissions to JSON.""" 385 result = jsonify_sqlobject(obj) 386 result['groups'] = [g.group_name for g in obj.groups] 387 return result
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