0010-views-make-sms-code-trigger-a-standard-registration-.patch
src/authentic2/views.py | ||
---|---|---|
1307 | 1307 |
request.token = self.token |
1308 | 1308 | |
1309 | 1309 |
self.authentication_method = self.token.get('authentication_method', 'email') |
1310 |
self.email = self.token['email'] |
|
1311 |
Lock.lock_email(self.email) |
|
1312 | 1310 |
if 'ou' in self.token: |
1313 | 1311 |
self.ou = OU.objects.get(pk=self.token['ou']) |
1314 | 1312 |
else: |
1315 | 1313 |
self.ou = get_default_ou() |
1316 |
self.users = User.objects.filter(email__iexact=self.email).order_by('date_joined') |
|
1314 | ||
1315 |
if self.token.get('email', None): |
|
1316 |
self.email = self.token['email'] |
|
1317 |
qs_filter = {'email__iexact': self.email} |
|
1318 |
Lock.lock_email(self.email) |
|
1319 |
elif self.token.get('phone', None): |
|
1320 |
self.phone = self.token['phone'] |
|
1321 |
qs_filter = {'phone': self.phone} |
|
1322 |
Lock.lock_identifier(self.phone) |
|
1323 |
else: |
|
1324 |
messages.warning(request, _('Activation failed')) |
|
1325 |
return utils_misc.redirect(request, 'registration_register') |
|
1326 | ||
1327 |
self.users = User.objects.filter(**qs_filter).order_by('date_joined') |
|
1317 | 1328 |
if self.ou: |
1318 | 1329 |
self.users = self.users.filter(ou=self.ou) |
1319 | 1330 |
self.email_is_unique = app_settings.A2_EMAIL_IS_UNIQUE or app_settings.A2_REGISTRATION_EMAIL_IS_UNIQUE |
... | ... | |
1340 | 1351 |
if app_settings.A2_REGISTRATION_FORM_USERNAME_HELP_TEXT: |
1341 | 1352 |
help_texts['username'] = app_settings.A2_REGISTRATION_FORM_USERNAME_HELP_TEXT |
1342 | 1353 |
required = list(app_settings.A2_REGISTRATION_REQUIRED_FIELDS) + list(required_fields) |
1343 |
if 'email' in fields: |
|
1344 |
fields.remove('email') |
|
1354 |
# identifier fields don't belong here |
|
1355 |
for field in ('email', 'phone'): |
|
1356 |
if field in fields: |
|
1357 |
fields.remove(field) |
|
1345 | 1358 |
for field in self.token.get('skip_fields') or []: |
1346 | 1359 |
if field in fields: |
1347 | 1360 |
fields.remove(field) |
... | ... | |
1385 | 1398 |
else: |
1386 | 1399 |
ou = get_default_ou() |
1387 | 1400 | |
1388 |
attributes = {'email': self.email, 'ou': ou} |
|
1401 |
attributes = {'ou': ou} |
|
1402 |
if hasattr(self, 'email'): |
|
1403 |
attributes['email'] = self.email |
|
1404 |
if hasattr(self, 'phone'): |
|
1405 |
attributes['phone'] = self.phone |
|
1406 | ||
1389 | 1407 |
for key in self.token: |
1390 | 1408 |
if key in app_settings.A2_PRE_REGISTRATION_FIELDS: |
1391 | 1409 |
attributes[key] = self.token[key] |
... | ... | |
1409 | 1427 |
kwargs['instance'] = User.objects.get(id=self.token.get('user_id')) |
1410 | 1428 |
else: |
1411 | 1429 |
init_kwargs = {} |
1412 |
for key in ('email', 'first_name', 'last_name', 'ou'): |
|
1430 |
for key in ('email', 'first_name', 'last_name', 'ou', 'phone'):
|
|
1413 | 1431 |
if key in attributes: |
1414 | 1432 |
init_kwargs[key] = attributes[key] |
1415 | 1433 |
kwargs['instance'] = get_user_model()(**init_kwargs) |
... | ... | |
1425 | 1443 |
ctx = super().get_context_data(**kwargs) |
1426 | 1444 |
ctx['token'] = self.token |
1427 | 1445 |
ctx['users'] = self.users |
1428 |
ctx['email'] = self.email |
|
1446 |
if hasattr(self, 'email'): |
|
1447 |
ctx['email'] = self.email |
|
1448 |
if hasattr(self, 'phone'): |
|
1449 |
ctx['phone'] = self.phone |
|
1429 | 1450 |
ctx['email_is_unique'] = self.email_is_unique |
1430 | 1451 |
ctx['create'] = 'create' in self.request.GET |
1431 | 1452 |
return ctx |
... | ... | |
1544 | 1565 |
return utils_misc.redirect(request, self.get_success_url()) |
1545 | 1566 | |
1546 | 1567 |
def send_registration_success_email(self, user): |
1568 |
# user may not have a registered email by then |
|
1547 | 1569 |
if not user.email: |
1548 | 1570 |
return |
1549 | 1571 |
tests/settings.py | ||
---|---|---|
46 | 46 |
'cache2.example.com', |
47 | 47 |
] |
48 | 48 | |
49 |
KNOWN_SERVICES = { |
|
50 |
'wcs': { |
|
51 |
'default': { |
|
52 |
'title': 'test', |
|
53 |
'url': 'http://example.org', |
|
54 |
'secret': 'chrono', |
|
55 |
'orig': 'chrono', |
|
56 |
'backoffice-menu-url': 'http://example.org/manage/', |
|
57 |
} |
|
58 |
}, |
|
59 |
'passerelle': { |
|
60 |
'default': { |
|
61 |
'title': 'test', |
|
62 |
'url': 'https://foo.whatever.none', |
|
63 |
'secret': 'passerelle', |
|
64 |
'orig': 'passerelle', |
|
65 |
'backoffice-menu-url': 'http://foo.whatever.none/manage/', |
|
66 |
} |
|
67 |
}, |
|
68 |
} |
|
69 | ||
49 | 70 |
A2_AUTH_KERBEROS_ENABLED = False |
50 | 71 | |
51 | 72 |
A2_VALIDATE_EMAIL_DOMAIN = False |
tests/test_registration.py | ||
---|---|---|
14 | 14 |
# You should have received a copy of the GNU Affero General Public License |
15 | 15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
16 | 16 | |
17 |
from datetime import date |
|
17 |
import json |
|
18 |
from datetime import date, timedelta |
|
18 | 19 |
from unittest import mock |
19 | 20 |
from urllib.parse import urlparse |
20 | 21 | |
22 |
import mock |
|
23 |
import requests |
|
21 | 24 |
from django.contrib.auth import REDIRECT_FIELD_NAME, get_user_model |
22 | 25 |
from django.urls import reverse |
23 | 26 |
from django.utils.http import urlquote |
27 |
from httmock import HTTMock, remember_called, urlmatch |
|
24 | 28 | |
25 | 29 |
from authentic2 import models |
26 | 30 |
from authentic2.a2_rbac.utils import get_default_ou |
27 | 31 |
from authentic2.apps.journal.models import Event |
28 | 32 |
from authentic2.forms.profile import modelform_factory |
29 | 33 |
from authentic2.forms.registration import RegistrationCompletionForm |
34 |
from authentic2.models import SMSCode, Token |
|
30 | 35 |
from authentic2.utils import misc as utils_misc |
31 | 36 |
from authentic2.validators import EmailValidator |
32 | 37 | |
... | ... | |
951 | 956 |
resp = resp.form.submit() |
952 | 957 | |
953 | 958 |
assert 'This password is not strong enough' in resp.text |
959 | ||
960 | ||
961 |
@urlmatch(netloc='foo.whatever.none') |
|
962 |
@remember_called |
|
963 |
def sms_service_mock(url, request): |
|
964 |
return { |
|
965 |
'content': {}, |
|
966 |
'headers': { |
|
967 |
'content-type': 'application/json', |
|
968 |
}, |
|
969 |
'status_code': 200, |
|
970 |
} |
|
971 | ||
972 | ||
973 |
def test_phone_registration_wrong_code(app, db, settings): |
|
974 |
settings.A2_ACCEPT_PHONE_AUTHENTICATION = True |
|
975 |
settings.SMS_URL = 'https://foo.whatever.none/' |
|
976 | ||
977 |
resp = app.get(reverse('registration_register')) |
|
978 |
resp.form.set('phone_1', '612345678') |
|
979 |
with HTTMock(sms_service_mock): |
|
980 |
resp = resp.form.submit().follow() |
|
981 |
resp.form.set('registration_code', 'abc') |
|
982 |
resp = resp.form.submit() |
|
983 |
assert not Token.objects.count() |
|
984 |
assert resp.pyquery('li')[0].text_content() == 'Wrong registration code.' |
|
985 | ||
986 | ||
987 |
def test_phone_registration_expired_code(app, db, settings, freezer): |
|
988 |
settings.A2_ACCEPT_PHONE_AUTHENTICATION = True |
|
989 |
settings.SMS_URL = 'https://foo.whatever.none/' |
|
990 | ||
991 |
resp = app.get(reverse('registration_register')) |
|
992 |
resp.form.set('phone_1', '612345678') |
|
993 |
with HTTMock(sms_service_mock): |
|
994 |
resp = resp.form.submit().follow() |
|
995 |
code = SMSCode.objects.get() |
|
996 |
resp.form.set('registration_code', code.value) |
|
997 |
freezer.move_to(timedelta(hours=1)) |
|
998 |
resp = resp.form.submit() |
|
999 |
assert not Token.objects.count() |
|
1000 |
assert resp.pyquery('li')[0].text_content() == 'The code has expired.' |
|
1001 | ||
1002 | ||
1003 |
def test_phone_registration_cancel(app, db, settings, freezer): |
|
1004 |
settings.A2_ACCEPT_PHONE_AUTHENTICATION = True |
|
1005 |
settings.SMS_URL = 'https://foo.whatever.none/' |
|
1006 | ||
1007 |
resp = app.get(reverse('registration_register')) |
|
1008 |
resp.form.set('phone_1', '612345678') |
|
1009 |
with HTTMock(sms_service_mock): |
|
1010 |
resp = resp.form.submit().follow() |
|
1011 |
code = SMSCode.objects.get() |
|
1012 |
resp.form.set('registration_code', code.value) |
|
1013 |
resp.form.submit('cancel').follow() |
|
1014 |
assert not Token.objects.count() |
|
1015 |
assert not SMSCode.objects.count() |
|
1016 | ||
1017 | ||
1018 |
def test_phone_registration_improperly_configured(app, db, settings, freezer, caplog): |
|
1019 |
settings.A2_ACCEPT_PHONE_AUTHENTICATION = True |
|
1020 |
settings.SMS_URL = '' |
|
1021 | ||
1022 |
resp = app.get(reverse('registration_register')) |
|
1023 |
resp.form.set('phone_1', '612345678') |
|
1024 |
with HTTMock(sms_service_mock): |
|
1025 |
resp = resp.form.submit().follow().maybe_follow() |
|
1026 |
assert not Token.objects.count() |
|
1027 |
assert not SMSCode.objects.count() |
|
1028 |
assert ( |
|
1029 |
"Something went wrong while trying to send the SMS registration code to you" |
|
1030 |
in resp.pyquery('li.warning')[0].text_content() |
|
1031 |
) |
|
1032 |
assert caplog.records[0].message == 'settings.SMS_URL is not set' |
|
1033 | ||
1034 | ||
1035 |
def test_phone_registration_connection_error(app, db, settings, freezer, caplog): |
|
1036 |
settings.A2_ACCEPT_PHONE_AUTHENTICATION = True |
|
1037 |
settings.SMS_URL = 'https://foo.whatever.none/' |
|
1038 | ||
1039 |
def mocked_requests_connection_error(*args, **kwargs): |
|
1040 |
raise requests.ConnectionError('unreachable') |
|
1041 | ||
1042 |
resp = app.get(reverse('registration_register')) |
|
1043 |
resp.form.set('phone_1', '612345678') |
|
1044 |
with mock.patch('authentic2.utils.misc.Requests.send') as mock_send: |
|
1045 |
mock_send.side_effect = mocked_requests_connection_error |
|
1046 |
mock_response = mock.Mock(status_code=200) |
|
1047 |
mock_send.return_value = mock_response |
|
1048 |
resp = resp.form.submit().follow().maybe_follow() |
|
1049 |
assert ( |
|
1050 |
"Something went wrong while trying to send the SMS registration code to you" |
|
1051 |
in resp.pyquery('li.warning')[0].text_content() |
|
1052 |
) |
|
1053 |
assert ( |
|
1054 |
caplog.records[0].message |
|
1055 |
== 'sms registration to +33612345678 using https://foo.whatever.none/ failed: unreachable' |
|
1056 |
) |
|
1057 | ||
1058 | ||
1059 |
def test_phone_registration(app, db, settings): |
|
1060 |
settings.A2_ACCEPT_PHONE_AUTHENTICATION = True |
|
1061 |
settings.SMS_URL = 'https://foo.whatever.none/' |
|
1062 |
code_length = settings.SMS_CODE_LENGTH |
|
1063 | ||
1064 |
assert not SMSCode.objects.count() |
|
1065 |
assert not Token.objects.count() |
|
1066 |
resp = app.get(reverse('registration_register')) |
|
1067 |
resp.form.set('phone_1', '612345678') |
|
1068 |
with HTTMock(sms_service_mock): |
|
1069 |
resp = resp.form.submit().follow() |
|
1070 |
body = json.loads(sms_service_mock.call['requests'][0].body) |
|
1071 |
assert body['message'].startswith('Your code is') |
|
1072 |
code = SMSCode.objects.get() |
|
1073 |
assert body['message'][-code_length:] == code.value |
|
1074 |
resp.form.set('registration_code', code.value) |
|
1075 |
resp = resp.form.submit().follow() |
|
1076 |
assert Token.objects.count() == 1 |
|
1077 | ||
1078 |
resp.form.set('password1', 'Password0') |
|
1079 |
resp.form.set('password2', 'Password0') |
|
1080 |
resp.form.set('first_name', 'John') |
|
1081 |
resp.form.set('last_name', 'Doe') |
|
1082 |
resp = resp.form.submit().follow() |
|
1083 |
assert "You have just created an account" in resp.text |
|
1084 | ||
1085 |
user = User.objects.get(first_name='John', last_name='Doe') |
|
1086 |
assert user.phone == '+33612345678' |
|
954 |
- |