diff --git a/frontend/src/routes/guest/register/authenticationMethod.ts b/frontend/src/routes/guest/register/authenticationMethod.ts index d25eecdb6e3d0883fb2a1337fc22a5aa9193f50d..f01a26db655697443c98a7e2c6dd9266cb00b920 100644 --- a/frontend/src/routes/guest/register/authenticationMethod.ts +++ b/frontend/src/routes/guest/register/authenticationMethod.ts @@ -3,7 +3,7 @@ */ enum AuthenticationMethod { Feide, - NationalIdNumberOrPassport, + Invite, } export default AuthenticationMethod diff --git a/frontend/src/routes/guest/register/guestDataForm.ts b/frontend/src/routes/guest/register/guestDataForm.ts index 75d0307fcbe0c2c4f87afef12bd39ca7e51109cf..44ab98237c271f3724206f00fe4891cae7278f88 100644 --- a/frontend/src/routes/guest/register/guestDataForm.ts +++ b/frontend/src/routes/guest/register/guestDataForm.ts @@ -22,6 +22,5 @@ export type ContactInformationBySponsor = { fnr?: string passport?: string - user_information_source: AuthenticationMethod - fnr_verified: boolean + authentication_method: AuthenticationMethod } diff --git a/frontend/src/routes/guest/register/index.tsx b/frontend/src/routes/guest/register/index.tsx index 8dc410bedb1617887482feb7b9f5dbce7bfe90f6..23c4864ab59200324feb92ddf4227d8b6bc87cb9 100644 --- a/frontend/src/routes/guest/register/index.tsx +++ b/frontend/src/routes/guest/register/index.tsx @@ -51,8 +51,7 @@ export default function GuestRegister() { mobile_phone: '', fnr: '', passport: '', - user_information_source: AuthenticationMethod.Feide, - fnr_verified: false, + authentication_method: AuthenticationMethod.Invite, }) const guestContactInfo = async () => { @@ -64,12 +63,10 @@ export default function GuestRegister() { console.log(`Guest data from server: ${JSON.stringify(jsonResponse)}`) const authenticationMethod = - jsonResponse.meta.form_type === 'manual' - ? AuthenticationMethod.NationalIdNumberOrPassport + jsonResponse.meta.session_type === 'invite' + ? AuthenticationMethod.Invite : AuthenticationMethod.Feide - const fnrVerified = jsonResponse.meta.fnr_verified === true - setGuestFormData({ first_name: jsonResponse.person.first_name, last_name: jsonResponse.person.last_name, @@ -86,8 +83,7 @@ export default function GuestRegister() { fnr: jsonResponse.fnr, passport: jsonResponse.passport, - user_information_source: authenticationMethod, - fnr_verified: fnrVerified, + authentication_method: authenticationMethod, }) }) } diff --git a/frontend/src/routes/guest/register/registerPage.test.tsx b/frontend/src/routes/guest/register/registerPage.test.tsx index 7df85cc354b34e68df654247dccfd2ebf0906abf..ccf4cfc8af9af38370d1c12f3b008adcc9a7ad26 100644 --- a/frontend/src/routes/guest/register/registerPage.test.tsx +++ b/frontend/src/routes/guest/register/registerPage.test.tsx @@ -24,7 +24,7 @@ test('Guest register page showing passport field on manual registration', async mobile_phone: '', fnr: '', passport: '', - user_information_source: AuthenticationMethod.NationalIdNumberOrPassport, + authentication_method: AuthenticationMethod.Invite, } render( diff --git a/frontend/src/routes/guest/register/registerPage.tsx b/frontend/src/routes/guest/register/registerPage.tsx index 8a41ed67d81720c8ab17cfc89db474fcf8dd38f1..32180f46f4b04bdcde55775d5e272641b8d21a16 100644 --- a/frontend/src/routes/guest/register/registerPage.tsx +++ b/frontend/src/routes/guest/register/registerPage.tsx @@ -161,8 +161,8 @@ const GuestRegisterStep = forwardRef( /> </Box> - {guestData.user_information_source === - AuthenticationMethod.NationalIdNumberOrPassport && ( + {guestData.authentication_method === + AuthenticationMethod.Invite && ( <> <TextField id="passport" @@ -187,7 +187,7 @@ const GuestRegisterStep = forwardRef( </> )} - {guestData.user_information_source === + {guestData.authentication_method === AuthenticationMethod.Feide && ( <TextField id="national_id_number" diff --git a/gregui/api/serializers/guest.py b/gregui/api/serializers/guest.py index 80f122306623420c4c140cca14f0398f7eef4318..ce702752a1b1d0613bd339a1d34e965a8909290b 100644 --- a/gregui/api/serializers/guest.py +++ b/gregui/api/serializers/guest.py @@ -21,7 +21,9 @@ class GuestRegisterSerializer(serializers.ModelSerializer): # TODO first_name and last_name set as not required to throwing an exception if they are not included in what is sent back from the frontend. It is perhaps not required that they are in the reponse from the client if the guest should be allowed to change them first_name = serializers.CharField(required=False) last_name = serializers.CharField(required=False) - email = serializers.CharField(required=True) + # E-mail set to not required to avoid raising exception if it is not included in input. It is not given that + # the guest should be allowed to update it + email = serializers.CharField(required=False) mobile_phone = serializers.CharField( required=True, validators=[_validatePhoneNumber] ) @@ -30,19 +32,20 @@ class GuestRegisterSerializer(serializers.ModelSerializer): ) def update(self, instance, validated_data): - email = validated_data.pop("email") mobile_phone = validated_data.pop("mobile_phone") - if not instance.private_email: - Identity.objects.create( - person=instance, - type=Identity.IdentityType.PRIVATE_EMAIL, - value=email, - ) - else: - private_email = instance.private_email - private_email.value = email - private_email.save() + if "email" in validated_data: + email = validated_data.pop("email") + if not instance.private_email: + Identity.objects.create( + person=instance, + type=Identity.IdentityType.PRIVATE_EMAIL, + value=email, + ) + else: + private_email = instance.private_email + private_email.value = email + private_email.save() if not instance.private_mobile: Identity.objects.create( diff --git a/gregui/api/views/invitation.py b/gregui/api/views/invitation.py index 04cc76a516116781ab6e0cfb43f14a7ad5d27197..2432ba0dede5c942bf325f90ee38f35656299ad8 100644 --- a/gregui/api/views/invitation.py +++ b/gregui/api/views/invitation.py @@ -1,26 +1,21 @@ -import json -import datetime -from uuid import uuid4 +from enum import Enum -from django.contrib.auth.models import AnonymousUser from django.core import exceptions from django.db import transaction from django.http.response import JsonResponse - from django.utils import timezone -from rest_framework import serializers, status +from rest_framework import status from rest_framework.authentication import SessionAuthentication, BasicAuthentication from rest_framework.generics import CreateAPIView, GenericAPIView from rest_framework.parsers import JSONParser -from rest_framework.permissions import AllowAny, IsAuthenticated +from rest_framework.permissions import AllowAny from rest_framework.response import Response from rest_framework.views import APIView -from greg.models import Identity, Invitation, InvitationLink, Person, Role, Sponsor +from greg.models import Identity, InvitationLink from greg.permissions import IsSponsor from gregui.api.serializers.guest import GuestRegisterSerializer from gregui.api.serializers.invitation import InviteGuestSerializer - from gregui.models import GregUserProfile @@ -102,6 +97,11 @@ class CheckInvitationView(APIView): return Response(status=status.HTTP_200_OK) +class SessionType(Enum): + INVITE = "invite" + FEIDE = "feide" + + class InvitedGuestView(GenericAPIView): authentication_classes = [SessionAuthentication, BasicAuthentication] # The endpoint is only for invited guests, but the authorization happens in the actual method @@ -110,7 +110,7 @@ class InvitedGuestView(GenericAPIView): serializer_class = GuestRegisterSerializer # TODO Update to make dynamic based on where we get the information from. If we get some from Feide, then the user should not be allowed to change it - fields_allowed_to_update = ["email", "fnr", "mobile_phone"] + fields_allowed_to_update = ["email", "fnr", "mobile_phone", "passport"] def get(self, request, *args, **kwargs): """ @@ -135,17 +135,16 @@ class InvitedGuestView(GenericAPIView): sponsor = role.sponsor_id # If the user is not logged in then tell the client to take him through the manual registration process - is_not_logged_in = request.user.is_anonymous + session_type = ( + SessionType.INVITE.value + if request.user.is_anonymous + else SessionType.FEIDE.value + ) - fnr_verified = False try: - fnr_identity = person.identities.get( + fnr = person.identities.get( type=Identity.IdentityType.NORWEGIAN_NATIONAL_ID_NUMBER - ) - fnr = fnr_identity.value - # TODO Maybe other criteria should be specified here - if fnr.verified == Identity.Verified.AUTOMATIC: - fnr_verified = True + ).value except Identity.DoesNotExist: fnr = None @@ -178,10 +177,7 @@ class InvitedGuestView(GenericAPIView): "end": role.end_date, "comments": role.comments, }, - "meta": { - "form_type": "manual" if is_not_logged_in else "feide", - "fnr_verified": fnr_verified, - }, + "meta": {"session_type": session_type}, } return JsonResponse(data=data, status=status.HTTP_200_OK) @@ -215,7 +211,11 @@ class InvitedGuestView(GenericAPIView): return Response(status=status.HTTP_400_BAD_REQUEST) with transaction.atomic(): - serializer = self.get_serializer(instance=person, data=request.data) + # Note this only serializes data for the person, it does not look at other sections + # in the response that happen to be there + serializer = self.get_serializer( + instance=person, data=request.data["person"] + ) serializer.is_valid(raise_exception=True) person = serializer.save() @@ -241,8 +241,9 @@ class InvitedGuestView(GenericAPIView): def _only_allowed_fields_in_request(self, request_data) -> bool: # Check how many of the allowed fields are filled in + person_data = request_data["person"] number_of_fields_filled_in = sum( - map(lambda x: x in request_data.keys(), self.fields_allowed_to_update) + map(lambda x: x in person_data.keys(), self.fields_allowed_to_update) ) # Check that there are no other fields filled in - return number_of_fields_filled_in == len(request_data.keys()) + return number_of_fields_filled_in == len(person_data.keys()) diff --git a/gregui/authentication/auth_backends.py b/gregui/authentication/auth_backends.py index 2ea983e5f2934514ad3cb60a4dbb79701a6283db..74b897c1de1035368d5977430e2aa4224348273d 100644 --- a/gregui/authentication/auth_backends.py +++ b/gregui/authentication/auth_backends.py @@ -6,10 +6,10 @@ from django.conf import settings from django.contrib.auth import get_user_model from django.contrib.auth.backends import BaseBackend from django.core.exceptions import SuspiciousOperation -from mozilla_django_oidc.auth import OIDCAuthenticationBackend - from django.utils import timezone +from mozilla_django_oidc.auth import OIDCAuthenticationBackend + from greg.models import Identity, Person, Sponsor from gregui.models import GregUserProfile diff --git a/gregui/tests/api/test_invitation.py b/gregui/tests/api/test_invitation.py index d4ed39df02c61c0bd2eacdaf173c650f5c485e67..58c3275b3d8780338156c336dcc838c8ad0e68ad 100644 --- a/gregui/tests/api/test_invitation.py +++ b/gregui/tests/api/test_invitation.py @@ -44,7 +44,7 @@ def test_get_invited_info_no_session(client, invitation_link): @pytest.mark.django_db def test_get_invited_info_session_okay( - client, invitation_link, person_foo_data, sponsor_guy_data, role_type_foo, unit_foo + client, invitation_link, person_foo_data, sponsor_guy_data, role_type_foo, unit_foo ): # get a session client.get( @@ -78,7 +78,7 @@ def test_get_invited_info_session_okay( @pytest.mark.django_db def test_get_invited_info_expired_link( - client, invitation_link, invitation_expired_date + client, invitation_link, invitation_expired_date ): # Get a session while link is valid client.get( @@ -96,7 +96,7 @@ def test_get_invited_info_expired_link( @pytest.mark.django_db def test_invited_guest_can_post_information( - client: APIClient, invitation_link, person_foo_data + client: APIClient, invitation_link, person_foo_data ): # get a session client.get( @@ -109,7 +109,7 @@ def test_invited_guest_can_post_information( # post updated info to confirm from guest new_email = "private@example.org" new_phone = "+4797543992" - data = dict(email=new_email, mobile_phone=new_phone) + data = dict(person=dict(email=new_email, mobile_phone=new_phone)) response = client.post( reverse("gregui-v1:invited-info"), data, @@ -125,7 +125,7 @@ def test_invited_guest_can_post_information( @pytest.mark.django_db def test_post_invited_info_expired_session( - client, invitation_link, invitation_expired_date + client, invitation_link, invitation_expired_date ): # get a session client.get( @@ -158,9 +158,9 @@ def test_post_invited_info_deleted_inv_link(client, invitation_link): @pytest.mark.django_db def test_post_invited_info_invalid_national_id_number( - client, invitation_link, person_foo_data, person + client, invitation_link, person_foo_data, person ): - data = {"mobile_phone": "+4707543001", "email": "test@example.com", "fnr": "123"} + data = {"person": {"mobile_phone": "+4707543001", "email": "test@example.com", "fnr": "123"}} url = reverse("gregui-v1:invited-info") assert person.fnr is None @@ -179,10 +179,10 @@ def test_post_invited_info_invalid_national_id_number( @pytest.mark.django_db def test_post_invited_info_valid_national_id_number( - client, invitation_link, person_foo_data, person + client, invitation_link, person_foo_data, person ): fnr = "11120618212" - data = {"mobile_phone": "+4797543992", "email": "test@example.com", "fnr": fnr} + data = {"person": {"mobile_phone": "+4797543992", "email": "test@example.com", "fnr": fnr}} url = reverse("gregui-v1:invited-info") assert person.fnr is None @@ -215,7 +215,7 @@ def test_email_update(client, invitation_link, person_foo_data, person): session["invite_id"] = str(invitation_link.uuid) session.save() - data = {"mobile_phone": "+4797543992", "email": "test2@example.com"} + data = {"person": {"mobile_phone": "+4797543992", "email": "test2@example.com"}} response = client.post(url, data, format="json") assert response.status_code == status.HTTP_200_OK