Skip to content

Commit

Permalink
Merge branch 'master' into test/usersync
Browse files Browse the repository at this point in the history
  • Loading branch information
BinamB committed Jan 30, 2023
2 parents 13accc6 + fd45bf4 commit 464a526
Show file tree
Hide file tree
Showing 39 changed files with 1,148 additions and 604 deletions.
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ repos:
hooks:
- id: black
- repo: git@github.com:Yelp/detect-secrets
rev: v1.2.0
rev: v1.4.0
hooks:
- id: detect-secrets
args: ['--baseline', '.secrets.baseline']
Expand Down
48 changes: 41 additions & 7 deletions .secrets.baseline
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "1.2.0",
"version": "1.4.0",
"plugins_used": [
{
"name": "ArtifactoryDetector"
Expand Down Expand Up @@ -115,6 +115,15 @@
}
],
"results": {
".github/workflows/buildpipeline.yaml": [
{
"type": "Secret Keyword",
"filename": ".github/workflows/buildpipeline.yaml",
"hashed_secret": "3e26d6750975d678acb8fa35a0f69237881576b0",
"is_verified": false,
"line_number": 17
}
],
"deployment/scripts/postgresql/postgresql_init.sql": [
{
"type": "Secret Keyword",
Expand Down Expand Up @@ -189,7 +198,16 @@
"filename": "fence/utils.py",
"hashed_secret": "8318df9ecda039deac9868adf1944a29a95c7114",
"is_verified": false,
"line_number": 112
"line_number": 129
}
],
"migrations/versions/a04a70296688_non_unique_client_name.py": [
{
"type": "Hex High Entropy String",
"filename": "migrations/versions/a04a70296688_non_unique_client_name.py",
"hashed_secret": "bb2372fb50034d559d2920e7229fb5879cf1be72",
"is_verified": false,
"line_number": 13
}
],
"migrations/versions/e4c7b0ab68d3_create_tables.py": [
Expand Down Expand Up @@ -269,7 +287,14 @@
"filename": "tests/data/test_indexed_file.py",
"hashed_secret": "a62f2225bf70bfaccbc7f1ef2a397836717377de",
"is_verified": false,
"line_number": 410
"line_number": 411
},
{
"type": "Secret Keyword",
"filename": "tests/data/test_indexed_file.py",
"hashed_secret": "c258a8d1264cc59de81f8b1975ac06732b1cf182",
"is_verified": false,
"line_number": 432
}
],
"tests/keys/2018-05-01T21:29:02Z/jwt_private_key.pem": [
Expand All @@ -290,20 +315,29 @@
"line_number": 48
}
],
"tests/migrations/test_a04a70296688.py": [
{
"type": "Hex High Entropy String",
"filename": "tests/migrations/test_a04a70296688.py",
"hashed_secret": "bb2372fb50034d559d2920e7229fb5879cf1be72",
"is_verified": false,
"line_number": 27
}
],
"tests/migrations/test_ea7e1b843f82.py": [
{
"type": "Hex High Entropy String",
"filename": "tests/migrations/test_ea7e1b843f82.py",
"hashed_secret": "adb1fcd33b07abf9b6a064745759accea5cb341f",
"is_verified": false,
"line_number": 27
"line_number": 26
},
{
"type": "Hex High Entropy String",
"filename": "tests/migrations/test_ea7e1b843f82.py",
"hashed_secret": "bb2372fb50034d559d2920e7229fb5879cf1be72",
"is_verified": false,
"line_number": 44
"line_number": 43
}
],
"tests/ras/test_ras.py": [
Expand All @@ -321,7 +355,7 @@
"filename": "tests/scripting/test_fence-create.py",
"hashed_secret": "e5e9fa1ba31ecd1ae84f75caaa474f3a663f05f4",
"is_verified": false,
"line_number": 264
"line_number": 301
}
],
"tests/test-fence-config.yaml": [
Expand All @@ -334,5 +368,5 @@
}
]
},
"generated_at": "2022-11-10T22:37:01Z"
"generated_at": "2023-01-26T20:09:03Z"
}
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
language: python

python:
- "3.6"
- "3.9"

sudo: false

Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# To run: docker run --rm -d -v /path/to/fence-config.yaml:/var/www/fence/fence-config.yaml --name=fence -p 80:80 fence
# To check running container do: docker exec -it fence /bin/bash

FROM quay.io/cdis/python:python3.6-buster-pybase3-3.0.2
FROM quay.io/cdis/python:python3.9-buster-2.0.0

ENV appname=fence

Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,14 @@ Add `--append` argument to add new callback urls or allowed scopes to existing c
fence-create client-modify --client CLIENT_NAME --urls http://localhost/api/v0/new/oauth2/authorize --append (--expires-in 30)
```

#### Rotate client credentials

Use the `client-rotate` command to receive a new set of credentials for a client. The old credentials are NOT deactivated and must be deleted or expired separately. This allows for a rotation without downtime.

```bash
fence-create client-rotate --client CLIENT_NAME (--expires-in 30)
```

#### Delete OAuth Client

```bash
Expand Down
11 changes: 11 additions & 0 deletions bin/fence_create.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
create_sample_data,
delete_client_action,
delete_expired_clients_action,
rotate_client_action,
delete_users,
delete_expired_google_access,
cleanup_expired_ga4gh_information,
Expand Down Expand Up @@ -163,6 +164,14 @@ def parse_arguments():
default=7,
)

client_rotate = subparsers.add_parser("client-rotate")
client_rotate.add_argument("--client", required=True)
client_rotate.add_argument(
"--expires-in",
help="days until the new client credentials expire",
required=False,
)

user_delete = subparsers.add_parser("user-delete")
user_delete.add_argument("--users", required=True, nargs="+")

Expand Down Expand Up @@ -477,6 +486,8 @@ def main():
list_client_action(DB)
elif args.action == "client-delete-expired":
delete_expired_clients_action(DB, args.slack_webhook, args.warning_days)
elif args.action == "client-rotate":
rotate_client_action(DB, args.client, args.expires_in)
elif args.action == "user-delete":
delete_users(DB, args.users)
elif args.action == "expired-service-account-delete":
Expand Down
1 change: 0 additions & 1 deletion deployment/uwsgi/uwsgi.ini
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ plugins = python3
vacuum = true
pythonpath = /var/www/fence/
pythonpath = /fence/
pythonpath = /usr/local/lib/python3.6/site-packages/
# poetry installs git dependencies at /usr/local/src
pythonpath = /usr/local/src/*

Expand Down
2 changes: 1 addition & 1 deletion fence/blueprints/login/ras.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ def post_login(self, user=None, token_result=None, id_from_idp=None):
refresh_token = flask.g.tokens["refresh_token"]
assert "id_token" in flask.g.tokens, "No id_token in user tokens"
id_token = flask.g.tokens["id_token"]
decoded_id = jwt.decode(id_token, verify=False)
decoded_id = jwt.decode(id_token, algorithms=["RS256"], options={"verify_signature": False})

# Add 15 days to iat to calculate refresh token expiration time
# TODO do they really not provide exp?
Expand Down
4 changes: 2 additions & 2 deletions fence/jwt/blacklist.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ def blacklist_encoded_token(encoded_token, public_key=None):
claims = jwt.decode(
encoded_token,
public_key,
algorithm="RS256",
algorithms=["RS256"],
options={"verify_aud": False},
)
except jwt.InvalidTokenError as e:
Expand Down Expand Up @@ -143,7 +143,7 @@ def is_token_blacklisted(encoded_token, public_key=None):
token = jwt.decode(
encoded_token,
public_key,
algorithm="RS256",
algorithms=["RS256"],
options={"verify_aud": False},
)
except jwt.exceptions.InvalidTokenError as e:
Expand Down
2 changes: 1 addition & 1 deletion fence/jwt/token.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ def from_signed_and_encoded_token(
payload = jwt.decode(
encoded_token,
public_key,
algorithms="RS256",
algorithms=["RS256"],
verify=verify,
audience=client_id,
)
Expand Down
12 changes: 9 additions & 3 deletions fence/jwt/validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,9 @@ def validate_jwt(
if oidc_iss:
issuers.append(oidc_iss)
try:
token_iss = jwt.decode(encoded_token, verify=False).get("iss")
token_iss = jwt.decode(encoded_token, algorithms=["RS256"], options={"verify_signature": False}).get(
"iss"
)
except jwt.InvalidTokenError as e:
raise JWTError(e)
attempt_refresh = attempt_refresh and (token_iss != iss)
Expand Down Expand Up @@ -133,7 +135,9 @@ def validate_jwt(
# 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)
unverified_claims = jwt.decode(
encoded_token, algorithms=["RS256"], options={"verify_signature": False}
)
if unverified_claims.get("pur") == "refresh" and isinstance(
e, JWTAudienceError
):
Expand Down Expand Up @@ -171,7 +175,9 @@ def validate_jwt(
else:
##### end refresh token, API key patch block #####
msg = "Invalid token : {}".format(str(e))
unverified_claims = jwt.decode(encoded_token, verify=False)
unverified_claims = jwt.decode(
encoded_token, algorithms=["RS256"], options={"verify_signature": False}
)
if not unverified_claims.get("scope") or "" in unverified_claims["scope"]:
msg += "; was OIDC client configured with scopes?"
raise JWTError(msg)
Expand Down
2 changes: 1 addition & 1 deletion fence/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ class Client(Base, OAuth2ClientMixin):
client_secret = Column(String(60), unique=True, index=True, nullable=True)

# human readable name
name = Column(String(40), unique=True, nullable=False)
name = Column(String(40), nullable=False)

# human readable description, not required
description = Column(String(400))
Expand Down
1 change: 1 addition & 0 deletions fence/resources/openid/idp_oauth2.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ def get_jwt_claims_identity(self, token_endpoint, jwks_endpoint, code):
token["id_token"],
keys,
options={"verify_aud": False, "verify_at_hash": False},
algorithms=["RS256"],
)

def get_value_from_discovery_doc(self, key, default_value):
Expand Down
6 changes: 5 additions & 1 deletion fence/resources/openid/ras_oauth2.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,11 +133,15 @@ def get_user_id(self, code):
token["id_token"],
keys,
options={"verify_aud": False, "verify_at_hash": False},
algorithms=["RS256"],
)

# Log txn in access token for RAS ISA compliance
at_claims = jose_jwt.decode(
token["access_token"], keys, options={"verify_aud": False}
token["access_token"],
keys,
options={"verify_aud": False},
algorithms=["RS256"],
)
self.logger.info(
"Received RAS access token with txn: {}".format(at_claims.get("txn"))
Expand Down
6 changes: 5 additions & 1 deletion fence/resources/user/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,11 @@ def get_user_info(current_session, username):
encoded_access_token = None

if encoded_access_token:
at_scopes = jwt.decode(encoded_access_token, verify=False).get("scope", "")
at_scopes = jwt.decode(
encoded_access_token,
algorithms=["RS256"],
options={"verify_signature": False},
).get("scope", "")
if "ga4gh_passport_v1" in at_scopes:
info["ga4gh_passport_v1"] = []

Expand Down
1 change: 1 addition & 0 deletions fence/resources/user/user_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ def __init__(self, session_token):
scope=None,
purpose="session",
public_key=default_public_key(),
options={"verify_exp": False},
)
except JWTError:
# if session token is invalid, create a new
Expand Down
Loading

0 comments on commit 464a526

Please sign in to comment.