mirror of
https://github.com/netbox-community/netbox.git
synced 2025-12-12 19:39:35 -06:00
Clean up auth backend
This commit is contained in:
parent
917a2c2618
commit
9b85d92ad0
@ -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)"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@ -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)',
|
||||
}
|
||||
|
||||
@ -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]
|
||||
|
||||
Loading…
Reference in New Issue
Block a user