Source code for simple_openid_connect.integrations.django.decorators

"""
View-function decorators
"""

import logging
from functools import wraps
from http import HTTPStatus
from typing import Any, Callable, Optional, TypeVar, Union

from django.http import HttpRequest, HttpResponse, JsonResponse

from simple_openid_connect.exceptions import ValidationError
from simple_openid_connect.integrations.django.apps import OpenidAppConfig
from simple_openid_connect.utils import is_application_json

logger = logging.getLogger(__name__)

View_Return = TypeVar("View_Return", bound=HttpResponse)


def _invalid_token_response(request: HttpRequest) -> HttpResponse:
    if "Accept" in request.headers.keys() and is_application_json(
        request.headers["Accept"]
    ):
        return JsonResponse(
            status=HTTPStatus.UNAUTHORIZED,
            headers={"WWW-Authenticate": "Bearer"},
            data={
                "error": "invalid_token",
                "error_description": "the used access token is not valid or does not grant enough access",
            },
        )
    else:
        return HttpResponse(
            status=HTTPStatus.UNAUTHORIZED,
            content="the used access token is not valid or does not grant enough access",
            headers={"WWW-Authenticate": "Bearer"},
        )


[docs] def access_token_required( *, required_scopes: Optional[str] = None ) -> Callable[..., Union[HttpResponse, View_Return]]: """ Decorator for views that checks that the request is authenticated using a valid access token, early-returning an appropriate http error response if necessary. :param required_scopes: Scopes to which the access token needs to have access. If not given, use the `settings.OPENID_SCOPE` value which defaults to "openid". :raises UnsupportedByProviderError: If the provider does not support token introspection. """ if required_scopes is None: _required_scopes = OpenidAppConfig.get_instance().safe_settings.OPENID_SCOPE else: _required_scopes = required_scopes def actual_decorator( view_func: Callable[..., View_Return], ) -> Callable[..., View_Return]: @wraps(view_func) def wrapped_view( request: HttpRequest, *args: Any, **kwargs: Any ) -> Union[HttpResponse, View_Return]: # verify that an Authorization Header of type Bearer is present if "Authorization" not in request.headers.keys() or not request.headers[ "Authorization" ].startswith("Bearer "): return HttpResponse( status=HTTPStatus.UNAUTHORIZED, headers={"WWW-Authenticate": "Bearer"}, ) oidc_client = OpenidAppConfig.get_instance().get_client(request) raw_token = request.headers["Authorization"].split(" ", 1)[1] try: ( request.user, _, ) = OpenidAppConfig.get_instance().user_mapper.handle_federated_access_token( raw_token, oidc_client, _required_scopes ) except ValidationError: return _invalid_token_response(request) # execute the decorated view function return view_func(request, *args, **kwargs) return wrapped_view # type: ignore return actual_decorator # type: ignore