mirror of
https://github.com/netbox-community/netbox.git
synced 2025-12-15 21:09:36 -06:00
Clean up auth backend
This commit is contained in:
parent
917a2c2618
commit
9b85d92ad0
@ -166003,86 +166003,25 @@
|
|||||||
"in": "query",
|
"in": "query",
|
||||||
"name": "last_used",
|
"name": "last_used",
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "array",
|
"type": "string",
|
||||||
"items": {
|
"format": "date-time"
|
||||||
"type": "string",
|
|
||||||
"format": "date-time"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"explode": true,
|
|
||||||
"style": "form"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"in": "query",
|
|
||||||
"name": "last_used__empty",
|
|
||||||
"schema": {
|
|
||||||
"type": "boolean"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"in": "query",
|
|
||||||
"name": "last_used__gt",
|
|
||||||
"schema": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "string",
|
|
||||||
"format": "date-time"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"explode": true,
|
|
||||||
"style": "form"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"in": "query",
|
"in": "query",
|
||||||
"name": "last_used__gte",
|
"name": "last_used__gte",
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "array",
|
"type": "string",
|
||||||
"items": {
|
"format": "date-time"
|
||||||
"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"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"in": "query",
|
"in": "query",
|
||||||
"name": "last_used__lte",
|
"name": "last_used__lte",
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "array",
|
"type": "string",
|
||||||
"items": {
|
"format": "date-time"
|
||||||
"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"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "limit",
|
"name": "limit",
|
||||||
@ -256896,7 +256835,7 @@
|
|||||||
"type": "apiKey",
|
"type": "apiKey",
|
||||||
"in": "header",
|
"in": "header",
|
||||||
"name": "Authorization",
|
"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 users.models import Token
|
||||||
from utilities.request import get_client_ip
|
from utilities.request import get_client_ip
|
||||||
|
|
||||||
V1_KEYWORD = 'token'
|
V1_KEYWORD = 'Token'
|
||||||
V2_KEYWORD = 'bearer'
|
V2_KEYWORD = 'Bearer'
|
||||||
|
|
||||||
|
|
||||||
class TokenAuthentication(BaseAuthentication):
|
class TokenAuthentication(BaseAuthentication):
|
||||||
@ -22,26 +22,37 @@ class TokenAuthentication(BaseAuthentication):
|
|||||||
model = Token
|
model = Token
|
||||||
|
|
||||||
def authenticate(self, request):
|
def authenticate(self, request):
|
||||||
|
# Ignore; Authorization header is not present
|
||||||
if not (auth := get_authorization_header(request).split()):
|
if not (auth := get_authorization_header(request).split()):
|
||||||
return
|
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():
|
if auth[0].lower() == V1_KEYWORD.lower().encode():
|
||||||
version = 1
|
version = 1
|
||||||
elif auth[0].lower() == V2_KEYWORD.lower().encode():
|
elif auth[0].lower() == V2_KEYWORD.lower().encode():
|
||||||
version = 2
|
version = 2
|
||||||
else:
|
else:
|
||||||
|
# Ignore; unrecognized header value
|
||||||
return
|
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:
|
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:
|
try:
|
||||||
auth_value = auth[1].decode()
|
auth_value = auth[1].decode()
|
||||||
except UnicodeError:
|
except UnicodeError:
|
||||||
raise exceptions.AuthenticationFailed("Invalid authorization header: Token contains invalid characters")
|
raise exceptions.AuthenticationFailed("Invalid authorization header: Token contains invalid characters")
|
||||||
|
|
||||||
# Look for a matching token in the database
|
|
||||||
if version == 1:
|
if version == 1:
|
||||||
key, plaintext = None, auth_value
|
key, plaintext = None, auth_value
|
||||||
else:
|
else:
|
||||||
@ -52,6 +63,8 @@ class TokenAuthentication(BaseAuthentication):
|
|||||||
"Invalid authorization header: Could not parse key from v2 token. Did you mean to use 'Token' "
|
"Invalid authorization header: Could not parse key from v2 token. Did you mean to use 'Token' "
|
||||||
"instead of 'Bearer'?"
|
"instead of 'Bearer'?"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Look for a matching token in the database
|
||||||
try:
|
try:
|
||||||
qs = Token.objects.prefetch_related('user')
|
qs = Token.objects.prefetch_related('user')
|
||||||
if version == 1:
|
if version == 1:
|
||||||
@ -61,8 +74,8 @@ class TokenAuthentication(BaseAuthentication):
|
|||||||
# Fetch v2 token by key, then validate the plaintext
|
# Fetch v2 token by key, then validate the plaintext
|
||||||
token = qs.get(version=version, key=key)
|
token = qs.get(version=version, key=key)
|
||||||
if not token.validate(plaintext):
|
if not token.validate(plaintext):
|
||||||
# TODO: Consider security implications of enabling validation of token key without valid plaintext
|
# Key is valid but plaintext is not. Raise DoesNotExist to guard against key enumeration.
|
||||||
raise exceptions.AuthenticationFailed(f"Validation failed for v2 token {key}")
|
raise Token.DoesNotExist()
|
||||||
except Token.DoesNotExist:
|
except Token.DoesNotExist:
|
||||||
raise exceptions.AuthenticationFailed(f"Invalid v{version} token")
|
raise exceptions.AuthenticationFailed(f"Invalid v{version} token")
|
||||||
|
|
||||||
@ -180,5 +193,5 @@ class TokenScheme(OpenApiAuthenticationExtension):
|
|||||||
'type': 'apiKey',
|
'type': 'apiKey',
|
||||||
'in': 'header',
|
'in': 'header',
|
||||||
'name': 'Authorization',
|
'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.conf import settings
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
|
||||||
from social_core.storage import NO_ASCII_REGEX, NO_SPECIAL_REGEX
|
from social_core.storage import NO_ASCII_REGEX, NO_SPECIAL_REGEX
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
@ -20,7 +19,7 @@ def get_current_pepper():
|
|||||||
"""
|
"""
|
||||||
Return the ID and value of the newest (highest ID) cryptographic pepper.
|
Return the ID and value of the newest (highest ID) cryptographic pepper.
|
||||||
"""
|
"""
|
||||||
if len(settings.API_TOKEN_PEPPERS) < 1:
|
if not settings.API_TOKEN_PEPPERS:
|
||||||
raise ImproperlyConfigured("Must define API_TOKEN_PEPPERS to use v2 API tokens")
|
raise ValueError("API_TOKEN_PEPPERS is not defined")
|
||||||
newest_id = sorted(settings.API_TOKEN_PEPPERS.keys())[-1]
|
newest_id = sorted(settings.API_TOKEN_PEPPERS.keys())[-1]
|
||||||
return newest_id, settings.API_TOKEN_PEPPERS[newest_id]
|
return newest_id, settings.API_TOKEN_PEPPERS[newest_id]
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user