From 17593fdeb4f3528a146da9a434e18fcc9f973e1d Mon Sep 17 00:00:00 2001 From: Pierre CHAISY Date: Wed, 26 Apr 2017 17:20:35 +0200 Subject: remove check on empty scopes --- oauthlib/oauth2/rfc6749/grant_types/implicit.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/oauthlib/oauth2/rfc6749/grant_types/implicit.py b/oauthlib/oauth2/rfc6749/grant_types/implicit.py index 51e95af..7ffed8d 100644 --- a/oauthlib/oauth2/rfc6749/grant_types/implicit.py +++ b/oauthlib/oauth2/rfc6749/grant_types/implicit.py @@ -201,11 +201,6 @@ class ImplicitGrant(GrantTypeBase): .. _`Section 7.1`: http://tools.ietf.org/html/rfc6749#section-7.1 """ try: - # request.scopes is only mandated in post auth and both pre and - # post auth use validate_authorization_request - if not request.scopes: - raise ValueError('Scopes must be set on post auth.') - self.validate_token_request(request) # If the request fails due to a missing, invalid, or mismatching -- cgit v1.2.1 From a4f39fc93ca2cb3b14eb1f3538ba5363148485be Mon Sep 17 00:00:00 2001 From: Free Duerinckx Date: Wed, 4 Jul 2018 14:35:03 +0200 Subject: `invalid_grant` status code should be 400 According to section 5.2 of rfc 6749 (https://tools.ietf.org/html/rfc6749#section-5.2) A server should respond with 400 in case of an invalid grant. The given grant is invalid and the client should give other data. A 401 is not applicable here because the client is required to give a suitable Authorization header field which doesn't make any sense if you are trying to acquire a grant authentication. According to sections 10.4.1 and 10.4.2 of rfc 2616 (https://tools.ietf.org/html/rfc2616#section-10.4.1) --- oauthlib/oauth2/rfc6749/errors.py | 2 +- tests/oauth2/rfc6749/grant_types/test_refresh_token.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/oauthlib/oauth2/rfc6749/errors.py b/oauthlib/oauth2/rfc6749/errors.py index 5a0cca2..7b31d47 100644 --- a/oauthlib/oauth2/rfc6749/errors.py +++ b/oauthlib/oauth2/rfc6749/errors.py @@ -245,7 +245,7 @@ class InvalidGrantError(OAuth2Error): issued to another client. """ error = 'invalid_grant' - status_code = 401 + status_code = 400 class UnauthorizedClientError(OAuth2Error): diff --git a/tests/oauth2/rfc6749/grant_types/test_refresh_token.py b/tests/oauth2/rfc6749/grant_types/test_refresh_token.py index 21540a2..f055c7d 100644 --- a/tests/oauth2/rfc6749/grant_types/test_refresh_token.py +++ b/tests/oauth2/rfc6749/grant_types/test_refresh_token.py @@ -109,7 +109,7 @@ class RefreshTokenGrantTest(TestCase): token = json.loads(body) self.assertEqual(self.mock_validator.save_token.call_count, 0) self.assertEqual(token['error'], 'invalid_grant') - self.assertEqual(status_code, 401) + self.assertEqual(status_code, 400) def test_invalid_client(self): self.mock_validator.authenticate_client.return_value = False -- cgit v1.2.1 From 3b6be54ab967d9ac6174fae97b5368c1d9f6c6c3 Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Mon, 30 Jul 2018 14:48:24 +0200 Subject: Call get_default_redirect_uri if no redirect_uri in token req --- .../rfc6749/grant_types/authorization_code.py | 11 +++++++++++ .../endpoints/test_credentials_preservation.py | 21 +++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py b/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py index 0660263..1ad6727 100644 --- a/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py +++ b/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py @@ -420,6 +420,17 @@ class AuthorizationCodeGrant(GrantTypeBase): # REQUIRED, if the "redirect_uri" parameter was included in the # authorization request as described in Section 4.1.1, and their # values MUST be identical. + if request.redirect_uri is None: + request.using_default_redirect_uri = True + request.redirect_uri = self.request_validator.get_default_redirect_uri( + request.client_id, request) + log.debug('Using default redirect_uri %s.', request.redirect_uri) + if not request.redirect_uri: + raise errors.MissingRedirectURIError(request=request) + else: + request.using_default_redirect_uri = False + log.debug('Using provided redirect_uri %s', request.redirect_uri) + if not self.request_validator.confirm_redirect_uri(request.client_id, request.code, request.redirect_uri, request.client, request): diff --git a/tests/oauth2/rfc6749/endpoints/test_credentials_preservation.py b/tests/oauth2/rfc6749/endpoints/test_credentials_preservation.py index 0eb719f..50c2956 100644 --- a/tests/oauth2/rfc6749/endpoints/test_credentials_preservation.py +++ b/tests/oauth2/rfc6749/endpoints/test_credentials_preservation.py @@ -116,3 +116,24 @@ class PreservationTest(TestCase): self.assertRaises(errors.MissingRedirectURIError, self.mobile.create_authorization_response, auth_uri + '&response_type=token', scopes=['random']) + + def test_default_uri_in_token(self): + auth_uri = 'http://example.com/path?state=xyz&client_id=abc' + token_uri = 'http://example.com/path' + + # authorization grant + h, _, s = self.web.create_authorization_response( + auth_uri + '&response_type=code', scopes=['random']) + self.assertEqual(s, 302) + self.assertIn('Location', h) + self.assertTrue(h['Location'].startswith(self.DEFAULT_REDIRECT_URI)) + + # confirm_redirect_uri should return true if the redirect uri + # was not given in the authorization AND not in the token request. + self.validator.confirm_redirect_uri.return_value = True + code = get_query_credentials(h['Location'])['code'][0] + self.validator.validate_code.side_effect = self.set_state('xyz') + _, body, s = self.web.create_token_response(token_uri, + body='grant_type=authorization_code&code=%s' % code) + self.assertEqual(s, 200) + self.assertEqual(self.validator.confirm_redirect_uri.call_args[0][2], self.DEFAULT_REDIRECT_URI) -- cgit v1.2.1 From 79962015ab8d020a390aa4872777efcc727f5440 Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Mon, 30 Jul 2018 14:49:44 +0200 Subject: confirm_r. is called after auth_client --- tests/oauth2/rfc6749/endpoints/test_error_responses.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/oauth2/rfc6749/endpoints/test_error_responses.py b/tests/oauth2/rfc6749/endpoints/test_error_responses.py index 875b3a5..9f46f34 100644 --- a/tests/oauth2/rfc6749/endpoints/test_error_responses.py +++ b/tests/oauth2/rfc6749/endpoints/test_error_responses.py @@ -237,7 +237,6 @@ class ErrorResponseTest(TestCase): def test_access_denied(self): self.validator.authenticate_client.side_effect = self.set_client - self.validator.confirm_redirect_uri.return_value = False token_uri = 'https://i.b/token' # Authorization code grant _, body, _ = self.web.create_token_response(token_uri, -- cgit v1.2.1 From 0c4ce54b4bbae9fb7eb750d59c268eebe98e4e8a Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Mon, 30 Jul 2018 14:50:20 +0200 Subject: Removed silent output, since tests are not writing output it is useful when using pdb from commandline. --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 3dded41..ebd9021 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ envlist = py27,py34,py35,py36,pypy,docs [testenv] deps= -rrequirements-test.txt -commands=nosetests --with-coverage --cover-erase --cover-package=oauthlib -w tests +commands=nosetests -s --with-coverage --cover-erase --cover-package=oauthlib -w tests [testenv:py27] deps=unittest2 -- cgit v1.2.1 From 3faf434e8d670bf2763bbdc5135cbd7e747194f8 Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Wed, 15 Aug 2018 00:12:20 +0200 Subject: Restore confirm = False test --- tests/oauth2/rfc6749/endpoints/test_error_responses.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/oauth2/rfc6749/endpoints/test_error_responses.py b/tests/oauth2/rfc6749/endpoints/test_error_responses.py index 9f46f34..677b895 100644 --- a/tests/oauth2/rfc6749/endpoints/test_error_responses.py +++ b/tests/oauth2/rfc6749/endpoints/test_error_responses.py @@ -237,6 +237,8 @@ class ErrorResponseTest(TestCase): def test_access_denied(self): self.validator.authenticate_client.side_effect = self.set_client + self.validator.get_default_redirect_uri.return_value = 'https://i.b/cb' + self.validator.confirm_redirect_uri.return_value = False token_uri = 'https://i.b/token' # Authorization code grant _, body, _ = self.web.create_token_response(token_uri, -- cgit v1.2.1 From 058746b3d9bed4aafbd55a7f26491b5761c35fa8 Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Wed, 15 Aug 2018 00:15:40 +0200 Subject: Add test when no redirecturi & no default --- tests/oauth2/rfc6749/endpoints/test_error_responses.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/oauth2/rfc6749/endpoints/test_error_responses.py b/tests/oauth2/rfc6749/endpoints/test_error_responses.py index 677b895..00f7ba6 100644 --- a/tests/oauth2/rfc6749/endpoints/test_error_responses.py +++ b/tests/oauth2/rfc6749/endpoints/test_error_responses.py @@ -245,6 +245,15 @@ class ErrorResponseTest(TestCase): body='grant_type=authorization_code&code=foo') self.assertEqual('invalid_request', json.loads(body)['error']) + def test_access_denied_no_default_redirecturi(self): + self.validator.authenticate_client.side_effect = self.set_client + self.validator.get_default_redirect_uri.return_value = None + token_uri = 'https://i.b/token' + # Authorization code grant + _, body, _ = self.web.create_token_response(token_uri, + body='grant_type=authorization_code&code=foo') + self.assertEqual('invalid_request', json.loads(body)['error']) + def test_unsupported_response_type(self): self.validator.get_default_redirect_uri.return_value = 'https://i.b/cb' -- cgit v1.2.1 From 6b00071b793067dfff8b42391692123023966075 Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Sat, 18 Aug 2018 00:10:40 +0200 Subject: Change sentences for better SEO --- README.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.rst b/README.rst index 394a984..03129f7 100644 --- a/README.rst +++ b/README.rst @@ -1,5 +1,5 @@ -OAuthLib -======== +OAuthLib - Python Framework for OAuth1 & OAuth2 +=============================================== *A generic, spec-compliant, thorough implementation of the OAuth request-signing logic for Python 2.7 and 3.4+.* @@ -34,7 +34,7 @@ both of the following: .. _`OAuth 1.0 spec`: https://tools.ietf.org/html/rfc5849 .. _`OAuth 2.0 spec`: https://tools.ietf.org/html/rfc6749 -OAuthLib is a generic utility which implements the logic of OAuth without +OAuthLib is a framework which implements the logic of OAuth1 or OAuth2 without assuming a specific HTTP request object or web framework. Use it to graft OAuth client support onto your favorite HTTP library, or provide support onto your favourite web framework. If you're a maintainer of such a library, write a thin @@ -119,7 +119,7 @@ requests. Changelog --------- -*OAuthLib is in active development, with the core of both OAuth 1 and 2 +*OAuthLib is in active development, with the core of both OAuth1 and OAuth2 completed, for providers as well as clients.* See `supported features`_ for details. -- cgit v1.2.1 From 39dad84a9b4cfa353ec3ed60aa8f8856957f6704 Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Sat, 18 Aug 2018 01:02:52 +0200 Subject: Remove headers from request attributes --- oauthlib/common.py | 1 - tests/test_common.py | 5 +++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/oauthlib/common.py b/oauthlib/common.py index c1180e6..6364761 100644 --- a/oauthlib/common.py +++ b/oauthlib/common.py @@ -426,7 +426,6 @@ class Request(object): } self._params.update(dict(urldecode(self.uri_query))) self._params.update(dict(self.decoded_body or [])) - self._params.update(self.headers) def __getattr__(self, name): if name in self._params: diff --git a/tests/test_common.py b/tests/test_common.py index fb4bd5b..f239368 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -214,6 +214,11 @@ class RequestTest(TestCase): self.assertNotIn('bar', repr(r)) self.assertIn('', repr(r)) + def test_headers_params(self): + r = Request(URI, headers={'token': 'foobar'}, body='token=banana') + self.assertEqual(r.headers['token'], 'foobar') + self.assertEqual(r.token, 'banana') + class CaseInsensitiveDictTest(TestCase): -- cgit v1.2.1 From f3d3eb9efd81459be48b052e172ffa5f76a7a445 Mon Sep 17 00:00:00 2001 From: Omer Katz Date: Wed, 29 Aug 2018 14:28:39 +0300 Subject: Added license check badge. (#581) Since we're used in enterprises where licensing matters, this may be useful when people evaluate our project. --- README.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.rst b/README.rst index 03129f7..a84307f 100644 --- a/README.rst +++ b/README.rst @@ -16,6 +16,9 @@ logic for Python 2.7 and 3.4+.* .. image:: https://img.shields.io/pypi/l/oauthlib.svg :target: https://pypi.org/project/oauthlib/ :alt: License +.. image:: https://app.fossa.io/api/projects/git%2Bgithub.com%2Foauthlib%2Foauthlib.svg?type=shield + :target: https://app.fossa.io/projects/git%2Bgithub.com%2Foauthlib%2Foauthlib?ref=badge_shield + :alt: FOSSA Status .. image:: https://img.shields.io/readthedocs/oauthlib.svg :target: https://oauthlib.readthedocs.io/en/latest/index.html :alt: Read the Docs -- cgit v1.2.1 From 997e8d061ae883a6460aeda71ab12b2b5bd4feed Mon Sep 17 00:00:00 2001 From: Theron Luhn Date: Sun, 2 Sep 2018 10:13:08 -0700 Subject: Make scope optional for authorization code grant. --- oauthlib/oauth2/rfc6749/grant_types/authorization_code.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py b/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py index 43d2efa..ab4c184 100644 --- a/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py +++ b/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py @@ -140,7 +140,6 @@ class AuthorizationCodeGrant(GrantTypeBase): oauthlib.oauth2.BearerToken. :returns: headers, body, status :raises: FatalClientError on invalid redirect URI or client id. - ValueError if scopes are not set on the request object. A few examples:: @@ -151,12 +150,6 @@ class AuthorizationCodeGrant(GrantTypeBase): >>> from oauthlib.oauth2 import AuthorizationCodeGrant, BearerToken >>> token = BearerToken(your_validator) >>> grant = AuthorizationCodeGrant(your_validator) - >>> grant.create_authorization_response(request, token) - Traceback (most recent call last): - File "", line 1, in - File "oauthlib/oauth2/rfc6749/grant_types.py", line 513, in create_authorization_response - raise ValueError('Scopes must be set on post auth.') - ValueError: Scopes must be set on post auth. >>> request.scopes = ['authorized', 'in', 'some', 'form'] >>> grant.create_authorization_response(request, token) (u'http://client.com/?error=invalid_request&error_description=Missing+response_type+parameter.', None, None, 400) @@ -182,11 +175,6 @@ class AuthorizationCodeGrant(GrantTypeBase): .. _`Section 10.12`: https://tools.ietf.org/html/rfc6749#section-10.12 """ try: - # request.scopes is only mandated in post auth and both pre and - # post auth use validate_authorization_request - if not request.scopes: - raise ValueError('Scopes must be set on post auth.') - self.validate_authorization_request(request) log.debug('Pre resource owner authorization validation ok for %r.', request) -- cgit v1.2.1 From f7df56a9286b3fd06d636ef43ab3d4a4c86c1918 Mon Sep 17 00:00:00 2001 From: Theron Luhn Date: Sun, 2 Sep 2018 10:52:18 -0700 Subject: Fix test_error_catching. --- tests/oauth2/rfc6749/endpoints/test_base_endpoint.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/oauth2/rfc6749/endpoints/test_base_endpoint.py b/tests/oauth2/rfc6749/endpoints/test_base_endpoint.py index 4ad0ed9..4f78d9b 100644 --- a/tests/oauth2/rfc6749/endpoints/test_base_endpoint.py +++ b/tests/oauth2/rfc6749/endpoints/test_base_endpoint.py @@ -24,7 +24,9 @@ class BaseEndpointTest(TestCase): validator = RequestValidator() server = Server(validator) server.catch_errors = True - h, b, s = server.create_authorization_response('https://example.com') + h, b, s = server.create_token_response( + 'https://example.com?grant_type=authorization_code&code=abc' + ) self.assertIn("server_error", b) self.assertEqual(s, 500) -- cgit v1.2.1 From fd5c9790e8219fdc6a85b4837ba4f5a2eb265d09 Mon Sep 17 00:00:00 2001 From: Theron Luhn Date: Mon, 3 Sep 2018 22:19:30 -0700 Subject: Write a test for authorization grant w/ no scope. --- tests/oauth2/rfc6749/grant_types/test_authorization_code.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/oauth2/rfc6749/grant_types/test_authorization_code.py b/tests/oauth2/rfc6749/grant_types/test_authorization_code.py index 704a254..acb23ac 100644 --- a/tests/oauth2/rfc6749/grant_types/test_authorization_code.py +++ b/tests/oauth2/rfc6749/grant_types/test_authorization_code.py @@ -77,6 +77,12 @@ class AuthorizationCodeGrantTest(TestCase): self.assertTrue(self.mock_validator.validate_response_type.called) self.assertTrue(self.mock_validator.validate_scopes.called) + def test_create_authorization_grant_no_scopes(self): + bearer = BearerToken(self.mock_validator) + self.request.response_mode = 'query' + self.request.scopes = [] + self.auth.create_authorization_response(self.request, bearer) + def test_create_authorization_grant_state(self): self.request.state = 'abc' self.request.redirect_uri = None -- cgit v1.2.1 From e81ae772e4f260cc02ce07a7396470821ac63b1e Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Thu, 2 Aug 2018 00:54:54 +0200 Subject: Add support of custom errors coming from providers Fix #431. The inherent function "raise_from_error" is called when "error=" is found in the payload. So it MUST raise something, and until now, only RFC errors were raised. --- oauthlib/oauth2/rfc6749/errors.py | 12 ++++++++++++ tests/oauth2/rfc6749/test_parameters.py | 4 ++++ 2 files changed, 16 insertions(+) diff --git a/oauthlib/oauth2/rfc6749/errors.py b/oauthlib/oauth2/rfc6749/errors.py index 5a0cca2..8882ab2 100644 --- a/oauthlib/oauth2/rfc6749/errors.py +++ b/oauthlib/oauth2/rfc6749/errors.py @@ -313,6 +313,7 @@ class ConsentRequired(OAuth2Error): error = 'consent_required' status_code = 401 + class LoginRequired(OAuth2Error): """ The Authorization Server requires End-User authentication. @@ -325,6 +326,16 @@ class LoginRequired(OAuth2Error): status_code = 401 +class CustomOAuth2Error(OAuth2Error): + """ + This error is a placeholder for all custom errors not described by the RFC. + Some of the popular OAuth2 providers are using custom errors. + """ + def __init__(self, error, *args, **kwargs): + self.error = error + super().__init__(*args, **kwargs) + + def raise_from_error(error, params=None): import inspect import sys @@ -336,3 +347,4 @@ def raise_from_error(error, params=None): for _, cls in inspect.getmembers(sys.modules[__name__], inspect.isclass): if cls.error == error: raise cls(**kwargs) + raise CustomOAuth2Error(error=error, **kwargs) diff --git a/tests/oauth2/rfc6749/test_parameters.py b/tests/oauth2/rfc6749/test_parameters.py index b211d1e..c42f516 100644 --- a/tests/oauth2/rfc6749/test_parameters.py +++ b/tests/oauth2/rfc6749/test_parameters.py @@ -103,6 +103,7 @@ class ParameterTests(TestCase): ' "refresh_token": "tGzv3JOkF0XG5Qx2TlKWIA",' ' "example_parameter": "example_value" }') + json_custom_error = '{ "error": "incorrect_client_credentials" }' json_error = '{ "error": "access_denied" }' json_notoken = ('{ "token_type": "example",' @@ -197,6 +198,9 @@ class ParameterTests(TestCase): self.assertRaises(ValueError, parse_implicit_response, self.implicit_wrongstate, state=self.state) + def test_custom_json_error(self): + self.assertRaises(CustomOAuth2Error, parse_token_response, self.json_custom_error) + def test_json_token_response(self): """Verify correct parameter parsing and validation for token responses. """ self.assertEqual(parse_token_response(self.json_response), self.json_dict) -- cgit v1.2.1 From 346bf28816da5705e7681a886247c0f32884723b Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Thu, 2 Aug 2018 10:04:13 +0200 Subject: Fixed py27/pypy support --- oauthlib/oauth2/rfc6749/errors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oauthlib/oauth2/rfc6749/errors.py b/oauthlib/oauth2/rfc6749/errors.py index 8882ab2..a15d6c5 100644 --- a/oauthlib/oauth2/rfc6749/errors.py +++ b/oauthlib/oauth2/rfc6749/errors.py @@ -333,7 +333,7 @@ class CustomOAuth2Error(OAuth2Error): """ def __init__(self, error, *args, **kwargs): self.error = error - super().__init__(*args, **kwargs) + super(CustomOAuth2Error, self).__init__(*args, **kwargs) def raise_from_error(error, params=None): -- cgit v1.2.1 From 5a9d8d92d3453355de86d614337affe69543207d Mon Sep 17 00:00:00 2001 From: jonathan vanasco Date: Mon, 10 Sep 2018 17:00:16 -0400 Subject: redid the docstring fixes --- oauthlib/oauth1/rfc5849/endpoints/access_token.py | 6 +- oauthlib/oauth1/rfc5849/endpoints/authorization.py | 3 +- oauthlib/oauth1/rfc5849/endpoints/request_token.py | 6 +- oauthlib/oauth1/rfc5849/request_validator.py | 63 +++++--- .../rfc6749/grant_types/authorization_code.py | 23 ++- oauthlib/oauth2/rfc6749/grant_types/base.py | 35 ++++- .../rfc6749/grant_types/client_credentials.py | 9 ++ oauthlib/oauth2/rfc6749/grant_types/implicit.py | 18 +++ .../oauth2/rfc6749/grant_types/refresh_token.py | 9 ++ .../resource_owner_password_credentials.py | 8 + oauthlib/oauth2/rfc6749/parameters.py | 25 +-- oauthlib/oauth2/rfc6749/request_validator.py | 167 +++++++++++++-------- oauthlib/oauth2/rfc6749/tokens.py | 53 ++++++- oauthlib/openid/connect/core/request_validator.py | 21 ++- 14 files changed, 331 insertions(+), 115 deletions(-) diff --git a/oauthlib/oauth1/rfc5849/endpoints/access_token.py b/oauthlib/oauth1/rfc5849/endpoints/access_token.py index 12d13e9..bea8274 100644 --- a/oauthlib/oauth1/rfc5849/endpoints/access_token.py +++ b/oauthlib/oauth1/rfc5849/endpoints/access_token.py @@ -37,7 +37,8 @@ class AccessTokenEndpoint(BaseEndpoint): Similar to OAuth 2, indication of granted scopes will be included as a space separated list in ``oauth_authorized_realms``. - :param request: An oauthlib.common.Request object. + :param request: OAuthlib request. + :type request: oauthlib.common.Request :returns: The token as an urlencoded string. """ request.realms = self.request_validator.get_realms( @@ -120,7 +121,8 @@ class AccessTokenEndpoint(BaseEndpoint): def validate_access_token_request(self, request): """Validate an access token request. - :param request: An oauthlib.common.Request object. + :param request: OAuthlib request. + :type request: oauthlib.common.Request :raises: OAuth1Error if the request is invalid. :returns: A tuple of 2 elements. 1. The validation result (True or False). diff --git a/oauthlib/oauth1/rfc5849/endpoints/authorization.py b/oauthlib/oauth1/rfc5849/endpoints/authorization.py index 1751a45..b465946 100644 --- a/oauthlib/oauth1/rfc5849/endpoints/authorization.py +++ b/oauthlib/oauth1/rfc5849/endpoints/authorization.py @@ -42,7 +42,8 @@ class AuthorizationEndpoint(BaseEndpoint): def create_verifier(self, request, credentials): """Create and save a new request token. - :param request: An oauthlib.common.Request object. + :param request: OAuthlib request. + :type request: oauthlib.common.Request :param credentials: A dict of extra token credentials. :returns: The verifier as a dict. """ diff --git a/oauthlib/oauth1/rfc5849/endpoints/request_token.py b/oauthlib/oauth1/rfc5849/endpoints/request_token.py index 88fd6c0..e9ca331 100644 --- a/oauthlib/oauth1/rfc5849/endpoints/request_token.py +++ b/oauthlib/oauth1/rfc5849/endpoints/request_token.py @@ -34,7 +34,8 @@ class RequestTokenEndpoint(BaseEndpoint): def create_request_token(self, request, credentials): """Create and save a new request token. - :param request: An oauthlib.common.Request object. + :param request: OAuthlib request. + :type request: oauthlib.common.Request :param credentials: A dict of extra token credentials. :returns: The token as an urlencoded string. """ @@ -111,7 +112,8 @@ class RequestTokenEndpoint(BaseEndpoint): def validate_request_token_request(self, request): """Validate a request token request. - :param request: An oauthlib.common.Request object. + :param request: OAuthlib request. + :type request: oauthlib.common.Request :raises: OAuth1Error if the request is invalid. :returns: A tuple of 2 elements. 1. The validation result (True or False). diff --git a/oauthlib/oauth1/rfc5849/request_validator.py b/oauthlib/oauth1/rfc5849/request_validator.py index bc62ea0..330bcbb 100644 --- a/oauthlib/oauth1/rfc5849/request_validator.py +++ b/oauthlib/oauth1/rfc5849/request_validator.py @@ -267,7 +267,8 @@ class RequestValidator(object): """Retrieves the client secret associated with the client key. :param client_key: The client/consumer key. - :param request: An oauthlib.common.Request object. + :param request: OAuthlib request. + :type request: oauthlib.common.Request :returns: The client secret as a string. This method must allow the use of a dummy client_key value. @@ -303,7 +304,8 @@ class RequestValidator(object): :param client_key: The client/consumer key. :param token: The request token string. - :param request: An oauthlib.common.Request object. + :param request: OAuthlib request. + :type request: oauthlib.common.Request :returns: The token secret as a string. This method must allow the use of a dummy values and the running time @@ -335,7 +337,8 @@ class RequestValidator(object): :param client_key: The client/consumer key. :param token: The access token string. - :param request: An oauthlib.common.Request object. + :param request: OAuthlib request. + :type request: oauthlib.common.Request :returns: The token secret as a string. This method must allow the use of a dummy values and the running time @@ -366,7 +369,8 @@ class RequestValidator(object): """Get the default realms for a client. :param client_key: The client/consumer key. - :param request: An oauthlib.common.Request object. + :param request: OAuthlib request. + :type request: oauthlib.common.Request :returns: The list of default realms associated with the client. The list of default realms will be set during client registration and @@ -382,7 +386,8 @@ class RequestValidator(object): """Get realms associated with a request token. :param token: The request token string. - :param request: An oauthlib.common.Request object. + :param request: OAuthlib request. + :type request: oauthlib.common.Request :returns: The list of realms associated with the request token. This method is used by @@ -396,7 +401,8 @@ class RequestValidator(object): """Get the redirect URI associated with a request token. :param token: The request token string. - :param request: An oauthlib.common.Request object. + :param request: OAuthlib request. + :type request: oauthlib.common.Request :returns: The redirect URI associated with the request token. It may be desirable to return a custom URI if the redirect is set to "oob". @@ -413,7 +419,8 @@ class RequestValidator(object): """Retrieves a previously stored client provided RSA key. :param client_key: The client/consumer key. - :param request: An oauthlib.common.Request object. + :param request: OAuthlib request. + :type request: oauthlib.common.Request :returns: The rsa public key as a string. This method must allow the use of a dummy client_key value. Fetching @@ -437,7 +444,8 @@ class RequestValidator(object): :param client_key: The client/consumer key. :param request_token: The request token string. - :param request: An oauthlib.common.Request object. + :param request: OAuthlib request. + :type request: oauthlib.common.Request :returns: None Per `Section 2.3`__ of the spec: @@ -462,7 +470,8 @@ class RequestValidator(object): """Validates that supplied client key is a registered and valid client. :param client_key: The client/consumer key. - :param request: An oauthlib.common.Request object. + :param request: OAuthlib request. + :type request: oauthlib.common.Request :returns: True or False Note that if the dummy client is supplied it should validate in same @@ -499,7 +508,8 @@ class RequestValidator(object): :param client_key: The client/consumer key. :param token: The request token string. - :param request: An oauthlib.common.Request object. + :param request: OAuthlib request. + :type request: oauthlib.common.Request :returns: True or False Note that if the dummy request_token is supplied it should validate in @@ -533,7 +543,8 @@ class RequestValidator(object): :param client_key: The client/consumer key. :param token: The access token string. - :param request: An oauthlib.common.Request object. + :param request: OAuthlib request. + :type request: oauthlib.common.Request :returns: True or False Note that if the dummy access token is supplied it should validate in @@ -571,7 +582,8 @@ class RequestValidator(object): :param nonce: The ``oauth_nonce`` parameter. :param request_token: Request token string, if any. :param access_token: Access token string, if any. - :param request: An oauthlib.common.Request object. + :param request: OAuthlib request. + :type request: oauthlib.common.Request :returns: True or False Per `Section 3.3`_ of the spec. @@ -618,7 +630,8 @@ class RequestValidator(object): :param client_key: The client/consumer key. :param redirect_uri: The URI the client which to redirect back to after authorization is successful. - :param request: An oauthlib.common.Request object. + :param request: OAuthlib request. + :type request: oauthlib.common.Request :returns: True or False It is highly recommended that OAuth providers require their clients @@ -650,7 +663,8 @@ class RequestValidator(object): :param client_key: The client/consumer key. :param realms: The list of realms that client is requesting access to. - :param request: An oauthlib.common.Request object. + :param request: OAuthlib request. + :type request: oauthlib.common.Request :returns: True or False This method is invoked when obtaining a request token and should @@ -669,7 +683,8 @@ class RequestValidator(object): :param client_key: The client/consumer key. :param token: A request token string. - :param request: An oauthlib.common.Request object. + :param request: OAuthlib request. + :type request: oauthlib.common.Request :param uri: The URI the realms is protecting. :param realms: A list of realms that must have been granted to the access token. @@ -703,7 +718,8 @@ class RequestValidator(object): :param client_key: The client/consumer key. :param token: A request token string. :param verifier: The authorization verifier string. - :param request: An oauthlib.common.Request object. + :param request: OAuthlib request. + :type request: oauthlib.common.Request :returns: True or False OAuth providers issue a verification code to clients after the @@ -732,7 +748,8 @@ class RequestValidator(object): """Verify that the given OAuth1 request token is valid. :param token: A request token string. - :param request: An oauthlib.common.Request object. + :param request: OAuthlib request. + :type request: oauthlib.common.Request :returns: True or False This method is used only in AuthorizationEndpoint to check whether the @@ -751,7 +768,8 @@ class RequestValidator(object): :param token: An access token string. :param realms: A list of realms the client attempts to access. - :param request: An oauthlib.common.Request object. + :param request: OAuthlib request. + :type request: oauthlib.common.Request :returns: True or False This prevents the list of authorized realms sent by the client during @@ -773,7 +791,8 @@ class RequestValidator(object): """Save an OAuth1 access token. :param token: A dict with token credentials. - :param request: An oauthlib.common.Request object. + :param request: OAuthlib request. + :type request: oauthlib.common.Request The token dictionary will at minimum include @@ -796,7 +815,8 @@ class RequestValidator(object): """Save an OAuth1 request token. :param token: A dict with token credentials. - :param request: An oauthlib.common.Request object. + :param request: OAuthlib request. + :type request: oauthlib.common.Request The token dictionary will at minimum include @@ -818,7 +838,8 @@ class RequestValidator(object): :param token: A request token string. :param verifier A dictionary containing the oauth_verifier and oauth_token - :param request: An oauthlib.common.Request object. + :param request: OAuthlib request. + :type request: oauthlib.common.Request We need to associate verifiers with tokens for validation during the access token request. diff --git a/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py b/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py index ab4c184..59366b1 100644 --- a/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py +++ b/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py @@ -98,7 +98,12 @@ class AuthorizationCodeGrant(GrantTypeBase): response_types = ['code'] def create_authorization_code(self, request): - """Generates an authorization grant represented as a dictionary.""" + """ + Generates an authorization grant represented as a dictionary. + + :param request: OAuthlib request. + :type request: oauthlib.common.Request + """ grant = {'code': common.generate_token()} if hasattr(request, 'state') and request.state: grant['state'] = request.state @@ -135,7 +140,8 @@ class AuthorizationCodeGrant(GrantTypeBase): HTTP redirection response, or by other means available to it via the user-agent. - :param request: oauthlib.commong.Request + :param request: OAuthlib request. + :type request: oauthlib.common.Request :param token_handler: A token handler instace, for example of type oauthlib.oauth2.BearerToken. :returns: headers, body, status @@ -220,6 +226,12 @@ class AuthorizationCodeGrant(GrantTypeBase): MUST deny the request and SHOULD revoke (when possible) all tokens previously issued based on that authorization code. The authorization code is bound to the client identifier and redirection URI. + + :param request: OAuthlib request. + :type request: oauthlib.common.Request + :param token_handler: A token handler instace, for example of type + oauthlib.oauth2.BearerToken. + """ headers = { 'Content-Type': 'application/json', @@ -253,6 +265,9 @@ class AuthorizationCodeGrant(GrantTypeBase): missing. These must be caught by the provider and handled, how this is done is outside of the scope of OAuthLib but showing an error page describing the issue is a good idea. + + :param request: OAuthlib request. + :type request: oauthlib.common.Request """ # First check for fatal errors @@ -353,6 +368,10 @@ class AuthorizationCodeGrant(GrantTypeBase): return request.scopes, request_info def validate_token_request(self, request): + """ + :param request: OAuthlib request. + :type request: oauthlib.common.Request + """ # REQUIRED. Value MUST be set to "authorization_code". if request.grant_type not in ('authorization_code', 'openid'): raise errors.UnsupportedGrantTypeError(request=request) diff --git a/oauthlib/oauth2/rfc6749/grant_types/base.py b/oauthlib/oauth2/rfc6749/grant_types/base.py index e5d8ddd..4d9381c 100644 --- a/oauthlib/oauth2/rfc6749/grant_types/base.py +++ b/oauthlib/oauth2/rfc6749/grant_types/base.py @@ -116,14 +116,32 @@ class GrantTypeBase(object): def register_token_modifier(self, modifier): self._token_modifiers.append(modifier) - def create_authorization_response(self, request, token_handler): + """ + :param request: OAuthlib request. + :type request: oauthlib.common.Request + :param token_handler: A token handler instace, for example of type + oauthlib.oauth2.BearerToken. + """ raise NotImplementedError('Subclasses must implement this method.') def create_token_response(self, request, token_handler): + """ + :param request: OAuthlib request. + :type request: oauthlib.common.Request + :param token_handler: A token handler instace, for example of type + oauthlib.oauth2.BearerToken. + """ raise NotImplementedError('Subclasses must implement this method.') def add_token(self, token, token_handler, request): + """ + :param token: + :param token_handler: A token handler instace, for example of type + oauthlib.oauth2.BearerToken. + :param request: OAuthlib request. + :type request: oauthlib.common.Request + """ # Only add a hybrid access token on auth step if asked for if not request.response_type in ["token", "code token", "id_token token", "code id_token token"]: return token @@ -132,6 +150,10 @@ class GrantTypeBase(object): return token def validate_grant_type(self, request): + """ + :param request: OAuthlib request. + :type request: oauthlib.common.Request + """ client_id = getattr(request, 'client_id', None) if not self.request_validator.validate_grant_type(client_id, request.grant_type, request.client, request): @@ -140,6 +162,10 @@ class GrantTypeBase(object): raise errors.UnauthorizedClientError(request=request) def validate_scopes(self, request): + """ + :param request: OAuthlib request. + :type request: oauthlib.common.Request + """ if not request.scopes: request.scopes = utils.scope_to_list(request.scope) or utils.scope_to_list( self.request_validator.get_default_scopes(request.client_id, request)) @@ -154,6 +180,13 @@ class GrantTypeBase(object): Base classes can define a default response mode for their authorization response by overriding the static `default_response_mode` member. + + :param request: OAuthlib request. + :type request: oauthlib.common.Request + :param token: + :param headers: + :param body: + :param status: """ request.response_mode = request.response_mode or self.default_response_mode diff --git a/oauthlib/oauth2/rfc6749/grant_types/client_credentials.py b/oauthlib/oauth2/rfc6749/grant_types/client_credentials.py index 4c50a78..884363f 100644 --- a/oauthlib/oauth2/rfc6749/grant_types/client_credentials.py +++ b/oauthlib/oauth2/rfc6749/grant_types/client_credentials.py @@ -53,6 +53,11 @@ class ClientCredentialsGrant(GrantTypeBase): def create_token_response(self, request, token_handler): """Return token or error in JSON format. + :param request: OAuthlib request. + :type request: oauthlib.common.Request + :param token_handler: A token handler instace, for example of type + oauthlib.oauth2.BearerToken. + If the access token request is valid and authorized, the authorization server issues an access token as described in `Section 5.1`_. A refresh token SHOULD NOT be included. If the request @@ -85,6 +90,10 @@ class ClientCredentialsGrant(GrantTypeBase): return headers, json.dumps(token), 200 def validate_token_request(self, request): + """ + :param request: OAuthlib request. + :type request: oauthlib.common.Request + """ for validator in self.custom_validators.pre_token: validator(request) diff --git a/oauthlib/oauth2/rfc6749/grant_types/implicit.py b/oauthlib/oauth2/rfc6749/grant_types/implicit.py index 3a5c058..600c0a5 100644 --- a/oauthlib/oauth2/rfc6749/grant_types/implicit.py +++ b/oauthlib/oauth2/rfc6749/grant_types/implicit.py @@ -121,6 +121,12 @@ class ImplicitGrant(GrantTypeBase): def create_authorization_response(self, request, token_handler): """Create an authorization response. + + :param request: OAuthlib request. + :type request: oauthlib.common.Request + :param token_handler: A token handler instace, for example of type + oauthlib.oauth2.BearerToken. + The client constructs the request URI by adding the following parameters to the query component of the authorization endpoint URI using the "application/x-www-form-urlencoded" format, per `Appendix B`_: @@ -163,6 +169,11 @@ class ImplicitGrant(GrantTypeBase): def create_token_response(self, request, token_handler): """Return token or error embedded in the URI fragment. + :param request: OAuthlib request. + :type request: oauthlib.common.Request + :param token_handler: A token handler instace, for example of type + oauthlib.oauth2.BearerToken. + If the resource owner grants the access request, the authorization server issues an access token and delivers it to the client by adding the following parameters to the fragment component of the redirection @@ -243,11 +254,18 @@ class ImplicitGrant(GrantTypeBase): request, token, {}, None, 302) def validate_authorization_request(self, request): + """ + :param request: OAuthlib request. + :type request: oauthlib.common.Request + """ return self.validate_token_request(request) def validate_token_request(self, request): """Check the token request for normal and fatal errors. + :param request: OAuthlib request. + :type request: oauthlib.common.Request + This method is very similar to validate_authorization_request in the AuthorizationCodeGrant but differ in a few subtle areas. diff --git a/oauthlib/oauth2/rfc6749/grant_types/refresh_token.py b/oauthlib/oauth2/rfc6749/grant_types/refresh_token.py index c2d86f7..55ddbb2 100644 --- a/oauthlib/oauth2/rfc6749/grant_types/refresh_token.py +++ b/oauthlib/oauth2/rfc6749/grant_types/refresh_token.py @@ -33,6 +33,11 @@ class RefreshTokenGrant(GrantTypeBase): def create_token_response(self, request, token_handler): """Create a new access token from a refresh_token. + :param request: OAuthlib request. + :type request: oauthlib.common.Request + :param token_handler: A token handler instace, for example of type + oauthlib.oauth2.BearerToken. + If valid and authorized, the authorization server issues an access token as described in `Section 5.1`_. If the request failed verification or is invalid, the authorization server returns an error @@ -72,6 +77,10 @@ class RefreshTokenGrant(GrantTypeBase): return headers, json.dumps(token), 200 def validate_token_request(self, request): + """ + :param request: OAuthlib request. + :type request: oauthlib.common.Request + """ # REQUIRED. Value MUST be set to "refresh_token". if request.grant_type != 'refresh_token': raise errors.UnsupportedGrantTypeError(request=request) diff --git a/oauthlib/oauth2/rfc6749/grant_types/resource_owner_password_credentials.py b/oauthlib/oauth2/rfc6749/grant_types/resource_owner_password_credentials.py index e5f04af..25fb1f1 100644 --- a/oauthlib/oauth2/rfc6749/grant_types/resource_owner_password_credentials.py +++ b/oauthlib/oauth2/rfc6749/grant_types/resource_owner_password_credentials.py @@ -73,6 +73,11 @@ class ResourceOwnerPasswordCredentialsGrant(GrantTypeBase): def create_token_response(self, request, token_handler): """Return token or error in json format. + :param request: OAuthlib request. + :type request: oauthlib.common.Request + :param token_handler: A token handler instace, for example of type + oauthlib.oauth2.BearerToken. + If the access token request is valid and authorized, the authorization server issues an access token and optional refresh token as described in `Section 5.1`_. If the request failed client @@ -114,6 +119,9 @@ class ResourceOwnerPasswordCredentialsGrant(GrantTypeBase): def validate_token_request(self, request): """ + :param request: OAuthlib request. + :type request: oauthlib.common.Request + The client makes a request to the token endpoint by adding the following parameters using the "application/x-www-form-urlencoded" format per Appendix B with a character encoding of UTF-8 in the HTTP diff --git a/oauthlib/oauth2/rfc6749/parameters.py b/oauthlib/oauth2/rfc6749/parameters.py index c5127e7..3f18733 100644 --- a/oauthlib/oauth2/rfc6749/parameters.py +++ b/oauthlib/oauth2/rfc6749/parameters.py @@ -37,14 +37,13 @@ def prepare_grant_uri(uri, client_id, response_type, redirect_uri=None, using the ``application/x-www-form-urlencoded`` format as defined by [`W3C.REC-html401-19991224`_]: + :param client_id: The client identifier as described in `Section 2.2`_. :param response_type: To indicate which OAuth 2 grant/flow is required, "code" and "token". - :param client_id: The client identifier as described in `Section 2.2`_. :param redirect_uri: The client provided URI to redirect back to after authorization as described in `Section 3.1.2`_. :param scope: The scope of the access request as described by `Section 3.3`_. - :param state: An opaque value used by the client to maintain state between the request and callback. The authorization server includes this value when redirecting the user-agent @@ -133,15 +132,19 @@ def prepare_token_revocation_request(url, token, token_type_hint="access_token", using the "application/x-www-form-urlencoded" format in the HTTP request entity-body: - token REQUIRED. The token that the client wants to get revoked. - - token_type_hint OPTIONAL. A hint about the type of the token submitted - for revocation. Clients MAY pass this parameter in order to help the - authorization server to optimize the token lookup. If the server is unable - to locate the token using the given hint, it MUST extend its search across - all of its supported token types. An authorization server MAY ignore this - parameter, particularly if it is able to detect the token type - automatically. This specification defines two such values: + :param token: REQUIRED. The token that the client wants to get revoked. + + param:token_type_hint: OPTIONAL. A hint about the type of the token + submitted for revocation. Clients MAY pass this + parameter in order to help the authorization server + to optimize the token lookup. If the server is + unable to locate the token using the given hint, it + MUST extend its search across all of its supported + token types. An authorization server MAY ignore this + parameter, particularly if it is able to detect the + token type automatically. + + This specification defines two values for `token_type_hint`: * access_token: An access token as defined in [RFC6749], `Section 1.4`_ diff --git a/oauthlib/oauth2/rfc6749/request_validator.py b/oauthlib/oauth2/rfc6749/request_validator.py index ff3bbd6..6ce7910 100644 --- a/oauthlib/oauth2/rfc6749/request_validator.py +++ b/oauthlib/oauth2/rfc6749/request_validator.py @@ -26,7 +26,8 @@ class RequestValidator(object): client credentials or whenever Client provided client authentication, see `Section 6`_ - :param request: oauthlib.common.Request + :param request: OAuthlib request. + :type request: oauthlib.common.Request :rtype: True or False Method is used by: @@ -51,7 +52,8 @@ class RequestValidator(object): both body and query can be obtained by direct attribute access, i.e. request.client_id for client_id in the URL query. - :param request: oauthlib.common.Request + :param request: OAuthlib request. + :type request: oauthlib.common.Request :rtype: True or False Method is used by: @@ -74,7 +76,9 @@ class RequestValidator(object): to set request.client to the client object associated with the given client_id. - :param request: oauthlib.common.Request + :param client_id: Unicode client identifier. + :param request: OAuthlib request. + :type request: oauthlib.common.Request :rtype: True or False Method is used by: @@ -93,11 +97,12 @@ class RequestValidator(object): the client's allowed redirect URIs, but against the URI used when the code was saved. - :param client_id: Unicode client identifier + :param client_id: Unicode client identifier. :param code: Unicode authorization_code. - :param redirect_uri: Unicode absolute URI + :param redirect_uri: Unicode absolute URI. :param client: Client object set by you, see authenticate_client. - :param request: The HTTP Request (oauthlib.common.Request) + :param request: OAuthlib request. + :type request: oauthlib.common.Request :rtype: True or False Method is used by: @@ -108,8 +113,9 @@ class RequestValidator(object): def get_default_redirect_uri(self, client_id, request, *args, **kwargs): """Get the default redirect URI for the client. - :param client_id: Unicode client identifier - :param request: The HTTP Request (oauthlib.common.Request) + :param client_id: Unicode client identifier. + :param request: OAuthlib request. + :type request: oauthlib.common.Request :rtype: The default redirect URI for the client Method is used by: @@ -121,8 +127,9 @@ class RequestValidator(object): def get_default_scopes(self, client_id, request, *args, **kwargs): """Get the default scopes for the client. - :param client_id: Unicode client identifier - :param request: The HTTP Request (oauthlib.common.Request) + :param client_id: Unicode client identifier. + :param request: OAuthlib request. + :type request: oauthlib.common.Request :rtype: List of default scopes Method is used by all core grant types: @@ -136,8 +143,9 @@ class RequestValidator(object): def get_original_scopes(self, refresh_token, request, *args, **kwargs): """Get the list of scopes associated with the refresh token. - :param refresh_token: Unicode refresh token - :param request: The HTTP Request (oauthlib.common.Request) + :param refresh_token: Unicode refresh token. + :param request: OAuthlib request. + :type request: oauthlib.common.Request :rtype: List of scopes. Method is used by: @@ -156,9 +164,10 @@ class RequestValidator(object): used in situations where returning all valid scopes from the get_original_scopes is not practical. - :param request_scopes: A list of scopes that were requested by client - :param refresh_token: Unicode refresh_token - :param request: The HTTP Request (oauthlib.common.Request) + :param request_scopes: A list of scopes that were requested by client. + :param refresh_token: Unicode refresh_token. + :param request: OAuthlib request. + :type request: oauthlib.common.Request :rtype: True or False Method is used by: @@ -196,7 +205,8 @@ class RequestValidator(object): :param token: The token string. :param token_type_hint: access_token or refresh_token. - :param request: The HTTP Request (oauthlib.common.Request) + :param request: OAuthlib request. + :type request: oauthlib.common.Request Method is used by: - Introspect Endpoint (all grants are compatible) @@ -209,9 +219,10 @@ class RequestValidator(object): def invalidate_authorization_code(self, client_id, code, request, *args, **kwargs): """Invalidate an authorization code after use. - :param client_id: Unicode client identifier + :param client_id: Unicode client identifier. :param code: The authorization code grant (request.code). - :param request: The HTTP Request (oauthlib.common.Request) + :param request: OAuthlib request. + :type request: oauthlib.common.Request Method is used by: - Authorization Code Grant @@ -223,7 +234,8 @@ class RequestValidator(object): :param token: The token string. :param token_type_hint: access_token or refresh_token. - :param request: The HTTP Request (oauthlib.common.Request) + :param request: OAuthlib request. + :type request: oauthlib.common.Request Method is used by: - Revocation Endpoint @@ -237,7 +249,8 @@ class RequestValidator(object): or replaced with a new one (rotated). Return True to rotate and and False for keeping original. - :param request: oauthlib.common.Request + :param request: OAuthlib request. + :type request: oauthlib.common.Request :rtype: True or False Method is used by: @@ -269,9 +282,10 @@ class RequestValidator(object): http://openid.net/specs/openid-connect-core-1_0.html#ClaimsParameter This value should be saved in this method and used again in 'validate_code'. - :param client_id: Unicode client identifier + :param client_id: Unicode client identifier. :param code: A dict of the authorization code grant and, optionally, state. - :param request: The HTTP Request (oauthlib.common.Request) + :param request: OAuthlib request. + :type request: oauthlib.common.Request Method is used by: - Authorization Code Grant @@ -292,10 +306,12 @@ class RequestValidator(object): blank value `""` don't forget to check it before using those values in a select query if a database is used. - :param client_id: Unicode client identifier - :param code: Unicode authorization code grant - :param redirect_uri: Unicode absolute URI - :return: A list of scope + :param client_id: Unicode client identifier. + :param code: Unicode authorization code grant. + :param redirect_uri: Unicode absolute URI. + :param request: OAuthlib request. + :type request: oauthlib.common.Request + :return: A list of scopes Method is used by: - Authorization Token Grant Dispatcher @@ -306,6 +322,10 @@ class RequestValidator(object): """Persist the token with a token type specific method. Currently, only save_bearer_token is supported. + + :param token: A (Bearer) token dict. + :param request: OAuthlib request. + :type request: oauthlib.common.Request """ return self.save_bearer_token(token, request, *args, **kwargs) @@ -346,8 +366,9 @@ class RequestValidator(object): the claims dict, which should be saved for later use when generating the id_token and/or UserInfo response content. - :param token: A Bearer token dict - :param request: The HTTP Request (oauthlib.common.Request) + :param token: A Bearer token dict. + :param request: OAuthlib request. + :type request: oauthlib.common.Request :rtype: The default redirect URI for the client Method is used by all core grant types issuing Bearer tokens: @@ -363,9 +384,10 @@ class RequestValidator(object): If using OpenID Connect this SHOULD call `oauthlib.oauth2.RequestValidator.get_id_token` - :param token: A Bearer token dict - :param token_handler: the token handler (BearerToken class) - :param request: the HTTP Request (oauthlib.common.Request) + :param token: A Bearer token dict. + :param token_handler: The token handler (BearerToken class). + :param request: OAuthlib request. + :type request: oauthlib.common.Request :return: The JWT Bearer token or OpenID Connect ID token (a JWS signed JWT) Method is used by JWT Bearer and OpenID Connect tokens: @@ -398,9 +420,10 @@ class RequestValidator(object): .. _`3.2.2.10`: http://openid.net/specs/openid-connect-core-1_0.html#ImplicitIDToken .. _`3.3.2.11`: http://openid.net/specs/openid-connect-core-1_0.html#HybridIDToken - :param token: A Bearer token dict - :param token_handler: the token handler (BearerToken class) - :param request: the HTTP Request (oauthlib.common.Request) + :param token: A Bearer token dict. + :param token_handler: The token handler (BearerToken class) + :param request: OAuthlib request. + :type request: oauthlib.common.Request :return: The ID Token (a JWS signed JWT) """ # the request.scope should be used by the get_id_token() method to determine which claims to include in the resulting id_token @@ -419,9 +442,10 @@ class RequestValidator(object): - http://openid.net/specs/openid-connect-core-1_0.html#HybridIDTValidation - http://openid.net/specs/openid-connect-core-1_0.html#HybridIDTValidation2 - :param token: Unicode Bearer token - :param scopes: List of scopes (defined by you) - :param request: The HTTP Request (oauthlib.common.Request) + :param token: Unicode Bearer token. + :param scopes: List of scopes (defined by you). + :param request: OAuthlib request. + :type request: oauthlib.common.Request :rtype: True or False Method is indirectly used by all core OpenID connect JWT token issuing grant types: @@ -440,9 +464,10 @@ class RequestValidator(object): - http://openid.net/specs/openid-connect-core-1_0.html#HybridIDTValidation - http://openid.net/specs/openid-connect-core-1_0.html#HybridIDTValidation2 - :param token: Unicode Bearer token - :param scopes: List of scopes (defined by you) - :param request: The HTTP Request (oauthlib.common.Request) + :param token: Unicode Bearer token. + :param scopes: List of scopes (defined by you). + :param request: OAuthlib request. + :type request: oauthlib.common.Request :rtype: True or False Method is indirectly used by all core OpenID connect JWT token issuing grant types: @@ -457,7 +482,8 @@ class RequestValidator(object): :param token: A string of random characters. :param scopes: A list of scopes associated with the protected resource. - :param request: The HTTP Request (oauthlib.common.Request) + :param request: OAuthlib request. + :type request: oauthlib.common.Request A key to OAuth 2 security and restricting impact of leaked tokens is the short expiration time of tokens, *always ensure the token has not @@ -491,7 +517,8 @@ class RequestValidator(object): :param token: Unicode Bearer token :param scopes: List of scopes (defined by you) - :param request: The HTTP Request (oauthlib.common.Request) + :param request: OAuthlib request. + :type request: oauthlib.common.Request :rtype: True or False Method is indirectly used by all core Bearer token issuing grant types: @@ -509,7 +536,9 @@ class RequestValidator(object): to set request.client to the client object associated with the given client_id. - :param request: oauthlib.common.Request + :param client_id: Unicode client identifier. + :param request: OAuthlib request. + :type request: oauthlib.common.Request :rtype: True or False Method is used by: @@ -535,10 +564,11 @@ class RequestValidator(object): The request.claims property, if it was given, should assigned a dict. - :param client_id: Unicode client identifier - :param code: Unicode authorization code + :param client_id: Unicode client identifier. + :param code: Unicode authorization code. :param client: Client object set by you, see authenticate_client. - :param request: The HTTP Request (oauthlib.common.Request) + :param request: OAuthlib request. + :type request: oauthlib.common.Request :rtype: True or False Method is used by: @@ -549,10 +579,11 @@ class RequestValidator(object): def validate_grant_type(self, client_id, grant_type, client, request, *args, **kwargs): """Ensure client is authorized to use the grant_type requested. - :param client_id: Unicode client identifier + :param client_id: Unicode client identifier. :param grant_type: Unicode grant type, i.e. authorization_code, password. :param client: Client object set by you, see authenticate_client. - :param request: The HTTP Request (oauthlib.common.Request) + :param request: OAuthlib request. + :type request: oauthlib.common.Request :rtype: True or False Method is used by: @@ -569,9 +600,10 @@ class RequestValidator(object): All clients should register the absolute URIs of all URIs they intend to redirect to. The registration is outside of the scope of oauthlib. - :param client_id: Unicode client identifier - :param redirect_uri: Unicode absolute URI - :param request: The HTTP Request (oauthlib.common.Request) + :param client_id: Unicode client identifier. + :param redirect_uri: Unicode absolute URI. + :param request: OAuthlib request. + :type request: oauthlib.common.Request :rtype: True or False Method is used by: @@ -586,9 +618,10 @@ class RequestValidator(object): OBS! The request.user attribute should be set to the resource owner associated with this refresh token. - :param refresh_token: Unicode refresh token + :param refresh_token: Unicode refresh token. :param client: Client object set by you, see authenticate_client. - :param request: The HTTP Request (oauthlib.common.Request) + :param request: OAuthlib request. + :type request: oauthlib.common.Request :rtype: True or False Method is used by: @@ -601,10 +634,11 @@ class RequestValidator(object): def validate_response_type(self, client_id, response_type, client, request, *args, **kwargs): """Ensure client is authorized to use the response_type requested. - :param client_id: Unicode client identifier + :param client_id: Unicode client identifier. :param response_type: Unicode response type, i.e. code, token. :param client: Client object set by you, see authenticate_client. - :param request: The HTTP Request (oauthlib.common.Request) + :param request: OAuthlib request. + :type request: oauthlib.common.Request :rtype: True or False Method is used by: @@ -616,10 +650,11 @@ class RequestValidator(object): def validate_scopes(self, client_id, scopes, client, request, *args, **kwargs): """Ensure the client is authorized access to requested scopes. - :param client_id: Unicode client identifier - :param scopes: List of scopes (defined by you) + :param client_id: Unicode client identifier. + :param scopes: List of scopes (defined by you). :param client: Client object set by you, see authenticate_client. - :param request: The HTTP Request (oauthlib.common.Request) + :param request: OAuthlib request. + :type request: oauthlib.common.Request :rtype: True or False Method is used by all core grant types: @@ -636,7 +671,8 @@ class RequestValidator(object): Silent OpenID authorization allows access tokens and id tokens to be granted to clients without any user prompt or interaction. - :param request: The HTTP Request (oauthlib.common.Request) + :param request: OAuthlib request. + :type request: oauthlib.common.Request :rtype: True or False Method is used by: @@ -656,7 +692,8 @@ class RequestValidator(object): not selected which one to link to the token then this method should raise an oauthlib.oauth2.AccountSelectionRequired error. - :param request: The HTTP Request (oauthlib.common.Request) + :param request: OAuthlib request. + :type request: oauthlib.common.Request :rtype: True or False Method is used by: @@ -674,10 +711,11 @@ class RequestValidator(object): not set you will be unable to associate a token with a user in the persistance method used (commonly, save_bearer_token). - :param username: Unicode username - :param password: Unicode password + :param username: Unicode username. + :param password: Unicode password. :param client: Client object set by you, see authenticate_client. - :param request: The HTTP Request (oauthlib.common.Request) + :param request: OAuthlib request. + :type request: oauthlib.common.Request :rtype: True or False Method is used by: @@ -694,7 +732,8 @@ class RequestValidator(object): :param id_token_hint: User identifier string. :param scopes: List of OAuth 2 scopes and OpenID claims (strings). :param claims: OpenID Connect claims dict. - :param request: The HTTP Request (oauthlib.common.Request) + :param request: OAuthlib request. + :type request: oauthlib.common.Request :rtype: True or False Method is used by: diff --git a/oauthlib/oauth2/rfc6749/tokens.py b/oauthlib/oauth2/rfc6749/tokens.py index 1d2b5eb..765251e 100644 --- a/oauthlib/oauth2/rfc6749/tokens.py +++ b/oauthlib/oauth2/rfc6749/tokens.py @@ -96,10 +96,14 @@ def prepare_mac_header(token, uri, key, http_method, .. _`MAC Access Authentication`: https://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01 .. _`extension algorithms`: https://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01#section-7.1 + :param token: :param uri: Request URI. - :param headers: Request headers as a dictionary. - :param http_method: HTTP Request method. :param key: MAC given provided by token endpoint. + :param http_method: HTTP Request method. + :param nonce: + :param headers: Request headers as a dictionary. + :param body: + :param ext: :param hash_algorithm: HMAC algorithm provided by token endpoint. :param issue_time: Time when the MAC credentials were issued (datetime). :param draft: MAC authentication specification version. @@ -181,6 +185,9 @@ def prepare_bearer_uri(token, uri): http://www.example.com/path?access_token=h480djs93hd8 .. _`Bearer Token`: https://tools.ietf.org/html/rfc6750 + + :param token: + :param uri: """ return add_params_to_uri(uri, [(('access_token', token))]) @@ -192,6 +199,9 @@ def prepare_bearer_headers(token, headers=None): Authorization: Bearer h480djs93hd8 .. _`Bearer Token`: https://tools.ietf.org/html/rfc6750 + + :param token: + :param headers: """ headers = headers or {} headers['Authorization'] = 'Bearer %s' % token @@ -204,15 +214,26 @@ def prepare_bearer_body(token, body=''): access_token=h480djs93hd8 .. _`Bearer Token`: https://tools.ietf.org/html/rfc6750 + + :param token: + :param body: """ return add_params_to_qs(body, [(('access_token', token))]) def random_token_generator(request, refresh_token=False): + """ + :param request: OAuthlib request. + :type request: oauthlib.common.Request + :param refresh_token: + """ return common.generate_token() def signed_token_generator(private_pem, **kwargs): + """ + :param private_pem: + """ def signed_token_generator(request): request.claims = kwargs return common.generate_signed_token(private_pem, request) @@ -223,7 +244,8 @@ def signed_token_generator(private_pem, **kwargs): def get_token_from_header(request): """ Helper function to extract a token from the request header. - :param request: The request object + :param request: OAuthlib request. + :type request: oauthlib.common.Request :return: Return the token or None if the Authorization header is malformed. """ token = None @@ -244,9 +266,17 @@ class TokenBase(object): raise NotImplementedError('Subclasses must implement this method.') def validate_request(self, request): + """ + :param request: OAuthlib request. + :type request: oauthlib.common.Request + """ raise NotImplementedError('Subclasses must implement this method.') def estimate_type(self, request): + """ + :param request: OAuthlib request. + :type request: oauthlib.common.Request + """ raise NotImplementedError('Subclasses must implement this method.') @@ -266,7 +296,14 @@ class BearerToken(TokenBase): self.expires_in = expires_in or 3600 def create_token(self, request, refresh_token=False, save_token=True): - """Create a BearerToken, by default without refresh token.""" + """ + Create a BearerToken, by default without refresh token. + + :param request: OAuthlib request. + :type request: oauthlib.common.Request + :param refresh_token: + :param save_token: + """ if callable(self.expires_in): expires_in = self.expires_in(request) @@ -304,11 +341,19 @@ class BearerToken(TokenBase): return token def validate_request(self, request): + """ + :param request: OAuthlib request. + :type request: oauthlib.common.Request + """ token = get_token_from_header(request) return self.request_validator.validate_bearer_token( token, request.scopes, request) def estimate_type(self, request): + """ + :param request: OAuthlib request. + :type request: oauthlib.common.Request + """ if request.headers.get('Authorization', '').split(' ')[0] == 'Bearer': return 9 elif request.access_token is not None: diff --git a/oauthlib/openid/connect/core/request_validator.py b/oauthlib/openid/connect/core/request_validator.py index f3bcbdb..1587754 100644 --- a/oauthlib/openid/connect/core/request_validator.py +++ b/oauthlib/openid/connect/core/request_validator.py @@ -45,7 +45,8 @@ class RequestValidator(OAuth2RequestValidator): :param token: A Bearer token dict :param token_handler: the token handler (BearerToken class) - :param request: the HTTP Request (oauthlib.common.Request) + :param request: OAuthlib request. + :type request: oauthlib.common.Request :return: The JWT Bearer token or OpenID Connect ID token (a JWS signed JWT) Method is used by JWT Bearer and OpenID Connect tokens: @@ -80,7 +81,8 @@ class RequestValidator(OAuth2RequestValidator): :param token: A Bearer token dict :param token_handler: the token handler (BearerToken class) - :param request: the HTTP Request (oauthlib.common.Request) + :param request: OAuthlib request. + :type request: oauthlib.common.Request :return: The ID Token (a JWS signed JWT) """ # the request.scope should be used by the get_id_token() method to determine which claims to include in the resulting id_token @@ -101,7 +103,8 @@ class RequestValidator(OAuth2RequestValidator): :param token: Unicode Bearer token :param scopes: List of scopes (defined by you) - :param request: The HTTP Request (oauthlib.common.Request) + :param request: OAuthlib request. + :type request: oauthlib.common.Request :rtype: True or False Method is indirectly used by all core OpenID connect JWT token issuing grant types: @@ -122,7 +125,8 @@ class RequestValidator(OAuth2RequestValidator): :param token: Unicode Bearer token :param scopes: List of scopes (defined by you) - :param request: The HTTP Request (oauthlib.common.Request) + :param request: OAuthlib request. + :type request: oauthlib.common.Request :rtype: True or False Method is indirectly used by all core OpenID connect JWT token issuing grant types: @@ -138,7 +142,8 @@ class RequestValidator(OAuth2RequestValidator): Silent OpenID authorization allows access tokens and id tokens to be granted to clients without any user prompt or interaction. - :param request: The HTTP Request (oauthlib.common.Request) + :param request: OAuthlib request. + :type request: oauthlib.common.Request :rtype: True or False Method is used by: @@ -158,7 +163,8 @@ class RequestValidator(OAuth2RequestValidator): not selected which one to link to the token then this method should raise an oauthlib.oauth2.AccountSelectionRequired error. - :param request: The HTTP Request (oauthlib.common.Request) + :param request: OAuthlib request. + :type request: oauthlib.common.Request :rtype: True or False Method is used by: @@ -177,7 +183,8 @@ class RequestValidator(OAuth2RequestValidator): :param id_token_hint: User identifier string. :param scopes: List of OAuth 2 scopes and OpenID claims (strings). :param claims: OpenID Connect claims dict. - :param request: The HTTP Request (oauthlib.common.Request) + :param request: OAuthlib request. + :type request: oauthlib.common.Request :rtype: True or False Method is used by: -- cgit v1.2.1 From b02809db83ad490f6ff3f1b86037272bebb4e041 Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Tue, 11 Sep 2018 09:09:50 +0200 Subject: Try to improve multibuild coverage --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index dd72d5c..f46bf43 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,10 +17,11 @@ install: - pip install -U setuptools - pip install tox coveralls script: tox -after_success: coveralls +after_success: COVERALLS_PARALLEL=true coveralls notifications: webhooks: urls: + - https://coveralls.io/webhook - https://webhooks.gitter.im/e/6008c872bf0ecee344f4 on_success: change on_failure: always -- cgit v1.2.1 From 0e963d8e55cabbd9b50fe2d6ec6659f01c1a2e00 Mon Sep 17 00:00:00 2001 From: jonathan vanasco Date: Tue, 11 Sep 2018 14:34:21 -0400 Subject: cleanup on docs fixes --- .../oauth2/rfc6749/clients/service_application.py | 2 +- oauthlib/oauth2/rfc6749/errors.py | 37 ++++++++++++---------- .../rfc6749/grant_types/authorization_code.py | 4 +-- oauthlib/oauth2/rfc6749/grant_types/base.py | 6 ++-- .../rfc6749/grant_types/client_credentials.py | 2 +- oauthlib/oauth2/rfc6749/grant_types/implicit.py | 4 +-- .../oauth2/rfc6749/grant_types/refresh_token.py | 2 +- .../resource_owner_password_credentials.py | 2 +- oauthlib/oauth2/rfc6749/parameters.py | 23 ++++++++------ oauthlib/oauth2/rfc6749/request_validator.py | 14 ++++---- oauthlib/oauth2/rfc6749/tokens.py | 1 + 11 files changed, 53 insertions(+), 44 deletions(-) diff --git a/oauthlib/oauth2/rfc6749/clients/service_application.py b/oauthlib/oauth2/rfc6749/clients/service_application.py index 7f336bb..3045676 100644 --- a/oauthlib/oauth2/rfc6749/clients/service_application.py +++ b/oauthlib/oauth2/rfc6749/clients/service_application.py @@ -54,7 +54,7 @@ class ServiceApplicationClient(Client): ``https://provider.com/oauth2/token``. :param kwargs: Additional arguments to pass to base client, such as - state and token. See Client.__init__.__doc__ for + state and token. See ``Client.__init__.__doc__`` for details. """ super(ServiceApplicationClient, self).__init__(client_id, **kwargs) diff --git a/oauthlib/oauth2/rfc6749/errors.py b/oauthlib/oauth2/rfc6749/errors.py index 5a0cca2..3f0bfc0 100644 --- a/oauthlib/oauth2/rfc6749/errors.py +++ b/oauthlib/oauth2/rfc6749/errors.py @@ -21,23 +21,26 @@ class OAuth2Error(Exception): def __init__(self, description=None, uri=None, state=None, status_code=None, request=None): """ - description: A human-readable ASCII [USASCII] text providing - additional information, used to assist the client - developer in understanding the error that occurred. - Values for the "error_description" parameter MUST NOT - include characters outside the set - x20-21 / x23-5B / x5D-7E. - - uri: A URI identifying a human-readable web page with information - about the error, used to provide the client developer with - additional information about the error. Values for the - "error_uri" parameter MUST conform to the URI- Reference - syntax, and thus MUST NOT include characters outside the set - x21 / x23-5B / x5D-7E. - - state: A CSRF protection value received from the client. - - request: Oauthlib Request object + :param description: A human-readable ASCII [USASCII] text providing + additional information, used to assist the client + developer in understanding the error that occurred. + Values for the "error_description" parameter + MUST NOT include characters outside the set + x20-21 / x23-5B / x5D-7E. + + :param uri: A URI identifying a human-readable web page with information + about the error, used to provide the client developer with + additional information about the error. Values for the + "error_uri" parameter MUST conform to the URI- Reference + syntax, and thus MUST NOT include characters outside the set + x21 / x23-5B / x5D-7E. + + :param state: A CSRF protection value received from the client. + + :param status_code: + + :param request: OAuthlib request. + :type request: oauthlib.common.Request """ if description is not None: self.description = description diff --git a/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py b/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py index 59366b1..8ebae49 100644 --- a/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py +++ b/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py @@ -142,7 +142,7 @@ class AuthorizationCodeGrant(GrantTypeBase): :param request: OAuthlib request. :type request: oauthlib.common.Request - :param token_handler: A token handler instace, for example of type + :param token_handler: A token handler instance, for example of type oauthlib.oauth2.BearerToken. :returns: headers, body, status :raises: FatalClientError on invalid redirect URI or client id. @@ -229,7 +229,7 @@ class AuthorizationCodeGrant(GrantTypeBase): :param request: OAuthlib request. :type request: oauthlib.common.Request - :param token_handler: A token handler instace, for example of type + :param token_handler: A token handler instance, for example of type oauthlib.oauth2.BearerToken. """ diff --git a/oauthlib/oauth2/rfc6749/grant_types/base.py b/oauthlib/oauth2/rfc6749/grant_types/base.py index 4d9381c..43f9db4 100644 --- a/oauthlib/oauth2/rfc6749/grant_types/base.py +++ b/oauthlib/oauth2/rfc6749/grant_types/base.py @@ -120,7 +120,7 @@ class GrantTypeBase(object): """ :param request: OAuthlib request. :type request: oauthlib.common.Request - :param token_handler: A token handler instace, for example of type + :param token_handler: A token handler instance, for example of type oauthlib.oauth2.BearerToken. """ raise NotImplementedError('Subclasses must implement this method.') @@ -129,7 +129,7 @@ class GrantTypeBase(object): """ :param request: OAuthlib request. :type request: oauthlib.common.Request - :param token_handler: A token handler instace, for example of type + :param token_handler: A token handler instance, for example of type oauthlib.oauth2.BearerToken. """ raise NotImplementedError('Subclasses must implement this method.') @@ -137,7 +137,7 @@ class GrantTypeBase(object): def add_token(self, token, token_handler, request): """ :param token: - :param token_handler: A token handler instace, for example of type + :param token_handler: A token handler instance, for example of type oauthlib.oauth2.BearerToken. :param request: OAuthlib request. :type request: oauthlib.common.Request diff --git a/oauthlib/oauth2/rfc6749/grant_types/client_credentials.py b/oauthlib/oauth2/rfc6749/grant_types/client_credentials.py index 884363f..7d4f74c 100644 --- a/oauthlib/oauth2/rfc6749/grant_types/client_credentials.py +++ b/oauthlib/oauth2/rfc6749/grant_types/client_credentials.py @@ -55,7 +55,7 @@ class ClientCredentialsGrant(GrantTypeBase): :param request: OAuthlib request. :type request: oauthlib.common.Request - :param token_handler: A token handler instace, for example of type + :param token_handler: A token handler instance, for example of type oauthlib.oauth2.BearerToken. If the access token request is valid and authorized, the diff --git a/oauthlib/oauth2/rfc6749/grant_types/implicit.py b/oauthlib/oauth2/rfc6749/grant_types/implicit.py index 600c0a5..b29953b 100644 --- a/oauthlib/oauth2/rfc6749/grant_types/implicit.py +++ b/oauthlib/oauth2/rfc6749/grant_types/implicit.py @@ -124,7 +124,7 @@ class ImplicitGrant(GrantTypeBase): :param request: OAuthlib request. :type request: oauthlib.common.Request - :param token_handler: A token handler instace, for example of type + :param token_handler: A token handler instance, for example of type oauthlib.oauth2.BearerToken. The client constructs the request URI by adding the following @@ -171,7 +171,7 @@ class ImplicitGrant(GrantTypeBase): :param request: OAuthlib request. :type request: oauthlib.common.Request - :param token_handler: A token handler instace, for example of type + :param token_handler: A token handler instance, for example of type oauthlib.oauth2.BearerToken. If the resource owner grants the access request, the authorization diff --git a/oauthlib/oauth2/rfc6749/grant_types/refresh_token.py b/oauthlib/oauth2/rfc6749/grant_types/refresh_token.py index 55ddbb2..5f7382a 100644 --- a/oauthlib/oauth2/rfc6749/grant_types/refresh_token.py +++ b/oauthlib/oauth2/rfc6749/grant_types/refresh_token.py @@ -35,7 +35,7 @@ class RefreshTokenGrant(GrantTypeBase): :param request: OAuthlib request. :type request: oauthlib.common.Request - :param token_handler: A token handler instace, for example of type + :param token_handler: A token handler instance, for example of type oauthlib.oauth2.BearerToken. If valid and authorized, the authorization server issues an access diff --git a/oauthlib/oauth2/rfc6749/grant_types/resource_owner_password_credentials.py b/oauthlib/oauth2/rfc6749/grant_types/resource_owner_password_credentials.py index 25fb1f1..87e8015 100644 --- a/oauthlib/oauth2/rfc6749/grant_types/resource_owner_password_credentials.py +++ b/oauthlib/oauth2/rfc6749/grant_types/resource_owner_password_credentials.py @@ -75,7 +75,7 @@ class ResourceOwnerPasswordCredentialsGrant(GrantTypeBase): :param request: OAuthlib request. :type request: oauthlib.common.Request - :param token_handler: A token handler instace, for example of type + :param token_handler: A token handler instance, for example of type oauthlib.oauth2.BearerToken. If the access token request is valid and authorized, the diff --git a/oauthlib/oauth2/rfc6749/parameters.py b/oauthlib/oauth2/rfc6749/parameters.py index 3f18733..e1780f8 100644 --- a/oauthlib/oauth2/rfc6749/parameters.py +++ b/oauthlib/oauth2/rfc6749/parameters.py @@ -37,6 +37,7 @@ def prepare_grant_uri(uri, client_id, response_type, redirect_uri=None, using the ``application/x-www-form-urlencoded`` format as defined by [`W3C.REC-html401-19991224`_]: + :param uri: :param client_id: The client identifier as described in `Section 2.2`_. :param response_type: To indicate which OAuth 2 grant/flow is required, "code" and "token". @@ -134,15 +135,15 @@ def prepare_token_revocation_request(url, token, token_type_hint="access_token", :param token: REQUIRED. The token that the client wants to get revoked. - param:token_type_hint: OPTIONAL. A hint about the type of the token - submitted for revocation. Clients MAY pass this - parameter in order to help the authorization server - to optimize the token lookup. If the server is - unable to locate the token using the given hint, it - MUST extend its search across all of its supported - token types. An authorization server MAY ignore this - parameter, particularly if it is able to detect the - token type automatically. + :param token_type_hint: OPTIONAL. A hint about the type of the token + submitted for revocation. Clients MAY pass this + parameter in order to help the authorization server + to optimize the token lookup. If the server is + unable to locate the token using the given hint, it + MUST extend its search across all of its supported + token types. An authorization server MAY ignore + this parameter, particularly if it is able to detect + the token type automatically. This specification defines two values for `token_type_hint`: @@ -267,6 +268,10 @@ def parse_implicit_response(uri, state=None, scope=None): authorization request. The exact value received from the client. + :param uri: + :param state: + :param scope: + Similar to the authorization code response, but with a full token provided in the URL fragment: diff --git a/oauthlib/oauth2/rfc6749/request_validator.py b/oauthlib/oauth2/rfc6749/request_validator.py index 6ce7910..2cf1b82 100644 --- a/oauthlib/oauth2/rfc6749/request_validator.py +++ b/oauthlib/oauth2/rfc6749/request_validator.py @@ -100,7 +100,7 @@ class RequestValidator(object): :param client_id: Unicode client identifier. :param code: Unicode authorization_code. :param redirect_uri: Unicode absolute URI. - :param client: Client object set by you, see authenticate_client. + :param client: Client object set by you, see ``.authenticate_client``. :param request: OAuthlib request. :type request: oauthlib.common.Request :rtype: True or False @@ -566,7 +566,7 @@ class RequestValidator(object): :param client_id: Unicode client identifier. :param code: Unicode authorization code. - :param client: Client object set by you, see authenticate_client. + :param client: Client object set by you, see ``.authenticate_client``. :param request: OAuthlib request. :type request: oauthlib.common.Request :rtype: True or False @@ -581,7 +581,7 @@ class RequestValidator(object): :param client_id: Unicode client identifier. :param grant_type: Unicode grant type, i.e. authorization_code, password. - :param client: Client object set by you, see authenticate_client. + :param client: Client object set by you, see ``.authenticate_client``. :param request: OAuthlib request. :type request: oauthlib.common.Request :rtype: True or False @@ -619,7 +619,7 @@ class RequestValidator(object): associated with this refresh token. :param refresh_token: Unicode refresh token. - :param client: Client object set by you, see authenticate_client. + :param client: Client object set by you, see ``.authenticate_client``. :param request: OAuthlib request. :type request: oauthlib.common.Request :rtype: True or False @@ -636,7 +636,7 @@ class RequestValidator(object): :param client_id: Unicode client identifier. :param response_type: Unicode response type, i.e. code, token. - :param client: Client object set by you, see authenticate_client. + :param client: Client object set by you, see ``.authenticate_client``. :param request: OAuthlib request. :type request: oauthlib.common.Request :rtype: True or False @@ -652,7 +652,7 @@ class RequestValidator(object): :param client_id: Unicode client identifier. :param scopes: List of scopes (defined by you). - :param client: Client object set by you, see authenticate_client. + :param client: Client object set by you, see ``.authenticate_client``. :param request: OAuthlib request. :type request: oauthlib.common.Request :rtype: True or False @@ -713,7 +713,7 @@ class RequestValidator(object): :param username: Unicode username. :param password: Unicode password. - :param client: Client object set by you, see authenticate_client. + :param client: Client object set by you, see ``.authenticate_client``. :param request: OAuthlib request. :type request: oauthlib.common.Request :rtype: True or False diff --git a/oauthlib/oauth2/rfc6749/tokens.py b/oauthlib/oauth2/rfc6749/tokens.py index 765251e..d78df09 100644 --- a/oauthlib/oauth2/rfc6749/tokens.py +++ b/oauthlib/oauth2/rfc6749/tokens.py @@ -244,6 +244,7 @@ def signed_token_generator(private_pem, **kwargs): def get_token_from_header(request): """ Helper function to extract a token from the request header. + :param request: OAuthlib request. :type request: oauthlib.common.Request :return: Return the token or None if the Authorization header is malformed. -- cgit v1.2.1 From 7b843b112b9b330268d99a3b1e65c8381a7ad945 Mon Sep 17 00:00:00 2001 From: jonathan vanasco Date: Tue, 11 Sep 2018 14:40:39 -0400 Subject: fixed spacing --- oauthlib/oauth2/rfc6749/parameters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oauthlib/oauth2/rfc6749/parameters.py b/oauthlib/oauth2/rfc6749/parameters.py index e1780f8..74a15f9 100644 --- a/oauthlib/oauth2/rfc6749/parameters.py +++ b/oauthlib/oauth2/rfc6749/parameters.py @@ -95,7 +95,7 @@ def prepare_token_request(grant_type, body='', **kwargs): format in the HTTP request entity-body: :param grant_type: To indicate grant type being used, i.e. "password", - "authorization_code" or "client_credentials". + "authorization_code" or "client_credentials". :param body: Existing request body to embed parameters in. :param code: If using authorization code grant, pass the previously obtained authorization code as the ``code`` argument. -- cgit v1.2.1 From 867802b14427378dc58516b1b1cabc8a7dbc6d14 Mon Sep 17 00:00:00 2001 From: jonathan vanasco Date: Tue, 11 Sep 2018 16:30:21 -0400 Subject: idea for documentation in contributing.rst --- docs/contributing.rst | 52 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/docs/contributing.rst b/docs/contributing.rst index cbdb519..771262d 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -238,6 +238,58 @@ Furthermore, the pixel shortage is over. We want to see: * `grid` instead of `g` * `my_function_that_does_things` instead of `mftdt` +Be sure to write documentation! +------------------------------- + +Documentation isn't just good, it's great - and necessary with large packages +like OAuthlib. Please make sure the next person who reads your function/method +can quickly understand what it does and how. Also, please ensure the parameters +passed to each function are properly documented as well. + +The project has these goals/requests for docstrings that are designed to make +the autogenerated documentation read more cleanly: + +#. Every parameter in the function should be listed in the docstring, and + should appear in the same order as they appear in the function itself. +#. If you are unsure of the best wording for a parameter description, leave it + blank, but still include the `:param foo:` line. This will make it easier for + maintainers to see and edit. +#. Use an existing standardized description of a parameter that appears + elsewhere in this project's documentation whenever possible. For example, + `request` is used as a parameter throughout the project with the description + "OAuthlib request." - there is no reason to describe it differently in your + function. Parameter descriptions should be a sentence that ends with a + period - even if it is just two words. +#. When possible, include a `type` declaration for the parameter. For example, + a "request" param is often accompanied with `:type request: oauthlib.common.Request`. + The type is expected to be an object type reference, and should never end + in a period. +#. If there is a textual docstring (recommended), use a single blank line to + separate the docstring and the params. +#. When you cite class functions, please use backticks. + +Consolidated example + + def foo(self, request, client, bar=None, key=None): + """ + This method checks the `key` against the `client`. The `request` is + passed to maintain context. + + Example MAC Authorization header, linebreaks added for clarity + + Authorization: MAC id="h480djs93hd8", + nonce="1336363200:dj83hs9s", + mac="bhCQXTVyfj5cmA9uKkPFx1zeOXM=" + + .. _`MAC Access Authentication`: https://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01 + + :param request: OAuthlib request. + :type request: oauthlib.common.Request + :param client: Client object set by you, see ``.authenticate_client``. + :param bar: + :param key: MAC given provided by token endpoint. + """ + How pull requests are checked, tested, and done =============================================== -- cgit v1.2.1 From 1b4fa60945022a1d8739d5ed6a9a915230fd1e5b Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Tue, 11 Sep 2018 17:56:22 -0700 Subject: Remove workarounds for unsupported Python 2.6 Python 2.6 support was removed in 91152df142bdde134d84ed27963cda8e6b878416. Drop unittest2 dependency. All necessary testing features are included in the stdlib unittest. --- setup.py | 7 +++---- tests/unittest/__init__.py | 21 ++------------------- tox.ini | 4 ---- 3 files changed, 5 insertions(+), 27 deletions(-) diff --git a/setup.py b/setup.py index 1d69e0d..17dcd7b 100755 --- a/setup.py +++ b/setup.py @@ -18,10 +18,9 @@ def fread(fn): with open(join(dirname(__file__), fn), 'r') as f: return f.read() -if sys.version_info[0] == 3: - tests_require = ['nose', 'cryptography', 'pyjwt>=1.0.0', 'blinker'] -else: - tests_require = ['nose', 'unittest2', 'cryptography', 'mock>=2.0', 'pyjwt>=1.0.0', 'blinker'] +tests_require = ['nose', 'cryptography', 'pyjwt>=1.0.0', 'blinker'] +if sys.version_info[0] == 2: + tests_require.append('mock>=2.0') rsa_require = ['cryptography'] signedtoken_require = ['cryptography', 'pyjwt>=1.0.0'] signals_require = ['blinker'] diff --git a/tests/unittest/__init__.py b/tests/unittest/__init__.py index 35b239a..6cb79a6 100644 --- a/tests/unittest/__init__.py +++ b/tests/unittest/__init__.py @@ -1,32 +1,15 @@ import collections import sys +from unittest import TestCase try: import urlparse except ImportError: import urllib.parse as urlparse -try: - # check the system path first - from unittest2 import * -except ImportError: - if sys.version_info >= (2, 7): - # unittest2 features are native in Python 2.7 - from unittest import * - else: - raise - -# Python 3.1 does not provide assertIsInstance -if sys.version_info[1] == 1: - TestCase.assertIsInstance = lambda self, obj, cls: self.assertTrue(isinstance(obj, cls)) # Somewhat consistent itemsequal between all python versions -if sys.version_info[1] == 3: +if sys.version_info[0] == 3: TestCase.assertItemsEqual = TestCase.assertCountEqual -elif sys.version_info[0] == 2 and sys.version_info[1] == 6: - pass -else: - TestCase.assertItemsEqual = lambda self, a, b: self.assertEqual( - collections.Counter(list(a)), collections.Counter(list(b))) # URL comparison where query param order is insignifcant diff --git a/tox.ini b/tox.ini index eac7a1e..b736cf1 100644 --- a/tox.ini +++ b/tox.ini @@ -6,10 +6,6 @@ deps= -rrequirements-test.txt commands=nosetests -s --with-coverage --cover-html --cover-html-dir={toxinidir}/htmlcov-{envname} --cover-erase --cover-package=oauthlib -w tests -[testenv:py27] -deps=unittest2 - {[testenv]deps} - # tox -e docs to mimick readthedocs build. # as of today, RTD is using python2.7 and doesn't run "setup.py install" [testenv:docs] -- cgit v1.2.1 From d6dfe4afc23086913f9b571d7a1b7ee58af5d809 Mon Sep 17 00:00:00 2001 From: jonathan vanasco Date: Thu, 13 Sep 2018 15:15:47 -0400 Subject: * addresing ticket #585 * `prepare_request_body` client_id is deprecated in favor of include_client_id * a new unit test `test_prepare_request_body` is added to ensure conformity of several use cases * the docstrings for the `body` param have been consolidated and standardized across multiple functions linked to `prepare_request_body` for clarity --- .../oauth2/rfc6749/clients/backend_application.py | 2 + oauthlib/oauth2/rfc6749/clients/base.py | 6 ++- .../oauth2/rfc6749/clients/legacy_application.py | 2 + .../oauth2/rfc6749/clients/service_application.py | 13 +++--- oauthlib/oauth2/rfc6749/clients/web_application.py | 30 ++++++++++--- oauthlib/oauth2/rfc6749/parameters.py | 4 +- .../oauth2/rfc6749/clients/test_web_application.py | 49 +++++++++++++++++++++- 7 files changed, 90 insertions(+), 16 deletions(-) diff --git a/oauthlib/oauth2/rfc6749/clients/backend_application.py b/oauthlib/oauth2/rfc6749/clients/backend_application.py index cbad8b7..99dbfc5 100644 --- a/oauthlib/oauth2/rfc6749/clients/backend_application.py +++ b/oauthlib/oauth2/rfc6749/clients/backend_application.py @@ -37,6 +37,8 @@ class BackendApplicationClient(Client): following parameters using the "application/x-www-form-urlencoded" format per `Appendix B`_ in the HTTP request entity-body: + :param body: Existing request body (URL encoded string) to embed parameters + into. This may contain extra paramters. Default ''. :param scope: The scope of the access request as described by `Section 3.3`_. :param kwargs: Extra credentials to include in the token request. diff --git a/oauthlib/oauth2/rfc6749/clients/base.py b/oauthlib/oauth2/rfc6749/clients/base.py index 406832d..d8ded50 100644 --- a/oauthlib/oauth2/rfc6749/clients/base.py +++ b/oauthlib/oauth2/rfc6749/clients/base.py @@ -254,7 +254,8 @@ class Client(object): :param redirect_url: The redirect_url supplied with the authorization request (if there was one). - :param body: Request body (URL encoded string). + :param body: Existing request body (URL encoded string) to embed parameters + into. This may contain extra paramters. Default ''. :param kwargs: Additional parameters to included in the request. @@ -286,7 +287,8 @@ class Client(object): :param refresh_token: Refresh token string. - :param body: Request body (URL encoded string). + :param body: Existing request body (URL encoded string) to embed parameters + into. This may contain extra paramters. Default ''. :param scope: List of scopes to request. Must be equal to or a subset of the scopes granted when obtaining the refresh diff --git a/oauthlib/oauth2/rfc6749/clients/legacy_application.py b/oauthlib/oauth2/rfc6749/clients/legacy_application.py index b16fc9f..8f03695 100644 --- a/oauthlib/oauth2/rfc6749/clients/legacy_application.py +++ b/oauthlib/oauth2/rfc6749/clients/legacy_application.py @@ -47,6 +47,8 @@ class LegacyApplicationClient(Client): :param username: The resource owner username. :param password: The resource owner password. + :param body: Existing request body (URL encoded string) to embed parameters + into. This may contain extra paramters. Default ''. :param scope: The scope of the access request as described by `Section 3.3`_. :param kwargs: Extra credentials to include in the token request. diff --git a/oauthlib/oauth2/rfc6749/clients/service_application.py b/oauthlib/oauth2/rfc6749/clients/service_application.py index 3045676..6bb784e 100644 --- a/oauthlib/oauth2/rfc6749/clients/service_application.py +++ b/oauthlib/oauth2/rfc6749/clients/service_application.py @@ -97,18 +97,19 @@ class ServiceApplicationClient(Client): :param issued_at: A unix timestamp of when the JWT was created. Defaults to now, i.e. ``time.time()``. + :param extra_claims: A dict of additional claims to include in the JWT. + + :param body: Existing request body (URL encoded string) to embed parameters + into. This may contain extra paramters. Default ''. + + :param scope: The scope of the access request. + :param not_before: A unix timestamp after which the JWT may be used. Not included unless provided. :param jwt_id: A unique JWT token identifier. Not included unless provided. - :param extra_claims: A dict of additional claims to include in the JWT. - - :param scope: The scope of the access request. - - :param body: Request body (string) with extra parameters. - :param kwargs: Extra credentials to include in the token request. The "scope" parameter may be used, as defined in the Assertion diff --git a/oauthlib/oauth2/rfc6749/clients/web_application.py b/oauthlib/oauth2/rfc6749/clients/web_application.py index c14a5f8..ec59b31 100644 --- a/oauthlib/oauth2/rfc6749/clients/web_application.py +++ b/oauthlib/oauth2/rfc6749/clients/web_application.py @@ -8,6 +8,8 @@ for consuming and providing OAuth 2.0 RFC6749. """ from __future__ import absolute_import, unicode_literals +import warnings + from ..parameters import (parse_authorization_code_response, parse_token_response, prepare_grant_uri, prepare_token_request) @@ -85,17 +87,14 @@ class WebApplicationClient(Client): return prepare_grant_uri(uri, self.client_id, 'code', redirect_uri=redirect_uri, scope=scope, state=state, **kwargs) - def prepare_request_body(self, client_id=None, code=None, body='', - redirect_uri=None, **kwargs): + def prepare_request_body(self, code=None, redirect_uri=None, body='', + include_client_id=True, **kwargs): """Prepare the access token request body. The client makes a request to the token endpoint by adding the following parameters using the "application/x-www-form-urlencoded" format in the HTTP request entity-body: - :param client_id: REQUIRED, if the client is not authenticating with the - authorization server as described in `Section 3.2.1`_. - :param code: REQUIRED. The authorization code received from the authorization server. @@ -103,6 +102,15 @@ class WebApplicationClient(Client): authorization request as described in `Section 4.1.1`_, and their values MUST be identical. + :param body: Existing request body (URL encoded string) to embed parameters + into. This may contain extra paramters. Default ''. + + :param include_client_id: `True` (default) to send the `client_id` in the + body of the upstream request. This is required + if the client is not authenticating with the + authorization server as described in `Section 3.2.1`_. + :type include_client_id: Boolean + :param kwargs: Extra parameters to include in the token request. In addition OAuthLib will add the ``grant_type`` parameter set to @@ -124,8 +132,18 @@ class WebApplicationClient(Client): .. _`Section 3.2.1`: https://tools.ietf.org/html/rfc6749#section-3.2.1 """ code = code or self.code + if 'client_id' in kwargs: + warnings.warn("`client_id` has been deprecated in favor of " + "`include_client_id`, a boolean value which will " + "include the already configured `self.client_id`.", + DeprecationWarning) + if kwargs['client_id'] != self.client_id: + raise ValueError("`client_id` was supplied as an argument, but " + "it does not match `self.client_id`") + if include_client_id: + kwargs['client_id'] = self.client_id return prepare_token_request('authorization_code', code=code, body=body, - client_id=client_id, redirect_uri=redirect_uri, **kwargs) + redirect_uri=redirect_uri, **kwargs) def parse_request_uri_response(self, uri, state=None): """Parse the URI query for code and state. diff --git a/oauthlib/oauth2/rfc6749/parameters.py b/oauthlib/oauth2/rfc6749/parameters.py index 74a15f9..2b6e854 100644 --- a/oauthlib/oauth2/rfc6749/parameters.py +++ b/oauthlib/oauth2/rfc6749/parameters.py @@ -96,7 +96,8 @@ def prepare_token_request(grant_type, body='', **kwargs): :param grant_type: To indicate grant type being used, i.e. "password", "authorization_code" or "client_credentials". - :param body: Existing request body to embed parameters in. + :param body: Existing request body (URL encoded string) to embed parameters + into. This may contain extra paramters. Default ''. :param code: If using authorization code grant, pass the previously obtained authorization code as the ``code`` argument. :param redirect_uri: If the "redirect_uri" parameter was included in the @@ -119,6 +120,7 @@ def prepare_token_request(grant_type, body='', **kwargs): kwargs['scope'] = list_to_scope(kwargs['scope']) for k in kwargs: + # this handles: `code`, `redirect_uri`, or undocumented params if kwargs[k]: params.append((unicode_type(k), kwargs[k])) diff --git a/tests/oauth2/rfc6749/clients/test_web_application.py b/tests/oauth2/rfc6749/clients/test_web_application.py index 4ecc3b3..d87005a 100644 --- a/tests/oauth2/rfc6749/clients/test_web_application.py +++ b/tests/oauth2/rfc6749/clients/test_web_application.py @@ -3,6 +3,7 @@ from __future__ import absolute_import, unicode_literals import datetime import os +import warnings from mock import patch @@ -38,7 +39,7 @@ class WebApplicationClientTest(TestCase): code = "zzzzaaaa" body = "not=empty" - body_code = "not=empty&grant_type=authorization_code&code=%s" % code + body_code = "not=empty&grant_type=authorization_code&code=%s&client_id=%s" % (code, client_id) body_redirect = body_code + "&redirect_uri=http%3A%2F%2Fmy.page.com%2Fcallback" body_kwargs = body_code + "&some=providers&require=extra+arguments" @@ -177,3 +178,49 @@ class WebApplicationClientTest(TestCase): # verify default header and body only self.assertEqual(header, {'Content-Type': 'application/x-www-form-urlencoded'}) self.assertEqual(body, '') + + def test_prepare_request_body(self): + """ + see issue #585 + https://github.com/oauthlib/oauthlib/issues/585 + + `prepare_request_body` should support the following scenarios: + 1. Include client_id alone in the body (default) + 2. Include client_id and client_secret in auth and not include them in the body (RFC preferred solution) + 3. Include client_id and client_secret in the body (RFC alternative solution) + """ + client = WebApplicationClient(self.client_id) + + # scenario 1, default behavior to include `client_id` + r1 = client.prepare_request_body() + self.assertEqual(r1, 'grant_type=authorization_code&client_id=someclientid') + + r1b = client.prepare_request_body(include_client_id=True) + self.assertEqual(r1b, 'grant_type=authorization_code&client_id=someclientid') + + # scenario 2, do not include `client_id` in the body, so it can be sent in auth. + r2 = client.prepare_request_body(include_client_id=False) + self.assertEqual(r2, 'grant_type=authorization_code') + + # scenario 3, Include client_id and client_secret in the body (RFC alternative solution) + r3 = client.prepare_request_body(client_secret='secret') + self.assertEqual(r3, 'grant_type=authorization_code&client_secret=secret&client_id=someclientid') + r3b = client.prepare_request_body(include_client_id=True, client_secret='secret') + self.assertEqual(r3b, 'grant_type=authorization_code&client_secret=secret&client_id=someclientid') + + # scenario Warnings + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") # catch all + + # warning1 - raise a DeprecationWarning if a `client_id` is submitted + rWarnings1 = client.prepare_request_body(client_id=self.client_id) + self.assertEqual(len(w), 1) + self.assertIsInstance(w[0].message, DeprecationWarning) + self.assertEqual(w[0].message.message, """`client_id` has been deprecated in favor of `include_client_id`, a boolean value which will include the already configured `self.client_id`.""") + + # scenario Exceptions + # exception1 - raise a ValueError if the a different `client_id` is submitted + with self.assertRaises(ValueError) as cm: + client.prepare_request_body(client_id='different_client_id') + self.assertEqual(cm.exception.message, + "`client_id` was supplied as an argument, but it does not match `self.client_id`") -- cgit v1.2.1 From e4658e048ac2b2126b56c41e0494cd17607ea190 Mon Sep 17 00:00:00 2001 From: jonathan vanasco Date: Thu, 13 Sep 2018 15:44:05 -0400 Subject: updated tests to pass on 2.x and 3.x --- tests/oauth2/rfc6749/clients/test_web_application.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/tests/oauth2/rfc6749/clients/test_web_application.py b/tests/oauth2/rfc6749/clients/test_web_application.py index d87005a..702fcf2 100644 --- a/tests/oauth2/rfc6749/clients/test_web_application.py +++ b/tests/oauth2/rfc6749/clients/test_web_application.py @@ -203,10 +203,15 @@ class WebApplicationClientTest(TestCase): self.assertEqual(r2, 'grant_type=authorization_code') # scenario 3, Include client_id and client_secret in the body (RFC alternative solution) + # the order of kwargs being appended is not guaranteed. for brevity, check the 2 permutations instead of sorting r3 = client.prepare_request_body(client_secret='secret') - self.assertEqual(r3, 'grant_type=authorization_code&client_secret=secret&client_id=someclientid') + self.assertIn(r3, ('grant_type=authorization_code&client_secret=secret&client_id=someclientid', + 'grant_type=authorization_code&client_id=someclientid&client_secret=secret',) + ) r3b = client.prepare_request_body(include_client_id=True, client_secret='secret') - self.assertEqual(r3b, 'grant_type=authorization_code&client_secret=secret&client_id=someclientid') + self.assertIn(r3b, ('grant_type=authorization_code&client_secret=secret&client_id=someclientid', + 'grant_type=authorization_code&client_id=someclientid&client_secret=secret',) + ) # scenario Warnings with warnings.catch_warnings(record=True) as w: @@ -216,11 +221,11 @@ class WebApplicationClientTest(TestCase): rWarnings1 = client.prepare_request_body(client_id=self.client_id) self.assertEqual(len(w), 1) self.assertIsInstance(w[0].message, DeprecationWarning) - self.assertEqual(w[0].message.message, """`client_id` has been deprecated in favor of `include_client_id`, a boolean value which will include the already configured `self.client_id`.""") + + # testing the exact warning message in Python2&Python3 is a pain # scenario Exceptions # exception1 - raise a ValueError if the a different `client_id` is submitted with self.assertRaises(ValueError) as cm: client.prepare_request_body(client_id='different_client_id') - self.assertEqual(cm.exception.message, - "`client_id` was supplied as an argument, but it does not match `self.client_id`") + # testing the exact exception message in Python2&Python3 is a pain -- cgit v1.2.1 From aef9a3e944f41c3afaaf22ba20f86a267a7d3bb3 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Fri, 14 Sep 2018 05:10:25 -0700 Subject: Prefer assertIsInstance(...) over assertTrue(isinstance(...)) It is a more explicit assert with a more information message in case of failure. For a full list of available assert methods, see: https://docs.python.org/3/library/unittest.html#assert-methods --- tests/oauth1/rfc5849/test_client.py | 2 +- tests/openid/connect/core/grant_types/test_dispatchers.py | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/oauth1/rfc5849/test_client.py b/tests/oauth1/rfc5849/test_client.py index 777efc2..069eefd 100644 --- a/tests/oauth1/rfc5849/test_client.py +++ b/tests/oauth1/rfc5849/test_client.py @@ -39,7 +39,7 @@ class ClientConstructorTests(TestCase): def test_convert_to_unicode_resource_owner(self): client = Client('client-key', resource_owner_key=b'owner key') - self.assertFalse(isinstance(client.resource_owner_key, bytes_type)) + self.assertNotIsInstance(client.resource_owner_key, bytes_type) self.assertEqual(client.resource_owner_key, 'owner key') def test_give_explicit_timestamp(self): diff --git a/tests/openid/connect/core/grant_types/test_dispatchers.py b/tests/openid/connect/core/grant_types/test_dispatchers.py index f90ec46..84f2688 100644 --- a/tests/openid/connect/core/grant_types/test_dispatchers.py +++ b/tests/openid/connect/core/grant_types/test_dispatchers.py @@ -36,23 +36,23 @@ class ImplicitTokenGrantDispatcherTest(TestCase): self.request.scopes = ('hello', 'openid') self.request.response_type = 'id_token' handler = self.dispatcher._handler_for_request(self.request) - self.assertTrue(isinstance(handler, ImplicitGrant)) + self.assertIsInstance(handler, ImplicitGrant) def test_validate_authorization_request_openid(self): self.request.scopes = ('hello', 'openid') self.request.response_type = 'id_token' handler = self.dispatcher._handler_for_request(self.request) - self.assertTrue(isinstance(handler, ImplicitGrant)) + self.assertIsInstance(handler, ImplicitGrant) def test_create_authorization_response_oauth(self): self.request.scopes = ('hello', 'world') handler = self.dispatcher._handler_for_request(self.request) - self.assertTrue(isinstance(handler, ImplicitGrant)) + self.assertIsInstance(handler, ImplicitGrant) def test_validate_authorization_request_oauth(self): self.request.scopes = ('hello', 'world') handler = self.dispatcher._handler_for_request(self.request) - self.assertTrue(isinstance(handler, ImplicitGrant)) + self.assertIsInstance(handler, ImplicitGrant) class DispatcherTest(TestCase): @@ -82,7 +82,7 @@ class AuthTokenGrantDispatcherOpenIdTest(DispatcherTest): def test_create_token_response_openid(self): handler = self.dispatcher._handler_for_request(self.request) - self.assertTrue(isinstance(handler, AuthorizationCodeGrant)) + self.assertIsInstance(handler, AuthorizationCodeGrant) self.assertTrue(self.dispatcher.request_validator.get_authorization_code_scopes.called) @@ -104,7 +104,7 @@ class AuthTokenGrantDispatcherOpenIdWithoutCodeTest(DispatcherTest): def test_create_token_response_openid_without_code(self): handler = self.dispatcher._handler_for_request(self.request) - self.assertTrue(isinstance(handler, OAuth2AuthorizationCodeGrant)) + self.assertIsInstance(handler, OAuth2AuthorizationCodeGrant) self.assertFalse(self.dispatcher.request_validator.get_authorization_code_scopes.called) @@ -121,5 +121,5 @@ class AuthTokenGrantDispatcherOAuthTest(DispatcherTest): def test_create_token_response_oauth(self): handler = self.dispatcher._handler_for_request(self.request) - self.assertTrue(isinstance(handler, OAuth2AuthorizationCodeGrant)) + self.assertIsInstance(handler, OAuth2AuthorizationCodeGrant) self.assertTrue(self.dispatcher.request_validator.get_authorization_code_scopes.called) -- cgit v1.2.1 From 8aa89569f14b493ba2672d7a64c7c1c138c82c3b Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Fri, 14 Sep 2018 05:07:46 -0700 Subject: Remove unnecessary workaround for bytes type The type 'bytes' is available on all supported Pythons. Likewise the byte literal b'...' is available on all supported Pythons. Use idiomatic Python and remove workaround for an issue that no longer exists. Makes the code more forward compatible with Python 3. --- oauthlib/common.py | 14 ++++++-------- oauthlib/oauth1/rfc5849/__init__.py | 5 ----- oauthlib/oauth1/rfc5849/signature.py | 4 ++-- oauthlib/oauth1/rfc5849/utils.py | 2 +- tests/oauth1/rfc5849/test_client.py | 12 ++++++------ tests/test_common.py | 13 ++++--------- 6 files changed, 19 insertions(+), 31 deletions(-) diff --git a/oauthlib/common.py b/oauthlib/common.py index 6364761..bd6ec56 100644 --- a/oauthlib/common.py +++ b/oauthlib/common.py @@ -54,10 +54,8 @@ PY3 = sys.version_info[0] == 3 if PY3: unicode_type = str - bytes_type = bytes else: unicode_type = unicode - bytes_type = str # 'safe' must be bytes (Python 2.6 requires bytes, other versions allow either) @@ -66,7 +64,7 @@ def quote(s, safe=b'/'): s = _quote(s, safe) # PY3 always returns unicode. PY2 may return either, depending on whether # it had to modify the string. - if isinstance(s, bytes_type): + if isinstance(s, bytes): s = s.decode('utf-8') return s @@ -76,7 +74,7 @@ def unquote(s): # PY3 always returns unicode. PY2 seems to always return what you give it, # which differs from quote's behavior. Just to be safe, make sure it is # unicode before we return. - if isinstance(s, bytes_type): + if isinstance(s, bytes): s = s.decode('utf-8') return s @@ -109,8 +107,8 @@ def decode_params_utf8(params): decoded = [] for k, v in params: decoded.append(( - k.decode('utf-8') if isinstance(k, bytes_type) else k, - v.decode('utf-8') if isinstance(v, bytes_type) else v)) + k.decode('utf-8') if isinstance(k, bytes) else k, + v.decode('utf-8') if isinstance(v, bytes) else v)) return decoded @@ -174,7 +172,7 @@ def extract_params(raw): empty list of parameters. Any other input will result in a return value of None. """ - if isinstance(raw, bytes_type) or isinstance(raw, unicode_type): + if isinstance(raw, bytes) or isinstance(raw, unicode_type): try: params = urldecode(raw) except ValueError: @@ -309,7 +307,7 @@ def to_unicode(data, encoding='UTF-8'): if isinstance(data, unicode_type): return data - if isinstance(data, bytes_type): + if isinstance(data, bytes): return unicode_type(data, encoding=encoding) if hasattr(data, '__iter__'): diff --git a/oauthlib/oauth1/rfc5849/__init__.py b/oauthlib/oauth1/rfc5849/__init__.py index 87a8e6b..887ab69 100644 --- a/oauthlib/oauth1/rfc5849/__init__.py +++ b/oauthlib/oauth1/rfc5849/__init__.py @@ -18,11 +18,6 @@ try: except ImportError: import urllib.parse as urlparse -if sys.version_info[0] == 3: - bytes_type = bytes -else: - bytes_type = str - from oauthlib.common import Request, urlencode, generate_nonce from oauthlib.common import generate_timestamp, to_unicode from . import parameters, signature diff --git a/oauthlib/oauth1/rfc5849/signature.py b/oauthlib/oauth1/rfc5849/signature.py index 4e672ba..e90d6f3 100644 --- a/oauthlib/oauth1/rfc5849/signature.py +++ b/oauthlib/oauth1/rfc5849/signature.py @@ -28,7 +28,7 @@ import hashlib import hmac import logging -from oauthlib.common import (bytes_type, extract_params, safe_string_equals, +from oauthlib.common import (extract_params, safe_string_equals, unicode_type, urldecode) from . import utils @@ -635,7 +635,7 @@ def verify_hmac_sha1(request, client_secret=None, def _prepare_key_plus(alg, keystr): - if isinstance(keystr, bytes_type): + if isinstance(keystr, bytes): keystr = keystr.decode('utf-8') return alg.prepare_key(keystr) diff --git a/oauthlib/oauth1/rfc5849/utils.py b/oauthlib/oauth1/rfc5849/utils.py index 3762e3b..735f21d 100644 --- a/oauthlib/oauth1/rfc5849/utils.py +++ b/oauthlib/oauth1/rfc5849/utils.py @@ -8,7 +8,7 @@ spec. """ from __future__ import absolute_import, unicode_literals -from oauthlib.common import bytes_type, quote, unicode_type, unquote +from oauthlib.common import quote, unicode_type, unquote try: import urllib2 diff --git a/tests/oauth1/rfc5849/test_client.py b/tests/oauth1/rfc5849/test_client.py index 777efc2..ab6f8da 100644 --- a/tests/oauth1/rfc5849/test_client.py +++ b/tests/oauth1/rfc5849/test_client.py @@ -5,7 +5,7 @@ from oauthlib.common import Request from oauthlib.oauth1 import (SIGNATURE_PLAINTEXT, SIGNATURE_HMAC_SHA1, SIGNATURE_HMAC_SHA256, SIGNATURE_RSA, SIGNATURE_TYPE_BODY, SIGNATURE_TYPE_QUERY) -from oauthlib.oauth1.rfc5849 import Client, bytes_type +from oauthlib.oauth1.rfc5849 import Client from ...unittest import TestCase @@ -39,7 +39,7 @@ class ClientConstructorTests(TestCase): def test_convert_to_unicode_resource_owner(self): client = Client('client-key', resource_owner_key=b'owner key') - self.assertFalse(isinstance(client.resource_owner_key, bytes_type)) + self.assertFalse(isinstance(client.resource_owner_key, bytes)) self.assertEqual(client.resource_owner_key, 'owner key') def test_give_explicit_timestamp(self): @@ -57,11 +57,11 @@ class ClientConstructorTests(TestCase): uri, headers, body = client.sign('http://a.b/path?query', http_method='POST', body='a=b', headers={'Content-Type': 'application/x-www-form-urlencoded'}) - self.assertIsInstance(uri, bytes_type) - self.assertIsInstance(body, bytes_type) + self.assertIsInstance(uri, bytes) + self.assertIsInstance(body, bytes) for k, v in headers.items(): - self.assertIsInstance(k, bytes_type) - self.assertIsInstance(v, bytes_type) + self.assertIsInstance(k, bytes) + self.assertIsInstance(v, bytes) def test_hmac_sha1(self): client = Client('client_key') diff --git a/tests/test_common.py b/tests/test_common.py index f239368..20d9f5b 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -10,11 +10,6 @@ from oauthlib.common import (CaseInsensitiveDict, Request, add_params_to_uri, from .unittest import TestCase -if sys.version_info[0] == 3: - bytes_type = bytes -else: - bytes_type = lambda s, e: str(s) - PARAMS_DICT = {'foo': 'bar', 'baz': '123', } PARAMS_TWOTUPLE = [('foo', 'bar'), ('baz', '123')] PARAMS_FORMENCODED = 'foo=bar&baz=123' @@ -122,11 +117,11 @@ class RequestTest(TestCase): def test_non_unicode_params(self): r = Request( - bytes_type('http://a.b/path?query', 'utf-8'), - http_method=bytes_type('GET', 'utf-8'), - body=bytes_type('you=shall+pass', 'utf-8'), + b'http://a.b/path?query', + http_method=b'GET', + body=b'you=shall+pass', headers={ - bytes_type('a', 'utf-8'): bytes_type('b', 'utf-8') + b'a': b'b', } ) self.assertEqual(r.uri, 'http://a.b/path?query') -- cgit v1.2.1 From a0f38f71cb8764bbff8dd2cdac5031a09086665e Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Fri, 14 Sep 2018 12:58:16 -0700 Subject: Correct recent merge (#596) Merge c8a7cb199a8d448c2934100a5bb06598be402939 mistakenly reverted a line from aef9a3e944f41c3afaaf22ba20f86a267a7d3bb3. --- tests/oauth1/rfc5849/test_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/oauth1/rfc5849/test_client.py b/tests/oauth1/rfc5849/test_client.py index ab6f8da..e1f83de 100644 --- a/tests/oauth1/rfc5849/test_client.py +++ b/tests/oauth1/rfc5849/test_client.py @@ -39,7 +39,7 @@ class ClientConstructorTests(TestCase): def test_convert_to_unicode_resource_owner(self): client = Client('client-key', resource_owner_key=b'owner key') - self.assertFalse(isinstance(client.resource_owner_key, bytes)) + self.assertNotIsInstance(client.resource_owner_key, bytes) self.assertEqual(client.resource_owner_key, 'owner key') def test_give_explicit_timestamp(self): -- cgit v1.2.1 From c8fcbf87ca38faa4dfbe56d0609a4ce15c2d7aca Mon Sep 17 00:00:00 2001 From: jonathan vanasco Date: Fri, 14 Sep 2018 19:08:56 -0400 Subject: standardized some test values integrated against requests_oauthlib idea --- oauthlib/oauth2/rfc6749/parameters.py | 2 +- tests/oauth2/rfc6749/clients/test_legacy_application.py | 4 ++-- tests/oauth2/rfc6749/clients/test_web_application.py | 12 ++++++------ 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/oauthlib/oauth2/rfc6749/parameters.py b/oauthlib/oauth2/rfc6749/parameters.py index 2b6e854..1229f31 100644 --- a/oauthlib/oauth2/rfc6749/parameters.py +++ b/oauthlib/oauth2/rfc6749/parameters.py @@ -146,7 +146,7 @@ def prepare_token_revocation_request(url, token, token_type_hint="access_token", token types. An authorization server MAY ignore this parameter, particularly if it is able to detect the token type automatically. - + This specification defines two values for `token_type_hint`: * access_token: An access token as defined in [RFC6749], diff --git a/tests/oauth2/rfc6749/clients/test_legacy_application.py b/tests/oauth2/rfc6749/clients/test_legacy_application.py index 3f97c02..1e11112 100644 --- a/tests/oauth2/rfc6749/clients/test_legacy_application.py +++ b/tests/oauth2/rfc6749/clients/test_legacy_application.py @@ -21,8 +21,8 @@ class LegacyApplicationClientTest(TestCase): "require": "extra arguments" } - username = "foo" - password = "bar" + username = "user_username" + password = "user_password" body = "not=empty" body_up = "not=empty&grant_type=password&username=%s&password=%s" % (username, password) diff --git a/tests/oauth2/rfc6749/clients/test_web_application.py b/tests/oauth2/rfc6749/clients/test_web_application.py index 702fcf2..9144659 100644 --- a/tests/oauth2/rfc6749/clients/test_web_application.py +++ b/tests/oauth2/rfc6749/clients/test_web_application.py @@ -204,13 +204,13 @@ class WebApplicationClientTest(TestCase): # scenario 3, Include client_id and client_secret in the body (RFC alternative solution) # the order of kwargs being appended is not guaranteed. for brevity, check the 2 permutations instead of sorting - r3 = client.prepare_request_body(client_secret='secret') - self.assertIn(r3, ('grant_type=authorization_code&client_secret=secret&client_id=someclientid', - 'grant_type=authorization_code&client_id=someclientid&client_secret=secret',) + r3 = client.prepare_request_body(client_secret='someclientsecret') + self.assertIn(r3, ('grant_type=authorization_code&client_secret=someclientsecret&client_id=someclientid', + 'grant_type=authorization_code&client_id=someclientid&client_secret=someclientsecret',) ) - r3b = client.prepare_request_body(include_client_id=True, client_secret='secret') - self.assertIn(r3b, ('grant_type=authorization_code&client_secret=secret&client_id=someclientid', - 'grant_type=authorization_code&client_id=someclientid&client_secret=secret',) + r3b = client.prepare_request_body(include_client_id=True, client_secret='someclientsecret') + self.assertIn(r3b, ('grant_type=authorization_code&client_secret=someclientsecret&client_id=someclientid', + 'grant_type=authorization_code&client_id=someclientid&client_secret=someclientsecret',) ) # scenario Warnings -- cgit v1.2.1 From 037453c6f92b502eaae2acafe11161e4bb2e38bb Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Fri, 14 Sep 2018 16:17:25 -0700 Subject: Remove unmaintained nose dependency from tests The nose project has ceased development. From their docs page: https://nose.readthedocs.io/ > Note to Users > > Nose has been in maintenance mode for the past several years and will > likely cease without a new person/team to take over maintainership. > New projects should consider using Nose2, py.test, or just plain > unittest/unittest2. Simplify test infrastructure by using the stdlib unittest discover command. One fewer dependency. --- requirements-test.txt | 1 - setup.py | 3 +-- tox.ini | 5 ++++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/requirements-test.txt b/requirements-test.txt index 5bf6e06..c3e0a7b 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1,4 +1,3 @@ -r requirements.txt coverage>=3.7.1 -nose==1.3.7 mock>=2.0 diff --git a/setup.py b/setup.py index 17dcd7b..640bbe1 100755 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ def fread(fn): with open(join(dirname(__file__), fn), 'r') as f: return f.read() -tests_require = ['nose', 'cryptography', 'pyjwt>=1.0.0', 'blinker'] +tests_require = ['cryptography', 'pyjwt>=1.0.0', 'blinker'] if sys.version_info[0] == 2: tests_require.append('mock>=2.0') rsa_require = ['cryptography'] @@ -40,7 +40,6 @@ setup( platforms='any', license='BSD', packages=find_packages(exclude=('docs', 'tests', 'tests.*')), - test_suite='nose.collector', tests_require=tests_require, extras_require={ 'test': tests_require, diff --git a/tox.ini b/tox.ini index b736cf1..6f6c18c 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,10 @@ envlist = py27,py34,py35,py36,pypy,docs,readme [testenv] deps= -rrequirements-test.txt -commands=nosetests -s --with-coverage --cover-html --cover-html-dir={toxinidir}/htmlcov-{envname} --cover-erase --cover-package=oauthlib -w tests +commands= + coverage run --source oauthlib -m unittest discover + coverage report + # tox -e docs to mimick readthedocs build. # as of today, RTD is using python2.7 and doesn't run "setup.py install" -- cgit v1.2.1 From 50eccb87b4119c6e98fafc9e38ea3276bc007942 Mon Sep 17 00:00:00 2001 From: Pieter Ennes Date: Sat, 15 Sep 2018 21:09:36 +0100 Subject: Remove last remaining G+ reference. (#598) (Cherry picked from f3ae98cef91e140b10d25fbd496622d879cc0c0c) --- docs/index.rst | 6 +++--- docs/oauth2/endpoints/endpoints.rst | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index 1da2ca5..b6ce191 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -7,14 +7,14 @@ Welcome to OAuthLib's documentation! ==================================== If you can't find what you need or have suggestions for improvement, don't -hesitate to open a `new issue on GitHub`_! +hesitate to open a `new issue on GitHub`_! Check out :doc:`error_reporting` for details on how to be an awesome bug reporter. -For news and discussions please head over to our `G+ OAuthLib community`_. +For news and discussions please head over to our `Gitter OAuthLib community`_. .. _`new issue on GitHub`: https://github.com/oauthlib/oauthlib/issues/new -.. _`G+ OAuthLib community`: https://plus.google.com/communities/101889017375384052571 +.. _`Gitter OAuthLib community`: https://gitter.im/oauthlib/Lobby .. toctree:: :maxdepth: 1 diff --git a/docs/oauth2/endpoints/endpoints.rst b/docs/oauth2/endpoints/endpoints.rst index 80d5fbe..98599e8 100644 --- a/docs/oauth2/endpoints/endpoints.rst +++ b/docs/oauth2/endpoints/endpoints.rst @@ -24,7 +24,7 @@ handles user authorization, the token endpoint which provides tokens and the resource endpoint which provides access to protected resources. It is to the endpoints you will feed requests and get back an almost complete response. This process is simplified for you using a decorator such as the django one described -later (but it's applicable to all other web frameworks librairies). +later (but it's applicable to all other web frameworks libraries). The main purpose of the endpoint in OAuthLib is to figure out which grant type or token to dispatch the request to. -- cgit v1.2.1 From c4903229d99b5d694d4256efc187ac452579811a Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Sun, 16 Sep 2018 16:55:41 -0700 Subject: Correct capitalization of PyPI As spelled on https://pypi.org/. --- README.rst | 2 +- docs/installation.rst | 2 +- tox.ini | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index a84307f..7c41a80 100644 --- a/README.rst +++ b/README.rst @@ -12,7 +12,7 @@ logic for Python 2.7 and 3.4+.* :alt: Coveralls .. image:: https://img.shields.io/pypi/pyversions/oauthlib.svg :target: https://pypi.org/project/oauthlib/ - :alt: Download from PyPi + :alt: Download from PyPI .. image:: https://img.shields.io/pypi/l/oauthlib.svg :target: https://pypi.org/project/oauthlib/ :alt: License diff --git a/docs/installation.rst b/docs/installation.rst index 48e4288..72d7b08 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -9,7 +9,7 @@ For various reasons you may wish to install using your OS packaging system and install instructions for a few are shown below. Please send a PR to add a missing one. -Latest release on PYPI +Latest release on PyPI ---------------------- diff --git a/tox.ini b/tox.ini index 6f6c18c..c45f657 100644 --- a/tox.ini +++ b/tox.ini @@ -19,7 +19,7 @@ changedir=docs whitelist_externals=make commands=make clean html -# tox -e readme to mimick pypi long_description check +# tox -e readme to mimick PyPI long_description check [testenv:readme] skipsdist=True deps=readme -- cgit v1.2.1 From e7bd936434f7268b0453fd25c637034f7efd8168 Mon Sep 17 00:00:00 2001 From: jonathan vanasco Date: Mon, 17 Sep 2018 13:18:36 -0400 Subject: * added support for empty strings of `client_secret` * added LegacyApplicationClient tests to ensure the grant supports a variety of allowed methods --- CHANGELOG.rst | 10 ++++++++ oauthlib/oauth2/rfc6749/clients/web_application.py | 8 ++++++ oauthlib/oauth2/rfc6749/parameters.py | 4 +++ .../rfc6749/clients/test_legacy_application.py | 28 ++++++++++++++++++++ .../oauth2/rfc6749/clients/test_web_application.py | 30 ++++++++++++++-------- 5 files changed, 70 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index a8e1941..fd53769 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,16 @@ Changelog ========= +Unreleased +------------------ + +* OAuth2's `prepare_token_request` supports sending an empty string for `client_id` (#585) +* OAuth2's `WebApplicationClient.prepare_request_body` was refactored to better + support sending or omitting the `client_id` via a new `include_client_id` kwarg. + By default this is included. The method will also emit a DeprecationWarning if + a `client_id` parameter is submitted; the already configured `self.client_id` + is the preferred option. (#585) + 2.1.0 (2018-05-21) ------------------ diff --git a/oauthlib/oauth2/rfc6749/clients/web_application.py b/oauthlib/oauth2/rfc6749/clients/web_application.py index ec59b31..b4b109e 100644 --- a/oauthlib/oauth2/rfc6749/clients/web_application.py +++ b/oauthlib/oauth2/rfc6749/clients/web_application.py @@ -128,6 +128,14 @@ class WebApplicationClient(Client): >>> client.prepare_request_body(code='sh35ksdf09sf', foo='bar') 'grant_type=authorization_code&code=sh35ksdf09sf&foo=bar' + `Section 3.2.1` also states: + In the "authorization_code" "grant_type" request to the token + endpoint, an unauthenticated client MUST send its "client_id" to + prevent itself from inadvertently accepting a code intended for a + client with a different "client_id". This protects the client from + substitution of the authentication code. (It provides no additional + security for the protected resource.) + .. _`Section 4.1.1`: https://tools.ietf.org/html/rfc6749#section-4.1.1 .. _`Section 3.2.1`: https://tools.ietf.org/html/rfc6749#section-3.2.1 """ diff --git a/oauthlib/oauth2/rfc6749/parameters.py b/oauthlib/oauth2/rfc6749/parameters.py index 1229f31..0a36e53 100644 --- a/oauthlib/oauth2/rfc6749/parameters.py +++ b/oauthlib/oauth2/rfc6749/parameters.py @@ -124,6 +124,10 @@ def prepare_token_request(grant_type, body='', **kwargs): if kwargs[k]: params.append((unicode_type(k), kwargs[k])) + if ('client_secret' in kwargs) and ('client_secret' not in params): + if kwargs['client_secret'] == '': + params.append((unicode_type('client_secret'), kwargs['client_secret'])) + return add_params_to_qs(body, params) diff --git a/tests/oauth2/rfc6749/clients/test_legacy_application.py b/tests/oauth2/rfc6749/clients/test_legacy_application.py index 1e11112..4e518e8 100644 --- a/tests/oauth2/rfc6749/clients/test_legacy_application.py +++ b/tests/oauth2/rfc6749/clients/test_legacy_application.py @@ -15,6 +15,7 @@ from ....unittest import TestCase class LegacyApplicationClientTest(TestCase): client_id = "someclientid" + client_secret = 'someclientsecret' scope = ["/profile"] kwargs = { "some": "providers", @@ -88,3 +89,30 @@ class LegacyApplicationClientTest(TestCase): finally: signals.scope_changed.disconnect(record_scope_change) del os.environ['OAUTHLIB_RELAX_TOKEN_SCOPE'] + + def test_prepare_request_body(self): + """ + see issue #585 + https://github.com/oauthlib/oauthlib/issues/585 + """ + client = LegacyApplicationClient(self.client_id) + + # scenario 1, default behavior to not include `client_id` + r1 = client.prepare_request_body(username=self.username, password=self.password) + self.assertEqual(r1, 'grant_type=password&username=user_username&password=user_password') + + # scenario 2, include `client_id` in the body + r2 = client.prepare_request_body(username=self.username, password=self.password, client_id=self.client_id) + self.assertEqual(r2, 'grant_type=password&username=user_username&password=user_password&client_id=%s' % self.client_id) + + # scenario 3, include `client_id` + `client_secret` in the body + r3 = client.prepare_request_body(username=self.username, password=self.password, client_id=self.client_id, client_secret=self.client_secret) + self.assertEqual(r3, 'grant_type=password&username=user_username&password=user_password&client_id=%s&client_secret=%s' % (self.client_id, self.client_secret)) + + # scenario 4, `client_secret` is an empty string + r4 = client.prepare_request_body(username=self.username, password=self.password, client_id=self.client_id, client_secret='') + self.assertEqual(r4, 'grant_type=password&username=user_username&password=user_password&client_id=%s&client_secret=%s' % (self.client_id, '')) + + # scenario 4b`,` client_secret is `None` + r4b = client.prepare_request_body(username=self.username, password=self.password, client_id=self.client_id, client_secret=None) + self.assertEqual(r4b, 'grant_type=password&username=user_username&password=user_password&client_id=%s' % (self.client_id, )) diff --git a/tests/oauth2/rfc6749/clients/test_web_application.py b/tests/oauth2/rfc6749/clients/test_web_application.py index 9144659..fb800f7 100644 --- a/tests/oauth2/rfc6749/clients/test_web_application.py +++ b/tests/oauth2/rfc6749/clients/test_web_application.py @@ -21,6 +21,7 @@ from ....unittest import TestCase class WebApplicationClientTest(TestCase): client_id = "someclientid" + client_secret = 'someclientsecret' uri = "https://example.com/path?query=world" uri_id = uri + "&response_type=code&client_id=" + client_id uri_redirect = uri_id + "&redirect_uri=http%3A%2F%2Fmy.page.com%2Fcallback" @@ -188,15 +189,16 @@ class WebApplicationClientTest(TestCase): 1. Include client_id alone in the body (default) 2. Include client_id and client_secret in auth and not include them in the body (RFC preferred solution) 3. Include client_id and client_secret in the body (RFC alternative solution) + 4. Include client_id in the body and an empty string for client_secret. """ client = WebApplicationClient(self.client_id) # scenario 1, default behavior to include `client_id` r1 = client.prepare_request_body() - self.assertEqual(r1, 'grant_type=authorization_code&client_id=someclientid') + self.assertEqual(r1, 'grant_type=authorization_code&client_id=%s' % self.client_id) r1b = client.prepare_request_body(include_client_id=True) - self.assertEqual(r1b, 'grant_type=authorization_code&client_id=someclientid') + self.assertEqual(r1b, 'grant_type=authorization_code&client_id=%s' % self.client_id) # scenario 2, do not include `client_id` in the body, so it can be sent in auth. r2 = client.prepare_request_body(include_client_id=False) @@ -204,14 +206,22 @@ class WebApplicationClientTest(TestCase): # scenario 3, Include client_id and client_secret in the body (RFC alternative solution) # the order of kwargs being appended is not guaranteed. for brevity, check the 2 permutations instead of sorting - r3 = client.prepare_request_body(client_secret='someclientsecret') - self.assertIn(r3, ('grant_type=authorization_code&client_secret=someclientsecret&client_id=someclientid', - 'grant_type=authorization_code&client_id=someclientid&client_secret=someclientsecret',) - ) - r3b = client.prepare_request_body(include_client_id=True, client_secret='someclientsecret') - self.assertIn(r3b, ('grant_type=authorization_code&client_secret=someclientsecret&client_id=someclientid', - 'grant_type=authorization_code&client_id=someclientid&client_secret=someclientsecret',) - ) + r3 = client.prepare_request_body(client_secret=self.client_secret) + self.assertIn(r3, ('grant_type=authorization_code&client_secret=%s&client_id=%s' % (self.client_secret, self.client_id, ), + 'grant_type=authorization_code&client_id=%s&client_secret=%s' % (self.client_id, self.client_secret, ), + )) + r3b = client.prepare_request_body(include_client_id=True, client_secret=self.client_secret) + self.assertIn(r3b, ('grant_type=authorization_code&client_secret=%s&client_id=%s' % (self.client_secret, self.client_id, ), + 'grant_type=authorization_code&client_id=%s&client_secret=%s' % (self.client_id, self.client_secret, ), + )) + + # scenario 4, `client_secret` is an empty string + r4 = client.prepare_request_body(include_client_id=True, client_secret='') + self.assertEqual(r4, 'grant_type=authorization_code&client_id=%s&client_secret=' % self.client_id) + + # scenario 4b, `client_secret` is `None` + r4b = client.prepare_request_body(include_client_id=True, client_secret=None) + self.assertEqual(r4b, 'grant_type=authorization_code&client_id=%s' % self.client_id) # scenario Warnings with warnings.catch_warnings(record=True) as w: -- cgit v1.2.1 From b4ceb8a7fae065817f86c259dfbf91344ecb2925 Mon Sep 17 00:00:00 2001 From: jonathan vanasco Date: Mon, 17 Sep 2018 19:54:37 -0400 Subject: migrated `include_client_id` to `prepare_request_token` --- .../oauth2/rfc6749/clients/backend_application.py | 13 +++++- .../oauth2/rfc6749/clients/legacy_application.py | 11 ++++- .../oauth2/rfc6749/clients/service_application.py | 20 +++++++-- oauthlib/oauth2/rfc6749/clients/web_application.py | 5 ++- oauthlib/oauth2/rfc6749/parameters.py | 32 +++++++++++++-- .../rfc6749/clients/test_backend_application.py | 1 + .../rfc6749/clients/test_legacy_application.py | 48 ++++++++++++++++++---- .../oauth2/rfc6749/clients/test_web_application.py | 35 ++++++++++++---- 8 files changed, 137 insertions(+), 28 deletions(-) diff --git a/oauthlib/oauth2/rfc6749/clients/backend_application.py b/oauthlib/oauth2/rfc6749/clients/backend_application.py index 99dbfc5..cd46f12 100644 --- a/oauthlib/oauth2/rfc6749/clients/backend_application.py +++ b/oauthlib/oauth2/rfc6749/clients/backend_application.py @@ -30,7 +30,8 @@ class BackendApplicationClient(Client): no additional authorization request is needed. """ - def prepare_request_body(self, body='', scope=None, **kwargs): + def prepare_request_body(self, body='', scope=None, + include_client_id=None, **kwargs): """Add the client credentials to the request body. The client makes a request to the token endpoint by adding the @@ -41,6 +42,14 @@ class BackendApplicationClient(Client): into. This may contain extra paramters. Default ''. :param scope: The scope of the access request as described by `Section 3.3`_. + + :param include_client_id: `True` to send the `client_id` in the body of + the upstream request. Default `None`. This is + required if the client is not authenticating + with the authorization server as described + in `Section 3.2.1`_. + :type include_client_id: Boolean + :param kwargs: Extra credentials to include in the token request. The client MUST authenticate with the authorization server as @@ -58,5 +67,7 @@ class BackendApplicationClient(Client): .. _`Section 3.3`: https://tools.ietf.org/html/rfc6749#section-3.3 .. _`Section 3.2.1`: https://tools.ietf.org/html/rfc6749#section-3.2.1 """ + kwargs['client_id'] = self.client_id + kwargs['include_client_id'] = include_client_id return prepare_token_request('client_credentials', body=body, scope=scope, **kwargs) diff --git a/oauthlib/oauth2/rfc6749/clients/legacy_application.py b/oauthlib/oauth2/rfc6749/clients/legacy_application.py index 8f03695..a13927a 100644 --- a/oauthlib/oauth2/rfc6749/clients/legacy_application.py +++ b/oauthlib/oauth2/rfc6749/clients/legacy_application.py @@ -38,7 +38,8 @@ class LegacyApplicationClient(Client): def __init__(self, client_id, **kwargs): super(LegacyApplicationClient, self).__init__(client_id, **kwargs) - def prepare_request_body(self, username, password, body='', scope=None, **kwargs): + def prepare_request_body(self, username, password, body='', scope=None, + include_client_id=None, **kwargs): """Add the resource owner password and username to the request body. The client makes a request to the token endpoint by adding the @@ -51,6 +52,12 @@ class LegacyApplicationClient(Client): into. This may contain extra paramters. Default ''. :param scope: The scope of the access request as described by `Section 3.3`_. + :param include_client_id: `True` to send the `client_id` in the body of + the upstream request. Default `None`. This is + required if the client is not authenticating + with the authorization server as described + in `Section 3.2.1`_. + :type include_client_id: Boolean :param kwargs: Extra credentials to include in the token request. If the client type is confidential or the client was issued client @@ -70,5 +77,7 @@ class LegacyApplicationClient(Client): .. _`Section 3.3`: https://tools.ietf.org/html/rfc6749#section-3.3 .. _`Section 3.2.1`: https://tools.ietf.org/html/rfc6749#section-3.2.1 """ + kwargs['client_id'] = self.client_id + kwargs['include_client_id'] = include_client_id return prepare_token_request('password', body=body, username=username, password=password, scope=scope, **kwargs) diff --git a/oauthlib/oauth2/rfc6749/clients/service_application.py b/oauthlib/oauth2/rfc6749/clients/service_application.py index 6bb784e..759e0d2 100644 --- a/oauthlib/oauth2/rfc6749/clients/service_application.py +++ b/oauthlib/oauth2/rfc6749/clients/service_application.py @@ -72,7 +72,8 @@ class ServiceApplicationClient(Client): issued_at=None, extra_claims=None, body='', - scope=None, + scope=None, + include_client_id=None, **kwargs): """Create and add a JWT assertion to the request body. @@ -104,14 +105,25 @@ class ServiceApplicationClient(Client): :param scope: The scope of the access request. + :param include_client_id: `True` to send the `client_id` in the body of + the upstream request. Default `None`. This is + required if the client is not authenticating + with the authorization server as described + in `Section 3.2.1`_. + :type include_client_id: Boolean + :param not_before: A unix timestamp after which the JWT may be used. - Not included unless provided. + Not included unless provided. * :param jwt_id: A unique JWT token identifier. Not included unless - provided. + provided. * :param kwargs: Extra credentials to include in the token request. + Parameters marked with a `*` above are not explicit arguments in the + function definition, but are specially documented arguments for items + appearing in the generic `**kwargs` keyworded input. + The "scope" parameter may be used, as defined in the Assertion Framework for OAuth 2.0 Client Authentication and Authorization Grants [I-D.ietf-oauth-assertions] specification, to indicate the requested @@ -169,6 +181,8 @@ class ServiceApplicationClient(Client): assertion = jwt.encode(claim, key, 'RS256') assertion = to_unicode(assertion) + kwargs['client_id'] = self.client_id + kwargs['include_client_id'] = include_client_id return prepare_token_request(self.grant_type, body=body, assertion=assertion, diff --git a/oauthlib/oauth2/rfc6749/clients/web_application.py b/oauthlib/oauth2/rfc6749/clients/web_application.py index b4b109e..487e3a0 100644 --- a/oauthlib/oauth2/rfc6749/clients/web_application.py +++ b/oauthlib/oauth2/rfc6749/clients/web_application.py @@ -148,8 +148,9 @@ class WebApplicationClient(Client): if kwargs['client_id'] != self.client_id: raise ValueError("`client_id` was supplied as an argument, but " "it does not match `self.client_id`") - if include_client_id: - kwargs['client_id'] = self.client_id + + kwargs['client_id'] = self.client_id + kwargs['include_client_id'] = include_client_id return prepare_token_request('authorization_code', code=code, body=body, redirect_uri=redirect_uri, **kwargs) diff --git a/oauthlib/oauth2/rfc6749/parameters.py b/oauthlib/oauth2/rfc6749/parameters.py index 0a36e53..21c8605 100644 --- a/oauthlib/oauth2/rfc6749/parameters.py +++ b/oauthlib/oauth2/rfc6749/parameters.py @@ -87,7 +87,7 @@ def prepare_grant_uri(uri, client_id, response_type, redirect_uri=None, return add_params_to_uri(uri, params) -def prepare_token_request(grant_type, body='', **kwargs): +def prepare_token_request(grant_type, body='', include_client_id=True, **kwargs): """Prepare the access token request. The client makes a request to the token endpoint by adding the @@ -96,15 +96,33 @@ def prepare_token_request(grant_type, body='', **kwargs): :param grant_type: To indicate grant type being used, i.e. "password", "authorization_code" or "client_credentials". + :param body: Existing request body (URL encoded string) to embed parameters into. This may contain extra paramters. Default ''. - :param code: If using authorization code grant, pass the previously - obtained authorization code as the ``code`` argument. + + :param code: If using authorization_code grant, pass the previously + obtained authorization code as the ``code`` argument. + :param redirect_uri: If the "redirect_uri" parameter was included in the authorization request as described in - `Section 4.1.1`_, and their values MUST be identical. + `Section 4.1.1`_, and their values MUST be identical. * + + :param include_client_id: `True` (default) to send the `client_id` in the + body of the upstream request. This is required + if the client is not authenticating with the + authorization server as described in + `Section 3.2.1`_. + :type include_client_id: Boolean + + :param client_id: Unicode client identifier. Will only appear if + `include_client_id` is True. * + :param kwargs: Extra arguments to embed in the request body. + Parameters marked with a `*` above are not explicit arguments in the + function definition, but are specially documented arguments for items + appearing in the generic `**kwargs` keyworded input. + An example of an authorization code token request body: .. code-block:: http @@ -119,6 +137,12 @@ def prepare_token_request(grant_type, body='', **kwargs): if 'scope' in kwargs: kwargs['scope'] = list_to_scope(kwargs['scope']) + # pull the `client_id` out of the kwargs. + client_id = kwargs.pop('client_id', None) + if include_client_id: + if client_id is not None: + params.append((unicode_type('client_id'), client_id)) + for k in kwargs: # this handles: `code`, `redirect_uri`, or undocumented params if kwargs[k]: diff --git a/tests/oauth2/rfc6749/clients/test_backend_application.py b/tests/oauth2/rfc6749/clients/test_backend_application.py index 6b342f0..aa2ba2b 100644 --- a/tests/oauth2/rfc6749/clients/test_backend_application.py +++ b/tests/oauth2/rfc6749/clients/test_backend_application.py @@ -15,6 +15,7 @@ from ....unittest import TestCase class BackendApplicationClientTest(TestCase): client_id = "someclientid" + client_secret = 'someclientsecret' scope = ["/profile"] kwargs = { "some": "providers", diff --git a/tests/oauth2/rfc6749/clients/test_legacy_application.py b/tests/oauth2/rfc6749/clients/test_legacy_application.py index 4e518e8..21af4a3 100644 --- a/tests/oauth2/rfc6749/clients/test_legacy_application.py +++ b/tests/oauth2/rfc6749/clients/test_legacy_application.py @@ -10,6 +10,12 @@ from oauthlib.oauth2 import LegacyApplicationClient from ....unittest import TestCase +# this is the same import method used in oauthlib/oauth2/rfc6749/parameters.py +try: + import urlparse +except ImportError: + import urllib.parse as urlparse + @patch('time.time', new=lambda: 1000) class LegacyApplicationClientTest(TestCase): @@ -99,20 +105,44 @@ class LegacyApplicationClientTest(TestCase): # scenario 1, default behavior to not include `client_id` r1 = client.prepare_request_body(username=self.username, password=self.password) - self.assertEqual(r1, 'grant_type=password&username=user_username&password=user_password') + self.assertIn(r1, ('grant_type=password&username=%s&password=%s' % (self.username, self.password, ), + 'grant_type=password&password=%s&username=%s' % (self.password, self.username, ), + )) # scenario 2, include `client_id` in the body - r2 = client.prepare_request_body(username=self.username, password=self.password, client_id=self.client_id) - self.assertEqual(r2, 'grant_type=password&username=user_username&password=user_password&client_id=%s' % self.client_id) + r2 = client.prepare_request_body(username=self.username, password=self.password, include_client_id=True) + r2_params = dict(urlparse.parse_qsl(r2, keep_blank_values=True)) + self.assertEqual(len(r2_params.keys()), 4) + self.assertEqual(r2_params['grant_type'], 'password') + self.assertEqual(r2_params['username'], self.username) + self.assertEqual(r2_params['password'], self.password) + self.assertEqual(r2_params['client_id'], self.client_id) # scenario 3, include `client_id` + `client_secret` in the body - r3 = client.prepare_request_body(username=self.username, password=self.password, client_id=self.client_id, client_secret=self.client_secret) - self.assertEqual(r3, 'grant_type=password&username=user_username&password=user_password&client_id=%s&client_secret=%s' % (self.client_id, self.client_secret)) + r3 = client.prepare_request_body(username=self.username, password=self.password, include_client_id=True, client_secret=self.client_secret) + r3_params = dict(urlparse.parse_qsl(r3, keep_blank_values=True)) + self.assertEqual(len(r3_params.keys()), 5) + self.assertEqual(r3_params['grant_type'], 'password') + self.assertEqual(r3_params['username'], self.username) + self.assertEqual(r3_params['password'], self.password) + self.assertEqual(r3_params['client_id'], self.client_id) + self.assertEqual(r3_params['client_secret'], self.client_secret) # scenario 4, `client_secret` is an empty string - r4 = client.prepare_request_body(username=self.username, password=self.password, client_id=self.client_id, client_secret='') - self.assertEqual(r4, 'grant_type=password&username=user_username&password=user_password&client_id=%s&client_secret=%s' % (self.client_id, '')) + r4 = client.prepare_request_body(username=self.username, password=self.password, include_client_id=True, client_secret='') + r4_params = dict(urlparse.parse_qsl(r4, keep_blank_values=True)) + self.assertEqual(len(r4_params.keys()), 5) + self.assertEqual(r4_params['grant_type'], 'password') + self.assertEqual(r4_params['username'], self.username) + self.assertEqual(r4_params['password'], self.password) + self.assertEqual(r4_params['client_id'], self.client_id) + self.assertEqual(r4_params['client_secret'], '') # scenario 4b`,` client_secret is `None` - r4b = client.prepare_request_body(username=self.username, password=self.password, client_id=self.client_id, client_secret=None) - self.assertEqual(r4b, 'grant_type=password&username=user_username&password=user_password&client_id=%s' % (self.client_id, )) + r4b = client.prepare_request_body(username=self.username, password=self.password, include_client_id=True, client_secret=None) + r4b_params = dict(urlparse.parse_qsl(r4b, keep_blank_values=True)) + self.assertEqual(len(r4b_params.keys()), 4) + self.assertEqual(r4b_params['grant_type'], 'password') + self.assertEqual(r4b_params['username'], self.username) + self.assertEqual(r4b_params['password'], self.password) + self.assertEqual(r4b_params['client_id'], self.client_id) diff --git a/tests/oauth2/rfc6749/clients/test_web_application.py b/tests/oauth2/rfc6749/clients/test_web_application.py index fb800f7..3d9c188 100644 --- a/tests/oauth2/rfc6749/clients/test_web_application.py +++ b/tests/oauth2/rfc6749/clients/test_web_application.py @@ -16,6 +16,12 @@ from oauthlib.oauth2.rfc6749.clients import AUTH_HEADER, BODY, URI_QUERY from ....unittest import TestCase +# this is the same import method used in oauthlib/oauth2/rfc6749/parameters.py +try: + import urlparse +except ImportError: + import urllib.parse as urlparse + @patch('time.time', new=lambda: 1000) class WebApplicationClientTest(TestCase): @@ -207,21 +213,34 @@ class WebApplicationClientTest(TestCase): # scenario 3, Include client_id and client_secret in the body (RFC alternative solution) # the order of kwargs being appended is not guaranteed. for brevity, check the 2 permutations instead of sorting r3 = client.prepare_request_body(client_secret=self.client_secret) - self.assertIn(r3, ('grant_type=authorization_code&client_secret=%s&client_id=%s' % (self.client_secret, self.client_id, ), - 'grant_type=authorization_code&client_id=%s&client_secret=%s' % (self.client_id, self.client_secret, ), - )) + r3_params = dict(urlparse.parse_qsl(r3, keep_blank_values=True)) + self.assertEqual(len(r3_params.keys()), 3) + self.assertEqual(r3_params['grant_type'], 'authorization_code') + self.assertEqual(r3_params['client_id'], self.client_id) + self.assertEqual(r3_params['client_secret'], self.client_secret) + r3b = client.prepare_request_body(include_client_id=True, client_secret=self.client_secret) - self.assertIn(r3b, ('grant_type=authorization_code&client_secret=%s&client_id=%s' % (self.client_secret, self.client_id, ), - 'grant_type=authorization_code&client_id=%s&client_secret=%s' % (self.client_id, self.client_secret, ), - )) + r3b_params = dict(urlparse.parse_qsl(r3b, keep_blank_values=True)) + self.assertEqual(len(r3b_params.keys()), 3) + self.assertEqual(r3b_params['grant_type'], 'authorization_code') + self.assertEqual(r3b_params['client_id'], self.client_id) + self.assertEqual(r3b_params['client_secret'], self.client_secret) # scenario 4, `client_secret` is an empty string r4 = client.prepare_request_body(include_client_id=True, client_secret='') - self.assertEqual(r4, 'grant_type=authorization_code&client_id=%s&client_secret=' % self.client_id) + r4_params = dict(urlparse.parse_qsl(r4, keep_blank_values=True)) + self.assertEqual(len(r4_params.keys()), 3) + self.assertEqual(r4_params['grant_type'], 'authorization_code') + self.assertEqual(r4_params['client_id'], self.client_id) + self.assertEqual(r4_params['client_secret'], '') + # scenario 4b, `client_secret` is `None` r4b = client.prepare_request_body(include_client_id=True, client_secret=None) - self.assertEqual(r4b, 'grant_type=authorization_code&client_id=%s' % self.client_id) + r4b_params = dict(urlparse.parse_qsl(r4b, keep_blank_values=True)) + self.assertEqual(len(r4b_params.keys()), 2) + self.assertEqual(r4b_params['grant_type'], 'authorization_code') + self.assertEqual(r4b_params['client_id'], self.client_id) # scenario Warnings with warnings.catch_warnings(record=True) as w: -- cgit v1.2.1 From 5873be567db7447ac7992357b6fd61c5dc5b4bf1 Mon Sep 17 00:00:00 2001 From: Free Duerinckx Date: Thu, 20 Sep 2018 15:48:12 +0200 Subject: fixup! `invalid_grant` status code should be 400 --- oauthlib/oauth2/rfc6749/errors.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/oauthlib/oauth2/rfc6749/errors.py b/oauthlib/oauth2/rfc6749/errors.py index 482c740..8c8bda3 100644 --- a/oauthlib/oauth2/rfc6749/errors.py +++ b/oauthlib/oauth2/rfc6749/errors.py @@ -246,6 +246,8 @@ class InvalidGrantError(OAuth2Error): owner credentials) or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client. + + https://tools.ietf.org/html/rfc6749#section-5.2 """ error = 'invalid_grant' status_code = 400 -- cgit v1.2.1 From a77fb1f1a9a9295553d29f20b5cdb6bbeb22cb78 Mon Sep 17 00:00:00 2001 From: jonathan vanasco Date: Thu, 20 Sep 2018 17:56:27 -0400 Subject: * changed "function definition" to "function signature" in two docstrings * fixed some formatting issues in `prepare_token_request` docstring * slightly altered `prepare_token_request` in handling nontruthy values for `client_secret`. --- .../oauth2/rfc6749/clients/service_application.py | 2 +- oauthlib/oauth2/rfc6749/parameters.py | 33 +++++++++++++--------- .../oauth2/rfc6749/clients/test_web_application.py | 1 - 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/oauthlib/oauth2/rfc6749/clients/service_application.py b/oauthlib/oauth2/rfc6749/clients/service_application.py index 759e0d2..35333d8 100644 --- a/oauthlib/oauth2/rfc6749/clients/service_application.py +++ b/oauthlib/oauth2/rfc6749/clients/service_application.py @@ -121,7 +121,7 @@ class ServiceApplicationClient(Client): :param kwargs: Extra credentials to include in the token request. Parameters marked with a `*` above are not explicit arguments in the - function definition, but are specially documented arguments for items + function signature, but are specially documented arguments for items appearing in the generic `**kwargs` keyworded input. The "scope" parameter may be used, as defined in the Assertion diff --git a/oauthlib/oauth2/rfc6749/parameters.py b/oauthlib/oauth2/rfc6749/parameters.py index 21c8605..4d0baee 100644 --- a/oauthlib/oauth2/rfc6749/parameters.py +++ b/oauthlib/oauth2/rfc6749/parameters.py @@ -100,13 +100,6 @@ def prepare_token_request(grant_type, body='', include_client_id=True, **kwargs) :param body: Existing request body (URL encoded string) to embed parameters into. This may contain extra paramters. Default ''. - :param code: If using authorization_code grant, pass the previously - obtained authorization code as the ``code`` argument. - - :param redirect_uri: If the "redirect_uri" parameter was included in the - authorization request as described in - `Section 4.1.1`_, and their values MUST be identical. * - :param include_client_id: `True` (default) to send the `client_id` in the body of the upstream request. This is required if the client is not authenticating with the @@ -117,10 +110,22 @@ def prepare_token_request(grant_type, body='', include_client_id=True, **kwargs) :param client_id: Unicode client identifier. Will only appear if `include_client_id` is True. * + :param client_secret: Unicode client secret. Will only appear if set to a + value that is not `None`. Invoking this function with + an empty string will send an empty `client_secret` + value to the server. * + + :param code: If using authorization_code grant, pass the previously + obtained authorization code as the ``code`` argument. * + + :param redirect_uri: If the "redirect_uri" parameter was included in the + authorization request as described in + `Section 4.1.1`_, and their values MUST be identical. * + :param kwargs: Extra arguments to embed in the request body. Parameters marked with a `*` above are not explicit arguments in the - function definition, but are specially documented arguments for items + function signature, but are specially documented arguments for items appearing in the generic `**kwargs` keyworded input. An example of an authorization code token request body: @@ -143,15 +148,17 @@ def prepare_token_request(grant_type, body='', include_client_id=True, **kwargs) if client_id is not None: params.append((unicode_type('client_id'), client_id)) + # the kwargs iteration below only supports including boolean truth (truthy) + # values, but some servers may require an empty string for `client_secret` + client_secret = kwargs.pop('client_secret', None) + if client_secret is not None: + params.append((unicode_type('client_secret'), client_secret)) + + # this handles: `code`, `redirect_uri`, and other undocumented params for k in kwargs: - # this handles: `code`, `redirect_uri`, or undocumented params if kwargs[k]: params.append((unicode_type(k), kwargs[k])) - if ('client_secret' in kwargs) and ('client_secret' not in params): - if kwargs['client_secret'] == '': - params.append((unicode_type('client_secret'), kwargs['client_secret'])) - return add_params_to_qs(body, params) diff --git a/tests/oauth2/rfc6749/clients/test_web_application.py b/tests/oauth2/rfc6749/clients/test_web_application.py index 3d9c188..092f93e 100644 --- a/tests/oauth2/rfc6749/clients/test_web_application.py +++ b/tests/oauth2/rfc6749/clients/test_web_application.py @@ -234,7 +234,6 @@ class WebApplicationClientTest(TestCase): self.assertEqual(r4_params['client_id'], self.client_id) self.assertEqual(r4_params['client_secret'], '') - # scenario 4b, `client_secret` is `None` r4b = client.prepare_request_body(include_client_id=True, client_secret=None) r4b_params = dict(urlparse.parse_qsl(r4b, keep_blank_values=True)) -- cgit v1.2.1 From 326456cb78eb6b50e6f44f01cb0eaccc7652cf1f Mon Sep 17 00:00:00 2001 From: Pieter Ennes Date: Thu, 20 Sep 2018 23:10:17 +0100 Subject: Fix OIDC tests (#565) * Unmute ignored OIDC tests. * Fix more import errors. * Remove recently invalidated test for id_token_hint. * Fix tested grants. * Fix import on py27. --- oauthlib/openid/connect/core/endpoints/__init__.py | 0 .../connect/core/endpoints/pre_configured.py | 30 ++++++++++++---------- .../openid/connect/core/grant_types/__init__.py | 6 ++--- .../oauth1/rfc5849/endpoints/test_authorization.py | 2 +- tests/openid/connect/core/endpoints/__init__.py | 0 .../connect/core/endpoints/test_claims_handling.py | 7 +++-- .../test_openid_connect_params_handling.py | 8 +++--- tests/openid/connect/core/grant_types/__init__.py | 0 .../core/grant_types/test_authorization_code.py | 10 +++----- .../connect/core/grant_types/test_dispatchers.py | 8 +++--- .../openid/connect/core/grant_types/test_hybrid.py | 3 ++- .../connect/core/grant_types/test_implicit.py | 18 +++---------- .../openid/connect/core/test_request_validator.py | 2 +- tests/openid/connect/core/test_server.py | 2 +- tests/openid/connect/core/test_tokens.py | 2 +- 15 files changed, 42 insertions(+), 56 deletions(-) create mode 100644 oauthlib/openid/connect/core/endpoints/__init__.py create mode 100644 tests/openid/connect/core/endpoints/__init__.py create mode 100644 tests/openid/connect/core/grant_types/__init__.py diff --git a/oauthlib/openid/connect/core/endpoints/__init__.py b/oauthlib/openid/connect/core/endpoints/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/oauthlib/openid/connect/core/endpoints/pre_configured.py b/oauthlib/openid/connect/core/endpoints/pre_configured.py index 3bcd24d..04bd628 100644 --- a/oauthlib/openid/connect/core/endpoints/pre_configured.py +++ b/oauthlib/openid/connect/core/endpoints/pre_configured.py @@ -8,29 +8,31 @@ for providing OpenID Connect servers. """ from __future__ import absolute_import, unicode_literals -from ..grant_types import ( +from oauthlib.oauth2.rfc6749.endpoints import ( + AuthorizationEndpoint, + ResourceEndpoint, + RevocationEndpoint, + TokenEndpoint +) +from oauthlib.oauth2.rfc6749.grant_types import ( AuthorizationCodeGrant as OAuth2AuthorizationCodeGrant, - ClientCredentialsGrant, ImplicitGrant as OAuth2ImplicitGrant, + ClientCredentialsGrant, RefreshTokenGrant, ResourceOwnerPasswordCredentialsGrant ) - -from oauthlib.openid.connect.core.grant_types.authorization_code import AuthorizationCodeGrant -from oauthlib.openid.connect.core.grant_types.dispatchers import ( +from oauthlib.oauth2.rfc6749.tokens import BearerToken +from ..grant_types import ( + AuthorizationCodeGrant, + ImplicitGrant, + HybridGrant, +) +from ..grant_types.dispatchers import ( AuthorizationCodeGrantDispatcher, ImplicitTokenGrantDispatcher, AuthorizationTokenGrantDispatcher ) -from oauthlib.openid.connect.core.grant_types.implicit import ImplicitGrant -from oauthlib.openid.connect.core.grant_types.hybrid import HybridGrant -from oauthlib.openid.connect.core.tokens import JWTToken - -from ..tokens import BearerToken -from .authorization import AuthorizationEndpoint -from .resource import ResourceEndpoint -from .revocation import RevocationEndpoint -from .token import TokenEndpoint +from ..tokens import JWTToken class Server(AuthorizationEndpoint, TokenEndpoint, ResourceEndpoint, diff --git a/oauthlib/openid/connect/core/grant_types/__init__.py b/oauthlib/openid/connect/core/grant_types/__init__.py index 7fc183d..63f30ac 100644 --- a/oauthlib/openid/connect/core/grant_types/__init__.py +++ b/oauthlib/openid/connect/core/grant_types/__init__.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """ -oauthlib.oauth2.rfc6749.grant_types -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +oauthlib.openid.connect.core.grant_types +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ """ from __future__ import unicode_literals, absolute_import @@ -10,7 +10,7 @@ from .implicit import ImplicitGrant from .base import GrantTypeBase from .hybrid import HybridGrant from .exceptions import OIDCNoPrompt -from oauthlib.openid.connect.core.grant_types.dispatchers import ( +from .dispatchers import ( AuthorizationCodeGrantDispatcher, ImplicitTokenGrantDispatcher, AuthorizationTokenGrantDispatcher diff --git a/tests/oauth1/rfc5849/endpoints/test_authorization.py b/tests/oauth1/rfc5849/endpoints/test_authorization.py index 022e8e9..e9d3604 100644 --- a/tests/oauth1/rfc5849/endpoints/test_authorization.py +++ b/tests/oauth1/rfc5849/endpoints/test_authorization.py @@ -6,7 +6,7 @@ from oauthlib.oauth1 import RequestValidator from oauthlib.oauth1.rfc5849 import errors from oauthlib.oauth1.rfc5849.endpoints import AuthorizationEndpoint -from ....unittest import TestCase +from tests.unittest import TestCase class AuthorizationEndpointTest(TestCase): diff --git a/tests/openid/connect/core/endpoints/__init__.py b/tests/openid/connect/core/endpoints/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/openid/connect/core/endpoints/test_claims_handling.py b/tests/openid/connect/core/endpoints/test_claims_handling.py index 37a7cdd..d5908a8 100644 --- a/tests/openid/connect/core/endpoints/test_claims_handling.py +++ b/tests/openid/connect/core/endpoints/test_claims_handling.py @@ -11,11 +11,10 @@ from __future__ import absolute_import, unicode_literals import mock from oauthlib.oauth2 import RequestValidator +from oauthlib.openid.connect.core.endpoints.pre_configured import Server -from oauthlib.oauth2.rfc6749.endpoints.pre_configured import Server - -from ....unittest import TestCase -from .test_utils import get_query_credentials +from tests.unittest import TestCase +from tests.oauth2.rfc6749.endpoints.test_utils import get_query_credentials class TestClaimsHandling(TestCase): diff --git a/tests/openid/connect/core/endpoints/test_openid_connect_params_handling.py b/tests/openid/connect/core/endpoints/test_openid_connect_params_handling.py index 89431b6..517239a 100644 --- a/tests/openid/connect/core/endpoints/test_openid_connect_params_handling.py +++ b/tests/openid/connect/core/endpoints/test_openid_connect_params_handling.py @@ -5,10 +5,10 @@ import mock from oauthlib.oauth2 import InvalidRequestError from oauthlib.oauth2.rfc6749.endpoints.authorization import \ AuthorizationEndpoint -from oauthlib.oauth2.rfc6749.grant_types import OpenIDConnectAuthCode from oauthlib.oauth2.rfc6749.tokens import BearerToken +from oauthlib.openid.connect.core.grant_types import AuthorizationCodeGrant -from ....unittest import TestCase +from tests.unittest import TestCase try: from urllib.parse import urlencode @@ -16,14 +16,12 @@ except ImportError: from urllib import urlencode - - class OpenIDConnectEndpointTest(TestCase): def setUp(self): self.mock_validator = mock.MagicMock() self.mock_validator.authenticate_client.side_effect = self.set_client - grant = OpenIDConnectAuthCode(request_validator=self.mock_validator) + grant = AuthorizationCodeGrant(request_validator=self.mock_validator) bearer = BearerToken(self.mock_validator) self.endpoint = AuthorizationEndpoint(grant, bearer, response_types={'code': grant}) diff --git a/tests/openid/connect/core/grant_types/__init__.py b/tests/openid/connect/core/grant_types/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/openid/connect/core/grant_types/test_authorization_code.py b/tests/openid/connect/core/grant_types/test_authorization_code.py index 1bad120..9bbe7fb 100644 --- a/tests/openid/connect/core/grant_types/test_authorization_code.py +++ b/tests/openid/connect/core/grant_types/test_authorization_code.py @@ -11,8 +11,9 @@ from oauthlib.oauth2.rfc6749.tokens import BearerToken from oauthlib.openid.connect.core.grant_types.authorization_code import AuthorizationCodeGrant from oauthlib.openid.connect.core.grant_types.exceptions import OIDCNoPrompt -from ....unittest import TestCase -from ....oauth2.rfc6749.grant_types.test_authorization_code import AuthorizationCodeGrantTest +from tests.unittest import TestCase +from tests.oauth2.rfc6749.grant_types.test_authorization_code import \ + AuthorizationCodeGrantTest def get_id_token_mock(token, token_handler, request): @@ -81,12 +82,7 @@ class OpenIDAuthCodeTest(TestCase): self.auth.validate_authorization_request, self.request) - # prompt == none requires id token hint bearer = BearerToken(self.mock_validator) - h, b, s = self.auth.create_authorization_response(self.request, bearer) - self.assertIn('error=invalid_request', h['Location']) - self.assertEqual(b, None) - self.assertEqual(s, 302) self.request.response_mode = 'query' self.request.id_token_hint = 'me@email.com' diff --git a/tests/openid/connect/core/grant_types/test_dispatchers.py b/tests/openid/connect/core/grant_types/test_dispatchers.py index 84f2688..e7dce45 100644 --- a/tests/openid/connect/core/grant_types/test_dispatchers.py +++ b/tests/openid/connect/core/grant_types/test_dispatchers.py @@ -17,7 +17,7 @@ from oauthlib.oauth2.rfc6749.grant_types import ( ) -from ....unittest import TestCase +from tests.unittest import TestCase class ImplicitTokenGrantDispatcherTest(TestCase): @@ -47,12 +47,12 @@ class ImplicitTokenGrantDispatcherTest(TestCase): def test_create_authorization_response_oauth(self): self.request.scopes = ('hello', 'world') handler = self.dispatcher._handler_for_request(self.request) - self.assertIsInstance(handler, ImplicitGrant) + self.assertIsInstance(handler, OAuth2ImplicitGrant) def test_validate_authorization_request_oauth(self): self.request.scopes = ('hello', 'world') handler = self.dispatcher._handler_for_request(self.request) - self.assertIsInstance(handler, ImplicitGrant) + self.assertIsInstance(handler, OAuth2ImplicitGrant) class DispatcherTest(TestCase): @@ -66,7 +66,7 @@ class DispatcherTest(TestCase): self.request_validator = mock.MagicMock() self.auth_grant = OAuth2AuthorizationCodeGrant(self.request_validator) - self.openid_connect_auth = OAuth2AuthorizationCodeGrant(self.request_validator) + self.openid_connect_auth = AuthorizationCodeGrant(self.request_validator) class AuthTokenGrantDispatcherOpenIdTest(DispatcherTest): diff --git a/tests/openid/connect/core/grant_types/test_hybrid.py b/tests/openid/connect/core/grant_types/test_hybrid.py index 531ae7f..6eb8037 100644 --- a/tests/openid/connect/core/grant_types/test_hybrid.py +++ b/tests/openid/connect/core/grant_types/test_hybrid.py @@ -2,7 +2,8 @@ from __future__ import absolute_import, unicode_literals from oauthlib.openid.connect.core.grant_types.hybrid import HybridGrant -from ....oauth2.rfc6749.grant_types.test_authorization_code import AuthorizationCodeGrantTest +from tests.oauth2.rfc6749.grant_types.test_authorization_code import \ + AuthorizationCodeGrantTest class OpenIDHybridInterferenceTest(AuthorizationCodeGrantTest): diff --git a/tests/openid/connect/core/grant_types/test_implicit.py b/tests/openid/connect/core/grant_types/test_implicit.py index 56247d9..c369bb6 100644 --- a/tests/openid/connect/core/grant_types/test_implicit.py +++ b/tests/openid/connect/core/grant_types/test_implicit.py @@ -4,18 +4,14 @@ from __future__ import absolute_import, unicode_literals import mock from oauthlib.common import Request - from oauthlib.oauth2.rfc6749.tokens import BearerToken - -from oauthlib.openid.connect.core.grant_types.implicit import ImplicitGrant -from oauthlib.openid.connect.core.grant_types.hybrid import HybridGrant from oauthlib.openid.connect.core.grant_types.exceptions import OIDCNoPrompt - -from ....unittest import TestCase +from oauthlib.openid.connect.core.grant_types.hybrid import HybridGrant +from oauthlib.openid.connect.core.grant_types.implicit import ImplicitGrant +from tests.oauth2.rfc6749.grant_types.test_implicit import ImplicitGrantTest +from tests.unittest import TestCase from .test_authorization_code import get_id_token_mock, OpenIDAuthCodeTest -from ....oauth2.rfc6749.grant_types.test_implicit import ImplicitGrantTest - class OpenIDImplicitInterferenceTest(ImplicitGrantTest): """Test that OpenID don't interfere with normal OAuth 2 flows.""" @@ -80,13 +76,7 @@ class OpenIDImplicitTest(TestCase): self.auth.validate_authorization_request, self.request) - # prompt == none requires id token hint bearer = BearerToken(self.mock_validator) - h, b, s = self.auth.create_authorization_response(self.request, bearer) - self.assertIn('error=invalid_request', h['Location']) - self.assertEqual(b, None) - self.assertEqual(s, 302) - self.request.id_token_hint = 'me@email.com' h, b, s = self.auth.create_authorization_response(self.request, bearer) self.assertURLEqual(h['Location'], self.url_fragment, parse_fragment=True) diff --git a/tests/openid/connect/core/test_request_validator.py b/tests/openid/connect/core/test_request_validator.py index 14a7c23..1e71fb1 100644 --- a/tests/openid/connect/core/test_request_validator.py +++ b/tests/openid/connect/core/test_request_validator.py @@ -3,7 +3,7 @@ from __future__ import absolute_import, unicode_literals from oauthlib.openid.connect.core.request_validator import RequestValidator -from ....unittest import TestCase +from tests.unittest import TestCase class RequestValidatorTest(TestCase): diff --git a/tests/openid/connect/core/test_server.py b/tests/openid/connect/core/test_server.py index 83290db..a83f22d 100644 --- a/tests/openid/connect/core/test_server.py +++ b/tests/openid/connect/core/test_server.py @@ -14,7 +14,7 @@ from oauthlib.openid.connect.core.grant_types.authorization_code import Authoriz from oauthlib.openid.connect.core.grant_types.implicit import ImplicitGrant from oauthlib.openid.connect.core.grant_types.hybrid import HybridGrant -from ....unittest import TestCase +from tests.unittest import TestCase class AuthorizationEndpointTest(TestCase): diff --git a/tests/openid/connect/core/test_tokens.py b/tests/openid/connect/core/test_tokens.py index 12c75f1..1fcfb51 100644 --- a/tests/openid/connect/core/test_tokens.py +++ b/tests/openid/connect/core/test_tokens.py @@ -4,7 +4,7 @@ import mock from oauthlib.openid.connect.core.tokens import JWTToken -from ....unittest import TestCase +from tests.unittest import TestCase class JWTTokenTestCase(TestCase): -- cgit v1.2.1