diff --git a/core/context_processors.py b/core/context_processors.py index 651cdf5..fa25e85 100644 --- a/core/context_processors.py +++ b/core/context_processors.py @@ -1,3 +1,5 @@ +from core.discord_auth import user_logged_in + SIDEBAR_LINKS = { "Genshin": [ {"name": "Genshin", "url": "/genshin", "icon": "gamepad"}, @@ -25,4 +27,12 @@ def sidebar_links(request): - return {"sidebar_links": SIDEBAR_LINKS} + user = user_logged_in(request) + if user is None: + return {} + links = {} + for role in user.roles.all(): + if SIDEBAR_LINKS.get(role.permissions) is None: + continue + links[role.permissions] = SIDEBAR_LINKS.get(role.permissions) + return {"sidebar_links": links} diff --git a/core/discord_auth.py b/core/discord_auth.py index d0bf54a..a6d5dc7 100644 --- a/core/discord_auth.py +++ b/core/discord_auth.py @@ -11,7 +11,6 @@ DISCORD_ID_COOKIE, ) from core.models import User, UserRole -from gwardia_hub.settings import MEMBER_ROLE_ID def user_logged_in(request: HttpRequest) -> User | None: @@ -46,12 +45,12 @@ def wrapper(request: HttpRequest, *args, **kwargs) -> HttpResponse: return decorator -def require_user(required_role_id: str | None = MEMBER_ROLE_ID): +def require_user(required_permissions: str = None): """Decorator that checks if a user is logged in and has the required role. Adds a second positional argument to the decorated function containting the guild member. - :param required_role_id: A role ID required for the user to be authorised. Defaults to MEMBER_ROLE_ID. + :param required_permissions: The required permissions. """ def decorator(func): @@ -61,9 +60,8 @@ def wrapper(request: HttpRequest, *args, **kwargs): if not user: return temporary_redirect("core:discord_refresh", request) try: - required_role = UserRole.objects.get(id=required_role_id) - if not user.roles.contains(required_role): - return set_id_cookie(HttpResponse("Unauthorized", status=401), user) + if not user.has_permission(required_permissions): + return redirect("core:profile", request) except UserRole.DoesNotExist: pass diff --git a/core/migrations/0002_user_data_valid_until.py b/core/migrations/0002_user_data_valid_until.py index f7da844..e6fd909 100644 --- a/core/migrations/0002_user_data_valid_until.py +++ b/core/migrations/0002_user_data_valid_until.py @@ -15,7 +15,7 @@ class Migration(migrations.Migration): name="data_valid_until", field=models.DateTimeField( default=datetime.datetime( - 2024, 5, 8, 9, 45, 28, 581656, tzinfo=datetime.timezone.utc + 1971, 1, 1, 0, 0, tzinfo=datetime.timezone.utc ) ), ), diff --git a/core/migrations/0003_userrole_permissions.py b/core/migrations/0003_userrole_permissions.py new file mode 100644 index 0000000..fa7306f --- /dev/null +++ b/core/migrations/0003_userrole_permissions.py @@ -0,0 +1,20 @@ +# Generated by Django 5.0.6 on 2024-06-02 11:08 + +import core.models +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("core", "0002_user_data_valid_until"), + ] + + operations = [ + migrations.AddField( + model_name="userrole", + name="permissions", + field=models.CharField( + choices=core.models.get_permission_choices, default="" + ), + ), + ] diff --git a/core/migrations/0004_userrole_prefill.py b/core/migrations/0004_userrole_prefill.py new file mode 100644 index 0000000..8cb8ef1 --- /dev/null +++ b/core/migrations/0004_userrole_prefill.py @@ -0,0 +1,29 @@ +# Generated by Django 5.0.6 on 2024-06-02 11:08 + +from django.db import migrations + + +def create_default_roles(apps, schema_editor): + UserRole = apps.get_model("core", "UserRole") + UserRole.objects.create( + name="Genshiniara", id=1215012858346086461, permissions="Genshin" + ) + UserRole.objects.create( + name="Członek gwardii", id=1200540359919411380, permissions="Gwardia" + ) + UserRole.objects.create( + name="Z klasy", id=1148642506859368448, permissions="Klasowe" + ) + + +def remove_default_roles(apps, schema_editor): + UserRole = apps.get_model("core", "UserRole") + UserRole.objects.all().delete() + + +class Migration(migrations.Migration): + dependencies = [ + ("core", "0003_userrole_permissions"), + ] + + operations = [migrations.RunPython(create_default_roles, remove_default_roles)] diff --git a/core/models.py b/core/models.py index dc2001f..fb388c4 100644 --- a/core/models.py +++ b/core/models.py @@ -1,11 +1,18 @@ -from datetime import timedelta +from datetime import datetime from django.utils import timezone from django.db import models +ROLE_PERMISSIONS = ["None", "Gwardia", "Genshin", "Klasowe"] + + +def get_permission_choices(): + return {i: i for i in ROLE_PERMISSIONS} + class UserRole(models.Model): id = models.BigIntegerField(unique=True, primary_key=True) name = models.CharField() + permissions = models.CharField(choices=get_permission_choices, default="") def __str__(self): return f"{self.name} ({self.id})" @@ -21,7 +28,7 @@ class User(models.Model): # caching data for 10 minutes to prevent Rate-Limits from Discord API data_valid_until = models.DateTimeField( - default=timezone.now() + timedelta(minutes=10) + default=datetime(1971, 1, 1, tzinfo=timezone.timezone.utc), ) def avatar_url(self): @@ -46,6 +53,15 @@ def to_json(self): "avatar": self.avatar_url(), } + def has_permission(self, permission): + if permission is None: + return True + for role in self.roles.all(): + if permission == role.permissions: + return True + + return False + @classmethod def exists(cls, user_id: int) -> bool: try: diff --git a/core/views.py b/core/views.py index ba498ca..f4111bc 100644 --- a/core/views.py +++ b/core/views.py @@ -38,7 +38,7 @@ def index(request): return render(request, "core/index.html", context) -@require_user(required_role_id=None) +@require_user() def profile(request, user, name: str = None): try: user = User.objects.get(username=name) @@ -99,7 +99,7 @@ def gh_webhook(request): return HttpResponse("Invalid request", status=500) -@require_user(required_role_id=None) +@require_user() @require_POST def logout(request, _user): return reset_cookies(redirect("core:index")) diff --git a/gwardia/views.py b/gwardia/views.py index 5cafa26..37bee14 100644 --- a/gwardia/views.py +++ b/gwardia/views.py @@ -13,7 +13,7 @@ def index(request, _user): return redirect("gwardia:meetings") -@require_user() +@require_user("Gwardia") def meetings(request, user: User): upcoming_meetings = Meeting.objects.filter(date__gte=timezone.now()).order_by( "-date" @@ -24,6 +24,6 @@ def meetings(request, user: User): context = { "upcoming_meetings": upcoming_meetings, "archival_meetings": archival_meetings, - user: user.to_json(), + "user": user.to_json(), } return render(request, "gwardia/meetings.html", context)