Skip to content

Commit 13643a7

Browse files
committed
handle oauthlib errors on create token and revoke token requests
1 parent da459a1 commit 13643a7

File tree

4 files changed

+141
-11
lines changed

4 files changed

+141
-11
lines changed

AUTHORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Aleksander Vaskevich
1616
Alessandro De Angelis
1717
Alex Szabó
1818
Allisson Azevedo
19+
Andrej Zbín
1920
Andrew Chen Wang
2021
Anvesh Agarwal
2122
Aristóbulo Meneses

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2727

2828
### Fixed
2929
* Remove upper version bound on Django, to allow upgrading to Django 4.1.1 bugfix release.
30+
* Handle oauthlib errors on create token and revoke token requests
3031

3132
## [2.1.0] 2022-06-19
3233

oauth2_provider/oauth2_backends.py

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -152,12 +152,14 @@ def create_token_response(self, request):
152152
uri, http_method, body, headers = self._extract_params(request)
153153
extra_credentials = self._get_extra_credentials(request)
154154

155-
headers, body, status = self.server.create_token_response(
156-
uri, http_method, body, headers, extra_credentials
157-
)
158-
uri = headers.get("Location", None)
159-
160-
return uri, headers, body, status
155+
try:
156+
headers, body, status = self.server.create_token_response(
157+
uri, http_method, body, headers, extra_credentials
158+
)
159+
uri = headers.get("Location", None)
160+
return uri, headers, body, status
161+
except OAuth2Error as exc:
162+
return None, exc.headers, exc.json, exc.status_code
161163

162164
def create_revocation_response(self, request):
163165
"""
@@ -168,10 +170,12 @@ def create_revocation_response(self, request):
168170
"""
169171
uri, http_method, body, headers = self._extract_params(request)
170172

171-
headers, body, status = self.server.create_revocation_response(uri, http_method, body, headers)
172-
uri = headers.get("Location", None)
173-
174-
return uri, headers, body, status
173+
try:
174+
headers, body, status = self.server.create_revocation_response(uri, http_method, body, headers)
175+
uri = headers.get("Location", None)
176+
return uri, headers, body, status
177+
except OAuth2Error as exc:
178+
return None, exc.headers, exc.json, exc.status_code
175179

176180
def create_userinfo_response(self, request):
177181
"""

tests/test_oauth2_backends.py

Lines changed: 125 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1+
import base64
12
import json
23

34
import pytest
5+
from django.contrib.auth import get_user_model
46
from django.test import RequestFactory, TestCase
7+
from django.utils.timezone import now, timedelta
8+
from oauthlib.oauth2 import OAuth2Error
59

610
from oauth2_provider.backends import get_oauthlib_core
7-
from oauth2_provider.models import redirect_to_uri_allowed
11+
from oauth2_provider.models import get_access_token_model, get_application_model, redirect_to_uri_allowed
812
from oauth2_provider.oauth2_backends import JSONOAuthLibCore, OAuthLibCore
913

1014

@@ -50,6 +54,126 @@ def test_application_json_extract_params(self):
5054
self.assertNotIn("password=123456", body)
5155

5256

57+
UserModel = get_user_model()
58+
ApplicationModel = get_application_model()
59+
AccessTokenModel = get_access_token_model()
60+
61+
62+
@pytest.mark.usefixtures("oauth2_settings")
63+
class TestOAuthLibCoreBackendErrorHandling(TestCase):
64+
def setUp(self):
65+
self.factory = RequestFactory()
66+
self.oauthlib_core = OAuthLibCore()
67+
self.user = UserModel.objects.create_user("john", "[email protected]", "123456")
68+
self.app = ApplicationModel.objects.create(
69+
name="app",
70+
client_id="app_id",
71+
client_secret="app_secret",
72+
client_type=ApplicationModel.CLIENT_CONFIDENTIAL,
73+
authorization_grant_type=ApplicationModel.GRANT_PASSWORD,
74+
user=self.user,
75+
)
76+
77+
def tearDown(self):
78+
self.user.delete()
79+
self.app.delete()
80+
81+
def test_create_token_response_valid(self):
82+
payload = (
83+
"grant_type=password&username=john&password=123456&client_id=app_id&client_secret=app_secret"
84+
)
85+
request = self.factory.post(
86+
"/o/token/",
87+
payload,
88+
content_type="application/x-www-form-urlencoded",
89+
HTTP_AUTHORIZATION="Basic %s" % base64.b64encode(b"john:123456").decode(),
90+
)
91+
92+
uri, headers, body, status = self.oauthlib_core.create_token_response(request)
93+
self.assertEqual(status, 200)
94+
95+
def test_create_token_response_raise_exception(self):
96+
payload = (
97+
"grant_type=password&username=john&password=123456&client_id=app_id&client_secret=app_secret"
98+
)
99+
request = self.factory.post(
100+
"/o/token/",
101+
payload,
102+
content_type="application/x-www-form-urlencoded",
103+
HTTP_AUTHORIZATION="Basic %s" % base64.b64encode(b"john:123456").decode(),
104+
)
105+
with mock.patch("oauthlib.oauth2.Server.create_token_response") as create_token_response:
106+
create_token_response.side_effect = OAuth2Error("test error description")
107+
uri, headers, body, status = self.oauthlib_core.create_token_response(request)
108+
109+
self.assertEqual(json.loads(body)["error_description"], "test error description")
110+
111+
def test_create_token_response_query_params(self):
112+
payload = (
113+
"grant_type=password&username=john&password=123456&client_id=app_id&client_secret=app_secret"
114+
)
115+
request = self.factory.post(
116+
"/o/token/?test=foo",
117+
payload,
118+
content_type="application/x-www-form-urlencoded",
119+
HTTP_AUTHORIZATION="Basic %s" % base64.b64encode(b"john:123456").decode(),
120+
)
121+
uri, headers, body, status = self.oauthlib_core.create_token_response(request)
122+
123+
self.assertEqual(status, 400)
124+
self.assertDictEqual(
125+
json.loads(body),
126+
{"error": "invalid_request", "error_description": "URL query parameters are not allowed"},
127+
)
128+
129+
def test_create_revocation_response_valid(self):
130+
AccessTokenModel.objects.create(
131+
user=self.user, token="tokstr", application=self.app, expires=now() + timedelta(days=365)
132+
)
133+
payload = "client_id=app_id&client_secret=app_secret&token=tokstr"
134+
request = self.factory.post(
135+
"/o/revoke_token/",
136+
payload,
137+
content_type="application/x-www-form-urlencoded",
138+
HTTP_AUTHORIZATION="Basic %s" % base64.b64encode(b"john:123456").decode(),
139+
)
140+
uri, headers, body, status = self.oauthlib_core.create_revocation_response(request)
141+
self.assertEqual(status, 200)
142+
143+
def test_create_revocation_response_raise_exception(self):
144+
payload = "client_id=app_id&client_secret=app_secret&token=tokstr"
145+
request = self.factory.post(
146+
"/o/revoke_token/",
147+
payload,
148+
content_type="application/x-www-form-urlencoded",
149+
HTTP_AUTHORIZATION="Basic %s" % base64.b64encode(b"john:123456").decode(),
150+
)
151+
with mock.patch("oauthlib.oauth2.Server.create_revocation_response") as create_revocation_response:
152+
create_revocation_response.side_effect = OAuth2Error("test error description")
153+
uri, headers, body, status = self.oauthlib_core.create_revocation_response(request)
154+
155+
self.assertEqual(json.loads(body)["error_description"], "test error description")
156+
157+
def test_create_revocation_response_query_params(self):
158+
token = AccessTokenModel.objects.create(
159+
user=self.user, token="tokstr", application=self.app, expires=now() + timedelta(days=365)
160+
)
161+
payload = "client_id=app_id&client_secret=app_secret&token=tokstr"
162+
request = self.factory.post(
163+
"/o/revoke_token/?test=foo",
164+
payload,
165+
content_type="application/x-www-form-urlencoded",
166+
HTTP_AUTHORIZATION="Basic %s" % base64.b64encode(b"john:123456").decode(),
167+
)
168+
uri, headers, body, status = self.oauthlib_core.create_revocation_response(request)
169+
self.assertEqual(status, 400)
170+
self.assertDictEqual(
171+
json.loads(body),
172+
{"error": "invalid_request", "error_description": "URL query parameters are not allowed"},
173+
)
174+
token.delete()
175+
176+
53177
class TestCustomOAuthLibCoreBackend(TestCase):
54178
"""
55179
Tests that the public API behaves as expected when we override

0 commit comments

Comments
 (0)