/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  The ASF licenses this file to You
 * under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.  For additional information regarding
 * copyright in this work, please see the NOTICE file in the top level
 * directory of this distribution.
 */
package org.apache.roller.weblogger.ui.rendering.plugins.comments;

import java.util.Hashtable;
import java.util.Locale;

import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.ldap.InitialLdapContext;
import javax.naming.ldap.LdapContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.roller.weblogger.config.WebloggerConfig;
import org.apache.roller.weblogger.util.I18nMessages;
import org.springframework.util.StringUtils;

/**
 * Requires the commenter to authenticate to a central LDAP server.  Here are the roller.properties that need to 
 * be present for this {@link CommentAuthenticator} to work correctly:
 * <br/>
 * <pre>
 * 		# default port is 389
 * 		comment.authenticator.ldap.port=389
 * 		# fully qualified host
 * 		comment.authenticator.ldap.host=
 * 		# name of dc to check against 
 * 		comment.authenticator.ldap.dc=
 * 		# csv list of dc names, ex: example,com
 * 		comment.authenticator.ldap.ou=
 * 		# options are "none" "simple" "strong", not required
 * 		comment.authenticator.ldap.securityLevel=
 * </pre>
 * <br/>
 * You can add these properties to the roller-custom.properties to ensure correct operations.  The property "securityLevel
 * is not required, will use the settings from the registered service provider; sets this property {@link Context#SECURITY_AUTHENTICATION}.
 * @author Nicholas Padilla (<a href="mailto:nicholas@monstersoftwarellc.com">nicholas@monstersoftwarellc.com</a>)
 *
 */
public class LdapCommentAuthenticator implements CommentAuthenticator {

	private static Log LOG = LogFactory.getLog(LdapCommentAuthenticator.class);

    @Override
	public String getHtml(HttpServletRequest request) {
		String ldapUser = "";
		String ldapPass  = "";
		HttpSession session = request.getSession(true);
		if (session.getAttribute("ldapUser") == null) {
			session.setAttribute("ldapUser", "");
			session.setAttribute("ldapPass", "");
		} else {
			// preserve user data
			String ldapUserTemp = request.getParameter("ldapUser");
			String ldapPassTemp = request.getParameter("ldapPass");
			ldapUser = ldapUserTemp != null ? ldapUserTemp : "";
			ldapPass = ldapPassTemp != null ? ldapPassTemp : "";
		}

		Locale locale = CommentAuthenticatorUtils.getLocale(request);
		I18nMessages messages = I18nMessages.getMessages(locale);
		StringBuilder sb = new StringBuilder();

		sb.append("<p>");
		sb.append(messages.getString("comments.ldapAuthenticatorUserName"));
		sb.append("</p>");
		sb.append("<p>");
		sb.append("<input name=\"ldapUser\" value=\"");
		sb.append(ldapUser + "\">");
		sb.append("</p>");
		sb.append("<p>");
		sb.append(messages.getString("comments.ldapAuthenticatorPassword"));
		sb.append("</p>");
		sb.append("<p>");
		sb.append("<input type=\"password\" name=\"ldapPass\" value=\"");
		sb.append(ldapPass + "\">");
		sb.append("</p>");

		return sb.toString();
	}

    @Override
	public boolean authenticate(HttpServletRequest request) {
		boolean validUser = false;
		LdapContext context = null;

		String ldapDc = WebloggerConfig.getProperty("comment.authenticator.ldap.dc");
		String ldapOu = WebloggerConfig.getProperty("comment.authenticator.ldap.ou");
		String ldapPort = WebloggerConfig.getProperty("comment.authenticator.ldap.port");
		String ldapHost = WebloggerConfig.getProperty("comment.authenticator.ldap.host");
		String ldapSecurityLevel = WebloggerConfig.getProperty("comment.authenticator.ldap.securityLevel");
		
		boolean rollerPropertiesValid = validateRollerProperties(ldapDc, ldapOu, ldapPort, ldapHost);
		
		String ldapUser = request.getParameter("ldapUser");
		String ldapPass = request.getParameter("ldapPass");
		
		boolean userDataValid = validateUsernamePass(ldapUser, ldapPass);
		
		if(rollerPropertiesValid && userDataValid){
			try {
				Hashtable<String,String> env = new Hashtable<>();
				env.put(Context.INITIAL_CONTEXT_FACTORY,  
						"com.sun.jndi.ldap.LdapCtxFactory"); 
				if(ldapSecurityLevel != null 
						&& (ldapSecurityLevel.equalsIgnoreCase("none")
								|| ldapSecurityLevel.equalsIgnoreCase("simple")
								|| ldapSecurityLevel.equalsIgnoreCase("strong"))){
					env.put(Context.SECURITY_AUTHENTICATION, ldapSecurityLevel);	
				}  
				env.put(Context.SECURITY_PRINCIPAL,  getQualifedDc(ldapDc, ldapOu, ldapUser));  
				env.put(Context.SECURITY_CREDENTIALS, ldapPass);
				env.put(Context.PROVIDER_URL, "ldap://" + ldapHost + ":" + ldapPort);  
				context = new InitialLdapContext(env, null);
				validUser = true;
				LOG.info("LDAP Authentication Successful. user: " + ldapUser);
			} catch (Exception e) {
				// unexpected
				LOG.error(e);
			} finally {
				if(context != null){
					try {
						context.close();
					} catch (NamingException e) {
						LOG.error(e);
					}
				}
			}
		}
		return validUser;
	}

	/**
	 * Get the qualified username string LDAP expects.
	 * @param ldapDc
	 * @param ldapOu
	 * @param ldapUser
	 * @return
	 */
	private String getQualifedDc(String ldapDc, String ldapOu, String ldapUser) {
		String qualifedDc = "";
		for (String token : StringUtils.delimitedListToStringArray(ldapDc, ",")) {
			if (!qualifedDc.isEmpty()) {
				qualifedDc += ",";
			}
			qualifedDc += "dc=" + token;
		}
		
		return "uid=" + ldapUser + ", ou=" + ldapOu + "," + qualifedDc;
	}

	/**
	 * Validate user provided data.
	 * @param ldapUser
	 * @param ldapPass
	 * @return
	 */
	private boolean validateUsernamePass(String ldapUser, String ldapPass) {
		boolean ret = false;
		
		if((ldapUser != null && !ldapUser.isEmpty()) 
				&& (ldapPass != null && !ldapPass.isEmpty())){
			ret = true;
		}
		
		return ret;
	}

	/**
	 * Validate required roller.properties, specified in custom-roller.properties.
	 * @param ldapDc
	 * @param ldapOu
	 * @param ldapPort
	 * @param ldapHost
	 * @return
	 */
	private boolean validateRollerProperties(String ldapDc, String ldapOu, String ldapPort, String ldapHost) {
		boolean ret = false;
		
		if((ldapDc != null && !ldapDc.isEmpty())
				&& (ldapOu != null && !ldapOu.isEmpty())
				&& (ldapPort != null && !ldapPort.isEmpty())
				&& (ldapHost != null && !ldapHost.isEmpty())){
			ret = true;
		}
		
		return ret;
	}

}
