Skip to content

Commit d35f030

Browse files
duck-nukemauvipy
andauthored
Handles ValueErrors with invalid hex values in query strings (#954) (#963)
* Handles ValueErrors with invalid hex values in query strings and reraises them as SuspiciousOperations (#954) * Unified erorr naming (err and error) when handling ValueErrors * Added Alex Szabó to AUTHORS * Adds fix message to CHANGELOG.md * Narrows handling of ValueErrors to a specific error (invalid hex in query string) * Fixes formatting Co-authored-by: Asif Saif Uddin <[email protected]>
1 parent 6085a2d commit d35f030

File tree

6 files changed

+72
-15
lines changed

6 files changed

+72
-15
lines changed

AUTHORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Abhishek Patel
1111
Alan Crosswell
1212
Aleksander Vaskevich
1313
Alessandro De Angelis
14+
Alex Szabó
1415
Allisson Azevedo
1516
Andrew Chen Wang
1617
Anvesh Agarwal

CHANGELOG.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2626

2727
### Fixed
2828
* #524 Restrict usage of timezone aware expire dates to Django projects with USE_TZ set to True.
29-
* #955 Avoid doubling of `oauth2_provider` urls mountpath in json response for OIDC view `ConnectDiscoveryInfoView`.
30-
Breaks existing OIDC discovery output
3129
* #953 Allow loopback redirect URIs with random ports using http scheme, localhost address and no explicit port
3230
configuration in the allowed redirect_uris for Oauth2 Applications (RFC8252)
31+
* #954 Query strings with invalid hex values now raise a SuspiciousOperation exception
32+
* #955 Avoid doubling of `oauth2_provider` urls mountpath in json response for OIDC view `ConnectDiscoveryInfoView`.
33+
Breaks existing OIDC discovery output
3334

3435
## [1.5.0] 2021-03-18
3536

oauth2_provider/backends.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from django.contrib.auth import get_user_model
2+
from django.core.exceptions import SuspiciousOperation
23

34
from .oauth2_backends import get_oauthlib_core
45

@@ -14,9 +15,17 @@ class OAuth2Backend:
1415

1516
def authenticate(self, request=None, **credentials):
1617
if request is not None:
17-
valid, r = OAuthLibCore.verify_request(request, scopes=[])
18-
if valid:
19-
return r.user
18+
try:
19+
valid, request = OAuthLibCore.verify_request(request, scopes=[])
20+
except ValueError as error:
21+
if str(error) == "Invalid hex encoding in query string.":
22+
raise SuspiciousOperation(error)
23+
else:
24+
raise
25+
else:
26+
if valid:
27+
return request.user
28+
2029
return None
2130

2231
def get_user(self, user_id):

oauth2_provider/views/mixins.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import logging
22

33
from django.conf import settings
4-
from django.core.exceptions import ImproperlyConfigured
4+
from django.core.exceptions import ImproperlyConfigured, SuspiciousOperation
55
from django.http import HttpResponseForbidden, HttpResponseNotFound
66

77
from ..exceptions import FatalClientError
@@ -150,7 +150,14 @@ def verify_request(self, request):
150150
:param request: The current django.http.HttpRequest object
151151
"""
152152
core = self.get_oauthlib_core()
153-
return core.verify_request(request, scopes=self.get_scopes())
153+
154+
try:
155+
return core.verify_request(request, scopes=self.get_scopes())
156+
except ValueError as error:
157+
if str(error) == "Invalid hex encoding in query string.":
158+
raise SuspiciousOperation(error)
159+
else:
160+
raise
154161

155162
def get_scopes(self):
156163
"""

tests/test_auth_backends.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1+
from unittest.mock import patch
2+
3+
import pytest
14
from django.contrib.auth import get_user_model
25
from django.contrib.auth.models import AnonymousUser
6+
from django.core.exceptions import SuspiciousOperation
37
from django.http import HttpResponse
48
from django.test import RequestFactory, TestCase
59
from django.test.utils import modify_settings, override_settings
@@ -51,6 +55,23 @@ def test_authenticate(self):
5155
u = backend.authenticate(**credentials)
5256
self.assertEqual(u, self.user)
5357

58+
def test_authenticate_raises_error_with_invalid_hex_in_query_params(self):
59+
auth_headers = {
60+
"HTTP_AUTHORIZATION": "Bearer " + "tokstr",
61+
}
62+
request = self.factory.get("/a-resource?auth_token=%%7A", **auth_headers)
63+
credentials = {"request": request}
64+
65+
with pytest.raises(SuspiciousOperation):
66+
OAuth2Backend().authenticate(**credentials)
67+
68+
@patch("oauth2_provider.backends.OAuthLibCore.verify_request")
69+
def test_value_errors_are_reraised(self, patched_verify_request):
70+
patched_verify_request.side_effect = ValueError("Generic error")
71+
72+
with pytest.raises(ValueError):
73+
OAuth2Backend().authenticate(request={})
74+
5475
def test_authenticate_fail(self):
5576
auth_headers = {
5677
"HTTP_AUTHORIZATION": "Bearer " + "badstring",

tests/test_client_credential.py

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import json
2+
from unittest.mock import patch
23
from urllib.parse import quote_plus
34

45
import pytest
56
from django.contrib.auth import get_user_model
7+
from django.core.exceptions import SuspiciousOperation
68
from django.test import RequestFactory, TestCase
79
from django.urls import reverse
810
from django.views.generic import View
@@ -101,21 +103,22 @@ def test_client_credential_user_is_none_on_access_token(self):
101103
self.assertIsNone(access_token.user)
102104

103105

106+
class TestView(OAuthLibMixin, View):
107+
server_class = BackendApplicationServer
108+
validator_class = OAuth2Validator
109+
oauthlib_backend_class = OAuthLibCore
110+
111+
def get_scopes(self):
112+
return ["read", "write"]
113+
114+
104115
class TestExtendedRequest(BaseTest):
105116
@classmethod
106117
def setUpClass(cls):
107118
cls.request_factory = RequestFactory()
108119
super().setUpClass()
109120

110121
def test_extended_request(self):
111-
class TestView(OAuthLibMixin, View):
112-
server_class = BackendApplicationServer
113-
validator_class = OAuth2Validator
114-
oauthlib_backend_class = OAuthLibCore
115-
116-
def get_scopes(self):
117-
return ["read", "write"]
118-
119122
token_request_data = {
120123
"grant_type": "client_credentials",
121124
}
@@ -143,6 +146,21 @@ def get_scopes(self):
143146
self.assertEqual(r.client, self.application)
144147
self.assertEqual(r.scopes, ["read", "write"])
145148

149+
def test_raises_error_with_invalid_hex_in_query_params(self):
150+
request = self.request_factory.get("/fake-req?auth_token=%%7A")
151+
152+
with pytest.raises(SuspiciousOperation):
153+
TestView().verify_request(request)
154+
155+
@patch("oauth2_provider.views.mixins.OAuthLibMixin.get_oauthlib_core")
156+
def test_reraises_value_errors_as_is(self, patched_core):
157+
patched_core.return_value.verify_request.side_effect = ValueError("Generic error")
158+
159+
request = self.request_factory.get("/fake-req")
160+
161+
with pytest.raises(ValueError):
162+
TestView().verify_request(request)
163+
146164

147165
class TestClientResourcePasswordBased(BaseTest):
148166
def test_client_resource_password_based(self):

0 commit comments

Comments
 (0)