44import globus_sdk
55from flask .wrappers import Response as flask_response
66from hubmap_commons .hm_auth import AuthHelper
7- from rest_framework .exceptions import ParseError
7+ from rest_framework .exceptions import ParseError , PermissionDenied
88from rest_framework .response import Response
99
1010from user_workspaces_server .controllers .userauthenticationmethods .abstract_user_authentication import (
@@ -21,13 +21,108 @@ def __init__(self, config):
2121 client_secret = self .connection_details ["client_secret" ]
2222 self .authentication_type = self .connection_details ["authentication_type" ]
2323 self .oauth = globus_sdk .ConfidentialAppAuthClient (client_id , client_secret )
24+ self .allowed_globus_groups = self .connection_details .get ("allowed_globus_groups" , [])
2425 if not AuthHelper .isInitialized ():
2526 self .auth_helper = AuthHelper .create (clientId = client_id , clientSecret = client_secret )
2627 else :
2728 self .auth_helper = AuthHelper .instance ()
2829
2930 def has_permission (self , internal_user ):
30- pass
31+ """
32+ Verify user has permission by checking external user mapping exists
33+ and optionally validating Globus group membership.
34+
35+ Returns:
36+ ExternalUserMapping on success, False on failure
37+ """
38+ external_user_mapping = self .get_external_user_mapping (
39+ {"user_id" : internal_user , "user_authentication_name" : type (self ).__name__ }
40+ )
41+
42+ if not external_user_mapping :
43+ # No mapping exists - user needs to authenticate first
44+ return False
45+
46+ # If group checking is enabled, validate membership
47+ if self .allowed_globus_groups :
48+ try :
49+ # Extract groups token from stored external_user_details
50+ external_user_details = external_user_mapping .external_user_details or {}
51+ groups_token = external_user_details .get ("globus_groups_token" )
52+
53+ if not groups_token :
54+ logger .error (
55+ f"Groups token not found for user { internal_user .username } . "
56+ "User may need to re-authenticate."
57+ )
58+ return False
59+
60+ # Check if user is still a member of allowed groups
61+ if not self ._check_group_membership (
62+ groups_token , external_user_mapping .external_user_id
63+ ):
64+ logger .warning (
65+ f"User { internal_user .username } is no longer a member of allowed Globus groups."
66+ )
67+ return False
68+
69+ except Exception as e :
70+ logger .error (
71+ f"Error checking group membership for { internal_user .username } : { repr (e )} "
72+ )
73+ return False
74+
75+ # User has valid mapping and (if required) is in allowed groups
76+ return external_user_mapping
77+
78+ def _check_group_membership (self , groups_token , user_id ):
79+ """
80+ Check if user is a member of any allowed Globus groups.
81+
82+ Args:
83+ groups_token: Access token for Globus Groups API
84+ user_id: Globus user ID (sub)
85+
86+ Returns:
87+ True if user is in at least one allowed group or if no groups configured, False otherwise
88+ """
89+ if not self .allowed_globus_groups :
90+ # No groups configured - skip check
91+ return True
92+
93+ try :
94+ # Create GroupsClient with access token
95+ authorizer = globus_sdk .AccessTokenAuthorizer (groups_token )
96+ groups_client = globus_sdk .GroupsClient (authorizer = authorizer )
97+
98+ # Get user's group memberships
99+ user_groups = groups_client .get_my_groups ()
100+
101+ # Extract group IDs from response
102+ user_group_ids = {group ["id" ] for group in user_groups }
103+
104+ # Check if user is in any allowed group (OR logic)
105+ allowed_groups_set = set (self .allowed_globus_groups )
106+ intersection = user_group_ids .intersection (allowed_groups_set )
107+
108+ if intersection :
109+ logger .info (f"User { user_id } is member of allowed groups: { intersection } " )
110+ return True
111+ else :
112+ logger .warning (
113+ f"User { user_id } is not a member of any allowed groups. "
114+ f"User groups: { user_group_ids } , Allowed: { allowed_groups_set } "
115+ )
116+ return False
117+
118+ except globus_sdk .GlobusAPIError as e :
119+ logger .error (f"Globus API error checking groups for { user_id } : { e .code } - { e .message } " )
120+ # Fail closed - deny access on API errors
121+ return False
122+ except Exception as e :
123+ logger .error (f"Unexpected error checking groups for { user_id } : { repr (e )} " )
124+ # Fail closed - deny access on unexpected errors
125+ return False
31126
32127 def api_authenticate (self , request ):
33128 try :
@@ -55,6 +150,19 @@ def api_authenticate(self, request):
55150 }
56151 )
57152
153+ # Check whether the user is part of predefined set of Globus groups
154+ if not external_user_mapping and self .allowed_globus_groups :
155+ # For new users, check group membership before creating account
156+ groups_token = globus_user_info .get ("globus_groups_token" )
157+ if not groups_token :
158+ raise PermissionDenied ("Groups token not available for authentication." )
159+
160+ if not self ._check_group_membership (groups_token , globus_user_info ["sub" ]):
161+ raise PermissionDenied (
162+ "User is not a member of any allowed Globus groups. "
163+ "Please contact your administrator for access."
164+ )
165+
58166 if not external_user_mapping :
59167 # Since its Globus, lets get the username from the email
60168 username = globus_user_info ["email" ].split ("@" )[0 ]
@@ -80,6 +188,7 @@ def api_authenticate(self, request):
80188 "user_authentication_name" : type (self ).__name__ ,
81189 "external_user_id" : globus_user_info ["sub" ],
82190 "external_username" : globus_user_info ["username" ],
191+ "external_user_details" : globus_user_info ,
83192 }
84193 )
85194 return internal_user
@@ -125,13 +234,23 @@ def globus_oauth_get_user_info(self, body):
125234 code = body ["code" ]
126235 tokens = self .oauth .oauth2_exchange_code_for_tokens (code )
127236
128- # Need to add call here to grab user profile info
129- return self .introspect_globus_user (
130- tokens .by_resource_server ["groups.api.globus.org" ]["access_token" ]
131- )
237+ # Get user profile info using groups token
238+ groups_token = tokens .by_resource_server ["groups.api.globus.org" ]["access_token" ]
239+ user_info = self .introspect_globus_user (groups_token )
240+
241+ # Store the groups token for later group membership checking
242+ user_info ["globus_groups_token" ] = groups_token
243+
244+ return user_info
132245
133246 def globus_token_get_user_info (self , body ):
134247 if "auth_token" not in body :
135248 raise ParseError ("Missing auth_token." )
136249
137- return self .introspect_globus_user (body .get ("auth_token" ))
250+ auth_token = body .get ("auth_token" )
251+ user_info = self .introspect_globus_user (auth_token )
252+
253+ # Store the auth token as groups token for group membership checking
254+ user_info ["globus_groups_token" ] = auth_token
255+
256+ return user_info
0 commit comments