Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feide update #813

Merged
merged 26 commits into from
Jul 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
6aeef42
Feat(kontres)/add image to bookable item (#785)
eriskjel Mar 23, 2024
c9b5975
Feat(kontres)/add approved by (#786)
eriskjel Apr 6, 2024
28067fa
Create minutes for Codex (#787)
MadsNyl Apr 8, 2024
9e4ff76
Feat(minute)/viewset (#788)
MadsNyl Apr 8, 2024
0544b2f
Feat(kontres)/add notification (#790)
eriskjel Apr 10, 2024
ae483dd
Memberships with fines activated (#791)
MadsNyl Apr 12, 2024
95ef58c
fixed merge
MadsNyl Apr 13, 2024
bfa2299
Feat(user)/user bio (#758)
haruixu Apr 16, 2024
3f56496
Update CHANGELOG.md
MadsNyl Apr 16, 2024
064da8a
added filter for allowed photos for user (#794)
MadsNyl Apr 17, 2024
81a3c5e
Upped payment time when coming from waiting list (#796)
MadsNyl Apr 17, 2024
a583c45
fixed paymenttime saved to db (#798)
MadsNyl Apr 17, 2024
0f24085
fixed bug (#800)
MadsNyl Apr 17, 2024
8a3cfd4
fixed mergeconflict
MadsNyl Apr 17, 2024
e597268
Disallow users to unregister when payment is done (#802)
MadsNyl May 1, 2024
3b84765
update changelog
MadsNyl May 1, 2024
f21e0ab
Added serializer for category in event (#804)
MadsNyl May 2, 2024
e30f102
fixed merge
MadsNyl May 2, 2024
64d717c
Permission middelware (#806)
MadsNyl Jun 9, 2024
ed57afc
Permission refactor of QR Codes (#807)
MadsNyl Jun 9, 2024
ab3cf15
Permissions for payment orders (#808)
MadsNyl Jun 10, 2024
062193d
chore(iac): updated docs and force https (#810)
martcl Jul 26, 2024
23b310a
feat(iac): add terraform guardrails so index don't nuke our infra (#811)
martcl Jul 26, 2024
fa31096
Automatic registration for new users with Feide (#809)
MadsNyl Jul 30, 2024
bef294d
changelog update
MadsNyl Jul 30, 2024
4db63b5
Merge branch 'dev' of https://github.com/TIHLDE/Lepton into dev
MadsNyl Jul 30, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@

## 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.

## Versjon 2024.04.16
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
6 changes: 6 additions & 0 deletions app/content/serializers/category.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,9 @@ class CategorySerializer(BaseModelSerializer):
class Meta:
model = Category
fields = "__all__" # bad form


class SimpleCategorySerializer(BaseModelSerializer):
class Meta:
model = Category
fields = ("id", "text")
3 changes: 3 additions & 0 deletions app/content/serializers/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from app.common.enums import GroupType
from app.common.serializers import BaseModelSerializer
from app.content.models import Event, PriorityPool
from app.content.serializers.category import SimpleCategorySerializer
from app.content.serializers.priority_pool import (
PriorityPoolCreateSerializer,
PriorityPoolSerializer,
Expand All @@ -30,6 +31,7 @@ class EventSerializer(serializers.ModelSerializer):
)
contact_person = DefaultUserSerializer(read_only=True, required=False)
reactions = ReactionSerializer(required=False, many=True)
category = SimpleCategorySerializer(read_only=True)

class Meta:
model = Event
Expand Down Expand Up @@ -104,6 +106,7 @@ def validate_limit(self, limit):
class EventListSerializer(serializers.ModelSerializer):
expired = serializers.BooleanField(read_only=True)
organizer = SimpleGroupSerializer(read_only=True)
category = SimpleCategorySerializer(read_only=True)

class Meta:
model = Event
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
Loading