diff --git a/CHANGELOG.md b/CHANGELOG.md index 9194c8781..e2ccef370 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Remove support for Django 3.0 * Add support for Django 3.2 * #989 Change any HttpResponse to JsonResponse if possible +* #981 redirect_uri is now required in authorization requests when multiple URIs are registered. ### Added * #712, #636, #808. Calls to `django.contrib.auth.authenticate()` now pass a `request` diff --git a/oauth2_provider/models.py b/oauth2_provider/models.py index 3f173346f..d7b767a78 100644 --- a/oauth2_provider/models.py +++ b/oauth2_provider/models.py @@ -12,6 +12,7 @@ from django.utils.translation import gettext_lazy as _ from jwcrypto import jwk from jwcrypto.common import base64url_encode +from oauthlib.oauth2.rfc6749 import errors from .generators import generate_client_id, generate_client_secret from .scopes import get_scopes_backend @@ -107,11 +108,13 @@ def __str__(self): @property def default_redirect_uri(self): """ - Returns the default redirect_uri extracting the first item from - the :attr:`redirect_uris` string + Returns the default redirect_uri, *if* only one is registered. """ if self.redirect_uris: - return self.redirect_uris.split().pop(0) + uris = self.redirect_uris.split() + if len(uris) == 1: + return self.redirect_uris.split().pop(0) + raise errors.MissingRedirectURIError() assert False, ( "If you are using implicit, authorization_code " diff --git a/tests/test_authorization_code.py b/tests/test_authorization_code.py index ea1bee86d..c9bef0f5c 100644 --- a/tests/test_authorization_code.py +++ b/tests/test_authorization_code.py @@ -257,6 +257,8 @@ def test_pre_auth_default_redirect(self): Test for default redirect uri if omitted from query string with response_type: code """ self.client.login(username="test_user", password="123456") + self.application.redirect_uris = "http://localhost" + self.application.save() query_data = { "client_id": self.application.client_id, @@ -269,6 +271,21 @@ def test_pre_auth_default_redirect(self): form = response.context["form"] self.assertEqual(form["redirect_uri"].value(), "http://localhost") + def test_pre_auth_missing_redirect(self): + """ + Test response if redirect_uri is missing and multiple URIs are registered. + @see https://datatracker.ietf.org/doc/html/rfc6749#section-3.1.2.3 + """ + self.client.login(username="test_user", password="123456") + + query_data = { + "client_id": self.application.client_id, + "response_type": "code", + } + + response = self.client.get(reverse("oauth2_provider:authorize"), data=query_data) + self.assertEqual(response.status_code, 400) + def test_pre_auth_forbibben_redirect(self): """ Test error when passing a forbidden redirect_uri in query string with response_type: code @@ -293,6 +310,7 @@ def test_pre_auth_wrong_response_type(self): query_data = { "client_id": self.application.client_id, "response_type": "WRONG", + "redirect_uri": "http://example.org", } response = self.client.get(reverse("oauth2_provider:authorize"), data=query_data) diff --git a/tests/test_hybrid.py b/tests/test_hybrid.py index d198988f6..4f9753979 100644 --- a/tests/test_hybrid.py +++ b/tests/test_hybrid.py @@ -370,6 +370,8 @@ def test_pre_auth_default_redirect(self): Test for default redirect uri if omitted from query string with response_type: code """ self.client.login(username="hy_test_user", password="123456") + self.application.redirect_uris = "http://localhost" + self.application.save() query_string = urlencode( { @@ -413,6 +415,7 @@ def test_pre_auth_wrong_response_type(self): { "client_id": self.application.client_id, "response_type": "WRONG", + "redirect_uri": "http://example.org", } ) url = "{url}?{qs}".format(url=reverse("oauth2_provider:authorize"), qs=query_string) diff --git a/tests/test_implicit.py b/tests/test_implicit.py index a5863401c..5fcad62b0 100644 --- a/tests/test_implicit.py +++ b/tests/test_implicit.py @@ -110,6 +110,8 @@ def test_pre_auth_default_redirect(self): Test for default redirect uri if omitted from query string with response_type: token """ self.client.login(username="test_user", password="123456") + self.application.redirect_uris = "http://localhost" + self.application.save() query_data = { "client_id": self.application.client_id,