Forráskód Böngészése

Clean up auth backend

Jeremy Stretch 4 hónapja
szülő
commit
9b85d92ad0
3 módosított fájl, 34 hozzáadás és 83 törlés
  1. 9 70
      contrib/openapi.json
  2. 23 10
      netbox/netbox/api/authentication.py
  3. 2 3
      netbox/users/utils.py

+ 9 - 70
contrib/openapi.json

@@ -166003,86 +166003,25 @@
                         "in": "query",
                         "name": "last_used",
                         "schema": {
-                            "type": "array",
-                            "items": {
-                                "type": "string",
-                                "format": "date-time"
-                            }
-                        },
-                        "explode": true,
-                        "style": "form"
-                    },
-                    {
-                        "in": "query",
-                        "name": "last_used__empty",
-                        "schema": {
-                            "type": "boolean"
+                            "type": "string",
+                            "format": "date-time"
                         }
                     },
-                    {
-                        "in": "query",
-                        "name": "last_used__gt",
-                        "schema": {
-                            "type": "array",
-                            "items": {
-                                "type": "string",
-                                "format": "date-time"
-                            }
-                        },
-                        "explode": true,
-                        "style": "form"
-                    },
                     {
                         "in": "query",
                         "name": "last_used__gte",
                         "schema": {
-                            "type": "array",
-                            "items": {
-                                "type": "string",
-                                "format": "date-time"
-                            }
-                        },
-                        "explode": true,
-                        "style": "form"
-                    },
-                    {
-                        "in": "query",
-                        "name": "last_used__lt",
-                        "schema": {
-                            "type": "array",
-                            "items": {
-                                "type": "string",
-                                "format": "date-time"
-                            }
-                        },
-                        "explode": true,
-                        "style": "form"
+                            "type": "string",
+                            "format": "date-time"
+                        }
                     },
                     {
                         "in": "query",
                         "name": "last_used__lte",
                         "schema": {
-                            "type": "array",
-                            "items": {
-                                "type": "string",
-                                "format": "date-time"
-                            }
-                        },
-                        "explode": true,
-                        "style": "form"
-                    },
-                    {
-                        "in": "query",
-                        "name": "last_used__n",
-                        "schema": {
-                            "type": "array",
-                            "items": {
-                                "type": "string",
-                                "format": "date-time"
-                            }
-                        },
-                        "explode": true,
-                        "style": "form"
+                            "type": "string",
+                            "format": "date-time"
+                        }
                     },
                     {
                         "name": "limit",
@@ -256896,7 +256835,7 @@
                 "type": "apiKey",
                 "in": "header",
                 "name": "Authorization",
-                "description": "Set `Token <token>` (v1) or `Bearer <token>` (v2) in the Authorization header"
+                "description": "`Token <token>` (v1) or `Bearer <key>.<token>` (v2)"
             }
         }
     },

+ 23 - 10
netbox/netbox/api/authentication.py

@@ -11,8 +11,8 @@ from netbox.config import get_config
 from users.models import Token
 from utilities.request import get_client_ip
 
-V1_KEYWORD = 'token'
-V2_KEYWORD = 'bearer'
+V1_KEYWORD = 'Token'
+V2_KEYWORD = 'Bearer'
 
 
 class TokenAuthentication(BaseAuthentication):
@@ -22,26 +22,37 @@ class TokenAuthentication(BaseAuthentication):
     model = Token
 
     def authenticate(self, request):
+        # Ignore; Authorization header is not present
         if not (auth := get_authorization_header(request).split()):
             return
 
-        # Check for Token/Bearer keyword in HTTP header value & infer token version
+        # Infer token version from Token/Bearer keyword in HTTP header
         if auth[0].lower() == V1_KEYWORD.lower().encode():
             version = 1
         elif auth[0].lower() == V2_KEYWORD.lower().encode():
             version = 2
         else:
+            # Ignore; unrecognized header value
             return
 
-        # Extract token key from authorization header
+        # Extract token from authorization header. This should be in one of the following two forms:
+        #  * Authorization: Token <token> (v1)
+        #  * Authorization: Bearer <key>.<token> (v2)
         if len(auth) != 2:
-            raise exceptions.AuthenticationFailed("Invalid authorization header: Error parsing token")
+            if version == 1:
+                raise exceptions.AuthenticationFailed(
+                    'Invalid authorization header: Must be in the form "Token <token>"'
+                )
+            else:
+                raise exceptions.AuthenticationFailed(
+                    'Invalid authorization header: Must be in the form "Bearer <key>.<token>"'
+                )
+
+        # Extract the key (if v2) & token plaintext from the auth header
         try:
             auth_value = auth[1].decode()
         except UnicodeError:
             raise exceptions.AuthenticationFailed("Invalid authorization header: Token contains invalid characters")
-
-        # Look for a matching token in the database
         if version == 1:
             key, plaintext = None, auth_value
         else:
@@ -52,6 +63,8 @@ class TokenAuthentication(BaseAuthentication):
                     "Invalid authorization header: Could not parse key from v2 token. Did you mean to use 'Token' "
                     "instead of 'Bearer'?"
                 )
+
+        # Look for a matching token in the database
         try:
             qs = Token.objects.prefetch_related('user')
             if version == 1:
@@ -61,8 +74,8 @@ class TokenAuthentication(BaseAuthentication):
                 # Fetch v2 token by key, then validate the plaintext
                 token = qs.get(version=version, key=key)
                 if not token.validate(plaintext):
-                    # TODO: Consider security implications of enabling validation of token key without valid plaintext
-                    raise exceptions.AuthenticationFailed(f"Validation failed for v2 token {key}")
+                    # Key is valid but plaintext is not. Raise DoesNotExist to guard against key enumeration.
+                    raise Token.DoesNotExist()
         except Token.DoesNotExist:
             raise exceptions.AuthenticationFailed(f"Invalid v{version} token")
 
@@ -180,5 +193,5 @@ class TokenScheme(OpenApiAuthenticationExtension):
             'type': 'apiKey',
             'in': 'header',
             'name': 'Authorization',
-            'description': 'Set `Token <token>` (v1) or `Bearer <token>` (v2) in the Authorization header',
+            'description': '`Token <token>` (v1) or `Bearer <key>.<token>` (v2)',
         }

+ 2 - 3
netbox/users/utils.py

@@ -1,5 +1,4 @@
 from django.conf import settings
-from django.core.exceptions import ImproperlyConfigured
 from social_core.storage import NO_ASCII_REGEX, NO_SPECIAL_REGEX
 
 __all__ = (
@@ -20,7 +19,7 @@ def get_current_pepper():
     """
     Return the ID and value of the newest (highest ID) cryptographic pepper.
     """
-    if len(settings.API_TOKEN_PEPPERS) < 1:
-        raise ImproperlyConfigured("Must define API_TOKEN_PEPPERS to use v2 API tokens")
+    if not settings.API_TOKEN_PEPPERS:
+        raise ValueError("API_TOKEN_PEPPERS is not defined")
     newest_id = sorted(settings.API_TOKEN_PEPPERS.keys())[-1]
     return newest_id, settings.API_TOKEN_PEPPERS[newest_id]