Skip to content

Commit

Permalink
Merge branch 'integration202412' of https://github.com/uc-cdis/fence
Browse files Browse the repository at this point in the history
…into uc-cdis-integration202412
  • Loading branch information
grugna committed Dec 10, 2024
2 parents efebef0 + 0f8a6bb commit 1d4770a
Show file tree
Hide file tree
Showing 14 changed files with 565 additions and 422 deletions.
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ See detailed explanation [here](docs/additional_documentation/setup.md)

1. [Terminologies](docs/additional_documentation/terminology.md)
2. [Accessing Data](docs/additional_documentation/data_access.md#accessing-data)
3. [Token management](docs/additional_documentation/token_management.md)
4. [fence-create](docs/additional_documentation/fence_create.md)
5. [Default expiration times](docs/additional_documentation/default_expiration_times.md)
3. [user.yaml guide](docs/additional_documentation/user.yaml_guide.md)
4. [Token management](docs/additional_documentation/token_management.md)
5. [fence-create](docs/additional_documentation/fence_create.md)
6. [Default expiration times](docs/additional_documentation/default_expiration_times.md)
2 changes: 1 addition & 1 deletion docs/additional_documentation/setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,4 +154,4 @@ saved by the OAuth client to use with
## Quickstart with Helm

You can now deploy individual services via Helm!
Please refer to the Helm quickstart guide HERE (https://github.com/uc-cdis/fence/blob/master/docs/quickstart_helm.md)
Please refer to the Helm quickstart guide HERE (https://github.com/uc-cdis/fence/blob/master/docs/additional_documentation/quickstart_helm.md)
45 changes: 15 additions & 30 deletions fence/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from fence.user import get_current_user
from fence.utils import clear_cookies
from cdislogging import get_logger
from pcdcutils.gen3 import Gen3RequestManager
from fence.authz.auth import check_arborist_auth

logger = get_logger(__name__)

Expand Down Expand Up @@ -104,6 +104,13 @@ def set_flask_session_values(user):

user = query_for_user(session=current_app.scoped_session(), username=username)
if user:
if user.active is False:
# Abort login if user.active is False (user.active is None or True are both
# considered active in this case):
raise Unauthorized(
"User is known but not authorized/activated in the system"
)

_update_users_email(user, email)
_update_users_id_from_idp(user, id_from_idp)
_update_users_last_auth(user)
Expand All @@ -115,7 +122,11 @@ def set_flask_session_values(user):
set_flask_session_values(user)
return
else:
# we need a new user
if not config["ALLOW_NEW_USER_ON_LOGIN"]:
# do not create new active users automatically
raise Unauthorized("New user is not yet authorized/activated in the system")

# add the new user
user = User(username=username)

if email:
Expand Down Expand Up @@ -275,35 +286,9 @@ def get_user_from_claims(claims):
)


def admin_required(f):
"""
Require user to be an admin user.
"""

@wraps(f)
def wrapper(*args, **kwargs):
# logger.debug("Decorator: admin required, wrapper")
if not flask.g.user:
# logger.debug("Decorator admin required, wrapper: not flask.g.user")
raise Unauthorized("Require login")
if flask.g.user.is_admin is not True:
# logger.debug("Decorator admin required, wrapper: flask.g.user.is_admin is not True")
g3rm = Gen3RequestManager(headers=flask.request.headers)
if g3rm.is_gen3_signed():
data = flask.request.get_json()
if not g3rm.valid_gen3_signature(json.dumps(data), config):
raise Unauthorized("Gen3 signed request is invalid")
else:
raise Unauthorized("Require admin user")
return f(*args, **kwargs)

return wrapper


def admin_login_required(function):
"""Compose the login required and admin required decorators."""
# logger.debug("Decorator: admin_login_required")
return login_required({"admin"})(admin_required(function))
"""Use the check_arborist_auth decorator checking on admin authorization."""
return check_arborist_auth(["/services/fence/admin"], "*", check_signature=True)(function)


def _update_users_email(user, email):
Expand Down
16 changes: 15 additions & 1 deletion fence/blueprints/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,22 @@ def create_user():
username = request.get_json().get("name", None)
role = request.get_json().get("role", None)
email = request.get_json().get("email", None)
display_name = request.get_json().get("display_name", None)
phone_number = request.get_json().get("phone_number", None)
idp_name = request.get_json().get("idp_name", None)
tags = request.get_json().get("tags", None)

return jsonify(
admin.create_user(current_app.scoped_session(), username, role, email)
admin.create_user(
current_app.scoped_session(),
username,
role,
email,
display_name,
phone_number,
idp_name,
tags,
)
)


Expand Down
11 changes: 8 additions & 3 deletions fence/blueprints/data/indexd.py
Original file line number Diff line number Diff line change
Expand Up @@ -1061,6 +1061,11 @@ def get_signed_url(

bucket_name = self.bucket_name()
bucket = s3_buckets.get(bucket_name)
# special handling for bucket names from fence config may contain not allowed characters (e.g.: wildcards)
# in this case, use indexd url to determine bucket name
real_bucket_name = bucket_name
if real_bucket_name and not re.match("^[a-z0-9-.]{3,63}$", real_bucket_name):
real_bucket_name = self.parsed_url.netloc

object_id = self.parsed_url.path.strip("/")

Expand Down Expand Up @@ -1114,7 +1119,7 @@ def get_signed_url(
# get presigned url for upload
if action == "PUT":
url = cirrus_aws.upload_presigned_url(
bucket_name, object_id, expires_in, None
real_bucket_name, object_id, expires_in, None
)
# get presigned url for download
else:
Expand All @@ -1123,11 +1128,11 @@ def get_signed_url(
# https://github.com/boto/boto3/issues/3685
auth_info["x-amz-request-payer"] = "requester"
url = cirrus_aws.requester_pays_download_presigned_url(
bucket_name, object_id, expires_in, auth_info
real_bucket_name, object_id, expires_in, auth_info
)
else:
url = cirrus_aws.download_presigned_url(
bucket_name, object_id, expires_in, auth_info
real_bucket_name, object_id, expires_in, auth_info
)

return url
Expand Down
10 changes: 10 additions & 0 deletions fence/config-default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,16 @@ DEFAULT_BACKOFF_SETTINGS_MAX_TRIES: 3
# here. Something like: [email protected]
SUPPORT_EMAIL_FOR_ERRORS: null

# //////////////////////////////////////////////////////////////////////////////////////
# USER ACTIVATION
# //////////////////////////////////////////////////////////////////////////////////////
# If you want new users (read: users that login for the first time) to automatically be
# allowed through and added to the Fence DB, set this to true. Otherwise, set this to false.
# Setting it to false will ensure the user will only be able to login after the user
# is added to the Fence DB via a separate process. This two-step process allows for
# a separate onboarding and user "approval" process, instead of the default automatic approval.
ALLOW_NEW_USER_ON_LOGIN: true

# //////////////////////////////////////////////////////////////////////////////////////
# SHIBBOLETH
# - Support using `shibboleth` in LOGIN_OPTIONS
Expand Down
8 changes: 1 addition & 7 deletions fence/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,7 @@ class FenceConfig(Config):
def post_process(self):
# backwards compatibility if no new YAML cfg provided
# these cfg use to be in settings.py so we need to make sure they gets defaulted
default_config = yaml_load(
open(
os.path.join(
os.path.dirname(os.path.abspath(__file__)), "config-default.yaml"
)
)
)
default_config = yaml_load(open(DEFAULT_CFG_PATH))

defaults = [
"APPLICATION_ROOT",
Expand Down
39 changes: 38 additions & 1 deletion fence/resources/admin/admin_users.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
UserGoogleAccount,
UserGoogleAccountToProxyGroup,
query_for_user,
IdentityProvider,
Tag,
)
from fence.resources import group as gp, project as pj, user as us, userdatamodel as udm
from flask import current_app as capp
Expand Down Expand Up @@ -126,7 +128,16 @@ def get_user_groups(current_session, username):
return {"groups": user_groups_info}


def create_user(current_session, username, role, email):
def create_user(
current_session,
username,
role,
email,
display_name=None,
phone_number=None,
idp_name=None,
tags=None,
):
"""
Create a user for all the projects or groups in the list.
If the user already exists, to avoid unadvertedly changing it, we suggest update
Expand All @@ -136,28 +147,54 @@ def create_user(current_session, username, role, email):
raise UserError(("Error: Please provide a username"))
try:
usr = us.get_user(current_session, username)
logger.debug(f"User already exists for: {username}")
raise UserError(
(
"Error: user already exist. If this is not a"
" mistake, please, retry using update"
)
)
except NotFound:
logger.debug(f"User not found for: {username}. Checking again ignoring case...")
user_list = [
user["name"].upper() for user in get_all_users(current_session)["users"]
]
if username.upper() in user_list:
logger.debug(f"User already exists for: {username}")
raise UserError(
(
"Error: user with a name with the same combination/order "
"of characters already exists. Please remove this other user"
" or modify the new one. Contact us in case of doubt"
)
)
logger.debug(f"User does not yet exist for: {username}. Creating a new one...")
is_admin = role == "admin"
email_add = email
usr = User(username=username, active=True, is_admin=is_admin, email=email_add)
usr.display_name = display_name
usr.phone_number = phone_number

if idp_name:
logger.debug(f"User {username} idp set to {idp_name}")
idp = (
current_session.query(IdentityProvider)
.filter(IdentityProvider.name == idp_name)
.first()
)
if not idp:
idp = IdentityProvider(name=idp_name)
usr.identity_provider = idp
if tags:
logger.debug(f"Setting {len(tags)} tags for user {username}...")
for key, value in tags.items():
tag = Tag(key=key, value=value)
usr.tags.append(tag)

logger.debug(f"Adding user {username}...")
current_session.add(usr)
current_session.commit()
logger.debug(f"Success adding user {username}. Returning...")
return us.get_user_info(current_session, username)


Expand Down
Loading

0 comments on commit 1d4770a

Please sign in to comment.