views: make sms code trigger a standard registration finalization (#69223)
This commit is contained in:
parent
26f9d2098d
commit
3582bad6b3
|
@ -1309,13 +1309,24 @@ class RegistrationCompletionView(CreateView):
|
|||
request.token = self.token
|
||||
|
||||
self.authentication_method = self.token.get('authentication_method', 'email')
|
||||
self.email = self.token['email']
|
||||
Lock.lock_email(self.email)
|
||||
if 'ou' in self.token:
|
||||
self.ou = OU.objects.get(pk=self.token['ou'])
|
||||
else:
|
||||
self.ou = get_default_ou()
|
||||
self.users = User.objects.filter(email__iexact=self.email).order_by('date_joined')
|
||||
|
||||
if self.token.get('email', None):
|
||||
self.email = self.token['email']
|
||||
qs_filter = {'email__iexact': self.email}
|
||||
Lock.lock_email(self.email)
|
||||
elif self.token.get('phone', None):
|
||||
self.phone = self.token['phone']
|
||||
qs_filter = {'phone': self.phone}
|
||||
Lock.lock_identifier(self.phone)
|
||||
else:
|
||||
messages.warning(request, _('Activation failed'))
|
||||
return utils_misc.redirect(request, 'registration_register')
|
||||
|
||||
self.users = User.objects.filter(**qs_filter).order_by('date_joined')
|
||||
if self.ou:
|
||||
self.users = self.users.filter(ou=self.ou)
|
||||
self.email_is_unique = app_settings.A2_EMAIL_IS_UNIQUE or app_settings.A2_REGISTRATION_EMAIL_IS_UNIQUE
|
||||
|
@ -1342,8 +1353,10 @@ class RegistrationCompletionView(CreateView):
|
|||
if app_settings.A2_REGISTRATION_FORM_USERNAME_HELP_TEXT:
|
||||
help_texts['username'] = app_settings.A2_REGISTRATION_FORM_USERNAME_HELP_TEXT
|
||||
required = list(app_settings.A2_REGISTRATION_REQUIRED_FIELDS) + list(required_fields)
|
||||
if 'email' in fields:
|
||||
fields.remove('email')
|
||||
# identifier fields don't belong here
|
||||
for field in ('email', 'phone'):
|
||||
if field in fields:
|
||||
fields.remove(field)
|
||||
for field in self.token.get('skip_fields') or []:
|
||||
if field in fields:
|
||||
fields.remove(field)
|
||||
|
@ -1387,7 +1400,12 @@ class RegistrationCompletionView(CreateView):
|
|||
else:
|
||||
ou = get_default_ou()
|
||||
|
||||
attributes = {'email': self.email, 'ou': ou}
|
||||
attributes = {'ou': ou}
|
||||
if hasattr(self, 'email'):
|
||||
attributes['email'] = self.email
|
||||
if hasattr(self, 'phone'):
|
||||
attributes['phone'] = self.phone
|
||||
|
||||
for key in self.token:
|
||||
if key in app_settings.A2_PRE_REGISTRATION_FIELDS:
|
||||
attributes[key] = self.token[key]
|
||||
|
@ -1411,7 +1429,7 @@ class RegistrationCompletionView(CreateView):
|
|||
kwargs['instance'] = User.objects.get(id=self.token.get('user_id'))
|
||||
else:
|
||||
init_kwargs = {}
|
||||
for key in ('email', 'first_name', 'last_name', 'ou'):
|
||||
for key in ('email', 'first_name', 'last_name', 'ou', 'phone'):
|
||||
if key in attributes:
|
||||
init_kwargs[key] = attributes[key]
|
||||
kwargs['instance'] = get_user_model()(**init_kwargs)
|
||||
|
@ -1427,7 +1445,10 @@ class RegistrationCompletionView(CreateView):
|
|||
ctx = super().get_context_data(**kwargs)
|
||||
ctx['token'] = self.token
|
||||
ctx['users'] = self.users
|
||||
ctx['email'] = self.email
|
||||
if hasattr(self, 'email'):
|
||||
ctx['email'] = self.email
|
||||
if hasattr(self, 'phone'):
|
||||
ctx['phone'] = self.phone
|
||||
ctx['email_is_unique'] = self.email_is_unique
|
||||
ctx['create'] = 'create' in self.request.GET
|
||||
return ctx
|
||||
|
@ -1546,6 +1567,7 @@ class RegistrationCompletionView(CreateView):
|
|||
return utils_misc.redirect(request, self.get_success_url())
|
||||
|
||||
def send_registration_success_email(self, user):
|
||||
# user may not have a registered email by then
|
||||
if not user.email:
|
||||
return
|
||||
|
||||
|
|
|
@ -46,6 +46,27 @@ ALLOWED_HOSTS = ALLOWED_HOSTS + [ # pylint: disable=used-before-assignment
|
|||
'cache2.example.com',
|
||||
]
|
||||
|
||||
KNOWN_SERVICES = {
|
||||
'wcs': {
|
||||
'default': {
|
||||
'title': 'test',
|
||||
'url': 'http://example.org',
|
||||
'secret': 'chrono',
|
||||
'orig': 'chrono',
|
||||
'backoffice-menu-url': 'http://example.org/manage/',
|
||||
}
|
||||
},
|
||||
'passerelle': {
|
||||
'default': {
|
||||
'title': 'test',
|
||||
'url': 'https://foo.whatever.none',
|
||||
'secret': 'passerelle',
|
||||
'orig': 'passerelle',
|
||||
'backoffice-menu-url': 'http://foo.whatever.none/manage/',
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
A2_AUTH_KERBEROS_ENABLED = False
|
||||
|
||||
A2_VALIDATE_EMAIL_DOMAIN = False
|
||||
|
|
|
@ -14,19 +14,23 @@
|
|||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from datetime import date
|
||||
import json
|
||||
from datetime import date, timedelta
|
||||
from unittest import mock
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import requests
|
||||
from django.contrib.auth import REDIRECT_FIELD_NAME, get_user_model
|
||||
from django.urls import reverse
|
||||
from django.utils.http import urlquote
|
||||
from httmock import HTTMock, remember_called, urlmatch
|
||||
|
||||
from authentic2 import models
|
||||
from authentic2.a2_rbac.utils import get_default_ou
|
||||
from authentic2.apps.journal.models import Event
|
||||
from authentic2.forms.profile import modelform_factory
|
||||
from authentic2.forms.registration import RegistrationCompletionForm
|
||||
from authentic2.models import SMSCode, Token
|
||||
from authentic2.utils import misc as utils_misc
|
||||
from authentic2.validators import EmailValidator
|
||||
|
||||
|
@ -953,3 +957,131 @@ def test_registration_no_identifier(app, db, settings):
|
|||
resp = app.get(reverse('registration_register'))
|
||||
resp = resp.form.submit()
|
||||
assert 'Please provide an email address or a mobile' in resp.text
|
||||
|
||||
|
||||
@urlmatch(netloc='foo.whatever.none')
|
||||
@remember_called
|
||||
def sms_service_mock(url, request):
|
||||
return {
|
||||
'content': {},
|
||||
'headers': {
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
'status_code': 200,
|
||||
}
|
||||
|
||||
|
||||
def test_phone_registration_wrong_code(app, db, settings):
|
||||
settings.A2_ACCEPT_PHONE_AUTHENTICATION = True
|
||||
settings.SMS_URL = 'https://foo.whatever.none/'
|
||||
|
||||
resp = app.get(reverse('registration_register'))
|
||||
resp.form.set('phone_1', '612345678')
|
||||
with HTTMock(sms_service_mock):
|
||||
resp = resp.form.submit().follow()
|
||||
resp.form.set('registration_code', 'abc')
|
||||
resp = resp.form.submit()
|
||||
assert not Token.objects.count()
|
||||
assert resp.pyquery('li')[0].text_content() == 'Wrong registration code.'
|
||||
|
||||
|
||||
def test_phone_registration_expired_code(app, db, settings, freezer):
|
||||
settings.A2_ACCEPT_PHONE_AUTHENTICATION = True
|
||||
settings.SMS_URL = 'https://foo.whatever.none/'
|
||||
|
||||
resp = app.get(reverse('registration_register'))
|
||||
resp.form.set('phone_1', '612345678')
|
||||
with HTTMock(sms_service_mock):
|
||||
resp = resp.form.submit().follow()
|
||||
code = SMSCode.objects.get()
|
||||
resp.form.set('registration_code', code.value)
|
||||
freezer.move_to(timedelta(hours=1))
|
||||
resp = resp.form.submit()
|
||||
assert not Token.objects.count()
|
||||
assert resp.pyquery('li')[0].text_content() == 'The code has expired.'
|
||||
|
||||
|
||||
def test_phone_registration_cancel(app, db, settings, freezer):
|
||||
settings.A2_ACCEPT_PHONE_AUTHENTICATION = True
|
||||
settings.SMS_URL = 'https://foo.whatever.none/'
|
||||
|
||||
resp = app.get(reverse('registration_register'))
|
||||
resp.form.set('phone_1', '612345678')
|
||||
with HTTMock(sms_service_mock):
|
||||
resp = resp.form.submit().follow()
|
||||
code = SMSCode.objects.get()
|
||||
resp.form.set('registration_code', code.value)
|
||||
resp.form.submit('cancel').follow()
|
||||
assert not Token.objects.count()
|
||||
assert not SMSCode.objects.count()
|
||||
|
||||
|
||||
def test_phone_registration_improperly_configured(app, db, settings, freezer, caplog):
|
||||
settings.A2_ACCEPT_PHONE_AUTHENTICATION = True
|
||||
settings.SMS_URL = ''
|
||||
|
||||
resp = app.get(reverse('registration_register'))
|
||||
resp.form.set('phone_1', '612345678')
|
||||
with HTTMock(sms_service_mock):
|
||||
resp = resp.form.submit().follow().maybe_follow()
|
||||
assert not Token.objects.count()
|
||||
assert not SMSCode.objects.count()
|
||||
assert (
|
||||
"Something went wrong while trying to send the SMS registration code to you"
|
||||
in resp.pyquery('li.warning')[0].text_content()
|
||||
)
|
||||
assert caplog.records[0].message == 'settings.SMS_URL is not set'
|
||||
|
||||
|
||||
def test_phone_registration_connection_error(app, db, settings, freezer, caplog):
|
||||
settings.A2_ACCEPT_PHONE_AUTHENTICATION = True
|
||||
settings.SMS_URL = 'https://foo.whatever.none/'
|
||||
|
||||
def mocked_requests_connection_error(*args, **kwargs):
|
||||
raise requests.ConnectionError('unreachable')
|
||||
|
||||
resp = app.get(reverse('registration_register'))
|
||||
resp.form.set('phone_1', '612345678')
|
||||
with mock.patch('authentic2.utils.sms.Requests.send') as mock_send:
|
||||
mock_send.side_effect = mocked_requests_connection_error
|
||||
mock_response = mock.Mock(status_code=200)
|
||||
mock_send.return_value = mock_response
|
||||
resp = resp.form.submit().follow().maybe_follow()
|
||||
assert (
|
||||
"Something went wrong while trying to send the SMS registration code to you"
|
||||
in resp.pyquery('li.warning')[0].text_content()
|
||||
)
|
||||
assert (
|
||||
caplog.records[0].message
|
||||
== 'sms registration to +33612345678 using https://foo.whatever.none/ failed: unreachable'
|
||||
)
|
||||
|
||||
|
||||
def test_phone_registration(app, db, settings):
|
||||
settings.A2_ACCEPT_PHONE_AUTHENTICATION = True
|
||||
settings.SMS_URL = 'https://foo.whatever.none/'
|
||||
code_length = settings.SMS_CODE_LENGTH
|
||||
|
||||
assert not SMSCode.objects.count()
|
||||
assert not Token.objects.count()
|
||||
resp = app.get(reverse('registration_register'))
|
||||
resp.form.set('phone_1', '612345678')
|
||||
with HTTMock(sms_service_mock):
|
||||
resp = resp.form.submit().follow()
|
||||
body = json.loads(sms_service_mock.call['requests'][0].body)
|
||||
assert body['message'].startswith('Your code is')
|
||||
code = SMSCode.objects.get()
|
||||
assert body['message'][-code_length:] == code.value
|
||||
resp.form.set('registration_code', code.value)
|
||||
resp = resp.form.submit().follow()
|
||||
assert Token.objects.count() == 1
|
||||
|
||||
resp.form.set('password1', 'Password0')
|
||||
resp.form.set('password2', 'Password0')
|
||||
resp.form.set('first_name', 'John')
|
||||
resp.form.set('last_name', 'Doe')
|
||||
resp = resp.form.submit().follow()
|
||||
assert "You have just created an account" in resp.text
|
||||
|
||||
user = User.objects.get(first_name='John', last_name='Doe')
|
||||
assert user.phone == '+33612345678'
|
||||
|
|
Loading…
Reference in New Issue