Esempio n. 1
0
    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()
Esempio n. 2
0
    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)
Esempio n. 3
0
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()
Esempio n. 4
0
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()
Esempio n. 5
0
  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()
Esempio n. 6
0
  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 )

    # 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 ) )

    return S_OK( resultDict )