LdapUserHandler.java 12.1 KB
package fi.codecrew.moya.beanutil;

/**
 * Copyright Iudex / Tuomas Riihimäki
 * 
 */
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;

import javax.naming.AuthenticationException;
import javax.naming.CommunicationException;
import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.BasicAttribute;
import javax.naming.directory.BasicAttributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.ModificationItem;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.naming.ldap.InitialLdapContext;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import fi.codecrew.moya.model.EventUser;
import fi.codecrew.moya.model.OrgRole;
import fi.codecrew.moya.utilities.PasswordFunctions;

public class LdapUserHandler {
	private final String ldapURI;
	private final String userBaseDn;
	//private final String roleBaseDn;
	private final String mgmtPass;
	private final String mgmtUser;
	private final String roleBaseDn;

	private String fallbackuser = null;
	private String fallbackpass = null;

	private static final String MEMBER_OF_FIELD = "memberOf";

	private static final Logger logger = LoggerFactory.getLogger(LdapUserHandler.class);
	private static final Charset LATIN1 = Charset.forName("ISO-8859-1");

	private Integer gidBase = 20000;
	private Integer uidBase = 20000;

	public enum PasswordChangeStatus {
		PWD_CHANGED, PWD_COMPLEXITY_NOT_MET, UNKNOWN_ERROR, WRONG_PASSWORD, UNKNOWN_USER,

	}

	public LdapUserHandler(String ldapUri, String userBaseDn, String roleBaseDn, String mgmtUser, String mgmtPass)
	{
		this.ldapURI = ldapUri;
		this.userBaseDn = userBaseDn;
		this.roleBaseDn = roleBaseDn;
		this.mgmtUser = mgmtUser;
		this.mgmtPass = mgmtPass;
	}

	protected InitialLdapContext getContext() throws NamingException {

		return createInitialContext(mgmtUser, mgmtPass, true);
	}

	private InitialLdapContext createInitialContext(String userRDN, String password, boolean pool) throws NamingException {
		Hashtable<String, String> authEnv = new Hashtable<String, String>(11);
		authEnv.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
		authEnv.put(Context.PROVIDER_URL, ldapURI);

		if (password != null && userRDN != null && !userRDN.isEmpty()) {

			authEnv.put(Context.SECURITY_AUTHENTICATION, "simple");
			authEnv.put(Context.SECURITY_PRINCIPAL, userRDN);
			authEnv.put(Context.SECURITY_CREDENTIALS, password);
			authEnv.put(Context.REFERRAL, "follow");

			if (ldapURI.startsWith("ldaps"))
			{
				authEnv.put("java.naming.ldap.factory.socket",
						"fi.iudex.utils.ldap.NocheckSSLSocketFactory");
				authEnv.put(Context.SECURITY_PROTOCOL, "ssl");

			}
			if (pool) {
				// Default AD timeout value is 900..
				authEnv.put("com.sun.jndi.ldap.connect.pool.timeout", "900");
				authEnv.put("com.sun.jndi.ldap.connect.pool", "true");
			}
			// authEnv.put(Context.SECURITY_PROTOCOL, "ssl");
		}

		return new InitialLdapContext(authEnv, null);
	}

	protected List<String> createLdapAttrSingleAttrSearch(String baseDn, String filter, String field) {
		Map<String, List<String>> retMap = createLdapAttrUniqueDnSearch(baseDn, filter, field);
		List<String> ret = null;
		if (retMap != null) {
			ret = retMap.get(field);
		}
		return ret;
	}

	protected Map<String, List<String>> createLdapAttrUniqueDnSearch(String baseDn, String filter, String... fields)
	{
		Map<String, Map<String, List<String>>> retMap = createLdapAttrSearch(baseDn, filter, fields);
		Map<String, List<String>> ret = null;
		if (retMap != null) {
			if (retMap.isEmpty()) {
				ret = new HashMap<>();
			} else if (retMap.size() != 1) {
				throw new RuntimeException("Got multiple results for attribute search with filter: " + filter);
			} else {
				ret = retMap.values().iterator().next();
			}
		}
		return ret;
	}

	protected Map<String, Map<String, List<String>>> createLdapAttrSearch(String baseDn, String filter, String... fields)
	{
		InitialLdapContext ctx = null;
		Map<String, Map<String, List<String>>> ret = new HashMap<>();
		try {
			ctx = getContext();
			SearchControls ctls = new SearchControls();
			ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
			ctls.setReturningAttributes(fields);

			NamingEnumeration<SearchResult> results = ctx.search(baseDn, filter, ctls);
			while (results.hasMore())
			{
				SearchResult result = results.next();
				logger.info("Got dn:{}", result.getName());
				Map<String, List<String>> resultAttrs = ret.get(result.getName());
				if (resultAttrs == null) {
					resultAttrs = new HashMap<>();
					ret.put(result.getName(), resultAttrs);
				}
				NamingEnumeration<? extends Attribute> searchAttrs = result.getAttributes().getAll();

				while (searchAttrs.hasMore()) {
					Attribute next = searchAttrs.next();
					logger.info("Got {}, {}, {}", next.getID(), next.get(), searchAttrs.hasMore());
					if (next == null || next.get() == null)
						continue;

					List<String> resultList = resultAttrs.get(next.getID());
					if (resultList == null) {
						resultList = new ArrayList<>();
						resultAttrs.put(next.getID(), resultList);
					}
					NamingEnumeration<?> searchField = next.getAll();
					while (searchField.hasMore()) {
						Object fieldResult = searchField.next();
						if (fieldResult != null) {
							resultList.add(fieldResult.toString());
						}
					}
				}
			}

		} catch (NamingException e) {
			logger.warn("Error searching parameters", e);
		} finally {
			close(ctx);
		}
		return ret;
	}

	//	public Set<String> getMemberships(String user) {
	//		Set<String> rolenames = new HashSet<String>();
	//		InitialLdapContext ctx = null;
	//		try {
	//			ctx = getContext();
	//			NamingEnumeration<SearchResult> roleSearch = getLdapParameters(ctx, user, MEMBER_OF_FIELD);
	//			if (roleSearch.hasMore())
	//			{
	//				SearchResult result = roleSearch.next();
	//
	//				if (roleSearch.hasMore()) {
	//					logger.warn("Found multiple users Clearing usergroups! {}", roleSearch.next());
	//					return null;
	//				}
	//				Attribute groupAttrs = result.getAttributes().get(MEMBER_OF_FIELD);
	//
	//				if (groupAttrs != null) {
	//					NamingEnumeration<?> roleOUs = groupAttrs.getAll();
	//					while (roleOUs.hasMore()) {
	//						String roleObject = roleOUs.next().toString();
	//						Matcher roleFinder = GROUP_PATTERN.matcher(roleObject);
	//
	//						if (roleFinder.find()) {
	//							String roleName = roleFinder.group(1);
	//							logger.info("Got group {} to user {}", roleName, user);
	//							rolenames.add(roleName);
	//						}
	//						else {
	//							logger.info("role pattern did not match {}", roleObject);
	//						}
	//					}
	//				}
	//
	//			}
	//		} catch (CommunicationException ce) {
	//			rolenames = null;
	//			logger.info("Error connecting to server", ce);
	//
	//		} catch (NamingException e) {
	//			logger.warn("Error while getting group names.", e);
	//		} finally {
	//			close(ctx);
	//		}
	//		return rolenames;
	//
	//	}

	protected NamingEnumeration<SearchResult> getLdapParameters(InitialLdapContext ctx, String user, String... attrs) throws NamingException {

		StringBuilder filter = new StringBuilder("(&(objectClass=posixAccount)(uid=").append(user).append("))");
		SearchControls ctls = new SearchControls();
		ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
		ctls.setReturningAttributes(attrs);

		return ctx.search(this.userBaseDn, filter.toString(), ctls);

	}

	public String createUser(EventUser user, List<OrgRole> roles)
	{
		InitialLdapContext ctx = null;
		OrgRole primary = null;

		for (OrgRole role : roles) {

			if (role.isLdapRole() && (primary == null || primary.getLdapWeight() < role.getLdapWeight())) {
				primary = role;
			}
		}
		String dnStr = null;

		try {

			StringBuilder dnBld = new StringBuilder();
			dnBld.append("uid=").append(user.getLogin());
			dnBld.append(",ou=").append(primary.getName());
			dnBld.append(",").append(userBaseDn);
			String dn = dnBld.toString();
			ctx = getContext();
			BasicAttributes entry = new BasicAttributes();
			entry.put(new BasicAttribute("uid", user.getLogin()));
			entry.put(new BasicAttribute("givenName", user.getFirstnames()));
			entry.put(new BasicAttribute("sn", user.getLastname()));
			entry.put(new BasicAttribute("cn", user.getWholeName()));
			entry.put(new BasicAttribute("loginShell", "/bin/bash"));
			entry.put(new BasicAttribute("homeDirectory", user.getLogin()));
			entry.put(new BasicAttribute("uidNumber", uidBase + user.getId()));
			entry.put(new BasicAttribute("gidNumber", gidBase + primary.getId()));
			entry.put(new BasicAttribute("userPassword", user.getPassword()));
			entry.put(new BasicAttribute("mail", user.getEmail()));

			BasicAttribute oc = new BasicAttribute("objectClass");
			oc.add("inetOrgPerson");
			oc.add("posixAccount");
			oc.add("shadowAccount");
			oc.add("person");
			oc.add("organizationalPerson");
			entry.put(oc);

			ctx.createSubcontext(dn, entry);
			dnStr = dn;

		} catch (NamingException e) {
			logger.warn("Error while creating user to ldap");
		} finally {
			close(ctx);
		}
		return dnStr;
	}

	protected String buildUserRdn(String username) {
		InitialLdapContext ctx = null;
		String ret = null;
		try {
			ctx = getContext();
			SearchControls ctls = new SearchControls();
			ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
			ctls.setReturningAttributes(new String[] { "dn" });
			NamingEnumeration<SearchResult> result = ctx.search(this.userBaseDn, "uid=" + username, ctls);
			if (result.hasMore()) {
				SearchResult dn = result.next();
				ret = dn.getNameInNamespace();
			}
		} catch (NamingException e) {
			logger.warn("Error building RDN", e);
		} finally {
			close(ctx);
		}
		return ret;
	}

	public boolean authenticate(String username, char[] passwd) {
		String pwd = new String(passwd);
		if (username == null || passwd == null || username.isEmpty() || pwd.isEmpty()) {
			return false;
		}

		InitialLdapContext userCtx = null;
		try {
			String userDn = buildUserRdn(username);
			if (userDn == null || userDn.isEmpty()) {
				logger.info("Username {}, not found when authenticating");
				return false;
			}
			userCtx = createInitialContext(userDn, pwd, false);
			return true;
		} catch (AuthenticationException authEx) {
			logger.info("Authentication failed!", authEx);
		} catch (CommunicationException ce) {
			// If ldap server can not be reached, check if we are trying to auth with fallback credentials 
			// WARNING!!!! THIS IS A BACKDOOR! DO NOT USE IN PRODUCTION!
			if (username.equals(fallbackuser) && pwd.equals(fallbackpass)) {
				logger.warn("Login successfull for backupuser. ");
				return true;
			} else {
				logger.info("Error connecting to ldapserver", ce);
			}
		} catch (NamingException e) {
			logger.warn("Error connecting to auth context", e);
		} finally {
			close(userCtx);
		}
		return false;
	}

	protected void close(InitialLdapContext userCtx) {
		try {
			if (userCtx != null) {
				userCtx.close();
			}
			userCtx = null;
		} catch (NamingException e) {
			logger.warn("Error closing...", e);
		}
	}

	public PasswordChangeStatus changePassword(String username, String oldPwd, String newPwd) {
		InitialLdapContext userCtx = null;
		try {
			String newHash = PasswordFunctions.getEncryptedPassword(newPwd);

			String userDn = buildUserRdn(username);
			if (userDn == null || userDn.isEmpty()) {
				return PasswordChangeStatus.UNKNOWN_USER;
			}
			logger.info("Chpass {} for user {} ", newHash, userDn);

			userCtx = createInitialContext(userDn, oldPwd, false);

			ModificationItem[] mods = new ModificationItem[1];
			Attribute mod0 = new BasicAttribute("userpassword", newHash);
			mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, mod0);

			userCtx.modifyAttributes(userDn, mods);
		} catch (NamingException e) {
			logger.warn("Error changing password", e);
			return PasswordChangeStatus.UNKNOWN_ERROR;
		} finally {
			close(userCtx);
		}
		return PasswordChangeStatus.PWD_CHANGED;
	}

	protected void setFallbackpass(String fallbackpass) {
		this.fallbackpass = fallbackpass;
	}

	protected void setFallbackuser(String fallbackuser) {
		this.fallbackuser = fallbackuser;
	}
}