Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handles ValueErrors with invalid hex values in query strings (#954) #963

Merged
merged 9 commits into from
Oct 19, 2021
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Abhishek Patel
Alan Crosswell
Aleksander Vaskevich
Alessandro De Angelis
Alex Szabó
Allisson Azevedo
Andrew Chen Wang
Anvesh Agarwal
Expand Down
5 changes: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

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

## [1.5.0] 2021-03-18

Expand Down
15 changes: 12 additions & 3 deletions oauth2_provider/backends.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from django.contrib.auth import get_user_model
from django.core.exceptions import SuspiciousOperation

from .oauth2_backends import get_oauthlib_core

Expand All @@ -14,9 +15,17 @@ class OAuth2Backend:

def authenticate(self, request=None, **credentials):
if request is not None:
valid, r = OAuthLibCore.verify_request(request, scopes=[])
if valid:
return r.user
try:
valid, request = OAuthLibCore.verify_request(request, scopes=[])
except ValueError as error:
if str(error) == "Invalid hex encoding in query string.":
raise SuspiciousOperation(error)
else:
raise
else:
if valid:
return request.user

return None

def get_user(self, user_id):
Expand Down
11 changes: 9 additions & 2 deletions oauth2_provider/views/mixins.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import logging

from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.core.exceptions import ImproperlyConfigured, SuspiciousOperation
from django.http import HttpResponseForbidden, HttpResponseNotFound

from ..exceptions import FatalClientError
Expand Down Expand Up @@ -150,7 +150,14 @@ def verify_request(self, request):
:param request: The current django.http.HttpRequest object
"""
core = self.get_oauthlib_core()
return core.verify_request(request, scopes=self.get_scopes())

try:
return core.verify_request(request, scopes=self.get_scopes())
except ValueError as error:
if str(error) == "Invalid hex encoding in query string.":
raise SuspiciousOperation(error)
else:
raise

def get_scopes(self):
"""
Expand Down
21 changes: 21 additions & 0 deletions tests/test_auth_backends.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
from unittest.mock import patch

import pytest
from django.contrib.auth import get_user_model
from django.contrib.auth.models import AnonymousUser
from django.core.exceptions import SuspiciousOperation
from django.http import HttpResponse
from django.test import RequestFactory, TestCase
from django.test.utils import modify_settings, override_settings
Expand Down Expand Up @@ -51,6 +55,23 @@ def test_authenticate(self):
u = backend.authenticate(**credentials)
self.assertEqual(u, self.user)

def test_authenticate_raises_error_with_invalid_hex_in_query_params(self):
auth_headers = {
"HTTP_AUTHORIZATION": "Bearer " + "tokstr",
}
request = self.factory.get("/a-resource?auth_token=%%7A", **auth_headers)
credentials = {"request": request}

with pytest.raises(SuspiciousOperation):
OAuth2Backend().authenticate(**credentials)

@patch("oauth2_provider.backends.OAuthLibCore.verify_request")
def test_value_errors_are_reraised(self, patched_verify_request):
patched_verify_request.side_effect = ValueError("Generic error")

with pytest.raises(ValueError):
OAuth2Backend().authenticate(request={})

def test_authenticate_fail(self):
auth_headers = {
"HTTP_AUTHORIZATION": "Bearer " + "badstring",
Expand Down
34 changes: 26 additions & 8 deletions tests/test_client_credential.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import json
from unittest.mock import patch
from urllib.parse import quote_plus

import pytest
from django.contrib.auth import get_user_model
from django.core.exceptions import SuspiciousOperation
from django.test import RequestFactory, TestCase
from django.urls import reverse
from django.views.generic import View
Expand Down Expand Up @@ -101,21 +103,22 @@ def test_client_credential_user_is_none_on_access_token(self):
self.assertIsNone(access_token.user)


class TestView(OAuthLibMixin, View):
server_class = BackendApplicationServer
validator_class = OAuth2Validator
oauthlib_backend_class = OAuthLibCore

def get_scopes(self):
return ["read", "write"]


class TestExtendedRequest(BaseTest):
@classmethod
def setUpClass(cls):
cls.request_factory = RequestFactory()
super().setUpClass()

def test_extended_request(self):
class TestView(OAuthLibMixin, View):
server_class = BackendApplicationServer
validator_class = OAuth2Validator
oauthlib_backend_class = OAuthLibCore

def get_scopes(self):
return ["read", "write"]

token_request_data = {
"grant_type": "client_credentials",
}
Expand Down Expand Up @@ -143,6 +146,21 @@ def get_scopes(self):
self.assertEqual(r.client, self.application)
self.assertEqual(r.scopes, ["read", "write"])

def test_raises_error_with_invalid_hex_in_query_params(self):
request = self.request_factory.get("/fake-req?auth_token=%%7A")

with pytest.raises(SuspiciousOperation):
TestView().verify_request(request)

@patch("oauth2_provider.views.mixins.OAuthLibMixin.get_oauthlib_core")
def test_reraises_value_errors_as_is(self, patched_core):
patched_core.return_value.verify_request.side_effect = ValueError("Generic error")

request = self.request_factory.get("/fake-req")

with pytest.raises(ValueError):
TestView().verify_request(request)


class TestClientResourcePasswordBased(BaseTest):
def test_client_resource_password_based(self):
Expand Down