| 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 |
|---|