Skip to content
Snippets Groups Projects
Forked from DM / dm-docs
261 commits behind, 515 commits ahead of the upstream repository.
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
ldapLinuxPlatformUtility.py 11.66 KiB
#!/usr/bin/env python

import grp
import ldap
import ldap.modlist
import copy

from dm.common.utility.loggingManager import LoggingManager
from dm.common.exceptions.configurationError import ConfigurationError
from dm.common.exceptions.internalError import InternalError
from dm.common.exceptions.authenticationError import AuthenticationError
from dm.common.exceptions.communicationError import CommunicationError
from dmSubprocess import DmSubprocess

class LdapLinuxPlatformUtility:

    SUDO_CMD = '/usr/bin/sudo'
    GROUPADD_CMD = '/usr/sbin/groupadd'
    USERMOD_CMD = '/usr/sbin/usermod'
    SETFACL_CMD = '/usr/bin/setfacl'
    CHOWN_CMD = '/bin/chown'
    GPASSWD_CMD = '/usr/bin/gpasswd'

    def __init__(self, serverUrl, adminDn, adminPasswordFile, groupDnFormat, minGidNumber=None):
        self.serverUrl = serverUrl
        self.adminDn = adminDn
        self.groupDnFormat = groupDnFormat
        self.minGidNumber = minGidNumber
        self.getLogger().debug('Min GID number: %s' % minGidNumber)
        # Remove first entry from the dn format to get tree base
        self.groupBaseDn = ','.join(groupDnFormat.split(',')[1:])
        self.getLogger().debug('Group base DN: %s' % self.groupBaseDn)
        self.getLogger().debug('Using LDAP Admin password file: %s' % adminPasswordFile)
        self.adminPassword = open(adminPasswordFile, 'r').readline().strip()
        if not self.adminPassword:
            raise ConfigurationError('LDAP password could not be found in %s file' % adminPasswordFile) 
        self.ldapClient = None

    @classmethod
    def getLogger(cls):
        logger = LoggingManager.getInstance().getLogger(cls.__name__)
        return logger

    @classmethod
    def executeSudoCommand(cls, cmd):
        p = DmSubprocess('%s %s' % (cls.SUDO_CMD, cmd))
        p.run()

    def getLdapClient(self):
        if self.ldapClient is not None:
            try:
                self.ldapClient.simple_bind_s(self.adminDn, self.adminPassword)
            except Exception, ex:
                self.getLogger().error('Invalidating LDAP client due to error: %s' % ex)
                self.unbind(self.ldapClient)
                self.ldapClient = None

        if not self.ldapClient:
            self.ldapClient = self.bind(self.serverUrl, self.adminDn, self.adminPassword)
        return self.ldapClient

    @classmethod 
    def bind(cls, serverUrl, adminDn, adminPassword):
        try:
            ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER)
            ldapClient = ldap.initialize(serverUrl)
            ldapClient.set_option(ldap.OPT_REFERRALS,0)
            ldapClient.set_option(ldap.OPT_PROTOCOL_VERSION, ldap.VERSION3)
            ldapClient.simple_bind_s(adminDn, adminPassword)
            cls.getLogger().debug('Successful binding with LDAP DN: %s' % adminDn)
            return ldapClient
        except ldap.INVALID_CREDENTIALS, ex:
            ldapClient.unbind()
            raise AuthenticationError('Invalid LDAP credentials for admin user %s' % adminDn)
        except ldap.SERVER_DOWN, ex:
            raise CommunicationError('Cannot reach LDAP server %s' % serverUrl)
        except Exception, ex:
            raise InternalError('Unrecognized exception while binding to LDAP server %s: %s' % (serverUrl, ex))

    @classmethod 
    def unbind(cls, ldapClient):
        try:
            ldapClient.unbind()
        except Exception, ex:
            cls.getLogger().error('Could not unbind LDAP client: %s' % ex)

    def createGroup(self, name):
        """ Create group if it does not exist. """
        logger = self.getLogger()
        ldapClient = self.getLdapClient()
        name = str(name)
        try:
            groupDn = self.groupDnFormat % name
            logger.debug('Looking for group DN: %s' % groupDn)
            # this method will throw exception if group is not found
            resultList = ldapClient.search_s(groupDn, ldap.SCOPE_BASE)
            groupTuple = resultList[0]
            logger.debug('Group %s already exists' % groupTuple[0])
            return
        except ldap.NO_SUCH_OBJECT, ex:
            logger.debug('Group DN %s must be created' % groupDn)
        except Exception, ex:
            raise InternalError(exception=ex)

        # determine gidNumber: look through all entries to get max value,
        # then increment it
        # ldap should really be configured to handle gid's automatically,
        # and should prevent invalid entries
        try:
            logger.debug('Looking for max group id')
            resultList = ldapClient.search_s(self.groupBaseDn, ldap.SCOPE_ONELEVEL, attrlist=['gidNumber'])
            maxGid = 0
            if self.minGidNumber:
                maxGid = self.minGidNumber 
            for result in resultList:
                gidList = result[1].get('gidNumber', [])
                gid = 0
                if gidList:
                    gid = int(gidList[0])

                if gid > maxGid:
                    maxGid = gid
            gidNumber = str(maxGid + 1)
            logger.debug('Max GID is %s, new group id will be %s' % (maxGid, gidNumber)) 
        except Exception, ex:
            raise InternalError(exception=ex)
        
        attrs = {}
        attrs['objectclass'] = ['posixGroup','top']
        attrs['cn'] = name
        attrs['gidNumber'] = [gidNumber]
        attrs['memberUid'] = []
        try:
            groupLdif = ldap.modlist.addModlist(attrs)
            ldapClient.add_s(groupDn, groupLdif)
        except Exception, ex:
            logger.error('Could not add group %s: %s' % (groupDn, ex))
            raise InternalError(exception=ex)

    def addUserToGroup(self, username, groupName):
        """ Add user to group. """
        logger = self.getLogger()
        ldapClient = self.getLdapClient()
        username = str(username)
        groupName = str(groupName)
        try:
            groupDn = self.groupDnFormat % groupName
            resultList = ldapClient.search_s(groupDn, ldap.SCOPE_BASE)
            groupTuple = resultList[0]
            groupAttrs = groupTuple[1]
            memberUidList = groupAttrs.get('memberUid', [])
            if username in memberUidList:
                logger.debug('Group %s already contains user %s' % (groupName, username))
                return
        except Exception, ex:
            raise InternalError(exception=ex)
        logger.debug('Adding user %s to group %s' % (username, groupName))
        memberUidList2 = copy.copy(memberUidList)
        memberUidList2.append(username)
        groupAttrs2 = copy.copy(groupAttrs)
        groupAttrs2['memberUid'] = memberUidList2
        try:
            groupLdif = ldap.modlist.modifyModlist(groupAttrs, groupAttrs2)
            ldapClient.modify_s(groupDn, groupLdif)
        except Exception, ex:
            logger.error('Could not add user %s to group %s: %s' % (username, groupName, ex))
            raise InternalError(exception=ex)

    def deleteUserFromGroup(self, username, groupName):
        """ Remove user from group. """
        logger = self.getLogger()
        ldapClient = self.getLdapClient()
        username = str(username)
        groupName = str(groupName)
        try:
            groupDn = self.groupDnFormat % groupName
            resultList = ldapClient.search_s(groupDn, ldap.SCOPE_BASE)
            groupTuple = resultList[0]
            groupAttrs = groupTuple[1]
            memberUidList = groupAttrs.get('memberUid', [])
            if username not in memberUidList:
                logger.debug('Group %s does not contain user %s' % (groupName, username))
                return
        except Exception, ex:
            raise InternalError(exception=ex)
        logger.debug('Adding user %s to group %s' % (username, groupName))
        memberUidList2 = copy.copy(memberUidList)
        memberUidList2.remove(username)
        groupAttrs2 = copy.copy(groupAttrs)
        groupAttrs2['memberUid'] = memberUidList2
        try:
            groupLdif = ldap.modlist.modifyModlist(groupAttrs, groupAttrs2)
            ldapClient.modify_s(groupDn, groupLdif)
        except Exception, ex:
            logger.error('Could not remove user %s from group %s: %s' % (username, groupName, ex))
            raise InternalError(exception=ex)

    @classmethod
    def createLocalGroup(cls, name):
        """ Create local group if it does not exist. """
    @classmethod
    def createLocalGroup(cls, name):
        """ Create local group if it does not exist. """
        logger = cls.getLogger()
        try:
            group = grp.getgrnam(name)
            logger.debug('Group %s already exists' % name)
            return
        except KeyError, ex:
            # ok, we need to create group
            pass
        logger.debug('Creating group %s' % name)
        cmd = '%s %s' % (cls.GROUPADD_CMD, name)
        cls.executeSudoCommand(cmd)

    @classmethod
    def addLocalUserToGroup(cls, username, groupName):
        """ Add local user to group. """
        cls.addUserToGroup(username, groupName)

    @classmethod
    def deleteLocalUserFromGroup(cls, username, groupName):
        """ Remove local user from group. """
        cls.deleteUserFromGroup(username, groupName)

    def getGroupInfo(self, groupName):
        """ Get given group info. """
        logger = self.getLogger()
        ldapClient = self.getLdapClient()
        groupName = str(groupName)
        try:
            groupDn = self.groupDnFormat % groupName
            resultList = ldapClient.search_s(groupDn, ldap.SCOPE_BASE)
            groupTuple = resultList[0]
            groupAttrs = groupTuple[1]
            return groupTuple
        except Exception, ex:
            raise InternalError(exception=ex)

    def setGroupUsers(self, groupName, usernameList):
        """ Set list of users for a given group. """
        logger = self.getLogger()
        ldapClient = self.getLdapClient()
        groupName = str(groupName)
        try:
            groupDn = self.groupDnFormat % groupName
            resultList = ldapClient.search_s(groupDn, ldap.SCOPE_BASE)
            groupTuple = resultList[0]
            groupAttrs = groupTuple[1]
        except Exception, ex:
            raise InternalError(exception=ex)
        logger.debug('Setting users %s for group %s' % (usernameList, groupName))
        memberUidList = []
        for username in usernameList:
            memberUidList.append(str(username))
        groupAttrs2 = copy.copy(groupAttrs)
        groupAttrs2['memberUid'] = memberUidList
        try:
            groupLdif = ldap.modlist.modifyModlist(groupAttrs, groupAttrs2)
            ldapClient.modify_s(groupDn, groupLdif)
        except Exception, ex:
            logger.error('Could not set users %s for group %s: %s' % (usernameList, groupName, ex))
            raise InternalError(exception=ex)

    @classmethod
    def setPathReadExecutePermissionsForGroup(cls, path, groupName):
        """ Set path permissions for the given group. """
        logger = cls.getLogger()
        logger.debug('Allowing group %s to read/execute path %s' % (groupName, path))
        cmd = '%s -m group\:%s\:rx %s' % (cls.SETFACL_CMD, groupName, path)
        cls.executeSudoCommand(cmd)

    @classmethod
    def changePathGroupOwner(cls, path, groupName):
        logger = cls.getLogger()
        logger.debug('Changing group owner to %s for path %s' % (groupName, path))
        cmd = '%s -R \:%s %s' % (cls.CHOWN_CMD, groupName, path)
        cls.executeSudoCommand(cmd)

#######################################################################
# Testing.

if __name__ == '__main__':
    utility = LdapLinuxPlatformUtility('ldaps://dmid-vm.xray.aps.anl.gov:636', 'uid=dmadmin,ou=People,o=aps.anl.gov,dc=aps,dc=anl,dc=gov', '/tmp/ldapPassword', groupDnFormat='cn=%s,ou=DM,ou=Group,o=aps.anl.gov,dc=aps,dc=anl,dc=gov', minGidNumber=66000)
    print utility.getGroupInfo(u'satija201510')