#!/usr/bin/env python import ldap import ldap.modlist import copy import re import types from base64 import b16encode from base64 import b16decode from base64 import b64encode from base64 import b64decode from dm.common.objects.ldapUserInfo import LdapUserInfo from dm.common.utility.loggingManager import LoggingManager from dm.common.utility.configurationManager import ConfigurationManager from dm.common.exceptions.configurationError import ConfigurationError from dm.common.exceptions.internalError import InternalError from dm.common.exceptions.objectNotFound import ObjectNotFound from dm.common.exceptions.authenticationError import AuthenticationError from dm.common.exceptions.communicationError import CommunicationError from dm.common.exceptions.invalidArgument import InvalidArgument from dm.common.exceptions.objectAlreadyExists import ObjectAlreadyExists from dm.common.exceptions.dmException import DmException from ldapClient import LdapClient class LdapUserManager(LdapClient): CONFIG_SECTION_NAME = 'LdapUserManager' SERVER_URL_KEY = 'serverurl' ADMIN_DN_KEY = 'admindn' ADMIN_PASSWORD_FILE_KEY = 'adminpasswordfile' USER_DN_FORMAT_KEY = 'userdnformat' GROUP_DN_FORMAT_KEY = 'groupdnformat' MIN_GID_NUMBER_KEY = 'mingidnumber' def __init__(self, serverUrl, adminDn, adminPasswordFile, userDnFormat, groupDnFormat, minGidNumber=None): LdapClient.__init__(self, serverUrl, adminDn, adminPasswordFile) self.userDnFormat = userDnFormat 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) @classmethod def createInstance(cls): cm = ConfigurationManager.getInstance() logger = cls.getLogger() logger.debug('Creating LDAP User Manager instance') serverUrl = cm.getConfigOption(LdapUserManager.CONFIG_SECTION_NAME, LdapUserManager.SERVER_URL_KEY) #logger.debug('Using server URL: %s' % serverUrl) adminDn = cm.getConfigOption(LdapUserManager.CONFIG_SECTION_NAME, LdapUserManager.ADMIN_DN_KEY) #logger.debug('Using admin DN: %s' % adminDn) adminPasswordFile = cm.getConfigOption(LdapUserManager.CONFIG_SECTION_NAME, LdapUserManager.ADMIN_PASSWORD_FILE_KEY) #logger.debug('Using admin password file: %s' % adminPasswordFile) userDnFormat = cm.getConfigOption(LdapUserManager.CONFIG_SECTION_NAME, LdapUserManager.USER_DN_FORMAT_KEY) #logger.debug('Using user DN format: %s' % userDnFormat) groupDnFormat = cm.getConfigOption(LdapUserManager.CONFIG_SECTION_NAME, LdapUserManager.GROUP_DN_FORMAT_KEY) #logger.debug('Using group DN format: %s' % groupDnFormat) minGidNumber = cm.getConfigOption(LdapUserManager.CONFIG_SECTION_NAME, LdapUserManager.MIN_GID_NUMBER_KEY) #logger.debug('Using min GID number: %s' % minGidNumber) return LdapUserManager(serverUrl, adminDn, adminPasswordFile, userDnFormat, groupDnFormat, minGidNumber) @classmethod def decodePasswordHash(cls, b64EncodedString): beginEncoding = b64EncodedString.find('}') decodedString = b64EncodedString[beginEncoding+1:] decodedString = b16encode(b64decode(decodedString)).upper() return decodedString @classmethod def encodePasswordHash(cls, passwordHash): encodedString = '{SHA}'+b64encode(b16decode(passwordHash)) return encodedString @LdapClient.executeLdapCall def getUserInfo(self, username): """ Get user info. """ userDn = self.userDnFormat % str(username) ldapClient = self.getLdapClient() resultList = ldapClient.search_s(userDn, ldap.SCOPE_BASE, attrlist=['*','createTimeStamp','modifyTimestamp']) userTuple = resultList[0] return LdapUserInfo({'userDn' : userTuple[0], 'userAttrs' : userTuple[1]}) @LdapClient.executeLdapCall def getUserInfos(self): """ Get user infos. """ baseDnPos = self.userDnFormat.find(',') baseDn = self.userDnFormat[baseDnPos+1:] searchFilter = self.userDnFormat[0:baseDnPos] % '*' ldapClient = self.getLdapClient() resultList = ldapClient.search_s(baseDn, ldap.SCOPE_SUBTREE, searchFilter, attrlist=['*','createTimeStamp','modifyTimestamp']) userList = [] for result in resultList: userDn = result[0] userAttrs = result[1] userList.append(LdapUserInfo({'userDn' : userDn, 'userAttrs' : userAttrs})) return userList @LdapClient.executeLdapCall def getUserInfoMapByUid(self): """ Get user infos. """ baseDnPos = self.userDnFormat.find(',') baseDn = self.userDnFormat[baseDnPos+1:] searchFilter = self.userDnFormat[0:baseDnPos] % '*' ldapClient = self.getLdapClient() resultList = ldapClient.search_s(baseDn, ldap.SCOPE_SUBTREE, searchFilter) userMap = {} for result in resultList: userDn = result[0] userAttrs = result[1] uid = userAttrs['uid'][0] userMap[uid] = LdapUserInfo({'userDn' : userDn, 'userAttrs' : userAttrs}) return userMap @LdapClient.executeLdapCall def modifyUserInfo(self, username, attrDict): """ Modify user. """ logger = self.getLogger() ldapClient = self.getLdapClient() ldapUserInfo = self.getUserInfo(username) userDn = ldapUserInfo.get('userDn') userAttrs = ldapUserInfo.get('userAttrs') # Remove internal LDAP attributes before creating copy for modifications for key in [ 'modifyTimestamp', 'createTimeStamp' ]: if userAttrs.has_key(key): del userAttrs[key] userAttrs2 = copy.copy(userAttrs) middleName = attrDict.get('middleName', '') fullName = '%s, %s' % (attrDict.get('lastName', ''), attrDict.get('firstName', '')) if middleName: fullName = '%s %s' % (fullName, middleName) if attrDict.has_key('name'): userAttrs2['cn'] = [attrDict.get('name')] userAttrs2['revcn'] = ['%s %s' % (attrDict.get('lastName'), attrDict.get('firstName'))] userAttrs2['sn'] = [attrDict.get('lastName')] userAttrs2['givenName'] = [attrDict.get('firstName')] userAttrs2['gecos'] = [attrDict.get('name')] for key in ['loginShell', 'allowed-host', 'homeDirectory', 'home7Directory', 'gidNumber' ]: if attrDict.has_key(key): userAttrs2[key] = [attrDict.get(key)] if attrDict.has_key('grp'): userAttrs2['o'] = [attrDict.get('grp')] if attrDict.has_key('inactive'): if attrDict.get('inactive', 'N') == 'Y': userAttrs2['inetUserStatus'] = ['inactive'] else: userAttrs2['inetUserStatus'] = ['active'] passwordHash = attrDict.get('userPassword') if not passwordHash: passwordHash = attrDict.get('passwordHashValue') if passwordHash: userAttrs2['userPassword'] = [self.encodePasswordHash(passwordHash)] #logger.debug('Encoded password entry: %s' % passwordHash) logger.debug('Old user %s attrs: %s' % (username, userAttrs)) logger.debug('Modified user %s attrs: %s' % (username, userAttrs2)) userLdif = ldap.modlist.modifyModlist(userAttrs, userAttrs2) ldapClient.modify_s(userDn, userLdif) return LdapUserInfo({'userDn' : userDn, 'userAttrs' : userAttrs2}) @LdapClient.executeLdapCall def createUserInfo(self, username, attrDict): """ Create user. """ logger = self.getLogger() ldapClient = self.getLdapClient() try: ldapUserInfo = self.getUserInfo(username) raise ObjectAlreadyExists('User %s already exists.' % username) except ObjectNotFound, ex: pass middleName = attrDict.get('middleName') fullName = '%s, %s' % (attrDict.get('lastName', ''), attrDict.get('firstName', '')) if middleName: fullName = '%s %s' % (fullName, middleName) badgeNumber = attrDict.get('badgeNumber') uid = 'd%s' % badgeNumber if username != uid: raise InvalidArgument('Username %s and badge number %s are not consistent.' % (username, badgeNumber)) userAttrs = {} userAttrs['objectclass'] = ['top', 'person', 'organizationalPerson', 'inetOrgPerson', 'inetUser', 'shadowAccount', 'posixAccount', 'passwordObject', 'APSexten', 'apple-user'] userAttrs['cn'] = [attrDict.get('name', fullName)] userAttrs['revcn'] = ['%s %s' % (attrDict.get('lastName', ''), attrDict.get('firstName', ''))] userAttrs['loginShell'] = ['/sbin/nologin'] userAttrs['allowed-host'] = ['dmid-vm'] userAttrs['uidNumber'] = ['10%s' % badgeNumber] userAttrs['uid'] = [uid] userAttrs['homeDirectory'] = ['/data'] userAttrs['home7Directory'] = ['/data'] userAttrs['o'] = [attrDict.get('grp')] if attrDict.get('inactive', 'N') == 'Y': userAttrs['inetUserStatus'] = ['inactive'] else: userAttrs['inetUserStatus'] = ['active'] userAttrs['gidNumber'] = ['66001'] userAttrs['gecos'] = [attrDict.get('name', fullName)] userAttrs['sn'] = [attrDict.get('lastName', '')] userAttrs['givenName'] = [attrDict.get('firstName', '')] passwordHash = attrDict.get('userPassword') if not passwordHash: passwordHash = attrDict.get('passwordHashValue') if passwordHash: userAttrs['userPassword'] = [self.encodePasswordHash(passwordHash)] logger.debug('Creating user %s with attrs %s' % (username, userAttrs)) userDn = self.userDnFormat % str(username) userLdif = ldap.modlist.addModlist(userAttrs) ldapClient.add_s(userDn, userLdif) return LdapUserInfo({'userDn' : userDn, 'userAttrs' : userAttrs}) 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('Removing user %s from 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) 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) ####################################################################### # Testing. if __name__ == '__main__': utility = LdapUserManager('ldaps://dmid-vm.xray.aps.anl.gov:636', 'uid=dmadmin,ou=People,o=aps.anl.gov,dc=aps,dc=anl,dc=gov', '/tmp/ldapPassword', userDnFormat='uid=%s,ou=DM,ou=People,o=aps.anl.gov,dc=aps,dc=anl,dc=gov', groupDnFormat='cn=%s,ou=DM,ou=Group,o=aps.anl.gov,dc=aps,dc=anl,dc=gov', minGidNumber=66000) #print utility.getGroupInfo(u's1id-test03') user = utility.getUserInfo(u'd225159') print user print user.getUserPassword() print utility.decodePasswordHash(user.getUserPassword()) userMap = utility.getUserInfoMapByUid() print 'N entries: ', len(userMap) print userMap['d225159'] #for user in userList: # print user utility.modifyUserInfo(u'd225159', {'homeDirectory' : '/data/'}) user = utility.getUserInfo(u'd225159') print user #user = utility.getUserInfo(u'd65114') #print user #passwordHash = LdapUserManager.decodePasswordHash(user['userAttrs']['userPassword'][0]) #print passwordHash #print 'Creating user d000001' #user = utility.getUserInfo(u'd000001') #print user #attrDict = {'badgeNumber' : '000001', 'name' : 'Test, User 1.', 'firstName' : 'User', 'middleName' : '1.', 'lastName' : 'Test', 'passwordHashValue' : '84673F4A8774846B1C096511C7F6B1329CEE5CCC', 'inactive' : 'N', 'grp' : 'XSD-TEST', 'lastChangeDate' : '2015-02-03 15:52:15', 'email' : 'UTEST1@APS.ANL.GOV', 'isUserNotAnlEmployee' : 'N' } #try: # user = utility.createUserInfo(u'd000001', attrDict) #except ObjectAlreadyExists, ex: # print 'User d000001 already exists, will modify it' # user = utility.updateUserInfo(u'd000001', attrDict) #print user #print LdapUserManager.encodePasswordHash(passwordHash) #utility.addLocalUserToGroup(u'sveseli', u'id8i-test02') #print utility.getGroupInfo(u'id8i-test02') #utility.deleteLocalUserFromGroup(u'sveseli', u'id8i-test02') #print utility.getGroupInfo(u'id8i-test02')