Skip to content

Commit dd5f83d

Browse files
committed
sweep: DIRACGrid#7784 IAM2CS: ForceNickname, fix duplicate accounts for users
1 parent 41c93fa commit dd5f83d

File tree

5 files changed

+62
-31
lines changed

5 files changed

+62
-31
lines changed

src/DIRAC/ConfigurationSystem/Agent/VOMS2CSAgent.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ def __init__(self, *args, **kwargs):
5555
self.syncPluginName = None
5656
self.compareWithIAM = False
5757
self.useIAM = False
58+
self.forceNickname = False
5859

5960
def initialize(self):
6061
"""Initialize the default parameters"""
@@ -70,6 +71,7 @@ def initialize(self):
7071
self.syncPluginName = self.am_getOption("SyncPluginName", self.syncPluginName)
7172
self.compareWithIAM = self.am_getOption("CompareWithIAM", self.compareWithIAM)
7273
self.useIAM = self.am_getOption("UseIAM", self.useIAM)
74+
self.forceNickname = self.am_getOption("ForceNickname", self.forceNickname)
7375

7476
self.detailedReport = self.am_getOption("DetailedReport", self.detailedReport)
7577
self.mailFrom = self.am_getOption("MailFrom", self.mailFrom)
@@ -127,6 +129,7 @@ def execute(self):
127129
compareWithIAM=compareWithIAM,
128130
useIAM=useIAM,
129131
accessToken=accessToken,
132+
forceNickname=self.forceNickname,
130133
)
131134

132135
result = self.__syncCSWithVOMS( # pylint: disable=unexpected-keyword-arg
@@ -145,6 +148,7 @@ def execute(self):
145148
csapi = resultDict.get("CSAPI")
146149
adminMessages = resultDict.get("AdminMessages", {"Errors": [], "Info": []})
147150
voChanged = resultDict.get("VOChanged", False)
151+
noNickname = resultDict.get("NoNickname", [])
148152
self.log.info(
149153
"Run user results",
150154
": new %d, modified %d, deleted %d, new/suspended %d"
@@ -194,6 +198,11 @@ def execute(self):
194198
mailMsg = ""
195199
if adminMessages["Errors"]:
196200
mailMsg += "\nErrors list:\n %s" % "\n ".join(adminMessages["Errors"])
201+
if self.forceNickname and noNickname:
202+
mailMsg += "There are users without nicknames in the IAM\n"
203+
for entry in noNickname:
204+
mailMsg += str(entry)
205+
mailMsg += "\n\n"
197206
if adminMessages["Info"]:
198207
mailMsg += "\nRun result:\n %s" % "\n ".join(adminMessages["Info"])
199208
if self.detailedReport:

src/DIRAC/ConfigurationSystem/Client/VOMS2CSSynchronizer.py

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ def __init__(
133133
compareWithIAM=False,
134134
useIAM=False,
135135
accessToken=None,
136+
forceNickname=False,
136137
):
137138
"""VOMS2CSSynchronizer class constructor
138139
@@ -167,6 +168,7 @@ def __init__(
167168
self.compareWithIAM = compareWithIAM
168169
self.useIAM = useIAM
169170
self.accessToken = accessToken
171+
self.forceNickname = forceNickname
170172

171173
if syncPluginName:
172174
objLoader = ObjectLoader()
@@ -224,15 +226,15 @@ def compareUsers(self, voms_users, iam_users):
224226
@convertToReturnValue
225227
def _getUsers(self):
226228
if self.compareWithIAM or self.useIAM:
227-
self.iamSrv = IAMService(self.accessToken, vo=self.vo)
229+
self.iamSrv = IAMService(self.accessToken, vo=self.vo, forceNickname=self.forceNickname)
228230
iam_users = returnValueOrRaise(self.iamSrv.getUsers())
229231
if self.useIAM:
230232
return iam_users
231233

232234
vomsSrv = VOMSService(self.vo)
233235
voms_users = returnValueOrRaise(vomsSrv.getUsers())
234236
if self.compareWithIAM:
235-
self.compareUsers(voms_users, iam_users)
237+
self.compareUsers(voms_users.get("Users", {}), iam_users.get("Users", {}))
236238
return voms_users
237239

238240
def syncCSWithVOMS(self):
@@ -259,8 +261,9 @@ def syncCSWithVOMS(self):
259261
if not result["OK"]:
260262
self.log.error("Could not retrieve user information", result["Message"])
261263
return result
262-
263-
self.vomsUserDict = result["Value"]
264+
if getUserErrors := result["Value"]["Errors"]:
265+
self.adminMsgs["Errors"].extend(getUserErrors)
266+
self.vomsUserDict = result["Value"]["Users"]
264267
message = f"There are {len(self.vomsUserDict)} user entries in VOMS for VO {self.vomsVOName}"
265268
self.adminMsgs["Info"].append(message)
266269
self.log.info("VOMS user entries", message)
@@ -331,30 +334,37 @@ def syncCSWithVOMS(self):
331334
# Check the nickName in the same VO to see if the user is already registered
332335
# with another DN
333336
nickName = self.vomsUserDict[dn].get("nickname")
337+
if not nickName and self.forceNickname:
338+
resultDict["NoNickname"].append(self.vomsUserDict[dn])
339+
self.log.error("No nickname defined for", self.vomsUserDict[dn])
340+
continue
334341
if nickName in diracUserDict or nickName in newAddedUserDict:
335342
diracName = nickName
336343
# This is a flag for adding the new DN to an already existing user
337344
newDNForExistingUser = dn
338345

339346
# We have a real new user
340347
if not diracName:
341-
if nickName:
342-
newDiracName = nickName.strip()
343-
else:
344-
newDiracName = self.getUserName(dn)
345-
346348
# Do not consider users with Suspended status in VOMS
347349
if self.vomsUserDict[dn]["suspended"] or self.vomsUserDict[dn]["certSuspended"]:
348350
resultDict["SuspendedUsers"].append(newDiracName)
349351
continue
350352

351-
# If the chosen user name exists already, append a distinguishing suffix
352-
ind = 1
353-
trialName = newDiracName
354-
while newDiracName in allDiracUsers:
355-
# We have a user with the same name but with a different DN
356-
newDiracName = "%s_%d" % (trialName, ind)
357-
ind += 1
353+
# if we have a nickname, we use the nickname no
354+
# matter what so we can have users from different
355+
# VOs with the same nickname / username
356+
if nickName:
357+
newDiracName = nickName.strip()
358+
else:
359+
newDiracName = self.getUserName(dn)
360+
361+
# If the chosen user name exists already, append a distinguishing suffix
362+
ind = 1
363+
trialName = newDiracName
364+
while newDiracName in allDiracUsers:
365+
# We have a user with the same name but with a different DN
366+
newDiracName = "%s_%d" % (trialName, ind)
367+
ind += 1
358368

359369
# We now have everything to add the new user
360370
userDict = {"DN": dn, "CA": self.vomsUserDict[dn]["CA"], "Email": self.vomsUserDict[dn]["mail"]}

src/DIRAC/ConfigurationSystem/ConfigTemplate.cfg

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ Agents
9898
CompareWithIAM = False
9999
# If set to true, will only query IAM and return the list of users from there
100100
UseIAM = False
101+
# If set to true only users with a nickname attribute defined in the IAM are created in DIRAC
102+
ForceNickname = False
101103
}
102104
##END
103105
##BEGIN GOCDB2CSAgent

src/DIRAC/Core/Security/IAMService.py

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,15 @@ def convert_dn(inStr):
2020

2121

2222
class IAMService:
23-
def __init__(self, access_token, vo=None):
23+
def __init__(self, access_token, vo=None, forceNickname=False):
2424
"""c'tor
2525
26-
:param str vo: name of the virtual organization (community)
2726
:param str access_token: the token used to talk to IAM, with the scim:read property
27+
:param str vo: name of the virtual organization (community)
28+
:param bool forceNickname: if enforce the presence of a nickname attribute and do not fall back to username in IAM
2829
2930
"""
31+
self.log = gLogger.getSubLogger(self.__class__.__name__)
3032

3133
if not access_token:
3234
raise ValueError("access_token not set")
@@ -36,6 +38,8 @@ def __init__(self, access_token, vo=None):
3638
if not vo:
3739
raise Exception("No VO name given")
3840

41+
self.forceNickname = forceNickname
42+
3943
self.vo = vo
4044

4145
self.iam_url = None
@@ -79,8 +83,7 @@ def _getIamUserDump(self):
7983
self.iam_users_raw.extend(data["Resources"])
8084
return self.iam_users_raw
8185

82-
@staticmethod
83-
def convert_iam_to_voms(iam_output):
86+
def convert_iam_to_voms(self, iam_output):
8487
"""Convert an IAM entry into the voms style, i.e. DN based"""
8588
converted_output = {}
8689

@@ -93,15 +96,16 @@ def convert_iam_to_voms(iam_output):
9396
# The nickname is available in the list of attributes
9497
# (if configured so)
9598
# in the form {'name': 'nickname', 'value': 'chaen'}
96-
# otherwise, we take the userName
99+
# otherwise, we take the userName unless we forceNickname
97100
try:
98101
cert_dict["nickname"] = [
99102
attr["value"]
100103
for attr in iam_output["urn:indigo-dc:scim:schemas:IndigoUser"]["attributes"]
101104
if attr["name"] == "nickname"
102105
][0]
103106
except (KeyError, IndexError):
104-
cert_dict["nickname"] = iam_output["userName"]
107+
if not self.forceNickname:
108+
cert_dict["nickname"] = iam_output["userName"]
105109

106110
# This is not correct, we take the overall status instead of the certificate one
107111
# however there are no known case of cert suspended while the user isn't
@@ -126,18 +130,23 @@ def convert_iam_to_voms(iam_output):
126130
return converted_output
127131

128132
def getUsers(self):
129-
iam_users_raw = self._getIamUserDump()
133+
"""Extract users from IAM user dump.
134+
135+
:return: dictionary of: "Users": user dictionary keyed by the user DN, "Errors": list of error messages
136+
"""
137+
self.iam_users_raw = self._getIamUserDump()
130138
users = {}
131-
errors = 0
132-
for user in iam_users_raw:
139+
errors = []
140+
for user in self.iam_users_raw:
133141
try:
134142
users.update(self.convert_iam_to_voms(user))
135143
except Exception as e:
136-
errors += 1
137-
print(f"Could not convert {user['name']} {e!r} ")
138-
print(f"There were in total {errors} errors")
144+
errors.append(f"{user['name']} {e!r}")
145+
self.log.error("Could not convert", f"{user['name']} {e!r}")
146+
self.log.error("There were in total", f"{len(errors)} errors")
139147
self.userDict = dict(users)
140-
return S_OK(users)
148+
result = S_OK({"Users": users, "Errors": errors})
149+
return result
141150

142151
def getUsersSub(self) -> dict[str, str]:
143152
"""

src/DIRAC/Core/Security/VOMSService.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ def __init__(self, vo=None, compareWithIAM=False, useIAM=False):
5959
def getUsers(self):
6060
"""Get all the users of the VOMS VO with their detailed information
6161
62-
:return: user dictionary keyed by the user DN
62+
:return: dictionary of: "Users": user dictionary keyed by the user DN, "Errors": empty list
6363
"""
6464
if not self.urls:
6565
return S_ERROR(DErrno.ENOAUTH, "No VOMS server defined")
@@ -126,4 +126,5 @@ def getUsers(self):
126126
resultDict[dn]["nickname"] = attribute.get("value")
127127

128128
self.userDict = dict(resultDict)
129-
return S_OK(resultDict)
129+
# for consistency with IAM interface, we add Errors
130+
return S_OK({"Users": resultDict, "Errors": []})

0 commit comments

Comments
 (0)