#!/usr/bin/env python 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' 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) @classmethod def addLocalUserToGroup(cls, username, groupName): """ Add local user to group. """ logger = cls.getLogger() logger.debug('Adding local user %s to group %s' % (username, groupName)) cmd = '%s -a -G %s %s' % (cls.USERMOD_CMD, groupName, username) cls.executeSudoCommand(cmd) 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')