Source code for dmr.security.token.auth.header
from typing import TYPE_CHECKING, Final
from django.http import HttpRequest
from django.views.decorators.debug import sensitive_variables
from dmr.metadata import EndpointMetadata
from dmr.openapi.objects import Reference, SecurityScheme
from dmr.security.token.auth.base import BaseTokenAsyncAuth, BaseTokenSyncAuth
from dmr.security.token.token import DEFAULT_TOKEN_ALGORITHM, DEFAULT_TOKEN_SALT
if TYPE_CHECKING:
from dmr.controller import Controller
from dmr.serializer import BaseSerializer
_AUTH_DESCRIPTION: Final = 'Opaque token authentication'
class _BaseHeaderTokenAuth:
__slots__ = ()
header_name: str
prefix: str
security_scheme_name: str
www_authenticate: bool
@property
def www_authenticate_challenge(self) -> str | None:
"""
Challenge naming the scheme this auth expects, like ``Bearer``.
Returns ``None`` for a custom *header_name*, because a challenge
can only ask the client for the ``Authorization`` header.
Also returns ``None`` for an empty *prefix*: without one
there is no scheme name to build a challenge from.
"""
if not self.www_authenticate or self.header_name != 'Authorization':
return None
return self.prefix.strip() or None
def security_schemes(
self,
metadata: EndpointMetadata,
controller_cls: type['Controller[BaseSerializer]'],
) -> dict[str, 'SecurityScheme | Reference']:
"""Provides a security schema definition."""
if self.header_name == 'Authorization':
return {
self.security_scheme_name: SecurityScheme(
type='http',
scheme='bearer',
description=_AUTH_DESCRIPTION,
),
}
return {
self.security_scheme_name: SecurityScheme(
type='apiKey',
name=self.header_name,
security_scheme_in='header',
description=_AUTH_DESCRIPTION,
),
}
@sensitive_variables()
def get_raw_token(self, request: HttpRequest) -> str | None:
"""Read the raw token from the request header, stripping any prefix."""
header_value = request.headers.get(self.header_name)
if header_value is None:
return None
if self.prefix:
expected = f'{self.prefix} '
if not header_value.startswith(expected):
return None
return header_value[len(expected) :]
return header_value
[docs]
class HeaderTokenSyncAuth(_BaseHeaderTokenAuth, BaseTokenSyncAuth):
"""
Sync opaque token auth; reads from ``X-API-Token`` by default.
.. versionadded:: 0.12.0
"""
__slots__ = ('header_name', 'prefix', 'www_authenticate')
def __init__( # noqa: WPS211
self,
*,
header_name: str = 'X-API-Token',
prefix: str = '',
www_authenticate: bool = True,
security_scheme_name: str = 'token',
update_last_used: bool = False,
token_secret: str | None = None,
token_salt: str = DEFAULT_TOKEN_SALT,
token_algorithm: str = DEFAULT_TOKEN_ALGORITHM,
) -> None:
"""
Apply possible customizations.
- *header_name* - which header carries the raw token (default
``X-API-Token``). Use ``'Authorization'`` for RFC 7235-style
bearer auth (see *prefix* below).
- *prefix* - scheme prefix expected before the token in the header
value (default ``''``, i.e. the header value is used verbatim).
Set to ``'Token'`` or ``'Bearer'`` when *header_name* is
``'Authorization'`` - e.g. ``prefix='Token'`` requires the client
to send ``Authorization: Token <raw-token>``.
- *www_authenticate* - whether ``401`` responses advertise this auth
in the ``WWW-Authenticate`` header. Only has an effect when
*header_name* is ``'Authorization'`` and *prefix* is set.
- *security_scheme_name* - name used in OpenAPI security scheme map.
**Common configurations:**
.. code-block:: python
>>> from dmr.security.token.auth.header import HeaderTokenSyncAuth
# Default - custom header, no prefix
>>> auth = HeaderTokenSyncAuth() # X-API-Token: <token>
# DRF-compatible
>>> auth = HeaderTokenSyncAuth(
... header_name='Authorization',
... prefix='Token',
... )
# Bearer style
>>> auth = HeaderTokenSyncAuth(
... header_name='Authorization',
... prefix='Bearer',
... )
"""
super().__init__(
security_scheme_name=security_scheme_name,
update_last_used=update_last_used,
token_secret=token_secret,
token_salt=token_salt,
token_algorithm=token_algorithm,
)
self.header_name = header_name
self.prefix = prefix
self.www_authenticate = www_authenticate
[docs]
class HeaderTokenAsyncAuth(_BaseHeaderTokenAuth, BaseTokenAsyncAuth):
"""
Async opaque token auth; reads from ``X-API-Token`` by default.
.. versionadded:: 0.12.0
"""
__slots__ = ('header_name', 'prefix', 'www_authenticate')
def __init__( # noqa: WPS211
self,
*,
header_name: str = 'X-API-Token',
prefix: str = '',
www_authenticate: bool = True,
security_scheme_name: str = 'token',
update_last_used: bool = False,
token_secret: str | None = None,
token_salt: str = DEFAULT_TOKEN_SALT,
token_algorithm: str = DEFAULT_TOKEN_ALGORITHM,
) -> None:
"""Apply possible customizations. See :class:`HeaderTokenSyncAuth`."""
super().__init__(
security_scheme_name=security_scheme_name,
update_last_used=update_last_used,
token_secret=token_secret,
token_salt=token_salt,
token_algorithm=token_algorithm,
)
self.header_name = header_name
self.prefix = prefix
self.www_authenticate = www_authenticate