From 7a51d6023bc67c382a7f5e75ff69dd8d1ddf6281 Mon Sep 17 00:00:00 2001 From: vpsx <19900057+vpsx@users.noreply.github.com> Date: Wed, 26 Aug 2020 13:56:33 -0500 Subject: [PATCH 01/34] fix(jwt-aud): Rm scopes from aud claim in id tokens * Don't pass scope to audiences arg in generate_token_response * In generate_id_token itself: * Don't append to audiences itself; instead make a copy * Check if client_id is None * Don't include aud claim in token if aud is empty --- fence/jwt/token.py | 14 ++++++-------- fence/oidc/jwt_generator.py | 1 - 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/fence/jwt/token.py b/fence/jwt/token.py index 8ce6f5b46..836b7b620 100644 --- a/fence/jwt/token.py +++ b/fence/jwt/token.py @@ -484,13 +484,6 @@ def generate_id_token( iat, exp = issued_and_expiration_times(expires_in) issuer = config.get("BASE_URL") - # include client_id if not already in audiences - if audiences: - if client_id not in audiences: - audiences.append(client_id) - else: - audiences = [client_id] - # If not provided, assume auth time is time this ID token is issued auth_time = auth_time or iat @@ -500,7 +493,6 @@ def generate_id_token( # ``fence/blueprints/well_known.py``. claims = { "pur": "id", - "aud": audiences, "sub": str(user.id), "iss": issuer, "iat": iat, @@ -518,6 +510,12 @@ def generate_id_token( } }, } + aud = audiences.copy() if audiences else [] + if client_id and client_id not in aud: + aud.append(client_id) + if aud: + claims["aud"] = aud + if user.tags: claims["context"]["user"]["tags"] = {tag.key: tag.value for tag in user.tags} diff --git a/fence/oidc/jwt_generator.py b/fence/oidc/jwt_generator.py index 19a1e826a..dd38e4d50 100644 --- a/fence/oidc/jwt_generator.py +++ b/fence/oidc/jwt_generator.py @@ -192,7 +192,6 @@ def generate_token_response( user=user, expires_in=config["ACCESS_TOKEN_EXPIRES_IN"], client_id=client.client_id, - audiences=scope, nonce=nonce, linked_google_email=linked_google_email, linked_google_account_exp=linked_google_account_exp, From e9548d82d944ea0d126eff0e6393e6423cec6004 Mon Sep 17 00:00:00 2001 From: vpsx <19900057+vpsx@users.noreply.github.com> Date: Tue, 8 Sep 2020 14:29:42 -0500 Subject: [PATCH 02/34] feat(scope): Add JWT scope validation * New scope arg in validate_jwt defaults to {'openid'} but allows None * No longer use aud claim for scopes --- fence/jwt/validate.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/fence/jwt/validate.py b/fence/jwt/validate.py index bf64e0c81..5367c9791 100644 --- a/fence/jwt/validate.py +++ b/fence/jwt/validate.py @@ -40,6 +40,7 @@ def validate_purpose(claims, pur): def validate_jwt( encoded_token=None, aud=None, + scope={"openid"}, purpose=None, public_key=None, attempt_refresh=False, @@ -54,9 +55,15 @@ def validate_jwt( Args: encoded_token (str): the base64 encoding of the token - aud (Optional[Iterable[str]]): - list of audiences that the token must satisfy; defaults to - ``{'openid'}`` (minimum expected by OpenID provider) + aud (Optional[str]): + audience with which the app identifies, usually an OIDC + client id, which the JWT will be expected to include in its ``aud`` + claim. Optional; if no ``aud`` argument given and the JWT has no + ``aud`` claim, validation will pass. + scope (Optional[Iterable[str]]): + list of scopes each of which the token must satisfy; defaults + to ``{'openid'}`` (minimum expected by OpenID provider). + Explicitly set this to None to skip scope validation. purpose (Optional[str]): which purpose the token is supposed to be used for (access, refresh, or id) @@ -77,8 +84,10 @@ def validate_jwt( except Unauthorized as e: raise JWTError(e.message) - aud = aud or {"openid"} - aud = set(aud) + assert ( + type(scope) is set or type(scope) is list or scope is None + ), "scope argument must be set or list or None" + iss = config["BASE_URL"] issuers = [iss] oidc_iss = ( @@ -98,6 +107,7 @@ def validate_jwt( claims = authutils.token.validate.validate_jwt( encoded_token=encoded_token, aud=aud, + scope=scope, purpose=purpose, issuers=issuers, public_key=public_key, @@ -107,7 +117,7 @@ def validate_jwt( except authutils.errors.JWTError as e: msg = "Invalid token : {}".format(str(e)) unverified_claims = jwt.decode(encoded_token, verify=False) - if "" in unverified_claims["aud"]: + if not unverified_claims.get("scope") or "" in unverified_claims["scope"]: msg += "; was OIDC client configured with scopes?" raise JWTError(msg) if purpose: From b510bfce388227709283b023b30847e3e8b76396 Mon Sep 17 00:00:00 2001 From: vpsx <19900057+vpsx@users.noreply.github.com> Date: Tue, 8 Sep 2020 14:39:39 -0500 Subject: [PATCH 03/34] fix(aud): Pass aud to validate_jwt as string, not set --- fence/blueprints/data/blueprint.py | 10 +++++----- fence/blueprints/data/indexd.py | 2 +- fence/blueprints/login/fence_login.py | 2 +- fence/resources/storage/cdis_jwt.py | 2 +- fence/resources/user/user_session.py | 5 +++-- 5 files changed, 11 insertions(+), 10 deletions(-) diff --git a/fence/blueprints/data/blueprint.py b/fence/blueprints/data/blueprint.py index 9114a0441..44e48f35d 100644 --- a/fence/blueprints/data/blueprint.py +++ b/fence/blueprints/data/blueprint.py @@ -19,7 +19,7 @@ @blueprint.route("/", methods=["DELETE"]) -@require_auth_header(aud={"data"}) +@require_auth_header(aud="data") @login_required({"data"}) def delete_data_file(file_id): """ @@ -106,7 +106,7 @@ def delete_data_file(file_id): @blueprint.route("/upload", methods=["POST"]) -@require_auth_header(aud={"data"}) +@require_auth_header(aud="data") @login_required({"data"}) def upload_data_file(): """ @@ -181,7 +181,7 @@ def upload_data_file(): @blueprint.route("/multipart/init", methods=["POST"]) -@require_auth_header(aud={"data"}) +@require_auth_header(aud="data") @login_required({"data"}) @check_arborist_auth(resource="/data_file", method="file_upload") def init_multipart_upload(): @@ -212,7 +212,7 @@ def init_multipart_upload(): @blueprint.route("/multipart/upload", methods=["POST"]) -@require_auth_header(aud={"data"}) +@require_auth_header(aud="data") @login_required({"data"}) @check_arborist_auth(resource="/data_file", method="file_upload") def generate_multipart_upload_presigned_url(): @@ -246,7 +246,7 @@ def generate_multipart_upload_presigned_url(): @blueprint.route("/multipart/complete", methods=["POST"]) -@require_auth_header(aud={"data"}) +@require_auth_header(aud="data") @login_required({"data"}) @check_arborist_auth(resource="/data_file", method="file_upload") def complete_multipart_upload(): diff --git a/fence/blueprints/data/indexd.py b/fence/blueprints/data/indexd.py index 08cbad473..8d5d605ab 100644 --- a/fence/blueprints/data/indexd.py +++ b/fence/blueprints/data/indexd.py @@ -1148,7 +1148,7 @@ def _get_user_info(sub_to_string=True): populated information about an anonymous user. """ try: - set_current_token(validate_request(aud={"user"})) + set_current_token(validate_request(aud="user")) user_id = current_token["sub"] if sub_to_string: user_id = str(user_id) diff --git a/fence/blueprints/login/fence_login.py b/fence/blueprints/login/fence_login.py index fbc7eeb0c..d530b2cc6 100644 --- a/fence/blueprints/login/fence_login.py +++ b/fence/blueprints/login/fence_login.py @@ -92,7 +92,7 @@ def get(self): redirect_uri, **flask.request.args.to_dict() ) id_token_claims = validate_jwt( - tokens["id_token"], aud={"openid"}, purpose="id", attempt_refresh=True + tokens["id_token"], aud="openid", purpose="id", attempt_refresh=True ) username = id_token_claims["context"]["user"]["name"] login_user( diff --git a/fence/resources/storage/cdis_jwt.py b/fence/resources/storage/cdis_jwt.py index ca3e70ac8..baba576c6 100644 --- a/fence/resources/storage/cdis_jwt.py +++ b/fence/resources/storage/cdis_jwt.py @@ -53,7 +53,7 @@ def create_user_access_token(keypair, api_key, expires_in): access token """ try: - claims = validate_jwt(api_key, aud={"fence"}, purpose="api_key") + claims = validate_jwt(api_key, aud="fence", purpose="api_key") scopes = claims["aud"] user = get_user_from_claims(claims) except Exception as e: diff --git a/fence/resources/user/user_session.py b/fence/resources/user/user_session.py index 01475d400..d622a33d1 100644 --- a/fence/resources/user/user_session.py +++ b/fence/resources/user/user_session.py @@ -48,7 +48,7 @@ def __init__(self, session_token): if session_token: try: - jwt_info = validate_jwt(session_token, aud={"fence"}) + jwt_info = validate_jwt(session_token, aud="fence") except JWTError: # if session token is invalid, create a new # empty one silently @@ -70,9 +70,10 @@ def _get_initial_session_token(self): expires_in=config.get("SESSION_TIMEOUT"), ).token self._encoded_token = session_token + initial_token = validate_jwt( session_token, - aud={"fence"}, + aud="fence", purpose="session", public_key=default_public_key(), ) From 04f7da8b9713b8bb1aebed71a5439228afa26245 Mon Sep 17 00:00:00 2001 From: vpsx <19900057+vpsx@users.noreply.github.com> Date: Tue, 8 Sep 2020 14:50:03 -0500 Subject: [PATCH 04/34] feat(scope): pass scope=None when validating session tokens --- fence/resources/user/user_session.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/fence/resources/user/user_session.py b/fence/resources/user/user_session.py index d622a33d1..21d4142d0 100644 --- a/fence/resources/user/user_session.py +++ b/fence/resources/user/user_session.py @@ -48,7 +48,13 @@ def __init__(self, session_token): if session_token: try: - jwt_info = validate_jwt(session_token, aud="fence") + jwt_info = validate_jwt( + session_token, + aud="fence", + scope=None, + purpose="session", + public_key=default_public_key(), + ) except JWTError: # if session token is invalid, create a new # empty one silently @@ -74,6 +80,7 @@ def _get_initial_session_token(self): initial_token = validate_jwt( session_token, aud="fence", + scope=None, purpose="session", public_key=default_public_key(), ) From 90ecb1746972c25614bf5bed8c5ce6450f932e88 Mon Sep 17 00:00:00 2001 From: vpsx <19900057+vpsx@users.noreply.github.com> Date: Thu, 10 Sep 2020 12:02:51 -0500 Subject: [PATCH 05/34] feat(scopes): Add scope claim to oidc tokens * add separate scope claim to id, refresh tkns (already in access tkns) * rm scopes from aud claim in all tokens * set aud claim to client_id in all oidc tokens * also do all of the above for user API key * update docstrings some fence.jwt.token functions --- fence/jwt/token.py | 49 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 38 insertions(+), 11 deletions(-) diff --git a/fence/jwt/token.py b/fence/jwt/token.py index 836b7b620..cb753499f 100644 --- a/fence/jwt/token.py +++ b/fence/jwt/token.py @@ -204,6 +204,7 @@ def generate_signed_id_token( expires_in, client_id, audiences=None, + scopes=None, auth_time=None, max_age=None, nonce=None, @@ -222,7 +223,10 @@ def generate_signed_id_token( user (fence.models.User): User to generate ID token for expires_in (int): seconds token should last client_id (str, optional): Client identifier - audiences (List(str), optional): Description + audiences (List(str), optional): + audiences the ID token is intended for (the aud claim). + client_id will get appended to this. + scopes (List[str], optional): oauth scopes for user auth_time (int, optional): Last time user authN'd in number of seconds from 1970-01-01T0:0:0Z as measured in UTC until the date/time @@ -230,6 +234,13 @@ def generate_signed_id_token( max number of seconds allowed since last user AuthN nonce (str, optional): string value used to associate a Client session with an ID Token + include_project_access (bool, optional): + whether to include user.project_access in the token context.user.projects + auth_flow_type (AuthFlowTypes, optional): + which auth flow (Auth Code or Implicit) is issuing this token + (token validation will be different for each flow) + access_token (string, optional): + the access token tied to this id token; used to generate the at_hash claim Return: str: encoded JWT ID token signed with ``private_key`` @@ -240,6 +251,7 @@ def generate_signed_id_token( client_id, include_project_access=include_project_access, audiences=audiences, + scopes=scopes, auth_time=auth_time, max_age=max_age, nonce=nonce, @@ -282,15 +294,18 @@ def generate_signed_refresh_token( ) claims = { "pur": "refresh", - "aud": scopes, "sub": sub, "iss": iss, "iat": iat, "exp": exp, "jti": jti, "azp": client_id or "", + "scope": scopes, } + if client_id: + claims["aud"] = [client_id] + logger.info("issuing JWT refresh token with id [{}] to [{}]".format(jti, sub)) logger.debug("issuing JWT refresh token\n" + json.dumps(claims, indent=4)) @@ -321,14 +336,18 @@ def generate_api_key(kid, private_key, user_id, expires_in, scopes, client_id): sub = str(user_id) claims = { "pur": "api_key", - "aud": scopes, "sub": sub, "iss": config.get("BASE_URL"), "iat": iat, "exp": exp, "jti": jti, "azp": client_id or "", + "scope": scopes, } + + if client_id: + claims["aud"] = [client_id] + logger.info("issuing JWT API key with id [{}] to [{}]".format(jti, sub)) logger.debug("issuing JWT API key\n" + json.dumps(claims, indent=4)) token = jwt.encode(claims, private_key, headers=headers, algorithm="RS256") @@ -377,16 +396,9 @@ def generate_signed_access_token( "must provide value for `iss` (issuer) field if" " running outside of flask application" ) - audiences = [] - if client_id: - audiences.append(client_id) - # append scopes for backwards compatibility - # eventual goal is to remove scopes from `aud` - audiences = audiences + scopes claims = { "pur": "access", - "aud": audiences, "sub": sub, "iss": iss, "iat": iat, @@ -403,6 +415,9 @@ def generate_signed_access_token( "azp": client_id or "", } + if client_id: + claims["aud"] = [client_id] + if include_project_access: # NOTE: "THIS IS A TERRIBLE STOP-GAP SOLUTION SO THAT USERS WITH # MINIMAL ACCESS CAN STILL USE LATEST VERSION OF FENCE @@ -452,6 +467,7 @@ def generate_id_token( expires_in, client_id, audiences=None, + scopes=None, auth_time=None, max_age=None, nonce=None, @@ -468,7 +484,10 @@ def generate_id_token( user (fence.models.User): User to generate ID token for expires_in (int): seconds token should last client_id (str, optional): Client identifier - audiences (List(str), optional): Description + audiences (List(str), optional): + audiences the ID token is intended for (the aud claim). + client_id will get appended to this. + scopes (List[str], optional): oauth scopes for user auth_time (int, optional): Last time user authN'd in number of seconds from 1970-01-01T0:0:0Z as measured in UTC until the date/time @@ -476,6 +495,13 @@ def generate_id_token( max number of seconds allowed since last user AuthN nonce (str, optional): string value used to associate a Client session with an ID Token + include_project_access (bool, optional): + whether to include user.project_access in the token context.user.projects + auth_flow_type (AuthFlowTypes, optional): + which auth flow (Auth Code or Implicit) is issuing this token + (token validation will be different for each flow) + access_token (string, optional): + the access token tied to this id token; used to generate the at_hash claim Returns: UnsignedIDToken: Unsigned ID token @@ -500,6 +526,7 @@ def generate_id_token( "jti": str(uuid.uuid4()), "auth_time": auth_time, "azp": client_id, + "scope": scopes, "context": { "user": { "name": user.username, From 9de487944e8d3dac8c19e75879065dcfabeeb6ec Mon Sep 17 00:00:00 2001 From: vpsx <19900057+vpsx@users.noreply.github.com> Date: Thu, 10 Sep 2020 12:15:31 -0500 Subject: [PATCH 06/34] feat(scope): pass scopes to generate_[implicit,token]_response --- fence/oidc/jwt_generator.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fence/oidc/jwt_generator.py b/fence/oidc/jwt_generator.py index dd38e4d50..28dcb18d8 100644 --- a/fence/oidc/jwt_generator.py +++ b/fence/oidc/jwt_generator.py @@ -115,7 +115,7 @@ def generate_implicit_response( user=user, expires_in=config["ACCESS_TOKEN_EXPIRES_IN"], client_id=client.client_id, - audiences=scope, + scopes=scope, nonce=nonce, linked_google_email=linked_google_email, linked_google_account_exp=linked_google_account_exp, @@ -192,6 +192,7 @@ def generate_token_response( user=user, expires_in=config["ACCESS_TOKEN_EXPIRES_IN"], client_id=client.client_id, + scopes=scope, nonce=nonce, linked_google_email=linked_google_email, linked_google_account_exp=linked_google_account_exp, From 6df376283b8f5e41c78c4ff4c267e877ad95de6b Mon Sep 17 00:00:00 2001 From: vpsx <19900057+vpsx@users.noreply.github.com> Date: Thu, 10 Sep 2020 12:16:10 -0500 Subject: [PATCH 07/34] fix(aud): Validate aud claim in refresh token grant flow --- fence/oidc/grants/refresh_token_grant.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fence/oidc/grants/refresh_token_grant.py b/fence/oidc/grants/refresh_token_grant.py index 026002b87..6122136d3 100644 --- a/fence/oidc/grants/refresh_token_grant.py +++ b/fence/oidc/grants/refresh_token_grant.py @@ -41,7 +41,7 @@ def authenticate_refresh_token(self, refresh_token): Return: dict: the claims from the validated token """ - return validate_jwt(refresh_token, purpose="refresh") + return validate_jwt(refresh_token, aud=self.client.client_id, purpose="refresh") def create_access_token(self, token, client, authenticated_token): """ From 5e731d09f9908d18f7ad151b4699a7d13517a055 Mon Sep 17 00:00:00 2001 From: vpsx <19900057+vpsx@users.noreply.github.com> Date: Thu, 10 Sep 2020 12:16:47 -0500 Subject: [PATCH 08/34] feat(scope): add scope claim to claims_supported --- fence/blueprints/well_known.py | 1 + 1 file changed, 1 insertion(+) diff --git a/fence/blueprints/well_known.py b/fence/blueprints/well_known.py index 3046bc166..93504a342 100644 --- a/fence/blueprints/well_known.py +++ b/fence/blueprints/well_known.py @@ -78,6 +78,7 @@ def openid_configuration(): "jti", "auth_time", "azp", + "scope", "nonce", "context", ] From 67fd871724015914e0ab64cce6ec0b9e8338f607 Mon Sep 17 00:00:00 2001 From: vpsx <19900057+vpsx@users.noreply.github.com> Date: Thu, 10 Sep 2020 13:33:37 -0500 Subject: [PATCH 09/34] fix(aud): Look for scopes in scope not aud claim when validating refresh token --- fence/oidc/grants/refresh_token_grant.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/fence/oidc/grants/refresh_token_grant.py b/fence/oidc/grants/refresh_token_grant.py index 6122136d3..3e3f930ae 100644 --- a/fence/oidc/grants/refresh_token_grant.py +++ b/fence/oidc/grants/refresh_token_grant.py @@ -164,10 +164,12 @@ def _validate_token_scope(self, token): return # token is dict so just get the scope, don't use get_scope() - original_scope = token.get("aud") + original_scope = token.get("scope") if not original_scope: - raise InvalidScopeError() + raise InvalidScopeError("No scope claim found in original refresh token.") original_scope = set(scope_to_list(original_scope)) if not original_scope.issuperset(set(scope_to_list(scope))): - raise InvalidScopeError() + raise InvalidScopeError( + "Cannot request scopes that were not in original refresh token." + ) From 44b977e08b6ec2a789cb0d43f31b5ccf0898e971 Mon Sep 17 00:00:00 2001 From: vpsx <19900057+vpsx@users.noreply.github.com> Date: Fri, 11 Sep 2020 15:26:40 -0500 Subject: [PATCH 10/34] fix(aud): Skip aud validation in login_required fn * see TECHDEBT.md for context * also rm unused has_oauth import --- TECHDEBT.md | 14 ++++++++++++++ fence/auth.py | 4 +++- fence/blueprints/data/indexd.py | 1 - 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/TECHDEBT.md b/TECHDEBT.md index 863659c7e..7a1724514 100644 --- a/TECHDEBT.md +++ b/TECHDEBT.md @@ -20,3 +20,17 @@ Fence presently guards several endpoints (e.g. /data, signed urls, SA registrati Address above. ##### Other notes: n/a + + +### Not validating aud claim in Bearer tokens +##### Problem: +- The login_required decorator purports to enforce requirement of a user session (see fence/auth.py). +- However, it also allows falling back on the presence of a Bearer token in the request. +- In the latter case, the decorator calls has_oauth, which validates the JWT and checks that it has the right scopes. However, the validation does not verify the 'aud' claim. This is a security risk in settings where one Authorization server is issuing JWTs for multiple Resource servers. See [RFC 6819 5.1.5.5](https://tools.ietf.org/html/rfc6819#section-5.1.5.5). +##### Historical context: +- Fence used to use the 'aud' claim for scopes, overriding conventional 'aud' validation; the 'aud' claim was therefore not being used or validated correctly. +- Now scopes have been moved into a custom 'scope' claim with custom 'scope' validation, and 'aud' is populated with client_id (required by OIDC for id_tokens). 'aud' validation has reverted to the conventional validation. +- However, this means that now Fence cannot consume its own JWTs in its capacity as its own Resource server, since it does not identify as the client_id. +- In order to keep allowing the affected Fence endpoints to be used with a Bearer token, has_oauth currently skips validation of the 'aud' claim. +##### Possible solution: +- Along with client_id, put iss in aud as well? Need to think about whether this captures all current use cases, e.g. when Fence is trusted as Auth server to different Resource servers with different domains. diff --git a/fence/auth.py b/fence/auth.py index 0c79231de..695881134 100644 --- a/fence/auth.py +++ b/fence/auth.py @@ -209,7 +209,9 @@ def has_oauth(scope=None): scope = scope or set() scope.update({"openid"}) try: - access_token_claims = validate_jwt(aud=scope, purpose="access") + access_token_claims = validate_jwt( + scope=scope, purpose="access", options={"verify_aud": False} + ) except JWTError as e: raise Unauthorized("failed to validate token: {}".format(e)) user_id = access_token_claims["sub"] diff --git a/fence/blueprints/data/indexd.py b/fence/blueprints/data/indexd.py index 8d5d605ab..5c9b6dd61 100644 --- a/fence/blueprints/data/indexd.py +++ b/fence/blueprints/data/indexd.py @@ -14,7 +14,6 @@ from fence.auth import ( get_jwt, - has_oauth, current_token, login_required, set_current_token, From 7760681dcf70359013d750e1a2b996e575aecf88 Mon Sep 17 00:00:00 2001 From: vpsx <19900057+vpsx@users.noreply.github.com> Date: Mon, 14 Sep 2020 15:22:54 -0500 Subject: [PATCH 11/34] fix(aud-to-scope): authutils require_auth_header now takes scope arg not aud arg --- fence/blueprints/data/blueprint.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/fence/blueprints/data/blueprint.py b/fence/blueprints/data/blueprint.py index 44e48f35d..e247e7178 100644 --- a/fence/blueprints/data/blueprint.py +++ b/fence/blueprints/data/blueprint.py @@ -19,7 +19,7 @@ @blueprint.route("/", methods=["DELETE"]) -@require_auth_header(aud="data") +@require_auth_header(scope="data") @login_required({"data"}) def delete_data_file(file_id): """ @@ -106,7 +106,7 @@ def delete_data_file(file_id): @blueprint.route("/upload", methods=["POST"]) -@require_auth_header(aud="data") +@require_auth_header(scope="data") @login_required({"data"}) def upload_data_file(): """ @@ -181,7 +181,7 @@ def upload_data_file(): @blueprint.route("/multipart/init", methods=["POST"]) -@require_auth_header(aud="data") +@require_auth_header(scope="data") @login_required({"data"}) @check_arborist_auth(resource="/data_file", method="file_upload") def init_multipart_upload(): @@ -212,7 +212,7 @@ def init_multipart_upload(): @blueprint.route("/multipart/upload", methods=["POST"]) -@require_auth_header(aud="data") +@require_auth_header(scope="data") @login_required({"data"}) @check_arborist_auth(resource="/data_file", method="file_upload") def generate_multipart_upload_presigned_url(): @@ -246,7 +246,7 @@ def generate_multipart_upload_presigned_url(): @blueprint.route("/multipart/complete", methods=["POST"]) -@require_auth_header(aud="data") +@require_auth_header(scope="data") @login_required({"data"}) @check_arborist_auth(resource="/data_file", method="file_upload") def complete_multipart_upload(): From 72796f8806c4038854ad0d0c2c921a3a4273d6a0 Mon Sep 17 00:00:00 2001 From: vpsx <19900057+vpsx@users.noreply.github.com> Date: Wed, 16 Sep 2020 11:28:59 -0500 Subject: [PATCH 12/34] fix(aud-scope): Don't include aud in API keys; fix API key validation --- TECHDEBT.md | 2 ++ fence/jwt/token.py | 4 ---- fence/resources/storage/cdis_jwt.py | 4 ++-- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/TECHDEBT.md b/TECHDEBT.md index 7a1724514..4278e29a3 100644 --- a/TECHDEBT.md +++ b/TECHDEBT.md @@ -34,3 +34,5 @@ n/a - In order to keep allowing the affected Fence endpoints to be used with a Bearer token, has_oauth currently skips validation of the 'aud' claim. ##### Possible solution: - Along with client_id, put iss in aud as well? Need to think about whether this captures all current use cases, e.g. when Fence is trusted as Auth server to different Resource servers with different domains. +### Other notes: +- Also applies to require_auth_header decorator in authutils. diff --git a/fence/jwt/token.py b/fence/jwt/token.py index cb753499f..246934e8f 100644 --- a/fence/jwt/token.py +++ b/fence/jwt/token.py @@ -344,10 +344,6 @@ def generate_api_key(kid, private_key, user_id, expires_in, scopes, client_id): "azp": client_id or "", "scope": scopes, } - - if client_id: - claims["aud"] = [client_id] - logger.info("issuing JWT API key with id [{}] to [{}]".format(jti, sub)) logger.debug("issuing JWT API key\n" + json.dumps(claims, indent=4)) token = jwt.encode(claims, private_key, headers=headers, algorithm="RS256") diff --git a/fence/resources/storage/cdis_jwt.py b/fence/resources/storage/cdis_jwt.py index baba576c6..d5c779657 100644 --- a/fence/resources/storage/cdis_jwt.py +++ b/fence/resources/storage/cdis_jwt.py @@ -53,8 +53,8 @@ def create_user_access_token(keypair, api_key, expires_in): access token """ try: - claims = validate_jwt(api_key, aud="fence", purpose="api_key") - scopes = claims["aud"] + claims = validate_jwt(api_key, scope={"fence"}, purpose="api_key") + scopes = claims["scope"] user = get_user_from_claims(claims) except Exception as e: raise Unauthorized(str(e)) From 315d4702a1fa3faca20544bf93be9f5dcfb0842f Mon Sep 17 00:00:00 2001 From: vpsx <19900057+vpsx@users.noreply.github.com> Date: Mon, 21 Sep 2020 17:13:38 -0500 Subject: [PATCH 13/34] fix(scope): change scope param to set in require_auth_header --- fence/blueprints/data/blueprint.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/fence/blueprints/data/blueprint.py b/fence/blueprints/data/blueprint.py index e247e7178..95798f1e0 100644 --- a/fence/blueprints/data/blueprint.py +++ b/fence/blueprints/data/blueprint.py @@ -19,7 +19,7 @@ @blueprint.route("/", methods=["DELETE"]) -@require_auth_header(scope="data") +@require_auth_header(scope={"data"}) @login_required({"data"}) def delete_data_file(file_id): """ @@ -106,7 +106,7 @@ def delete_data_file(file_id): @blueprint.route("/upload", methods=["POST"]) -@require_auth_header(scope="data") +@require_auth_header(scope={"data"}) @login_required({"data"}) def upload_data_file(): """ @@ -181,7 +181,7 @@ def upload_data_file(): @blueprint.route("/multipart/init", methods=["POST"]) -@require_auth_header(scope="data") +@require_auth_header(scope={"data"}) @login_required({"data"}) @check_arborist_auth(resource="/data_file", method="file_upload") def init_multipart_upload(): @@ -212,7 +212,7 @@ def init_multipart_upload(): @blueprint.route("/multipart/upload", methods=["POST"]) -@require_auth_header(scope="data") +@require_auth_header(scope={"data"}) @login_required({"data"}) @check_arborist_auth(resource="/data_file", method="file_upload") def generate_multipart_upload_presigned_url(): @@ -246,7 +246,7 @@ def generate_multipart_upload_presigned_url(): @blueprint.route("/multipart/complete", methods=["POST"]) -@require_auth_header(scope="data") +@require_auth_header(scope={"data"}) @login_required({"data"}) @check_arborist_auth(resource="/data_file", method="file_upload") def complete_multipart_upload(): From 5d70405ed9aeffc7ec9ef09b19bbc9a1e4f0ff8e Mon Sep 17 00:00:00 2001 From: vpsx <19900057+vpsx@users.noreply.github.com> Date: Mon, 21 Sep 2020 17:25:40 -0500 Subject: [PATCH 14/34] fix(aud-scope): Fix validate_request argument --- fence/blueprints/data/indexd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fence/blueprints/data/indexd.py b/fence/blueprints/data/indexd.py index 5c9b6dd61..ed59d6baa 100644 --- a/fence/blueprints/data/indexd.py +++ b/fence/blueprints/data/indexd.py @@ -1147,7 +1147,7 @@ def _get_user_info(sub_to_string=True): populated information about an anonymous user. """ try: - set_current_token(validate_request(aud="user")) + set_current_token(validate_request(scope={"user"})) user_id = current_token["sub"] if sub_to_string: user_id = str(user_id) From 42539f340c0f5573870aa47906e24b7611f8c6b4 Mon Sep 17 00:00:00 2001 From: vpsx <19900057+vpsx@users.noreply.github.com> Date: Mon, 21 Sep 2020 17:29:10 -0500 Subject: [PATCH 15/34] fix(aud-scope): Don't validate aud when blacklisting tokens * previously passed 'openid' only to appease validation code, bc using aud for scopes --- fence/jwt/blacklist.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/fence/jwt/blacklist.py b/fence/jwt/blacklist.py index 873f117ee..cbccb10e3 100644 --- a/fence/jwt/blacklist.py +++ b/fence/jwt/blacklist.py @@ -89,7 +89,10 @@ def blacklist_encoded_token(encoded_token, public_key=None): public_key = public_key or keys.default_public_key() try: claims = jwt.decode( - encoded_token, public_key, algorithm="RS256", audience="openid" + encoded_token, + public_key, + algorithm="RS256", + options={"verify_aud": False}, ) except jwt.InvalidTokenError as e: raise BlacklistingError("failed to decode token: {}".format(e)) @@ -138,7 +141,10 @@ def is_token_blacklisted(encoded_token, public_key=None): public_key = public_key or keys.default_public_key() try: token = jwt.decode( - encoded_token, public_key, algorithm="RS256", audience="openid" + encoded_token, + public_key, + algorithm="RS256", + options={"verify_aud": False}, ) except jwt.exceptions.InvalidTokenError as e: raise JWTError("could not decode token to check blacklisting: {}".format(e)) From 21cfe036008ea4b0a283b8aedb0075737e8d162d Mon Sep 17 00:00:00 2001 From: vpsx <19900057+vpsx@users.noreply.github.com> Date: Mon, 21 Sep 2020 17:32:30 -0500 Subject: [PATCH 16/34] fix(aud-scope): Get scope from new scope claim in refresh token grant --- fence/oidc/grants/refresh_token_grant.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fence/oidc/grants/refresh_token_grant.py b/fence/oidc/grants/refresh_token_grant.py index 3e3f930ae..e616b1b62 100644 --- a/fence/oidc/grants/refresh_token_grant.py +++ b/fence/oidc/grants/refresh_token_grant.py @@ -126,7 +126,7 @@ def create_token_response(self): scope = self.request.scope if not scope: - scope = credential["aud"] + scope = credential["scope"] client = self.request.client expires_in = credential["exp"] From 226318d3bf9ad87d6e4f3c4d9e44d203ddac8d10 Mon Sep 17 00:00:00 2001 From: vpsx <19900057+vpsx@users.noreply.github.com> Date: Mon, 21 Sep 2020 17:36:28 -0500 Subject: [PATCH 17/34] fix(aud): Skip aud validation for refresh tokens * see TECHDEBT.md for context --- TECHDEBT.md | 14 ++++++++++++++ fence/oidc/grants/refresh_token_grant.py | 7 ++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/TECHDEBT.md b/TECHDEBT.md index 4278e29a3..b1b12d898 100644 --- a/TECHDEBT.md +++ b/TECHDEBT.md @@ -36,3 +36,17 @@ n/a - Along with client_id, put iss in aud as well? Need to think about whether this captures all current use cases, e.g. when Fence is trusted as Auth server to different Resource servers with different domains. ### Other notes: - Also applies to require_auth_header decorator in authutils. + + +### Tokens not bound to client when issued via fence-create +##### Problem: +- `fence-create` allows the creation of access and refresh tokens that are not bound to any Fence client. This is a deliberate feature. +- Tokens issued this way do not have an 'aud' claim. +- It is unclear what to do when, for example, such a refresh token without an aud claim is used in a token request. The token _request_ is still bound to a client (since the client_id query parameter is required), and the validation code checks the token aud claim for the client_id, does not find it, and fails. On the other hand, if the client_id from the query parameter is not passed to the validation code, then when there is a token request made with a refresh token _with_ an aud claim (one issued during normal auth code flow), validation will also fail. There is no way for Fence to know which sort of token to expect. +##### Other notes: +- Per OAuth2 spec, "The authorization server MUST maintain the binding between a refresh token and the client to whom it was issued" [(10.4)](https://tools.ietf.org/html/rfc6749#section-10.4) [(see also 6)](https://tools.ietf.org/html/rfc6749#section-6). +##### Possible solution: +- Require client_id as an argument in `fence-create token-create`? In other words, require that tokens issued via `fence-create` need to be bound to a client. +##### For now: +- Disable aud validation altogether for refresh tokens. +- Obviously this is a band-aid solution: Skipping validation is not ideal and JWTCreator still issues access tokens without aud claims as well. The more general issue is that Fence should either always expect the aud claim, or never expect it. diff --git a/fence/oidc/grants/refresh_token_grant.py b/fence/oidc/grants/refresh_token_grant.py index e616b1b62..2f8dce228 100644 --- a/fence/oidc/grants/refresh_token_grant.py +++ b/fence/oidc/grants/refresh_token_grant.py @@ -41,7 +41,12 @@ def authenticate_refresh_token(self, refresh_token): Return: dict: the claims from the validated token """ - return validate_jwt(refresh_token, aud=self.client.client_id, purpose="refresh") + return validate_jwt( + refresh_token, + aud=self.client.client_id, + purpose="refresh", + options={"verify_aud": False}, + ) def create_access_token(self, token, client, authenticated_token): """ From d229a1a19a880b1bfb9c1281e435dbe8d0cf0a43 Mon Sep 17 00:00:00 2001 From: vpsx <19900057+vpsx@users.noreply.github.com> Date: Mon, 21 Sep 2020 17:51:42 -0500 Subject: [PATCH 18/34] test(aud-scope): Update aud and scope claims in bunch of fixtures --- tests/admin/test_admin_users_endpoints.py | 2 +- tests/conftest.py | 1 - tests/utils/__init__.py | 32 ++++++++++++++--------- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/tests/admin/test_admin_users_endpoints.py b/tests/admin/test_admin_users_endpoints.py index 6719dc652..2d8b15f01 100644 --- a/tests/admin/test_admin_users_endpoints.py +++ b/tests/admin/test_admin_users_endpoints.py @@ -53,10 +53,10 @@ def encoded_admin_jwt(kid, rsa_private_key): headers = {"kid": kid} claims = utils.default_claims() claims["context"]["user"]["name"] = "admin_user@fake.com" - claims["aud"].append("admin") claims["sub"] = "5678" claims["iss"] = config["BASE_URL"] claims["exp"] += 600 + claims["scope"].append("admin") return jwt.encode( claims, key=rsa_private_key, headers=headers, algorithm="RS256" ).decode("utf-8") diff --git a/tests/conftest.py b/tests/conftest.py index 5fb46dcc4..bf5b4002e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -74,7 +74,6 @@ def mock_assume_role(self, role_arn, duration_seconds, config=None): def claims_refresh(): new_claims = tests.utils.default_claims() new_claims["pur"] = "refresh" - new_claims["aud"].append("fence") return new_claims diff --git a/tests/utils/__init__.py b/tests/utils/__init__.py index a2a4e0a64..fa8716ca0 100644 --- a/tests/utils/__init__.py +++ b/tests/utils/__init__.py @@ -235,19 +235,19 @@ def default_claims(): Return: dict: dictionary of claims """ - aud = ["openid", "user"] iss = "https://user-api.test.net" jti = new_jti() iat, exp = iat_and_exp() return { "pur": "access", - "aud": aud, + "aud": ["fake-client-id"], "sub": "1234", "iss": iss, "iat": iat, "exp": exp, "jti": jti, "azp": "", + "scope": ["openid", "user"], "context": { "user": { "name": "test-user", @@ -265,12 +265,11 @@ def unauthorized_context_claims(user_name, user_id): Return: dict: dictionary of claims """ - aud = ["access", "data", "user", "openid"] iss = config["BASE_URL"] jti = new_jti() iat, exp = iat_and_exp() return { - "aud": aud, + "aud": ["fake-client-id"], "sub": user_id, "pur": "access", "iss": iss, @@ -278,6 +277,7 @@ def unauthorized_context_claims(user_name, user_id): "exp": exp, "jti": jti, "azp": "", + "scope": ["access", "data", "user", "openid"], "context": { "user": { "name": "test", @@ -298,12 +298,11 @@ def authorized_download_context_claims(user_name, user_id): Return: dict: dictionary of claims """ - aud = ["access", "data", "user", "openid"] iss = config["BASE_URL"] jti = new_jti() iat, exp = iat_and_exp() return { - "aud": aud, + "aud": ["fake-client-id"], "sub": user_id, "iss": iss, "iat": iat, @@ -311,6 +310,7 @@ def authorized_download_context_claims(user_name, user_id): "jti": jti, "azp": "", "pur": "access", + "scope": ["access", "data", "user", "openid"], "context": { "user": { "name": user_name, @@ -331,12 +331,11 @@ def authorized_service_account_management_claims(user_name, user_id, client_id): Return: dict: dictionary of claims """ - aud = ["access", "data", "user", "openid", "google_link", "google_service_account"] iss = config["BASE_URL"] jti = new_jti() iat, exp = iat_and_exp() return { - "aud": aud, + "aud": ["fake-client-id"], "sub": user_id, "iss": iss, "iat": iat, @@ -344,6 +343,14 @@ def authorized_service_account_management_claims(user_name, user_id, client_id): "jti": jti, "azp": client_id, "pur": "access", + "scope": [ + "access", + "data", + "user", + "openid", + "google_link", + "google_service_account", + ], "context": { "user": { "name": user_name, @@ -365,7 +372,7 @@ def authorized_download_credentials_context_claims( Return: dict: dictionary of claims """ - aud = [ + scope = [ "access", "data", "user", @@ -378,7 +385,7 @@ def authorized_download_credentials_context_claims( jti = new_jti() iat, exp = iat_and_exp() return { - "aud": aud, + "aud": [client_id], "sub": user_id, "iss": iss, "iat": iat, @@ -386,6 +393,7 @@ def authorized_download_credentials_context_claims( "jti": jti, "azp": client_id, "pur": "access", + "scope": scope, "context": { "user": { "name": user_name, @@ -406,12 +414,11 @@ def authorized_upload_context_claims(user_name, user_id): Return: dict: dictionary of claims """ - aud = ["access", "data", "user", "openid"] iss = config["BASE_URL"] jti = new_jti() iat, exp = iat_and_exp() return { - "aud": aud, + "aud": ["fake-client-id"], "sub": user_id, "iss": iss, "pur": "access", @@ -419,6 +426,7 @@ def authorized_upload_context_claims(user_name, user_id): "exp": exp, "jti": jti, "azp": "test-client", + "scope": ["access", "data", "user", "openid"], "context": { "user": { "name": user_name, From 518dbab66aa14205b47b1f0b472c03c43e28fd27 Mon Sep 17 00:00:00 2001 From: vpsx <19900057+vpsx@users.noreply.github.com> Date: Mon, 21 Sep 2020 17:54:00 -0500 Subject: [PATCH 19/34] test(aud-scope): Update validate_jwt calls in bunch of tests --- tests/oidc/core/authorization/test_max_age.py | 2 +- tests/oidc/core/token/test_id_token.py | 4 ++-- tests/oidc/core/token/test_refresh.py | 9 +++++++-- tests/oidc/core/token/test_token_response.py | 8 ++++++-- tests/scripting/test_fence-create.py | 6 +++++- 5 files changed, 21 insertions(+), 8 deletions(-) diff --git a/tests/oidc/core/authorization/test_max_age.py b/tests/oidc/core/authorization/test_max_age.py index 39d75e87a..a2ef55ebe 100644 --- a/tests/oidc/core/authorization/test_max_age.py +++ b/tests/oidc/core/authorization/test_max_age.py @@ -29,5 +29,5 @@ def test_id_token_contains_auth_time(oauth_test_client): data = {"confirm": "yes", "max_age": 3600} oauth_test_client.authorize(data=data) id_token = oauth_test_client.token().id_token - id_token_claims = validate_jwt(id_token, {"openid"}) + id_token_claims = validate_jwt(id_token, aud="test-client") assert "auth_time" in id_token_claims diff --git a/tests/oidc/core/token/test_id_token.py b/tests/oidc/core/token/test_id_token.py index e6ea886ab..69d2ff5b6 100644 --- a/tests/oidc/core/token/test_id_token.py +++ b/tests/oidc/core/token/test_id_token.py @@ -135,7 +135,7 @@ def test_id_token_has_nonce(oauth_test_client): data = {"confirm": "yes", "nonce": nonce} oauth_test_client.authorize(data=data) response_json = oauth_test_client.token(data=data).response.json - id_token = validate_jwt(response_json["id_token"], {"openid"}) + id_token = validate_jwt(response_json["id_token"], aud="test-client") assert "nonce" in id_token assert nonce == id_token["nonce"] @@ -144,6 +144,6 @@ def test_aud(client, oauth_client, id_token): """ Test that the audiences of the ID token contain the OAuth client id. """ - id_claims = validate_jwt(id_token, {"openid"}) + id_claims = validate_jwt(id_token, aud=oauth_client.client_id) assert "aud" in id_claims assert oauth_client.client_id in id_claims["aud"] diff --git a/tests/oidc/core/token/test_refresh.py b/tests/oidc/core/token/test_refresh.py index 2efe2ef4d..a62996bc8 100644 --- a/tests/oidc/core/token/test_refresh.py +++ b/tests/oidc/core/token/test_refresh.py @@ -27,13 +27,15 @@ def test_same_claims(oauth_test_client, token_response_json): original_id_token = token_response_json["id_token"] - original_claims = validate_jwt(original_id_token, {"openid"}) + original_claims = validate_jwt(original_id_token, aud=oauth_test_client.client_id) refresh_token = token_response_json["refresh_token"] refresh_token_response = oauth_test_client.refresh( refresh_token=refresh_token ).response assert "id_token" in refresh_token_response.json - new_claims = validate_jwt(refresh_token_response.json["id_token"], {"openid"}) + new_claims = validate_jwt( + refresh_token_response.json["id_token"], aud=oauth_test_client.client_id + ) assert original_claims["iss"] == new_claims["iss"] assert original_claims["sub"] == new_claims["sub"] assert original_claims["iat"] <= new_claims["iat"] @@ -42,3 +44,6 @@ def test_same_claims(oauth_test_client, token_response_json): assert original_claims["azp"] == new_claims["azp"] else: assert "azp" not in new_claims + + # Also test that custom (non-OIDC) scope claim is unchanged + assert original_claims["scope"] == new_claims["scope"] diff --git a/tests/oidc/core/token/test_token_response.py b/tests/oidc/core/token/test_token_response.py index 74468fb7b..d87b6847e 100644 --- a/tests/oidc/core/token/test_token_response.py +++ b/tests/oidc/core/token/test_token_response.py @@ -38,7 +38,9 @@ def test_id_token_required_fields(token_response): """ assert "id_token" in token_response.json # Check that the ID token is a valid JWT. - id_token = validate_jwt(token_response.json["id_token"], {"openid"}) + id_token = validate_jwt( + token_response.json["id_token"], aud="test-client", scope={"openid"} + ) # Check for required fields. assert "pur" in id_token and id_token["pur"] == "id" @@ -62,7 +64,9 @@ def test_access_token_correct_fields(token_response): expected fields. """ encoded_access_token = token_response.json["access_token"] - access_token = validate_jwt(encoded_access_token, {"openid"}) + access_token = validate_jwt( + encoded_access_token, aud="test-client", scope={"openid"} + ) access_token_fields = set(access_token.keys()) expected_fields = { "pur", diff --git a/tests/scripting/test_fence-create.py b/tests/scripting/test_fence-create.py index 4a87e678d..3790b67e7 100644 --- a/tests/scripting/test_fence-create.py +++ b/tests/scripting/test_fence-create.py @@ -367,7 +367,11 @@ def test_create_refresh_token_with_found_user( refresh_token=jwt_result.token ).response - ret_claims = validate_jwt(refresh_token_response.json["id_token"], {"openid"}) + ret_claims = validate_jwt( + refresh_token_response.json["id_token"], + aud=oauth_test_client.client_id, + scope={"openid"}, + ) assert jwt_result.claims["iss"] == ret_claims["iss"] assert jwt_result.claims["sub"] == ret_claims["sub"] assert jwt_result.claims["iat"] <= ret_claims["iat"] From a084977f9b16dc67c52636dede24bc9dfb5e8328 Mon Sep 17 00:00:00 2001 From: vpsx <19900057+vpsx@users.noreply.github.com> Date: Wed, 23 Sep 2020 15:40:41 -0500 Subject: [PATCH 20/34] Temporarily install authutils requirement from branch --- poetry.lock | 34 +++++++++++++++++++--------------- pyproject.toml | 3 ++- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/poetry.lock b/poetry.lock index dfde7a00c..994e3e19e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -65,19 +65,25 @@ version = "5.0.5" description = "Gen3 auth utility functions" category = "main" optional = false -python-versions = ">=3.6,<4.0" +python-versions = "^3.6" [package.dependencies] -authlib = ">=0.11,<1.0" -cached-property = ">=1.4,<2.0" +authlib = "~=0.11" +cached-property = "~=1.4" cdiserrors = "<2.0.0" httpx = ">=0.12.1,<1.0.0" -pyjwt = {version = ">=1.5,<2.0", extras = ["crypto"]} -xmltodict = ">=0.9,<1.0" +pyjwt = {version = "~=1.5", extras = ["crypto"]} +xmltodict = "~=0.9" [package.extras] flask = ["Flask (>=0.10.1)"] -fastapi = ["fastapi (>=0.54.1,<0.55.0)"] +fastapi = ["fastapi (^0.54.1)"] + +[package.source] +type = "git" +url = "https://github.com/uc-cdis/authutils.git" +reference = "fix/aud" +resolved_reference = "229f586e3412c3eeb1668532c3b6a9e88bfe846b" [[package]] name = "aws-xray-sdk" @@ -113,7 +119,7 @@ cffi = ">=1.1" six = ">=1.4.1" [package.extras] -tests = ["pytest (>=3.2.1,!=3.3.0)"] +tests = ["pytest (>=3.2.1,<3.3.0 || >3.3.0)"] typecheck = ["mypy"] [[package]] @@ -214,7 +220,6 @@ description = "Collection of test data and tools" category = "dev" optional = false python-versions = "*" -develop = false [package.source] type = "git" @@ -334,11 +339,11 @@ cffi = ">=1.8,<1.11.3 || >1.11.3" six = ">=1.4.1" [package.extras] -docs = ["sphinx (>=1.6.5,!=1.8.0)", "sphinx-rtd-theme"] +docs = ["sphinx (>=1.6.5,<1.8.0 || >1.8.0)", "sphinx-rtd-theme"] docstest = ["doc8", "pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"] idna = ["idna (>=2.1)"] pep8test = ["flake8", "flake8-import-order", "pep8-naming"] -test = ["pytest (>=3.6.0,!=3.9.0,!=3.9.1,!=3.9.2)", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] +test = ["pytest (>=3.6.0,<3.9.0 || >3.9.0,<3.9.1 || >3.9.1,<3.9.2 || >3.9.2)", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,<3.79.2 || >3.79.2)"] [[package]] name = "decorator" @@ -1110,7 +1115,7 @@ six = "*" [package.extras] docs = ["sphinx (>=1.6.5)", "sphinx-rtd-theme"] -tests = ["pytest (>=3.2.1,!=3.3.0)", "hypothesis (>=3.27.0)"] +tests = ["pytest (>=3.2.1,<3.3.0 || >3.3.0)", "hypothesis (>=3.27.0)"] [[package]] name = "pyparsing" @@ -1150,7 +1155,7 @@ coverage = ">=4.4" pytest = ">=3.6" [package.extras] -testing = ["fields", "hunter", "process-tests (==2.0.2)", "six", "pytest-xdist", "virtualenv"] +testing = ["fields", "hunter", "process-tests (2.0.2)", "six", "pytest-xdist", "virtualenv"] [[package]] name = "pytest-flask" @@ -1237,7 +1242,7 @@ urllib3 = ">=1.21.1,<1.27" [package.extras] security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] -socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] +socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"] [[package]] name = "responses" @@ -1349,7 +1354,6 @@ description = "" category = "main" optional = false python-versions = "*" -develop = false [package.dependencies] boto = ">=2.36.0,<3.0.0" @@ -1394,7 +1398,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" [package.extras] brotli = ["brotlipy (>=0.6.0)"] secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] -socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] +socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"] [[package]] name = "userdatamodel" diff --git a/pyproject.toml b/pyproject.toml index 91130f6ee..513c89025 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,8 @@ include = [ [tool.poetry.dependencies] python = "^3.6" authlib = "^0.11" -authutils = "^5.0.5" +#authutils = "^5.0.5" +authutils = {git = "https://github.com/uc-cdis/authutils.git", branch = "fix/aud"} bcrypt = "^3.1.4" boto3 = "~1.9.91" botocore = "^1.12.253" From 9da4dbe05561a8a6fe72a41725053643486ace2d Mon Sep 17 00:00:00 2001 From: vpsx <19900057+vpsx@users.noreply.github.com> Date: Thu, 8 Oct 2020 09:32:17 -0500 Subject: [PATCH 21/34] fix(aud): Include issuer in aud claim for all tokens * (session, refresh, access, id, API key) --- fence/jwt/token.py | 17 +++++++++++------ tests/utils/__init__.py | 2 +- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/fence/jwt/token.py b/fence/jwt/token.py index 246934e8f..706e639fb 100644 --- a/fence/jwt/token.py +++ b/fence/jwt/token.py @@ -178,7 +178,7 @@ def generate_signed_session_token(kid, private_key, expires_in, context=None): claims = { "pur": "session", - "aud": ["fence"], + "aud": ["fence", issuer], "sub": context.get("user_id", ""), "iss": issuer, "iat": iat, @@ -296,6 +296,7 @@ def generate_signed_refresh_token( "pur": "refresh", "sub": sub, "iss": iss, + "aud": [iss], "iat": iat, "exp": exp, "jti": jti, @@ -304,7 +305,7 @@ def generate_signed_refresh_token( } if client_id: - claims["aud"] = [client_id] + claims["aud"].append(client_id) logger.info("issuing JWT refresh token with id [{}] to [{}]".format(jti, sub)) logger.debug("issuing JWT refresh token\n" + json.dumps(claims, indent=4)) @@ -334,10 +335,12 @@ def generate_api_key(kid, private_key, user_id, expires_in, scopes, client_id): iat, exp = issued_and_expiration_times(expires_in) jti = str(uuid.uuid4()) sub = str(user_id) + iss = config.get("BASE_URL") claims = { "pur": "api_key", "sub": sub, - "iss": config.get("BASE_URL"), + "iss": iss, + "aud": [iss], "iat": iat, "exp": exp, "jti": jti, @@ -397,6 +400,7 @@ def generate_signed_access_token( "pur": "access", "sub": sub, "iss": iss, + "aud": [iss], "iat": iat, "exp": exp, "jti": jti, @@ -412,7 +416,7 @@ def generate_signed_access_token( } if client_id: - claims["aud"] = [client_id] + claims["aud"].append(client_id) if include_project_access: # NOTE: "THIS IS A TERRIBLE STOP-GAP SOLUTION SO THAT USERS WITH @@ -536,8 +540,9 @@ def generate_id_token( aud = audiences.copy() if audiences else [] if client_id and client_id not in aud: aud.append(client_id) - if aud: - claims["aud"] = aud + if issuer not in aud: + aud.append(issuer) + claims["aud"] = aud if user.tags: claims["context"]["user"]["tags"] = {tag.key: tag.value for tag in user.tags} diff --git a/tests/utils/__init__.py b/tests/utils/__init__.py index fa8716ca0..03fb32cb2 100644 --- a/tests/utils/__init__.py +++ b/tests/utils/__init__.py @@ -385,7 +385,7 @@ def authorized_download_credentials_context_claims( jti = new_jti() iat, exp = iat_and_exp() return { - "aud": [client_id], + "aud": [client_id, iss], "sub": user_id, "iss": iss, "iat": iat, From 5048aa574a60fa17c8910f503f21cf0ce8e85a46 Mon Sep 17 00:00:00 2001 From: vpsx <19900057+vpsx@users.noreply.github.com> Date: Thu, 8 Oct 2020 09:33:44 -0500 Subject: [PATCH 22/34] fix(aud): In validate_jwt, set aud argument default to issuer --- fence/jwt/validate.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/fence/jwt/validate.py b/fence/jwt/validate.py index 5367c9791..8028df8da 100644 --- a/fence/jwt/validate.py +++ b/fence/jwt/validate.py @@ -56,10 +56,11 @@ def validate_jwt( Args: encoded_token (str): the base64 encoding of the token aud (Optional[str]): - audience with which the app identifies, usually an OIDC - client id, which the JWT will be expected to include in its ``aud`` - claim. Optional; if no ``aud`` argument given and the JWT has no - ``aud`` claim, validation will pass. + audience as which the app identifies, which the JWT will be + expected to include in its ``aud`` claim. + Optional; will default to issuer (config["BASE_URL"]). + To skip aud validation, pass the following as a kwarg: + options={"verify_aud": False} scope (Optional[Iterable[str]]): list of scopes each of which the token must satisfy; defaults to ``{'openid'}`` (minimum expected by OpenID provider). @@ -88,6 +89,10 @@ def validate_jwt( type(scope) is set or type(scope) is list or scope is None ), "scope argument must be set or list or None" + # Can't set arg default to config[x] in fn def, so doing it this way. + if aud is None: + aud = config["BASE_URL"] + iss = config["BASE_URL"] issuers = [iss] oidc_iss = ( From e4dc5b878d6c480a05f3260f974ffee388b4b4db Mon Sep 17 00:00:00 2001 From: vpsx <19900057+vpsx@users.noreply.github.com> Date: Thu, 8 Oct 2020 14:30:51 -0500 Subject: [PATCH 23/34] test(aud): Switch test claims fixtures to have iss in aud, not client_id --- tests/utils/__init__.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/utils/__init__.py b/tests/utils/__init__.py index 03fb32cb2..c7193a4f3 100644 --- a/tests/utils/__init__.py +++ b/tests/utils/__init__.py @@ -235,12 +235,12 @@ def default_claims(): Return: dict: dictionary of claims """ - iss = "https://user-api.test.net" + iss = config["BASE_URL"] jti = new_jti() iat, exp = iat_and_exp() return { "pur": "access", - "aud": ["fake-client-id"], + "aud": [iss], "sub": "1234", "iss": iss, "iat": iat, @@ -269,7 +269,7 @@ def unauthorized_context_claims(user_name, user_id): jti = new_jti() iat, exp = iat_and_exp() return { - "aud": ["fake-client-id"], + "aud": [iss], "sub": user_id, "pur": "access", "iss": iss, @@ -302,7 +302,7 @@ def authorized_download_context_claims(user_name, user_id): jti = new_jti() iat, exp = iat_and_exp() return { - "aud": ["fake-client-id"], + "aud": [iss], "sub": user_id, "iss": iss, "iat": iat, @@ -335,7 +335,7 @@ def authorized_service_account_management_claims(user_name, user_id, client_id): jti = new_jti() iat, exp = iat_and_exp() return { - "aud": ["fake-client-id"], + "aud": [iss], "sub": user_id, "iss": iss, "iat": iat, @@ -385,7 +385,7 @@ def authorized_download_credentials_context_claims( jti = new_jti() iat, exp = iat_and_exp() return { - "aud": [client_id, iss], + "aud": [iss], "sub": user_id, "iss": iss, "iat": iat, @@ -418,7 +418,7 @@ def authorized_upload_context_claims(user_name, user_id): jti = new_jti() iat, exp = iat_and_exp() return { - "aud": ["fake-client-id"], + "aud": [iss], "sub": user_id, "iss": iss, "pur": "access", From 963f8da843492038959d31fda1e659b07dcc5f37 Mon Sep 17 00:00:00 2001 From: vpsx <19900057+vpsx@users.noreply.github.com> Date: Thu, 8 Oct 2020 17:25:05 -0500 Subject: [PATCH 24/34] fix(aud): Enable aud claim validation in has_oauth/login_required * Remove corresponding TECHDEBT entry * Reverts fc6fc6c04eab9a978e7e24abdf2d55c150f008ba, minus the unusued import cleanup --- TECHDEBT.md | 15 --------------- fence/auth.py | 3 ++- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/TECHDEBT.md b/TECHDEBT.md index b1b12d898..a740e63b3 100644 --- a/TECHDEBT.md +++ b/TECHDEBT.md @@ -22,21 +22,6 @@ Address above. n/a -### Not validating aud claim in Bearer tokens -##### Problem: -- The login_required decorator purports to enforce requirement of a user session (see fence/auth.py). -- However, it also allows falling back on the presence of a Bearer token in the request. -- In the latter case, the decorator calls has_oauth, which validates the JWT and checks that it has the right scopes. However, the validation does not verify the 'aud' claim. This is a security risk in settings where one Authorization server is issuing JWTs for multiple Resource servers. See [RFC 6819 5.1.5.5](https://tools.ietf.org/html/rfc6819#section-5.1.5.5). -##### Historical context: -- Fence used to use the 'aud' claim for scopes, overriding conventional 'aud' validation; the 'aud' claim was therefore not being used or validated correctly. -- Now scopes have been moved into a custom 'scope' claim with custom 'scope' validation, and 'aud' is populated with client_id (required by OIDC for id_tokens). 'aud' validation has reverted to the conventional validation. -- However, this means that now Fence cannot consume its own JWTs in its capacity as its own Resource server, since it does not identify as the client_id. -- In order to keep allowing the affected Fence endpoints to be used with a Bearer token, has_oauth currently skips validation of the 'aud' claim. -##### Possible solution: -- Along with client_id, put iss in aud as well? Need to think about whether this captures all current use cases, e.g. when Fence is trusted as Auth server to different Resource servers with different domains. -### Other notes: -- Also applies to require_auth_header decorator in authutils. - ### Tokens not bound to client when issued via fence-create ##### Problem: diff --git a/fence/auth.py b/fence/auth.py index 695881134..b7e86b729 100644 --- a/fence/auth.py +++ b/fence/auth.py @@ -210,7 +210,8 @@ def has_oauth(scope=None): scope.update({"openid"}) try: access_token_claims = validate_jwt( - scope=scope, purpose="access", options={"verify_aud": False} + scope=scope, + purpose="access", ) except JWTError as e: raise Unauthorized("failed to validate token: {}".format(e)) From 162d719d7947f9ba1922c2791e3b74ec562e2e49 Mon Sep 17 00:00:00 2001 From: vpsx <19900057+vpsx@users.noreply.github.com> Date: Fri, 9 Oct 2020 11:22:12 -0500 Subject: [PATCH 25/34] fix(aud): Enable aud validation for refresh tokens * Expect iss, not client_id, in refresh token aud claim * Otherwise reverts f3a87ba037075ef5c661e7cf3915febea13bbb81 --- TECHDEBT.md | 15 --------------- fence/oidc/grants/refresh_token_grant.py | 2 -- tests/scripting/test_fence-create.py | 1 - 3 files changed, 18 deletions(-) diff --git a/TECHDEBT.md b/TECHDEBT.md index a740e63b3..863659c7e 100644 --- a/TECHDEBT.md +++ b/TECHDEBT.md @@ -20,18 +20,3 @@ Fence presently guards several endpoints (e.g. /data, signed urls, SA registrati Address above. ##### Other notes: n/a - - - -### Tokens not bound to client when issued via fence-create -##### Problem: -- `fence-create` allows the creation of access and refresh tokens that are not bound to any Fence client. This is a deliberate feature. -- Tokens issued this way do not have an 'aud' claim. -- It is unclear what to do when, for example, such a refresh token without an aud claim is used in a token request. The token _request_ is still bound to a client (since the client_id query parameter is required), and the validation code checks the token aud claim for the client_id, does not find it, and fails. On the other hand, if the client_id from the query parameter is not passed to the validation code, then when there is a token request made with a refresh token _with_ an aud claim (one issued during normal auth code flow), validation will also fail. There is no way for Fence to know which sort of token to expect. -##### Other notes: -- Per OAuth2 spec, "The authorization server MUST maintain the binding between a refresh token and the client to whom it was issued" [(10.4)](https://tools.ietf.org/html/rfc6749#section-10.4) [(see also 6)](https://tools.ietf.org/html/rfc6749#section-6). -##### Possible solution: -- Require client_id as an argument in `fence-create token-create`? In other words, require that tokens issued via `fence-create` need to be bound to a client. -##### For now: -- Disable aud validation altogether for refresh tokens. -- Obviously this is a band-aid solution: Skipping validation is not ideal and JWTCreator still issues access tokens without aud claims as well. The more general issue is that Fence should either always expect the aud claim, or never expect it. diff --git a/fence/oidc/grants/refresh_token_grant.py b/fence/oidc/grants/refresh_token_grant.py index 2f8dce228..e5e16c4cc 100644 --- a/fence/oidc/grants/refresh_token_grant.py +++ b/fence/oidc/grants/refresh_token_grant.py @@ -43,9 +43,7 @@ def authenticate_refresh_token(self, refresh_token): """ return validate_jwt( refresh_token, - aud=self.client.client_id, purpose="refresh", - options={"verify_aud": False}, ) def create_access_token(self, token, client, authenticated_token): diff --git a/tests/scripting/test_fence-create.py b/tests/scripting/test_fence-create.py index 3790b67e7..88a7fa73d 100644 --- a/tests/scripting/test_fence-create.py +++ b/tests/scripting/test_fence-create.py @@ -369,7 +369,6 @@ def test_create_refresh_token_with_found_user( ret_claims = validate_jwt( refresh_token_response.json["id_token"], - aud=oauth_test_client.client_id, scope={"openid"}, ) assert jwt_result.claims["iss"] == ret_claims["iss"] From 4dfb3cfd509ca6d9f9bc4f6e20e22753e3c0bcf8 Mon Sep 17 00:00:00 2001 From: vpsx <19900057+vpsx@users.noreply.github.com> Date: Fri, 9 Oct 2020 13:14:13 -0500 Subject: [PATCH 26/34] fix(aud): Update remaining validate_jwt calls * Most updates are to rm aud argument and use the default value --- fence/blueprints/login/fence_login.py | 2 +- fence/resources/storage/cdis_jwt.py | 4 ++-- fence/resources/user/user_session.py | 2 -- tests/oidc/core/authorization/test_max_age.py | 2 +- tests/oidc/core/token/test_id_token.py | 4 ++-- tests/oidc/core/token/test_refresh.py | 6 ++---- tests/oidc/core/token/test_token_response.py | 8 ++------ 7 files changed, 10 insertions(+), 18 deletions(-) diff --git a/fence/blueprints/login/fence_login.py b/fence/blueprints/login/fence_login.py index d530b2cc6..dd12e7917 100644 --- a/fence/blueprints/login/fence_login.py +++ b/fence/blueprints/login/fence_login.py @@ -92,7 +92,7 @@ def get(self): redirect_uri, **flask.request.args.to_dict() ) id_token_claims = validate_jwt( - tokens["id_token"], aud="openid", purpose="id", attempt_refresh=True + tokens["id_token"], scope="openid", purpose="id", attempt_refresh=True ) username = id_token_claims["context"]["user"]["name"] login_user( diff --git a/fence/resources/storage/cdis_jwt.py b/fence/resources/storage/cdis_jwt.py index d5c779657..f8a5120f0 100644 --- a/fence/resources/storage/cdis_jwt.py +++ b/fence/resources/storage/cdis_jwt.py @@ -10,8 +10,8 @@ def create_access_token(user, keypair, api_key, expires_in, scopes): try: - claims = validate_jwt(api_key, aud=scopes, purpose="api_key") - if not set(claims["aud"]).issuperset(scopes): + claims = validate_jwt(api_key, scope=scopes, purpose="api_key") + if not set(claims["scope"]).issuperset(scopes): raise JWTError("cannot issue access token with scope beyond refresh token") except Exception as e: return flask.jsonify({"errors": str(e)}) diff --git a/fence/resources/user/user_session.py b/fence/resources/user/user_session.py index 21d4142d0..8f3703b29 100644 --- a/fence/resources/user/user_session.py +++ b/fence/resources/user/user_session.py @@ -50,7 +50,6 @@ def __init__(self, session_token): try: jwt_info = validate_jwt( session_token, - aud="fence", scope=None, purpose="session", public_key=default_public_key(), @@ -79,7 +78,6 @@ def _get_initial_session_token(self): initial_token = validate_jwt( session_token, - aud="fence", scope=None, purpose="session", public_key=default_public_key(), diff --git a/tests/oidc/core/authorization/test_max_age.py b/tests/oidc/core/authorization/test_max_age.py index a2ef55ebe..a5c94b34b 100644 --- a/tests/oidc/core/authorization/test_max_age.py +++ b/tests/oidc/core/authorization/test_max_age.py @@ -29,5 +29,5 @@ def test_id_token_contains_auth_time(oauth_test_client): data = {"confirm": "yes", "max_age": 3600} oauth_test_client.authorize(data=data) id_token = oauth_test_client.token().id_token - id_token_claims = validate_jwt(id_token, aud="test-client") + id_token_claims = validate_jwt(id_token) assert "auth_time" in id_token_claims diff --git a/tests/oidc/core/token/test_id_token.py b/tests/oidc/core/token/test_id_token.py index 69d2ff5b6..6c780d368 100644 --- a/tests/oidc/core/token/test_id_token.py +++ b/tests/oidc/core/token/test_id_token.py @@ -135,7 +135,7 @@ def test_id_token_has_nonce(oauth_test_client): data = {"confirm": "yes", "nonce": nonce} oauth_test_client.authorize(data=data) response_json = oauth_test_client.token(data=data).response.json - id_token = validate_jwt(response_json["id_token"], aud="test-client") + id_token = validate_jwt(response_json["id_token"]) assert "nonce" in id_token assert nonce == id_token["nonce"] @@ -144,6 +144,6 @@ def test_aud(client, oauth_client, id_token): """ Test that the audiences of the ID token contain the OAuth client id. """ - id_claims = validate_jwt(id_token, aud=oauth_client.client_id) + id_claims = validate_jwt(id_token) assert "aud" in id_claims assert oauth_client.client_id in id_claims["aud"] diff --git a/tests/oidc/core/token/test_refresh.py b/tests/oidc/core/token/test_refresh.py index a62996bc8..c1b6db47f 100644 --- a/tests/oidc/core/token/test_refresh.py +++ b/tests/oidc/core/token/test_refresh.py @@ -27,15 +27,13 @@ def test_same_claims(oauth_test_client, token_response_json): original_id_token = token_response_json["id_token"] - original_claims = validate_jwt(original_id_token, aud=oauth_test_client.client_id) + original_claims = validate_jwt(original_id_token) refresh_token = token_response_json["refresh_token"] refresh_token_response = oauth_test_client.refresh( refresh_token=refresh_token ).response assert "id_token" in refresh_token_response.json - new_claims = validate_jwt( - refresh_token_response.json["id_token"], aud=oauth_test_client.client_id - ) + new_claims = validate_jwt(refresh_token_response.json["id_token"]) assert original_claims["iss"] == new_claims["iss"] assert original_claims["sub"] == new_claims["sub"] assert original_claims["iat"] <= new_claims["iat"] diff --git a/tests/oidc/core/token/test_token_response.py b/tests/oidc/core/token/test_token_response.py index d87b6847e..fccb5f348 100644 --- a/tests/oidc/core/token/test_token_response.py +++ b/tests/oidc/core/token/test_token_response.py @@ -38,9 +38,7 @@ def test_id_token_required_fields(token_response): """ assert "id_token" in token_response.json # Check that the ID token is a valid JWT. - id_token = validate_jwt( - token_response.json["id_token"], aud="test-client", scope={"openid"} - ) + id_token = validate_jwt(token_response.json["id_token"], scope={"openid"}) # Check for required fields. assert "pur" in id_token and id_token["pur"] == "id" @@ -64,9 +62,7 @@ def test_access_token_correct_fields(token_response): expected fields. """ encoded_access_token = token_response.json["access_token"] - access_token = validate_jwt( - encoded_access_token, aud="test-client", scope={"openid"} - ) + access_token = validate_jwt(encoded_access_token, scope={"openid"}) access_token_fields = set(access_token.keys()) expected_fields = { "pur", From 0df7db4c9fec7d15b8e371efe4a21606f8fe9267 Mon Sep 17 00:00:00 2001 From: vpsx <19900057+vpsx@users.noreply.github.com> Date: Fri, 9 Oct 2020 15:49:21 -0500 Subject: [PATCH 27/34] docs(aud): Remove techdebt item! about using aud claim for scopes --- TECHDEBT.md | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/TECHDEBT.md b/TECHDEBT.md index 863659c7e..83ac5d87f 100644 --- a/TECHDEBT.md +++ b/TECHDEBT.md @@ -1,22 +1 @@ # Tech debt - -### Using 'aud' claim for scopes -- Observed: July 2020 -- Impact: (If this tech debt affected your work somehow, add a +1 here with a date and note) - - +1 Zoe 2020 July 15 This is an example of a +1 - - +1 Vahid Oct 2020 - -##### Problem: -Fence puts OAuth2 scopes into the 'aud' claim of access tokens. -##### Why it was done this way: -We don't know. -##### Why this way is problematic: -Per RFC7519 the aud claim [is not meant for scopes](https://tools.ietf.org/html/rfc7519#section-4.1.3). -##### What the solution might be: -GA4GH AAI [already requires](https://github.com/ga4gh/data-security/blob/master/AAI/AAIConnectProfile.md#access_token-issued-by-broker) that a 'scope' claim be included in access tokens issued by Passport Brokers. So as of July 2020 we will put scopes in the 'scope' claim. However, this is in addition to keeping them in the 'aud' claim. Ideally we would only have the scopes in the 'scope' claim. -##### Why we aren't already doing the above: -Fence presently guards several endpoints (e.g. /data, signed urls, SA registration) by checking the scopes in the 'aud' claim of the JWT. This code would need to be changed. -##### Next steps: -Address above. -##### Other notes: -n/a From 75f17cf1bf9a34bef8234a99dcc907d37595638c Mon Sep 17 00:00:00 2001 From: vpsx <19900057+vpsx@users.noreply.github.com> Date: Tue, 16 Feb 2021 12:14:21 -0600 Subject: [PATCH 28/34] fix(aud): Update validate_request calls --- fence/blueprints/data/indexd.py | 4 +++- fence/blueprints/link.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/fence/blueprints/data/indexd.py b/fence/blueprints/data/indexd.py index ed59d6baa..c122bb3b7 100644 --- a/fence/blueprints/data/indexd.py +++ b/fence/blueprints/data/indexd.py @@ -1147,7 +1147,9 @@ def _get_user_info(sub_to_string=True): populated information about an anonymous user. """ try: - set_current_token(validate_request(scope={"user"})) + set_current_token( + validate_request(scope={"user"}, audience=config.get("BASE_URL")) + ) user_id = current_token["sub"] if sub_to_string: user_id = str(user_id) diff --git a/fence/blueprints/link.py b/fence/blueprints/link.py index 988a52816..b77c587bf 100644 --- a/fence/blueprints/link.py +++ b/fence/blueprints/link.py @@ -274,7 +274,9 @@ def get(self): # if we're mocking google auth, mock response to include the email # from the provided access token try: - token = validate_request({"user"}) + token = validate_request( + scope={"user"}, audience=config.get("BASE_URL") + ) email = get_user_from_claims(token).username except Exception as exc: logger.info( From aa654ba1cbd53b8e989b9b9d9c6391c5705480d5 Mon Sep 17 00:00:00 2001 From: vpsx <19900057+vpsx@users.noreply.github.com> Date: Fri, 23 Apr 2021 11:02:35 -0500 Subject: [PATCH 29/34] fix(aud-scope): Use isinstance() not type() --- fence/jwt/validate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fence/jwt/validate.py b/fence/jwt/validate.py index 8028df8da..81f33e8f8 100644 --- a/fence/jwt/validate.py +++ b/fence/jwt/validate.py @@ -86,7 +86,7 @@ def validate_jwt( raise JWTError(e.message) assert ( - type(scope) is set or type(scope) is list or scope is None + isinstance(scope, set) or isinstance(scope, list) or scope is None ), "scope argument must be set or list or None" # Can't set arg default to config[x] in fn def, so doing it this way. From 20c80a455de6d61b7b11ea81771183e06c8d9e48 Mon Sep 17 00:00:00 2001 From: vpsx <19900057+vpsx@users.noreply.github.com> Date: Thu, 29 Apr 2021 15:52:34 -0500 Subject: [PATCH 30/34] chore(aud-scope): bump authutils to ^6.0.0 --- poetry.lock | 390 ++++++++++++++++--------------------------------- pyproject.toml | 3 +- 2 files changed, 130 insertions(+), 263 deletions(-) diff --git a/poetry.lock b/poetry.lock index 994e3e19e..7cf88e9a6 100644 --- a/poetry.lock +++ b/poetry.lock @@ -61,42 +61,23 @@ requests = "*" [[package]] name = "authutils" -version = "5.0.5" +version = "6.0.1" description = "Gen3 auth utility functions" category = "main" optional = false -python-versions = "^3.6" +python-versions = ">=3.6,<4.0" [package.dependencies] -authlib = "~=0.11" -cached-property = "~=1.4" +authlib = ">=0.11,<1.0" +cached-property = ">=1.4,<2.0" cdiserrors = "<2.0.0" httpx = ">=0.12.1,<1.0.0" -pyjwt = {version = "~=1.5", extras = ["crypto"]} -xmltodict = "~=0.9" +pyjwt = {version = ">=1.5,<2.0", extras = ["crypto"]} +xmltodict = ">=0.9,<1.0" [package.extras] flask = ["Flask (>=0.10.1)"] -fastapi = ["fastapi (^0.54.1)"] - -[package.source] -type = "git" -url = "https://github.com/uc-cdis/authutils.git" -reference = "fix/aud" -resolved_reference = "229f586e3412c3eeb1668532c3b6a9e88bfe846b" - -[[package]] -name = "aws-xray-sdk" -version = "0.95" -description = "The AWS X-Ray SDK for Python (the SDK) enables Python developers to record and emit information from within their applications to the AWS X-Ray service." -category = "dev" -optional = false -python-versions = "*" - -[package.dependencies] -jsonpickle = "*" -requests = "*" -wrapt = "*" +fastapi = ["fastapi (>=0.54.1,<0.55.0)"] [[package]] name = "backoff" @@ -256,14 +237,11 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "click" -version = "8.0.0" +version = "7.1.2" description = "Composable command line interface toolkit" category = "main" optional = false -python-versions = ">=3.6" - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "codacy-coverage" @@ -284,7 +262,7 @@ test = ["coverage", "nosetests"] name = "colorama" version = "0.4.4" description = "Cross-platform colored terminal text." -category = "main" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" @@ -347,7 +325,7 @@ test = ["pytest (>=3.6.0,<3.9.0 || >3.9.0,<3.9.1 || >3.9.1,<3.9.2 || >3.9.2)", " [[package]] name = "decorator" -version = "5.0.7" +version = "5.0.9" description = "Decorators for Humans" category = "main" optional = false @@ -368,23 +346,6 @@ idna = ["idna (>=2.1)"] curio = ["curio (>=1.2)", "sniffio (>=1.1)"] trio = ["trio (>=0.14.0)", "sniffio (>=1.1)"] -[[package]] -name = "docker" -version = "5.0.0" -description = "A Python library for the Docker Engine API." -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -pywin32 = {version = "227", markers = "sys_platform == \"win32\""} -requests = ">=2.14.2,<2.18.0 || >2.18.0" -websocket-client = ">=0.32.0" - -[package.extras] -ssh = ["paramiko (>=2.4.2)"] -tls = ["pyOpenSSL (>=17.5.0)", "cryptography (>=3.4.7)", "idna (>=2.0.0)"] - [[package]] name = "docopt" version = "0.6.2" @@ -430,17 +391,17 @@ idna = ">=2.0.0" [[package]] name = "flask" -version = "1.1.2" +version = "1.1.4" description = "A simple framework for building complex web applications." category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.dependencies] -click = ">=5.1" -itsdangerous = ">=0.24" -Jinja2 = ">=2.10.1" -Werkzeug = ">=0.15" +click = ">=5.1,<8.0" +itsdangerous = ">=0.24,<2.0" +Jinja2 = ">=2.10.1,<3.0" +Werkzeug = ">=0.15,<2.0" [package.extras] dev = ["pytest", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinxcontrib-log-cabinet", "sphinx-issues"] @@ -461,7 +422,7 @@ Six = "*" [[package]] name = "flask-restful" -version = "0.3.8" +version = "0.3.9" description = "Simple framework for creating REST APIs" category = "main" optional = false @@ -533,7 +494,7 @@ oauth2client = ">=2.0.0,<4.0dev" [[package]] name = "gen3config" -version = "0.1.8" +version = "0.1.9" description = "Gen3 Configuration Library" category = "main" optional = false @@ -561,14 +522,14 @@ PyYAML = ">=5.1,<6.0" [[package]] name = "google-api-core" -version = "1.26.3" +version = "1.28.0" description = "Google API client core library" category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*" [package.dependencies] -google-auth = ">=1.21.1,<2.0dev" +google-auth = ">=1.25.0,<2.0dev" googleapis-common-protos = ">=1.6.0,<2.0dev" packaging = ">=14.3" protobuf = ">=3.12.0" @@ -599,7 +560,7 @@ uritemplate = ">=3.0.0,<4dev" [[package]] name = "google-auth" -version = "1.30.0" +version = "1.30.1" description = "Google Authentication Library" category = "main" optional = false @@ -675,7 +636,7 @@ testing = ["pytest"] [[package]] name = "google-resumable-media" -version = "1.2.0" +version = "1.3.0" description = "Utilities for Google Media Downloads and Resumable Uploads" category = "main" optional = false @@ -777,7 +738,7 @@ test = ["flake8 (>=3.8.4,<3.9.0)", "pycodestyle (>=2.6.0,<2.7.0)"] [[package]] name = "importlib-metadata" -version = "4.0.1" +version = "4.2.0" description = "Read metadata from Python packages" category = "main" optional = false @@ -793,11 +754,11 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytes [[package]] name = "itsdangerous" -version = "2.0.0" -description = "Safely pass data to untrusted environments and back." +version = "1.1.0" +description = "Various helpers to pass data to untrusted environments and back." category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "jinja2" @@ -821,30 +782,6 @@ category = "main" optional = false python-versions = "*" -[[package]] -name = "jsondiff" -version = "1.1.1" -description = "Diff JSON and JSON-like structures in Python" -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "jsonpickle" -version = "2.0.0" -description = "Python library for serializing any arbitrary object graph into JSON" -category = "dev" -optional = false -python-versions = ">=2.7" - -[package.dependencies] -importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} - -[package.extras] -docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] -testing = ["coverage (<5)", "pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-black-multipy", "pytest-cov", "ecdsa", "feedparser", "numpy", "pandas", "pymongo", "sklearn", "sqlalchemy", "enum34", "jsonlib"] -"testing.libs" = ["demjson", "simplejson", "ujson", "yajl"] - [[package]] name = "markdown" version = "3.3.4" @@ -861,11 +798,11 @@ testing = ["coverage", "pyyaml"] [[package]] name = "markupsafe" -version = "2.0.0" +version = "1.1.1" description = "Safely add untrusted strings to HTML/XML markup." category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" [[package]] name = "mock" @@ -885,7 +822,7 @@ test = ["unittest2 (>=1.1.0)"] [[package]] name = "more-itertools" -version = "8.7.0" +version = "8.8.0" description = "More routines for operating on iterables, beyond itertools" category = "dev" optional = false @@ -893,34 +830,41 @@ python-versions = ">=3.5" [[package]] name = "moto" -version = "1.3.7" +version = "1.3.15" description = "A library that allows your python tests to easily mock out the boto library" category = "dev" optional = false python-versions = "*" [package.dependencies] -aws-xray-sdk = ">=0.93,<0.96" boto = ">=2.36.0" -boto3 = ">=1.6.16" -botocore = ">=1.12.13" -cryptography = ">=2.3.0" -docker = ">=2.5.1" -Jinja2 = ">=2.7.3" -jsondiff = "1.1.1" +boto3 = ">=1.9.201" +botocore = ">=1.12.201" +Jinja2 = ">=2.10.1" +MarkupSafe = "<2.0" mock = "*" -pyaml = "*" +more-itertools = "*" python-dateutil = ">=2.1,<3.0.0" -python-jose = "<3.0.0" pytz = "*" requests = ">=2.5" responses = ">=0.9.0" six = ">1.9" werkzeug = "*" xmltodict = "*" +zipp = "*" [package.extras] +acm = ["cryptography (>=2.3.0)"] +all = ["cryptography (>=2.3.0)", "PyYAML (>=5.1)", "python-jose[cryptography] (>=3.1.0,<4.0.0)", "ecdsa (<0.15)", "docker (>=2.5.1)", "jsondiff (>=1.1.2)", "aws-xray-sdk (>=0.93,<0.96 || >0.96)", "idna (>=2.5,<3)", "cfn-lint (>=0.4.0)", "sshpubkeys (>=3.1.0,<4.0)", "sshpubkeys (>=3.1.0)"] +awslambda = ["docker (>=2.5.1)"] +batch = ["docker (>=2.5.1)"] +cloudformation = ["PyYAML (>=5.1)", "cfn-lint (>=0.4.0)"] +cognitoidp = ["python-jose[cryptography] (>=3.1.0,<4.0.0)", "ecdsa (<0.15)"] +ec2 = ["cryptography (>=2.3.0)", "sshpubkeys (>=3.1.0,<4.0)", "sshpubkeys (>=3.1.0)"] +iam = ["cryptography (>=2.3.0)"] +iotdata = ["jsondiff (>=1.1.2)"] server = ["flask"] +xray = ["aws-xray-sdk (>=0.93,<0.96 || >0.96)"] [[package]] name = "oauth2client" @@ -1014,7 +958,7 @@ prometheus_client = "*" [[package]] name = "protobuf" -version = "3.16.0" +version = "3.17.1" description = "Protocol Buffers" category = "main" optional = false @@ -1039,17 +983,6 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -[[package]] -name = "pyaml" -version = "20.4.0" -description = "PyYAML-based module to produce pretty and readable YAML-serialized data" -category = "dev" -optional = false -python-versions = "*" - -[package.dependencies] -PyYAML = "*" - [[package]] name = "pyasn1" version = "0.4.8" @@ -1210,14 +1143,6 @@ category = "main" optional = false python-versions = "*" -[[package]] -name = "pywin32" -version = "227" -description = "Python for Window Extensions" -category = "dev" -optional = false -python-versions = "*" - [[package]] name = "pyyaml" version = "5.4.1" @@ -1412,17 +1337,6 @@ python-versions = "*" cdislogging = "*" sqlalchemy = ">=1.3.3,<1.4.0" -[[package]] -name = "websocket-client" -version = "0.59.0" -description = "WebSocket client for Python with low level API options" -category = "dev" -optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[package.dependencies] -six = "*" - [[package]] name = "werkzeug" version = "0.16.1" @@ -1436,14 +1350,6 @@ dev = ["pytest", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinx-i termcolor = ["termcolor"] watchdog = ["watchdog"] -[[package]] -name = "wrapt" -version = "1.12.1" -description = "Module for decorators, wrappers and monkey patching." -category = "dev" -optional = false -python-versions = "*" - [[package]] name = "xmltodict" version = "0.12.0" @@ -1467,7 +1373,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pyt [metadata] lock-version = "1.1" python-versions = "^3.6" -content-hash = "98d14d525da527c84f4c65c75dd40e42318bc99643cf147e417b11bb45be6829" +content-hash = "e43c1eed7e8a3b3a4e771bdd76315471030e23377ba5b0df910b9d1600effa83" [metadata.files] addict = [ @@ -1495,12 +1401,8 @@ authlib = [ {file = "Authlib-0.11.tar.gz", hash = "sha256:9741db6de2950a0a5cefbdb72ec7ab12f7e9fd530ff47219f1530e79183cbaaf"}, ] authutils = [ - {file = "authutils-5.0.5-py3-none-any.whl", hash = "sha256:91e81838b8ba419d5fd92550747f8f60a1f5e91fee4146109e35728bea140034"}, - {file = "authutils-5.0.5.tar.gz", hash = "sha256:ffe8f0425f065353106f5fbe8eb2d38284d34406724e175c3aa2f1806723f361"}, -] -aws-xray-sdk = [ - {file = "aws-xray-sdk-0.95.tar.gz", hash = "sha256:9e7ba8dd08fd2939376c21423376206bff01d0deaea7d7721c6b35921fed1943"}, - {file = "aws_xray_sdk-0.95-py2.py3-none-any.whl", hash = "sha256:72791618feb22eaff2e628462b0d58f398ce8c1bacfa989b7679817ab1fad60c"}, + {file = "authutils-6.0.1-py3-none-any.whl", hash = "sha256:53aedffabb3225c528e48e97767ec5d88b552aaf45bd036c525db8978f164c8e"}, + {file = "authutils-6.0.1.tar.gz", hash = "sha256:3466ef7f22538a8d4d1581174bf24492ccc814449d1ac6daa129a48381ae07dc"}, ] backoff = [ {file = "backoff-1.10.0-py2.py3-none-any.whl", hash = "sha256:5e73e2cbe780e1915a204799dba0a01896f45f4385e636bcca7a0614d879d0cd"}, @@ -1594,8 +1496,8 @@ chardet = [ {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, ] click = [ - {file = "click-8.0.0-py3-none-any.whl", hash = "sha256:e90e62ced43dc8105fb9a26d62f0d9340b5c8db053a814e25d95c19873ae87db"}, - {file = "click-8.0.0.tar.gz", hash = "sha256:7d8c289ee437bcb0316820ccee14aefcb056e58d31830ecab8e47eda6540e136"}, + {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, + {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, ] codacy-coverage = [ {file = "codacy-coverage-1.3.11.tar.gz", hash = "sha256:b94651934745c638a980ad8d67494077e60f71e19e29aad1c275b66e0a070cbc"}, @@ -1690,17 +1592,13 @@ cryptography = [ {file = "cryptography-2.8.tar.gz", hash = "sha256:3cda1f0ed8747339bbdf71b9f38ca74c7b592f24f65cdb3ab3765e4b02871651"}, ] decorator = [ - {file = "decorator-5.0.7-py3-none-any.whl", hash = "sha256:945d84890bb20cc4a2f4a31fc4311c0c473af65ea318617f13a7257c9a58bc98"}, - {file = "decorator-5.0.7.tar.gz", hash = "sha256:6f201a6c4dac3d187352661f508b9364ec8091217442c9478f1f83c003a0f060"}, + {file = "decorator-5.0.9-py3-none-any.whl", hash = "sha256:6e5c199c16f7a9f0e3a61a4a54b3d27e7dad0dbdde92b944426cb20914376323"}, + {file = "decorator-5.0.9.tar.gz", hash = "sha256:72ecfba4320a893c53f9706bebb2d55c270c1e51a28789361aa93e4a21319ed5"}, ] dnspython = [ {file = "dnspython-2.1.0-py3-none-any.whl", hash = "sha256:95d12f6ef0317118d2a1a6fc49aac65ffec7eb8087474158f42f26a639135216"}, {file = "dnspython-2.1.0.zip", hash = "sha256:e4a87f0b573201a0f3727fa18a516b055fd1107e0e5477cded4a2de497df1dd4"}, ] -docker = [ - {file = "docker-5.0.0-py2.py3-none-any.whl", hash = "sha256:fc961d622160e8021c10d1bcabc388c57d55fb1f917175afbe24af442e6879bd"}, - {file = "docker-5.0.0.tar.gz", hash = "sha256:3e8bc47534e0ca9331d72c32f2881bb13b93ded0bcdeab3c833fb7cf61c0a9a5"}, -] docopt = [ {file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"}, ] @@ -1718,16 +1616,16 @@ email-validator = [ {file = "email_validator-1.1.2-py2.py3-none-any.whl", hash = "sha256:094b1d1c60d790649989d38d34f69e1ef07792366277a2cf88684d03495d018f"}, ] flask = [ - {file = "Flask-1.1.2-py2.py3-none-any.whl", hash = "sha256:8a4fdd8936eba2512e9c85df320a37e694c93945b33ef33c89946a340a238557"}, - {file = "Flask-1.1.2.tar.gz", hash = "sha256:4efa1ae2d7c9865af48986de8aeb8504bf32c7f3d6fdc9353d34b21f4b127060"}, + {file = "Flask-1.1.4-py2.py3-none-any.whl", hash = "sha256:c34f04500f2cbbea882b1acb02002ad6fe6b7ffa64a6164577995657f50aed22"}, + {file = "Flask-1.1.4.tar.gz", hash = "sha256:0fbeb6180d383a9186d0d6ed954e0042ad9f18e0e8de088b2b419d526927d196"}, ] flask-cors = [ {file = "Flask-Cors-3.0.10.tar.gz", hash = "sha256:b60839393f3b84a0f3746f6cdca56c1ad7426aa738b70d6c61375857823181de"}, {file = "Flask_Cors-3.0.10-py2.py3-none-any.whl", hash = "sha256:74efc975af1194fc7891ff5cd85b0f7478be4f7f59fe158102e91abb72bb4438"}, ] flask-restful = [ - {file = "Flask-RESTful-0.3.8.tar.gz", hash = "sha256:5ea9a5991abf2cb69b4aac19793faac6c032300505b325687d7c305ffaa76915"}, - {file = "Flask_RESTful-0.3.8-py2.py3-none-any.whl", hash = "sha256:d891118b951921f1cec80cabb4db98ea6058a35e6404788f9e70d5b243813ec2"}, + {file = "Flask-RESTful-0.3.9.tar.gz", hash = "sha256:ccec650b835d48192138c85329ae03735e6ced58e9b2d9c2146d6c84c06fa53e"}, + {file = "Flask_RESTful-0.3.9-py2.py3-none-any.whl", hash = "sha256:4970c49b6488e46c520b325f54833374dc2b98e211f1b272bd4b0c516232afe2"}, ] flask-sqlalchemy-session = [ {file = "Flask-SQLAlchemy-Session-1.1.tar.gz", hash = "sha256:0fbc520d587c891c2a0d58aa7952bc37edd73660f10006ff4732a72aa16d8157"}, @@ -1743,22 +1641,22 @@ gen3cirrus = [ {file = "gen3cirrus-1.3.0.tar.gz", hash = "sha256:e73c9a77d2e98adee066e6547af6c2a7d49bf5d44d11eaca7fbb6549184e9635"}, ] gen3config = [ - {file = "gen3config-0.1.8.tar.gz", hash = "sha256:33d49b7f291146be12651aa4abe4612e436c9068548c4eb309b7837fa8e48d3b"}, + {file = "gen3config-0.1.9.tar.gz", hash = "sha256:2b2fa3e5f3d9a417d5173de26303d06e4b03f5f7d7005e44d4ba91f55e7b0fec"}, ] gen3users = [ {file = "gen3users-0.6.0.tar.gz", hash = "sha256:3b9b56798a7d8b34712389dbbab93c00b0f92524f890513f899c31630ea986da"}, ] google-api-core = [ - {file = "google-api-core-1.26.3.tar.gz", hash = "sha256:b914345c7ea23861162693a27703bab804a55504f7e6e9abcaff174d80df32ac"}, - {file = "google_api_core-1.26.3-py2.py3-none-any.whl", hash = "sha256:099762d4b4018cd536bcf85136bf337957da438807572db52f21dc61251be089"}, + {file = "google-api-core-1.28.0.tar.gz", hash = "sha256:02646803bd728e12dd1f45ee1dcc31c12614c9a1ac451b9a1ce26aa65df9b957"}, + {file = "google_api_core-1.28.0-py2.py3-none-any.whl", hash = "sha256:a658a4e367511a444c7daf2c58855ff6fd7f7d138c154e5b17186c1f8154c8cb"}, ] google-api-python-client = [ {file = "google-api-python-client-1.11.0.tar.gz", hash = "sha256:caf4015800ef1a18d06d117f47f0219c0c0641f21978f6b1bb5ede7912fab97b"}, {file = "google_api_python_client-1.11.0-py2.py3-none-any.whl", hash = "sha256:4f596894f702736da84cf89490a810b55ca02a81f0cddeacb3022e2900b11ec6"}, ] google-auth = [ - {file = "google-auth-1.30.0.tar.gz", hash = "sha256:9ad25fba07f46a628ad4d0ca09f38dcb262830df2ac95b217f9b0129c9e42206"}, - {file = "google_auth-1.30.0-py2.py3-none-any.whl", hash = "sha256:588bdb03a41ecb4978472b847881e5518b5d9ec6153d3d679aa127a55e13b39f"}, + {file = "google-auth-1.30.1.tar.gz", hash = "sha256:044d81b1e58012f8ebc71cc134e191c1fa312f543f1fbc99973afe28c25e3228"}, + {file = "google_auth-1.30.1-py2.py3-none-any.whl", hash = "sha256:b3ca7a8ff9ab3bdefee3ad5aefb11fc6485423767eee016f5942d8e606ca23fb"}, ] google-auth-httplib2 = [ {file = "google-auth-httplib2-0.1.0.tar.gz", hash = "sha256:a07c39fd632becacd3f07718dfd6021bf396978f03ad3ce4321d060015cc30ac"}, @@ -1804,8 +1702,8 @@ google-crc32c = [ {file = "google_crc32c-1.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:78cf5b1bd30f3a6033b41aa4ce8c796870bc4645a15d3ef47a4b05d31b0a6dc1"}, ] google-resumable-media = [ - {file = "google-resumable-media-1.2.0.tar.gz", hash = "sha256:ee98b1921e5bda94867a08c864e55b4763d63887664f49ee1c231988f56b9d43"}, - {file = "google_resumable_media-1.2.0-py2.py3-none-any.whl", hash = "sha256:dbe670cd7f02f3586705fd5a108c8ab8552fa36a1cad8afbc5a54e982cf34f0c"}, + {file = "google-resumable-media-1.3.0.tar.gz", hash = "sha256:030a650e6dd18faad1b86c8f64be8b6cd59a90dbc22937d25631576f0c23a305"}, + {file = "google_resumable_media-1.3.0-py2.py3-none-any.whl", hash = "sha256:e2075b40a645965e4312fe6dac20a274b6718801630017b7b75afe7f1081bd70"}, ] googleapis-common-protos = [ {file = "googleapis-common-protos-1.53.0.tar.gz", hash = "sha256:a88ee8903aa0a81f6c3cec2d5cf62d3c8aa67c06439b0496b49048fb1854ebf4"}, @@ -1849,12 +1747,12 @@ immutables = [ {file = "immutables-0.15.tar.gz", hash = "sha256:3713ab1ebbb6946b7ce1387bb9d1d7f5e09c45add58c2a2ee65f963c171e746b"}, ] importlib-metadata = [ - {file = "importlib_metadata-4.0.1-py3-none-any.whl", hash = "sha256:d7eb1dea6d6a6086f8be21784cc9e3bcfa55872b52309bc5fad53a8ea444465d"}, - {file = "importlib_metadata-4.0.1.tar.gz", hash = "sha256:8c501196e49fb9df5df43833bdb1e4328f64847763ec8a50703148b73784d581"}, + {file = "importlib_metadata-4.2.0-py3-none-any.whl", hash = "sha256:057e92c15bc8d9e8109738a48db0ccb31b4d9d5cfbee5a8670879a30be66304b"}, + {file = "importlib_metadata-4.2.0.tar.gz", hash = "sha256:b7e52a1f8dec14a75ea73e0891f3060099ca1d8e6a462a4dff11c3e119ea1b31"}, ] itsdangerous = [ - {file = "itsdangerous-2.0.0-py3-none-any.whl", hash = "sha256:e2cb4ae918f07ab2a2f9a91dec2695bd1f25a19d31861a70015ad537ccb5e807"}, - {file = "itsdangerous-2.0.0.tar.gz", hash = "sha256:99b1053ccce68066dfc0b4465ef8779027e6d577377c8270e21a3d6289cac111"}, + {file = "itsdangerous-1.1.0-py2.py3-none-any.whl", hash = "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749"}, + {file = "itsdangerous-1.1.0.tar.gz", hash = "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19"}, ] jinja2 = [ {file = "Jinja2-2.10.3-py2.py3-none-any.whl", hash = "sha256:74320bb91f31270f9551d46522e33af46a80c3d619f4a4bf42b3164d30b5911f"}, @@ -1864,64 +1762,56 @@ jmespath = [ {file = "jmespath-0.9.2-py2.py3-none-any.whl", hash = "sha256:3f03b90ac8e0f3ba472e8ebff083e460c89501d8d41979771535efe9a343177e"}, {file = "jmespath-0.9.2.tar.gz", hash = "sha256:54c441e2e08b23f12d7fa7d8e6761768c47c969e6aed10eead57505ba760aee9"}, ] -jsondiff = [ - {file = "jsondiff-1.1.1.tar.gz", hash = "sha256:2d0437782de9418efa34e694aa59f43d7adb1899bd9a793f063867ddba8f7893"}, -] -jsonpickle = [ - {file = "jsonpickle-2.0.0-py2.py3-none-any.whl", hash = "sha256:c1010994c1fbda87a48f8a56698605b598cb0fc6bb7e7927559fc1100e69aeac"}, - {file = "jsonpickle-2.0.0.tar.gz", hash = "sha256:0be49cba80ea6f87a168aa8168d717d00c6ca07ba83df3cec32d3b30bfe6fb9a"}, -] markdown = [ {file = "Markdown-3.3.4-py3-none-any.whl", hash = "sha256:96c3ba1261de2f7547b46a00ea8463832c921d3f9d6aba3f255a6f71386db20c"}, {file = "Markdown-3.3.4.tar.gz", hash = "sha256:31b5b491868dcc87d6c24b7e3d19a0d730d59d3e46f4eea6430a321bed387a49"}, ] markupsafe = [ - {file = "MarkupSafe-2.0.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:2efaeb1baff547063bad2b2893a8f5e9c459c4624e1a96644bbba08910ae34e0"}, - {file = "MarkupSafe-2.0.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:441ce2a8c17683d97e06447fcbccbdb057cbf587c78eb75ae43ea7858042fe2c"}, - {file = "MarkupSafe-2.0.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:45535241baa0fc0ba2a43961a1ac7562ca3257f46c4c3e9c0de38b722be41bd1"}, - {file = "MarkupSafe-2.0.0-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:90053234a6479738fd40d155268af631c7fca33365f964f2208867da1349294b"}, - {file = "MarkupSafe-2.0.0-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:3b54a9c68995ef4164567e2cd1a5e16db5dac30b2a50c39c82db8d4afaf14f63"}, - {file = "MarkupSafe-2.0.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:f58b5ba13a5689ca8317b98439fccfbcc673acaaf8241c1869ceea40f5d585bf"}, - {file = "MarkupSafe-2.0.0-cp36-cp36m-win32.whl", hash = "sha256:a00dce2d96587651ef4fa192c17e039e8cfab63087c67e7d263a5533c7dad715"}, - {file = "MarkupSafe-2.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:007dc055dbce5b1104876acee177dbfd18757e19d562cd440182e1f492e96b95"}, - {file = "MarkupSafe-2.0.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a08cd07d3c3c17cd33d9e66ea9dee8f8fc1c48e2d11bd88fd2dc515a602c709b"}, - {file = "MarkupSafe-2.0.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:3c352ff634e289061711608f5e474ec38dbaa21e3e168820d53d5f4015e5b91b"}, - {file = "MarkupSafe-2.0.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:32200f562daaab472921a11cbb63780f1654552ae49518196fc361ed8e12e901"}, - {file = "MarkupSafe-2.0.0-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:fef86115fdad7ae774720d7103aa776144cf9b66673b4afa9bcaa7af990ed07b"}, - {file = "MarkupSafe-2.0.0-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:e79212d09fc0e224d20b43ad44bb0a0a3416d1e04cf6b45fed265114a5d43d20"}, - {file = "MarkupSafe-2.0.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:79b2ae94fa991be023832e6bcc00f41dbc8e5fe9d997a02db965831402551730"}, - {file = "MarkupSafe-2.0.0-cp37-cp37m-win32.whl", hash = "sha256:3261fae28155e5c8634dd7710635fe540a05b58f160cef7713c7700cb9980e66"}, - {file = "MarkupSafe-2.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:e4570d16f88c7f3032ed909dc9e905a17da14a1c4cfd92608e3fda4cb1208bbd"}, - {file = "MarkupSafe-2.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8f806bfd0f218477d7c46a11d3e52dc7f5fdfaa981b18202b7dc84bbc287463b"}, - {file = "MarkupSafe-2.0.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e77e4b983e2441aff0c0d07ee711110c106b625f440292dfe02a2f60c8218bd6"}, - {file = "MarkupSafe-2.0.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:031bf79a27d1c42f69c276d6221172417b47cb4b31cdc73d362a9bf5a1889b9f"}, - {file = "MarkupSafe-2.0.0-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:83cf0228b2f694dcdba1374d5312f2277269d798e65f40344964f642935feac1"}, - {file = "MarkupSafe-2.0.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:4cc563836f13c57f1473bc02d1e01fc37bab70ad4ee6be297d58c1d66bc819bf"}, - {file = "MarkupSafe-2.0.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:d00a669e4a5bec3ee6dbeeeedd82a405ced19f8aeefb109a012ea88a45afff96"}, - {file = "MarkupSafe-2.0.0-cp38-cp38-win32.whl", hash = "sha256:161d575fa49395860b75da5135162481768b11208490d5a2143ae6785123e77d"}, - {file = "MarkupSafe-2.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:58bc9fce3e1557d463ef5cee05391a05745fd95ed660f23c1742c711712c0abb"}, - {file = "MarkupSafe-2.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3fb47f97f1d338b943126e90b79cad50d4fcfa0b80637b5a9f468941dbbd9ce5"}, - {file = "MarkupSafe-2.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dab0c685f21f4a6c95bfc2afd1e7eae0033b403dd3d8c1b6d13a652ada75b348"}, - {file = "MarkupSafe-2.0.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:664832fb88b8162268928df233f4b12a144a0c78b01d38b81bdcf0fc96668ecb"}, - {file = "MarkupSafe-2.0.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:df561f65049ed3556e5b52541669310e88713fdae2934845ec3606f283337958"}, - {file = "MarkupSafe-2.0.0-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:24bbc3507fb6dfff663af7900a631f2aca90d5a445f272db5fc84999fa5718bc"}, - {file = "MarkupSafe-2.0.0-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:87de598edfa2230ff274c4de7fcf24c73ffd96208c8e1912d5d0fee459767d75"}, - {file = "MarkupSafe-2.0.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:a19d39b02a24d3082856a5b06490b714a9d4179321225bbf22809ff1e1887cc8"}, - {file = "MarkupSafe-2.0.0-cp39-cp39-win32.whl", hash = "sha256:4aca81a687975b35e3e80bcf9aa93fe10cd57fac37bf18b2314c186095f57e05"}, - {file = "MarkupSafe-2.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:70820a1c96311e02449591cbdf5cd1c6a34d5194d5b55094ab725364375c9eb2"}, - {file = "MarkupSafe-2.0.0.tar.gz", hash = "sha256:4fae0677f712ee090721d8b17f412f1cbceefbf0dc180fe91bab3232f38b4527"}, + {file = "MarkupSafe-1.1.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161"}, + {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"}, + {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183"}, + {file = "MarkupSafe-1.1.1-cp27-cp27m-win32.whl", hash = "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b"}, + {file = "MarkupSafe-1.1.1-cp27-cp27m-win_amd64.whl", hash = "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e"}, + {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f"}, + {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1"}, + {file = "MarkupSafe-1.1.1-cp34-cp34m-macosx_10_6_intel.whl", hash = "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5"}, + {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1"}, + {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735"}, + {file = "MarkupSafe-1.1.1-cp34-cp34m-win32.whl", hash = "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21"}, + {file = "MarkupSafe-1.1.1-cp34-cp34m-win_amd64.whl", hash = "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235"}, + {file = "MarkupSafe-1.1.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b"}, + {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f"}, + {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905"}, + {file = "MarkupSafe-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1"}, + {file = "MarkupSafe-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"}, + {file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"}, + {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, ] mock = [ {file = "mock-2.0.0-py2.py3-none-any.whl", hash = "sha256:5ce3c71c5545b472da17b72268978914d0252980348636840bd34a00b5cc96c1"}, {file = "mock-2.0.0.tar.gz", hash = "sha256:b158b6df76edd239b8208d481dc46b6afd45a846b7812ff0ce58971cf5bc8bba"}, ] more-itertools = [ - {file = "more-itertools-8.7.0.tar.gz", hash = "sha256:c5d6da9ca3ff65220c3bfd2a8db06d698f05d4d2b9be57e1deb2be5a45019713"}, - {file = "more_itertools-8.7.0-py3-none-any.whl", hash = "sha256:5652a9ac72209ed7df8d9c15daf4e1aa0e3d2ccd3c87f8265a0673cd9cbc9ced"}, + {file = "more-itertools-8.8.0.tar.gz", hash = "sha256:83f0308e05477c68f56ea3a888172c78ed5d5b3c282addb67508e7ba6c8f813a"}, + {file = "more_itertools-8.8.0-py3-none-any.whl", hash = "sha256:2cf89ec599962f2ddc4d568a05defc40e0a587fbc10d5989713638864c36be4d"}, ] moto = [ - {file = "moto-1.3.7-py2.py3-none-any.whl", hash = "sha256:4df37936ff8d6a4b8229aab347a7b412cd2ca4823ff47bd1362ddfbc6c5e4ecf"}, - {file = "moto-1.3.7.tar.gz", hash = "sha256:129de2e04cb250d9f8b2c722ec152ed1b5426ef179b4ebb03e9ec36e6eb3fcc5"}, + {file = "moto-1.3.15-py2.py3-none-any.whl", hash = "sha256:3be7e1f406ef7e9c222dbcbfd8cefa2cb1062200e26deae49b5df446e17be3df"}, + {file = "moto-1.3.15.tar.gz", hash = "sha256:fd98f7b219084ba8aadad263849c4dbe8be73979e035d8dc5c86e11a86f11b7f"}, ] oauth2client = [ {file = "oauth2client-3.0.0.tar.gz", hash = "sha256:5b5b056ec6f2304e7920b632885bd157fa71d1a7f3ddd00a43b1541a8d1a2460"}, @@ -1950,26 +1840,29 @@ prometheus-flask-exporter = [ {file = "prometheus_flask_exporter-0.18.2.tar.gz", hash = "sha256:fc487e385d95cb5efd045d6a315c4ecf68c42661e7bfde0526af75ed3c4f8c1b"}, ] protobuf = [ - {file = "protobuf-3.16.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:d3b9988f1a3591d900a090887ae4c41592e252bef5b249ad7e1fc46c21617534"}, - {file = "protobuf-3.16.0-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:8ec649186f5443cab12692438190988bb9058dbfa5851d10d59a1c7214159a5f"}, - {file = "protobuf-3.16.0-cp35-cp35m-macosx_10_9_intel.whl", hash = "sha256:9191e97d92b62424423ce5a5604047fd76c80a4f463fbd10c9d8b82928f152cf"}, - {file = "protobuf-3.16.0-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:49dd3550bb42050d1676378c3fef91ff058d7023b77ac6f3179eb2a1c6c562d7"}, - {file = "protobuf-3.16.0-cp35-cp35m-win32.whl", hash = "sha256:675d8e7463e03cf8343792935d62b80d90839d56a228c941dfdddda946eea066"}, - {file = "protobuf-3.16.0-cp35-cp35m-win_amd64.whl", hash = "sha256:b85ad5fe54163350067a53c0c170211c9f752dd4b4d8f339eb5aea8e987614a9"}, - {file = "protobuf-3.16.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:bc1ba824ff750c2ead1e7b8dd049bb5ddf8658d056cf4b989f04c68b049a47a7"}, - {file = "protobuf-3.16.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:80b233553ff500378becc372721541902c567e51b2654b68513d7b89c43dbc4b"}, - {file = "protobuf-3.16.0-cp36-cp36m-win32.whl", hash = "sha256:2cec059f4821c8f58890920a5c8a828ea027d46d5b18cb5e9dd4c727c65a2aa0"}, - {file = "protobuf-3.16.0-cp36-cp36m-win_amd64.whl", hash = "sha256:ef9c2d0b3c0935725b547175745ceacb86f4d410b1e984d47e320c9efb1936c5"}, - {file = "protobuf-3.16.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c0f760adc1dd3dfe6d13af529b2ab42bb3fff1a2a00a6873b583b4ce0048ddff"}, - {file = "protobuf-3.16.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:c94ee9832fded92a11920b10d75c5aa7f4ef910b0bd463c039e102c8d7eb2c46"}, - {file = "protobuf-3.16.0-cp37-cp37m-win32.whl", hash = "sha256:8c0d3a5aa3412a440a9384349f6095991e8a5012e619cf5f57f042829f65cdb3"}, - {file = "protobuf-3.16.0-cp37-cp37m-win_amd64.whl", hash = "sha256:11301f1993f67dc81fc5c4756623652c899f7b5574b1e095d63bfc78347b11f3"}, - {file = "protobuf-3.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4bb7064727953d9187f6806230968b98e1ce4ec03bd737600e8380a9e5a6ac15"}, - {file = "protobuf-3.16.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:bf9a5caa0e0093552c2cc6051facef15b9c9ad4b1bde70430964edf99eaf2dad"}, - {file = "protobuf-3.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c356d038a4e4cf52ccd7aff8022fc6a76daa0be5ecf2517ed75b87d32be0405d"}, - {file = "protobuf-3.16.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:648178381d9dbbc736849443151533ab958e7b8bcbd658a62ad10906552fddcc"}, - {file = "protobuf-3.16.0-py2.py3-none-any.whl", hash = "sha256:be831508b9207156309a88b9d115dbae0caa4a987b05f1aa4fed4c5ac53ec51b"}, - {file = "protobuf-3.16.0.tar.gz", hash = "sha256:228eecbedd46d75010f1e0f8ce34dbcd11ae5a40c165a9fc9d330a58aa302818"}, + {file = "protobuf-3.17.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:6e48c9b26d8e262331c79b208c4bf6b499a71912b1213d77b63c5ca248fc2a4e"}, + {file = "protobuf-3.17.1-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d06ef0f7873e04e2d1a2bfa0701d063e4dde5242b2946c6f071a442e4cb3be0b"}, + {file = "protobuf-3.17.1-cp35-cp35m-macosx_10_9_intel.whl", hash = "sha256:437d4681160ac5310457c4dc8ab973ec56769a236c479393c53fa71a26dfb38f"}, + {file = "protobuf-3.17.1-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:4ef4865ee740d5b45adfc96481adf1fcbfbb454bd51b86f4c4a5065262d0b08b"}, + {file = "protobuf-3.17.1-cp35-cp35m-win32.whl", hash = "sha256:5057744a363d65d2780dc01fa751bb14e860c507b2598b22af241aabb28a0ae9"}, + {file = "protobuf-3.17.1-cp35-cp35m-win_amd64.whl", hash = "sha256:94be4d5c2ba372ff159b2cc804d274acbaa7ebf361966f5619f99880c7c09a3c"}, + {file = "protobuf-3.17.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:fea786428b34385b073ef619d896282889b432b87dc043c0b815c72d35d64025"}, + {file = "protobuf-3.17.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:2c6ad26f079301bcf9f1b5d5b4b2dbac0e2e7c0111c6fbecf94f558952c78ee0"}, + {file = "protobuf-3.17.1-cp36-cp36m-win32.whl", hash = "sha256:ad17d3dd80f3377fc70a9dd677ec51231a892f9f65783cf7d2d24ee381f0feca"}, + {file = "protobuf-3.17.1-cp36-cp36m-win_amd64.whl", hash = "sha256:0f237c1e84e46747397a3e8173edb3116e81163b2878fc944a5193b05876eaee"}, + {file = "protobuf-3.17.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c1631570af7aae611f1cd7c6918fe4a7154507169ef30e8c5f380eba36ee2659"}, + {file = "protobuf-3.17.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:8a509097ba25452c9b44198843b0df67f921bd36f37adcbcfaaf6ee5cbe09132"}, + {file = "protobuf-3.17.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:cbd132f0aa7180f099d3c47fecfbcdca1590ef3b7da8346146192ba580c9b24e"}, + {file = "protobuf-3.17.1-cp37-cp37m-win32.whl", hash = "sha256:762faa8873d0569466cf66128bdbadd5e500600bdf2f87cf039493ed9d2725b6"}, + {file = "protobuf-3.17.1-cp37-cp37m-win_amd64.whl", hash = "sha256:27c7c933b838a0837eb1f429e7994310ee8577a7b69abc38e84d5db97a2650a2"}, + {file = "protobuf-3.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1fcace96670b8512e805d6e275bd59b860967a7648e05cfad35ec1e7c03d7262"}, + {file = "protobuf-3.17.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:2a88be54fce118d69f083b847554afd1852053c0869bdac396678ec9ec6a94d5"}, + {file = "protobuf-3.17.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:ad8cdbc594c886483970ac274b5af98483b272e873b7c5dac60aa3a7222301b3"}, + {file = "protobuf-3.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b92f95994ff27a6992ea2cf3f369476ad0065986eb00252b760d0bc55c5454a8"}, + {file = "protobuf-3.17.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:40bf764b63f06a816b1c079f7718184fdd8dbfd9dad3cd1a446382492ca00b3e"}, + {file = "protobuf-3.17.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a15898d88307dba0363767b30960396a2dec74b8ffe7bcb4e885312a569eda3f"}, + {file = "protobuf-3.17.1-py2.py3-none-any.whl", hash = "sha256:36d306dda3c159a5fe0025ba0e9728aac00dd69523dd12ee3fb7b1717cd80e7d"}, + {file = "protobuf-3.17.1.tar.gz", hash = "sha256:25bc4f1c23aced9b3a9e70eef7f03e63bcbd6cfbd881a91b5688412dce8992e1"}, ] psycopg2 = [ {file = "psycopg2-2.8.6-cp27-cp27m-win32.whl", hash = "sha256:068115e13c70dc5982dfc00c5d70437fe37c014c808acce119b5448361c03725"}, @@ -1992,10 +1885,6 @@ py = [ {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, ] -pyaml = [ - {file = "pyaml-20.4.0-py2.py3-none-any.whl", hash = "sha256:67081749a82b72c45e5f7f812ee3a14a03b3f5c25ff36ec3b290514f8c4c4b99"}, - {file = "pyaml-20.4.0.tar.gz", hash = "sha256:29a5c2a68660a799103d6949167bd6c7953d031449d08802386372de1db6ad71"}, -] pyasn1 = [ {file = "pyasn1-0.4.8-py2.4.egg", hash = "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"}, {file = "pyasn1-0.4.8-py2.5.egg", hash = "sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf"}, @@ -2114,20 +2003,6 @@ pytz = [ {file = "pytz-2021.1-py2.py3-none-any.whl", hash = "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"}, {file = "pytz-2021.1.tar.gz", hash = "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da"}, ] -pywin32 = [ - {file = "pywin32-227-cp27-cp27m-win32.whl", hash = "sha256:371fcc39416d736401f0274dd64c2302728c9e034808e37381b5e1b22be4a6b0"}, - {file = "pywin32-227-cp27-cp27m-win_amd64.whl", hash = "sha256:4cdad3e84191194ea6d0dd1b1b9bdda574ff563177d2adf2b4efec2a244fa116"}, - {file = "pywin32-227-cp35-cp35m-win32.whl", hash = "sha256:f4c5be1a293bae0076d93c88f37ee8da68136744588bc5e2be2f299a34ceb7aa"}, - {file = "pywin32-227-cp35-cp35m-win_amd64.whl", hash = "sha256:a929a4af626e530383a579431b70e512e736e9588106715215bf685a3ea508d4"}, - {file = "pywin32-227-cp36-cp36m-win32.whl", hash = "sha256:300a2db938e98c3e7e2093e4491439e62287d0d493fe07cce110db070b54c0be"}, - {file = "pywin32-227-cp36-cp36m-win_amd64.whl", hash = "sha256:9b31e009564fb95db160f154e2aa195ed66bcc4c058ed72850d047141b36f3a2"}, - {file = "pywin32-227-cp37-cp37m-win32.whl", hash = "sha256:47a3c7551376a865dd8d095a98deba954a98f326c6fe3c72d8726ca6e6b15507"}, - {file = "pywin32-227-cp37-cp37m-win_amd64.whl", hash = "sha256:31f88a89139cb2adc40f8f0e65ee56a8c585f629974f9e07622ba80199057511"}, - {file = "pywin32-227-cp38-cp38-win32.whl", hash = "sha256:7f18199fbf29ca99dff10e1f09451582ae9e372a892ff03a28528a24d55875bc"}, - {file = "pywin32-227-cp38-cp38-win_amd64.whl", hash = "sha256:7c1ae32c489dc012930787f06244426f8356e129184a02c25aef163917ce158e"}, - {file = "pywin32-227-cp39-cp39-win32.whl", hash = "sha256:c054c52ba46e7eb6b7d7dfae4dbd987a1bb48ee86debe3f245a2884ece46e295"}, - {file = "pywin32-227-cp39-cp39-win_amd64.whl", hash = "sha256:f27cec5e7f588c3d1051651830ecc00294f90728d19c3bf6916e6dba93ea357c"}, -] pyyaml = [ {file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"}, {file = "PyYAML-5.4.1-cp27-cp27m-win32.whl", hash = "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393"}, @@ -2236,17 +2111,10 @@ urllib3 = [ userdatamodel = [ {file = "userdatamodel-2.3.3.tar.gz", hash = "sha256:b846b7efd2d002a653474fa3bd7bf2a2c964277ff5f8d9bde8e9d975aca8d130"}, ] -websocket-client = [ - {file = "websocket-client-0.59.0.tar.gz", hash = "sha256:d376bd60eace9d437ab6d7ee16f4ab4e821c9dae591e1b783c58ebd8aaf80c5c"}, - {file = "websocket_client-0.59.0-py2.py3-none-any.whl", hash = "sha256:2e50d26ca593f70aba7b13a489435ef88b8fc3b5c5643c1ce8808ff9b40f0b32"}, -] werkzeug = [ {file = "Werkzeug-0.16.1-py2.py3-none-any.whl", hash = "sha256:1e0dedc2acb1f46827daa2e399c1485c8fa17c0d8e70b6b875b4e7f54bf408d2"}, {file = "Werkzeug-0.16.1.tar.gz", hash = "sha256:b353856d37dec59d6511359f97f6a4b2468442e454bd1c98298ddce53cac1f04"}, ] -wrapt = [ - {file = "wrapt-1.12.1.tar.gz", hash = "sha256:b62ffa81fb85f4332a4f609cab4ac40709470da05643a082ec1eb88e6d9b97d7"}, -] xmltodict = [ {file = "xmltodict-0.12.0-py2.py3-none-any.whl", hash = "sha256:8bbcb45cc982f48b2ca8fe7e7827c5d792f217ecf1792626f808bf41c3b86051"}, {file = "xmltodict-0.12.0.tar.gz", hash = "sha256:50d8c638ed7ecb88d90561beedbf720c9b4e851a9fa6c47ebd64e99d166d8a21"}, diff --git a/pyproject.toml b/pyproject.toml index 513c89025..1763d2c5e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,8 +13,7 @@ include = [ [tool.poetry.dependencies] python = "^3.6" authlib = "^0.11" -#authutils = "^5.0.5" -authutils = {git = "https://github.com/uc-cdis/authutils.git", branch = "fix/aud"} +authutils = "^6.0.0" bcrypt = "^3.1.4" boto3 = "~1.9.91" botocore = "^1.12.253" From 171dc2060e254d60436aa3b3ca468a9c3e34f716 Mon Sep 17 00:00:00 2001 From: vpsx <19900057+vpsx@users.noreply.github.com> Date: Thu, 29 Apr 2021 17:00:13 -0500 Subject: [PATCH 31/34] chore(black): pre-commit autoupdate and fix black --- .pre-commit-config.yaml | 6 +- .secrets.baseline | 131 ++++++++++++++-------------------------- 2 files changed, 48 insertions(+), 89 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 75a6a6198..a29604a6e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,18 +1,18 @@ repos: - repo: git@github.com:Yelp/detect-secrets - rev: v0.13.1 + rev: v1.1.0 hooks: - id: detect-secrets args: ['--baseline', '.secrets.baseline'] exclude: poetry.lock - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v2.5.0 + rev: v4.0.1 hooks: - id: trailing-whitespace - id: end-of-file-fixer - id: no-commit-to-branch args: [--branch, develop, --branch, master, --pattern, release/.*] - repo: https://github.com/psf/black - rev: stable + rev: 21.5b1 hooks: - id: black diff --git a/.secrets.baseline b/.secrets.baseline index 696fada09..5139a8121 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -1,9 +1,5 @@ { - "exclude": { - "files": "poetry.lock", - "lines": null - }, - "generated_at": "2021-05-25T21:22:19Z", + "version": "1.1.0", "plugins_used": [ { "name": "ArtifactoryDetector" @@ -80,6 +76,12 @@ { "path": "detect_secrets.filters.heuristic.is_likely_id_string" }, + { + "path": "detect_secrets.filters.heuristic.is_lock_file" + }, + { + "path": "detect_secrets.filters.heuristic.is_not_alphanumeric_string" + }, { "path": "detect_secrets.filters.heuristic.is_potential_uuid" }, @@ -89,6 +91,9 @@ { "path": "detect_secrets.filters.heuristic.is_sequential_string" }, + { + "path": "detect_secrets.filters.heuristic.is_swagger_file" + }, { "path": "detect_secrets.filters.heuristic.is_templated_secret" } @@ -110,6 +115,13 @@ "hashed_secret": "98c144f5ecbb4dbe575147a39698b6be1a5649dd", "is_verified": false, "line_number": 66 + }, + { + "type": "Secret Keyword", + "filename": "fence/blueprints/storage_creds/other.py", + "hashed_secret": "98c144f5ecbb4dbe575147a39698b6be1a5649dd", + "is_verified": false, + "line_number": 66 } ], "fence/config-default.yaml": [ @@ -119,13 +131,6 @@ "hashed_secret": "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3", "is_verified": false, "line_number": 31 - }, - { - "type": "Secret Keyword", - "filename": "fence/config-default.yaml", - "hashed_secret": "dd29ecf524b030a65261e3059c48ab9e1ecb2585", - "is_verified": false, - "line_number": 101 } ], "fence/local_settings.example.py": [ @@ -167,91 +172,52 @@ "hashed_secret": "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8", "is_verified": false, "line_number": 248 - } - ], - "openapis/swagger.yaml": [ - { - "type": "Private Key", - "filename": "openapis/swagger.yaml", - "hashed_secret": "1348b145fa1a555461c1b790a2f66614781091e9", - "is_verified": false, - "line_number": 1927 }, { "type": "Secret Keyword", - "filename": "openapis/swagger.yaml", - "hashed_secret": "8cb81a55dff48721f04fe341f33ee5b623dd9144", - "is_verified": false, - "line_number": 1927 - }, - { - "type": "Secret Keyword", - "filename": "openapis/swagger.yaml", - "hashed_secret": "41c39979fd01095376b3e9456f1058c33483dbbe", - "is_verified": false, - "line_number": 1994 - }, - { - "type": "JSON Web Token", - "filename": "openapis/swagger.yaml", - "hashed_secret": "d6b66ddd9ea7dbe760114bfe9a97352a5e139134", + "filename": "fence/utils.py", + "hashed_secret": "8954f53c9dc3f57137230a016d65bfaee24f8bc5", "is_verified": false, - "line_number": 1994 - }, + "line_number": 249 + } + ], + "tests/conftest.py": [ { - "type": "Base64 High Entropy String", - "filename": "openapis/swagger.yaml", - "hashed_secret": "98c144f5ecbb4dbe575147a39698b6be1a5649dd", + "type": "Private Key", + "filename": "tests/conftest.py", + "hashed_secret": "1348b145fa1a555461c1b790a2f66614781091e9", "is_verified": false, - "line_number": 2041 + "line_number": 1176 }, { "type": "Base64 High Entropy String", - "filename": "openapis/swagger.yaml", - "hashed_secret": "2f58edc671a89190115ecebddf4c70bdd87e3267", + "filename": "tests/conftest.py", + "hashed_secret": "227dea087477346785aefd575f91dd13ab86c108", "is_verified": false, - "line_number": 2084 + "line_number": 1199 } ], - "poetry.lock": [ + "tests/credentials/google/test_credentials.py": [ { - "type": "Hex High Entropy String", - "filename": "poetry.lock", - "hashed_secret": "640e60795f08744221f6816fe9dc949c58465256", - "is_verified": false, - "line_number": 1177, - "type": "Private Key" - }, - { - "type": "Hex High Entropy String", - "filename": "poetry.lock", - "hashed_secret": "6642e431aaa417100a91214385af6657acb3fab7", + "type": "Secret Keyword", + "filename": "tests/credentials/google/test_credentials.py", + "hashed_secret": "a06bdb09c0106ab559bd6acab2f1935e19f7e939", "is_verified": false, - "line_number": 1368 + "line_number": 381 }, { - "type": "Hex High Entropy String", - "filename": "poetry.lock", - "hashed_secret": "205b95ce89ff252c6045d78ca9d007e73b45dc00", - "is_verified": false, - "line_number": 1200, - "type": "Base64 High Entropy String" - } - ], - "tests/conftest.py": [ - { - "type": "Private Key", - "filename": "tests/conftest.py", - "hashed_secret": "1348b145fa1a555461c1b790a2f66614781091e9", + "type": "Secret Keyword", + "filename": "tests/credentials/google/test_credentials.py", + "hashed_secret": "93aa43c580f5347782e17fba5091f944767b15f0", "is_verified": false, - "line_number": 1151 + "line_number": 474 }, { - "type": "Base64 High Entropy String", - "filename": "tests/conftest.py", - "hashed_secret": "227dea087477346785aefd575f91dd13ab86c108", + "type": "Secret Keyword", + "filename": "tests/credentials/google/test_credentials.py", + "hashed_secret": "768b7fe00de4fd233c0c72375d12f87ce9670144", "is_verified": false, - "line_number": 1174 + "line_number": 476 } ], "tests/keys/2018-05-01T21:29:02Z/jwt_private_key.pem": [ @@ -287,7 +253,7 @@ "filename": "tests/scripting/test_fence-create.py", "hashed_secret": "e5e9fa1ba31ecd1ae84f75caaa474f3a663f05f4", "is_verified": false, - "line_number": 1117 + "line_number": 1120 } ], "tests/test-fence-config.yaml": [ @@ -298,13 +264,6 @@ "is_verified": false, "line_number": 31 }, - { - "type": "Secret Keyword", - "filename": "tests/test-fence-config.yaml", - "hashed_secret": "dd29ecf524b030a65261e3059c48ab9e1ecb2585", - "is_verified": false, - "line_number": 85 - }, { "type": "Secret Keyword", "filename": "tests/test-fence-config.yaml", @@ -314,5 +273,5 @@ } ] }, - "generated_at": "2021-05-26T14:24:12Z" + "generated_at": "2021-05-27T15:18:42Z" } From 9bc37669e6ff6cc09852c0443447b6fac4664e19 Mon Sep 17 00:00:00 2001 From: vpsx <19900057+vpsx@users.noreply.github.com> Date: Wed, 26 May 2021 16:26:07 -0500 Subject: [PATCH 32/34] fix(aud-scope): Give up and keep scopes in aud claim in access tkns for bkwd cmp --- fence/jwt/token.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/fence/jwt/token.py b/fence/jwt/token.py index 706e639fb..747228e46 100644 --- a/fence/jwt/token.py +++ b/fence/jwt/token.py @@ -418,6 +418,10 @@ def generate_signed_access_token( if client_id: claims["aud"].append(client_id) + # Keep scopes in aud claim in access tokens for backwards comp.... + if scopes: + claims["aud"] += scopes + if include_project_access: # NOTE: "THIS IS A TERRIBLE STOP-GAP SOLUTION SO THAT USERS WITH # MINIMAL ACCESS CAN STILL USE LATEST VERSION OF FENCE From 383ef413a16b07b9eca36382ce05b688b5f7f98f Mon Sep 17 00:00:00 2001 From: vpsx <19900057+vpsx@users.noreply.github.com> Date: Wed, 2 Jun 2021 18:58:39 -0500 Subject: [PATCH 33/34] fix(aud-scope): Allow old style refresh tokens in this tag * To help transition --- fence/jwt/validate.py | 37 +++++++++++++++++++++--- fence/oidc/grants/refresh_token_grant.py | 10 +++++++ 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/fence/jwt/validate.py b/fence/jwt/validate.py index 81f33e8f8..e018ad869 100644 --- a/fence/jwt/validate.py +++ b/fence/jwt/validate.py @@ -120,11 +120,40 @@ def validate_jwt( **kwargs ) except authutils.errors.JWTError as e: - msg = "Invalid token : {}".format(str(e)) + + ##### begin refresh token patch block ##### + # TODO: In the next release, remove this if block and take the else block + # back out of the else. + # Old refresh tokens are not compatible with new validation, so to smooth + # the transition, allow old style refresh tokens with this patch; + # remove patch in next tag. Refresh tokens have default TTL of 30 days. + from authutils.errors import JWTAudienceError + unverified_claims = jwt.decode(encoded_token, verify=False) - if not unverified_claims.get("scope") or "" in unverified_claims["scope"]: - msg += "; was OIDC client configured with scopes?" - raise JWTError(msg) + if unverified_claims.get("pur") == "refresh" and isinstance( + e, JWTAudienceError + ): + # Check everything else is fine minus the audience + try: + claims = authutils.token.validate.validate_jwt( + encoded_token=encoded_token, + aud="openid", + scope=None, + purpose="refresh", + issuers=issuers, + public_key=public_key, + attempt_refresh=attempt_refresh, + **kwargs + ) + except Error as e: + raise JWTError("Invalid refresh token: {}".format(e)) + else: + ##### end refresh token patch block ##### + msg = "Invalid token : {}".format(str(e)) + unverified_claims = jwt.decode(encoded_token, verify=False) + if not unverified_claims.get("scope") or "" in unverified_claims["scope"]: + msg += "; was OIDC client configured with scopes?" + raise JWTError(msg) if purpose: validate_purpose(claims, purpose) if "pur" not in claims: diff --git a/fence/oidc/grants/refresh_token_grant.py b/fence/oidc/grants/refresh_token_grant.py index e5e16c4cc..7e3d18437 100644 --- a/fence/oidc/grants/refresh_token_grant.py +++ b/fence/oidc/grants/refresh_token_grant.py @@ -168,6 +168,16 @@ def _validate_token_scope(self, token): # token is dict so just get the scope, don't use get_scope() original_scope = token.get("scope") + + ##### begin refresh token patch block ##### + # TODO: In the next release, remove this if block + # Old refresh tokens are not compatible with new validation, so to smooth + # the transition, allow old style refresh tokens with this patch; + # remove patch in next tag. Refresh tokens have default TTL of 30 days. + if not original_scope: + original_scope = token.get("aud") + ##### end refresh token patch block ##### + if not original_scope: raise InvalidScopeError("No scope claim found in original refresh token.") From e6238eb706630ba4673b03f10a4c7e97c8090307 Mon Sep 17 00:00:00 2001 From: vpsx <19900057+vpsx@users.noreply.github.com> Date: Fri, 4 Jun 2021 11:33:05 -0500 Subject: [PATCH 34/34] fix(aud-scope): Allow old style API keys in this tag * To help transition --- fence/jwt/validate.py | 29 +++++++++++++++++++++++------ fence/resources/storage/cdis_jwt.py | 13 ++++++++++++- 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/fence/jwt/validate.py b/fence/jwt/validate.py index e018ad869..74dd4027c 100644 --- a/fence/jwt/validate.py +++ b/fence/jwt/validate.py @@ -121,12 +121,12 @@ def validate_jwt( ) except authutils.errors.JWTError as e: - ##### begin refresh token patch block ##### - # TODO: In the next release, remove this if block and take the else block + ##### begin refresh token and API key patch block ##### + # TODO: In the next release, remove this if/elif block and take the else block # back out of the else. - # Old refresh tokens are not compatible with new validation, so to smooth - # the transition, allow old style refresh tokens with this patch; - # remove patch in next tag. Refresh tokens have default TTL of 30 days. + # Old refresh tokens and API keys are not compatible with new validation, so to smooth + # the transition, allow old style refresh tokens/API keys with this patch; + # remove patch in next tag. Refresh tokens and API keys have default TTL of 30 days. from authutils.errors import JWTAudienceError unverified_claims = jwt.decode(encoded_token, verify=False) @@ -147,8 +147,25 @@ def validate_jwt( ) except Error as e: raise JWTError("Invalid refresh token: {}".format(e)) + elif unverified_claims.get("pur") == "api_key" and isinstance( + e, JWTAudienceError + ): + # Check everything else is fine minus the audience + try: + claims = authutils.token.validate.validate_jwt( + encoded_token=encoded_token, + aud="fence", + scope=None, + purpose="api_key", + issuers=issuers, + public_key=public_key, + attempt_refresh=attempt_refresh, + **kwargs + ) + except Error as e: + raise JWTError("Invalid API key: {}".format(e)) else: - ##### end refresh token patch block ##### + ##### end refresh token, API key patch block ##### msg = "Invalid token : {}".format(str(e)) unverified_claims = jwt.decode(encoded_token, verify=False) if not unverified_claims.get("scope") or "" in unverified_claims["scope"]: diff --git a/fence/resources/storage/cdis_jwt.py b/fence/resources/storage/cdis_jwt.py index f8a5120f0..45c89cb47 100644 --- a/fence/resources/storage/cdis_jwt.py +++ b/fence/resources/storage/cdis_jwt.py @@ -54,7 +54,18 @@ def create_user_access_token(keypair, api_key, expires_in): """ try: claims = validate_jwt(api_key, scope={"fence"}, purpose="api_key") - scopes = claims["scope"] + # scopes = claims["scope"] + + ##### begin api key patch block ##### + # TODO: In the next release, remove this block and uncomment line above. + # Old API keys are not compatible with new validation + # This is to help transition + try: + scopes = claims["scope"] + except KeyError as e: + scopes = claims["aud"] + ##### end api key patch block ##### + user = get_user_from_claims(claims) except Exception as e: raise Unauthorized(str(e))