From e8492471b446c585bf5edd7070ad01cb3eb19de6 Mon Sep 17 00:00:00 2001 From: Christophe Haen Date: Thu, 27 Jun 2024 17:56:50 +0200 Subject: [PATCH] feat (IAM): populate diracx section --- .../Client/VOMS2CSSynchronizer.py | 16 +++- .../scripts/dirac_admin_voms_sync.py | 1 + src/DIRAC/Core/Security/IAMService.py | 77 +++++++++++++------ 3 files changed, 69 insertions(+), 25 deletions(-) diff --git a/src/DIRAC/ConfigurationSystem/Client/VOMS2CSSynchronizer.py b/src/DIRAC/ConfigurationSystem/Client/VOMS2CSSynchronizer.py index 7512428f383..88283f2d023 100644 --- a/src/DIRAC/ConfigurationSystem/Client/VOMS2CSSynchronizer.py +++ b/src/DIRAC/ConfigurationSystem/Client/VOMS2CSSynchronizer.py @@ -4,6 +4,7 @@ from collections import defaultdict +from diraccfg import CFG from DIRAC import S_OK, S_ERROR, gLogger, gConfig from DIRAC.Core.Utilities.ReturnValues import returnValueOrRaise, convertToReturnValue from DIRAC.Core.Security.IAMService import IAMService @@ -162,6 +163,7 @@ def __init__( self.autoLiftSuspendedStatus = autoLiftSuspendedStatus self.voChanged = False self.syncPlugin = None + self.iamSrv = None self.compareWithIAM = compareWithIAM self.useIAM = useIAM self.accessToken = accessToken @@ -222,8 +224,8 @@ def compareUsers(self, voms_users, iam_users): @convertToReturnValue def _getUsers(self): if self.compareWithIAM or self.useIAM: - iamSrv = IAMService(self.accessToken, vo=self.vo) - iam_users = returnValueOrRaise(iamSrv.getUsers()) + self.iamSrv = IAMService(self.accessToken, vo=self.vo) + iam_users = returnValueOrRaise(self.iamSrv.getUsers()) if self.useIAM: return iam_users @@ -567,6 +569,16 @@ def syncCSWithVOMS(self): ) self.log.info("The following users to be checked for deletion:", "\n\t".join(sorted(oldUsers))) + # Try to fill in the DiracX section + if self.useIAM: + iam_subs = self.iamSrv.getUsersSub() + diracx_vo_config = {"DiracX": {"CsSync": {"VOs": {self.vo: {"UserSubjects": iam_subs}}}}} + iam_sub_cfg = CFG() + iam_sub_cfg.loadFromDict(diracx_vo_config) + result = self.csapi.mergeFromCFG(iam_sub_cfg) + if not result["OK"]: + return result + resultDict["CSAPI"] = self.csapi resultDict["AdminMessages"] = self.adminMsgs resultDict["VOChanged"] = self.voChanged diff --git a/src/DIRAC/ConfigurationSystem/scripts/dirac_admin_voms_sync.py b/src/DIRAC/ConfigurationSystem/scripts/dirac_admin_voms_sync.py index 453014fcf05..56929a15a3d 100755 --- a/src/DIRAC/ConfigurationSystem/scripts/dirac_admin_voms_sync.py +++ b/src/DIRAC/ConfigurationSystem/scripts/dirac_admin_voms_sync.py @@ -95,6 +95,7 @@ def syncCSWithVOMS(vomsSync): if csapi and csapi.csModified: if dryRun: gLogger.notice("There are changes to Registry ready to commit, skipped because of dry run") + csapi.showDiff() else: yn = input("There are changes to Registry ready to commit, do you want to proceed ? [Y|n]:") if yn == "" or yn[0].lower() == "y": diff --git a/src/DIRAC/Core/Security/IAMService.py b/src/DIRAC/Core/Security/IAMService.py index 9faad6b14bc..2a4c453f07d 100644 --- a/src/DIRAC/Core/Security/IAMService.py +++ b/src/DIRAC/Core/Security/IAMService.py @@ -52,31 +52,32 @@ def __init__(self, access_token, vo=None): self.userDict = None self.access_token = access_token + self.iam_users_raw = [] def _getIamUserDump(self): """List the users from IAM""" - headers = {"Authorization": f"Bearer {self.access_token}"} - iam_list_url = f"{self.iam_url}/scim/Users" - iam_users = [] - startIndex = 1 - # These are just initial values, they are updated - # while we loop to their actual values - totalResults = 1000 # total number of users - itemsPerPage = 10 - while startIndex <= totalResults: - resp = requests.get(iam_list_url, headers=headers, params={"startIndex": startIndex}) - resp.raise_for_status() - data = resp.json() - # These 2 should never change while looping - # but you may have a new user appearing - # while looping - totalResults = data["totalResults"] - itemsPerPage = data["itemsPerPage"] - - startIndex += itemsPerPage - iam_users.extend(data["Resources"]) - return iam_users + if not self.iam_users_raw: + headers = {"Authorization": f"Bearer {self.access_token}"} + iam_list_url = f"{self.iam_url}/scim/Users" + startIndex = 1 + # These are just initial values, they are updated + # while we loop to their actual values + totalResults = 1000 # total number of users + itemsPerPage = 10 + while startIndex <= totalResults: + resp = requests.get(iam_list_url, headers=headers, params={"startIndex": startIndex}) + resp.raise_for_status() + data = resp.json() + # These 2 should never change while looping + # but you may have a new user appearing + # while looping + totalResults = data["totalResults"] + itemsPerPage = data["itemsPerPage"] + + startIndex += itemsPerPage + self.iam_users_raw.extend(data["Resources"]) + return self.iam_users_raw @staticmethod def convert_iam_to_voms(iam_output): @@ -113,10 +114,10 @@ def convert_iam_to_voms(iam_output): return converted_output def getUsers(self): - self.iam_users_raw = self._getIamUserDump() + iam_users_raw = self._getIamUserDump() users = {} errors = 0 - for user in self.iam_users_raw: + for user in iam_users_raw: try: users.update(self.convert_iam_to_voms(user)) except Exception as e: @@ -125,3 +126,33 @@ def getUsers(self): print(f"There were in total {errors} errors") self.userDict = dict(users) return S_OK(users) + + def getUsersSub(self) -> dict[str, str]: + """ + Return the mapping based on IAM sub: + {sub : nickname} + + """ + iam_users_raw = self._getIamUserDump() + diracx_user_section = {} + for user_info in iam_users_raw: + # The nickname is available in the list of attributes + # (if configured so) + # in the form {'name': 'nickname', 'value': 'chaen'} + # otherwise, we take the userName + try: + nickname = [ + attr["value"] + for attr in user_info["urn:indigo-dc:scim:schemas:IndigoUser"]["attributes"] + if attr["name"] == "nickname" + ][0] + except (KeyError, IndexError): + nickname = user_info["userName"] + sub = user_info["id"] + + diracx_user_section[sub] = nickname + + # reorder it + diracx_user_section = dict(sorted(diracx_user_section.items())) + + return diracx_user_section