From a8eaf06eabedba10b5bc928a831e71682c9e88ee Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Mon, 13 May 2019 11:27:19 +0200 Subject: Removed duplicated code for oauth2.BaseEndpoint --- oauthlib/oauth2/rfc6749/__init__.py | 50 ++----------------------------------- 1 file changed, 2 insertions(+), 48 deletions(-) diff --git a/oauthlib/oauth2/rfc6749/__init__.py b/oauthlib/oauth2/rfc6749/__init__.py index aff0ed8..1a4128c 100644 --- a/oauthlib/oauth2/rfc6749/__init__.py +++ b/oauthlib/oauth2/rfc6749/__init__.py @@ -11,56 +11,10 @@ from __future__ import absolute_import, unicode_literals import functools import logging +from .endpoints.base import BaseEndpoint +from .endpoints.base import catch_errors_and_unavailability from .errors import TemporarilyUnavailableError, ServerError from .errors import FatalClientError, OAuth2Error log = logging.getLogger(__name__) - - -class BaseEndpoint(object): - - def __init__(self): - self._available = True - self._catch_errors = False - - @property - def available(self): - return self._available - - @available.setter - def available(self, available): - self._available = available - - @property - def catch_errors(self): - return self._catch_errors - - @catch_errors.setter - def catch_errors(self, catch_errors): - self._catch_errors = catch_errors - - -def catch_errors_and_unavailability(f): - @functools.wraps(f) - def wrapper(endpoint, uri, *args, **kwargs): - if not endpoint.available: - e = TemporarilyUnavailableError() - log.info('Endpoint unavailable, ignoring request %s.' % uri) - return {}, e.json, 503 - - if endpoint.catch_errors: - try: - return f(endpoint, uri, *args, **kwargs) - except OAuth2Error: - raise - except FatalClientError: - raise - except Exception as e: - error = ServerError() - log.warning( - 'Exception caught while processing request, %s.' % e) - return {}, error.json, 500 - else: - return f(endpoint, uri, *args, **kwargs) - return wrapper -- cgit v1.2.1 From 0821ab7467fec977cc8dbe357435d03861c16027 Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Mon, 13 May 2019 11:36:20 +0200 Subject: Add UserInfoEndpoint to the OIDC Provider support. --- docs/oauth2/oidc/validator.rst | 1 + oauthlib/openid/__init__.py | 1 + oauthlib/openid/connect/core/endpoints/__init__.py | 1 + .../connect/core/endpoints/pre_configured.py | 4 +- oauthlib/openid/connect/core/endpoints/userinfo.py | 102 +++++++++++++++++++++ oauthlib/openid/connect/core/request_validator.py | 42 +++++++++ .../core/endpoints/test_userinfo_endpoint.py | 70 ++++++++++++++ 7 files changed, 220 insertions(+), 1 deletion(-) create mode 100644 oauthlib/openid/connect/core/endpoints/userinfo.py create mode 100644 tests/openid/connect/core/endpoints/test_userinfo_endpoint.py diff --git a/docs/oauth2/oidc/validator.rst b/docs/oauth2/oidc/validator.rst index 7a6f574..17f5825 100644 --- a/docs/oauth2/oidc/validator.rst +++ b/docs/oauth2/oidc/validator.rst @@ -20,6 +20,7 @@ Into from oauthlib.openid import RequestValidator Then, you have to implement the new RequestValidator methods as shown below. +Note that a new UserInfo endpoint is defined and need a new controller into your webserver. RequestValidator Extension ---------------------------------------------------- diff --git a/oauthlib/openid/__init__.py b/oauthlib/openid/__init__.py index 7f1a876..8157c29 100644 --- a/oauthlib/openid/__init__.py +++ b/oauthlib/openid/__init__.py @@ -7,4 +7,5 @@ oauthlib.openid from __future__ import absolute_import, unicode_literals from .connect.core.endpoints import Server +from .connect.core.endpoints import UserInfoEndpoint from .connect.core.request_validator import RequestValidator diff --git a/oauthlib/openid/connect/core/endpoints/__init__.py b/oauthlib/openid/connect/core/endpoints/__init__.py index 719f883..528841f 100644 --- a/oauthlib/openid/connect/core/endpoints/__init__.py +++ b/oauthlib/openid/connect/core/endpoints/__init__.py @@ -9,3 +9,4 @@ for consuming and providing OpenID Connect from __future__ import absolute_import, unicode_literals from .pre_configured import Server +from .userinfo import UserInfoEndpoint diff --git a/oauthlib/openid/connect/core/endpoints/pre_configured.py b/oauthlib/openid/connect/core/endpoints/pre_configured.py index 6367847..fde2739 100644 --- a/oauthlib/openid/connect/core/endpoints/pre_configured.py +++ b/oauthlib/openid/connect/core/endpoints/pre_configured.py @@ -34,10 +34,11 @@ from ..grant_types.dispatchers import ( AuthorizationTokenGrantDispatcher ) from ..tokens import JWTToken +from .userinfo import UserInfoEndpoint class Server(AuthorizationEndpoint, IntrospectEndpoint, TokenEndpoint, - ResourceEndpoint, RevocationEndpoint): + ResourceEndpoint, RevocationEndpoint, UserInfoEndpoint): """An all-in-one endpoint featuring all four major grant types.""" @@ -105,3 +106,4 @@ class Server(AuthorizationEndpoint, IntrospectEndpoint, TokenEndpoint, token_types={'Bearer': bearer, 'JWT': jwt}) RevocationEndpoint.__init__(self, request_validator) IntrospectEndpoint.__init__(self, request_validator) + UserInfoEndpoint.__init__(self, request_validator) diff --git a/oauthlib/openid/connect/core/endpoints/userinfo.py b/oauthlib/openid/connect/core/endpoints/userinfo.py new file mode 100644 index 0000000..7a39f76 --- /dev/null +++ b/oauthlib/openid/connect/core/endpoints/userinfo.py @@ -0,0 +1,102 @@ +""" +oauthlib.openid.connect.core.endpoints.userinfo +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This module is an implementation of userinfo endpoint. +""" +from __future__ import absolute_import, unicode_literals + +import json +import logging + +from oauthlib.common import Request +from oauthlib.common import unicode_type +from oauthlib.oauth2.rfc6749.endpoints.base import BaseEndpoint +from oauthlib.oauth2.rfc6749.endpoints.base import catch_errors_and_unavailability +from oauthlib.oauth2.rfc6749.tokens import BearerToken +from oauthlib.oauth2.rfc6749 import errors + + +log = logging.getLogger(__name__) + + +class UserInfoEndpoint(BaseEndpoint): + """Authorizes access to userinfo resource. + """ + def __init__(self, request_validator): + self.bearer = BearerToken(request_validator, None, None, None) + self.request_validator = request_validator + BaseEndpoint.__init__(self) + + @catch_errors_and_unavailability + def create_userinfo_response(self, uri, http_method='GET', body=None, headers=None): + """Validate BearerToken and return userinfo from RequestValidator + + The UserInfo Endpoint MUST return a + content-type header to indicate which format is being returned. The + content-type of the HTTP response MUST be application/json if the + response body is a text JSON object; the response body SHOULD be encoded + using UTF-8. + """ + request = Request(uri, http_method, body, headers) + request.scopes = ["openid"] + self.validate_userinfo_request(request) + + claims = self.request_validator.get_userinfo_claims(request) + if claims is None: + log.error('Userinfo MUST have claims for %r.', request) + raise errors.ServerError(status_code=500) + + if isinstance(claims, dict): + resp_headers = { + 'Content-Type': 'application/json' + } + if "sub" not in claims: + log.error('Userinfo MUST have "sub" for %r.', request) + raise errors.ServerError(status_code=500) + body = json.dumps(claims) + elif isinstance(claims, unicode_type): + resp_headers = { + 'Content-Type': 'application/jwt' + } + body = claims + else: + log.error('Userinfo return unknown response for %r.', request) + raise errors.ServerError(status_code=500) + log.debug('Userinfo access valid for %r.', request) + return resp_headers, body, 200 + + def validate_userinfo_request(self, request): + """Ensure the request is valid. + + 5.3.1. UserInfo Request + The Client sends the UserInfo Request using either HTTP GET or HTTP + POST. The Access Token obtained from an OpenID Connect Authentication + Request MUST be sent as a Bearer Token, per Section 2 of OAuth 2.0 + Bearer Token Usage [RFC6750]. + + It is RECOMMENDED that the request use the HTTP GET method and the + Access Token be sent using the Authorization header field. + + The following is a non-normative example of a UserInfo Request: + + GET /userinfo HTTP/1.1 + Host: server.example.com + Authorization: Bearer SlAV32hkKG + + 5.3.3. UserInfo Error Response + When an error condition occurs, the UserInfo Endpoint returns an Error + Response as defined in Section 3 of OAuth 2.0 Bearer Token Usage + [RFC6750]. (HTTP errors unrelated to RFC 6750 are returned to the User + Agent using the appropriate HTTP status code.) + + The following is a non-normative example of a UserInfo Error Response: + + HTTP/1.1 401 Unauthorized + WWW-Authenticate: Bearer error="invalid_token", + error_description="The Access Token expired" + """ + if not self.bearer.validate_request(request): + raise errors.InvalidTokenError() + if "openid" not in request.scopes: + raise errors.InsufficientScopeError() diff --git a/oauthlib/openid/connect/core/request_validator.py b/oauthlib/openid/connect/core/request_validator.py index d96c9ef..e853d39 100644 --- a/oauthlib/openid/connect/core/request_validator.py +++ b/oauthlib/openid/connect/core/request_validator.py @@ -265,3 +265,45 @@ class RequestValidator(OAuth2RequestValidator): - OpenIDConnectHybrid """ raise NotImplementedError('Subclasses must implement this method.') + + def get_userinfo_claims(self, request): + """Return the UserInfo claims in JSON or Signed or Encrypted. + + The UserInfo Claims MUST be returned as the members of a JSON object + unless a signed or encrypted response was requested during Client + Registration. The Claims defined in Section 5.1 can be returned, as can + additional Claims not specified there. + + For privacy reasons, OpenID Providers MAY elect to not return values for + some requested Claims. + + If a Claim is not returned, that Claim Name SHOULD be omitted from the + JSON object representing the Claims; it SHOULD NOT be present with a + null or empty string value. + + The sub (subject) Claim MUST always be returned in the UserInfo + Response. + + Upon receipt of the UserInfo Request, the UserInfo Endpoint MUST return + the JSON Serialization of the UserInfo Response as in Section 13.3 in + the HTTP response body unless a different format was specified during + Registration [OpenID.Registration]. + + If the UserInfo Response is signed and/or encrypted, then the Claims are + returned in a JWT and the content-type MUST be application/jwt. The + response MAY be encrypted without also being signed. If both signing and + encryption are requested, the response MUST be signed then encrypted, + with the result being a Nested JWT, as defined in [JWT]. + + If signed, the UserInfo Response SHOULD contain the Claims iss (issuer) + and aud (audience) as members. The iss value SHOULD be the OP's Issuer + Identifier URL. The aud value SHOULD be or include the RP's Client ID + value. + + :param request: OAuthlib request. + :type request: oauthlib.common.Request + :rtype: Claims as a dict OR JWT/JWS/JWE as a string + + Method is used by: + UserInfoEndpoint + """ diff --git a/tests/openid/connect/core/endpoints/test_userinfo_endpoint.py b/tests/openid/connect/core/endpoints/test_userinfo_endpoint.py new file mode 100644 index 0000000..4593d79 --- /dev/null +++ b/tests/openid/connect/core/endpoints/test_userinfo_endpoint.py @@ -0,0 +1,70 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import, unicode_literals + +import mock +import json + +from oauthlib.openid import RequestValidator +from oauthlib.openid import UserInfoEndpoint +from oauthlib.oauth2.rfc6749 import errors + +from tests.unittest import TestCase + + +def set_scopes_valid(token, scopes, request): + request.scopes = ["openid", "bar"] + return True + + +class UserInfoEndpointTest(TestCase): + def setUp(self): + self.claims = { + "sub": "john", + "fruit": "banana" + } + # Can't use MagicMock/wraps below. + # Triggers error when endpoint copies to self.bearer.request_validator + self.validator = RequestValidator() + self.validator.validate_bearer_token = mock.Mock() + self.validator.validate_bearer_token.side_effect = set_scopes_valid + self.validator.get_userinfo_claims = mock.Mock() + self.validator.get_userinfo_claims.return_value = self.claims + self.endpoint = UserInfoEndpoint(self.validator) + + self.uri = 'should_not_matter' + self.headers = { + 'Authorization': 'Bearer eyJxx' + } + + def test_userinfo_no_auth(self): + self.endpoint.create_userinfo_response(self.uri) + + def test_userinfo_wrong_auth(self): + self.headers['Authorization'] = 'Basic foifoifoi' + self.endpoint.create_userinfo_response(self.uri, headers=self.headers) + + def test_userinfo_token_expired(self): + self.validator.validate_bearer_token.return_value = False + self.endpoint.create_userinfo_response(self.uri, headers=self.headers) + + def test_userinfo_token_no_openid_scope(self): + def set_scopes_invalid(token, scopes, request): + request.scopes = ["foo", "bar"] + return True + self.validator.validate_bearer_token.side_effect = set_scopes_invalid + with self.assertRaises(errors.InsufficientScopeError) as context: + self.endpoint.create_userinfo_response(self.uri) + + def test_userinfo_json_response(self): + h, b, s = self.endpoint.create_userinfo_response(self.uri) + self.assertEqual(s, 200) + body_json = json.loads(b) + self.assertEqual(self.claims, body_json) + self.assertEqual("application/json", h['Content-Type']) + + def test_userinfo_jwt_response(self): + self.validator.get_userinfo_claims.return_value = "eyJzzzzz" + h, b, s = self.endpoint.create_userinfo_response(self.uri) + self.assertEqual(s, 200) + self.assertEqual(b, "eyJzzzzz") + self.assertEqual("application/jwt", h['Content-Type']) -- cgit v1.2.1 From f25b9133646d0120f6221e9d7cbf16e747dd6aa7 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 acf62ce7c66f87a26f47c460a9934bede6126061 Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Mon, 13 May 2019 14:54:03 +0200 Subject: Force bandit python version to be sure no conflict with others --- tox.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/tox.ini b/tox.ini index 1cac71c..d242791 100644 --- a/tox.ini +++ b/tox.ini @@ -28,6 +28,7 @@ commands= echo setup.py/long description is syntaxly correct [testenv:bandit] +basepython=python3.6 skipsdist=True deps=bandit commands=bandit -b bandit.json -r oauthlib/ -- cgit v1.2.1 From 588abb50010d434c0de5ad9c479d666b7b6ab0bd 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 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index d242791..9cd2a9f 100644 --- a/tox.ini +++ b/tox.ini @@ -28,7 +28,7 @@ commands= echo setup.py/long description is syntaxly correct [testenv:bandit] -basepython=python3.6 +basepython=python2.7 skipsdist=True deps=bandit commands=bandit -b bandit.json -r oauthlib/ -- cgit v1.2.1 From cb6af4b44da264613250cb3d99be420dbeb8e268 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 | 8 +++++ .../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, 72 insertions(+), 49 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 2cc0dd3..9e0efda 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,14 @@ 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 + 3.0.0 (2019-01-01) ------------------ OAuth2.0 Provider - outstanding Features 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 66d7c0035a8d33109ffaec9c8a620dd40255f99d 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 c17a4a25a71b3b342ad522427c23038f417fb22e 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 87972cc4346a4c7b698fa1f026c76ddf717dec6b 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 ff6844524b3fe28e7122a8177fb7c2e0993d5162 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 9e0efda..dbd8c3f 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,7 +1,7 @@ Changelog ========= -TBD +3.0.2 (TBD) ------------------ * #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 0d423ac7af419b69530cd05ab786527d941b4ffb 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 ad7b15428bde9eaa55bbc0ca0ce338342740a7c9 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 c3c7824..89401ab 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 @@ -148,3 +149,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 2d9a89c23d0e0088ac84606e28be51f59f9fa12c 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 3ccaeb128156a2ab4519f177bf8811c89513862a 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 1ef4209f71d6abe20e71a65a4b9d1141205b087e 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 4e945e9bcc847e77b7da37279853b853c4579cf7 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 5b2bfd5d5de6525fbe12bbbf0dbe77a7640a6c09 Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Thu, 4 Jul 2019 11:19:38 +0200 Subject: Update for 3.0.2 --- CHANGELOG.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index dbd8c3f..c5346eb 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,9 +1,12 @@ Changelog ========= -3.0.2 (TBD) +3.0.2 (2019-07-04) ------------------ +* #650: Fixed space encoding in base string URI used in the signature base string. * #652: Fixed OIDC /token response which wrongly returned "&state=None" +* #654: Doc: The value `state` must not be stored by the AS, only returned in /authorize response. +* #656: Fixed OIDC "nonce" checks: raise errors when it's mandatory 3.0.1 (2019-01-24) ------------------ -- cgit v1.2.1 From 9e824cfb0eb36b4d23ab73171b821b1a74ec659c Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Thu, 4 Jul 2019 11:22:12 +0200 Subject: Bump version --- oauthlib/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oauthlib/__init__.py b/oauthlib/__init__.py index 8eb82a6..089977c 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.2-dev' +__version__ = '3.0.2' logging.getLogger('oauthlib').addHandler(NullHandler()) -- cgit v1.2.1 From 0a9fd41faed16e15e04d6bfeef2b532d090f05bf Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Fri, 19 Jul 2019 12:42:43 +0200 Subject: Bump version --- oauthlib/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oauthlib/__init__.py b/oauthlib/__init__.py index f1457a9..c7d19a1 100644 --- a/oauthlib/__init__.py +++ b/oauthlib/__init__.py @@ -12,7 +12,7 @@ import logging from logging import NullHandler __author__ = 'The OAuthlib Community' -__version__ = '3.0.2' +__version__ = '3.1.0-dev' logging.getLogger('oauthlib').addHandler(NullHandler()) -- cgit v1.2.1 From 36e4512762278abf4e21f0263a00eae8a22e1f1b Mon Sep 17 00:00:00 2001 From: Hamish Moffatt Date: Thu, 25 Jul 2019 13:49:05 +1000 Subject: add HMAC-SHA256 signature validation --- oauthlib/oauth1/rfc5849/endpoints/base.py | 7 +++++-- oauthlib/oauth1/rfc5849/signature.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/oauthlib/oauth1/rfc5849/endpoints/base.py b/oauthlib/oauth1/rfc5849/endpoints/base.py index ecf8a50..f005256 100644 --- a/oauthlib/oauth1/rfc5849/endpoints/base.py +++ b/oauthlib/oauth1/rfc5849/endpoints/base.py @@ -12,7 +12,7 @@ import time from oauthlib.common import CaseInsensitiveDict, Request, generate_token -from .. import (CONTENT_TYPE_FORM_URLENCODED, SIGNATURE_HMAC, SIGNATURE_RSA, +from .. import (CONTENT_TYPE_FORM_URLENCODED, SIGNATURE_HMAC_SHA1, SIGNATURE_HMAC_SHA256, SIGNATURE_RSA, SIGNATURE_TYPE_AUTH_HEADER, SIGNATURE_TYPE_BODY, SIGNATURE_TYPE_QUERY, errors, signature, utils) @@ -204,9 +204,12 @@ class BaseEndpoint(object): resource_owner_secret = self.request_validator.get_access_token_secret( request.client_key, request.resource_owner_key, request) - if request.signature_method == SIGNATURE_HMAC: + if request.signature_method == SIGNATURE_HMAC_SHA1: valid_signature = signature.verify_hmac_sha1(request, client_secret, resource_owner_secret) + elif request.signature_method == SIGNATURE_HMAC_SHA256: + valid_signature = signature.verify_hmac_sha256(request, + client_secret, resource_owner_secret) else: valid_signature = signature.verify_plaintext(request, client_secret, resource_owner_secret) diff --git a/oauthlib/oauth1/rfc5849/signature.py b/oauthlib/oauth1/rfc5849/signature.py index f899aca..a60bee2 100644 --- a/oauthlib/oauth1/rfc5849/signature.py +++ b/oauthlib/oauth1/rfc5849/signature.py @@ -661,6 +661,36 @@ def verify_hmac_sha1(request, client_secret=None, return match +def verify_hmac_sha256(request, client_secret=None, + resource_owner_secret=None): + """Verify a HMAC-SHA256 signature. + + Per `section 3.4`_ of the spec. + + .. _`section 3.4`: https://tools.ietf.org/html/rfc5849#section-3.4 + + To satisfy `RFC2616 section 5.2`_ item 1, the request argument's uri + attribute MUST be an absolute URI whose netloc part identifies the + origin server or gateway on which the resource resides. Any Host + item of the request argument's headers dict attribute will be + ignored. + + .. _`RFC2616 section 5.2`: https://tools.ietf.org/html/rfc2616#section-5.2 + + """ + norm_params = normalize_parameters(request.params) + bs_uri = base_string_uri(request.uri) + sig_base_str = signature_base_string(request.http_method, bs_uri, + norm_params) + signature = sign_hmac_sha256(sig_base_str, client_secret, + resource_owner_secret) + match = safe_string_equals(signature, request.signature) + if not match: + log.debug('Verify HMAC-SHA256 failed: signature base string: %s', + sig_base_str) + return match + + def _prepare_key_plus(alg, keystr): if isinstance(keystr, bytes): keystr = keystr.decode('utf-8') -- cgit v1.2.1