authenticators: move registration_open flag to password authenticator (#77789) #66

Merged
vdeniaud merged 1 commits from wip/77789-pouvoir-desactiver-la-creation-d into main 2023-06-12 12:26:37 +02:00
10 changed files with 78 additions and 20 deletions

View File

@ -0,0 +1,19 @@
# Generated by Django 3.2.18 on 2023-06-01 15:42
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('authenticators', '0005_addroleaction'),
]
operations = [
migrations.AddField(
model_name='loginpasswordauthenticator',
name='registration_open',
field=models.BooleanField(
default=True, help_text='Allow users to create accounts.', verbose_name='Registration open'
),
),
]

View File

@ -0,0 +1,24 @@
# Generated by Django 3.2.18 on 2023-06-01 15:44
from django.conf import settings
from django.db import migrations
def migrate_registration_open(apps, schema_editor):
LoginPasswordAuthenticator = apps.get_model('authenticators', 'LoginPasswordAuthenticator')
authenticator = LoginPasswordAuthenticator.objects.get_or_create(
slug='password-authenticator',
defaults={'enabled': True},
)[0]
authenticator.registration_open = getattr(settings, 'REGISTRATION_OPEN', True)
authenticator.save()
class Migration(migrations.Migration):
dependencies = [
('authenticators', '0006_loginpasswordauthenticator_registration_open'),
]
operations = [
migrations.RunPython(migrate_registration_open, reverse_code=migrations.RunPython.noop),
]

View File

@ -278,6 +278,9 @@ class AddRoleAction(AuthenticatorRelatedObjectBase):
class LoginPasswordAuthenticator(BaseAuthenticator):
registration_open = models.BooleanField(
_('Registration open'), default=True, help_text=_('Allow users to create accounts.')
)
remember_me = models.PositiveIntegerField(
_('Remember me duration'),
blank=True,

View File

@ -18,7 +18,6 @@ import copy
import math
from django import forms
from django.conf import settings
from django.contrib.auth import forms as auth_forms
from django.forms.widgets import Media
from django.utils import html
@ -155,15 +154,13 @@ class AuthenticationForm(auth_forms.AuthenticationForm):
invalid_login_message = [
_('Incorrect %(username_label)s or password.') % {'username_label': username_label},
]
if app_settings.A2_USER_CAN_RESET_PASSWORD is not False and getattr(
settings, 'REGISTRATION_OPEN', True
):
if app_settings.A2_USER_CAN_RESET_PASSWORD is not False and self.authenticator.registration_open:
invalid_login_message.append(
_('Try again, use the forgotten password link below, or create an account.')
)
elif app_settings.A2_USER_CAN_RESET_PASSWORD is not False:
invalid_login_message.append(_('Try again or use the forgotten password link below.'))
elif getattr(settings, 'REGISTRATION_OPEN', True):
elif self.authenticator.registration_open:
invalid_login_message.append(_('Try again or create an account.'))
error_messages['invalid_login'] = ' '.join([force_str(x) for x in invalid_login_message])
return error_messages

View File

@ -18,7 +18,6 @@ import logging
from collections import OrderedDict
from django import forms
from django.conf import settings
from django.contrib.auth import forms as auth_forms
from django.contrib.auth import get_user_model
from django.core.exceptions import ValidationError
@ -53,6 +52,7 @@ class PasswordResetForm(HoneypotForm):
)
def __init__(self, *args, **kwargs):
self.authenticator = kwargs.pop('password_authenticator')
super().__init__(*args, **kwargs)
self.users = []
if app_settings.A2_USER_CAN_RESET_PASSWORD_BY_USERNAME:
@ -182,7 +182,7 @@ class PasswordResetForm(HoneypotForm):
sms_sent = True
if not email_sent and email:
logger.info('password reset request for "%s", no user found', email)
if getattr(settings, 'REGISTRATION_OPEN', True):
if self.authenticator.registration_open:
ctx = {
'registration_url': utils_misc.make_url(
'registration_register',

View File

@ -175,16 +175,22 @@ def get_backends():
return backends
def get_password_authenticator():
from authentic2.apps.authenticators.models import LoginPasswordAuthenticator
return LoginPasswordAuthenticator.objects.get_or_create(
slug='password-authenticator',
defaults={'enabled': True},
)[0]
def get_authenticators():
from authentic2.apps.authenticators.models import BaseAuthenticator, LoginPasswordAuthenticator
from authentic2.apps.authenticators.models import BaseAuthenticator
backends = list(
BaseAuthenticator.authenticators.filter(enabled=True).exclude(slug='password-authenticator')
)
password_backend, dummy = LoginPasswordAuthenticator.objects.get_or_create(
slug='password-authenticator',
defaults={'enabled': True},
)
password_backend = get_password_authenticator()
if password_backend.enabled:
backends.append(password_backend)

View File

@ -401,6 +401,12 @@ def login(request, template_name='authentic2/login.html', redirect_field_name=RE
authenticators = utils_misc.get_authenticators()
password_authenticators = [x for x in authenticators if x.slug == 'password-authenticator']
if not password_authenticators:
registration_open = False
else:
registration_open = password_authenticators[0].registration_open
blocks = []
registration_url = utils_misc.get_registration_url(request)
@ -408,7 +414,7 @@ def login(request, template_name='authentic2/login.html', redirect_field_name=RE
context = {
'cancel': app_settings.A2_LOGIN_DISPLAY_A_CANCEL_BUTTON and nonce is not None,
'can_reset_password': app_settings.A2_USER_CAN_RESET_PASSWORD is not False,
'registration_authorized': getattr(settings, 'REGISTRATION_OPEN', True),
'registration_authorized': registration_open,
'registration_url': registration_url,
}
@ -864,6 +870,7 @@ class PasswordResetView(FormView):
def get_form_kwargs(self, **kwargs):
kwargs = super().get_form_kwargs(**kwargs)
kwargs['password_authenticator'] = utils_misc.get_password_authenticator()
initial = kwargs.setdefault('initial', {})
initial['next_url'] = utils_misc.select_next_url(self.request, '')
return kwargs
@ -1109,7 +1116,8 @@ class BaseRegistrationView(HomeURLMixin, FormView):
title = _('Registration')
def dispatch(self, request, *args, **kwargs):
if not getattr(settings, 'REGISTRATION_OPEN', True):
password_authenticator = utils_misc.get_password_authenticator()
if not password_authenticator.registration_open:
raise Http404('Registration is not open.')
self.token = {}

View File

@ -409,7 +409,6 @@ def test_login_csrf_no_middleware(app, simple_user, settings):
def test_login_error_messages(app, settings, simple_user):
settings.A2_USER_CAN_RESET_PASSWORD = True
settings.REGISTRATION_OPEN = True
resp = app.get('/login/')
resp.form.set('username', 'x')
resp.form.set('password', 'y')
@ -419,7 +418,7 @@ def test_login_error_messages(app, settings, simple_user):
assert 'or create an account.' in resp
settings.A2_USER_CAN_RESET_PASSWORD = False
settings.REGISTRATION_OPEN = False
LoginPasswordAuthenticator.objects.update(registration_open=False)
resp.form.set('username', 'x')
resp.form.set('password', 'y')
resp = resp.form.submit(name='login-password-submit')
@ -428,7 +427,6 @@ def test_login_error_messages(app, settings, simple_user):
assert 'or create an account.' not in resp
settings.A2_USER_CAN_RESET_PASSWORD = True
settings.REGISTRATION_OPEN = False
resp.form.set('username', 'x')
resp.form.set('password', 'y')
resp = resp.form.submit(name='login-password-submit')
@ -437,7 +435,7 @@ def test_login_error_messages(app, settings, simple_user):
assert 'or create an account.' not in resp
settings.A2_USER_CAN_RESET_PASSWORD = False
settings.REGISTRATION_OPEN = True
LoginPasswordAuthenticator.objects.update(registration_open=True)
resp.form.set('username', 'x')
resp.form.set('password', 'y')
resp = resp.form.submit(name='login-password-submit')

View File

@ -79,6 +79,7 @@ def test_authenticators_password(app, superuser_or_admin):
'csrfmiddlewaretoken',
'show_condition',
'button_description',
'registration_open',
'remember_me',
'include_ou_selector',
None,
@ -142,6 +143,7 @@ def test_authenticators_password_export(app, superuser):
'include_ou_selector': False,
'name': '',
'ou': None,
'registration_open': True,
'related_objects': [],
'remember_me': None,
'show_condition': '',

View File

@ -22,6 +22,7 @@ from django.test.utils import override_settings
from django.urls import reverse
from httmock import HTTMock, remember_called, urlmatch
from authentic2.apps.authenticators.models import LoginPasswordAuthenticator
from authentic2.models import SMSCode, Token
from authentic2.utils.misc import send_password_reset_mail
@ -290,7 +291,7 @@ def test_old_url_redirect(app, db):
[True, False],
)
def test_send_password_reset_email_no_account(app, db, mailoutbox, settings, registration_open):
settings.REGISTRATION_OPEN = registration_open
LoginPasswordAuthenticator.objects.update(registration_open=registration_open)
resp = app.get('/password/reset/?next=/whatever/', status=200)
resp.form.set('email', 'test@entrouvert.com')
resp = resp.form.submit()
@ -299,7 +300,7 @@ def test_send_password_reset_email_no_account(app, db, mailoutbox, settings, reg
assert mail.subject == 'Password reset on testserver'
for body in (mail.body, mail.alternatives[0][0]):
assert 'no account was found associated with this address' in body
if settings.REGISTRATION_OPEN:
if registration_open:
assert 'https://testserver/register/' in body
# check next_url was preserved
assert 'next=/whatever/' in body