def initialize( self ): self.am_setOption( "PollingTime", 3600 * 6 ) # Every 6 hours self.vomsSrv = VOMSService() self.proxyLocation = os.path.join( self.am_getWorkDirectory(), ".volatileId" ) self.__adminMsgs = {} # print self.getLFCRegisteredDNs() return S_OK()
def printUsers(): """Print the list of users in the VO""" clip = Params() clip.registerSwitches() Script.parseCommandLine() clip.setURLs() from DIRAC.Core.Security.VOMSService import VOMSService voms = VOMSService(clip.adminUrl, clip.attributeUrl) res = voms.admListMembers() if not res['OK']: gLogger.error(res['Message']) dexit(1) users = res['Value'] for user in users: if not clip.username: printUser(user, clip.addPrint) else: if user['DN'].lower().count(clip.username.lower()): printUser(user, clip.addPrint)
def printUsers(): """Print the list of users in the VO""" clip = Params() clip.registerSwitches() Script.parseCommandLine() clip.setURLs() from DIRAC.Core.Security.VOMSService import VOMSService voms = VOMSService(vo=clip.voName) res = voms.getUsers() if not res['OK']: gLogger.error(res['Message']) dexit(1) users = res['Value'] for userDN, userInfo in users.iteritems(): userInfo['DN'] = userDN if not clip.username: printUser(userInfo, clip.addPrint) else: if userDN.lower().count(clip.username.lower()): printUser(userInfo, clip.addPrint)
def printUsers(): """Print the list of users in the VO""" clip = Params() clip.registerSwitches() Script.parseCommandLine() clip.setURLs() from DIRAC.Core.Security.VOMSService import VOMSService voms = VOMSService(vo=clip.voName, adminUrl=clip.adminUrl, attributesUrl=clip.attributeUrl) res = voms.admListMembers() if not res['OK']: gLogger.error(res['Message']) dexit(1) users = res['Value'] for user in users: if not clip.username: printUser(user, clip.addPrint) else: if user['DN'].lower().count(clip.username.lower()): printUser(user, clip.addPrint)
def __syncCSWithVOMS(self, vo): self.__adminMsgs = {'Errors': [], 'Info': []} # Get DIRAC group vs VOMS Role Mappings result = getVOMSRoleGroupMapping(vo) if not result['OK']: return result vomsDIRACMapping = result['Value']['VOMSDIRAC'] diracVOMSMapping = result['Value']['DIRACVOMS'] noVOMSGroups = result['Value']['NoVOMS'] vomsSrv = VOMSService(vo) # Get VOMS VO name result = vomsSrv.admGetVOName() if not result['OK']: self.log.error('Could not retrieve VOMS VO name', "for %s" % vo) return result vomsVOName = result['Value'].lstrip('/') self.log.verbose("VOMS VO Name for %s is %s" % (vo, vomsVOName)) # Get VOMS users result = vomsSrv.getUsers() if not result['OK']: self.log.error('Could not retrieve user information from VOMS', result['Message']) return result vomsUserDict = result['Value'] message = "There are %s registered users in VOMS VO %s" % ( len(vomsUserDict), vomsVOName) self.__adminMsgs['Info'].append(message) self.log.info(message) # Get DIRAC users diracUsers = getUsersInVO(vo) if not diracUsers: return S_ERROR("No VO users found for %s" % vo) result = self.csapi.describeUsers(diracUsers) if not result['OK']: self.log.error('Could not retrieve CS User description') return result diracUserDict = result['Value'] self.__adminMsgs['Info'].append( "There are %s registered users in DIRAC for VO %s" % (len(diracUserDict), vo)) self.log.info("There are %s registered users in DIRAC VO %s" % (len(diracUserDict), vo)) # Find new and obsoleted user DNs existingDNs = [] obsoletedDNs = [] newDNs = [] for user in diracUserDict: dn = diracUserDict[user]['DN'] existingDNs.append(dn) if dn not in vomsUserDict: obsoletedDNs.append(dn) for dn in vomsUserDict: if dn not in existingDNs: newDNs.append(dn) allDiracUsers = getAllUsers() nonVOusers = list(set(allDiracUsers) - set(diracUsers)) result = self.csapi.describeUsers(nonVOusers) if not result['OK']: self.log.error('Could not retrieve CS User description') return result nonVOUserDict = result['Value'] # Process users defaultVOGroup = getVOOption(vo, "DefaultGroup", "%s_user" % vo) for dn in vomsUserDict: if dn in newDNs: # Find if the DN is already registered in the DIRAC CS diracName = '' for user in nonVOUserDict: if dn == nonVOUserDict[user]['DN']: diracName = user # We have a real new user if not diracName: nickName = '' result = vomsSrv.attGetUserNickname( dn, vomsUserDict[dn]['CA']) if result['OK']: nickName = result['Value'] if nickName: newDiracName = nickName else: newDiracName = getUserName(dn, vomsUserDict[dn]['mail']) ind = 1 trialName = newDiracName while newDiracName in allDiracUsers: # We have a user with the same name but with a different DN newDiracName = "%s_%d" % (trialName, ind) ind += 1 # We now have everything to add the new user userDict = { "DN": dn, "CA": vomsUserDict[dn]['CA'], "Email": vomsUserDict[dn]['mail'] } groupsWithRole = [] for role in vomsUserDict[dn]['Roles']: fullRole = "/%s/%s" % (vomsVOName, role) group = vomsDIRACMapping.get(fullRole) if group: groupsWithRole.extend(group) userDict['Groups'] = list( set(groupsWithRole + [defaultVOGroup])) self.__adminMsgs['Info'].append( "Adding new user %s: %s" % (newDiracName, str(userDict))) self.voChanged = True if self.autoAddUsers: self.log.info("Adding new user %s: %s" % (newDiracName, str(userDict))) result = self.csapi.modifyUser( newDiracName, userDict, createIfNonExistant=True) if not result['OK']: self.log.warn('Failed adding new user %s' % newDiracName) continue # We have an already existing user userDict = { "DN": dn, "CA": vomsUserDict[dn]['CA'], "Email": vomsUserDict[dn]['mail'] } nonVOGroups = nonVOUserDict.get(diracName, {}).get('Groups', []) existingGroups = diracUserDict.get(diracName, {}).get('Groups', []) groupsWithRole = [] for role in vomsUserDict[dn]['Roles']: fullRole = "/%s/%s" % (vomsVOName, role) group = vomsDIRACMapping.get(fullRole) if group: groupsWithRole.extend(group) keepGroups = nonVOGroups + groupsWithRole + [defaultVOGroup] for group in existingGroups: role = diracVOMSMapping[group] # Among already existing groups for the user keep those without a special VOMS Role # because this membership is done by hand in the CS if not "Role" in role: keepGroups.append(group) # Keep existing groups with no VOMS attribute if any if group in noVOMSGroups: keepGroups.append(group) userDict['Groups'] = keepGroups if self.autoModifyUsers: result = self.csapi.modifyUser(diracName, userDict) if result['OK'] and result['Value']: self.voChanged = True # Check if there are potentially obsoleted users oldUsers = set() for user in diracUserDict: dn = diracUserDict[user]['DN'] if not dn in vomsUserDict and not user in nonVOUserDict: for group in diracUserDict[user]['Groups']: if not group in noVOMSGroups: oldUsers.add(user) if oldUsers: self.voChanged = True self.__adminMsgs['Info'].append( 'The following users to be checked for deletion: %s' % str(oldUsers)) self.log.info( 'The following users to be checked for deletion: %s' % str(oldUsers)) return S_OK()
def __syncCSWithVOMS(self, vo): self.__adminMsgs = {'Errors': [], 'Info': []} resultDict = defaultdict(list) # Get DIRAC group vs VOMS Role Mappings result = getVOMSRoleGroupMapping(vo) if not result['OK']: return result vomsDIRACMapping = result['Value']['VOMSDIRAC'] diracVOMSMapping = result['Value']['DIRACVOMS'] noVOMSGroups = result['Value']['NoVOMS'] noSyncVOMSGroups = result['Value']['NoSyncVOMS'] vomsSrv = VOMSService(vo) # Get VOMS VO name result = vomsSrv.admGetVOName() if not result['OK']: self.log.error('Could not retrieve VOMS VO name', "for %s" % vo) return result vomsVOName = result['Value'].lstrip('/') self.log.verbose("VOMS VO Name for %s is %s" % (vo, vomsVOName)) # Get VOMS users result = vomsSrv.getUsers() if not result['OK']: self.log.error('Could not retrieve user information from VOMS', result['Message']) return result vomsUserDict = result['Value'] message = "There are %s user entries in VOMS for VO %s" % ( len(vomsUserDict), vomsVOName) self.__adminMsgs['Info'].append(message) self.log.info(message) # Get DIRAC users result = self.getVOUserData(vo) if not result['OK']: return result diracUserDict = result['Value'] self.__adminMsgs['Info'].append( "There are %s registered users in DIRAC for VO %s" % (len(diracUserDict), vo)) self.log.info("There are %s registered users in DIRAC VO %s" % (len(diracUserDict), vo)) # Find new and obsoleted user DNs existingDNs = [] obsoletedDNs = [] newDNs = [] for user in diracUserDict: dn = diracUserDict[user]['DN'] # We can have users with more than one DN registered dnList = fromChar(dn) existingDNs.extend(dnList) for dn in dnList: if dn not in vomsUserDict: obsoletedDNs.append(dn) for dn in vomsUserDict: if dn not in existingDNs: newDNs.append(dn) allDiracUsers = getAllUsers() nonVOUserDict = {} nonVOUsers = list(set(allDiracUsers) - set(diracUserDict.keys())) if nonVOUsers: result = self.csapi.describeUsers(nonVOUsers) if not result['OK']: self.log.error('Could not retrieve CS User description') return result nonVOUserDict = result['Value'] # Process users defaultVOGroup = getVOOption(vo, "DefaultGroup", "%s_user" % vo) newAddedUserDict = {} for dn in vomsUserDict: nickName = '' newDNForExistingUser = '' diracName = '' if dn in existingDNs: for user in diracUserDict: if dn == diracUserDict[user]['DN']: diracName = user if dn in newDNs: # Find if the DN is already registered in the DIRAC CS for user in nonVOUserDict: if dn == nonVOUserDict[user]['DN']: diracName = user # Check the nickName in the same VO to see if the user is already registered # with another DN result = vomsSrv.attGetUserNickname(dn, vomsUserDict[dn]['CA']) if result['OK']: nickName = result['Value'] if nickName in diracUserDict or nickName in newAddedUserDict: diracName = nickName # This is a flag for adding the new DN to an already existing user newDNForExistingUser = dn # We have a real new user if not diracName: if nickName: newDiracName = nickName else: newDiracName = getUserName(dn, vomsUserDict[dn]['mail'], vo) # If the chosen user name exists already, append a distinguishing suffix ind = 1 trialName = newDiracName while newDiracName in allDiracUsers: # We have a user with the same name but with a different DN newDiracName = "%s_%d" % (trialName, ind) ind += 1 # We now have everything to add the new user userDict = { "DN": dn, "CA": vomsUserDict[dn]['CA'], "Email": vomsUserDict[dn]['mail'] } groupsWithRole = [] for role in vomsUserDict[dn]['Roles']: fullRole = "/%s/%s" % (vomsVOName, role) groupList = vomsDIRACMapping.get(fullRole, []) for group in groupList: if group not in noSyncVOMSGroups: groupsWithRole.append(group) userDict['Groups'] = list( set(groupsWithRole + [defaultVOGroup])) message = "\n Added new user %s:\n" % newDiracName for key in userDict: message += " %s: %s\n" % (key, str(userDict[key])) self.__adminMsgs['Info'].append(message) self.voChanged = True if self.autoAddUsers: self.log.info("Adding new user %s: %s" % (newDiracName, str(userDict))) result = self.csapi.modifyUser( newDiracName, userDict, createIfNonExistant=True) if not result['OK']: self.log.warn('Failed adding new user %s' % newDiracName) resultDict['NewUsers'].append(newDiracName) newAddedUserDict[newDiracName] = userDict continue # We have an already existing user modified = False userDict = { "DN": dn, "CA": vomsUserDict[dn]['CA'], "Email": vomsUserDict[dn]['mail'] } if newDNForExistingUser: userDict['DN'] = ','.join([dn, diracUserDict[diracName]['DN']]) modified = True existingGroups = diracUserDict.get(diracName, {}).get('Groups', []) nonVOGroups = list( set(existingGroups) - set(diracVOMSMapping.keys())) groupsWithRole = [] for role in vomsUserDict[dn]['Roles']: fullRole = "/%s/%s" % (vomsVOName, role) groupList = vomsDIRACMapping.get(fullRole, []) for group in groupList: if group not in noSyncVOMSGroups: groupsWithRole.append(group) keepGroups = nonVOGroups + groupsWithRole + [defaultVOGroup] for group in existingGroups: if group in nonVOGroups: continue role = diracVOMSMapping.get(group, '') # Among already existing groups for the user keep those without a special VOMS Role # because this membership is done by hand in the CS if not "Role" in role: keepGroups.append(group) # Keep existing groups with no VOMS attribute if any if group in noVOMSGroups: keepGroups.append(group) # Keep groups for which syncronization with VOMS is forbidden if group in noSyncVOMSGroups: keepGroups.append(group) userDict['Groups'] = list(set(keepGroups)) # Merge together groups for the same user but different DNs if diracName in newAddedUserDict: otherGroups = newAddedUserDict[diracName].get('Groups', []) userDict['Groups'] = list(set(keepGroups + otherGroups)) modified = True # Check if something changed before asking CSAPI to modify if diracName in diracUserDict: message = "\n Modified user %s:\n" % diracName modMsg = '' for key in userDict: if key == "Groups": addedGroups = set(userDict[key]) - set( diracUserDict.get(diracName, {}).get(key, [])) removedGroups = set( diracUserDict.get(diracName, {}).get( key, [])) - set(userDict[key]) if addedGroups: modMsg += " Added to group(s) %s\n" % ','.join( addedGroups) if removedGroups: modMsg += " Removed from group(s) %s\n" % ','.join( removedGroups) else: oldValue = str( diracUserDict.get(diracName, {}).get(key, '')) if str(userDict[key]) != oldValue: modMsg += " %s: %s -> %s\n" % ( key, oldValue, str(userDict[key])) if modMsg: self.__adminMsgs['Info'].append(message + modMsg) modified = True if self.autoModifyUsers and modified: result = self.csapi.modifyUser(diracName, userDict) if result['OK'] and result['Value']: self.log.info("Modified user %s: %s" % (diracName, str(userDict))) self.voChanged = True resultDict['ModifiedUsers'].append(diracName) # Check if there are potentially obsoleted users oldUsers = set() for user in diracUserDict: dnSet = set(fromChar(diracUserDict[user]['DN'])) if not dnSet.intersection(set( vomsUserDict.keys())) and user not in nonVOUserDict: for group in diracUserDict[user]['Groups']: if group not in noVOMSGroups: oldUsers.add(user) if oldUsers: self.voChanged = True if self.autoDeleteUsers: self.log.info('The following users will be deleted: %s' % str(oldUsers)) result = self.csapi.deleteUsers(oldUsers) if result['OK']: self.__adminMsgs['Info'].append( 'The following users are deleted from CS:\n %s\n' % str(oldUsers)) resultDict['DeletedUsers'] = oldUsers else: self.__adminMsgs['Errors'].append( 'Error in deleting users from CS:\n %s' % str(oldUsers)) self.log.error('Error while user deletion from CS', result) else: self.__adminMsgs['Info'].append( 'The following users to be checked for deletion:\n %s' % str(oldUsers)) self.log.info( 'The following users to be checked for deletion: %s' % str(oldUsers)) return S_OK(resultDict)
class UsersAndGroups(AgentModule): def initialize(self): self.am_setOption("PollingTime", 3600 * 6) # Every 6 hours self.vomsSrv = VOMSService() self.proxyLocation = os.path.join(self.am_getWorkDirectory(), ".volatileId") self.__adminMsgs = {} # print self.getLFCRegisteredDNs() return S_OK() def __generateProxy(self): self.log.info("Generating proxy...") certLoc = Locations.getHostCertificateAndKeyLocation() if not certLoc: self.log.error("Can not find certificate!") return False chain = X509Chain.X509Chain() result = chain.loadChainFromFile(certLoc[0]) if not result['OK']: self.log.error("Can not load certificate file", "%s : %s" % (certLoc[0], result['Message'])) return False result = chain.loadKeyFromFile(certLoc[1]) if not result['OK']: self.log.error("Can not load key file", "%s : %s" % (certLoc[1], result['Message'])) return False result = chain.generateProxyToFile(self.proxyLocation, 3600) if not result['OK']: self.log.error("Could not generate proxy file", result['Message']) return False self.log.info("Proxy generated") return True def getLFCRegisteredDNs(self): #Request a proxy if gConfig.useServerCertificate(): if not self.__generateProxy(): return False #Execute the call cmdEnv = dict(os.environ) cmdEnv['LFC_HOST'] = 'lfc-egee.in2p3.fr' if os.path.isfile(self.proxyLocation): cmdEnv['X509_USER_PROXY'] = self.proxyLocation lfcDNs = [] try: retlfc = Subprocess.systemCall(30, ('lfc-listusrmap', ), env=cmdEnv) if not retlfc['OK']: self.log.fatal('Can not get LFC User List', retlfc['Message']) return retlfc if retlfc['Value'][0]: self.log.fatal('Can not get LFC User List', retlfc['Value'][2]) return S_ERROR("lfc-listusrmap failed") else: for item in List.fromChar(retlfc['Value'][1], '\n'): dn = item.split(' ', 1)[1] lfcDNs.append(dn) return S_OK(lfcDNs) finally: if os.path.isfile(self.proxyLocation): self.log.info("Destroying proxy...") os.unlink(self.proxyLocation) def checkLFCRegisteredUsers(self, usersData): self.log.info("Checking LFC registered users") usersToBeRegistered = {} result = self.getLFCRegisteredDNs() if not result['OK']: self.log.error("Could not get a list of registered DNs from LFC", result['Message']) return result lfcDNs = result['Value'] for user in usersData: for userDN in usersData[user]['DN']: if userDN not in lfcDNs: self.log.info( 'DN "%s" need to be registered in LFC for user %s' % (userDN, user)) if user not in usersToBeRegistered: usersToBeRegistered[user] = [] usersToBeRegistered[user].append(userDN) address = self.am_getOption('MailTo', '*****@*****.**') fromAddress = self.am_getOption('mailFrom', '*****@*****.**') if usersToBeRegistered: subject = 'New LFC Users found' self.log.info(subject, ", ".join(usersToBeRegistered)) body = 'Command to add new entries into LFC: \n' body += 'login to volhcbXX and run : \n' body += 'source /afs/cern.ch/lhcb/software/releases/LBSCRIPTS/prod/InstallArea/scripts/LbLogin.csh \n\n' for lfcuser in usersToBeRegistered: for lfc_dn in usersToBeRegistered[lfcuser]: print lfc_dn body += 'add_DN_LFC --userDN="' + lfc_dn.strip( ) + '" --nickname=' + lfcuser + '\n' NotificationClient().sendMail(address, 'UsersAndGroupsAgent: %s' % subject, body, fromAddress) return S_OK() def execute(self): result = self.__syncCSWithVOMS() mailMsg = "" if self.__adminMsgs['Errors']: mailMsg += "\nErrors list:\n %s" % "\n ".join( self.__adminMsgs['Errors']) if self.__adminMsgs['Info']: mailMsg += "\nRun result:\n %s" % "\n ".join( self.__adminMsgs['Info']) NotificationClient().sendMail( self.am_getOption('MailTo', '*****@*****.**'), "UsersAndGroupsAgent run log", mailMsg, self.am_getOption('mailFrom', '*****@*****.**')) return result def __syncCSWithVOMS(self): self.__adminMsgs = {'Errors': [], 'Info': []} #Get DIRAC VOMS Mapping self.log.info("Getting DIRAC VOMS mapping") mappingSection = '/Registry/VOMS/Mapping' ret = gConfig.getOptionsDict(mappingSection) if not ret['OK']: self.log.fatal('No VOMS to DIRAC Group Mapping Available') return ret vomsMapping = ret['Value'] self.log.info("There are %s registered voms mappings in DIRAC" % len(vomsMapping)) #Get VOMS VO name self.log.info("Getting VOMS VO name") result = self.vomsSrv.admGetVOName() if not ret['OK']: self.log.fatal('Could not retrieve VOMS VO name') voNameInVOMS = result['Value'] self.log.info("VOMS VO Name is %s" % voNameInVOMS) #Get VOMS roles self.log.info("Getting the list of registered roles in VOMS") result = self.vomsSrv.admListRoles() if not ret['OK']: self.log.fatal('Could not retrieve registered roles in VOMS') rolesInVOMS = result['Value'] self.log.info("There are %s registered roles in VOMS" % len(rolesInVOMS)) print rolesInVOMS rolesInVOMS.append('') #Map VOMS roles vomsRoles = {} for role in rolesInVOMS: if role: role = "%s/%s" % (voNameInVOMS, role) else: role = voNameInVOMS groupsForRole = [] for group in vomsMapping: if vomsMapping[group] == role: groupsForRole.append(group) if groupsForRole: vomsRoles[role] = {'Groups': groupsForRole, 'Users': []} self.log.info("DIRAC valid VOMS roles are:\n\t", "\n\t ".join(vomsRoles.keys())) #Get DIRAC users self.log.info("Getting the list of registered users in DIRAC") csapi = CSAPI() ret = csapi.listUsers() if not ret['OK']: self.log.fatal('Could not retrieve current list of Users') return ret currentUsers = ret['Value'] ret = csapi.describeUsers(currentUsers) if not ret['OK']: self.log.fatal('Could not retrieve current User description') return ret currentUsers = ret['Value'] self.__adminMsgs['Info'].append( "There are %s registered users in DIRAC" % len(currentUsers)) self.log.info("There are %s registered users in DIRAC" % len(currentUsers)) #Get VOMS user entries self.log.info("Getting the list of registered user entries in VOMS") result = self.vomsSrv.admListMembers() if not ret['OK']: self.log.fatal( 'Could not retrieve registered user entries in VOMS') usersInVOMS = result['Value'] self.__adminMsgs['Info'].append( "There are %s registered user entries in VOMS" % len(usersInVOMS)) self.log.info("There are %s registered user entries in VOMS" % len(usersInVOMS)) #Consolidate users by nickname usersData = {} newUserNames = [] knownUserNames = [] obsoleteUserNames = [] self.log.info("Retrieving usernames...") usersInVOMS.sort() for iUPos in range(len(usersInVOMS)): userName = '' user = usersInVOMS[iUPos] for oldUser in currentUsers: if user['DN'].strip() in List.fromChar( currentUsers[oldUser]['DN']): userName = oldUser if not userName: result = self.vomsSrv.attGetUserNickname( user['DN'], user['CA']) if result['OK']: userName = result['Value'] else: self.__adminMsgs['Errors'].append( "Could not retrieve nickname for DN %s" % user['DN']) self.log.error("Could not get nickname for DN", user['DN']) userName = user['mail'][:user['mail'].find('@')] if not userName: self.log.error("Empty nickname for DN", user['DN']) self.__adminMsgs['Errors'].append("Empty nickname for DN %s" % user['DN']) continue self.log.info( " (%02d%%) Found username %s : %s " % ((iUPos * 100 / len(usersInVOMS)), userName, user['DN'])) if userName not in usersData: usersData[userName] = { 'DN': [], 'CA': [], 'Email': [], 'Groups': ['user'] } for key in ('DN', 'CA', 'mail'): value = user[key] if value: if key == "mail": List.appendUnique(usersData[userName]['Email'], value) else: usersData[userName][key].append(value.strip()) if userName not in currentUsers: List.appendUnique(newUserNames, userName) else: List.appendUnique(knownUserNames, userName) self.log.info("Finished retrieving usernames") if newUserNames: self.log.info("There are %s new users" % len(newUserNames)) else: self.log.info("There are no new users") #Get the list of users for each group result = csapi.listGroups() if not result['OK']: self.log.error("Could not get the list of groups in DIRAC", result['Message']) return result staticGroups = result['Value'] vomsGroups = [] self.log.info("Mapping users in VOMS to groups") for vomsRole in vomsRoles: self.log.info(" Getting users for role %s" % vomsRole) groupsForRole = vomsRoles[vomsRole]['Groups'] vomsMap = vomsRole.split("Role=") for g in groupsForRole: if g in staticGroups: staticGroups.pop(staticGroups.index(g)) else: vomsGroups.append(g) if len(vomsMap) == 1: # no Role users = usersInVOMS else: vomsGroup = "Role=".join(vomsMap[:-1]) if vomsGroup[-1] == "/": vomsGroup = vomsGroup[:-1] vomsRole = "Role=%s" % vomsMap[-1] result = self.vomsSrv.admListUsersWithRole(vomsGroup, vomsRole) if not result['OK']: errorMsg = "Could not get list of users for VOMS %s" % ( vomsMapping[group]) self.__adminMsgs['Errors'].append(errorMsg) self.log.error(errorMsg, result['Message']) return result users = result['Value'] numUsersInGroup = 0 for vomsUser in users: for userName in usersData: if vomsUser['DN'] in usersData[userName]['DN']: numUsersInGroup += 1 usersData[userName]['Groups'].extend(groupsForRole) infoMsg = "There are %s users in group(s) %s for VOMS Role %s" % ( numUsersInGroup, ",".join(groupsForRole), vomsRole) self.__adminMsgs['Info'].append(infoMsg) self.log.info(" %s" % infoMsg) self.log.info("Checking static groups") staticUsers = [] for group in staticGroups: self.log.info(" Checking static group %s" % group) numUsersInGroup = 0 result = csapi.listUsers(group) if not result['OK']: self.log.error( "Could not get the list of users in DIRAC group %s" % group, result['Message']) return result for userName in result['Value']: if userName in usersData: numUsersInGroup += 1 usersData[userName]['Groups'].append(group) else: if group not in vomsGroups and userName not in staticUsers: staticUsers.append(userName) infoMsg = "There are %s users in group %s" % (numUsersInGroup, group) self.__adminMsgs['Info'].append(infoMsg) self.log.info(" %s" % infoMsg) if staticUsers: infoMsg = "There are %s static users: %s" % ( len(staticUsers), ', '.join(staticUsers)) self.__adminMsgs['Info'].append(infoMsg) self.log.info("%s" % infoMsg) for user in currentUsers: if user not in usersData and user not in staticUsers: self.log.info('User %s is no longer valid' % user) obsoleteUserNames.append(user) #Do the CS Sync self.log.info("Updating CS...") ret = csapi.downloadCSData() if not ret['OK']: self.log.fatal('Can not update from CS', ret['Message']) return ret usersWithMoreThanOneDN = {} for user in usersData: csUserData = dict(usersData[user]) if len(csUserData['DN']) > 1: usersWithMoreThanOneDN[user] = csUserData['DN'] result = csapi.describeUsers([user]) if result['OK']: if result['Value']: prevUser = result['Value'][user] prevDNs = List.fromChar(prevUser['DN']) newDNs = csUserData['DN'] for DN in newDNs: if DN not in prevDNs: self.__adminMsgs['Info'].append( "User %s has new DN %s" % (user, DN)) for DN in prevDNs: if DN not in newDNs: self.__adminMsgs['Info'].append( "User %s has lost a DN %s" % (user, DN)) else: newDNs = csUserData['DN'] for DN in newDNs: self.__adminMsgs['Info'].append( "New user %s has new DN %s" % (user, DN)) for k in ('DN', 'CA', 'Email'): csUserData[k] = ", ".join(csUserData[k]) result = csapi.modifyUser(user, csUserData, createIfNonExistant=True) if not result['OK']: self.__adminMsgs['Error'].append("Cannot modify user %s: %s" % (user, result['Message'])) self.log.error("Cannot modify user", user) if usersWithMoreThanOneDN: self.__adminMsgs['Info'].append("\nUsers with more than one DN:") for uwmtod in sorted(usersWithMoreThanOneDN): self.__adminMsgs['Info'].append(" %s" % uwmtod) self.__adminMsgs['Info'].append(" + DN list:") for DN in usersWithMoreThanOneDN[uwmtod]: self.__adminMsgs['Info'].append(" - %s" % DN) if obsoleteUserNames: self.__adminMsgs['Info'].append("\nObsolete users:") address = self.am_getOption('MailTo', '*****@*****.**') fromAddress = self.am_getOption('mailFrom', '*****@*****.**') subject = 'Obsolete LFC Users found' body = 'Delete entries into LFC: \n' for obsoleteUser in obsoleteUserNames: self.log.info(subject, ", ".join(obsoleteUserNames)) body += 'for ' + obsoleteUser + '\n' self.__adminMsgs['Info'].append(" %s" % obsoleteUser) self.log.info("Deleting %s users" % len(obsoleteUserNames)) NotificationClient().sendMail(address, 'UsersAndGroupsAgent: %s' % subject, body, fromAddress) csapi.deleteUsers(obsoleteUserNames) if newUserNames: self.__adminMsgs['Info'].append("\nNew users:") for newUser in newUserNames: self.__adminMsgs['Info'].append(" %s" % newUser) self.__adminMsgs['Info'].append(" + DN list:") for DN in usersData[newUser]['DN']: self.__adminMsgs['Info'].append(" - %s" % DN) self.__adminMsgs['Info'].append(" + EMail: %s" % usersData[newUser]['Email']) result = csapi.commitChanges() if not result['OK']: self.log.error("Could not commit configuration changes", result['Message']) return result self.log.info("Configuration committed") #LFC Check if self.am_getOption("LFCCheckEnabled", True): result = self.checkLFCRegisteredUsers(usersData) if not result['OK']: return result return S_OK()
def syncCSWithVOMS(self): """Performs the synchronization of the DIRAC registry with the VOMS data. The resulting CSAPI object containing modifications is returned as part of the output dictionary. Those changes can be applied by the caller depending on the mode (dry or a real run) :return: S_OK with a dictionary containing the results of the synchronization operation """ resultDict = defaultdict(list) # Get DIRAC group vs VOMS Role Mappings result = getVOMSRoleGroupMapping(self.vo) if not result["OK"]: return result vomsDIRACMapping = result["Value"]["VOMSDIRAC"] diracVOMSMapping = result["Value"]["DIRACVOMS"] noVOMSGroups = result["Value"]["NoVOMS"] noSyncVOMSGroups = result["Value"]["NoSyncVOMS"] vomsSrv = VOMSService(self.vo) # Get VOMS users result = vomsSrv.getUsers() if not result["OK"]: self.log.error("Could not retrieve user information from VOMS", result["Message"]) return result self.vomsUserDict = result["Value"] message = "There are %s user entries in VOMS for VO %s" % (len(self.vomsUserDict), self.vomsVOName) self.adminMsgs["Info"].append(message) self.log.info("VOMS user entries", message) self.log.debug(self.vomsUserDict) # Get DIRAC users result = self.getVOUserData(self.vo) if not result["OK"]: return result diracUserDict = result["Value"] self.adminMsgs["Info"].append( "There are %s registered users in DIRAC for VO %s" % (len(diracUserDict), self.vo) ) self.log.info( "Users already registered", ": there are %s registered users in DIRAC VO %s" % (len(diracUserDict), self.vo) ) # Find new and obsoleted user DNs existingDNs = [] obsoletedDNs = [] newDNs = [] for user in diracUserDict: dn = diracUserDict[user]["DN"] # We can have users with more than one DN registered dnList = fromChar(dn) existingDNs.extend(dnList) for dn in dnList: if dn not in self.vomsUserDict: obsoletedDNs.append(dn) for dn in self.vomsUserDict: if dn not in existingDNs: newDNs.append(dn) allDiracUsers = getAllUsers() nonVOUserDict = {} nonVOUsers = list(set(allDiracUsers) - set(diracUserDict)) if nonVOUsers: result = self.csapi.describeUsers(nonVOUsers) if not result["OK"]: self.log.error("Could not retrieve CS User description") return result nonVOUserDict = result["Value"] # Process users defaultVOGroup = getVOOption(self.vo, "DefaultGroup", "%s_user" % self.vo) # If a user is (previously put by hand) in a "QuarantineGroup", # then the default group will be ignored. # So, this option is only considered for the case of existing users. quarantineVOGroup = getVOOption(self.vo, "QuarantineGroup") newAddedUserDict = {} for dn in self.vomsUserDict: newDNForExistingUser = "" diracName = "" if dn in existingDNs: for user in diracUserDict: if dn in fromChar(diracUserDict[user]["DN"]): diracName = user break if dn in newDNs: # Find if the DN is already registered in the DIRAC CS for user in nonVOUserDict: if dn in fromChar(nonVOUserDict[user]["DN"]): diracName = user diracUserDict[diracName] = nonVOUserDict[user] break # Check the nickName in the same VO to see if the user is already registered # with another DN nickName = self.vomsUserDict[dn].get("nickname") if nickName in diracUserDict or nickName in newAddedUserDict: diracName = nickName # This is a flag for adding the new DN to an already existing user newDNForExistingUser = dn # We have a real new user if not diracName: if nickName: newDiracName = nickName else: newDiracName = self.getUserName(dn) # Do not consider users with Suspended status in VOMS if self.vomsUserDict[dn]["suspended"] or self.vomsUserDict[dn]["certSuspended"]: resultDict["SuspendedUsers"].append(newDiracName) continue # If the chosen user name exists already, append a distinguishing suffix ind = 1 trialName = newDiracName while newDiracName in allDiracUsers: # We have a user with the same name but with a different DN newDiracName = "%s_%d" % (trialName, ind) ind += 1 # We now have everything to add the new user userDict = {"DN": dn, "CA": self.vomsUserDict[dn]["CA"], "Email": self.vomsUserDict[dn]["mail"]} groupsWithRole = [] for role in self.vomsUserDict[dn]["Roles"]: groupList = vomsDIRACMapping.get(role, []) for group in groupList: if group not in noSyncVOMSGroups: groupsWithRole.append(group) userDict["Groups"] = list(set(groupsWithRole + [defaultVOGroup])) # Run the sync plugins for extra info and/or validations if self.syncPlugin: try: self.syncPlugin.verifyAndUpdateUserInfo(newDiracName, userDict) except ValueError as e: self.log.error("Error validating new user", "nickname %s\n error %s" % (newDiracName, e)) self.adminMsgs["Errors"].append( "Error validating new user %s: %s\n %s" % (newDiracName, userDict, e) ) continue message = "\n Added new user %s:\n" % newDiracName for key in userDict: message += " %s: %s\n" % (key, str(userDict[key])) self.adminMsgs["Info"].append(message) self.voChanged = True if self.autoAddUsers: self.log.info("Adding new user %s: %s" % (newDiracName, str(userDict))) result = self.csapi.modifyUser(newDiracName, userDict, createIfNonExistant=True) if not result["OK"]: self.log.warn("Failed adding new user %s" % newDiracName) resultDict["NewUsers"].append(newDiracName) newAddedUserDict[newDiracName] = userDict continue # We have an already existing user modified = False suspendedInVOMS = self.vomsUserDict[dn]["suspended"] or self.vomsUserDict[dn]["certSuspended"] suspendedVOList = getUserOption(diracName, "Suspended", []) knownEmail = getUserOption(diracName, "Email", None) userDict = { "DN": diracUserDict[diracName]["DN"], "CA": diracUserDict[diracName]["CA"], "Email": self.vomsUserDict[dn].get("mail", self.vomsUserDict[dn].get("emailAddress")) or knownEmail, } # Set Suspended status for the user for this particular VO if suspendedInVOMS and self.vo not in suspendedVOList: suspendedVOList.append(self.vo) userDict["Suspended"] = ",".join(suspendedVOList) modified = True # Remove the lifted Suspended status if not suspendedInVOMS and self.vo in suspendedVOList and self.autoLiftSuspendedStatus: newList = [] for vo in suspendedVOList: if vo != self.vo: newList.append(vo) if not newList: newList = ["None"] userDict["Suspended"] = ",".join(newList) modified = True if newDNForExistingUser: userDict["DN"] = ",".join([dn, diracUserDict.get(diracName, newAddedUserDict.get(diracName))["DN"]]) userDict["CA"] = ",".join( [self.vomsUserDict[dn]["CA"], diracUserDict.get(diracName, newAddedUserDict.get(diracName))["CA"]] ) modified = True existingGroups = diracUserDict.get(diracName, {}).get("Groups", []) nonVOGroups = list(set(existingGroups) - set(diracVOMSMapping)) groupsWithRole = [] for role in self.vomsUserDict[dn]["Roles"]: groupList = vomsDIRACMapping.get(role, []) for group in groupList: if group not in noSyncVOMSGroups: groupsWithRole.append(group) keepGroups = nonVOGroups + groupsWithRole if not quarantineVOGroup or quarantineVOGroup not in existingGroups: keepGroups += [defaultVOGroup] if quarantineVOGroup and quarantineVOGroup in existingGroups: keepGroups = [quarantineVOGroup] for group in existingGroups: if group in nonVOGroups: continue role = diracVOMSMapping.get(group, "") # Among already existing groups for the user keep those without a special VOMS Role # because this membership is done by hand in the CS if "Role" not in role: keepGroups.append(group) # Keep existing groups with no VOMS attribute if any if group in noVOMSGroups: keepGroups.append(group) # Keep groups for which syncronization with VOMS is forbidden if group in noSyncVOMSGroups: keepGroups.append(group) userDict["Groups"] = list(set(keepGroups)) # Merge together groups for the same user but different DNs if diracName in newAddedUserDict: otherGroups = newAddedUserDict[diracName].get("Groups", []) userDict["Groups"] = list(set(keepGroups + otherGroups)) modified = True if not existingGroups and diracName in allDiracUsers: groups = getGroupsForUser(diracName) if groups["OK"]: self.log.info("Found groups for user %s %s" % (diracName, groups["Value"])) userDict["Groups"] = list(set(groups["Value"] + keepGroups)) addedGroups = list(set(userDict["Groups"]) - set(groups["Value"])) modified = True message = "\n Modified user %s:\n" % diracName message += " Added to group(s) %s\n" % ",".join(addedGroups) self.adminMsgs["Info"].append(message) # Check if something changed before asking CSAPI to modify if diracName in diracUserDict: message = "\n Modified user %s:\n" % diracName modMsg = "" for key in userDict: if key == "Groups": addedGroups = set(userDict[key]) - set(diracUserDict.get(diracName, {}).get(key, [])) removedGroups = set(diracUserDict.get(diracName, {}).get(key, [])) - set(userDict[key]) if addedGroups: modMsg += " Added to group(s) %s\n" % ",".join(addedGroups) if removedGroups: modMsg += " Removed from group(s) %s\n" % ",".join(removedGroups) elif key == "Suspended": if userDict["Suspended"] == "None": modMsg += " Suspended status removed\n" else: modMsg += " User Suspended in VOs: %s\n" % userDict["Suspended"] else: oldValue = str(diracUserDict.get(diracName, {}).get(key, "")) if str(userDict[key]) != oldValue: modMsg += " %s: %s -> %s\n" % (key, oldValue, str(userDict[key])) if modMsg: self.adminMsgs["Info"].append(message + modMsg) modified = True if self.autoModifyUsers and modified: result = self.csapi.modifyUser(diracName, userDict) if result["OK"] and result["Value"]: self.log.info("Modified user %s: %s" % (diracName, str(userDict))) self.voChanged = True resultDict["ModifiedUsers"].append(diracName) # Check if there are potentially obsoleted users oldUsers = set() for user in diracUserDict: dnSet = set(fromChar(diracUserDict[user]["DN"])) if not dnSet.intersection(set(self.vomsUserDict)) and user not in nonVOUserDict: existingGroups = diracUserDict.get(user, {}).get("Groups", []) nonVOGroups = list(set(existingGroups) - set(diracVOMSMapping)) removedGroups = list(set(existingGroups) - set(nonVOGroups)) if removedGroups: self.log.info("Checking user for deletion", "%s: %s" % (user, existingGroups)) self.log.info("User has groups in other VOs", "%s: %s" % (user, nonVOGroups)) userDict = diracUserDict[user] userDict["Groups"] = nonVOGroups if self.autoModifyUsers: result = self.csapi.modifyUser(user, userDict) if result["OK"] and result["Value"]: self.log.info("Modified user %s: %s" % (user, str(userDict))) self.voChanged = True message = "\n Modified user %s:\n" % user modMsg = " Removed from group(s) %s\n" % ",".join(removedGroups) self.adminMsgs["Info"].append(message + modMsg) resultDict["ModifiedUsers"].append(user) continue if not any(group in noVOMSGroups for group in existingGroups): oldUsers.add(user) # Check for obsoleted DNs for user in diracUserDict: dnSet = set(fromChar(diracUserDict[user]["DN"])) for dn in dnSet: if dn in obsoletedDNs and user not in oldUsers: existingGroups = diracUserDict.get(user, {}).get("Groups", []) nonVOGroups = list(set(existingGroups) - set(diracVOMSMapping)) if nonVOGroups: self.log.verbose("User has groups in other VOs", "%s: %s" % (user, nonVOGroups)) continue self.log.verbose("Modified user %s: dropped DN %s" % (user, dn)) if self.autoModifyUsers: userDict = diracUserDict[user] modDNSet = dnSet - set([dn]) if modDNSet: userDict["DN"] = ",".join(modDNSet) result = self.csapi.modifyUser(user, userDict) if result["OK"] and result["Value"]: self.log.info("Modified user %s: dropped DN %s" % (user, dn)) self.adminMsgs["Info"].append("Modified user %s: dropped DN %s" % (user, dn)) self.voChanged = True resultDict["ModifiedUsers"].append(diracName) else: oldUsers.add(user) if oldUsers: self.voChanged = True if self.autoDeleteUsers: self.log.info("The following users will be deleted: %s" % str(oldUsers)) result = self.csapi.deleteUsers(oldUsers) if result["OK"]: self.adminMsgs["Info"].append("The following users are deleted from CS:\n %s\n" % str(oldUsers)) resultDict["DeletedUsers"] = oldUsers else: self.adminMsgs["Errors"].append("Error in deleting users from CS:\n %s" % str(oldUsers)) self.log.error("Error while user deletion from CS", result) else: self.adminMsgs["Info"].append( "The following users to be checked for deletion:\n\t%s" % "\n\t".join(sorted(oldUsers)) ) self.log.info("The following users to be checked for deletion:", "\n\t".join(sorted(oldUsers))) resultDict["CSAPI"] = self.csapi resultDict["AdminMessages"] = self.adminMsgs resultDict["VOChanged"] = self.voChanged return S_OK(resultDict)
def syncCSWithVOMS(self): """ Performs the synchronization of the DIRAC registry with the VOMS data. The resulting CSAPI object containing modifications is returned as part of the output dictionary. Those changes can be applied by the caller depending on the mode (dry or a real run) :return: S_OK with a dictionary containing the results of the synchronization operation """ resultDict = defaultdict(list) # Get DIRAC group vs VOMS Role Mappings result = getVOMSRoleGroupMapping(self.vo) if not result['OK']: return result vomsDIRACMapping = result['Value']['VOMSDIRAC'] diracVOMSMapping = result['Value']['DIRACVOMS'] noVOMSGroups = result['Value']['NoVOMS'] noSyncVOMSGroups = result['Value']['NoSyncVOMS'] vomsSrv = VOMSService(self.vo) # Get VOMS users result = vomsSrv.getUsers() if not result['OK']: self.log.error('Could not retrieve user information from VOMS', result['Message']) return result self.vomsUserDict = result['Value'] message = "There are %s user entries in VOMS for VO %s" % (len(self.vomsUserDict), self.vomsVOName) self.adminMsgs['Info'].append(message) self.log.info(message) # Get DIRAC users result = self.getVOUserData(self.vo) if not result['OK']: return result diracUserDict = result['Value'] self.adminMsgs['Info'].append("There are %s registered users in DIRAC for VO %s" % (len(diracUserDict), self.vo)) self.log.info("There are %s registered users in DIRAC VO %s" % (len(diracUserDict), self.vo)) # Find new and obsoleted user DNs existingDNs = [] obsoletedDNs = [] newDNs = [] for user in diracUserDict: dn = diracUserDict[user]['DN'] # We can have users with more than one DN registered dnList = fromChar(dn) existingDNs.extend(dnList) for dn in dnList: if dn not in self.vomsUserDict: obsoletedDNs.append(dn) for dn in self.vomsUserDict: if dn not in existingDNs: newDNs.append(dn) allDiracUsers = getAllUsers() nonVOUserDict = {} nonVOUsers = list(set(allDiracUsers) - set(diracUserDict)) if nonVOUsers: result = self.csapi.describeUsers(nonVOUsers) if not result['OK']: self.log.error('Could not retrieve CS User description') return result nonVOUserDict = result['Value'] # Process users defaultVOGroup = getVOOption(self.vo, "DefaultGroup", "%s_user" % self.vo) # If a user is (previously put by hand) in a "QuarantineGroup", # then the default group will be ignored. # So, this option is only considered for the case of existing users. quarantineVOGroup = getVOOption(self.vo, "QuarantineGroup") newAddedUserDict = {} for dn in self.vomsUserDict: newDNForExistingUser = '' diracName = '' if dn in existingDNs: for user in diracUserDict: if dn == diracUserDict[user]['DN']: diracName = user if dn in newDNs: # Find if the DN is already registered in the DIRAC CS for user in nonVOUserDict: if dn == nonVOUserDict[user]['DN']: diracName = user # Check the nickName in the same VO to see if the user is already registered # with another DN nickName = self.vomsUserDict[dn].get('nickname') if nickName in diracUserDict or nickName in newAddedUserDict: diracName = nickName # This is a flag for adding the new DN to an already existing user newDNForExistingUser = dn # We have a real new user if not diracName: if nickName: newDiracName = nickName else: newDiracName = self.getUserName(dn) # Do not consider users with Suspended status in VOMS if self.vomsUserDict[dn]['suspended'] or self.vomsUserDict[dn]['certSuspended']: resultDict["SuspendedUsers"].append(newDiracName) continue # If the chosen user name exists already, append a distinguishing suffix ind = 1 trialName = newDiracName while newDiracName in allDiracUsers: # We have a user with the same name but with a different DN newDiracName = "%s_%d" % (trialName, ind) ind += 1 # We now have everything to add the new user userDict = {"DN": dn, "CA": self.vomsUserDict[dn]['CA'], "Email": self.vomsUserDict[dn]['mail']} groupsWithRole = [] for role in self.vomsUserDict[dn]['Roles']: groupList = vomsDIRACMapping.get(role, []) for group in groupList: if group not in noSyncVOMSGroups: groupsWithRole.append(group) userDict['Groups'] = list(set(groupsWithRole + [defaultVOGroup])) message = "\n Added new user %s:\n" % newDiracName for key in userDict: message += " %s: %s\n" % (key, str(userDict[key])) self.adminMsgs['Info'].append(message) self.voChanged = True if self.autoAddUsers: self.log.info("Adding new user %s: %s" % (newDiracName, str(userDict))) result = self.csapi.modifyUser(newDiracName, userDict, createIfNonExistant=True) if not result['OK']: self.log.warn('Failed adding new user %s' % newDiracName) resultDict['NewUsers'].append(newDiracName) newAddedUserDict[newDiracName] = userDict continue # We have an already existing user modified = False userDict = {"DN": dn, "CA": self.vomsUserDict[dn]['CA'], "Email": self.vomsUserDict[dn]['mail']} if newDNForExistingUser: userDict['DN'] = ','.join([dn, diracUserDict.get(diracName, newAddedUserDict.get(diracName))['DN']]) modified = True existingGroups = diracUserDict.get(diracName, {}).get('Groups', []) nonVOGroups = list(set(existingGroups) - set(diracVOMSMapping)) groupsWithRole = [] for role in self.vomsUserDict[dn]['Roles']: groupList = vomsDIRACMapping.get(role, []) for group in groupList: if group not in noSyncVOMSGroups: groupsWithRole.append(group) keepGroups = nonVOGroups + groupsWithRole if not quarantineVOGroup or quarantineVOGroup not in existingGroups: keepGroups += [defaultVOGroup] for group in existingGroups: if group in nonVOGroups: continue role = diracVOMSMapping.get(group, '') # Among already existing groups for the user keep those without a special VOMS Role # because this membership is done by hand in the CS if "Role" not in role: keepGroups.append(group) # Keep existing groups with no VOMS attribute if any if group in noVOMSGroups: keepGroups.append(group) # Keep groups for which syncronization with VOMS is forbidden if group in noSyncVOMSGroups: keepGroups.append(group) userDict['Groups'] = list(set(keepGroups)) # Merge together groups for the same user but different DNs if diracName in newAddedUserDict: otherGroups = newAddedUserDict[diracName].get('Groups', []) userDict['Groups'] = list(set(keepGroups + otherGroups)) modified = True # Check if something changed before asking CSAPI to modify if diracName in diracUserDict: message = "\n Modified user %s:\n" % diracName modMsg = '' for key in userDict: if key == "Groups": addedGroups = set(userDict[key]) - set(diracUserDict.get(diracName, {}).get(key, [])) removedGroups = set(diracUserDict.get(diracName, {}).get(key, [])) - set(userDict[key]) if addedGroups: modMsg += " Added to group(s) %s\n" % ','.join(addedGroups) if removedGroups: modMsg += " Removed from group(s) %s\n" % ','.join(removedGroups) else: oldValue = str(diracUserDict.get(diracName, {}).get(key, '')) if str(userDict[key]) != oldValue: modMsg += " %s: %s -> %s\n" % (key, oldValue, str(userDict[key])) if modMsg: self.adminMsgs['Info'].append(message + modMsg) modified = True if self.autoModifyUsers and modified: result = self.csapi.modifyUser(diracName, userDict) if result['OK'] and result['Value']: self.log.info("Modified user %s: %s" % (diracName, str(userDict))) self.voChanged = True resultDict['ModifiedUsers'].append(diracName) # Check if there are potentially obsoleted users oldUsers = set() for user in diracUserDict: dnSet = set(fromChar(diracUserDict[user]['DN'])) if not dnSet.intersection(set(self.vomsUserDict)) and user not in nonVOUserDict: for group in diracUserDict[user]['Groups']: if group not in noVOMSGroups: oldUsers.add(user) # Check for obsoleted DNs for user in diracUserDict: dnSet = set(fromChar(diracUserDict[user]['DN'])) for dn in dnSet: if dn in obsoletedDNs and user not in oldUsers: self.log.verbose("Modified user %s: dropped DN %s" % (user, dn)) if self.autoModifyUsers: userDict = diracUserDict[user] modDNSet = dnSet - set([dn]) if modDNSet: userDict['DN'] = ','.join(modDNSet) result = self.csapi.modifyUser(user, userDict) if result['OK'] and result['Value']: self.log.info("Modified user %s: dropped DN %s" % (user, dn)) self.adminMsgs['Info'].append("Modified user %s: dropped DN %s" % (user, dn)) self.voChanged = True resultDict['ModifiedUsers'].append(diracName) else: oldUsers.add(user) if oldUsers: self.voChanged = True if self.autoDeleteUsers: self.log.info('The following users will be deleted: %s' % str(oldUsers)) result = self.csapi.deleteUsers(oldUsers) if result['OK']: self.adminMsgs['Info'].append('The following users are deleted from CS:\n %s\n' % str(oldUsers)) resultDict['DeletedUsers'] = oldUsers else: self.adminMsgs['Errors'].append('Error in deleting users from CS:\n %s' % str(oldUsers)) self.log.error('Error while user deletion from CS', result) else: self.adminMsgs['Info'].append('The following users to be checked for deletion:\n %s' % str(oldUsers)) self.log.info('The following users to be checked for deletion: %s' % str(oldUsers)) resultDict['CSAPI'] = self.csapi resultDict['AdminMessages'] = self.adminMsgs return S_OK(resultDict)
class UsersAndGroups( AgentModule ): def initialize( self ): self.am_setOption( "PollingTime", 3600 * 6 ) # Every 6 hours self.vomsSrv = VOMSService() self.proxyLocation = os.path.join( self.am_getWorkDirectory(), ".volatileId" ) self.__adminMsgs = {} # print self.getLFCRegisteredDNs() return S_OK() def __generateProxy( self ): self.log.info( "Generating proxy..." ) certLoc = Locations.getHostCertificateAndKeyLocation() if not certLoc: self.log.error( "Can not find certificate!" ) return False chain = X509Chain.X509Chain() result = chain.loadChainFromFile( certLoc[0] ) if not result[ 'OK' ]: self.log.error( "Can not load certificate file", "%s : %s" % ( certLoc[0], result[ 'Message' ] ) ) return False result = chain.loadKeyFromFile( certLoc[1] ) if not result[ 'OK' ]: self.log.error( "Can not load key file", "%s : %s" % ( certLoc[1], result[ 'Message' ] ) ) return False result = chain.generateProxyToFile( self.proxyLocation, 3600 ) if not result[ 'OK' ]: self.log.error( "Could not generate proxy file", result[ 'Message' ] ) return False self.log.info( "Proxy generated" ) return True def getLFCRegisteredDNs( self ): #Request a proxy if gConfig.useServerCertificate(): if not self.__generateProxy(): return False #Execute the call cmdEnv = dict( os.environ ) cmdEnv['LFC_HOST'] = 'lfc-egee.in2p3.fr' if os.path.isfile( self.proxyLocation ): cmdEnv[ 'X509_USER_PROXY' ] = self.proxyLocation lfcDNs = [] try: retlfc = Subprocess.systemCall( 30, ( 'lfc-listusrmap', ), env = cmdEnv ) if not retlfc['OK']: self.log.fatal( 'Can not get LFC User List', retlfc['Message'] ) return retlfc if retlfc['Value'][0]: self.log.fatal( 'Can not get LFC User List', retlfc['Value'][2] ) return S_ERROR( "lfc-listusrmap failed" ) else: for item in List.fromChar( retlfc['Value'][1], '\n' ): dn = item.split( ' ', 1 )[1] lfcDNs.append( dn ) return S_OK( lfcDNs ) finally: if os.path.isfile( self.proxyLocation ): self.log.info( "Destroying proxy..." ) os.unlink( self.proxyLocation ) def checkLFCRegisteredUsers( self, usersData ): self.log.info( "Checking LFC registered users" ) usersToBeRegistered = {} result = self.getLFCRegisteredDNs() if not result[ 'OK' ]: self.log.error( "Could not get a list of registered DNs from LFC", result[ 'Message' ] ) return result lfcDNs = result[ 'Value' ] for user in usersData: for userDN in usersData[ user ][ 'DN' ]: if userDN not in lfcDNs: self.log.info( 'DN "%s" need to be registered in LFC for user %s' % ( userDN, user ) ) if user not in usersToBeRegistered: usersToBeRegistered[ user ] = [] usersToBeRegistered[ user ].append( userDN ) address = self.am_getOption( 'MailTo', '*****@*****.**' ) fromAddress = self.am_getOption( 'mailFrom', '*****@*****.**' ) if usersToBeRegistered: subject = 'New LFC Users found' self.log.info( subject, ", ".join( usersToBeRegistered ) ) body = 'Command to add new entries into LFC: \n' body += 'login to volhcbXX and run : \n' body += 'source /afs/cern.ch/lhcb/software/releases/LBSCRIPTS/prod/InstallArea/scripts/LbLogin.csh \n\n' for lfcuser in usersToBeRegistered: for lfc_dn in usersToBeRegistered[lfcuser]: print lfc_dn body += 'add_DN_LFC --userDN="' + lfc_dn.strip() + '" --nickname=' + lfcuser + '\n' NotificationClient().sendMail( address, 'UsersAndGroupsAgent: %s' % subject, body, fromAddress ) return S_OK() def execute( self ): result = self.__syncCSWithVOMS() mailMsg = "" if self.__adminMsgs[ 'Errors' ]: mailMsg += "\nErrors list:\n %s" % "\n ".join( self.__adminMsgs[ 'Errors' ] ) if self.__adminMsgs[ 'Info' ]: mailMsg += "\nRun result:\n %s" % "\n ".join( self.__adminMsgs[ 'Info' ] ) NotificationClient().sendMail( self.am_getOption( 'MailTo', '*****@*****.**' ), "UsersAndGroupsAgent run log", mailMsg, self.am_getOption( 'mailFrom', '*****@*****.**' ) ) return result def __syncCSWithVOMS( self ): self.__adminMsgs = { 'Errors' : [], 'Info' : [] } #Get DIRAC VOMS Mapping self.log.info( "Getting DIRAC VOMS mapping" ) mappingSection = '/Registry/VOMS/Mapping' ret = gConfig.getOptionsDict( mappingSection ) if not ret['OK']: self.log.fatal( 'No VOMS to DIRAC Group Mapping Available' ) return ret vomsMapping = ret['Value'] self.log.info( "There are %s registered voms mappings in DIRAC" % len( vomsMapping ) ) #Get VOMS VO name self.log.info( "Getting VOMS VO name" ) result = self.vomsSrv.admGetVOName() if not ret['OK']: self.log.fatal( 'Could not retrieve VOMS VO name' ) voNameInVOMS = result[ 'Value' ] self.log.info( "VOMS VO Name is %s" % voNameInVOMS ) #Get VOMS roles self.log.info( "Getting the list of registered roles in VOMS" ) result = self.vomsSrv.admListRoles() if not ret['OK']: self.log.fatal( 'Could not retrieve registered roles in VOMS' ) rolesInVOMS = result[ 'Value' ] self.log.info( "There are %s registered roles in VOMS" % len( rolesInVOMS ) ) print rolesInVOMS rolesInVOMS.append( '' ) #Map VOMS roles vomsRoles = {} for role in rolesInVOMS: if role: role = "%s/%s" % ( voNameInVOMS, role ) else: role = voNameInVOMS groupsForRole = [] for group in vomsMapping: if vomsMapping[ group ] == role: groupsForRole.append( group ) if groupsForRole: vomsRoles[ role ] = { 'Groups' : groupsForRole, 'Users' : [] } self.log.info( "DIRAC valid VOMS roles are:\n\t", "\n\t ".join( vomsRoles.keys() ) ) #Get DIRAC users self.log.info( "Getting the list of registered users in DIRAC" ) csapi = CSAPI() ret = csapi.listUsers() if not ret['OK']: self.log.fatal( 'Could not retrieve current list of Users' ) return ret currentUsers = ret['Value'] ret = csapi.describeUsers( currentUsers ) if not ret['OK']: self.log.fatal( 'Could not retrieve current User description' ) return ret currentUsers = ret['Value'] self.__adminMsgs[ 'Info' ].append( "There are %s registered users in DIRAC" % len( currentUsers ) ) self.log.info( "There are %s registered users in DIRAC" % len( currentUsers ) ) #Get VOMS user entries self.log.info( "Getting the list of registered user entries in VOMS" ) result = self.vomsSrv.admListMembers() if not ret['OK']: self.log.fatal( 'Could not retrieve registered user entries in VOMS' ) usersInVOMS = result[ 'Value' ] self.__adminMsgs[ 'Info' ].append( "There are %s registered user entries in VOMS" % len( usersInVOMS ) ) self.log.info( "There are %s registered user entries in VOMS" % len( usersInVOMS ) ) #Consolidate users by nickname usersData = {} newUserNames = [] knownUserNames = [] obsoleteUserNames = [] self.log.info( "Retrieving usernames..." ) usersInVOMS.sort() for iUPos in range( len( usersInVOMS ) ): userName = '' user = usersInVOMS[ iUPos ] for oldUser in currentUsers: if user[ 'DN' ].strip() in List.fromChar( currentUsers[oldUser][ 'DN' ] ): userName = oldUser if not userName: result = self.vomsSrv.attGetUserNickname( user[ 'DN' ], user[ 'CA' ] ) if result[ 'OK' ]: userName = result[ 'Value' ] else: self.__adminMsgs[ 'Errors' ].append( "Could not retrieve nickname for DN %s" % user[ 'DN' ] ) self.log.error( "Could not get nickname for DN", user[ 'DN' ] ) userName = user[ 'mail' ][:user[ 'mail' ].find( '@' )] if not userName: self.log.error( "Empty nickname for DN", user[ 'DN' ] ) self.__adminMsgs[ 'Errors' ].append( "Empty nickname for DN %s" % user[ 'DN' ] ) continue self.log.info( " (%02d%%) Found username %s : %s " % ( ( iUPos * 100 / len( usersInVOMS ) ), userName, user[ 'DN' ] ) ) if userName not in usersData: usersData[ userName ] = { 'DN': [], 'CA': [], 'Email': [], 'Groups' : ['user'] } for key in ( 'DN', 'CA', 'mail' ): value = user[ key ] if value: if key == "mail": List.appendUnique( usersData[ userName ][ 'Email' ], value ) else: usersData[ userName ][ key ].append( value.strip() ) if userName not in currentUsers: List.appendUnique( newUserNames, userName ) else: List.appendUnique( knownUserNames, userName ) self.log.info( "Finished retrieving usernames" ) if newUserNames: self.log.info( "There are %s new users" % len( newUserNames ) ) else: self.log.info( "There are no new users" ) #Get the list of users for each group result = csapi.listGroups() if not result[ 'OK' ]: self.log.error( "Could not get the list of groups in DIRAC", result[ 'Message' ] ) return result staticGroups = result[ 'Value' ] vomsGroups = [] self.log.info( "Mapping users in VOMS to groups" ) for vomsRole in vomsRoles: self.log.info( " Getting users for role %s" % vomsRole ) groupsForRole = vomsRoles[ vomsRole ][ 'Groups' ] vomsMap = vomsRole.split( "Role=" ) for g in groupsForRole: if g in staticGroups: staticGroups.pop( staticGroups.index( g ) ) else: vomsGroups.append( g ) if len( vomsMap ) == 1: # no Role users = usersInVOMS else: vomsGroup = "Role=".join( vomsMap[:-1] ) if vomsGroup[-1] == "/": vomsGroup = vomsGroup[:-1] vomsRole = "Role=%s" % vomsMap[-1] result = self.vomsSrv.admListUsersWithRole( vomsGroup, vomsRole ) if not result[ 'OK' ]: errorMsg = "Could not get list of users for VOMS %s" % ( vomsMapping[ group ] ) self.__adminMsgs[ 'Errors' ].append( errorMsg ) self.log.error( errorMsg, result[ 'Message' ] ) return result users = result['Value'] numUsersInGroup = 0 for vomsUser in users: for userName in usersData: if vomsUser[ 'DN' ] in usersData[ userName ][ 'DN' ]: numUsersInGroup += 1 usersData[ userName ][ 'Groups' ].extend( groupsForRole ) infoMsg = "There are %s users in group(s) %s for VOMS Role %s" % ( numUsersInGroup, ",".join( groupsForRole ), vomsRole ) self.__adminMsgs[ 'Info' ].append( infoMsg ) self.log.info( " %s" % infoMsg ) self.log.info( "Checking static groups" ) staticUsers = [] for group in staticGroups: self.log.info( " Checking static group %s" % group ) numUsersInGroup = 0 result = csapi.listUsers( group ) if not result[ 'OK' ]: self.log.error( "Could not get the list of users in DIRAC group %s" % group , result[ 'Message' ] ) return result for userName in result[ 'Value' ]: if userName in usersData: numUsersInGroup += 1 usersData[ userName ][ 'Groups' ].append( group ) else: if group not in vomsGroups and userName not in staticUsers: staticUsers.append( userName ) infoMsg = "There are %s users in group %s" % ( numUsersInGroup, group ) self.__adminMsgs[ 'Info' ].append( infoMsg ) self.log.info( " %s" % infoMsg ) if staticUsers: infoMsg = "There are %s static users: %s" % ( len( staticUsers ) , ', '.join( staticUsers ) ) self.__adminMsgs[ 'Info' ].append( infoMsg ) self.log.info( "%s" % infoMsg ) for user in currentUsers: if user not in usersData and user not in staticUsers: self.log.info( 'User %s is no longer valid' % user ) obsoleteUserNames.append( user ) #Do the CS Sync self.log.info( "Updating CS..." ) ret = csapi.downloadCSData() if not ret['OK']: self.log.fatal( 'Can not update from CS', ret['Message'] ) return ret usersWithMoreThanOneDN = {} for user in usersData: csUserData = dict( usersData[ user ] ) if len( csUserData[ 'DN' ] ) > 1: usersWithMoreThanOneDN[ user ] = csUserData[ 'DN' ] result = csapi.describeUsers( [ user ] ) if result[ 'OK' ]: if result[ 'Value' ]: prevUser = result[ 'Value' ][ user ] prevDNs = List.fromChar( prevUser[ 'DN' ] ) newDNs = csUserData[ 'DN' ] for DN in newDNs: if DN not in prevDNs: self.__adminMsgs[ 'Info' ].append( "User %s has new DN %s" % ( user, DN ) ) for DN in prevDNs: if DN not in newDNs: self.__adminMsgs[ 'Info' ].append( "User %s has lost a DN %s" % ( user, DN ) ) else: newDNs = csUserData[ 'DN' ] for DN in newDNs: self.__adminMsgs[ 'Info' ].append( "New user %s has new DN %s" % ( user, DN ) ) for k in ( 'DN', 'CA', 'Email' ): csUserData[ k ] = ", ".join( csUserData[ k ] ) result = csapi.modifyUser( user, csUserData, createIfNonExistant = True ) if not result[ 'OK' ]: self.__adminMsgs[ 'Error' ].append( "Cannot modify user %s: %s" % ( user, result[ 'Message' ] ) ) self.log.error( "Cannot modify user", user ) if usersWithMoreThanOneDN: self.__adminMsgs[ 'Info' ].append( "\nUsers with more than one DN:" ) for uwmtod in sorted( usersWithMoreThanOneDN ): self.__adminMsgs[ 'Info' ].append( " %s" % uwmtod ) self.__adminMsgs[ 'Info' ].append( " + DN list:" ) for DN in usersWithMoreThanOneDN[uwmtod]: self.__adminMsgs[ 'Info' ].append( " - %s" % DN ) if obsoleteUserNames: self.__adminMsgs[ 'Info' ].append( "\nObsolete users:" ) address = self.am_getOption( 'MailTo', '*****@*****.**' ) fromAddress = self.am_getOption( 'mailFrom', '*****@*****.**' ) subject = 'Obsolete LFC Users found' body = 'Delete entries into LFC: \n' for obsoleteUser in obsoleteUserNames: self.log.info( subject, ", ".join( obsoleteUserNames ) ) body += 'for ' + obsoleteUser + '\n' self.__adminMsgs[ 'Info' ].append( " %s" % obsoleteUser ) self.log.info( "Deleting %s users" % len( obsoleteUserNames ) ) NotificationClient().sendMail( address, 'UsersAndGroupsAgent: %s' % subject, body, fromAddress ) csapi.deleteUsers( obsoleteUserNames ) if newUserNames: self.__adminMsgs[ 'Info' ].append( "\nNew users:" ) for newUser in newUserNames: self.__adminMsgs[ 'Info' ].append( " %s" % newUser ) self.__adminMsgs[ 'Info' ].append( " + DN list:" ) for DN in usersData[newUser][ 'DN' ]: self.__adminMsgs[ 'Info' ].append( " - %s" % DN ) self.__adminMsgs[ 'Info' ].append( " + EMail: %s" % usersData[newUser][ 'Email' ] ) result = csapi.commitChanges() if not result[ 'OK' ]: self.log.error( "Could not commit configuration changes", result[ 'Message' ] ) return result self.log.info( "Configuration committed" ) #LFC Check if self.am_getOption( "LFCCheckEnabled", True ): result = self.checkLFCRegisteredUsers( usersData ) if not result[ 'OK' ]: return result return S_OK()
def syncCSWithVOMS(self): """ Performs the synchronization of the DIRAC registry with the VOMS data. The resulting CSAPI object containing modifications is returned as part of the output dictionary. Those changes can be applied by the caller depending on the mode (dry or a real run) :return: S_OK with a dictionary containing the results of the synchronization operation """ resultDict = defaultdict(list) # Get DIRAC group vs VOMS Role Mappings result = getVOMSRoleGroupMapping(self.vo) if not result['OK']: return result vomsDIRACMapping = result['Value']['VOMSDIRAC'] diracVOMSMapping = result['Value']['DIRACVOMS'] noVOMSGroups = result['Value']['NoVOMS'] noSyncVOMSGroups = result['Value']['NoSyncVOMS'] vomsSrv = VOMSService(self.vo) # Get VOMS users result = vomsSrv.getUsers() if not result['OK']: self.log.error('Could not retrieve user information from VOMS', result['Message']) return result self.vomsUserDict = result['Value'] message = "There are %s user entries in VOMS for VO %s" % (len( self.vomsUserDict), self.vomsVOName) self.adminMsgs['Info'].append(message) self.log.info(message) # Get DIRAC users result = self.getVOUserData(self.vo) if not result['OK']: return result diracUserDict = result['Value'] self.adminMsgs['Info'].append( "There are %s registered users in DIRAC for VO %s" % (len(diracUserDict), self.vo)) self.log.info("There are %s registered users in DIRAC VO %s" % (len(diracUserDict), self.vo)) # Find new and obsoleted user DNs existingDNs = [] obsoletedDNs = [] newDNs = [] for user in diracUserDict: dn = diracUserDict[user]['DN'] # We can have users with more than one DN registered dnList = fromChar(dn) existingDNs.extend(dnList) for dn in dnList: if dn not in self.vomsUserDict: obsoletedDNs.append(dn) for dn in self.vomsUserDict: if dn not in existingDNs: newDNs.append(dn) allDiracUsers = getAllUsers() nonVOUserDict = {} nonVOUsers = list(set(allDiracUsers) - set(diracUserDict)) if nonVOUsers: result = self.csapi.describeUsers(nonVOUsers) if not result['OK']: self.log.error('Could not retrieve CS User description') return result nonVOUserDict = result['Value'] # Process users defaultVOGroup = getVOOption(self.vo, "DefaultGroup", "%s_user" % self.vo) # If a user is (previously put by hand) in a "QuarantineGroup", # then the default group will be ignored. # So, this option is only considered for the case of existing users. quarantineVOGroup = getVOOption(self.vo, "QuarantineGroup") newAddedUserDict = {} for dn in self.vomsUserDict: newDNForExistingUser = '' diracName = '' if dn in existingDNs: for user in diracUserDict: if dn == diracUserDict[user]['DN']: diracName = user if dn in newDNs: # Find if the DN is already registered in the DIRAC CS for user in nonVOUserDict: if dn == nonVOUserDict[user]['DN']: diracName = user # Check the nickName in the same VO to see if the user is already registered # with another DN nickName = self.vomsUserDict[dn].get('nickname') if nickName in diracUserDict or nickName in newAddedUserDict: diracName = nickName # This is a flag for adding the new DN to an already existing user newDNForExistingUser = dn # We have a real new user if not diracName: if nickName: newDiracName = nickName else: newDiracName = self.getUserName(dn) # Do not consider users with Suspended status in VOMS if self.vomsUserDict[dn]['suspended'] or self.vomsUserDict[ dn]['certSuspended']: resultDict["SuspendedUsers"].append(newDiracName) continue # If the chosen user name exists already, append a distinguishing suffix ind = 1 trialName = newDiracName while newDiracName in allDiracUsers: # We have a user with the same name but with a different DN newDiracName = "%s_%d" % (trialName, ind) ind += 1 # We now have everything to add the new user userDict = { "DN": dn, "CA": self.vomsUserDict[dn]['CA'], "Email": self.vomsUserDict[dn]['mail'] } groupsWithRole = [] for role in self.vomsUserDict[dn]['Roles']: groupList = vomsDIRACMapping.get(role, []) for group in groupList: if group not in noSyncVOMSGroups: groupsWithRole.append(group) userDict['Groups'] = list( set(groupsWithRole + [defaultVOGroup])) message = "\n Added new user %s:\n" % newDiracName for key in userDict: message += " %s: %s\n" % (key, str(userDict[key])) self.adminMsgs['Info'].append(message) self.voChanged = True if self.autoAddUsers: self.log.info("Adding new user %s: %s" % (newDiracName, str(userDict))) result = self.csapi.modifyUser( newDiracName, userDict, createIfNonExistant=True) if not result['OK']: self.log.warn('Failed adding new user %s' % newDiracName) resultDict['NewUsers'].append(newDiracName) newAddedUserDict[newDiracName] = userDict continue # We have an already existing user modified = False userDict = { "DN": dn, "CA": self.vomsUserDict[dn]['CA'], "Email": self.vomsUserDict[dn]['mail'] } if newDNForExistingUser: userDict['DN'] = ','.join([ dn, diracUserDict.get(diracName, newAddedUserDict.get(diracName))['DN'] ]) modified = True existingGroups = diracUserDict.get(diracName, {}).get('Groups', []) nonVOGroups = list(set(existingGroups) - set(diracVOMSMapping)) groupsWithRole = [] for role in self.vomsUserDict[dn]['Roles']: groupList = vomsDIRACMapping.get(role, []) for group in groupList: if group not in noSyncVOMSGroups: groupsWithRole.append(group) keepGroups = nonVOGroups + groupsWithRole if not quarantineVOGroup or quarantineVOGroup not in existingGroups: keepGroups += [defaultVOGroup] for group in existingGroups: if group in nonVOGroups: continue role = diracVOMSMapping.get(group, '') # Among already existing groups for the user keep those without a special VOMS Role # because this membership is done by hand in the CS if "Role" not in role: keepGroups.append(group) # Keep existing groups with no VOMS attribute if any if group in noVOMSGroups: keepGroups.append(group) # Keep groups for which syncronization with VOMS is forbidden if group in noSyncVOMSGroups: keepGroups.append(group) userDict['Groups'] = list(set(keepGroups)) # Merge together groups for the same user but different DNs if diracName in newAddedUserDict: otherGroups = newAddedUserDict[diracName].get('Groups', []) userDict['Groups'] = list(set(keepGroups + otherGroups)) modified = True # Check if something changed before asking CSAPI to modify if diracName in diracUserDict: message = "\n Modified user %s:\n" % diracName modMsg = '' for key in userDict: if key == "Groups": addedGroups = set(userDict[key]) - set( diracUserDict.get(diracName, {}).get(key, [])) removedGroups = set( diracUserDict.get(diracName, {}).get( key, [])) - set(userDict[key]) if addedGroups: modMsg += " Added to group(s) %s\n" % ','.join( addedGroups) if removedGroups: modMsg += " Removed from group(s) %s\n" % ','.join( removedGroups) else: oldValue = str( diracUserDict.get(diracName, {}).get(key, '')) if str(userDict[key]) != oldValue: modMsg += " %s: %s -> %s\n" % ( key, oldValue, str(userDict[key])) if modMsg: self.adminMsgs['Info'].append(message + modMsg) modified = True if self.autoModifyUsers and modified: result = self.csapi.modifyUser(diracName, userDict) if result['OK'] and result['Value']: self.log.info("Modified user %s: %s" % (diracName, str(userDict))) self.voChanged = True resultDict['ModifiedUsers'].append(diracName) # Check if there are potentially obsoleted users oldUsers = set() for user in diracUserDict: dnSet = set(fromChar(diracUserDict[user]['DN'])) if not dnSet.intersection(set( self.vomsUserDict)) and user not in nonVOUserDict: for group in diracUserDict[user]['Groups']: if group not in noVOMSGroups: oldUsers.add(user) # Check for obsoleted DNs for user in diracUserDict: dnSet = set(fromChar(diracUserDict[user]['DN'])) for dn in dnSet: if dn in obsoletedDNs and user not in oldUsers: self.log.verbose("Modified user %s: dropped DN %s" % (user, dn)) if self.autoModifyUsers: userDict = diracUserDict[user] modDNSet = dnSet - set([dn]) if modDNSet: userDict['DN'] = ','.join(modDNSet) result = self.csapi.modifyUser(user, userDict) if result['OK'] and result['Value']: self.log.info( "Modified user %s: dropped DN %s" % (user, dn)) self.adminMsgs['Info'].append( "Modified user %s: dropped DN %s" % (user, dn)) self.voChanged = True resultDict['ModifiedUsers'].append(diracName) else: oldUsers.add(user) if oldUsers: self.voChanged = True if self.autoDeleteUsers: self.log.info('The following users will be deleted: %s' % str(oldUsers)) result = self.csapi.deleteUsers(oldUsers) if result['OK']: self.adminMsgs['Info'].append( 'The following users are deleted from CS:\n %s\n' % str(oldUsers)) resultDict['DeletedUsers'] = oldUsers else: self.adminMsgs['Errors'].append( 'Error in deleting users from CS:\n %s' % str(oldUsers)) self.log.error('Error while user deletion from CS', result) else: self.adminMsgs['Info'].append( 'The following users to be checked for deletion:\n %s' % str(oldUsers)) self.log.info( 'The following users to be checked for deletion: %s' % str(oldUsers)) resultDict['CSAPI'] = self.csapi resultDict['AdminMessages'] = self.adminMsgs return S_OK(resultDict)
def __syncCSWithVOMS( self, vo ): self.__adminMsgs = { 'Errors' : [], 'Info' : [] } # Get DIRAC group vs VOMS Role Mappings result = getVOMSRoleGroupMapping( vo ) if not result['OK']: return result vomsDIRACMapping = result['Value']['VOMSDIRAC'] diracVOMSMapping = result['Value']['DIRACVOMS'] noVOMSGroups = result['Value']['NoVOMS'] vomsSrv = VOMSService( vo ) # Get VOMS VO name result = vomsSrv.admGetVOName() if not result['OK']: self.log.error( 'Could not retrieve VOMS VO name', "for %s" % vo ) return result vomsVOName = result[ 'Value' ].lstrip( '/' ) self.log.verbose( "VOMS VO Name for %s is %s" % ( vo, vomsVOName ) ) # Get VOMS users result = vomsSrv.getUsers() if not result['OK']: self.log.error( 'Could not retrieve user information from VOMS', result['Message'] ) return result vomsUserDict = result[ 'Value' ] message = "There are %s registered users in VOMS VO %s" % ( len( vomsUserDict ), vomsVOName ) self.__adminMsgs[ 'Info' ].append( message ) self.log.info( message ) # Get DIRAC users diracUsers = getUsersInVO( vo ) if not diracUsers: return S_ERROR( "No VO users found for %s" % vo ) result = self.csapi.describeUsers( diracUsers ) if not result['OK']: self.log.error( 'Could not retrieve CS User description' ) return result diracUserDict = result['Value'] self.__adminMsgs[ 'Info' ].append( "There are %s registered users in DIRAC for VO %s" % ( len( diracUserDict ), vo ) ) self.log.info( "There are %s registered users in DIRAC VO %s" % ( len( diracUserDict ), vo ) ) # Find new and obsoleted user DNs existingDNs = [] obsoletedDNs = [] newDNs = [] for user in diracUserDict: dn = diracUserDict[user]['DN'] existingDNs.append( dn ) if dn not in vomsUserDict: obsoletedDNs.append( dn ) for dn in vomsUserDict: if dn not in existingDNs: newDNs.append( dn ) allDiracUsers = getAllUsers() nonVOusers = list( set( allDiracUsers ) - set(diracUsers) ) result = self.csapi.describeUsers( nonVOusers ) if not result['OK']: self.log.error( 'Could not retrieve CS User description' ) return result nonVOUserDict = result['Value'] # Process users defaultVOGroup = getVOOption( vo, "DefaultGroup", "%s_user" % vo ) for dn in vomsUserDict: if dn in newDNs: # Find if the DN is already registered in the DIRAC CS diracName = '' for user in nonVOUserDict: if dn == nonVOUserDict[user]['DN']: diracName = user # We have a real new user if not diracName: nickName = '' result = vomsSrv.attGetUserNickname( dn, vomsUserDict[dn]['CA'] ) if result['OK']: nickName = result['Value'] if nickName: newDiracName = nickName else: newDiracName = getUserName( dn, vomsUserDict[dn]['mail'] ) ind = 1 trialName = newDiracName while newDiracName in allDiracUsers: # We have a user with the same name but with a different DN newDiracName = "%s_%d" % ( trialName, ind ) ind += 1 # We now have everything to add the new user userDict = { "DN": dn, "CA": vomsUserDict[dn]['CA'], "Email": vomsUserDict[dn]['mail'] } groupsWithRole = [] for role in vomsUserDict[dn]['Roles']: fullRole = "/%s/%s" % ( vomsVOName, role ) group = vomsDIRACMapping.get( fullRole ) if group: groupsWithRole.append( group ) userDict['Groups'] = list( set( groupsWithRole + [defaultVOGroup] ) ) self.__adminMsgs[ 'Info' ].append( "Adding new user %s: %s" % ( newDiracName, str( userDict ) ) ) self.voChanged = True if self.autoAddUsers: self.log.info( "Adding new user %s: %s" % ( newDiracName, str( userDict ) ) ) result = self.csapi.modifyUser( newDiracName, userDict, createIfNonExistant = True ) if not result['OK']: self.log.warn( 'Failed adding new user %s' % newDiracName ) continue # We have an already existing user userDict = { "DN": dn, "CA": vomsUserDict[dn]['CA'], "Email": vomsUserDict[dn]['mail'] } nonVOGroups = nonVOUserDict.get( diracName, {} ).get( 'Groups', [] ) existingGroups = diracUserDict.get( diracName, {} ).get( 'Groups', [] ) groupsWithRole = [] for role in vomsUserDict[dn]['Roles']: fullRole = "/%s/%s" % ( vomsVOName, role ) group = vomsDIRACMapping.get( fullRole ) if group: groupsWithRole.append( group ) keepGroups = nonVOGroups + groupsWithRole + [defaultVOGroup] for group in existingGroups: role = diracVOMSMapping[group] # Among already existing groups for the user keep those without a special VOMS Role # because this membership is done by hand in the CS if not "Role" in role: keepGroups.append( group ) # Keep existing groups with no VOMS attribute if any if group in noVOMSGroups: keepGroups.append( group ) userDict['Groups'] = keepGroups if self.autoModifyUsers: result = self.csapi.modifyUser( diracName, userDict ) if result['OK'] and result['Value']: self.voChanged = True # Check if there are potentially obsoleted users oldUsers = set() for user in diracUserDict: dn = diracUserDict[user]['DN'] if not dn in vomsUserDict and not user in nonVOUserDict: for group in diracUserDict[user]['Groups']: if not group in noVOMSGroups: oldUsers.add( user ) if oldUsers: self.voChanged = True self.__adminMsgs[ 'Info' ].append( 'The following users to be checked for deletion: %s' % str( oldUsers ) ) self.log.info( 'The following users to be checked for deletion: %s' % str( oldUsers ) ) return S_OK()