/*
 * Licensed to Jasig under one or more contributor license
 * agreements. See the NOTICE file distributed with this work
 * for additional information regarding copyright ownership.
 * Jasig 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 the following location:
 *
 *   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 org.jasig.cas.support.spnego.web.flow;

import jcifs.util.Base64;
import org.jasig.cas.authentication.principal.Credentials;
import org.jasig.cas.support.spnego.authentication.principal.SpnegoCredentials;
import org.jasig.cas.support.spnego.util.SpnegoConstants;
import org.jasig.cas.web.flow.AbstractNonInteractiveCredentialsAction;
import org.jasig.cas.web.support.WebUtils;
import org.springframework.util.StringUtils;
import org.springframework.webflow.execution.RequestContext;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Second action of a SPNEGO flow : decode the gssapi-data and build a new
 * {@link org.jasig.cas.support.spnego.authentication.principal.SpnegoCredentials}.<br/>
 * Once AbstractNonInteractiveCredentialsAction has executed the authentication
 * procedure, this action check wether a principal is present in Credentials and
 * add correspondings response headers.
 * 
 * @author Arnaud Lesueur
 * @author Marc-Antoine Garrigue
 * @version $Revision$ $Date$
 * @see <a href='http://ietfreport.isoc.org/idref/rfc4559/#page-2'>RFC 4559</a>
 * @since 3.1
 */
public final class SpnegoCredentialsAction extends
    AbstractNonInteractiveCredentialsAction {

    private boolean ntlm = false;

    private String messageBeginPrefix = constructMessagePrefix();
    
    /**
     * Behavior in case of SPNEGO authentication failure :<br />
     * <ul><li>True : if spnego is the last authentication method with no fallback.</li>
     * <li>False : if an interactive view (eg: login page) should be send to user as SPNEGO failure fallback</li>
     * </ul>
     */
    private boolean send401OnAuthenticationFailure = true;

    protected Credentials constructCredentialsFromRequest(
        final RequestContext context) {
        final HttpServletRequest request = WebUtils
            .getHttpServletRequest(context);

        final String authorizationHeader = request
            .getHeader(SpnegoConstants.HEADER_AUTHORIZATION);

        if (StringUtils.hasText(authorizationHeader)
            && authorizationHeader.startsWith(this.messageBeginPrefix)
            && authorizationHeader.length() > this.messageBeginPrefix.length()) {
            if (logger.isDebugEnabled()) {
                logger.debug("SPNEGO Authorization header found with "
                    + (authorizationHeader.length() - this.messageBeginPrefix
                        .length()) + " bytes");
            }
            final byte[] token = Base64.decode(authorizationHeader
                .substring(this.messageBeginPrefix.length()));
            if (logger.isDebugEnabled()) {
                logger.debug("Obtained token: " + new String(token));
            }
            return new SpnegoCredentials(token);
        }

        return null;
    }

    protected String constructMessagePrefix() {
        return (this.ntlm ? SpnegoConstants.NTLM : SpnegoConstants.NEGOTIATE)
            + " ";
    }

    protected void onError(final RequestContext context,
        final Credentials credentials) {
        setResponseHeader(context, credentials);
    }

    protected void onSuccess(final RequestContext context,
        final Credentials credentials) {
        setResponseHeader(context, credentials);
    }

    private void setResponseHeader(final RequestContext context,
        final Credentials credentials) {
        if (credentials == null) {
            return;
        }

        final HttpServletResponse response = WebUtils
            .getHttpServletResponse(context);
        final SpnegoCredentials spnegoCredentials = (SpnegoCredentials) credentials;
        final byte[] nextToken = spnegoCredentials.getNextToken();
        if (nextToken != null) {
            if (logger.isDebugEnabled()) {
                logger.debug("Obtained output token: " + new String(nextToken));
            }
            response.setHeader(SpnegoConstants.HEADER_AUTHENTICATE, (this.ntlm
                ? SpnegoConstants.NTLM : SpnegoConstants.NEGOTIATE)
                + " " + Base64.encode(nextToken));
        } else {
            logger.debug("Unable to obtain the output token required.");
        }

        if ((spnegoCredentials.getPrincipal() == null) && send401OnAuthenticationFailure) {
            logger.debug("Setting HTTP Status to 401");
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        }
    }

    public void setNtlm(final boolean ntlm) {
        this.ntlm = ntlm;
        this.messageBeginPrefix = constructMessagePrefix();
    }

    public void setSend401OnAuthenticationFailure(final boolean send401OnAuthenticationFailure) {
        this.send401OnAuthenticationFailure = send401OnAuthenticationFailure;
    }

}
