diff --git a/src/python/dm/aps_user_db/cli/__init__.py b/src/python/dm/aps_user_db/cli/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/python/dm/aps_user_db/cli/updateUserFromApsDbCli.py b/src/python/dm/aps_user_db/cli/updateUserFromApsDbCli.py new file mode 100755 index 0000000000000000000000000000000000000000..130333f68bd437eac6b547ca456f90811d8da30d --- /dev/null +++ b/src/python/dm/aps_user_db/cli/updateUserFromApsDbCli.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python + +from dm.common.exceptions.invalidRequest import InvalidRequest +from dm.common.cli.dmCli import DmCli +from dm.common.utility.ldapUserManager import LdapUserManager +from dm.aps_user_db.api.apsUserDbApi import ApsUserDbApi + +class UpdateUserFromApsDbCli(DmCli): + def __init__(self): + DmCli.__init__(self) + self.addOption('', '--badge', dest='badge', help='User badge number.') + + def checkArgs(self): + if self.options.badge is None: + raise InvalidRequest('Badge number must be provided.') + + def getBadge(self): + return self.options.badge + + def runCommand(self): + self.parseArgs(usage=""" + dm-update-user-from-aps-db --badge=BADGE + +Description: + Updates user in DM LDAP and database using information from APS DB. + """) + self.checkArgs() + apsUserDbApi = ApsUserDbApi() + badgeNumber = self.getBadge() + username = 'd%s' % badgeNumber + apsUser = apsUserDbApi.getApsUserByBadgeNumber(badgeNumber) + print 'APS User Info: ', apsUser.getDisplayString(self.getDisplayKeys(), self.getDisplayFormat()) + ldapUserManager = 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) + ldapUser = ldapUserManager.getUserInfo(username) + print 'Old LDAP User Info: ', ldapUser + attrDict = {} + attrDict['gecos'] = apsUser['name'] + attrDict['userPassword'] = apsUser['passwordHashValue'] + ldapUserManager.modifyUserInfo(username, attrDict) + ldapUser = ldapUserManager.getUserInfo(username) + print 'New LDAP User Info: ', ldapUser + +####################################################################### +# Run command. +if __name__ == '__main__': + cli = UpdateUserFromApsDbCli() + cli.run() diff --git a/src/python/dm/common/utility/ldapClient.py b/src/python/dm/common/utility/ldapClient.py new file mode 100755 index 0000000000000000000000000000000000000000..2a9df07603eeb59b703f75a0c8d599b292bf1296 --- /dev/null +++ b/src/python/dm/common/utility/ldapClient.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python + +import ldap +import re + +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.objectNotFound import ObjectNotFound +from dm.common.exceptions.authenticationError import AuthenticationError +from dm.common.exceptions.communicationError import CommunicationError +from dm.common.exceptions.dmException import DmException + +class LdapClient: + + LDAP_ERROR_REGEX_LIST = [ + (re.compile('.*No such object.*'),ObjectNotFound), + (re.compile('.*'),DmException) + ] + + def __init__(self, serverUrl, adminDn, adminPasswordFile): + self.serverUrl = serverUrl + self.adminDn = adminDn + 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 + + # Decorator for all LDAP transactions + @classmethod + def executeLdapCall(cls, func): + def ldapCall(*args, **kwargs): + try: + result = func(*args, **kwargs) + return result + except DmException, ex: + raise + except Exception, ex: + error = str(ex) + for (pattern,dmExClass) in LdapClient.LDAP_ERROR_REGEX_LIST: + if pattern.match(error): + raise dmExClass(exception=ex) + raise DmException(exception=ex) + return ldapCall + + @classmethod + def getLogger(cls): + logger = LoggingManager.getInstance().getLogger(cls.__name__) + return logger + + 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) + +####################################################################### +# Testing. + +if __name__ == '__main__': + pass + diff --git a/src/python/dm/common/utility/ldapUserManager.py b/src/python/dm/common/utility/ldapUserManager.py new file mode 100755 index 0000000000000000000000000000000000000000..7c551e2014d0b400374921e7a1cf16d2cf232956 --- /dev/null +++ b/src/python/dm/common/utility/ldapUserManager.py @@ -0,0 +1,248 @@ +#!/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.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.dmException import DmException +from ldapClient import LdapClient + +class LdapUserManager(LdapClient): + + 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 decodePasswordHash(cls, b64EncodedString): + decodedString = b64EncodedString.replace('{SHA}','') + 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) + userTuple = resultList[0] + return userTuple + + def modifyUserInfo(self, username, attrDict): + """ Modify user. """ + logger = self.getLogger() + ldapClient = self.getLdapClient() + userDn,userAttrs = self.getUserInfo(username) + logger.debug('Modifying user %s attrs %s' % (username, attrDict)) + userAttrs2 = copy.copy(userAttrs) + for name,value in attrDict.items(): + if not userAttrs2.has_key(name): + raise InvalidArgument('No such attribute: %s' % name) + if type(value) == types.ListType: + userAttrs2[name] = value + else: + if name == 'userPassword': + value = self.encodePasswordHash(value) + userAttrs2[name] = [str(value)] + + userLdif = ldap.modlist.modifyModlist(userAttrs, userAttrs2) + ldapClient.modify_s(userDn, userLdif) + + 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 + utility.modifyUserInfo(u'd225159', {'homeDirectory' : '/data'}) + user = utility.getUserInfo(u'd225159') + print user + user = utility.getUserInfo(u'd65114') + print user + + passwordHash = LdapUserManager.decodePasswordHash(user[1]['userPassword'][0]) + print passwordHash + #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') +