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 AuthorizeError as error: error.to_event(redirect_uri=error.redirect_uri).from_http(self.request) raise RequestValidationError(HttpResponseRedirect(error.create_uri())) except OAuth2Error as error: error.to_event().from_http(self.request) raise RequestValidationError( bad_request_message(self.request, error.description, title=error.error) ) except OAuth2Provider.DoesNotExist: raise Http404 if PROMPT_NONE in self.params.prompt 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, "login_required", self.params.grant_type, self.params.state, ) error.to_event(redirect_uri=error.redirect_uri).from_http(self.request) raise RequestValidationError(HttpResponseRedirect(error.create_uri()))
def check_code_challenge(self): """PKCE validation of the transformation method.""" if self.code_challenge: if not (self.code_challenge_method in ["plain", "S256"]): raise AuthorizeError( self.redirect_uri, "invalid_request", self.grant_type, self.state )
def check_redirect_uri(self): """Redirect URI validation.""" allowed_redirect_urls = self.provider.redirect_uris.split() if not self.redirect_uri: LOGGER.warning("Missing redirect uri.") raise RedirectUriError("", allowed_redirect_urls) if self.provider.redirect_uris == "": LOGGER.info("Setting redirect for blank redirect_uris", redirect=self.redirect_uri) self.provider.redirect_uris = self.redirect_uri self.provider.save() allowed_redirect_urls = self.provider.redirect_uris.split() if self.provider.redirect_uris == "*": LOGGER.warning( "Provider has wildcard allowed redirect_uri set, allowing all.", allow=self.redirect_uri, ) return if self.redirect_uri not in [x.lower() for x in allowed_redirect_urls]: LOGGER.warning( "Invalid redirect uri", redirect_uri=self.redirect_uri, excepted=allowed_redirect_urls, ) raise RedirectUriError(self.redirect_uri, allowed_redirect_urls) if self.request: raise AuthorizeError(self.redirect_uri, "request_not_supported", self.grant_type, self.state)
def check_scope(self): """Ensure openid scope is set in Hybrid flows, or when requesting an id_token""" if SCOPE_OPENID not in self.scope and ( self.grant_type == GrantTypes.HYBRID or self.response_type in [ResponseTypes.ID_TOKEN, ResponseTypes.ID_TOKEN_TOKEN]): LOGGER.warning("Missing 'openid' scope.") raise AuthorizeError(self.redirect_uri, "invalid_scope", self.grant_type, self.state)
def check_nonce(self): """Nonce parameter validation.""" if not self.nonce: self.nonce = self.state LOGGER.warning("Using state as nonce for OpenID Request") if not self.nonce: if SCOPE_OPENID in self.scope: LOGGER.warning("Missing nonce for OpenID Request") raise AuthorizeError(self.redirect_uri, "invalid_request", self.grant_type, self.state)
def from_request(request: HttpRequest) -> "OAuthAuthorizationParams": """ Get all the params used by the Authorization Code Flow (and also for the Implicit and Hybrid). See: http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest """ # Because in this endpoint we handle both GET # and POST request. query_dict = request.POST if request.method == "POST" else request.GET state = query_dict.get("state") redirect_uri = query_dict.get("redirect_uri", "") response_type = query_dict.get("response_type", "") grant_type = None # Determine which flow to use. if response_type in [ResponseTypes.CODE]: grant_type = GrantTypes.AUTHORIZATION_CODE elif response_type in [ ResponseTypes.ID_TOKEN, ResponseTypes.ID_TOKEN_TOKEN, ]: grant_type = GrantTypes.IMPLICIT elif response_type in [ ResponseTypes.CODE_TOKEN, ResponseTypes.CODE_ID_TOKEN, ResponseTypes.CODE_ID_TOKEN_TOKEN, ]: grant_type = GrantTypes.HYBRID # Grant type validation. if not grant_type: LOGGER.warning("Invalid response type", type=response_type) raise AuthorizeError(redirect_uri, "unsupported_response_type", "", state) max_age = query_dict.get("max_age") return OAuthAuthorizationParams( client_id=query_dict.get("client_id", ""), redirect_uri=redirect_uri, response_type=response_type, grant_type=grant_type, scope=query_dict.get("scope", "").split(), state=state, nonce=query_dict.get("nonce"), prompt=ALLOWED_PROMPT_PARAMS.intersection( set(query_dict.get("prompt", "").split()) ), request=query_dict.get("request", None), max_age=int(max_age) if max_age else None, code_challenge=query_dict.get("code_challenge"), code_challenge_method=query_dict.get("code_challenge_method"), )
def check_nonce(self): """Nonce parameter validation.""" # https://openid.net/specs/openid-connect-core-1_0.html#ImplicitIDTValidation # Nonce is only required for Implicit flows if self.grant_type != GrantTypes.IMPLICIT: return if not self.nonce: self.nonce = self.state LOGGER.warning("Using state as nonce for OpenID Request") if not self.nonce: if SCOPE_OPENID in self.scope: LOGGER.warning("Missing nonce for OpenID Request") raise AuthorizeError(self.redirect_uri, "invalid_request", self.grant_type, self.state)
def check_redirect_uri(self): """Redirect URI validation.""" if not self.redirect_uri: LOGGER.warning("Missing redirect uri.") raise RedirectUriError("", self.provider.redirect_uris.split()) if self.redirect_uri.lower() not in [ x.lower() for x in self.provider.redirect_uris.split() ]: LOGGER.warning( "Invalid redirect uri", redirect_uri=self.redirect_uri, excepted=self.provider.redirect_uris.split(), ) raise RedirectUriError(self.redirect_uri, self.provider.redirect_uris.split()) if self.request: raise AuthorizeError(self.redirect_uri, "request_not_supported", self.grant_type, self.state)
def create_response_uri(self) -> str: """Create a final Response URI the user is redirected to.""" uri = urlsplit(self.params.redirect_uri) query_params = parse_qs(uri.query) try: code = None if self.params.grant_type in [ GrantTypes.AUTHORIZATION_CODE, GrantTypes.HYBRID, ]: code = self.params.create_code(self.request) code.save(force_insert=True) if self.params.grant_type == GrantTypes.AUTHORIZATION_CODE: query_params["code"] = code.code query_params["state"] = [ str(self.params.state) if self.params.state else "" ] uri = uri._replace(query=urlencode(query_params, doseq=True)) return urlunsplit(uri) if self.params.grant_type in [ GrantTypes.IMPLICIT, GrantTypes.HYBRID ]: query_fragment = self.create_implicit_response(code) uri = uri._replace(fragment=uri.fragment + urlencode(query_fragment, doseq=True), ) return urlunsplit(uri) raise OAuth2Error() except OAuth2Error as error: LOGGER.warning("Error when trying to create response uri", error=error) raise AuthorizeError( self.params.redirect_uri, "server_error", self.params.grant_type, self.params.state, )
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: """final Stage of an OAuth2 Flow""" if PLAN_CONTEXT_PARAMS not in self.executor.plan.context: LOGGER.warning("Got to fulfillment stage with no pending context") return HttpResponseBadRequest() self.params: OAuthAuthorizationParams = self.executor.plan.context.pop( PLAN_CONTEXT_PARAMS) application: Application = self.executor.plan.context.pop( PLAN_CONTEXT_APPLICATION) self.provider = get_object_or_404(OAuth2Provider, pk=application.provider_id) try: # At this point we don't need to check permissions anymore if {PROMPT_NONE, PROMPT_CONSNET}.issubset(self.params.prompt): raise AuthorizeError( self.params.redirect_uri, "consent_required", self.params.grant_type, self.params.state, ) Event.new( EventAction.AUTHORIZE_APPLICATION, authorized_application=application, flow=self.executor.plan.flow_pk, scopes=", ".join(self.params.scope), ).from_http(self.request) return self.redirect(self.create_response_uri()) except (ClientIdError, RedirectUriError) as error: error.to_event(application=application).from_http(request) self.executor.stage_invalid() # pylint: disable=no-member return bad_request_message(request, error.description, title=error.error) except AuthorizeError as error: error.to_event(application=application).from_http(request) self.executor.stage_invalid() return self.redirect(error.create_uri())