From 3dec83bb645ef4be5f0b636687a065b6cb6b45d7 Mon Sep 17 00:00:00 2001 From: Benjamin Dauvergne Date: Thu, 13 Aug 2020 15:43:49 +0200 Subject: [PATCH] misc: unserialize attribute in registration view (#45710) Registration view pass attribute values in JSON tokens, so we need to get a JSON compatible serialization of attribute values for the token using their serialization function. --- src/authentic2/attribute_kinds.py | 8 +++-- src/authentic2/custom_user/models.py | 6 ++++ src/authentic2/views.py | 17 ++++++---- tests/test_registration.py | 48 +++++++++++++++++++++++----- 4 files changed, 63 insertions(+), 16 deletions(-) diff --git a/src/authentic2/attribute_kinds.py b/src/authentic2/attribute_kinds.py index b8520310..76946bf6 100644 --- a/src/authentic2/attribute_kinds.py +++ b/src/authentic2/attribute_kinds.py @@ -355,11 +355,15 @@ def get_form_field(kind, **kwargs): return defn['field_class'](**kwargs) +def identity(x): + return x + + def get_kind(kind): d = get_attribute_kinds()[kind] d.setdefault('default', None) - d.setdefault('serialize', lambda x: x) - d.setdefault('deserialize', lambda x: x) + d.setdefault('serialize', identity) + d.setdefault('deserialize', identity) rest_field_kwargs = d.setdefault('rest_framework_field_kwargs', {}) if 'rest_framework_field_class' not in d: d['rest_framework_field_class'] = serializers.CharField diff --git a/src/authentic2/custom_user/models.py b/src/authentic2/custom_user/models.py index 4aa9fe9f..9452c161 100644 --- a/src/authentic2/custom_user/models.py +++ b/src/authentic2/custom_user/models.py @@ -53,6 +53,12 @@ def get_attributes_map(): return mapping +def iter_attributes(): + for key, value in get_attributes_map().items(): + if isinstance(key, str): + yield value + + class Attributes(object): def __init__(self, owner, verified=None): self.__dict__['owner'] = owner diff --git a/src/authentic2/views.py b/src/authentic2/views.py index aa6b0ad0..44557137 100644 --- a/src/authentic2/views.py +++ b/src/authentic2/views.py @@ -17,7 +17,6 @@ import collections from email.utils import parseaddr import logging -import random import re from ratelimit.utils import is_ratelimited @@ -28,8 +27,7 @@ from django.template.loader import render_to_string from django.views.generic.edit import UpdateView, FormView from django.views.generic import TemplateView from django.views.generic.base import View -from django.contrib.auth import SESSION_KEY -from django import http, shortcuts +from django import shortcuts from django.core import signing from django.core.exceptions import ValidationError from django.contrib import messages @@ -42,7 +40,6 @@ from django.contrib.auth.views import PasswordChangeView as DjPasswordChangeView from django.http import (HttpResponseRedirect, HttpResponseForbidden, HttpResponse) from django.views.decorators.csrf import csrf_exempt, ensure_csrf_cookie from django.views.decorators.cache import never_cache -from django.views.decorators.debug import sensitive_post_parameters from django.contrib.auth.decorators import login_required from django.db.models.fields import FieldDoesNotExist from django.db.models.query import Q @@ -54,9 +51,10 @@ from django.forms import CharField from django.http import HttpResponseBadRequest from django.template import loader +from authentic2.custom_user.models import iter_attributes from authentic2.compat.misc import default_token_generator from . import (utils, app_settings, decorators, constants, - models, cbv, hooks, validators) + models, cbv, hooks, validators, attribute_kinds) from .utils import switch_user from .a2_rbac.utils import get_default_ou from .a2_rbac.models import OrganizationalUnit as OU @@ -1082,7 +1080,14 @@ class RegistrationCompletionView(CreateView): and ('email' not in self.token or self.request.POST['email'] != self.token['email']) and not self.token.get('skip_email_check')): # If an email is submitted it must be validated or be the same as in the token - data = form.cleaned_data + data = form.cleaned_data.copy() + # handle complex attributes + for attribute in iter_attributes(): + kind = attribute.get_kind() + if kind['serialize'] == attribute_kinds.identity: + continue + data[attribute.name] = kind['serialize'](data[attribute.name]) + data['no_password'] = self.token.get('no_password', False) utils.send_registration_mail( self.request, diff --git a/tests/test_registration.py b/tests/test_registration.py index 766f846d..0d6b5ab8 100644 --- a/tests/test_registration.py +++ b/tests/test_registration.py @@ -15,8 +15,6 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -import re - from django.contrib.auth import get_user_model, REDIRECT_FIELD_NAME from django.urls import reverse from django.utils.http import urlquote @@ -28,6 +26,9 @@ from authentic2.validators import EmailValidator from .utils import get_link_from_mail +User = get_user_model() + + def test_registration(app, db, settings, mailoutbox, external_redirect): next_url, good_next_url = external_redirect @@ -37,7 +38,6 @@ def test_registration(app, db, settings, mailoutbox, external_redirect): # disable existing attributes models.Attribute.objects.update(disabled=True) - User = get_user_model() url = utils.make_url('registration_register', params={REDIRECT_FIELD_NAME: next_url}) response = app.get(url) response.form.set('email', 'testbot@entrouvert.com') @@ -109,7 +109,6 @@ def test_registration_realm(app, db, settings, mailoutbox): # disable existing attributes models.Attribute.objects.update(disabled=True) - User = get_user_model() next_url = 'http://relying-party.org/' url = utils.make_url('registration_register', params={REDIRECT_FIELD_NAME: next_url}) @@ -494,7 +493,6 @@ def test_email_is_unique_multiple_objects_returned(app, db, settings, mailoutbox settings.A2_REGISTRATION_EMAIL_IS_UNIQUE = True # Create two user objects - User = get_user_model() User.objects.create(email='testbot@entrouvert.com') User.objects.create(email='testbot@entrouvert.com') @@ -518,7 +516,6 @@ def test_username_is_unique_multiple_objects_returned(app, db, settings, mailout settings.A2_REQUIRED_FIELDS = ['username', 'first_name', 'last_name'] # Create two user objects - User = get_user_model() User.objects.create(username='testbot', email='testbot1@entrouvert.com') User.objects.create(username='testbot', email='testbot2@entrouvert.com') @@ -550,7 +547,6 @@ def test_registration_redirect(app, db, settings, mailoutbox, external_redirect) # disable existing attributes models.Attribute.objects.update(disabled=True) - User = get_user_model() url = utils.make_url('registration_register', params={REDIRECT_FIELD_NAME: next_url}) response = app.get(url) response.form.set('email', 'testbot@entrouvert.com') @@ -674,8 +670,44 @@ def test_registration_with_email_suggestions(app, db, settings): 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.build_activation_url( + rf.post('/accounts/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 + import pdb + pdb.set_trace() -- 2.28.0