Skip to content

Commit

Permalink
Merge pull request #984 from uc-cdis/feat/create_presigned_url_passports
Browse files Browse the repository at this point in the history
Feat/create presigned url passports
  • Loading branch information
BinamB committed Nov 29, 2021
2 parents fc74833 + da5a612 commit 8883117
Show file tree
Hide file tree
Showing 6 changed files with 784 additions and 382 deletions.
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ RUN pip install --upgrade pip
RUN pip install --upgrade poetry
RUN apt-get update \
&& apt-get install -y --no-install-recommends curl bash git \
&& apt-get install -y vim \
libmcrypt4 libmhash2 mcrypt \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/
Expand Down
70 changes: 51 additions & 19 deletions fence/blueprints/data/indexd.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

from datetime import datetime, timedelta

from sqlalchemy.sql.functions import user

from cached_property import cached_property
import cirrus
from cirrus import GoogleCloudManager
Expand All @@ -19,6 +21,7 @@
AccountSasPermissions,
generate_blob_sas,
)
from fence import auth

from fence.auth import (
get_jwt,
Expand Down Expand Up @@ -46,7 +49,7 @@
from fence.resources.ga4gh.passports import get_gen3_users_from_ga4gh_passports
from fence.utils import get_valid_expiration_from_request
from . import multipart_upload
from ...models import AssumeRoleCacheAWS
from ...models import AssumeRoleCacheAWS, query_for_user
from ...models import AssumeRoleCacheGCP

logger = get_logger(__name__)
Expand Down Expand Up @@ -103,7 +106,6 @@ def get_signed_url_for_file(
)

prepare_presigned_url_audit_log(requested_protocol, indexed_file)

signed_url = indexed_file.get_signed_url(
requested_protocol,
action,
Expand Down Expand Up @@ -411,20 +413,25 @@ def get_signed_url(
file_name=None,
user_ids_from_passports=None,
):
authorized_user_id = None
if self.index_document.get("authz"):
action_to_permission = {
"upload": "write-storage",
"download": "read-storage",
}
if not self.check_authz(
authorized_user_id = self.check_authz(
action_to_permission[action],
user_ids_from_passports=user_ids_from_passports,
):
)
if not authorized_user_id:
raise Unauthorized(
f"Either you weren't logged in or you don't have "
f"{action_to_permission[action]} permission "
f"on authz resource: {self.index_document['authz']}"
)
authorized_user_id = (
authorized_user_id if isinstance(authorized_user_id, str) else None
)
else:
if self.public_acl and action == "upload":
raise Unauthorized(
Expand All @@ -438,15 +445,27 @@ def get_signed_url(
raise Unauthorized(
f"You don't have access permission on this file: {self.file_id}"
)

if action is not None and action not in SUPPORTED_ACTIONS:
raise NotSupported("action {} is not supported".format(action))
return self._get_signed_url(
protocol, action, expires_in, force_signed_url, r_pays_project, file_name
protocol,
action,
expires_in,
force_signed_url,
r_pays_project,
file_name,
authorized_user_id,
)

def _get_signed_url(
self, protocol, action, expires_in, force_signed_url, r_pays_project, file_name
self,
protocol,
action,
expires_in,
force_signed_url,
r_pays_project,
file_name,
user_id=None,
):
if action == "upload":
# NOTE: self.index_document ensures the GUID exists in indexd and raises
Expand Down Expand Up @@ -481,6 +500,7 @@ def _get_signed_url(
public_data=self.public,
force_signed_url=force_signed_url,
r_pays_project=r_pays_project,
user_id=user_id,
)

raise NotFound(
Expand All @@ -506,17 +526,21 @@ def check_authz(self, action, user_ids_from_passports=None):
)

# handle multiple GA4GH passports as a means of authn/z

if user_ids_from_passports:
for user_id in user_ids_from_passports:
authorized = flask.current_app.arborist.auth_request(
jwt=None,
user_id=user_id,
service="fence",
methods=action,
resources=self.index_document["authz"],
)
# if any passport provides access, user is authorized
if authorized:
return authorized
# for google proxy groups we need to know which user_id gave access
return user_id
return authorized
else:
try:
token = get_jwt()
Expand Down Expand Up @@ -693,6 +717,7 @@ def get_signed_url(
expires_in,
public_data=False,
force_signed_url=True,
user_ids_from_passports=None,
**kwargs,
):
return self.url
Expand Down Expand Up @@ -1056,10 +1081,11 @@ def get_signed_url(
public_data=False,
force_signed_url=True,
r_pays_project=None,
user_id=None,
):
resource_path = self.get_resource_path()

user_info = _get_user_info()
user_info = _get_user_info(user=user_id)

if public_data and not force_signed_url:
url = "https://storage.cloud.google.com/" + resource_path
Expand Down Expand Up @@ -1126,7 +1152,7 @@ def _generate_google_storage_signed_url(
r_pays_project=None,
):

proxy_group_id = get_or_create_proxy_group_id()
proxy_group_id = get_or_create_proxy_group_id(user_id=user_id)
expiration_time = int(time.time()) + expires_in

is_cached = False
Expand Down Expand Up @@ -1434,21 +1460,27 @@ def delete(self, container, blob): # pylint: disable=R0201
return ("Failed to delete data file.", status_code)


def _get_user_info(sub_type=str):
def _get_user_info(sub_type=str, user=None):
"""
Attempt to parse the request for token to authenticate the user. fallback to
populated information about an anonymous user.
By default, cast `sub` to str. Use `sub_type` to override this behavior.
"""
# TODO Update to support POSTed passport
try:
set_current_token(
validate_request(scope={"user"}, audience=config.get("BASE_URL"))
)
user_id = current_token["sub"]
if sub_type:
user_id = sub_type(user_id)
username = current_token["context"]["user"]["name"]
if user:
if hasattr(flask.current_app, "db"):
with flask.current_app.db.session as session:
result = query_for_user(session, user)
username = result.username
user_id = result.id
else:
set_current_token(
validate_request(scope={"user"}, audience=config.get("BASE_URL"))
)
user_id = current_token["sub"]
if sub_type:
user_id = sub_type(user_id)
username = current_token["context"]["user"]["name"]
except JWTError:
# this is fine b/c it might be public data, sign with anonymous username/id
user_id = None
Expand Down
20 changes: 12 additions & 8 deletions fence/resources/google/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -515,18 +515,23 @@ def _update_service_account_db_entry(
return service_account_db_entry


def get_or_create_proxy_group_id(expires=None):
def get_or_create_proxy_group_id(expires=None, user_id=None):
"""
If no username returned from token or database, create a new proxy group
for the give user. Also, add the access privileges.
Returns:
int: id of (possibly newly created) proxy group associated with user
"""
proxy_group_id = _get_proxy_group_id()
proxy_group_id = _get_proxy_group_id(user_id=user_id)
if not proxy_group_id:
user_id = current_token["sub"]
username = current_token.get("context", {}).get("user", {}).get("name", "")
if user_id:
user = current_session.query(User).filter_by(id=int(user_id)).first()
user_id = user_id
username = user.username
else:
user_id = current_token["sub"]
username = current_token.get("context", {}).get("user", {}).get("name", "")
proxy_group_id = _create_proxy_group(user_id, username).id

privileges = current_session.query(AccessPrivilege).filter(
Expand Down Expand Up @@ -557,7 +562,7 @@ def get_or_create_proxy_group_id(expires=None):
return proxy_group_id


def _get_proxy_group_id():
def _get_proxy_group_id(user_id=None):
"""
Get users proxy group id from the current token, if possible.
Otherwise, check the database for it.
Expand All @@ -568,9 +573,8 @@ def _get_proxy_group_id():
proxy_group_id = get_users_proxy_group_from_token()

if not proxy_group_id:
user = (
current_session.query(User).filter(User.id == current_token["sub"]).first()
)
user_id = user_id or current_token["sub"]
user = current_session.query(User).filter(User.id == user_id).first()
proxy_group_id = user.google_proxy_group_id

return proxy_group_id
Expand Down

0 comments on commit 8883117

Please sign in to comment.