pyramid-fullauth 0.4.0__py3.4.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 +184 -0
- EGG-INFO/SOURCES.txt +93 -0
- EGG-INFO/dependency_links.txt +1 -0
- EGG-INFO/not-zip-safe +1 -0
- EGG-INFO/requires.txt +13 -0
- EGG-INFO/top_level.txt +2 -0
- pyramid_fullauth/__init__.py +106 -0
- pyramid_fullauth/__pycache__/__init__.cpython-34.pyc +0 -0
- pyramid_fullauth/__pycache__/auth.cpython-34.pyc +0 -0
- pyramid_fullauth/__pycache__/events.cpython-34.pyc +0 -0
- pyramid_fullauth/__pycache__/exceptions.cpython-34.pyc +0 -0
- pyramid_fullauth/__pycache__/request.cpython-34.pyc +0 -0
- pyramid_fullauth/__pycache__/tools.cpython-34.pyc +0 -0
- pyramid_fullauth/auth.py +67 -0
- pyramid_fullauth/config/default.yaml +26 -0
- pyramid_fullauth/events.py +281 -0
- pyramid_fullauth/exceptions.py +47 -0
- pyramid_fullauth/models/__init__.py +190 -0
- pyramid_fullauth/models/__pycache__/__init__.cpython-34.pyc +0 -0
- pyramid_fullauth/models/extensions/__init__.py +39 -0
- pyramid_fullauth/models/extensions/__pycache__/__init__.cpython-34.pyc +0 -0
- pyramid_fullauth/models/mixins/__init__.py +9 -0
- pyramid_fullauth/models/mixins/__pycache__/__init__.cpython-34.pyc +0 -0
- pyramid_fullauth/models/mixins/__pycache__/email.cpython-34.pyc +0 -0
- pyramid_fullauth/models/mixins/__pycache__/password.cpython-34.pyc +0 -0
- pyramid_fullauth/models/mixins/email.py +128 -0
- pyramid_fullauth/models/mixins/password.py +141 -0
- pyramid_fullauth/request.py +70 -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 +17 -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/__pycache__/__init__.cpython-34.pyc +0 -0
- pyramid_fullauth/routing/__pycache__/predicates.cpython-34.pyc +0 -0
- pyramid_fullauth/routing/predicates.py +86 -0
- pyramid_fullauth/tools.py +69 -0
- pyramid_fullauth/views/__init__.py +15 -0
- pyramid_fullauth/views/__pycache__/__init__.cpython-34.pyc +0 -0
- pyramid_fullauth/views/__pycache__/social.cpython-34.pyc +0 -0
- pyramid_fullauth/views/basic/__init__.py +5 -0
- pyramid_fullauth/views/basic/__pycache__/__init__.cpython-34.pyc +0 -0
- pyramid_fullauth/views/basic/__pycache__/activate.cpython-34.pyc +0 -0
- pyramid_fullauth/views/basic/__pycache__/forbidden.cpython-34.pyc +0 -0
- pyramid_fullauth/views/basic/__pycache__/login.cpython-34.pyc +0 -0
- pyramid_fullauth/views/basic/__pycache__/logout.cpython-34.pyc +0 -0
- pyramid_fullauth/views/basic/__pycache__/register.cpython-34.pyc +0 -0
- pyramid_fullauth/views/basic/activate.py +44 -0
- pyramid_fullauth/views/basic/forbidden.py +46 -0
- pyramid_fullauth/views/basic/login.py +128 -0
- pyramid_fullauth/views/basic/logout.py +28 -0
- pyramid_fullauth/views/basic/profile/__init__.py +5 -0
- pyramid_fullauth/views/basic/profile/__pycache__/__init__.cpython-34.pyc +0 -0
- pyramid_fullauth/views/basic/profile/__pycache__/email.cpython-34.pyc +0 -0
- pyramid_fullauth/views/basic/profile/__pycache__/password.cpython-34.pyc +0 -0
- pyramid_fullauth/views/basic/profile/email.py +108 -0
- pyramid_fullauth/views/basic/profile/password.py +129 -0
- pyramid_fullauth/views/basic/register.py +111 -0
- pyramid_fullauth/views/social.py +221 -0
- tests/__init__.py +1 -0
- tests/__pycache__/__init__.cpython-34.pyc +0 -0
- tests/__pycache__/conftest.cpython-34.pyc +0 -0
- tests/__pycache__/tools.cpython-34.pyc +0 -0
- tests/conftest.py +201 -0
- tests/tools.py +54 -0
- tests/unit/__pycache__/test_default_settings.cpython-34.pyc +0 -0
- tests/unit/__pycache__/test_groupfinder.cpython-34.pyc +0 -0
- tests/unit/__pycache__/test_social_view.cpython-34.pyc +0 -0
- tests/unit/__pycache__/test_tools.cpython-34.pyc +0 -0
- tests/unit/__pycache__/test_user_active.cpython-34.pyc +0 -0
- tests/unit/__pycache__/test_user_admin.cpython-34.pyc +0 -0
- tests/unit/__pycache__/test_user_email.cpython-34.pyc +0 -0
- tests/unit/__pycache__/test_user_password.cpython-34.pyc +0 -0
- tests/unit/__pycache__/test_user_providers.cpython-34.pyc +0 -0
- tests/unit/__pycache__/test_user_repr.cpython-34.pyc +0 -0
- tests/unit/__pycache__/test_validators.cpython-34.pyc +0 -0
- tests/unit/test_default_settings.py +15 -0
- tests/unit/test_groupfinder.py +20 -0
- tests/unit/test_social_view.py +69 -0
- tests/unit/test_tools.py +24 -0
- tests/unit/test_user_active.py +31 -0
- tests/unit/test_user_admin.py +61 -0
- tests/unit/test_user_email.py +37 -0
- tests/unit/test_user_password.py +52 -0
- tests/unit/test_user_providers.py +24 -0
- tests/unit/test_user_repr.py +29 -0
- tests/unit/test_validators.py +44 -0
- tests/views/__init__.py +1 -0
- tests/views/__pycache__/__init__.cpython-34.pyc +0 -0
- tests/views/__pycache__/conftest.cpython-34.pyc +0 -0
- tests/views/__pycache__/test_activation.cpython-34.pyc +0 -0
- tests/views/__pycache__/test_email.cpython-34.pyc +0 -0
- tests/views/__pycache__/test_events.cpython-34.pyc +0 -0
- tests/views/__pycache__/test_login.cpython-34.pyc +0 -0
- tests/views/__pycache__/test_login_social.cpython-34.pyc +0 -0
- tests/views/__pycache__/test_logout.cpython-34.pyc +0 -0
- tests/views/__pycache__/test_register.cpython-34.pyc +0 -0
- tests/views/__pycache__/test_reset.cpython-34.pyc +0 -0
- tests/views/conftest.py +20 -0
- tests/views/test_activation.py +86 -0
- tests/views/test_email.py +175 -0
- tests/views/test_events.py +558 -0
- tests/views/test_login.py +172 -0
- tests/views/test_login_social.py +289 -0
- tests/views/test_logout.py +42 -0
- tests/views/test_register.py +138 -0
- tests/views/test_reset.py +105 -0
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,108 @@
|
|
|
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 related views."""
|
|
6
|
+
|
|
7
|
+
from pyramid.view import view_config, view_defaults
|
|
8
|
+
from pyramid.httpexceptions import HTTPRedirection, HTTPSeeOther
|
|
9
|
+
from pyramid.security import NO_PERMISSION_REQUIRED
|
|
10
|
+
|
|
11
|
+
from sqlalchemy.orm.exc import NoResultFound
|
|
12
|
+
from pyramid.compat import text_type
|
|
13
|
+
import pyramid_basemodel
|
|
14
|
+
|
|
15
|
+
from pyramid_fullauth.views import BaseView
|
|
16
|
+
from pyramid_fullauth.models import User
|
|
17
|
+
|
|
18
|
+
from pyramid_fullauth.events import BeforeEmailChange
|
|
19
|
+
from pyramid_fullauth.events import AfterEmailChange
|
|
20
|
+
from pyramid_fullauth.events import AfterEmailChangeActivation
|
|
21
|
+
from pyramid_fullauth.exceptions import EmptyError, EmailValidationError
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@view_defaults(route_name='email:change', permission='email_change',
|
|
25
|
+
renderer='pyramid_fullauth:resources/templates/email_change.mako')
|
|
26
|
+
class EmailChangeViews(BaseView):
|
|
27
|
+
|
|
28
|
+
"""Email change related views."""
|
|
29
|
+
|
|
30
|
+
@view_config(request_method='GET')
|
|
31
|
+
def get(self):
|
|
32
|
+
"""Display email change request form."""
|
|
33
|
+
return {'status': True, 'csrf_token': self.request.session.get_csrf_token()}
|
|
34
|
+
|
|
35
|
+
@view_config(request_method='POST', check_csrf='csrf_token')
|
|
36
|
+
@view_config(request_method='POST', check_csrf='csrf_token', xhr=True, renderer="json")
|
|
37
|
+
def post(self):
|
|
38
|
+
"""Process email change request."""
|
|
39
|
+
csrf_token = self.request.session.get_csrf_token()
|
|
40
|
+
try:
|
|
41
|
+
pyramid_basemodel.Session.query(User).filter(
|
|
42
|
+
User.email == self.request.POST.get('email', '')
|
|
43
|
+
).one()
|
|
44
|
+
return {'status': False,
|
|
45
|
+
'msg': self.request._('User with this email exists',
|
|
46
|
+
domain='pyramid_fullauth'),
|
|
47
|
+
'csrf_token': csrf_token}
|
|
48
|
+
except NoResultFound:
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
user = self.request.user
|
|
52
|
+
try:
|
|
53
|
+
self.request.registry.notify(BeforeEmailChange(self.request, user))
|
|
54
|
+
except AttributeError as e:
|
|
55
|
+
return {'status': False, 'msg': text_type(e), 'csrf_token': csrf_token}
|
|
56
|
+
|
|
57
|
+
try:
|
|
58
|
+
user.set_new_email(self.request.POST.get('email', ''))
|
|
59
|
+
except EmptyError:
|
|
60
|
+
return {'status': False,
|
|
61
|
+
'msg': self.request._(
|
|
62
|
+
'E-mail is empty',
|
|
63
|
+
domain='pyramid_fullauth'),
|
|
64
|
+
'csrf_token': csrf_token}
|
|
65
|
+
except EmailValidationError:
|
|
66
|
+
return {'status': False,
|
|
67
|
+
'msg': self.request._(
|
|
68
|
+
'Incorrect e-mail format',
|
|
69
|
+
domain='pyramid_fullauth'),
|
|
70
|
+
'csrf_token': csrf_token}
|
|
71
|
+
|
|
72
|
+
response_values = {
|
|
73
|
+
'status': True,
|
|
74
|
+
'msg': self.request._('We sent you email to activate your new email address',
|
|
75
|
+
domain='pyramid_fullauth')
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
try:
|
|
79
|
+
self.request.registry.notify(AfterEmailChange(self.request, user))
|
|
80
|
+
except HTTPRedirection as redirect:
|
|
81
|
+
if self.request.is_xhr:
|
|
82
|
+
response_values['url'] = redirect.location
|
|
83
|
+
return response_values
|
|
84
|
+
else:
|
|
85
|
+
return redirect
|
|
86
|
+
else:
|
|
87
|
+
if self.request.is_xhr:
|
|
88
|
+
return response_values
|
|
89
|
+
else:
|
|
90
|
+
return HTTPSeeOther(location='/')
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@view_config(route_name='email:change:continue', permission=NO_PERMISSION_REQUIRED,
|
|
94
|
+
renderer='pyramid_fullauth:resources/templates/email_change.mako')
|
|
95
|
+
class EmailChangeAccept(BaseView):
|
|
96
|
+
|
|
97
|
+
"""Display email change success."""
|
|
98
|
+
|
|
99
|
+
def __call__(self):
|
|
100
|
+
"""View that changes user email."""
|
|
101
|
+
user = self.request.matchdict.get('user')
|
|
102
|
+
user.change_email()
|
|
103
|
+
|
|
104
|
+
try:
|
|
105
|
+
self.request.registry.notify(AfterEmailChangeActivation(self.request, user))
|
|
106
|
+
except HTTPRedirection as redirect:
|
|
107
|
+
return redirect
|
|
108
|
+
return HTTPSeeOther(location='/')
|
|
@@ -0,0 +1,129 @@
|
|
|
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
|
+
"""Profile related views."""
|
|
6
|
+
|
|
7
|
+
from pyramid.view import view_config, view_defaults
|
|
8
|
+
from pyramid.httpexceptions import HTTPRedirection, HTTPSeeOther
|
|
9
|
+
from pyramid.security import NO_PERMISSION_REQUIRED
|
|
10
|
+
|
|
11
|
+
from sqlalchemy.orm.exc import NoResultFound
|
|
12
|
+
from pyramid.compat import text_type
|
|
13
|
+
import pyramid_basemodel
|
|
14
|
+
|
|
15
|
+
from pyramid_fullauth.views import BaseView
|
|
16
|
+
from pyramid_fullauth.models import User, AuthenticationProvider
|
|
17
|
+
|
|
18
|
+
from pyramid_fullauth.events import BeforeReset
|
|
19
|
+
from pyramid_fullauth.events import AfterResetRequest
|
|
20
|
+
from pyramid_fullauth.events import AfterReset
|
|
21
|
+
from pyramid_fullauth.exceptions import ValidateError
|
|
22
|
+
from pyramid_fullauth.tools import validate_passsword
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@view_defaults(route_name='password:reset', permission=NO_PERMISSION_REQUIRED,
|
|
26
|
+
renderer='pyramid_fullauth:resources/templates/reset.mako')
|
|
27
|
+
class PasswordResetView(BaseView):
|
|
28
|
+
|
|
29
|
+
"""Profile related views."""
|
|
30
|
+
|
|
31
|
+
@view_config()
|
|
32
|
+
def get(self):
|
|
33
|
+
"""Password reset request view."""
|
|
34
|
+
csrf_token = self.request.session.get_csrf_token()
|
|
35
|
+
|
|
36
|
+
return {'status': True, 'csrf_token': csrf_token}
|
|
37
|
+
|
|
38
|
+
@view_config(request_method='POST', check_csrf=True)
|
|
39
|
+
def post(self):
|
|
40
|
+
"""View processing requests to reset password."""
|
|
41
|
+
csrf_token = self.request.session.get_csrf_token()
|
|
42
|
+
|
|
43
|
+
try:
|
|
44
|
+
user = pyramid_basemodel.Session.query(User).filter(
|
|
45
|
+
User.email == self.request.POST.get('email', '')).one()
|
|
46
|
+
except NoResultFound:
|
|
47
|
+
return {'status': False,
|
|
48
|
+
'msg': self.request._('user-not-exists',
|
|
49
|
+
default='User does not exists',
|
|
50
|
+
domain='pyramid_fullauth'),
|
|
51
|
+
'csrf_token': csrf_token}
|
|
52
|
+
|
|
53
|
+
user.set_reset()
|
|
54
|
+
try:
|
|
55
|
+
self.request.registry.notify(AfterResetRequest(self.request, user))
|
|
56
|
+
except HTTPRedirection as redirect:
|
|
57
|
+
return redirect
|
|
58
|
+
|
|
59
|
+
return HTTPSeeOther(location='/')
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@view_defaults(route_name='password:reset:continue', permission=NO_PERMISSION_REQUIRED,
|
|
63
|
+
renderer='pyramid_fullauth:resources/templates/reset.proceed.mako')
|
|
64
|
+
class PasswordResetContinueView(BaseView):
|
|
65
|
+
|
|
66
|
+
"""
|
|
67
|
+
Password reset views.
|
|
68
|
+
|
|
69
|
+
These views display actual reset password views.
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
@view_config(request_method='GET')
|
|
73
|
+
def get(self):
|
|
74
|
+
"""Display actual password reset form."""
|
|
75
|
+
self.request.logout()
|
|
76
|
+
return {
|
|
77
|
+
'status': True,
|
|
78
|
+
'csrf_token': self.request.session.get_csrf_token()
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
@view_config(request_method='POST', check_csrf=True)
|
|
82
|
+
def post(self):
|
|
83
|
+
"""Validate and possibly accept new email."""
|
|
84
|
+
user = self.request.matchdict.get('user')
|
|
85
|
+
|
|
86
|
+
password = self.request.POST.get('password', None)
|
|
87
|
+
password_confirm = self.request.POST.get('confirm_password', None)
|
|
88
|
+
if password == password_confirm:
|
|
89
|
+
|
|
90
|
+
try:
|
|
91
|
+
self.request.registry.notify(BeforeReset(self.request, user))
|
|
92
|
+
validate_passsword(self.request, password, user)
|
|
93
|
+
|
|
94
|
+
user.reset_key = None
|
|
95
|
+
try:
|
|
96
|
+
pyramid_basemodel.Session.query(AuthenticationProvider).filter(
|
|
97
|
+
AuthenticationProvider.user_id == user.id,
|
|
98
|
+
AuthenticationProvider.provider == text_type('email')
|
|
99
|
+
).one()
|
|
100
|
+
except NoResultFound:
|
|
101
|
+
user.providers.append(
|
|
102
|
+
AuthenticationProvider(
|
|
103
|
+
provider=text_type('email'),
|
|
104
|
+
provider_id=user.id
|
|
105
|
+
)
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
pyramid_basemodel.Session.flush()
|
|
109
|
+
except (ValidateError, AttributeError) as e:
|
|
110
|
+
return {
|
|
111
|
+
'status': False,
|
|
112
|
+
'msg': text_type(e),
|
|
113
|
+
'csrf_token': self.request.session.get_csrf_token()
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
try:
|
|
117
|
+
self.request.registry.notify(AfterReset(self.request, user))
|
|
118
|
+
except HTTPRedirection as redirect:
|
|
119
|
+
return redirect
|
|
120
|
+
else:
|
|
121
|
+
return {
|
|
122
|
+
'status': False,
|
|
123
|
+
'msg': self.request._('password-mismatch',
|
|
124
|
+
default='Password doesn\'t match',
|
|
125
|
+
domain='pyramid_fullauth'),
|
|
126
|
+
'csrf_token': self.request.session.get_csrf_token()
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return self.get()
|
|
@@ -0,0 +1,111 @@
|
|
|
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
|
+
"""Registration related views."""
|
|
6
|
+
|
|
7
|
+
from pyramid.view import view_config, view_defaults
|
|
8
|
+
from pyramid.httpexceptions import HTTPRedirection
|
|
9
|
+
from pyramid.security import NO_PERMISSION_REQUIRED
|
|
10
|
+
|
|
11
|
+
from pyramid.compat import text_type
|
|
12
|
+
import pyramid_basemodel
|
|
13
|
+
|
|
14
|
+
from pyramid_fullauth.views import BaseView
|
|
15
|
+
from pyramid_fullauth.models import User, AuthenticationProvider
|
|
16
|
+
from pyramid_fullauth.events import BeforeRegister, AfterRegister
|
|
17
|
+
from pyramid_fullauth import tools
|
|
18
|
+
from pyramid_fullauth.exceptions import ValidateError
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@view_defaults(route_name='register', permission=NO_PERMISSION_REQUIRED,
|
|
22
|
+
renderer="pyramid_fullauth:resources/templates/register.mako")
|
|
23
|
+
class RegisterView(BaseView):
|
|
24
|
+
|
|
25
|
+
"""Registration views."""
|
|
26
|
+
|
|
27
|
+
@view_config(request_method='GET')
|
|
28
|
+
def get(self):
|
|
29
|
+
"""Display registration form."""
|
|
30
|
+
csrf_token = self.request.session.get_csrf_token()
|
|
31
|
+
return {'status': True, 'msg': None, 'csrf_token': csrf_token, 'errors': {}}
|
|
32
|
+
|
|
33
|
+
@view_config(request_method='POST', check_csrf=True)
|
|
34
|
+
@view_config(request_method='POST', check_csrf=True, xhr=True, renderer="json")
|
|
35
|
+
def post(self):
|
|
36
|
+
"""Process registration request."""
|
|
37
|
+
response = {
|
|
38
|
+
'status': False,
|
|
39
|
+
'msg': self.request._('You have an error in your registration form',
|
|
40
|
+
domain='pyramid_fullauth'),
|
|
41
|
+
'csrf_token': self.request.session.get_csrf_token(),
|
|
42
|
+
'errors': {},
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
user = User()
|
|
46
|
+
user.address_ip = self.request.remote_addr
|
|
47
|
+
|
|
48
|
+
response = self._fillin_user(response, user)
|
|
49
|
+
|
|
50
|
+
if not response['errors']:
|
|
51
|
+
response['status'] = True
|
|
52
|
+
response['msg'] = self.request._(
|
|
53
|
+
'You have successfully registered', domain='pyramid_fullauth')
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
self.request.registry.notify(AfterRegister(self.request, user, response))
|
|
57
|
+
except HTTPRedirection as redirect:
|
|
58
|
+
return redirect
|
|
59
|
+
|
|
60
|
+
return response
|
|
61
|
+
|
|
62
|
+
def _fillin_user(self, response, user):
|
|
63
|
+
"""
|
|
64
|
+
Fill new user object in, with sent data.
|
|
65
|
+
|
|
66
|
+
:param dict response: response to return from register view
|
|
67
|
+
:param pyramid_fullauth.models.User user: new user object
|
|
68
|
+
|
|
69
|
+
:returns: response
|
|
70
|
+
:rtype: dict
|
|
71
|
+
"""
|
|
72
|
+
email = self.request.POST.get('email', text_type(''))
|
|
73
|
+
# here if e-mail is already in database
|
|
74
|
+
|
|
75
|
+
if pyramid_basemodel.Session.query(User).filter(User.email == email).count() != 0:
|
|
76
|
+
response['errors']['email'] = self.request._(
|
|
77
|
+
'User with given e-mail already exists!',
|
|
78
|
+
domain='pyramid_fullauth')
|
|
79
|
+
try:
|
|
80
|
+
try:
|
|
81
|
+
user.email = email
|
|
82
|
+
except ValidateError as e:
|
|
83
|
+
# do not overwrite existing error
|
|
84
|
+
if 'email' not in response['errors']:
|
|
85
|
+
response['errors']['email'] = text_type(e)
|
|
86
|
+
|
|
87
|
+
if self.config.register.password.require:
|
|
88
|
+
try:
|
|
89
|
+
tools.validate_passsword(self.request,
|
|
90
|
+
self.request.POST.get('password', text_type('')),
|
|
91
|
+
user)
|
|
92
|
+
except ValidateError as e:
|
|
93
|
+
response['errors']['password'] = text_type(e)
|
|
94
|
+
else:
|
|
95
|
+
user.password = tools.password_generator(
|
|
96
|
+
self.config.register.password.length_min)
|
|
97
|
+
|
|
98
|
+
self.request.registry.notify(BeforeRegister(self.request, user, response['errors']))
|
|
99
|
+
if not response['errors']:
|
|
100
|
+
pyramid_basemodel.Session.add(user)
|
|
101
|
+
pyramid_basemodel.Session.flush()
|
|
102
|
+
|
|
103
|
+
# lets add AuthenticationProvider as email!
|
|
104
|
+
user.providers.append(
|
|
105
|
+
AuthenticationProvider(provider=text_type('email'), provider_id=user.id))
|
|
106
|
+
else:
|
|
107
|
+
return response
|
|
108
|
+
except AttributeError as e:
|
|
109
|
+
response['errors']['msg'] = text_type(e)
|
|
110
|
+
|
|
111
|
+
return response
|
|
@@ -0,0 +1,221 @@
|
|
|
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
|
+
"""Social login/registration view."""
|
|
6
|
+
import logging
|
|
7
|
+
|
|
8
|
+
from pyramid.view import view_config
|
|
9
|
+
from pyramid.httpexceptions import HTTPRedirection
|
|
10
|
+
from pyramid.security import NO_PERMISSION_REQUIRED
|
|
11
|
+
|
|
12
|
+
from sqlalchemy.orm.exc import NoResultFound
|
|
13
|
+
from sqlalchemy.exc import IntegrityError
|
|
14
|
+
from pyramid.compat import text_type
|
|
15
|
+
import pyramid_basemodel
|
|
16
|
+
|
|
17
|
+
from pyramid_fullauth.views import BaseView
|
|
18
|
+
from pyramid_fullauth.models import User
|
|
19
|
+
from pyramid_fullauth.models import AuthenticationProvider
|
|
20
|
+
from pyramid_fullauth.events import BeforeSocialRegister
|
|
21
|
+
from pyramid_fullauth.events import AfterSocialRegister
|
|
22
|
+
from pyramid_fullauth.events import AfterSocialLogIn
|
|
23
|
+
from pyramid_fullauth.events import SocialAccountAlreadyConnected
|
|
24
|
+
from pyramid_fullauth import tools
|
|
25
|
+
|
|
26
|
+
logger = logging.getLogger(__name__)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@view_config(context='velruse.AuthenticationComplete',
|
|
30
|
+
permission=NO_PERMISSION_REQUIRED,
|
|
31
|
+
renderer="pyramid_fullauth:resources/templates/register.mako")
|
|
32
|
+
class SocialLoginViews(BaseView):
|
|
33
|
+
|
|
34
|
+
"""Social login views definition."""
|
|
35
|
+
|
|
36
|
+
def set_provider(self, user, provider_name, user_provider_id):
|
|
37
|
+
"""
|
|
38
|
+
Set authentication provider on user.
|
|
39
|
+
|
|
40
|
+
This method will connect given provider with given user,
|
|
41
|
+
unless provider_id has already been used on another user.
|
|
42
|
+
|
|
43
|
+
:param pyramid_fullauth.user.User user: user object
|
|
44
|
+
:param str provider_name: provider name
|
|
45
|
+
:param str user_provider_id: user id delivered by given provider
|
|
46
|
+
|
|
47
|
+
:returns: whether user got/was connected or the connection was made with another user.
|
|
48
|
+
:rtype: bool
|
|
49
|
+
"""
|
|
50
|
+
if user.id:
|
|
51
|
+
try:
|
|
52
|
+
provider_auth = pyramid_basemodel.Session.query(
|
|
53
|
+
AuthenticationProvider
|
|
54
|
+
).filter(
|
|
55
|
+
AuthenticationProvider.user_id == user.id,
|
|
56
|
+
AuthenticationProvider.provider == provider_name
|
|
57
|
+
).one()
|
|
58
|
+
if provider_auth.provider_id != user_provider_id:
|
|
59
|
+
return False
|
|
60
|
+
return True
|
|
61
|
+
except NoResultFound:
|
|
62
|
+
pass
|
|
63
|
+
|
|
64
|
+
provider_auth = AuthenticationProvider(
|
|
65
|
+
provider=provider_name,
|
|
66
|
+
provider_id=user_provider_id
|
|
67
|
+
)
|
|
68
|
+
user.providers.append(provider_auth)
|
|
69
|
+
pyramid_basemodel.Session.flush()
|
|
70
|
+
return True
|
|
71
|
+
|
|
72
|
+
def __call__(self):
|
|
73
|
+
"""
|
|
74
|
+
Action provides social authorization functionality.
|
|
75
|
+
|
|
76
|
+
When authorization with facebook or twitter is done successfully
|
|
77
|
+
action tries to find existing user in database,
|
|
78
|
+
if it exists - login this user, otherwise creates new user.
|
|
79
|
+
"""
|
|
80
|
+
context = self.request.context
|
|
81
|
+
response_values = {
|
|
82
|
+
'status': False,
|
|
83
|
+
'csrf_token': self.request.session.get_csrf_token()
|
|
84
|
+
}
|
|
85
|
+
user = self.request.user
|
|
86
|
+
|
|
87
|
+
if user:
|
|
88
|
+
# Lets try to connect our user
|
|
89
|
+
return self._connect_user(response_values)
|
|
90
|
+
|
|
91
|
+
else:
|
|
92
|
+
try:
|
|
93
|
+
user = pyramid_basemodel.Session.query(
|
|
94
|
+
User
|
|
95
|
+
).join(AuthenticationProvider).filter(
|
|
96
|
+
AuthenticationProvider.provider == context.provider_name,
|
|
97
|
+
AuthenticationProvider.provider_id == context.profile['accounts'][0]['userid']
|
|
98
|
+
).one()
|
|
99
|
+
except NoResultFound:
|
|
100
|
+
user = None
|
|
101
|
+
|
|
102
|
+
# If the user for the provider was not found then check
|
|
103
|
+
# if in the DB exists user with the same email
|
|
104
|
+
if not user:
|
|
105
|
+
user = self._register_user()
|
|
106
|
+
try:
|
|
107
|
+
self.request.registry.notify(
|
|
108
|
+
AfterSocialRegister(
|
|
109
|
+
self.request, user, context.profile, response_values))
|
|
110
|
+
except HTTPRedirection as redirect:
|
|
111
|
+
# it's a redirect, let's follow it!
|
|
112
|
+
return redirect
|
|
113
|
+
|
|
114
|
+
# if we're here, user exists,
|
|
115
|
+
try:
|
|
116
|
+
self.request.registry.notify(AfterSocialLogIn(self.request, user, context.profile))
|
|
117
|
+
except HTTPRedirection as redirect:
|
|
118
|
+
# it's a redirect, let's follow it!
|
|
119
|
+
return self.request.login_perform(user, location=redirect.location)
|
|
120
|
+
else:
|
|
121
|
+
return self.request.login_perform(user)
|
|
122
|
+
|
|
123
|
+
def _connect_user(self, response_values):
|
|
124
|
+
"""
|
|
125
|
+
Connect user to social account.
|
|
126
|
+
|
|
127
|
+
:param dict response_values:
|
|
128
|
+
|
|
129
|
+
:returns: response values with any message,
|
|
130
|
+
or HTTPRedirection if raised in SocialAccountAlreadyConnected
|
|
131
|
+
:rtype: dict
|
|
132
|
+
"""
|
|
133
|
+
context = self.request.context
|
|
134
|
+
user = self.request.user
|
|
135
|
+
try:
|
|
136
|
+
if not self.set_provider(
|
|
137
|
+
user, context.provider_name, context.profile['accounts'][0]['userid']
|
|
138
|
+
):
|
|
139
|
+
response_values['msg'] = self.request._(
|
|
140
|
+
'Your account is already connected to other ${provider} account.',
|
|
141
|
+
domain='pyramid_fullauth',
|
|
142
|
+
mapping={'provider': context.provider_name}
|
|
143
|
+
)
|
|
144
|
+
else:
|
|
145
|
+
response_values['status'] = True
|
|
146
|
+
response_values['msg'] = self.request._(
|
|
147
|
+
'Your account has been connected to ${provider} account.',
|
|
148
|
+
domain='pyramid_fullauth',
|
|
149
|
+
mapping={'provider': context.provider_name}
|
|
150
|
+
)
|
|
151
|
+
except IntegrityError:
|
|
152
|
+
response_values['msg'] = self.request._(
|
|
153
|
+
'This ${provider} account is already connected with other account.',
|
|
154
|
+
domain='pyramid_fullauth',
|
|
155
|
+
mapping={'provider': context.provider_name}
|
|
156
|
+
)
|
|
157
|
+
try:
|
|
158
|
+
self.request.registry.notify(
|
|
159
|
+
SocialAccountAlreadyConnected(
|
|
160
|
+
self.request, user, context.profile, response_values))
|
|
161
|
+
except HTTPRedirection as redirect:
|
|
162
|
+
# it's a redirect, let's follow it!
|
|
163
|
+
return redirect
|
|
164
|
+
return response_values
|
|
165
|
+
|
|
166
|
+
def _register_user(self):
|
|
167
|
+
"""Actually register new user in the system based on context values."""
|
|
168
|
+
context = self.request.context
|
|
169
|
+
email = self._email_from_context(context)
|
|
170
|
+
|
|
171
|
+
try:
|
|
172
|
+
user = pyramid_basemodel.Session.query(User).filter(User.email == email).one()
|
|
173
|
+
# If we are here that means that in the DB exists user with the same email but without the provider
|
|
174
|
+
# then we connect social account to this user
|
|
175
|
+
if not self.set_provider(
|
|
176
|
+
user, context.provider_name, context.profile['accounts'][0]['userid']
|
|
177
|
+
):
|
|
178
|
+
# authenticating user with different social account than assigned,
|
|
179
|
+
# recogniced by same email address used
|
|
180
|
+
logger.debug('''Authenticated {user.id} connected to
|
|
181
|
+
{context.provider_name} id {connected_id},
|
|
182
|
+
with {userid}'''.format(
|
|
183
|
+
user=user,
|
|
184
|
+
context=context,
|
|
185
|
+
connected_id=user.provider_id(context.provider_name),
|
|
186
|
+
userid=context.profile['accounts'][0]['userid']))
|
|
187
|
+
pyramid_basemodel.Session.flush()
|
|
188
|
+
except NoResultFound:
|
|
189
|
+
length_min = self.config.register.password.length_min
|
|
190
|
+
user = User(
|
|
191
|
+
email=email,
|
|
192
|
+
password=tools.password_generator(length_min),
|
|
193
|
+
address_ip=self.request.remote_addr
|
|
194
|
+
)
|
|
195
|
+
self.request.registry.notify(BeforeSocialRegister(self.request, user, context.profile))
|
|
196
|
+
self.set_provider(user, context.provider_name, context.profile['accounts'][0]['userid'])
|
|
197
|
+
pyramid_basemodel.Session.add(user)
|
|
198
|
+
pyramid_basemodel.Session.flush()
|
|
199
|
+
user.is_active = True
|
|
200
|
+
return user
|
|
201
|
+
|
|
202
|
+
def _email_from_context(self, context):
|
|
203
|
+
"""
|
|
204
|
+
Extract or generate email from context values.
|
|
205
|
+
|
|
206
|
+
:param velruse.AuthenticationComplete context: velruse context
|
|
207
|
+
"""
|
|
208
|
+
# getting verified email from provider
|
|
209
|
+
if 'verifiedEmail' in context.profile:
|
|
210
|
+
return context.profile['verifiedEmail']
|
|
211
|
+
# getting first of the emails provided by social login provider
|
|
212
|
+
elif 'emails' in context.profile and context.profile['emails'] and 'value' in context.profile['emails'][0]:
|
|
213
|
+
return context.profile['emails'][0]['value']
|
|
214
|
+
# generating some random email address based on social userid and provider domain
|
|
215
|
+
else:
|
|
216
|
+
return text_type(
|
|
217
|
+
'{0}@{1}'.format(
|
|
218
|
+
context.profile['accounts'][0]['userid'],
|
|
219
|
+
context.profile['accounts'][0]['domain']
|
|
220
|
+
)
|
|
221
|
+
)
|
tests/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Tests."""
|
|
Binary file
|
|
Binary file
|
|
Binary file
|