authentic/tests/test_registration.py

1089 lines
40 KiB
Python

# authentic2 - versatile identity manager
# Copyright (C) 2010-2019 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# 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/>.
import json
from datetime import date, timedelta
from unittest import mock
from urllib.parse import quote, urlparse
import requests
from django.contrib.auth import REDIRECT_FIELD_NAME, get_user_model
from django.urls import reverse
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
from .utils import assert_event, get_link_from_mail
User = get_user_model()
def test_registration_success(app, db, settings, mailoutbox, external_redirect):
next_url, good_next_url = external_redirect
settings.LANGUAGE_CODE = 'en-us'
settings.DEFAULT_FROM_EMAIL = 'show only addr <noreply@example.net>'
# disable existing attributes
models.Attribute.objects.update(disabled=True)
url = utils_misc.make_url('registration_register', params={REDIRECT_FIELD_NAME: next_url})
response = app.get(url)
response.form.set('email', 'testbot@entrouvert.com')
response = response.form.submit()
assert_event('user.registration.request', email='testbot@entrouvert.com')
assert urlparse(response['Location']).path == reverse('registration_complete')
if not good_next_url:
assert not urlparse(response['Location']).query
response = response.follow()
if good_next_url:
assert response.pyquery('a[href="%s"]' % next_url)
else:
assert response.pyquery('a[href="/"]')
assert 'Follow the instructions' in response.text
assert 'testbot@entrouvert.com' in response.text
assert 'considered as spam' in response.text
assert '"noreply@example.net"' in response.text
assert 'show only addr' not in response.text
assert len(mailoutbox) == 1
link = get_link_from_mail(mailoutbox[0])
# test password validation
response = app.get(link)
response.form.set('password1', 'toto')
response.form.set('password2', 'toto')
response = response.form.submit()
assert '8 characters' in response.text
# set valid password
response.form.set('password1', 'T0==toto')
response.form.set('password2', 'T0==toto')
response = response.form.submit().maybe_follow()
if good_next_url:
assert 'You have just created an account.' in response.text
assert next_url in response.text
assert response.request.path == '/continue/'
else:
assert response.request.path == '/'
assert 'You have just created an account.' in response.text
assert User.objects.count() == 1
assert len(mailoutbox) == 2
assert 'was successful' in mailoutbox[1].body
new_user = User.objects.get()
assert_event('user.registration', user=new_user, how='email')
assert not Event.objects.filter(type__name='user.login').exists()
assert new_user.email == 'testbot@entrouvert.com'
assert new_user.username is None
assert new_user.check_password('T0==toto')
assert new_user.is_active
assert not new_user.is_staff
assert not new_user.is_superuser
assert str(app.session['_auth_user_id']) == str(new_user.pk)
response = app.get('/login/')
response.form.set('username', 'testbot@entrouvert.com')
response.form.set('password', 'T0==toto')
response = response.form.submit(name='login-password-submit')
assert urlparse(response['Location']).path == reverse('auth_homepage')
def test_registration_realm(app, db, settings, mailoutbox):
settings.LANGUAGE_CODE = 'en-us'
settings.A2_REGISTRATION_REALM = 'realm'
settings.A2_REDIRECT_WHITELIST = ['http://relying-party.org/']
settings.A2_REQUIRED_FIELDS = ['username']
# disable existing attributes
models.Attribute.objects.update(disabled=True)
next_url = 'http://relying-party.org/'
url = utils_misc.make_url('registration_register', params={REDIRECT_FIELD_NAME: next_url})
response = app.get(url)
response.form.set('email', 'testbot@entrouvert.com')
response = response.form.submit()
assert urlparse(response['Location']).path == reverse('registration_complete')
response = response.follow()
assert 'Follow the instructions' in response.text
assert 'testbot@entrouvert.com' in response.text
assert len(mailoutbox) == 1
link = get_link_from_mail(mailoutbox[0])
# register
response = app.get(link)
response.form.set('username', 'toto')
response.form.set('password1', 'T0==toto')
response.form.set('password2', 'T0==toto')
response = response.form.submit().follow()
assert 'You have just created an account.' in response.text
assert next_url in response.text
assert len(mailoutbox) == 2
assert 'was successful' in mailoutbox[1].body
# verify user has expected attributes
new_user = User.objects.get()
assert new_user.username == 'toto@realm'
assert new_user.email == 'testbot@entrouvert.com'
assert new_user.check_password('T0==toto')
assert new_user.is_active
assert not new_user.is_staff
assert not new_user.is_superuser
assert str(app.session['_auth_user_id']) == str(new_user.pk)
# test login
response = app.get('/login/')
response.form.set('username', 'testbot@entrouvert.com')
response.form.set('password', 'T0==toto')
response = response.form.submit(name='login-password-submit')
assert urlparse(response['Location']).path == reverse('auth_homepage')
def test_registration_email_validation(app, db, monkeypatch, settings):
settings.A2_VALIDATE_EMAIL_DOMAIN = True
monkeypatch.setattr(EmailValidator, 'query_mxs', lambda x, y: ['mx1.entrouvert.org'])
resp = app.get(reverse('registration_register'))
resp.form.set('email', 'testbot@entrouvert.com')
resp = resp.form.submit().follow()
assert 'Follow the instructions' in resp.text
monkeypatch.setattr(EmailValidator, 'query_mxs', lambda x, y: [])
resp = app.get(reverse('registration_register'))
resp.form.set('email', 'testbot@entrouvert.com')
resp = resp.form.submit()
assert 'domain is invalid' in resp.text
def test_username_settings(app, db, settings, mailoutbox):
settings.LANGUAGE_CODE = 'en-us'
settings.A2_REGISTRATION_FORM_USERNAME_REGEX = r'^(ab)+$'
settings.A2_REGISTRATION_FORM_USERNAME_LABEL = 'Identifiant'
settings.A2_REGISTRATION_FORM_USERNAME_HELP_TEXT = 'Bien remplir'
settings.A2_REGISTRATION_FIELDS = ['username']
settings.A2_REQUIRED_FIELDS = ['username']
# disable existing attributes
models.Attribute.objects.update(disabled=True)
response = app.get(reverse('registration_register'))
response.form.set('email', 'testbot@entrouvert.com')
response = response.form.submit()
assert urlparse(response['Location']).path == reverse('registration_complete')
response = response.follow()
assert 'Follow the instructions' in response.text
assert 'testbot@entrouvert.com' in response.text
assert len(mailoutbox) == 1
link = get_link_from_mail(mailoutbox[0])
# register
response = app.get(link)
# check form render has changed
assert response.pyquery('[for=id_username]').text() == 'Identifiant:'
for key in ['username', 'password1', 'password2']:
assert response.pyquery('[for=id_%s]' % key)
assert response.pyquery('[for=id_%s]' % key).attr('class') == 'form-field-required'
assert response.pyquery('#id_username').next('.hint').text() == 'Bien remplir'
assert not response.pyquery('.errorlist')
# check username is validated using regexp
response.form.set('username', 'abx')
response.form.set('password1', 'T0==toto')
response.form.set('password2', 'T0==toto')
response = response.form.submit()
assert 'Enter a valid value' in response.text
# check regexp accepts some valid values
response.form.set('username', 'abab')
response.form.set('password1', 'T0==toto')
response.form.set('password2', 'T0==toto')
response = response.form.submit()
assert urlparse(response['Location']).path == reverse('auth_homepage')
response = response.follow()
assert 'You have just created an account.' in response.text
assert len(mailoutbox) == 2
assert 'was successful' in mailoutbox[1].body
def test_username_is_unique(app, db, settings, mailoutbox):
settings.LANGUAGE_CODE = 'en-us'
settings.A2_REGISTRATION_FIELDS = ['username']
settings.A2_REQUIRED_FIELDS = ['username']
settings.A2_USERNAME_IS_UNIQUE = True
# disable existing attributes
models.Attribute.objects.update(disabled=True)
response = app.get(reverse('registration_register'))
response.form.set('email', 'testbot@entrouvert.com')
response = response.form.submit()
assert urlparse(response['Location']).path == reverse('registration_complete')
response = response.follow()
assert 'Follow the instructions' in response.text
assert 'testbot@entrouvert.com' in response.text
assert len(mailoutbox) == 1
link = get_link_from_mail(mailoutbox[0])
response = app.get(link)
response.form.set('username', 'john.doe')
response.form.set('password1', 'T0==toto')
response.form.set('password2', 'T0==toto')
response = response.form.submit()
assert urlparse(response['Location']).path == reverse('auth_homepage')
response = response.follow()
assert 'You have just created an account.' in response.text
assert len(mailoutbox) == 2
assert 'was successful' in mailoutbox[1].body
# logout
app.session.flush()
# try again
response = app.get(reverse('registration_register'))
response.form.set('email', 'testbot@entrouvert.com')
response = response.form.submit()
link = get_link_from_mail(mailoutbox[2])
response = app.get(link)
response = response.click('create')
response.form.set('username', 'john.doe')
response.form.set('password1', 'T0==toto')
response.form.set('password2', 'T0==toto')
response = response.form.submit()
assert 'This username is already in use. Please supply a different username.' in response.text
def test_email_is_unique(app, db, settings, mailoutbox):
settings.LANGUAGE_CODE = 'en-us'
settings.A2_EMAIL_IS_UNIQUE = True
# disable existing attributes
models.Attribute.objects.update(disabled=True)
response = app.get('/register/')
response.form.set('email', 'testbot@entrouvert.com')
response = response.form.submit()
assert urlparse(response['Location']).path == reverse('registration_complete')
response = response.follow()
assert 'Follow the instructions' in response.text
assert 'testbot@entrouvert.com' in response.text
assert len(mailoutbox) == 1
link = get_link_from_mail(mailoutbox[0])
response = app.get(link)
response.form.set('password1', 'T0==toto')
response.form.set('password2', 'T0==toto')
response = response.form.submit()
assert urlparse(response['Location']).path == reverse('auth_homepage')
response = response.follow()
assert 'You have just created an account.' in response.text
assert len(mailoutbox) == 2
assert 'was successful' in mailoutbox[1].body
# logout
app.session.flush()
response = app.get('/register/?next=/whatever/')
response.form.set('email', 'testbot@entrouvert.com')
response = response.form.submit()
assert urlparse(response['Location']).path == reverse('registration_complete')
response = response.follow()
assert 'Follow the instructions' in response.text
assert 'testbot@entrouvert.com' in response.text
assert 'This email address is already in use.' not in response.text
assert len(mailoutbox) == 3
assert 'You already have' in mailoutbox[2].body
link = get_link_from_mail(mailoutbox[2])
response = app.get(link)
# check next_url was preserved
assert response.location == '/whatever/'
def test_attribute_model(app, db, settings, mailoutbox):
settings.LANGUAGE_CODE = 'en-us'
# disable existing attributes
models.Attribute.objects.update(disabled=True)
models.Attribute.objects.create(label='Prénom', name='prenom', required=True, kind='string')
models.Attribute.objects.create(
label='Nom', name='nom', asked_on_registration=True, user_visible=True, kind='string'
)
models.Attribute.objects.create(label='Profession', name='profession', user_editable=True, kind='string')
response = app.get(reverse('registration_register'))
response.form.set('email', 'testbot@entrouvert.com')
response = response.form.submit()
assert urlparse(response['Location']).path == reverse('registration_complete')
response = response.follow()
assert 'Follow the instructions' in response.text
assert 'testbot@entrouvert.com' in response.text
assert len(mailoutbox) == 1
link = get_link_from_mail(mailoutbox[0])
response = app.get(link)
for key in ['prenom', 'nom', 'password1', 'password2']:
assert response.pyquery('#id_%s' % key)
response.form.set('prenom', 'John')
response.form.set('nom', 'Doe')
response.form.set('password1', 'T0==toto')
response.form.set('password2', 'T0==toto')
response = response.form.submit()
assert urlparse(response['Location']).path == reverse('auth_homepage')
response = response.follow()
assert 'You have just created an account.' in response.text
assert len(mailoutbox) == 2
assert 'was successful' in mailoutbox[1].body
response = app.get(reverse('account_management'))
assert 'Nom' in response.text
assert 'Prénom' not in response.text
response = app.get(reverse('profile_edit'))
assert 'profession' in response.form.fields
assert 'prenom' not in response.form.fields
assert 'nom' not in response.form.fields
assert response.pyquery('[for=id_profession]')
assert not response.pyquery('[for=id_profession].form-field-required')
response.form.set('profession', 'pompier')
response = response.form.submit()
assert urlparse(response['Location']).path == reverse('account_management')
response = response.follow()
assert 'Nom' in response.text
assert 'Doe' in response.text
assert 'Profession' not in response.text
assert 'pompier' not in response.text
assert 'Prénom' not in response.text
assert 'John' not in response.text
def test_registration_email_blacklist(app, settings, db):
def test_register(email):
response = app.get('/register/')
assert 'email' in response.form.fields
response.form.set('email', email)
response = response.form.submit()
return response.status_code == 302
settings.A2_REGISTRATION_EMAIL_BLACKLIST = [r'a*@example\.com']
assert not test_register('aaaa@example.com')
assert test_register('aaaa@example.com.zob')
assert test_register('baaaa@example.com')
settings.A2_REGISTRATION_EMAIL_BLACKLIST = [r'a*@example\.com', r'^ba*@example\.com$']
assert not test_register('aaaa@example.com')
assert not test_register('baaaa@example.com')
assert test_register('bbaaaa@example.com')
def test_registration_bad_email(app, db, settings):
settings.LANGUAGE_CODE = 'en-us'
response = app.post(reverse('registration_register'), params={'email': 'fred@0d..be'}, status=200)
assert 'Enter a valid email address.' in response.context['form'].errors['email']
response = app.post(reverse('registration_register'), params={'email': 'ééééé'}, status=200)
assert 'Enter a valid email address.' in response.context['form'].errors['email']
response = app.post(reverse('registration_register'), params={'email': ''}, status=200)
assert 'This field is required.' in response.context['form'].errors['email']
def test_registration_confirm_data(app, settings, db, rf):
# make first name not required
models.Attribute.objects.filter(name='first_name').update(required=False)
activation_url = utils_misc.build_activation_url(
rf.post('/register/'),
email='john.doe@example.com',
next_url='/',
first_name='John',
last_name='Doe',
no_password=True,
confirm_data=False,
)
response = app.get(activation_url, status=302)
activation_url = utils_misc.build_activation_url(
rf.post('/register/'),
email='john.doe@example.com',
next_url='/',
last_name='Doe',
no_password=True,
confirm_data=False,
)
response = app.get(activation_url, status=200)
assert 'form' in response.context
assert set(response.context['form'].fields.keys()) == {'first_name', 'last_name'}
activation_url = utils_misc.build_activation_url(
rf.post('/register/'),
email='john.doe@example.com',
next_url='/',
last_name='Doe',
no_password=True,
confirm_data='required',
)
response = app.get(activation_url, status=302)
def test_revalidate_email(app, rf, db, settings, mailoutbox):
settings.LANGUAGE_CODE = 'en-us'
# disable existing attributes
models.Attribute.objects.update(disabled=True)
url = utils_misc.build_activation_url(
rf.get('/'), 'testbot@entrouvert.com', next_url=None, valid_email=False, franceconnect=True
)
assert len(mailoutbox) == 0
# register
response = app.get(url)
response.form.set('email', 'johndoe@example.com')
response.form.set('password1', 'T0==toto')
response.form.set('password2', 'T0==toto')
response = response.form.submit()
assert urlparse(response['Location']).path == reverse('registration_complete')
response = response.follow()
assert 'Follow the instructions' in response.text
assert 'johndoe@example.com' in response.text
assert len(mailoutbox) == 1
def test_email_is_unique_multiple_objects_returned(app, db, settings, mailoutbox, rf):
settings.LANGUAGE_CODE = 'en-us'
settings.A2_REGISTRATION_EMAIL_IS_UNIQUE = True
# Create two user objects
User.objects.create(email='testbot@entrouvert.com')
User.objects.create(email='testbot@entrouvert.com')
url = utils_misc.build_activation_url(
rf.get('/'),
'testbot@entrouvert.com',
first_name='Test',
last_name='Bot',
password='ABcd12345',
next_url=None,
valid_email=False,
franceconnect=True,
)
response = app.get(url)
assert 'This email address is already in use.' in response.text
def test_username_is_unique_multiple_objects_returned(app, db, settings, mailoutbox, rf):
settings.LANGUAGE_CODE = 'en-us'
settings.A2_REGISTRATION_USERNAME_IS_UNIQUE = True
settings.A2_REQUIRED_FIELDS = ['username', 'first_name', 'last_name']
# Create two user objects
User.objects.create(username='testbot', email='testbot1@entrouvert.com')
User.objects.create(username='testbot', email='testbot2@entrouvert.com')
url = utils_misc.build_activation_url(
rf.get('/'),
'testbot@entrouvert.com',
username='testbot',
first_name='Test',
last_name='Bot',
password='ABcd12345',
next_url=None,
valid_email=False,
franceconnect=True,
)
response = app.get(url)
assert 'This username is already in use.' in response.text
def test_registration_redirect(app, db, settings, mailoutbox, external_redirect):
next_url, good_next_url = external_redirect
settings.A2_REGISTRATION_REDIRECT = 'http://cms/welcome/'
settings.LANGUAGE_CODE = 'en-us'
new_next_url = settings.A2_REGISTRATION_REDIRECT
if good_next_url:
new_next_url += '?next=' + quote(next_url)
# disable existing attributes
models.Attribute.objects.update(disabled=True)
url = utils_misc.make_url('registration_register', params={REDIRECT_FIELD_NAME: next_url})
response = app.get(url)
response.form.set('email', 'testbot@entrouvert.com')
response = response.form.submit()
assert urlparse(response['Location']).path == reverse('registration_complete')
response = response.follow()
assert 'Follow the instructions' in response.text
assert 'testbot@entrouvert.com' in response.text
assert len(mailoutbox) == 1
link = get_link_from_mail(mailoutbox[0])
response = app.get(link)
response.form.set('password1', 'T0==toto')
response.form.set('password2', 'T0==toto')
response = response.form.submit().follow()
assert 'You have just created an account.' in response.text
assert new_next_url in response.text
assert User.objects.count() == 1
assert len(mailoutbox) == 2
assert 'was successful' in mailoutbox[1].body
new_user = User.objects.get()
assert new_user.email == 'testbot@entrouvert.com'
assert new_user.username is None
assert new_user.check_password('T0==toto')
assert new_user.is_active
assert not new_user.is_staff
assert not new_user.is_superuser
assert str(app.session['_auth_user_id']) == str(new_user.pk)
response = app.get('/login/')
response.form.set('username', 'testbot@entrouvert.com')
response.form.set('password', 'T0==toto')
response = response.form.submit(name='login-password-submit')
assert urlparse(response['Location']).path == reverse('auth_homepage')
def test_registration_redirect_tuple(app, db, settings, mailoutbox, external_redirect):
next_url, good_next_url = external_redirect
settings.A2_REGISTRATION_REDIRECT = 'http://cms/welcome/', 'target'
new_next_url = settings.A2_REGISTRATION_REDIRECT[0]
if good_next_url:
new_next_url += '?target=' + quote(next_url)
# disable existing attributes
models.Attribute.objects.update(disabled=True)
url = utils_misc.make_url('registration_register', params={REDIRECT_FIELD_NAME: next_url})
response = app.get(url)
response.form.set('email', 'testbot@entrouvert.com')
response = response.form.submit()
response = response.follow()
link = get_link_from_mail(mailoutbox[0])
response = app.get(link)
response.form.set('password1', 'T0==toto')
response.form.set('password2', 'T0==toto')
response = response.form.submit().follow()
assert new_next_url in response.text
def test_registration_activate_passwords_not_equal(app, db, settings, mailoutbox):
settings.LANGUAGE_CODE = 'en-us'
settings.A2_EMAIL_IS_UNIQUE = True
response = app.get(reverse('registration_register'))
response.form.set('email', 'testbot@entrouvert.com')
response = response.form.submit()
response = response.follow()
link = get_link_from_mail(mailoutbox[0])
response = app.get(link)
response.form.set('first_name', 'john')
response.form.set('last_name', 'doe')
response.form.set('password1', 'azerty12AZ')
response.form.set('password2', 'AAAazerty12AZ')
response = response.form.submit()
assert "The two password fields didn" in response.text
def test_authentication_method(app, db, rf, hooks):
activation_url = utils_misc.build_activation_url(
rf.post('/register/'),
email='john.doe@example.com',
next_url='/',
first_name='John',
last_name='Doe',
no_password=True,
confirm_data=False,
)
app.get(activation_url)
assert len(hooks.calls['event']) == 2
assert hooks.calls['event'][-2]['kwargs']['name'] == 'registration'
assert hooks.calls['event'][-2]['kwargs']['authentication_method'] == 'email'
assert hooks.calls['event'][-1]['kwargs']['name'] == 'login'
assert hooks.calls['event'][-1]['kwargs']['how'] == 'email'
activation_url = utils_misc.build_activation_url(
rf.post('/register/'),
email='jane.doe@example.com',
next_url='/',
first_name='Jane',
last_name='Doe',
no_password=True,
authentication_method='another',
confirm_data=False,
)
app.get(activation_url)
assert len(hooks.calls['event']) == 4
assert hooks.calls['event'][-2]['kwargs']['name'] == 'registration'
assert hooks.calls['event'][-2]['kwargs']['authentication_method'] == 'another'
assert hooks.calls['event'][-1]['kwargs']['name'] == 'login'
assert hooks.calls['event'][-1]['kwargs']['how'] == 'another'
def test_registration_with_email_suggestions(app, db, settings):
url = utils_misc.make_url('registration_register')
response = app.get(url)
assert "email_domains_suggestions.js" in response.text
assert "field-live-hint" in response.text
settings.A2_SUGGESTED_EMAIL_DOMAINS = []
response = app.get(url)
assert "email_domains_suggestions.js" not in response.text
assert "field-live-hint" not in response.text
def test_registration_no_email_full_profile_no_password(app, db, rf, mailoutbox):
models.Attribute.objects.create(kind='birthdate', name='birthdate', label='birthdate', required=True)
data = {
'email': 'john.doe@example.com',
'first_name': 'John',
'last_name': 'Doe',
'confirm_data': 'required',
'no_password': True,
'valid_email': False,
'franceconnect': True,
'authentication_method': 'france-connect',
}
activation_url = utils_misc.build_activation_url(rf.post('/register/'), next_url='/', **data)
response = app.get(activation_url)
response.form.set('first_name', data['first_name'])
response.form.set('last_name', data['last_name'])
response.form.set('birthdate', '1981-01-01')
response.form.set('email', 'john.doe2@example.com')
response = response.form.submit().follow()
link = get_link_from_mail(mailoutbox[0])
response = app.get(link)
assert response.location == '/'
assert User.objects.count() == 1
user = User.objects.get()
assert user.first_name == 'John'
assert user.last_name == 'Doe'
assert user.attributes.birthdate == date(1981, 1, 1)
assert user.email == 'john.doe2@example.com'
assert user.email_verified is True
def test_registration_link_unique_use(app, db, mailoutbox):
models.Attribute.objects.update(disabled=True)
response = app.get(reverse('registration_register'))
response.form.set('email', 'testbot@entrouvert.com')
response = response.form.submit()
link = get_link_from_mail(mailoutbox[0])
response = app.get(link)
response.form.set('password1', 'T0==toto')
# accessing multiple times work
response = app.get(link)
response.form.set('password1', 'T0==toto')
response.form.set('password2', 'T0==toto')
response = response.form.submit().follow()
assert 'You have just created an account.' in response.text
response = app.get(link)
assert urlparse(response['Location']).path == reverse('registration_register')
response = response.follow()
assert 'activation key is unknown or expired' in response.text
def test_double_registration_impossible(app, db, mailoutbox):
models.Attribute.objects.update(disabled=True)
for _ in range(2):
response = app.get(reverse('registration_register'))
response.form.set('email', 'testbot@entrouvert.com')
response = response.form.submit()
assert len(mailoutbox) == 2
link1, link2 = get_link_from_mail(mailoutbox[0]), get_link_from_mail(mailoutbox[1])
response = app.get(link1)
assert urlparse(response['Location']).path == reverse('registration_register')
response = response.follow()
assert 'activation key is unknown or expired' in response.text
response = app.get(link2)
response.form.set('password1', 'T0==toto')
response.form.set('password2', 'T0==toto')
response = response.form.submit().follow()
assert 'You have just created an account.' in response.text
def test_registration_email_not_verified_required_and_unrequired_attributes(app, db, rf, mailoutbox):
models.Attribute.objects.create(
kind='birthdate', name='birthdate', label='birthdate', asked_on_registration=False
)
models.Attribute.objects.create(
kind='string', name='preferred_color', label='couleur préférée', required=True
)
data = {
'email': 'john.doe@example.com',
'first_name': 'John',
'last_name': 'Doe',
'confirm_data': 'required',
'no_password': True,
'valid_email': False,
'franceconnect': True,
'authentication_method': 'france-connect',
}
activation_url = utils_misc.build_activation_url(rf.post('/register/'), next_url='/', **data)
response = app.get(activation_url)
response.form.set('first_name', data['first_name'])
response.form.set('last_name', data['last_name'])
response.form.set('preferred_color', 'bleu')
response.form.set('email', 'john.doe2@example.com')
response = response.form.submit().follow()
link = get_link_from_mail(mailoutbox[0])
response = app.get(link)
assert response.location == '/'
assert User.objects.count() == 1
user = User.objects.get()
assert user.first_name == 'John'
assert user.last_name == 'Doe'
assert user.attributes.birthdate is None
assert user.attributes.preferred_color == 'bleu'
assert user.email == 'john.doe2@example.com'
assert user.email_verified is True
def test_honeypot(app, db, settings, mailoutbox):
settings.DEFAULT_FROM_EMAIL = 'show only addr <noreply@example.net>'
response = app.get(utils_misc.make_url('registration_register'))
response.form.set('email', 'testbot@entrouvert.com')
response.form.set('robotcheck', True)
response = response.form.submit()
response = response.follow()
assert len(mailoutbox) == 0
assert 'Your registration request has been refused' in response
def test_registration_name_validation(app, db, mailoutbox):
resp = app.get(reverse('registration_register'))
resp.form.set('email', 'testbot@entrouvert.com')
resp = resp.form.submit().follow()
link = get_link_from_mail(mailoutbox[0])
resp = app.get(link)
resp.form.set('password1', 'T0==toto')
resp.form.set('password2', 'T0==toto')
resp.form.set('first_name', '01/01/1871')
resp.form.set('last_name', 'Doe')
resp = resp.form.submit()
assert 'Special characters are not allowed' in resp.text
resp.form.set('password1', 'T0==toto')
resp.form.set('password2', 'T0==toto')
resp.form.set('first_name', 'John')
resp.form.set('last_name', 'a(a')
resp = resp.form.submit()
assert 'Special characters are not allowed' in resp.text
resp.form.set('password1', 'T0==toto')
resp.form.set('password2', 'T0==toto')
resp.form.set('first_name', 'Léo')
resp.form.set('last_name', 'D\'Équerre')
resp = resp.form.submit().follow()
assert 'You have just created an account' in resp.text
def test_attribute_model_autocomplete(app, db, settings, mailoutbox):
settings.LANGUAGE_CODE = 'en-us'
response = app.get(reverse('registration_register'))
response.form.set('email', 'testbot@entrouvert.com')
response = response.form.submit()
assert urlparse(response['Location']).path == reverse('registration_complete')
link = get_link_from_mail(mailoutbox[0])
response = app.get(link)
assert response.form['first_name'].attrs['autocomplete'] == 'given-name'
assert response.form['last_name'].attrs['autocomplete'] == 'family-name'
def test_registration_race_condition(app, db, mailoutbox):
models.Attribute.objects.update(disabled=True)
response = app.get(reverse('registration_register'))
response.form.set('email', 'testbot@entrouvert.com')
response = response.form.submit()
link = get_link_from_mail(mailoutbox[0])
mailoutbox.clear()
response = app.get(link)
response.form.set('password1', 'T0==toto')
response.form.set('password2', 'T0==toto')
with mock.patch('authentic2.views.models.Token.delete', return_value=(0, {})):
response = response.form.submit().follow()
# hypothetical case where token disappeared while user has not been created
assert 'An error occured' in response.text
# real case where user was created by previous almost simultaneous request
User.objects.create(email='testbot@entrouvert.com')
response = app.get(link)
response.form.set('password1', 'T0==toto')
response.form.set('password2', 'T0==toto')
with mock.patch('authentic2.views.models.Token.delete', return_value=(0, {})):
response = response.form.submit().follow()
assert 'You have just created an account.' in response.text
assert len(mailoutbox) == 0
def test_registration_service_integration(app, service, settings):
service.home_url = 'https://portail.example.net/'
service.save()
response = app.get('/register/?next=https://portail.example.net/page/')
assert app.session['home_url'] == 'https://portail.example.net/page/'
assert app.session['service_pk'] == service.pk
assert response.context['home_ou'] == service.ou
assert response.context['home_service'] == service
assert response.context['home_url'] == 'https://portail.example.net/page/'
def test_registration_completion_form(db, simple_user):
form_class = modelform_factory(get_user_model(), form=RegistrationCompletionForm)
data = {
'email': 'jonh.doe@yopmail.com',
'password': 'blah',
'password1': 'Password0',
'password2': 'Password0',
'date_joined': '2022-02-07',
'ou': simple_user.ou.pk,
}
form = form_class(instance=simple_user, data=data)
assert form.fields['password1'].widget.min_strength is None
assert 'password1' not in form.errors
simple_user.ou.min_password_strength = 3
form = form_class(instance=simple_user, data=data)
assert form.fields['password1'].widget.min_strength == 3
assert form.errors['password1'] == ['This password is not strong enough.']
def test_registration_completion(db, app, mailoutbox):
default_ou = get_default_ou()
default_ou.min_password_strength = 3
default_ou.save()
resp = app.get(reverse('registration_register'))
resp.form.set('email', 'testbot@entrouvert.com')
resp = resp.form.submit().follow()
link = get_link_from_mail(mailoutbox[0])
resp = app.get(link)
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()
assert 'This password is not strong enough' in resp.text
def test_registration_no_identifier(app, db, settings):
settings.A2_ACCEPT_PHONE_AUTHENTICATION = True
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
assert ("Your code is valid for the next %s minute" % (SMSCode.CODE_DURATION // 60)) in resp.text
assert "The registration code you received by SMS." in resp.text
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'