/* - Klasse zum Implementieren einer Benutzer-Authentifikation mittels LDAP
 *   in der IDM-Datenbank (Identit�tsmanagement) von Eric Seubert, IDS.
 * - externe Bibliothek ist Novel JLDAP.
 * 27.01.17/FB
 *
 * Sourcen:
 * - https://www.novell.com/documentation/developer/samplecode/jldap_sample/VerifyPassword.java.html
 * - https://www.novell.com/documentation/developer/samplecode/jldap_sample/LDAPOIDs.java.html
 * - https://www.novell.com/documentation/developer/jldap/jldapenu/data/a90352e.html
 * WICHTIG:
 * - Novell-Bibliothek liefert 0 Treffer, wenn man nacheinander sucht!
 *   Grund daf�r nicht gefunden.
 *
 * Version von unboundID - 19.04.17/FB
 *
 * UnboundID LDAP SDK For Java � 3.2.1
 * The UnboundID LDAP SDK for Java is a fast, comprehensive, and easy-to-use Java API for 
 * communicating with LDAP directory servers and performing related tasks like reading and writing LDIF, 
 * encoding and decoding data using base64 and ASN.1 BER, and performing secure communication. This package 
 * contains the Standard Edition of the LDAP SDK, which is a complete, general-purpose library for 
 * communicating with LDAPv3 directory servers. 
 * TODO:
 * - gesichertes Login mit gesch�tztem Passwort.
 * - Passwort des Admin verschl�sseln.
 */
 
package de.ids_mannheim.korap.authentication;

import com.unboundid.ldap.sdk.*;

import de.ids_mannheim.korap.config.TokenType;
import de.ids_mannheim.korap.config.KustvaktConfiguration;

import java.io.*;
import java.util.*;


/**
 * LDAP Login Tests
 * 
 * @author bodmer, margaretha
 * @see APIAuthentication
 */
public class LdapAuth3 extends APIAuthentication {

    /* For SSL Connection to LDAP, see: https://www.novell.com/documentation/developer/jldap/jldapenu/data/cchcbejj.html.
	* and use DEFAULT_SSL_PORT.
   * For now, plain text connection is used.
	* FB
	*/
	final static Boolean DEBUGLOG = false; // log debug output.
	final static String attC2 		= "idsC2";		// if value == TRUE: registered for COSMAS II (KorAP) Service.
	final static String attStatus	= "idsStatus";	// value must be 0..2, 3 = locked account.
	final static int ldapPort 		= 389; //LDAPConnection.DEFAULT_PORT;
	// final static int ldapVersion 	= LDAPConnection.LDAP_V3;
	final static String ldapHost 	= "ldap.ids-mannheim.de";
	final static String ldapBase	= "dc=ids-mannheim,dc=de";
	final static String sLoginDN 	= "cn=casaling,dc=ids-mannheim,dc=de";
	static String sPwd 				= null;

	/**
	 * return   : 0 = OK, User Account + Pwd are OK, no restrictions;
	 *            1 = internal error: cannot verify User+Pwd;
	 *            2 = User Account or Pwd unknown;
	 *            3 = User Account locked;
	 *            4 = User known, but has not registered to KorAP/C2 Service yet;
	 */

	public static final int LDAP_AUTH_ROK		= 0; 
	public static final int LDAP_AUTH_RINTERR	= 1;
	public static final int LDAP_AUTH_RUNKNOWN	= 2;
	public static final int LDAP_AUTH_RLOCKED	= 3;
	public static final int LDAP_AUTH_RNOTREG	= 4;

    public LdapAuth3 (KustvaktConfiguration config) {
        super(config);
    }	
    
	
	@Override
	public TokenType getTokenType () {
	    return TokenType.API;
	}
	 
	/**
	 * getErrMessage:
	 * returns String Message for LDAP_AUTH_Rxxx code.
	 * @date 20.04.17/FB
	 * @param code
	 * @return Message in string form.
	 */
	public static String getErrMessage(int code)
	
	{
	switch(code)
		{
	case LDAP_AUTH_ROK: 
		return "LDAP Authentication successfull.";
	case LDAP_AUTH_RINTERR: 
		return "LDAP Authentication failed due to an internal error!";
	case LDAP_AUTH_RUNKNOWN:
		return "LDAP Authentication failed due to unknown user or password!";
	case LDAP_AUTH_RLOCKED:
		return "LDAP Authentication: known user is locked!";
	case LDAP_AUTH_RNOTREG:
		return "LDAP Authentication: known user has not registered yet for COSMAS II/KorAP!";
	default:
		return "LDAP Authentication failed with unknown error code!";
		}
	} // getErrMessage
	
	/**
	 * ldapCode2StatusCode:
	 * - converts a LDAP_AUTH_xxx Error Code to an Error Code of StatusCode.java.
	 * @param base : Base value inside of StatusCode.java reserved for LDAP_AUTH Error Codes.
	 * @param ldapErrCode : the LDAP_AUTH Error code
	 * @return the StatusCode in the range reserved for LDAP_AUTH Errors.
	 * @date 21.04.17/FB
	 */
	public int ldapCode2StatusCode(int base, int ldapErrCode)
	
	{
	return base + ldapErrCode;	
	} // ldapCode2StatusCode
	
	/*
	 *  load properties for LDAP Handling.
	 *  17.02.17/FB
	 */
	
	static String loadProp(String sConfFile) throws IOException
	
	{
		String sPwd = null;
		FileInputStream in;
		Properties prop;
		
        try {
            in = new FileInputStream(sConfFile);
            }
        catch( IOException ex )
            {
        	System.err.printf("Error: LDAP.loadProp: cannot load Property file '%s'!\n", sConfFile); 
            ex.printStackTrace();
            return null;
            }

        if( in == null )
	        {
        	System.err.printf("Error: LDAP.loadProp: cannot load Property file '%s'!\n", sConfFile); 
	        return null;
	        }
	    else
	        {
	        if( DEBUGLOG ) System.out.println("Debug: loaded: " + sConfFile);
	        }

        prop = new Properties();
        Enumeration<?> e;
        
        try {
            prop.load(in);
            e = prop.propertyNames();

            while( e.hasMoreElements() )
                {
                String key = (String)e.nextElement();
                String val = prop.getProperty(key);
                if( key.compareTo("pwd") == 0 )
                	return val; 
                
                //System.out.println("Property '" + key + "' = '" + val + "'.");
                }
             }
          catch( IOException ex )
             {
             ex.printStackTrace();
             }

		return sPwd;

	} // loadProp

	/**
	 * ldapLogin
	 * Arguments:
	 * sUserDN  : either COSMAS II specific Account Name or IDS wide (IDM) account name;
	 * sUserPwd : either COSMAS II specific Password     or IDS wide (IDM) password;
	 * return   : 0 = OK, User Account + Pwd are OK, no restrictions;
	 *            1 = internal error: cannot verify User+Pwd;
	 *            2 = User Account or Pwd unknown;
	 *            3 = User Account locked;
	 *            4 = User known, but has not registered to KorAP/C2 Service yet;
	 * LDAP Attributes that are checked (definition by Eric Seubert, 02.02.17):
	 *  idsC2 = TRUE  -> Zugang zu C2 (registriert und zugelassen)
	 *  idsC2 = FALSE (bzw Attribut nicht vorhanden) 
	 *		            -> kein Zugang zu C2 (nicht zugelassen, egal ob registriert oder nicht)
	 *
	 *	 idsStatus = 0 -> Nutzerkennung OK;
	 *	 idsStatus = 1 -> Nutzer ist kein aktiver IDS-Mitarbeiter
	 *  idsStatus = 3 -> Nutzer ist LDAP-weit gesperrt
	 */

	public static int login(String sUserDN, String sUserPwd, String ldapConfig) throws LDAPException

	{

	String sUserC2DN	= sUserDN;
	String sUserC2Pwd	= sUserPwd;

	String ldapFilter = String.format("(|(&(uid=%s)(userPassword=%s))(&(idsC2Profile=%s)(idsC2Password=%s)))",
												 sUserDN, sUserPwd, sUserC2DN, sUserC2Pwd);
	SearchResult srchRes = null;

	try{
		sPwd = loadProp(ldapConfig);
		}
	catch( IOException e )
		{
		System.out.println("Error: LDAPAuth.login: cannot load Property file!");
		return LDAP_AUTH_RINTERR;
		}
															
	if( DEBUGLOG )
		{
		//System.out.printf("LDAP Version      = %d.\n", LDAPConnection.LDAP_V3);
		System.out.printf("LDAP Host & Port  = '%s':%d.\n", ldapHost, ldapPort);
		System.out.printf("Login User & Pwd  = '%s' + '%s'\n", sUserDN, sUserPwd);
		}

	// LDAP Connection:
	if( DEBUGLOG ) System.out.println("");

	LDAPConnection lc = new LDAPConnection();
	try {
		// connect to LDAP Server:
		lc.connect(ldapHost, ldapPort);
		if( DEBUGLOG ) System.out.println("LDAP Connection = OK\n");
		}
	catch( LDAPException e) 	
		{
		System.out.printf("Connecting to LDAP Server: failed: '%s'!\n", e.toString());
		return LDAP_AUTH_RINTERR;
		}

	if( DEBUGLOG ) 
		System.out.printf("Debug: isConnected=%d\n", lc.isConnected() ? 1 : 0);
	
	try {
		// bind to server:
		if( DEBUGLOG ) System.out.printf("Binding with '%s' + '%s'...\n", sLoginDN, sPwd);
		lc.bind(sLoginDN, sPwd);
		if( DEBUGLOG ) System.out.printf("Binding: OK.\n");
		}
	catch( LDAPException e )
		{
		System.out.printf("Binding failed: '%s'!\n", e.toString());
		return ldapTerminate(lc, LDAP_AUTH_RINTERR);
		}

	if( DEBUGLOG ) 
		System.out.printf("Debug: isConnected=%d\n", lc.isConnected() ? 1 : 0);
		
	if( DEBUGLOG ) System.out.printf("Finding user '%s'...\n", sUserDN);
	try{
		// SCOPE_SUB = Scope Subtree.
		if( DEBUGLOG ) System.out.printf("Finding Filter: '%s'.\n", ldapFilter);

		// hier werden alle Attribute abgefragt:
		//srchRes = lc.search(ldapBase, SearchScope.SUB, ldapFilter, null);
		// wir fragen nur diese Attribute ab:
		srchRes = lc.search(ldapBase, SearchScope.SUB, ldapFilter, attStatus, attC2);

		if( DEBUGLOG ) System.out.printf("Finding '%s': %d entries.\n", sUserDN, srchRes.getEntryCount());
		}
	catch( LDAPSearchException e )
		{
		System.out.printf("Search for User failed: '%s'!\n", e.toString());
		return ldapTerminate(lc, LDAP_AUTH_RUNKNOWN);
		}

	if( srchRes.getEntryCount() == 0 )
		{
		if( DEBUGLOG ) System.out.printf("Finding '%s': no entry found!\n", sUserDN);
		return ldapTerminate(lc, LDAP_AUTH_RUNKNOWN);
		}

	if( DEBUGLOG ) System.out.println("Display results:");

	Boolean
		bStatus = false,
		bC2     = false;
	
	// Attribute pr�fen:
	for (SearchResultEntry e : srchRes.getSearchEntries())
		{
		for( Attribute attr : e.getAttributes() )
			{
			Integer val;

			if( DEBUGLOG ) 
				System.out.printf(" att: '%s'='%s'.\n", attr.getName(), attr.getValue());

			// checking pertinent attribut/value pairs:
			// "idsStatus": values 0=OK, 1=inaktiv=OK, 2-3 = locked account.
			if( attr.getName().equals(attStatus) )
				{
				if( (val = attr.getValueAsInteger()) == null || (val != 0 && val != 1) )
					{
					if( DEBUGLOG ) System.out.printf("idsStatus = '%s' -> User locked!\n", attr.getValue());
					return ldapTerminate(lc, LDAP_AUTH_RLOCKED);
					}
				if( DEBUGLOG ) System.out.printf(" att: '%s'='%s': OK.\n", attr.getName(), attr.getValue());
				bStatus = true;
				}

			// "c2IDS" must be set to "TRUE" = User known, but has not yet registered to C2 Service -> KorAP Service.
			if( attr.getName().equals(attC2) ) 
				{
				if( attr.getValue().equals("FALSE") )
					{
					if( DEBUGLOG ) 
						System.out.printf("idsC2 = '%s'-> User known, but has not registered C2/KorAP Service yet!\n", 
							attr.getValue());
					return ldapTerminate(lc, LDAP_AUTH_RNOTREG);
					}
				if( DEBUGLOG ) 
					System.out.printf(" att: idsC2 = '%s'-> registered User: OK.\n", attr.getValue());
				bC2 = true;
				}
			}

		if( DEBUGLOG ) System.out.println();
		}

	if( bStatus == true && bC2 == true )
		return ldapTerminate(lc, LDAP_AUTH_ROK); // OK.
	else
		return ldapTerminate(lc, LDAP_AUTH_RNOTREG); // Attribute konnten nicht gepr�ft werden.
	
	} // ldapLogin

/**
 * ldapTerminate
 */
 
public static int ldapTerminate(LDAPConnection lc, int ret)

	{
	if( DEBUGLOG ) System.out.println("Terminating...");
	/*
	try{
		lc.finalize();
		if( DEBUGLOG ) System.out.println("Debug: finalize: OK.");
		}
	catch( LDAPException e )
		{
		System.out.printf("finalize failed: '%s'!\n", e.toString());
		}
	*/

	lc.close(null);
	if( DEBUGLOG ) System.out.println("closing connection: done.\n");
	return ret;
	} // ldapTerminate

}

