From 3b73a4111ea4871f35f30bf3fd833fcdca8058ec Mon Sep 17 00:00:00 2001
From: Lucas Brandstaetter <lucas@brandstaetter.tech>
Date: Tue, 10 Dec 2024 18:25:37 +0100
Subject: [PATCH] Add teams authentication backend

This backend will allow us to authenticate users based on the teams they
are part of. It is based on the default ModelBackend.
---
 src/backoffice/views/auth.py             |  1 +
 src/backoffice/views/moderation/users.py |  3 ++-
 src/core/backends.py                     | 18 ++++++++++++++++++
 src/hub/settings/base.py                 |  3 ++-
 src/plainui/views/ticket_tokens.py       |  3 ++-
 5 files changed, 25 insertions(+), 3 deletions(-)
 create mode 100644 src/core/backends.py

diff --git a/src/backoffice/views/auth.py b/src/backoffice/views/auth.py
index 5e0e8ff63..89832a316 100644
--- a/src/backoffice/views/auth.py
+++ b/src/backoffice/views/auth.py
@@ -48,6 +48,7 @@ class AuthDebugView(ConferenceLoginRequiredMixin, View):
             'active': u.is_active,
             'flags': [],
             'groups': list(u.groups.values_list('name', flat=True)),
+            'teams': [f"{x['team__conference__name']}: {x['team__name']}" for x in u.teams.values('team__name', 'team__conference__name')],
             'permissions': [str(x) for x in u.get_all_permissions()],
         }
         if u.is_superuser:
diff --git a/src/backoffice/views/moderation/users.py b/src/backoffice/views/moderation/users.py
index d4580d09a..a2816f6c0 100644
--- a/src/backoffice/views/moderation/users.py
+++ b/src/backoffice/views/moderation/users.py
@@ -2,6 +2,7 @@ import logging
 
 from oauth2_provider.models import AccessToken
 
+from django.conf import settings
 from django.contrib import messages
 from django.contrib.sessions.exceptions import SuspiciousSession
 from django.contrib.sessions.models import Session
@@ -58,7 +59,7 @@ class ModerationUserDetailView(ModerationAdminMixin, DetailView):
         for session in Session.objects.all():
             try:
                 session_data = session.get_decoded()
-                if session_data.get('_auth_user_id') == str(user.pk) and session_data.get('_auth_user_backend') == 'django.contrib.auth.backends.ModelBackend':
+                if session_data.get('_auth_user_id') == str(user.pk) and session_data.get('_auth_user_backend') == settings.BASE_AUTHENTICATION_BACKEND:
                     session.delete()
                     deleted_sessions += 1
 
diff --git a/src/core/backends.py b/src/core/backends.py
new file mode 100644
index 000000000..c5472ee02
--- /dev/null
+++ b/src/core/backends.py
@@ -0,0 +1,18 @@
+from django.contrib.auth import get_user_model
+from django.contrib.auth.backends import ModelBackend
+from django.contrib.auth.models import Permission
+from django.db.models import Q
+
+UserModel = get_user_model()
+
+
+class TeamsBackend(ModelBackend):
+    """
+    Authenticates against settings.AUTH_USER_MODEL.
+    """
+
+    def _get_group_permissions(self, user_obj):
+        user_groups_field = get_user_model()._meta.get_field('groups')
+        user_groups_query = f'group__{user_groups_field.related_query_name()}'
+        teams = user_obj.teams.values('team')
+        return Permission.objects.filter(Q(**{user_groups_query: user_obj}) | Q(group__in=teams))
diff --git a/src/hub/settings/base.py b/src/hub/settings/base.py
index 087ef7e96..cfffd0307 100644
--- a/src/hub/settings/base.py
+++ b/src/hub/settings/base.py
@@ -270,10 +270,11 @@ AUTH_PASSWORD_VALIDATORS = [
     },
 ]
 
+BASE_AUTHENTICATION_BACKEND = 'core.backends.TeamsBackend'
 AUTHENTICATION_BACKENDS = (
     'rules.permissions.ObjectPermissionBackend',
     'oauth2_provider.backends.OAuth2Backend',
-    'django.contrib.auth.backends.ModelBackend',
+    BASE_AUTHENTICATION_BACKEND,
 )
 
 # Session Cookie configuration
diff --git a/src/plainui/views/ticket_tokens.py b/src/plainui/views/ticket_tokens.py
index 648c02d5b..ee824d9b5 100644
--- a/src/plainui/views/ticket_tokens.py
+++ b/src/plainui/views/ticket_tokens.py
@@ -7,6 +7,7 @@ __all__ = (
 
 from django_ratelimit.decorators import ratelimit
 
+from django.conf import settings
 from django.contrib import messages
 from django.contrib.auth import login
 from django.contrib.auth import views as auth_views
@@ -143,7 +144,7 @@ class RedeemTokenUserCreateView(ConferenceRequiredMixin, FormView):
                 user = form.save()
                 ConferenceMemberTicket.redeem_pretix_ticket(self.conf, user, form.cleaned_data['token'])
                 ConferenceMember.objects.update_or_create(conference=self.conf, user=user, defaults={'has_ticket': True})
-                login(self.request, user, backend='django.contrib.auth.backends.ModelBackend')
+                login(self.request, user, backend=settings.BASE_AUTHENTICATION_BACKEND)
                 return HttpResponseRedirect(self.get_success_url())
         except TicketValidationError as e:
             form.add_error(None, str(e))
-- 
GitLab