Skip to content

Commit

Permalink
Feide update (#813)
Browse files Browse the repository at this point in the history
* Feat(kontres)/add image to bookable item (#785)

* added optional image to bookable item model

* added update method in serializer to handle new images

* linting

* remove update method for images

* Feat(kontres)/add approved by (#786)

* added approved by field

* endpoint will now set approved by

* serializer will return full user object in approved_by_detail

* created test for approved by

* migration

* remove unnecessary code

* removed write-only field in approved-by context

* Create minutes for Codex (#787)

* init

* format

* Feat(minute)/viewset (#788)

* added richer reponse on post and put

* added to admin panel

* added filter for minute

* Feat(kontres)/add notification (#790)

* created methods for sending notification to admin and user

* endpoint will now send notification if needed

* add migrations for new notification types

* Memberships with fines activated (#791)

init

* Feat(user)/user bio (#758)

* Created model, serializer and view for user-bio

* Created user bio model and made migrations

* Created user bio serializer + viewsets + added new endpoint

* Tested create method + added bio serializer to user serializer

* Format

* Created update method and started testing

* Debugging test failures in user retrieve

* fixed model error

* Created user_bio_factory + started testing put method

* Created fixture for UserBio

* Created custom excpetion for duplicate user bio

* Added permissions and inherited from BaseModel

* Modularized serializer for bio

* Use correct serializers in viewset + added destroy method

* Finished testing bio viewset integration + format

* Changed environent file to .env to avoid pushing up keys

* Fix: Flipped assertion statement in test, since user bio should not be deleted

* skiped buggy test from kontres

* added mark to pytest.skip

* Moved keys to .env file and reverted docker variables

* Skip buggy kontres test

* format

* Added str method to user_bio

* Removed unused imports

* format

* Changed user relation to a OneToOne-field (same affect as ForeignKey(unique=True) + removed check for duplicate bio in serializer

* Migrations + changed assertion status code in duplicate bio test (could try catch in serializer to produce 400 status code)

* format

* format

* Changed limit for description 50 -> 500 + migrations

* Migrate

* added id to serializer

* merged leaf nodes in migrations

* format

---------

Co-authored-by: Ester2109 <[email protected]>
Co-authored-by: Mads Nylund <[email protected]>
Co-authored-by: Mads Nylund <[email protected]>
Co-authored-by: Tam Le <[email protected]>

* Update CHANGELOG.md

* added filter for allowed photos for user (#794)

added filter for allowed photos

* Upped payment time when coming from waiting list (#796)

* fixed paymenttime saved to db (#798)

* fixed bug (#800)

* Disallow users to unregister when payment is done (#802)

added 400 status code for deleting paid registration

* update changelog

* Added serializer for category in  event (#804)

added serializer for category in  event

* Permission middelware (#806)

* added a check for existing user and id on request

* format

* Permission refactor of QR Codes (#807)

* added permissions to qr code and refactored viewset

* format

* removed unused imports

* Permissions for payment orders (#808)

* added read permissions

* added permissions for payment order and tests

* format

* chore(iac): updated docs and force https (#810)

chore: updated docs and force https

* feat(iac): add terraform guardrails so index don't nuke our infra (#811)

feat: add guardrails so index don't fup

* Automatic registration for new users with Feide (#809)

* started on feide registration endpoint

* made endpoint for creating user with Feide

* added test for parse group

* finished

* format

* removes three years if in digtrans

* changelog update

---------

Co-authored-by: Erik Skjellevik <[email protected]>
Co-authored-by: haruixu <[email protected]>
Co-authored-by: Ester2109 <[email protected]>
Co-authored-by: Tam Le <[email protected]>
Co-authored-by: martcl <[email protected]>
  • Loading branch information
6 people authored Jul 30, 2024
1 parent e939867 commit 6f118fc
Show file tree
Hide file tree
Showing 26 changed files with 815 additions and 97 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@

## Neste versjon

## Versjon 2024.07.30
-**Feide**. Man kan nå registrere bruker automatisk med Feide.

## Versjon 2024.05.01
-**Arrangement**. Et arrangement vil nå få kategori sendt som navn på kategori istedenfor kun id.
-**Påmelding**. En bruker som har betalt for en påmelding på et arrangement kan ikke lenger melde seg av.
Expand Down
9 changes: 9 additions & 0 deletions app/common/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ def check_has_access(groups_with_access, request):
set_user_id(request)
user = request.user

if not user:
return False

try:
groups = map(str, groups_with_access)
return (
Expand All @@ -61,6 +64,12 @@ def check_has_access(groups_with_access, request):


def set_user_id(request):
# If the id and user of the request is already set, return
if (hasattr(request, "id") and request.id) and (
hasattr(request, "user") and request.user
):
return

token = request.META.get("HTTP_X_CSRF_TOKEN")
request.id = None
request.user = None
Expand Down
92 changes: 92 additions & 0 deletions app/content/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,95 @@ class EventIsFullError(ValueError):

class RefundFailedError(ValueError):
pass


class FeideError(ValueError):
def __init__(
self,
message="Det skjedde en feil under registrering av din bruker ved hjelp av Feide.",
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
):
self.message = message
self.status_code = status_code


class FeideTokenNotFoundError(FeideError):
def __init__(
self,
message="Fikk ikke tak i Feide token for din bruker. Prøv igjen eller registrer deg manuelt.",
):
self.message = message
super().__init__(self.message, status_code=status.HTTP_404_NOT_FOUND)


class FeideUserInfoNotFoundError(FeideError):
def __init__(
self,
message="Fikk ikke tak i brukerinformasjon om deg fra Feide. Prøv igjen eller registrer deg manuelt.",
):
self.message = message
super().__init__(self.message, status_code=status.HTTP_404_NOT_FOUND)


class FeideUsernameNotFoundError(FeideError):
def __init__(
self,
message="Fikk ikke tak i brukernavn fra Feide. Prøv igjen eller registrer deg manuelt.",
):
self.message = message
super().__init__(self.message, status_code=status.HTTP_404_NOT_FOUND)


class FeideUserGroupsNotFoundError(FeideError):
def __init__(
self,
message="Fikk ikke tak i dine gruppetilhørigheter fra Feide. Prøv igjen eller registrer deg manuelt.",
):
self.message = message
super().__init__(self.message, status_code=status.HTTP_404_NOT_FOUND)


class FeideGetTokenError(FeideError):
def __init__(
self,
message="Fikk ikke tilgang til Feide sitt API for å hente ut din token. Prøv igjen eller registrer deg manuelt.",
):
self.message = message
super().__init__(
self.message, status_code=status.HTTP_500_INTERNAL_SERVER_ERROR
)


class FeideUsedUserCode(FeideError):
def __init__(
self,
message="Feide innloggings kode har allerede blitt brukt. Prøv å registrere deg på nytt.",
):
self.message = message
super().__init__(self.message, status_code=status.HTTP_409_CONFLICT)


class FeideGetUserGroupsError(FeideError):
def __init__(
self,
message="Fikk ikke tilgang til Feide sitt API for å hente ut dine utdanninger. Prøv igjen eller registrer deg manuelt.",
):
self.message = message
super().__init__(
self.message, status_code=status.HTTP_500_INTERNAL_SERVER_ERROR
)


class FeideParseGroupsError(FeideError):
def __init__(
self,
message="Vi fant ingen utdanningen du tilhører som er en del av TIHLDE. Hvis du mener dette er feil så kan du opprette en bruker manuelt og sende mail til [email protected] for å den godkjent.",
):
self.message = message
super().__init__(self.message, status_code=status.HTTP_404_NOT_FOUND)


class FeideUserExistsError(FeideError):
def __init__(self, message="Det finnes allerede en bruker med dette brukernavnet."):
self.message = message
super().__init__(self.message, status_code=status.HTTP_409_CONFLICT)
31 changes: 30 additions & 1 deletion app/content/models/qr_code.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from django.db import models

from app.common.enums import Groups
from app.common.permissions import BasePermissionModel
from app.common.permissions import BasePermissionModel, check_has_access
from app.content.models import User
from app.util.models import BaseModel, OptionalImage

Expand All @@ -20,3 +20,32 @@ class Meta:

def __str__(self):
return f"{self.name} - {self.user.user_id}"

@classmethod
def has_read_permission(cls, request):
return check_has_access(cls.read_access, request)

@classmethod
def has_retrieve_permission(cls, request):
return check_has_access(cls.read_access, request)

@classmethod
def has_destroy_permission(cls, request):
return check_has_access(cls.write_access, request)

@classmethod
def has_create_permission(cls, request):
return check_has_access(cls.write_access, request)

@classmethod
def has_update_permission(cls, request):
return check_has_access(cls.write_access, request)

def has_object_retrieve_permission(self, request):
return request.user == self.user

def has_object_update_permission(self, request):
return request.user == self.user

def has_object_destroy_permission(self, request):
return request.user == self.user
1 change: 1 addition & 0 deletions app/content/serializers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
UserSerializer,
DefaultUserSerializer,
UserPermissionsSerializer,
FeideUserCreateSerializer,
)
from app.content.serializers.minute import (
MinuteCreateSerializer,
Expand Down
95 changes: 93 additions & 2 deletions app/content/serializers/user.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,26 @@
from rest_framework import serializers
from rest_framework.exceptions import ValidationError

from django.contrib.auth.hashers import make_password

from dry_rest_permissions.generics import DRYGlobalPermissionsField

from app.common.enums import GroupType
from app.communication.notifier import Notify
from app.communication.enums import UserNotificationSettingType
from app.common.enums import GroupType, Groups
from app.common.serializers import BaseModelSerializer
from app.content.models import User
from app.content.serializers.user_bio import UserBioSerializer
from app.group.models import Group, Membership
from app.content.util.feide_utils import (
get_feide_tokens,
get_feide_user_groups,
parse_feide_groups,
generate_random_password,
get_study_year,
get_feide_user_info_from_jwt,
)
from app.content.exceptions import FeideUserExistsError


class DefaultUserSerializer(BaseModelSerializer):
Expand Down Expand Up @@ -117,6 +130,76 @@ class Meta:
)


class FeideUserCreateSerializer(serializers.Serializer):
code = serializers.CharField(max_length=36)

def create(self, validated_data):
code = validated_data["code"]

access_token, jwt_token = get_feide_tokens(code)
full_name, username = get_feide_user_info_from_jwt(jwt_token)

existing_user = User.objects.filter(user_id=username).first()
if existing_user:
raise FeideUserExistsError()

groups = get_feide_user_groups(access_token)
group_slugs = parse_feide_groups(groups)
password = generate_random_password()

user_info = {
"user_id": username,
"password": make_password(password),
"first_name": full_name.split()[0],
"last_name": " ".join(full_name.split()[1:]),
"email": f"{username}@stud.ntnu.no",
}

user = User.objects.create(**user_info)

self.make_TIHLDE_member(user, password)

for slug in group_slugs:
self.add_user_to_study(user, slug)

return user

def add_user_to_study(self, user, slug):
study = Group.objects.filter(type=GroupType.STUDY, slug=slug).first()
study_year = get_study_year(slug)
class_ = Group.objects.get_or_create(
name=study_year,
type=GroupType.STUDYYEAR,
slug=study_year
)

if not study or not class_:
return

Membership.objects.create(user=user, group=study)
Membership.objects.create(user=user, group=class_[0])

def make_TIHLDE_member(self, user, password):
TIHLDE = Group.objects.get(slug=Groups.TIHLDE)
Membership.objects.get_or_create(user=user, group=TIHLDE)

Notify(
[user], "Velkommen til TIHLDE", UserNotificationSettingType.OTHER
).add_paragraph(f"Hei, {user.first_name}!").add_paragraph(
f"Din bruker har nå blitt automatisk generert ved hjelp av Feide. Ditt brukernavn er dermed ditt brukernavn fra Feide: {user.user_id}. Du kan nå logge inn og ta i bruk våre sider."
).add_paragraph(
f"Ditt autogenererte passord: {password}"
).add_paragraph(
"Vi anbefaler at du bytter passord ved å følge lenken under:"
).add_link(
"Bytt passord", "/glemt-passord/"
).add_link(
"Logg inn", "/logg-inn/"
).send(
website=False, slack=False
)


class UserCreateSerializer(serializers.ModelSerializer):
study = serializers.SlugRelatedField(
slug_field="slug",
Expand Down Expand Up @@ -177,7 +260,15 @@ def get_fields(self):

class UserPermissionsSerializer(serializers.ModelSerializer):
permissions = DRYGlobalPermissionsField(
actions=["write", "write_all", "read", "destroy", "update", "retrieve"]
actions=[
"write",
"write_all",
"read",
"read_all",
"destroy",
"update",
"retrieve",
]
)

class Meta:
Expand Down
32 changes: 32 additions & 0 deletions app/content/tests/test_feide_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import pytest

from app.content.util.feide_utils import parse_feide_groups


@pytest.mark.django_db
def test_parse_feide_groups():
"""A list of group ids should return the slugs that is in TIHLDE"""
groups = [
"fc:fs:fs:prg:ntnu.no:BDIGSEC",
"fc:fs:fs:prg:ntnu.no:ITBAITBEDR",
"fc:fs:fs:prg:ntnu.no:ITJEETTE",
"fc:fs:fs:prg:ntnu.no:ITJESE",
"fc:fs:fs:prg:ntnu.no:BDIGSEREC",
"fc:fs:fs:prg:ntnu.no:BIDATA",
"fc:fs:fs:prg:ntnu.no:ITMAIKTSA",
"fc:fs:fs:prg:ntnu.no:ITBAINFODR",
"fc:fs:fs:prg:ntnu.no:ITBAINFO",
]

slugs = parse_feide_groups(groups)

correct_slugs = [
"dataingenir",
"digital-forretningsutvikling",
"digital-infrastruktur-og-cybersikkerhet",
"digital-samhandling",
"drift-studie",
"informasjonsbehandling",
]

assert sorted(slugs) == sorted(correct_slugs)
2 changes: 2 additions & 0 deletions app/content/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
UserViewSet,
accept_form,
upload,
register_with_feide,
)

router = routers.DefaultRouter()
Expand Down Expand Up @@ -51,5 +52,6 @@
re_path(r"", include(router.urls)),
path("accept-form/", accept_form),
path("upload/", upload),
path("feide/", register_with_feide),
re_path(r"users/(?P<user_id>[^/.]+)/events.ics", UserCalendarEvents()),
]
Loading

0 comments on commit 6f118fc

Please sign in to comment.