Project

General

Profile

Statistics
| Branch: | Tag: | Revision:

vigiboard / dashboard / model / auth.py @ 805cc54a

History | View | Annotate | Download (6.63 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 dashboard 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 dashboard.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
)
42

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

    
52

    
53
#{ The auth* model itself
54

    
55

    
56
class Group(DeclarativeBase):
57
    """
58
    Group definition for :mod:`repoze.what`.
59
    
60
    Only the ``group_name`` column is required by :mod:`repoze.what`.
61
    
62
    """
63
    
64
    __tablename__ = 'tg_group'
65
    
66
    #{ Columns
67
    
68
    group_id = Column(Integer, autoincrement=True, primary_key=True)
69
    
70
    group_name = Column(Unicode(16), unique=True, nullable=False)
71
    
72
    display_name = Column(Unicode(255))
73
    
74
    created = Column(DateTime, default=datetime.now)
75
    
76
    #{ Relations
77
    
78
    users = relation('User', secondary=user_group_table, backref='groups')
79
    
80
    #{ Special methods
81
    
82
    def __repr__(self):
83
        return '<Group: name=%s>' % self.group_name
84
    
85
    def __unicode__(self):
86
        return self.group_name
87
    
88
    #}
89

    
90

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

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

    
122
    def __repr__(self):
123
        return '<User: email="%s", display name="%s">' % (
124
                self.email_address, self.display_name)
125

    
126
    def __unicode__(self):
127
        return self.display_name or self.user_name
128
    
129
    #{ Getters and setters
130

    
131
    @property
132
    def permissions(self):
133
        """Return a set of strings for the permissions granted."""
134
        perms = set()
135
        for g in self.groups:
136
            perms = perms | set(g.permissions)
137
        return perms
138

    
139
    @classmethod
140
    def by_email_address(cls, email):
141
        """Return the user object whose email address is ``email``."""
142
        return DBSession.query(cls).filter(cls.email_address==email).first()
143

    
144
    @classmethod
145
    def by_user_name(cls, username):
146
        """Return the user object whose user name is ``username``."""
147
        return DBSession.query(cls).filter(cls.user_name==username).first()
148

    
149
    def _set_password(self, password):
150
        """Hash ``password`` on the fly and store its hashed version."""
151
        hashed_password = password
152
        
153
        if isinstance(password, unicode):
154
            password_8bit = password.encode('UTF-8')
155
        else:
156
            password_8bit = password
157

    
158
        salt = sha1()
159
        salt.update(os.urandom(60))
160
        hash = sha1()
161
        hash.update(password_8bit + salt.hexdigest())
162
        hashed_password = salt.hexdigest() + hash.hexdigest()
163

    
164
        # Make sure the hashed password is an UTF-8 object at the end of the
165
        # process because SQLAlchemy _wants_ a unicode object for Unicode
166
        # columns
167
        if not isinstance(hashed_password, unicode):
168
            hashed_password = hashed_password.decode('UTF-8')
169

    
170
        self._password = hashed_password
171

    
172
    def _get_password(self):
173
        """Return the hashed version of the password."""
174
        return self._password
175

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

192
        """
193
        hashed_pass = sha1()
194
        hashed_pass.update(password + self.password[:40])
195
        return self.password[40:] == hashed_pass.hexdigest()
196

    
197

    
198
class Permission(DeclarativeBase):
199
    """
200
    Permission definition for :mod:`repoze.what`.
201
    
202
    Only the ``permission_name`` column is required by :mod:`repoze.what`.
203
    
204
    """
205
    
206
    __tablename__ = 'tg_permission'
207
    
208
    #{ Columns
209

    
210
    permission_id = Column(Integer, autoincrement=True, primary_key=True)
211
    
212
    permission_name = Column(Unicode(16), unique=True, nullable=False)
213
    
214
    description = Column(Unicode(255))
215
    
216
    #{ Relations
217
    
218
    groups = relation(Group, secondary=group_permission_table,
219
                      backref='permissions')
220
    
221
    #{ Special methods
222
    
223
    def __repr__(self):
224
        return '<Permission: name=%s>' % self.permission_name
225

    
226
    def __unicode__(self):
227
        return self.permission_name
228
    
229
    #}
230

    
231

    
232
#}