package eu.dnetlib.enabling.ui.server.auth;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.ldap.core.simple.ParameterizedContextMapper;
import org.springframework.ldap.core.simple.SimpleLdapTemplate;

public class AuthenticationManagerLDAP implements AuthenticationManager {

	private static final Log log = LogFactory.getLog(AuthenticationManagerLDAP.class);
	private SimpleLdapTemplate ldapTemplate;
	private String baseDN;
	private String filter;
	private String ldapOrgAttribute = "o";

	/**
	 * Authenticates the given credentials and returns a Principal instance that can be used for authorization request.
	 * <p>
	 * The principal's security context is set to the value of the LDap attribute named
	 * <code>this.ldapContextAttribute</code>.
	 * </p>
	 * {@inheritDoc}
	 * 
	 * @see eu.dnetlib.enabling.ui.server.auth.AuthenticationManager#authenticate(java.lang.String, java.lang.String)
	 */
	@Override
	public Principal authenticate(String login, String password) {
		if (ldapTemplate.authenticate("", this.makeFilter(login), password)) {
			log.debug("User " + login + " authenticated");
			String org = this.getUserContext(login);
			ExtendedPrincipal princ = new ExtendedPrincipal(login, null);
			princ.addProperty(ldapOrgAttribute, org);
			return princ;
		} else {
			log.debug("Cannot authenticate user " + login);
			return null;
		}

	}

	@Override
	public boolean authorize(Principal principal) {
		log.debug("authorizing " + this + ": " + principal);
		if (principal == null)
			return false;
		log.warn("authorizing user " + principal.getUserName());
		if (principal.getUserName() == null || principal.getUserName().isEmpty())
			return false;
		return this.userExists(principal.getUserName());
	}

	/**
	 * <p>
	 * Authorizes the principal on the resource. Action is ignored.
	 * </p>
	 * {@inheritDoc}
	 * 
	 * @see eu.dnetlib.enabling.ui.server.auth.AuthenticationManager#authorize(eu.dnetlib.enabling.ui.server.auth.Principal,
	 *      java.lang.String, java.lang.String)
	 */
	@Override
	public boolean authorize(Principal principal, String resource, String action) {
		log.debug("authorizing " + this + ": " + principal + " for resource: " + resource);
		if (!this.authorize(principal))
			return false;
		log.warn("authorizing " + this + ": " + principal.getUserName() + " for resource: " + resource);
		if (principal instanceof ExtendedPrincipal) {
			ExtendedPrincipal extPrinc = (ExtendedPrincipal) principal;
			return extPrinc.getProperties().get(this.ldapOrgAttribute).equalsIgnoreCase(resource);
		} else
			return false;
	}

	private String makeFilter(String login) {
		return this.filter.replace("%s", login);
	}

	/**
	 * Checks if a user with the given login exists.
	 * 
	 * @param login
	 *            user name
	 * @return true if the user exists, false otherwise
	 */
	private boolean userExists(String login) {
		String dn = "cn=" + login + "," + this.baseDN;
		return this.ldapTemplate.lookup(dn, new ParameterizedContextMapper<Boolean>() {
			@Override
			public Boolean mapFromContext(Object ctx) {
				DirContextAdapter ctxAdapter = (DirContextAdapter) ctx;
				return ctxAdapter.attributeExists("uid");
			}
		});
	}

	/**
	 * Gets the
	 * 
	 * @param userName
	 * @return
	 */
	private String getUserContext(String userName) {
		String dn = "cn=" + userName + "," + this.baseDN;
		return this.ldapTemplate.lookup(dn, new ParameterizedContextMapper<String>() {
			@Override
			public String mapFromContext(Object ctx) {
				DirContextAdapter ctxAdapter = (DirContextAdapter) ctx;
				return ctxAdapter.getStringAttribute(ldapOrgAttribute);
			}
		});
	}

	public SimpleLdapTemplate getLdapTemplate() {
		return ldapTemplate;
	}

	@Required
	public void setLdapTemplate(SimpleLdapTemplate ldapTemplate) {
		this.ldapTemplate = ldapTemplate;
	}

	public String getBaseDN() {
		return baseDN;
	}

	@Required
	public void setBaseDN(String baseDN) {
		this.baseDN = baseDN;
	}

	public String getFilter() {
		return filter;
	}

	@Required
	public void setFilter(String filter) {
		this.filter = filter;
	}

	public String getLdapContextAttribute() {
		return ldapOrgAttribute;
	}

	public void setLdapContextAttribute(String ldapContextAttribute) {
		this.ldapOrgAttribute = ldapContextAttribute;
	}

}
