From 1dd58397d6a86fc930062cfecc6e2ea94e546aa7 Mon Sep 17 00:00:00 2001 From: Duane King Date: Thu, 24 Jan 2019 20:36:52 -0800 Subject: pep8 and docs --- oauthlib/oauth2/rfc6749/clients/base.py | 35 +++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/oauthlib/oauth2/rfc6749/clients/base.py b/oauthlib/oauth2/rfc6749/clients/base.py index 1a50644..9b05ad5 100644 --- a/oauthlib/oauth2/rfc6749/clients/base.py +++ b/oauthlib/oauth2/rfc6749/clients/base.py @@ -28,8 +28,8 @@ FORM_ENC_HEADERS = { 'Content-Type': 'application/x-www-form-urlencoded' } -class Client(object): +class Client(object): """Base OAuth2 client responsible for access token management. This class also acts as a generic interface providing methods common to all @@ -201,7 +201,7 @@ class Client(object): headers, token_placement, **kwargs) def prepare_authorization_request(self, authorization_url, state=None, - redirect_url=None, scope=None, **kwargs): + redirect_url=None, scope=None, **kwargs): """Prepare the authorization request. This is the first step in many OAuth flows in which the user is @@ -222,6 +222,8 @@ class Client(object): the provider. If provided then it must also be provided in the token request. + :param scope: + :param kwargs: Additional parameters to included in the request. :returns: The prepared request tuple with (url, headers, body). @@ -233,12 +235,12 @@ class Client(object): self.redirect_url = redirect_url or self.redirect_url self.scope = scope or self.scope auth_url = self.prepare_request_uri( - authorization_url, redirect_uri=self.redirect_url, - scope=self.scope, state=self.state, **kwargs) + authorization_url, redirect_uri=self.redirect_url, + scope=self.scope, state=self.state, **kwargs) return auth_url, FORM_ENC_HEADERS, '' def prepare_token_request(self, token_url, authorization_response=None, - redirect_url=None, state=None, body='', **kwargs): + redirect_url=None, state=None, body='', **kwargs): """Prepare a token creation request. Note that these requests usually require client authentication, either @@ -255,6 +257,8 @@ class Client(object): :param redirect_url: The redirect_url supplied with the authorization request (if there was one). + :param state: + :param body: Existing request body (URL encoded string) to embed parameters into. This may contain extra paramters. Default ''. @@ -268,15 +272,15 @@ class Client(object): state = state or self.state if authorization_response: self.parse_request_uri_response( - authorization_response, state=state) + authorization_response, state=state) self.redirect_url = redirect_url or self.redirect_url body = self.prepare_request_body(body=body, - redirect_uri=self.redirect_url, **kwargs) + redirect_uri=self.redirect_url, **kwargs) return token_url, FORM_ENC_HEADERS, body def prepare_refresh_token_request(self, token_url, refresh_token=None, - body='', scope=None, **kwargs): + body='', scope=None, **kwargs): """Prepare an access token refresh request. Expired access tokens can be replaced by new access tokens without @@ -304,11 +308,11 @@ class Client(object): self.scope = scope or self.scope body = self.prepare_refresh_body(body=body, - refresh_token=refresh_token, scope=self.scope, **kwargs) + refresh_token=refresh_token, scope=self.scope, **kwargs) return token_url, FORM_ENC_HEADERS, body def prepare_token_revocation_request(self, revocation_url, token, - token_type_hint="access_token", body='', callback=None, **kwargs): + token_type_hint="access_token", body='', callback=None, **kwargs): """Prepare a token revocation request. :param revocation_url: Provider token revocation endpoint URL. @@ -319,6 +323,8 @@ class Client(object): ``"refresh_token"``. This is optional and if you wish to not pass it you must provide ``token_type_hint=None``. + :param body: + :param callback: A jsonp callback such as ``package.callback`` to be invoked upon receiving the response. Not that it should not include a () suffix. @@ -363,8 +369,8 @@ class Client(object): raise InsecureTransportError() return prepare_token_revocation_request(revocation_url, token, - token_type_hint=token_type_hint, body=body, callback=callback, - **kwargs) + token_type_hint=token_type_hint, body=body, callback=callback, + **kwargs) def parse_request_body_response(self, body, scope=None, **kwargs): """Parse the JSON response body. @@ -404,7 +410,7 @@ class Client(object): If omitted, the authorization server SHOULD provide the expiration time via other means or document the default value. - **scope** + **scope** Providers may supply this in all responses but are required to only if it has changed since the authorization request. @@ -461,6 +467,9 @@ class Client(object): Warning: MAC token support is experimental as the spec is not yet stable. """ + if token_placement != AUTH_HEADER: + raise ValueError("Invalid token placement.") + headers = tokens.prepare_mac_header(self.access_token, uri, self.mac_key, http_method, headers=headers, body=body, ext=ext, hash_algorithm=self.mac_algorithm, **kwargs) -- cgit v1.2.1 From e2562942279ad832f80b27e9629cf4d46a1c9bc5 Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Thu, 31 Jan 2019 13:44:21 +0100 Subject: Add 3.0.1 changelog --- CHANGELOG.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 2cc0dd3..a5cb324 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,10 @@ Changelog ========= +3.0.1 (2019-01-24) +------------------ +* Fixed OAuth2.0 regression introduced in 3.0.0: Revocation with Basic auth no longer possible #644 + 3.0.0 (2019-01-01) ------------------ OAuth2.0 Provider - outstanding Features -- cgit v1.2.1 From 0ef0a9c4342dfee4bd3aef7d6d9fa09e7226a732 Mon Sep 17 00:00:00 2001 From: Hoylen Sue Date: Tue, 19 Feb 2019 20:51:49 +1000 Subject: Fixed space encoding in base string URI used in the signature base string. --- CHANGELOG.rst | 5 +++++ oauthlib/oauth1/rfc5849/signature.py | 38 +++++++++++++++++++++++++++------ tests/oauth1/rfc5849/test_signatures.py | 27 ++++++++++++++++------- 3 files changed, 55 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 2cc0dd3..8036614 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,11 @@ Changelog ========= +TBD +--- + +* Fixed space encoding in base string URI used in the signature base string. + 3.0.0 (2019-01-01) ------------------ OAuth2.0 Provider - outstanding Features diff --git a/oauthlib/oauth1/rfc5849/signature.py b/oauthlib/oauth1/rfc5849/signature.py index e90d6f3..589b68a 100644 --- a/oauthlib/oauth1/rfc5849/signature.py +++ b/oauthlib/oauth1/rfc5849/signature.py @@ -105,9 +105,9 @@ def construct_base_string(http_method, base_string_uri, return base_string -def normalize_base_string_uri(uri, host=None): +def base_string_uri(uri, host=None): """**Base String URI** - Per `section 3.4.1.2`_ of the spec. + Per `section 3.4.1.2`_ of RFC 5849. For example, the HTTP request:: @@ -177,7 +177,31 @@ def normalize_base_string_uri(uri, host=None): if (scheme, port) in default_ports: netloc = host - return urlparse.urlunparse((scheme, netloc, path, params, '', '')) + v = urlparse.urlunparse((scheme, netloc, path, params, '', '')) + + # RFC 5849 does not specify which characters are encoded in the + # "base string URI", nor how they are encoded - which is very bad, since + # the signatures won't match if there are any differences. Fortunately, + # most URIs only use characters that are clearly not encoded (e.g. digits + # and A-Z, a-z), so have avoided any differences between implementations. + # + # The example from its section 3.4.1.2 illustrates that spaces in + # the path are percent encoded. But it provides no guidance as to what other + # characters (if any) must be encoded (nor how); nor if characters in the + # other components are to be encoded or not. + # + # This implementation **assumes** that **only** the space is percent-encoded + # and it is done to the entire value (not just to spaces in the path). + # + # This code may need to be changed if it is discovered that other characters + # are expected to be encoded. + # + # Note: the "base string URI" returned by this function will be encoded + # again before being concatenated into the "signature base string". So any + # spaces in the URI will actually appear in the "signature base string" + # as "%2520" (the "%20" further encoded according to section 3.6). + + return v.replace(' ', '%20') # ** Request Parameters ** @@ -624,8 +648,8 @@ def verify_hmac_sha1(request, client_secret=None, """ norm_params = normalize_parameters(request.params) - uri = normalize_base_string_uri(request.uri) - base_string = construct_base_string(request.http_method, uri, norm_params) + bs_uri = base_string_uri(request.uri) + base_string = construct_base_string(request.http_method, bs_uri, norm_params) signature = sign_hmac_sha1(base_string, client_secret, resource_owner_secret) match = safe_string_equals(signature, request.signature) @@ -657,8 +681,8 @@ def verify_rsa_sha1(request, rsa_public_key): .. _`RFC2616 section 5.2`: https://tools.ietf.org/html/rfc2616#section-5.2 """ norm_params = normalize_parameters(request.params) - uri = normalize_base_string_uri(request.uri) - message = construct_base_string(request.http_method, uri, norm_params).encode('utf-8') + bs_uri = base_string_uri(request.uri) + message = construct_base_string(request.http_method, bs_uri, norm_params).encode('utf-8') sig = binascii.a2b_base64(request.signature.encode('utf-8')) alg = _jwt_rs1_signing_algorithm() diff --git a/tests/oauth1/rfc5849/test_signatures.py b/tests/oauth1/rfc5849/test_signatures.py index 48609e5..db2c532 100644 --- a/tests/oauth1/rfc5849/test_signatures.py +++ b/tests/oauth1/rfc5849/test_signatures.py @@ -4,7 +4,7 @@ from __future__ import absolute_import, unicode_literals from oauthlib.common import unicode_type from oauthlib.oauth1.rfc5849.signature import (collect_parameters, construct_base_string, - normalize_base_string_uri, + base_string_uri, normalize_parameters, sign_hmac_sha1, sign_hmac_sha1_with_client, @@ -125,7 +125,7 @@ class SignatureTests(TestCase): self.assertEqual(self.control_base_string, base_string) - def test_normalize_base_string_uri(self): + def test_base_string_uri(self): """ Example text to be turned into a normalized base string uri:: @@ -137,33 +137,44 @@ class SignatureTests(TestCase): https://www.example.net:8080/ """ + # test first example from RFC 5849 section 3.4.1.2. + # Note: there is a space between "r" and "v" + uri = 'http://EXAMPLE.COM:80/r v/X?id=123' + self.assertEqual(base_string_uri(uri), + 'http://example.com/r%20v/X') + + # test second example from RFC 5849 section 3.4.1.2. + uri = 'https://www.example.net:8080/?q=1' + self.assertEqual(base_string_uri(uri), + 'https://www.example.net:8080/') + # test for unicode failure uri = b"www.example.com:8080" - self.assertRaises(ValueError, normalize_base_string_uri, uri) + self.assertRaises(ValueError, base_string_uri, uri) # test for missing scheme uri = "www.example.com:8080" - self.assertRaises(ValueError, normalize_base_string_uri, uri) + self.assertRaises(ValueError, base_string_uri, uri) # test a URI with the default port uri = "http://www.example.com:80/" - self.assertEqual(normalize_base_string_uri(uri), + self.assertEqual(base_string_uri(uri), "http://www.example.com/") # test a URI missing a path uri = "http://www.example.com" - self.assertEqual(normalize_base_string_uri(uri), + self.assertEqual(base_string_uri(uri), "http://www.example.com/") # test a relative URI uri = "/a-host-relative-uri" host = "www.example.com" - self.assertRaises(ValueError, normalize_base_string_uri, (uri, host)) + self.assertRaises(ValueError, base_string_uri, (uri, host)) # test overriding the URI's netloc with a host argument uri = "http://www.example.com/a-path" host = "alternatehost.example.com" - self.assertEqual(normalize_base_string_uri(uri, host), + self.assertEqual(base_string_uri(uri, host), "http://alternatehost.example.com/a-path") def test_collect_parameters(self): -- cgit v1.2.1 From 8c9f0a3cee9fab35fdf7269441daab666b931f59 Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Wed, 20 Feb 2019 14:30:03 +0100 Subject: Fix 652: removed "state" from /token response. Fix OIDC /token flow where &state=None was always returned, and fix OAuth2.0 /token flow where &state=foobar was returned if &state=foobar was present in the token request. Remove "save_token" from create_token() signature cuz it was not used internally. Deprecated the option to let upstream libraries have a chance to remove it, if ever used. --- CHANGELOG.rst | 4 +++ .../rfc6749/grant_types/authorization_code.py | 4 ++- .../rfc6749/grant_types/client_credentials.py | 3 +- oauthlib/oauth2/rfc6749/grant_types/implicit.py | 5 ++- .../oauth2/rfc6749/grant_types/refresh_token.py | 3 +- .../resource_owner_password_credentials.py | 3 +- oauthlib/oauth2/rfc6749/tokens.py | 18 +++++----- oauthlib/openid/connect/core/grant_types/base.py | 3 -- .../openid/connect/core/grant_types/implicit.py | 5 +++ oauthlib/openid/connect/core/tokens.py | 2 +- .../endpoints/test_credentials_preservation.py | 12 ------- tests/oauth2/rfc6749/test_server.py | 39 +++++++++++++++------- tests/openid/connect/core/test_server.py | 16 +++++---- 13 files changed, 68 insertions(+), 49 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index a5cb324..9e0efda 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,10 @@ Changelog ========= +TBD +------------------ +* #652: Fixed OIDC /token response which wrongly returned "&state=None" + 3.0.1 (2019-01-24) ------------------ * Fixed OAuth2.0 regression introduced in 3.0.0: Revocation with Basic auth no longer possible #644 diff --git a/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py b/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py index 6463391..5f03d9c 100644 --- a/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py +++ b/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py @@ -305,9 +305,11 @@ class AuthorizationCodeGrant(GrantTypeBase): headers.update(e.headers) return headers, e.json, e.status_code - token = token_handler.create_token(request, refresh_token=self.refresh_token, save_token=False) + token = token_handler.create_token(request, refresh_token=self.refresh_token) + for modifier in self._token_modifiers: token = modifier(token, token_handler, request) + self.request_validator.save_token(token, request) self.request_validator.invalidate_authorization_code( request.client_id, request.code, request) diff --git a/oauthlib/oauth2/rfc6749/grant_types/client_credentials.py b/oauthlib/oauth2/rfc6749/grant_types/client_credentials.py index c966795..7e50857 100644 --- a/oauthlib/oauth2/rfc6749/grant_types/client_credentials.py +++ b/oauthlib/oauth2/rfc6749/grant_types/client_credentials.py @@ -76,10 +76,11 @@ class ClientCredentialsGrant(GrantTypeBase): headers.update(e.headers) return headers, e.json, e.status_code - token = token_handler.create_token(request, refresh_token=False, save_token=False) + token = token_handler.create_token(request, refresh_token=False) for modifier in self._token_modifiers: token = modifier(token) + self.request_validator.save_token(token, request) log.debug('Issuing token to client id %r (%r), %r.', diff --git a/oauthlib/oauth2/rfc6749/grant_types/implicit.py b/oauthlib/oauth2/rfc6749/grant_types/implicit.py index d6de906..48bae7a 100644 --- a/oauthlib/oauth2/rfc6749/grant_types/implicit.py +++ b/oauthlib/oauth2/rfc6749/grant_types/implicit.py @@ -237,10 +237,13 @@ class ImplicitGrant(GrantTypeBase): # "id_token token" - return the access token and the id token # "id_token" - don't return the access token if "token" in request.response_type.split(): - token = token_handler.create_token(request, refresh_token=False, save_token=False) + token = token_handler.create_token(request, refresh_token=False) else: token = {} + if request.state is not None: + token['state'] = request.state + for modifier in self._token_modifiers: token = modifier(token, token_handler, request) diff --git a/oauthlib/oauth2/rfc6749/grant_types/refresh_token.py b/oauthlib/oauth2/rfc6749/grant_types/refresh_token.py index bd519e8..fc61d65 100644 --- a/oauthlib/oauth2/rfc6749/grant_types/refresh_token.py +++ b/oauthlib/oauth2/rfc6749/grant_types/refresh_token.py @@ -64,10 +64,11 @@ class RefreshTokenGrant(GrantTypeBase): return headers, e.json, e.status_code token = token_handler.create_token(request, - refresh_token=self.issue_new_refresh_tokens, save_token=False) + refresh_token=self.issue_new_refresh_tokens) for modifier in self._token_modifiers: token = modifier(token) + self.request_validator.save_token(token, request) log.debug('Issuing new token to client id %r (%r), %r.', 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 f765d91..5929afb 100644 --- a/oauthlib/oauth2/rfc6749/grant_types/resource_owner_password_credentials.py +++ b/oauthlib/oauth2/rfc6749/grant_types/resource_owner_password_credentials.py @@ -104,10 +104,11 @@ class ResourceOwnerPasswordCredentialsGrant(GrantTypeBase): headers.update(e.headers) return headers, e.json, e.status_code - token = token_handler.create_token(request, self.refresh_token, save_token=False) + token = token_handler.create_token(request, self.refresh_token) for modifier in self._token_modifiers: token = modifier(token) + self.request_validator.save_token(token, request) log.debug('Issuing token %r to client id %r (%r) and username %s.', diff --git a/oauthlib/oauth2/rfc6749/tokens.py b/oauthlib/oauth2/rfc6749/tokens.py index d78df09..44a9a97 100644 --- a/oauthlib/oauth2/rfc6749/tokens.py +++ b/oauthlib/oauth2/rfc6749/tokens.py @@ -12,6 +12,7 @@ from __future__ import absolute_import, unicode_literals import hashlib import hmac from binascii import b2a_base64 +import warnings from oauthlib import common from oauthlib.common import add_params_to_qs, add_params_to_uri, unicode_type @@ -296,15 +297,18 @@ class BearerToken(TokenBase): ) self.expires_in = expires_in or 3600 - def create_token(self, request, refresh_token=False, save_token=True): + def create_token(self, request, refresh_token=False, **kwargs): """ Create a BearerToken, by default without refresh token. - + :param request: OAuthlib request. :type request: oauthlib.common.Request :param refresh_token: - :param save_token: """ + if "save_token" in kwargs: + warnings.warn("`save_token` has been deprecated, it was not used internally." + "If you do, use `request_validator.save_token()` instead.", + DeprecationWarning) if callable(self.expires_in): expires_in = self.expires_in(request) @@ -325,9 +329,6 @@ class BearerToken(TokenBase): if request.scopes is not None: token['scope'] = ' '.join(request.scopes) - if request.state is not None: - token['state'] = request.state - if refresh_token: if (request.refresh_token and not self.request_validator.rotate_refresh_token(request)): @@ -336,10 +337,7 @@ class BearerToken(TokenBase): token['refresh_token'] = self.refresh_token_generator(request) token.update(request.extra_credentials or {}) - token = OAuth2Token(token) - if save_token: - self.request_validator.save_bearer_token(token, request) - return token + return OAuth2Token(token) def validate_request(self, request): """ diff --git a/oauthlib/openid/connect/core/grant_types/base.py b/oauthlib/openid/connect/core/grant_types/base.py index fa578a5..05cdd37 100644 --- a/oauthlib/openid/connect/core/grant_types/base.py +++ b/oauthlib/openid/connect/core/grant_types/base.py @@ -58,9 +58,6 @@ class GrantTypeBase(object): if request.response_type and 'id_token' not in request.response_type: return token - if 'state' not in token: - token['state'] = request.state - if request.max_age: d = datetime.datetime.utcnow() token['auth_time'] = d.isoformat("T") + "Z" diff --git a/oauthlib/openid/connect/core/grant_types/implicit.py b/oauthlib/openid/connect/core/grant_types/implicit.py index 0eaa5b3..0a6fcb7 100644 --- a/oauthlib/openid/connect/core/grant_types/implicit.py +++ b/oauthlib/openid/connect/core/grant_types/implicit.py @@ -26,3 +26,8 @@ class ImplicitGrant(GrantTypeBase): self.custom_validators.post_auth.append( self.openid_implicit_authorization_validator) self.register_token_modifier(self.add_id_token) + + def add_id_token(self, token, token_handler, request): + if 'state' not in token: + token['state'] = request.state + return super(ImplicitGrant, self).add_id_token(token, token_handler, request) diff --git a/oauthlib/openid/connect/core/tokens.py b/oauthlib/openid/connect/core/tokens.py index 6b68891..b67cdf2 100644 --- a/oauthlib/openid/connect/core/tokens.py +++ b/oauthlib/openid/connect/core/tokens.py @@ -25,7 +25,7 @@ class JWTToken(TokenBase): ) self.expires_in = expires_in or 3600 - def create_token(self, request, refresh_token=False, save_token=False): + def create_token(self, request, refresh_token=False): """Create a JWT Token, using requestvalidator method.""" if callable(self.expires_in): diff --git a/tests/oauth2/rfc6749/endpoints/test_credentials_preservation.py b/tests/oauth2/rfc6749/endpoints/test_credentials_preservation.py index 1a2f66b..c77d18e 100644 --- a/tests/oauth2/rfc6749/endpoints/test_credentials_preservation.py +++ b/tests/oauth2/rfc6749/endpoints/test_credentials_preservation.py @@ -42,18 +42,6 @@ class PreservationTest(TestCase): def test_state_preservation(self): auth_uri = 'http://example.com/path?state=xyz&client_id=abc&response_type=' - token_uri = 'http://example.com/path' - - # authorization grant - h, _, s = self.web.create_authorization_response( - auth_uri + 'code', scopes=['random']) - self.assertEqual(s, 302) - self.assertIn('Location', h) - code = get_query_credentials(h['Location'])['code'][0] - self.validator.validate_code.side_effect = self.set_state('xyz') - _, body, _ = self.web.create_token_response(token_uri, - body='grant_type=authorization_code&code=%s' % code) - self.assertEqual(json.loads(body)['state'], 'xyz') # implicit grant h, _, s = self.mobile.create_authorization_response( diff --git a/tests/oauth2/rfc6749/test_server.py b/tests/oauth2/rfc6749/test_server.py index b623a9b..2c6ecff 100644 --- a/tests/oauth2/rfc6749/test_server.py +++ b/tests/oauth2/rfc6749/test_server.py @@ -144,7 +144,7 @@ class TokenEndpointTest(TestCase): @mock.patch('oauthlib.common.generate_token', new=lambda: 'abc') def test_authorization_grant(self): - body = 'grant_type=authorization_code&code=abc&scope=all+of+them&state=xyz' + body = 'grant_type=authorization_code&code=abc&scope=all+of+them' headers, body, status_code = self.endpoint.create_token_response( '', body=body) token = { @@ -152,23 +152,27 @@ class TokenEndpointTest(TestCase): 'expires_in': self.expires_in, 'access_token': 'abc', 'refresh_token': 'abc', - 'scope': 'all of them', - 'state': 'xyz' + 'scope': 'all of them' } self.assertEqual(json.loads(body), token) - body = 'grant_type=authorization_code&code=abc&state=xyz' + body = 'grant_type=authorization_code&code=abc' headers, body, status_code = self.endpoint.create_token_response( '', body=body) token = { 'token_type': 'Bearer', 'expires_in': self.expires_in, 'access_token': 'abc', - 'refresh_token': 'abc', - 'state': 'xyz' + 'refresh_token': 'abc' } self.assertEqual(json.loads(body), token) + # try with additional custom variables + body = 'grant_type=authorization_code&code=abc&state=foobar' + headers, body, status_code = self.endpoint.create_token_response( + '', body=body) + self.assertEqual(json.loads(body), token) + @mock.patch('oauthlib.common.generate_token', new=lambda: 'abc') def test_password_grant(self): body = 'grant_type=password&username=a&password=hello&scope=all+of+them' @@ -277,7 +281,7 @@ twIDAQAB @mock.patch('oauthlib.common.generate_token', new=lambda: 'abc') def test_authorization_grant(self): - body = 'client_id=me&redirect_uri=http%3A%2F%2Fback.to%2Fme&grant_type=authorization_code&code=abc&scope=all+of+them&state=xyz' + body = 'client_id=me&redirect_uri=http%3A%2F%2Fback.to%2Fme&grant_type=authorization_code&code=abc&scope=all+of+them' headers, body, status_code = self.endpoint.create_token_response( '', body=body) body = json.loads(body) @@ -286,12 +290,11 @@ twIDAQAB 'expires_in': self.expires_in, 'access_token': body['access_token'], 'refresh_token': 'abc', - 'scope': 'all of them', - 'state': 'xyz' + 'scope': 'all of them' } self.assertEqual(body, token) - body = 'client_id=me&redirect_uri=http%3A%2F%2Fback.to%2Fme&grant_type=authorization_code&code=abc&state=xyz' + body = 'client_id=me&redirect_uri=http%3A%2F%2Fback.to%2Fme&grant_type=authorization_code&code=abc' headers, body, status_code = self.endpoint.create_token_response( '', body=body) body = json.loads(body) @@ -299,8 +302,20 @@ twIDAQAB 'token_type': 'Bearer', 'expires_in': self.expires_in, 'access_token': body['access_token'], - 'refresh_token': 'abc', - 'state': 'xyz' + 'refresh_token': 'abc' + } + self.assertEqual(body, token) + + # try with additional custom variables + body = 'client_id=me&redirect_uri=http%3A%2F%2Fback.to%2Fme&grant_type=authorization_code&code=abc&state=foobar' + headers, body, status_code = self.endpoint.create_token_response( + '', body=body) + body = json.loads(body) + token = { + 'token_type': 'Bearer', + 'expires_in': self.expires_in, + 'access_token': body['access_token'], + 'refresh_token': 'abc' } self.assertEqual(body, token) diff --git a/tests/openid/connect/core/test_server.py b/tests/openid/connect/core/test_server.py index ffab7b0..756c9d0 100644 --- a/tests/openid/connect/core/test_server.py +++ b/tests/openid/connect/core/test_server.py @@ -143,7 +143,7 @@ class TokenEndpointTest(TestCase): @mock.patch('oauthlib.common.generate_token', new=lambda: 'abc') def test_authorization_grant(self): - body = 'grant_type=authorization_code&code=abc&scope=all+of+them&state=xyz' + body = 'grant_type=authorization_code&code=abc&scope=all+of+them' headers, body, status_code = self.endpoint.create_token_response( '', body=body) token = { @@ -151,23 +151,27 @@ class TokenEndpointTest(TestCase): 'expires_in': self.expires_in, 'access_token': 'abc', 'refresh_token': 'abc', - 'scope': 'all of them', - 'state': 'xyz' + 'scope': 'all of them' } self.assertEqual(json.loads(body), token) - body = 'grant_type=authorization_code&code=abc&state=xyz' + body = 'grant_type=authorization_code&code=abc' headers, body, status_code = self.endpoint.create_token_response( '', body=body) token = { 'token_type': 'Bearer', 'expires_in': self.expires_in, 'access_token': 'abc', - 'refresh_token': 'abc', - 'state': 'xyz' + 'refresh_token': 'abc' } self.assertEqual(json.loads(body), token) + # ignore useless fields + body = 'grant_type=authorization_code&code=abc&state=foobar' + headers, body, status_code = self.endpoint.create_token_response( + '', body=body) + self.assertEqual(json.loads(body), token) + def test_missing_type(self): _, body, _ = self.endpoint.create_token_response('', body='') token = {'error': 'unsupported_grant_type'} -- cgit v1.2.1 From 58f1c3fe4020d13d4c2f7b80902b2c157fde807d Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Thu, 21 Feb 2019 10:01:29 +0100 Subject: Add clarity to the deprecation warning --- oauthlib/oauth2/rfc6749/tokens.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/oauthlib/oauth2/rfc6749/tokens.py b/oauthlib/oauth2/rfc6749/tokens.py index 44a9a97..7973923 100644 --- a/oauthlib/oauth2/rfc6749/tokens.py +++ b/oauthlib/oauth2/rfc6749/tokens.py @@ -306,8 +306,8 @@ class BearerToken(TokenBase): :param refresh_token: """ if "save_token" in kwargs: - warnings.warn("`save_token` has been deprecated, it was not used internally." - "If you do, use `request_validator.save_token()` instead.", + warnings.warn("`save_token` has been deprecated, it was not called internally." + "If you do, call `request_validator.save_token()` instead.", DeprecationWarning) if callable(self.expires_in): -- cgit v1.2.1 From 4205dc1b4240e30d966c3fd4fe872f83413b2e2c Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Thu, 21 Feb 2019 10:16:55 +0100 Subject: Add authorization "state" preservation back for AuthCode --- tests/oauth2/rfc6749/endpoints/test_credentials_preservation.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/oauth2/rfc6749/endpoints/test_credentials_preservation.py b/tests/oauth2/rfc6749/endpoints/test_credentials_preservation.py index c77d18e..c0cf86d 100644 --- a/tests/oauth2/rfc6749/endpoints/test_credentials_preservation.py +++ b/tests/oauth2/rfc6749/endpoints/test_credentials_preservation.py @@ -43,6 +43,13 @@ class PreservationTest(TestCase): def test_state_preservation(self): auth_uri = 'http://example.com/path?state=xyz&client_id=abc&response_type=' + # authorization grant + h, _, s = self.web.create_authorization_response( + auth_uri + 'code', scopes=['random']) + self.assertEqual(s, 302) + self.assertIn('Location', h) + self.assertEqual(get_query_credentials(h['Location'])['state'][0], 'xyz') + # implicit grant h, _, s = self.mobile.create_authorization_response( auth_uri + 'token', scopes=['random']) -- cgit v1.2.1 From 2904de612a5e52c14776978dd5a31cdde2bfc34e Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Thu, 21 Feb 2019 10:17:23 +0100 Subject: Removed useless set_state internal function Does not have purpose for /token request --- tests/oauth2/rfc6749/endpoints/test_credentials_preservation.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/tests/oauth2/rfc6749/endpoints/test_credentials_preservation.py b/tests/oauth2/rfc6749/endpoints/test_credentials_preservation.py index c0cf86d..e7c66b6 100644 --- a/tests/oauth2/rfc6749/endpoints/test_credentials_preservation.py +++ b/tests/oauth2/rfc6749/endpoints/test_credentials_preservation.py @@ -29,12 +29,6 @@ class PreservationTest(TestCase): self.web = WebApplicationServer(self.validator) self.mobile = MobileApplicationServer(self.validator) - def set_state(self, state): - def set_request_state(client_id, code, client, request): - request.state = state - return True - return set_request_state - def set_client(self, request): request.client = mock.MagicMock() request.client.client_id = 'mocked' @@ -128,7 +122,7 @@ class PreservationTest(TestCase): # 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') + self.validator.validate_code.return_value = True _, body, s = self.web.create_token_response(token_uri, body='grant_type=authorization_code&code=%s' % code) self.assertEqual(s, 200) -- cgit v1.2.1 From 0a0a718355354f621e475b8bd8162b726d838c11 Mon Sep 17 00:00:00 2001 From: Hoylen Sue Date: Thu, 21 Feb 2019 20:51:45 +1000 Subject: Renamed normalize_base_string_uri to base_string_uri. --- oauthlib/oauth1/rfc5849/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/oauthlib/oauth1/rfc5849/__init__.py b/oauthlib/oauth1/rfc5849/__init__.py index 7313286..d6c44ea 100644 --- a/oauthlib/oauth1/rfc5849/__init__.py +++ b/oauthlib/oauth1/rfc5849/__init__.py @@ -133,8 +133,7 @@ class Client(object): log.debug("Collected params: {0}".format(collected_params)) normalized_params = signature.normalize_parameters(collected_params) - normalized_uri = signature.normalize_base_string_uri(uri, - headers.get('Host', None)) + normalized_uri = signature.base_string_uri(uri, headers.get('Host', None)) log.debug("Normalized params: {0}".format(normalized_params)) log.debug("Normalized URI: {0}".format(normalized_uri)) -- cgit v1.2.1 From 42023d8303113073e31a57e1bbf70216b7120e20 Mon Sep 17 00:00:00 2001 From: Hoylen Sue Date: Fri, 22 Feb 2019 09:32:24 +1000 Subject: Renamed construct_base_string to signature_base_string. --- oauthlib/oauth1/rfc5849/__init__.py | 2 +- oauthlib/oauth1/rfc5849/signature.py | 23 ++++++++++++++--------- tests/oauth1/rfc5849/test_signatures.py | 12 ++++++------ 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/oauthlib/oauth1/rfc5849/__init__.py b/oauthlib/oauth1/rfc5849/__init__.py index d6c44ea..4f462bb 100644 --- a/oauthlib/oauth1/rfc5849/__init__.py +++ b/oauthlib/oauth1/rfc5849/__init__.py @@ -137,7 +137,7 @@ class Client(object): log.debug("Normalized params: {0}".format(normalized_params)) log.debug("Normalized URI: {0}".format(normalized_uri)) - base_string = signature.construct_base_string(request.http_method, + base_string = signature.signature_base_string(request.http_method, normalized_uri, normalized_params) log.debug("Signing: signature base string: {0}".format(base_string)) diff --git a/oauthlib/oauth1/rfc5849/signature.py b/oauthlib/oauth1/rfc5849/signature.py index 589b68a..f899aca 100644 --- a/oauthlib/oauth1/rfc5849/signature.py +++ b/oauthlib/oauth1/rfc5849/signature.py @@ -40,9 +40,10 @@ except ImportError: log = logging.getLogger(__name__) -def construct_base_string(http_method, base_string_uri, + +def signature_base_string(http_method, base_str_uri, normalized_encoded_request_parameters): - """**String Construction** + """**Construct the signature base string.** Per `section 3.4.1.1`_ of the spec. For example, the HTTP request:: @@ -90,7 +91,7 @@ def construct_base_string(http_method, base_string_uri, # # .. _`Section 3.4.1.2`: https://tools.ietf.org/html/rfc5849#section-3.4.1.2 # .. _`Section 3.4.6`: https://tools.ietf.org/html/rfc5849#section-3.4.6 - base_string += utils.escape(base_string_uri) + base_string += utils.escape(base_str_uri) # 4. An "&" character (ASCII code 38). base_string += '&' @@ -649,12 +650,14 @@ def verify_hmac_sha1(request, client_secret=None, """ norm_params = normalize_parameters(request.params) bs_uri = base_string_uri(request.uri) - base_string = construct_base_string(request.http_method, bs_uri, norm_params) - signature = sign_hmac_sha1(base_string, client_secret, + sig_base_str = signature_base_string(request.http_method, bs_uri, + norm_params) + signature = sign_hmac_sha1(sig_base_str, client_secret, resource_owner_secret) match = safe_string_equals(signature, request.signature) if not match: - log.debug('Verify HMAC-SHA1 failed: sig base string: %s', base_string) + log.debug('Verify HMAC-SHA1 failed: signature base string: %s', + sig_base_str) return match @@ -682,15 +685,17 @@ def verify_rsa_sha1(request, rsa_public_key): """ norm_params = normalize_parameters(request.params) bs_uri = base_string_uri(request.uri) - message = construct_base_string(request.http_method, bs_uri, norm_params).encode('utf-8') + sig_base_str = signature_base_string(request.http_method, bs_uri, + norm_params).encode('utf-8') sig = binascii.a2b_base64(request.signature.encode('utf-8')) alg = _jwt_rs1_signing_algorithm() key = _prepare_key_plus(alg, rsa_public_key) - verify_ok = alg.verify(message, key, sig) + verify_ok = alg.verify(sig_base_str, key, sig) if not verify_ok: - log.debug('Verify RSA-SHA1 failed: sig base string: %s', message) + log.debug('Verify RSA-SHA1 failed: signature base string: %s', + sig_base_str) return verify_ok diff --git a/tests/oauth1/rfc5849/test_signatures.py b/tests/oauth1/rfc5849/test_signatures.py index db2c532..bb0dc78 100644 --- a/tests/oauth1/rfc5849/test_signatures.py +++ b/tests/oauth1/rfc5849/test_signatures.py @@ -3,7 +3,7 @@ from __future__ import absolute_import, unicode_literals from oauthlib.common import unicode_type from oauthlib.oauth1.rfc5849.signature import (collect_parameters, - construct_base_string, + signature_base_string, base_string_uri, normalize_parameters, sign_hmac_sha1, @@ -79,7 +79,7 @@ class SignatureTests(TestCase): resource_owner_secret = self.resource_owner_secret ) - def test_construct_base_string(self): + def test_signature_base_string(self): """ Example text to be turned into a base string:: @@ -104,20 +104,20 @@ class SignatureTests(TestCase): D%2522137131201%2522%252Coauth_nonce%253D%25227d8f3e4a%2522%252Coau th_signature%253D%2522bYT5CMsGcbgUdFHObYMEfcx6bsw%25253D%2522 """ - self.assertRaises(ValueError, construct_base_string, + self.assertRaises(ValueError, signature_base_string, self.http_method, self.base_string_url, self.normalized_encoded_request_parameters) - self.assertRaises(ValueError, construct_base_string, + self.assertRaises(ValueError, signature_base_string, self.http_method.decode('utf-8'), self.base_string_url, self.normalized_encoded_request_parameters) - self.assertRaises(ValueError, construct_base_string, + self.assertRaises(ValueError, signature_base_string, self.http_method.decode('utf-8'), self.base_string_url.decode('utf-8'), self.normalized_encoded_request_parameters) - base_string = construct_base_string( + base_string = signature_base_string( self.http_method.decode('utf-8'), self.base_string_url.decode('utf-8'), self.normalized_encoded_request_parameters.decode('utf-8') -- cgit v1.2.1 From 54db1bfd65d1d17d1d45c12c8626b9e7fa84e694 Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Fri, 22 Feb 2019 11:12:49 +0100 Subject: Remove usage of "state" for code/token response. --- examples/skeleton_oauth2_web_application_server.py | 6 +++--- oauthlib/oauth2/rfc6749/request_validator.py | 8 +------- tests/openid/connect/core/grant_types/test_authorization_code.py | 1 - 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/examples/skeleton_oauth2_web_application_server.py b/examples/skeleton_oauth2_web_application_server.py index e53232f..9a30373 100644 --- a/examples/skeleton_oauth2_web_application_server.py +++ b/examples/skeleton_oauth2_web_application_server.py @@ -48,7 +48,7 @@ class SkeletonValidator(RequestValidator): def save_authorization_code(self, client_id, code, request, *args, **kwargs): # Remember to associate it with request.scopes, request.redirect_uri - # request.client, request.state and request.user (the last is passed in + # request.client and request.user (the last is passed in # post_authorization credentials, i.e. { 'user': request.user}. pass @@ -63,8 +63,8 @@ class SkeletonValidator(RequestValidator): return False def validate_code(self, client_id, code, client, request, *args, **kwargs): - # Validate the code belongs to the client. Add associated scopes, - # state and user to request.scopes and request.user. + # Validate the code belongs to the client. Add associated scopes + # and user to request.scopes and request.user. pass def confirm_redirect_uri(self, client_id, code, redirect_uri, client, request, *args, **kwargs): diff --git a/oauthlib/oauth2/rfc6749/request_validator.py b/oauthlib/oauth2/rfc6749/request_validator.py index 193a9e1..5ff30d8 100644 --- a/oauthlib/oauth2/rfc6749/request_validator.py +++ b/oauthlib/oauth2/rfc6749/request_validator.py @@ -266,7 +266,6 @@ class RequestValidator(object): - the redirect URI used (``request.redirect_uri``) - a resource owner / user (``request.user``) - the authorized scopes (``request.scopes``) - - the client state, if given (``code.get('state')``) To support PKCE, you MUST associate the code with: - Code Challenge (``request.code_challenge``) and @@ -277,10 +276,6 @@ class RequestValidator(object): ``{'code': 'sdf345jsdf0934f'}`` - It may also have a ``state`` key containing a nonce for the client, if it - chose to send one. That value should be saved and used in - ``.validate_code``. - It may also have a ``claims`` parameter which, when present, will be a dict deserialized from JSON as described at http://openid.net/specs/openid-connect-core-1_0.html#ClaimsParameter @@ -352,7 +347,7 @@ class RequestValidator(object): 'expires_in': 3600, 'scope': 'string of space separated authorized scopes', 'refresh_token': '23sdf876234', # if issued - 'state': 'given_by_client', # if supplied by client + 'state': 'given_by_client', # if supplied by client (implicit ONLY) } Note that while "scope" is a string-separated list of authorized scopes, @@ -559,7 +554,6 @@ class RequestValidator(object): with the code in 'save_authorization_code': - request.user - - request.state (if given) - request.scopes - request.claims (if given) OBS! The request.user attribute should be set to the resource owner 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 c3c7824..fbbd5ff 100644 --- a/tests/openid/connect/core/grant_types/test_authorization_code.py +++ b/tests/openid/connect/core/grant_types/test_authorization_code.py @@ -116,7 +116,6 @@ class OpenIDAuthCodeTest(TestCase): def set_scopes(self, client_id, code, client, request): request.scopes = self.request.scopes - request.state = self.request.state request.user = 'bob' return True -- cgit v1.2.1 From ff67daa5b3c9640a1e4754a949584c9315dbf5cd Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Mon, 25 Feb 2019 15:36:13 +0100 Subject: Change to 3.0.2-dev as long as master is in "dev" --- CHANGELOG.rst | 2 +- oauthlib/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f49fb92..ade6243 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,7 +1,7 @@ Changelog ========= -TBD +3.0.2 (TBD) ------------------ * #650 Fixed space encoding in base string URI used in the signature base string. * #652: Fixed OIDC /token response which wrongly returned "&state=None" diff --git a/oauthlib/__init__.py b/oauthlib/__init__.py index b23102c..8eb82a6 100644 --- a/oauthlib/__init__.py +++ b/oauthlib/__init__.py @@ -12,6 +12,6 @@ import logging from logging import NullHandler __author__ = 'The OAuthlib Community' -__version__ = '3.0.1' +__version__ = '3.0.2-dev' logging.getLogger('oauthlib').addHandler(NullHandler()) -- cgit v1.2.1 From aee1bb88135090202ebdfc5974c16730b52bc5e7 Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Mon, 25 Feb 2019 20:57:56 +0100 Subject: OIDC: Raise error=invalid_request when nonce is mandatory Until now, only OIDC implicit was raising an error, but OIDC hybrid contain a couple of mandatory nonce, too. --- oauthlib/openid/connect/core/grant_types/base.py | 23 --------- oauthlib/openid/connect/core/grant_types/hybrid.py | 25 +++++++++ .../openid/connect/core/grant_types/implicit.py | 23 ++++++++- .../connect/core/grant_types/test_implicit.py | 60 +++++++++++++++++++--- 4 files changed, 99 insertions(+), 32 deletions(-) diff --git a/oauthlib/openid/connect/core/grant_types/base.py b/oauthlib/openid/connect/core/grant_types/base.py index 05cdd37..4f5c944 100644 --- a/oauthlib/openid/connect/core/grant_types/base.py +++ b/oauthlib/openid/connect/core/grant_types/base.py @@ -247,28 +247,5 @@ class GrantTypeBase(object): return request_info - def openid_implicit_authorization_validator(self, request): - """Additional validation when following the implicit flow. - """ - # Undefined in OpenID Connect, fall back to OAuth2 definition. - if request.response_type == 'token': - return {} - - # Treat it as normal OAuth 2 auth code request if openid is not present - if not request.scopes or 'openid' not in request.scopes: - return {} - - # REQUIRED. String value used to associate a Client session with an ID - # Token, and to mitigate replay attacks. The value is passed through - # unmodified from the Authentication Request to the ID Token. - # Sufficient entropy MUST be present in the nonce values used to - # prevent attackers from guessing values. For implementation notes, see - # Section 15.5.2. - if not request.nonce: - desc = 'Request is missing mandatory nonce parameter.' - raise InvalidRequestError(request=request, description=desc) - - return {} - OpenIDConnectBase = GrantTypeBase diff --git a/oauthlib/openid/connect/core/grant_types/hybrid.py b/oauthlib/openid/connect/core/grant_types/hybrid.py index 54669ae..685fa08 100644 --- a/oauthlib/openid/connect/core/grant_types/hybrid.py +++ b/oauthlib/openid/connect/core/grant_types/hybrid.py @@ -8,6 +8,7 @@ from __future__ import absolute_import, unicode_literals import logging from oauthlib.oauth2.rfc6749.grant_types.authorization_code import AuthorizationCodeGrant as OAuth2AuthorizationCodeGrant +from oauthlib.oauth2.rfc6749.errors import InvalidRequestError from .base import GrantTypeBase from ..request_validator import RequestValidator @@ -34,3 +35,27 @@ class HybridGrant(GrantTypeBase): self.register_code_modifier(self.add_token) self.register_code_modifier(self.add_id_token) self.register_token_modifier(self.add_id_token) + + def openid_authorization_validator(self, request): + """Additional validation when following the Authorization Code flow. + """ + request_info = super(HybridGrant, self).openid_authorization_validator(request) + if not request_info: # returns immediately if OAuth2.0 + return request_info + + # REQUIRED if the Response Type of the request is `code + # id_token` or `code id_token token` and OPTIONAL when the + # Response Type of the request is `code token`. It is a string + # value used to associate a Client session with an ID Token, + # and to mitigate replay attacks. The value is passed through + # unmodified from the Authentication Request to the ID + # Token. Sufficient entropy MUST be present in the `nonce` + # values used to prevent attackers from guessing values. For + # implementation notes, see Section 15.5.2. + if request.response_type in ["code id_token", "code id_token token"]: + if not request.nonce: + raise InvalidRequestError( + request=request, + description='Request is missing mandatory nonce parameter.' + ) + return request_info diff --git a/oauthlib/openid/connect/core/grant_types/implicit.py b/oauthlib/openid/connect/core/grant_types/implicit.py index 0a6fcb7..d3797b2 100644 --- a/oauthlib/openid/connect/core/grant_types/implicit.py +++ b/oauthlib/openid/connect/core/grant_types/implicit.py @@ -10,6 +10,7 @@ import logging from .base import GrantTypeBase from oauthlib.oauth2.rfc6749.grant_types.implicit import ImplicitGrant as OAuth2ImplicitGrant +from oauthlib.oauth2.rfc6749.errors import InvalidRequestError log = logging.getLogger(__name__) @@ -23,11 +24,29 @@ class ImplicitGrant(GrantTypeBase): self.register_response_type('id_token token') self.custom_validators.post_auth.append( self.openid_authorization_validator) - self.custom_validators.post_auth.append( - self.openid_implicit_authorization_validator) self.register_token_modifier(self.add_id_token) def add_id_token(self, token, token_handler, request): if 'state' not in token: token['state'] = request.state return super(ImplicitGrant, self).add_id_token(token, token_handler, request) + + def openid_authorization_validator(self, request): + """Additional validation when following the implicit flow. + """ + request_info = super(ImplicitGrant, self).openid_authorization_validator(request) + if not request_info: # returns immediately if OAuth2.0 + return request_info + + # REQUIRED. String value used to associate a Client session with an ID + # Token, and to mitigate replay attacks. The value is passed through + # unmodified from the Authentication Request to the ID Token. + # Sufficient entropy MUST be present in the nonce values used to + # prevent attackers from guessing values. For implementation notes, see + # Section 15.5.2. + if not request.nonce: + raise InvalidRequestError( + request=request, + description='Request is missing mandatory nonce parameter.' + ) + return request_info diff --git a/tests/openid/connect/core/grant_types/test_implicit.py b/tests/openid/connect/core/grant_types/test_implicit.py index 7ab198a..54fd8b9 100644 --- a/tests/openid/connect/core/grant_types/test_implicit.py +++ b/tests/openid/connect/core/grant_types/test_implicit.py @@ -4,6 +4,7 @@ from __future__ import absolute_import, unicode_literals import mock from oauthlib.common import Request +from oauthlib.oauth2.rfc6749 import errors from oauthlib.oauth2.rfc6749.tokens import BearerToken from oauthlib.openid.connect.core.grant_types.exceptions import OIDCNoPrompt from oauthlib.openid.connect.core.grant_types.hybrid import HybridGrant @@ -30,8 +31,8 @@ class OpenIDImplicitTest(TestCase): self.request.client_id = 'abcdef' self.request.response_type = 'id_token token' self.request.redirect_uri = 'https://a.b/cb' - self.request.nonce = 'zxc' self.request.state = 'abc' + self.request.nonce = 'xyz' self.mock_validator = mock.MagicMock() self.mock_validator.get_id_token.side_effect = get_id_token_mock @@ -61,12 +62,6 @@ class OpenIDImplicitTest(TestCase): self.assertEqual(b, None) self.assertEqual(s, 302) - self.request.nonce = None - 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) - @mock.patch('oauthlib.common.generate_token') def test_no_prompt_authorization(self, generate_token): generate_token.return_value = 'abc' @@ -105,16 +100,41 @@ class OpenIDImplicitTest(TestCase): h, b, s = self.auth.create_authorization_response(self.request, bearer) self.assertIn('error=login_required', h['Location']) + @mock.patch('oauthlib.common.generate_token') + def test_required_nonce(self, generate_token): + generate_token.return_value = 'abc' + self.request.nonce = None + self.assertRaises(errors.InvalidRequestError, self.auth.validate_authorization_request, self.request) + + 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) + class OpenIDHybridCodeTokenTest(OpenIDAuthCodeTest): def setUp(self): super(OpenIDHybridCodeTokenTest, self).setUp() self.request.response_type = 'code token' + self.request.nonce = None self.auth = HybridGrant(request_validator=self.mock_validator) self.url_query = 'https://a.b/cb?code=abc&state=abc&token_type=Bearer&expires_in=3600&scope=hello+openid&access_token=abc' self.url_fragment = 'https://a.b/cb#code=abc&state=abc&token_type=Bearer&expires_in=3600&scope=hello+openid&access_token=abc' + @mock.patch('oauthlib.common.generate_token') + def test_optional_nonce(self, generate_token): + generate_token.return_value = 'abc' + self.request.nonce = 'xyz' + scope, info = self.auth.validate_authorization_request(self.request) + + bearer = BearerToken(self.mock_validator) + h, b, s = self.auth.create_authorization_response(self.request, bearer) + self.assertURLEqual(h['Location'], self.url_fragment, parse_fragment=True) + self.assertEqual(b, None) + self.assertEqual(s, 302) + class OpenIDHybridCodeIdTokenTest(OpenIDAuthCodeTest): @@ -122,11 +142,24 @@ class OpenIDHybridCodeIdTokenTest(OpenIDAuthCodeTest): super(OpenIDHybridCodeIdTokenTest, self).setUp() self.mock_validator.get_code_challenge.return_value = None self.request.response_type = 'code id_token' + self.request.nonce = 'zxc' self.auth = HybridGrant(request_validator=self.mock_validator) token = 'MOCKED_TOKEN' self.url_query = 'https://a.b/cb?code=abc&state=abc&id_token=%s' % token self.url_fragment = 'https://a.b/cb#code=abc&state=abc&id_token=%s' % token + @mock.patch('oauthlib.common.generate_token') + def test_required_nonce(self, generate_token): + generate_token.return_value = 'abc' + self.request.nonce = None + self.assertRaises(errors.InvalidRequestError, self.auth.validate_authorization_request, self.request) + + 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) + class OpenIDHybridCodeIdTokenTokenTest(OpenIDAuthCodeTest): @@ -134,7 +167,20 @@ class OpenIDHybridCodeIdTokenTokenTest(OpenIDAuthCodeTest): super(OpenIDHybridCodeIdTokenTokenTest, self).setUp() self.mock_validator.get_code_challenge.return_value = None self.request.response_type = 'code id_token token' + self.request.nonce = 'xyz' self.auth = HybridGrant(request_validator=self.mock_validator) token = 'MOCKED_TOKEN' self.url_query = 'https://a.b/cb?code=abc&state=abc&token_type=Bearer&expires_in=3600&scope=hello+openid&access_token=abc&id_token=%s' % token self.url_fragment = 'https://a.b/cb#code=abc&state=abc&token_type=Bearer&expires_in=3600&scope=hello+openid&access_token=abc&id_token=%s' % token + + @mock.patch('oauthlib.common.generate_token') + def test_required_nonce(self, generate_token): + generate_token.return_value = 'abc' + self.request.nonce = None + self.assertRaises(errors.InvalidRequestError, self.auth.validate_authorization_request, self.request) + + 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) -- cgit v1.2.1 From 39f213b2106d079ce371f541e180ac4cd685d4e3 Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Mon, 25 Feb 2019 21:34:31 +0100 Subject: Add nonce auth request check for authorization_code --- .../connect/core/grant_types/test_authorization_code.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) 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 fbbd5ff..b721a19 100644 --- a/tests/openid/connect/core/grant_types/test_authorization_code.py +++ b/tests/openid/connect/core/grant_types/test_authorization_code.py @@ -40,6 +40,7 @@ class OpenIDAuthCodeTest(TestCase): self.request.grant_type = 'authorization_code' self.request.redirect_uri = 'https://a.b/cb' self.request.state = 'abc' + self.request.nonce = None self.mock_validator = mock.MagicMock() self.mock_validator.authenticate_client.side_effect = self.set_client @@ -147,3 +148,16 @@ class OpenIDAuthCodeTest(TestCase): self.assertIn('scope', token) self.assertNotIn('id_token', token) self.assertNotIn('openid', token['scope']) + + @mock.patch('oauthlib.common.generate_token') + def test_optional_nonce(self, generate_token): + generate_token.return_value = 'abc' + self.request.nonce = 'xyz' + scope, info = self.auth.validate_authorization_request(self.request) + + bearer = BearerToken(self.mock_validator) + self.request.response_mode = 'query' + h, b, s = self.auth.create_authorization_response(self.request, bearer) + self.assertURLEqual(h['Location'], self.url_query) + self.assertEqual(b, None) + self.assertEqual(s, 302) -- cgit v1.2.1 From c76db93ed3b20295a04ef58f0048ef53cee9714c Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Mon, 25 Feb 2019 21:34:48 +0100 Subject: Add nonce mandatory check for "id_token" response_type --- .../connect/core/grant_types/test_implicit.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/openid/connect/core/grant_types/test_implicit.py b/tests/openid/connect/core/grant_types/test_implicit.py index 54fd8b9..948edd3 100644 --- a/tests/openid/connect/core/grant_types/test_implicit.py +++ b/tests/openid/connect/core/grant_types/test_implicit.py @@ -113,6 +113,27 @@ class OpenIDImplicitTest(TestCase): self.assertEqual(s, 302) +class OpenIDImplicitNoAccessTokenTest(OpenIDImplicitTest): + def setUp(self): + super(OpenIDImplicitNoAccessTokenTest, self).setUp() + self.request.response_type = 'id_token' + token = 'MOCKED_TOKEN' + self.url_query = 'https://a.b/cb?state=abc&id_token=%s' % token + self.url_fragment = 'https://a.b/cb#state=abc&id_token=%s' % token + + @mock.patch('oauthlib.common.generate_token') + def test_required_nonce(self, generate_token): + generate_token.return_value = 'abc' + self.request.nonce = None + self.assertRaises(errors.InvalidRequestError, self.auth.validate_authorization_request, self.request) + + 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) + + class OpenIDHybridCodeTokenTest(OpenIDAuthCodeTest): def setUp(self): -- cgit v1.2.1 From 512de2c2b0be394dbe873c39b7ef085a665cdc14 Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Mon, 25 Feb 2019 21:36:14 +0100 Subject: Move HybridGrant test into its respective file. --- .../openid/connect/core/grant_types/test_hybrid.py | 75 +++++++++++++++++++++ .../connect/core/grant_types/test_implicit.py | 76 +--------------------- 2 files changed, 76 insertions(+), 75 deletions(-) diff --git a/tests/openid/connect/core/grant_types/test_hybrid.py b/tests/openid/connect/core/grant_types/test_hybrid.py index 6eb8037..8964053 100644 --- a/tests/openid/connect/core/grant_types/test_hybrid.py +++ b/tests/openid/connect/core/grant_types/test_hybrid.py @@ -4,6 +4,8 @@ from oauthlib.openid.connect.core.grant_types.hybrid import HybridGrant from tests.oauth2.rfc6749.grant_types.test_authorization_code import \ AuthorizationCodeGrantTest +from .test_authorization_code import OpenIDAuthCodeTest + class OpenIDHybridInterferenceTest(AuthorizationCodeGrantTest): @@ -12,3 +14,76 @@ class OpenIDHybridInterferenceTest(AuthorizationCodeGrantTest): def setUp(self): super(OpenIDHybridInterferenceTest, self).setUp() self.auth = HybridGrant(request_validator=self.mock_validator) + + +class OpenIDHybridCodeTokenTest(OpenIDAuthCodeTest): + + def setUp(self): + super(OpenIDHybridCodeTokenTest, self).setUp() + self.request.response_type = 'code token' + self.request.nonce = None + self.auth = HybridGrant(request_validator=self.mock_validator) + self.url_query = 'https://a.b/cb?code=abc&state=abc&token_type=Bearer&expires_in=3600&scope=hello+openid&access_token=abc' + self.url_fragment = 'https://a.b/cb#code=abc&state=abc&token_type=Bearer&expires_in=3600&scope=hello+openid&access_token=abc' + + @mock.patch('oauthlib.common.generate_token') + def test_optional_nonce(self, generate_token): + generate_token.return_value = 'abc' + self.request.nonce = 'xyz' + scope, info = self.auth.validate_authorization_request(self.request) + + bearer = BearerToken(self.mock_validator) + h, b, s = self.auth.create_authorization_response(self.request, bearer) + self.assertURLEqual(h['Location'], self.url_fragment, parse_fragment=True) + self.assertEqual(b, None) + self.assertEqual(s, 302) + + +class OpenIDHybridCodeIdTokenTest(OpenIDAuthCodeTest): + + def setUp(self): + super(OpenIDHybridCodeIdTokenTest, self).setUp() + self.mock_validator.get_code_challenge.return_value = None + self.request.response_type = 'code id_token' + self.request.nonce = 'zxc' + self.auth = HybridGrant(request_validator=self.mock_validator) + token = 'MOCKED_TOKEN' + self.url_query = 'https://a.b/cb?code=abc&state=abc&id_token=%s' % token + self.url_fragment = 'https://a.b/cb#code=abc&state=abc&id_token=%s' % token + + @mock.patch('oauthlib.common.generate_token') + def test_required_nonce(self, generate_token): + generate_token.return_value = 'abc' + self.request.nonce = None + self.assertRaises(errors.InvalidRequestError, self.auth.validate_authorization_request, self.request) + + 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) + + +class OpenIDHybridCodeIdTokenTokenTest(OpenIDAuthCodeTest): + + def setUp(self): + super(OpenIDHybridCodeIdTokenTokenTest, self).setUp() + self.mock_validator.get_code_challenge.return_value = None + self.request.response_type = 'code id_token token' + self.request.nonce = 'xyz' + self.auth = HybridGrant(request_validator=self.mock_validator) + token = 'MOCKED_TOKEN' + self.url_query = 'https://a.b/cb?code=abc&state=abc&token_type=Bearer&expires_in=3600&scope=hello+openid&access_token=abc&id_token=%s' % token + self.url_fragment = 'https://a.b/cb#code=abc&state=abc&token_type=Bearer&expires_in=3600&scope=hello+openid&access_token=abc&id_token=%s' % token + + @mock.patch('oauthlib.common.generate_token') + def test_required_nonce(self, generate_token): + generate_token.return_value = 'abc' + self.request.nonce = None + self.assertRaises(errors.InvalidRequestError, self.auth.validate_authorization_request, self.request) + + 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) diff --git a/tests/openid/connect/core/grant_types/test_implicit.py b/tests/openid/connect/core/grant_types/test_implicit.py index 948edd3..1ee805c 100644 --- a/tests/openid/connect/core/grant_types/test_implicit.py +++ b/tests/openid/connect/core/grant_types/test_implicit.py @@ -7,11 +7,10 @@ from oauthlib.common import Request from oauthlib.oauth2.rfc6749 import errors from oauthlib.oauth2.rfc6749.tokens import BearerToken from oauthlib.openid.connect.core.grant_types.exceptions import OIDCNoPrompt -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 .test_authorization_code import get_id_token_mock class OpenIDImplicitInterferenceTest(ImplicitGrantTest): @@ -132,76 +131,3 @@ class OpenIDImplicitNoAccessTokenTest(OpenIDImplicitTest): self.assertIn('error=invalid_request', h['Location']) self.assertEqual(b, None) self.assertEqual(s, 302) - - -class OpenIDHybridCodeTokenTest(OpenIDAuthCodeTest): - - def setUp(self): - super(OpenIDHybridCodeTokenTest, self).setUp() - self.request.response_type = 'code token' - self.request.nonce = None - self.auth = HybridGrant(request_validator=self.mock_validator) - self.url_query = 'https://a.b/cb?code=abc&state=abc&token_type=Bearer&expires_in=3600&scope=hello+openid&access_token=abc' - self.url_fragment = 'https://a.b/cb#code=abc&state=abc&token_type=Bearer&expires_in=3600&scope=hello+openid&access_token=abc' - - @mock.patch('oauthlib.common.generate_token') - def test_optional_nonce(self, generate_token): - generate_token.return_value = 'abc' - self.request.nonce = 'xyz' - scope, info = self.auth.validate_authorization_request(self.request) - - bearer = BearerToken(self.mock_validator) - h, b, s = self.auth.create_authorization_response(self.request, bearer) - self.assertURLEqual(h['Location'], self.url_fragment, parse_fragment=True) - self.assertEqual(b, None) - self.assertEqual(s, 302) - - -class OpenIDHybridCodeIdTokenTest(OpenIDAuthCodeTest): - - def setUp(self): - super(OpenIDHybridCodeIdTokenTest, self).setUp() - self.mock_validator.get_code_challenge.return_value = None - self.request.response_type = 'code id_token' - self.request.nonce = 'zxc' - self.auth = HybridGrant(request_validator=self.mock_validator) - token = 'MOCKED_TOKEN' - self.url_query = 'https://a.b/cb?code=abc&state=abc&id_token=%s' % token - self.url_fragment = 'https://a.b/cb#code=abc&state=abc&id_token=%s' % token - - @mock.patch('oauthlib.common.generate_token') - def test_required_nonce(self, generate_token): - generate_token.return_value = 'abc' - self.request.nonce = None - self.assertRaises(errors.InvalidRequestError, self.auth.validate_authorization_request, self.request) - - 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) - - -class OpenIDHybridCodeIdTokenTokenTest(OpenIDAuthCodeTest): - - def setUp(self): - super(OpenIDHybridCodeIdTokenTokenTest, self).setUp() - self.mock_validator.get_code_challenge.return_value = None - self.request.response_type = 'code id_token token' - self.request.nonce = 'xyz' - self.auth = HybridGrant(request_validator=self.mock_validator) - token = 'MOCKED_TOKEN' - self.url_query = 'https://a.b/cb?code=abc&state=abc&token_type=Bearer&expires_in=3600&scope=hello+openid&access_token=abc&id_token=%s' % token - self.url_fragment = 'https://a.b/cb#code=abc&state=abc&token_type=Bearer&expires_in=3600&scope=hello+openid&access_token=abc&id_token=%s' % token - - @mock.patch('oauthlib.common.generate_token') - def test_required_nonce(self, generate_token): - generate_token.return_value = 'abc' - self.request.nonce = None - self.assertRaises(errors.InvalidRequestError, self.auth.validate_authorization_request, self.request) - - 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) -- cgit v1.2.1 From 4dd2b730759f9c3dedc6e77ccf79d779bbdad7f2 Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Mon, 25 Feb 2019 21:45:05 +0100 Subject: Added missing import after test moved --- tests/openid/connect/core/grant_types/test_hybrid.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/openid/connect/core/grant_types/test_hybrid.py b/tests/openid/connect/core/grant_types/test_hybrid.py index 8964053..0aa0add 100644 --- a/tests/openid/connect/core/grant_types/test_hybrid.py +++ b/tests/openid/connect/core/grant_types/test_hybrid.py @@ -1,13 +1,16 @@ # -*- coding: utf-8 -*- from __future__ import absolute_import, unicode_literals -from oauthlib.openid.connect.core.grant_types.hybrid import HybridGrant +import mock + +from oauthlib.oauth2.rfc6749 import errors +from oauthlib.oauth2.rfc6749.tokens import BearerToken +from oauthlib.openid.connect.core.grant_types.hybrid import HybridGrant from tests.oauth2.rfc6749.grant_types.test_authorization_code import \ AuthorizationCodeGrantTest from .test_authorization_code import OpenIDAuthCodeTest - class OpenIDHybridInterferenceTest(AuthorizationCodeGrantTest): """Test that OpenID don't interfere with normal OAuth 2 flows.""" -- cgit v1.2.1 From 8ef421b85b17b6cfb651ea4f13258be0c53ed879 Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Mon, 25 Feb 2019 21:57:53 +0100 Subject: Notifications must be sent for every build I hope fixing the longstanding issue mentionned at https://github.com/oauthlib/oauthlib/issues/582. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index c7978d7..f2e68a6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,7 +29,7 @@ notifications: urls: - https://coveralls.io/webhook - https://webhooks.gitter.im/e/6008c872bf0ecee344f4 - on_success: change + on_success: always on_failure: always on_start: never deploy: -- cgit v1.2.1 From 73032fe688a899f80d2a65479c72fec450ec51a1 Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Thu, 28 Feb 2019 10:06:37 +0100 Subject: Removed duplicated OIDC members in OAuth2.RequestValidator --- docs/oauth2/oidc/validator.rst | 6 +- oauthlib/oauth2/rfc6749/request_validator.py | 182 --------------------------- oauthlib/openid/__init__.py | 1 + 3 files changed, 5 insertions(+), 184 deletions(-) diff --git a/docs/oauth2/oidc/validator.rst b/docs/oauth2/oidc/validator.rst index a03adfe..7a6f574 100644 --- a/docs/oauth2/oidc/validator.rst +++ b/docs/oauth2/oidc/validator.rst @@ -10,12 +10,14 @@ upgrade it by replacing one line of code: .. code-block:: python from oauthlib.oauth2 import Server + from oauthlib.oauth2 import RequestValidator Into .. code-block:: python from oauthlib.openid import Server + from oauthlib.openid import RequestValidator Then, you have to implement the new RequestValidator methods as shown below. @@ -24,5 +26,5 @@ RequestValidator Extension A couple of methods must be implemented in your validator subclass if you wish to support OpenID Connect: -.. autoclass:: oauthlib.oauth2.RequestValidator - :members: validate_silent_authorization, validate_silent_login, validate_user_match, get_id_token, get_authorization_code_scopes, validate_jwt_bearer_token +.. autoclass:: oauthlib.openid.RequestValidator + :members: diff --git a/oauthlib/oauth2/rfc6749/request_validator.py b/oauthlib/oauth2/rfc6749/request_validator.py index 5ff30d8..d6ec2ab 100644 --- a/oauthlib/oauth2/rfc6749/request_validator.py +++ b/oauthlib/oauth2/rfc6749/request_validator.py @@ -291,32 +291,6 @@ class RequestValidator(object): """ raise NotImplementedError('Subclasses must implement this method.') - def get_authorization_code_scopes(self, client_id, code, redirect_uri, request): - """ Extracts scopes from saved authorization code. - - The scopes returned by this method is used to route token requests - based on scopes passed to Authorization Code requests. - - With that the token endpoint knows when to include OpenIDConnect - id_token in token response only based on authorization code scopes. - - Only code param should be sufficient to retrieve grant code from - any storage you are using, `client_id` and `redirect_uri` can gave a - 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. - :param request: OAuthlib request. - :type request: oauthlib.common.Request - :return: A list of scopes - - Method is used by: - - Authorization Token Grant Dispatcher - """ - raise NotImplementedError('Subclasses must implement this method.') - def save_token(self, token, request, *args, **kwargs): """Persist the token with a token type specific method. @@ -378,104 +352,6 @@ class RequestValidator(object): """ raise NotImplementedError('Subclasses must implement this method.') - def get_jwt_bearer_token(self, token, token_handler, request): - """Get JWT Bearer token or OpenID Connect ID token - - 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: 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: - - JWTToken.create_token - """ - raise NotImplementedError('Subclasses must implement this method.') - - def get_id_token(self, token, token_handler, request): - """Get OpenID Connect ID token - - In the OpenID Connect workflows when an ID Token is requested this method is called. - Subclasses should implement the construction, signing and optional encryption of the - ID Token as described in the OpenID Connect spec. - - In addition to the standard OAuth2 request properties, the request may also contain - these OIDC specific properties which are useful to this method: - - - nonce, if workflow is implicit or hybrid and it was provided - - claims, if provided to the original Authorization Code request - - The token parameter is a dict which may contain an ``access_token`` entry, in which - case the resulting ID Token *should* include a calculated ``at_hash`` claim. - - Similarly, when the request parameter has a ``code`` property defined, the ID Token - *should* include a calculated ``c_hash`` claim. - - http://openid.net/specs/openid-connect-core-1_0.html (sections `3.1.3.6`_, `3.2.2.10`_, `3.3.2.11`_) - - .. _`3.1.3.6`: http://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken - .. _`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: 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 - raise NotImplementedError('Subclasses must implement this method.') - - def validate_jwt_bearer_token(self, token, scopes, request): - """Ensure the JWT Bearer token or OpenID Connect ID token are valids and authorized access to scopes. - - If using OpenID Connect this SHOULD call `oauthlib.oauth2.RequestValidator.get_id_token` - - If not using OpenID Connect this can `return None` to avoid 5xx rather 401/3 response. - - OpenID connect core 1.0 describe how to validate an id_token: - - http://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation - - http://openid.net/specs/openid-connect-core-1_0.html#ImplicitIDTValidation - - 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: 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: - - Authorization Code Grant - - Implicit Grant - - Hybrid Grant - """ - raise NotImplementedError('Subclasses must implement this method.') - - def validate_id_token(self, token, scopes, request): - """Ensure the id token is valid and authorized access to scopes. - - OpenID connect core 1.0 describe how to validate an id_token: - - http://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation - - http://openid.net/specs/openid-connect-core-1_0.html#ImplicitIDTValidation - - 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: 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: - - Authorization Code Grant - - Implicit Grant - - Hybrid Grant - """ - raise NotImplementedError('Subclasses must implement this method.') - def validate_bearer_token(self, token, scopes, request): """Ensure the Bearer token is valid and authorized access to scopes. @@ -668,44 +544,6 @@ class RequestValidator(object): """ raise NotImplementedError('Subclasses must implement this method.') - def validate_silent_authorization(self, request): - """Ensure the logged in user has authorized silent OpenID authorization. - - Silent OpenID authorization allows access tokens and id tokens to be - granted to clients without any user prompt or interaction. - - :param request: OAuthlib request. - :type request: oauthlib.common.Request - :rtype: True or False - - Method is used by: - - OpenIDConnectAuthCode - - OpenIDConnectImplicit - - OpenIDConnectHybrid - """ - raise NotImplementedError('Subclasses must implement this method.') - - def validate_silent_login(self, request): - """Ensure session user has authorized silent OpenID login. - - If no user is logged in or has not authorized silent login, this - method should return False. - - If the user is logged in but associated with multiple accounts and - not selected which one to link to the token then this method should - raise an oauthlib.oauth2.AccountSelectionRequired error. - - :param request: OAuthlib request. - :type request: oauthlib.common.Request - :rtype: True or False - - Method is used by: - - OpenIDConnectAuthCode - - OpenIDConnectImplicit - - OpenIDConnectHybrid - """ - raise NotImplementedError('Subclasses must implement this method.') - def validate_user(self, username, password, client, request, *args, **kwargs): """Ensure the username and password is valid. @@ -726,26 +564,6 @@ class RequestValidator(object): """ raise NotImplementedError('Subclasses must implement this method.') - def validate_user_match(self, id_token_hint, scopes, claims, request): - """Ensure client supplied user id hint matches session user. - - If the sub claim or id_token_hint is supplied then the session - user must match the given ID. - - :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: OAuthlib request. - :type request: oauthlib.common.Request - :rtype: True or False - - Method is used by: - - OpenIDConnectAuthCode - - OpenIDConnectImplicit - - OpenIDConnectHybrid - """ - raise NotImplementedError('Subclasses must implement this method.') - def is_pkce_required(self, client_id, request): """Determine if current request requires PKCE. Default, False. This is called for both "authorization" and "token" requests. diff --git a/oauthlib/openid/__init__.py b/oauthlib/openid/__init__.py index 03f0fa2..7f1a876 100644 --- a/oauthlib/openid/__init__.py +++ b/oauthlib/openid/__init__.py @@ -7,3 +7,4 @@ oauthlib.openid from __future__ import absolute_import, unicode_literals from .connect.core.endpoints import Server +from .connect.core.request_validator import RequestValidator -- cgit v1.2.1 From 7c570c763725fdaa40778d6cd6689b09b3971f50 Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Thu, 28 Feb 2019 10:16:27 +0100 Subject: Add technicals fields of `id_token` in oauthlib OIDC support A new RequestValidator `fill_id_token` has been introduced to replace `get_id_token`. It aims to have the bare minimum amount of fields to complete a full OIDC id_token support. `get_id_token` is still valid but optional, and if it is implemented, `fill_id_token` will not be called. The current `fill_id_token` came with full support of `aud`, `iat`, `nonce`, `at_hash` and `c_hash`. More could come in the future e.g. `auth_time`, ... --- docs/oauth2/oidc/id_tokens.rst | 17 ++--- oauthlib/oauth2/rfc6749/request_validator.py | 3 + .../connect/core/grant_types/authorization_code.py | 20 ++++++ oauthlib/openid/connect/core/grant_types/base.py | 64 ++++++++++++++++-- .../openid/connect/core/grant_types/implicit.py | 4 +- oauthlib/openid/connect/core/request_validator.py | 75 +++++++++++++++++++++- 6 files changed, 166 insertions(+), 17 deletions(-) diff --git a/docs/oauth2/oidc/id_tokens.rst b/docs/oauth2/oidc/id_tokens.rst index 999cfa7..2387c01 100644 --- a/docs/oauth2/oidc/id_tokens.rst +++ b/docs/oauth2/oidc/id_tokens.rst @@ -1,9 +1,9 @@ ID Tokens ========= -The creation of `ID Tokens`_ is ultimately done not by OAuthLib but by your ``RequestValidator`` subclass. This is because their +The creation of `ID Tokens`_ is ultimately not done by OAuthLib but by your ``RequestValidator`` subclass. This is because their content is dependent on your implementation of users, their attributes, any claims you may wish to support, as well as the -details of how you model the notion of a Client Application. As such OAuthLib simply calls your validator's ``get_id_token`` +details of how you model the notion of a Client Application. As such OAuthLib simply calls your validator's ``fill_id_token`` method at the appropriate times during the authorization flow, depending on the grant type requested (Authorization Code, Implicit, Hybrid, etc.). @@ -12,7 +12,7 @@ See examples below. .. _`ID Tokens`: http://openid.net/specs/openid-connect-core-1_0.html#IDToken .. autoclass:: oauthlib.oauth2.RequestValidator - :members: get_id_token + :members: fill_id_token JWT/JWS example with pyjwt library @@ -38,12 +38,13 @@ You can switch to jwcrypto library if you want to return JWE instead. super().__init__(self, **kwargs) - def get_id_token(self, token, token_handler, request): + def fill_id_token(self, id_token, token, token_handler, request): import jwt - data = {"nonce": request.nonce} if request.nonce is not None else {} - + id_token["iss"] = "https://my.cool.app.com" + id_token["sub"] = request.user.id + id_token["exp"] = id_token["iat"] + 3600 * 24 # keep it valid for 24hours for claim_key in request.claims: - data[claim_key] = request.userattributes[claim_key] # this must be set in another callback + id_token[claim_key] = request.userattributes[claim_key] # this must be set in another callback - return jwt.encode(data, self.private_pem, 'RS256') + return jwt.encode(id_token, self.private_pem, 'RS256') diff --git a/oauthlib/oauth2/rfc6749/request_validator.py b/oauthlib/oauth2/rfc6749/request_validator.py index d6ec2ab..86509b6 100644 --- a/oauthlib/oauth2/rfc6749/request_validator.py +++ b/oauthlib/oauth2/rfc6749/request_validator.py @@ -271,6 +271,9 @@ class RequestValidator(object): - Code Challenge (``request.code_challenge``) and - Code Challenge Method (``request.code_challenge_method``) + To support OIDC, you MUST associate the code with: + - nonce, if present (``code["nonce"]``) + The ``code`` argument is actually a dictionary, containing at least a ``code`` key with the actual authorization code: diff --git a/oauthlib/openid/connect/core/grant_types/authorization_code.py b/oauthlib/openid/connect/core/grant_types/authorization_code.py index b0b1015..becfcfa 100644 --- a/oauthlib/openid/connect/core/grant_types/authorization_code.py +++ b/oauthlib/openid/connect/core/grant_types/authorization_code.py @@ -22,3 +22,23 @@ class AuthorizationCodeGrant(GrantTypeBase): self.custom_validators.post_auth.append( self.openid_authorization_validator) self.register_token_modifier(self.add_id_token) + + def add_id_token(self, token, token_handler, request): + """ + Construct an initial version of id_token, and let the + request_validator sign or encrypt it. + + The authorization_code version of this method is used to + retrieve the nonce accordingly to the code storage. + """ + # Treat it as normal OAuth 2 auth code request if openid is not present + if not request.scopes or 'openid' not in request.scopes: + return token + + nonce = self.request_validator.get_authorization_code_nonce( + request.client_id, + request.code, + request.redirect_uri, + request + ) + return super(AuthorizationCodeGrant, self).add_id_token(token, token_handler, request, nonce=nonce) diff --git a/oauthlib/openid/connect/core/grant_types/base.py b/oauthlib/openid/connect/core/grant_types/base.py index 4f5c944..19a7f4f 100644 --- a/oauthlib/openid/connect/core/grant_types/base.py +++ b/oauthlib/openid/connect/core/grant_types/base.py @@ -1,6 +1,8 @@ from .exceptions import OIDCNoPrompt +import base64 import datetime +import hashlib import logging from json import loads @@ -49,7 +51,45 @@ class GrantTypeBase(object): raise InvalidRequestError(description="Malformed claims parameter", uri="http://openid.net/specs/openid-connect-core-1_0.html#ClaimsParameter") - def add_id_token(self, token, token_handler, request): + def hash_id_token(self, value, hashfunc=hashlib.sha256): + """ + Its value is the base64url encoding of the left-most half of the + hash of the octets of the ASCII representation of the access_token + value, where the hash algorithm used is the hash algorithm used in + the alg Header Parameter of the ID Token's JOSE Header. + + For instance, if the alg is RS256, hash the access_token value + with SHA-256, then take the left-most 128 bits and + base64url-encode them. + For instance, if the alg is HS512, hash the code value with + SHA-512, then take the left-most 256 bits and base64url-encode + them. The c_hash value is a case-sensitive string. + + Example of hash from OIDC specification (bound to a JWS using RS256): + + code: + Qcb0Orv1zh30vL1MPRsbm-diHiMwcLyZvn1arpZv-Jxf_11jnpEX3Tgfvk + + c_hash: + LDktKdoQak3Pk0cnXxCltA + """ + digest = hashfunc(value.encode()).digest() + left_most = int(len(digest) / 2) + return base64.urlsafe_b64encode(digest[:left_most]).decode().rstrip("=") + + def add_id_token(self, token, token_handler, request, nonce=None): + """ + Construct an initial version of id_token, and let the + request_validator sign or encrypt it. + + The initial version can contain the fields below, accordingly + to the spec: + - aud + - iat + - nonce + - at_hash + - c_hash + """ # Treat it as normal OAuth 2 auth code request if openid is not present if not request.scopes or 'openid' not in request.scopes: return token @@ -58,13 +98,25 @@ class GrantTypeBase(object): if request.response_type and 'id_token' not in request.response_type: return token - if request.max_age: - d = datetime.datetime.utcnow() - token['auth_time'] = d.isoformat("T") + "Z" + # Implementation mint its own id_token without help. + id_token = self.request_validator.get_id_token(token, token_handler, request) + if id_token: + token['id_token'] = id_token + return token + + # Fallback for asking some help from oauthlib framework. + # Start with technicals fields bound to the specification. + id_token = {} + id_token['aud'] = request.client_id + id_token['iat'] = int(datetime.datetime.now().timestamp()) + if nonce is not None: + id_token["nonce"] = nonce - # TODO: acr claims (probably better handled by server code using oauthlib in get_id_token) + if "access_token" in token: + id_token["at_hash"] = self.hash_id_token(token["access_token"]) - token['id_token'] = self.request_validator.get_id_token(token, token_handler, request) + # Call request_validator to complete/sign/encrypt id_token + token['id_token'] = self.request_validator.fill_id_token(id_token, token, token_handler, request) return token diff --git a/oauthlib/openid/connect/core/grant_types/implicit.py b/oauthlib/openid/connect/core/grant_types/implicit.py index d3797b2..c2dbc27 100644 --- a/oauthlib/openid/connect/core/grant_types/implicit.py +++ b/oauthlib/openid/connect/core/grant_types/implicit.py @@ -27,9 +27,9 @@ class ImplicitGrant(GrantTypeBase): self.register_token_modifier(self.add_id_token) def add_id_token(self, token, token_handler, request): - if 'state' not in token: + if 'state' not in token and request.state: token['state'] = request.state - return super(ImplicitGrant, self).add_id_token(token, token_handler, request) + return super(ImplicitGrant, self).add_id_token(token, token_handler, request, nonce=request.nonce) def openid_authorization_validator(self, request): """Additional validation when following the implicit flow. diff --git a/oauthlib/openid/connect/core/request_validator.py b/oauthlib/openid/connect/core/request_validator.py index 1587754..f8aeed8 100644 --- a/oauthlib/openid/connect/core/request_validator.py +++ b/oauthlib/openid/connect/core/request_validator.py @@ -38,6 +38,31 @@ class RequestValidator(OAuth2RequestValidator): """ raise NotImplementedError('Subclasses must implement this method.') + def get_authorization_code_nonce(self, client_id, code, redirect_uri, request): + """ Extracts nonce from saved authorization code. + + If present in the Authentication Request, Authorization + Servers MUST include a nonce Claim in the ID Token with the + Claim Value being the nonce value sent in the Authentication + Request. Authorization Servers SHOULD perform no other + processing on nonce values used. The nonce value is a + case-sensitive string. + + Only code param should be sufficient to retrieve grant code from + any storage you are using, `client_id` and `redirect_uri` can gave a + 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 + + Method is used by: + - Authorization Token Grant Dispatcher + """ + raise NotImplementedError('Subclasses must implement this method.') + def get_jwt_bearer_token(self, token, token_handler, request): """Get JWT Bearer token or OpenID Connect ID token @@ -57,6 +82,12 @@ class RequestValidator(OAuth2RequestValidator): def get_id_token(self, token, token_handler, request): """Get OpenID Connect ID token + This method is OPTIONAL and is NOT RECOMMENDED. + `fill_id_token` SHOULD be implemented instead. However, if you + want a full control over the minting of the `id_token`, you + MAY want to override `get_id_token` instead of using + `fill_id_token`. + In the OpenID Connect workflows when an ID Token is requested this method is called. Subclasses should implement the construction, signing and optional encryption of the ID Token as described in the OpenID Connect spec. @@ -85,7 +116,49 @@ class RequestValidator(OAuth2RequestValidator): :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 + return None + + def fill_id_token(self, id_token, token, token_handler, request): + """Fill OpenID Connect ID token & Sign or Encrypt. + + In the OpenID Connect workflows when an ID Token is requested + this method is called. Subclasses should implement the + construction, signing and optional encryption of the ID Token + as described in the OpenID Connect spec. + + The `id_token` parameter is a dict containing a couple of OIDC + technical fields related to the specification. Prepopulated + attributes are: + + - `aud`, equals to `request.client_id`. + - `iat`, equals to current time. + - `nonce`, if present, is equals to the `nonce` from the + authorization request. + - `at_hash`, hash of `access_token`, if relevant. + - `c_hash`, hash of `code`, if relevant. + + This method MUST provide required fields as below: + + - `iss`, REQUIRED. Issuer Identifier for the Issuer of the response. + - `sub`, REQUIRED. Subject Identifier + - `exp`, REQUIRED. Expiration time on or after which the ID + Token MUST NOT be accepted by the RP when performing + authentication with the OP. + + Additionals claims must be added, note that `request.scope` + should be used to determine the list of claims. + + More information can be found at `OpenID Connect Core#Claims`_ + + .. _`OpenID Connect Core#Claims`: https://openid.net/specs/openid-connect-core-1_0.html#Claims + + :param id_token: A dict containing technical fields of id_token + :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 or JWE encrypted JWT) + """ raise NotImplementedError('Subclasses must implement this method.') def validate_jwt_bearer_token(self, token, scopes, request): -- cgit v1.2.1 From 62152d48e83cbc0eac3a2991b3b7fed2e84f7ec7 Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Thu, 28 Feb 2019 15:03:34 +0100 Subject: Add c_hash. Add summary about when nonce/hashes are added to id_token --- oauthlib/openid/connect/core/grant_types/base.py | 29 ++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/oauthlib/openid/connect/core/grant_types/base.py b/oauthlib/openid/connect/core/grant_types/base.py index 19a7f4f..f925c64 100644 --- a/oauthlib/openid/connect/core/grant_types/base.py +++ b/oauthlib/openid/connect/core/grant_types/base.py @@ -109,12 +109,41 @@ class GrantTypeBase(object): id_token = {} id_token['aud'] = request.client_id id_token['iat'] = int(datetime.datetime.now().timestamp()) + + # nonce is REQUIRED when response_type value is: + # - id_token token (Implicit) + # - id_token (Implicit) + # - code id_token (Hybrid) + # - code id_token token (Hybrid) + # + # nonce is OPTIONAL when response_type value is: + # - code (Authorization Code) + # - code token (Hybrid) if nonce is not None: id_token["nonce"] = nonce + # at_hash is REQUIRED when response_type value is: + # - id_token token (Implicit) + # - code id_token token (Hybrid) + # + # at_hash is OPTIONAL when: + # - code (Authorization code) + # - code id_token (Hybrid) + # - code token (Hybrid) + # + # at_hash MAY NOT be used when: + # - id_token (Implicit) if "access_token" in token: id_token["at_hash"] = self.hash_id_token(token["access_token"]) + # c_hash is REQUIRED when response_type value is: + # - code id_token (Hybrid) + # - code id_token token (Hybrid) + # + # c_hash is OPTIONAL for others. + if "code" in token: + id_token["c_hash"] = self.hash_id_token(token["code"]) + # Call request_validator to complete/sign/encrypt id_token token['id_token'] = self.request_validator.fill_id_token(id_token, token, token_handler, request) -- cgit v1.2.1 From 225399aad32d508a3cf00f8bcaaf7328e90cc904 Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Thu, 28 Feb 2019 10:06:37 +0100 Subject: Removed duplicated OIDC members in OAuth2.RequestValidator --- docs/oauth2/oidc/validator.rst | 6 +- oauthlib/oauth2/rfc6749/request_validator.py | 182 --------------------------- oauthlib/openid/__init__.py | 1 + 3 files changed, 5 insertions(+), 184 deletions(-) diff --git a/docs/oauth2/oidc/validator.rst b/docs/oauth2/oidc/validator.rst index a03adfe..7a6f574 100644 --- a/docs/oauth2/oidc/validator.rst +++ b/docs/oauth2/oidc/validator.rst @@ -10,12 +10,14 @@ upgrade it by replacing one line of code: .. code-block:: python from oauthlib.oauth2 import Server + from oauthlib.oauth2 import RequestValidator Into .. code-block:: python from oauthlib.openid import Server + from oauthlib.openid import RequestValidator Then, you have to implement the new RequestValidator methods as shown below. @@ -24,5 +26,5 @@ RequestValidator Extension A couple of methods must be implemented in your validator subclass if you wish to support OpenID Connect: -.. autoclass:: oauthlib.oauth2.RequestValidator - :members: validate_silent_authorization, validate_silent_login, validate_user_match, get_id_token, get_authorization_code_scopes, validate_jwt_bearer_token +.. autoclass:: oauthlib.openid.RequestValidator + :members: diff --git a/oauthlib/oauth2/rfc6749/request_validator.py b/oauthlib/oauth2/rfc6749/request_validator.py index 5ff30d8..d6ec2ab 100644 --- a/oauthlib/oauth2/rfc6749/request_validator.py +++ b/oauthlib/oauth2/rfc6749/request_validator.py @@ -291,32 +291,6 @@ class RequestValidator(object): """ raise NotImplementedError('Subclasses must implement this method.') - def get_authorization_code_scopes(self, client_id, code, redirect_uri, request): - """ Extracts scopes from saved authorization code. - - The scopes returned by this method is used to route token requests - based on scopes passed to Authorization Code requests. - - With that the token endpoint knows when to include OpenIDConnect - id_token in token response only based on authorization code scopes. - - Only code param should be sufficient to retrieve grant code from - any storage you are using, `client_id` and `redirect_uri` can gave a - 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. - :param request: OAuthlib request. - :type request: oauthlib.common.Request - :return: A list of scopes - - Method is used by: - - Authorization Token Grant Dispatcher - """ - raise NotImplementedError('Subclasses must implement this method.') - def save_token(self, token, request, *args, **kwargs): """Persist the token with a token type specific method. @@ -378,104 +352,6 @@ class RequestValidator(object): """ raise NotImplementedError('Subclasses must implement this method.') - def get_jwt_bearer_token(self, token, token_handler, request): - """Get JWT Bearer token or OpenID Connect ID token - - 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: 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: - - JWTToken.create_token - """ - raise NotImplementedError('Subclasses must implement this method.') - - def get_id_token(self, token, token_handler, request): - """Get OpenID Connect ID token - - In the OpenID Connect workflows when an ID Token is requested this method is called. - Subclasses should implement the construction, signing and optional encryption of the - ID Token as described in the OpenID Connect spec. - - In addition to the standard OAuth2 request properties, the request may also contain - these OIDC specific properties which are useful to this method: - - - nonce, if workflow is implicit or hybrid and it was provided - - claims, if provided to the original Authorization Code request - - The token parameter is a dict which may contain an ``access_token`` entry, in which - case the resulting ID Token *should* include a calculated ``at_hash`` claim. - - Similarly, when the request parameter has a ``code`` property defined, the ID Token - *should* include a calculated ``c_hash`` claim. - - http://openid.net/specs/openid-connect-core-1_0.html (sections `3.1.3.6`_, `3.2.2.10`_, `3.3.2.11`_) - - .. _`3.1.3.6`: http://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken - .. _`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: 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 - raise NotImplementedError('Subclasses must implement this method.') - - def validate_jwt_bearer_token(self, token, scopes, request): - """Ensure the JWT Bearer token or OpenID Connect ID token are valids and authorized access to scopes. - - If using OpenID Connect this SHOULD call `oauthlib.oauth2.RequestValidator.get_id_token` - - If not using OpenID Connect this can `return None` to avoid 5xx rather 401/3 response. - - OpenID connect core 1.0 describe how to validate an id_token: - - http://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation - - http://openid.net/specs/openid-connect-core-1_0.html#ImplicitIDTValidation - - 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: 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: - - Authorization Code Grant - - Implicit Grant - - Hybrid Grant - """ - raise NotImplementedError('Subclasses must implement this method.') - - def validate_id_token(self, token, scopes, request): - """Ensure the id token is valid and authorized access to scopes. - - OpenID connect core 1.0 describe how to validate an id_token: - - http://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation - - http://openid.net/specs/openid-connect-core-1_0.html#ImplicitIDTValidation - - 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: 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: - - Authorization Code Grant - - Implicit Grant - - Hybrid Grant - """ - raise NotImplementedError('Subclasses must implement this method.') - def validate_bearer_token(self, token, scopes, request): """Ensure the Bearer token is valid and authorized access to scopes. @@ -668,44 +544,6 @@ class RequestValidator(object): """ raise NotImplementedError('Subclasses must implement this method.') - def validate_silent_authorization(self, request): - """Ensure the logged in user has authorized silent OpenID authorization. - - Silent OpenID authorization allows access tokens and id tokens to be - granted to clients without any user prompt or interaction. - - :param request: OAuthlib request. - :type request: oauthlib.common.Request - :rtype: True or False - - Method is used by: - - OpenIDConnectAuthCode - - OpenIDConnectImplicit - - OpenIDConnectHybrid - """ - raise NotImplementedError('Subclasses must implement this method.') - - def validate_silent_login(self, request): - """Ensure session user has authorized silent OpenID login. - - If no user is logged in or has not authorized silent login, this - method should return False. - - If the user is logged in but associated with multiple accounts and - not selected which one to link to the token then this method should - raise an oauthlib.oauth2.AccountSelectionRequired error. - - :param request: OAuthlib request. - :type request: oauthlib.common.Request - :rtype: True or False - - Method is used by: - - OpenIDConnectAuthCode - - OpenIDConnectImplicit - - OpenIDConnectHybrid - """ - raise NotImplementedError('Subclasses must implement this method.') - def validate_user(self, username, password, client, request, *args, **kwargs): """Ensure the username and password is valid. @@ -726,26 +564,6 @@ class RequestValidator(object): """ raise NotImplementedError('Subclasses must implement this method.') - def validate_user_match(self, id_token_hint, scopes, claims, request): - """Ensure client supplied user id hint matches session user. - - If the sub claim or id_token_hint is supplied then the session - user must match the given ID. - - :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: OAuthlib request. - :type request: oauthlib.common.Request - :rtype: True or False - - Method is used by: - - OpenIDConnectAuthCode - - OpenIDConnectImplicit - - OpenIDConnectHybrid - """ - raise NotImplementedError('Subclasses must implement this method.') - def is_pkce_required(self, client_id, request): """Determine if current request requires PKCE. Default, False. This is called for both "authorization" and "token" requests. diff --git a/oauthlib/openid/__init__.py b/oauthlib/openid/__init__.py index 03f0fa2..7f1a876 100644 --- a/oauthlib/openid/__init__.py +++ b/oauthlib/openid/__init__.py @@ -7,3 +7,4 @@ oauthlib.openid from __future__ import absolute_import, unicode_literals from .connect.core.endpoints import Server +from .connect.core.request_validator import RequestValidator -- cgit v1.2.1 From 7f7d38e6a48b85b906fc14ce186fa39ee468176d Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Thu, 28 Feb 2019 15:26:09 +0100 Subject: Fixed missing references in unittests --- tests/openid/connect/core/endpoints/test_claims_handling.py | 2 +- tests/openid/connect/core/test_request_validator.py | 2 +- tests/openid/connect/core/test_tokens.py | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/openid/connect/core/endpoints/test_claims_handling.py b/tests/openid/connect/core/endpoints/test_claims_handling.py index 270ef69..5f39d96 100644 --- a/tests/openid/connect/core/endpoints/test_claims_handling.py +++ b/tests/openid/connect/core/endpoints/test_claims_handling.py @@ -10,7 +10,7 @@ from __future__ import absolute_import, unicode_literals import mock -from oauthlib.oauth2 import RequestValidator +from oauthlib.openid import RequestValidator from oauthlib.openid.connect.core.endpoints.pre_configured import Server from tests.unittest import TestCase diff --git a/tests/openid/connect/core/test_request_validator.py b/tests/openid/connect/core/test_request_validator.py index 1e71fb1..e20e88f 100644 --- a/tests/openid/connect/core/test_request_validator.py +++ b/tests/openid/connect/core/test_request_validator.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import absolute_import, unicode_literals -from oauthlib.openid.connect.core.request_validator import RequestValidator +from oauthlib.openid import RequestValidator from tests.unittest import TestCase diff --git a/tests/openid/connect/core/test_tokens.py b/tests/openid/connect/core/test_tokens.py index 1fcfb51..fde89d6 100644 --- a/tests/openid/connect/core/test_tokens.py +++ b/tests/openid/connect/core/test_tokens.py @@ -42,7 +42,7 @@ class JWTTokenTestCase(TestCase): """ request_mock = mock.MagicMock() - with mock.patch('oauthlib.oauth2.rfc6749.request_validator.RequestValidator', + with mock.patch('oauthlib.openid.RequestValidator', autospec=True) as RequestValidatorMock: request_validator = RequestValidatorMock() @@ -58,7 +58,7 @@ class JWTTokenTestCase(TestCase): """ with mock.patch('oauthlib.common.Request', autospec=True) as RequestMock, \ - mock.patch('oauthlib.oauth2.rfc6749.request_validator.RequestValidator', + mock.patch('oauthlib.openid.RequestValidator', autospec=True) as RequestValidatorMock: request_validator_mock = RequestValidatorMock() @@ -84,7 +84,7 @@ class JWTTokenTestCase(TestCase): """ with mock.patch('oauthlib.common.Request', autospec=True) as RequestMock, \ - mock.patch('oauthlib.oauth2.rfc6749.request_validator.RequestValidator', + mock.patch('oauthlib.openid.RequestValidator', autospec=True) as RequestValidatorMock: request_validator_mock = RequestValidatorMock() -- cgit v1.2.1 From 0a1b4f34d1d2f3fcbca8a7ff2d07b78612a3b766 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Sat, 2 Mar 2019 09:01:43 -0800 Subject: Combine multiple isinstance() calls to one --- oauthlib/common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oauthlib/common.py b/oauthlib/common.py index 970d7a5..96de1f1 100644 --- a/oauthlib/common.py +++ b/oauthlib/common.py @@ -172,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) or isinstance(raw, unicode_type): + if isinstance(raw, (bytes, unicode_type)): try: params = urldecode(raw) except ValueError: -- cgit v1.2.1 From 84cd5a4265c2670af5a4b7ad2143c47fa68582c1 Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Mon, 25 Feb 2019 15:36:13 +0100 Subject: Change to 3.0.2-dev as long as master is in "dev" --- CHANGELOG.rst | 2 +- oauthlib/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f49fb92..ade6243 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,7 +1,7 @@ Changelog ========= -TBD +3.0.2 (TBD) ------------------ * #650 Fixed space encoding in base string URI used in the signature base string. * #652: Fixed OIDC /token response which wrongly returned "&state=None" diff --git a/oauthlib/__init__.py b/oauthlib/__init__.py index b23102c..8eb82a6 100644 --- a/oauthlib/__init__.py +++ b/oauthlib/__init__.py @@ -12,6 +12,6 @@ import logging from logging import NullHandler __author__ = 'The OAuthlib Community' -__version__ = '3.0.1' +__version__ = '3.0.2-dev' logging.getLogger('oauthlib').addHandler(NullHandler()) -- cgit v1.2.1 From 53d3d335879f205ae705d93420f34984073cd5a1 Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Tue, 26 Mar 2019 14:50:41 +0100 Subject: Renamed fill into finalize to add clarity --- docs/oauth2/oidc/id_tokens.rst | 6 +++--- oauthlib/openid/connect/core/grant_types/base.py | 2 +- oauthlib/openid/connect/core/request_validator.py | 8 ++++---- tests/openid/connect/core/test_request_validator.py | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/oauth2/oidc/id_tokens.rst b/docs/oauth2/oidc/id_tokens.rst index 2387c01..a1bf7cf 100644 --- a/docs/oauth2/oidc/id_tokens.rst +++ b/docs/oauth2/oidc/id_tokens.rst @@ -3,7 +3,7 @@ ID Tokens The creation of `ID Tokens`_ is ultimately not done by OAuthLib but by your ``RequestValidator`` subclass. This is because their content is dependent on your implementation of users, their attributes, any claims you may wish to support, as well as the -details of how you model the notion of a Client Application. As such OAuthLib simply calls your validator's ``fill_id_token`` +details of how you model the notion of a Client Application. As such OAuthLib simply calls your validator's ``finalize_id_token`` method at the appropriate times during the authorization flow, depending on the grant type requested (Authorization Code, Implicit, Hybrid, etc.). @@ -12,7 +12,7 @@ See examples below. .. _`ID Tokens`: http://openid.net/specs/openid-connect-core-1_0.html#IDToken .. autoclass:: oauthlib.oauth2.RequestValidator - :members: fill_id_token + :members: finalize_id_token JWT/JWS example with pyjwt library @@ -38,7 +38,7 @@ You can switch to jwcrypto library if you want to return JWE instead. super().__init__(self, **kwargs) - def fill_id_token(self, id_token, token, token_handler, request): + def finalize_id_token(self, id_token, token, token_handler, request): import jwt id_token["iss"] = "https://my.cool.app.com" diff --git a/oauthlib/openid/connect/core/grant_types/base.py b/oauthlib/openid/connect/core/grant_types/base.py index f925c64..31ff82e 100644 --- a/oauthlib/openid/connect/core/grant_types/base.py +++ b/oauthlib/openid/connect/core/grant_types/base.py @@ -145,7 +145,7 @@ class GrantTypeBase(object): id_token["c_hash"] = self.hash_id_token(token["code"]) # Call request_validator to complete/sign/encrypt id_token - token['id_token'] = self.request_validator.fill_id_token(id_token, token, token_handler, request) + token['id_token'] = self.request_validator.finalize_id_token(id_token, token, token_handler, request) return token diff --git a/oauthlib/openid/connect/core/request_validator.py b/oauthlib/openid/connect/core/request_validator.py index f8aeed8..7ce7e17 100644 --- a/oauthlib/openid/connect/core/request_validator.py +++ b/oauthlib/openid/connect/core/request_validator.py @@ -83,10 +83,10 @@ class RequestValidator(OAuth2RequestValidator): """Get OpenID Connect ID token This method is OPTIONAL and is NOT RECOMMENDED. - `fill_id_token` SHOULD be implemented instead. However, if you + `finalize_id_token` SHOULD be implemented instead. However, if you want a full control over the minting of the `id_token`, you MAY want to override `get_id_token` instead of using - `fill_id_token`. + `finalize_id_token`. In the OpenID Connect workflows when an ID Token is requested this method is called. Subclasses should implement the construction, signing and optional encryption of the @@ -118,8 +118,8 @@ class RequestValidator(OAuth2RequestValidator): """ return None - def fill_id_token(self, id_token, token, token_handler, request): - """Fill OpenID Connect ID token & Sign or Encrypt. + def finalize_id_token(self, id_token, token, token_handler, request): + """Finalize OpenID Connect ID token & Sign or Encrypt. In the OpenID Connect workflows when an ID Token is requested this method is called. Subclasses should implement the diff --git a/tests/openid/connect/core/test_request_validator.py b/tests/openid/connect/core/test_request_validator.py index e20e88f..ebe0aeb 100644 --- a/tests/openid/connect/core/test_request_validator.py +++ b/tests/openid/connect/core/test_request_validator.py @@ -22,8 +22,8 @@ class RequestValidatorTest(TestCase): ) self.assertRaises( NotImplementedError, - v.get_id_token, - 'token', 'token_handler', 'request' + v.finalize_id_token, + 'id_token', 'token', 'token_handler', 'request' ) self.assertRaises( NotImplementedError, -- cgit v1.2.1 From 4877b4837a9355bc74c9f3d59343d689be4c86fa Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Tue, 26 Mar 2019 14:50:55 +0100 Subject: Use native operator instead type conversion --- oauthlib/openid/connect/core/grant_types/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oauthlib/openid/connect/core/grant_types/base.py b/oauthlib/openid/connect/core/grant_types/base.py index 31ff82e..c5d91e7 100644 --- a/oauthlib/openid/connect/core/grant_types/base.py +++ b/oauthlib/openid/connect/core/grant_types/base.py @@ -74,7 +74,7 @@ class GrantTypeBase(object): LDktKdoQak3Pk0cnXxCltA """ digest = hashfunc(value.encode()).digest() - left_most = int(len(digest) / 2) + left_most = len(digest) // 2 return base64.urlsafe_b64encode(digest[:left_most]).decode().rstrip("=") def add_id_token(self, token, token_handler, request, nonce=None): -- cgit v1.2.1 From 09538c93d562f6230f3d257b6782d58eeb0a7c3e Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Tue, 26 Mar 2019 16:15:13 +0100 Subject: Add unittests for OIDC GrantTypeBase. Rename hash_id_token into id_token_hash --- oauthlib/openid/connect/core/grant_types/base.py | 6 +- tests/openid/connect/core/grant_types/test_base.py | 104 +++++++++++++++++++++ 2 files changed, 107 insertions(+), 3 deletions(-) create mode 100644 tests/openid/connect/core/grant_types/test_base.py diff --git a/oauthlib/openid/connect/core/grant_types/base.py b/oauthlib/openid/connect/core/grant_types/base.py index c5d91e7..6272ea2 100644 --- a/oauthlib/openid/connect/core/grant_types/base.py +++ b/oauthlib/openid/connect/core/grant_types/base.py @@ -51,7 +51,7 @@ class GrantTypeBase(object): raise InvalidRequestError(description="Malformed claims parameter", uri="http://openid.net/specs/openid-connect-core-1_0.html#ClaimsParameter") - def hash_id_token(self, value, hashfunc=hashlib.sha256): + def id_token_hash(self, value, hashfunc=hashlib.sha256): """ Its value is the base64url encoding of the left-most half of the hash of the octets of the ASCII representation of the access_token @@ -134,7 +134,7 @@ class GrantTypeBase(object): # at_hash MAY NOT be used when: # - id_token (Implicit) if "access_token" in token: - id_token["at_hash"] = self.hash_id_token(token["access_token"]) + id_token["at_hash"] = self.id_token_hash(token["access_token"]) # c_hash is REQUIRED when response_type value is: # - code id_token (Hybrid) @@ -142,7 +142,7 @@ class GrantTypeBase(object): # # c_hash is OPTIONAL for others. if "code" in token: - id_token["c_hash"] = self.hash_id_token(token["code"]) + id_token["c_hash"] = self.id_token_hash(token["code"]) # Call request_validator to complete/sign/encrypt id_token token['id_token'] = self.request_validator.finalize_id_token(id_token, token, token_handler, request) diff --git a/tests/openid/connect/core/grant_types/test_base.py b/tests/openid/connect/core/grant_types/test_base.py new file mode 100644 index 0000000..319904b --- /dev/null +++ b/tests/openid/connect/core/grant_types/test_base.py @@ -0,0 +1,104 @@ +# -*- coding: utf-8 -*- +import datetime +import mock + +from oauthlib.common import Request +from oauthlib.openid.connect.core.grant_types.base import GrantTypeBase + +from tests.unittest import TestCase + + +class GrantBase(GrantTypeBase): + """Class to test GrantTypeBase""" + def __init__(self, request_validator=None, **kwargs): + self.request_validator = request_validator + + +class IDTokenTest(TestCase): + + def setUp(self): + self.request = Request('http://a.b/path') + self.request.scopes = ('hello', 'openid') + self.request.expires_in = 1800 + self.request.client_id = 'abcdef' + self.request.code = '1234' + self.request.response_type = 'id_token' + self.request.grant_type = 'authorization_code' + self.request.redirect_uri = 'https://a.b/cb' + self.request.state = 'abc' + self.request.nonce = None + + self.mock_validator = mock.MagicMock() + self.mock_validator.get_id_token.return_value = None + self.mock_validator.finalize_id_token.return_value = "eyJ.body.signature" + self.token = {} + + self.grant = GrantBase(request_validator=self.mock_validator) + + self.url_query = 'https://a.b/cb?code=abc&state=abc' + self.url_fragment = 'https://a.b/cb#code=abc&state=abc' + + def test_id_token_hash(self): + self.assertEqual(self.grant.id_token_hash( + "Qcb0Orv1zh30vL1MPRsbm-diHiMwcLyZvn1arpZv-Jxf_11jnpEX3Tgfvk", + ), "LDktKdoQak3Pk0cnXxCltA", "hash differs from RFC") + + def test_get_id_token_no_openid(self): + self.request.scopes = ('hello') + token = self.grant.add_id_token(self.token, "token_handler_mock", self.request) + self.assertNotIn("id_token", token) + + self.request.scopes = None + token = self.grant.add_id_token(self.token, "token_handler_mock", self.request) + self.assertNotIn("id_token", token) + + self.request.scopes = () + token = self.grant.add_id_token(self.token, "token_handler_mock", self.request) + self.assertNotIn("id_token", token) + + def test_get_id_token(self): + self.mock_validator.get_id_token.return_value = "toto" + token = self.grant.add_id_token(self.token, "token_handler_mock", self.request) + self.assertIn("id_token", token) + self.assertEqual(token["id_token"], "toto") + + def test_finalize_id_token(self): + token = self.grant.add_id_token(self.token, "token_handler_mock", self.request) + self.assertIn("id_token", token) + self.assertEqual(token["id_token"], "eyJ.body.signature") + id_token = self.mock_validator.finalize_id_token.call_args[0][0] + self.assertEqual(id_token['aud'], 'abcdef') + self.assertGreaterEqual(id_token['iat'], int(datetime.datetime.now().timestamp())) + + def test_finalize_id_token_with_nonce(self): + token = self.grant.add_id_token(self.token, "token_handler_mock", self.request, "my_nonce") + self.assertIn("id_token", token) + self.assertEqual(token["id_token"], "eyJ.body.signature") + id_token = self.mock_validator.finalize_id_token.call_args[0][0] + self.assertEqual(id_token['nonce'], 'my_nonce') + + def test_finalize_id_token_with_at_hash(self): + self.token["access_token"] = "Qcb0Orv1zh30vL1MPRsbm-diHiMwcLyZvn1arpZv-Jxf_11jnpEX3Tgfvk" + token = self.grant.add_id_token(self.token, "token_handler_mock", self.request) + self.assertIn("id_token", token) + self.assertEqual(token["id_token"], "eyJ.body.signature") + id_token = self.mock_validator.finalize_id_token.call_args[0][0] + self.assertEqual(id_token['at_hash'], 'LDktKdoQak3Pk0cnXxCltA') + + def test_finalize_id_token_with_c_hash(self): + self.token["code"] = "Qcb0Orv1zh30vL1MPRsbm-diHiMwcLyZvn1arpZv-Jxf_11jnpEX3Tgfvk" + token = self.grant.add_id_token(self.token, "token_handler_mock", self.request) + self.assertIn("id_token", token) + self.assertEqual(token["id_token"], "eyJ.body.signature") + id_token = self.mock_validator.finalize_id_token.call_args[0][0] + self.assertEqual(id_token['c_hash'], 'LDktKdoQak3Pk0cnXxCltA') + + def test_finalize_id_token_with_c_and_at_hash(self): + self.token["code"] = "Qcb0Orv1zh30vL1MPRsbm-diHiMwcLyZvn1arpZv-Jxf_11jnpEX3Tgfvk" + self.token["access_token"] = "Qcb0Orv1zh30vL1MPRsbm-diHiMwcLyZvn1arpZv-Jxf_11jnpEX3Tgfvk" + token = self.grant.add_id_token(self.token, "token_handler_mock", self.request) + self.assertIn("id_token", token) + self.assertEqual(token["id_token"], "eyJ.body.signature") + id_token = self.mock_validator.finalize_id_token.call_args[0][0] + self.assertEqual(id_token['at_hash'], 'LDktKdoQak3Pk0cnXxCltA') + self.assertEqual(id_token['c_hash'], 'LDktKdoQak3Pk0cnXxCltA') -- cgit v1.2.1 From ed8c4f253def93a0d4d78a6ead1a63091f8e4c26 Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Tue, 26 Mar 2019 16:32:51 +0100 Subject: Python2.7 compatible --- oauthlib/openid/connect/core/grant_types/base.py | 4 ++-- tests/openid/connect/core/grant_types/test_base.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/oauthlib/openid/connect/core/grant_types/base.py b/oauthlib/openid/connect/core/grant_types/base.py index 6272ea2..32a21b6 100644 --- a/oauthlib/openid/connect/core/grant_types/base.py +++ b/oauthlib/openid/connect/core/grant_types/base.py @@ -1,9 +1,9 @@ from .exceptions import OIDCNoPrompt import base64 -import datetime import hashlib import logging +import time from json import loads from oauthlib.oauth2.rfc6749.errors import ConsentRequired, InvalidRequestError, LoginRequired @@ -108,7 +108,7 @@ class GrantTypeBase(object): # Start with technicals fields bound to the specification. id_token = {} id_token['aud'] = request.client_id - id_token['iat'] = int(datetime.datetime.now().timestamp()) + id_token['iat'] = int(time.time()) # nonce is REQUIRED when response_type value is: # - id_token token (Implicit) diff --git a/tests/openid/connect/core/grant_types/test_base.py b/tests/openid/connect/core/grant_types/test_base.py index 319904b..76e017f 100644 --- a/tests/openid/connect/core/grant_types/test_base.py +++ b/tests/openid/connect/core/grant_types/test_base.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -import datetime import mock +import time from oauthlib.common import Request from oauthlib.openid.connect.core.grant_types.base import GrantTypeBase @@ -68,7 +68,7 @@ class IDTokenTest(TestCase): self.assertEqual(token["id_token"], "eyJ.body.signature") id_token = self.mock_validator.finalize_id_token.call_args[0][0] self.assertEqual(id_token['aud'], 'abcdef') - self.assertGreaterEqual(id_token['iat'], int(datetime.datetime.now().timestamp())) + self.assertGreaterEqual(id_token['iat'], int(time.time())) def test_finalize_id_token_with_nonce(self): token = self.grant.add_id_token(self.token, "token_handler_mock", self.request, "my_nonce") -- cgit v1.2.1 From 43eece8bddccb1453481b5962c3cff279c7b4ea8 Mon Sep 17 00:00:00 2001 From: Arjan Keeman Date: Thu, 21 Mar 2019 14:17:58 +0100 Subject: fix include_client_id argument --- .../oauth2/rfc6749/clients/backend_application.py | 16 +++++----- .../oauth2/rfc6749/clients/legacy_application.py | 14 ++++----- .../oauth2/rfc6749/clients/service_application.py | 36 +++++++++++----------- oauthlib/oauth2/rfc6749/parameters.py | 4 +-- 4 files changed, 35 insertions(+), 35 deletions(-) diff --git a/oauthlib/oauth2/rfc6749/clients/backend_application.py b/oauthlib/oauth2/rfc6749/clients/backend_application.py index a000ecf..2483e56 100644 --- a/oauthlib/oauth2/rfc6749/clients/backend_application.py +++ b/oauthlib/oauth2/rfc6749/clients/backend_application.py @@ -29,11 +29,11 @@ class BackendApplicationClient(Client): Since the client authentication is used as the authorization grant, no additional authorization request is needed. """ - + grant_type = 'client_credentials' - + def prepare_request_body(self, body='', scope=None, - include_client_id=None, **kwargs): + include_client_id=False, **kwargs): """Add the client credentials to the request body. The client makes a request to the token endpoint by adding the @@ -45,11 +45,11 @@ class BackendApplicationClient(Client): :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`_. + :param include_client_id: `True` 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`_. False otherwise (default). :type include_client_id: Boolean :param kwargs: Extra credentials to include in the token request. diff --git a/oauthlib/oauth2/rfc6749/clients/legacy_application.py b/oauthlib/oauth2/rfc6749/clients/legacy_application.py index 2449363..ca218e4 100644 --- a/oauthlib/oauth2/rfc6749/clients/legacy_application.py +++ b/oauthlib/oauth2/rfc6749/clients/legacy_application.py @@ -34,14 +34,14 @@ class LegacyApplicationClient(Client): credentials is beyond the scope of this specification. The client MUST discard the credentials once an access token has been obtained. """ - + grant_type = 'password' def __init__(self, client_id, **kwargs): super(LegacyApplicationClient, self).__init__(client_id, **kwargs) def prepare_request_body(self, username, password, body='', scope=None, - include_client_id=None, **kwargs): + include_client_id=False, **kwargs): """Add the resource owner password and username to the request body. The client makes a request to the token endpoint by adding the @@ -54,11 +54,11 @@ 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`_. + :param include_client_id: `True` 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`_. False otherwise (default). :type include_client_id: Boolean :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 35333d8..ea946ce 100644 --- a/oauthlib/oauth2/rfc6749/clients/service_application.py +++ b/oauthlib/oauth2/rfc6749/clients/service_application.py @@ -41,20 +41,20 @@ class ServiceApplicationClient(Client): :param private_key: Private key used for signing and encrypting. Must be given as a string. - :param subject: The principal that is the subject of the JWT, i.e. + :param subject: The principal that is the subject of the JWT, i.e. which user is the token requested on behalf of. For example, ``foo@example.com. :param issuer: The JWT MUST contain an "iss" (issuer) claim that contains a unique identifier for the entity that issued - the JWT. For example, ``your-client@provider.com``. + the JWT. For example, ``your-client@provider.com``. :param audience: A value identifying the authorization server as an intended audience, e.g. ``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) @@ -63,17 +63,17 @@ class ServiceApplicationClient(Client): self.issuer = issuer self.audience = audience - def prepare_request_body(self, + def prepare_request_body(self, private_key=None, - subject=None, - issuer=None, - audience=None, - expires_at=None, + subject=None, + issuer=None, + audience=None, + expires_at=None, issued_at=None, extra_claims=None, - body='', + body='', scope=None, - include_client_id=None, + include_client_id=False, **kwargs): """Create and add a JWT assertion to the request body. @@ -86,7 +86,7 @@ class ServiceApplicationClient(Client): :param issuer: (iss) The JWT MUST contain an "iss" (issuer) claim that contains a unique identifier for the entity that issued - the JWT. For example, ``your-client@provider.com``. + the JWT. For example, ``your-client@provider.com``. :param audience: (aud) A value identifying the authorization server as an intended audience, e.g. @@ -105,11 +105,11 @@ 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`_. + :param include_client_id: `True` 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`_. False otherwise (default). :type include_client_id: Boolean :param not_before: A unix timestamp after which the JWT may be used. @@ -129,7 +129,7 @@ class ServiceApplicationClient(Client): [I-D.ietf-oauth-assertions] specification, to indicate the requested scope. - Authentication of the client is optional, as described in + Authentication of the client is optional, as described in `Section 3.2.1`_ of OAuth 2.0 [RFC6749] and consequently, the "client_id" is only needed when a form of client authentication that relies on the parameter is used. @@ -186,5 +186,5 @@ class ServiceApplicationClient(Client): return prepare_token_request(self.grant_type, body=body, assertion=assertion, - scope=scope, + scope=scope, **kwargs) diff --git a/oauthlib/oauth2/rfc6749/parameters.py b/oauthlib/oauth2/rfc6749/parameters.py index 4d0baee..6b9d630 100644 --- a/oauthlib/oauth2/rfc6749/parameters.py +++ b/oauthlib/oauth2/rfc6749/parameters.py @@ -98,7 +98,7 @@ def prepare_token_request(grant_type, body='', include_client_id=True, **kwargs) "authorization_code" or "client_credentials". :param body: Existing request body (URL encoded string) to embed parameters - into. This may contain extra paramters. Default ''. + into. This may contain extra parameters. Default ''. :param include_client_id: `True` (default) to send the `client_id` in the body of the upstream request. This is required @@ -142,7 +142,7 @@ def prepare_token_request(grant_type, body='', include_client_id=True, **kwargs) if 'scope' in kwargs: kwargs['scope'] = list_to_scope(kwargs['scope']) - # pull the `client_id` out of the kwargs. + # 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: -- cgit v1.2.1 From 5ae97b984f9fe29717c125c205f79c87c6370613 Mon Sep 17 00:00:00 2001 From: Abhishek Patel Date: Sun, 21 Apr 2019 12:09:00 -0700 Subject: Add method to get/set debug flag - By default debug mode is always off - Debug mode turned on automatically for tests - Complete requests sanitized in non debug mode --- oauthlib/__init__.py | 9 +++++++++ oauthlib/common.py | 3 +++ tests/__init__.py | 3 +++ tests/test_common.py | 16 ++++++++++++++++ 4 files changed, 31 insertions(+) diff --git a/oauthlib/__init__.py b/oauthlib/__init__.py index 8eb82a6..8639e88 100644 --- a/oauthlib/__init__.py +++ b/oauthlib/__init__.py @@ -15,3 +15,12 @@ __author__ = 'The OAuthlib Community' __version__ = '3.0.2-dev' logging.getLogger('oauthlib').addHandler(NullHandler()) + +_DEBUG = False + +def set_debug(debug_val): + global _DEBUG + _DEBUG = debug_val + +def get_debug_flag(): + return _DEBUG diff --git a/oauthlib/common.py b/oauthlib/common.py index 96de1f1..ea5bfe7 100644 --- a/oauthlib/common.py +++ b/oauthlib/common.py @@ -14,6 +14,7 @@ import logging import re import sys import time +from . import get_debug_flag try: from secrets import randbits @@ -435,6 +436,8 @@ class Request(object): raise AttributeError(name) def __repr__(self): + if not get_debug_flag(): + return "" body = self.body headers = self.headers.copy() if body: diff --git a/tests/__init__.py b/tests/__init__.py index e69de29..f33236b 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -0,0 +1,3 @@ +import oauthlib + +oauthlib.set_debug(True) diff --git a/tests/test_common.py b/tests/test_common.py index 20d9f5b..ae2531b 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -1,8 +1,10 @@ # -*- coding: utf-8 -*- from __future__ import absolute_import, unicode_literals +import os import sys +import oauthlib from oauthlib.common import (CaseInsensitiveDict, Request, add_params_to_uri, extract_params, generate_client_id, generate_nonce, generate_timestamp, @@ -214,6 +216,20 @@ class RequestTest(TestCase): self.assertEqual(r.headers['token'], 'foobar') self.assertEqual(r.token, 'banana') + def test_sanitized_request_non_debug_mode(self): + """make sure requests are sanitized when in non debug mode. + For the debug mode, the other tests checking sanitization should prove + that debug mode is working. + """ + try: + oauthlib.set_debug(False) + r = Request(URI, headers={'token': 'foobar'}, body='token=banana') + self.assertNotIn('token', repr(r)) + self.assertIn('SANITIZED', repr(r)) + finally: + # set flag back for other tests + oauthlib.set_debug(True) + class CaseInsensitiveDictTest(TestCase): -- cgit v1.2.1 From 53b2db53a9b91183e8828f138ca3d64dd601584c Mon Sep 17 00:00:00 2001 From: Abhishek Patel Date: Sun, 21 Apr 2019 12:13:06 -0700 Subject: add doc --- oauthlib/__init__.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/oauthlib/__init__.py b/oauthlib/__init__.py index 8639e88..25132f0 100644 --- a/oauthlib/__init__.py +++ b/oauthlib/__init__.py @@ -19,8 +19,16 @@ logging.getLogger('oauthlib').addHandler(NullHandler()) _DEBUG = False def set_debug(debug_val): + """Set value of debug flag + + :param debug_val: Value to set. Must be a bool value. + """ global _DEBUG _DEBUG = debug_val def get_debug_flag(): + """Get debug mode value. + + :return: `True` if debug mode is on, `False` otherwise + """ return _DEBUG -- cgit v1.2.1 From 8390fa8ec6c019b3403b05287392e7a517c71963 Mon Sep 17 00:00:00 2001 From: Abhishek Patel Date: Tue, 23 Apr 2019 19:39:50 -0700 Subject: refactor to get_debug - Oauthlib's debug mode can be checked with method --- oauthlib/__init__.py | 2 +- oauthlib/common.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/oauthlib/__init__.py b/oauthlib/__init__.py index 25132f0..0e413bc 100644 --- a/oauthlib/__init__.py +++ b/oauthlib/__init__.py @@ -26,7 +26,7 @@ def set_debug(debug_val): global _DEBUG _DEBUG = debug_val -def get_debug_flag(): +def get_debug(): """Get debug mode value. :return: `True` if debug mode is on, `False` otherwise diff --git a/oauthlib/common.py b/oauthlib/common.py index ea5bfe7..5aeb015 100644 --- a/oauthlib/common.py +++ b/oauthlib/common.py @@ -14,7 +14,7 @@ import logging import re import sys import time -from . import get_debug_flag +from . import get_debug try: from secrets import randbits @@ -436,7 +436,7 @@ class Request(object): raise AttributeError(name) def __repr__(self): - if not get_debug_flag(): + if not get_debug(): return "" body = self.body headers = self.headers.copy() -- cgit v1.2.1 From 924a58fa1a9cc6eb2175ac038ca409708951915b Mon Sep 17 00:00:00 2001 From: Abhishek Patel Date: Tue, 23 Apr 2019 21:08:04 -0700 Subject: Update documentation --- docs/error_reporting.rst | 6 +++++- docs/oauth1/server.rst | 2 ++ docs/oauth2/server.rst | 3 +++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/docs/error_reporting.rst b/docs/error_reporting.rst index 705f447..a80287b 100644 --- a/docs/error_reporting.rst +++ b/docs/error_reporting.rst @@ -10,16 +10,20 @@ case where that is not true please let us know! When reporting bugs, especially when they are hard or impossible to reproduce, it is useful to include logging output. You can enable logging for all -oauthlib modules by adding a logger to the `oauthlib` namespace. +oauthlib modules by adding a logger to the `oauthlib` namespace. You might also +want to enable debugging mode to include request data in output. .. code-block:: python import logging + import oauthlib import sys + oauthlib.set_debug(True) log = logging.getLogger('oauthlib') log.addHandler(logging.StreamHandler(sys.stdout)) log.setLevel(logging.DEBUG) + If you are using a library that builds upon OAuthLib please also enable the logging for their modules, e.g. for `requests-oauthlib` diff --git a/docs/oauth1/server.rst b/docs/oauth1/server.rst index db469d2..2f30c65 100644 --- a/docs/oauth1/server.rst +++ b/docs/oauth1/server.rst @@ -441,7 +441,9 @@ Drop a line in our `Gitter OAuthLib community`_ or open a `GitHub issue`_ =) If you run into issues it can be helpful to enable debug logging:: import logging + import oauthlib import sys + oauthlib.set_debug(True) log = logging.getLogger('oauthlib') log.addHandler(logging.StreamHandler(sys.stdout)) log.setLevel(logging.DEBUG) diff --git a/docs/oauth2/server.rst b/docs/oauth2/server.rst index dad0aae..d9846c5 100644 --- a/docs/oauth2/server.rst +++ b/docs/oauth2/server.rst @@ -524,7 +524,10 @@ If you run into issues it can be helpful to enable debug logging. .. code-block:: python import logging + import oauthlib import sys + + oauthlib.set_debug(True) log = logging.getLogger('oauthlib') log.addHandler(logging.StreamHandler(sys.stdout)) log.setLevel(logging.DEBUG) -- cgit v1.2.1 From 71be50afdeaf99a0ba6ce5048851dcdd5620d880 Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Fri, 26 Apr 2019 15:59:57 +0200 Subject: Fix 670. AuthCode API must return the new PKCE attribute --- oauthlib/oauth2/rfc6749/grant_types/authorization_code.py | 3 +++ tests/oauth2/rfc6749/grant_types/test_authorization_code.py | 6 ++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py b/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py index 5f03d9c..9b84c4c 100644 --- a/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py +++ b/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py @@ -405,12 +405,15 @@ class AuthorizationCodeGrant(GrantTypeBase): raise errors.MissingCodeChallengeError(request=request) if request.code_challenge is not None: + request_info["code_challenge"] = request.code_challenge + # OPTIONAL, defaults to "plain" if not present in the request. if request.code_challenge_method is None: request.code_challenge_method = "plain" if request.code_challenge_method not in self._code_challenge_methods: raise errors.UnsupportedCodeChallengeMethodError(request=request) + request_info["code_challenge_method"] = request.code_challenge_method # OPTIONAL. The scope of the access request as described by Section 3.3 # https://tools.ietf.org/html/rfc6749#section-3.3 diff --git a/tests/oauth2/rfc6749/grant_types/test_authorization_code.py b/tests/oauth2/rfc6749/grant_types/test_authorization_code.py index 00e2b6d..2c9db3c 100644 --- a/tests/oauth2/rfc6749/grant_types/test_authorization_code.py +++ b/tests/oauth2/rfc6749/grant_types/test_authorization_code.py @@ -215,8 +215,10 @@ class AuthorizationCodeGrantTest(TestCase): self.mock_validator.is_pkce_required.return_value = required self.request.code_challenge = "present" _, ri = self.auth.validate_authorization_request(self.request) - self.assertIsNotNone(ri["request"].code_challenge_method) - self.assertEqual(ri["request"].code_challenge_method, "plain") + self.assertIn("code_challenge", ri) + self.assertIn("code_challenge_method", ri) + self.assertEqual(ri["code_challenge"], "present") + self.assertEqual(ri["code_challenge_method"], "plain") def test_pkce_wrong_method(self): for required in [True, False]: -- cgit v1.2.1 From 5405ca4cae31146ce2d2c6860b0c46dbbbe879c9 Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Mon, 29 Apr 2019 10:11:24 +0200 Subject: Fix docstring about return value --- oauthlib/openid/connect/core/request_validator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oauthlib/openid/connect/core/request_validator.py b/oauthlib/openid/connect/core/request_validator.py index 7ce7e17..eebab55 100644 --- a/oauthlib/openid/connect/core/request_validator.py +++ b/oauthlib/openid/connect/core/request_validator.py @@ -56,7 +56,7 @@ class RequestValidator(OAuth2RequestValidator): :param client_id: Unicode client identifier :param code: Unicode authorization code grant :param redirect_uri: Unicode absolute URI - :return: A list of scope + :return: Unicode nonce Method is used by: - Authorization Token Grant Dispatcher -- cgit v1.2.1 From 247c89e13bdd017b99f22b154e521084df53d2f0 Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Mon, 29 Apr 2019 10:12:10 +0200 Subject: Fix typo gave/have --- oauthlib/openid/connect/core/request_validator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/oauthlib/openid/connect/core/request_validator.py b/oauthlib/openid/connect/core/request_validator.py index eebab55..344fd7d 100644 --- a/oauthlib/openid/connect/core/request_validator.py +++ b/oauthlib/openid/connect/core/request_validator.py @@ -24,7 +24,7 @@ class RequestValidator(OAuth2RequestValidator): id_token in token response only based on authorization code scopes. Only code param should be sufficient to retrieve grant code from - any storage you are using, `client_id` and `redirect_uri` can gave a + any storage you are using, `client_id` and `redirect_uri` can have a blank value `""` don't forget to check it before using those values in a select query if a database is used. @@ -49,7 +49,7 @@ class RequestValidator(OAuth2RequestValidator): case-sensitive string. Only code param should be sufficient to retrieve grant code from - any storage you are using, `client_id` and `redirect_uri` can gave a + any storage you are using, `client_id` and `redirect_uri` can have a blank value `""` don't forget to check it before using those values in a select query if a database is used. -- cgit v1.2.1 From d4d3f1088dc943a83641c9e86b7a09d98f6adce8 Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Mon, 29 Apr 2019 10:20:39 +0200 Subject: Removed wrong assumption from copy/paste of get_autho.._scopes. This function should always have a good client_id and redirect_uri, because it is called after validate_token_request() --- oauthlib/openid/connect/core/request_validator.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/oauthlib/openid/connect/core/request_validator.py b/oauthlib/openid/connect/core/request_validator.py index 344fd7d..d96c9ef 100644 --- a/oauthlib/openid/connect/core/request_validator.py +++ b/oauthlib/openid/connect/core/request_validator.py @@ -49,9 +49,8 @@ class RequestValidator(OAuth2RequestValidator): case-sensitive string. Only code param should be sufficient to retrieve grant code from - any storage you are using, `client_id` and `redirect_uri` can have a - blank value `""` don't forget to check it before using those values - in a select query if a database is used. + any storage you are using. However, `client_id` and `redirect_uri` + have been validated and can be used also. :param client_id: Unicode client identifier :param code: Unicode authorization code grant -- cgit v1.2.1 From b714c05bcec2e254bbe07298f03f3def7de9179b Mon Sep 17 00:00:00 2001 From: ume Date: Wed, 1 May 2019 00:18:08 +0900 Subject: token_type should be case insensitive --- oauthlib/oauth2/rfc6749/tokens.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/oauthlib/oauth2/rfc6749/tokens.py b/oauthlib/oauth2/rfc6749/tokens.py index 7973923..3587af4 100644 --- a/oauthlib/oauth2/rfc6749/tokens.py +++ b/oauthlib/oauth2/rfc6749/tokens.py @@ -254,7 +254,7 @@ def get_token_from_header(request): if 'Authorization' in request.headers: split_header = request.headers.get('Authorization').split() - if len(split_header) == 2 and split_header[0] == 'Bearer': + if len(split_header) == 2 and split_header[0].lower() == 'bearer': token = split_header[1] else: token = request.access_token @@ -353,7 +353,7 @@ class BearerToken(TokenBase): :param request: OAuthlib request. :type request: oauthlib.common.Request """ - if request.headers.get('Authorization', '').split(' ')[0] == 'Bearer': + if request.headers.get('Authorization', '').split(' ')[0].lower() == 'bearer': return 9 elif request.access_token is not None: return 5 -- cgit v1.2.1 From 73092d039fa67a88d0989e7bf0ae7d0044a0bdc6 Mon Sep 17 00:00:00 2001 From: Josh Holmer Date: Tue, 30 Apr 2019 12:50:43 -0400 Subject: Handle null value in expires_in field in JSON handler Closes #672 --- oauthlib/oauth2/rfc6749/parameters.py | 5 ++++- tests/oauth2/rfc6749/test_parameters.py | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/oauthlib/oauth2/rfc6749/parameters.py b/oauthlib/oauth2/rfc6749/parameters.py index 6b9d630..f8d42db 100644 --- a/oauthlib/oauth2/rfc6749/parameters.py +++ b/oauthlib/oauth2/rfc6749/parameters.py @@ -419,7 +419,10 @@ def parse_token_response(body, scope=None): params['scope'] = scope_to_list(params['scope']) if 'expires_in' in params: - params['expires_at'] = time.time() + int(params['expires_in']) + if params['expires_in'] is None: + params.pop('expires_in') + else: + params['expires_at'] = time.time() + int(params['expires_in']) params = OAuth2Token(params, old_scope=scope) validate_token_parameters(params) diff --git a/tests/oauth2/rfc6749/test_parameters.py b/tests/oauth2/rfc6749/test_parameters.py index c42f516..b9fda9c 100644 --- a/tests/oauth2/rfc6749/test_parameters.py +++ b/tests/oauth2/rfc6749/test_parameters.py @@ -102,6 +102,15 @@ class ParameterTests(TestCase): ' "expires_in": 3600,' ' "refresh_token": "tGzv3JOkF0XG5Qx2TlKWIA",' ' "example_parameter": "example_value" }') + json_response_noexpire = ('{ "access_token": "2YotnFZFEjr1zCsicMWpAA",' + ' "token_type": "example",' + ' "refresh_token": "tGzv3JOkF0XG5Qx2TlKWIA",' + ' "example_parameter": "example_value"}') + json_response_expirenull = ('{ "access_token": "2YotnFZFEjr1zCsicMWpAA",' + ' "token_type": "example",' + ' "expires_in": null,' + ' "refresh_token": "tGzv3JOkF0XG5Qx2TlKWIA",' + ' "example_parameter": "example_value"}') json_custom_error = '{ "error": "incorrect_client_credentials" }' json_error = '{ "error": "access_denied" }' @@ -135,6 +144,13 @@ class ParameterTests(TestCase): 'example_parameter': 'example_value' } + json_noexpire_dict = { + 'access_token': '2YotnFZFEjr1zCsicMWpAA', + 'token_type': 'example', + 'refresh_token': 'tGzv3JOkF0XG5Qx2TlKWIA', + 'example_parameter': 'example_value' + } + json_notype_dict = { 'access_token': '2YotnFZFEjr1zCsicMWpAA', 'expires_in': 3600, @@ -209,6 +225,8 @@ class ParameterTests(TestCase): self.assertEqual(parse_token_response(self.json_response_noscope, scope=['all', 'the', 'scopes']), self.json_noscope_dict) + self.assertEqual(parse_token_response(self.json_response_noexpire), self.json_noexpire_dict) + self.assertEqual(parse_token_response(self.json_response_expirenull), self.json_noexpire_dict) scope_changes_recorded = [] def record_scope_change(sender, message, old, new): -- cgit v1.2.1 From f037c1153e7e8e22299e9169b8d765164855b246 Mon Sep 17 00:00:00 2001 From: Jordan Gardner Date: Wed, 24 Apr 2019 15:09:51 -0600 Subject: Add case-insensitive headers to oauth1 BaseEndpoint --- oauthlib/oauth1/rfc5849/endpoints/base.py | 4 ++-- tests/oauth1/rfc5849/endpoints/test_base.py | 13 ++++++++++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/oauthlib/oauth1/rfc5849/endpoints/base.py b/oauthlib/oauth1/rfc5849/endpoints/base.py index 9702939..ecf8a50 100644 --- a/oauthlib/oauth1/rfc5849/endpoints/base.py +++ b/oauthlib/oauth1/rfc5849/endpoints/base.py @@ -10,7 +10,7 @@ from __future__ import absolute_import, unicode_literals import time -from oauthlib.common import Request, generate_token +from oauthlib.common import CaseInsensitiveDict, Request, generate_token from .. import (CONTENT_TYPE_FORM_URLENCODED, SIGNATURE_HMAC, SIGNATURE_RSA, SIGNATURE_TYPE_AUTH_HEADER, SIGNATURE_TYPE_BODY, @@ -67,7 +67,7 @@ class BaseEndpoint(object): def _create_request(self, uri, http_method, body, headers): # Only include body data from x-www-form-urlencoded requests - headers = headers or {} + headers = CaseInsensitiveDict(headers or {}) if ("Content-Type" in headers and CONTENT_TYPE_FORM_URLENCODED in headers["Content-Type"]): request = Request(uri, http_method, body, headers) diff --git a/tests/oauth1/rfc5849/endpoints/test_base.py b/tests/oauth1/rfc5849/endpoints/test_base.py index 60f7860..795ddee 100644 --- a/tests/oauth1/rfc5849/endpoints/test_base.py +++ b/tests/oauth1/rfc5849/endpoints/test_base.py @@ -4,7 +4,7 @@ from re import sub from mock import MagicMock -from oauthlib.common import safe_string_equals +from oauthlib.common import CaseInsensitiveDict, safe_string_equals from oauthlib.oauth1 import Client, RequestValidator from oauthlib.oauth1.rfc5849 import (SIGNATURE_HMAC, SIGNATURE_PLAINTEXT, SIGNATURE_RSA, errors) @@ -179,6 +179,17 @@ class BaseEndpointTest(TestCase): self.assertRaises(errors.InvalidRequestError, e._check_mandatory_parameters, r) + def test_case_insensitive_headers(self): + """Ensure headers are case-insensitive""" + v = RequestValidator() + e = BaseEndpoint(v) + r = e._create_request('https://a.b', 'POST', + ('oauth_signature=a&oauth_consumer_key=b&oauth_nonce=c&' + 'oauth_version=1.0&oauth_signature_method=RSA-SHA1&' + 'oauth_timestamp=123456789a'), + URLENCODED) + self.assertIsInstance(r.headers, CaseInsensitiveDict) + def test_signature_method_validation(self): """Ensure valid signature method is used.""" -- cgit v1.2.1 From d4a3a419b1aed1a370b29785d3121f4877d4cd54 Mon Sep 17 00:00:00 2001 From: "Y.Umezaki" Date: Tue, 7 May 2019 18:53:06 +0900 Subject: Add token tests from #491 --- tests/oauth2/rfc6749/test_tokens.py | 51 +++++++++++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/tests/oauth2/rfc6749/test_tokens.py b/tests/oauth2/rfc6749/test_tokens.py index 061754f..95e8f6c 100644 --- a/tests/oauth2/rfc6749/test_tokens.py +++ b/tests/oauth2/rfc6749/test_tokens.py @@ -1,10 +1,14 @@ from __future__ import absolute_import, unicode_literals +import mock + +from oauthlib.common import Request from oauthlib.oauth2.rfc6749.tokens import ( - prepare_mac_header, - prepare_bearer_headers, + BearerToken, prepare_bearer_body, + prepare_bearer_headers, prepare_bearer_uri, + prepare_mac_header, ) from ...unittest import TestCase @@ -98,3 +102,46 @@ class TokenTest(TestCase): self.assertEqual(prepare_bearer_headers(self.token), self.bearer_headers) self.assertEqual(prepare_bearer_body(self.token), self.bearer_body) self.assertEqual(prepare_bearer_uri(self.token, uri=self.uri), self.bearer_uri) + + def test_fake_bearer_is_not_validated(self): + request_validator = mock.MagicMock() + request_validator.validate_bearer_token = self._mocked_validate_bearer_token + + for fake_header in self.fake_bearer_headers: + request = Request("/", headers=fake_header) + result = BearerToken(request_validator=request_validator).validate_request( + request + ) + + self.assertFalse(result) + + def test_header_with_multispaces_is_validated(self): + request_validator = mock.MagicMock() + request_validator.validate_bearer_token = self._mocked_validate_bearer_token + + request = Request("/", headers=self.valid_header_with_multiple_spaces) + result = BearerToken(request_validator=request_validator).validate_request( + request + ) + + self.assertTrue(result) + + def test_estimate_type_with_fake_header_returns_type_0(self): + request_validator = mock.MagicMock() + request_validator.validate_bearer_token = self._mocked_validate_bearer_token + + for fake_header in self.fake_bearer_headers: + request = Request("/", headers=fake_header) + result = BearerToken(request_validator=request_validator).estimate_type( + request + ) + + if ( + fake_header["Authorization"].count(" ") == 2 + and fake_header["Authorization"].split()[0] == "Bearer" + ): + # If we're dealing with the header containing 2 spaces, it will be recognized + # as a Bearer valid header, the token itself will be invalid by the way. + self.assertEqual(result, 9) + else: + self.assertEqual(result, 0) -- cgit v1.2.1 From 056383bf96892b7428b5de17bb2011374fe1c7bf Mon Sep 17 00:00:00 2001 From: "Y.Umezaki" Date: Tue, 7 May 2019 19:10:14 +0900 Subject: Add valid testcase --- tests/oauth2/rfc6749/test_tokens.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/oauth2/rfc6749/test_tokens.py b/tests/oauth2/rfc6749/test_tokens.py index 95e8f6c..e6f49b1 100644 --- a/tests/oauth2/rfc6749/test_tokens.py +++ b/tests/oauth2/rfc6749/test_tokens.py @@ -68,6 +68,7 @@ class TokenTest(TestCase): bearer_headers = { 'Authorization': 'Bearer vF9dft4qmT' } + valid_bearer_header_lowercase = {"Authorization": "bearer vF9dft4qmT"} fake_bearer_headers = [ {'Authorization': 'Beaver vF9dft4qmT'}, {'Authorization': 'BeavervF9dft4qmT'}, @@ -103,6 +104,26 @@ class TokenTest(TestCase): self.assertEqual(prepare_bearer_body(self.token), self.bearer_body) self.assertEqual(prepare_bearer_uri(self.token, uri=self.uri), self.bearer_uri) + def test_valid_bearer_is_validated(self): + request_validator = mock.MagicMock() + request_validator.validate_bearer_token = self._mocked_validate_bearer_token + + request = Request("/", headers=self.bearer_headers) + result = BearerToken(request_validator=request_validator).validate_request( + request + ) + self.assertTrue(result) + + def test_lowercase_bearer_is_validated(self): + request_validator = mock.MagicMock() + request_validator.validate_bearer_token = self._mocked_validate_bearer_token + + request = Request("/", headers=self.valid_bearer_header_lowercase) + result = BearerToken(request_validator=request_validator).validate_request( + request + ) + self.assertTrue(result) + def test_fake_bearer_is_not_validated(self): request_validator = mock.MagicMock() request_validator.validate_bearer_token = self._mocked_validate_bearer_token @@ -126,6 +147,13 @@ class TokenTest(TestCase): self.assertTrue(result) + def test_estimate_type(self): + request_validator = mock.MagicMock() + request_validator.validate_bearer_token = self._mocked_validate_bearer_token + request = Request("/", headers=self.bearer_headers) + result = BearerToken(request_validator=request_validator).estimate_type(request) + self.assertEqual(result, 9) + def test_estimate_type_with_fake_header_returns_type_0(self): request_validator = mock.MagicMock() request_validator.validate_bearer_token = self._mocked_validate_bearer_token -- cgit v1.2.1 From bbbcca731d5db16d7b1765070880aa54288788e9 Mon Sep 17 00:00:00 2001 From: Abhishek Patel <5524161+Abhishek8394@users.noreply.github.com> Date: Mon, 6 May 2019 22:28:23 -0700 Subject: Add validation check for presence of forbidden query parameters in OAuth2 TokenEndpoint, IntrospectionEndpoint and RevocationEndpoint --- oauthlib/oauth2/rfc6749/endpoints/base.py | 12 ++++++++++++ oauthlib/oauth2/rfc6749/endpoints/introspect.py | 1 + oauthlib/oauth2/rfc6749/endpoints/revocation.py | 1 + oauthlib/oauth2/rfc6749/endpoints/token.py | 5 ++++- 4 files changed, 18 insertions(+), 1 deletion(-) diff --git a/oauthlib/oauth2/rfc6749/endpoints/base.py b/oauthlib/oauth2/rfc6749/endpoints/base.py index c0fc726..29086e4 100644 --- a/oauthlib/oauth2/rfc6749/endpoints/base.py +++ b/oauthlib/oauth2/rfc6749/endpoints/base.py @@ -15,6 +15,8 @@ from ..errors import (FatalClientError, OAuth2Error, ServerError, TemporarilyUnavailableError, InvalidRequestError, InvalidClientError, UnsupportedTokenTypeError) +from oauthlib.common import CaseInsensitiveDict + log = logging.getLogger(__name__) @@ -23,6 +25,7 @@ class BaseEndpoint(object): def __init__(self): self._available = True self._catch_errors = False + self._blacklist_query_params = {'client_secret', 'code_verifier'} @property def available(self): @@ -62,6 +65,15 @@ class BaseEndpoint(object): request.token_type_hint not in self.supported_token_types): raise UnsupportedTokenTypeError(request=request) + def _raise_on_bad_post_request(self, request): + """Raise if invalid POST request received + """ + if request.http_method.lower() == 'post': + query_params = CaseInsensitiveDict(urldecode(request.uri_query)) + for k in self._blacklist_query_params: + if k in query_params: + raise InvalidRequestError(request=request, + description='Query parameters not allowed') def catch_errors_and_unavailability(f): @functools.wraps(f) diff --git a/oauthlib/oauth2/rfc6749/endpoints/introspect.py b/oauthlib/oauth2/rfc6749/endpoints/introspect.py index 47022fd..547e7db 100644 --- a/oauthlib/oauth2/rfc6749/endpoints/introspect.py +++ b/oauthlib/oauth2/rfc6749/endpoints/introspect.py @@ -117,6 +117,7 @@ class IntrospectEndpoint(BaseEndpoint): .. _`section 1.5`: http://tools.ietf.org/html/rfc6749#section-1.5 .. _`RFC6749`: http://tools.ietf.org/html/rfc6749 """ + self._raise_on_bad_post_request(request) self._raise_on_missing_token(request) self._raise_on_invalid_client(request) self._raise_on_unsupported_token(request) diff --git a/oauthlib/oauth2/rfc6749/endpoints/revocation.py b/oauthlib/oauth2/rfc6749/endpoints/revocation.py index fda3f30..1439491 100644 --- a/oauthlib/oauth2/rfc6749/endpoints/revocation.py +++ b/oauthlib/oauth2/rfc6749/endpoints/revocation.py @@ -121,6 +121,7 @@ class RevocationEndpoint(BaseEndpoint): .. _`Section 4.1.2`: https://tools.ietf.org/html/draft-ietf-oauth-revocation-11#section-4.1.2 .. _`RFC6749`: https://tools.ietf.org/html/rfc6749 """ + self._raise_on_bad_post_request(request) self._raise_on_missing_token(request) self._raise_on_invalid_client(request) self._raise_on_unsupported_token(request) diff --git a/oauthlib/oauth2/rfc6749/endpoints/token.py b/oauthlib/oauth2/rfc6749/endpoints/token.py index 90fb16f..223e8d0 100644 --- a/oauthlib/oauth2/rfc6749/endpoints/token.py +++ b/oauthlib/oauth2/rfc6749/endpoints/token.py @@ -91,7 +91,7 @@ class TokenEndpoint(BaseEndpoint): """Extract grant_type and route to the designated handler.""" request = Request( uri, http_method=http_method, body=body, headers=headers) - + self.validate_token_request(request) # 'scope' is an allowed Token Request param in both the "Resource Owner Password Credentials Grant" # and "Client Credentials Grant" flows # https://tools.ietf.org/html/rfc6749#section-4.3.2 @@ -115,3 +115,6 @@ class TokenEndpoint(BaseEndpoint): request.grant_type, grant_type_handler) return grant_type_handler.create_token_response( request, self.default_token_type) + + def validate_token_request(self, request): + self._raise_on_bad_post_request(request) -- cgit v1.2.1 From 047ceccf48ea7ccd4ecc6b48a8ddb6dd4a14abd6 Mon Sep 17 00:00:00 2001 From: Abhishek Patel <5524161+Abhishek8394@users.noreply.github.com> Date: Mon, 6 May 2019 23:26:29 -0700 Subject: Add tests + create a global variable for blacklisted query parameters --- oauthlib/oauth2/rfc6749/endpoints/base.py | 16 +++++++------ .../rfc6749/endpoints/test_error_responses.py | 27 ++++++++++++++++++++++ .../rfc6749/endpoints/test_introspect_endpoint.py | 16 +++++++++++++ .../rfc6749/endpoints/test_revocation_endpoint.py | 16 +++++++++++++ 4 files changed, 68 insertions(+), 7 deletions(-) diff --git a/oauthlib/oauth2/rfc6749/endpoints/base.py b/oauthlib/oauth2/rfc6749/endpoints/base.py index 29086e4..dc3204b 100644 --- a/oauthlib/oauth2/rfc6749/endpoints/base.py +++ b/oauthlib/oauth2/rfc6749/endpoints/base.py @@ -15,17 +15,18 @@ from ..errors import (FatalClientError, OAuth2Error, ServerError, TemporarilyUnavailableError, InvalidRequestError, InvalidClientError, UnsupportedTokenTypeError) -from oauthlib.common import CaseInsensitiveDict +from oauthlib.common import CaseInsensitiveDict, urldecode log = logging.getLogger(__name__) +BLACKLIST_QUERY_PARAMS = {'client_secret', 'code_verifier'} class BaseEndpoint(object): def __init__(self): self._available = True self._catch_errors = False - self._blacklist_query_params = {'client_secret', 'code_verifier'} + self._blacklist_query_params = BLACKLIST_QUERY_PARAMS @property def available(self): @@ -33,7 +34,7 @@ class BaseEndpoint(object): @available.setter def available(self, available): - self._available = available + self._available = available @property def catch_errors(self): @@ -69,11 +70,12 @@ class BaseEndpoint(object): """Raise if invalid POST request received """ if request.http_method.lower() == 'post': - query_params = CaseInsensitiveDict(urldecode(request.uri_query)) - for k in self._blacklist_query_params: - if k in query_params: + query_params = CaseInsensitiveDict(dict(urldecode(request.uri_query))) + for param in self._blacklist_query_params: + if param in query_params: raise InvalidRequestError(request=request, - description='Query parameters not allowed') + description=('"%s" is not allowed as a url query' +\ + ' parameter') % (param)) def catch_errors_and_unavailability(f): @functools.wraps(f) diff --git a/tests/oauth2/rfc6749/endpoints/test_error_responses.py b/tests/oauth2/rfc6749/endpoints/test_error_responses.py index a249cb1..4a288ad 100644 --- a/tests/oauth2/rfc6749/endpoints/test_error_responses.py +++ b/tests/oauth2/rfc6749/endpoints/test_error_responses.py @@ -6,10 +6,12 @@ import json import mock +from oauthlib.common import urlencode from oauthlib.oauth2 import (BackendApplicationServer, LegacyApplicationServer, MobileApplicationServer, RequestValidator, WebApplicationServer) from oauthlib.oauth2.rfc6749 import errors +from oauthlib.oauth2.rfc6749.endpoints.base import BLACKLIST_QUERY_PARAMS from ....unittest import TestCase @@ -437,3 +439,28 @@ class ErrorResponseTest(TestCase): _, body, _ = self.backend.create_token_response('https://i.b/token', body='grant_type=bar') self.assertEqual('unsupported_grant_type', json.loads(body)['error']) + + def test_invalid_post_request(self): + self.validator.authenticate_client.side_effect = self.set_client + for param in BLACKLIST_QUERY_PARAMS: + uri = 'https://i/b/token?' + urlencode([(param, 'secret')]) + _, body, s = self.web.create_introspect_response(uri, + body='grant_type=access_token&code=123') + self.assertEqual(json.loads(body)['error'], 'invalid_request') + self.assertIn(param, json.loads(body)['error_description']) + self.assertIn('not allowed', json.loads(body)['error_description']) + self.assertEqual(s, 400) + + _, body, s = self.legacy.create_introspect_response(uri, + body='grant_type=access_token&code=123') + self.assertEqual(json.loads(body)['error'], 'invalid_request') + self.assertIn(param, json.loads(body)['error_description']) + self.assertIn('not allowed', json.loads(body)['error_description']) + self.assertEqual(s, 400) + + _, body, s = self.backend.create_introspect_response(uri, + body='grant_type=access_token&code=123') + self.assertEqual(json.loads(body)['error'], 'invalid_request') + self.assertIn(param, json.loads(body)['error_description']) + self.assertIn('not allowed', json.loads(body)['error_description']) + self.assertEqual(s, 400) diff --git a/tests/oauth2/rfc6749/endpoints/test_introspect_endpoint.py b/tests/oauth2/rfc6749/endpoints/test_introspect_endpoint.py index b9bf76a..234a4ef 100644 --- a/tests/oauth2/rfc6749/endpoints/test_introspect_endpoint.py +++ b/tests/oauth2/rfc6749/endpoints/test_introspect_endpoint.py @@ -7,6 +7,7 @@ from mock import MagicMock from oauthlib.common import urlencode from oauthlib.oauth2 import RequestValidator, IntrospectEndpoint +from oauthlib.oauth2.rfc6749.endpoints.base import BLACKLIST_QUERY_PARAMS from ....unittest import TestCase @@ -139,3 +140,18 @@ class IntrospectEndpointTest(TestCase): self.assertEqual(h, self.resp_h) self.assertEqual(loads(b)['error'], 'invalid_request') self.assertEqual(s, 400) + + def test_introspect_bad_post_request(self): + endpoint = IntrospectEndpoint(self.validator, + supported_token_types=['access_token']) + for param in BLACKLIST_QUERY_PARAMS: + uri = 'http://some.endpoint?' + urlencode([(param, 'secret')]) + body = urlencode([('token', 'foo'), + ('token_type_hint', 'access_token')]) + h, b, s = endpoint.create_introspect_response(uri, + headers=self.headers, body=body) + self.assertEqual(h, self.resp_h) + self.assertEqual(loads(b)['error'], 'invalid_request') + self.assertIn(param, loads(b)['error_description']) + self.assertIn('not allowed', loads(b)['error_description']) + self.assertEqual(s, 400) diff --git a/tests/oauth2/rfc6749/endpoints/test_revocation_endpoint.py b/tests/oauth2/rfc6749/endpoints/test_revocation_endpoint.py index 2a24177..e89c3bd 100644 --- a/tests/oauth2/rfc6749/endpoints/test_revocation_endpoint.py +++ b/tests/oauth2/rfc6749/endpoints/test_revocation_endpoint.py @@ -7,6 +7,7 @@ from mock import MagicMock from oauthlib.common import urlencode from oauthlib.oauth2 import RequestValidator, RevocationEndpoint +from oauthlib.oauth2.rfc6749.endpoints.base import BLACKLIST_QUERY_PARAMS from ....unittest import TestCase @@ -120,3 +121,18 @@ class RevocationEndpointTest(TestCase): self.assertEqual(h, self.resp_h) self.assertEqual(loads(b)['error'], 'invalid_request') self.assertEqual(s, 400) + + def test_revoke_bad_post_request(self): + endpoint = RevocationEndpoint(self.validator, + supported_token_types=['access_token']) + for param in BLACKLIST_QUERY_PARAMS: + uri = 'http://some.endpoint?' + urlencode([(param, 'secret')]) + body = urlencode([('token', 'foo'), + ('token_type_hint', 'access_token')]) + h, b, s = endpoint.create_revocation_response(uri, + headers=self.headers, body=body) + self.assertEqual(h, self.resp_h) + self.assertEqual(loads(b)['error'], 'invalid_request') + self.assertIn(param, loads(b)['error_description']) + self.assertIn('not allowed', loads(b)['error_description']) + self.assertEqual(s, 400) -- cgit v1.2.1 From ee06f0f3349d7fd656d35a2eef40ee18fb74e303 Mon Sep 17 00:00:00 2001 From: Abhishek Patel <5524161+Abhishek8394@users.noreply.github.com> Date: Sun, 12 May 2019 20:35:00 -0700 Subject: Ban all query parameters on Intropspection, Token and Revocation endpopoint --- oauthlib/oauth2/rfc6749/endpoints/base.py | 12 ++++-------- tests/oauth2/rfc6749/endpoints/test_error_responses.py | 12 ++++-------- tests/oauth2/rfc6749/endpoints/test_introspect_endpoint.py | 11 +++++------ tests/oauth2/rfc6749/endpoints/test_revocation_endpoint.py | 6 ++---- 4 files changed, 15 insertions(+), 26 deletions(-) diff --git a/oauthlib/oauth2/rfc6749/endpoints/base.py b/oauthlib/oauth2/rfc6749/endpoints/base.py index dc3204b..c99c22d 100644 --- a/oauthlib/oauth2/rfc6749/endpoints/base.py +++ b/oauthlib/oauth2/rfc6749/endpoints/base.py @@ -19,14 +19,12 @@ from oauthlib.common import CaseInsensitiveDict, urldecode log = logging.getLogger(__name__) -BLACKLIST_QUERY_PARAMS = {'client_secret', 'code_verifier'} class BaseEndpoint(object): def __init__(self): self._available = True self._catch_errors = False - self._blacklist_query_params = BLACKLIST_QUERY_PARAMS @property def available(self): @@ -70,12 +68,10 @@ class BaseEndpoint(object): """Raise if invalid POST request received """ if request.http_method.lower() == 'post': - query_params = CaseInsensitiveDict(dict(urldecode(request.uri_query))) - for param in self._blacklist_query_params: - if param in query_params: - raise InvalidRequestError(request=request, - description=('"%s" is not allowed as a url query' +\ - ' parameter') % (param)) + query_params = request.uri_query or "" + if query_params: + raise InvalidRequestError(request=request, + description=('URL query parameters are not allowed')) def catch_errors_and_unavailability(f): @functools.wraps(f) diff --git a/tests/oauth2/rfc6749/endpoints/test_error_responses.py b/tests/oauth2/rfc6749/endpoints/test_error_responses.py index 4a288ad..2b87032 100644 --- a/tests/oauth2/rfc6749/endpoints/test_error_responses.py +++ b/tests/oauth2/rfc6749/endpoints/test_error_responses.py @@ -11,7 +11,6 @@ from oauthlib.oauth2 import (BackendApplicationServer, LegacyApplicationServer, MobileApplicationServer, RequestValidator, WebApplicationServer) from oauthlib.oauth2.rfc6749 import errors -from oauthlib.oauth2.rfc6749.endpoints.base import BLACKLIST_QUERY_PARAMS from ....unittest import TestCase @@ -442,25 +441,22 @@ class ErrorResponseTest(TestCase): def test_invalid_post_request(self): self.validator.authenticate_client.side_effect = self.set_client - for param in BLACKLIST_QUERY_PARAMS: + for param in ['token', 'secret', 'code', 'foo']: uri = 'https://i/b/token?' + urlencode([(param, 'secret')]) _, body, s = self.web.create_introspect_response(uri, body='grant_type=access_token&code=123') self.assertEqual(json.loads(body)['error'], 'invalid_request') - self.assertIn(param, json.loads(body)['error_description']) - self.assertIn('not allowed', json.loads(body)['error_description']) + self.assertIn('query parameters are not allowed', json.loads(body)['error_description']) self.assertEqual(s, 400) _, body, s = self.legacy.create_introspect_response(uri, body='grant_type=access_token&code=123') self.assertEqual(json.loads(body)['error'], 'invalid_request') - self.assertIn(param, json.loads(body)['error_description']) - self.assertIn('not allowed', json.loads(body)['error_description']) + self.assertIn('query parameters are not allowed', json.loads(body)['error_description']) self.assertEqual(s, 400) _, body, s = self.backend.create_introspect_response(uri, body='grant_type=access_token&code=123') self.assertEqual(json.loads(body)['error'], 'invalid_request') - self.assertIn(param, json.loads(body)['error_description']) - self.assertIn('not allowed', json.loads(body)['error_description']) + self.assertIn('query parameters are not allowed', json.loads(body)['error_description']) self.assertEqual(s, 400) diff --git a/tests/oauth2/rfc6749/endpoints/test_introspect_endpoint.py b/tests/oauth2/rfc6749/endpoints/test_introspect_endpoint.py index 234a4ef..a34c970 100644 --- a/tests/oauth2/rfc6749/endpoints/test_introspect_endpoint.py +++ b/tests/oauth2/rfc6749/endpoints/test_introspect_endpoint.py @@ -7,7 +7,6 @@ from mock import MagicMock from oauthlib.common import urlencode from oauthlib.oauth2 import RequestValidator, IntrospectEndpoint -from oauthlib.oauth2.rfc6749.endpoints.base import BLACKLIST_QUERY_PARAMS from ....unittest import TestCase @@ -144,14 +143,14 @@ class IntrospectEndpointTest(TestCase): def test_introspect_bad_post_request(self): endpoint = IntrospectEndpoint(self.validator, supported_token_types=['access_token']) - for param in BLACKLIST_QUERY_PARAMS: + for param in ['token', 'secret', 'code', 'foo']: uri = 'http://some.endpoint?' + urlencode([(param, 'secret')]) body = urlencode([('token', 'foo'), ('token_type_hint', 'access_token')]) - h, b, s = endpoint.create_introspect_response(uri, - headers=self.headers, body=body) + h, b, s = endpoint.create_introspect_response( + uri, + headers=self.headers, body=body) self.assertEqual(h, self.resp_h) self.assertEqual(loads(b)['error'], 'invalid_request') - self.assertIn(param, loads(b)['error_description']) - self.assertIn('not allowed', loads(b)['error_description']) + self.assertIn('query parameters are not allowed', loads(b)['error_description']) self.assertEqual(s, 400) diff --git a/tests/oauth2/rfc6749/endpoints/test_revocation_endpoint.py b/tests/oauth2/rfc6749/endpoints/test_revocation_endpoint.py index e89c3bd..c73a1ef 100644 --- a/tests/oauth2/rfc6749/endpoints/test_revocation_endpoint.py +++ b/tests/oauth2/rfc6749/endpoints/test_revocation_endpoint.py @@ -7,7 +7,6 @@ from mock import MagicMock from oauthlib.common import urlencode from oauthlib.oauth2 import RequestValidator, RevocationEndpoint -from oauthlib.oauth2.rfc6749.endpoints.base import BLACKLIST_QUERY_PARAMS from ....unittest import TestCase @@ -125,7 +124,7 @@ class RevocationEndpointTest(TestCase): def test_revoke_bad_post_request(self): endpoint = RevocationEndpoint(self.validator, supported_token_types=['access_token']) - for param in BLACKLIST_QUERY_PARAMS: + for param in ['token', 'secret', 'code', 'foo']: uri = 'http://some.endpoint?' + urlencode([(param, 'secret')]) body = urlencode([('token', 'foo'), ('token_type_hint', 'access_token')]) @@ -133,6 +132,5 @@ class RevocationEndpointTest(TestCase): headers=self.headers, body=body) self.assertEqual(h, self.resp_h) self.assertEqual(loads(b)['error'], 'invalid_request') - self.assertIn(param, loads(b)['error_description']) - self.assertIn('not allowed', loads(b)['error_description']) + self.assertIn('query parameters are not allowed', loads(b)['error_description']) self.assertEqual(s, 400) -- cgit v1.2.1 From 056948ac7c14a435d0b65dd27692fe2494bc3743 Mon Sep 17 00:00:00 2001 From: Abhishek Patel <5524161+Abhishek8394@users.noreply.github.com> Date: Tue, 14 May 2019 00:36:10 -0700 Subject: Enforce POST HTTP method on TokenEndpoint, IntrospectEndpoint and RevocationEndpoint - Add validation checks for HTTP method in TokenEndpoint, IntrospectEndpoint and RevocationEndpoint. - CHANGE DEFAULT HTTP method for TokenEndpoint from 'GET' to 'POST'. - Add tests + Fix an old test in . It used to send query params to TokenEndpoint which is not allowed anymore. Fixed it so payload is sent as POST body. --- oauthlib/oauth2/rfc6749/endpoints/base.py | 21 ++++++- oauthlib/oauth2/rfc6749/endpoints/introspect.py | 2 + oauthlib/oauth2/rfc6749/endpoints/revocation.py | 2 + oauthlib/oauth2/rfc6749/endpoints/token.py | 5 +- .../oauth2/rfc6749/endpoints/test_base_endpoint.py | 2 +- .../rfc6749/endpoints/test_error_responses.py | 66 ++++++++++++++++------ .../rfc6749/endpoints/test_introspect_endpoint.py | 15 +++++ .../rfc6749/endpoints/test_revocation_endpoint.py | 15 +++++ 8 files changed, 107 insertions(+), 21 deletions(-) diff --git a/oauthlib/oauth2/rfc6749/endpoints/base.py b/oauthlib/oauth2/rfc6749/endpoints/base.py index c99c22d..e39232f 100644 --- a/oauthlib/oauth2/rfc6749/endpoints/base.py +++ b/oauthlib/oauth2/rfc6749/endpoints/base.py @@ -25,6 +25,18 @@ class BaseEndpoint(object): def __init__(self): self._available = True self._catch_errors = False + self._valid_request_methods = None + + @property + def valid_request_methods(self): + return self._valid_request_methods + + @valid_request_methods.setter + def valid_request_methods(self, valid_request_methods): + if valid_request_methods is not None: + valid_request_methods = [x.upper() for x in valid_request_methods] + self._valid_request_methods = valid_request_methods + @property def available(self): @@ -64,10 +76,17 @@ class BaseEndpoint(object): request.token_type_hint not in self.supported_token_types): raise UnsupportedTokenTypeError(request=request) + def _raise_on_bad_method(self, request): + if self.valid_request_methods is None: + raise ValueError('Configure "valid_request_methods" property first') + if request.http_method.upper() not in self.valid_request_methods: + raise InvalidRequestError(request=request, + description=('Unsupported request method %s' % request.http_method.upper())) + def _raise_on_bad_post_request(self, request): """Raise if invalid POST request received """ - if request.http_method.lower() == 'post': + if request.http_method.upper() == 'POST': query_params = request.uri_query or "" if query_params: raise InvalidRequestError(request=request, diff --git a/oauthlib/oauth2/rfc6749/endpoints/introspect.py b/oauthlib/oauth2/rfc6749/endpoints/introspect.py index 547e7db..4accbdc 100644 --- a/oauthlib/oauth2/rfc6749/endpoints/introspect.py +++ b/oauthlib/oauth2/rfc6749/endpoints/introspect.py @@ -39,6 +39,7 @@ class IntrospectEndpoint(BaseEndpoint): """ valid_token_types = ('access_token', 'refresh_token') + valid_request_methods = ('POST',) def __init__(self, request_validator, supported_token_types=None): BaseEndpoint.__init__(self) @@ -117,6 +118,7 @@ class IntrospectEndpoint(BaseEndpoint): .. _`section 1.5`: http://tools.ietf.org/html/rfc6749#section-1.5 .. _`RFC6749`: http://tools.ietf.org/html/rfc6749 """ + self._raise_on_bad_method(request) self._raise_on_bad_post_request(request) self._raise_on_missing_token(request) self._raise_on_invalid_client(request) diff --git a/oauthlib/oauth2/rfc6749/endpoints/revocation.py b/oauthlib/oauth2/rfc6749/endpoints/revocation.py index 1439491..1fabd03 100644 --- a/oauthlib/oauth2/rfc6749/endpoints/revocation.py +++ b/oauthlib/oauth2/rfc6749/endpoints/revocation.py @@ -28,6 +28,7 @@ class RevocationEndpoint(BaseEndpoint): """ valid_token_types = ('access_token', 'refresh_token') + valid_request_methods = ('POST',) def __init__(self, request_validator, supported_token_types=None, enable_jsonp=False): @@ -121,6 +122,7 @@ class RevocationEndpoint(BaseEndpoint): .. _`Section 4.1.2`: https://tools.ietf.org/html/draft-ietf-oauth-revocation-11#section-4.1.2 .. _`RFC6749`: https://tools.ietf.org/html/rfc6749 """ + self._raise_on_bad_method(request) self._raise_on_bad_post_request(request) self._raise_on_missing_token(request) self._raise_on_invalid_client(request) diff --git a/oauthlib/oauth2/rfc6749/endpoints/token.py b/oauthlib/oauth2/rfc6749/endpoints/token.py index 223e8d0..bc87e9b 100644 --- a/oauthlib/oauth2/rfc6749/endpoints/token.py +++ b/oauthlib/oauth2/rfc6749/endpoints/token.py @@ -62,6 +62,8 @@ class TokenEndpoint(BaseEndpoint): .. _`Appendix B`: https://tools.ietf.org/html/rfc6749#appendix-B """ + valid_request_methods = ('POST',) + def __init__(self, default_grant_type, default_token_type, grant_types): BaseEndpoint.__init__(self) self._grant_types = grant_types @@ -85,7 +87,7 @@ class TokenEndpoint(BaseEndpoint): return self._default_token_type @catch_errors_and_unavailability - def create_token_response(self, uri, http_method='GET', body=None, + def create_token_response(self, uri, http_method='POST', body=None, headers=None, credentials=None, grant_type_for_scope=None, claims=None): """Extract grant_type and route to the designated handler.""" @@ -117,4 +119,5 @@ class TokenEndpoint(BaseEndpoint): request, self.default_token_type) def validate_token_request(self, request): + self._raise_on_bad_method(request) self._raise_on_bad_post_request(request) diff --git a/tests/oauth2/rfc6749/endpoints/test_base_endpoint.py b/tests/oauth2/rfc6749/endpoints/test_base_endpoint.py index 4f78d9b..bf04a42 100644 --- a/tests/oauth2/rfc6749/endpoints/test_base_endpoint.py +++ b/tests/oauth2/rfc6749/endpoints/test_base_endpoint.py @@ -25,7 +25,7 @@ class BaseEndpointTest(TestCase): server = Server(validator) server.catch_errors = True h, b, s = server.create_token_response( - 'https://example.com?grant_type=authorization_code&code=abc' + 'https://example.com', body='grant_type=authorization_code&code=abc' ) self.assertIn("server_error", b) self.assertEqual(s, 500) diff --git a/tests/oauth2/rfc6749/endpoints/test_error_responses.py b/tests/oauth2/rfc6749/endpoints/test_error_responses.py index 2b87032..2479836 100644 --- a/tests/oauth2/rfc6749/endpoints/test_error_responses.py +++ b/tests/oauth2/rfc6749/endpoints/test_error_responses.py @@ -11,7 +11,6 @@ from oauthlib.oauth2 import (BackendApplicationServer, LegacyApplicationServer, MobileApplicationServer, RequestValidator, WebApplicationServer) from oauthlib.oauth2.rfc6749 import errors - from ....unittest import TestCase @@ -439,24 +438,55 @@ class ErrorResponseTest(TestCase): body='grant_type=bar') self.assertEqual('unsupported_grant_type', json.loads(body)['error']) + def test_invalid_request_method(self): + test_methods = ['GET', 'pUt', 'dEleTe', 'paTcH'] + test_methods = test_methods + [x.lower() for x in test_methods] + [x.upper() for x in test_methods] + for method in test_methods: + self.validator.authenticate_client.side_effect = self.set_client + + uri = "http://i/b/token/" + try: + _, body, s = self.web.create_token_response(uri, + body='grant_type=access_token&code=123', http_method=method) + self.fail('This should have failed with InvalidRequestError') + except errors.InvalidRequestError as ire: + self.assertIn('Unsupported request method', ire.description) + + try: + _, body, s = self.legacy.create_token_response(uri, + body='grant_type=access_token&code=123', http_method=method) + self.fail('This should have failed with InvalidRequestError') + except errors.InvalidRequestError as ire: + self.assertIn('Unsupported request method', ire.description) + + try: + _, body, s = self.backend.create_token_response(uri, + body='grant_type=access_token&code=123', http_method=method) + self.fail('This should have failed with InvalidRequestError') + except errors.InvalidRequestError as ire: + self.assertIn('Unsupported request method', ire.description) + def test_invalid_post_request(self): self.validator.authenticate_client.side_effect = self.set_client for param in ['token', 'secret', 'code', 'foo']: uri = 'https://i/b/token?' + urlencode([(param, 'secret')]) - _, body, s = self.web.create_introspect_response(uri, - body='grant_type=access_token&code=123') - self.assertEqual(json.loads(body)['error'], 'invalid_request') - self.assertIn('query parameters are not allowed', json.loads(body)['error_description']) - self.assertEqual(s, 400) - - _, body, s = self.legacy.create_introspect_response(uri, - body='grant_type=access_token&code=123') - self.assertEqual(json.loads(body)['error'], 'invalid_request') - self.assertIn('query parameters are not allowed', json.loads(body)['error_description']) - self.assertEqual(s, 400) - - _, body, s = self.backend.create_introspect_response(uri, - body='grant_type=access_token&code=123') - self.assertEqual(json.loads(body)['error'], 'invalid_request') - self.assertIn('query parameters are not allowed', json.loads(body)['error_description']) - self.assertEqual(s, 400) + try: + _, body, s = self.web.create_token_response(uri, + body='grant_type=access_token&code=123') + self.fail('This should have failed with InvalidRequestError') + except errors.InvalidRequestError as ire: + self.assertIn('URL query parameters are not allowed', ire.description) + + try: + _, body, s = self.legacy.create_token_response(uri, + body='grant_type=access_token&code=123') + self.fail('This should have failed with InvalidRequestError') + except errors.InvalidRequestError as ire: + self.assertIn('URL query parameters are not allowed', ire.description) + + try: + _, body, s = self.backend.create_token_response(uri, + body='grant_type=access_token&code=123') + self.fail('This should have failed with InvalidRequestError') + except errors.InvalidRequestError as ire: + self.assertIn('URL query parameters are not allowed', ire.description) diff --git a/tests/oauth2/rfc6749/endpoints/test_introspect_endpoint.py b/tests/oauth2/rfc6749/endpoints/test_introspect_endpoint.py index a34c970..ae3deae 100644 --- a/tests/oauth2/rfc6749/endpoints/test_introspect_endpoint.py +++ b/tests/oauth2/rfc6749/endpoints/test_introspect_endpoint.py @@ -140,6 +140,21 @@ class IntrospectEndpointTest(TestCase): self.assertEqual(loads(b)['error'], 'invalid_request') self.assertEqual(s, 400) + def test_introspect_invalid_request_method(self): + endpoint = IntrospectEndpoint(self.validator, + supported_token_types=['access_token']) + test_methods = ['GET', 'pUt', 'dEleTe', 'paTcH'] + test_methods = test_methods + [x.lower() for x in test_methods] + [x.upper() for x in test_methods] + for method in test_methods: + body = urlencode([('token', 'foo'), + ('token_type_hint', 'refresh_token')]) + h, b, s = endpoint.create_introspect_response(self.uri, + http_method = method, headers=self.headers, body=body) + self.assertEqual(h, self.resp_h) + self.assertEqual(loads(b)['error'], 'invalid_request') + self.assertIn('Unsupported request method', loads(b)['error_description']) + self.assertEqual(s, 400) + def test_introspect_bad_post_request(self): endpoint = IntrospectEndpoint(self.validator, supported_token_types=['access_token']) diff --git a/tests/oauth2/rfc6749/endpoints/test_revocation_endpoint.py b/tests/oauth2/rfc6749/endpoints/test_revocation_endpoint.py index c73a1ef..17be3a5 100644 --- a/tests/oauth2/rfc6749/endpoints/test_revocation_endpoint.py +++ b/tests/oauth2/rfc6749/endpoints/test_revocation_endpoint.py @@ -121,6 +121,21 @@ class RevocationEndpointTest(TestCase): self.assertEqual(loads(b)['error'], 'invalid_request') self.assertEqual(s, 400) + def test_revoke_invalid_request_method(self): + endpoint = RevocationEndpoint(self.validator, + supported_token_types=['access_token']) + test_methods = ['GET', 'pUt', 'dEleTe', 'paTcH'] + test_methods = test_methods + [x.lower() for x in test_methods] + [x.upper() for x in test_methods] + for method in test_methods: + body = urlencode([('token', 'foo'), + ('token_type_hint', 'refresh_token')]) + h, b, s = endpoint.create_revocation_response(self.uri, + http_method = method, headers=self.headers, body=body) + self.assertEqual(h, self.resp_h) + self.assertEqual(loads(b)['error'], 'invalid_request') + self.assertIn('Unsupported request method', loads(b)['error_description']) + self.assertEqual(s, 400) + def test_revoke_bad_post_request(self): endpoint = RevocationEndpoint(self.validator, supported_token_types=['access_token']) -- cgit v1.2.1 From 16a5e630a1d1eeb2511e1049ee2291b33f57ab25 Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Mon, 13 May 2019 14:53:41 +0200 Subject: Updated bandit baseline after review --- bandit.json | 1184 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 1179 insertions(+), 5 deletions(-) diff --git a/bandit.json b/bandit.json index 02e15a8..4d3bfe1 100644 --- a/bandit.json +++ b/bandit.json @@ -1,21 +1,1034 @@ { "errors": [], - "generated_at": "2018-12-13T10:39:37Z", + "generated_at": "2019-05-13T12:51:49Z", + "metrics": { + "_totals": { + "CONFIDENCE.HIGH": 3.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 10.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 12.0, + "SEVERITY.MEDIUM": 1.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 8338, + "nosec": 0 + }, + "oauthlib/__init__.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 25, + "nosec": 0 + }, + "oauthlib/common.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 337, + "nosec": 0 + }, + "oauthlib/oauth1/__init__.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 16, + "nosec": 0 + }, + "oauthlib/oauth1/rfc5849/__init__.py": { + "CONFIDENCE.HIGH": 1.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 1.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 230, + "nosec": 0 + }, + "oauthlib/oauth1/rfc5849/endpoints/__init__.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 8, + "nosec": 0 + }, + "oauthlib/oauth1/rfc5849/endpoints/access_token.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 152, + "nosec": 0 + }, + "oauthlib/oauth1/rfc5849/endpoints/authorization.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 135, + "nosec": 0 + }, + "oauthlib/oauth1/rfc5849/endpoints/base.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 142, + "nosec": 0 + }, + "oauthlib/oauth1/rfc5849/endpoints/pre_configured.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 10, + "nosec": 0 + }, + "oauthlib/oauth1/rfc5849/endpoints/request_token.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 141, + "nosec": 0 + }, + "oauthlib/oauth1/rfc5849/endpoints/resource.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 97, + "nosec": 0 + }, + "oauthlib/oauth1/rfc5849/endpoints/signature_only.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 53, + "nosec": 0 + }, + "oauthlib/oauth1/rfc5849/errors.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 58, + "nosec": 0 + }, + "oauthlib/oauth1/rfc5849/parameters.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 75, + "nosec": 0 + }, + "oauthlib/oauth1/rfc5849/request_validator.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 630, + "nosec": 0 + }, + "oauthlib/oauth1/rfc5849/signature.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 379, + "nosec": 0 + }, + "oauthlib/oauth1/rfc5849/utils.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 61, + "nosec": 0 + }, + "oauthlib/oauth2/__init__.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 33, + "nosec": 0 + }, + "oauthlib/oauth2/rfc6749/__init__.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 14, + "nosec": 0 + }, + "oauthlib/oauth2/rfc6749/clients/__init__.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 13, + "nosec": 0 + }, + "oauthlib/oauth2/rfc6749/clients/backend_application.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 56, + "nosec": 0 + }, + "oauthlib/oauth2/rfc6749/clients/base.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 3.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 3.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 384, + "nosec": 0 + }, + "oauthlib/oauth2/rfc6749/clients/legacy_application.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 67, + "nosec": 0 + }, + "oauthlib/oauth2/rfc6749/clients/mobile_application.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 140, + "nosec": 0 + }, + "oauthlib/oauth2/rfc6749/clients/service_application.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 144, + "nosec": 0 + }, + "oauthlib/oauth2/rfc6749/clients/web_application.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 165, + "nosec": 0 + }, + "oauthlib/oauth2/rfc6749/endpoints/__init__.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 18, + "nosec": 0 + }, + "oauthlib/oauth2/rfc6749/endpoints/authorization.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 85, + "nosec": 0 + }, + "oauthlib/oauth2/rfc6749/endpoints/base.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 71, + "nosec": 0 + }, + "oauthlib/oauth2/rfc6749/endpoints/introspect.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 98, + "nosec": 0 + }, + "oauthlib/oauth2/rfc6749/endpoints/metadata.py": { + "CONFIDENCE.HIGH": 2.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 2.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 182, + "nosec": 0 + }, + "oauthlib/oauth2/rfc6749/endpoints/pre_configured.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 5.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 5.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 189, + "nosec": 0 + }, + "oauthlib/oauth2/rfc6749/endpoints/resource.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 65, + "nosec": 0 + }, + "oauthlib/oauth2/rfc6749/endpoints/revocation.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 96, + "nosec": 0 + }, + "oauthlib/oauth2/rfc6749/endpoints/token.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 76, + "nosec": 0 + }, + "oauthlib/oauth2/rfc6749/errors.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 311, + "nosec": 0 + }, + "oauthlib/oauth2/rfc6749/grant_types/__init__.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 10, + "nosec": 0 + }, + "oauthlib/oauth2/rfc6749/grant_types/authorization_code.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 389, + "nosec": 0 + }, + "oauthlib/oauth2/rfc6749/grant_types/base.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 199, + "nosec": 0 + }, + "oauthlib/oauth2/rfc6749/grant_types/client_credentials.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 96, + "nosec": 0 + }, + "oauthlib/oauth2/rfc6749/grant_types/implicit.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 259, + "nosec": 0 + }, + "oauthlib/oauth2/rfc6749/grant_types/refresh_token.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 102, + "nosec": 0 + }, + "oauthlib/oauth2/rfc6749/grant_types/resource_owner_password_credentials.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 156, + "nosec": 0 + }, + "oauthlib/oauth2/rfc6749/parameters.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 1.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 1.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 335, + "nosec": 0 + }, + "oauthlib/oauth2/rfc6749/request_validator.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 504, + "nosec": 0 + }, + "oauthlib/oauth2/rfc6749/tokens.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 277, + "nosec": 0 + }, + "oauthlib/oauth2/rfc6749/utils.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 70, + "nosec": 0 + }, + "oauthlib/openid/__init__.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 8, + "nosec": 0 + }, + "oauthlib/openid/connect/__init__.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 0, + "nosec": 0 + }, + "oauthlib/openid/connect/core/__init__.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 0, + "nosec": 0 + }, + "oauthlib/openid/connect/core/endpoints/__init__.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 9, + "nosec": 0 + }, + "oauthlib/openid/connect/core/endpoints/pre_configured.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 1.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 1.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 93, + "nosec": 0 + }, + "oauthlib/openid/connect/core/endpoints/userinfo.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 83, + "nosec": 0 + }, + "oauthlib/openid/connect/core/exceptions.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 117, + "nosec": 0 + }, + "oauthlib/openid/connect/core/grant_types/__init__.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 15, + "nosec": 0 + }, + "oauthlib/openid/connect/core/grant_types/authorization_code.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 32, + "nosec": 0 + }, + "oauthlib/openid/connect/core/grant_types/base.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 234, + "nosec": 0 + }, + "oauthlib/openid/connect/core/grant_types/dispatchers.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 66, + "nosec": 0 + }, + "oauthlib/openid/connect/core/grant_types/exceptions.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 26, + "nosec": 0 + }, + "oauthlib/openid/connect/core/grant_types/hybrid.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 38, + "nosec": 0 + }, + "oauthlib/openid/connect/core/grant_types/implicit.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 35, + "nosec": 0 + }, + "oauthlib/openid/connect/core/request_validator.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 235, + "nosec": 0 + }, + "oauthlib/openid/connect/core/tokens.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 42, + "nosec": 0 + }, + "oauthlib/signals.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 32, + "nosec": 0 + }, + "oauthlib/tokens/__init__.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 0, + "nosec": 0 + }, + "oauthlib/tokens/access_token.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 13, + "nosec": 0 + }, + "oauthlib/tokens/base.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 8, + "nosec": 0 + }, + "oauthlib/tokens/id_token.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 6, + "nosec": 0 + }, + "oauthlib/uri_validate.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 93, + "nosec": 0 + } + }, "results": [ { - "code": "182 if request.body is not None and content_type_eligible:\n183 params.append(('oauth_body_hash', base64.b64encode(hashlib.sha1(request.body.encode('utf-8')).digest()).decode('utf-8')))\n184 \n", + "code": "183 if request.body is not None and content_type_eligible:\n184 params.append(('oauth_body_hash', base64.b64encode(hashlib.sha1(request.body.encode('utf-8')).digest()).decode('utf-8')))\n185 \n", "filename": "oauthlib/oauth1/rfc5849/__init__.py", "issue_confidence": "HIGH", "issue_severity": "MEDIUM", "issue_text": "Use of insecure MD2, MD4, MD5, or SHA1 hash function.", - "line_number": 183, + "line_number": 184, "line_range": [ - 183 + 184 ], "more_info": "https://bandit.readthedocs.io/en/latest/blacklists/blacklist_calls.html#b303-md5", "test_id": "B303", "test_name": "blacklist" }, + { + "code": "49 \"\"\"\n50 refresh_token_key = 'refresh_token'\n51 \n52 def __init__(self, client_id,\n", + "filename": "oauthlib/oauth2/rfc6749/clients/base.py", + "issue_confidence": "MEDIUM", + "issue_severity": "LOW", + "issue_text": "Possible hardcoded password: 'refresh_token'", + "line_number": 50, + "line_range": [ + 50, + 51 + ], + "more_info": "https://bandit.readthedocs.io/en/latest/plugins/b105_hardcoded_password_string.html", + "test_id": "B105", + "test_name": "hardcoded_password_string" + }, + { + "code": "51 \n52 def __init__(self, client_id,\n53 default_token_placement=AUTH_HEADER,\n54 token_type='Bearer',\n55 access_token=None,\n56 refresh_token=None,\n57 mac_key=None,\n58 mac_algorithm=None,\n59 token=None,\n60 scope=None,\n61 state=None,\n62 redirect_url=None,\n63 state_generator=generate_token,\n64 **kwargs):\n65 \"\"\"Initialize a client with commonly used attributes.\n66 \n67 :param client_id: Client identifier given by the OAuth provider upon\n68 registration.\n69 \n70 :param default_token_placement: Tokens can be supplied in the Authorization\n71 header (default), the URL query component (``query``) or the request\n72 body (``body``).\n73 \n74 :param token_type: OAuth 2 token type. Defaults to Bearer. Change this\n75 if you specify the ``access_token`` parameter and know it is of a\n76 different token type, such as a MAC, JWT or SAML token. Can\n77 also be supplied as ``token_type`` inside the ``token`` dict parameter.\n78 \n79 :param access_token: An access token (string) used to authenticate\n80 requests to protected resources. Can also be supplied inside the\n81 ``token`` dict parameter.\n82 \n83 :param refresh_token: A refresh token (string) used to refresh expired\n84 tokens. Can also be supplied inside the ``token`` dict parameter.\n85 \n86 :param mac_key: Encryption key used with MAC tokens.\n87 \n88 :param mac_algorithm: Hashing algorithm for MAC tokens.\n89 \n90 :param token: A dict of token attributes such as ``access_token``,\n91 ``token_type`` and ``expires_at``.\n92 \n93 :param scope: A list of default scopes to request authorization for.\n94 \n95 :param state: A CSRF protection string used during authorization.\n96 \n97 :param redirect_url: The redirection endpoint on the client side to which\n98 the user returns after authorization.\n99 \n100 :param state_generator: A no argument state generation callable. Defaults\n101 to :py:meth:`oauthlib.common.generate_token`.\n102 \"\"\"\n103 \n104 self.client_id = client_id\n105 self.default_token_placement = default_token_placement\n106 self.token_type = token_type\n107 self.access_token = access_token\n108 self.refresh_token = refresh_token\n109 self.mac_key = mac_key\n110 self.mac_algorithm = mac_algorithm\n111 self.token = token or {}\n112 self.scope = scope\n113 self.state_generator = state_generator\n114 self.state = state\n115 self.redirect_url = redirect_url\n116 self.code = None\n117 self.expires_in = None\n118 self._expires_at = None\n119 self.populate_token_attributes(self.token)\n120 \n121 @property\n", + "filename": "oauthlib/oauth2/rfc6749/clients/base.py", + "issue_confidence": "MEDIUM", + "issue_severity": "LOW", + "issue_text": "Possible hardcoded password: 'Bearer'", + "line_number": 52, + "line_range": [ + 52, + 53, + 54, + 55, + 56, + 57, + 58, + 59, + 60, + 61, + 62, + 63, + 64, + 65, + 66, + 67, + 68, + 69, + 70, + 71, + 72, + 73, + 74, + 75, + 76, + 77, + 78, + 79, + 80, + 81, + 82, + 83, + 84, + 85, + 86, + 87, + 88, + 89, + 90, + 91, + 92, + 93, + 94, + 95, + 96, + 97, + 98, + 99, + 100, + 101, + 102, + 103, + 104, + 105, + 106, + 107, + 108, + 109, + 110, + 111, + 112, + 113, + 114, + 115, + 116, + 117, + 118, + 119, + 120 + ], + "more_info": "https://bandit.readthedocs.io/en/latest/plugins/b107_hardcoded_password_default.html", + "test_id": "B107", + "test_name": "hardcoded_password_default" + }, + { + "code": "313 \n314 def prepare_token_revocation_request(self, revocation_url, token,\n315 token_type_hint=\"access_token\", body='', callback=None, **kwargs):\n316 \"\"\"Prepare a token revocation request.\n317 \n318 :param revocation_url: Provider token revocation endpoint URL.\n319 \n320 :param token: The access or refresh token to be revoked (string).\n321 \n322 :param token_type_hint: ``\"access_token\"`` (default) or\n323 ``\"refresh_token\"``. This is optional and if you wish to not pass it you\n324 must provide ``token_type_hint=None``.\n325 \n326 :param body:\n327 \n328 :param callback: A jsonp callback such as ``package.callback`` to be invoked\n329 upon receiving the response. Not that it should not include a () suffix.\n330 \n331 :param kwargs: Additional parameters to included in the request.\n332 \n333 :returns: The prepared request tuple with (url, headers, body).\n334 \n335 Note that JSONP request may use GET requests as the parameters will\n336 be added to the request URL query as opposed to the request body.\n337 \n338 An example of a revocation request\n339 \n340 .. code-block: http\n341 \n342 POST /revoke HTTP/1.1\n343 Host: server.example.com\n344 Content-Type: application/x-www-form-urlencoded\n345 Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW\n346 \n347 token=45ghiukldjahdnhzdauz&token_type_hint=refresh_token\n348 \n349 An example of a jsonp revocation request\n350 \n351 .. code-block: http\n352 \n353 GET /revoke?token=agabcdefddddafdd&callback=package.myCallback HTTP/1.1\n354 Host: server.example.com\n355 Content-Type: application/x-www-form-urlencoded\n356 Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW\n357 \n358 and an error response\n359 \n360 .. code-block: http\n361 \n362 package.myCallback({\"error\":\"unsupported_token_type\"});\n363 \n364 Note that these requests usually require client credentials, client_id in\n365 the case for public clients and provider specific authentication\n366 credentials for confidential clients.\n367 \"\"\"\n368 if not is_secure_transport(revocation_url):\n369 raise InsecureTransportError()\n370 \n371 return prepare_token_revocation_request(revocation_url, token,\n372 token_type_hint=token_type_hint, body=body, callback=callback,\n373 **kwargs)\n374 \n375 def parse_request_body_response(self, body, scope=None, **kwargs):\n", + "filename": "oauthlib/oauth2/rfc6749/clients/base.py", + "issue_confidence": "MEDIUM", + "issue_severity": "LOW", + "issue_text": "Possible hardcoded password: 'access_token'", + "line_number": 314, + "line_range": [ + 314, + 315, + 316, + 317, + 318, + 319, + 320, + 321, + 322, + 323, + 324, + 325, + 326, + 327, + 328, + 329, + 330, + 331, + 332, + 333, + 334, + 335, + 336, + 337, + 338, + 339, + 340, + 341, + 342, + 343, + 344, + 345, + 346, + 347, + 348, + 349, + 350, + 351, + 352, + 353, + 354, + 355, + 356, + 357, + 358, + 359, + 360, + 361, + 362, + 363, + 364, + 365, + 366, + 367, + 368, + 369, + 370, + 371, + 372, + 373, + 374 + ], + "more_info": "https://bandit.readthedocs.io/en/latest/plugins/b107_hardcoded_password_default.html", + "test_id": "B107", + "test_name": "hardcoded_password_default" + }, { "code": "45 def __init__(self, endpoints, claims={}, raise_errors=True):\n46 assert isinstance(claims, dict)\n47 for endpoint in endpoints:\n", "filename": "oauthlib/oauth2/rfc6749/endpoints/metadata.py", @@ -43,6 +1056,167 @@ "more_info": "https://bandit.readthedocs.io/en/latest/plugins/b101_assert_used.html", "test_id": "B101", "test_name": "assert_used" + }, + { + "code": "70 default_token_type=bearer)\n71 ResourceEndpoint.__init__(self, default_token='Bearer',\n72 token_types={'Bearer': bearer})\n73 RevocationEndpoint.__init__(self, request_validator)\n", + "filename": "oauthlib/oauth2/rfc6749/endpoints/pre_configured.py", + "issue_confidence": "MEDIUM", + "issue_severity": "LOW", + "issue_text": "Possible hardcoded password: 'Bearer'", + "line_number": 71, + "line_range": [ + 71, + 72 + ], + "more_info": "https://bandit.readthedocs.io/en/latest/plugins/b106_hardcoded_password_funcarg.html", + "test_id": "B106", + "test_name": "hardcoded_password_funcarg" + }, + { + "code": "109 default_token_type=bearer)\n110 ResourceEndpoint.__init__(self, default_token='Bearer',\n111 token_types={'Bearer': bearer})\n112 RevocationEndpoint.__init__(self, request_validator)\n", + "filename": "oauthlib/oauth2/rfc6749/endpoints/pre_configured.py", + "issue_confidence": "MEDIUM", + "issue_severity": "LOW", + "issue_text": "Possible hardcoded password: 'Bearer'", + "line_number": 110, + "line_range": [ + 110, + 111 + ], + "more_info": "https://bandit.readthedocs.io/en/latest/plugins/b106_hardcoded_password_funcarg.html", + "test_id": "B106", + "test_name": "hardcoded_password_funcarg" + }, + { + "code": "142 default_token_type=bearer)\n143 ResourceEndpoint.__init__(self, default_token='Bearer',\n144 token_types={'Bearer': bearer})\n145 RevocationEndpoint.__init__(self, request_validator,\n", + "filename": "oauthlib/oauth2/rfc6749/endpoints/pre_configured.py", + "issue_confidence": "MEDIUM", + "issue_severity": "LOW", + "issue_text": "Possible hardcoded password: 'Bearer'", + "line_number": 143, + "line_range": [ + 143, + 144 + ], + "more_info": "https://bandit.readthedocs.io/en/latest/plugins/b106_hardcoded_password_funcarg.html", + "test_id": "B106", + "test_name": "hardcoded_password_funcarg" + }, + { + "code": "181 default_token_type=bearer)\n182 ResourceEndpoint.__init__(self, default_token='Bearer',\n183 token_types={'Bearer': bearer})\n184 RevocationEndpoint.__init__(self, request_validator)\n", + "filename": "oauthlib/oauth2/rfc6749/endpoints/pre_configured.py", + "issue_confidence": "MEDIUM", + "issue_severity": "LOW", + "issue_text": "Possible hardcoded password: 'Bearer'", + "line_number": 182, + "line_range": [ + 182, + 183 + ], + "more_info": "https://bandit.readthedocs.io/en/latest/plugins/b106_hardcoded_password_funcarg.html", + "test_id": "B106", + "test_name": "hardcoded_password_funcarg" + }, + { + "code": "214 default_token_type=bearer)\n215 ResourceEndpoint.__init__(self, default_token='Bearer',\n216 token_types={'Bearer': bearer})\n217 RevocationEndpoint.__init__(self, request_validator,\n", + "filename": "oauthlib/oauth2/rfc6749/endpoints/pre_configured.py", + "issue_confidence": "MEDIUM", + "issue_severity": "LOW", + "issue_text": "Possible hardcoded password: 'Bearer'", + "line_number": 215, + "line_range": [ + 215, + 216 + ], + "more_info": "https://bandit.readthedocs.io/en/latest/plugins/b106_hardcoded_password_funcarg.html", + "test_id": "B106", + "test_name": "hardcoded_password_funcarg" + }, + { + "code": "164 \n165 def prepare_token_revocation_request(url, token, token_type_hint=\"access_token\",\n166 callback=None, body='', **kwargs):\n167 \"\"\"Prepare a token revocation request.\n168 \n169 The client constructs the request by including the following parameters\n170 using the \"application/x-www-form-urlencoded\" format in the HTTP request\n171 entity-body:\n172 \n173 :param token: REQUIRED. The token that the client wants to get revoked.\n174 \n175 :param token_type_hint: OPTIONAL. A hint about the type of the token\n176 submitted for revocation. Clients MAY pass this\n177 parameter in order to help the authorization server\n178 to optimize the token lookup. If the server is\n179 unable to locate the token using the given hint, it\n180 MUST extend its search across all of its supported\n181 token types. An authorization server MAY ignore\n182 this parameter, particularly if it is able to detect\n183 the token type automatically.\n184 \n185 This specification defines two values for `token_type_hint`:\n186 \n187 * access_token: An access token as defined in [RFC6749],\n188 `Section 1.4`_\n189 \n190 * refresh_token: A refresh token as defined in [RFC6749],\n191 `Section 1.5`_\n192 \n193 Specific implementations, profiles, and extensions of this\n194 specification MAY define other values for this parameter using the\n195 registry defined in `Section 4.1.2`_.\n196 \n197 .. _`Section 1.4`: https://tools.ietf.org/html/rfc6749#section-1.4\n198 .. _`Section 1.5`: https://tools.ietf.org/html/rfc6749#section-1.5\n199 .. _`Section 4.1.2`: https://tools.ietf.org/html/rfc7009#section-4.1.2\n200 \n201 \"\"\"\n202 if not is_secure_transport(url):\n203 raise InsecureTransportError()\n204 \n205 params = [('token', token)]\n206 \n207 if token_type_hint:\n208 params.append(('token_type_hint', token_type_hint))\n209 \n210 for k in kwargs:\n211 if kwargs[k]:\n212 params.append((unicode_type(k), kwargs[k]))\n213 \n214 headers = {'Content-Type': 'application/x-www-form-urlencoded'}\n215 \n216 if callback:\n217 params.append(('callback', callback))\n218 return add_params_to_uri(url, params), headers, body\n219 else:\n220 return url, headers, add_params_to_qs(body, params)\n221 \n222 \n223 def parse_authorization_code_response(uri, state=None):\n", + "filename": "oauthlib/oauth2/rfc6749/parameters.py", + "issue_confidence": "MEDIUM", + "issue_severity": "LOW", + "issue_text": "Possible hardcoded password: 'access_token'", + "line_number": 165, + "line_range": [ + 165, + 166, + 167, + 168, + 169, + 170, + 171, + 172, + 173, + 174, + 175, + 176, + 177, + 178, + 179, + 180, + 181, + 182, + 183, + 184, + 185, + 186, + 187, + 188, + 189, + 190, + 191, + 192, + 193, + 194, + 195, + 196, + 197, + 198, + 199, + 200, + 201, + 202, + 203, + 204, + 205, + 206, + 207, + 208, + 209, + 210, + 211, + 212, + 213, + 214, + 215, + 216, + 217, + 218, + 219, + 220, + 221, + 222 + ], + "more_info": "https://bandit.readthedocs.io/en/latest/plugins/b107_hardcoded_password_default.html", + "test_id": "B107", + "test_name": "hardcoded_password_default" + }, + { + "code": "104 default_token_type=bearer)\n105 ResourceEndpoint.__init__(self, default_token='Bearer',\n106 token_types={'Bearer': bearer, 'JWT': jwt})\n107 RevocationEndpoint.__init__(self, request_validator)\n", + "filename": "oauthlib/openid/connect/core/endpoints/pre_configured.py", + "issue_confidence": "MEDIUM", + "issue_severity": "LOW", + "issue_text": "Possible hardcoded password: 'Bearer'", + "line_number": 105, + "line_range": [ + 105, + 106 + ], + "more_info": "https://bandit.readthedocs.io/en/latest/plugins/b106_hardcoded_password_funcarg.html", + "test_id": "B106", + "test_name": "hardcoded_password_funcarg" } ] -} +} \ No newline at end of file -- cgit v1.2.1 From f09f687ebf02cb05ffdf3378e8207fb0083b3049 Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Mon, 13 May 2019 15:08:18 +0200 Subject: Downgrade python to match with Travis --- tox.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/tox.ini b/tox.ini index 1cac71c..9cd2a9f 100644 --- a/tox.ini +++ b/tox.ini @@ -28,6 +28,7 @@ commands= echo setup.py/long description is syntaxly correct [testenv:bandit] +basepython=python2.7 skipsdist=True deps=bandit commands=bandit -b bandit.json -r oauthlib/ -- cgit v1.2.1 From 09bcb01032a21a4bfa0c478ea8ae66ec8ace957a Mon Sep 17 00:00:00 2001 From: Mark Gregson Date: Thu, 6 Jun 2019 14:08:18 +1000 Subject: Check for authorization response errors --- oauthlib/oauth2/rfc6749/parameters.py | 9 ++++++--- tests/oauth2/rfc6749/test_parameters.py | 9 ++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/oauthlib/oauth2/rfc6749/parameters.py b/oauthlib/oauth2/rfc6749/parameters.py index 6b9d630..df724ee 100644 --- a/oauthlib/oauth2/rfc6749/parameters.py +++ b/oauthlib/oauth2/rfc6749/parameters.py @@ -264,12 +264,15 @@ def parse_authorization_code_response(uri, state=None): query = urlparse.urlparse(uri).query params = dict(urlparse.parse_qsl(query)) - if not 'code' in params: - raise MissingCodeError("Missing code parameter in response.") - if state and params.get('state', None) != state: raise MismatchingStateError() + if 'error' in params: + raise_from_error(params.get('error'), params) + + if not 'code' in params: + raise MissingCodeError("Missing code parameter in response.") + return params diff --git a/tests/oauth2/rfc6749/test_parameters.py b/tests/oauth2/rfc6749/test_parameters.py index c42f516..0d293cc 100644 --- a/tests/oauth2/rfc6749/test_parameters.py +++ b/tests/oauth2/rfc6749/test_parameters.py @@ -73,7 +73,8 @@ class ParameterTests(TestCase): error_nocode = 'https://client.example.com/cb?state=xyz' error_nostate = 'https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA' error_wrongstate = 'https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=abc' - error_response = 'https://client.example.com/cb?error=access_denied&state=xyz' + error_denied = 'https://client.example.com/cb?error=access_denied&state=xyz' + error_invalid = 'https://client.example.com/cb?error=invalid_request&state=xyz' implicit_base = 'https://example.com/cb#access_token=2YotnFZFEjr1zCsicMWpAA&scope=abc&' implicit_response = implicit_base + 'state={0}&token_type=example&expires_in=3600'.format(state) @@ -180,8 +181,10 @@ class ParameterTests(TestCase): self.assertRaises(MissingCodeError, parse_authorization_code_response, self.error_nocode) - self.assertRaises(MissingCodeError, parse_authorization_code_response, - self.error_response) + self.assertRaises(AccessDeniedError, parse_authorization_code_response, + self.error_denied) + self.assertRaises(InvalidRequestFatalError, parse_authorization_code_response, + self.error_invalid) self.assertRaises(MismatchingStateError, parse_authorization_code_response, self.error_nostate, state=self.state) self.assertRaises(MismatchingStateError, parse_authorization_code_response, -- cgit v1.2.1 From d2dcb0f5bb247c9e48fa876e3c99ff3298b3a4c0 Mon Sep 17 00:00:00 2001 From: Omer Katz Date: Tue, 11 Jun 2019 11:45:06 +0300 Subject: Create FUNDING.yml --- .github/FUNDING.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..9d4faec --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,12 @@ +# These are supported funding model platforms + +github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +custom: # Replace with a single custom sponsorship URL -- cgit v1.2.1 From 34166c5182871b53b2761dd8fae00aaec611a7d3 Mon Sep 17 00:00:00 2001 From: qporest Date: Tue, 2 Jul 2019 15:31:51 -0400 Subject: Fix BackendApplicationClient.prepare_request_body Currently, if no `scope` is passed to `prepare_request_body`, None will be passed on to `prepare_token_request`, even if BackendApplicationClient was initialized with `scope`. --- oauthlib/oauth2/rfc6749/clients/backend_application.py | 1 + 1 file changed, 1 insertion(+) diff --git a/oauthlib/oauth2/rfc6749/clients/backend_application.py b/oauthlib/oauth2/rfc6749/clients/backend_application.py index 2483e56..5737814 100644 --- a/oauthlib/oauth2/rfc6749/clients/backend_application.py +++ b/oauthlib/oauth2/rfc6749/clients/backend_application.py @@ -71,5 +71,6 @@ class BackendApplicationClient(Client): """ kwargs['client_id'] = self.client_id kwargs['include_client_id'] = include_client_id + scope = self.scope if scope is None else scope return prepare_token_request(self.grant_type, body=body, scope=scope, **kwargs) -- cgit v1.2.1 From 19d111df1c55456c0c85b6ba8051d3a9b3ac3733 Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Wed, 3 Jul 2019 18:28:34 +0200 Subject: Error in timestamp comparison --- tests/openid/connect/core/grant_types/test_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/openid/connect/core/grant_types/test_base.py b/tests/openid/connect/core/grant_types/test_base.py index 76e017f..d506b7e 100644 --- a/tests/openid/connect/core/grant_types/test_base.py +++ b/tests/openid/connect/core/grant_types/test_base.py @@ -68,7 +68,7 @@ class IDTokenTest(TestCase): self.assertEqual(token["id_token"], "eyJ.body.signature") id_token = self.mock_validator.finalize_id_token.call_args[0][0] self.assertEqual(id_token['aud'], 'abcdef') - self.assertGreaterEqual(id_token['iat'], int(time.time())) + self.assertGreaterEqual(int(time.time()), id_token['iat']) def test_finalize_id_token_with_nonce(self): token = self.grant.add_id_token(self.token, "token_handler_mock", self.request, "my_nonce") -- cgit v1.2.1