Source code for dmr.security.token.auth.header
from typing import Final
from django.http import HttpRequest
from typing_extensions import override
from dmr.openapi.objects import Reference, SecurityScheme
from dmr.security.token.auth.base import (
_BaseTokenAsyncAuth, # noqa: WPS450 # pyright: ignore[reportPrivateUsage]
_BaseTokenSyncAuth, # noqa: WPS450 # pyright: ignore[reportPrivateUsage]
)
_AUTH_DESCRIPTION: Final = 'Opaque token authentication'
class _BaseHeaderTokenAuth:
__slots__ = ()
header_name: str
prefix: str
security_scheme_name: str
@property
def security_schemes(self) -> 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,
),
}
def _raw_token_from_header(
self,
request: HttpRequest,
*,
header_name: str,
prefix: str,
) -> str | None:
"""Read token from header and strip expected prefix when configured."""
header_value = request.headers.get(header_name)
if header_value is None:
return None
if prefix:
expected = f'{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."""
__slots__ = ('header_name', 'prefix')
def __init__(
self,
*,
header_name: str = 'X-API-Token',
prefix: str = '',
security_scheme_name: str = 'token',
update_last_used: bool = True,
) -> 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>``.
- *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,
)
self.header_name = header_name
self.prefix = prefix
[docs]
@override
def get_raw_token(self, request: HttpRequest) -> str | None:
"""Read the raw token from the request header, stripping any prefix."""
return self._raw_token_from_header(
request,
header_name=self.header_name,
prefix=self.prefix,
)
[docs]
class HeaderTokenAsyncAuth(_BaseHeaderTokenAuth, _BaseTokenAsyncAuth):
"""Async opaque token auth; reads from ``X-API-Token`` by default."""
__slots__ = ('header_name', 'prefix')
def __init__(
self,
*,
header_name: str = 'X-API-Token',
prefix: str = '',
security_scheme_name: str = 'token',
update_last_used: bool = True,
) -> None:
"""Apply possible customizations. See :class:`HeaderTokenSyncAuth`."""
super().__init__(
security_scheme_name=security_scheme_name,
update_last_used=update_last_used,
)
self.header_name = header_name
self.prefix = prefix
[docs]
@override
def get_raw_token(self, request: HttpRequest) -> str | None:
"""Read the raw token from the request header, stripping any prefix."""
return self._raw_token_from_header(
request,
header_name=self.header_name,
prefix=self.prefix,
)