authentic/src/authentic2/forms/registration.py

204 lines
7.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 re
from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.auth.models import BaseUserManager, Group
from django.core.exceptions import ValidationError
from django.core.validators import RegexValidator
from django.forms import Form
from django.utils.translation import gettext
from django.utils.translation import gettext_lazy as _
from authentic2.a2_rbac.models import OrganizationalUnit
from authentic2.forms.fields import CharField, CheckPasswordField, NewPasswordField
from authentic2.passwords import get_min_password_strength
from .. import app_settings, models
from . import profile as profile_forms
from .fields import PhoneField, ValidatedEmailField
from .honeypot import HoneypotForm
User = get_user_model()
class RegistrationForm(HoneypotForm):
error_css_class = 'form-field-error'
required_css_class = 'form-field-required'
email = ValidatedEmailField(
label=_('Email'),
help_text=_('Your email address'),
required=False,
)
phone = PhoneField(
label=_('Phone number'),
help_text=_('Your mobile phone number.'),
required=False,
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
attributes = {a.name: a for a in models.Attribute.objects.all()}
if not app_settings.A2_ACCEPT_PHONE_AUTHENTICATION or not get_user_model()._meta.get_field('phone'):
del self.fields['phone']
self.fields['email'].required = True
for field in app_settings.A2_PRE_REGISTRATION_FIELDS:
if field in ('first_name', 'last_name'):
self.fields[field] = User._meta.get_field(field).formfield()
elif field in attributes:
self.fields[field] = attributes[field].get_form_field()
self.fields[field].required = True
def clean_email(self):
email = self.cleaned_data['email']
for email_pattern in app_settings.A2_REGISTRATION_EMAIL_BLACKLIST:
if not email_pattern.startswith('^'):
email_pattern = '^' + email_pattern
if not email_pattern.endswith('$'):
email_pattern += '$'
if re.match(email_pattern, email):
raise ValidationError(gettext('You cannot register with this email.'))
return email
def clean(self):
if app_settings.A2_ACCEPT_PHONE_AUTHENTICATION and get_user_model()._meta.get_field('phone'):
if not self.cleaned_data['email'] and not self.cleaned_data['phone']:
raise ValidationError(gettext('Please provide an email address or a mobile phone number.'))
validate_name = RegexValidator(
r'[0-9_!¡?÷?¿/\\+=@#$%ˆ&*(){}|~<>;:[\]]',
message=_('Special characters are not allowed.'),
inverse_match=True,
)
class RegistrationCompletionFormNoPassword(profile_forms.BaseUserForm):
error_css_class = 'form-field-error'
required_css_class = 'form-field-required'
html5_autocomplete_map = {
'first_name': 'given-name',
'last_name': 'family-name',
'address': 'address-line1',
'zipcode': 'postal-code',
'city': 'address-level2',
'country': 'country',
'phone': 'tel',
'email': 'email',
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if 'first_name' in self.fields:
self.fields['first_name'].validators.append(validate_name)
if 'last_name' in self.fields:
self.fields['last_name'].validators.append(validate_name)
for field, autocomplete_value in self.html5_autocomplete_map.items():
if field in self.fields:
self.fields[field].widget.attrs['autocomplete'] = autocomplete_value
def clean_username(self):
if self.cleaned_data.get('username'):
username = self.cleaned_data['username']
username_is_unique = app_settings.A2_REGISTRATION_USERNAME_IS_UNIQUE
if app_settings.A2_REGISTRATION_REALM:
username += '@' + app_settings.A2_REGISTRATION_REALM
if 'ou' in self.data:
ou = OrganizationalUnit.objects.get(pk=self.data['ou'])
username_is_unique |= ou.username_is_unique
if username_is_unique:
exist = False
try:
User.objects.get(username=username)
except User.DoesNotExist:
pass
except User.MultipleObjectsReturned:
exist = True
else:
exist = True
if exist:
raise ValidationError(
_('This username is already in use. Please supply a different username.')
)
return username
def clean_email(self):
if self.cleaned_data.get('email'):
email = self.cleaned_data['email']
if app_settings.A2_REGISTRATION_EMAIL_IS_UNIQUE:
exist = False
try:
User.objects.get(email__iexact=email)
except User.DoesNotExist:
pass
except User.MultipleObjectsReturned:
exist = True
else:
exist = True
if exist:
raise ValidationError(
_('This email address is already in use. Please supply a different email address.')
)
return BaseUserManager.normalize_email(email)
def save(self, commit=True):
self.instance.set_email_verified(True, source='registration')
self.instance.is_active = True
user = super().save(commit=commit)
if commit and app_settings.A2_REGISTRATION_GROUPS:
groups = []
for name in app_settings.A2_REGISTRATION_GROUPS:
group, dummy = Group.objects.get_or_create(name=name)
groups.append(group)
user.groups = groups
return user
class RegistrationCompletionForm(RegistrationCompletionFormNoPassword):
password1 = NewPasswordField(label=_('Password'))
password2 = CheckPasswordField(label=_("Password (again)"))
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['password1'].min_strength = get_min_password_strength(self.instance)
def clean(self):
"""
Verifiy that the values entered into the two password fields
match. Note that an error here will end up in
``non_field_errors()`` because it doesn't apply to a single
field.
"""
if 'password1' in self.cleaned_data and 'password2' in self.cleaned_data:
if self.cleaned_data['password1'] != self.cleaned_data['password2']:
raise ValidationError(_("The two password fields didn't match."))
self.instance.set_password(self.cleaned_data['password1'])
return self.cleaned_data
class InputSMSCodeForm(Form):
sms_code = CharField(
label=_('SMS code'),
help_text=_('The code you received by SMS.'),
max_length=settings.SMS_CODE_LENGTH,
)