/*
 * LDAPServlet.java
 *
 * Version: $Revision: 1.2 $
 *
 * Date: $Date: 2005/04/20 14:22:38 $
 *
 * Copyright (c) 2002-2005, Hewlett-Packard Company and Massachusetts
 * Institute of Technology.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * - Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 *
 * - Neither the name of the Hewlett-Packard Company nor the name of the
 * Massachusetts Institute of Technology nor the names of their
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 *
 * Robin Taylor 21/8/06
 * Modified to cater for St Andrews LDAP setup.
 *
 *
 */

package org.dspace.app.webui.servlet;

import java.io.IOException;
import java.sql.SQLException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.log4j.Logger;

import org.dspace.app.webui.util.Authenticate;
import org.dspace.app.webui.util.JSPManager;
import org.dspace.authorize.AuthorizeException;
import org.dspace.core.ConfigurationManager;
import org.dspace.core.Context;
import org.dspace.core.LogManager;
import org.dspace.eperson.EPerson;
import java.util.Hashtable;

import javax.naming.directory.*;
import javax.naming.*;
import javax.naming.ldap.LdapContext;
import javax.naming.ldap.InitialLdapContext;


/**
 * LDAP username and password authentication servlet.  Displays the
 * login form <code>/login/ldap.jsp</code> on a GET,
 * otherwise process the parameters as an ldap username and password.
 *
 * @author  John Finlay (Brigham Young University)
 * @version $Revision: 1.2 $
 */
public class LDAPServlet extends DSpaceServlet
{
    /** log4j logger */
    private static Logger log = Logger.getLogger(LDAPServlet.class);

    /** ldap email result */
    private String ldapEmail;

    /** ldap name result */
    private String ldapGivenName;
    private String ldapSurname;
    private String ldapPhone;

    protected void doDSGet(Context context,
        HttpServletRequest request,
        HttpServletResponse response)
        throws ServletException, IOException, SQLException, AuthorizeException
    {
        // check if ldap is enables and forward to the correct login form
        boolean ldap_enabled = ConfigurationManager.getBooleanProperty("ldap.enable");
        if (ldap_enabled)
            JSPManager.showJSP(request, response, "/login/ldap.jsp");
        else
            JSPManager.showJSP(request, response, "/login/password.jsp");
    }
    
    
    protected void doDSPost(Context context,
        HttpServletRequest request,
        HttpServletResponse response)
        throws ServletException, IOException, SQLException, AuthorizeException
    {
        // Process the POSTed email and password
        String netid = request.getParameter("login_netid");
        String password = request.getParameter("login_password");
        
        // Locate the eperson
        EPerson eperson = EPerson.findByNetid(context, netid.toLowerCase());
        EPerson eperson2 = EPerson.findByEmail(context, netid.toLowerCase());
        boolean loggedIn = false;

        // make sure ldap values are null with every request
        ldapGivenName = null;
        ldapSurname = null;
        ldapEmail = null;
        ldapPhone = null;

        // if they entered a netid that matches an eperson
        if (eperson != null && eperson.canLogIn())
        {

            log.info(LogManager.getHeader(context, "login", "netid matches eperson"));

            // e-mail address corresponds to active account
            if (eperson.getRequireCertificate())
            {
                // they must use a certificate
                JSPManager.showJSP(request,
                    response,
                    "/error/require-certificate.jsp");
                return;
            }
            else 
            {
                if (ldapAuthenticate(netid, password, context))
                {
                    // Logged in OK.
                    Authenticate.loggedIn(context, request, eperson);

                    log.info(LogManager
                        .getHeader(context, "login", "type=ldap"));

                    // resume previous request
                    Authenticate.resumeInterruptedRequest(request, response);
                    return;
                }
                else 
                {
                   JSPManager.showJSP(request, response, "/login/ldap-incorrect.jsp");
                   return;
                }
            }
        }
        // if they entered an email address that matches an eperson
        else if (eperson2 != null && eperson2.canLogIn())
        {

            log.info(LogManager.getHeader(context, "login", "email matches eperson"));

            // e-mail address corresponds to active account
            if (eperson2.getRequireCertificate())
            {
                // they must use a certificate
                JSPManager.showJSP(request,
                    response,
                    "/error/require-certificate.jsp");
                return;
            }
            else
            {
                if (eperson2.checkPassword(password))
                {
                    // Logged in OK.
                    Authenticate.loggedIn(context, request, eperson2);

                    log.info(LogManager
                        .getHeader(context, "login", "type=password"));

                    // resume previous request
                    Authenticate.resumeInterruptedRequest(request, response);
                    return;
                }
                else
                {
                   JSPManager.showJSP(request, response, "/login/ldap-incorrect.jsp");
                   return;
                }
            }
        }
        // the user does not already exist so try and authenticate them with ldap and create an eperson for them
        else {

            log.info(LogManager.getHeader(context, "login", "new user"));

            if (ldapAuthenticate(netid, password, context))
            {
                if (ConfigurationManager.getBooleanProperty("webui.ldap.autoregister"))
                {
                    // Register the new user automatically
                    log.info(LogManager.getHeader(context,
                                    "autoregister", "netid=" + netid));

                    if ((ldapEmail!=null)&&(!ldapEmail.equals(""))) 
                    {
                        eperson = EPerson.findByEmail(context, ldapEmail);
                        if (eperson!=null)
                        {
                            log.info(LogManager.getHeader(context,
                                    "failed_autoregister", "type=ldap_but_already_email"));
                            JSPManager.showJSP(request, response,
                                    "/register/already-registered.jsp");
                            return;
                        }
                    }
                    // TEMPORARILY turn off authorisation
                    context.setIgnoreAuthorization(true);
                    eperson = EPerson.create(context);
                    if ((ldapEmail!=null)&&(!ldapEmail.equals(""))) eperson.setEmail(ldapEmail);
                    else eperson.setEmail(netid + "@st-andrews.ac.uk");
                    if ((ldapGivenName!=null)&&(!ldapGivenName.equals(""))) eperson.setFirstName(ldapGivenName);
                    if ((ldapSurname!=null)&&(!ldapSurname.equals(""))) eperson.setLastName(ldapSurname);
                    if ((ldapPhone!=null)&&(!ldapPhone.equals(""))) eperson.setMetadata("phone", ldapPhone);
                    eperson.setNetid(netid);
                    eperson.setCanLogIn(true);
                    Authenticate.getSiteAuth().initEPerson(context, request, eperson);
                    eperson.update();
                    context.commit();
                    context.setIgnoreAuthorization(false); 

                    Authenticate.loggedIn(context, request, eperson);
                    log.info(LogManager.getHeader(context, "login",
                                "type=ldap-login"));
                    Authenticate.resumeInterruptedRequest(request, response);
                    return;
                }
                else
                {
                    // No auto-registration for valid certs
                    log.info(LogManager.getHeader(context,
                                    "failed_login", "type=ldap_but_no_record"));
                    JSPManager.showJSP(request, response,
                                    "/login/not-in-records.jsp");
                    return;
                }
            }
        }


        // If we reach here, supplied email/password was duff.
        log.info(LogManager.getHeader(context,
            "failed_login",
            "netid=" + netid));
        JSPManager.showJSP(request, response, "/login/ldap-incorrect.jsp");
    }

    /**
     * contact the ldap server and attempt to authenticate
     */
    protected boolean ldapAuthenticate(String netid, String password, Context context){
            //--------- START LDAP AUTH SECTION -------------
        if (!password.equals(""))
        {
            String ldap_provider_url = ConfigurationManager.getProperty("ldap.provider_url");
            String ldap_id_field = ConfigurationManager.getProperty("ldap.id_field");
            String ldap_search_context = ConfigurationManager.getProperty("ldap.search_context");

            // Set up environment for creating initial context
            Hashtable env = new Hashtable(11);
            env.put(javax.naming.Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
            env.put(javax.naming.Context.PROVIDER_URL, ldap_provider_url);

            try
            {
                // Create initial context - anonymous bind
                DirContext ctx = new InitialDirContext(env);

                String ldap_email_field = ConfigurationManager.getProperty("ldap.email_field");
                String ldap_givenname_field = ConfigurationManager.getProperty("ldap.givenname_field");
                String ldap_surname_field = ConfigurationManager.getProperty("ldap.surname_field");
                String ldap_phone_field = ConfigurationManager.getProperty("ldap.phone_field");

                // For St Andrews, search all the way down the tree.
                SearchControls ctls = new SearchControls();
                ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);

                String filter = "(" + ldap_id_field + "=" + netid + ")";
		String foundDN = "";

                // Now try searching for a user match
                try
                {
                    NamingEnumeration answer = ctx.search(ldap_search_context, filter, ctls);
                                
                    while(answer.hasMore()) {
                        SearchResult sr = (SearchResult)answer.next();

			// Record the users position in the directory tree
			foundDN = sr.getName();

			// Since we are here we might as well save the attributes so we don't have to look them up again.
	                Attributes attributes = sr.getAttributes();
                
                        Object ldapEmailObj = attributes.get(ldap_email_field).get();
                        if (ldapEmailObj != null)
                            ldapEmail = ldapEmailObj.toString();
                        
                        Object ldapGivenNameObj = attributes.get(ldap_givenname_field).get();
                        if (ldapGivenNameObj != null)
                            ldapGivenName = ldapGivenNameObj.toString();
                        
                        Object ldapSurnameObj = attributes.get(ldap_surname_field).get();
                        if (ldapSurnameObj != null)
                            ldapSurname = ldapSurnameObj.toString();
                        
                        Object ldapPhoneObj = attributes.get(ldap_phone_field).get();
                        if (ldapPhoneObj != null)
                            ldapPhone = ldapPhoneObj.toString();

                    }
                }
                catch (NamingException e)
                {
                    // if the lookup fails go ahead and create a new record for them because the authentication
                    // succeeded
                    log.warn(LogManager.getHeader(context,
                                    "ldap_search", "type=failed_search "+e));
                    return false;
                }
                
		// Close the context when we're done
                ctx.close();

                // Now we know where the user lives in the tree, rebind to authenticate them.
                env.put(javax.naming.Context.SECURITY_AUTHENTICATION, "simple");
		env.put(javax.naming.Context.SECURITY_PRINCIPAL, foundDN + "," + ldap_search_context);
                env.put(javax.naming.Context.SECURITY_CREDENTIALS, password);

                try
                {
                    ctx = new InitialDirContext(env);
                    ctx.close();

                }
                catch (NamingException e)
                {
                     log.warn(LogManager.getHeader(context,
                                    "ldap_authentication", "type=failed_auth "+e));
                    return false;
                }

		log.info(LogManager.getHeader(context, "ldap_authentication ", "success"));


            }
            catch (NamingException e)
            {
                log.warn(LogManager.getHeader(context,
                                    "ldap_anon_bind", "type=failed_bind "+e));
                return false;
            }
        }
        else
        {
            return false;
        }
        //--------- END LDAP AUTH SECTION -------------

        return true;
    }
}
