From 73032fe688a899f80d2a65479c72fec450ec51a1 Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Thu, 28 Feb 2019 10:06:37 +0100 Subject: Removed duplicated OIDC members in OAuth2.RequestValidator --- docs/oauth2/oidc/validator.rst | 6 +- oauthlib/oauth2/rfc6749/request_validator.py | 182 --------------------------- oauthlib/openid/__init__.py | 1 + 3 files changed, 5 insertions(+), 184 deletions(-) diff --git a/docs/oauth2/oidc/validator.rst b/docs/oauth2/oidc/validator.rst index a03adfe..7a6f574 100644 --- a/docs/oauth2/oidc/validator.rst +++ b/docs/oauth2/oidc/validator.rst @@ -10,12 +10,14 @@ upgrade it by replacing one line of code: .. code-block:: python from oauthlib.oauth2 import Server + from oauthlib.oauth2 import RequestValidator Into .. code-block:: python from oauthlib.openid import Server + from oauthlib.openid import RequestValidator Then, you have to implement the new RequestValidator methods as shown below. @@ -24,5 +26,5 @@ RequestValidator Extension A couple of methods must be implemented in your validator subclass if you wish to support OpenID Connect: -.. autoclass:: oauthlib.oauth2.RequestValidator - :members: validate_silent_authorization, validate_silent_login, validate_user_match, get_id_token, get_authorization_code_scopes, validate_jwt_bearer_token +.. autoclass:: oauthlib.openid.RequestValidator + :members: diff --git a/oauthlib/oauth2/rfc6749/request_validator.py b/oauthlib/oauth2/rfc6749/request_validator.py index 5ff30d8..d6ec2ab 100644 --- a/oauthlib/oauth2/rfc6749/request_validator.py +++ b/oauthlib/oauth2/rfc6749/request_validator.py @@ -291,32 +291,6 @@ class RequestValidator(object): """ raise NotImplementedError('Subclasses must implement this method.') - def get_authorization_code_scopes(self, client_id, code, redirect_uri, request): - """ Extracts scopes from saved authorization code. - - The scopes returned by this method is used to route token requests - based on scopes passed to Authorization Code requests. - - With that the token endpoint knows when to include OpenIDConnect - id_token in token response only based on authorization code scopes. - - Only code param should be sufficient to retrieve grant code from - any storage you are using, `client_id` and `redirect_uri` can gave a - blank value `""` don't forget to check it before using those values - in a select query if a database is used. - - :param client_id: Unicode client identifier. - :param code: Unicode authorization code grant. - :param redirect_uri: Unicode absolute URI. - :param request: OAuthlib request. - :type request: oauthlib.common.Request - :return: A list of scopes - - Method is used by: - - Authorization Token Grant Dispatcher - """ - raise NotImplementedError('Subclasses must implement this method.') - def save_token(self, token, request, *args, **kwargs): """Persist the token with a token type specific method. @@ -378,104 +352,6 @@ class RequestValidator(object): """ raise NotImplementedError('Subclasses must implement this method.') - def get_jwt_bearer_token(self, token, token_handler, request): - """Get JWT Bearer token or OpenID Connect ID token - - If using OpenID Connect this SHOULD call `oauthlib.oauth2.RequestValidator.get_id_token` - - :param token: A Bearer token dict. - :param token_handler: The token handler (BearerToken class). - :param request: OAuthlib request. - :type request: oauthlib.common.Request - :return: The JWT Bearer token or OpenID Connect ID token (a JWS signed JWT) - - Method is used by JWT Bearer and OpenID Connect tokens: - - JWTToken.create_token - """ - raise NotImplementedError('Subclasses must implement this method.') - - def get_id_token(self, token, token_handler, request): - """Get OpenID Connect ID token - - In the OpenID Connect workflows when an ID Token is requested this method is called. - Subclasses should implement the construction, signing and optional encryption of the - ID Token as described in the OpenID Connect spec. - - In addition to the standard OAuth2 request properties, the request may also contain - these OIDC specific properties which are useful to this method: - - - nonce, if workflow is implicit or hybrid and it was provided - - claims, if provided to the original Authorization Code request - - The token parameter is a dict which may contain an ``access_token`` entry, in which - case the resulting ID Token *should* include a calculated ``at_hash`` claim. - - Similarly, when the request parameter has a ``code`` property defined, the ID Token - *should* include a calculated ``c_hash`` claim. - - http://openid.net/specs/openid-connect-core-1_0.html (sections `3.1.3.6`_, `3.2.2.10`_, `3.3.2.11`_) - - .. _`3.1.3.6`: http://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken - .. _`3.2.2.10`: http://openid.net/specs/openid-connect-core-1_0.html#ImplicitIDToken - .. _`3.3.2.11`: http://openid.net/specs/openid-connect-core-1_0.html#HybridIDToken - - :param token: A Bearer token dict. - :param token_handler: The token handler (BearerToken class) - :param request: OAuthlib request. - :type request: oauthlib.common.Request - :return: The ID Token (a JWS signed JWT) - """ - # the request.scope should be used by the get_id_token() method to determine which claims to include in the resulting id_token - raise NotImplementedError('Subclasses must implement this method.') - - def validate_jwt_bearer_token(self, token, scopes, request): - """Ensure the JWT Bearer token or OpenID Connect ID token are valids and authorized access to scopes. - - If using OpenID Connect this SHOULD call `oauthlib.oauth2.RequestValidator.get_id_token` - - If not using OpenID Connect this can `return None` to avoid 5xx rather 401/3 response. - - OpenID connect core 1.0 describe how to validate an id_token: - - http://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation - - http://openid.net/specs/openid-connect-core-1_0.html#ImplicitIDTValidation - - http://openid.net/specs/openid-connect-core-1_0.html#HybridIDTValidation - - http://openid.net/specs/openid-connect-core-1_0.html#HybridIDTValidation2 - - :param token: Unicode Bearer token. - :param scopes: List of scopes (defined by you). - :param request: OAuthlib request. - :type request: oauthlib.common.Request - :rtype: True or False - - Method is indirectly used by all core OpenID connect JWT token issuing grant types: - - Authorization Code Grant - - Implicit Grant - - Hybrid Grant - """ - raise NotImplementedError('Subclasses must implement this method.') - - def validate_id_token(self, token, scopes, request): - """Ensure the id token is valid and authorized access to scopes. - - OpenID connect core 1.0 describe how to validate an id_token: - - http://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation - - http://openid.net/specs/openid-connect-core-1_0.html#ImplicitIDTValidation - - http://openid.net/specs/openid-connect-core-1_0.html#HybridIDTValidation - - http://openid.net/specs/openid-connect-core-1_0.html#HybridIDTValidation2 - - :param token: Unicode Bearer token. - :param scopes: List of scopes (defined by you). - :param request: OAuthlib request. - :type request: oauthlib.common.Request - :rtype: True or False - - Method is indirectly used by all core OpenID connect JWT token issuing grant types: - - Authorization Code Grant - - Implicit Grant - - Hybrid Grant - """ - raise NotImplementedError('Subclasses must implement this method.') - def validate_bearer_token(self, token, scopes, request): """Ensure the Bearer token is valid and authorized access to scopes. @@ -668,44 +544,6 @@ class RequestValidator(object): """ raise NotImplementedError('Subclasses must implement this method.') - def validate_silent_authorization(self, request): - """Ensure the logged in user has authorized silent OpenID authorization. - - Silent OpenID authorization allows access tokens and id tokens to be - granted to clients without any user prompt or interaction. - - :param request: OAuthlib request. - :type request: oauthlib.common.Request - :rtype: True or False - - Method is used by: - - OpenIDConnectAuthCode - - OpenIDConnectImplicit - - OpenIDConnectHybrid - """ - raise NotImplementedError('Subclasses must implement this method.') - - def validate_silent_login(self, request): - """Ensure session user has authorized silent OpenID login. - - If no user is logged in or has not authorized silent login, this - method should return False. - - If the user is logged in but associated with multiple accounts and - not selected which one to link to the token then this method should - raise an oauthlib.oauth2.AccountSelectionRequired error. - - :param request: OAuthlib request. - :type request: oauthlib.common.Request - :rtype: True or False - - Method is used by: - - OpenIDConnectAuthCode - - OpenIDConnectImplicit - - OpenIDConnectHybrid - """ - raise NotImplementedError('Subclasses must implement this method.') - def validate_user(self, username, password, client, request, *args, **kwargs): """Ensure the username and password is valid. @@ -726,26 +564,6 @@ class RequestValidator(object): """ raise NotImplementedError('Subclasses must implement this method.') - def validate_user_match(self, id_token_hint, scopes, claims, request): - """Ensure client supplied user id hint matches session user. - - If the sub claim or id_token_hint is supplied then the session - user must match the given ID. - - :param id_token_hint: User identifier string. - :param scopes: List of OAuth 2 scopes and OpenID claims (strings). - :param claims: OpenID Connect claims dict. - :param request: OAuthlib request. - :type request: oauthlib.common.Request - :rtype: True or False - - Method is used by: - - OpenIDConnectAuthCode - - OpenIDConnectImplicit - - OpenIDConnectHybrid - """ - raise NotImplementedError('Subclasses must implement this method.') - def is_pkce_required(self, client_id, request): """Determine if current request requires PKCE. Default, False. This is called for both "authorization" and "token" requests. diff --git a/oauthlib/openid/__init__.py b/oauthlib/openid/__init__.py index 03f0fa2..7f1a876 100644 --- a/oauthlib/openid/__init__.py +++ b/oauthlib/openid/__init__.py @@ -7,3 +7,4 @@ oauthlib.openid from __future__ import absolute_import, unicode_literals from .connect.core.endpoints import Server +from .connect.core.request_validator import RequestValidator -- cgit v1.2.1 From 7c570c763725fdaa40778d6cd6689b09b3971f50 Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Thu, 28 Feb 2019 10:16:27 +0100 Subject: Add technicals fields of `id_token` in oauthlib OIDC support A new RequestValidator `fill_id_token` has been introduced to replace `get_id_token`. It aims to have the bare minimum amount of fields to complete a full OIDC id_token support. `get_id_token` is still valid but optional, and if it is implemented, `fill_id_token` will not be called. The current `fill_id_token` came with full support of `aud`, `iat`, `nonce`, `at_hash` and `c_hash`. More could come in the future e.g. `auth_time`, ... --- docs/oauth2/oidc/id_tokens.rst | 17 ++--- oauthlib/oauth2/rfc6749/request_validator.py | 3 + .../connect/core/grant_types/authorization_code.py | 20 ++++++ oauthlib/openid/connect/core/grant_types/base.py | 64 ++++++++++++++++-- .../openid/connect/core/grant_types/implicit.py | 4 +- oauthlib/openid/connect/core/request_validator.py | 75 +++++++++++++++++++++- 6 files changed, 166 insertions(+), 17 deletions(-) diff --git a/docs/oauth2/oidc/id_tokens.rst b/docs/oauth2/oidc/id_tokens.rst index 999cfa7..2387c01 100644 --- a/docs/oauth2/oidc/id_tokens.rst +++ b/docs/oauth2/oidc/id_tokens.rst @@ -1,9 +1,9 @@ ID Tokens ========= -The creation of `ID Tokens`_ is ultimately done not by OAuthLib but by your ``RequestValidator`` subclass. This is because their +The creation of `ID Tokens`_ is ultimately not done by OAuthLib but by your ``RequestValidator`` subclass. This is because their content is dependent on your implementation of users, their attributes, any claims you may wish to support, as well as the -details of how you model the notion of a Client Application. As such OAuthLib simply calls your validator's ``get_id_token`` +details of how you model the notion of a Client Application. As such OAuthLib simply calls your validator's ``fill_id_token`` method at the appropriate times during the authorization flow, depending on the grant type requested (Authorization Code, Implicit, Hybrid, etc.). @@ -12,7 +12,7 @@ See examples below. .. _`ID Tokens`: http://openid.net/specs/openid-connect-core-1_0.html#IDToken .. autoclass:: oauthlib.oauth2.RequestValidator - :members: get_id_token + :members: fill_id_token JWT/JWS example with pyjwt library @@ -38,12 +38,13 @@ You can switch to jwcrypto library if you want to return JWE instead. super().__init__(self, **kwargs) - def get_id_token(self, token, token_handler, request): + def fill_id_token(self, id_token, token, token_handler, request): import jwt - data = {"nonce": request.nonce} if request.nonce is not None else {} - + id_token["iss"] = "https://my.cool.app.com" + id_token["sub"] = request.user.id + id_token["exp"] = id_token["iat"] + 3600 * 24 # keep it valid for 24hours for claim_key in request.claims: - data[claim_key] = request.userattributes[claim_key] # this must be set in another callback + id_token[claim_key] = request.userattributes[claim_key] # this must be set in another callback - return jwt.encode(data, self.private_pem, 'RS256') + return jwt.encode(id_token, self.private_pem, 'RS256') diff --git a/oauthlib/oauth2/rfc6749/request_validator.py b/oauthlib/oauth2/rfc6749/request_validator.py index d6ec2ab..86509b6 100644 --- a/oauthlib/oauth2/rfc6749/request_validator.py +++ b/oauthlib/oauth2/rfc6749/request_validator.py @@ -271,6 +271,9 @@ class RequestValidator(object): - Code Challenge (``request.code_challenge``) and - Code Challenge Method (``request.code_challenge_method``) + To support OIDC, you MUST associate the code with: + - nonce, if present (``code["nonce"]``) + The ``code`` argument is actually a dictionary, containing at least a ``code`` key with the actual authorization code: diff --git a/oauthlib/openid/connect/core/grant_types/authorization_code.py b/oauthlib/openid/connect/core/grant_types/authorization_code.py index b0b1015..becfcfa 100644 --- a/oauthlib/openid/connect/core/grant_types/authorization_code.py +++ b/oauthlib/openid/connect/core/grant_types/authorization_code.py @@ -22,3 +22,23 @@ class AuthorizationCodeGrant(GrantTypeBase): self.custom_validators.post_auth.append( self.openid_authorization_validator) self.register_token_modifier(self.add_id_token) + + def add_id_token(self, token, token_handler, request): + """ + Construct an initial version of id_token, and let the + request_validator sign or encrypt it. + + The authorization_code version of this method is used to + retrieve the nonce accordingly to the code storage. + """ + # Treat it as normal OAuth 2 auth code request if openid is not present + if not request.scopes or 'openid' not in request.scopes: + return token + + nonce = self.request_validator.get_authorization_code_nonce( + request.client_id, + request.code, + request.redirect_uri, + request + ) + return super(AuthorizationCodeGrant, self).add_id_token(token, token_handler, request, nonce=nonce) diff --git a/oauthlib/openid/connect/core/grant_types/base.py b/oauthlib/openid/connect/core/grant_types/base.py index 4f5c944..19a7f4f 100644 --- a/oauthlib/openid/connect/core/grant_types/base.py +++ b/oauthlib/openid/connect/core/grant_types/base.py @@ -1,6 +1,8 @@ from .exceptions import OIDCNoPrompt +import base64 import datetime +import hashlib import logging from json import loads @@ -49,7 +51,45 @@ class GrantTypeBase(object): raise InvalidRequestError(description="Malformed claims parameter", uri="http://openid.net/specs/openid-connect-core-1_0.html#ClaimsParameter") - def add_id_token(self, token, token_handler, request): + def hash_id_token(self, value, hashfunc=hashlib.sha256): + """ + Its value is the base64url encoding of the left-most half of the + hash of the octets of the ASCII representation of the access_token + value, where the hash algorithm used is the hash algorithm used in + the alg Header Parameter of the ID Token's JOSE Header. + + For instance, if the alg is RS256, hash the access_token value + with SHA-256, then take the left-most 128 bits and + base64url-encode them. + For instance, if the alg is HS512, hash the code value with + SHA-512, then take the left-most 256 bits and base64url-encode + them. The c_hash value is a case-sensitive string. + + Example of hash from OIDC specification (bound to a JWS using RS256): + + code: + Qcb0Orv1zh30vL1MPRsbm-diHiMwcLyZvn1arpZv-Jxf_11jnpEX3Tgfvk + + c_hash: + LDktKdoQak3Pk0cnXxCltA + """ + digest = hashfunc(value.encode()).digest() + left_most = int(len(digest) / 2) + return base64.urlsafe_b64encode(digest[:left_most]).decode().rstrip("=") + + def add_id_token(self, token, token_handler, request, nonce=None): + """ + Construct an initial version of id_token, and let the + request_validator sign or encrypt it. + + The initial version can contain the fields below, accordingly + to the spec: + - aud + - iat + - nonce + - at_hash + - c_hash + """ # Treat it as normal OAuth 2 auth code request if openid is not present if not request.scopes or 'openid' not in request.scopes: return token @@ -58,13 +98,25 @@ class GrantTypeBase(object): if request.response_type and 'id_token' not in request.response_type: return token - if request.max_age: - d = datetime.datetime.utcnow() - token['auth_time'] = d.isoformat("T") + "Z" + # Implementation mint its own id_token without help. + id_token = self.request_validator.get_id_token(token, token_handler, request) + if id_token: + token['id_token'] = id_token + return token + + # Fallback for asking some help from oauthlib framework. + # Start with technicals fields bound to the specification. + id_token = {} + id_token['aud'] = request.client_id + id_token['iat'] = int(datetime.datetime.now().timestamp()) + if nonce is not None: + id_token["nonce"] = nonce - # TODO: acr claims (probably better handled by server code using oauthlib in get_id_token) + if "access_token" in token: + id_token["at_hash"] = self.hash_id_token(token["access_token"]) - token['id_token'] = self.request_validator.get_id_token(token, token_handler, request) + # Call request_validator to complete/sign/encrypt id_token + token['id_token'] = self.request_validator.fill_id_token(id_token, token, token_handler, request) return token diff --git a/oauthlib/openid/connect/core/grant_types/implicit.py b/oauthlib/openid/connect/core/grant_types/implicit.py index d3797b2..c2dbc27 100644 --- a/oauthlib/openid/connect/core/grant_types/implicit.py +++ b/oauthlib/openid/connect/core/grant_types/implicit.py @@ -27,9 +27,9 @@ class ImplicitGrant(GrantTypeBase): self.register_token_modifier(self.add_id_token) def add_id_token(self, token, token_handler, request): - if 'state' not in token: + if 'state' not in token and request.state: token['state'] = request.state - return super(ImplicitGrant, self).add_id_token(token, token_handler, request) + return super(ImplicitGrant, self).add_id_token(token, token_handler, request, nonce=request.nonce) def openid_authorization_validator(self, request): """Additional validation when following the implicit flow. diff --git a/oauthlib/openid/connect/core/request_validator.py b/oauthlib/openid/connect/core/request_validator.py index 1587754..f8aeed8 100644 --- a/oauthlib/openid/connect/core/request_validator.py +++ b/oauthlib/openid/connect/core/request_validator.py @@ -38,6 +38,31 @@ class RequestValidator(OAuth2RequestValidator): """ raise NotImplementedError('Subclasses must implement this method.') + def get_authorization_code_nonce(self, client_id, code, redirect_uri, request): + """ Extracts nonce from saved authorization code. + + If present in the Authentication Request, Authorization + Servers MUST include a nonce Claim in the ID Token with the + Claim Value being the nonce value sent in the Authentication + Request. Authorization Servers SHOULD perform no other + processing on nonce values used. The nonce value is a + case-sensitive string. + + Only code param should be sufficient to retrieve grant code from + any storage you are using, `client_id` and `redirect_uri` can gave a + blank value `""` don't forget to check it before using those values + in a select query if a database is used. + + :param client_id: Unicode client identifier + :param code: Unicode authorization code grant + :param redirect_uri: Unicode absolute URI + :return: A list of scope + + Method is used by: + - Authorization Token Grant Dispatcher + """ + raise NotImplementedError('Subclasses must implement this method.') + def get_jwt_bearer_token(self, token, token_handler, request): """Get JWT Bearer token or OpenID Connect ID token @@ -57,6 +82,12 @@ class RequestValidator(OAuth2RequestValidator): def get_id_token(self, token, token_handler, request): """Get OpenID Connect ID token + This method is OPTIONAL and is NOT RECOMMENDED. + `fill_id_token` SHOULD be implemented instead. However, if you + want a full control over the minting of the `id_token`, you + MAY want to override `get_id_token` instead of using + `fill_id_token`. + In the OpenID Connect workflows when an ID Token is requested this method is called. Subclasses should implement the construction, signing and optional encryption of the ID Token as described in the OpenID Connect spec. @@ -85,7 +116,49 @@ class RequestValidator(OAuth2RequestValidator): :type request: oauthlib.common.Request :return: The ID Token (a JWS signed JWT) """ - # the request.scope should be used by the get_id_token() method to determine which claims to include in the resulting id_token + return None + + def fill_id_token(self, id_token, token, token_handler, request): + """Fill OpenID Connect ID token & Sign or Encrypt. + + In the OpenID Connect workflows when an ID Token is requested + this method is called. Subclasses should implement the + construction, signing and optional encryption of the ID Token + as described in the OpenID Connect spec. + + The `id_token` parameter is a dict containing a couple of OIDC + technical fields related to the specification. Prepopulated + attributes are: + + - `aud`, equals to `request.client_id`. + - `iat`, equals to current time. + - `nonce`, if present, is equals to the `nonce` from the + authorization request. + - `at_hash`, hash of `access_token`, if relevant. + - `c_hash`, hash of `code`, if relevant. + + This method MUST provide required fields as below: + + - `iss`, REQUIRED. Issuer Identifier for the Issuer of the response. + - `sub`, REQUIRED. Subject Identifier + - `exp`, REQUIRED. Expiration time on or after which the ID + Token MUST NOT be accepted by the RP when performing + authentication with the OP. + + Additionals claims must be added, note that `request.scope` + should be used to determine the list of claims. + + More information can be found at `OpenID Connect Core#Claims`_ + + .. _`OpenID Connect Core#Claims`: https://openid.net/specs/openid-connect-core-1_0.html#Claims + + :param id_token: A dict containing technical fields of id_token + :param token: A Bearer token dict + :param token_handler: the token handler (BearerToken class) + :param request: OAuthlib request. + :type request: oauthlib.common.Request + :return: The ID Token (a JWS signed JWT or JWE encrypted JWT) + """ raise NotImplementedError('Subclasses must implement this method.') def validate_jwt_bearer_token(self, token, scopes, request): -- cgit v1.2.1 From 62152d48e83cbc0eac3a2991b3b7fed2e84f7ec7 Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Thu, 28 Feb 2019 15:03:34 +0100 Subject: Add c_hash. Add summary about when nonce/hashes are added to id_token --- oauthlib/openid/connect/core/grant_types/base.py | 29 ++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/oauthlib/openid/connect/core/grant_types/base.py b/oauthlib/openid/connect/core/grant_types/base.py index 19a7f4f..f925c64 100644 --- a/oauthlib/openid/connect/core/grant_types/base.py +++ b/oauthlib/openid/connect/core/grant_types/base.py @@ -109,12 +109,41 @@ class GrantTypeBase(object): id_token = {} id_token['aud'] = request.client_id id_token['iat'] = int(datetime.datetime.now().timestamp()) + + # nonce is REQUIRED when response_type value is: + # - id_token token (Implicit) + # - id_token (Implicit) + # - code id_token (Hybrid) + # - code id_token token (Hybrid) + # + # nonce is OPTIONAL when response_type value is: + # - code (Authorization Code) + # - code token (Hybrid) if nonce is not None: id_token["nonce"] = nonce + # at_hash is REQUIRED when response_type value is: + # - id_token token (Implicit) + # - code id_token token (Hybrid) + # + # at_hash is OPTIONAL when: + # - code (Authorization code) + # - code id_token (Hybrid) + # - code token (Hybrid) + # + # at_hash MAY NOT be used when: + # - id_token (Implicit) if "access_token" in token: id_token["at_hash"] = self.hash_id_token(token["access_token"]) + # c_hash is REQUIRED when response_type value is: + # - code id_token (Hybrid) + # - code id_token token (Hybrid) + # + # c_hash is OPTIONAL for others. + if "code" in token: + id_token["c_hash"] = self.hash_id_token(token["code"]) + # Call request_validator to complete/sign/encrypt id_token token['id_token'] = self.request_validator.fill_id_token(id_token, token, token_handler, request) -- cgit v1.2.1 From 84cd5a4265c2670af5a4b7ad2143c47fa68582c1 Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Mon, 25 Feb 2019 15:36:13 +0100 Subject: Change to 3.0.2-dev as long as master is in "dev" --- CHANGELOG.rst | 2 +- oauthlib/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f49fb92..ade6243 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,7 +1,7 @@ Changelog ========= -TBD +3.0.2 (TBD) ------------------ * #650 Fixed space encoding in base string URI used in the signature base string. * #652: Fixed OIDC /token response which wrongly returned "&state=None" diff --git a/oauthlib/__init__.py b/oauthlib/__init__.py index b23102c..8eb82a6 100644 --- a/oauthlib/__init__.py +++ b/oauthlib/__init__.py @@ -12,6 +12,6 @@ import logging from logging import NullHandler __author__ = 'The OAuthlib Community' -__version__ = '3.0.1' +__version__ = '3.0.2-dev' logging.getLogger('oauthlib').addHandler(NullHandler()) -- cgit v1.2.1 From 53d3d335879f205ae705d93420f34984073cd5a1 Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Tue, 26 Mar 2019 14:50:41 +0100 Subject: Renamed fill into finalize to add clarity --- docs/oauth2/oidc/id_tokens.rst | 6 +++--- oauthlib/openid/connect/core/grant_types/base.py | 2 +- oauthlib/openid/connect/core/request_validator.py | 8 ++++---- tests/openid/connect/core/test_request_validator.py | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/oauth2/oidc/id_tokens.rst b/docs/oauth2/oidc/id_tokens.rst index 2387c01..a1bf7cf 100644 --- a/docs/oauth2/oidc/id_tokens.rst +++ b/docs/oauth2/oidc/id_tokens.rst @@ -3,7 +3,7 @@ ID Tokens The creation of `ID Tokens`_ is ultimately not done by OAuthLib but by your ``RequestValidator`` subclass. This is because their content is dependent on your implementation of users, their attributes, any claims you may wish to support, as well as the -details of how you model the notion of a Client Application. As such OAuthLib simply calls your validator's ``fill_id_token`` +details of how you model the notion of a Client Application. As such OAuthLib simply calls your validator's ``finalize_id_token`` method at the appropriate times during the authorization flow, depending on the grant type requested (Authorization Code, Implicit, Hybrid, etc.). @@ -12,7 +12,7 @@ See examples below. .. _`ID Tokens`: http://openid.net/specs/openid-connect-core-1_0.html#IDToken .. autoclass:: oauthlib.oauth2.RequestValidator - :members: fill_id_token + :members: finalize_id_token JWT/JWS example with pyjwt library @@ -38,7 +38,7 @@ You can switch to jwcrypto library if you want to return JWE instead. super().__init__(self, **kwargs) - def fill_id_token(self, id_token, token, token_handler, request): + def finalize_id_token(self, id_token, token, token_handler, request): import jwt id_token["iss"] = "https://my.cool.app.com" diff --git a/oauthlib/openid/connect/core/grant_types/base.py b/oauthlib/openid/connect/core/grant_types/base.py index f925c64..31ff82e 100644 --- a/oauthlib/openid/connect/core/grant_types/base.py +++ b/oauthlib/openid/connect/core/grant_types/base.py @@ -145,7 +145,7 @@ class GrantTypeBase(object): id_token["c_hash"] = self.hash_id_token(token["code"]) # Call request_validator to complete/sign/encrypt id_token - token['id_token'] = self.request_validator.fill_id_token(id_token, token, token_handler, request) + token['id_token'] = self.request_validator.finalize_id_token(id_token, token, token_handler, request) return token diff --git a/oauthlib/openid/connect/core/request_validator.py b/oauthlib/openid/connect/core/request_validator.py index f8aeed8..7ce7e17 100644 --- a/oauthlib/openid/connect/core/request_validator.py +++ b/oauthlib/openid/connect/core/request_validator.py @@ -83,10 +83,10 @@ class RequestValidator(OAuth2RequestValidator): """Get OpenID Connect ID token This method is OPTIONAL and is NOT RECOMMENDED. - `fill_id_token` SHOULD be implemented instead. However, if you + `finalize_id_token` SHOULD be implemented instead. However, if you want a full control over the minting of the `id_token`, you MAY want to override `get_id_token` instead of using - `fill_id_token`. + `finalize_id_token`. In the OpenID Connect workflows when an ID Token is requested this method is called. Subclasses should implement the construction, signing and optional encryption of the @@ -118,8 +118,8 @@ class RequestValidator(OAuth2RequestValidator): """ return None - def fill_id_token(self, id_token, token, token_handler, request): - """Fill OpenID Connect ID token & Sign or Encrypt. + def finalize_id_token(self, id_token, token, token_handler, request): + """Finalize OpenID Connect ID token & Sign or Encrypt. In the OpenID Connect workflows when an ID Token is requested this method is called. Subclasses should implement the diff --git a/tests/openid/connect/core/test_request_validator.py b/tests/openid/connect/core/test_request_validator.py index e20e88f..ebe0aeb 100644 --- a/tests/openid/connect/core/test_request_validator.py +++ b/tests/openid/connect/core/test_request_validator.py @@ -22,8 +22,8 @@ class RequestValidatorTest(TestCase): ) self.assertRaises( NotImplementedError, - v.get_id_token, - 'token', 'token_handler', 'request' + v.finalize_id_token, + 'id_token', 'token', 'token_handler', 'request' ) self.assertRaises( NotImplementedError, -- cgit v1.2.1 From 4877b4837a9355bc74c9f3d59343d689be4c86fa Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Tue, 26 Mar 2019 14:50:55 +0100 Subject: Use native operator instead type conversion --- oauthlib/openid/connect/core/grant_types/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oauthlib/openid/connect/core/grant_types/base.py b/oauthlib/openid/connect/core/grant_types/base.py index 31ff82e..c5d91e7 100644 --- a/oauthlib/openid/connect/core/grant_types/base.py +++ b/oauthlib/openid/connect/core/grant_types/base.py @@ -74,7 +74,7 @@ class GrantTypeBase(object): LDktKdoQak3Pk0cnXxCltA """ digest = hashfunc(value.encode()).digest() - left_most = int(len(digest) / 2) + left_most = len(digest) // 2 return base64.urlsafe_b64encode(digest[:left_most]).decode().rstrip("=") def add_id_token(self, token, token_handler, request, nonce=None): -- cgit v1.2.1 From 09538c93d562f6230f3d257b6782d58eeb0a7c3e Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Tue, 26 Mar 2019 16:15:13 +0100 Subject: Add unittests for OIDC GrantTypeBase. Rename hash_id_token into id_token_hash --- oauthlib/openid/connect/core/grant_types/base.py | 6 +- tests/openid/connect/core/grant_types/test_base.py | 104 +++++++++++++++++++++ 2 files changed, 107 insertions(+), 3 deletions(-) create mode 100644 tests/openid/connect/core/grant_types/test_base.py diff --git a/oauthlib/openid/connect/core/grant_types/base.py b/oauthlib/openid/connect/core/grant_types/base.py index c5d91e7..6272ea2 100644 --- a/oauthlib/openid/connect/core/grant_types/base.py +++ b/oauthlib/openid/connect/core/grant_types/base.py @@ -51,7 +51,7 @@ class GrantTypeBase(object): raise InvalidRequestError(description="Malformed claims parameter", uri="http://openid.net/specs/openid-connect-core-1_0.html#ClaimsParameter") - def hash_id_token(self, value, hashfunc=hashlib.sha256): + def id_token_hash(self, value, hashfunc=hashlib.sha256): """ Its value is the base64url encoding of the left-most half of the hash of the octets of the ASCII representation of the access_token @@ -134,7 +134,7 @@ class GrantTypeBase(object): # at_hash MAY NOT be used when: # - id_token (Implicit) if "access_token" in token: - id_token["at_hash"] = self.hash_id_token(token["access_token"]) + id_token["at_hash"] = self.id_token_hash(token["access_token"]) # c_hash is REQUIRED when response_type value is: # - code id_token (Hybrid) @@ -142,7 +142,7 @@ class GrantTypeBase(object): # # c_hash is OPTIONAL for others. if "code" in token: - id_token["c_hash"] = self.hash_id_token(token["code"]) + id_token["c_hash"] = self.id_token_hash(token["code"]) # Call request_validator to complete/sign/encrypt id_token token['id_token'] = self.request_validator.finalize_id_token(id_token, token, token_handler, request) diff --git a/tests/openid/connect/core/grant_types/test_base.py b/tests/openid/connect/core/grant_types/test_base.py new file mode 100644 index 0000000..319904b --- /dev/null +++ b/tests/openid/connect/core/grant_types/test_base.py @@ -0,0 +1,104 @@ +# -*- coding: utf-8 -*- +import datetime +import mock + +from oauthlib.common import Request +from oauthlib.openid.connect.core.grant_types.base import GrantTypeBase + +from tests.unittest import TestCase + + +class GrantBase(GrantTypeBase): + """Class to test GrantTypeBase""" + def __init__(self, request_validator=None, **kwargs): + self.request_validator = request_validator + + +class IDTokenTest(TestCase): + + def setUp(self): + self.request = Request('http://a.b/path') + self.request.scopes = ('hello', 'openid') + self.request.expires_in = 1800 + self.request.client_id = 'abcdef' + self.request.code = '1234' + self.request.response_type = 'id_token' + self.request.grant_type = 'authorization_code' + self.request.redirect_uri = 'https://a.b/cb' + self.request.state = 'abc' + self.request.nonce = None + + self.mock_validator = mock.MagicMock() + self.mock_validator.get_id_token.return_value = None + self.mock_validator.finalize_id_token.return_value = "eyJ.body.signature" + self.token = {} + + self.grant = GrantBase(request_validator=self.mock_validator) + + self.url_query = 'https://a.b/cb?code=abc&state=abc' + self.url_fragment = 'https://a.b/cb#code=abc&state=abc' + + def test_id_token_hash(self): + self.assertEqual(self.grant.id_token_hash( + "Qcb0Orv1zh30vL1MPRsbm-diHiMwcLyZvn1arpZv-Jxf_11jnpEX3Tgfvk", + ), "LDktKdoQak3Pk0cnXxCltA", "hash differs from RFC") + + def test_get_id_token_no_openid(self): + self.request.scopes = ('hello') + token = self.grant.add_id_token(self.token, "token_handler_mock", self.request) + self.assertNotIn("id_token", token) + + self.request.scopes = None + token = self.grant.add_id_token(self.token, "token_handler_mock", self.request) + self.assertNotIn("id_token", token) + + self.request.scopes = () + token = self.grant.add_id_token(self.token, "token_handler_mock", self.request) + self.assertNotIn("id_token", token) + + def test_get_id_token(self): + self.mock_validator.get_id_token.return_value = "toto" + token = self.grant.add_id_token(self.token, "token_handler_mock", self.request) + self.assertIn("id_token", token) + self.assertEqual(token["id_token"], "toto") + + def test_finalize_id_token(self): + token = self.grant.add_id_token(self.token, "token_handler_mock", self.request) + self.assertIn("id_token", token) + self.assertEqual(token["id_token"], "eyJ.body.signature") + id_token = self.mock_validator.finalize_id_token.call_args[0][0] + self.assertEqual(id_token['aud'], 'abcdef') + self.assertGreaterEqual(id_token['iat'], int(datetime.datetime.now().timestamp())) + + def test_finalize_id_token_with_nonce(self): + token = self.grant.add_id_token(self.token, "token_handler_mock", self.request, "my_nonce") + self.assertIn("id_token", token) + self.assertEqual(token["id_token"], "eyJ.body.signature") + id_token = self.mock_validator.finalize_id_token.call_args[0][0] + self.assertEqual(id_token['nonce'], 'my_nonce') + + def test_finalize_id_token_with_at_hash(self): + self.token["access_token"] = "Qcb0Orv1zh30vL1MPRsbm-diHiMwcLyZvn1arpZv-Jxf_11jnpEX3Tgfvk" + token = self.grant.add_id_token(self.token, "token_handler_mock", self.request) + self.assertIn("id_token", token) + self.assertEqual(token["id_token"], "eyJ.body.signature") + id_token = self.mock_validator.finalize_id_token.call_args[0][0] + self.assertEqual(id_token['at_hash'], 'LDktKdoQak3Pk0cnXxCltA') + + def test_finalize_id_token_with_c_hash(self): + self.token["code"] = "Qcb0Orv1zh30vL1MPRsbm-diHiMwcLyZvn1arpZv-Jxf_11jnpEX3Tgfvk" + token = self.grant.add_id_token(self.token, "token_handler_mock", self.request) + self.assertIn("id_token", token) + self.assertEqual(token["id_token"], "eyJ.body.signature") + id_token = self.mock_validator.finalize_id_token.call_args[0][0] + self.assertEqual(id_token['c_hash'], 'LDktKdoQak3Pk0cnXxCltA') + + def test_finalize_id_token_with_c_and_at_hash(self): + self.token["code"] = "Qcb0Orv1zh30vL1MPRsbm-diHiMwcLyZvn1arpZv-Jxf_11jnpEX3Tgfvk" + self.token["access_token"] = "Qcb0Orv1zh30vL1MPRsbm-diHiMwcLyZvn1arpZv-Jxf_11jnpEX3Tgfvk" + token = self.grant.add_id_token(self.token, "token_handler_mock", self.request) + self.assertIn("id_token", token) + self.assertEqual(token["id_token"], "eyJ.body.signature") + id_token = self.mock_validator.finalize_id_token.call_args[0][0] + self.assertEqual(id_token['at_hash'], 'LDktKdoQak3Pk0cnXxCltA') + self.assertEqual(id_token['c_hash'], 'LDktKdoQak3Pk0cnXxCltA') -- cgit v1.2.1 From ed8c4f253def93a0d4d78a6ead1a63091f8e4c26 Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Tue, 26 Mar 2019 16:32:51 +0100 Subject: Python2.7 compatible --- oauthlib/openid/connect/core/grant_types/base.py | 4 ++-- tests/openid/connect/core/grant_types/test_base.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/oauthlib/openid/connect/core/grant_types/base.py b/oauthlib/openid/connect/core/grant_types/base.py index 6272ea2..32a21b6 100644 --- a/oauthlib/openid/connect/core/grant_types/base.py +++ b/oauthlib/openid/connect/core/grant_types/base.py @@ -1,9 +1,9 @@ from .exceptions import OIDCNoPrompt import base64 -import datetime import hashlib import logging +import time from json import loads from oauthlib.oauth2.rfc6749.errors import ConsentRequired, InvalidRequestError, LoginRequired @@ -108,7 +108,7 @@ class GrantTypeBase(object): # Start with technicals fields bound to the specification. id_token = {} id_token['aud'] = request.client_id - id_token['iat'] = int(datetime.datetime.now().timestamp()) + id_token['iat'] = int(time.time()) # nonce is REQUIRED when response_type value is: # - id_token token (Implicit) diff --git a/tests/openid/connect/core/grant_types/test_base.py b/tests/openid/connect/core/grant_types/test_base.py index 319904b..76e017f 100644 --- a/tests/openid/connect/core/grant_types/test_base.py +++ b/tests/openid/connect/core/grant_types/test_base.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -import datetime import mock +import time from oauthlib.common import Request from oauthlib.openid.connect.core.grant_types.base import GrantTypeBase @@ -68,7 +68,7 @@ class IDTokenTest(TestCase): self.assertEqual(token["id_token"], "eyJ.body.signature") id_token = self.mock_validator.finalize_id_token.call_args[0][0] self.assertEqual(id_token['aud'], 'abcdef') - self.assertGreaterEqual(id_token['iat'], int(datetime.datetime.now().timestamp())) + self.assertGreaterEqual(id_token['iat'], int(time.time())) def test_finalize_id_token_with_nonce(self): token = self.grant.add_id_token(self.token, "token_handler_mock", self.request, "my_nonce") -- cgit v1.2.1 From 71be50afdeaf99a0ba6ce5048851dcdd5620d880 Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Fri, 26 Apr 2019 15:59:57 +0200 Subject: Fix 670. AuthCode API must return the new PKCE attribute --- oauthlib/oauth2/rfc6749/grant_types/authorization_code.py | 3 +++ tests/oauth2/rfc6749/grant_types/test_authorization_code.py | 6 ++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py b/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py index 5f03d9c..9b84c4c 100644 --- a/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py +++ b/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py @@ -405,12 +405,15 @@ class AuthorizationCodeGrant(GrantTypeBase): raise errors.MissingCodeChallengeError(request=request) if request.code_challenge is not None: + request_info["code_challenge"] = request.code_challenge + # OPTIONAL, defaults to "plain" if not present in the request. if request.code_challenge_method is None: request.code_challenge_method = "plain" if request.code_challenge_method not in self._code_challenge_methods: raise errors.UnsupportedCodeChallengeMethodError(request=request) + request_info["code_challenge_method"] = request.code_challenge_method # OPTIONAL. The scope of the access request as described by Section 3.3 # https://tools.ietf.org/html/rfc6749#section-3.3 diff --git a/tests/oauth2/rfc6749/grant_types/test_authorization_code.py b/tests/oauth2/rfc6749/grant_types/test_authorization_code.py index 00e2b6d..2c9db3c 100644 --- a/tests/oauth2/rfc6749/grant_types/test_authorization_code.py +++ b/tests/oauth2/rfc6749/grant_types/test_authorization_code.py @@ -215,8 +215,10 @@ class AuthorizationCodeGrantTest(TestCase): self.mock_validator.is_pkce_required.return_value = required self.request.code_challenge = "present" _, ri = self.auth.validate_authorization_request(self.request) - self.assertIsNotNone(ri["request"].code_challenge_method) - self.assertEqual(ri["request"].code_challenge_method, "plain") + self.assertIn("code_challenge", ri) + self.assertIn("code_challenge_method", ri) + self.assertEqual(ri["code_challenge"], "present") + self.assertEqual(ri["code_challenge_method"], "plain") def test_pkce_wrong_method(self): for required in [True, False]: -- cgit v1.2.1 From 5405ca4cae31146ce2d2c6860b0c46dbbbe879c9 Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Mon, 29 Apr 2019 10:11:24 +0200 Subject: Fix docstring about return value --- oauthlib/openid/connect/core/request_validator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oauthlib/openid/connect/core/request_validator.py b/oauthlib/openid/connect/core/request_validator.py index 7ce7e17..eebab55 100644 --- a/oauthlib/openid/connect/core/request_validator.py +++ b/oauthlib/openid/connect/core/request_validator.py @@ -56,7 +56,7 @@ class RequestValidator(OAuth2RequestValidator): :param client_id: Unicode client identifier :param code: Unicode authorization code grant :param redirect_uri: Unicode absolute URI - :return: A list of scope + :return: Unicode nonce Method is used by: - Authorization Token Grant Dispatcher -- cgit v1.2.1 From 247c89e13bdd017b99f22b154e521084df53d2f0 Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Mon, 29 Apr 2019 10:12:10 +0200 Subject: Fix typo gave/have --- oauthlib/openid/connect/core/request_validator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/oauthlib/openid/connect/core/request_validator.py b/oauthlib/openid/connect/core/request_validator.py index eebab55..344fd7d 100644 --- a/oauthlib/openid/connect/core/request_validator.py +++ b/oauthlib/openid/connect/core/request_validator.py @@ -24,7 +24,7 @@ class RequestValidator(OAuth2RequestValidator): id_token in token response only based on authorization code scopes. Only code param should be sufficient to retrieve grant code from - any storage you are using, `client_id` and `redirect_uri` can gave a + any storage you are using, `client_id` and `redirect_uri` can have a blank value `""` don't forget to check it before using those values in a select query if a database is used. @@ -49,7 +49,7 @@ class RequestValidator(OAuth2RequestValidator): case-sensitive string. Only code param should be sufficient to retrieve grant code from - any storage you are using, `client_id` and `redirect_uri` can gave a + any storage you are using, `client_id` and `redirect_uri` can have a blank value `""` don't forget to check it before using those values in a select query if a database is used. -- cgit v1.2.1 From d4d3f1088dc943a83641c9e86b7a09d98f6adce8 Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Mon, 29 Apr 2019 10:20:39 +0200 Subject: Removed wrong assumption from copy/paste of get_autho.._scopes. This function should always have a good client_id and redirect_uri, because it is called after validate_token_request() --- oauthlib/openid/connect/core/request_validator.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/oauthlib/openid/connect/core/request_validator.py b/oauthlib/openid/connect/core/request_validator.py index 344fd7d..d96c9ef 100644 --- a/oauthlib/openid/connect/core/request_validator.py +++ b/oauthlib/openid/connect/core/request_validator.py @@ -49,9 +49,8 @@ class RequestValidator(OAuth2RequestValidator): case-sensitive string. Only code param should be sufficient to retrieve grant code from - any storage you are using, `client_id` and `redirect_uri` can have a - blank value `""` don't forget to check it before using those values - in a select query if a database is used. + any storage you are using. However, `client_id` and `redirect_uri` + have been validated and can be used also. :param client_id: Unicode client identifier :param code: Unicode authorization code grant -- cgit v1.2.1 From b714c05bcec2e254bbe07298f03f3def7de9179b Mon Sep 17 00:00:00 2001 From: ume Date: Wed, 1 May 2019 00:18:08 +0900 Subject: token_type should be case insensitive --- oauthlib/oauth2/rfc6749/tokens.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/oauthlib/oauth2/rfc6749/tokens.py b/oauthlib/oauth2/rfc6749/tokens.py index 7973923..3587af4 100644 --- a/oauthlib/oauth2/rfc6749/tokens.py +++ b/oauthlib/oauth2/rfc6749/tokens.py @@ -254,7 +254,7 @@ def get_token_from_header(request): if 'Authorization' in request.headers: split_header = request.headers.get('Authorization').split() - if len(split_header) == 2 and split_header[0] == 'Bearer': + if len(split_header) == 2 and split_header[0].lower() == 'bearer': token = split_header[1] else: token = request.access_token @@ -353,7 +353,7 @@ class BearerToken(TokenBase): :param request: OAuthlib request. :type request: oauthlib.common.Request """ - if request.headers.get('Authorization', '').split(' ')[0] == 'Bearer': + if request.headers.get('Authorization', '').split(' ')[0].lower() == 'bearer': return 9 elif request.access_token is not None: return 5 -- cgit v1.2.1 From 73092d039fa67a88d0989e7bf0ae7d0044a0bdc6 Mon Sep 17 00:00:00 2001 From: Josh Holmer Date: Tue, 30 Apr 2019 12:50:43 -0400 Subject: Handle null value in expires_in field in JSON handler Closes #672 --- oauthlib/oauth2/rfc6749/parameters.py | 5 ++++- tests/oauth2/rfc6749/test_parameters.py | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/oauthlib/oauth2/rfc6749/parameters.py b/oauthlib/oauth2/rfc6749/parameters.py index 6b9d630..f8d42db 100644 --- a/oauthlib/oauth2/rfc6749/parameters.py +++ b/oauthlib/oauth2/rfc6749/parameters.py @@ -419,7 +419,10 @@ def parse_token_response(body, scope=None): params['scope'] = scope_to_list(params['scope']) if 'expires_in' in params: - params['expires_at'] = time.time() + int(params['expires_in']) + if params['expires_in'] is None: + params.pop('expires_in') + else: + params['expires_at'] = time.time() + int(params['expires_in']) params = OAuth2Token(params, old_scope=scope) validate_token_parameters(params) diff --git a/tests/oauth2/rfc6749/test_parameters.py b/tests/oauth2/rfc6749/test_parameters.py index c42f516..b9fda9c 100644 --- a/tests/oauth2/rfc6749/test_parameters.py +++ b/tests/oauth2/rfc6749/test_parameters.py @@ -102,6 +102,15 @@ class ParameterTests(TestCase): ' "expires_in": 3600,' ' "refresh_token": "tGzv3JOkF0XG5Qx2TlKWIA",' ' "example_parameter": "example_value" }') + json_response_noexpire = ('{ "access_token": "2YotnFZFEjr1zCsicMWpAA",' + ' "token_type": "example",' + ' "refresh_token": "tGzv3JOkF0XG5Qx2TlKWIA",' + ' "example_parameter": "example_value"}') + json_response_expirenull = ('{ "access_token": "2YotnFZFEjr1zCsicMWpAA",' + ' "token_type": "example",' + ' "expires_in": null,' + ' "refresh_token": "tGzv3JOkF0XG5Qx2TlKWIA",' + ' "example_parameter": "example_value"}') json_custom_error = '{ "error": "incorrect_client_credentials" }' json_error = '{ "error": "access_denied" }' @@ -135,6 +144,13 @@ class ParameterTests(TestCase): 'example_parameter': 'example_value' } + json_noexpire_dict = { + 'access_token': '2YotnFZFEjr1zCsicMWpAA', + 'token_type': 'example', + 'refresh_token': 'tGzv3JOkF0XG5Qx2TlKWIA', + 'example_parameter': 'example_value' + } + json_notype_dict = { 'access_token': '2YotnFZFEjr1zCsicMWpAA', 'expires_in': 3600, @@ -209,6 +225,8 @@ class ParameterTests(TestCase): self.assertEqual(parse_token_response(self.json_response_noscope, scope=['all', 'the', 'scopes']), self.json_noscope_dict) + self.assertEqual(parse_token_response(self.json_response_noexpire), self.json_noexpire_dict) + self.assertEqual(parse_token_response(self.json_response_expirenull), self.json_noexpire_dict) scope_changes_recorded = [] def record_scope_change(sender, message, old, new): -- cgit v1.2.1 From f037c1153e7e8e22299e9169b8d765164855b246 Mon Sep 17 00:00:00 2001 From: Jordan Gardner Date: Wed, 24 Apr 2019 15:09:51 -0600 Subject: Add case-insensitive headers to oauth1 BaseEndpoint --- oauthlib/oauth1/rfc5849/endpoints/base.py | 4 ++-- tests/oauth1/rfc5849/endpoints/test_base.py | 13 ++++++++++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/oauthlib/oauth1/rfc5849/endpoints/base.py b/oauthlib/oauth1/rfc5849/endpoints/base.py index 9702939..ecf8a50 100644 --- a/oauthlib/oauth1/rfc5849/endpoints/base.py +++ b/oauthlib/oauth1/rfc5849/endpoints/base.py @@ -10,7 +10,7 @@ from __future__ import absolute_import, unicode_literals import time -from oauthlib.common import Request, generate_token +from oauthlib.common import CaseInsensitiveDict, Request, generate_token from .. import (CONTENT_TYPE_FORM_URLENCODED, SIGNATURE_HMAC, SIGNATURE_RSA, SIGNATURE_TYPE_AUTH_HEADER, SIGNATURE_TYPE_BODY, @@ -67,7 +67,7 @@ class BaseEndpoint(object): def _create_request(self, uri, http_method, body, headers): # Only include body data from x-www-form-urlencoded requests - headers = headers or {} + headers = CaseInsensitiveDict(headers or {}) if ("Content-Type" in headers and CONTENT_TYPE_FORM_URLENCODED in headers["Content-Type"]): request = Request(uri, http_method, body, headers) diff --git a/tests/oauth1/rfc5849/endpoints/test_base.py b/tests/oauth1/rfc5849/endpoints/test_base.py index 60f7860..795ddee 100644 --- a/tests/oauth1/rfc5849/endpoints/test_base.py +++ b/tests/oauth1/rfc5849/endpoints/test_base.py @@ -4,7 +4,7 @@ from re import sub from mock import MagicMock -from oauthlib.common import safe_string_equals +from oauthlib.common import CaseInsensitiveDict, safe_string_equals from oauthlib.oauth1 import Client, RequestValidator from oauthlib.oauth1.rfc5849 import (SIGNATURE_HMAC, SIGNATURE_PLAINTEXT, SIGNATURE_RSA, errors) @@ -179,6 +179,17 @@ class BaseEndpointTest(TestCase): self.assertRaises(errors.InvalidRequestError, e._check_mandatory_parameters, r) + def test_case_insensitive_headers(self): + """Ensure headers are case-insensitive""" + v = RequestValidator() + e = BaseEndpoint(v) + r = e._create_request('https://a.b', 'POST', + ('oauth_signature=a&oauth_consumer_key=b&oauth_nonce=c&' + 'oauth_version=1.0&oauth_signature_method=RSA-SHA1&' + 'oauth_timestamp=123456789a'), + URLENCODED) + self.assertIsInstance(r.headers, CaseInsensitiveDict) + def test_signature_method_validation(self): """Ensure valid signature method is used.""" -- cgit v1.2.1 From d4a3a419b1aed1a370b29785d3121f4877d4cd54 Mon Sep 17 00:00:00 2001 From: "Y.Umezaki" Date: Tue, 7 May 2019 18:53:06 +0900 Subject: Add token tests from #491 --- tests/oauth2/rfc6749/test_tokens.py | 51 +++++++++++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/tests/oauth2/rfc6749/test_tokens.py b/tests/oauth2/rfc6749/test_tokens.py index 061754f..95e8f6c 100644 --- a/tests/oauth2/rfc6749/test_tokens.py +++ b/tests/oauth2/rfc6749/test_tokens.py @@ -1,10 +1,14 @@ from __future__ import absolute_import, unicode_literals +import mock + +from oauthlib.common import Request from oauthlib.oauth2.rfc6749.tokens import ( - prepare_mac_header, - prepare_bearer_headers, + BearerToken, prepare_bearer_body, + prepare_bearer_headers, prepare_bearer_uri, + prepare_mac_header, ) from ...unittest import TestCase @@ -98,3 +102,46 @@ class TokenTest(TestCase): self.assertEqual(prepare_bearer_headers(self.token), self.bearer_headers) self.assertEqual(prepare_bearer_body(self.token), self.bearer_body) self.assertEqual(prepare_bearer_uri(self.token, uri=self.uri), self.bearer_uri) + + def test_fake_bearer_is_not_validated(self): + request_validator = mock.MagicMock() + request_validator.validate_bearer_token = self._mocked_validate_bearer_token + + for fake_header in self.fake_bearer_headers: + request = Request("/", headers=fake_header) + result = BearerToken(request_validator=request_validator).validate_request( + request + ) + + self.assertFalse(result) + + def test_header_with_multispaces_is_validated(self): + request_validator = mock.MagicMock() + request_validator.validate_bearer_token = self._mocked_validate_bearer_token + + request = Request("/", headers=self.valid_header_with_multiple_spaces) + result = BearerToken(request_validator=request_validator).validate_request( + request + ) + + self.assertTrue(result) + + def test_estimate_type_with_fake_header_returns_type_0(self): + request_validator = mock.MagicMock() + request_validator.validate_bearer_token = self._mocked_validate_bearer_token + + for fake_header in self.fake_bearer_headers: + request = Request("/", headers=fake_header) + result = BearerToken(request_validator=request_validator).estimate_type( + request + ) + + if ( + fake_header["Authorization"].count(" ") == 2 + and fake_header["Authorization"].split()[0] == "Bearer" + ): + # If we're dealing with the header containing 2 spaces, it will be recognized + # as a Bearer valid header, the token itself will be invalid by the way. + self.assertEqual(result, 9) + else: + self.assertEqual(result, 0) -- cgit v1.2.1 From 056383bf96892b7428b5de17bb2011374fe1c7bf Mon Sep 17 00:00:00 2001 From: "Y.Umezaki" Date: Tue, 7 May 2019 19:10:14 +0900 Subject: Add valid testcase --- tests/oauth2/rfc6749/test_tokens.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/oauth2/rfc6749/test_tokens.py b/tests/oauth2/rfc6749/test_tokens.py index 95e8f6c..e6f49b1 100644 --- a/tests/oauth2/rfc6749/test_tokens.py +++ b/tests/oauth2/rfc6749/test_tokens.py @@ -68,6 +68,7 @@ class TokenTest(TestCase): bearer_headers = { 'Authorization': 'Bearer vF9dft4qmT' } + valid_bearer_header_lowercase = {"Authorization": "bearer vF9dft4qmT"} fake_bearer_headers = [ {'Authorization': 'Beaver vF9dft4qmT'}, {'Authorization': 'BeavervF9dft4qmT'}, @@ -103,6 +104,26 @@ class TokenTest(TestCase): self.assertEqual(prepare_bearer_body(self.token), self.bearer_body) self.assertEqual(prepare_bearer_uri(self.token, uri=self.uri), self.bearer_uri) + def test_valid_bearer_is_validated(self): + request_validator = mock.MagicMock() + request_validator.validate_bearer_token = self._mocked_validate_bearer_token + + request = Request("/", headers=self.bearer_headers) + result = BearerToken(request_validator=request_validator).validate_request( + request + ) + self.assertTrue(result) + + def test_lowercase_bearer_is_validated(self): + request_validator = mock.MagicMock() + request_validator.validate_bearer_token = self._mocked_validate_bearer_token + + request = Request("/", headers=self.valid_bearer_header_lowercase) + result = BearerToken(request_validator=request_validator).validate_request( + request + ) + self.assertTrue(result) + def test_fake_bearer_is_not_validated(self): request_validator = mock.MagicMock() request_validator.validate_bearer_token = self._mocked_validate_bearer_token @@ -126,6 +147,13 @@ class TokenTest(TestCase): self.assertTrue(result) + def test_estimate_type(self): + request_validator = mock.MagicMock() + request_validator.validate_bearer_token = self._mocked_validate_bearer_token + request = Request("/", headers=self.bearer_headers) + result = BearerToken(request_validator=request_validator).estimate_type(request) + self.assertEqual(result, 9) + def test_estimate_type_with_fake_header_returns_type_0(self): request_validator = mock.MagicMock() request_validator.validate_bearer_token = self._mocked_validate_bearer_token -- cgit v1.2.1 From bbbcca731d5db16d7b1765070880aa54288788e9 Mon Sep 17 00:00:00 2001 From: Abhishek Patel <5524161+Abhishek8394@users.noreply.github.com> Date: Mon, 6 May 2019 22:28:23 -0700 Subject: Add validation check for presence of forbidden query parameters in OAuth2 TokenEndpoint, IntrospectionEndpoint and RevocationEndpoint --- oauthlib/oauth2/rfc6749/endpoints/base.py | 12 ++++++++++++ oauthlib/oauth2/rfc6749/endpoints/introspect.py | 1 + oauthlib/oauth2/rfc6749/endpoints/revocation.py | 1 + oauthlib/oauth2/rfc6749/endpoints/token.py | 5 ++++- 4 files changed, 18 insertions(+), 1 deletion(-) diff --git a/oauthlib/oauth2/rfc6749/endpoints/base.py b/oauthlib/oauth2/rfc6749/endpoints/base.py index c0fc726..29086e4 100644 --- a/oauthlib/oauth2/rfc6749/endpoints/base.py +++ b/oauthlib/oauth2/rfc6749/endpoints/base.py @@ -15,6 +15,8 @@ from ..errors import (FatalClientError, OAuth2Error, ServerError, TemporarilyUnavailableError, InvalidRequestError, InvalidClientError, UnsupportedTokenTypeError) +from oauthlib.common import CaseInsensitiveDict + log = logging.getLogger(__name__) @@ -23,6 +25,7 @@ class BaseEndpoint(object): def __init__(self): self._available = True self._catch_errors = False + self._blacklist_query_params = {'client_secret', 'code_verifier'} @property def available(self): @@ -62,6 +65,15 @@ class BaseEndpoint(object): request.token_type_hint not in self.supported_token_types): raise UnsupportedTokenTypeError(request=request) + def _raise_on_bad_post_request(self, request): + """Raise if invalid POST request received + """ + if request.http_method.lower() == 'post': + query_params = CaseInsensitiveDict(urldecode(request.uri_query)) + for k in self._blacklist_query_params: + if k in query_params: + raise InvalidRequestError(request=request, + description='Query parameters not allowed') def catch_errors_and_unavailability(f): @functools.wraps(f) diff --git a/oauthlib/oauth2/rfc6749/endpoints/introspect.py b/oauthlib/oauth2/rfc6749/endpoints/introspect.py index 47022fd..547e7db 100644 --- a/oauthlib/oauth2/rfc6749/endpoints/introspect.py +++ b/oauthlib/oauth2/rfc6749/endpoints/introspect.py @@ -117,6 +117,7 @@ class IntrospectEndpoint(BaseEndpoint): .. _`section 1.5`: http://tools.ietf.org/html/rfc6749#section-1.5 .. _`RFC6749`: http://tools.ietf.org/html/rfc6749 """ + self._raise_on_bad_post_request(request) self._raise_on_missing_token(request) self._raise_on_invalid_client(request) self._raise_on_unsupported_token(request) diff --git a/oauthlib/oauth2/rfc6749/endpoints/revocation.py b/oauthlib/oauth2/rfc6749/endpoints/revocation.py index fda3f30..1439491 100644 --- a/oauthlib/oauth2/rfc6749/endpoints/revocation.py +++ b/oauthlib/oauth2/rfc6749/endpoints/revocation.py @@ -121,6 +121,7 @@ class RevocationEndpoint(BaseEndpoint): .. _`Section 4.1.2`: https://tools.ietf.org/html/draft-ietf-oauth-revocation-11#section-4.1.2 .. _`RFC6749`: https://tools.ietf.org/html/rfc6749 """ + self._raise_on_bad_post_request(request) self._raise_on_missing_token(request) self._raise_on_invalid_client(request) self._raise_on_unsupported_token(request) diff --git a/oauthlib/oauth2/rfc6749/endpoints/token.py b/oauthlib/oauth2/rfc6749/endpoints/token.py index 90fb16f..223e8d0 100644 --- a/oauthlib/oauth2/rfc6749/endpoints/token.py +++ b/oauthlib/oauth2/rfc6749/endpoints/token.py @@ -91,7 +91,7 @@ class TokenEndpoint(BaseEndpoint): """Extract grant_type and route to the designated handler.""" request = Request( uri, http_method=http_method, body=body, headers=headers) - + self.validate_token_request(request) # 'scope' is an allowed Token Request param in both the "Resource Owner Password Credentials Grant" # and "Client Credentials Grant" flows # https://tools.ietf.org/html/rfc6749#section-4.3.2 @@ -115,3 +115,6 @@ class TokenEndpoint(BaseEndpoint): request.grant_type, grant_type_handler) return grant_type_handler.create_token_response( request, self.default_token_type) + + def validate_token_request(self, request): + self._raise_on_bad_post_request(request) -- cgit v1.2.1 From 047ceccf48ea7ccd4ecc6b48a8ddb6dd4a14abd6 Mon Sep 17 00:00:00 2001 From: Abhishek Patel <5524161+Abhishek8394@users.noreply.github.com> Date: Mon, 6 May 2019 23:26:29 -0700 Subject: Add tests + create a global variable for blacklisted query parameters --- oauthlib/oauth2/rfc6749/endpoints/base.py | 16 +++++++------ .../rfc6749/endpoints/test_error_responses.py | 27 ++++++++++++++++++++++ .../rfc6749/endpoints/test_introspect_endpoint.py | 16 +++++++++++++ .../rfc6749/endpoints/test_revocation_endpoint.py | 16 +++++++++++++ 4 files changed, 68 insertions(+), 7 deletions(-) diff --git a/oauthlib/oauth2/rfc6749/endpoints/base.py b/oauthlib/oauth2/rfc6749/endpoints/base.py index 29086e4..dc3204b 100644 --- a/oauthlib/oauth2/rfc6749/endpoints/base.py +++ b/oauthlib/oauth2/rfc6749/endpoints/base.py @@ -15,17 +15,18 @@ from ..errors import (FatalClientError, OAuth2Error, ServerError, TemporarilyUnavailableError, InvalidRequestError, InvalidClientError, UnsupportedTokenTypeError) -from oauthlib.common import CaseInsensitiveDict +from oauthlib.common import CaseInsensitiveDict, urldecode log = logging.getLogger(__name__) +BLACKLIST_QUERY_PARAMS = {'client_secret', 'code_verifier'} class BaseEndpoint(object): def __init__(self): self._available = True self._catch_errors = False - self._blacklist_query_params = {'client_secret', 'code_verifier'} + self._blacklist_query_params = BLACKLIST_QUERY_PARAMS @property def available(self): @@ -33,7 +34,7 @@ class BaseEndpoint(object): @available.setter def available(self, available): - self._available = available + self._available = available @property def catch_errors(self): @@ -69,11 +70,12 @@ class BaseEndpoint(object): """Raise if invalid POST request received """ if request.http_method.lower() == 'post': - query_params = CaseInsensitiveDict(urldecode(request.uri_query)) - for k in self._blacklist_query_params: - if k in query_params: + query_params = CaseInsensitiveDict(dict(urldecode(request.uri_query))) + for param in self._blacklist_query_params: + if param in query_params: raise InvalidRequestError(request=request, - description='Query parameters not allowed') + description=('"%s" is not allowed as a url query' +\ + ' parameter') % (param)) def catch_errors_and_unavailability(f): @functools.wraps(f) diff --git a/tests/oauth2/rfc6749/endpoints/test_error_responses.py b/tests/oauth2/rfc6749/endpoints/test_error_responses.py index a249cb1..4a288ad 100644 --- a/tests/oauth2/rfc6749/endpoints/test_error_responses.py +++ b/tests/oauth2/rfc6749/endpoints/test_error_responses.py @@ -6,10 +6,12 @@ import json import mock +from oauthlib.common import urlencode from oauthlib.oauth2 import (BackendApplicationServer, LegacyApplicationServer, MobileApplicationServer, RequestValidator, WebApplicationServer) from oauthlib.oauth2.rfc6749 import errors +from oauthlib.oauth2.rfc6749.endpoints.base import BLACKLIST_QUERY_PARAMS from ....unittest import TestCase @@ -437,3 +439,28 @@ class ErrorResponseTest(TestCase): _, body, _ = self.backend.create_token_response('https://i.b/token', body='grant_type=bar') self.assertEqual('unsupported_grant_type', json.loads(body)['error']) + + def test_invalid_post_request(self): + self.validator.authenticate_client.side_effect = self.set_client + for param in BLACKLIST_QUERY_PARAMS: + uri = 'https://i/b/token?' + urlencode([(param, 'secret')]) + _, body, s = self.web.create_introspect_response(uri, + body='grant_type=access_token&code=123') + self.assertEqual(json.loads(body)['error'], 'invalid_request') + self.assertIn(param, json.loads(body)['error_description']) + self.assertIn('not allowed', json.loads(body)['error_description']) + self.assertEqual(s, 400) + + _, body, s = self.legacy.create_introspect_response(uri, + body='grant_type=access_token&code=123') + self.assertEqual(json.loads(body)['error'], 'invalid_request') + self.assertIn(param, json.loads(body)['error_description']) + self.assertIn('not allowed', json.loads(body)['error_description']) + self.assertEqual(s, 400) + + _, body, s = self.backend.create_introspect_response(uri, + body='grant_type=access_token&code=123') + self.assertEqual(json.loads(body)['error'], 'invalid_request') + self.assertIn(param, json.loads(body)['error_description']) + self.assertIn('not allowed', json.loads(body)['error_description']) + self.assertEqual(s, 400) diff --git a/tests/oauth2/rfc6749/endpoints/test_introspect_endpoint.py b/tests/oauth2/rfc6749/endpoints/test_introspect_endpoint.py index b9bf76a..234a4ef 100644 --- a/tests/oauth2/rfc6749/endpoints/test_introspect_endpoint.py +++ b/tests/oauth2/rfc6749/endpoints/test_introspect_endpoint.py @@ -7,6 +7,7 @@ from mock import MagicMock from oauthlib.common import urlencode from oauthlib.oauth2 import RequestValidator, IntrospectEndpoint +from oauthlib.oauth2.rfc6749.endpoints.base import BLACKLIST_QUERY_PARAMS from ....unittest import TestCase @@ -139,3 +140,18 @@ class IntrospectEndpointTest(TestCase): self.assertEqual(h, self.resp_h) self.assertEqual(loads(b)['error'], 'invalid_request') self.assertEqual(s, 400) + + def test_introspect_bad_post_request(self): + endpoint = IntrospectEndpoint(self.validator, + supported_token_types=['access_token']) + for param in BLACKLIST_QUERY_PARAMS: + uri = 'http://some.endpoint?' + urlencode([(param, 'secret')]) + body = urlencode([('token', 'foo'), + ('token_type_hint', 'access_token')]) + h, b, s = endpoint.create_introspect_response(uri, + headers=self.headers, body=body) + self.assertEqual(h, self.resp_h) + self.assertEqual(loads(b)['error'], 'invalid_request') + self.assertIn(param, loads(b)['error_description']) + self.assertIn('not allowed', loads(b)['error_description']) + self.assertEqual(s, 400) diff --git a/tests/oauth2/rfc6749/endpoints/test_revocation_endpoint.py b/tests/oauth2/rfc6749/endpoints/test_revocation_endpoint.py index 2a24177..e89c3bd 100644 --- a/tests/oauth2/rfc6749/endpoints/test_revocation_endpoint.py +++ b/tests/oauth2/rfc6749/endpoints/test_revocation_endpoint.py @@ -7,6 +7,7 @@ from mock import MagicMock from oauthlib.common import urlencode from oauthlib.oauth2 import RequestValidator, RevocationEndpoint +from oauthlib.oauth2.rfc6749.endpoints.base import BLACKLIST_QUERY_PARAMS from ....unittest import TestCase @@ -120,3 +121,18 @@ class RevocationEndpointTest(TestCase): self.assertEqual(h, self.resp_h) self.assertEqual(loads(b)['error'], 'invalid_request') self.assertEqual(s, 400) + + def test_revoke_bad_post_request(self): + endpoint = RevocationEndpoint(self.validator, + supported_token_types=['access_token']) + for param in BLACKLIST_QUERY_PARAMS: + uri = 'http://some.endpoint?' + urlencode([(param, 'secret')]) + body = urlencode([('token', 'foo'), + ('token_type_hint', 'access_token')]) + h, b, s = endpoint.create_revocation_response(uri, + headers=self.headers, body=body) + self.assertEqual(h, self.resp_h) + self.assertEqual(loads(b)['error'], 'invalid_request') + self.assertIn(param, loads(b)['error_description']) + self.assertIn('not allowed', loads(b)['error_description']) + self.assertEqual(s, 400) -- cgit v1.2.1 From ee06f0f3349d7fd656d35a2eef40ee18fb74e303 Mon Sep 17 00:00:00 2001 From: Abhishek Patel <5524161+Abhishek8394@users.noreply.github.com> Date: Sun, 12 May 2019 20:35:00 -0700 Subject: Ban all query parameters on Intropspection, Token and Revocation endpopoint --- oauthlib/oauth2/rfc6749/endpoints/base.py | 12 ++++-------- tests/oauth2/rfc6749/endpoints/test_error_responses.py | 12 ++++-------- tests/oauth2/rfc6749/endpoints/test_introspect_endpoint.py | 11 +++++------ tests/oauth2/rfc6749/endpoints/test_revocation_endpoint.py | 6 ++---- 4 files changed, 15 insertions(+), 26 deletions(-) diff --git a/oauthlib/oauth2/rfc6749/endpoints/base.py b/oauthlib/oauth2/rfc6749/endpoints/base.py index dc3204b..c99c22d 100644 --- a/oauthlib/oauth2/rfc6749/endpoints/base.py +++ b/oauthlib/oauth2/rfc6749/endpoints/base.py @@ -19,14 +19,12 @@ from oauthlib.common import CaseInsensitiveDict, urldecode log = logging.getLogger(__name__) -BLACKLIST_QUERY_PARAMS = {'client_secret', 'code_verifier'} class BaseEndpoint(object): def __init__(self): self._available = True self._catch_errors = False - self._blacklist_query_params = BLACKLIST_QUERY_PARAMS @property def available(self): @@ -70,12 +68,10 @@ class BaseEndpoint(object): """Raise if invalid POST request received """ if request.http_method.lower() == 'post': - query_params = CaseInsensitiveDict(dict(urldecode(request.uri_query))) - for param in self._blacklist_query_params: - if param in query_params: - raise InvalidRequestError(request=request, - description=('"%s" is not allowed as a url query' +\ - ' parameter') % (param)) + query_params = request.uri_query or "" + if query_params: + raise InvalidRequestError(request=request, + description=('URL query parameters are not allowed')) def catch_errors_and_unavailability(f): @functools.wraps(f) diff --git a/tests/oauth2/rfc6749/endpoints/test_error_responses.py b/tests/oauth2/rfc6749/endpoints/test_error_responses.py index 4a288ad..2b87032 100644 --- a/tests/oauth2/rfc6749/endpoints/test_error_responses.py +++ b/tests/oauth2/rfc6749/endpoints/test_error_responses.py @@ -11,7 +11,6 @@ from oauthlib.oauth2 import (BackendApplicationServer, LegacyApplicationServer, MobileApplicationServer, RequestValidator, WebApplicationServer) from oauthlib.oauth2.rfc6749 import errors -from oauthlib.oauth2.rfc6749.endpoints.base import BLACKLIST_QUERY_PARAMS from ....unittest import TestCase @@ -442,25 +441,22 @@ class ErrorResponseTest(TestCase): def test_invalid_post_request(self): self.validator.authenticate_client.side_effect = self.set_client - for param in BLACKLIST_QUERY_PARAMS: + for param in ['token', 'secret', 'code', 'foo']: uri = 'https://i/b/token?' + urlencode([(param, 'secret')]) _, body, s = self.web.create_introspect_response(uri, body='grant_type=access_token&code=123') self.assertEqual(json.loads(body)['error'], 'invalid_request') - self.assertIn(param, json.loads(body)['error_description']) - self.assertIn('not allowed', json.loads(body)['error_description']) + self.assertIn('query parameters are not allowed', json.loads(body)['error_description']) self.assertEqual(s, 400) _, body, s = self.legacy.create_introspect_response(uri, body='grant_type=access_token&code=123') self.assertEqual(json.loads(body)['error'], 'invalid_request') - self.assertIn(param, json.loads(body)['error_description']) - self.assertIn('not allowed', json.loads(body)['error_description']) + self.assertIn('query parameters are not allowed', json.loads(body)['error_description']) self.assertEqual(s, 400) _, body, s = self.backend.create_introspect_response(uri, body='grant_type=access_token&code=123') self.assertEqual(json.loads(body)['error'], 'invalid_request') - self.assertIn(param, json.loads(body)['error_description']) - self.assertIn('not allowed', json.loads(body)['error_description']) + self.assertIn('query parameters are not allowed', json.loads(body)['error_description']) self.assertEqual(s, 400) diff --git a/tests/oauth2/rfc6749/endpoints/test_introspect_endpoint.py b/tests/oauth2/rfc6749/endpoints/test_introspect_endpoint.py index 234a4ef..a34c970 100644 --- a/tests/oauth2/rfc6749/endpoints/test_introspect_endpoint.py +++ b/tests/oauth2/rfc6749/endpoints/test_introspect_endpoint.py @@ -7,7 +7,6 @@ from mock import MagicMock from oauthlib.common import urlencode from oauthlib.oauth2 import RequestValidator, IntrospectEndpoint -from oauthlib.oauth2.rfc6749.endpoints.base import BLACKLIST_QUERY_PARAMS from ....unittest import TestCase @@ -144,14 +143,14 @@ class IntrospectEndpointTest(TestCase): def test_introspect_bad_post_request(self): endpoint = IntrospectEndpoint(self.validator, supported_token_types=['access_token']) - for param in BLACKLIST_QUERY_PARAMS: + for param in ['token', 'secret', 'code', 'foo']: uri = 'http://some.endpoint?' + urlencode([(param, 'secret')]) body = urlencode([('token', 'foo'), ('token_type_hint', 'access_token')]) - h, b, s = endpoint.create_introspect_response(uri, - headers=self.headers, body=body) + h, b, s = endpoint.create_introspect_response( + uri, + headers=self.headers, body=body) self.assertEqual(h, self.resp_h) self.assertEqual(loads(b)['error'], 'invalid_request') - self.assertIn(param, loads(b)['error_description']) - self.assertIn('not allowed', loads(b)['error_description']) + self.assertIn('query parameters are not allowed', loads(b)['error_description']) self.assertEqual(s, 400) diff --git a/tests/oauth2/rfc6749/endpoints/test_revocation_endpoint.py b/tests/oauth2/rfc6749/endpoints/test_revocation_endpoint.py index e89c3bd..c73a1ef 100644 --- a/tests/oauth2/rfc6749/endpoints/test_revocation_endpoint.py +++ b/tests/oauth2/rfc6749/endpoints/test_revocation_endpoint.py @@ -7,7 +7,6 @@ from mock import MagicMock from oauthlib.common import urlencode from oauthlib.oauth2 import RequestValidator, RevocationEndpoint -from oauthlib.oauth2.rfc6749.endpoints.base import BLACKLIST_QUERY_PARAMS from ....unittest import TestCase @@ -125,7 +124,7 @@ class RevocationEndpointTest(TestCase): def test_revoke_bad_post_request(self): endpoint = RevocationEndpoint(self.validator, supported_token_types=['access_token']) - for param in BLACKLIST_QUERY_PARAMS: + for param in ['token', 'secret', 'code', 'foo']: uri = 'http://some.endpoint?' + urlencode([(param, 'secret')]) body = urlencode([('token', 'foo'), ('token_type_hint', 'access_token')]) @@ -133,6 +132,5 @@ class RevocationEndpointTest(TestCase): headers=self.headers, body=body) self.assertEqual(h, self.resp_h) self.assertEqual(loads(b)['error'], 'invalid_request') - self.assertIn(param, loads(b)['error_description']) - self.assertIn('not allowed', loads(b)['error_description']) + self.assertIn('query parameters are not allowed', loads(b)['error_description']) self.assertEqual(s, 400) -- cgit v1.2.1 From 056948ac7c14a435d0b65dd27692fe2494bc3743 Mon Sep 17 00:00:00 2001 From: Abhishek Patel <5524161+Abhishek8394@users.noreply.github.com> Date: Tue, 14 May 2019 00:36:10 -0700 Subject: Enforce POST HTTP method on TokenEndpoint, IntrospectEndpoint and RevocationEndpoint - Add validation checks for HTTP method in TokenEndpoint, IntrospectEndpoint and RevocationEndpoint. - CHANGE DEFAULT HTTP method for TokenEndpoint from 'GET' to 'POST'. - Add tests + Fix an old test in . It used to send query params to TokenEndpoint which is not allowed anymore. Fixed it so payload is sent as POST body. --- oauthlib/oauth2/rfc6749/endpoints/base.py | 21 ++++++- oauthlib/oauth2/rfc6749/endpoints/introspect.py | 2 + oauthlib/oauth2/rfc6749/endpoints/revocation.py | 2 + oauthlib/oauth2/rfc6749/endpoints/token.py | 5 +- .../oauth2/rfc6749/endpoints/test_base_endpoint.py | 2 +- .../rfc6749/endpoints/test_error_responses.py | 66 ++++++++++++++++------ .../rfc6749/endpoints/test_introspect_endpoint.py | 15 +++++ .../rfc6749/endpoints/test_revocation_endpoint.py | 15 +++++ 8 files changed, 107 insertions(+), 21 deletions(-) diff --git a/oauthlib/oauth2/rfc6749/endpoints/base.py b/oauthlib/oauth2/rfc6749/endpoints/base.py index c99c22d..e39232f 100644 --- a/oauthlib/oauth2/rfc6749/endpoints/base.py +++ b/oauthlib/oauth2/rfc6749/endpoints/base.py @@ -25,6 +25,18 @@ class BaseEndpoint(object): def __init__(self): self._available = True self._catch_errors = False + self._valid_request_methods = None + + @property + def valid_request_methods(self): + return self._valid_request_methods + + @valid_request_methods.setter + def valid_request_methods(self, valid_request_methods): + if valid_request_methods is not None: + valid_request_methods = [x.upper() for x in valid_request_methods] + self._valid_request_methods = valid_request_methods + @property def available(self): @@ -64,10 +76,17 @@ class BaseEndpoint(object): request.token_type_hint not in self.supported_token_types): raise UnsupportedTokenTypeError(request=request) + def _raise_on_bad_method(self, request): + if self.valid_request_methods is None: + raise ValueError('Configure "valid_request_methods" property first') + if request.http_method.upper() not in self.valid_request_methods: + raise InvalidRequestError(request=request, + description=('Unsupported request method %s' % request.http_method.upper())) + def _raise_on_bad_post_request(self, request): """Raise if invalid POST request received """ - if request.http_method.lower() == 'post': + if request.http_method.upper() == 'POST': query_params = request.uri_query or "" if query_params: raise InvalidRequestError(request=request, diff --git a/oauthlib/oauth2/rfc6749/endpoints/introspect.py b/oauthlib/oauth2/rfc6749/endpoints/introspect.py index 547e7db..4accbdc 100644 --- a/oauthlib/oauth2/rfc6749/endpoints/introspect.py +++ b/oauthlib/oauth2/rfc6749/endpoints/introspect.py @@ -39,6 +39,7 @@ class IntrospectEndpoint(BaseEndpoint): """ valid_token_types = ('access_token', 'refresh_token') + valid_request_methods = ('POST',) def __init__(self, request_validator, supported_token_types=None): BaseEndpoint.__init__(self) @@ -117,6 +118,7 @@ class IntrospectEndpoint(BaseEndpoint): .. _`section 1.5`: http://tools.ietf.org/html/rfc6749#section-1.5 .. _`RFC6749`: http://tools.ietf.org/html/rfc6749 """ + self._raise_on_bad_method(request) self._raise_on_bad_post_request(request) self._raise_on_missing_token(request) self._raise_on_invalid_client(request) diff --git a/oauthlib/oauth2/rfc6749/endpoints/revocation.py b/oauthlib/oauth2/rfc6749/endpoints/revocation.py index 1439491..1fabd03 100644 --- a/oauthlib/oauth2/rfc6749/endpoints/revocation.py +++ b/oauthlib/oauth2/rfc6749/endpoints/revocation.py @@ -28,6 +28,7 @@ class RevocationEndpoint(BaseEndpoint): """ valid_token_types = ('access_token', 'refresh_token') + valid_request_methods = ('POST',) def __init__(self, request_validator, supported_token_types=None, enable_jsonp=False): @@ -121,6 +122,7 @@ class RevocationEndpoint(BaseEndpoint): .. _`Section 4.1.2`: https://tools.ietf.org/html/draft-ietf-oauth-revocation-11#section-4.1.2 .. _`RFC6749`: https://tools.ietf.org/html/rfc6749 """ + self._raise_on_bad_method(request) self._raise_on_bad_post_request(request) self._raise_on_missing_token(request) self._raise_on_invalid_client(request) diff --git a/oauthlib/oauth2/rfc6749/endpoints/token.py b/oauthlib/oauth2/rfc6749/endpoints/token.py index 223e8d0..bc87e9b 100644 --- a/oauthlib/oauth2/rfc6749/endpoints/token.py +++ b/oauthlib/oauth2/rfc6749/endpoints/token.py @@ -62,6 +62,8 @@ class TokenEndpoint(BaseEndpoint): .. _`Appendix B`: https://tools.ietf.org/html/rfc6749#appendix-B """ + valid_request_methods = ('POST',) + def __init__(self, default_grant_type, default_token_type, grant_types): BaseEndpoint.__init__(self) self._grant_types = grant_types @@ -85,7 +87,7 @@ class TokenEndpoint(BaseEndpoint): return self._default_token_type @catch_errors_and_unavailability - def create_token_response(self, uri, http_method='GET', body=None, + def create_token_response(self, uri, http_method='POST', body=None, headers=None, credentials=None, grant_type_for_scope=None, claims=None): """Extract grant_type and route to the designated handler.""" @@ -117,4 +119,5 @@ class TokenEndpoint(BaseEndpoint): request, self.default_token_type) def validate_token_request(self, request): + self._raise_on_bad_method(request) self._raise_on_bad_post_request(request) diff --git a/tests/oauth2/rfc6749/endpoints/test_base_endpoint.py b/tests/oauth2/rfc6749/endpoints/test_base_endpoint.py index 4f78d9b..bf04a42 100644 --- a/tests/oauth2/rfc6749/endpoints/test_base_endpoint.py +++ b/tests/oauth2/rfc6749/endpoints/test_base_endpoint.py @@ -25,7 +25,7 @@ class BaseEndpointTest(TestCase): server = Server(validator) server.catch_errors = True h, b, s = server.create_token_response( - 'https://example.com?grant_type=authorization_code&code=abc' + 'https://example.com', body='grant_type=authorization_code&code=abc' ) self.assertIn("server_error", b) self.assertEqual(s, 500) diff --git a/tests/oauth2/rfc6749/endpoints/test_error_responses.py b/tests/oauth2/rfc6749/endpoints/test_error_responses.py index 2b87032..2479836 100644 --- a/tests/oauth2/rfc6749/endpoints/test_error_responses.py +++ b/tests/oauth2/rfc6749/endpoints/test_error_responses.py @@ -11,7 +11,6 @@ from oauthlib.oauth2 import (BackendApplicationServer, LegacyApplicationServer, MobileApplicationServer, RequestValidator, WebApplicationServer) from oauthlib.oauth2.rfc6749 import errors - from ....unittest import TestCase @@ -439,24 +438,55 @@ class ErrorResponseTest(TestCase): body='grant_type=bar') self.assertEqual('unsupported_grant_type', json.loads(body)['error']) + def test_invalid_request_method(self): + test_methods = ['GET', 'pUt', 'dEleTe', 'paTcH'] + test_methods = test_methods + [x.lower() for x in test_methods] + [x.upper() for x in test_methods] + for method in test_methods: + self.validator.authenticate_client.side_effect = self.set_client + + uri = "http://i/b/token/" + try: + _, body, s = self.web.create_token_response(uri, + body='grant_type=access_token&code=123', http_method=method) + self.fail('This should have failed with InvalidRequestError') + except errors.InvalidRequestError as ire: + self.assertIn('Unsupported request method', ire.description) + + try: + _, body, s = self.legacy.create_token_response(uri, + body='grant_type=access_token&code=123', http_method=method) + self.fail('This should have failed with InvalidRequestError') + except errors.InvalidRequestError as ire: + self.assertIn('Unsupported request method', ire.description) + + try: + _, body, s = self.backend.create_token_response(uri, + body='grant_type=access_token&code=123', http_method=method) + self.fail('This should have failed with InvalidRequestError') + except errors.InvalidRequestError as ire: + self.assertIn('Unsupported request method', ire.description) + def test_invalid_post_request(self): self.validator.authenticate_client.side_effect = self.set_client for param in ['token', 'secret', 'code', 'foo']: uri = 'https://i/b/token?' + urlencode([(param, 'secret')]) - _, body, s = self.web.create_introspect_response(uri, - body='grant_type=access_token&code=123') - self.assertEqual(json.loads(body)['error'], 'invalid_request') - self.assertIn('query parameters are not allowed', json.loads(body)['error_description']) - self.assertEqual(s, 400) - - _, body, s = self.legacy.create_introspect_response(uri, - body='grant_type=access_token&code=123') - self.assertEqual(json.loads(body)['error'], 'invalid_request') - self.assertIn('query parameters are not allowed', json.loads(body)['error_description']) - self.assertEqual(s, 400) - - _, body, s = self.backend.create_introspect_response(uri, - body='grant_type=access_token&code=123') - self.assertEqual(json.loads(body)['error'], 'invalid_request') - self.assertIn('query parameters are not allowed', json.loads(body)['error_description']) - self.assertEqual(s, 400) + try: + _, body, s = self.web.create_token_response(uri, + body='grant_type=access_token&code=123') + self.fail('This should have failed with InvalidRequestError') + except errors.InvalidRequestError as ire: + self.assertIn('URL query parameters are not allowed', ire.description) + + try: + _, body, s = self.legacy.create_token_response(uri, + body='grant_type=access_token&code=123') + self.fail('This should have failed with InvalidRequestError') + except errors.InvalidRequestError as ire: + self.assertIn('URL query parameters are not allowed', ire.description) + + try: + _, body, s = self.backend.create_token_response(uri, + body='grant_type=access_token&code=123') + self.fail('This should have failed with InvalidRequestError') + except errors.InvalidRequestError as ire: + self.assertIn('URL query parameters are not allowed', ire.description) diff --git a/tests/oauth2/rfc6749/endpoints/test_introspect_endpoint.py b/tests/oauth2/rfc6749/endpoints/test_introspect_endpoint.py index a34c970..ae3deae 100644 --- a/tests/oauth2/rfc6749/endpoints/test_introspect_endpoint.py +++ b/tests/oauth2/rfc6749/endpoints/test_introspect_endpoint.py @@ -140,6 +140,21 @@ class IntrospectEndpointTest(TestCase): self.assertEqual(loads(b)['error'], 'invalid_request') self.assertEqual(s, 400) + def test_introspect_invalid_request_method(self): + endpoint = IntrospectEndpoint(self.validator, + supported_token_types=['access_token']) + test_methods = ['GET', 'pUt', 'dEleTe', 'paTcH'] + test_methods = test_methods + [x.lower() for x in test_methods] + [x.upper() for x in test_methods] + for method in test_methods: + body = urlencode([('token', 'foo'), + ('token_type_hint', 'refresh_token')]) + h, b, s = endpoint.create_introspect_response(self.uri, + http_method = method, headers=self.headers, body=body) + self.assertEqual(h, self.resp_h) + self.assertEqual(loads(b)['error'], 'invalid_request') + self.assertIn('Unsupported request method', loads(b)['error_description']) + self.assertEqual(s, 400) + def test_introspect_bad_post_request(self): endpoint = IntrospectEndpoint(self.validator, supported_token_types=['access_token']) diff --git a/tests/oauth2/rfc6749/endpoints/test_revocation_endpoint.py b/tests/oauth2/rfc6749/endpoints/test_revocation_endpoint.py index c73a1ef..17be3a5 100644 --- a/tests/oauth2/rfc6749/endpoints/test_revocation_endpoint.py +++ b/tests/oauth2/rfc6749/endpoints/test_revocation_endpoint.py @@ -121,6 +121,21 @@ class RevocationEndpointTest(TestCase): self.assertEqual(loads(b)['error'], 'invalid_request') self.assertEqual(s, 400) + def test_revoke_invalid_request_method(self): + endpoint = RevocationEndpoint(self.validator, + supported_token_types=['access_token']) + test_methods = ['GET', 'pUt', 'dEleTe', 'paTcH'] + test_methods = test_methods + [x.lower() for x in test_methods] + [x.upper() for x in test_methods] + for method in test_methods: + body = urlencode([('token', 'foo'), + ('token_type_hint', 'refresh_token')]) + h, b, s = endpoint.create_revocation_response(self.uri, + http_method = method, headers=self.headers, body=body) + self.assertEqual(h, self.resp_h) + self.assertEqual(loads(b)['error'], 'invalid_request') + self.assertIn('Unsupported request method', loads(b)['error_description']) + self.assertEqual(s, 400) + def test_revoke_bad_post_request(self): endpoint = RevocationEndpoint(self.validator, supported_token_types=['access_token']) -- cgit v1.2.1 From 16a5e630a1d1eeb2511e1049ee2291b33f57ab25 Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Mon, 13 May 2019 14:53:41 +0200 Subject: Updated bandit baseline after review --- bandit.json | 1184 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 1179 insertions(+), 5 deletions(-) diff --git a/bandit.json b/bandit.json index 02e15a8..4d3bfe1 100644 --- a/bandit.json +++ b/bandit.json @@ -1,21 +1,1034 @@ { "errors": [], - "generated_at": "2018-12-13T10:39:37Z", + "generated_at": "2019-05-13T12:51:49Z", + "metrics": { + "_totals": { + "CONFIDENCE.HIGH": 3.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 10.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 12.0, + "SEVERITY.MEDIUM": 1.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 8338, + "nosec": 0 + }, + "oauthlib/__init__.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 25, + "nosec": 0 + }, + "oauthlib/common.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 337, + "nosec": 0 + }, + "oauthlib/oauth1/__init__.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 16, + "nosec": 0 + }, + "oauthlib/oauth1/rfc5849/__init__.py": { + "CONFIDENCE.HIGH": 1.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 1.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 230, + "nosec": 0 + }, + "oauthlib/oauth1/rfc5849/endpoints/__init__.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 8, + "nosec": 0 + }, + "oauthlib/oauth1/rfc5849/endpoints/access_token.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 152, + "nosec": 0 + }, + "oauthlib/oauth1/rfc5849/endpoints/authorization.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 135, + "nosec": 0 + }, + "oauthlib/oauth1/rfc5849/endpoints/base.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 142, + "nosec": 0 + }, + "oauthlib/oauth1/rfc5849/endpoints/pre_configured.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 10, + "nosec": 0 + }, + "oauthlib/oauth1/rfc5849/endpoints/request_token.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 141, + "nosec": 0 + }, + "oauthlib/oauth1/rfc5849/endpoints/resource.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 97, + "nosec": 0 + }, + "oauthlib/oauth1/rfc5849/endpoints/signature_only.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 53, + "nosec": 0 + }, + "oauthlib/oauth1/rfc5849/errors.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 58, + "nosec": 0 + }, + "oauthlib/oauth1/rfc5849/parameters.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 75, + "nosec": 0 + }, + "oauthlib/oauth1/rfc5849/request_validator.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 630, + "nosec": 0 + }, + "oauthlib/oauth1/rfc5849/signature.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 379, + "nosec": 0 + }, + "oauthlib/oauth1/rfc5849/utils.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 61, + "nosec": 0 + }, + "oauthlib/oauth2/__init__.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 33, + "nosec": 0 + }, + "oauthlib/oauth2/rfc6749/__init__.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 14, + "nosec": 0 + }, + "oauthlib/oauth2/rfc6749/clients/__init__.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 13, + "nosec": 0 + }, + "oauthlib/oauth2/rfc6749/clients/backend_application.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 56, + "nosec": 0 + }, + "oauthlib/oauth2/rfc6749/clients/base.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 3.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 3.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 384, + "nosec": 0 + }, + "oauthlib/oauth2/rfc6749/clients/legacy_application.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 67, + "nosec": 0 + }, + "oauthlib/oauth2/rfc6749/clients/mobile_application.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 140, + "nosec": 0 + }, + "oauthlib/oauth2/rfc6749/clients/service_application.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 144, + "nosec": 0 + }, + "oauthlib/oauth2/rfc6749/clients/web_application.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 165, + "nosec": 0 + }, + "oauthlib/oauth2/rfc6749/endpoints/__init__.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 18, + "nosec": 0 + }, + "oauthlib/oauth2/rfc6749/endpoints/authorization.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 85, + "nosec": 0 + }, + "oauthlib/oauth2/rfc6749/endpoints/base.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 71, + "nosec": 0 + }, + "oauthlib/oauth2/rfc6749/endpoints/introspect.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 98, + "nosec": 0 + }, + "oauthlib/oauth2/rfc6749/endpoints/metadata.py": { + "CONFIDENCE.HIGH": 2.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 2.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 182, + "nosec": 0 + }, + "oauthlib/oauth2/rfc6749/endpoints/pre_configured.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 5.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 5.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 189, + "nosec": 0 + }, + "oauthlib/oauth2/rfc6749/endpoints/resource.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 65, + "nosec": 0 + }, + "oauthlib/oauth2/rfc6749/endpoints/revocation.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 96, + "nosec": 0 + }, + "oauthlib/oauth2/rfc6749/endpoints/token.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 76, + "nosec": 0 + }, + "oauthlib/oauth2/rfc6749/errors.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 311, + "nosec": 0 + }, + "oauthlib/oauth2/rfc6749/grant_types/__init__.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 10, + "nosec": 0 + }, + "oauthlib/oauth2/rfc6749/grant_types/authorization_code.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 389, + "nosec": 0 + }, + "oauthlib/oauth2/rfc6749/grant_types/base.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 199, + "nosec": 0 + }, + "oauthlib/oauth2/rfc6749/grant_types/client_credentials.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 96, + "nosec": 0 + }, + "oauthlib/oauth2/rfc6749/grant_types/implicit.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 259, + "nosec": 0 + }, + "oauthlib/oauth2/rfc6749/grant_types/refresh_token.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 102, + "nosec": 0 + }, + "oauthlib/oauth2/rfc6749/grant_types/resource_owner_password_credentials.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 156, + "nosec": 0 + }, + "oauthlib/oauth2/rfc6749/parameters.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 1.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 1.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 335, + "nosec": 0 + }, + "oauthlib/oauth2/rfc6749/request_validator.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 504, + "nosec": 0 + }, + "oauthlib/oauth2/rfc6749/tokens.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 277, + "nosec": 0 + }, + "oauthlib/oauth2/rfc6749/utils.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 70, + "nosec": 0 + }, + "oauthlib/openid/__init__.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 8, + "nosec": 0 + }, + "oauthlib/openid/connect/__init__.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 0, + "nosec": 0 + }, + "oauthlib/openid/connect/core/__init__.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 0, + "nosec": 0 + }, + "oauthlib/openid/connect/core/endpoints/__init__.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 9, + "nosec": 0 + }, + "oauthlib/openid/connect/core/endpoints/pre_configured.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 1.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 1.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 93, + "nosec": 0 + }, + "oauthlib/openid/connect/core/endpoints/userinfo.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 83, + "nosec": 0 + }, + "oauthlib/openid/connect/core/exceptions.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 117, + "nosec": 0 + }, + "oauthlib/openid/connect/core/grant_types/__init__.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 15, + "nosec": 0 + }, + "oauthlib/openid/connect/core/grant_types/authorization_code.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 32, + "nosec": 0 + }, + "oauthlib/openid/connect/core/grant_types/base.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 234, + "nosec": 0 + }, + "oauthlib/openid/connect/core/grant_types/dispatchers.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 66, + "nosec": 0 + }, + "oauthlib/openid/connect/core/grant_types/exceptions.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 26, + "nosec": 0 + }, + "oauthlib/openid/connect/core/grant_types/hybrid.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 38, + "nosec": 0 + }, + "oauthlib/openid/connect/core/grant_types/implicit.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 35, + "nosec": 0 + }, + "oauthlib/openid/connect/core/request_validator.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 235, + "nosec": 0 + }, + "oauthlib/openid/connect/core/tokens.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 42, + "nosec": 0 + }, + "oauthlib/signals.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 32, + "nosec": 0 + }, + "oauthlib/tokens/__init__.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 0, + "nosec": 0 + }, + "oauthlib/tokens/access_token.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 13, + "nosec": 0 + }, + "oauthlib/tokens/base.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 8, + "nosec": 0 + }, + "oauthlib/tokens/id_token.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 6, + "nosec": 0 + }, + "oauthlib/uri_validate.py": { + "CONFIDENCE.HIGH": 0.0, + "CONFIDENCE.LOW": 0.0, + "CONFIDENCE.MEDIUM": 0.0, + "CONFIDENCE.UNDEFINED": 0.0, + "SEVERITY.HIGH": 0.0, + "SEVERITY.LOW": 0.0, + "SEVERITY.MEDIUM": 0.0, + "SEVERITY.UNDEFINED": 0.0, + "loc": 93, + "nosec": 0 + } + }, "results": [ { - "code": "182 if request.body is not None and content_type_eligible:\n183 params.append(('oauth_body_hash', base64.b64encode(hashlib.sha1(request.body.encode('utf-8')).digest()).decode('utf-8')))\n184 \n", + "code": "183 if request.body is not None and content_type_eligible:\n184 params.append(('oauth_body_hash', base64.b64encode(hashlib.sha1(request.body.encode('utf-8')).digest()).decode('utf-8')))\n185 \n", "filename": "oauthlib/oauth1/rfc5849/__init__.py", "issue_confidence": "HIGH", "issue_severity": "MEDIUM", "issue_text": "Use of insecure MD2, MD4, MD5, or SHA1 hash function.", - "line_number": 183, + "line_number": 184, "line_range": [ - 183 + 184 ], "more_info": "https://bandit.readthedocs.io/en/latest/blacklists/blacklist_calls.html#b303-md5", "test_id": "B303", "test_name": "blacklist" }, + { + "code": "49 \"\"\"\n50 refresh_token_key = 'refresh_token'\n51 \n52 def __init__(self, client_id,\n", + "filename": "oauthlib/oauth2/rfc6749/clients/base.py", + "issue_confidence": "MEDIUM", + "issue_severity": "LOW", + "issue_text": "Possible hardcoded password: 'refresh_token'", + "line_number": 50, + "line_range": [ + 50, + 51 + ], + "more_info": "https://bandit.readthedocs.io/en/latest/plugins/b105_hardcoded_password_string.html", + "test_id": "B105", + "test_name": "hardcoded_password_string" + }, + { + "code": "51 \n52 def __init__(self, client_id,\n53 default_token_placement=AUTH_HEADER,\n54 token_type='Bearer',\n55 access_token=None,\n56 refresh_token=None,\n57 mac_key=None,\n58 mac_algorithm=None,\n59 token=None,\n60 scope=None,\n61 state=None,\n62 redirect_url=None,\n63 state_generator=generate_token,\n64 **kwargs):\n65 \"\"\"Initialize a client with commonly used attributes.\n66 \n67 :param client_id: Client identifier given by the OAuth provider upon\n68 registration.\n69 \n70 :param default_token_placement: Tokens can be supplied in the Authorization\n71 header (default), the URL query component (``query``) or the request\n72 body (``body``).\n73 \n74 :param token_type: OAuth 2 token type. Defaults to Bearer. Change this\n75 if you specify the ``access_token`` parameter and know it is of a\n76 different token type, such as a MAC, JWT or SAML token. Can\n77 also be supplied as ``token_type`` inside the ``token`` dict parameter.\n78 \n79 :param access_token: An access token (string) used to authenticate\n80 requests to protected resources. Can also be supplied inside the\n81 ``token`` dict parameter.\n82 \n83 :param refresh_token: A refresh token (string) used to refresh expired\n84 tokens. Can also be supplied inside the ``token`` dict parameter.\n85 \n86 :param mac_key: Encryption key used with MAC tokens.\n87 \n88 :param mac_algorithm: Hashing algorithm for MAC tokens.\n89 \n90 :param token: A dict of token attributes such as ``access_token``,\n91 ``token_type`` and ``expires_at``.\n92 \n93 :param scope: A list of default scopes to request authorization for.\n94 \n95 :param state: A CSRF protection string used during authorization.\n96 \n97 :param redirect_url: The redirection endpoint on the client side to which\n98 the user returns after authorization.\n99 \n100 :param state_generator: A no argument state generation callable. Defaults\n101 to :py:meth:`oauthlib.common.generate_token`.\n102 \"\"\"\n103 \n104 self.client_id = client_id\n105 self.default_token_placement = default_token_placement\n106 self.token_type = token_type\n107 self.access_token = access_token\n108 self.refresh_token = refresh_token\n109 self.mac_key = mac_key\n110 self.mac_algorithm = mac_algorithm\n111 self.token = token or {}\n112 self.scope = scope\n113 self.state_generator = state_generator\n114 self.state = state\n115 self.redirect_url = redirect_url\n116 self.code = None\n117 self.expires_in = None\n118 self._expires_at = None\n119 self.populate_token_attributes(self.token)\n120 \n121 @property\n", + "filename": "oauthlib/oauth2/rfc6749/clients/base.py", + "issue_confidence": "MEDIUM", + "issue_severity": "LOW", + "issue_text": "Possible hardcoded password: 'Bearer'", + "line_number": 52, + "line_range": [ + 52, + 53, + 54, + 55, + 56, + 57, + 58, + 59, + 60, + 61, + 62, + 63, + 64, + 65, + 66, + 67, + 68, + 69, + 70, + 71, + 72, + 73, + 74, + 75, + 76, + 77, + 78, + 79, + 80, + 81, + 82, + 83, + 84, + 85, + 86, + 87, + 88, + 89, + 90, + 91, + 92, + 93, + 94, + 95, + 96, + 97, + 98, + 99, + 100, + 101, + 102, + 103, + 104, + 105, + 106, + 107, + 108, + 109, + 110, + 111, + 112, + 113, + 114, + 115, + 116, + 117, + 118, + 119, + 120 + ], + "more_info": "https://bandit.readthedocs.io/en/latest/plugins/b107_hardcoded_password_default.html", + "test_id": "B107", + "test_name": "hardcoded_password_default" + }, + { + "code": "313 \n314 def prepare_token_revocation_request(self, revocation_url, token,\n315 token_type_hint=\"access_token\", body='', callback=None, **kwargs):\n316 \"\"\"Prepare a token revocation request.\n317 \n318 :param revocation_url: Provider token revocation endpoint URL.\n319 \n320 :param token: The access or refresh token to be revoked (string).\n321 \n322 :param token_type_hint: ``\"access_token\"`` (default) or\n323 ``\"refresh_token\"``. This is optional and if you wish to not pass it you\n324 must provide ``token_type_hint=None``.\n325 \n326 :param body:\n327 \n328 :param callback: A jsonp callback such as ``package.callback`` to be invoked\n329 upon receiving the response. Not that it should not include a () suffix.\n330 \n331 :param kwargs: Additional parameters to included in the request.\n332 \n333 :returns: The prepared request tuple with (url, headers, body).\n334 \n335 Note that JSONP request may use GET requests as the parameters will\n336 be added to the request URL query as opposed to the request body.\n337 \n338 An example of a revocation request\n339 \n340 .. code-block: http\n341 \n342 POST /revoke HTTP/1.1\n343 Host: server.example.com\n344 Content-Type: application/x-www-form-urlencoded\n345 Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW\n346 \n347 token=45ghiukldjahdnhzdauz&token_type_hint=refresh_token\n348 \n349 An example of a jsonp revocation request\n350 \n351 .. code-block: http\n352 \n353 GET /revoke?token=agabcdefddddafdd&callback=package.myCallback HTTP/1.1\n354 Host: server.example.com\n355 Content-Type: application/x-www-form-urlencoded\n356 Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW\n357 \n358 and an error response\n359 \n360 .. code-block: http\n361 \n362 package.myCallback({\"error\":\"unsupported_token_type\"});\n363 \n364 Note that these requests usually require client credentials, client_id in\n365 the case for public clients and provider specific authentication\n366 credentials for confidential clients.\n367 \"\"\"\n368 if not is_secure_transport(revocation_url):\n369 raise InsecureTransportError()\n370 \n371 return prepare_token_revocation_request(revocation_url, token,\n372 token_type_hint=token_type_hint, body=body, callback=callback,\n373 **kwargs)\n374 \n375 def parse_request_body_response(self, body, scope=None, **kwargs):\n", + "filename": "oauthlib/oauth2/rfc6749/clients/base.py", + "issue_confidence": "MEDIUM", + "issue_severity": "LOW", + "issue_text": "Possible hardcoded password: 'access_token'", + "line_number": 314, + "line_range": [ + 314, + 315, + 316, + 317, + 318, + 319, + 320, + 321, + 322, + 323, + 324, + 325, + 326, + 327, + 328, + 329, + 330, + 331, + 332, + 333, + 334, + 335, + 336, + 337, + 338, + 339, + 340, + 341, + 342, + 343, + 344, + 345, + 346, + 347, + 348, + 349, + 350, + 351, + 352, + 353, + 354, + 355, + 356, + 357, + 358, + 359, + 360, + 361, + 362, + 363, + 364, + 365, + 366, + 367, + 368, + 369, + 370, + 371, + 372, + 373, + 374 + ], + "more_info": "https://bandit.readthedocs.io/en/latest/plugins/b107_hardcoded_password_default.html", + "test_id": "B107", + "test_name": "hardcoded_password_default" + }, { "code": "45 def __init__(self, endpoints, claims={}, raise_errors=True):\n46 assert isinstance(claims, dict)\n47 for endpoint in endpoints:\n", "filename": "oauthlib/oauth2/rfc6749/endpoints/metadata.py", @@ -43,6 +1056,167 @@ "more_info": "https://bandit.readthedocs.io/en/latest/plugins/b101_assert_used.html", "test_id": "B101", "test_name": "assert_used" + }, + { + "code": "70 default_token_type=bearer)\n71 ResourceEndpoint.__init__(self, default_token='Bearer',\n72 token_types={'Bearer': bearer})\n73 RevocationEndpoint.__init__(self, request_validator)\n", + "filename": "oauthlib/oauth2/rfc6749/endpoints/pre_configured.py", + "issue_confidence": "MEDIUM", + "issue_severity": "LOW", + "issue_text": "Possible hardcoded password: 'Bearer'", + "line_number": 71, + "line_range": [ + 71, + 72 + ], + "more_info": "https://bandit.readthedocs.io/en/latest/plugins/b106_hardcoded_password_funcarg.html", + "test_id": "B106", + "test_name": "hardcoded_password_funcarg" + }, + { + "code": "109 default_token_type=bearer)\n110 ResourceEndpoint.__init__(self, default_token='Bearer',\n111 token_types={'Bearer': bearer})\n112 RevocationEndpoint.__init__(self, request_validator)\n", + "filename": "oauthlib/oauth2/rfc6749/endpoints/pre_configured.py", + "issue_confidence": "MEDIUM", + "issue_severity": "LOW", + "issue_text": "Possible hardcoded password: 'Bearer'", + "line_number": 110, + "line_range": [ + 110, + 111 + ], + "more_info": "https://bandit.readthedocs.io/en/latest/plugins/b106_hardcoded_password_funcarg.html", + "test_id": "B106", + "test_name": "hardcoded_password_funcarg" + }, + { + "code": "142 default_token_type=bearer)\n143 ResourceEndpoint.__init__(self, default_token='Bearer',\n144 token_types={'Bearer': bearer})\n145 RevocationEndpoint.__init__(self, request_validator,\n", + "filename": "oauthlib/oauth2/rfc6749/endpoints/pre_configured.py", + "issue_confidence": "MEDIUM", + "issue_severity": "LOW", + "issue_text": "Possible hardcoded password: 'Bearer'", + "line_number": 143, + "line_range": [ + 143, + 144 + ], + "more_info": "https://bandit.readthedocs.io/en/latest/plugins/b106_hardcoded_password_funcarg.html", + "test_id": "B106", + "test_name": "hardcoded_password_funcarg" + }, + { + "code": "181 default_token_type=bearer)\n182 ResourceEndpoint.__init__(self, default_token='Bearer',\n183 token_types={'Bearer': bearer})\n184 RevocationEndpoint.__init__(self, request_validator)\n", + "filename": "oauthlib/oauth2/rfc6749/endpoints/pre_configured.py", + "issue_confidence": "MEDIUM", + "issue_severity": "LOW", + "issue_text": "Possible hardcoded password: 'Bearer'", + "line_number": 182, + "line_range": [ + 182, + 183 + ], + "more_info": "https://bandit.readthedocs.io/en/latest/plugins/b106_hardcoded_password_funcarg.html", + "test_id": "B106", + "test_name": "hardcoded_password_funcarg" + }, + { + "code": "214 default_token_type=bearer)\n215 ResourceEndpoint.__init__(self, default_token='Bearer',\n216 token_types={'Bearer': bearer})\n217 RevocationEndpoint.__init__(self, request_validator,\n", + "filename": "oauthlib/oauth2/rfc6749/endpoints/pre_configured.py", + "issue_confidence": "MEDIUM", + "issue_severity": "LOW", + "issue_text": "Possible hardcoded password: 'Bearer'", + "line_number": 215, + "line_range": [ + 215, + 216 + ], + "more_info": "https://bandit.readthedocs.io/en/latest/plugins/b106_hardcoded_password_funcarg.html", + "test_id": "B106", + "test_name": "hardcoded_password_funcarg" + }, + { + "code": "164 \n165 def prepare_token_revocation_request(url, token, token_type_hint=\"access_token\",\n166 callback=None, body='', **kwargs):\n167 \"\"\"Prepare a token revocation request.\n168 \n169 The client constructs the request by including the following parameters\n170 using the \"application/x-www-form-urlencoded\" format in the HTTP request\n171 entity-body:\n172 \n173 :param token: REQUIRED. The token that the client wants to get revoked.\n174 \n175 :param token_type_hint: OPTIONAL. A hint about the type of the token\n176 submitted for revocation. Clients MAY pass this\n177 parameter in order to help the authorization server\n178 to optimize the token lookup. If the server is\n179 unable to locate the token using the given hint, it\n180 MUST extend its search across all of its supported\n181 token types. An authorization server MAY ignore\n182 this parameter, particularly if it is able to detect\n183 the token type automatically.\n184 \n185 This specification defines two values for `token_type_hint`:\n186 \n187 * access_token: An access token as defined in [RFC6749],\n188 `Section 1.4`_\n189 \n190 * refresh_token: A refresh token as defined in [RFC6749],\n191 `Section 1.5`_\n192 \n193 Specific implementations, profiles, and extensions of this\n194 specification MAY define other values for this parameter using the\n195 registry defined in `Section 4.1.2`_.\n196 \n197 .. _`Section 1.4`: https://tools.ietf.org/html/rfc6749#section-1.4\n198 .. _`Section 1.5`: https://tools.ietf.org/html/rfc6749#section-1.5\n199 .. _`Section 4.1.2`: https://tools.ietf.org/html/rfc7009#section-4.1.2\n200 \n201 \"\"\"\n202 if not is_secure_transport(url):\n203 raise InsecureTransportError()\n204 \n205 params = [('token', token)]\n206 \n207 if token_type_hint:\n208 params.append(('token_type_hint', token_type_hint))\n209 \n210 for k in kwargs:\n211 if kwargs[k]:\n212 params.append((unicode_type(k), kwargs[k]))\n213 \n214 headers = {'Content-Type': 'application/x-www-form-urlencoded'}\n215 \n216 if callback:\n217 params.append(('callback', callback))\n218 return add_params_to_uri(url, params), headers, body\n219 else:\n220 return url, headers, add_params_to_qs(body, params)\n221 \n222 \n223 def parse_authorization_code_response(uri, state=None):\n", + "filename": "oauthlib/oauth2/rfc6749/parameters.py", + "issue_confidence": "MEDIUM", + "issue_severity": "LOW", + "issue_text": "Possible hardcoded password: 'access_token'", + "line_number": 165, + "line_range": [ + 165, + 166, + 167, + 168, + 169, + 170, + 171, + 172, + 173, + 174, + 175, + 176, + 177, + 178, + 179, + 180, + 181, + 182, + 183, + 184, + 185, + 186, + 187, + 188, + 189, + 190, + 191, + 192, + 193, + 194, + 195, + 196, + 197, + 198, + 199, + 200, + 201, + 202, + 203, + 204, + 205, + 206, + 207, + 208, + 209, + 210, + 211, + 212, + 213, + 214, + 215, + 216, + 217, + 218, + 219, + 220, + 221, + 222 + ], + "more_info": "https://bandit.readthedocs.io/en/latest/plugins/b107_hardcoded_password_default.html", + "test_id": "B107", + "test_name": "hardcoded_password_default" + }, + { + "code": "104 default_token_type=bearer)\n105 ResourceEndpoint.__init__(self, default_token='Bearer',\n106 token_types={'Bearer': bearer, 'JWT': jwt})\n107 RevocationEndpoint.__init__(self, request_validator)\n", + "filename": "oauthlib/openid/connect/core/endpoints/pre_configured.py", + "issue_confidence": "MEDIUM", + "issue_severity": "LOW", + "issue_text": "Possible hardcoded password: 'Bearer'", + "line_number": 105, + "line_range": [ + 105, + 106 + ], + "more_info": "https://bandit.readthedocs.io/en/latest/plugins/b106_hardcoded_password_funcarg.html", + "test_id": "B106", + "test_name": "hardcoded_password_funcarg" } ] -} +} \ No newline at end of file -- cgit v1.2.1 From f09f687ebf02cb05ffdf3378e8207fb0083b3049 Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Mon, 13 May 2019 15:08:18 +0200 Subject: Downgrade python to match with Travis --- tox.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/tox.ini b/tox.ini index 1cac71c..9cd2a9f 100644 --- a/tox.ini +++ b/tox.ini @@ -28,6 +28,7 @@ commands= echo setup.py/long description is syntaxly correct [testenv:bandit] +basepython=python2.7 skipsdist=True deps=bandit commands=bandit -b bandit.json -r oauthlib/ -- cgit v1.2.1 From 09bcb01032a21a4bfa0c478ea8ae66ec8ace957a Mon Sep 17 00:00:00 2001 From: Mark Gregson Date: Thu, 6 Jun 2019 14:08:18 +1000 Subject: Check for authorization response errors --- oauthlib/oauth2/rfc6749/parameters.py | 9 ++++++--- tests/oauth2/rfc6749/test_parameters.py | 9 ++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/oauthlib/oauth2/rfc6749/parameters.py b/oauthlib/oauth2/rfc6749/parameters.py index 6b9d630..df724ee 100644 --- a/oauthlib/oauth2/rfc6749/parameters.py +++ b/oauthlib/oauth2/rfc6749/parameters.py @@ -264,12 +264,15 @@ def parse_authorization_code_response(uri, state=None): query = urlparse.urlparse(uri).query params = dict(urlparse.parse_qsl(query)) - if not 'code' in params: - raise MissingCodeError("Missing code parameter in response.") - if state and params.get('state', None) != state: raise MismatchingStateError() + if 'error' in params: + raise_from_error(params.get('error'), params) + + if not 'code' in params: + raise MissingCodeError("Missing code parameter in response.") + return params diff --git a/tests/oauth2/rfc6749/test_parameters.py b/tests/oauth2/rfc6749/test_parameters.py index c42f516..0d293cc 100644 --- a/tests/oauth2/rfc6749/test_parameters.py +++ b/tests/oauth2/rfc6749/test_parameters.py @@ -73,7 +73,8 @@ class ParameterTests(TestCase): error_nocode = 'https://client.example.com/cb?state=xyz' error_nostate = 'https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA' error_wrongstate = 'https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=abc' - error_response = 'https://client.example.com/cb?error=access_denied&state=xyz' + error_denied = 'https://client.example.com/cb?error=access_denied&state=xyz' + error_invalid = 'https://client.example.com/cb?error=invalid_request&state=xyz' implicit_base = 'https://example.com/cb#access_token=2YotnFZFEjr1zCsicMWpAA&scope=abc&' implicit_response = implicit_base + 'state={0}&token_type=example&expires_in=3600'.format(state) @@ -180,8 +181,10 @@ class ParameterTests(TestCase): self.assertRaises(MissingCodeError, parse_authorization_code_response, self.error_nocode) - self.assertRaises(MissingCodeError, parse_authorization_code_response, - self.error_response) + self.assertRaises(AccessDeniedError, parse_authorization_code_response, + self.error_denied) + self.assertRaises(InvalidRequestFatalError, parse_authorization_code_response, + self.error_invalid) self.assertRaises(MismatchingStateError, parse_authorization_code_response, self.error_nostate, state=self.state) self.assertRaises(MismatchingStateError, parse_authorization_code_response, -- cgit v1.2.1 From d2dcb0f5bb247c9e48fa876e3c99ff3298b3a4c0 Mon Sep 17 00:00:00 2001 From: Omer Katz Date: Tue, 11 Jun 2019 11:45:06 +0300 Subject: Create FUNDING.yml --- .github/FUNDING.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..9d4faec --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,12 @@ +# These are supported funding model platforms + +github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +custom: # Replace with a single custom sponsorship URL -- cgit v1.2.1 From 34166c5182871b53b2761dd8fae00aaec611a7d3 Mon Sep 17 00:00:00 2001 From: qporest Date: Tue, 2 Jul 2019 15:31:51 -0400 Subject: Fix BackendApplicationClient.prepare_request_body Currently, if no `scope` is passed to `prepare_request_body`, None will be passed on to `prepare_token_request`, even if BackendApplicationClient was initialized with `scope`. --- oauthlib/oauth2/rfc6749/clients/backend_application.py | 1 + 1 file changed, 1 insertion(+) diff --git a/oauthlib/oauth2/rfc6749/clients/backend_application.py b/oauthlib/oauth2/rfc6749/clients/backend_application.py index 2483e56..5737814 100644 --- a/oauthlib/oauth2/rfc6749/clients/backend_application.py +++ b/oauthlib/oauth2/rfc6749/clients/backend_application.py @@ -71,5 +71,6 @@ class BackendApplicationClient(Client): """ kwargs['client_id'] = self.client_id kwargs['include_client_id'] = include_client_id + scope = self.scope if scope is None else scope return prepare_token_request(self.grant_type, body=body, scope=scope, **kwargs) -- cgit v1.2.1 From 19d111df1c55456c0c85b6ba8051d3a9b3ac3733 Mon Sep 17 00:00:00 2001 From: Jonathan Huot Date: Wed, 3 Jul 2019 18:28:34 +0200 Subject: Error in timestamp comparison --- tests/openid/connect/core/grant_types/test_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/openid/connect/core/grant_types/test_base.py b/tests/openid/connect/core/grant_types/test_base.py index 76e017f..d506b7e 100644 --- a/tests/openid/connect/core/grant_types/test_base.py +++ b/tests/openid/connect/core/grant_types/test_base.py @@ -68,7 +68,7 @@ class IDTokenTest(TestCase): self.assertEqual(token["id_token"], "eyJ.body.signature") id_token = self.mock_validator.finalize_id_token.call_args[0][0] self.assertEqual(id_token['aud'], 'abcdef') - self.assertGreaterEqual(id_token['iat'], int(time.time())) + self.assertGreaterEqual(int(time.time()), id_token['iat']) def test_finalize_id_token_with_nonce(self): token = self.grant.add_id_token(self.token, "token_handler_mock", self.request, "my_nonce") -- cgit v1.2.1