1 import logging
2 from datetime import datetime
3
4 from turbogears import config, identity
5 from turbogears.database import bind_metadata, metadata, session
6 from turbogears.util import load_class
7 from turbojson.jsonify import jsonify_saobject, jsonify
8
9 from sqlalchemy import (Table, Column, ForeignKey,
10 String, Unicode, Integer, DateTime)
11 from sqlalchemy.orm import class_mapper, mapper, relation
12 try:
13 from sqlalchemy.exc import IntegrityError
14 except ImportError:
15 from sqlalchemy.exceptions import IntegrityError
16 try:
17 from sqlalchemy.orm.exc import UnmappedClassError
18 except ImportError:
19 from sqlalchemy.exceptions import InvalidRequestError as UnmappedClassError
20
21 log = logging.getLogger('turbogears.identity.saprovider')
22
23
24
25
26 user_class = None
27 group_class = None
28 permission_class = None
29 visit_class = None
33 """Identity that uses a model from a database (via SQLAlchemy)."""
34
35 - def __init__(self, visit_key=None, user=None):
41
42 @property
44 """Get user instance for this identity."""
45 try:
46 return self._user
47 except AttributeError:
48
49 pass
50
51
52 visit = self.visit_link
53 try:
54 self._user = visit and session.query(user_class).get(visit.user_id)
55 except TypeError:
56
57 return None
58 return self._user
59
60 @property
62 """Get user name of this identity."""
63 if not self.user:
64 return None
65 return self.user.user_name
66
67 @property
69 """Get user id of this identity."""
70 if not self.user:
71 return None
72 return self.user.user_id
73
74 @property
76 """Return true if not logged in."""
77 return not self.user
78
79 @property
81 """Get set of permission names of this identity."""
82 try:
83 return self._permissions
84 except AttributeError:
85
86 pass
87 if not self.user:
88 self._permissions = frozenset()
89 else:
90 self._permissions = frozenset(
91 p.permission_name for p in self.user.permissions)
92 return self._permissions
93
94 @property
96 """Get set of group names of this identity."""
97 try:
98 return self._groups
99 except AttributeError:
100
101 pass
102 if not self.user:
103 self._groups = frozenset()
104 else:
105 self._groups = frozenset(g.group_name for g in self.user.groups)
106 return self._groups
107
108 @property
110 """Get set of group IDs of this identity."""
111 try:
112 return self._group_ids
113 except AttributeError:
114
115 pass
116 if not self.user:
117 self._group_ids = frozenset()
118 else:
119 self._group_ids = frozenset(g.group_id for g in self.user.groups)
120 return self._group_ids
121
122 @property
129
130 @property
134
151
160
163 """IdentityProvider that uses a model from a database (via SQLAlchemy)."""
164
166 super(SqlAlchemyIdentityProvider, self).__init__()
167 glob_ns = globals()
168
169 for classname in ('user', 'group', 'permission', 'visit'):
170 default_classname = '.TG_' + (classname == 'visit'
171 and 'VisitIdentity' or classname.capitalize())
172 class_path = config.get('identity.saprovider.model.%s' % classname,
173 __name__ + default_classname)
174 class_ = load_class(class_path)
175 if class_:
176 if class_path == __name__ + default_classname:
177 try:
178 class_mapper(class_)
179 except UnmappedClassError:
180 class_._map()
181 log.info('Successfully loaded "%s".', class_path)
182 glob_ns['%s_class' % classname] = class_
183 else:
184 log.error('Could not load class "%s". Check '
185 'identity.saprovider.model.%s setting', class_path, classname)
186
191
203
227
229 """Check the user_name and password against existing credentials.
230
231 Note: user_name is not used here, but is required by external
232 password validation schemes that might override this method.
233 If you use SqlAlchemyIdentityProvider, but want to check the passwords
234 against an external source (i.e. PAM, LDAP, Windows domain, etc),
235 subclass SqlAlchemyIdentityProvider, and override this method.
236
237 """
238 return user.password == self.encrypt_password(password)
239
241 """Lookup the principal represented by user_name.
242
243 Return None if there is no principal for the given user ID.
244
245 Must return an object with the following properties:
246 user_name: original user name
247 user: a provider dependent object (TG_User or similar)
248 groups: a set of group names
249 permissions: a set of permission names
250
251 """
252 return SqlAlchemyIdentity(visit_key)
253
255 """Return anonymous identity.
256
257 Must return an object with the following properties:
258 user_name: original user name
259 user: a provider dependent object (TG_User or similar)
260 groups: a set of group names
261 permissions: a set of permission names
262
263 """
264 return SqlAlchemyIdentity()
265
269
270
271
272
273
274 -class TG_User(object):
275 """Reasonably basic User definition."""
276
280
283
284 @property
286 """Return all permissions of all groups the user belongs to."""
287 p = set()
288 for g in self.groups:
289 p |= set(g.permissions)
290 return p
291
292 @classmethod
295
296 @classmethod
299 by_name = by_user_name
300
317
319 """Returns password."""
320 return self._password
321
322 password = property(_get_password, _set_password)
323
329
330 @classmethod
332 cls._table = Table('tg_user', metadata,
333 Column('user_id', Integer, primary_key=True),
334 Column('user_name', Unicode(16), unique=True, nullable=False),
335 Column('email_address', Unicode(255), unique=True),
336 Column('display_name', Unicode(255)),
337 Column('password', Unicode(40)),
338 Column('created', DateTime, default=datetime.now))
339 cls._mapper = mapper(cls, cls._table,
340 properties=dict(_password=cls._table.c.password))
341
342 @jsonify.when('isinstance(obj, TG_User)')
343 -def jsonify_user(obj):
344 """Convert user to JSON."""
345 result = jsonify_saobject(obj)
346 result.pop('password', None)
347 result.pop('_password', None)
348 result['groups'] = [g.group_name for g in obj.groups]
349 result['permissions'] = [p.permission_name for p in obj.permissions]
350 return result
351
354 """An ultra-simple Group definition."""
355
359
362
363 @classmethod
367 by_name = by_group_name
368
369 @classmethod
371 cls._table = Table('tg_group', metadata,
372 Column('group_id', Integer, primary_key=True),
373 Column('group_name', Unicode(16), unique=True, nullable=False),
374 Column('display_name', Unicode(255)),
375 Column('created', DateTime, default=datetime.now))
376 cls._user_association_table = Table('user_group', metadata,
377 Column('user_id', Integer, ForeignKey('tg_user.user_id',
378 onupdate='CASCADE', ondelete='CASCADE'), primary_key=True),
379 Column('group_id', Integer, ForeignKey('tg_group.group_id',
380 onupdate='CASCADE', ondelete='CASCADE'), primary_key=True))
381 cls._mapper = mapper(cls, cls._table,
382 properties=dict(users=relation(TG_User,
383 secondary=cls._user_association_table, backref='groups')))
384
385 @jsonify.when('isinstance(obj, TG_Group)')
386 -def jsonify_group(obj):
387 """Convert group to JSON."""
388 result = jsonify_saobject(obj)
389 result['users'] = [u.user_name for u in obj.users]
390 result['permissions'] = [p.permission_name for p in obj.permissions]
391 return result
392
395 """A relationship that determines what each Group can do."""
396
399
402
403 @classmethod
407 by_name = by_permission_name
408
409 @classmethod
411 cls._table = Table('permission', metadata,
412 Column('permission_id', Integer, primary_key=True),
413 Column('permission_name', Unicode(16), unique=True, nullable=False),
414 Column('description', Unicode(255)))
415 cls._group_association_table = Table('group_permission', metadata,
416 Column('group_id', Integer, ForeignKey('tg_group.group_id',
417 onupdate='CASCADE', ondelete='CASCADE'), primary_key=True),
418 Column('permission_id',
419 Integer, ForeignKey('permission.permission_id',
420 onupdate='CASCADE', ondelete='CASCADE'), primary_key=True))
421 cls._mapper = mapper(cls, cls._table,
422 properties=dict(groups=relation(TG_Group,
423 secondary=cls._group_association_table, backref='permissions')))
424
427 """Convert permissions to JSON."""
428 result = jsonify_saobject(obj)
429 result['groups'] = [g.group_name for g in obj.groups]
430 return result
431
434 """A Visit that is linked to a User object."""
435
436 @classmethod
438 """Look up VisitIdentity by given visit key."""
439 return session.query(cls).get(visit_key)
440
441 @classmethod
443 cls._table = Table('visit_identity', metadata,
444 Column('visit_key', String(40), primary_key=True),
445 Column('user_id', Integer,
446 ForeignKey('tg_user.user_id'), index=True))
447 cls._mapper = mapper(cls, cls._table,
448 properties=dict(user=relation(TG_User, backref='visit_identity')))
449
464