* initial implementation Signed-off-by: Jens Langhammer <jens@goauthentik.io> * check for openid/profile claims Signed-off-by: Jens Langhammer <jens@goauthentik.io> * include jwks sources in proxy provider Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add web ui for jwks Signed-off-by: Jens Langhammer <jens@goauthentik.io> * only show sources with JWKS data configured Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix introspection tests Signed-off-by: Jens Langhammer <jens@goauthentik.io> * start basic Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add basic auth Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add docs, update admonitions Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add client_id to api, add tab for auth Signed-off-by: Jens Langhammer <jens@goauthentik.io> * update locale Signed-off-by: Jens Langhammer <jens@goauthentik.io> Signed-off-by: Jens Langhammer <jens@goauthentik.io>
85 lines
2.9 KiB
Python
85 lines
2.9 KiB
Python
"""authentik OAuth2 Token Introspection Views"""
|
|
from dataclasses import dataclass, field
|
|
|
|
from django.http import HttpRequest, HttpResponse
|
|
from django.utils.decorators import method_decorator
|
|
from django.views import View
|
|
from django.views.decorators.csrf import csrf_exempt
|
|
from structlog.stdlib import get_logger
|
|
|
|
from authentik.providers.oauth2.errors import TokenIntrospectionError
|
|
from authentik.providers.oauth2.models import IDToken, OAuth2Provider, RefreshToken
|
|
from authentik.providers.oauth2.utils import TokenResponse, authenticate_provider
|
|
|
|
LOGGER = get_logger()
|
|
|
|
|
|
@dataclass
|
|
class TokenIntrospectionParams:
|
|
"""Parameters for Token Introspection"""
|
|
|
|
token: RefreshToken
|
|
provider: OAuth2Provider
|
|
|
|
id_token: IDToken = field(init=False)
|
|
|
|
def __post_init__(self):
|
|
if self.token.is_expired:
|
|
LOGGER.debug("Token is not valid")
|
|
raise TokenIntrospectionError()
|
|
|
|
self.id_token = self.token.id_token
|
|
|
|
if not self.token.id_token:
|
|
LOGGER.debug(
|
|
"token not an authentication token",
|
|
token=self.token,
|
|
)
|
|
raise TokenIntrospectionError()
|
|
|
|
@staticmethod
|
|
def from_request(request: HttpRequest) -> "TokenIntrospectionParams":
|
|
"""Extract required Parameters from HTTP Request"""
|
|
raw_token = request.POST.get("token")
|
|
token_type_hint = request.POST.get("token_type_hint", "access_token")
|
|
token_filter = {token_type_hint: raw_token}
|
|
|
|
if token_type_hint not in ["access_token", "refresh_token"]:
|
|
LOGGER.debug("token_type_hint has invalid value", value=token_type_hint)
|
|
raise TokenIntrospectionError()
|
|
|
|
provider = authenticate_provider(request)
|
|
if not provider:
|
|
raise TokenIntrospectionError
|
|
|
|
token: RefreshToken = RefreshToken.objects.filter(provider=provider, **token_filter).first()
|
|
if not token:
|
|
LOGGER.debug("Token does not exist", token=raw_token)
|
|
raise TokenIntrospectionError()
|
|
|
|
return TokenIntrospectionParams(token=token, provider=provider)
|
|
|
|
|
|
@method_decorator(csrf_exempt, name="dispatch")
|
|
class TokenIntrospectionView(View):
|
|
"""Token Introspection
|
|
https://tools.ietf.org/html/rfc7662"""
|
|
|
|
token: RefreshToken
|
|
params: TokenIntrospectionParams
|
|
provider: OAuth2Provider
|
|
|
|
def post(self, request: HttpRequest) -> HttpResponse:
|
|
"""Introspection handler"""
|
|
try:
|
|
self.params = TokenIntrospectionParams.from_request(request)
|
|
response = {}
|
|
if self.params.id_token:
|
|
response.update(self.params.id_token.to_dict())
|
|
response["active"] = True
|
|
response["scope"] = " ".join(self.params.token.scope)
|
|
response["client_id"] = self.params.token.provider.client_id
|
|
return TokenResponse(response)
|
|
except TokenIntrospectionError:
|
|
return TokenResponse({"active": False})
|