Repository: jazzband/django-oauth-toolkit

Issue:
Using GET parameters in /o/token/ endpoint causes http 500 error (instead of 400)
**Describe the bug**
When GET parameters are in /o/token/, e.g. /o/token/?test=test , the server returns 500 error. 
This is caused because OAuth2Error (with status code 400) from oauthlib is not caught:
https://github.com/oauthlib/oauthlib/blob/a70e13ff19d74f7fc1e397742782c7b881e2d496/oauthlib/oauth2/rfc6749/endpoints/base.py#L88

I think the solution is to change this:
https://github.com/jazzband/django-oauth-toolkit/blob/9d2aac2480b2a1875eb52612661992f73606bade/oauth2_provider/oauth2_backends.py#L155

to something like this:
https://github.com/jazzband/django-oauth-toolkit/blob/9d2aac2480b2a1875eb52612661992f73606bade/oauth2_provider/oauth2_backends.py#L184


**To Reproduce**
Add any GET paremeters to /o/token endpoint

**Expected behavior**
Server returns 400 error

**Additional context**
We are migrating from very old version of this library where RFC6749 was not enforced by oauthlib. We would like to send proper error when the request is invalid.




Discussion:
@AndrejZbin Looks good. Please submit a PR!

Resolving patch:
diff --git a/oauth2_provider/oauth2_backends.py b/oauth2_provider/oauth2_backends.py
--- a/oauth2_provider/oauth2_backends.py
+++ b/oauth2_provider/oauth2_backends.py
@@ -152,12 +152,14 @@ def create_token_response(self, request):
         uri, http_method, body, headers = self._extract_params(request)
         extra_credentials = self._get_extra_credentials(request)
 
-        headers, body, status = self.server.create_token_response(
-            uri, http_method, body, headers, extra_credentials
-        )
-        uri = headers.get("Location", None)
-
-        return uri, headers, body, status
+        try:
+            headers, body, status = self.server.create_token_response(
+                uri, http_method, body, headers, extra_credentials
+            )
+            uri = headers.get("Location", None)
+            return uri, headers, body, status
+        except OAuth2Error as exc:
+            return None, exc.headers, exc.json, exc.status_code
 
     def create_revocation_response(self, request):
         """


Test patch:
diff --git a/tests/test_oauth2_backends.py b/tests/test_oauth2_backends.py
--- a/tests/test_oauth2_backends.py
+++ b/tests/test_oauth2_backends.py
@@ -1,10 +1,13 @@
+import base64
 import json
 
 import pytest
+from django.contrib.auth import get_user_model
 from django.test import RequestFactory, TestCase
+from django.utils.timezone import now, timedelta
 
 from oauth2_provider.backends import get_oauthlib_core
-from oauth2_provider.models import redirect_to_uri_allowed
+from oauth2_provider.models import get_access_token_model, get_application_model, redirect_to_uri_allowed
 from oauth2_provider.oauth2_backends import JSONOAuthLibCore, OAuthLibCore
 
 
@@ -50,6 +53,96 @@ def test_application_json_extract_params(self):
         self.assertNotIn("password=123456", body)
 
 
+UserModel = get_user_model()
+ApplicationModel = get_application_model()
+AccessTokenModel = get_access_token_model()
+
+
+@pytest.mark.usefixtures("oauth2_settings")
+class TestOAuthLibCoreBackendErrorHandling(TestCase):
+    def setUp(self):
+        self.factory = RequestFactory()
+        self.oauthlib_core = OAuthLibCore()
+        self.user = UserModel.objects.create_user("john", "test@example.com", "123456")
+        self.app = ApplicationModel.objects.create(
+            name="app",
+            client_id="app_id",
+            client_secret="app_secret",
+            client_type=ApplicationModel.CLIENT_CONFIDENTIAL,
+            authorization_grant_type=ApplicationModel.GRANT_PASSWORD,
+            user=self.user,
+        )
+
+    def tearDown(self):
+        self.user.delete()
+        self.app.delete()
+
+    def test_create_token_response_valid(self):
+        payload = (
+            "grant_type=password&username=john&password=123456&client_id=app_id&client_secret=app_secret"
+        )
+        request = self.factory.post(
+            "/o/token/",
+            payload,
+            content_type="application/x-www-form-urlencoded",
+            HTTP_AUTHORIZATION="Basic %s" % base64.b64encode(b"john:123456").decode(),
+        )
+
+        uri, headers, body, status = self.oauthlib_core.create_token_response(request)
+        self.assertEqual(status, 200)
+
+    def test_create_token_response_query_params(self):
+        payload = (
+            "grant_type=password&username=john&password=123456&client_id=app_id&client_secret=app_secret"
+        )
+        request = self.factory.post(
+            "/o/token/?test=foo",
+            payload,
+            content_type="application/x-www-form-urlencoded",
+            HTTP_AUTHORIZATION="Basic %s" % base64.b64encode(b"john:123456").decode(),
+        )
+        uri, headers, body, status = self.oauthlib_core.create_token_response(request)
+
+        self.assertEqual(status, 400)
+        self.assertDictEqual(
+            json.loads(body),
+            {"error": "invalid_request", "error_description": "URL query parameters are not allowed"},
+        )
+
+    def test_create_revocation_response_valid(self):
+        AccessTokenModel.objects.create(
+            user=self.user, token="tokstr", application=self.app, expires=now() + timedelta(days=365)
+        )
+        payload = "client_id=app_id&client_secret=app_secret&token=tokstr"
+        request = self.factory.post(
+            "/o/revoke_token/",
+            payload,
+            content_type="application/x-www-form-urlencoded",
+            HTTP_AUTHORIZATION="Basic %s" % base64.b64encode(b"john:123456").decode(),
+        )
+        uri, headers, body, status = self.oauthlib_core.create_revocation_response(request)
+        self.assertEqual(status, 200)
+
+    def test_create_revocation_response_query_params(self):
+        token = AccessTokenModel.objects.create(
+            user=self.user, token="tokstr", application=self.app, expires=now() + timedelta(days=365)
+        )
+        payload = "client_id=app_id&client_secret=app_secret&token=tokstr"
+        request = self.factory.post(
+            "/o/revoke_token/?test=foo",
+            payload,
+            content_type="application/x-www-form-urlencoded",
+            HTTP_AUTHORIZATION="Basic %s" % base64.b64encode(b"john:123456").decode(),
+        )
+        uri, headers, body, status = self.oauthlib_core.create_revocation_response(request)
+        self.assertEqual(status, 400)
+        self.assertDictEqual(
+            json.loads(body),
+            {"error": "invalid_request", "error_description": "URL query parameters are not allowed"},
+        )
+        token.delete()
+
+
 class TestCustomOAuthLibCoreBackend(TestCase):
     """
     Tests that the public API behaves as expected when we override