Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • andretol/greg
1 result
Show changes
......@@ -143,7 +143,6 @@ def test_is_registered_completed_in_future(person_registered_future):
(Identity.IdentityType.PRIVATE_EMAIL, False),
(Identity.IdentityType.PRIVATE_MOBILE_NUMBER, False),
(Identity.IdentityType.FEIDE_ID, False),
(Identity.IdentityType.OTHER, False),
),
)
def test_is_verified(identity_type, is_verified, person):
......
......@@ -9,9 +9,17 @@ def camel_to_snake(s: str) -> str:
return re.sub("([A-Z])", "_\\1", s).lower().lstrip("_")
def is_valid_id_number(input_digits: str) -> bool:
"""
Checks whether the input represents a valid national ID number (fødselsnummer) or a valid D-number.
"""
is_dnumber = int(input_digits[0:1]) >= 4
return is_valid_norwegian_national_id_number(input_digits, is_dnumber)
def is_valid_norwegian_national_id_number(input_digits: str, is_dnumber: bool) -> bool:
"""
Checks whether input_digits is a valid national ID number of D-number.
Checks whether input_digits is a valid national ID number or D-number.
It is based on the code found here:
https://github.com/navikt/fnrvalidator/blob/master/src/validator.js
......
......@@ -301,3 +301,7 @@ ORGREG_NAME = "orgreg_id"
# Extra ids to be imported from orgreg, list of dict with source/type.
# [{"source": "sapuio", "type": "legacy_stedkode"}]
ORGREG_EXTRA_IDS = []
# Acronyms to be imported as identifiers from orgreg. List of acronym values, supported ones are: nob, nno, eng
# Example list: ["nob"]
ORGREG_ACRONYMS = []
import logging
from enum import Enum
from typing import Optional, List
import structlog
from django.core import exceptions
from django.db import transaction
......@@ -24,7 +24,7 @@ from gregui.api.serializers.invitation import InviteGuestSerializer
from gregui.mailutils import send_invite_mail
from gregui.models import GregUserProfile
logger = logging.getLogger(__name__)
logger = structlog.getLogger(__name__)
class InvitationView(CreateAPIView, DestroyAPIView):
......@@ -91,9 +91,7 @@ class InvitationView(CreateAPIView, DestroyAPIView):
# not be verified, but including that check just in case here.
# If this is the case then there is an unexpected situation, the cancel option
# should only apply to guests that have not completed the registration
logger.warning(
f"Attempting to delete invitation for already registered guest with person ID {person_id}"
)
logger.warning("try_delete_registered_invite", person_id=person_id)
return Response(status=status.HTTP_400_BAD_REQUEST)
# Delete the person. The delete will cascade and all roles, identities and invitations will be removed.
......@@ -109,7 +107,7 @@ class CheckInvitationView(APIView):
permission_classes = [AllowAny]
throttle_classes = [AnonRateThrottle]
def post(self, request, *args, **kwargs):
def post(self, request, *args, **kwargs) -> Response:
"""
Endpoint for verifying and setting invite_id in session.
......@@ -132,6 +130,16 @@ class CheckInvitationView(APIView):
request.session["invite_id"] = invite_id
return Response(status=status.HTTP_200_OK)
def delete(self, request, *args, **kwargs) -> Response:
if "invite_id" in request.session:
logger.info(
"invitation_session_deleted", invite_id=request.session["invite_id"]
)
del request.session["invite_id"]
return Response(status.HTTP_200_OK)
return Response(status=status.HTTP_403_FORBIDDEN)
class SessionType(Enum):
INVITE = "invite"
......@@ -322,9 +330,7 @@ class ResendInvitationView(UpdateModelMixin, APIView):
if non_expired_links.count() > 0:
if non_expired_links.count() > 1:
# Do not expect this to happen
logger.warning(
f"Person with ID {person_id} has multiple invitation links"
)
logger.warning("found_multiple_invitation_links", person_id=person_id)
# Just resend all and do not create a new one
for link in non_expired_links:
......@@ -339,9 +345,7 @@ class ResendInvitationView(UpdateModelMixin, APIView):
# Do not expected that a person has several open invitations, it could happen
# if he has been invited by different sponsor at the same time, but that
# could be an indication that there has been a mixup
logger.warning(
f"Multiple invitations exist for person with ID {person_id}"
)
logger.warning("found_multiple_invitations", person_id=person_id)
for invitation in invitations_to_resend:
invitation_link = InvitationLink.objects.create(
......
......@@ -34,6 +34,10 @@ class UserInfoView(APIView):
user = request.user
invite_id = request.session.get("invite_id")
auth_type = "invite"
if "oidc_states" in request.session.keys():
auth_type = "oidc"
person = None
sponsor = None
content = {
......@@ -41,6 +45,7 @@ class UserInfoView(APIView):
"sponsor_id": None,
"person_id": None,
"roles": [],
"auth_type": auth_type,
}
# Fetch sponsor and/or person object from profile of authenticated user
......
......@@ -79,12 +79,6 @@ def send_invite_mail(link: InvitationLink) -> Optional[str]:
return None
sponsor = link.invitation.role.sponsor
if not sponsor:
logger.warning(
"Unable to determine sponsor for invitation link with ID: {%s}", link.id
)
return None
return send_registration_mail(
email_address.value, f"{sponsor.first_name} {sponsor.last_name}"
)
import pytest
from rest_framework.reverse import reverse
from greg.models import Identity
from gregui.models import GregUserProfile
@pytest.mark.django_db
def test_identity_get(client, log_in, user_sponsor, person_foo):
log_in(user_sponsor)
response = client.get(
reverse("gregui-v1:identity-detail", kwargs={"pk": person_foo.fnr.id})
)
assert response.status_code == 200
content = response.json()
assert content["person"] == person_foo.id
@pytest.mark.django_db
def test_identity_patch(client, log_in, user_sponsor, person_foo):
log_in(user_sponsor)
ident = Identity.objects.get(pk=person_foo.fnr.id)
assert not ident.verified
client.patch(
reverse("gregui-v1:identity-detail", kwargs={"pk": person_foo.fnr.id}),
data={},
)
ident.refresh_from_db()
assert ident.verified == Identity.Verified.MANUAL
assert ident.verified_by == GregUserProfile.objects.get(user=user_sponsor).sponsor
......@@ -467,3 +467,29 @@ def test_saves_consents(client, invited_person, consent_type_foo, consent_type_b
type=consent_type_bar,
choice=consent_type_bar.choices.filter(value="yes").first(),
).exists()
@pytest.mark.django_db
def test_post_invited_info_valid_dnumber(client, invited_person):
person, invitation_link = invited_person
d_number = "53097248016"
data = {
"person": {
"mobile_phone": "+4797543992",
"email": "test@example.com",
"fnr": d_number,
}
}
url = reverse("gregui-v1:invited-info")
assert person.fnr is None
session = client.session
session["invite_id"] = str(invitation_link.uuid)
session.save()
response = client.post(url, data, format="json")
assert response.status_code == status.HTTP_200_OK, response.data
person.refresh_from_db()
assert person.fnr.value == d_number
......@@ -19,6 +19,7 @@ def test_userinfo_invited_get(client, invitation_link):
response = client.get(reverse("api-userinfo"))
assert response.status_code == status.HTTP_200_OK
assert response.json() == {
"auth_type": "invite",
"feide_id": None,
"sponsor_id": None,
"person_id": 1,
......@@ -43,6 +44,14 @@ def test_userinfo_invited_get(client, invitation_link):
}
@pytest.mark.django_db
def test_userinfo_user_no_profile(client, log_in, user_no_profile):
"""Pure django users should be forbidden"""
log_in(user_no_profile)
response = client.get(reverse("api-userinfo"))
assert response.status_code == status.HTTP_403_FORBIDDEN
@pytest.mark.django_db
def test_userinfo_sponsor_get(client, log_in, user_sponsor):
"""Sponsors should get info about themselves"""
......@@ -51,6 +60,7 @@ def test_userinfo_sponsor_get(client, log_in, user_sponsor):
response = client.get(reverse("api-userinfo"))
assert response.status_code == status.HTTP_200_OK
assert response.json() == {
"auth_type": "oidc",
"feide_id": "",
"person_id": None,
"roles": [],
......@@ -65,6 +75,7 @@ def test_userinfo_guest_get(client, log_in, user_person):
response = client.get(reverse("api-userinfo"))
assert response.status_code == status.HTTP_200_OK
assert response.json() == {
"auth_type": "oidc",
"feide_id": "",
"sponsor_id": None,
"person_id": 1,
......
......@@ -142,6 +142,19 @@ def create_user() -> Callable[[str, str, str, str], UserModel]:
return create_user
@pytest.fixture
def user_no_profile(create_user) -> User:
"""User without a GregUserProfile"""
user_model = get_user_model()
user = create_user(
username="no_profile",
email="no_profile@example.org",
first_name="No",
last_name="Profile",
)
return user_model.objects.get(id=user.id)
@pytest.fixture
def user_sponsor(sponsor_foo: Sponsor, create_user) -> User:
user_model = get_user_model()
......@@ -458,6 +471,7 @@ def log_in(client) -> Callable[[UserModel], APIClient]:
# It seems like the session was not updated automatically this way
session = client.session
session["oidc_id_token_payload"] = {"iat": time.time()}
session["oidc_states"] = {}
session.save()
return client
......
......@@ -57,3 +57,22 @@ def test_confirmation_mail(confirmation_template):
assert result(task_id) == 1
assert len(mail.outbox) == 1
assert mail.outbox[0].to == ["test@example.no"]
@pytest.mark.django_db
def test_send_invite_mail(registration_template, invited_person):
"""Verify function queues an email when called"""
_, link = invited_person
assert len(mail.outbox) == 0
assert mailutils.send_invite_mail(link)
assert len(mail.outbox) == 1
@pytest.mark.django_db
def test_send_invite_mail_no_mail(invited_person):
"""Verify function returns None if an email is not present"""
person, link = invited_person
person.private_email.delete()
assert len(mail.outbox) == 0
assert mailutils.send_invite_mail(link) is None
assert len(mail.outbox) == 0
from gregui.utils import name_diff, restricted_damarau_levenshtein
def test_rdm_replace():
assert restricted_damarau_levenshtein("abc", "abd") == 1
def test_rdm_swap():
assert restricted_damarau_levenshtein("abc", "bac") == 1
def test_name_diff():
assert name_diff("Foo Bar", "Foo Baz") == 1
def test_name_longer():
"""Verify stops early with default threshold"""
assert name_diff("Abcdefgh Ijklmnop", "Qrstuvw Xyz") == 5
def test_name_threshold():
"""Verify continues with raised threshold"""
assert name_diff("Abcdefgh Ijklmnop", "Qrstuvw Xyz", 100) == 10
......@@ -3,16 +3,17 @@ from typing import Optional
import phonenumbers
from rest_framework import serializers
from greg.utils import is_valid_norwegian_national_id_number
from greg.utils import is_valid_id_number
_valid_email_regex = r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b"
def validate_norwegian_national_id_number(value):
# Not excepted that D-numbers will be entered through the form, so only
# accept national ID numbers
if not is_valid_norwegian_national_id_number(value, False):
raise serializers.ValidationError("Not a valid Norwegian national ID number")
"""
Both fødselsnummer and D-nummer are accepted
"""
if not is_valid_id_number(value):
raise serializers.ValidationError("Not a valid ID number")
def validate_phone_number(value):
......