""" The VOMS2CSAgent extracts VO user information from VOMS servers. It performs the following operations: - Extracts user info from the VOMS server using its REST interface - Finds user DN's not yet registered in the DIRAC Registry - For each new DN it constructs a DIRAC login name by a best guess or using the nickname VOMS attribute - Registers new users to the DIRAC Registry including group membership - Updates information for already registered users, including group membership - Sends report for performed operation to the VO administrator The agent is performing its operations with credentials of the VO administrator as defined in the ``/Registry/VO/`` configuration section. The following options can be set for the VOMS2CSAgent. .. literalinclude:: ../ConfigTemplate.cfg :start-after: ##BEGIN VOMS2CSAgent :end-before: ##END :dedent: 2 :caption: VOMS2CSAgent options .. note:: The options *AutoAddUsers*, *AutoModifyUsers*, *AutoDeleteUsers* can be overridden by the corresponding options defined in the ``/Registry/VO/`` configuration section. """ from __future__ import absolute_import from __future__ import division from __future__ import print_function from DIRAC import S_OK, gConfig, S_ERROR from DIRAC.Core.Base.AgentModule import AgentModule from DIRAC.Core.Utilities.Proxy import executeWithUserProxy from DIRAC.ConfigurationSystem.Client.Helpers.Registry import getVOOption, getUserOption from DIRAC.ConfigurationSystem.Client.VOMS2CSSynchronizer import VOMS2CSSynchronizer from DIRAC.FrameworkSystem.Client.NotificationClient import NotificationClient from DIRAC.Resources.Catalog.FileCatalog import FileCatalog __RCSID__ = "614350952 (2021-09-29 10:24:42 +0200) Chris Burr " class VOMS2CSAgent(AgentModule): def __init__(self, *args, **kwargs): """Defines default parameters""" super(VOMS2CSAgent, self).__init__(*args, **kwargs) self.voList = ["any"] self.dryRun = True self.autoAddUsers = True self.autoModifyUsers = True self.autoDeleteUsers = True self.detailedReport = True self.makeFCEntry = False self.autoLiftSuspendedStatus = True self.mailFrom = "noreply@dirac.system" self.syncPluginName = None def initialize(self): """Initialize the default parameters""" self.dryRun = self.am_getOption("DryRun", self.dryRun) # # General agent options, can be overridden by VO options self.autoAddUsers = self.am_getOption("AutoAddUsers", self.autoAddUsers) self.autoModifyUsers = self.am_getOption("AutoModifyUsers", self.autoModifyUsers) self.autoDeleteUsers = self.am_getOption("AutoDeleteUsers", self.autoDeleteUsers) self.autoLiftSuspendedStatus = self.am_getOption("AutoLiftSuspendedStatus", self.autoLiftSuspendedStatus) self.makeFCEntry = self.am_getOption("MakeHomeDirectory", self.makeFCEntry) self.syncPluginName = self.am_getOption("SyncPluginName", self.syncPluginName) self.detailedReport = self.am_getOption("DetailedReport", self.detailedReport) self.mailFrom = self.am_getOption("MailFrom", self.mailFrom) self.voList = self.am_getOption("VO", self.voList) if not self.voList: return S_ERROR("Option 'VO' not configured") if self.voList[0].lower() == "any": result = gConfig.getSections("/Registry/VO") if not result["OK"]: return result self.voList = result["Value"] self.log.notice("VOs", self.voList) return S_OK() def execute(self): for vo in self.voList: voAdminUser = getVOOption(vo, "VOAdmin") voAdminMail = None if voAdminUser: voAdminMail = getUserOption(voAdminUser, "Email") voAdminGroup = getVOOption(vo, "VOAdminGroup", getVOOption(vo, "DefaultGroup")) self.log.info("Performing VOMS sync", "for VO %s with credentials %s@%s" % (vo, voAdminUser, voAdminGroup)) autoAddUsers = getVOOption(vo, "AutoAddUsers", self.autoAddUsers) autoModifyUsers = getVOOption(vo, "AutoModifyUsers", self.autoModifyUsers) autoDeleteUsers = getVOOption(vo, "AutoDeleteUsers", self.autoDeleteUsers) autoLiftSuspendedStatus = getVOOption(vo, "AutoLiftSuspendedStatus", self.autoLiftSuspendedStatus) syncPluginName = getVOOption(vo, "SyncPluginName", self.syncPluginName) vomsSync = VOMS2CSSynchronizer( vo, autoAddUsers=autoAddUsers, autoModifyUsers=autoModifyUsers, autoDeleteUsers=autoDeleteUsers, autoLiftSuspendedStatus=autoLiftSuspendedStatus, syncPluginName=syncPluginName, ) result = self.__syncCSWithVOMS( # pylint: disable=unexpected-keyword-arg vomsSync, proxyUserName=voAdminUser, proxyUserGroup=voAdminGroup, ) if not result["OK"]: self.log.error("Failed to perform VOMS to CS synchronization:", "VO %s: %s" % (vo, result["Message"])) continue resultDict = result["Value"] newUsers = resultDict.get("NewUsers", []) modUsers = resultDict.get("ModifiedUsers", []) delUsers = resultDict.get("DeletedUsers", []) susUsers = resultDict.get("SuspendedUsers", []) csapi = resultDict.get("CSAPI") adminMessages = resultDict.get("AdminMessages", {"Errors": [], "Info": []}) voChanged = resultDict.get("VOChanged", False) self.log.info( "Run user results", ": new %d, modified %d, deleted %d, new/suspended %d" % (len(newUsers), len(modUsers), len(delUsers), len(susUsers)), ) if csapi.csModified: # We have accumulated all the changes, commit them now self.log.info("There are changes to the CS ready to be committed", "for VO %s" % vo) if self.dryRun: self.log.info("Dry Run: CS won't be updated") csapi.showDiff() else: result = csapi.commitChanges() if not result["OK"]: self.log.error("Could not commit configuration changes", result["Message"]) return result self.log.notice("Configuration committed", "for VO %s" % vo) else: self.log.info("No changes to the CS recorded at this cycle", "for VO %s" % vo) # Add user home directory in the file catalog if self.makeFCEntry and newUsers: self.log.info("Creating home directories for users", str(newUsers)) result = self.__addHomeDirectory( # pylint: disable=unexpected-keyword-arg vo, newUsers, proxyUserName=voAdminUser, proxyUserGroup=voAdminGroup, ) if not result["OK"]: self.log.error("Failed to create user home directories:", "VO %s: %s" % (vo, result["Message"])) else: for user in result["Value"]["Failed"]: self.log.error( "Failed to create home directory", "user: %s, operation: %s" % (user, result["Value"]["Failed"][user]), ) adminMessages["Errors"].append( "Failed to create home directory for user %s: operation %s" % (user, result["Value"]["Failed"][user]) ) for user in result["Value"]["Successful"]: adminMessages["Info"].append("Created home directory for user %s" % user) if voChanged or self.detailedReport: mailMsg = "" if adminMessages["Errors"]: mailMsg += "\nErrors list:\n %s" % "\n ".join(adminMessages["Errors"]) if adminMessages["Info"]: mailMsg += "\nRun result:\n %s" % "\n ".join(adminMessages["Info"]) if self.detailedReport: result = vomsSync.getVOUserReport() if result["OK"]: mailMsg += "\n\n" mailMsg += result["Value"] else: mailMsg += "Failed to produce a detailed user report" mailMsg += result["Message"] if self.dryRun: self.log.info("Dry Run: mail won't be sent") self.log.info(mailMsg) else: NotificationClient().sendMail( self.am_getOption("MailTo", voAdminMail), "VOMS2CSAgent run log", mailMsg, self.mailFrom ) return S_OK() @executeWithUserProxy def __syncCSWithVOMS(self, vomsSync): return vomsSync.syncCSWithVOMS() @executeWithUserProxy def __addHomeDirectory(self, vo, newUsers): fc = FileCatalog(vo=vo) defaultVOGroup = getVOOption(vo, "DefaultGroup", "%s_user" % vo) failed = {} successful = {} for user in newUsers: result = fc.addUser(user) if not result["OK"]: failed[user] = "addUser" continue dirName = "/%s/user/%s/%s" % (vo, user[0], user) result = fc.createDirectory(dirName) if not result["OK"]: failed[user] = "createDirectory" continue result = fc.changePathOwner({dirName: user}, recursive=False) if not result["OK"]: failed[user] = "changePathOwner" continue result = fc.changePathGroup({dirName: defaultVOGroup}, recursive=False) if not result["OK"]: failed[user] = "changePathGroup" continue successful[user] = True return S_OK({"Successful": successful, "Failed": failed})