com.google.gsa.valve.modules.krb.KerberosAuthenticationProcess.java Source code

Java tutorial

Introduction

Here is the source code for com.google.gsa.valve.modules.krb.KerberosAuthenticationProcess.java

Source

/**
 * 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.modules.krb;

import com.google.gsa.AuthenticationProcessImpl;

import com.google.gsa.Credential;
import com.google.gsa.Credentials;

import com.google.gsa.krb5.GssSpNegoAuth;

import com.google.gsa.krb5.GssSpNegoServer;

import java.io.IOException;

import javax.security.auth.Subject;

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 com.google.gsa.sessions.UserIDEncoder;
import com.google.gsa.valve.configuration.ValveConfiguration;
import com.google.krb5.Krb5Credentials;

import com.google.krb5.NegotiateCallbackHandler;

import com.sun.security.auth.module.Krb5LoginModule;

import java.net.URLEncoder;

import java.security.Principal;

import java.util.Date;
import java.util.Enumeration;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import java.util.Vector;

import javax.security.auth.login.LoginException;

import org.apache.commons.httpclient.UsernamePasswordCredentials;

import org.ietf.jgss.GSSCredential;

/**
 * This class manages the authentication process for Kerberos protected 
 * content sources. It creates an HTTP connection to any Kerberized URL 
 * that is passed to the authenticate method. If the authentication 
 * process is succesful, a 200 (OK) error message is returned, and if there 
 * is any other error is sent back as well.
 * <p>
 * Once the process has finished successfully, a cookie is created with an  
 * encoded information that includes the username to be reused if this is 
 * needed in any other Authn/AuthZ module. It also populates the credentials 
 * vector with the user's Kerberos credential ("krb5") that the caller 
 * process should reuse when authorizing. An important consideration is the 
 * Kerberos tickets are usually max aged. So take that into account if you 
 * want to reused the ticket for authorization.
 * <p>
 * The Kerberos getting process could be done in two different ways (this is 
 * specified in the isNegotiate var):
 * <ul>
 *     <li>
 *       <b>Username/Password</b>: the Kerberos ticket is created using the 
 *       user credentials passed to the authorize() method. This can be done 
 *       just in the default Kerberos domain.
 *     </li>
 *     <li>
 *       <b>Negotiation</b>: the Kerberos ticket is got based on an HTTP 
 *       negotiation process between the browser and the frontend server that 
 *       instances this class-
 *     </li>
 *    </ul>
 *
 *@see KerberosAuthorizationProcess
 * 
 */
public class KerberosAuthenticationProcess implements AuthenticationProcessImpl {

    //Vars
    private static final String COOKIE_NAME = "gsa_krb5_auth";
    private static Logger logger = null;

    //Config
    private ValveConfiguration valveConf;

    //KRB vars
    private String krbconfig = null;
    private String krbini = null;

    //User vars
    private String username = null;
    private String timemills = null;
    private String id = null;
    private Subject userSubject = null;

    //KRB vars
    private GssSpNegoAuth spnegoAuth = null;
    private GssSpNegoServer spnegoServer = null;
    private Krb5Credentials credentials = null;
    private GSSCredential serverCreds = null;
    private Subject serverSubject = null;
    private String challenge = null;

    //KRB headers
    private static final String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate";
    private static final String HEADER_AUTHORIZATION = "authorization";
    private static final String NEG_TOKEN = "Negotiate";

    private Cookie gsaKrbAuthCookie = null;

    //Var that tells the default Credential ID for Kerberos
    private static final String KRB5_ID = "krb5";

    //This indicates if we are using Negotiation or just reuse username and passwords
    private boolean isNegotiate = false;

    //Cookie Max Age
    private int authMaxAge = -1;

    //Encoding
    private static final String encoder = "UTF-8";

    /**
     * Class constructor
     * <p>
     * Sets if it's a negotiation process or not
     * 
     * @param isNegotiate
     */
    public KerberosAuthenticationProcess(boolean isNegotiate) {

        this.isNegotiate = isNegotiate;

        //Instantiate logger
        logger = Logger.getLogger(KerberosAuthenticationProcess.class);

    }

    /**
     * Class constructor - default
     * <p>
     * It automatically sets the Negotiation process to false.
     * 
     */
    public KerberosAuthenticationProcess() {

        isNegotiate = false;

        //Instantiate logger
        logger = Logger.getLogger(KerberosAuthenticationProcess.class);

    }

    /**
     * Gets the username got during the negotiation process
     * 
     * @return the username
     */
    public String getUsername() {
        return username;
    }

    /**
     * Returns the time stamp
     * 
     * @return the time stamp (mills)
     */
    public String getTimemills() {
        return timemills;
    }

    /**
     * Gets the credential id
     * 
     * @return the credential id
     */
    public String getId() {
        return id;
    }

    /**
     * Gets the user subject that contains the Kerberos ticket as a result of 
     * a succesful authentication process
     * 
     * @return the user subject
     */
    public Subject getUserSubject() {
        return userSubject;
    }

    /**
     * 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;
    }

    /**
     * This is the main method that does the Kerberos authentication and 
     * should be invoked by the classes that would like to open a new 
     * authentication process against a Kerberized protected source.
     * <p>
     * It behaves differently if the it's set up as a Negotiation process or 
     * the Kerberos credentials are got from the username and password 
     * credentials. It reads "isNegotiate" var and invokes the proper method 
     * that manages Kerberos authentication specifically for each method.
     * <p>
     * If the Kerberos authentication result is OK, a cookie is created with an  
     * encoded information that includes the username to be reused if this is 
     * needed in any other Authn/AuthZ module. It also populates the credentials 
     * vector with the user's Kerberos credential ("krb5") that the caller 
     * process should reuse when authorizing.
     * 
     * @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 {

        //Vars             
        int responseCode = HttpServletResponse.SC_UNAUTHORIZED;
        Cookie[] cookies = null;

        // Read cookies
        cookies = request.getCookies();

        //Protection
        logger.debug("Checking if user already has Krb credentials. If so, return OK");

        try {
            if (creds != null) {
                if (creds.getCredential(KRB5_ID) != null) {

                    logger.debug("Credential found: " + KRB5_ID);

                    if (creds.getCredential(KRB5_ID).getSubject() != null) {

                        //user Kerberos subject already created, so user is authenticated                        
                        logger.debug("Kerberos subject already exists. Returning...");

                        // Set status code
                        responseCode = HttpServletResponse.SC_OK;

                        // Return
                        return responseCode;
                    }
                }
            }
        } catch (NullPointerException e) {
            logger.debug("Krb subject does not exist. Continue with the process...");
        }

        try {
            authMaxAge = Integer.parseInt(valveConf.getAuthMaxAge());
        } catch (NumberFormatException nfe) {
            logger.error(
                    "Configuration error: chack the configuration file as the number set for authMaxAge is not OK:");
        }

        try {
            logger.debug("Getting credentials");
            //Get Krb config files            
            krbconfig = valveConf.getKrbConfig().getKrbconfig();
            logger.debug("Krb config file: " + krbconfig);
            krbini = valveConf.getKrbConfig().getKrbini();
            logger.debug("Krb ini file: " + krbini);

            if ((isNegotiate) && (serverSubject == null)) {

                try {

                    initializeKerberos();

                } catch (Exception ex) {
                    logger.error("Exception during Server Kerberos config initialization: " + ex.getMessage(), ex);
                } finally {
                }

            }

            //Get user credentials
            //First read the u/p the credentails store, in this case using the same as the root login
            Credential userNamePwdCred = null;

            if (isNegotiate) {
                logger.debug("KerbAuth: IsNegotiate");
                responseCode = authNegotiate(request, response);
            } else {
                logger.debug("KerbAuth: It's NOT IsNegotiate with id: " + id);

                try {
                    logger.debug("HttpKrb: trying to get creds from repository id: " + id);
                    userNamePwdCred = creds.getCredential(id);
                } catch (NullPointerException npe) {
                    logger.error("NPE while reading credentials of ID: " + id);
                }

                if (userNamePwdCred == null) {
                    logger.debug("HttpKrb: trying to get creds from repository \"root\"");
                    userNamePwdCred = creds.getCredential("root");
                }

                //Execute Authentication method with username and password
                responseCode = authUsernamePassword(userNamePwdCred);
            }

            if (responseCode == HttpServletResponse.SC_OK) {
                //create cookie
                createCookie(request, response);
                //add cookie to the cookie array
                authCookies.add(gsaKrbAuthCookie);
                //add Krb credentials
                Credential krb5Cred = new Credential(KRB5_ID);
                krb5Cred.setKrbSubject(getUserSubject());
                krb5Cred.setUsername(getUsername());
                creds.add(krb5Cred);
            }

        } catch (Exception e) {
            logger.debug("Error creating Credentials: " + e.getMessage());
            e.printStackTrace();
            responseCode = HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
        }

        return responseCode;
    }

    /**
     * It does the Kerberos authentication when it has to be done through 
     * username and password. It looks in the default Kerberos domain defined 
     * in the Kerberos config file (krb5.ini or krb5.conf) if there is a valid 
     * user with those credentials. If so, it gets his/her Kerberos ticket.
     * 
     * @param userCred username and password credentials
     *
     * @return the method result in HTTP error format
     */
    public int authUsernamePassword(Credential userCred) {

        int result = HttpServletResponse.SC_UNAUTHORIZED;

        Krb5LoginModule login = null;
        userSubject = new Subject();

        logger.debug("authUsernamePassword: using username and password");

        try {

            //Create config objects and pass the credentials      
            Map state = new HashMap();
            UsernamePasswordCredentials usrpwdCred = new UsernamePasswordCredentials(userCred.getUsername(),
                    userCred.getPassword());
            state.put("javax.security.auth.login.name", usrpwdCred.getUserName());
            state.put("javax.security.auth.login.password", usrpwdCred.getPassword().toCharArray());
            state.put("java.security.krb5.conf", krbini);

            if (logger.isDebugEnabled()) {
                logger.debug("Username: " + usrpwdCred.getUserName());
            }

            Map option = new HashMap();
            String isDebug = "false";
            if (logger.isDebugEnabled()) {
                isDebug = "true";
            }
            option.put("debug", isDebug);
            option.put("tryFirstPass", "true");
            option.put("useTicketCache", "false");
            option.put("doNotPrompt", "false");
            option.put("storePass", "false");
            option.put("forwardable", "true");

            login = new Krb5LoginModule();
            login.initialize(userSubject, new NegotiateCallbackHandler(), state, option);

            if (login.login()) {
                login.commit();
                logger.debug("Login commit");
                if (id == null) {
                    username = usrpwdCred.getUserName();
                    id = username;
                }
                logger.debug("username is ... " + id);
                result = HttpServletResponse.SC_OK;
            }
        } catch (LoginException e) {
            logger.error("LoginException while creating id: " + e.getMessage(), e);
            result = HttpServletResponse.SC_UNAUTHORIZED;
        } catch (Exception e) {
            e.printStackTrace();
            logger.error("Exception while creating id: " + e.getMessage(), e);
            result = HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
        }

        return result;

    }

    /**
     *  It does the Kerberos authentication using the negotiation way. It 
     *  establishes a negotiation with the browser sending HTTP error messages.
     *  
     * @param request HTTP request
     * @param response HTTP response
     * 
     * @return the method result in HTTP error format
     */
    public int authNegotiate(HttpServletRequest request, HttpServletResponse response) {
        //Implement Kerberos negotiatiation and authentication

        int result = HttpServletResponse.SC_UNAUTHORIZED;

        //read Authorization header
        boolean isAuthorization = false;

        //reset challenge
        challenge = null;

        Enumeration headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String headerName = (String) headerNames.nextElement();
            if (headerName.toLowerCase().equals(HEADER_AUTHORIZATION)) {
                isAuthorization = true;
                challenge = request.getHeader(headerName);
                logger.debug("Authorization header read: " + challenge);
                break;
            }
        }

        // Instantiate the authentication process class
        try {

            //Check if the header sent by the client is Authorization or not
            if (!isAuthorization) {
                logger.debug("Sending.... " + HEADER_WWW_AUTHENTICATE);

                response.addHeader(HEADER_WWW_AUTHENTICATE, NEG_TOKEN);

                // Return
                return HttpServletResponse.SC_UNAUTHORIZED;
            } else {
                if (challenge == null) {

                    // Log error
                    logger.error("The browser did not send the challenge properly");

                    // Return
                    return HttpServletResponse.SC_INTERNAL_SERVER_ERROR;

                }
            }

            //Check if serverCreds and subject are properly set                    
            if ((serverCreds == null) || (serverSubject == null)) {

                // Log error
                logger.error("The GSA authentication servlet cannot get Server credentials");

                // Return
                return HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
            }

            //Initialize Spnego server
            spnegoServer = new GssSpNegoServer(serverCreds, spnegoAuth.getManager(), serverSubject);

            boolean isComplete = false;

            try {
                isComplete = spnegoServer.processSpNego(challenge);
                logger.debug("isComplete? " + isComplete);

                if (!isComplete) {
                    logger.debug("Sending.... " + HEADER_WWW_AUTHENTICATE);
                    // Raise error
                    response.addHeader(HEADER_WWW_AUTHENTICATE, NEG_TOKEN + " " + spnegoServer.getResponseToken());

                    return HttpServletResponse.SC_UNAUTHORIZED;
                } else {
                    if (spnegoServer.isFailed()) {
                        logger.error("Error during the negotiation process");

                        return HttpServletResponse.SC_UNAUTHORIZED;
                    } else { //Negotiation result is OK

                        //Add cookies before returning

                        //Get client subject
                        userSubject = spnegoServer.getClientSubject();

                        //Preparing Unique id
                        username = getPrincipalStr(userSubject);
                        id = username;

                        logger.debug("username is ... " + id);

                        result = HttpServletResponse.SC_OK;

                    }
                }

            } catch (Exception ex) {
                logger.error("Exception during the negotiation: " + ex.getMessage(), ex);
                return HttpServletResponse.SC_UNAUTHORIZED;
            } finally {
            }

        } catch (Exception e) {

            // Log error
            logger.error("Exception during the negotiation: " + e.getMessage(), e);

            return HttpServletResponse.SC_UNAUTHORIZED;
        }

        return result;
    }

    /**
     * Initializes Kerberos server configuration
     * 
     */
    public void initializeKerberos() {
        //Read Krb ticket and instantiate                     
        setKrbCredentials(new Krb5Credentials(krbconfig, krbini, krbconfig));
        spnegoAuth = new GssSpNegoAuth(credentials);
        spnegoAuth.createServerCreds();
        serverSubject = spnegoAuth.getSubject();
        serverCreds = spnegoAuth.getServerCreds();

        // Debug
        if (logger.isDebugEnabled()) {
            logger.debug("AuthenticationKerb initialize");
        }
    }

    /**
     * Sets Kerberos credentials
     * 
     * @param credentials Kerberos credentials
     */
    public void setKrbCredentials(Krb5Credentials credentials) {
        this.credentials = credentials;
    }

    /**
     * Gets if the Kerberos authentication process is configured as 
     * Negotiate or not
     * 
     * @return boolean - "true" if it's a Kerberos negotiation process
     */
    public boolean getIsNegotiate() {
        return isNegotiate;
    }

    /**
     * Sets if it's a Kerberos negotiation process or not
     * 
     * @param isNegotiate if it's a Kerberos negotiation process
     */
    public void setIsNegotiate(boolean isNegotiate) {
        logger.debug("IsNegotiate: " + isNegotiate);
        this.isNegotiate = isNegotiate;
    }

    /**
     * Gets the Kerberos config file location (krb5.ini or krb5.conf)
     * 
     * @return the system's kerberos config file path
     */
    public String getKrbini() {
        return krbini;
    }

    /**
     * Sets the Kerberos config file location (krb5.ini or krb5.conf)
     * 
     * @param krbini the system's kerberos config file path
     */
    public void setKrbini(String krbini) {
        logger.debug("krbini: " + krbini);
        this.krbini = krbini;
    }

    /**
     * Gets the Kerberos Java config file location. It contains the Java 
     * parameters needed when Kerberos negotiation is in place
     * 
     * @return the kerberos Java config file path
     */
    public String getKrbconfig() {
        return krbconfig;
    }

    /**
     * Sets the Kerberos Java config file location
     * 
     * @param krbconfig the kerberos Java config file path
     */
    public void setKrbconfig(String krbconfig) {
        logger.debug("krbconfig: " + krbconfig);
        this.krbconfig = krbconfig;
    }

    /**
     * Gets the current time in String format
     * 
     * @return the current time
     */
    public String getTimeStr() {
        Date date = new Date();
        long mills = date.getTime();
        return new String(new Long(mills).toString());
    }

    /**
     * Gets the main principal from the user subject got as a result 
     * of the Kerberos authentication process
     * 
     * @param subject user subject
     * 
     * @return the user principal
     */
    public static String getPrincipalStr(Subject subject) {

        String principal = null;

        logger.debug("Getting principal from Subject");
        try {
            Set principals = subject.getPrincipals();
            if (!principals.isEmpty()) {
                logger.debug("Subject contains at least one Principal");
                Iterator it = principals.iterator();
                if (it.hasNext()) {
                    Principal ppal = (Principal) it.next();
                    principal = ppal.getName().substring(0, ppal.getName().indexOf("@"));
                    logger.debug("Getting the first principal: " + principal);
                }
            }
        } catch (Exception e) {
            logger.error("Error retrieving the client's Principal from the Subject: " + e.getMessage(), e);
        }
        return principal;
    }

    /**
     * Creates the authentication cookie sent back to the caller as a 
     * result of a successful Kerberos authentication process
     * 
     * @param request HTTP request
     * @param response HTTP response
     */
    public void createCookie(HttpServletRequest request, HttpServletResponse response) {

        logger.debug("Creating the Kerberos Authn cookie");

        //Cookie value
        String krbCookie = null;
        try {

            //Get the Base64-encoded ID for the Cookie
            String krbIDBase64Encoded = (new UserIDEncoder()).getID(getUsername(), System.currentTimeMillis());
            //URL encode the value of the cookie before adding
            krbCookie = URLEncoder.encode(krbIDBase64Encoded, encoder);

            if (krbCookie == null) {
                krbCookie = "";
            }

        } catch (Exception ex) {
            logger.error("Error when setting the Krb cookie value: " + ex.getMessage(), ex);
            krbCookie = "";
        }

        // Instantiate authentication cookie with default value
        gsaKrbAuthCookie = new Cookie(COOKIE_NAME, krbCookie);

        // Set cookie domain
        gsaKrbAuthCookie.setDomain(valveConf.getAuthCookieDomain());

        // Set cookie path
        gsaKrbAuthCookie.setPath(valveConf.getAuthCookiePath());

        // Set cookie max age
        gsaKrbAuthCookie.setMaxAge(authMaxAge);

        // Debug
        if (logger.isDebugEnabled())
            logger.debug("Kerb Auth cookie set");

        //add sendCookies support
        boolean isSessionEnabled = new Boolean(valveConf.getSessionConfig().isSessionEnabled()).booleanValue();
        boolean sendCookies = false;
        if (isSessionEnabled) {
            sendCookies = new Boolean(valveConf.getSessionConfig().getSendCookies()).booleanValue();
        }
        if ((!isSessionEnabled) || ((isSessionEnabled) && (sendCookies))) {
            response.addCookie(gsaKrbAuthCookie);
        }

    }

}