manager: prevent phone id duplicates at user edition time (#85276)
gitea/authentic/pipeline/head This commit looks good Details

This commit is contained in:
Paul Marillonnet 2024-01-04 16:25:30 +01:00
parent 5a969bf584
commit 5b62273fd1
2 changed files with 118 additions and 1 deletions

View File

@ -23,6 +23,7 @@ from io import StringIO
from django import forms
from django.contrib.auth import get_user_model
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ValidationError
from django.core.validators import validate_slug
from django.urls import reverse
@ -43,7 +44,7 @@ from authentic2.forms.fields import (
)
from authentic2.forms.mixins import SlugMixin
from authentic2.forms.profile import BaseUserForm
from authentic2.models import APIClient, Attribute, PasswordReset, Service, Setting
from authentic2.models import APIClient, Attribute, AttributeValue, PasswordReset, Service, Setting
from authentic2.passwords import generate_password, validate_password
from authentic2.utils.misc import (
RUNTIME_SETTINGS,
@ -172,6 +173,43 @@ class UserEditForm(LimitQuerysetFormMixin, CssClass, BaseUserForm):
self.data[self.add_prefix('ou')] = qs[0].pk
self.data._mutable = False
def clean(self):
super().clean()
authn = get_password_authenticator()
if (
authn.is_phone_authn_active
and (name := authn.phone_identifier_field.name)
and self.cleaned_data.get(name, '')
):
if a2_app_settings.A2_PHONE_IS_UNIQUE:
if (
AttributeValue.objects.filter(
content_type=ContentType.objects.get_for_model(get_user_model()),
attribute=authn.phone_identifier_field,
)
.exclude(object_id=self.instance.id)
.exists()
):
raise ValidationError(_('This phone number identifier is already used.'))
elif getattr(self.instance.ou, 'phone_is_unique', False):
other_owners = (
AttributeValue.objects.filter(
content_type=ContentType.objects.get_for_model(get_user_model()),
attribute=authn.phone_identifier_field,
)
.exclude(object_id=self.instance.id)
.values('object_id')
)
if other_owners and User.objects.filter(
pk__in=other_owners,
ou=self.instance.ou,
):
raise ValidationError(
_(
'This phone number identifier is already used within organizational unit {ou}.'
).format(ou=self.instance.ou)
)
class Meta:
model = User
exclude = (

View File

@ -180,6 +180,85 @@ def test_create_user_email_is_unique(app, superuser, settings):
assert 'This email address is already in use' in response
def test_create_user_phone_is_unique(app, superuser, settings, phone_activated_authn):
settings.A2_PHONE_IS_UNIQUE = True
Attribute.objects.update(required=False)
response = login(app, superuser, '/manage/users/')
response = response.click('Add user')
assert User.objects.count() == 1
response.form.set('username', 'john.doe')
response.form.set('phone_0', '33')
response.form.set('phone_1', '0622332233')
response.form.set('password1', '1234Password')
response.form.set('password2', '1234Password')
response.form.set('send_password_reset', False)
response = response.form.submit(status=302)
assert User.objects.count() == 2
# try again
response = app.get('/manage/users/')
response = response.click('Add user')
response.form.set('username', 'john.doe2')
response.form.set('phone_0', '33')
response.form.set('phone_1', '0622332233')
response.form.set('password1', '1234Password')
response.form.set('password2', '1234Password')
response.form.set('send_password_reset', False)
response = response.form.submit(status=200)
assert User.objects.count() == 2
assert 'This phone number identifier is already used.' in response
def test_create_user_ou_phone_is_unique(app, superuser, settings, phone_activated_authn, ou1, ou2):
settings.A2_PHONE_IS_UNIQUE = False
ou2.phone_is_unique = ou1.phone_is_unique = True
ou1.save()
ou2.save()
Attribute.objects.update(required=False)
response = login(app, superuser, '/manage/users/')
response = response.click('Add user')
assert User.objects.count() == 1
response.form.set('ou', ou1.id)
response = response.form.submit().follow()
response.form.set('username', 'john.doe')
response.form.set('phone_0', '33')
response.form.set('phone_1', '0622332233')
response.form.set('password1', '1234Password')
response.form.set('password2', '1234Password')
response.form.set('send_password_reset', False)
response = response.form.submit(status=302)
assert User.objects.count() == 2
# try again, same ou -> failure
response = app.get('/manage/users/')
response = response.click('Add user')
response.form.set('ou', ou1.id)
response = response.form.submit().follow()
response.form.set('username', 'john.doe2')
response.form.set('phone_0', '33')
response.form.set('phone_1', '0622332233')
response.form.set('password1', '1234Password')
response.form.set('password2', '1234Password')
response.form.set('send_password_reset', False)
response = response.form.submit(status=200)
assert User.objects.count() == 2
assert 'This phone number identifier is already used within organizational unit OU1.' in response
# try again, different ou -> ok
response = app.get('/manage/users/')
response = response.click('Add user')
response.form.set('ou', ou2.id)
response = response.form.submit().follow()
response.form.set('username', 'john.doe2')
response.form.set('phone_0', '33')
response.form.set('phone_1', '0622332233')
response.form.set('password1', '1234Password')
response.form.set('password2', '1234Password')
response.form.set('send_password_reset', False)
response = response.form.submit(status=302)
assert User.objects.count() == 3
def test_create_user_no_password(app, superuser):
response = login(app, superuser, '/manage/users/')
response = response.click('Add user')