Repository: vitalik/django-ninja
Pull request #351 (2022-02-08 15:20:37)

[feat] add superuser session authentication only
I have some use cases where some APIs are only for some privileged root user and it quite helpful to have an helper to add this extra security.

Let me know


Patch:
diff --git a/ninja/security/__init__.py b/ninja/security/__init__.py
index 2c4dd6f..1cecb71 100644
--- a/ninja/security/__init__.py
+++ b/ninja/security/__init__.py
@@ -1,6 +1,6 @@
 from ninja.security.apikey import APIKeyCookie, APIKeyHeader, APIKeyQuery
 from ninja.security.http import HttpBasicAuth, HttpBearer
-from ninja.security.session import SessionAuth
+from ninja.security.session import SessionAuth, SessionAuthSuperUser
 
 __all__ = [
     "APIKeyCookie",
@@ -9,7 +9,9 @@ __all__ = [
     "HttpBasicAuth",
     "HttpBearer",
     "SessionAuth",
+    "SessionAuthSuperUser",
     "django_auth",
 ]
 
 django_auth = SessionAuth()
+django_auth_superuser = SessionAuthSuperUser()
diff --git a/ninja/security/session.py b/ninja/security/session.py
index 2fdae35..80a592b 100644
--- a/ninja/security/session.py
+++ b/ninja/security/session.py
@@ -5,7 +5,7 @@ from django.http import HttpRequest
 
 from ninja.security.apikey import APIKeyCookie
 
-__all__ = ["SessionAuth"]
+__all__ = ["SessionAuth", "SessionAuthSuperUser"]
 
 
 class SessionAuth(APIKeyCookie):
@@ -17,3 +17,15 @@ class SessionAuth(APIKeyCookie):
             return request.user
 
         return None
+
+
+class SessionAuthSuperUser(APIKeyCookie):
+    "Reusing Django session authentication & verify that the user is a super user"
+    param_name: str = settings.SESSION_COOKIE_NAME
+
+    def authenticate(self, request: HttpRequest, key: Optional[str]) -> Optional[Any]:
+        is_superuser = getattr(request.user, "is_superuser", None)
+        if request.user.is_authenticated and is_superuser:
+            return request.user
+
+        return None


Test patch:
diff --git a/tests/test_auth.py b/tests/test_auth.py
index 85855f5..01121bd 100644
--- a/tests/test_auth.py
+++ b/tests/test_auth.py
@@ -11,6 +11,7 @@ from ninja.security import (
     HttpBasicAuth,
     HttpBearer,
     django_auth,
+    django_auth_superuser,
 )
 from ninja.security.base import AuthBase
 from ninja.testing import TestClient
@@ -75,6 +76,7 @@ def on_custom_error(request, exc):
 
 for path, auth in [
     ("django_auth", django_auth),
+    ("django_auth_superuser", django_auth_superuser),
     ("callable", callable_auth),
     ("apikeyquery", KeyQuery()),
     ("apikeyheader", KeyHeader()),
@@ -91,6 +93,12 @@ client = TestClient(api)
 
 class MockUser(str):
     is_authenticated = True
+    is_superuser = False
+
+
+class MockSuperUser(str):
+    is_authenticated = True
+    is_superuser = True
 
 
 BODY_UNAUTHORIZED_DEFAULT = dict(detail="Unauthorized")
@@ -101,6 +109,19 @@ BODY_UNAUTHORIZED_DEFAULT = dict(detail="Unauthorized")
     [
         ("/django_auth", {}, 401, BODY_UNAUTHORIZED_DEFAULT),
         ("/django_auth", dict(user=MockUser("admin")), 200, dict(auth="admin")),
+        ("/django_auth_superuser", {}, 401, BODY_UNAUTHORIZED_DEFAULT),
+        (
+            "/django_auth_superuser",
+            dict(user=MockUser("admin")),
+            401,
+            BODY_UNAUTHORIZED_DEFAULT,
+        ),
+        (
+            "/django_auth_superuser",
+            dict(user=MockSuperUser("admin")),
+            200,
+            dict(auth="admin"),
+        ),
         ("/callable", {}, 401, BODY_UNAUTHORIZED_DEFAULT),
         ("/callable?auth=demo", {}, 200, dict(auth="demo")),
         ("/apikeyquery", {}, 401, BODY_UNAUTHORIZED_DEFAULT),
@@ -184,6 +205,7 @@ def test_schema():
         "KeyHeaderCustomException": {"in": "header", "name": "key", "type": "apiKey"},
         "KeyQuery": {"in": "query", "name": "key", "type": "apiKey"},
         "SessionAuth": {"in": "cookie", "name": "sessionid", "type": "apiKey"},
+        "SessionAuthSuperUser": {"in": "cookie", "name": "sessionid", "type": "apiKey"},
     }
     # TODO: Samename for schema check
     # TODO: check operation security attributes