/* See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * Esri Inc. licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.esri.gpt.framework.security.identity.ldap; import java.sql.SQLException; import java.util.ArrayList; import javax.naming.NameAlreadyBoundException; import javax.naming.NamingEnumeration; import javax.naming.NamingException; import javax.naming.directory.Attribute; import javax.naming.directory.Attributes; import javax.naming.directory.BasicAttribute; import javax.naming.directory.BasicAttributes; import javax.naming.directory.DirContext; import javax.naming.directory.ModificationItem; import com.esri.gpt.framework.collection.StringSet; import com.esri.gpt.framework.context.RequestContext; import com.esri.gpt.framework.jsf.FacesContextBroker; import com.esri.gpt.framework.jsf.MessageBroker; import com.esri.gpt.framework.security.credentials.CredentialPolicyException; import com.esri.gpt.framework.security.credentials.UsernamePasswordCredentials; import com.esri.gpt.framework.security.identity.IdentityException; import com.esri.gpt.framework.security.principal.Role; import com.esri.gpt.framework.security.principal.Roles; import com.esri.gpt.framework.security.principal.User; import com.esri.gpt.framework.security.principal.UserAttribute; import com.esri.gpt.framework.security.principal.UserAttributeMap; import com.esri.gpt.framework.util.LogUtil; import com.esri.gpt.framework.util.Val; /** * Handles functionality related to editing an LDAP identity store. */ public class LdapEditFunctions extends LdapFunctions { // class variables ============================================================= // instance variables ========================================================== // constructors ================================================================ /** Default constructor. */ protected LdapEditFunctions() { super(); } /** * Construct with a supplied configuration. * @param configuration the configuration */ protected LdapEditFunctions(LdapConfiguration configuration) { super(configuration); } // properties ================================================================== // methods ===================================================================== /** * Adds and attribute(s) to an LDAP object. * @param dirContext the directory context * @param objectDN the distinguished name for the object to modify * @param attributes the attribute collection to add * @throws NamingException if an exception occurs */ protected void addAttribute(DirContext dirContext, String objectDN, Attributes attributes) throws NamingException { modifyEntry(dirContext,objectDN,DirContext.ADD_ATTRIBUTE,attributes); } /** * Adds an entry to LDAP. * @param dirContext the directory context * @param objectDN the distinguished name for the new entry * @param attributes the attributes for the new entry * @throws NamingException if an exception occurs */ protected void addEntry(DirContext dirContext, String objectDN, Attributes attributes) throws NamingException { dirContext.createSubcontext(objectDN,attributes); } /** * Modifies the attribute(s) for an LDAP object. * @param dirContext the directory context * @param objectDN the distinguished name for the object to modify * @param operation the operation to perform * DirContext.[ADD_ATTRIBUTE|REPLACE_ATTRIBUTE|REMOVE_ATTRIBUTE] * @param attributes the attribute collection to modify * @throws NamingException if an exception occurs */ private void modifyEntry(DirContext dirContext, String objectDN, int operation, Attributes attributes) throws NamingException { try{ dirContext.modifyAttributes(objectDN,operation,attributes); }catch(javax.naming.directory.InvalidAttributeValueException iave){ LogUtil.getLogger().severe(iave.getMessage()); FacesContextBroker contextBroker = new FacesContextBroker(); MessageBroker msgBroker = contextBroker.extractMessageBroker(); String errMsg = "javax.naming.directory.InvalidAttributeValueException"; if(msgBroker != null){ errMsg = msgBroker.getMessage("javax.naming.directory.InvalidAttributeValueException").getSummary(); } throw new LdapException(errMsg); } } /** * Prepares attributes for a user that is about to be registered. * @param credentials the user credentials * @param localMap the user profile attribute map */ private Attributes prepareRegistrationAttributes(UsernamePasswordCredentials credentials, UserAttributeMap localMap) { BasicAttributes ldapAttributes = new BasicAttributes(); LdapUserProperties userProps = getConfiguration().getUserProperties(); LdapNameMapping nameMap = userProps.getUserProfileMapping(); // append required object classes Attribute objectClasses = userProps.getUserObjectClasses(); if ((objectClasses != null) && (objectClasses.size() > 0)) { ldapAttributes.put(objectClasses); } // add the username and password from the credentials // ignore those profile attributes that do not have a corresponding LDAP key String sUsername = credentials.getUsername(); String sPassword = credentials.encryptLdapPassword( userProps.getPasswordEncryptionAlgorithm()); localMap.set(UserAttributeMap.TAG_USER_NAME,sUsername); localMap.set(UserAttributeMap.TAG_USER_PASSWORD,sPassword); // append all attributes of the supplied user profile, // ignore those profile attributes that do not have a corresponding LDAP key BasicAttribute basicAttr; boolean bHasCN = false; for (UserAttribute localAttr: localMap.values()) { String sLdapKey = nameMap.findLdapName(localAttr.getKey()); String sLocalValue = localAttr.getValue(); if ((sLdapKey.length() > 0) && (sLocalValue != null) && (sLocalValue.length() > 0)) { basicAttr = new BasicAttribute(sLdapKey,localAttr.getValue()); ldapAttributes.put(basicAttr); if (sLdapKey.equalsIgnoreCase("cn")) { bHasCN = false; } } } // ensure that a CN was added if (!bHasCN) { basicAttr = new BasicAttribute("cn",sUsername); ldapAttributes.put(basicAttr); } return ldapAttributes; } /** * Recovers a password. * <br/>The password is not actually recovered from LDAP, * a new password is generated and written to LDAP, the new password * is returned within the credentials. * @param dirContext the directory context * @param username the username * @param emailAddress the email address * @return the user associated with the recovered credentials (null if no match) * @throws NamingException if an LDAP naming exception occurs */ protected User recoverUserPassword(DirContext dirContext, String username, String emailAddress) throws NamingException { User userFound = null; UsernamePasswordCredentials credentials = null; username = Val.chkStr(username); emailAddress = Val.chkStr(emailAddress); if ((username.length() > 0) && (emailAddress.length() > 0)) { LdapQueryFunctions queryFunctions = new LdapQueryFunctions(getConfiguration()); LdapUserProperties userProps = getConfiguration().getUserProperties(); boolean bMultipleFound = false; String sBaseDN = userProps.getUserSearchDIT(); String sFilter = userProps.returnUserLoginSearchFilter(username); StringSet ssDNs = queryFunctions.searchDNs(dirContext,sBaseDN,sFilter); // loop through each DN found, check for an email address match for (String sDN: ssDNs) { User userTmp = new User(); userTmp.setDistinguishedName(sDN); queryFunctions.readUserProfile(dirContext,userTmp); if (userTmp.getProfile().getEmailAddress().equals(emailAddress)) { if (userFound == null) { credentials = new UsernamePasswordCredentials(); credentials.setUsername(username); credentials.generatePassword(); userFound = userTmp; userFound.setCredentials(credentials); } else { bMultipleFound = true; userFound = null; break; } } } if (userFound != null) { updateUserPassword(dirContext,userFound,credentials); } else if (bMultipleFound) { String sMsg = "Multiple LDAP usernames with same email address were located: "+ "username:"+username+ " emailAddress="+emailAddress; LogUtil.getLogger().warning(sMsg); } } return userFound; } /** * Extract the request context. * @return the request context */ public RequestContext extractRequestContext() { return RequestContext.extract(null); } /** * Register a new user. * @param dirContext the directory context * @param user the subject user * @throws CredentialPolicyException if the username or password is empty * @throws NamingException if an LDAP naming exception occurs * @throws NameAlreadyBoundException if the new user DN already exists */ protected void registerUser(DirContext dirContext, User user) throws CredentialPolicyException, NamingException, NameAlreadyBoundException { // initialize user.setDistinguishedName(""); LdapUserProperties userProps = getConfiguration().getUserProperties(); LdapGroupProperties groupProps = getConfiguration().getGroupProperties(); UsernamePasswordCredentials upCreds; upCreds = user.getCredentials().getUsernamePasswordCredentials(); if (upCreds != null) { user.setDistinguishedName(userProps.returnNewUserDN(upCreds.getUsername())); } if (upCreds == null) { throw new CredentialPolicyException("The credentials were not supplied."); } else if (user.getDistinguishedName().length() == 0) { throw new CredentialPolicyException("The supplied username is invalid."); } else if ((upCreds.getPassword() == null) || (upCreds.getPassword().length() == 0)) { throw new CredentialPolicyException("The supplied password is invalid."); } // prepare attributes and add the new user to LDAP Attributes attributes = prepareRegistrationAttributes(upCreds,user.getProfile()); addEntry(dirContext,user.getDistinguishedName(),attributes); // add user to general user group Roles configuredRoles = getConfiguration().getIdentityConfiguration().getConfiguredRoles(); if (configuredRoles.getAuthenticatedUserRequiresRole()) { String sRoleRegistered = configuredRoles.getRegisteredUserRoleKey(); Role roleRegistered = configuredRoles.get(sRoleRegistered); String sGeneralDN = roleRegistered.getDistinguishedName(); String sGroupAttribute = groupProps.getGroupMemberAttribute(); BasicAttribute groupAttribute = new BasicAttribute(sGroupAttribute); BasicAttributes groupAttributes = new BasicAttributes(); groupAttribute.add(user.getDistinguishedName()); groupAttributes.put(groupAttribute); addAttribute(dirContext,sGeneralDN,groupAttributes); } } /** * Adds user to role. * @param dirContext the directory context * @param user the subject user * @param role the role key for the role * @throws CredentialPolicyException if the username or password is empty * @throws NamingException if an LDAP naming exception occurs * @throws NameAlreadyBoundException if the new user DN already exists */ protected void addUserToRole(DirContext dirContext, User user, String role) throws CredentialPolicyException, NamingException { //TODO: need to check if the user is already in role. // add user to general user group Roles configuredRoles = getConfiguration().getIdentityConfiguration().getConfiguredRoles(); Role roleRegistered = configuredRoles.get(role); String sGeneralDN = roleRegistered.getDistinguishedName(); addUserToGroup(dirContext, user, sGeneralDN); } /** * Adds user to group. * @param dirContext the directory context * @param user the subject user * @param groupDn the dn for the group * @throws CredentialPolicyException if the username or password is empty * @throws NamingException if an LDAP naming exception occurs * @throws NameAlreadyBoundException if the new user DN already exists */ protected void addUserToGroup(DirContext dirContext, User user, String groupDn) throws CredentialPolicyException, NamingException { // initialize LdapGroupProperties groupProps = getConfiguration().getGroupProperties(); // add user to general user group String sGroupAttribute = groupProps.getGroupMemberAttribute(); BasicAttribute groupAttribute = new BasicAttribute(sGroupAttribute); BasicAttributes groupAttributes = new BasicAttributes(); groupAttribute.add(user.getDistinguishedName()); groupAttributes.put(groupAttribute); addAttribute(dirContext,groupDn,groupAttributes); /* Roles configuredRoles = getConfiguration().getIdentityConfiguration().getConfiguredRoles(); for (Role role : configuredRoles.values()){ if(role.getDistinguishedName().equalsIgnoreCase(groupDn)){ String accessKeyAttribute = Val.chkStr(role.getAccessKey()); if(accessKeyAttribute.length() > 0){ BasicAttribute accessAttr = new BasicAttribute(accessKeyAttribute); BasicAttributes attributes = new BasicAttributes(); attributes.put(accessAttr); try { removeEntry(dirContext,user.getDistinguishedName(), attributes); }catch(javax.naming.directory.AttributeInUseException aue){} catch(javax.naming.directory.NoSuchAttributeException nse){} break; } } }*/ } /** * Removes an attribute(s) from an LDAP object. * @param dirContext the directory context * @param objectDN the distinguished name for the object to modify * @param attributes the attribute collection to remove * @throws NamingException if an exception occurs */ protected void removeEntry(DirContext dirContext, String objectDN, Attributes attributes) throws NamingException { modifyEntry(dirContext,objectDN,DirContext.REMOVE_ATTRIBUTE,attributes); } /** * Updates the profile attributes for a user. * @param dirContext the directory context * @param user the subject user * @param considerUsername true if the username should be considered for update * @param considerPassword true if the password should be considered for update * @throws NamingException if an LDAP naming exception occurs */ protected void updateUserProfile(DirContext dirContext, User user, boolean considerUsername, boolean considerPassword) throws NamingException { // initialize ArrayList<ModificationItem> alModItems = new ArrayList<ModificationItem>(); Attributes ldapAttributes = null; String sUserDN = user.getDistinguishedName(); if (sUserDN.length() > 0) { ldapAttributes = dirContext.getAttributes(sUserDN); } if (ldapAttributes != null) { // iterate through the attributes of the supplied user profile LdapNameMapping nameMap = getConfiguration().getUserProperties().getUserProfileMapping(); UserAttributeMap localMap = user.getProfile(); ModificationItem modItem; for (UserAttribute localAttr: localMap.values()) { // determine the local and LDAP keys String sLocalKey = localAttr.getKey(); String sLocalValue = localAttr.getValue(); String sLdapKey = nameMap.findLdapName(localAttr.getKey()); if ((sLocalValue == null) || (sLocalValue.length() == 0)) { sLocalValue = null; } //System.err.println("sLocalKey="+sLocalKey+" sLdapKey="+sLdapKey); if (sLocalKey.equalsIgnoreCase(UserAttributeMap.TAG_USER_NAME)) { if (!considerUsername) sLdapKey = ""; } else if (sLocalKey.equalsIgnoreCase(UserAttributeMap.TAG_USER_PASSWORD)) { if (!considerPassword) sLdapKey = ""; } if (sLdapKey.length() > 0) { // if the attribute exists in LDAP then replace the value, // otherwise create a new attribute Attribute ldapAttribute = ldapAttributes.get(sLdapKey); if (ldapAttribute != null) { if (!ldapAttribute.isOrdered()) { ldapAttribute.clear(); ldapAttribute.add(sLocalValue); } else { ldapAttribute.set(0,sLocalValue); } modItem = new ModificationItem(DirContext.REPLACE_ATTRIBUTE,ldapAttribute); alModItems.add(modItem); } else { if ((sLocalValue != null) && (sLocalValue.length() > 0)) { BasicAttribute basicAttr = new BasicAttribute(sLdapKey,sLocalValue); modItem = new ModificationItem(DirContext.ADD_ATTRIBUTE,basicAttr); alModItems.add(modItem); } } } else { // no associated LDAP key, we won't throw an exception in this case } } } // execute the LDAP modification if modification items exist if (alModItems.size() > 0) { ModificationItem[] modItems = (ModificationItem[])alModItems.toArray( new ModificationItem[0]); dirContext.modifyAttributes(sUserDN,modItems); } } /** * Updates the password for a user. * @param dirContext the directory context * @param user the subject user * @param newCredentials the credentials containing the new password * @throws NamingException if an LDAP naming exception occurs */ protected void updateUserPassword(DirContext dirContext, User user, UsernamePasswordCredentials newCredentials) throws NamingException { User userUpd = new User(); userUpd.setDistinguishedName(user.getDistinguishedName()); String sPassword = newCredentials.encryptLdapPassword( getConfiguration().getUserProperties().getPasswordEncryptionAlgorithm()); userUpd.getProfile().set(UserAttributeMap.TAG_USER_PASSWORD,sPassword); updateUserProfile(dirContext,userUpd,false,true); } /** * Removes user from group. * @param user the subject user * @param groupDn the distinguishedName for the ldap group * @throws CredentialPolicyException if the credentials are invalid * @throws IdentityException if a system error occurs preventing the action * @throws NamingException if an LDAP naming exception occurs * @throws SQLException if a database communication exception occurs */ protected void removeUserFromGroup(DirContext dirContext,User user, String groupDn) throws CredentialPolicyException, IdentityException, NamingException, SQLException { LdapClient client = null; ArrayList<ModificationItem> alModItems = new ArrayList<ModificationItem>(); ModificationItem[] modItems = null; try { Attributes attributes = dirContext.getAttributes(groupDn); // initialize ModificationItem modItem; try { if (attributes != null) { Attribute attr = attributes.get("uniqueMember"); NamingEnumeration<?> vals = attr.getAll();; while (vals.hasMore()) { String val = (String) vals.next(); if(val.equalsIgnoreCase(user.getDistinguishedName())){ attr.remove(val); } } modItem = new ModificationItem(DirContext.REPLACE_ATTRIBUTE,attr); alModItems.add(modItem); } } finally { } // execute the LDAP modification if modification items exist if (alModItems.size() > 0) { modItems = (ModificationItem[])alModItems.toArray( new ModificationItem[0]); dirContext.modifyAttributes(groupDn,modItems); } } finally { if (client != null) client.close(); } } }