diff options
author | Abhishek Patel <5524161+Abhishek8394@users.noreply.github.com> | 2019-05-14 00:36:10 -0700 |
---|---|---|
committer | Abhishek Patel <5524161+Abhishek8394@users.noreply.github.com> | 2019-05-14 00:37:59 -0700 |
commit | 056948ac7c14a435d0b65dd27692fe2494bc3743 (patch) | |
tree | 912dff38bb170a34df0f84af9e52f0d91e4efdea | |
parent | ee06f0f3349d7fd656d35a2eef40ee18fb74e303 (diff) | |
download | oauthlib-056948ac7c14a435d0b65dd27692fe2494bc3743.tar.gz |
Enforce POST HTTP method on TokenEndpoint, IntrospectEndpoint and RevocationEndpoint
- Add validation checks for HTTP method in TokenEndpoint, IntrospectEndpoint and RevocationEndpoint.
- CHANGE DEFAULT HTTP method for TokenEndpoint from 'GET' to 'POST'.
- Add tests + Fix an old test in . It used to send query params to TokenEndpoint which is not allowed anymore. Fixed it so payload is sent as POST body.
8 files changed, 107 insertions, 21 deletions
diff --git a/oauthlib/oauth2/rfc6749/endpoints/base.py b/oauthlib/oauth2/rfc6749/endpoints/base.py index c99c22d..e39232f 100644 --- a/oauthlib/oauth2/rfc6749/endpoints/base.py +++ b/oauthlib/oauth2/rfc6749/endpoints/base.py @@ -25,6 +25,18 @@ class BaseEndpoint(object): def __init__(self): self._available = True self._catch_errors = False + self._valid_request_methods = None + + @property + def valid_request_methods(self): + return self._valid_request_methods + + @valid_request_methods.setter + def valid_request_methods(self, valid_request_methods): + if valid_request_methods is not None: + valid_request_methods = [x.upper() for x in valid_request_methods] + self._valid_request_methods = valid_request_methods + @property def available(self): @@ -64,10 +76,17 @@ class BaseEndpoint(object): request.token_type_hint not in self.supported_token_types): raise UnsupportedTokenTypeError(request=request) + def _raise_on_bad_method(self, request): + if self.valid_request_methods is None: + raise ValueError('Configure "valid_request_methods" property first') + if request.http_method.upper() not in self.valid_request_methods: + raise InvalidRequestError(request=request, + description=('Unsupported request method %s' % request.http_method.upper())) + def _raise_on_bad_post_request(self, request): """Raise if invalid POST request received """ - if request.http_method.lower() == 'post': + if request.http_method.upper() == 'POST': query_params = request.uri_query or "" if query_params: raise InvalidRequestError(request=request, diff --git a/oauthlib/oauth2/rfc6749/endpoints/introspect.py b/oauthlib/oauth2/rfc6749/endpoints/introspect.py index 547e7db..4accbdc 100644 --- a/oauthlib/oauth2/rfc6749/endpoints/introspect.py +++ b/oauthlib/oauth2/rfc6749/endpoints/introspect.py @@ -39,6 +39,7 @@ class IntrospectEndpoint(BaseEndpoint): """ valid_token_types = ('access_token', 'refresh_token') + valid_request_methods = ('POST',) def __init__(self, request_validator, supported_token_types=None): BaseEndpoint.__init__(self) @@ -117,6 +118,7 @@ class IntrospectEndpoint(BaseEndpoint): .. _`section 1.5`: http://tools.ietf.org/html/rfc6749#section-1.5 .. _`RFC6749`: http://tools.ietf.org/html/rfc6749 """ + self._raise_on_bad_method(request) self._raise_on_bad_post_request(request) self._raise_on_missing_token(request) self._raise_on_invalid_client(request) diff --git a/oauthlib/oauth2/rfc6749/endpoints/revocation.py b/oauthlib/oauth2/rfc6749/endpoints/revocation.py index 1439491..1fabd03 100644 --- a/oauthlib/oauth2/rfc6749/endpoints/revocation.py +++ b/oauthlib/oauth2/rfc6749/endpoints/revocation.py @@ -28,6 +28,7 @@ class RevocationEndpoint(BaseEndpoint): """ valid_token_types = ('access_token', 'refresh_token') + valid_request_methods = ('POST',) def __init__(self, request_validator, supported_token_types=None, enable_jsonp=False): @@ -121,6 +122,7 @@ class RevocationEndpoint(BaseEndpoint): .. _`Section 4.1.2`: https://tools.ietf.org/html/draft-ietf-oauth-revocation-11#section-4.1.2 .. _`RFC6749`: https://tools.ietf.org/html/rfc6749 """ + self._raise_on_bad_method(request) self._raise_on_bad_post_request(request) self._raise_on_missing_token(request) self._raise_on_invalid_client(request) diff --git a/oauthlib/oauth2/rfc6749/endpoints/token.py b/oauthlib/oauth2/rfc6749/endpoints/token.py index 223e8d0..bc87e9b 100644 --- a/oauthlib/oauth2/rfc6749/endpoints/token.py +++ b/oauthlib/oauth2/rfc6749/endpoints/token.py @@ -62,6 +62,8 @@ class TokenEndpoint(BaseEndpoint): .. _`Appendix B`: https://tools.ietf.org/html/rfc6749#appendix-B """ + valid_request_methods = ('POST',) + def __init__(self, default_grant_type, default_token_type, grant_types): BaseEndpoint.__init__(self) self._grant_types = grant_types @@ -85,7 +87,7 @@ class TokenEndpoint(BaseEndpoint): return self._default_token_type @catch_errors_and_unavailability - def create_token_response(self, uri, http_method='GET', body=None, + def create_token_response(self, uri, http_method='POST', body=None, headers=None, credentials=None, grant_type_for_scope=None, claims=None): """Extract grant_type and route to the designated handler.""" @@ -117,4 +119,5 @@ class TokenEndpoint(BaseEndpoint): request, self.default_token_type) def validate_token_request(self, request): + self._raise_on_bad_method(request) self._raise_on_bad_post_request(request) diff --git a/tests/oauth2/rfc6749/endpoints/test_base_endpoint.py b/tests/oauth2/rfc6749/endpoints/test_base_endpoint.py index 4f78d9b..bf04a42 100644 --- a/tests/oauth2/rfc6749/endpoints/test_base_endpoint.py +++ b/tests/oauth2/rfc6749/endpoints/test_base_endpoint.py @@ -25,7 +25,7 @@ class BaseEndpointTest(TestCase): server = Server(validator) server.catch_errors = True h, b, s = server.create_token_response( - 'https://example.com?grant_type=authorization_code&code=abc' + 'https://example.com', body='grant_type=authorization_code&code=abc' ) self.assertIn("server_error", b) self.assertEqual(s, 500) diff --git a/tests/oauth2/rfc6749/endpoints/test_error_responses.py b/tests/oauth2/rfc6749/endpoints/test_error_responses.py index 2b87032..2479836 100644 --- a/tests/oauth2/rfc6749/endpoints/test_error_responses.py +++ b/tests/oauth2/rfc6749/endpoints/test_error_responses.py @@ -11,7 +11,6 @@ from oauthlib.oauth2 import (BackendApplicationServer, LegacyApplicationServer, MobileApplicationServer, RequestValidator, WebApplicationServer) from oauthlib.oauth2.rfc6749 import errors - from ....unittest import TestCase @@ -439,24 +438,55 @@ class ErrorResponseTest(TestCase): body='grant_type=bar') self.assertEqual('unsupported_grant_type', json.loads(body)['error']) + def test_invalid_request_method(self): + test_methods = ['GET', 'pUt', 'dEleTe', 'paTcH'] + test_methods = test_methods + [x.lower() for x in test_methods] + [x.upper() for x in test_methods] + for method in test_methods: + self.validator.authenticate_client.side_effect = self.set_client + + uri = "http://i/b/token/" + try: + _, body, s = self.web.create_token_response(uri, + body='grant_type=access_token&code=123', http_method=method) + self.fail('This should have failed with InvalidRequestError') + except errors.InvalidRequestError as ire: + self.assertIn('Unsupported request method', ire.description) + + try: + _, body, s = self.legacy.create_token_response(uri, + body='grant_type=access_token&code=123', http_method=method) + self.fail('This should have failed with InvalidRequestError') + except errors.InvalidRequestError as ire: + self.assertIn('Unsupported request method', ire.description) + + try: + _, body, s = self.backend.create_token_response(uri, + body='grant_type=access_token&code=123', http_method=method) + self.fail('This should have failed with InvalidRequestError') + except errors.InvalidRequestError as ire: + self.assertIn('Unsupported request method', ire.description) + def test_invalid_post_request(self): self.validator.authenticate_client.side_effect = self.set_client for param in ['token', 'secret', 'code', 'foo']: uri = 'https://i/b/token?' + urlencode([(param, 'secret')]) - _, body, s = self.web.create_introspect_response(uri, - body='grant_type=access_token&code=123') - self.assertEqual(json.loads(body)['error'], 'invalid_request') - self.assertIn('query parameters are not allowed', json.loads(body)['error_description']) - self.assertEqual(s, 400) - - _, body, s = self.legacy.create_introspect_response(uri, - body='grant_type=access_token&code=123') - self.assertEqual(json.loads(body)['error'], 'invalid_request') - self.assertIn('query parameters are not allowed', json.loads(body)['error_description']) - self.assertEqual(s, 400) - - _, body, s = self.backend.create_introspect_response(uri, - body='grant_type=access_token&code=123') - self.assertEqual(json.loads(body)['error'], 'invalid_request') - self.assertIn('query parameters are not allowed', json.loads(body)['error_description']) - self.assertEqual(s, 400) + try: + _, body, s = self.web.create_token_response(uri, + body='grant_type=access_token&code=123') + self.fail('This should have failed with InvalidRequestError') + except errors.InvalidRequestError as ire: + self.assertIn('URL query parameters are not allowed', ire.description) + + try: + _, body, s = self.legacy.create_token_response(uri, + body='grant_type=access_token&code=123') + self.fail('This should have failed with InvalidRequestError') + except errors.InvalidRequestError as ire: + self.assertIn('URL query parameters are not allowed', ire.description) + + try: + _, body, s = self.backend.create_token_response(uri, + body='grant_type=access_token&code=123') + self.fail('This should have failed with InvalidRequestError') + except errors.InvalidRequestError as ire: + self.assertIn('URL query parameters are not allowed', ire.description) diff --git a/tests/oauth2/rfc6749/endpoints/test_introspect_endpoint.py b/tests/oauth2/rfc6749/endpoints/test_introspect_endpoint.py index a34c970..ae3deae 100644 --- a/tests/oauth2/rfc6749/endpoints/test_introspect_endpoint.py +++ b/tests/oauth2/rfc6749/endpoints/test_introspect_endpoint.py @@ -140,6 +140,21 @@ class IntrospectEndpointTest(TestCase): self.assertEqual(loads(b)['error'], 'invalid_request') self.assertEqual(s, 400) + def test_introspect_invalid_request_method(self): + endpoint = IntrospectEndpoint(self.validator, + supported_token_types=['access_token']) + test_methods = ['GET', 'pUt', 'dEleTe', 'paTcH'] + test_methods = test_methods + [x.lower() for x in test_methods] + [x.upper() for x in test_methods] + for method in test_methods: + body = urlencode([('token', 'foo'), + ('token_type_hint', 'refresh_token')]) + h, b, s = endpoint.create_introspect_response(self.uri, + http_method = method, headers=self.headers, body=body) + self.assertEqual(h, self.resp_h) + self.assertEqual(loads(b)['error'], 'invalid_request') + self.assertIn('Unsupported request method', loads(b)['error_description']) + self.assertEqual(s, 400) + def test_introspect_bad_post_request(self): endpoint = IntrospectEndpoint(self.validator, supported_token_types=['access_token']) diff --git a/tests/oauth2/rfc6749/endpoints/test_revocation_endpoint.py b/tests/oauth2/rfc6749/endpoints/test_revocation_endpoint.py index c73a1ef..17be3a5 100644 --- a/tests/oauth2/rfc6749/endpoints/test_revocation_endpoint.py +++ b/tests/oauth2/rfc6749/endpoints/test_revocation_endpoint.py @@ -121,6 +121,21 @@ class RevocationEndpointTest(TestCase): self.assertEqual(loads(b)['error'], 'invalid_request') self.assertEqual(s, 400) + def test_revoke_invalid_request_method(self): + endpoint = RevocationEndpoint(self.validator, + supported_token_types=['access_token']) + test_methods = ['GET', 'pUt', 'dEleTe', 'paTcH'] + test_methods = test_methods + [x.lower() for x in test_methods] + [x.upper() for x in test_methods] + for method in test_methods: + body = urlencode([('token', 'foo'), + ('token_type_hint', 'refresh_token')]) + h, b, s = endpoint.create_revocation_response(self.uri, + http_method = method, headers=self.headers, body=body) + self.assertEqual(h, self.resp_h) + self.assertEqual(loads(b)['error'], 'invalid_request') + self.assertIn('Unsupported request method', loads(b)['error_description']) + self.assertEqual(s, 400) + def test_revoke_bad_post_request(self): endpoint = RevocationEndpoint(self.validator, supported_token_types=['access_token']) |