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

VADC-845 #1131

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
32 changes: 32 additions & 0 deletions fence/blueprints/login/cognito.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

from fence.blueprints.login.base import DefaultOAuth2Login, DefaultOAuth2Callback
from fence.models import IdentityProvider
from cdislogging import get_logger

from fence.config import config
from fence.blueprints.login.base import DefaultOAuth2Login, DefaultOAuth2Callback
import fence.resources.cognito.groups
from flask import current_app

logger = get_logger(__name__)


class CognitoLogin(DefaultOAuth2Login):
Expand All @@ -16,3 +24,27 @@ def __init__(self):
super(CognitoCallback, self).__init__(
idp_name=IdentityProvider.cognito, client=flask.current_app.cognito_client
)

def post_login(self, user=None, token_result=None, id_from_idp=None):
Copy link
Contributor

Choose a reason for hiding this comment

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

token_result is actually used in super.post_login(). Why is it not passed?

userinfo = flask.g.userinfo

email = userinfo.get("email")

assign_groups_as_policies = config["cognito"]["assign_groups_as_policies"]
assign_groups_claim_name = config["cognito"]["assign_groups_claim_name"]

if assign_groups_as_policies:
try:
groups = flask.current_app.cognito_client.get_group_claims(
userinfo, assign_groups_claim_name
)
except Exception as e:
err_msg = "Could not retrieve groups"
logger.error("{}: {}".format(e, err_msg))
raise

fence.resources.cognito.groups.sync_gen3_users_authz_from_adfs_groups(
email, groups, db_session=current_app.scoped_session()
)

super(CognitoCallback, self).post_login(id_from_idp=id_from_idp)
Copy link
Contributor

Choose a reason for hiding this comment

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

why not just super(CognitoCallback, self).post_login()?

2 changes: 2 additions & 0 deletions fence/config-default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,8 @@ OPENID_CONNECT:
# and that IdP is a SAML IdP with no 'email_verified' outgoing claim, but it is safe
# to assume all emails from this SAML IdP are in fact verified, we may set this to True
assume_emails_verified: False
assign_groups_as_policies: True
assign_groups_claim_name: ''
# CILogon subscribers can create and manage OIDC clients using COmanage Registry.
# Free tier users may request OIDC clients at https://cilogon.org/oauth2/register
cilogon:
Expand Down
37 changes: 37 additions & 0 deletions fence/resources/cognito/groups.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import fence.scripting.fence_create


def sync_gen3_users_authz_from_adfs_groups(current_session, email, groups):
"""
Sync the authorization of users in the Gen3 database with the groups
they are in on the ADFS server.
Args:
groups (list): list of groups to sync
db_session (flask_sqlalchemy_session.SQLAlchemySession): db session to use
Copy link
Contributor

Choose a reason for hiding this comment

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

db_session is not part of this method's arguments

Return:
dict: dictionary of users that were synced and the groups they were
synced with
"""
# for each group, assign current user the following resources:
# /cohort-middleware/{group}
# with both role_ids: 'cohort_middleware_admin' and 'cohort_middleware_outputs_admin_reader'
db_session = db_session or current_session
Copy link
Contributor

Choose a reason for hiding this comment

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

db_session is not part of this method's arguments

_sync_adfs_groups(
email,
groups,
db_session=db_session,
)


def _sync_adfs_groups(gen3_user, groups, current_session, db_session=None):
db_session = db_session or current_session
Copy link
Contributor

Choose a reason for hiding this comment

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

does not seem to be used?


default_args = fence.scripting.fence_create.get_default_init_syncer_inputs(
authz_provider="Cognito"
)
syncer = fence.scripting.fence_create.init_syncer(**default_args)

groups = syncer.sync_single_user_groups(
gen3_user,
groups,
)
19 changes: 19 additions & 0 deletions fence/resources/openid/cognito_oauth2.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,25 @@ def get_auth_url(self):

return uri

def get_group_claims(self, userinfo, claims):
"""
Return group claims from userinfo response
Args:
userinfo (dict): userinfo response
Return:
str: list of groups
Copy link
Contributor

Choose a reason for hiding this comment

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

is this a single string (e.g. a comma-separated list)? If yes, where is is split into a real list?

"""
result = None

attributes = userinfo.get("Attributes")
if attributes and len(attributes) > 0:
for a in attributes:
if a["Name"] == "custom:groups":
result = a["Value"]
break

return result

def get_auth_info(self, code):
"""
Exchange code for tokens, get email from id token claims.
Expand Down
53 changes: 53 additions & 0 deletions fence/sync/sync_users.py
Original file line number Diff line number Diff line change
Expand Up @@ -2520,3 +2520,56 @@ def sync_single_user_visas(self, user, ga4gh_visas, sess=None, expires=None):
self.logger.error("No arborist client set; skipping arborist sync")

return parsed_visas

def sync_single_user_groups(self, user, groups, sess=None):
"""
Sync a single user's groups during login
Args:
user (userdatamodel.user.User): Fence user whose group
authz info is being synced
groups (list): a list of groups that the user is a member of
Return:
list of successfully assigned groups
"""
try:
user_yaml = UserYAML.from_file(
self.sync_from_local_yaml_file, encrypted=False, logger=self.logger
)
except (EnvironmentError, AssertionError) as e:
self.logger.error(str(e))
self.logger.error("aborting early")
raise

user_projects = dict()
projects = {}

for group in groups:
project = {}
privileges = {"read-storage", "read"}
project[group] = privileges
projects = {**projects, **project}

user_projects[user.username] = projects
user_projects = self.parse_projects(user_projects)

# update arborist db (user access)
if self.arborist_client:
self.logger.info("Synchronizing arborist with authorization info...")
success = self._update_authz_in_arborist(
sess,
user_projects,
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this is for another feature in Gen3, not related to "team projects" and not reusable as such. Can you please check?

user_yaml=user_yaml,
single_user_sync=True,
)
if success:
self.logger.info(
"Finished synchronizing authorization info to arborist"
)
else:
self.logger.error(
"Could not synchronize authorization info successfully to arborist"
)
else:
self.logger.error("No arborist client set; skipping arborist sync")

return
Loading