* core: initial migration to /if Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * core: move jsi18n to api Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * tests: fix static URLs in tests Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * web: add new html files to rollup Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * web: fix rollup config and nginx config Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * core: add Impersonation support to user API Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * web: add banner for impersonation Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * tests: fix test_user function for new User API Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * flows: add background to API Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * web: set background from flow API Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * core: make root view login_required for redirect Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * flows: redirect to root-redirect instead of if-admin direct Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * api: add header to prevent Authorization Basic prompt in browser Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * web: redirect to root when user/me request fails Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
62 lines
2.1 KiB
Python
62 lines
2.1 KiB
Python
"""API Authentication"""
|
|
from base64 import b64decode
|
|
from binascii import Error
|
|
from typing import Any, Optional, Union
|
|
|
|
from rest_framework.authentication import BaseAuthentication, get_authorization_header
|
|
from rest_framework.request import Request
|
|
from structlog.stdlib import get_logger
|
|
|
|
from authentik.core.models import Token, TokenIntents, User
|
|
|
|
LOGGER = get_logger()
|
|
X_AUTHENTIK_PREVENT_BASIC_HEADER = "HTTP_X_AUTHENTIK_PREVENT_BASIC"
|
|
|
|
|
|
def token_from_header(raw_header: bytes) -> Optional[Token]:
|
|
"""raw_header in the Format of `Basic dGVzdDp0ZXN0`"""
|
|
auth_credentials = raw_header.decode()
|
|
# Accept headers with Type format and without
|
|
if " " in auth_credentials:
|
|
auth_type, auth_credentials = auth_credentials.split()
|
|
if auth_type.lower() != "basic":
|
|
LOGGER.debug(
|
|
"Unsupported authentication type, denying", type=auth_type.lower()
|
|
)
|
|
return None
|
|
try:
|
|
auth_credentials = b64decode(auth_credentials.encode()).decode()
|
|
except (UnicodeDecodeError, Error):
|
|
return None
|
|
# Accept credentials with username and without
|
|
if ":" in auth_credentials:
|
|
_, password = auth_credentials.split(":")
|
|
else:
|
|
password = auth_credentials
|
|
if password == "": # nosec
|
|
return None
|
|
tokens = Token.filter_not_expired(key=password, intent=TokenIntents.INTENT_API)
|
|
if not tokens.exists():
|
|
LOGGER.debug("Token not found")
|
|
return None
|
|
return tokens.first()
|
|
|
|
|
|
class AuthentikTokenAuthentication(BaseAuthentication):
|
|
"""Token-based authentication using HTTP Basic authentication"""
|
|
|
|
def authenticate(self, request: Request) -> Union[tuple[User, Any], None]:
|
|
"""Token-based authentication using HTTP Basic authentication"""
|
|
auth = get_authorization_header(request)
|
|
|
|
token = token_from_header(auth)
|
|
if not token:
|
|
return None
|
|
|
|
return (token.user, None)
|
|
|
|
def authenticate_header(self, request: Request) -> str:
|
|
if X_AUTHENTIK_PREVENT_BASIC_HEADER in request._request.META:
|
|
return ""
|
|
return 'Basic realm="authentik"'
|