This repository has been archived on 2024-05-31. You can view files and clone it, but cannot push or open issues or pull requests.
authentik/authentik/api/authentication.py
Jens L af43330fd6
providers/oauth2: rework OAuth2 Provider (#4652)
* always treat flow as openid flow

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* improve issuer URL generation

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* more refactoring

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* update introspection

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* more refinement

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* migrate more

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix more things, update api

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* regen migrations

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix a bunch of things

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* start updating tests

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix implicit flow, auto set exp

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix timeozone not used correctly

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix revoke

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* more timezone shenanigans

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix userinfo tests

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* update web

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix proxy outpost

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix api tests

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix missing at_hash for implicit flows

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix tests

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* re-include at_hash in implicit auth flow

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* use folder context for outpost build

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2023-02-09 20:19:48 +01:00

103 lines
3.6 KiB
Python

"""API Authentication"""
from typing import Any, Optional
from django.conf import settings
from rest_framework.authentication import BaseAuthentication, get_authorization_header
from rest_framework.exceptions import AuthenticationFailed
from rest_framework.request import Request
from structlog.stdlib import get_logger
from authentik.core.middleware import CTX_AUTH_VIA
from authentik.core.models import Token, TokenIntents, User
from authentik.outposts.models import Outpost
from authentik.providers.oauth2.constants import SCOPE_AUTHENTIK_API
LOGGER = get_logger()
def validate_auth(header: bytes) -> Optional[str]:
"""Validate that the header is in a correct format,
returns type and credentials"""
auth_credentials = header.decode().strip()
if auth_credentials == "" or " " not in auth_credentials:
return None
auth_type, _, auth_credentials = auth_credentials.partition(" ")
if auth_type.lower() != "bearer":
LOGGER.debug("Unsupported authentication type, denying", type=auth_type.lower())
raise AuthenticationFailed("Unsupported authentication type")
if auth_credentials == "": # nosec # noqa
raise AuthenticationFailed("Malformed header")
return auth_credentials
def bearer_auth(raw_header: bytes) -> Optional[User]:
"""raw_header in the Format of `Bearer ....`"""
user = auth_user_lookup(raw_header)
if not user:
return None
if not user.is_active:
raise AuthenticationFailed("Token invalid/expired")
return user
def auth_user_lookup(raw_header: bytes) -> Optional[User]:
"""raw_header in the Format of `Bearer ....`"""
from authentik.providers.oauth2.models import AccessToken
auth_credentials = validate_auth(raw_header)
if not auth_credentials:
return None
# first, check traditional tokens
key_token = Token.filter_not_expired(
key=auth_credentials, intent=TokenIntents.INTENT_API
).first()
if key_token:
CTX_AUTH_VIA.set("api_token")
return key_token.user
# then try to auth via JWT
jwt_token = AccessToken.filter_not_expired(
token=auth_credentials, _scope__icontains=SCOPE_AUTHENTIK_API
).first()
if jwt_token:
# Double-check scopes, since they are saved in a single string
# we want to check the parsed version too
if SCOPE_AUTHENTIK_API not in jwt_token.scope:
raise AuthenticationFailed("Token invalid/expired")
CTX_AUTH_VIA.set("jwt")
return jwt_token.user
# then try to auth via secret key (for embedded outpost/etc)
user = token_secret_key(auth_credentials)
if user:
CTX_AUTH_VIA.set("secret_key")
return user
raise AuthenticationFailed("Token invalid/expired")
def token_secret_key(value: str) -> Optional[User]:
"""Check if the token is the secret key
and return the service account for the managed outpost"""
from authentik.outposts.apps import MANAGED_OUTPOST
if value != settings.SECRET_KEY:
return None
outposts = Outpost.objects.filter(managed=MANAGED_OUTPOST)
if not outposts:
return None
outpost = outposts.first()
return outpost.user
class TokenAuthentication(BaseAuthentication):
"""Token-based authentication using HTTP Bearer authentication"""
def authenticate(self, request: Request) -> tuple[User, Any] | None:
"""Token-based authentication using HTTP Bearer authentication"""
auth = get_authorization_header(request)
user = bearer_auth(auth)
# None is only returned when the header isn't set.
if not user:
return None
return (user, None) # pragma: no cover