/*
* JBoss, Home of Professional Open Source
*
* Copyright 2013 Red Hat, Inc. and/or its affiliates.
*
* 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 org.picketlink.identity.federation.bindings.jetty.sp;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.security.DefaultUserIdentity;
import org.eclipse.jetty.security.ServerAuthException;
import org.eclipse.jetty.security.UserAuthentication;
import org.eclipse.jetty.security.authentication.FormAuthenticator;
import org.eclipse.jetty.server.Authentication;
import org.eclipse.jetty.server.HttpChannel;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.UserIdentity;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.MultiMap;
import org.eclipse.jetty.util.URIUtil;
import org.jboss.security.audit.AuditLevel;
import org.picketlink.common.ErrorCodes;
import org.picketlink.common.PicketLinkLogger;
import org.picketlink.common.PicketLinkLoggerFactory;
import org.picketlink.common.constants.GeneralConstants;
import org.picketlink.common.exceptions.ConfigurationException;
import org.picketlink.common.exceptions.ParsingException;
import org.picketlink.common.exceptions.ProcessingException;
import org.picketlink.common.exceptions.fed.AssertionExpiredException;
import org.picketlink.common.util.DocumentUtil;
import org.picketlink.common.util.StringUtil;
import org.picketlink.common.util.SystemPropertiesUtil;
import org.picketlink.config.federation.AuthPropertyType;
import org.picketlink.config.federation.KeyProviderType;
import org.picketlink.config.federation.PicketLinkType;
import org.picketlink.config.federation.SPType;
import org.picketlink.config.federation.handler.Handlers;
import org.picketlink.identity.federation.api.saml.v2.metadata.MetaDataExtractor;
import org.picketlink.identity.federation.core.audit.PicketLinkAuditEvent;
import org.picketlink.identity.federation.core.audit.PicketLinkAuditEventType;
import org.picketlink.identity.federation.core.audit.PicketLinkAuditHelper;
import org.picketlink.identity.federation.core.interfaces.TrustKeyManager;
import org.picketlink.identity.federation.core.parsers.saml.SAMLParser;
import org.picketlink.identity.federation.core.saml.v2.factories.SAML2HandlerChainFactory;
import org.picketlink.identity.federation.core.saml.v2.impl.DefaultSAML2HandlerChainConfig;
import org.picketlink.identity.federation.core.saml.v2.interfaces.SAML2Handler;
import org.picketlink.identity.federation.core.saml.v2.interfaces.SAML2HandlerChain;
import org.picketlink.identity.federation.core.saml.v2.interfaces.SAML2HandlerChainConfig;
import org.picketlink.identity.federation.core.saml.v2.interfaces.SAML2HandlerResponse;
import org.picketlink.identity.federation.core.saml.v2.util.HandlerUtil;
import org.picketlink.identity.federation.core.saml.workflow.ServiceProviderSAMLWorkflow;
import org.picketlink.identity.federation.core.util.CoreConfigUtil;
import org.picketlink.identity.federation.core.util.XMLSignatureUtil;
import org.picketlink.identity.federation.saml.v2.metadata.EndpointType;
import org.picketlink.identity.federation.saml.v2.metadata.EntitiesDescriptorType;
import org.picketlink.identity.federation.saml.v2.metadata.EntityDescriptorType;
import org.picketlink.identity.federation.saml.v2.metadata.IDPSSODescriptorType;
import org.picketlink.identity.federation.saml.v2.metadata.KeyDescriptorType;
import org.picketlink.identity.federation.web.config.AbstractSAMLConfigurationProvider;
import org.picketlink.identity.federation.web.core.HTTPContext;
import org.picketlink.identity.federation.web.core.SessionManager;
import org.picketlink.identity.federation.web.process.ServiceProviderBaseProcessor;
import org.picketlink.identity.federation.web.process.ServiceProviderSAMLRequestProcessor;
import org.picketlink.identity.federation.web.process.ServiceProviderSAMLResponseProcessor;
import org.picketlink.identity.federation.web.util.ConfigurationUtil;
import org.picketlink.identity.federation.web.util.SAMLConfigurationProvider;
import org.w3c.dom.Document;
import javax.security.auth.Subject;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionListener;
import javax.xml.crypto.dsig.CanonicalizationMethod;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.security.Principal;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import static org.picketlink.common.constants.GeneralConstants.CONFIG_FILE_LOCATION;
import static org.picketlink.common.util.StringUtil.isNotNull;
import static org.picketlink.common.util.StringUtil.isNullOrEmpty;
/**
* @author Anil Saldhana
* @since December 09, 2013
*/
public class SPFormAuthenticator extends FormAuthenticator {
private static final PicketLinkLogger logger = PicketLinkLoggerFactory.getLogger();
protected transient String samlHandlerChainClass = null;
protected ServletContext theServletContext = null;
protected Map<String, Object> chainConfigOptions = new HashMap<String, Object>();
/**
* The user can inject a fully qualified name of a
* {@link org.picketlink.identity.federation.web.util.SAMLConfigurationProvider}
*/
protected SAMLConfigurationProvider configProvider = null;
/**
* If the service provider is configured with an IDP metadata file, then this certificate can be picked up from the metadata
*/
protected transient X509Certificate idpCertificate = null;
protected int timerInterval = -1;
protected Timer timer = null;
public static final String EMPTY_PASSWORD = "EMPTY_STR";
protected boolean enableAudit = false;
public static final String FORM_PRINCIPAL_NOTE = "picketlink.form.principal";
public static final String FORM_ROLES_NOTE = "picketlink.form.roles";
public static final String FORM_REQUEST_NOTE = "picketlink.REQUEST";
public static final String logoutPage = "/logout.html"; // get from configuration
protected transient SAML2HandlerChain chain = null;
protected SPType spConfiguration = null;
protected PicketLinkType picketLinkConfiguration = null;
protected String serviceURL = null;
protected String identityURL = null;
protected String issuerID = null;
protected String configFile;
// Whether the authenticator has to to save and restore request
protected boolean saveRestoreRequest = true;
/**
* A Lock for Handler operations in the chain
*/
protected Lock chainLock = new ReentrantLock();
protected String canonicalizationMethod = CanonicalizationMethod.EXCLUSIVE_WITH_COMMENTS;
protected PicketLinkAuditHelper auditHelper = null;
protected TrustKeyManager keyManager;
public SPFormAuthenticator() {
}
public SPFormAuthenticator(String login, String error, boolean dispatch) {
super(login, error, dispatch);
}
@Override
public void setConfiguration(AuthConfiguration configuration) {
super.setConfiguration(configuration);
String contextPath = ContextHandler.getCurrentContext().getContextPath();
theServletContext = ContextHandler.getCurrentContext().getContext(contextPath);
startPicketLink();
}
@Override
public Authentication validateRequest(ServletRequest servletRequest, ServletResponse servletResponse, boolean mandatory)
throws ServerAuthException {
// TODO: Deal with character encoding
// request.setCharacterEncoding(xyz)
String contextPath = ContextHandler.getCurrentContext().getContextPath();
theServletContext = ContextHandler.getCurrentContext().getContext(contextPath);
// Get the session
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
HttpSession session = request.getSession();
System.out.println("Request ID=" + servletRequest.toString());
System.out.println("Session ID=" + session.getId());
// check if this call is resulting from the redirect after successful authentication.
// if so, make the authentication successful and continue the original request
if (saveRestoreRequest && matchRequest(request)) {
Principal savedPrincipal = (Principal) session.getAttribute(FORM_PRINCIPAL_NOTE);
List<String> savedRoles = (List<String>) session.getAttribute(FORM_ROLES_NOTE);
Authentication registeredAuthentication = register(request, savedPrincipal, savedRoles);
// try to restore the original request (including post data, etc...)
if (restoreRequest(request, session)) {
// success! user is authenticated; continue processing original request
return registeredAuthentication;
} else {
// no saved request found...
return Authentication.UNAUTHENTICATED;
}
}
ServiceProviderSAMLWorkflow serviceProviderSAMLWorkflow = new ServiceProviderSAMLWorkflow();
serviceProviderSAMLWorkflow.setRedirectionHandler(new JettyRedirectionHandler());
// Eagerly look for Local LogOut
boolean localLogout = serviceProviderSAMLWorkflow.isLocalLogoutRequest(request);
if (localLogout) {
try {
serviceProviderSAMLWorkflow.sendToLogoutPage(request, response, session, theServletContext, logoutPage);
} catch (ServletException e) {
logger.samlLogoutError(e);
throw new RuntimeException(e);
} catch (IOException e1) {
logger.samlLogoutError(e1);
throw new RuntimeException(e1);
}
return Authentication.UNAUTHENTICATED;
}
String samlRequest = request.getParameter(GeneralConstants.SAML_REQUEST_KEY);
String samlResponse = request.getParameter(GeneralConstants.SAML_RESPONSE_KEY);
Principal principal = request.getUserPrincipal();
try {
// If we have already authenticated the user and there is no request from IDP or logout from user
if (principal != null
&& !(serviceProviderSAMLWorkflow.isLocalLogoutRequest(request) || isNotNull(samlRequest) || isNotNull(samlResponse)))
return Authentication.SEND_SUCCESS;
// General User Request
if (!isNotNull(samlRequest) && !isNotNull(samlResponse)) {
return generalUserRequest(servletRequest, servletResponse, mandatory);
}
// Handle a SAML Response from IDP
if (isNotNull(samlResponse)) {
return handleSAMLResponse(servletRequest, servletResponse, mandatory);
}
// Handle SAML Requests from IDP
if (isNotNull(samlRequest)) {
return handleSAMLRequest(servletRequest, servletResponse, mandatory);
}// end if
// local authentication
return localAuthentication(servletRequest, servletResponse, mandatory);
} catch (IOException e) {
if (StringUtil.isNotNull(spConfiguration.getErrorPage())) {
try {
request.getRequestDispatcher(spConfiguration.getErrorPage()).forward(request, response);
} catch (ServletException e1) {
logger.samlErrorPageForwardError(spConfiguration.getErrorPage(), e1);
} catch (IOException e1) {
logger.samlErrorPageForwardError(spConfiguration.getErrorPage(), e1);
}
return Authentication.UNAUTHENTICATED;
} else {
throw new RuntimeException(e);
}
}
}
/**
* Handle the user invocation for the first time
*
* @param servletRequest
* @param servletResponse
* @param mandatory
* @return
* @throws IOException
*/
private Authentication generalUserRequest(ServletRequest servletRequest, ServletResponse servletResponse, boolean mandatory)
throws IOException, ServerAuthException {
//only perform SAML Authentication if it is mandatory
if(!mandatory){
Request request = (Request) servletRequest;
return request.getAuthentication();
}
ServiceProviderSAMLWorkflow serviceProviderSAMLWorkflow = new ServiceProviderSAMLWorkflow();
serviceProviderSAMLWorkflow.setRedirectionHandler(new JettyRedirectionHandler());
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
HttpSession session = request.getSession(false);
boolean willSendRequest = false;
HTTPContext httpContext = new HTTPContext(request, response, theServletContext);
Set<SAML2Handler> handlers = chain.handlers();
boolean postBinding = spConfiguration.getBindingType().equals("POST");
// Neither saml request nor response from IDP
// So this is a user request
SAML2HandlerResponse saml2HandlerResponse = null;
try {
ServiceProviderBaseProcessor baseProcessor = new ServiceProviderBaseProcessor(postBinding, serviceURL,
this.picketLinkConfiguration);
if (issuerID != null)
baseProcessor.setIssuer(issuerID);
baseProcessor.setIdentityURL(identityURL);
baseProcessor.setAuditHelper(auditHelper);
saml2HandlerResponse = baseProcessor.process(httpContext, handlers, chainLock);
} catch (ProcessingException pe) {
logger.samlSPHandleRequestError(pe);
throw new RuntimeException(pe);
} catch (ParsingException pe) {
logger.samlSPHandleRequestError(pe);
throw new RuntimeException(pe);
} catch (ConfigurationException pe) {
logger.samlSPHandleRequestError(pe);
throw new RuntimeException(pe);
}
willSendRequest = saml2HandlerResponse.getSendRequest();
Document samlResponseDocument = saml2HandlerResponse.getResultingDocument();
String relayState = saml2HandlerResponse.getRelayState();
String destination = saml2HandlerResponse.getDestination();
String destinationQueryStringWithSignature = saml2HandlerResponse.getDestinationQueryStringWithSignature();
if (destination != null && samlResponseDocument != null) {
try {
if (saveRestoreRequest) {
this.saveRequest(request, session);
}
if (enableAudit) {
PicketLinkAuditEvent auditEvent = new PicketLinkAuditEvent(AuditLevel.INFO);
auditEvent.setType(PicketLinkAuditEventType.REQUEST_TO_IDP);
auditEvent.setWhoIsAuditing(theServletContext.getContextPath());
auditHelper.audit(auditEvent);
}
serviceProviderSAMLWorkflow.sendRequestToIDP(destination, samlResponseDocument, relayState, response,
willSendRequest, destinationQueryStringWithSignature, isHttpPostBinding());
return Authentication.SEND_CONTINUE;
} catch (Exception e) {
logger.samlSPHandleRequestError(e);
throw logger.samlSPProcessingExceptionError(e);
}
}
return localAuthentication(servletRequest, servletResponse, mandatory);
}
protected boolean matchRequest(HttpServletRequest request) {
HttpSession session = request.getSession(false);
synchronized (session) {
String j_uri = (String) session.getAttribute(__J_URI);
if (j_uri != null) {
// check if the request is for the same url as the original and restore
// params if it was a post
StringBuffer buf = request.getRequestURL();
if (request.getQueryString() != null)
buf.append("?").append(request.getQueryString());
if (j_uri.equals(buf.toString())) {
return true;
}
}
return false;
}
}
protected Authentication register(HttpServletRequest httpServletRequest, Principal principal, List<String> roles) {
if (roles == null) {
roles = new ArrayList<String>();
}
HttpSession session = httpServletRequest.getSession(false);
session.setAttribute(FORM_PRINCIPAL_NOTE, principal);
session.setAttribute(FORM_ROLES_NOTE, roles);
Request request = (Request) httpServletRequest;
Authentication authentication = request.getAuthentication();
if (!(authentication instanceof UserAuthentication)) {
Subject theSubject = new Subject();
String[] theRoles = new String[roles.size()];
roles.toArray(theRoles);
UserIdentity userIdentity = new DefaultUserIdentity(theSubject, principal, theRoles);
authentication = new UserAuthentication(getAuthMethod(), userIdentity);
request.setAuthentication(authentication);
}
return authentication;
}
protected boolean restoreRequest(HttpServletRequest request, HttpSession session) {
synchronized (session) {
String j_uri = (String) session.getAttribute(__J_URI);
if (j_uri != null) {
// check if the request is for the same url as the original and restore
// params if it was a post
StringBuffer buf = request.getRequestURL();
if (request.getQueryString() != null)
buf.append("?").append(request.getQueryString());
/*
* if (j_uri.equals(buf.toString())) {
*/
MultiMap<String> j_post = (MultiMap<String>) session.getAttribute(__J_POST);
if (j_post != null) {
Request base_request = HttpChannel.getCurrentHttpChannel().getRequest();
base_request.setParameters(j_post);
}
session.removeAttribute(__J_URI);
session.removeAttribute(__J_METHOD);
session.removeAttribute(__J_POST);
// }
return true;
}
return false;
}
}
protected void saveRequest(HttpServletRequest request, HttpSession session) {
// remember the current URI
synchronized (session) {
// But only if it is not set already, or we save every uri that leads to a login form redirect
if (session.getAttribute(__J_URI) == null) {
StringBuffer buf = request.getRequestURL();
if (request.getQueryString() != null)
buf.append("?").append(request.getQueryString());
session.setAttribute(__J_URI, buf.toString());
session.setAttribute(__J_METHOD, request.getMethod());
if (MimeTypes.Type.FORM_ENCODED.is(request.getContentType()) && HttpMethod.POST.is(request.getMethod())) {
Request base_request = (request instanceof Request) ? (Request) request : HttpChannel
.getCurrentHttpChannel().getRequest();
base_request.extractParameters();
session.setAttribute(__J_POST, new MultiMap<String>(base_request.getParameters()));
}
}
}
}
/**
* Fall back on local authentication at the service provider side
*
* @param servletRequest
* @param servletRequest
* @param mandatory
* @return
* @throws IOException
*/
protected Authentication localAuthentication(ServletRequest servletRequest, ServletResponse servletResponse,
boolean mandatory) throws IOException, ServerAuthException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
if (request.getUserPrincipal() == null) {
logger.samlSPFallingBackToLocalFormAuthentication();// fallback
try {
return super.validateRequest(servletRequest, servletResponse, mandatory);
} catch (NoSuchMethodError e) {
/*
* // Use Reflection try { Method method = super.getClass().getMethod("authenticate", new Class[] {
* HttpServletRequest.class, HttpServletResponse.class, LoginConfig.class }); return (Boolean)
* method.invoke(this, new Object[] { request.getRequest(), response.getResponse(), loginConfig }); } catch
* (Exception ex) { throw logger.unableLocalAuthentication(ex); }
*/
}
} else {
return Authentication.SEND_SUCCESS;
}
return Authentication.UNAUTHENTICATED;
}
/**
* Handle the IDP Request
*
* @param servletRequest
* @param servletResponse
* @param mandatory
* @return
* @throws IOException
*/
private Authentication handleSAMLRequest(ServletRequest servletRequest, ServletResponse servletResponse, boolean mandatory)
throws IOException, ServerAuthException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
String samlRequest = request.getParameter(GeneralConstants.SAML_REQUEST_KEY);
HTTPContext httpContext = new HTTPContext(request, response, theServletContext);
Set<SAML2Handler> handlers = chain.handlers();
try {
ServiceProviderSAMLRequestProcessor requestProcessor = new ServiceProviderSAMLRequestProcessor(request.getMethod()
.equals("POST"), this.serviceURL, this.picketLinkConfiguration);
requestProcessor.setTrustKeyManager(keyManager);
boolean result = requestProcessor.process(samlRequest, httpContext, handlers, chainLock);
if (enableAudit) {
PicketLinkAuditEvent auditEvent = new PicketLinkAuditEvent(AuditLevel.INFO);
auditEvent.setType(PicketLinkAuditEventType.REQUEST_FROM_IDP);
auditEvent.setWhoIsAuditing(theServletContext.getContextPath());
auditHelper.audit(auditEvent);
}
// If response is already commited, we need to stop with processing of HTTP request
if (response.isCommitted())
return Authentication.UNAUTHENTICATED;
if (result)
return Authentication.SEND_SUCCESS;
} catch (Exception e) {
logger.samlSPHandleRequestError(e);
throw logger.samlSPProcessingExceptionError(e);
}
return localAuthentication(servletRequest, servletResponse, mandatory);
}
/**
* Handle IDP Response
*
* @param servletRequest
* @param servletResponse
* @return
* @throws IOException
*/
private Authentication handleSAMLResponse(ServletRequest servletRequest, ServletResponse servletResponse, boolean mandatory)
throws IOException, ServerAuthException {
ServiceProviderSAMLWorkflow serviceProviderSAMLWorkflow = new ServiceProviderSAMLWorkflow();
serviceProviderSAMLWorkflow.setRedirectionHandler(new JettyRedirectionHandler());
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
HttpSession session = request.getSession(false);
String samlResponse = request.getParameter(GeneralConstants.SAML_RESPONSE_KEY);
boolean willSendRequest = false;
HTTPContext httpContext = new HTTPContext(request, response, theServletContext);
Set<SAML2Handler> handlers = chain.handlers();
Principal principal = request.getUserPrincipal();
if (!serviceProviderSAMLWorkflow.validate(request)) {
throw new IOException(ErrorCodes.VALIDATION_CHECK_FAILED);
}
// deal with SAML response from IDP
try {
ServiceProviderSAMLResponseProcessor responseProcessor = new ServiceProviderSAMLResponseProcessor(request
.getMethod().equals("POST"), serviceURL, this.picketLinkConfiguration);
if (auditHelper != null) {
responseProcessor.setAuditHelper(auditHelper);
}
responseProcessor.setTrustKeyManager(keyManager);
SAML2HandlerResponse saml2HandlerResponse = responseProcessor.process(samlResponse, httpContext, handlers,
chainLock);
Document samlResponseDocument = saml2HandlerResponse.getResultingDocument();
String relayState = saml2HandlerResponse.getRelayState();
String destination = saml2HandlerResponse.getDestination();
willSendRequest = saml2HandlerResponse.getSendRequest();
String destinationQueryStringWithSignature = saml2HandlerResponse.getDestinationQueryStringWithSignature();
if (destination != null && samlResponseDocument != null) {
serviceProviderSAMLWorkflow.sendRequestToIDP(destination, samlResponseDocument, relayState, response,
willSendRequest, destinationQueryStringWithSignature, spConfiguration.getBindingType()
.equalsIgnoreCase("POST"));
} else {
// See if the session has been invalidated
boolean sessionValidity = sessionIsValid(session);
if (!sessionValidity) {
serviceProviderSAMLWorkflow.sendToLogoutPage(request, response, session, theServletContext, logoutPage);
return Authentication.UNAUTHENTICATED;
}
// We got a response with the principal
List<String> roles = saml2HandlerResponse.getRoles();
if (principal == null)
principal = (Principal) session.getAttribute(GeneralConstants.PRINCIPAL_ID);
String username = principal.getName();
String password = EMPTY_PASSWORD;
if (logger.isTraceEnabled()) {
logger.trace("Roles determined for username=" + username + "=" + Arrays.toString(roles.toArray()));
}
// TODO: figure out getting the principal via authentication
/*
* // Map to JBoss specific principal if ((new ServerDetector()).isJboss() || jbossEnv) { // Push a context
* ServiceProviderSAMLContext.push(username, roles); principal = context.getRealm().authenticate(username,
* password); ServiceProviderSAMLContext.clear(); } else { // tomcat env principal =
* getGenericPrincipal(request, username, roles); }
*/
// Register the principal with the request
Authentication registeredAuthentication = register(request, principal, roles);
if (enableAudit) {
PicketLinkAuditEvent auditEvent = new PicketLinkAuditEvent(AuditLevel.INFO);
auditEvent.setType(PicketLinkAuditEventType.RESPONSE_FROM_IDP);
auditEvent.setSubjectName(username);
auditEvent.setWhoIsAuditing(theServletContext.getContextPath());
auditHelper.audit(auditEvent);
}
// Redirect the user to the originally requested URL
if (saveRestoreRequest) {
// Redirect to original request
String nuri;
synchronized (session) {
nuri = (String) session.getAttribute(__J_URI);
if (nuri == null || nuri.length() == 0) {
nuri = request.getContextPath();
if (nuri.length() == 0)
nuri = URIUtil.SLASH;
}
}
response.setContentLength(0);
Response base_response = HttpChannel.getCurrentHttpChannel().getResponse();
Request base_request = HttpChannel.getCurrentHttpChannel().getRequest();
int redirectCode = (base_request.getHttpVersion().getVersion() < HttpVersion.HTTP_1_1.getVersion() ? HttpServletResponse.SC_MOVED_TEMPORARILY
: HttpServletResponse.SC_SEE_OTHER);
base_response.sendRedirect(redirectCode, response.encodeRedirectURL(nuri));
return Authentication.SEND_SUCCESS; //since a redirect was made to the original requested URL inform Jetty the response has already been handled
// restoreRequest(request,session);
/*
* // Store the authenticated principal in the session. session.setAttribute(FORM_PRINCIPAL_NOTE,
* principal);
*
* // Redirect to the original URL. Note that this will trigger the // authenticator again, but on
* resubmission we will look in the // session notes to retrieve the authenticated principal and // prevent
* reauthentication String requestURI = savedRequestURL(session);
*
* if (requestURI == null) { requestURI = spConfiguration.getServiceURL(); }
*
* logger.trace("Redirecting back to original Request URI: " + requestURI);
* response.sendRedirect(response.encodeRedirectURL(requestURI)); return Authentication.UNAUTHENTICATED;
*/
}
// register(request, principal, null);
return registeredAuthentication;
}
} catch (ProcessingException pe) {
Throwable t = pe.getCause();
if (t != null && t instanceof AssertionExpiredException) {
logger.error("Assertion has expired. Asking IDP for reissue");
if (enableAudit) {
PicketLinkAuditEvent auditEvent = new PicketLinkAuditEvent(AuditLevel.INFO);
auditEvent.setType(PicketLinkAuditEventType.EXPIRED_ASSERTION);
auditEvent.setAssertionID(((AssertionExpiredException) t).getId());
auditHelper.audit(auditEvent);
}
// Just issue a fresh request back to IDP
return generalUserRequest(servletRequest, servletResponse, mandatory);
}
logger.samlSPHandleRequestError(pe);
throw logger.samlSPProcessingExceptionError(pe);
} catch (Exception e) {
logger.samlSPHandleRequestError(e);
throw logger.samlSPProcessingExceptionError(e);
}
return localAuthentication(servletRequest, servletResponse, mandatory);
}
/**
* <p>
* Indicates if the SP is configure with HTTP POST Binding.
* </p>
*
* @return
*/
protected boolean isHttpPostBinding() {
return spConfiguration.getBindingType().equalsIgnoreCase("POST");
}
protected boolean sessionIsValid(HttpSession session) {
try {
long sessionTime = session.getCreationTime();
} catch (IllegalStateException ise) {
return false;
}
return true;
}
protected String savedRequestURL(HttpSession session) {
StringBuilder builder = new StringBuilder();
HttpServletRequest request = (HttpServletRequest) session.getAttribute(FORM_REQUEST_NOTE);
if (request != null) {
builder.append(request.getRequestURI());
if (request.getQueryString() != null) {
builder.append("?").append(request.getQueryString());
}
}
return builder.toString();
}
protected void startPicketLink() {
SystemPropertiesUtil.ensure();
Handlers handlers = null;
// Introduce a timer to reload configuration if desired
if (timerInterval > 0) {
if (timer == null) {
timer = new Timer();
}
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
processConfiguration();
initKeyProvider(theServletContext);
}
}, timerInterval, timerInterval);
}
// Get the chain from config
if (StringUtil.isNullOrEmpty(samlHandlerChainClass)) {
chain = SAML2HandlerChainFactory.createChain();
} else {
try {
chain = SAML2HandlerChainFactory.createChain(this.samlHandlerChainClass);
} catch (ProcessingException e1) {
throw new RuntimeException(e1);
}
}
this.processConfiguration();
try {
if (picketLinkConfiguration != null) {
handlers = picketLinkConfiguration.getHandlers();
} else {
// Get the handlers
String handlerConfigFileName = GeneralConstants.HANDLER_CONFIG_FILE_LOCATION;
handlers = ConfigurationUtil.getHandlers(theServletContext.getResourceAsStream(handlerConfigFileName));
}
chain.addAll(HandlerUtil.getHandlers(handlers));
this.initKeyProvider(theServletContext);
this.populateChainConfig();
this.initializeHandlerChain();
} catch (Exception e) {
throw new RuntimeException(e);
}
if (this.picketLinkConfiguration == null) {
this.picketLinkConfiguration = new PicketLinkType();
this.picketLinkConfiguration.setIdpOrSP(spConfiguration);
this.picketLinkConfiguration.setHandlers(handlers);
}
new SessionManager(this.theServletContext, new SessionManager.InitializationCallback() {
@Override
public void registerSessionListener(Class<? extends HttpSessionListener> listener) {
theServletContext.addListener(listener);
}
});
}
/**
* <p>
* Initialize the KeyProvider configurations. This configurations are to be used during signing and validation of SAML
* assertions.
* </p>
*
* @param context
*/
protected void initKeyProvider(ServletContext context) {
if (!doSupportSignature()) {
return;
}
KeyProviderType keyProvider = this.spConfiguration.getKeyProvider();
if (keyProvider == null && doSupportSignature())
throw new RuntimeException(ErrorCodes.NULL_VALUE + "KeyProvider is null for context=" + context.getContextPath());
try {
String keyManagerClassName = keyProvider.getClassName();
if (keyManagerClassName == null)
throw new RuntimeException(ErrorCodes.NULL_VALUE + "KeyManager class name");
Class<?> clazz = SecurityActions.loadClass(getClass(), keyManagerClassName);
if (clazz == null)
throw new ClassNotFoundException(ErrorCodes.CLASS_NOT_LOADED + keyManagerClassName);
this.keyManager = (TrustKeyManager) clazz.newInstance();
List<AuthPropertyType> authProperties = CoreConfigUtil.getKeyProviderProperties(keyProvider);
keyManager.setAuthProperties(authProperties);
keyManager.setValidatingAlias(keyProvider.getValidatingAlias());
String identityURL = this.spConfiguration.getIdentityURL();
// Special case when you need X509Data in SignedInfo
if (authProperties != null) {
for (AuthPropertyType authPropertyType : authProperties) {
String key = authPropertyType.getKey();
if (GeneralConstants.X509CERTIFICATE.equals(key)) {
// we need X509Certificate in SignedInfo. The value is the alias name
keyManager.addAdditionalOption(GeneralConstants.X509CERTIFICATE, authPropertyType.getValue());
break;
}
}
}
keyManager.addAdditionalOption(ServiceProviderBaseProcessor.IDP_KEY, new URL(identityURL).getHost());
} catch (Exception e) {
logger.trustKeyManagerCreationError(e);
throw new RuntimeException(e.getLocalizedMessage());
}
logger.trace("Key Provider=" + keyProvider.getClassName());
}
/**
* <p>
* Indicates if digital signatures/validation of SAML assertions are enabled. Subclasses that supports signature should
* override this method.
* </p>
*
* @return
*/
protected boolean doSupportSignature() {
if (spConfiguration != null) {
return spConfiguration.isSupportsSignature();
}
return false;
}
protected void processConfiguration() {
InputStream is = null;
if (isNullOrEmpty(this.configFile)) {
this.configFile = CONFIG_FILE_LOCATION;
is = theServletContext.getResourceAsStream(this.configFile);
} else {
try {
is = new FileInputStream(this.configFile);
} catch (FileNotFoundException e) {
throw logger.samlIDPConfigurationError(e);
}
}
try {
// Work on the IDP Configuration
if (configProvider != null) {
try {
if (is == null) {
// Try the older version
is = theServletContext.getResourceAsStream(GeneralConstants.DEPRECATED_CONFIG_FILE_LOCATION);
// Additionally parse the deprecated config file
if (is != null && configProvider instanceof AbstractSAMLConfigurationProvider) {
((AbstractSAMLConfigurationProvider) configProvider).setConfigFile(is);
}
} else {
// Additionally parse the consolidated config file
if (is != null && configProvider instanceof AbstractSAMLConfigurationProvider) {
((AbstractSAMLConfigurationProvider) configProvider).setConsolidatedConfigFile(is);
}
}
picketLinkConfiguration = configProvider.getPicketLinkConfiguration();
spConfiguration = configProvider.getSPConfiguration();
} catch (ProcessingException e) {
throw logger.samlSPConfigurationError(e);
} catch (ParsingException e) {
throw logger.samlSPConfigurationError(e);
}
} else {
if (is != null) {
try {
picketLinkConfiguration = ConfigurationUtil.getConfiguration(is);
spConfiguration = (SPType) picketLinkConfiguration.getIdpOrSP();
} catch (ParsingException e) {
logger.trace(e);
throw logger.samlSPConfigurationError(e);
}
} else {
is = theServletContext.getResourceAsStream(GeneralConstants.DEPRECATED_CONFIG_FILE_LOCATION);
if (is == null)
throw logger.configurationFileMissing(configFile);
spConfiguration = ConfigurationUtil.getSPConfiguration(is);
}
}
if (this.picketLinkConfiguration != null) {
enableAudit = picketLinkConfiguration.isEnableAudit();
// See if we have the system property enabled
if (!enableAudit) {
String sysProp = SecurityActions.getSystemProperty(GeneralConstants.AUDIT_ENABLE, "NULL");
if (!"NULL".equals(sysProp)) {
enableAudit = Boolean.parseBoolean(sysProp);
}
}
if (enableAudit) {
if (auditHelper == null) {
String securityDomainName = PicketLinkAuditHelper.getSecurityDomainName(theServletContext);
auditHelper = new PicketLinkAuditHelper(securityDomainName);
}
}
}
if (StringUtil.isNotNull(spConfiguration.getIdpMetadataFile())) {
processIDPMetadataFile(spConfiguration.getIdpMetadataFile());
} else {
this.identityURL = spConfiguration.getIdentityURL();
}
this.serviceURL = spConfiguration.getServiceURL();
this.canonicalizationMethod = spConfiguration.getCanonicalizationMethod();
logger.samlSPSettingCanonicalizationMethod(canonicalizationMethod);
XMLSignatureUtil.setCanonicalizationMethodType(canonicalizationMethod);
logger.trace("Identity Provider URL=" + this.identityURL);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Attempt to process a metadata file available locally
*/
protected void processIDPMetadataFile(String idpMetadataFile) {
InputStream is = theServletContext.getResourceAsStream(idpMetadataFile);
if (is == null)
return;
Object metadata = null;
try {
Document samlDocument = DocumentUtil.getDocument(is);
SAMLParser parser = new SAMLParser();
metadata = parser.parse(DocumentUtil.getNodeAsStream(samlDocument));
} catch (Exception e) {
throw new RuntimeException(e);
}
IDPSSODescriptorType idpSSO = null;
if (metadata instanceof EntitiesDescriptorType) {
EntitiesDescriptorType entities = (EntitiesDescriptorType) metadata;
idpSSO = handleMetadata(entities);
} else {
idpSSO = handleMetadata((EntityDescriptorType) metadata);
}
if (idpSSO == null) {
logger.samlSPUnableToGetIDPDescriptorFromMetadata();
return;
}
List<EndpointType> endpoints = idpSSO.getSingleSignOnService();
for (EndpointType endpoint : endpoints) {
String endpointBinding = endpoint.getBinding().toString();
if (endpointBinding.contains("HTTP-POST"))
endpointBinding = "POST";
else if (endpointBinding.contains("HTTP-Redirect"))
endpointBinding = "REDIRECT";
if (spConfiguration.getBindingType().equals(endpointBinding)) {
identityURL = endpoint.getLocation().toString();
break;
}
}
List<KeyDescriptorType> keyDescriptors = idpSSO.getKeyDescriptor();
if (keyDescriptors.size() > 0) {
this.idpCertificate = MetaDataExtractor.getCertificate(keyDescriptors.get(0));
}
}
protected IDPSSODescriptorType handleMetadata(EntitiesDescriptorType entities) {
IDPSSODescriptorType idpSSO = null;
List<Object> entityDescs = entities.getEntityDescriptor();
for (Object entityDescriptor : entityDescs) {
if (entityDescriptor instanceof EntitiesDescriptorType) {
idpSSO = getIDPSSODescriptor(entities);
} else
idpSSO = handleMetadata((EntityDescriptorType) entityDescriptor);
if (idpSSO != null)
break;
}
return idpSSO;
}
protected IDPSSODescriptorType handleMetadata(EntityDescriptorType entityDescriptor) {
return CoreConfigUtil.getIDPDescriptor(entityDescriptor);
}
protected IDPSSODescriptorType getIDPSSODescriptor(EntitiesDescriptorType entities) {
List<Object> entityDescs = entities.getEntityDescriptor();
for (Object entityDescriptor : entityDescs) {
if (entityDescriptor instanceof EntitiesDescriptorType) {
return getIDPSSODescriptor((EntitiesDescriptorType) entityDescriptor);
}
return CoreConfigUtil.getIDPDescriptor((EntityDescriptorType) entityDescriptor);
}
return null;
}
protected void initializeHandlerChain() throws ConfigurationException, ProcessingException {
populateChainConfig();
SAML2HandlerChainConfig handlerChainConfig = new DefaultSAML2HandlerChainConfig(chainConfigOptions);
Set<SAML2Handler> samlHandlers = chain.handlers();
for (SAML2Handler handler : samlHandlers) {
handler.initChainConfig(handlerChainConfig);
}
}
protected void populateChainConfig() throws ConfigurationException, ProcessingException {
chainConfigOptions.put(GeneralConstants.CONFIGURATION, spConfiguration);
chainConfigOptions.put(GeneralConstants.ROLE_VALIDATOR_IGNORE, "false"); // No validator as tomcat realm does validn
if (doSupportSignature()) {
chainConfigOptions.put(GeneralConstants.KEYPAIR, keyManager.getSigningKeyPair());
// If there is a need for X509Data in signedinfo
String certificateAlias = (String) keyManager.getAdditionalOption(GeneralConstants.X509CERTIFICATE);
if (certificateAlias != null) {
chainConfigOptions.put(GeneralConstants.X509CERTIFICATE, keyManager.getCertificate(certificateAlias));
}
}
}
/**
* An instance of {@link org.picketlink.identity.federation.core.saml.workflow.ServiceProviderSAMLWorkflow.RedirectionHandler}
* that performs JETTY specific redirection and post workflows
*/
public class JettyRedirectionHandler extends ServiceProviderSAMLWorkflow.RedirectionHandler {
@Override
public void sendRedirectForRequestor(String destination, HttpServletResponse response) throws IOException {
common(destination, response);
response.setHeader("Cache-Control", "no-cache, no-store");
sendRedirect(response, destination);
}
@Override
public void sendRedirectForResponder(String destination, HttpServletResponse response) throws IOException {
common(destination, response);
response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate,private");
sendRedirect(response, destination);
}
private void common(String destination, HttpServletResponse response) {
response.setCharacterEncoding("UTF-8");
response.setHeader("Location", destination);
response.setHeader("Pragma", "no-cache");
}
private void sendRedirect(HttpServletResponse response, String destination) throws IOException {
// response.reset();
response.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
response.sendRedirect(destination);
}
}
}