/*
* 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.web.filters;
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.constants.JBossSAMLConstants;
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.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.request.SAML2Request;
import org.picketlink.identity.federation.api.saml.v2.sig.SAML2Signature;
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.common.IDGenerator;
import org.picketlink.identity.federation.core.saml.v2.factories.SAML2HandlerChainFactory;
import org.picketlink.identity.federation.core.saml.v2.holders.DestinationInfoHolder;
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.AssertionUtil;
import org.picketlink.identity.federation.core.saml.v2.util.HandlerUtil;
import org.picketlink.identity.federation.core.util.CoreConfigUtil;
import org.picketlink.identity.federation.core.util.XMLSignatureUtil;
import org.picketlink.identity.federation.saml.v1.assertion.SAML11AssertionType;
import org.picketlink.identity.federation.saml.v1.assertion.SAML11AuthenticationStatementType;
import org.picketlink.identity.federation.saml.v1.assertion.SAML11StatementAbstractType;
import org.picketlink.identity.federation.saml.v1.assertion.SAML11SubjectType;
import org.picketlink.identity.federation.saml.v1.protocol.SAML11ResponseType;
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.protocol.AuthnRequestType;
import org.picketlink.identity.federation.web.config.AbstractSAMLConfigurationProvider;
import org.picketlink.identity.federation.web.core.HTTPContext;
import org.picketlink.identity.federation.web.interfaces.IRoleValidator;
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.roles.DefaultRoleValidator;
import org.picketlink.identity.federation.web.util.ConfigurationUtil;
import org.picketlink.identity.federation.web.util.HTTPRedirectUtil;
import org.picketlink.identity.federation.web.util.PostBindingUtil;
import org.picketlink.identity.federation.web.util.RedirectBindingUtil;
import org.picketlink.identity.federation.web.util.SAMLConfigurationProvider;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.SAXException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.RequestDispatcher;
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.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
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.GeneralSecurityException;
import java.security.KeyPair;
import java.security.Principal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
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;
/**
* A service provider filter for web container agnostic providers
*
* @author Anil.Saldhana@redhat.com
* @since Aug 21, 2009
*/
public class SPFilter implements Filter {
protected static final PicketLinkLogger logger = PicketLinkLoggerFactory.getLogger();
public static final String ISSUER_ID = "ISSUER_ID";
public static final String DESIRED_IDP = "picketlink.desired.idp";
public static final String CHARACTER_ENCODING = "CHARACTER_ENCODING";
public static final String CONFIGURATION_PROVIDER = "CONFIGURATION_PROVIDER";
public static final String SAML_HANDLER_CHAIN_CLASS = "SAML_HANDLER_CHAIN_CLASS";
private final boolean trace = logger.isTraceEnabled();
protected SPType spConfiguration = null;
protected PicketLinkType picketLinkConfiguration = null;
protected String configFile;
protected String serviceURL = null;
protected String identityURL = null;
protected transient String samlHandlerChainClass = null;
private TrustKeyManager keyManager;
private ServletContext servletContext = null;
private transient SAML2HandlerChain chain = null;
protected boolean ignoreSignatures = false;
private IRoleValidator roleValidator = new DefaultRoleValidator();
private String logOutPage = GeneralConstants.LOGOUT_PAGE_NAME;
protected String canonicalizationMethod = CanonicalizationMethod.EXCLUSIVE_WITH_COMMENTS;
protected volatile PicketLinkAuditHelper auditHelper = null;
protected volatile String issuerID = null;
protected IDPSSODescriptorType idpMetadata;
protected Lock chainLock = new ReentrantLock();
private String characterEncoding;
protected SAMLConfigurationProvider configProvider = null;
private Map<String, Object> chainConfigOptions;
public void destroy() {
}
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest request = createHttpServletRequestWrapper((HttpServletRequest) servletRequest);
HttpServletResponse response = (HttpServletResponse) servletResponse;
try {
// needs to be done first, *before* accessing any parameters. super.authenticate(..) gets called to late
String characterEncoding = getCharacterEncoding();
if (characterEncoding != null) {
request.setCharacterEncoding(characterEncoding);
}
HttpSession session = request.getSession(true);
// Eagerly look for Local LogOut
boolean localLogout = isLocalLogout(request);
if (localLogout) {
try {
sendToLogoutPage(request, response, session);
} catch (ServletException e) {
logger.samlLogoutError(e);
throw new IOException(e);
}
return;
}
String samlRequest = request.getParameter(GeneralConstants.SAML_REQUEST_KEY);
String samlResponse = request.getParameter(GeneralConstants.SAML_RESPONSE_KEY);
Principal principal = request.getUserPrincipal();
// If we have already authenticated the user and there is no request from IDP or logout from user
if (principal != null && !(isGlobalLogout(request) || isNotNull(samlRequest) || isNotNull(samlResponse))) {
filterChain.doFilter(request, response);
} else {
// General User Request
if (!isNotNull(samlRequest) && !isNotNull(samlResponse)) {
generalUserRequest(request, response);
}
// Handle a SAML Response from IDP
if (isNotNull(samlResponse)) {
handleSAMLResponse(request, response);
}
// Handle SAML Requests from IDP
if (isNotNull(samlRequest)) {
handleSAMLRequest(request, response);
}// end if
request = createHttpServletRequestWrapper((HttpServletRequest) servletRequest);
principal = request.getUserPrincipal();
if (principal != null && !response.isCommitted()) {
filterChain.doFilter(request, response);
} else {
localAuthentication(request, response);
}
}
} catch (IOException e) {
SPType configuration = getConfiguration();
if (StringUtil.isNotNull(configuration.getErrorPage())) {
try {
request.getRequestDispatcher(configuration.getErrorPage()).forward(request, response);
} catch (ServletException e1) {
logger.samlErrorPageForwardError(configuration.getErrorPage(), e1);
}
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
} else {
throw e;
}
}
}
private HttpServletRequest createHttpServletRequestWrapper(final HttpServletRequest request) {
return new HttpServletRequestWrapper(request) {
@Override
public Principal getUserPrincipal() {
HttpSession session = getSession(false);
if (session != null) {
return (Principal) session.getAttribute(GeneralConstants.PRINCIPAL_ID);
}
return super.getUserPrincipal();
}
};
}
private String getCharacterEncoding() {
return this.characterEncoding;
}
public void init(FilterConfig filterConfig) throws ServletException {
this.servletContext = filterConfig.getServletContext();
processConfiguration(filterConfig);
}
/**
* Create a SAML2 auth request
*
* @param serviceURL URL of the service
* @param identityURL URL of the identity provider
*
* @return
*
* @throws ConfigurationException
*/
private AuthnRequestType createSAMLRequest(String serviceURL, String identityURL) throws ConfigurationException {
if (serviceURL == null)
throw new IllegalArgumentException(ErrorCodes.NULL_ARGUMENT + "serviceURL");
if (identityURL == null)
throw new IllegalArgumentException(ErrorCodes.NULL_ARGUMENT + "identityURL");
SAML2Request saml2Request = new SAML2Request();
String id = IDGenerator.create("ID_");
return saml2Request.createAuthnRequestType(id, serviceURL, identityURL, serviceURL);
}
protected void sendToDestination(Document samlDocument, String relayState, String destination,
HttpServletResponse response, boolean request) throws IOException, SAXException, GeneralSecurityException {
if (!ignoreSignatures) {
SAML2Signature samlSignature = new SAML2Signature();
Node nextSibling = samlSignature.getNextSiblingOfIssuer(samlDocument);
if (nextSibling != null) {
samlSignature.setNextSibling(nextSibling);
}
KeyPair keypair = keyManager.getSigningKeyPair();
samlSignature.signSAMLDocument(samlDocument, keypair);
}
String samlMessage = PostBindingUtil.base64Encode(DocumentUtil.getDocumentAsString(samlDocument));
PostBindingUtil.sendPost(new DestinationInfoHolder(destination, samlMessage, relayState), response, request);
}
private boolean handleSAMLResponse(HttpServletRequest request, HttpServletResponse response) throws IOException {
if (!validate(request)) {
throw new IOException(ErrorCodes.VALIDATION_CHECK_FAILED);
}
String samlVersion = getSAMLVersion(request);
if (!JBossSAMLConstants.VERSION_2_0.get().equals(samlVersion)) {
return handleSAML11UnsolicitedResponse(request, response);
}
return handleSAML2Response(request, response);
}
private boolean isLocalLogout(HttpServletRequest request) {
String lloStr = request.getParameter(GeneralConstants.LOCAL_LOGOUT);
return isNotNull(lloStr) && "true".equalsIgnoreCase(lloStr);
}
protected void sendToLogoutPage(HttpServletRequest request, HttpServletResponse response, HttpSession session) throws IOException, ServletException {
// we are invalidated.
RequestDispatcher dispatch = this.servletContext.getRequestDispatcher(this.getConfiguration().getLogOutPage());
if (dispatch == null) {
logger.samlSPCouldNotDispatchToLogoutPage(this.getConfiguration().getLogOutPage());
} else {
logger.trace("Forwarding request to logOutPage: " + this.getConfiguration().getLogOutPage());
try {
session.invalidate();
} catch (IllegalStateException e) {
// if session was already invalidated we just ignore the exception.
}
try {
dispatch.forward(request, response);
} catch (Exception e) {
// JBAS5.1 and 6 quirkiness
dispatch.forward(request, response);
}
}
}
private SPType getConfiguration() {
return (SPType) this.picketLinkConfiguration.getIdpOrSP();
}
private boolean isGlobalLogout(HttpServletRequest request) {
String gloStr = request.getParameter(GeneralConstants.GLOBAL_LOGOUT);
return isNotNull(gloStr) && "true".equalsIgnoreCase(gloStr);
}
private boolean generalUserRequest(HttpServletRequest request, HttpServletResponse response) throws IOException {
HttpSession session = request.getSession(true);
boolean willSendRequest = false;
HTTPContext httpContext = new HTTPContext(request, response, this.servletContext);
Set<SAML2Handler> handlers = chain.handlers();
boolean postBinding = getConfiguration().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, this.idpMetadata);
if (issuerID != null) {
baseProcessor.setIssuer(issuerID);
}
// If the user has a different desired idp
String idp = (String) request.getAttribute(DESIRED_IDP);
if (StringUtil.isNotNull(idp)) {
baseProcessor.setIdentityURL(idp);
} else {
baseProcessor.setIdentityURL(getIdentityURL());
}
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 (isEnableAudit()) {
PicketLinkAuditEvent auditEvent = new PicketLinkAuditEvent(AuditLevel.INFO);
auditEvent.setType(PicketLinkAuditEventType.REQUEST_TO_IDP);
auditEvent.setWhoIsAuditing(getContextPath());
auditHelper.audit(auditEvent);
}
sendRequestToIDP(destination, samlResponseDocument, relayState, request, response, willSendRequest, destinationQueryStringWithSignature);
return false;
} catch (Exception e) {
logger.samlSPHandleRequestError(e);
throw logger.samlSPProcessingExceptionError(e);
}
}
return localAuthentication(request, response);
}
private String getContextPath() {
return this.servletContext.getContextPath();
}
public String getIdentityURL() {
return getConfiguration().getIdentityURL();
}
private boolean isEnableAudit() {
return this.picketLinkConfiguration.isEnableAudit();
}
protected void sendRequestToIDP(String destination, Document samlDocument, String relayState, HttpServletRequest request, HttpServletResponse response,
boolean willSendRequest, String destinationQueryStringWithSignature) throws ProcessingException, ConfigurationException, IOException {
if (isAjaxRequest(request) && request.getUserPrincipal() == null) {
response.sendError(HttpServletResponse.SC_FORBIDDEN);
} else {
if (isHttpPostBinding()) {
sendHttpPostBindingRequest(destination, samlDocument, relayState, response, willSendRequest);
} else {
sendHttpRedirectRequest(destination, samlDocument, relayState, response, willSendRequest, destinationQueryStringWithSignature);
}
}
}
private boolean isAjaxRequest(HttpServletRequest request) {
String requestedWithHeader = request.getHeader(GeneralConstants.HTTP_HEADER_X_REQUESTED_WITH);
return requestedWithHeader != null && "XMLHttpRequest".equalsIgnoreCase(requestedWithHeader);
}
protected boolean isHttpPostBinding() {
return getBinding().equalsIgnoreCase("POST");
}
protected void sendHttpPostBindingRequest(String destination, Document samlDocument, String relayState, HttpServletResponse response,
boolean willSendRequest) throws ProcessingException, IOException,
ConfigurationException {
String samlMessage = PostBindingUtil.base64Encode(DocumentUtil.getDocumentAsString(samlDocument));
DestinationInfoHolder destinationHolder = new DestinationInfoHolder(destination, samlMessage, relayState);
PostBindingUtil.sendPost(destinationHolder, response, willSendRequest);
}
protected void sendHttpRedirectRequest(String destination, Document samlDocument, String relayState, HttpServletResponse response,
boolean willSendRequest, String destinationQueryStringWithSignature) throws IOException,
ProcessingException, ConfigurationException {
String destinationQueryString = null;
// We already have queryString with signature from SAML2SignatureGenerationHandler
if (destinationQueryStringWithSignature != null) {
destinationQueryString = destinationQueryStringWithSignature;
} else {
String samlMessage = DocumentUtil.getDocumentAsString(samlDocument);
String base64Request = RedirectBindingUtil.deflateBase64URLEncode(samlMessage.getBytes("UTF-8"));
destinationQueryString = RedirectBindingUtil.getDestinationQueryString(base64Request, relayState, willSendRequest);
}
RedirectBindingUtil.RedirectBindingUtilDestHolder holder = new RedirectBindingUtil.RedirectBindingUtilDestHolder();
holder.setDestination(destination).setDestinationQueryString(destinationQueryString);
HTTPRedirectUtil.sendRedirectForRequestor(RedirectBindingUtil.getDestinationURL(holder), response);
}
protected String getBinding() {
return getConfiguration().getBindingType();
}
protected boolean localAuthentication(HttpServletRequest request, HttpServletResponse response) throws IOException {
// check how to deal with local authentication
return true;
}
protected boolean validate(HttpServletRequest request) {
return request.getParameter("SAMLResponse") != null;
}
private String getSAMLVersion(HttpServletRequest request) {
String samlResponse = request.getParameter(GeneralConstants.SAML_RESPONSE_KEY);
String version;
try {
Document samlDocument = toSAMLResponseDocument(samlResponse, "POST".equalsIgnoreCase(request.getMethod()));
Element element = samlDocument.getDocumentElement();
// let's try SAML 2.0 Version attribute first
version = element.getAttribute("Version");
if (isNullOrEmpty(version)) {
// fallback to SAML 1.1 Minor and Major attributes
String minorVersion = element.getAttribute("MinorVersion");
String majorVersion = element.getAttribute("MajorVersion");
version = minorVersion + "." + majorVersion;
}
} catch (Exception e) {
throw new RuntimeException("Could not extract version from SAML Response.", e);
}
return version;
}
private Document toSAMLResponseDocument(String samlResponse, boolean isPostBinding) throws ParsingException {
InputStream dataStream = null;
if (isPostBinding) {
// deal with SAML response from IDP
dataStream = PostBindingUtil.base64DecodeAsStream(samlResponse);
} else {
// deal with SAML response from IDP
dataStream = RedirectBindingUtil.base64DeflateDecode(samlResponse);
}
try {
return DocumentUtil.getDocument(dataStream);
} catch (Exception e) {
logger.samlResponseFromIDPParsingFailed();
throw new ParsingException("", e);
}
}
public boolean handleSAML11UnsolicitedResponse(HttpServletRequest request, HttpServletResponse response) throws IOException {
String samlResponse = request.getParameter(GeneralConstants.SAML_RESPONSE_KEY);
Principal principal = request.getUserPrincipal();
// If we have already authenticated the user and there is no request from IDP or logout from user
if (principal != null) {
return true;
}
HttpSession session = request.getSession(true);
// See if we got a response from IDP
if (isNotNull(samlResponse)) {
boolean isValid = false;
try {
isValid = validate(request);
} catch (Exception e) {
logger.samlSPHandleRequestError(e);
throw new IOException();
}
if (!isValid) {
throw new IOException(ErrorCodes.VALIDATION_CHECK_FAILED);
}
try {
InputStream base64DecodedResponse = null;
if ("GET".equalsIgnoreCase(request.getMethod())) {
base64DecodedResponse = RedirectBindingUtil.base64DeflateDecode(samlResponse);
} else {
base64DecodedResponse = PostBindingUtil.base64DecodeAsStream(samlResponse);
}
SAMLParser parser = new SAMLParser();
SAML11ResponseType saml11Response = (SAML11ResponseType) parser.parse(base64DecodedResponse);
List<SAML11AssertionType> assertions = saml11Response.get();
if (assertions.size() > 1) {
logger.trace("More than one assertion from IDP. Considering the first one.");
}
String username = null;
List<String> roles = new ArrayList<String>();
SAML11AssertionType assertion = assertions.get(0);
if (assertion != null) {
// Get the subject
List<SAML11StatementAbstractType> statements = assertion.getStatements();
for (SAML11StatementAbstractType statement : statements) {
if (statement instanceof SAML11AuthenticationStatementType) {
SAML11AuthenticationStatementType subStat = (SAML11AuthenticationStatementType) statement;
SAML11SubjectType subject = subStat.getSubject();
username = subject.getChoice().getNameID().getValue();
}
}
roles = AssertionUtil.getRoles(assertion, null);
}
return true;
} catch (Exception e) {
logger.samlSPHandleRequestError(e);
}
}
return false;
}
private boolean handleSAML2Response(HttpServletRequest request, HttpServletResponse response) throws IOException {
HttpSession session = request.getSession(true);
String samlResponse = request.getParameter(GeneralConstants.SAML_RESPONSE_KEY);
HTTPContext httpContext = new HTTPContext(request, response, this.servletContext);
Set<SAML2Handler> handlers = chain.handlers();
Principal principal = request.getUserPrincipal();
boolean willSendRequest;// deal with SAML response from IDP
try {
ServiceProviderSAMLResponseProcessor responseProcessor = new ServiceProviderSAMLResponseProcessor(request.getMethod()
.equals("POST"), serviceURL, this.picketLinkConfiguration, this.idpMetadata);
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) {
sendRequestToIDP(destination, samlResponseDocument, relayState, request, response, willSendRequest, destinationQueryStringWithSignature);
} else {
// See if the session has been invalidated
boolean sessionValidity = request.getUserPrincipal() != null;
if (!sessionValidity) {
sendToLogoutPage(request, response, session);
return false;
}
// We got a response with the principal
List<String> roles = saml2HandlerResponse.getRoles();
if (principal == null) {
principal = (Principal) session.getAttribute(GeneralConstants.PRINCIPAL_ID);
}
if(principal == null) {
throw new RuntimeException(ErrorCodes.NULL_VALUE + " principal");
}
if (isEnableAudit()) {
PicketLinkAuditEvent auditEvent = new PicketLinkAuditEvent(AuditLevel.INFO);
auditEvent.setType(PicketLinkAuditEventType.RESPONSE_FROM_IDP);
auditEvent.setSubjectName(principal.getName());
auditEvent.setWhoIsAuditing(getContextPath());
auditHelper.audit(auditEvent);
}
return true;
}
} catch (ProcessingException pe) {
Throwable t = pe.getCause();
if (t != null && t instanceof AssertionExpiredException) {
logger.error("Assertion has expired. Asking IDP for reissue");
if (isEnableAudit()) {
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(request, response);
}
logger.samlSPHandleRequestError(pe);
throw logger.samlSPProcessingExceptionError(pe);
} catch (Exception e) {
logger.samlSPHandleRequestError(e);
throw logger.samlSPProcessingExceptionError(e);
}
return localAuthentication(request, response);
}
private void processIdPMetadata(SPType spConfiguration) {
IDPSSODescriptorType idpssoDescriptorType = null;
if (isNotNull(spConfiguration.getIdpMetadataFile())) {
idpssoDescriptorType = getIdpMetadataFromFile(spConfiguration);
} else {
idpssoDescriptorType = getIdpMetadataFromProvider(spConfiguration);
}
if (idpssoDescriptorType != null) {
List<EndpointType> endpoints = idpssoDescriptorType.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)) {
spConfiguration.setIdentityURL(endpoint.getLocation().toString());
break;
}
}
this.idpMetadata = idpssoDescriptorType;
}
}
private IDPSSODescriptorType getIdpMetadataFromProvider(SPType spConfiguration) {
List<EntityDescriptorType> entityDescriptors = CoreConfigUtil.getMetadataConfiguration(spConfiguration,
this.servletContext);
if (entityDescriptors != null) {
for (EntityDescriptorType entityDescriptorType : entityDescriptors) {
IDPSSODescriptorType idpssoDescriptorType = handleMetadata(entityDescriptorType);
if (idpssoDescriptorType != null) {
return idpssoDescriptorType;
}
}
}
return null;
}
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 IDPSSODescriptorType getIdpMetadataFromFile(SPType configuration) {
InputStream is = this.servletContext.getResourceAsStream(configuration.getIdpMetadataFile());
if (is == null) {
return null;
}
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 idpSSO;
}
return idpSSO;
}
private boolean handleSAMLRequest(HttpServletRequest request, HttpServletResponse response) throws IOException {
String samlRequest = request.getParameter(GeneralConstants.SAML_REQUEST_KEY);
HTTPContext httpContext = new HTTPContext(request, response, this.servletContext);
Set<SAML2Handler> handlers = chain.handlers();
try {
ServiceProviderSAMLRequestProcessor requestProcessor = new ServiceProviderSAMLRequestProcessor(
request.getMethod().equals("POST"), this.serviceURL, this.picketLinkConfiguration, this.idpMetadata);
requestProcessor.setTrustKeyManager(keyManager);
boolean result = requestProcessor.process(samlRequest, httpContext, handlers, chainLock);
if (isEnableAudit()) {
PicketLinkAuditEvent auditEvent = new PicketLinkAuditEvent(AuditLevel.INFO);
auditEvent.setType(PicketLinkAuditEventType.REQUEST_FROM_IDP);
auditEvent.setWhoIsAuditing(getContextPath());
auditHelper.audit(auditEvent);
}
// If response is already commited, we need to stop with processing of HTTP request
if (response.isCommitted()) {
return false;
}
if (result) {
return result;
}
} catch (Exception e) {
logger.samlSPHandleRequestError(e);
throw logger.samlSPProcessingExceptionError(e);
}
return localAuthentication(request, response);
}
protected void processConfiguration(FilterConfig filterConfig) {
InputStream is;
if (isNullOrEmpty(this.configFile)) {
is = servletContext.getResourceAsStream(CONFIG_FILE_LOCATION);
} else {
try {
is = new FileInputStream(this.configFile);
} catch (FileNotFoundException e) {
throw logger.samlIDPConfigurationError(e);
}
}
PicketLinkType picketLinkType;
String configurationProviderName = filterConfig.getInitParameter(CONFIGURATION_PROVIDER);
if (configurationProviderName != null) {
try {
Class<?> clazz = SecurityActions.loadClass(getClass(), configurationProviderName);
if (clazz == null) {
throw new ClassNotFoundException(ErrorCodes.CLASS_NOT_LOADED + configurationProviderName);
}
this.configProvider = (SAMLConfigurationProvider) clazz.newInstance();
} catch (Exception e) {
throw new RuntimeException("Could not create configuration provider [" + configurationProviderName + "].", e);
}
}
try {
// Work on the IDP Configuration
if (configProvider != null) {
try {
if (is == null) {
// Try the older version
is = servletContext.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);
}
}
picketLinkType = configProvider.getPicketLinkConfiguration();
picketLinkType.setIdpOrSP(configProvider.getSPConfiguration());
} catch (ProcessingException e) {
throw logger.samlSPConfigurationError(e);
} catch (ParsingException e) {
throw logger.samlSPConfigurationError(e);
}
} else {
if (is != null) {
try {
picketLinkType = ConfigurationUtil.getConfiguration(is);
} catch (ParsingException e) {
logger.trace(e);
throw logger.samlSPConfigurationError(e);
}
} else {
is = servletContext.getResourceAsStream(GeneralConstants.DEPRECATED_CONFIG_FILE_LOCATION);
if (is == null) {
throw logger.configurationFileMissing(configFile);
}
picketLinkType = new PicketLinkType();
picketLinkType.setIdpOrSP(ConfigurationUtil.getSPConfiguration(is));
}
}
//Close the InputStream as we no longer need it
if(is != null){
try {
is.close();
} catch (IOException e) {
//ignore
}
}
Boolean enableAudit = picketLinkType.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(servletContext);
auditHelper = new PicketLinkAuditHelper(securityDomainName);
}
}
SPType spConfiguration = (SPType) picketLinkType.getIdpOrSP();
processIdPMetadata(spConfiguration);
this.serviceURL = spConfiguration.getServiceURL();
this.canonicalizationMethod = spConfiguration.getCanonicalizationMethod();
this.picketLinkConfiguration = picketLinkType;
this.issuerID = filterConfig.getInitParameter(ISSUER_ID);
this.characterEncoding = filterConfig.getInitParameter(CHARACTER_ENCODING);
this.samlHandlerChainClass = filterConfig.getInitParameter(SAML_HANDLER_CHAIN_CLASS);
logger.samlSPSettingCanonicalizationMethod(canonicalizationMethod);
XMLSignatureUtil.setCanonicalizationMethodType(canonicalizationMethod);
try {
this.initKeyProvider();
this.initializeHandlerChain(picketLinkType);
} catch (Exception e) {
throw new RuntimeException(e);
}
logger.trace("Identity Provider URL=" + getConfiguration().getIdentityURL());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
protected void initKeyProvider() {
if (!doSupportSignature()) {
return;
}
SPType configuration = getConfiguration();
KeyProviderType keyProvider = configuration.getKeyProvider();
if (keyProvider == null && doSupportSignature()) {
throw new RuntimeException(ErrorCodes.NULL_VALUE + "KeyProvider is null for 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);
}
TrustKeyManager keyManager = (TrustKeyManager) clazz.newInstance();
List<AuthPropertyType> authProperties = CoreConfigUtil.getKeyProviderProperties(keyProvider);
keyManager.setAuthProperties(authProperties);
keyManager.setValidatingAlias(keyProvider.getValidatingAlias());
String identityURL = configuration.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());
this.keyManager = keyManager;
} catch (Exception e) {
logger.trustKeyManagerCreationError(e);
throw new RuntimeException(e.getLocalizedMessage());
}
logger.trace("Key Provider=" + keyProvider.getClassName());
}
protected boolean doSupportSignature() {
return getConfiguration().isSupportsSignature();
}
protected void initializeHandlerChain(PicketLinkType picketLinkType) throws Exception {
SAML2HandlerChain handlerChain;
// Get the chain from config
if (isNullOrEmpty(samlHandlerChainClass)) {
handlerChain = SAML2HandlerChainFactory.createChain();
} else {
try {
handlerChain = SAML2HandlerChainFactory.createChain(this.samlHandlerChainClass);
} catch (ProcessingException e1) {
throw new RuntimeException(e1);
}
}
Handlers handlers = picketLinkType.getHandlers();
if (handlers == null) {
// Get the handlers
String handlerConfigFileName = GeneralConstants.HANDLER_CONFIG_FILE_LOCATION;
handlers = ConfigurationUtil.getHandlers(servletContext.getResourceAsStream(handlerConfigFileName));
}
picketLinkType.setHandlers(handlers);
handlerChain.addAll(HandlerUtil.getHandlers(handlers));
populateChainConfig(picketLinkType);
SAML2HandlerChainConfig handlerChainConfig = new DefaultSAML2HandlerChainConfig(chainConfigOptions);
Set<SAML2Handler> samlHandlers = handlerChain.handlers();
for (SAML2Handler handler : samlHandlers) {
handler.initChainConfig(handlerChainConfig);
}
chain = handlerChain;
}
protected void populateChainConfig(PicketLinkType picketLinkType) throws ConfigurationException, ProcessingException {
Map<String, Object> chainConfigOptions = new HashMap<String, Object>();
chainConfigOptions.put(GeneralConstants.CONFIGURATION, picketLinkType.getIdpOrSP());
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));
}
}
this.chainConfigOptions = chainConfigOptions;
}
}