/**
* Copyright (C) 2008 Google - Enterprise EMEA SE
*
* Licensed 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.
*/
package com.google.gsa.valve.rootAuth;
import java.io.IOException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.httpclient.HttpException;
import org.apache.log4j.Logger;
import org.apache.regexp.RE;
import com.google.gsa.AuthenticationProcessImpl;
import com.google.gsa.Credentials;
import com.google.gsa.valve.configuration.ValveConfiguration;
import com.google.gsa.valve.configuration.ValveRepositoryConfiguration;
import java.util.HashMap;
import java.util.Map;
import java.util.Vector;
/**
* This is the default class that processes the authentication. It reads the
* repositories defined in the config file and invokes those repository's
* authentication classes that require it to be triggered. Those that includes
* the tag "checkauthN" set to false are not processed.
* <p>
* The name of the authentication classes that need to be processed are included
* in a vector that is reused multiple times. Whenever a new authentication
* process needs to be relaunched, all these classes are processed and the
* individual authentication process is treated.
* <p>
* There is a special repository named "root" that is treatly in a special way.
* If any repository is named as "root", it means this is the main authentication
* mechanim and that's why it's trated first. If it fails, the authentication
* process stops here and the return result is an error. If not, the whole
* processing continues.
*
* @see RootAuthorizationProcess
*
*/
public class RootAuthenticationProcess implements AuthenticationProcessImpl {
//logger
private Logger logger = null;
//Valve configuration
private ValveConfiguration valveConf = null;
//Support for Krb creds
boolean isKerberos = false;
boolean isNegotiate = false;
String loginUrl = null;
private Vector<Cookie> rootAuthCookies = new Vector<Cookie>();
private Vector<Cookie> repositoryAuthCookies = new Vector<Cookie>();
//Map that represents the authentication modules
private Map<String, AuthenticationProcessImpl> authenticationImplementations =
new HashMap<String, AuthenticationProcessImpl>();
//Map that represents the order of the authentication modules
private Map<Integer, String> authenticationImplementationsOrder =
new HashMap<Integer, String>();
/**
* Class constructor
*
*/
public RootAuthenticationProcess() {
// Invoke parent constructor
super();
// Instantiate logger
logger = Logger.getLogger(RootAuthenticationProcess.class);
logger.debug("Initializing " +
RootAuthenticationProcess.class.getName());
}
/**
* Sets the request is a Kerberos negotiation process
*
* @param newIsNegotiate boolean - if it's a negotiation process
*/
public void setIsNegotiate(boolean newIsNegotiate) {
isNegotiate = newIsNegotiate;
}
/**
* Gets if the request is a Kerberos negotiation process
*
* @return boolean - if it's a negotiation process
*/
public boolean getIsNegotiate() {
return isNegotiate;
}
/**
* This is the main method that drives the whole authentication
* process. It launches each individual authentication method declared in
* the configuration files. Those that includes the tag "checkauthN" set to
* false are not processed.
* <p>
* The name of the authentication classes that need to be processed are included
* in a vector that is reused multiple times. Whenever a new authentication
* process needs to be relaunched, all these classes are processed and the
* individual authentication process is treated.
* <p>
* It returns the HTTP error code associated to the process result. If it was
* OK, this methods returns a 200 and 401 (unauthorized) otherwise.
* <p>
* There is a special repository named "root" that is treatly in a special way.
* If any repository is named as "root", it means this is the main authentication
* mechanim and that's why it's trated first. If it fails, the authentication
* process stops here and the return result is an error. If not, the whole
* processing continues.
* <p>
* If there is a "root" repository and the authentication process for this
* repository is OK, although any other repository would fail, the overall
* authentication method returns an OK. If there is not such a "root"
* repository, any authentication error will cause the authentication process
* to fail.
*
* @param request HTTP request
* @param response HTTP response
* @param authCookies vector that contains the authentication cookies
* @param url the document url
* @param creds an array of credentials for all external sources
* @param id the default credential id to be retrieved from creds
* @return the HTTP error code
* @throws HttpException
* @throws IOException
*/
public int authenticate(HttpServletRequest request,
HttpServletResponse response,
Vector<Cookie> authCookies, String url,
Credentials creds, String id) throws HttpException,
IOException {
// Initialize status code
int rootStatusCode = HttpServletResponse.SC_UNAUTHORIZED;
int repositoryAuthStatusCode = HttpServletResponse.SC_UNAUTHORIZED;
//Check if authn is Ok in multiple repository
boolean repositoryOKAuthN = false;
//clear authCookies
authCookies.clear();
boolean rootAuthNDefined = false;
logger.debug("AuthN authenticate [" + url + "]");
//Read vars
if (valveConf != null) {
isKerberos =
new Boolean(valveConf.getKrbConfig().isKerberos()).booleanValue();
if (isKerberos) {
isNegotiate =
new Boolean(valveConf.getKrbConfig().isNegotiate()).booleanValue();
}
loginUrl = valveConf.getLoginUrl();
} else {
logger.error("Configuration error: Config file is not present");
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
"Configuration error - Kerberos is not set properly");
return HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
}
//ValveHost: it's the same URL as the login page, without
String valveHost =
loginUrl.substring(0, loginUrl.lastIndexOf("/") + 1);
RE internal = new RE(valveHost, RE.MATCH_CASEINDEPENDENT);
// The host and URL of the GSA for the search
//TODO add support for multiple GSA's
RE query = new RE("/search", RE.MATCH_CASEINDEPENDENT);
//Request has come from the same host as the valve, so must be the login authenticate
if (internal.match(url)) {
//Authentication vars
String repositoryID = null;
AuthenticationProcessImpl authProcess = null;
ValveRepositoryConfiguration repositoryConfig = null;
int order = 1;
int size = authenticationImplementationsOrder.size();
if (authenticationImplementationsOrder == null) {
order = 0;
logger.error("No Authentication module has been defined. Please check and add those needed at config file");
}
while ((1 <= order) && (order <= size)) {
//Get the repository ID
logger.debug("###Processing repository # " + order + " ###");
Integer orderInt = new Integer(order);
if (authenticationImplementationsOrder.containsKey(orderInt)) {
repositoryID =
authenticationImplementationsOrder.get(orderInt);
} else {
logger.error("Error during processing authentication methods. Order is not valid");
break;
}
//Get the Repository config and authentication class
authProcess = authenticationImplementations.get(repositoryID);
repositoryConfig = valveConf.getRepository(repositoryID);
logger.debug("Authenticating ID: " + repositoryConfig.getId());
if (repositoryConfig.getId().equals("root")) {
//Root should be used for main authentication against an identity repository (LDAP, DB, ..)
//and should not be used as a content repository that contains documents
try {
//add support to cookie array
rootAuthCookies.clear();
rootStatusCode =
authProcess.authenticate(request, response,
rootAuthCookies, url,
creds, "root");
logger.info("Repository authentication - " +
repositoryConfig.getId() +
" completed. Response was " +
rootStatusCode);
if (rootStatusCode ==
HttpServletResponse.SC_UNAUTHORIZED) {
logger.error("Root AuthN failed");
} else {
//Support to cookie array
if (rootStatusCode == HttpServletResponse.SC_OK) {
logger.debug("Root AuthN is SC_OK (200)");
if (!rootAuthCookies.isEmpty()) {
logger.debug("Root AuthN returns cookies");
for (int j = 0; j < rootAuthCookies.size();
j++) {
logger.debug("Root Cookie found: " +
rootAuthCookies.elementAt(j).getName() +
":" +
rootAuthCookies.elementAt(j).getValue());
authCookies.add(rootAuthCookies.elementAt(j));
}
} else {
logger.debug("Root AuthN does NOT return cookies");
}
}
}
//If no repository is defined called root then rootStatusCode must be set to OK
// This flag is used to indicate that a root repository has been defined.
rootAuthNDefined = true;
//
} catch (Exception e) {
logger.debug("Exception with authentication for ID: " +
repositoryConfig.getId() + " - " +
e.getMessage());
rootAuthNDefined = true;
}
} else {
try {
//add support to cookie array
repositoryAuthCookies.clear();
logger.debug("Let's do the authentication");
repositoryAuthStatusCode =
authProcess.authenticate(request, response,
repositoryAuthCookies,
url, creds,
repositoryConfig.getId());
//add support to cookie array
if (repositoryAuthStatusCode ==
HttpServletResponse.SC_OK) {
logger.debug("Repository AuthN [" +
repositoryConfig.getId() +
"] is SC_OK (200)");
//check if multiple repository is set to valid
if (repositoryOKAuthN == false) {
repositoryOKAuthN = true;
}
//check if cookie array is not empty and consume it
if (!repositoryAuthCookies.isEmpty()) {
logger.debug("Repository AuthN [" +
repositoryConfig.getId() +
"] returns " +
repositoryAuthCookies.size() +
" cookies");
for (int j = 0;
j < repositoryAuthCookies.size(); j++) {
logger.debug("Repository Cookie found: " +
repositoryAuthCookies.elementAt(j).getName() +
":" +
repositoryAuthCookies.elementAt(j).getValue());
authCookies.add(repositoryAuthCookies.elementAt(j));
}
} else {
logger.debug("Repository AuthN [" +
repositoryConfig.getId() +
"] does NOT return cookies");
}
}
//end Krb support
logger.info("Repository authentication - " +
repositoryConfig.getId() +
" completed. Response was " +
repositoryAuthStatusCode);
} catch (Exception e) {
logger.debug("Exception with authentication for ID: " +
repositoryConfig.getId() + " - " +
e.getMessage());
}
}
//increase order
order++;
}
} else if (query.match(url)) {
logger.debug("Query pattern [" + url + "]");
// Don't do anything in here
rootStatusCode = HttpServletResponse.SC_OK;
} else {
logger.error("No pattern defined for URL: " + url +
". It should not have been possible to get here!");
// Protection
rootStatusCode = HttpServletResponse.SC_UNAUTHORIZED;
}
//add support to multiple repositories
if ((!rootAuthNDefined) && (repositoryOKAuthN)) {
//If no root repository has been defined then rootStatusCode has to be set valid, to OK
rootStatusCode = HttpServletResponse.SC_OK;
}
// Return status code
logger.debug("RootAuthN Complete - Status Code: " + rootStatusCode);
return rootStatusCode;
}
/**
* Sets the Valve Configuration instance to read the parameters
* from there
*
* @param valveConf the Valve configuration instance
*/
public void setValveConfiguration(ValveConfiguration valveConf) {
this.valveConf = valveConf;
//Protection. Make sure the Map is empty before proceeding
authenticationImplementations.clear();
//Authentication process instance
AuthenticationProcessImpl authenticationProcess = null;
String repositoryIds[] = valveConf.getRepositoryIds();
ValveRepositoryConfiguration repository = null;
int order = 1;
for (int i = 0; i < repositoryIds.length; i++) {
try {
repository = valveConf.getRepository(repositoryIds[i]);
//Check if repository has to be included in the authentication process. By default set it to true
boolean checkAuthN = true;
try {
if ((repository.getCheckAuthN() != null) &&
(!repository.getCheckAuthN().equals(""))) {
checkAuthN =
new Boolean(repository.getCheckAuthN()).booleanValue();
}
} catch (Exception e) {
logger.error("Error when reading checkAuthN param: " +
e.getMessage(), e);
//protection
checkAuthN = true;
}
if (checkAuthN) {
logger.info("Initialising authentication process for " +
repository.getId() + " [#" + order + "]");
authenticationProcess =
(AuthenticationProcessImpl)Class.forName(repository.getAuthN()).newInstance();
authenticationProcess.setValveConfiguration(valveConf);
//add this authentication process to the Map
synchronized (authenticationImplementations) {
synchronized (authenticationImplementations) {
authenticationImplementations.put(repository.getId(),
authenticationProcess);
authenticationImplementationsOrder.put(new Integer(order),
repository.getId());
order++;
}
}
} else {
logger.debug("Authentication process for repository [" +
repository.getId() +
"] is not going to be launched");
}
} catch (LinkageError le) {
logger.error(repository.getId() +
" - Can't instantiate class [AuthenticationProcess-LinkageError]: " +
le.getMessage(), le);
} catch (InstantiationException ie) {
logger.error(repository.getId() +
" - Can't instantiate class [AuthenticationProcess-InstantiationException]: " +
ie.getMessage(), ie);
} catch (IllegalAccessException iae) {
logger.error(repository.getId() +
" - Can't instantiate class [AuthenticationProcess-IllegalAccessException]: " +
iae.getMessage(), iae);
} catch (ClassNotFoundException cnfe) {
logger.error(repository.getId() +
" - Can't instantiate class [AuthenticationProcess-ClassNotFoundException]: " +
cnfe.getMessage(), cnfe);
} catch (Exception e) {
logger.error(repository.getId() +
" - Can't instantiate class [AuthenticationProcess-Exception]: " +
e.getMessage(), e);
}
}
logger.debug(RootAuthenticationProcess.class.getName() +
" initialised");
}
}