All Downloads are FREE. Search and download functionalities are using the official Maven repository.

prerna.util.ldap.LdapSearchUserStructureConnection Maven / Gradle / Ivy

The newest version!
package prerna.util.ldap;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attributes;
import javax.naming.directory.BasicAttribute;
import javax.naming.directory.DirContext;
import javax.naming.directory.ModificationItem;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import prerna.auth.AccessToken;
import prerna.util.Constants;
import prerna.util.SocialPropertiesUtil;

/**
 * Assumes that the authentication for each user has the same DN structure
 * but only the CN will change based on the user input
 */
public class LdapSearchUserStructureConnection extends AbstractLdapAuthenticator {

	private static final Logger classLogger = LogManager.getLogger(LdapSearchUserStructureConnection.class);

	String providerUrl = null;

	// if multi structure searching
	// need to have an application login to search for the user CN
	// and then try to login as the user
	String applicationMasterPrincipal = null;
	String applicationMasterCredentials = null;
	transient DirContext applicationContext = null;	

	// attribute mapping
	String attributeIdKey = null;
	String attributeNameKey = null;
	String attributeEmailKey = null;
	String attributeUserNameKey = null;

	// if we require password changes
	String attributeLastPwdChangeKey = null;
	int requirePwdChangeAfterDays = 90;

	// do we use a different user/pass for changing pwds
	boolean useCustomContextForPwdChange = false;
	transient DirContext customPwdChangeLdapContext = null;	

	String[] requestAttributes = null;

	// searching for user
	String searchContextName = null;
	String searchContextScopeString = null;
	int searchContextScope = 2;
	String searchMatchingAttributes = null;

	public void load() throws IOException {

		// now load the values in social props
		SocialPropertiesUtil socialData = SocialPropertiesUtil.getInstance();

		this.providerUrl = socialData.getProperty(LDAP_PROVIDER_URL);
		this.applicationMasterPrincipal = socialData.getProperty(LDAP_APPLICATION_SECURITY_PRINCIPAL);
		this.applicationMasterCredentials = socialData.getProperty(LDAP_APPLICATION_SECURITY_CREDENTIALS);

		this.attributeIdKey = socialData.getProperty(LDAP_ID_KEY);
		this.attributeNameKey = socialData.getProperty(LDAP_NAME_KEY);
		this.attributeEmailKey = socialData.getProperty(LDAP_EMAIL_KEY);
		this.attributeUserNameKey = socialData.getProperty(LDAP_USERNAME_KEY);

		this.attributeLastPwdChangeKey = socialData.getProperty(LDAP_LAST_PWD_CHANGE_KEY);
		String requirePwdChangeDays = socialData.getProperty(LDAP_FORCE_PWD_CHANGE_KEY);
		if(requirePwdChangeDays != null && !requirePwdChangeDays.isEmpty()) {
			try {
				requirePwdChangeAfterDays = Integer.parseInt(requirePwdChangeDays);
			} catch(NumberFormatException e) {
				throw new IllegalArgumentException("Invalid value for " + LDAP_FORCE_PWD_CHANGE_KEY + ". " + e.getMessage());
			}
		}
		this.searchContextName = socialData.getProperty(LDAP_SEARCH_CONTEXT_NAME);
		if(this.searchContextName == null || (this.searchContextName=this.searchContextName.trim()).isEmpty()) {
			this.searchContextName = "(&(cn=)(objectClass=inetOrgPerson))";
		}
		// should match integer values of
		// OBJECT_SCOPE, ONELEVEL_SCOPE, SUBTREE_SCOPE
		this.searchContextScopeString = socialData.getProperty(LDAP_SEARCH_CONTEXT_SCOPE);
		if(this.searchContextScopeString == null || (this.searchContextScopeString=this.searchContextScopeString.trim()).isEmpty()) {
			this.searchContextScopeString = "2";
		}
		this.searchMatchingAttributes = socialData.getProperty(LDAP_SEARCH_MATCHING_ATTRIBUTES);
		if(this.searchMatchingAttributes == null || (this.searchMatchingAttributes=this.searchMatchingAttributes.trim()).isEmpty()) {
			this.searchMatchingAttributes = "(objectClass=inetOrgPerson)";
		}

		List requestAttributesList = new ArrayList<>();
		requestAttributesList.add(this.attributeIdKey);
		if(this.attributeNameKey != null && !this.attributeNameKey.isEmpty()) {
			requestAttributesList.add(this.attributeNameKey);
		}
		if(this.attributeEmailKey != null && !this.attributeEmailKey.isEmpty()) {
			requestAttributesList.add(this.attributeEmailKey);
		}
		if(this.attributeUserNameKey != null && !this.attributeUserNameKey.isEmpty()) {
			requestAttributesList.add(this.attributeUserNameKey);
		}
		if(this.attributeLastPwdChangeKey != null && !this.attributeLastPwdChangeKey.isEmpty()) {
			requestAttributesList.add(this.attributeLastPwdChangeKey);
		}
		this.requestAttributes = requestAttributesList.toArray(new String[]{});


		// validate any inputs and throw errors if invalid
		validate();
	}

	@Override
	public void validate() throws IOException {
		// always need the provider url
		if(this.providerUrl == null || (this.providerUrl=this.providerUrl.trim()).isEmpty()) {
			throw new IllegalArgumentException("Must provide the AD connection URL");
		}
		if(!this.providerUrl.startsWith("ldaps://") && !this.providerUrl.startsWith("ldap://")) {
			this.providerUrl = "ldap://" + this.providerUrl;
		}

		// we require a valid application context
		if(this.applicationMasterPrincipal == null || (this.applicationMasterPrincipal=this.applicationMasterPrincipal.trim()).isEmpty()) {
			throw new IllegalArgumentException("Must provide the attribute for the application ldap principal");
		}
		if(this.applicationMasterCredentials == null || (this.applicationMasterCredentials=this.applicationMasterCredentials.trim()).isEmpty()) {
			throw new IllegalArgumentException("Must provide the attribute for the application ldap credentials");
		}
		try {
			this.applicationContext = LDAPConnectionHelper.createLdapContext(this.providerUrl, this.applicationMasterPrincipal, this.applicationMasterCredentials);
		} catch (Exception e) {
			throw new IllegalArgumentException("Unable to login to create the application ldap context");
		}


		// need to at least have the ID
		if(this.attributeIdKey == null || (this.attributeIdKey=this.attributeIdKey.trim()).isEmpty()) {
			throw new IllegalArgumentException("Must provide the attribute for the user id");
		}

		try {
			this.searchContextScope = Integer.parseInt(this.searchContextScopeString);
		} catch(NumberFormatException e) {
			throw new IllegalArgumentException("Search Context scope must be of value 0, 1, or 2");
		}

		// do we have anything for custom context to change pwds if users are unable to change
		SocialPropertiesUtil socialData = SocialPropertiesUtil.getInstance();
		this.useCustomContextForPwdChange = Boolean.parseBoolean(socialData.getProperty(LDAP_USE_CUSTOM_CONTEXT_FOR_PWD_CHANGE_KEY)+"");
		if(this.useCustomContextForPwdChange) {
			String customUsername = socialData.getProperty(LDAP_USE_CUSTOM_CONTEXT_FOR_PWD_USERNAME_KEY);
			String customPwd = socialData.getProperty(LDAP_USE_CUSTOM_CONTEXT_FOR_PWD_PASSWORD_KEY);
			try {
				this.customPwdChangeLdapContext = LDAPConnectionHelper.createLdapContext(this.providerUrl, customUsername, customPwd);
			} catch (Exception e) {
				classLogger.error(Constants.STACKTRACE + "Unable to login for processing password changes");
				classLogger.error(Constants.STACKTRACE, e);
			}
		}
	}


	@Override
	public AccessToken authenticate(String username, String password) throws Exception {
		// first, need to find the users DN 
		// then need to validate the password is accurate

		// need to search for the user who just logged in
		// so that i can grab the attributes
		String searchContextName = this.searchContextName.replace(SECURITY_PRINCIPAL_TEMPLATE_USERNAME, username);
		String searchFilter = this.searchMatchingAttributes.replace(SECURITY_PRINCIPAL_TEMPLATE_USERNAME, username);

		SearchControls controls = new SearchControls();
		controls.setSearchScope(this.searchContextScope);
		controls.setReturningAttributes(this.requestAttributes);

		NamingEnumeration findUser = applicationContext.search(searchContextName, searchFilter, controls);

		boolean foundUser = false;
		SearchResult result = null;
		while(findUser.hasMoreElements()) {
			foundUser = true;
			result = findUser.next();
			// confirm the password works for this user
			String userDN = result.getNameInNamespace();
			classLogger.info("Found user DN = " + userDN + " > attemping to login");
			DirContext userConnection = null;
			try {
				// if successful at login, we are good
				userConnection = LDAPConnectionHelper.createLdapContext(this.providerUrl, userDN, password);
				
				Attributes attr = result.getAttributes();
				return this.generateAccessToken(attr, userDN, this.attributeIdKey, this.attributeNameKey, this.attributeEmailKey, 
						this.attributeUserNameKey, this.attributeLastPwdChangeKey, this.requirePwdChangeAfterDays);
			} catch(Exception e) {
				String message = "Incorrect login for '" + userDN + "'. ";
				if(e instanceof NamingException) {
					String possibleExplanation = ((NamingException) e).getExplanation();
					if(possibleExplanation != null && !(possibleExplanation=possibleExplanation.trim()).isEmpty()) {
						message += "Error message explanation: " + possibleExplanation;
					} else {
						message += "Error message = " + e.getMessage();
					}
				} else {
					message += "Error message = " + e.getMessage();
				}
				classLogger.error(message);
				classLogger.error(Constants.STACKTRACE, e);
				continue;
			} finally {
				if(userConnection != null) {
					userConnection.close();
				}
			}
		}

		if(foundUser) {
			// see if we can determine that they cannot signin but actually need to reset their password details
			if(result != null) {
				// this method will throw LDAPPasswordChangeRequiredException if the password needs to be reset to login
				try {
					this.getLastPwdChange(result.getAttributes(), this.attributeLastPwdChangeKey, this.requirePwdChangeAfterDays);
				} catch(LDAPPasswordChangeRequiredException e) {
					throw e;
				} catch(Exception e) {
					classLogger.error("Error occurred seeing if login issue is that user must change password");
					classLogger.error(Constants.STACKTRACE, e);
				}
			}
			
			throw new IllegalArgumentException("Found username but invalid credentials to login");
		} else {
			throw new IllegalArgumentException("Unable to find username = " + username);
		}
	}


	@Override
	public void updateUserPassword(String username, String curPassword, String newPassword) throws NamingException {
		// first find the user DN
		String searchContextName = this.searchContextName.replace(SECURITY_PRINCIPAL_TEMPLATE_USERNAME, username);
		String searchFilter = this.searchMatchingAttributes.replace(SECURITY_PRINCIPAL_TEMPLATE_USERNAME, username);

		SearchControls controls = new SearchControls();
		controls.setSearchScope(this.searchContextScope);
		controls.setReturningAttributes(this.requestAttributes);

		NamingEnumeration findUser = this.applicationContext.search(searchContextName, searchFilter, controls);

		String principalDN = null;
		boolean foundUser = false;
		SearchResult result = null;
		while(findUser.hasMoreElements()) {
			foundUser = true;
			result = findUser.next();
			// we got the user DN
			principalDN = result.getNameInNamespace();
			classLogger.info("Found user DN = " + principalDN + " > attemping to login");
		}
		
		if(!foundUser) {
			throw new IllegalArgumentException("Unable to find username = " + username);
		}

		// we do not need to authenticate
		// just hash the old and new so that way we know it is accurate
		// this will allow us to account for expired passwords due or
		// forced password changes on login
		try {
			byte[] oldPwd = LDAPConnectionHelper.toUnicodePassword(curPassword);
			byte[] newPwd = LDAPConnectionHelper.toUnicodePassword(newPassword);
			ModificationItem[] mods = new ModificationItem[2];
			mods[0] = new ModificationItem(DirContext.REMOVE_ATTRIBUTE, new BasicAttribute("unicodePwd", oldPwd));
			mods[1] = new ModificationItem(DirContext.ADD_ATTRIBUTE, new BasicAttribute("unicodePwd", newPwd));
			classLogger.info(principalDN + " is attemping to change password");
			if(this.useCustomContextForPwdChange) {
				if(this.customPwdChangeLdapContext == null) {
					throw new IllegalArgumentException("Invalid configuration for changing user passwords - please contact your administrator");
				}
				this.customPwdChangeLdapContext.modifyAttributes(principalDN, mods);
			} else {
				this.applicationContext.modifyAttributes(principalDN, mods);
			}
			classLogger.info(principalDN + " successfully changed password");
		} catch (Exception e) {
			String message = "Failed to change password. ";
			if(e instanceof NamingException) {
				String possibleExplanation = ((NamingException) e).getExplanation();
				if(possibleExplanation != null && !(possibleExplanation=possibleExplanation.trim()).isEmpty()) {
					message += "Error message explanation: " + possibleExplanation;
				} else {
					message += "Error message = " + e.getMessage();
				}
			} else {
				message += "Error message = " + e.getMessage();
			}
			
			classLogger.error(Constants.STACKTRACE, e);
			throw new IllegalArgumentException(message);
		}
	}

	@Override
	public void updateForgottenPassword(String username, String newPassword) throws Exception {
		// first find the user DN
		String searchContextName = this.searchContextName.replace(SECURITY_PRINCIPAL_TEMPLATE_USERNAME, username);
		String searchFilter = this.searchMatchingAttributes.replace(SECURITY_PRINCIPAL_TEMPLATE_USERNAME, username);

		SearchControls controls = new SearchControls();
		controls.setSearchScope(this.searchContextScope);
		controls.setReturningAttributes(this.requestAttributes);

		NamingEnumeration findUser = this.applicationContext.search(searchContextName, searchFilter, controls);

		String principalDN = null;
		boolean foundUser = false;
		SearchResult result = null;
		while(findUser.hasMoreElements()) {
			foundUser = true;
			result = findUser.next();
			// we got the user DN
			principalDN = result.getNameInNamespace();
			classLogger.info("Found user DN = " + principalDN + " > attemping to login");
		}
		
		if(!foundUser) {
			throw new IllegalArgumentException("Unable to find username = " + username);
		}
		
		try {
			byte[] pwd = LDAPConnectionHelper.toUnicodePassword(newPassword);
			ModificationItem[] mods = new ModificationItem[1];
			mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, new BasicAttribute("unicodePwd", pwd));

			classLogger.info(principalDN + " is attemping to change password");
			if(this.useCustomContextForPwdChange) {
				if(this.customPwdChangeLdapContext == null) {
					throw new IllegalArgumentException("Invalid configuration for changing user passwords - please contact your administrator");
				}
				this.customPwdChangeLdapContext.modifyAttributes(principalDN, mods);
			} else {
				this.applicationContext.modifyAttributes(principalDN, mods);
			}
			classLogger.info(principalDN + " successfully changed password");
		} catch (Exception e) {
			String message = "Failed to change password. ";
			if(e instanceof NamingException) {
				String possibleExplanation = ((NamingException) e).getExplanation();
				if(possibleExplanation != null && !(possibleExplanation=possibleExplanation.trim()).isEmpty()) {
					message += "Error message explanation: " + possibleExplanation;
				} else {
					message += "Error message = " + e.getMessage();
				}
			} else {
				message += "Error message = " + e.getMessage();
			}
			
			classLogger.error(Constants.STACKTRACE, e);
			throw new IllegalArgumentException(message);
		}
	}
	
	@Override
	public void close() {
		try {
			this.applicationContext.close();
		} catch (NamingException e) {
			classLogger.error(Constants.STACKTRACE, e);
		}
	}

	@Override
	public List findUsers(String searchContextName, String searchFilter, int searchContextScope) throws Exception {
		List foundUsers = new ArrayList<>();
		
		if(searchContextScope < 0) {
			searchContextScope = this.searchContextScope;
		}

		SearchControls controls = new SearchControls();
		controls.setSearchScope(searchContextScope);
		controls.setReturningAttributes(requestAttributes);

		NamingEnumeration findUser = this.applicationContext.search(searchContextName, searchFilter, controls);

		SearchResult result = null;
		while(findUser.hasMoreElements()) {
			result = findUser.next();
			String userDN = result.getNameInNamespace();

			Attributes attr = result.getAttributes();
			AccessToken accessToken = this.generateAccessToken(attr, userDN, this.attributeIdKey, this.attributeNameKey, this.attributeEmailKey, 
					this.attributeUserNameKey, this.attributeLastPwdChangeKey, this.requirePwdChangeAfterDays, true);
			foundUsers.add(accessToken);
		}
		
		return foundUsers;
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy