package org.apereo.cas.web.flow;
import org.apereo.cas.authentication.Credential;
import org.apereo.cas.authentication.adaptive.AdaptiveAuthenticationPolicy;
import org.apereo.cas.support.spnego.authentication.principal.SpnegoCredential;
import org.apereo.cas.support.spnego.util.SpnegoConstants;
import org.apereo.cas.util.EncodingUtils;
import org.apereo.cas.web.flow.resolver.CasDelegatingWebflowEventResolver;
import org.apereo.cas.web.flow.resolver.CasWebflowEventResolver;
import org.apereo.cas.web.support.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import org.springframework.webflow.execution.RequestContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.nio.charset.Charset;
/**
* Second action of a SPNEGO flow : decode the gssapi-data and build a new
* {@link SpnegoCredential}.
* <p>
* Once AbstractNonInteractiveCredentialsAction has executed the authentication
* procedure, this action check whether a principal is present in Credential and
* add corresponding response headers.</p>
*
* @author Arnaud Lesueur
* @author Marc-Antoine Garrigue
* @see <a href="http://ietfreport.isoc.org/idref/rfc4559/#page-2">RFC 4559</a>
* @since 3.1
*/
public class SpnegoCredentialsAction extends AbstractNonInteractiveCredentialsAction {
private static final Logger LOGGER = LoggerFactory.getLogger(SpnegoCredentialsAction.class);
private final boolean ntlm;
private final String messageBeginPrefix;
/**
* Behavior in case of SPNEGO authentication failure :
* <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;
public SpnegoCredentialsAction(final CasDelegatingWebflowEventResolver initialAuthenticationAttemptWebflowEventResolver,
final CasWebflowEventResolver serviceTicketRequestWebflowEventResolver,
final AdaptiveAuthenticationPolicy adaptiveAuthenticationPolicy, final boolean ntlm,
final boolean send401OnAuthenticationFailure) {
super(initialAuthenticationAttemptWebflowEventResolver, serviceTicketRequestWebflowEventResolver, adaptiveAuthenticationPolicy);
this.ntlm = ntlm;
this.messageBeginPrefix = (ntlm ? SpnegoConstants.NTLM : SpnegoConstants.NEGOTIATE) + ' ';
this.send401OnAuthenticationFailure = send401OnAuthenticationFailure;
}
@Override
protected Credential constructCredentialsFromRequest(final RequestContext context) {
final HttpServletRequest request = WebUtils.getHttpServletRequest(context);
final String authorizationHeader = request.getHeader(SpnegoConstants.HEADER_AUTHORIZATION);
LOGGER.debug("SPNEGO Authorization header located as [{}]", authorizationHeader);
if (StringUtils.hasText(authorizationHeader)
&& authorizationHeader.startsWith(this.messageBeginPrefix)
&& authorizationHeader.length() > this.messageBeginPrefix.length()) {
LOGGER.debug("SPNEGO Authorization header found with [{}] bytes",
authorizationHeader.length() - this.messageBeginPrefix.length());
final byte[] token = EncodingUtils.decodeBase64(authorizationHeader.substring(this.messageBeginPrefix.length()));
if (token == null) {
LOGGER.warn("Could not decode authorization header in Base64");
return null;
}
LOGGER.debug("Obtained token: [{}]. Creating SPNEGO credential...", new String(token, Charset.defaultCharset()));
return new SpnegoCredential(token);
}
LOGGER.warn("SPNEGO Authorization header not found under [{}] or it does not begin with the prefix [{}]",
SpnegoConstants.HEADER_AUTHORIZATION, this.messageBeginPrefix);
return null;
}
@Override
protected void onError(final RequestContext context) {
setResponseHeader(context);
}
@Override
protected void onSuccess(final RequestContext context) {
setResponseHeader(context);
}
/**
* Sets the response header based on the retrieved token.
*
* @param context the context
*/
private void setResponseHeader(final RequestContext context) {
final Credential credential = WebUtils.getCredential(context);
if (credential == null) {
LOGGER.debug("No credential was provided. No response header set.");
return;
}
final HttpServletResponse response = WebUtils.getHttpServletResponse(context);
final SpnegoCredential spnegoCredentials = (SpnegoCredential) credential;
final byte[] nextToken = spnegoCredentials.getNextToken();
if (nextToken != null) {
LOGGER.debug("Obtained output token: [{}]", new String(nextToken, Charset.defaultCharset()));
response.setHeader(SpnegoConstants.HEADER_AUTHENTICATE, (this.ntlm
? SpnegoConstants.NTLM : SpnegoConstants.NEGOTIATE)
+ ' ' + EncodingUtils.encodeBase64(nextToken));
} else {
LOGGER.debug("Unable to obtain the output token required.");
}
if (spnegoCredentials.getPrincipal() == null && this.send401OnAuthenticationFailure) {
LOGGER.debug("Setting HTTP Status to 401");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
}
}
}