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,128 @@
|
|
|
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 HTTPFound
|
|
9
|
+
from pyramid.security import NO_PERMISSION_REQUIRED
|
|
10
|
+
|
|
11
|
+
from sqlalchemy.orm.exc import NoResultFound
|
|
12
|
+
import pyramid_basemodel
|
|
13
|
+
|
|
14
|
+
from pyramid_fullauth.views import BaseView
|
|
15
|
+
from pyramid_fullauth.models import User, AuthenticationProvider
|
|
16
|
+
|
|
17
|
+
from pyramid_fullauth.events import BeforeReset
|
|
18
|
+
from pyramid_fullauth.events import AfterResetRequest
|
|
19
|
+
from pyramid_fullauth.events import AfterReset
|
|
20
|
+
from pyramid_fullauth.exceptions import ValidateError
|
|
21
|
+
from pyramid_fullauth.tools import validate_passsword
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@view_defaults(route_name='password:reset', permission=NO_PERMISSION_REQUIRED,
|
|
25
|
+
renderer='pyramid_fullauth:resources/templates/reset.mako')
|
|
26
|
+
class PasswordResetView(BaseView):
|
|
27
|
+
|
|
28
|
+
"""Profile related views."""
|
|
29
|
+
|
|
30
|
+
@view_config()
|
|
31
|
+
def get(self):
|
|
32
|
+
"""Password reset request view."""
|
|
33
|
+
csrf_token = self.request.session.get_csrf_token()
|
|
34
|
+
|
|
35
|
+
return {'status': True, 'csrf_token': csrf_token}
|
|
36
|
+
|
|
37
|
+
@view_config(request_method='POST', check_csrf=True)
|
|
38
|
+
def post(self):
|
|
39
|
+
"""View processing requests to reset password."""
|
|
40
|
+
csrf_token = self.request.session.get_csrf_token()
|
|
41
|
+
|
|
42
|
+
try:
|
|
43
|
+
user = pyramid_basemodel.Session.query(User).filter(
|
|
44
|
+
User.email == self.request.POST.get('email', '')).one()
|
|
45
|
+
except NoResultFound:
|
|
46
|
+
return {'status': False,
|
|
47
|
+
'msg': self.request._('user-not-exists',
|
|
48
|
+
default='User does not exists',
|
|
49
|
+
domain='pyramid_fullauth'),
|
|
50
|
+
'csrf_token': csrf_token}
|
|
51
|
+
|
|
52
|
+
user.set_reset()
|
|
53
|
+
try:
|
|
54
|
+
self.request.registry.notify(AfterResetRequest(self.request, user))
|
|
55
|
+
except HTTPFound as redirect:
|
|
56
|
+
return redirect
|
|
57
|
+
|
|
58
|
+
return HTTPFound(location='/')
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@view_defaults(route_name='password:reset:continue', permission=NO_PERMISSION_REQUIRED,
|
|
62
|
+
renderer='pyramid_fullauth:resources/templates/reset.proceed.mako')
|
|
63
|
+
class PasswordResetContinueView(BaseView):
|
|
64
|
+
|
|
65
|
+
"""
|
|
66
|
+
Password reset views.
|
|
67
|
+
|
|
68
|
+
These views display actual reset password views.
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
@view_config(request_method='GET')
|
|
72
|
+
def get(self):
|
|
73
|
+
"""Display actual password reset form."""
|
|
74
|
+
self.request.logout()
|
|
75
|
+
return {
|
|
76
|
+
'status': True,
|
|
77
|
+
'csrf_token': self.request.session.get_csrf_token()
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
@view_config(request_method='POST', check_csrf=True)
|
|
81
|
+
def post(self):
|
|
82
|
+
"""Validate and possibly accept new email."""
|
|
83
|
+
user = self.request.matchdict.get('user')
|
|
84
|
+
|
|
85
|
+
password = self.request.POST.get('password', None)
|
|
86
|
+
password_confirm = self.request.POST.get('confirm_password', None)
|
|
87
|
+
if password == password_confirm:
|
|
88
|
+
|
|
89
|
+
try:
|
|
90
|
+
self.request.registry.notify(BeforeReset(self.request, user))
|
|
91
|
+
validate_passsword(self.request, password, user)
|
|
92
|
+
|
|
93
|
+
user.reset_key = None
|
|
94
|
+
try:
|
|
95
|
+
pyramid_basemodel.Session.query(AuthenticationProvider).filter(
|
|
96
|
+
AuthenticationProvider.user_id == user.id,
|
|
97
|
+
AuthenticationProvider.provider == u'email'
|
|
98
|
+
).one()
|
|
99
|
+
except NoResultFound:
|
|
100
|
+
user.providers.append(
|
|
101
|
+
AuthenticationProvider(
|
|
102
|
+
provider=u'email',
|
|
103
|
+
provider_id=user.id
|
|
104
|
+
)
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
pyramid_basemodel.Session.flush()
|
|
108
|
+
except (ValidateError, AttributeError) as e:
|
|
109
|
+
return {
|
|
110
|
+
'status': False,
|
|
111
|
+
'msg': str(e),
|
|
112
|
+
'csrf_token': self.request.session.get_csrf_token()
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
try:
|
|
116
|
+
self.request.registry.notify(AfterReset(self.request, user))
|
|
117
|
+
except HTTPFound as redirect:
|
|
118
|
+
return redirect
|
|
119
|
+
else:
|
|
120
|
+
return {
|
|
121
|
+
'status': False,
|
|
122
|
+
'msg': self.request._('password-mismatch',
|
|
123
|
+
default='Password doesn\'t match',
|
|
124
|
+
domain='pyramid_fullauth'),
|
|
125
|
+
'csrf_token': self.request.session.get_csrf_token()
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return self.get()
|
|
Binary file
|
|
@@ -0,0 +1,110 @@
|
|
|
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 HTTPFound
|
|
9
|
+
from pyramid.security import NO_PERMISSION_REQUIRED
|
|
10
|
+
|
|
11
|
+
import pyramid_basemodel
|
|
12
|
+
|
|
13
|
+
from pyramid_fullauth.views import BaseView
|
|
14
|
+
from pyramid_fullauth.models import User, AuthenticationProvider
|
|
15
|
+
from pyramid_fullauth.events import BeforeRegister, AfterRegister
|
|
16
|
+
from pyramid_fullauth import tools
|
|
17
|
+
from pyramid_fullauth.exceptions import ValidateError
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@view_defaults(route_name='register', permission=NO_PERMISSION_REQUIRED,
|
|
21
|
+
renderer="pyramid_fullauth:resources/templates/register.mako")
|
|
22
|
+
class RegisterView(BaseView):
|
|
23
|
+
|
|
24
|
+
"""Registration views."""
|
|
25
|
+
|
|
26
|
+
@view_config(request_method='GET')
|
|
27
|
+
def get(self):
|
|
28
|
+
"""Display registration form."""
|
|
29
|
+
csrf_token = self.request.session.get_csrf_token()
|
|
30
|
+
return {'status': True, 'msg': None, 'csrf_token': csrf_token, 'errors': {}}
|
|
31
|
+
|
|
32
|
+
@view_config(request_method='POST', check_csrf=True)
|
|
33
|
+
@view_config(request_method='POST', check_csrf=True, xhr=True, renderer="json")
|
|
34
|
+
def post(self):
|
|
35
|
+
"""Process registration request."""
|
|
36
|
+
response = {
|
|
37
|
+
'status': False,
|
|
38
|
+
'msg': self.request._('You have an error in your registration form',
|
|
39
|
+
domain='pyramid_fullauth'),
|
|
40
|
+
'csrf_token': self.request.session.get_csrf_token(),
|
|
41
|
+
'errors': {},
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
user = User()
|
|
45
|
+
user.address_ip = self.request.remote_addr
|
|
46
|
+
|
|
47
|
+
response = self._fillin_user(response, user)
|
|
48
|
+
|
|
49
|
+
if not response['errors']:
|
|
50
|
+
response['status'] = True
|
|
51
|
+
response['msg'] = self.request._(
|
|
52
|
+
'You have successfully registered', domain='pyramid_fullauth')
|
|
53
|
+
|
|
54
|
+
try:
|
|
55
|
+
self.request.registry.notify(AfterRegister(self.request, user, response))
|
|
56
|
+
except HTTPFound as redirect:
|
|
57
|
+
return redirect
|
|
58
|
+
|
|
59
|
+
return response
|
|
60
|
+
|
|
61
|
+
def _fillin_user(self, response, user):
|
|
62
|
+
"""
|
|
63
|
+
Fill new user object in, with sent data.
|
|
64
|
+
|
|
65
|
+
:param dict response: response to return from register view
|
|
66
|
+
:param pyramid_fullauth.models.User user: new user object
|
|
67
|
+
|
|
68
|
+
:returns: response
|
|
69
|
+
:rtype: dict
|
|
70
|
+
"""
|
|
71
|
+
email = self.request.POST.get('email', u'')
|
|
72
|
+
# here if e-mail is already in database
|
|
73
|
+
|
|
74
|
+
if pyramid_basemodel.Session.query(User).filter(User.email == email).count() != 0:
|
|
75
|
+
response['errors']['email'] = self.request._(
|
|
76
|
+
'User with given e-mail already exists!',
|
|
77
|
+
domain='pyramid_fullauth')
|
|
78
|
+
try:
|
|
79
|
+
try:
|
|
80
|
+
user.email = email
|
|
81
|
+
except ValidateError as e:
|
|
82
|
+
# do not overwrite existing error
|
|
83
|
+
if 'email' not in response['errors']:
|
|
84
|
+
response['errors']['email'] = str(e)
|
|
85
|
+
|
|
86
|
+
if self.config.register.password.require:
|
|
87
|
+
try:
|
|
88
|
+
tools.validate_passsword(self.request,
|
|
89
|
+
self.request.POST.get('password', u''),
|
|
90
|
+
user)
|
|
91
|
+
except ValidateError as e:
|
|
92
|
+
response['errors']['password'] = e.message
|
|
93
|
+
else:
|
|
94
|
+
user.password = tools.password_generator(
|
|
95
|
+
self.config.register.password.length_min)
|
|
96
|
+
|
|
97
|
+
self.request.registry.notify(BeforeRegister(self.request, user, response['errors']))
|
|
98
|
+
if not response['errors']:
|
|
99
|
+
pyramid_basemodel.Session.add(user)
|
|
100
|
+
pyramid_basemodel.Session.flush()
|
|
101
|
+
|
|
102
|
+
# lets add AuthenticationProvider as email!
|
|
103
|
+
user.providers.append(
|
|
104
|
+
AuthenticationProvider(provider=u'email', provider_id=user.id))
|
|
105
|
+
else:
|
|
106
|
+
return response
|
|
107
|
+
except AttributeError as e:
|
|
108
|
+
response['errors']['msg'] = str(e)
|
|
109
|
+
|
|
110
|
+
return response
|
|
Binary file
|
|
@@ -0,0 +1,218 @@
|
|
|
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 HTTPFound
|
|
10
|
+
from pyramid.security import NO_PERMISSION_REQUIRED
|
|
11
|
+
|
|
12
|
+
from sqlalchemy.orm.exc import NoResultFound
|
|
13
|
+
from sqlalchemy.exc import IntegrityError
|
|
14
|
+
import pyramid_basemodel
|
|
15
|
+
|
|
16
|
+
from pyramid_fullauth.views import BaseView
|
|
17
|
+
from pyramid_fullauth.models import User
|
|
18
|
+
from pyramid_fullauth.models import AuthenticationProvider
|
|
19
|
+
from pyramid_fullauth.events import BeforeSocialRegister
|
|
20
|
+
from pyramid_fullauth.events import AfterSocialRegister
|
|
21
|
+
from pyramid_fullauth.events import AfterSocialLogIn
|
|
22
|
+
from pyramid_fullauth.events import SocialAccountAlreadyConnected
|
|
23
|
+
from pyramid_fullauth import tools
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@view_config(context='velruse.AuthenticationComplete',
|
|
29
|
+
permission=NO_PERMISSION_REQUIRED,
|
|
30
|
+
renderer="pyramid_fullauth:resources/templates/register.mako")
|
|
31
|
+
class SocialLoginViews(BaseView):
|
|
32
|
+
|
|
33
|
+
"""Social login views definition."""
|
|
34
|
+
|
|
35
|
+
def set_provider(self, user, provider_name, user_provider_id):
|
|
36
|
+
"""
|
|
37
|
+
Set authentication provider on user.
|
|
38
|
+
|
|
39
|
+
This method will connect given provider with given user,
|
|
40
|
+
unless provider_id has already been used on another user.
|
|
41
|
+
|
|
42
|
+
:param pyramid_fullauth.user.User user: user object
|
|
43
|
+
:param str provider_name: provider name
|
|
44
|
+
:param str user_provider_id: user id delivered by given provider
|
|
45
|
+
|
|
46
|
+
:returns: whether user got/was connected or the connection was made with another user.
|
|
47
|
+
:rtype: bool
|
|
48
|
+
"""
|
|
49
|
+
if user.id:
|
|
50
|
+
try:
|
|
51
|
+
provider_auth = pyramid_basemodel.Session.query(
|
|
52
|
+
AuthenticationProvider
|
|
53
|
+
).filter(
|
|
54
|
+
AuthenticationProvider.user_id == user.id,
|
|
55
|
+
AuthenticationProvider.provider == provider_name
|
|
56
|
+
).one()
|
|
57
|
+
if provider_auth.provider_id != user_provider_id:
|
|
58
|
+
return False
|
|
59
|
+
return True
|
|
60
|
+
except NoResultFound:
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
provider_auth = AuthenticationProvider(
|
|
64
|
+
provider=provider_name,
|
|
65
|
+
provider_id=user_provider_id
|
|
66
|
+
)
|
|
67
|
+
user.providers.append(provider_auth)
|
|
68
|
+
pyramid_basemodel.Session.flush()
|
|
69
|
+
return True
|
|
70
|
+
|
|
71
|
+
def __call__(self):
|
|
72
|
+
"""
|
|
73
|
+
Action provides social authorization functionality.
|
|
74
|
+
|
|
75
|
+
When authorization with facebook or twitter is done successfully
|
|
76
|
+
action tries to find existing user in database,
|
|
77
|
+
if it exists - login this user, otherwise creates new user.
|
|
78
|
+
"""
|
|
79
|
+
context = self.request.context
|
|
80
|
+
response_values = {
|
|
81
|
+
'status': False,
|
|
82
|
+
'csrf_token': self.request.session.get_csrf_token()
|
|
83
|
+
}
|
|
84
|
+
user = self.request.user
|
|
85
|
+
|
|
86
|
+
if user:
|
|
87
|
+
# Lets try to connect our user
|
|
88
|
+
return self._connect_user(response_values)
|
|
89
|
+
|
|
90
|
+
else:
|
|
91
|
+
try:
|
|
92
|
+
user = pyramid_basemodel.Session.query(
|
|
93
|
+
User
|
|
94
|
+
).join(AuthenticationProvider).filter(
|
|
95
|
+
AuthenticationProvider.provider == context.provider_name,
|
|
96
|
+
AuthenticationProvider.provider_id == context.profile['accounts'][0]['userid']
|
|
97
|
+
).one()
|
|
98
|
+
except NoResultFound:
|
|
99
|
+
user = None
|
|
100
|
+
|
|
101
|
+
# If the user for the provider was not found then check
|
|
102
|
+
# if in the DB exists user with the same email
|
|
103
|
+
if not user:
|
|
104
|
+
user = self._register_user()
|
|
105
|
+
try:
|
|
106
|
+
self.request.registry.notify(
|
|
107
|
+
AfterSocialRegister(
|
|
108
|
+
self.request, user, context.profile, response_values))
|
|
109
|
+
except HTTPFound as redirect:
|
|
110
|
+
# it's a redirect, let's follow it!
|
|
111
|
+
return redirect
|
|
112
|
+
|
|
113
|
+
# if we're here, user exists,
|
|
114
|
+
try:
|
|
115
|
+
self.request.registry.notify(AfterSocialLogIn(self.request, user, context.profile))
|
|
116
|
+
except HTTPFound as redirect:
|
|
117
|
+
# it's a redirect, let's follow it!
|
|
118
|
+
return self.request.login_perform(user, location=redirect.location)
|
|
119
|
+
else:
|
|
120
|
+
return self.request.login_perform(user)
|
|
121
|
+
|
|
122
|
+
def _connect_user(self, response_values):
|
|
123
|
+
"""
|
|
124
|
+
Connect user to social account.
|
|
125
|
+
|
|
126
|
+
:param dict response_values:
|
|
127
|
+
|
|
128
|
+
:returns: response values with any message,
|
|
129
|
+
or HTTPFound if raised in SocialAccountAlreadyConnected
|
|
130
|
+
:rtype: dict
|
|
131
|
+
"""
|
|
132
|
+
context = self.request.context
|
|
133
|
+
user = self.request.user
|
|
134
|
+
try:
|
|
135
|
+
if not self.set_provider(
|
|
136
|
+
user, context.provider_name, context.profile['accounts'][0]['userid']
|
|
137
|
+
):
|
|
138
|
+
response_values['msg'] = self.request._(
|
|
139
|
+
'Your account is already connected to other ${provider} account.',
|
|
140
|
+
domain='pyramid_fullauth',
|
|
141
|
+
mapping={'provider': context.provider_name}
|
|
142
|
+
)
|
|
143
|
+
else:
|
|
144
|
+
response_values['status'] = True
|
|
145
|
+
response_values['msg'] = self.request._(
|
|
146
|
+
'Your account has been connected to ${provider} account.',
|
|
147
|
+
domain='pyramid_fullauth',
|
|
148
|
+
mapping={'provider': context.provider_name}
|
|
149
|
+
)
|
|
150
|
+
except IntegrityError:
|
|
151
|
+
response_values['msg'] = self.request._(
|
|
152
|
+
'This ${provider} account is already connected with other account.',
|
|
153
|
+
domain='pyramid_fullauth',
|
|
154
|
+
mapping={'provider': context.provider_name}
|
|
155
|
+
)
|
|
156
|
+
try:
|
|
157
|
+
self.request.registry.notify(
|
|
158
|
+
SocialAccountAlreadyConnected(
|
|
159
|
+
self.request, user, context.profile, response_values))
|
|
160
|
+
except HTTPFound as redirect:
|
|
161
|
+
# it's a redirect, let's follow it!
|
|
162
|
+
return redirect
|
|
163
|
+
return response_values
|
|
164
|
+
|
|
165
|
+
def _register_user(self):
|
|
166
|
+
"""Actually register new user in the system based on context values."""
|
|
167
|
+
context = self.request.context
|
|
168
|
+
email = self._email_from_context(context)
|
|
169
|
+
|
|
170
|
+
try:
|
|
171
|
+
user = pyramid_basemodel.Session.query(User).filter(User.email == email).one()
|
|
172
|
+
# If we are here that means that in the DB exists user with the same email but without the provider
|
|
173
|
+
# then we connect social account to this user
|
|
174
|
+
if not self.set_provider(
|
|
175
|
+
user, context.provider_name, context.profile['accounts'][0]['userid']
|
|
176
|
+
):
|
|
177
|
+
# authenticating user with different social account than assigned,
|
|
178
|
+
# recogniced by same email address used
|
|
179
|
+
logger.debug('''Authenticated {user.id} connected to
|
|
180
|
+
{context.provider_name} id {connected_id},
|
|
181
|
+
with {userid}'''.format(
|
|
182
|
+
user=user,
|
|
183
|
+
context=context,
|
|
184
|
+
connected_id=user.provider_id(context.provider_name),
|
|
185
|
+
userid=context.profile['accounts'][0]['userid']))
|
|
186
|
+
pyramid_basemodel.Session.flush()
|
|
187
|
+
except NoResultFound:
|
|
188
|
+
length_min = self.config.register.password.length_min
|
|
189
|
+
user = User(
|
|
190
|
+
email=email,
|
|
191
|
+
password=tools.password_generator(length_min),
|
|
192
|
+
address_ip=self.request.remote_addr
|
|
193
|
+
)
|
|
194
|
+
self.request.registry.notify(BeforeSocialRegister(self.request, user, context.profile))
|
|
195
|
+
self.set_provider(user, context.provider_name, context.profile['accounts'][0]['userid'])
|
|
196
|
+
pyramid_basemodel.Session.add(user)
|
|
197
|
+
pyramid_basemodel.Session.flush()
|
|
198
|
+
user.is_active = True
|
|
199
|
+
return user
|
|
200
|
+
|
|
201
|
+
def _email_from_context(self, context):
|
|
202
|
+
"""
|
|
203
|
+
Extract or generate email from context values.
|
|
204
|
+
|
|
205
|
+
:param velruse.AuthenticationComplete context: velruse context
|
|
206
|
+
"""
|
|
207
|
+
# getting verified email from provider
|
|
208
|
+
if 'verifiedEmail' in context.profile:
|
|
209
|
+
return context.profile['verifiedEmail']
|
|
210
|
+
# getting first of the emails provided by social login provider
|
|
211
|
+
elif 'emails' in context.profile and context.profile['emails'] and 'value' in context.profile['emails'][0]:
|
|
212
|
+
return context.profile['emails'][0]['value']
|
|
213
|
+
# generating some random email address based on social userid and provider domain
|
|
214
|
+
else:
|
|
215
|
+
return u'{0}@{1}'.format(
|
|
216
|
+
context.profile['accounts'][0]['userid'],
|
|
217
|
+
context.profile['accounts'][0]['domain']
|
|
218
|
+
)
|
|
Binary file
|
tests/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Tests."""
|
tests/__init__.pyc
ADDED
|
Binary file
|
tests/conftest.py
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"""Most of the fixtures needed."""
|
|
2
|
+
from mock import Mock
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
import transaction
|
|
6
|
+
from sqlalchemy import create_engine
|
|
7
|
+
from sqlalchemy.orm import scoped_session, sessionmaker
|
|
8
|
+
from sqlalchemy.pool import NullPool
|
|
9
|
+
from zope.sqlalchemy import ZopeTransactionExtension
|
|
10
|
+
|
|
11
|
+
from pyramid.compat import text_type
|
|
12
|
+
from pytest_pyramid import factories
|
|
13
|
+
import pyramid_basemodel
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@pytest.fixture
|
|
17
|
+
def web_request():
|
|
18
|
+
"""Mocked web request for views testing."""
|
|
19
|
+
request = Mock()
|
|
20
|
+
config = Mock()
|
|
21
|
+
config.configure_mock(
|
|
22
|
+
**{'fullauth.register.password':
|
|
23
|
+
{'length_min': 8, 'confirm': True},
|
|
24
|
+
'POST': {'confirm_password': '987654321'}
|
|
25
|
+
}
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
def _(message, *args, **kwargs):
|
|
29
|
+
return message
|
|
30
|
+
request._ = Mock(side_effect=_)
|
|
31
|
+
request.configure_mock(
|
|
32
|
+
**{'registry': {'config': config}}
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
return request
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@pytest.fixture(scope='function', params=['sqlite', 'mysql', 'postgresql'])
|
|
39
|
+
def db_session(request):
|
|
40
|
+
"""SQLAlchemy session."""
|
|
41
|
+
from pyramid_fullauth.models import Base
|
|
42
|
+
|
|
43
|
+
if request.param == 'sqlite':
|
|
44
|
+
connection = 'sqlite:///fullauth.sqlite'
|
|
45
|
+
elif request.param == 'mysql':
|
|
46
|
+
request.getfuncargvalue('mysqldb') # takes care of creating database
|
|
47
|
+
connection = 'mysql+mysqldb://root:@127.0.0.1:3307/tests?charset=utf8'
|
|
48
|
+
elif request.param == 'postgresql':
|
|
49
|
+
request.getfuncargvalue('postgresql') # takes care of creating database
|
|
50
|
+
connection = 'postgresql+psycopg2://postgres:@127.0.0.1:5433/tests'
|
|
51
|
+
|
|
52
|
+
engine = create_engine(connection, echo=False, poolclass=NullPool)
|
|
53
|
+
pyramid_basemodel.Session = scoped_session(sessionmaker(extension=ZopeTransactionExtension()))
|
|
54
|
+
pyramid_basemodel.bind_engine(engine, pyramid_basemodel.Session, should_drop=True)
|
|
55
|
+
|
|
56
|
+
def destroy():
|
|
57
|
+
transaction.commit()
|
|
58
|
+
Base.metadata.drop_all(engine)
|
|
59
|
+
|
|
60
|
+
request.addfinalizer(destroy)
|
|
61
|
+
|
|
62
|
+
return pyramid_basemodel.Session
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@pytest.fixture
|
|
66
|
+
def user(db_session):
|
|
67
|
+
"""Default user."""
|
|
68
|
+
from pyramid_fullauth.models import User
|
|
69
|
+
from tests.tools import DEFAULT_USER
|
|
70
|
+
user = User(**DEFAULT_USER)
|
|
71
|
+
db_session.add(user)
|
|
72
|
+
transaction.commit()
|
|
73
|
+
return user
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@pytest.fixture
|
|
77
|
+
def active_user(user, db_session):
|
|
78
|
+
"""Active user."""
|
|
79
|
+
user = db_session.merge(user)
|
|
80
|
+
user.is_active = True
|
|
81
|
+
transaction.commit()
|
|
82
|
+
return user
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@pytest.fixture(params=[
|
|
86
|
+
# (an @ character must separate the local and domain parts)
|
|
87
|
+
text_type('Abc.example.com'),
|
|
88
|
+
# (character dot(.) is last in local part)
|
|
89
|
+
text_type('Abc.@example.com'),
|
|
90
|
+
# (character dot(.) is double)
|
|
91
|
+
text_type('Abc..123@example.com'),
|
|
92
|
+
# (only one @ is allowed outside quotation marks)
|
|
93
|
+
text_type('A@b@c@example.com'),
|
|
94
|
+
# (none of the special characters in this local part is allowed outside quotation marks)
|
|
95
|
+
text_type('a"b(c)d,e:f;g<h>i[j\k]l@example.com'),
|
|
96
|
+
# (quoted strings must be dot separated, or the only element making up the local-part)
|
|
97
|
+
text_type('just"not"right@example.com'),
|
|
98
|
+
# (spaces, quotes, and backslashes may only exist when within quoted strings and preceded by a backslash)
|
|
99
|
+
text_type('this is"not\allowed@example.com'),
|
|
100
|
+
# (even if escaped (preceded by a backslash), spaces, quotes, and backslashes must still be contained by quotes)
|
|
101
|
+
text_type('this\ still\"not\\allowed@example.com'),
|
|
102
|
+
text_type('bad-mail'),
|
|
103
|
+
])
|
|
104
|
+
def invalid_email(request):
|
|
105
|
+
"""Parametrized fixture with all the incorrect emails."""
|
|
106
|
+
return request.param
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
default_config = factories.pyramid_config({
|
|
110
|
+
'pyramid.includes': [
|
|
111
|
+
'pyramid_tm',
|
|
112
|
+
'pyramid_fullauth'
|
|
113
|
+
]
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
extended_config = factories.pyramid_config({
|
|
118
|
+
'pyramid.includes': [
|
|
119
|
+
'pyramid_tm',
|
|
120
|
+
'pyramid_fullauth',
|
|
121
|
+
'tests.tools.include_views'
|
|
122
|
+
]
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
short_config = factories.pyramid_config({
|
|
127
|
+
'yml.location': 'tests:config/short_memory.yaml',
|
|
128
|
+
'pyramid.includes': [
|
|
129
|
+
'pyramid_tm',
|
|
130
|
+
'tzf.pyramid_yml',
|
|
131
|
+
'pyramid_fullauth',
|
|
132
|
+
'tests.tools.include_views'
|
|
133
|
+
]
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
social_config = factories.pyramid_config({
|
|
138
|
+
'yml.location': 'tests:config/social.yaml',
|
|
139
|
+
'pyramid.includes': [
|
|
140
|
+
'pyramid_tm',
|
|
141
|
+
'tzf.pyramid_yml',
|
|
142
|
+
'pyramid_fullauth',
|
|
143
|
+
'tests.tools.include_views'
|
|
144
|
+
]
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
authable_config = factories.pyramid_config({
|
|
149
|
+
'yml.location': 'tests:config',
|
|
150
|
+
'env': 'login',
|
|
151
|
+
'pyramid.includes': [
|
|
152
|
+
'pyramid_tm',
|
|
153
|
+
'tzf.pyramid_yml',
|
|
154
|
+
'pyramid_fullauth',
|
|
155
|
+
'tests.tools.include_views'
|
|
156
|
+
]
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
nopassconfirm_config = factories.pyramid_config({
|
|
161
|
+
'yml.location': 'tests:config/no_password_confirm.yaml',
|
|
162
|
+
'env': 'login',
|
|
163
|
+
'pyramid.includes': [
|
|
164
|
+
'pyramid_tm',
|
|
165
|
+
'tzf.pyramid_yml',
|
|
166
|
+
'pyramid_fullauth',
|
|
167
|
+
'tests.tools.include_views'
|
|
168
|
+
]
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
nopassregister_config = factories.pyramid_config({
|
|
173
|
+
'yml.location': 'tests:config/no_password_register.yaml',
|
|
174
|
+
'env': 'login',
|
|
175
|
+
'pyramid.includes': [
|
|
176
|
+
'pyramid_tm',
|
|
177
|
+
'tzf.pyramid_yml',
|
|
178
|
+
'pyramid_fullauth',
|
|
179
|
+
'tests.tools.include_views'
|
|
180
|
+
]
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
default_app = factories.pyramid_app('default_config')
|
|
185
|
+
extended_app = factories.pyramid_app('extended_config')
|
|
186
|
+
short_app = factories.pyramid_app('short_config')
|
|
187
|
+
social_app = factories.pyramid_app('social_config')
|
|
188
|
+
authable_app = factories.pyramid_app('authable_config')
|
|
189
|
+
nopassconfirm_app = factories.pyramid_app('nopassconfirm_config')
|
|
190
|
+
nopassregister_app = factories.pyramid_app('nopassregister_config')
|
tests/conftest.pyc
ADDED
|
Binary file
|