From 65d43caf15cffd3eae005b3d8de62db520f13a1e Mon Sep 17 00:00:00 2001
From: imperosol <thgirod@hotmail.com>
Date: Tue, 20 May 2025 21:04:49 +0200
Subject: [PATCH] adapt `CanAccessLookup` to api key auth

---
 club/api.py                              |  7 ++++--
 club/tests/test_club_controller.py       | 15 ++++++++-----
 core/api.py                              |  4 ++++
 core/auth/api_permissions.py             |  2 +-
 core/management/commands/populate.py     |  2 ++
 core/migrations/0046_permissionrights.py | 28 ++++++++++++++++++++++++
 core/models.py                           | 17 ++++++++++++++
 counter/api.py                           |  4 ++++
 pedagogy/tests/test_api.py               |  2 +-
 sas/api.py                               |  3 +++
 10 files changed, 75 insertions(+), 9 deletions(-)
 create mode 100644 core/migrations/0046_permissionrights.py

diff --git a/club/api.py b/club/api.py
index 147f6379..decdf8f8 100644
--- a/club/api.py
+++ b/club/api.py
@@ -8,7 +8,7 @@ from ninja_extra.schemas import PaginatedResponseSchema
 
 from apikey.auth import ApiKeyAuth
 from club.models import Club
-from club.schemas import ClubSchema
+from club.schemas import ClubSchema, SimpleClubSchema
 from core.auth.api_permissions import CanAccessLookup, HasPerm
 
 
@@ -16,8 +16,10 @@ from core.auth.api_permissions import CanAccessLookup, HasPerm
 class ClubController(ControllerBase):
     @route.get(
         "/search",
-        response=PaginatedResponseSchema[ClubSchema],
+        response=PaginatedResponseSchema[SimpleClubSchema],
+        auth=[SessionAuth(), ApiKeyAuth()],
         permissions=[CanAccessLookup],
+        url_name="search_club",
     )
     @paginate(PageNumberPaginationExtra, page_size=50)
     def search_club(self, search: Annotated[str, MinLen(1)]):
@@ -28,6 +30,7 @@ class ClubController(ControllerBase):
         response=ClubSchema,
         auth=[SessionAuth(), ApiKeyAuth()],
         permissions=[HasPerm("club.view_club")],
+        url_name="fetch_club",
     )
     def fetch_club(self, club_id: int):
         return self.get_object_or_exception(
diff --git a/club/tests/test_club_controller.py b/club/tests/test_club_controller.py
index e48a4513..ade8eb4d 100644
--- a/club/tests/test_club_controller.py
+++ b/club/tests/test_club_controller.py
@@ -1,16 +1,21 @@
 import pytest
+from django.test import Client
+from django.urls import reverse
 from model_bakery import baker
-from ninja_extra.testing import TestClient
 from pytest_django.asserts import assertNumQueries
 
-from club.api import ClubController
 from club.models import Club, Membership
+from core.baker_recipes import subscriber_user
 
 
 @pytest.mark.django_db
-def test_fetch_club():
+def test_fetch_club(client: Client):
     club = baker.make(Club)
     baker.make(Membership, club=club, _quantity=10, _bulk_create=True)
-    with assertNumQueries(3):
-        res = TestClient(ClubController).get(f"/{club.id}")
+    user = subscriber_user.make()
+    client.force_login(user)
+    with assertNumQueries(7):
+        # - 4 queries for authentication
+        # - 3 queries for the actual data
+        res = client.get(reverse("api:fetch_club", kwargs={"club_id": club.id}))
         assert res.status_code == 200
diff --git a/core/api.py b/core/api.py
index 830e06e9..bd74875b 100644
--- a/core/api.py
+++ b/core/api.py
@@ -5,11 +5,13 @@ from django.conf import settings
 from django.db.models import F
 from django.http import HttpResponse
 from ninja import File, Query
+from ninja.security import SessionAuth
 from ninja_extra import ControllerBase, api_controller, paginate, route
 from ninja_extra.exceptions import PermissionDenied
 from ninja_extra.pagination import PageNumberPaginationExtra
 from ninja_extra.schemas import PaginatedResponseSchema
 
+from apikey.auth import ApiKeyAuth
 from club.models import Mailing
 from core.auth.api_permissions import CanAccessLookup, CanView, HasPerm
 from core.models import Group, QuickUploadImage, SithFile, User
@@ -90,6 +92,7 @@ class SithFileController(ControllerBase):
     @route.get(
         "/search",
         response=PaginatedResponseSchema[SithFileSchema],
+        auth=[SessionAuth(), ApiKeyAuth()],
         permissions=[CanAccessLookup],
     )
     @paginate(PageNumberPaginationExtra, page_size=50)
@@ -102,6 +105,7 @@ class GroupController(ControllerBase):
     @route.get(
         "/search",
         response=PaginatedResponseSchema[GroupSchema],
+        auth=[SessionAuth(), ApiKeyAuth()],
         permissions=[CanAccessLookup],
     )
     @paginate(PageNumberPaginationExtra, page_size=50)
diff --git a/core/auth/api_permissions.py b/core/auth/api_permissions.py
index 3d18529e..73f9fa84 100644
--- a/core/auth/api_permissions.py
+++ b/core/auth/api_permissions.py
@@ -189,4 +189,4 @@ class IsLoggedInCounter(BasePermission):
         return Counter.objects.filter(token=token).exists()
 
 
-CanAccessLookup = IsOldSubscriber | IsRoot | IsLoggedInCounter
+CanAccessLookup = IsLoggedInCounter | HasPerm("core.access_lookup")
diff --git a/core/management/commands/populate.py b/core/management/commands/populate.py
index ea1f0342..8f101d9f 100644
--- a/core/management/commands/populate.py
+++ b/core/management/commands/populate.py
@@ -805,6 +805,8 @@ class Command(BaseCommand):
                         "add_peoplepicturerelation",
                         "add_page",
                         "add_quickuploadimage",
+                        "view_club",
+                        "access_lookup",
                     ]
                 )
             )
diff --git a/core/migrations/0046_permissionrights.py b/core/migrations/0046_permissionrights.py
new file mode 100644
index 00000000..33df716b
--- /dev/null
+++ b/core/migrations/0046_permissionrights.py
@@ -0,0 +1,28 @@
+# Generated by Django 5.2 on 2025-05-20 17:50
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+    dependencies = [("core", "0045_quickuploadimage")]
+
+    operations = [
+        migrations.CreateModel(
+            name="GlobalPermissionRights",
+            fields=[
+                (
+                    "id",
+                    models.AutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+            ],
+            options={
+                "permissions": [("access_lookup", "Can access any lookup in the sith")],
+                "managed": False,
+                "default_permissions": [],
+            },
+        ),
+    ]
diff --git a/core/models.py b/core/models.py
index b71f5408..9426dfa6 100644
--- a/core/models.py
+++ b/core/models.py
@@ -754,6 +754,23 @@ class UserBan(models.Model):
         return f"Ban of user {self.user.id}"
 
 
+class GlobalPermissionRights(models.Model):
+    """Little hack to have permissions not linked to a specific db table."""
+
+    class Meta:
+        # No database table creation or deletion
+        # operations will be performed for this model.
+        managed = False
+
+        # disable "add", "change", "delete" and "view" default permissions
+        default_permissions = []
+
+        permissions = [("access_lookup", "Can access any lookup in the sith")]
+
+    def __str__(self):
+        return self.__class__.__name__
+
+
 class Preferences(models.Model):
     user = models.OneToOneField(
         User, related_name="_preferences", on_delete=models.CASCADE
diff --git a/counter/api.py b/counter/api.py
index 44b58488..11bf56c9 100644
--- a/counter/api.py
+++ b/counter/api.py
@@ -16,10 +16,12 @@ from django.conf import settings
 from django.db.models import F
 from django.shortcuts import get_object_or_404
 from ninja import Query
+from ninja.security import SessionAuth
 from ninja_extra import ControllerBase, api_controller, paginate, route
 from ninja_extra.pagination import PageNumberPaginationExtra
 from ninja_extra.schemas import PaginatedResponseSchema
 
+from apikey.auth import ApiKeyAuth
 from core.auth.api_permissions import CanAccessLookup, CanView, IsInGroup, IsRoot
 from counter.models import Counter, Product, ProductType
 from counter.schemas import (
@@ -62,6 +64,7 @@ class CounterController(ControllerBase):
     @route.get(
         "/search",
         response=PaginatedResponseSchema[SimplifiedCounterSchema],
+        auth=[SessionAuth(), ApiKeyAuth()],
         permissions=[CanAccessLookup],
     )
     @paginate(PageNumberPaginationExtra, page_size=50)
@@ -74,6 +77,7 @@ class ProductController(ControllerBase):
     @route.get(
         "/search",
         response=PaginatedResponseSchema[SimpleProductSchema],
+        auth=[SessionAuth(), ApiKeyAuth()],
         permissions=[CanAccessLookup],
     )
     @paginate(PageNumberPaginationExtra, page_size=50)
diff --git a/pedagogy/tests/test_api.py b/pedagogy/tests/test_api.py
index cbb99c18..a95acf20 100644
--- a/pedagogy/tests/test_api.py
+++ b/pedagogy/tests/test_api.py
@@ -68,7 +68,7 @@ class TestUVSearch(TestCase):
     def test_permissions(self):
         # Test with anonymous user
         response = self.client.get(self.url)
-        assert response.status_code == 403
+        assert response.status_code == 401
 
         # Test with not subscribed user
         self.client.force_login(baker.make(User))
diff --git a/sas/api.py b/sas/api.py
index b82ff5e1..83d7f0c8 100644
--- a/sas/api.py
+++ b/sas/api.py
@@ -5,6 +5,7 @@ from django.core.exceptions import ValidationError
 from django.db.models import F
 from django.urls import reverse
 from ninja import Body, File, Query
+from ninja.security import SessionAuth
 from ninja_extra import ControllerBase, api_controller, paginate, route
 from ninja_extra.exceptions import NotFound, PermissionDenied
 from ninja_extra.pagination import PageNumberPaginationExtra
@@ -12,6 +13,7 @@ from ninja_extra.permissions import IsAuthenticated
 from ninja_extra.schemas import PaginatedResponseSchema
 from pydantic import NonNegativeInt
 
+from apikey.auth import ApiKeyAuth
 from core.auth.api_permissions import (
     CanAccessLookup,
     CanEdit,
@@ -53,6 +55,7 @@ class AlbumController(ControllerBase):
     @route.get(
         "/autocomplete-search",
         response=PaginatedResponseSchema[AlbumAutocompleteSchema],
+        auth=[SessionAuth(), ApiKeyAuth()],
         permissions=[CanAccessLookup],
     )
     @paginate(PageNumberPaginationExtra, page_size=50)