From b8546953992473e6e0b2cd254019c5cf62c1c4a1 Mon Sep 17 00:00:00 2001
From: mpeltriaux <Michel.Peltriaux@sgdnord.rlp.de>
Date: Thu, 17 Aug 2023 10:44:58 +0200
Subject: [PATCH] Unit test api

* adds unit test for APIUserToken
* enhances handling of token fetching for API
---
 api/models/token.py          |  6 +--
 api/tests/unit/__init__.py   |  7 ++++
 api/tests/unit/test_token.py | 71 ++++++++++++++++++++++++++++++++++++
 api/views/views.py           |  8 +++-
 4 files changed, 87 insertions(+), 5 deletions(-)
 create mode 100644 api/tests/unit/__init__.py
 create mode 100644 api/tests/unit/test_token.py

diff --git a/api/models/token.py b/api/models/token.py
index e0ad6646..c528528c 100644
--- a/api/models/token.py
+++ b/api/models/token.py
@@ -14,7 +14,7 @@ class APIUserToken(models.Model):
     valid_until = models.DateField(
         blank=True,
         null=True,
-        help_text="Token is only valid until this date",
+        help_text="Token is only valid until this date. Forever if null/blank.",
     )
     is_active = models.BooleanField(
         default=False,
@@ -25,12 +25,11 @@ class APIUserToken(models.Model):
         return self.token
 
     @staticmethod
-    def get_user_from_token(token: str, username: str):
+    def get_user_from_token(token: str):
         """ Getter for the related user object
 
         Args:
             token (str): The used token
-            username (str): The username
 
         Returns:
             user (User): Otherwise None
@@ -39,7 +38,6 @@ class APIUserToken(models.Model):
         try:
             token_obj = APIUserToken.objects.get(
                 token=token,
-                user__username=username
             )
             if not token_obj.is_active:
                 raise PermissionError("Token unverified")
diff --git a/api/tests/unit/__init__.py b/api/tests/unit/__init__.py
new file mode 100644
index 00000000..5be1b4ac
--- /dev/null
+++ b/api/tests/unit/__init__.py
@@ -0,0 +1,7 @@
+"""
+Author: Michel Peltriaux
+Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
+Contact: ksp-servicestelle@sgdnord.rlp.de
+Created on: 17.08.23
+
+"""
diff --git a/api/tests/unit/test_token.py b/api/tests/unit/test_token.py
new file mode 100644
index 00000000..0cdd9f80
--- /dev/null
+++ b/api/tests/unit/test_token.py
@@ -0,0 +1,71 @@
+"""
+Author: Michel Peltriaux
+Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
+Contact: ksp-servicestelle@sgdnord.rlp.de
+Created on: 17.08.23
+
+"""
+from datetime import timedelta
+
+from django.utils.timezone import now
+
+from api.models import APIUserToken
+from konova.tests.test_views import BaseTestCase
+
+
+class APIUserTokenTestCase(BaseTestCase):
+    def setUp(self) -> None:
+        super().setUp()
+
+        self.token = APIUserToken.objects.create()
+        self.superuser.api_token = self.token
+        self.superuser.save()
+
+    def test_str(self):
+        self.assertEqual(str(self.token), self.token.token)
+
+    def test_get_user_from_token(self):
+        a_day = timedelta(days=1)
+        today = now().date()
+
+        self.assertFalse(self.token.is_active)
+        self.assertIsNone(self.token.valid_until)
+
+        try:
+            #Token not existing --> fail
+            token_user = APIUserToken.get_user_from_token(self.token.token[::-1])
+            self.fail("There should not have been any token")
+        except PermissionError:
+            pass
+
+        try:
+            # Token not active --> fail
+            token_user = APIUserToken.get_user_from_token(self.token.token)
+            self.fail("Token is unverified but token user has been fetchable.")
+        except PermissionError:
+            pass
+        self.token.is_active = True
+        self.token.valid_until = today - a_day
+        self.token.save()
+
+        try:
+            # Token valid until yesterday --> fail
+            token_user = APIUserToken.get_user_from_token(self.token.token)
+            self.fail("Token reached end of lifetime but token user has been fetchable.")
+        except PermissionError:
+            pass
+
+        # Token valid until tomorrow --> success
+        self.token.valid_until = today + a_day
+        self.token.save()
+
+        token_user = APIUserToken.get_user_from_token(self.token.token)
+        self.assertEqual(token_user, self.superuser)
+        del token_user
+
+        # Token valid forever --> success
+        self.token.valid_until = None
+        self.token.save()
+        token_user = APIUserToken.get_user_from_token(self.token.token)
+        self.assertEqual(token_user, self.superuser)
+
diff --git a/api/views/views.py b/api/views/views.py
index 75d764e8..f3a86bc6 100644
--- a/api/views/views.py
+++ b/api/views/views.py
@@ -53,7 +53,13 @@ class AbstractAPIView(View):
             # Fetch the proper user from the given request header token
             ksp_token = request.headers.get(KSP_TOKEN_HEADER_IDENTIFIER, None)
             ksp_user = request.headers.get(KSP_USER_HEADER_IDENTIFIER, None)
-            self.user = APIUserToken.get_user_from_token(ksp_token, ksp_user)
+            token_user = APIUserToken.get_user_from_token(ksp_token)
+
+            if ksp_user != token_user.username:
+                raise PermissionError(f"Invalid token for {ksp_user}")
+            else:
+                self.user = token_user
+
             request.user = self.user
             if not self.user.is_default_user():
                 raise PermissionError("Default permissions required")