Project

General

Profile

Statistics
| Branch: | Tag: | Revision:

vigiboard / vigiboard / model / auth.py @ 20367931

History | View | Annotate | Download (6.94 KB)

1
# -*- coding: utf-8 -*-
2
"""
3
Auth* related model.
4

5
This is where the models used by :mod:`repoze.who` and :mod:`repoze.what` are
6
defined.
7

8
It's perfectly fine to re-use this definition in the vigiboard application,
9
though.
10

11
"""
12
import os
13
from datetime import datetime
14
import sys
15
try:
16
    from hashlib import sha1
17
except ImportError:
18
    sys.exit('ImportError: No module named hashlib\n'
19
             'If you are on python2.4 this library is not part of python. '
20
             'Please install it. Example: easy_install hashlib')
21

    
22
from sqlalchemy import Table, ForeignKey, Column
23
from sqlalchemy.types import Unicode, Integer, DateTime
24
from sqlalchemy.orm import relation, synonym
25

    
26
from vigiboard.model import DeclarativeBase, metadata, DBSession
27

    
28
__all__ = ['User', 'Group', 'Permission']
29

    
30

    
31
#{ Association tables
32

    
33

    
34
# This is the association table for the many-to-many relationship between
35
# groups and permissions. This is required by repoze.what.
36
group_permission_table = Table('tg_group_permission', metadata,
37
    Column('group_id', Integer, ForeignKey('tg_group.group_id',
38
        onupdate="CASCADE", ondelete="CASCADE")),
39
    Column('permission_id', Integer, ForeignKey('tg_permission.permission_id',
40
        onupdate="CASCADE", ondelete="CASCADE")),
41
    mysql_engine='InnoDB',
42
    mysql_charset='utf8'
43
)
44

    
45
# This is the association table for the many-to-many relationship between
46
# groups and members - this is, the memberships. It's required by repoze.what.
47
user_group_table = Table('tg_user_group', metadata,
48
    Column('user_id', Integer, ForeignKey('tg_user.user_id',
49
        onupdate="CASCADE", ondelete="CASCADE")),
50
    Column('group_id', Integer, ForeignKey('tg_group.group_id',
51
        onupdate="CASCADE", ondelete="CASCADE")),
52
    mysql_engine='InnoDB',
53
    mysql_charset='utf8'
54
)
55

    
56

    
57
#{ The auth* model itself
58

    
59

    
60
class Group(DeclarativeBase):
61
    """
62
    Group definition for :mod:`repoze.what`.
63
    
64
    Only the ``group_name`` column is required by :mod:`repoze.what`.
65
    
66
    """
67
    
68
    __tablename__ = 'tg_group'
69
    __table_args__ = {'mysql_engine':'InnoDB','mysql_charset':'utf8'}
70

    
71
    #{ Columns
72
    
73
    group_id = Column(Integer, autoincrement=True, primary_key=True)
74
    
75
    group_name = Column(Unicode(16), unique=True, nullable=False)
76
    
77
    display_name = Column(Unicode(255))
78
    
79
    created = Column(DateTime, default=datetime.now)
80
    
81
    #{ Relations
82
    
83
    users = relation('User', secondary=user_group_table, backref='groups')
84
    
85
    #{ Special methods
86
    
87
    def __repr__(self):
88
        return '<Group: name=%s>' % self.group_name
89
    
90
    def __unicode__(self):
91
        return self.group_name
92
    
93
    #}
94

    
95

    
96
# The 'info' argument we're passing to the email_address and password columns
97
# contain metadata that Rum (http://python-rum.org/) can use generate an
98
# admin interface for your models.
99
class User(DeclarativeBase):
100
    """
101
    User definition.
102
    
103
    This is the user definition used by :mod:`repoze.who`, which requires at
104
    least the ``user_name`` column.
105
    
106
    """
107
    __tablename__ = 'tg_user'
108
    __table_args__ = {'mysql_engine':'InnoDB','mysql_charset':'utf8'}
109
    
110
    #{ Columns
111

    
112
    user_id = Column(Integer, autoincrement=True, primary_key=True)
113
    
114
    user_name = Column(Unicode(16), unique=True, nullable=False)
115
    
116
    email_address = Column(Unicode(255), unique=True, nullable=False,
117
                           info={'rum': {'field':'Email'}})
118
    
119
    display_name = Column(Unicode(255))
120
    
121
    _password = Column('password', Unicode(80),
122
                       info={'rum': {'field':'Password'}})
123
    
124
    created = Column(DateTime, default=datetime.now)
125
    
126
    #{ Special methods
127

    
128
    def __repr__(self):
129
        return '<User: email="%s", display name="%s">' % (
130
                self.email_address, self.display_name)
131

    
132
    def __unicode__(self):
133
        return self.display_name or self.user_name
134
    
135
    #{ Getters and setters
136

    
137
    @property
138
    def permissions(self):
139
        """Return a set of strings for the permissions granted."""
140
        perms = set()
141
        for g in self.groups:
142
            perms = perms | set(g.permissions)
143
        return perms
144

    
145
    @classmethod
146
    def by_email_address(cls, email):
147
        """Return the user object whose email address is ``email``."""
148
        return DBSession.query(cls).filter(cls.email_address==email).first()
149

    
150
    @classmethod
151
    def by_user_name(cls, username):
152
        """Return the user object whose user name is ``username``."""
153
        return DBSession.query(cls).filter(cls.user_name==username).first()
154

    
155
    def _set_password(self, password):
156
        """Hash ``password`` on the fly and store its hashed version."""
157
        hashed_password = password
158
        
159
        if isinstance(password, unicode):
160
            password_8bit = password.encode('UTF-8')
161
        else:
162
            password_8bit = password
163

    
164
        salt = sha1()
165
        salt.update(os.urandom(60))
166
        hash = sha1()
167
        hash.update(password_8bit + salt.hexdigest())
168
        hashed_password = salt.hexdigest() + hash.hexdigest()
169

    
170
        # Make sure the hashed password is an UTF-8 object at the end of the
171
        # process because SQLAlchemy _wants_ a unicode object for Unicode
172
        # columns
173
        if not isinstance(hashed_password, unicode):
174
            hashed_password = hashed_password.decode('UTF-8')
175

    
176
        self._password = hashed_password
177

    
178
    def _get_password(self):
179
        """Return the hashed version of the password."""
180
        return self._password
181

    
182
    password = synonym('_password', descriptor=property(_get_password,
183
                                                        _set_password))
184
    
185
    #}
186
    
187
    def validate_password(self, password):
188
        """
189
        Check the password against existing credentials.
190
        
191
        :param password: the password that was provided by the user to
192
            try and authenticate. This is the clear text version that we will
193
            need to match against the hashed one in the database.
194
        :type password: unicode object.
195
        :return: Whether the password is valid.
196
        :rtype: bool
197

198
        """
199
        hashed_pass = sha1()
200
        hashed_pass.update(password + self.password[:40])
201
        return self.password[40:] == hashed_pass.hexdigest()
202

    
203

    
204
class Permission(DeclarativeBase):
205
    """
206
    Permission definition for :mod:`repoze.what`.
207
    
208
    Only the ``permission_name`` column is required by :mod:`repoze.what`.
209
    
210
    """
211
    
212
    __tablename__ = 'tg_permission'
213
    __table_args__ = {'mysql_engine':'InnoDB','mysql_charset':'utf8'}
214

    
215
    #{ Columns
216

    
217
    permission_id = Column(Integer, autoincrement=True, primary_key=True)
218
    
219
    permission_name = Column(Unicode(16), unique=True, nullable=False)
220
    
221
    description = Column(Unicode(255))
222
    
223
    #{ Relations
224
    
225
    groups = relation(Group, secondary=group_permission_table,
226
                      backref='permissions')
227
    
228
    #{ Special methods
229
    
230
    def __repr__(self):
231
        return '<Permission: name=%s>' % self.permission_name
232

    
233
    def __unicode__(self):
234
        return self.permission_name
235
    
236
    #}
237

    
238

    
239
#}