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')
+