[973dd91] | 1 | import sys |
---|
| 2 | try: |
---|
| 3 | from hashlib import sha1 |
---|
| 4 | except ImportError: |
---|
| 5 | sys.exit('ImportError: no module named hashlib\nIf you are on python2.4 this library is not part of python. Please install it. Example: easy_install hashlib') |
---|
| 6 | import os |
---|
| 7 | from datetime import datetime |
---|
| 8 | |
---|
| 9 | from sqlalchemy import Table, ForeignKey, Column |
---|
| 10 | from sqlalchemy.types import String, Unicode, UnicodeText, Integer, DateTime, \ |
---|
| 11 | Boolean, Float |
---|
| 12 | from sqlalchemy.orm import relation, backref, synonym |
---|
| 13 | |
---|
| 14 | from sipbmp3web.model import DeclarativeBase, metadata, DBSession |
---|
| 15 | |
---|
| 16 | |
---|
| 17 | # This is the association table for the many-to-many relationship between |
---|
| 18 | # groups and permissions. |
---|
| 19 | group_permission_table = Table('tg_group_permission', metadata, |
---|
| 20 | Column('group_id', Integer, ForeignKey('tg_group.group_id', |
---|
| 21 | onupdate="CASCADE", ondelete="CASCADE")), |
---|
| 22 | Column('permission_id', Integer, ForeignKey('tg_permission.permission_id', |
---|
| 23 | onupdate="CASCADE", ondelete="CASCADE")) |
---|
| 24 | ) |
---|
| 25 | |
---|
| 26 | # This is the association table for the many-to-many relationship between |
---|
| 27 | # groups and members - this is, the memberships. |
---|
| 28 | user_group_table = Table('tg_user_group', metadata, |
---|
| 29 | Column('user_id', Integer, ForeignKey('tg_user.user_id', |
---|
| 30 | onupdate="CASCADE", ondelete="CASCADE")), |
---|
| 31 | Column('group_id', Integer, ForeignKey('tg_group.group_id', |
---|
| 32 | onupdate="CASCADE", ondelete="CASCADE")) |
---|
| 33 | ) |
---|
| 34 | |
---|
| 35 | # auth model |
---|
| 36 | |
---|
| 37 | class Group(DeclarativeBase): |
---|
| 38 | """An ultra-simple group definition. |
---|
| 39 | """ |
---|
| 40 | __tablename__ = 'tg_group' |
---|
| 41 | |
---|
| 42 | group_id = Column(Integer, autoincrement=True, primary_key=True) |
---|
| 43 | group_name = Column(Unicode(16), unique=True, nullable=False) |
---|
| 44 | display_name = Column(Unicode(255)) |
---|
| 45 | created = Column(DateTime, default=datetime.now) |
---|
| 46 | users = relation('User', secondary=user_group_table, backref='groups') |
---|
| 47 | |
---|
| 48 | def __repr__(self): |
---|
| 49 | return '<Group: name=%s>' % self.group_name |
---|
| 50 | |
---|
| 51 | def __unicode__(self): |
---|
| 52 | return self.group_name |
---|
| 53 | |
---|
| 54 | # |
---|
| 55 | # The 'info' argument we're passing to the email_address and password columns |
---|
| 56 | # contain metadata that Rum (http://python-rum.org/) can use generate an |
---|
| 57 | # admin interface for your models. |
---|
| 58 | # |
---|
| 59 | class User(DeclarativeBase): |
---|
| 60 | """Reasonably basic User definition. Probably would want additional |
---|
| 61 | attributes. |
---|
| 62 | """ |
---|
| 63 | __tablename__ = 'tg_user' |
---|
| 64 | |
---|
| 65 | user_id = Column(Integer, autoincrement=True, primary_key=True) |
---|
| 66 | user_name = Column(Unicode(16), unique=True, nullable=False) |
---|
| 67 | email_address = Column(Unicode(255), unique=True, nullable=False, |
---|
| 68 | info={'rum': {'field':'Email'}}) |
---|
| 69 | display_name = Column(Unicode(255)) |
---|
| 70 | _password = Column('password', Unicode(80), |
---|
| 71 | info={'rum': {'field':'Password'}}) |
---|
| 72 | created = Column(DateTime, default=datetime.now) |
---|
| 73 | |
---|
| 74 | def __repr__(self): |
---|
| 75 | return '<User: email="%s", display name="%s">' % ( |
---|
| 76 | self.email_address, self.display_name) |
---|
| 77 | |
---|
| 78 | def __unicode__(self): |
---|
| 79 | return self.display_name or self.user_name |
---|
| 80 | |
---|
| 81 | @property |
---|
| 82 | def permissions(self): |
---|
| 83 | perms = set() |
---|
| 84 | for g in self.groups: |
---|
| 85 | perms = perms | set(g.permissions) |
---|
| 86 | return perms |
---|
| 87 | |
---|
| 88 | @classmethod |
---|
| 89 | def by_email_address(cls, email): |
---|
| 90 | """A class method that can be used to search users |
---|
| 91 | based on their email addresses since it is unique. |
---|
| 92 | """ |
---|
| 93 | return DBSession.query(cls).filter(cls.email_address==email).first() |
---|
| 94 | |
---|
| 95 | @classmethod |
---|
| 96 | def by_user_name(cls, username): |
---|
| 97 | """A class method that permits to search users |
---|
| 98 | based on their user_name attribute. |
---|
| 99 | """ |
---|
| 100 | return DBSession.query(cls).filter(cls.user_name==username).first() |
---|
| 101 | |
---|
| 102 | |
---|
| 103 | def _set_password(self, password): |
---|
| 104 | """Hash password on the fly.""" |
---|
| 105 | hashed_password = password |
---|
| 106 | |
---|
| 107 | if isinstance(password, unicode): |
---|
| 108 | password_8bit = password.encode('UTF-8') |
---|
| 109 | else: |
---|
| 110 | password_8bit = password |
---|
| 111 | |
---|
| 112 | salt = sha1() |
---|
| 113 | salt.update(os.urandom(60)) |
---|
| 114 | hash = sha1() |
---|
| 115 | hash.update(password_8bit + salt.hexdigest()) |
---|
| 116 | hashed_password = salt.hexdigest() + hash.hexdigest() |
---|
| 117 | |
---|
| 118 | # make sure the hased password is an UTF-8 object at the end of the |
---|
| 119 | # process because SQLAlchemy _wants_ a unicode object for Unicode columns |
---|
| 120 | if not isinstance(hashed_password, unicode): |
---|
| 121 | hashed_password = hashed_password.decode('UTF-8') |
---|
| 122 | |
---|
| 123 | self._password = hashed_password |
---|
| 124 | |
---|
| 125 | def _get_password(self): |
---|
| 126 | """returns password |
---|
| 127 | """ |
---|
| 128 | return self._password |
---|
| 129 | |
---|
| 130 | password = synonym('_password', descriptor=property(_get_password, |
---|
| 131 | _set_password)) |
---|
| 132 | |
---|
| 133 | def validate_password(self, password): |
---|
| 134 | """Check the password against existing credentials. |
---|
| 135 | |
---|
| 136 | :param password: the password that was provided by the user to |
---|
| 137 | try and authenticate. This is the clear text version that we will |
---|
| 138 | need to match against the hashed one in the database. |
---|
| 139 | :type password: unicode object. |
---|
| 140 | :return: Whether the password is valid. |
---|
| 141 | :rtype: bool |
---|
| 142 | |
---|
| 143 | """ |
---|
| 144 | hashed_pass = sha1() |
---|
| 145 | hashed_pass.update(password + self.password[:40]) |
---|
| 146 | return self.password[40:] == hashed_pass.hexdigest() |
---|
| 147 | |
---|
| 148 | |
---|
| 149 | class Permission(DeclarativeBase): |
---|
| 150 | """A relationship that determines what each Group can do |
---|
| 151 | """ |
---|
| 152 | __tablename__ = 'tg_permission' |
---|
| 153 | |
---|
| 154 | permission_id = Column(Integer, autoincrement=True, primary_key=True) |
---|
| 155 | permission_name = Column(Unicode(16), unique=True, nullable=False) |
---|
| 156 | description = Column(Unicode(255)) |
---|
| 157 | groups = relation(Group, secondary=group_permission_table, |
---|
| 158 | backref='permissions') |
---|
| 159 | |
---|
| 160 | def __unicode__(self): |
---|
| 161 | return self.permission_name |
---|