diff --git a/AUTHORS b/AUTHORS index 689ab48de..8596063b9 100644 --- a/AUTHORS +++ b/AUTHORS @@ -23,6 +23,7 @@ Allisson Azevedo Andrea Greco Andrej Zbín Andrew Chen Wang +Andrew Zickler Antoine Laurent Anvesh Agarwal Aristóbulo Meneses diff --git a/CHANGELOG.md b/CHANGELOG.md index 28125afa3..12a4e02fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,8 +26,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * #1311 Add option to disable client_secret hashing to allow verifying JWTs' signatures. * #1337 Gracefully handle expired or deleted refresh tokens, in `validate_user`. * #1350 Support Python 3.12 and Django 5.0 -* #1249 Add code_challenge_methods_supported property to auto discovery informations - per [RFC 8414 section 2](https://www.rfc-editor.org/rfc/rfc8414.html#page-7) +* #1249 Add code_challenge_methods_supported property to auto discovery informations, per [RFC 8414 section 2](https://www.rfc-editor.org/rfc/rfc8414.html#page-7) +* #1268 Support prompt=none in authentication request for Silent Authentication ### Fixed * #1322 Instructions in documentation on how to create a code challenge and code verifier diff --git a/oauth2_provider/views/base.py b/oauth2_provider/views/base.py index abaa81f59..846be3e73 100644 --- a/oauth2_provider/views/base.py +++ b/oauth2_provider/views/base.py @@ -244,6 +244,33 @@ def handle_prompt_login(self): self.get_redirect_field_name(), ) + def handle_no_permission(self): + """ + Generate response for unauthorized users. + + If prompt is set to none, then we redirect with an error code + as defined by OIDC 3.1.2.6 + + Some code copied from OAuthLibMixin.error_response, but that is designed + to operated on OAuth1Error from oauthlib wrapped in a OAuthToolkitError + """ + prompt = self.request.GET.get("prompt") + redirect_uri = self.request.GET.get("redirect_uri") + if prompt == "none" and redirect_uri: + response_parameters = {"error": "login_required"} + + # REQUIRED if the Authorization Request included the state parameter. + # Set to the value received from the Client + state = self.request.GET.get("state") + if state: + response_parameters["state"] = state + + separator = "&" if "?" in redirect_uri else "?" + redirect_to = redirect_uri + separator + urlencode(response_parameters) + return self.redirect(redirect_to, application=None) + else: + return super().handle_no_permission() + @method_decorator(csrf_exempt, name="dispatch") class TokenView(OAuthLibMixin, View): diff --git a/tests/test_authorization_code.py b/tests/test_authorization_code.py index 9d71016d3..4aed613e8 100644 --- a/tests/test_authorization_code.py +++ b/tests/test_authorization_code.py @@ -645,6 +645,33 @@ def test_prompt_login(self): self.assertNotIn("prompt=login", next) + def test_prompt_none_unauthorized(self): + """ + Test response for redirect when supplied with prompt: none + + Should redirect to redirect_uri with an error of login_required + """ + self.oauth2_settings.PKCE_REQUIRED = False + + query_data = { + "client_id": self.application.client_id, + "response_type": "code", + "state": "random_state_string", + "scope": "read write", + "redirect_uri": "http://example.org", + "prompt": "none", + } + + response = self.client.get(reverse("oauth2_provider:authorize"), data=query_data) + + self.assertEqual(response.status_code, 302) + + scheme, netloc, path, params, query, fragment = urlparse(response["Location"]) + parsed_query = parse_qs(query) + + self.assertIn("login_required", parsed_query["error"]) + self.assertIn("random_state_string", parsed_query["state"]) + class BaseAuthorizationCodeTokenView(BaseTest): def get_auth(self, scope="read write"):