Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Chore/python3 #39

Merged
merged 14 commits into from
Jul 2, 2019
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/main.workflow
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
workflow "Run python formatter" {
on = "pull_request"
resolves = ["Run wool"]
}

action "Run wool" {
uses = "uc-cdis/wool@master"
secrets = ["GITHUB_TOKEN"]
}
6 changes: 5 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ language: python
dist: xenial
python:
- 2.7
- 3.6
matrix:
allow_failures:
- python: 2.7
install:
- pip install pipenv
- pipenv install --dev --deploy --python `which python`
Expand All @@ -25,7 +29,7 @@ deploy:
env:
global:
- PIPENV_IGNORE_VIRTUALENVS=1
- secure: ov66mTdCG+Gl+wLAjYdqR0HrKbgpQFSxSgCi0ihTmErzQl/vudZyPXdoSEQfFsWpKGI+UmhYklsvGySnYzyhTuUTrmovQ5C9tMMASWtJrwzAgun6PIli7ETjw5j273WTwIPy/HfrysqW6ab563VYbbuxn7ZANV86wN17TKp0qyaJPD4Kc6iFp8yKei7msXPy6Pc36AYz4vVjq1AZasFLIrRy5va4VNxtNUvK4peOdKcxDTmj4GMS8eF/Y55HuOzuH/B3hw0XqCgwk+u+4DCImpvQIhsfChgI71m70/scWwhiSaVY3WEmZBbJPgFLsoBm+div2+kG8zcNvOK2OEhQgvocxzgpTik5rvZX2SRCpuBxgbZEzUEbvNPArJVLwOBlbadIpuM8+sHTj/2A/4+AboVat10jjHrevTKlCo6CXe2QPXO474eZP0f5lOsGRQ9iSW52MRRCt8yOIALWwmn5DDrwt41ezn5+JDglxPxebqFLuvutG/7XMhhCuU91MYLxI/xhliRcYL9SiZXjnEz0p91YUNQQ2seJNvw/d65ypYdJDqS8kM8II7+lL/9l41hZ2En3LDSQ5diXPzEeNVp5cz/mn2rwlehTLmGbdb0Qy3WbD9eKoY1ADR+yDTE9l3t9UtRqkJ6Jgdig1YNkmdaFmrMfMHzTntY5He78b95zyk8=
- secure: qoMlJG2/e/ExVimSYtWbFxaePA7wSUgjsENaJPmAZKvisaSmbh3+BjlGc0128NViYGXMg2ljk2ofzST7/ktlJhnfewFrVHMctJsDppC0cmYXYQsXdGlFC59UDZXzp+jj4dRVSBiFUbIcznQDuGQ9z9rZs/dAHxPyVfgZoKbvuAiW+vNh+9veVvMfTFdSl6xNC4VBAk1BSWwRgYzYRyUFCXTmX+LbKN6SbyXpZXEwHoLCzKGePDK4SOHeFMSVhPryWtID1aCZYHHRm1ctFTjgvuAJ6s58Tlui5K9w4LFzlyhbWPE/T42cO1Azuqm+T/UZkD9k//rIj6yZb+JuxTNaIhosmB83MYzD/XhOEqCKoUjvYAHT1FwVpuml8/lm16EEloerag6yudAuVwerOQtPp3bcYANtKy78dLtVbu6AUDlpgjy3TELR60n02JDP2C5eu/3Bf2yLY3zOgshWluq6pTgX1PDaBgVL6PwDjnGtWI4Xpblt56rCYKvqHVqkQJzmTGP6epooiNEKaWy9tSo4aHRw82uzqOJY7Y3vr2eJ1vUtV5MBTSDnZyclRvEJA+qB6vHHSO7FRzTnzdvRmCO2VFOF+VfRGk8p331bBhotHIKxu5SwYxG97L8QuS4H9yni9zw039tCqluj2sEZ2xER/2AWCFtayty5ldz1SXIyY6U=
Copy link
Contributor

@fantix fantix Jul 1, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I cannot decrypt, but I think it is a change like this:

-GH_TOKEN=xxxx
+GITHUB_TOKEN=xxx

We actually need both GH_TOKEN - gen3git needs the first one (perhaps it is even better to make gen3git support both)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right 🤦‍♀

after_deploy:
- pipenv run pip install gen3git
- pipenv run gen3git release
2 changes: 1 addition & 1 deletion Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ mock = "*"
cdispyutils = {editable = true,extras = ["profiling"],path = "."}

[requires]
python_version = "2.7"
python_version = "3.6"
405 changes: 182 additions & 223 deletions Pipfile.lock

Large diffs are not rendered by default.

6 changes: 1 addition & 5 deletions cdispyutils/auth/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,2 @@
from .errors import JWTValidationError
from .jwt_validation import (
get_public_key_for_kid,
validate_jwt,
validate_request_jwt,
)
from .jwt_validation import get_public_key_for_kid, validate_jwt, validate_request_jwt
59 changes: 27 additions & 32 deletions cdispyutils/auth/jwt_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,7 @@
import jwt
import requests

from .errors import (
JWTValidationError,
JWTAudienceError,
)
from .errors import JWTValidationError, JWTAudienceError


def refresh_jwt_public_keys(user_api=None):
Expand Down Expand Up @@ -65,14 +62,13 @@ def refresh_jwt_public_keys(user_api=None):
Raises:
ValueError: if user_api is not provided or set in app config
"""
user_api = user_api or flask.current_app.config.get('USER_API')
user_api = user_api or flask.current_app.config.get("USER_API")
if not user_api:
raise ValueError('no URL provided for user API')
path = '/'.join(path.strip('/') for path in [user_api, 'jwt', 'keys'])
jwt_public_keys = requests.get(path).json()['keys']
raise ValueError("no URL provided for user API")
path = "/".join(path.strip("/") for path in [user_api, "jwt", "keys"])
jwt_public_keys = requests.get(path).json()["keys"]
flask.current_app.logger.info(
'refreshing public keys; updated to:\n'
+ json.dumps(jwt_public_keys, indent=4)
"refreshing public keys; updated to:\n" + json.dumps(jwt_public_keys, indent=4)
)
flask.current_app.jwt_public_keys = OrderedDict(jwt_public_keys)

Expand Down Expand Up @@ -112,24 +108,22 @@ def get_public_key_for_kid(kid, attempt_refresh=True):
JWTValidationError:
if the key id is provided and public key with that key id is found
"""
need_refresh = (
not hasattr(flask.current_app, 'jwt_public_keys')
or (kid and kid not in flask.current_app.jwt_public_keys)
need_refresh = not hasattr(flask.current_app, "jwt_public_keys") or (
kid and kid not in flask.current_app.jwt_public_keys
)
if need_refresh and attempt_refresh:
refresh_jwt_public_keys()
if kid:
try:
return flask.current_app.jwt_public_keys[kid]
except KeyError:
raise JWTValidationError('no key exists with this key id')
raise JWTValidationError("no key exists with this key id")
else:
# Grab the key from the first in the list of keys.
return flask.current_app.jwt_public_keys.items()[0][1]
return list(flask.current_app.jwt_public_keys.items())[0][1]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about:

next(iter(flask.current_app.jwt_public_keys.values()))



def validate_request_jwt(
aud, request=None, user_api=None, attempt_refresh=True):
def validate_request_jwt(aud, request=None, user_api=None, attempt_refresh=True):
"""
Verify the JWT authorization header from a Flask request.

Expand Down Expand Up @@ -167,32 +161,30 @@ def validate_request_jwt(
- from ``validate_jwt``, if any step of the validation fails
"""


aud = set(aud)
iss = user_api or flask.current_app.config['USER_API']
iss = user_api or flask.current_app.config["USER_API"]
if not aud:
raise JWTAudienceError('no audiences provided')
raise JWTAudienceError("no audiences provided")
request = request or flask.request
try:
encoded_token = request.headers['Authorization'].split(' ')[1]
encoded_token = request.headers["Authorization"].split(" ")[1]
except IndexError:
raise JWTValidationError('could not parse authorization header')
raise JWTValidationError("could not parse authorization header")
except KeyError:
raise JWTValidationError('no authorization token provided')
raise JWTValidationError("no authorization token provided")

try:
token_headers = jwt.get_unverified_header(encoded_token)
except jwt.DecodeError as e:
raise JWTValidationError('invalid user token')
raise JWTValidationError("invalid user token")

public_key = get_public_key_for_kid(
token_headers.get('kid'), attempt_refresh=attempt_refresh
token_headers.get("kid"), attempt_refresh=attempt_refresh
)

return validate_jwt(encoded_token, public_key, aud, iss)



def require_jwt(aud):
"""
Define a decorator for utility which calls ``validate_request_jwt`` using
Expand All @@ -204,6 +196,7 @@ def require_jwt(aud):
Return:
Callable[[Any], Any]: decorated function
"""

def decorator(f):
"""
Define the actual decorator, which takes the decorated function as an
Expand All @@ -215,12 +208,15 @@ def decorator(f):
Return:
Callable[[Any], Any]: the same type as the function
"""

@functools.wraps(f)
def wrapper(*args, **kwargs):
"""Validate the JWT and call the actual function here."""
validate_request_jwt(aud)
return f(*args, **kwargs)

return wrapper

return decorator


Expand Down Expand Up @@ -256,8 +252,7 @@ def validate_jwt(encoded_token, public_key, aud, iss):
random_aud = list(aud)[0]
try:
token = jwt.decode(
encoded_token, key=public_key, algorithms=['RS256'],
audience=random_aud
encoded_token, key=public_key, algorithms=["RS256"], audience=random_aud
)
except jwt.InvalidTokenError as e:
raise JWTValidationError(e)
Expand All @@ -267,16 +262,16 @@ def validate_jwt(encoded_token, public_key, aud, iss):

# iss
# Check that the issuer of the token is the `USER_API` of the current app.
if token['iss'] != iss:
msg = 'invalid issuer {}; expected {}'.format(token['iss'], iss)
if token["iss"] != iss:
msg = "invalid issuer {}; expected {}".format(token["iss"], iss)
raise JWTValidationError(msg)

# aud
# The audiences listed in the token must completely satisfy all the
# required audiences provided. Note that this is stricter than the
# specification suggested in RFC 7519.
missing = aud - set(token['aud'])
missing = aud - set(token["aud"])
if missing:
raise JWTAudienceError('missing audiences: ' + str(missing))
raise JWTAudienceError("missing audiences: " + str(missing))

return token
2 changes: 1 addition & 1 deletion cdispyutils/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,5 @@ def get_value(dictionary, key, ex=None):
if ex is not None:
raise ex
else:
raise NotFoundError('{} is missing'.format(key))
raise NotFoundError("{} is missing".format(key))
return res
34 changes: 17 additions & 17 deletions cdispyutils/constants.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
BIONIMBUS_REQUEST = 'bionimbus_request'
AWS4_REQUEST = 'aws4_request'
ALGORITHM = 'HMAC-SHA256'
AWS_ALGORITHM = 'AWS4-HMAC-SHA256'
REQUEST_DATE_HEADER = 'x-amz-date'
HASHED_REQUEST_CONTENT = 'x-amz-content-sha256'
REQUEST_HEADER_PREFIX = 'x-amz-'
AUTHORIZATION_HEADER = 'Authorization'
CLIENT_CONTEXT_HEADER = 'x-amz-client-context'
FULL_DATE_TIME_FORMAT = '%Y%m%dT%H%M%SZ'
ABRIDGED_DATE_TIME_FORMAT = '%Y%m%d'
AWS_ALGORITHM_KEY = 'X-Amz-Algorithm'
AWS_CREDENTIAL_KEY = 'X-Amz-Credential'
AWS_DATE_KEY = 'X-Amz-Date'
AWS_EXPIRES_KEY = 'X-Amz-Expires'
AWS_SIGNED_HEADERS_KEY = 'X-Amz-SignedHeaders'
AWS_SIGNATURE_KEY = 'X-Amz-Signature'
BIONIMBUS_REQUEST = "bionimbus_request"
AWS4_REQUEST = "aws4_request"
ALGORITHM = "HMAC-SHA256"
AWS_ALGORITHM = "AWS4-HMAC-SHA256"
REQUEST_DATE_HEADER = "x-amz-date"
HASHED_REQUEST_CONTENT = "x-amz-content-sha256"
REQUEST_HEADER_PREFIX = "x-amz-"
AUTHORIZATION_HEADER = "Authorization"
CLIENT_CONTEXT_HEADER = "x-amz-client-context"
FULL_DATE_TIME_FORMAT = "%Y%m%dT%H%M%SZ"
ABRIDGED_DATE_TIME_FORMAT = "%Y%m%d"
AWS_ALGORITHM_KEY = "X-Amz-Algorithm"
AWS_CREDENTIAL_KEY = "X-Amz-Credential"
AWS_DATE_KEY = "X-Amz-Date"
AWS_EXPIRES_KEY = "X-Amz-Expires"
AWS_SIGNED_HEADERS_KEY = "X-Amz-SignedHeaders"
AWS_SIGNATURE_KEY = "X-Amz-Signature"
31 changes: 22 additions & 9 deletions cdispyutils/hmac4/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,26 @@ def verify_hmac(request, service, get_secret_key):
return verify(service, request, secret_key)


def generate_aws_presigned_url(url, method, cred, service, region,
expires, additional_signed_qs):
def generate_aws_presigned_url(
url, method, cred, service, region, expires, additional_signed_qs
):
request_date = datetime.datetime.utcnow()
session_token = cred.get('aws_session_token', None)
sig_key = HMAC4SigningKey(cred.get('aws_secret_access_key'), service, region=region,
date=request_date.strftime(constants.ABRIDGED_DATE_TIME_FORMAT),
prefix='AWS4', postfix='aws4_request')
return generate_presigned_url(url, method, cred.get('aws_access_key_id'), sig_key,
request_date.strftime(constants.FULL_DATE_TIME_FORMAT),
expires, additional_signed_qs, session_token)
session_token = cred.get("aws_session_token", None)
sig_key = HMAC4SigningKey(
cred.get("aws_secret_access_key"),
service,
region=region,
date=request_date.strftime(constants.ABRIDGED_DATE_TIME_FORMAT),
prefix="AWS4",
postfix="aws4_request",
)
return generate_presigned_url(
url,
method,
cred.get("aws_access_key_id"),
sig_key,
request_date.strftime(constants.FULL_DATE_TIME_FORMAT),
expires,
additional_signed_qs,
session_token,
)
7 changes: 3 additions & 4 deletions cdispyutils/hmac4/error.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@

class DateFormatError(Exception):
pass


class HMAC4Error(Exception):
def __init__(self, message='', json=None):
def __init__(self, message="", json=None):
self.message = message
self.json = json


class UnauthorizedError(HMAC4Error):
def __init__(self, message='', json=None):
def __init__(self, message="", json=None):
super(UnauthorizedError, self).__init__(message, json)
self.code = 401


class ExpiredTimeError(HMAC4Error):
def __init__(self, message='', json=None):
def __init__(self, message="", json=None):
super(ExpiredTimeError, self).__init__(message, json)
self.code = 401
18 changes: 12 additions & 6 deletions cdispyutils/hmac4/hmac4_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@ class HMAC4Auth(AuthBase):
>>> response = requests.get(endpoint, auth=auth)
"""

def __init__(self, access_key, signing_key,
raise_invalid_date=False):
def __init__(self, access_key, signing_key, raise_invalid_date=False):
"""
HMAC4Auth instances can be created by supplying key scope parameters
directly or by using an AWS4SigningKey instance:
Expand Down Expand Up @@ -59,12 +58,19 @@ def __init__(self, access_key, signing_key,
if raise_invalid_date in [True, False]:
self.raise_invalid_date = raise_invalid_date
else:
raise ValueError('raise_invalid_date must be True or False in AWS4Auth.__init__()')
raise ValueError(
"raise_invalid_date must be True or False in AWS4Auth.__init__()"
)

super(HMAC4Auth, self).__init__()

def __call__(self, req):
req = sign_request(req, self.access_key, self.signing_key, self.service,
datetime.datetime.utcnow().strftime(constants.FULL_DATE_TIME_FORMAT))

req = sign_request(
req,
self.access_key,
self.signing_key,
self.service,
datetime.datetime.utcnow().strftime(constants.FULL_DATE_TIME_FORMAT),
)

return req
Loading