pyramid-fullauth 0.3.0__py2.7.egg
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- EGG-INFO/PKG-INFO +176 -0
- EGG-INFO/SOURCES.txt +91 -0
- EGG-INFO/dependency_links.txt +1 -0
- EGG-INFO/not-zip-safe +1 -0
- EGG-INFO/requires.txt +17 -0
- EGG-INFO/top_level.txt +2 -0
- pyramid_fullauth/__init__.py +105 -0
- pyramid_fullauth/__init__.pyc +0 -0
- pyramid_fullauth/auth.py +66 -0
- pyramid_fullauth/auth.pyc +0 -0
- pyramid_fullauth/events.py +267 -0
- pyramid_fullauth/events.pyc +0 -0
- pyramid_fullauth/exceptions.py +47 -0
- pyramid_fullauth/exceptions.pyc +0 -0
- pyramid_fullauth/models/__init__.py +189 -0
- pyramid_fullauth/models/__init__.pyc +0 -0
- pyramid_fullauth/models/mixins/__init__.py +9 -0
- pyramid_fullauth/models/mixins/__init__.pyc +0 -0
- pyramid_fullauth/models/mixins/email.py +81 -0
- pyramid_fullauth/models/mixins/email.pyc +0 -0
- pyramid_fullauth/models/mixins/password.py +141 -0
- pyramid_fullauth/models/mixins/password.pyc +0 -0
- pyramid_fullauth/request.py +69 -0
- pyramid_fullauth/request.pyc +0 -0
- pyramid_fullauth/resources/helpers/social.mako +17 -0
- pyramid_fullauth/resources/locale/pyramid_fullauth.pot +216 -0
- pyramid_fullauth/resources/templates/403.mako +11 -0
- pyramid_fullauth/resources/templates/_form.login.mako +44 -0
- pyramid_fullauth/resources/templates/_form.register.mako +47 -0
- pyramid_fullauth/resources/templates/_social.login.mako +17 -0
- pyramid_fullauth/resources/templates/activate.mako +14 -0
- pyramid_fullauth/resources/templates/email_change.mako +33 -0
- pyramid_fullauth/resources/templates/layout.mako +13 -0
- pyramid_fullauth/resources/templates/login.mako +14 -0
- pyramid_fullauth/resources/templates/register.mako +22 -0
- pyramid_fullauth/resources/templates/reset.mako +34 -0
- pyramid_fullauth/resources/templates/reset.proceed.mako +41 -0
- pyramid_fullauth/routing/__init__.py +5 -0
- pyramid_fullauth/routing/__init__.pyc +0 -0
- pyramid_fullauth/routing/predicates.py +86 -0
- pyramid_fullauth/routing/predicates.pyc +0 -0
- pyramid_fullauth/tools.py +66 -0
- pyramid_fullauth/tools.pyc +0 -0
- pyramid_fullauth/views/__init__.py +15 -0
- pyramid_fullauth/views/__init__.pyc +0 -0
- pyramid_fullauth/views/basic/__init__.py +5 -0
- pyramid_fullauth/views/basic/__init__.pyc +0 -0
- pyramid_fullauth/views/basic/activate.py +44 -0
- pyramid_fullauth/views/basic/activate.pyc +0 -0
- pyramid_fullauth/views/basic/forbidden.py +47 -0
- pyramid_fullauth/views/basic/forbidden.pyc +0 -0
- pyramid_fullauth/views/basic/login.py +128 -0
- pyramid_fullauth/views/basic/login.pyc +0 -0
- pyramid_fullauth/views/basic/logout.py +28 -0
- pyramid_fullauth/views/basic/logout.pyc +0 -0
- pyramid_fullauth/views/basic/profile/__init__.py +5 -0
- pyramid_fullauth/views/basic/profile/__init__.pyc +0 -0
- pyramid_fullauth/views/basic/profile/email.py +107 -0
- pyramid_fullauth/views/basic/profile/email.pyc +0 -0
- pyramid_fullauth/views/basic/profile/password.py +128 -0
- pyramid_fullauth/views/basic/profile/password.pyc +0 -0
- pyramid_fullauth/views/basic/register.py +110 -0
- pyramid_fullauth/views/basic/register.pyc +0 -0
- pyramid_fullauth/views/social.py +218 -0
- pyramid_fullauth/views/social.pyc +0 -0
- tests/__init__.py +1 -0
- tests/__init__.pyc +0 -0
- tests/conftest.py +190 -0
- tests/conftest.pyc +0 -0
- tests/tools.py +54 -0
- tests/tools.pyc +0 -0
- tests/unit/test_default_settings.py +15 -0
- tests/unit/test_default_settings.pyc +0 -0
- tests/unit/test_groupfinder.py +20 -0
- tests/unit/test_groupfinder.pyc +0 -0
- tests/unit/test_social_view.py +65 -0
- tests/unit/test_social_view.pyc +0 -0
- tests/unit/test_tools.py +24 -0
- tests/unit/test_tools.pyc +0 -0
- tests/unit/test_user_active.py +31 -0
- tests/unit/test_user_active.pyc +0 -0
- tests/unit/test_user_admin.py +61 -0
- tests/unit/test_user_admin.pyc +0 -0
- tests/unit/test_user_email.py +37 -0
- tests/unit/test_user_email.pyc +0 -0
- tests/unit/test_user_password.py +52 -0
- tests/unit/test_user_password.pyc +0 -0
- tests/unit/test_user_providers.py +24 -0
- tests/unit/test_user_providers.pyc +0 -0
- tests/unit/test_user_repr.py +29 -0
- tests/unit/test_user_repr.pyc +0 -0
- tests/unit/test_validators.py +44 -0
- tests/unit/test_validators.pyc +0 -0
- tests/views/__init__.py +1 -0
- tests/views/__init__.pyc +0 -0
- tests/views/conftest.py +15 -0
- tests/views/conftest.pyc +0 -0
- tests/views/test_activation.py +83 -0
- tests/views/test_activation.pyc +0 -0
- tests/views/test_email.py +175 -0
- tests/views/test_email.pyc +0 -0
- tests/views/test_events.py +546 -0
- tests/views/test_events.pyc +0 -0
- tests/views/test_login.py +151 -0
- tests/views/test_login.pyc +0 -0
- tests/views/test_login_social.py +254 -0
- tests/views/test_login_social.pyc +0 -0
- tests/views/test_logout.py +42 -0
- tests/views/test_logout.pyc +0 -0
- tests/views/test_register.py +134 -0
- tests/views/test_register.pyc +0 -0
- tests/views/test_reset.py +100 -0
- tests/views/test_reset.pyc +0 -0
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
# Copyright (c) 2013 - 2014 by pyramid_fullauth authors and contributors <see AUTHORS file>
|
|
2
|
+
#
|
|
3
|
+
# This module is part of pyramid_fullauth and is released under
|
|
4
|
+
# the MIT License (MIT): http://opensource.org/licenses/MIT
|
|
5
|
+
|
|
6
|
+
"""pyramid_fullauth emits these events during whole cycle."""
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class _BaseRegisterEvent(object):
|
|
10
|
+
|
|
11
|
+
"""
|
|
12
|
+
Base fullauth event.
|
|
13
|
+
|
|
14
|
+
most of the fullauth's event will provide both request and user
|
|
15
|
+
object with some additional data (these will be described then).
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(self, request, user):
|
|
19
|
+
"""
|
|
20
|
+
Initialize event.
|
|
21
|
+
|
|
22
|
+
:param pyramid.request.Request request: request object
|
|
23
|
+
:param pyramid_fullauth.models.User user: user object
|
|
24
|
+
"""
|
|
25
|
+
self.request = request
|
|
26
|
+
self.user = user
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class BeforeRegister(_BaseRegisterEvent):
|
|
30
|
+
|
|
31
|
+
"""
|
|
32
|
+
Execute custom code at the start of registration process.
|
|
33
|
+
|
|
34
|
+
.. note::
|
|
35
|
+
|
|
36
|
+
User object is not yet in session.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(self, request, user, errors):
|
|
40
|
+
"""
|
|
41
|
+
Initialize event.
|
|
42
|
+
|
|
43
|
+
:param pyramid.request.Request request: request object
|
|
44
|
+
:param pyramid_fullauth.models.User user: user object
|
|
45
|
+
:param dict errors: a dictionary with wrong/not submitted fields
|
|
46
|
+
with format - fields for which error occured: error message
|
|
47
|
+
"""
|
|
48
|
+
_BaseRegisterEvent.__init__(self, request, user)
|
|
49
|
+
self.errors = errors
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class AfterRegister(_BaseRegisterEvent):
|
|
53
|
+
|
|
54
|
+
"""
|
|
55
|
+
Add custom post-processing code in registration process.
|
|
56
|
+
|
|
57
|
+
Can be used to add e.g. e-mail sending with registration links.
|
|
58
|
+
|
|
59
|
+
.. note::
|
|
60
|
+
User object is already in a session.
|
|
61
|
+
|
|
62
|
+
.. note::
|
|
63
|
+
Action emitting this event, should catch all HTTPFound that might be risen in event listener.
|
|
64
|
+
|
|
65
|
+
.. warning::
|
|
66
|
+
If HTTPFound is risen from event listener, then response_values will not be used!
|
|
67
|
+
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
def __init__(self, request, user, response_values):
|
|
71
|
+
"""
|
|
72
|
+
Initialize event.
|
|
73
|
+
|
|
74
|
+
:param pyramid.request.Request request: request object
|
|
75
|
+
:param pyramid_fullauth.models.User user: user object
|
|
76
|
+
:param dict response_values: a dictionary with response values
|
|
77
|
+
"""
|
|
78
|
+
_BaseRegisterEvent.__init__(self, request, user)
|
|
79
|
+
self.response_values = response_values
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class AfterActivate(_BaseRegisterEvent):
|
|
83
|
+
|
|
84
|
+
"""
|
|
85
|
+
Add custom post-processing logic after user gets activated.
|
|
86
|
+
|
|
87
|
+
.. note::
|
|
88
|
+
Action emitting this event, should catch all HTTPFound that might be risen in event listener.
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
pass
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class AfterResetRequest(_BaseRegisterEvent):
|
|
95
|
+
|
|
96
|
+
"""
|
|
97
|
+
Add custom post-processing after user sends request to reset password.
|
|
98
|
+
|
|
99
|
+
.. note::
|
|
100
|
+
Action emitting this event, should catch all HTTPFound that might be risen in event listener.
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
pass
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class BeforeReset(_BaseRegisterEvent):
|
|
107
|
+
|
|
108
|
+
"""Add custom pre-processing before the actual reset-password process."""
|
|
109
|
+
|
|
110
|
+
pass
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class AfterReset(_BaseRegisterEvent):
|
|
114
|
+
|
|
115
|
+
"""
|
|
116
|
+
Add custom post-processing after the actual reset-password process.
|
|
117
|
+
|
|
118
|
+
.. note::
|
|
119
|
+
Action emitting this event, should catch all HTTPFound that might be risen in event listener.
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
pass
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class AlreadyLoggedIn(object):
|
|
126
|
+
|
|
127
|
+
"""
|
|
128
|
+
Allow execute custom logic, when logged in user tries to log in again.
|
|
129
|
+
|
|
130
|
+
.. note::
|
|
131
|
+
Action emitting this event, should catch all HTTPFound that might be risen in event listener.
|
|
132
|
+
|
|
133
|
+
"""
|
|
134
|
+
|
|
135
|
+
def __init__(self, request):
|
|
136
|
+
"""
|
|
137
|
+
Initialize event.
|
|
138
|
+
|
|
139
|
+
:param pyramid.request.Request request: request object
|
|
140
|
+
"""
|
|
141
|
+
self.request = request
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class BeforeLogIn(_BaseRegisterEvent):
|
|
145
|
+
|
|
146
|
+
"""
|
|
147
|
+
Add custom logic before user gets logged in.
|
|
148
|
+
|
|
149
|
+
.. note::
|
|
150
|
+
Action emitting this event, should catch all AttributeError that might be risen in event listener.
|
|
151
|
+
User param set to None when user is not found or request method is GET.
|
|
152
|
+
"""
|
|
153
|
+
|
|
154
|
+
pass
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class AfterLogIn(_BaseRegisterEvent):
|
|
158
|
+
|
|
159
|
+
"""Add custom logic after user logs in."""
|
|
160
|
+
|
|
161
|
+
pass
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
# Social events
|
|
165
|
+
# TODO: extract to sub module
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class _BaseSocialRegister(_BaseRegisterEvent):
|
|
169
|
+
|
|
170
|
+
"""Base for all social requests."""
|
|
171
|
+
|
|
172
|
+
def __init__(self, request, user, profile):
|
|
173
|
+
"""
|
|
174
|
+
Initialize base events.
|
|
175
|
+
|
|
176
|
+
:param pyramid.request.Request request: request object
|
|
177
|
+
:param pyramid_fullauth.models.User user: user object
|
|
178
|
+
:param dict profile: a dictionary with profile data
|
|
179
|
+
"""
|
|
180
|
+
_BaseRegisterEvent.__init__(self, request, user)
|
|
181
|
+
self.profile = profile
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class BeforeSocialRegister(_BaseSocialRegister):
|
|
185
|
+
|
|
186
|
+
"""Adds custom logic before the social login process start."""
|
|
187
|
+
|
|
188
|
+
pass
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
class AfterSocialRegister(_BaseSocialRegister):
|
|
192
|
+
|
|
193
|
+
"""
|
|
194
|
+
Add custom logic after user registers through social network.
|
|
195
|
+
|
|
196
|
+
.. note::
|
|
197
|
+
Action emitting this event, should catch all HTTPFound that might be risen in event listener.
|
|
198
|
+
"""
|
|
199
|
+
|
|
200
|
+
def __init__(self, request, user, profile, response_values):
|
|
201
|
+
"""
|
|
202
|
+
Initialize event.
|
|
203
|
+
|
|
204
|
+
:param pyramid.request.Request request: request object
|
|
205
|
+
:param pyramid_fullauth.models.User user: user object
|
|
206
|
+
:param dict profile: a dictionary with profile data
|
|
207
|
+
:param dict response_values: a dictionary with response values
|
|
208
|
+
"""
|
|
209
|
+
_BaseSocialRegister.__init__(self, request, user, profile)
|
|
210
|
+
self.response_values = response_values
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
class AfterSocialLogIn(_BaseSocialRegister):
|
|
214
|
+
|
|
215
|
+
"""
|
|
216
|
+
Custom logic after user logs in through social network.
|
|
217
|
+
|
|
218
|
+
.. note::
|
|
219
|
+
Action emitting this event, should catch all HTTPFound that might be risen in event listener.
|
|
220
|
+
"""
|
|
221
|
+
|
|
222
|
+
pass
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
class SocialAccountAlreadyConnected(_BaseSocialRegister):
|
|
226
|
+
|
|
227
|
+
"""
|
|
228
|
+
Allow to add custom logic, when someone tries to connect social account to second user in application.
|
|
229
|
+
|
|
230
|
+
.. note::
|
|
231
|
+
Action emitting this event, should catch all HTTPFound that might be risen in event listener.
|
|
232
|
+
"""
|
|
233
|
+
|
|
234
|
+
def __init__(self, request, user, profile, response_values):
|
|
235
|
+
"""
|
|
236
|
+
Initialize event.
|
|
237
|
+
|
|
238
|
+
:param pyramid.request.Request request: request object
|
|
239
|
+
:param pyramid_fullauth.models.User user: user object
|
|
240
|
+
:param dict profile: a dictionary with profile data
|
|
241
|
+
:param dict response_values: a dictionary with response values
|
|
242
|
+
"""
|
|
243
|
+
_BaseSocialRegister.__init__(self, request, user, profile)
|
|
244
|
+
self.response_values = response_values
|
|
245
|
+
|
|
246
|
+
# Email change events.
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
class BeforeEmailChange(_BaseRegisterEvent):
|
|
250
|
+
|
|
251
|
+
"""Allow to add custom validation (like checking password) before email change process."""
|
|
252
|
+
|
|
253
|
+
pass
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
class AfterEmailChange(_BaseRegisterEvent):
|
|
257
|
+
|
|
258
|
+
"""Allow to add some custom post-processing, like e-mail sending, after email change process."""
|
|
259
|
+
|
|
260
|
+
pass
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
class AfterEmailChangeActivation(_BaseRegisterEvent):
|
|
264
|
+
|
|
265
|
+
"""Allow to add custom logic, after changed email had been activated."""
|
|
266
|
+
|
|
267
|
+
pass
|
|
Binary file
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Copyright (c) 2013 - 2014 by pyramid_fullauth authors and contributors <see AUTHORS file>
|
|
2
|
+
#
|
|
3
|
+
# This module is part of pyramid_fullauth and is released under
|
|
4
|
+
# the MIT License (MIT): http://opensource.org/licenses/MIT
|
|
5
|
+
"""Fullauth's exceptions definition."""
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class DeleteException(Exception):
|
|
9
|
+
|
|
10
|
+
"""Exception risen when the user can't be deleted."""
|
|
11
|
+
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ValidateError(ValueError):
|
|
16
|
+
|
|
17
|
+
"""Base of every validate error in pyramid_fullauth."""
|
|
18
|
+
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class EmptyError(ValidateError):
|
|
23
|
+
|
|
24
|
+
"""Thrown whenever user is trying to set empty value."""
|
|
25
|
+
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class ShortPasswordError(ValidateError):
|
|
30
|
+
|
|
31
|
+
"""Thrown when password doesn't meet the length requirement."""
|
|
32
|
+
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class PasswordConfirmMismatchError(ValidateError):
|
|
37
|
+
|
|
38
|
+
"""Thrown when there's a mismatch with cpassword_confirm."""
|
|
39
|
+
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class EmailValidationError(ValidateError):
|
|
44
|
+
|
|
45
|
+
"""Exception thrown, when there's incorrect email provided."""
|
|
46
|
+
|
|
47
|
+
pass
|
|
Binary file
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# Copyright (c) 2013 - 2014 by pyramid_fullauth authors and contributors <see AUTHORS file>
|
|
2
|
+
#
|
|
3
|
+
# This module is part of pyramid_fullauth and is released under
|
|
4
|
+
# the MIT License (MIT): http://opensource.org/licenses/MIT
|
|
5
|
+
"""Models needed for registration, and user servicing."""
|
|
6
|
+
|
|
7
|
+
import sys
|
|
8
|
+
import uuid
|
|
9
|
+
|
|
10
|
+
from sqlalchemy import (
|
|
11
|
+
Column, Unicode, String, Integer, Boolean, Sequence, DateTime,
|
|
12
|
+
Table, ForeignKey, UniqueConstraint
|
|
13
|
+
)
|
|
14
|
+
from sqlalchemy.sql import func
|
|
15
|
+
from sqlalchemy.orm import validates, relationship
|
|
16
|
+
from sqlalchemy.orm.session import object_session
|
|
17
|
+
from sqlalchemy.orm.util import has_identity
|
|
18
|
+
|
|
19
|
+
from pyramid_basemodel import Base
|
|
20
|
+
from pyramid_fullauth import exceptions
|
|
21
|
+
from pyramid_fullauth.models.mixins import UserPasswordMixin, UserEmailMixin
|
|
22
|
+
from datetime import datetime
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class User(UserPasswordMixin, UserEmailMixin, Base):
|
|
26
|
+
|
|
27
|
+
"""User object."""
|
|
28
|
+
|
|
29
|
+
__tablename__ = 'users'
|
|
30
|
+
|
|
31
|
+
id = Column(Integer, Sequence(__tablename__ + '_sq'), primary_key=True)
|
|
32
|
+
username = Column(Unicode(32), unique=True, nullable=True)
|
|
33
|
+
firstname = Column(Unicode(100), nullable=True)
|
|
34
|
+
lastname = Column(Unicode(100), nullable=True)
|
|
35
|
+
activate_key = Column(String(255), default=lambda: str(uuid.uuid4()), unique=True)
|
|
36
|
+
address_ip = Column(String(15), nullable=False)
|
|
37
|
+
|
|
38
|
+
registered_at = Column(DateTime, default=func.now(), nullable=False)
|
|
39
|
+
logged_at = Column(DateTime, default=func.now(), nullable=False)
|
|
40
|
+
activated_at = Column(DateTime, nullable=True)
|
|
41
|
+
deactivated_at = Column(DateTime, nullable=True)
|
|
42
|
+
deleted_at = Column(DateTime, nullable=True)
|
|
43
|
+
|
|
44
|
+
is_admin = Column(Boolean, default=False, nullable=False)
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def is_active(self):
|
|
48
|
+
"""
|
|
49
|
+
Check if user is active.
|
|
50
|
+
|
|
51
|
+
:returns: Returns False if user account is not active (or deleted).
|
|
52
|
+
:rtype: bool
|
|
53
|
+
|
|
54
|
+
"""
|
|
55
|
+
return not (self.deactivated_at or self.deleted_at or self.activate_key) and bool(self.activated_at)
|
|
56
|
+
|
|
57
|
+
@is_active.setter
|
|
58
|
+
def is_active(self, value):
|
|
59
|
+
"""
|
|
60
|
+
Set user as active/inactive.
|
|
61
|
+
|
|
62
|
+
:param bood value:
|
|
63
|
+
True - removes deactivated_at, deleted_at, activate_key and set activated_at to datetime now
|
|
64
|
+
False - set deactivated_at to now and activated_at to None
|
|
65
|
+
|
|
66
|
+
"""
|
|
67
|
+
# Allow to modify this only if object is in the persistent state to prevent "default values" errors/bugs.
|
|
68
|
+
# http://stackoverflow.com/questions/3885601/sqlalchemy-get-object-instance-state
|
|
69
|
+
if object_session(self) is not None and has_identity(self):
|
|
70
|
+
if value:
|
|
71
|
+
self.deactivated_at = None
|
|
72
|
+
self.deleted_at = None
|
|
73
|
+
self.activate_key = None
|
|
74
|
+
self.activated_at = datetime.now()
|
|
75
|
+
else:
|
|
76
|
+
self.deactivated_at = datetime.now()
|
|
77
|
+
self.activated_at = None
|
|
78
|
+
else:
|
|
79
|
+
raise AttributeError('User has to be in the persistent state - stored in the DB')
|
|
80
|
+
|
|
81
|
+
def provider_id(self, provider):
|
|
82
|
+
"""
|
|
83
|
+
Return provider identification for give user.
|
|
84
|
+
|
|
85
|
+
:param str provider: provider name
|
|
86
|
+
:returns: provider identification
|
|
87
|
+
:rtype: str
|
|
88
|
+
|
|
89
|
+
"""
|
|
90
|
+
for user_provider in self.providers:
|
|
91
|
+
if user_provider.provider == provider:
|
|
92
|
+
return user_provider.provider_id
|
|
93
|
+
|
|
94
|
+
def __repr__(self):
|
|
95
|
+
"""Object representation."""
|
|
96
|
+
return "<User ('{0}', '{1}')>".format(self.id, str(self))
|
|
97
|
+
|
|
98
|
+
def __unicode__(self):
|
|
99
|
+
"""Unicode cast rules."""
|
|
100
|
+
if self.username:
|
|
101
|
+
return self.username
|
|
102
|
+
elif self.email:
|
|
103
|
+
return self.email.split('@')[0] + '@...'
|
|
104
|
+
else:
|
|
105
|
+
return str(self.id)
|
|
106
|
+
|
|
107
|
+
def __str__(self): # pragma: no cover
|
|
108
|
+
"""String cast rules."""
|
|
109
|
+
if sys.version[0] == '3':
|
|
110
|
+
return self.__unicode__()
|
|
111
|
+
else:
|
|
112
|
+
return self.__unicode__().encode('utf-8')
|
|
113
|
+
|
|
114
|
+
@validates('is_admin')
|
|
115
|
+
def validate_is_admin(self, key, value):
|
|
116
|
+
"""
|
|
117
|
+
Validate is_admin value, we forbid the deletion of the last superadmin.
|
|
118
|
+
|
|
119
|
+
.. note::
|
|
120
|
+
|
|
121
|
+
More about simple validators: http://docs.sqlalchemy.org/en/latest/orm/mapper_config.html#simple-validators
|
|
122
|
+
|
|
123
|
+
:raises AttributeError: Information about an error
|
|
124
|
+
|
|
125
|
+
"""
|
|
126
|
+
if self.is_admin and not value:
|
|
127
|
+
admin_counter = object_session(self).query(User).filter(
|
|
128
|
+
User.is_admin, User.deleted_at.is_(None)
|
|
129
|
+
).count()
|
|
130
|
+
if admin_counter and admin_counter <= 1:
|
|
131
|
+
raise AttributeError('Can\'t delete last superadmin!')
|
|
132
|
+
return value
|
|
133
|
+
|
|
134
|
+
def delete(self):
|
|
135
|
+
"""
|
|
136
|
+
Perform soft delete action. along with checking if it's super admin, or not.
|
|
137
|
+
|
|
138
|
+
:rises pyramid_fullauth.exceptions.DeleteException: if you try to delete last super admin.
|
|
139
|
+
|
|
140
|
+
.. note::
|
|
141
|
+
You should use this method to delete users
|
|
142
|
+
|
|
143
|
+
"""
|
|
144
|
+
if self.is_admin:
|
|
145
|
+
admin_counter = object_session(self).query(User).filter(
|
|
146
|
+
User.is_admin, User.deleted_at.is_(None)
|
|
147
|
+
).count()
|
|
148
|
+
if admin_counter and admin_counter <= 1:
|
|
149
|
+
raise exceptions.DeleteException('Can\'t delete last superadmin!')
|
|
150
|
+
|
|
151
|
+
self.deleted_at = datetime.now()
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class Group(Base):
|
|
155
|
+
|
|
156
|
+
"""User group object."""
|
|
157
|
+
|
|
158
|
+
__tablename__ = 'groups'
|
|
159
|
+
|
|
160
|
+
id = Column(Integer, Sequence(__tablename__ + '_sq'), primary_key=True)
|
|
161
|
+
name = Column(Unicode(100), unique=True, nullable=False)
|
|
162
|
+
|
|
163
|
+
#: Relation to User object
|
|
164
|
+
users = relationship(User, secondary=lambda: user_group, backref='groups')
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
class AuthenticationProvider(Base):
|
|
168
|
+
|
|
169
|
+
"""Model to store authentication methods for different providers."""
|
|
170
|
+
|
|
171
|
+
__tablename__ = 'user_authentication_provider'
|
|
172
|
+
|
|
173
|
+
__table_args__ = (
|
|
174
|
+
UniqueConstraint('provider', 'provider_id', name='user_authentication_methods_provider_id'),
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
user_id = Column(Integer, ForeignKey(User.id), primary_key=True)
|
|
178
|
+
provider = Column(Unicode(15), primary_key=True)
|
|
179
|
+
provider_id = Column(String(255), nullable=False)
|
|
180
|
+
|
|
181
|
+
user = relationship(User, backref='providers')
|
|
182
|
+
|
|
183
|
+
#: Association table between User and Group models.
|
|
184
|
+
user_group = Table(
|
|
185
|
+
'users_groups',
|
|
186
|
+
Base.metadata,
|
|
187
|
+
Column('user_id', Integer, ForeignKey(User.id), primary_key=True),
|
|
188
|
+
Column('group_id', Integer, ForeignKey(Group.id), primary_key=True)
|
|
189
|
+
)
|
|
Binary file
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Copyright (c) 2013 - 2014 by pyramid_fullauth authors and contributors <see AUTHORS file>
|
|
2
|
+
#
|
|
3
|
+
# This module is part of pyramid_fullauth and is released under
|
|
4
|
+
# the MIT License (MIT): http://opensource.org/licenses/MIT
|
|
5
|
+
|
|
6
|
+
from pyramid_fullauth.models.mixins.password import UserPasswordMixin
|
|
7
|
+
from pyramid_fullauth.models.mixins.email import UserEmailMixin
|
|
8
|
+
|
|
9
|
+
__all__ = (UserPasswordMixin, UserEmailMixin)
|
|
Binary file
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Copyright (c) 2013 - 2014 by pyramid_fullauth authors and contributors <see AUTHORS file>
|
|
2
|
+
#
|
|
3
|
+
# This module is part of pyramid_fullauth and is released under
|
|
4
|
+
# the MIT License (MIT): http://opensource.org/licenses/MIT
|
|
5
|
+
"""Email mixin related module."""
|
|
6
|
+
|
|
7
|
+
import re
|
|
8
|
+
import uuid
|
|
9
|
+
|
|
10
|
+
from sqlalchemy import Column
|
|
11
|
+
from sqlalchemy import Unicode
|
|
12
|
+
from sqlalchemy import String
|
|
13
|
+
from sqlalchemy.orm import validates
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
from pyramid_fullauth.exceptions import EmptyError, EmailValidationError
|
|
17
|
+
|
|
18
|
+
pattern_mail = re.compile(
|
|
19
|
+
r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]{0,256}(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+){0,256}" # dot-atom
|
|
20
|
+
# quoted-string, see also http://tools.ietf.org/html/rfc2822#section-3.2.5
|
|
21
|
+
r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177 ]|\\[\001-\011\013\014\016-\177 ]){0,256}"'
|
|
22
|
+
r')@((?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2}\.?)$)' # domain
|
|
23
|
+
r'|\[(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}\]$', re.IGNORECASE) # literal form, ipv4 address (SMTP 4.1.3)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class UserEmailMixin(object):
|
|
27
|
+
|
|
28
|
+
"""User email fields and functionality."""
|
|
29
|
+
|
|
30
|
+
__pattern_mail = pattern_mail
|
|
31
|
+
|
|
32
|
+
email = Column(Unicode(254), unique=True, nullable=False) # RFC5321 and RFC3696(errata)
|
|
33
|
+
new_email = Column(Unicode(254), unique=True, nullable=True) # RFC5321 and RFC3696(errata)
|
|
34
|
+
email_change_key = Column(String(255), default=None, unique=True)
|
|
35
|
+
|
|
36
|
+
@validates('email', 'new_email')
|
|
37
|
+
def validate_email(self, key, address):
|
|
38
|
+
"""
|
|
39
|
+
Validate email addresses.
|
|
40
|
+
|
|
41
|
+
.. note::
|
|
42
|
+
|
|
43
|
+
See pyramid docs about `simple validators <http://docs.sqlalchemy.org/en/latest/orm/mapper_config.html#simple-validators>`_
|
|
44
|
+
|
|
45
|
+
:param str key: field key
|
|
46
|
+
:param str address: email address
|
|
47
|
+
|
|
48
|
+
:raises EmailValidationError:
|
|
49
|
+
:raises EmptyError:
|
|
50
|
+
"""
|
|
51
|
+
if address:
|
|
52
|
+
if pattern_mail.match(address):
|
|
53
|
+
return address
|
|
54
|
+
else:
|
|
55
|
+
raise EmailValidationError('Incorrect e-mail format')
|
|
56
|
+
|
|
57
|
+
raise EmptyError('E-mail is empty')
|
|
58
|
+
|
|
59
|
+
def set_new_email(self, email_new):
|
|
60
|
+
"""
|
|
61
|
+
Set new email and generate new email change hash.
|
|
62
|
+
|
|
63
|
+
:param str email_new: email address
|
|
64
|
+
|
|
65
|
+
:returns: generated email_change_key
|
|
66
|
+
:trype: str
|
|
67
|
+
|
|
68
|
+
"""
|
|
69
|
+
self.new_email = email_new
|
|
70
|
+
self.email_change_key = str(uuid.uuid4())
|
|
71
|
+
return self.email_change_key
|
|
72
|
+
|
|
73
|
+
def change_email(self):
|
|
74
|
+
"""
|
|
75
|
+
Change email after activation.
|
|
76
|
+
|
|
77
|
+
We don't clear new email field because of validator of email which won't allow to None value.
|
|
78
|
+
|
|
79
|
+
"""
|
|
80
|
+
self.email = self.new_email
|
|
81
|
+
self.email_change_key = None
|
|
Binary file
|