/* 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 com.esri.gpt.framework.collection.StringSet; import com.esri.gpt.framework.context.RequestContext; import com.esri.gpt.framework.security.credentials.ChangePasswordCriteria; import com.esri.gpt.framework.security.credentials.CredentialPolicy; import com.esri.gpt.framework.security.credentials.CredentialPolicyException; import com.esri.gpt.framework.security.credentials.CredentialsDeniedException; import com.esri.gpt.framework.security.credentials.RecoverPasswordCriteria; import com.esri.gpt.framework.security.credentials.UsernamePasswordCredentials; import com.esri.gpt.framework.security.identity.IdentityAdapter; import com.esri.gpt.framework.security.identity.IdentityException; import com.esri.gpt.framework.security.principal.Group; import com.esri.gpt.framework.security.principal.Groups; import com.esri.gpt.framework.security.principal.User; import com.esri.gpt.framework.security.principal.Users; import java.io.IOException; import java.sql.SQLException; import java.util.ArrayList; 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; /** * Identity adapter for an LDAP based identity store. */ public class LdapIdentityAdapter extends IdentityAdapter { // class variables ============================================================= // instance variables ========================================================== // constructors ================================================================ /** Default constructor. */ public LdapIdentityAdapter() { super(); } // properties ================================================================== /** * Gets the LDAP configuration. * @return the LDAP configuration */ protected LdapConfiguration getLdapConfiguration() { return getApplicationConfiguration().getIdentityConfiguration().getLdapConfiguration(); } // methods ===================================================================== /** * Authenticates a user. * @param user the subject user * @throws CredentialsDeniedException if credentials are denied * @throws IdentityException if a system error occurs preventing authentication * @throws SQLException if a database communication exception occurs */ @Override public void authenticate(User user) throws CredentialsDeniedException, IdentityException, SQLException { LdapClient client = null; try { user.getAuthenticationStatus().reset(); client = newServiceConnection(); client.authenticate(getRequestContext(),user); } finally { if (client != null) client.close(); } } /** * Changes the password for a user. * @param user the subject user * @param criteria the criteria associated with the password change * @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 */ @Override public void changePassword(User user, ChangePasswordCriteria criteria) throws CredentialsDeniedException, CredentialPolicyException, IdentityException, NamingException, SQLException { LdapClient client = null; try { // initialize parameters String sUsername = user.getProfile().getUsername(); UsernamePasswordCredentials origCred = criteria.getOriginalCredentials(); UsernamePasswordCredentials newCred = criteria.getNewCredentials(); origCred.setUsername(sUsername); newCred.setUsername(sUsername); // ensure that the old password was supplied correctly for the user UsernamePasswordCredentials testCred = new UsernamePasswordCredentials(); testCred.setUsername(origCred.getUsername()); testCred.setPassword(origCred.getPassword()); User testUser = new User(); testUser.setCredentials(testCred); authenticate(testUser); // ensure that the new credentials are valid CredentialPolicy policy = new CredentialPolicy(); policy.validatePasswordPolicy(newCred); // update the password client = newServiceConnection(); client.getEditFunctions().updateUserPassword( client.getConnectedContext(),user,newCred); } finally { if (client != null) client.close(); } } /** * Makes a new LDAP client. * @return the new LDAP client */ protected LdapClient newLdapClient() { return new LdapClient(getLdapConfiguration()); } /** * Makes a new connected LDAP client based upon the service account credentials. * @return the new LDAP client * @throws IdentityException if a service account connection cannot be established */ protected LdapClient newServiceConnection() throws IdentityException { try { LdapClient client = newLdapClient(); client.connect(); return client; } catch (Exception e) { throw new IdentityException("Unable to connect to LDAP.",e); } } /** * Populate user profile information from ldap. * @param context the RequestContext * @param user the user to be read * @throws IdentityException if a service account connection cannot be established * @throws NamingException if an LDAP naming exception occurs * @throws SQLException if a database communication exception occurs */ public void populateUser(RequestContext context,User user)throws IdentityException, NamingException, SQLException { LdapClient client = null; try { client = newServiceConnection(); client.populateUser(context, user, ""); } finally { if (client != null) client.close(); } } /** * Reads the members of a group. * @param groupDN the distinguished name for the group * @return the collection of users belonging to the group * @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 */ @Override public Users readGroupMembers(String groupDN) throws IdentityException, NamingException, SQLException { LdapClient client = null; Users users = new Users(); try { client = newServiceConnection(); DirContext dirContext = client.getConnectedContext(); LdapQueryFunctions queryF = client.getQueryFunctions(); StringSet ssDNs = queryF.readGroupMembers(dirContext,groupDN); for (String sDN: ssDNs) { String sName = queryF.readUsername(dirContext,sDN); User user = new User(); user.setDistinguishedName(sDN); user.setKey(user.getDistinguishedName()); user.setName(sName); users.add(user); } } finally { if (client != null) client.close(); } return users; } /** * Reads the groups to which a user belongs. * @param user the subject user * @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 */ @Override public void readUserGroups(User user) throws IdentityException, NamingException, SQLException { LdapClient client = null; try { client = newServiceConnection(); client.getQueryFunctions().readUserGroups( client.getConnectedContext(),user); } finally { if (client != null) client.close(); } } /** * Reads the profile attributes for a user. * @param user the subject user * @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 */ @Override public void readUserProfile(User user) throws IdentityException, NamingException, SQLException { LdapClient client = null; try { client = newServiceConnection(); client.getQueryFunctions().readUserProfile( client.getConnectedContext(),user); } finally { if (client != null) client.close(); } } /** * Recovers a user password. * @param criteria the criteria associated with the password recovery * @return the user associated with the recovered credentials (null if no match) * @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 */ @Override public User recoverPassword(RecoverPasswordCriteria criteria) throws IdentityException, NamingException, SQLException { LdapClient client = null; try { client = newServiceConnection(); return client.getEditFunctions().recoverUserPassword( client.getConnectedContext(), criteria.getUsername(), criteria.getEmailAddress()); } finally { if (client != null) client.close(); } } /** * Registers a new user. * @param user the subject user * @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 */ @Override public void registerUser(User user) throws CredentialPolicyException, IdentityException, NamingException, SQLException { LdapClient client = null; try { // ensure that the new credentials are valid CredentialPolicy policy = new CredentialPolicy(); UsernamePasswordCredentials cred; cred = user.getCredentials().getUsernamePasswordCredentials(); policy.validateUsernamePolicy(cred); policy.validatePasswordPolicy(cred); policy.validateEmailPolicy(user.getProfile().getEmailAddress()); // register the user client = newServiceConnection(); client.getEditFunctions().registerUser(client.getConnectedContext(),user); } finally { if (client != null) client.close(); } } /** * Adds user to role. * @param user the subject user * @param role * @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 */ @Override public void addUserToRole(User user, String role) throws CredentialPolicyException, IdentityException, NamingException, SQLException { LdapClient client = null; try { // register the user client = newServiceConnection(); client.getEditFunctions().addUserToRole(client.getConnectedContext(),user, role); } finally { if (client != null) client.close(); } } /** * Adds user to group. * @param user the subject user * @param groupDn * @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 */ @Override public void addUserToGroup(User user, String groupDn) throws CredentialPolicyException, IdentityException, NamingException, SQLException { LdapClient client = null; try { // register the user client = newServiceConnection(); client.getEditFunctions().addUserToGroup(client.getConnectedContext(),user, groupDn); } finally { if (client != null) client.close(); } } /** * Removes user from group. * @param user the subject user * @param groupDn * @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 */ @Override public void removeUserFromGroup(User user, String groupDn) throws CredentialPolicyException, IdentityException, NamingException, SQLException { LdapClient client = null; try { // register the user client = newServiceConnection(); client.getEditFunctions().removeUserFromGroup(client.getConnectedContext(),user, groupDn); } finally { if (client != null) client.close(); } } /** * Adds user attribute. * @param objectDn the subject dn * @param attributeName the user attribute will be added. * @param attributeValue the user attribute value will be added. * @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 */ @Override public void addAttribute(String objectDn, String attributeName, String attributeValue) throws CredentialPolicyException, IdentityException, NamingException, SQLException { LdapClient client = null; try { // register the user client = newServiceConnection(); BasicAttributes ldapAttributes = new BasicAttributes(); BasicAttribute ldapAttribute = new BasicAttribute(attributeName,attributeValue); ldapAttributes.put(ldapAttribute); client.getEditFunctions().addAttribute(client.getConnectedContext(), objectDn, ldapAttributes); } finally { if (client != null) client.close(); } } /** * Adds user attribute. * @param objectDn the subject dn * @param attributeName the user attribute will be removed. * @param attributeValue the user attribute value will be removed * @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 */ @Override public void removeAttribute(String objectDn,String attributeName, String attributeValue) throws CredentialPolicyException, IdentityException, NamingException, SQLException { LdapClient client = null; try { // register the user client = newServiceConnection(); BasicAttributes ldapAttributes = new BasicAttributes(); BasicAttribute ldapAttribute = new BasicAttribute(attributeName,attributeValue); ldapAttributes.put(ldapAttribute); client.getEditFunctions().removeEntry(client.getConnectedContext(), objectDn, ldapAttributes); } finally { if (client != null) client.close(); } } /** * Updates the profile attributes for a user. * @param user the subject user * @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 */ @Override public void updateUserProfile(User user) throws CredentialPolicyException, IdentityException, NamingException, SQLException { LdapClient client = null; try { // ensure that the email address is valid, update the profile CredentialPolicy policy = new CredentialPolicy(); policy.validateEmailPolicy(user.getProfile().getEmailAddress()); client = newServiceConnection(); client.getEditFunctions().updateUserProfile( client.getConnectedContext(),user,false,false); } finally { if (client != null) client.close(); } } /** * Builds list of ldap users matching filter. * @param filter the user search filter for ldap * @return the list of users matching filter * @throws IdentityException if a system error occurs preventing the action * @throws NamingException if an LDAP naming exception occurs */ public Users readUsers(String filter, String attributeName) throws IdentityException, NamingException { LdapClient client = null; try { client = newServiceConnection(); return client.getQueryFunctions().readUsers(client.getConnectedContext(), filter,attributeName); } finally { if (client != null) client.close(); } } /** * Builds list of ldap groups matching filter. * @param filter the group search filter for ldap * @return the list of groups matching filter * @throws NamingException if an LDAP naming exception occurs * @throws IdentityException */ public Groups readGroups(String filter) throws NamingException, IdentityException{ LdapClient client = null; try { client = newServiceConnection(); return client.getQueryFunctions().readGroups(client.getConnectedContext(), filter); } finally { if (client != null) client.close(); } } /** * Delete user from ldap * @param user the user to be deleted from ldap. * @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 */ public void deleteUser(User user) throws CredentialPolicyException, IdentityException, NamingException, SQLException { LdapClient client = null; try { // register the user client = newServiceConnection(); removeUserFromGroups(user); client.getConnectedContext().destroySubcontext(user.getDistinguishedName()); } finally { if (client != null) client.close(); } } /** * Removes user from groups its member of. * @param user the subject user * @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 */ private void removeUserFromGroups(User user) throws CredentialPolicyException, IdentityException, NamingException, SQLException { try { Groups grps = user.getGroups(); for(Group g : grps.values()){ removeUserFromGroup(user, g.getDistinguishedName()); } } finally { } } }