@@ -144,13 +144,12 @@ def _extract_groups_and_namespaces_from_token(
144144
145145 # Extract namespaces from the user info
146146 if response .status .user :
147- # For service accounts, the namespace is typically in the username
148- # For regular users, we might need to extract from groups or other fields
149147 username = getattr (response .status .user , "username" , "" ) or ""
148+
150149 if ":" in username and username .startswith (
151150 "system:serviceaccount:"
152151 ):
153- # Extract namespace from service account username
152+ # Service account logic - extract namespace from username
154153 parts = username .split (":" )
155154 if len (parts ) >= 4 :
156155 service_account_namespace = parts [
@@ -163,6 +162,15 @@ def _extract_groups_and_namespaces_from_token(
163162 service_account_namespace
164163 )
165164 groups .extend (namespace_groups )
165+ else :
166+ # Regular user logic - extract namespaces from dashboard-permissions RoleBindings
167+ user_namespaces = self ._extract_user_data_science_projects (
168+ username
169+ )
170+ namespaces .extend (user_namespaces )
171+ logger .info (
172+ f"Found { len (user_namespaces )} data science projects for user { username } : { user_namespaces } "
173+ )
166174
167175 # Also check if there are namespace-specific groups
168176 for group in groups :
@@ -181,6 +189,10 @@ def _extract_groups_and_namespaces_from_token(
181189 except Exception as e :
182190 logger .error (f"Failed to perform Token Access Review: { e } " )
183191 # We dont need to extract groups and namespaces from jwt decoding, not ideal for kubernetes auth
192+
193+ # Remove duplicates
194+ groups = sorted (list (set (groups )))
195+ namespaces = sorted (list (set (namespaces )))
184196 return groups , namespaces
185197
186198 def _extract_namespace_access_groups (self , namespace : str ) -> list [str ]:
@@ -232,6 +244,56 @@ def _extract_namespace_access_groups(self, namespace: str) -> list[str]:
232244
233245 return groups
234246
247+ def _extract_user_data_science_projects (self , username : str ) -> list [str ]:
248+ """
249+ Extract data science project namespaces where a user has been added via dashboard-permissions RoleBindings.
250+
251+ This method queries all RoleBindings where the user is a subject and filters for
252+ 'dashboard-permissions-*' RoleBindings, which indicate the user has been added to that data science project.
253+
254+ Args:
255+ username: The username to search for in RoleBindings
256+
257+ Returns:
258+ list[str]: List of namespace names where the user has dashboard permissions
259+ """
260+ user_namespaces = []
261+ try :
262+ # Query all RoleBindings where the user is a subject
263+ # This is much more efficient than scanning all namespaces
264+ all_role_bindings = self .rbac_v1 .list_role_binding_for_all_namespaces ()
265+
266+ for rb in all_role_bindings .items :
267+ # Check if this is a dashboard-permissions RoleBinding
268+ if (
269+ rb .metadata .name .startswith ("dashboard-permissions-" )
270+ and rb .metadata .labels
271+ and rb .metadata .labels .get ("opendatahub.io/dashboard" ) == "true"
272+ ):
273+ # Check if the user is a subject in this RoleBinding
274+ for subject in rb .subjects or []:
275+ if subject .kind == "User" and subject .name == username :
276+ namespace_name = rb .metadata .namespace
277+ user_namespaces .append (namespace_name )
278+ logger .debug (
279+ f"Found user { username } in dashboard-permissions RoleBinding "
280+ f"{ rb .metadata .name } in namespace { namespace_name } "
281+ )
282+ break # Found the user in this RoleBinding, no need to check other subjects
283+
284+ # Remove duplicates and sort
285+ user_namespaces = sorted (list (set (user_namespaces )))
286+ logger .info (
287+ f"User { username } has dashboard permissions in { len (user_namespaces )} namespaces: { user_namespaces } "
288+ )
289+
290+ except Exception as e :
291+ logger .error (
292+ f"Failed to extract user data science projects for { username } : { e } "
293+ )
294+
295+ return user_namespaces
296+
235297 def _cluster_role_binding_grants_namespace_access (
236298 self , cluster_role_binding , namespace : str
237299 ) -> bool :
0 commit comments