From 3759e96e7d09daf895e0a13b35c03acb8760e7cd Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Sat, 26 Dec 2020 18:44:24 +0100 Subject: [PATCH] providers/oauth2: ensure interaction_required is raised when prompt=none and user not logged in --- authentik/policies/views.py | 4 ++ authentik/providers/oauth2/views/authorize.py | 41 ++++++++++++++----- 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/authentik/policies/views.py b/authentik/policies/views.py index 432bd916f..b6d8cd669 100644 --- a/authentik/policies/views.py +++ b/authentik/policies/views.py @@ -24,6 +24,10 @@ class RequestValidationError(SentryIgnoredException): response: Optional[HttpResponse] + def __init__(self, response: Optional[HttpResponse] = None): + super().__init__() + if response: + self.response = response class BaseMixin: """Base Mixin class, used to annotate View Member variables""" diff --git a/authentik/providers/oauth2/views/authorize.py b/authentik/providers/oauth2/views/authorize.py index cf7904b4c..ab1e91e7e 100644 --- a/authentik/providers/oauth2/views/authorize.py +++ b/authentik/providers/oauth2/views/authorize.py @@ -24,7 +24,7 @@ from authentik.flows.views import SESSION_KEY_PLAN from authentik.lib.utils.time import timedelta_from_string from authentik.lib.utils.urls import redirect_with_qs from authentik.lib.views import bad_request_message -from authentik.policies.views import PolicyAccessView +from authentik.policies.views import PolicyAccessView, RequestValidationError from authentik.providers.oauth2.constants import ( PROMPT_CONSNET, PROMPT_NONE, @@ -332,6 +332,32 @@ class OAuthFulfillmentStage(StageView): class AuthorizationFlowInitView(PolicyAccessView): """OAuth2 Flow initializer, checks access to application and starts flow""" + params: OAuthAuthorizationParams + + def pre_permission_check(self): + """Check prompt parameter before checking permission/authentication, + see https://openid.net/specs/openid-connect-core-1_0.html#rfc.section.3.1.2.6""" + try: + self.params = OAuthAuthorizationParams.from_request(self.request) + except OAuth2Error as error: + raise RequestValidationError( + bad_request_message( + self.request, error.description, title=error.error + ) + ) + except OAuth2Provider.DoesNotExist: + raise Http404 + if self.params.prompt == PROMPT_NONE and not self.request.user.is_authenticated: + # When "prompt" is set to "none" but the user is not logged in, show an error message + error = AuthorizeError( + self.params.redirect_uri, "interaction_required", self.params.grant_type + ) + raise RequestValidationError( + bad_request_message( + self.request, error.description, title=error.error + ) + ) + def resolve_provider_application(self): client_id = self.request.GET.get("client_id") self.provider = get_object_or_404(OAuth2Provider, client_id=client_id) @@ -340,13 +366,6 @@ class AuthorizationFlowInitView(PolicyAccessView): # pylint: disable=unused-argument def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: """Check access to application, start FlowPLanner, return to flow executor shell""" - # Extract params so we can save them in the plan context - try: - params = OAuthAuthorizationParams.from_request(request) - except OAuth2Error as error: - return bad_request_message(request, error.description, title=error.error) - except OAuth2Provider.DoesNotExist: - raise Http404 # Regardless, we start the planner and return to it planner = FlowPlanner(self.provider.authorization_flow) # planner.use_cache = False @@ -357,9 +376,9 @@ class AuthorizationFlowInitView(PolicyAccessView): PLAN_CONTEXT_SSO: True, PLAN_CONTEXT_APPLICATION: self.application, # OAuth2 related params - PLAN_CONTEXT_PARAMS: params, + PLAN_CONTEXT_PARAMS: self.params, PLAN_CONTEXT_SCOPE_DESCRIPTIONS: UserInfoView().get_scope_descriptions( - params.scope + self.params.scope ), # Consent related params PLAN_CONTEXT_CONSENT_TEMPLATE: "providers/oauth2/consent.html", @@ -367,7 +386,7 @@ class AuthorizationFlowInitView(PolicyAccessView): ) # OpenID clients can specify a `prompt` parameter, and if its set to consent we # need to inject a consent stage - if PROMPT_CONSNET in params.prompt: + if PROMPT_CONSNET in self.params.prompt: if not any([isinstance(x, ConsentStageView) for x in plan.stages]): # Plan does not have any consent stage, so we add an in-memory one stage = ConsentStage(