/*
* JBoss, Home of Professional Open Source.
* Copyright 2012, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.picketlink.identity.federation.bindings.tomcat.idp;
import org.apache.catalina.Context;
import org.apache.catalina.Globals;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.Session;
import org.apache.catalina.Valve;
import org.apache.catalina.authenticator.Constants;
import org.apache.catalina.authenticator.SSLAuthenticator;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.deploy.LoginConfig;
import org.apache.catalina.realm.GenericPrincipal;
import org.apache.catalina.valves.ValveBase;
import org.apache.coyote.ActionCode;
import org.jboss.security.audit.AuditLevel;
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.constants.JBossSAMLURIConstants;
import org.picketlink.common.exceptions.ConfigurationException;
import org.picketlink.common.exceptions.ParsingException;
import org.picketlink.common.exceptions.ProcessingException;
import org.picketlink.common.exceptions.fed.IssuerNotTrustedException;
import org.picketlink.common.util.StaxUtil;
import org.picketlink.common.util.StringUtil;
import org.picketlink.common.util.SystemPropertiesUtil;
import org.picketlink.config.federation.AuthPropertyType;
import org.picketlink.config.federation.IDPType;
import org.picketlink.config.federation.KeyProviderType;
import org.picketlink.config.federation.PicketLinkType;
import org.picketlink.config.federation.handler.Handlers;
import org.picketlink.identity.federation.api.saml.v2.request.SAML2Request;
import org.picketlink.identity.federation.bindings.tomcat.TomcatRoleGenerator;
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.impl.DelegatedAttributeManager;
import org.picketlink.identity.federation.core.interfaces.AttributeManager;
import org.picketlink.identity.federation.core.interfaces.ProtocolContext;
import org.picketlink.identity.federation.core.interfaces.RoleGenerator;
import org.picketlink.identity.federation.core.interfaces.TrustKeyManager;
import org.picketlink.identity.federation.core.saml.v1.SAML11ProtocolContext;
import org.picketlink.identity.federation.core.saml.v1.writers.SAML11ResponseWriter;
import org.picketlink.identity.federation.core.saml.v2.common.IDGenerator;
import org.picketlink.identity.federation.core.saml.v2.common.SAMLDocumentHolder;
import org.picketlink.identity.federation.core.saml.v2.factories.SAML2HandlerChainFactory;
import org.picketlink.identity.federation.core.saml.v2.holders.IssuerInfoHolder;
import org.picketlink.identity.federation.core.saml.v2.impl.DefaultSAML2HandlerChainConfig;
import org.picketlink.identity.federation.core.saml.v2.impl.DefaultSAML2HandlerRequest;
import org.picketlink.identity.federation.core.saml.v2.impl.DefaultSAML2HandlerResponse;
import org.picketlink.identity.federation.core.saml.v2.interfaces.SAML2Handler;
import org.picketlink.identity.federation.core.saml.v2.interfaces.SAML2Handler.HANDLER_TYPE;
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.SAML2HandlerRequest;
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.DocumentUtil;
import org.picketlink.identity.federation.core.saml.v2.util.HandlerUtil;
import org.picketlink.identity.federation.core.saml.v2.util.XMLTimeUtil;
import org.picketlink.identity.federation.core.sts.PicketLinkCoreSTS;
import org.picketlink.identity.federation.core.util.CoreConfigUtil;
import org.picketlink.identity.federation.core.util.XMLSignatureUtil;
import org.picketlink.identity.federation.core.wstrust.PicketLinkSTSConfiguration;
import org.picketlink.identity.federation.saml.v1.assertion.SAML11AssertionType;
import org.picketlink.identity.federation.saml.v1.assertion.SAML11AttributeStatementType;
import org.picketlink.identity.federation.saml.v1.assertion.SAML11AttributeType;
import org.picketlink.identity.federation.saml.v1.assertion.SAML11NameIdentifierType;
import org.picketlink.identity.federation.saml.v1.assertion.SAML11SubjectType;
import org.picketlink.identity.federation.saml.v1.assertion.SAML11SubjectType.SAML11SubjectTypeChoice;
import org.picketlink.identity.federation.saml.v1.protocol.SAML11ResponseType;
import org.picketlink.identity.federation.saml.v1.protocol.SAML11StatusType;
import org.picketlink.identity.federation.saml.v2.SAML2Object;
import org.picketlink.identity.federation.saml.v2.assertion.AttributeStatementType;
import org.picketlink.identity.federation.saml.v2.assertion.NameIDType;
import org.picketlink.identity.federation.saml.v2.metadata.EntityDescriptorType;
import org.picketlink.identity.federation.saml.v2.metadata.SPSSODescriptorType;
import org.picketlink.identity.federation.saml.v2.protocol.AuthnRequestType;
import org.picketlink.identity.federation.saml.v2.protocol.LogoutRequestType;
import org.picketlink.identity.federation.saml.v2.protocol.RequestAbstractType;
import org.picketlink.identity.federation.saml.v2.protocol.StatusResponseType;
import org.picketlink.identity.federation.web.config.AbstractSAMLConfigurationProvider;
import org.picketlink.identity.federation.web.core.HTTPContext;
import org.picketlink.identity.federation.web.core.IdentityParticipantStack;
import org.picketlink.identity.federation.web.core.IdentityServer;
import org.picketlink.identity.federation.web.util.ConfigurationUtil;
import org.picketlink.identity.federation.web.util.IDPWebRequestUtil;
import org.picketlink.identity.federation.web.util.IDPWebRequestUtil.WebRequestUtilHolder;
import org.picketlink.identity.federation.web.util.RedirectBindingUtil;
import org.picketlink.identity.federation.web.util.SAMLConfigurationProvider;
import org.w3c.dom.Document;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.security.GeneralSecurityException;
import java.security.Principal;
import java.security.PublicKey;
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.constants.GeneralConstants.DEPRECATED_CONFIG_FILE_LOCATION;
import static org.picketlink.common.constants.GeneralConstants.SAML_REQUEST_KEY;
import static org.picketlink.common.util.StringUtil.isNotNull;
import static org.picketlink.common.util.StringUtil.isNullOrEmpty;
/**
* Base Class for the IDPWebBrowserSSOValve
*
* @author anil saldhana
*/
public abstract class AbstractIDPValve extends ValveBase {
private static final PicketLinkLogger logger = PicketLinkLoggerFactory.getLogger();
public static final String IDP_SESSION_USER = "org.picketlink.idp.session.user";
protected PicketLinkAuditHelper auditHelper = null;
protected volatile PicketLinkType picketLinkConfiguration = null;
private volatile RoleGenerator roleGenerator = new TomcatRoleGenerator();
private volatile TrustKeyManager keyManager;
private transient DelegatedAttributeManager attribManager;
private final List<String> attributeKeys = new ArrayList<String>();
private transient SAML2HandlerChain chain = null;
/**
* The user can inject a fully qualified name of a {@link SAMLConfigurationProvider}
*/
protected SAMLConfigurationProvider configProvider = null;
protected int timerInterval = -1;
protected Timer timer = null;
/**
* <p>Specifies a different location for the configuration file.</p>
*/
private String configFile;
/**
* A Lock for Handler operations in the chain
*/
private final Lock chainLock = new ReentrantLock();
private Map<String, SPSSODescriptorType> spSSOMetadataMap = new HashMap<String, SPSSODescriptorType>();
private SSLAuthenticator sslAuthenticator;
private Boolean passUserPrincipalToAttributeManager = false;
/**
* Character encoding to use when reading the request parameters
*/
protected String characterEncoding = null;
/**
* Return the character encoding to use when reading the request parameters
*/
public String getCharacterEncoding() {
return characterEncoding;
}
/**
* Set the character encoding to use when reading the request parameters
*/
public void setCharacterEncoding(String encoding) {
characterEncoding = encoding;
}
// Set a list of attributes we are interested in separated by comma
public void setAttributeList(String attribList) {
if (StringUtil.isNotNull(attribList)) {
this.attributeKeys.clear();
this.attributeKeys.addAll(StringUtil.tokenize(attribList));
}
}
/**
* Set the Timer Value to reload the configuration
*
* @param value an integer value that represents timer value (in miliseconds)
*/
public void setTimerInterval(String value) {
if (StringUtil.isNotNull(value)) {
timerInterval = Integer.parseInt(value);
}
}
/**
* Set the {@link SAMLConfigurationProvider} fqn
*
* @param cp fqn of a {@link SAMLConfigurationProvider}
*/
public void setConfigProvider(String cp) {
if (cp == null) {
throw logger.nullArgumentError("configProvider");
}
Class<?> clazz = SecurityActions.loadClass(getClass(), cp);
if (clazz == null) {
throw new RuntimeException(logger.classNotLoadedError(cp));
}
try {
configProvider = (SAMLConfigurationProvider) clazz.newInstance();
} catch (Exception e) {
throw new RuntimeException(logger.couldNotCreateInstance(cp, e));
}
}
public void setConfigFile(final String configFile) {
this.configFile = configFile;
}
public void setConfigProvider(SAMLConfigurationProvider configurationProvider) {
this.configProvider = configurationProvider;
}
@Deprecated
public void setRoleGenerator(String rgName) {
logger
.warn("Option 'roleGenerator' is deprecated and should not be used. This configuration is now set in picketlink.xml.");
}
@Deprecated
public void setSamlHandlerChainClass(String samlHandlerChainClass) {
logger
.warn("Option 'samlHandlerChainClass' is deprecated and should not be used. This configuration is now set in picketlink.xml.");
}
@Deprecated
public void setIdentityParticipantStack(String fqn) {
logger
.warn("Option 'identityParticipantStack' is deprecated and should not be used. This configuration is now set in picketlink.xml.");
}
@Deprecated
public void setStrictPostBinding(Boolean strictPostBinding) {
logger
.warn("Option 'strictPostBinding' is deprecated and should not be used. This configuration is now set in picketlink.xml.");
}
@Deprecated
public Boolean getIgnoreIncomingSignatures() {
logger.warn("Option 'ignoreIncomingSignatures' is deprecated and should not be used. Signatures are verified if "
+ "SAML2SignatureValidationHandler is available.");
return false;
}
@Deprecated
public void setIgnoreIncomingSignatures(Boolean ignoreIncomingSignature) {
logger.warn("Option 'ignoreIncomingSignatures' is deprecated and not used. Signatures are verified if "
+ "SAML2SignatureValidationHandler is available.");
}
/**
* PLFED-248 Allows to validate the token's signature against the keystore using the token's issuer.
*/
@Deprecated
public void setValidatingAliasToTokenIssuer(Boolean validatingAliasToTokenIssuer) {
logger
.warn("Option 'validatingAliasToTokenIssuer' is deprecated and not used. The IDP will always use the issuer host to validate signatures.");
}
/**
* IDP should not do any attributes such as generation of roles etc
*
* @param ignoreAttributes
*/
public void setIgnoreAttributesGeneration(Boolean ignoreAttributes) {
if (ignoreAttributes == Boolean.TRUE) {
this.attribManager = null;
}
}
@Deprecated
public Boolean getSignOutgoingMessages() {
logger.warn("Option signOutgoingMessages is used for signing of error messages. Normal SAML messages are "
+ "signed by SAML2SignatureGenerationHandler.");
return true;
}
@Deprecated
public void setSignOutgoingMessages(Boolean signOutgoingMessages) {
logger.warn("Option signOutgoingMessages is used for signing of error messages. Normal SAML messages are "
+ "signed by SAML2SignatureGenerationHandler.");
}
/**
* IDP should get the user principal from Request.getUserPrincipal() and send that to the attribute manager
*
* @param passUserPrincipalToAttributeManager
*/
public void setPassUserPrincipalToAttributeManager(Boolean passUserPrincipalToAttributeManager) {
this.passUserPrincipalToAttributeManager = passUserPrincipalToAttributeManager;
}
/**
* <p> Returns the configurations used. </p>
*
* @return
*/
public PicketLinkType getConfiguration() {
return this.picketLinkConfiguration;
}
/**
* Return the {@link TrustKeyManager}
*
* @return
*/
public TrustKeyManager getKeyManager() {
return this.keyManager;
}
@Override
public void invoke(Request request, Response response) throws IOException, ServletException {
String characterEncoding = getCharacterEncoding();
if (characterEncoding != null) {
request.setCharacterEncoding(characterEncoding);
}
// Look for unauthorized status
if (isUnauthorized(response)) {
handleUnauthorizedResponse(request, response);
return;
}
// first, we populate all required parameters sent into session for later retrieval. If they exists.
populateSessionWithSAMLParameters(request);
// get an authenticated user or tries to authenticate if this is a authentication request
Principal userPrincipal = request.getPrincipal();
if (userPrincipal == null) {
if (getIdpConfiguration().isSSLClientAuthentication()) {
if (request.isSecure()) {
getSSLAuthenticator().invoke(request, response);
// we always reset/recycle the response to remove any data written to the response by the ssl
// authenticator
response.resetBuffer();
response.recycle();
}
}
}
HttpSession session = request.getSession();
if (isAjaxRequest(request) && session.getAttribute(IDP_SESSION_USER) == null) {
response.sendError(403);
return;
}
invokeNextValve(request, response);
if (isUnauthorized(response)) {
return;
}
userPrincipal = request.getUserPrincipal();
// we only handle SAML messages for authenticated users.
if (userPrincipal != null) {
if (session.getAttribute(IDP_SESSION_USER) == null) {
session.setAttribute(IDP_SESSION_USER, userPrincipal);
}
if (isGlobalLogout(request) && request.getParameter(SAML_REQUEST_KEY) == null) {
prepareLocalGlobalLogoutRequest(request, userPrincipal);
}
handleSAMLMessage(request, response);
}
}
private boolean isUserAuthenticated(Request request) {
Session sessionInternal = request.getSessionInternal(false);
String authMethod = getContext().getLoginConfig().getAuthMethod();
return (authMethod != null && "FORM".equals(authMethod)) && sessionInternal != null && sessionInternal.getNote(Constants.FORM_PRINCIPAL_NOTE) != null;
}
private IDPType getIdpConfiguration() {
return (IDPType) getConfiguration().getIdpOrSP();
}
/**
* <p>This method populate the request and session with a logout requests to start a global logout from the IdP.</p>
*
* @param request
* @param userPrincipal
*/
private void prepareLocalGlobalLogoutRequest(Request request, Principal userPrincipal) {
try {
SAML2Request saml2Request = new SAML2Request();
LogoutRequestType lort = saml2Request.createLogoutRequest(getIdentityURL());
NameIDType nameID = new NameIDType();
nameID.setValue(userPrincipal.getName());
nameID.setFormat(URI.create(JBossSAMLURIConstants.NAMEID_FORMAT_PERSISTENT.get()));
lort.setNameID(nameID);
lort.setDestination(URI.create(getIdentityURL()));
byte[] responseBytes = DocumentUtil.getDocumentAsString(saml2Request.convert(lort)).getBytes("UTF-8");
String samlRequest = RedirectBindingUtil.deflateBase64Encode(responseBytes);
Session session = request.getSessionInternal();
session.setNote(SAML_REQUEST_KEY, samlRequest);
} catch (Exception e) {
throw new RuntimeException("Could not perform IdP Initiated Single Logout.", e);
}
}
private boolean isGlobalLogout(Request request) {
return request.getParameter(GeneralConstants.GLOBAL_LOGOUT) != null;
}
/**
* <p> Handles SAML messages. </p>
*
* @param request
* @param response
*
* @throws IOException
* @throws ServletException
*/
private void handleSAMLMessage(Request request, Response response) throws IOException, ServletException {
if (isUnsolicitedResponse(request)) {
String samlVersion = request.getParameter(JBossSAMLConstants.UNSOLICITED_RESPONSE_SAML_VERSION.get());
if (samlVersion != null && JBossSAMLConstants.VERSION_2_0.get().equals(samlVersion)) {
// We have SAML 2 IDP-Initiated SSO/Unsolicited Response. Now we need to create a SAMLResponse and send back
// to SP as per target
handleSAML2UnsolicitedResponse(request, response);
} else {
// We have SAML 1.1 IDP first scenario. Now we need to create a SAMLResponse and send back
// to SP as per target
handleSAML11UnsolicitedResponse(request, response);
}
} else {
Session session = request.getSessionInternal();
String samlRequestMessage = (String) session.getNote(GeneralConstants.SAML_REQUEST_KEY);
String samlResponseMessage = (String) session.getNote(GeneralConstants.SAML_RESPONSE_KEY);
/**
* Since the container has finished the authentication, we can retrieve the original saml message as well as any
* relay state from the SP
*/
String relayState = (String) session.getNote(GeneralConstants.RELAY_STATE);
String signature = (String) session.getNote(GeneralConstants.SAML_SIGNATURE_REQUEST_KEY);
String sigAlg = (String) session.getNote(GeneralConstants.SAML_SIG_ALG_REQUEST_KEY);
if (logger.isTraceEnabled()) {
StringBuilder builder = new StringBuilder();
builder.append("Retrieved saml messages and relay state from session");
builder.append("saml Request message=").append(samlRequestMessage);
builder.append("::").append("SAMLResponseMessage=");
builder.append(samlResponseMessage).append(":").append("relay state=").append(relayState);
builder.append("Signature=").append(signature).append("::sigAlg=").append(sigAlg);
logger.trace(builder.toString());
}
if (isNotNull(samlRequestMessage)) {
processSAMLRequestMessage(request, response, null, isGlobalLogout(request));
} else if (isNotNull(samlResponseMessage)) {
processSAMLResponseMessage(request, response);
} else if (request.getRequestURI().equals(request.getContextPath() + "/")) {
// no SAML processing and the request is asking for /.
forwardHosted(request, response);
}
}
}
/**
* <p> Checks if the given {@link Request} containes a SAML11 Target parameter. Usually this indicates that the given request is
* a SAML11 request. </p>
*
* @param request
*
* @return
*/
private boolean isUnsolicitedResponse(Request request) {
return isNotNull(request.getParameter(JBossSAMLConstants.UNSOLICITED_RESPONSE_TARGET.get()));
}
private void forwardHosted(Request request, Response response) throws ServletException, IOException {
logger.trace("SAML 1.1::Proceeding to IDP index page");
RequestDispatcher dispatch = getContext().getServletContext()
.getRequestDispatcher(getIdpConfiguration().getHostedURI());
recycle(response);
try {
includeResource(request, response, dispatch);
} catch (ClassCastException cce) {
// JBAS5.1 and 6 quirkiness
includeResource(request.getRequest(), response, dispatch);
}
}
/**
* <p> Before forwarding we need to know the content length of the target resource in order to configure the response properly.
* This is necessary because the valve already have written to the response, and we want to override with the target resource
* data. </p>
*
* @param request
* @param response
* @param dispatch
*
* @throws ServletException
* @throws IOException
*/
private void includeResource(ServletRequest request, Response response, RequestDispatcher dispatch)
throws ServletException, IOException {
dispatch.include(request, response);
// we need to re-configure the content length because Tomcat will truncate the output with the size of the welcome page
// (eg.: index.html).
response.getCoyoteResponse().setContentLength(response.getContentCount());
}
/**
* <p> SAML parameters are also populated into session if they are present in the request. This allows the IDP to retrieve them
* later when handling a specific SAML request or response. </p>
*
* @param request
*
* @return
*
* @throws IOException
*/
private void populateSessionWithSAMLParameters(Request request) throws IOException {
String samlRequestMessage = request.getParameter(GeneralConstants.SAML_REQUEST_KEY);
String samlResponseMessage = request.getParameter(GeneralConstants.SAML_RESPONSE_KEY);
boolean containsSAMLRequestMessage = isNotNull(samlRequestMessage);
boolean containsSAMLResponseMessage = isNotNull(samlResponseMessage);
String signature = request.getParameter(GeneralConstants.SAML_SIGNATURE_REQUEST_KEY);
String sigAlg = request.getParameter(GeneralConstants.SAML_SIG_ALG_REQUEST_KEY);
String relayState = request.getParameter(GeneralConstants.RELAY_STATE);
Session session = request.getSessionInternal();
if (containsSAMLRequestMessage || containsSAMLResponseMessage) {
logger.trace("Storing the SAMLRequest/SAMLResponse and RelayState in session");
if (isNotNull(samlRequestMessage)) {
session.setNote(GeneralConstants.SAML_REQUEST_KEY, samlRequestMessage);
session.setNote(JBossSAMLConstants.BINDING.get(), request.getMethod());
}
if (isNotNull(samlResponseMessage)) {
session.setNote(GeneralConstants.SAML_RESPONSE_KEY, samlResponseMessage);
}
if (isNotNull(relayState)) {
session.setNote(GeneralConstants.RELAY_STATE, relayState.trim());
}
if (isNotNull(signature)) {
session.setNote(GeneralConstants.SAML_SIGNATURE_REQUEST_KEY, signature.trim());
}
if (isNotNull(sigAlg)) {
session.setNote(GeneralConstants.SAML_SIG_ALG_REQUEST_KEY, sigAlg.trim());
}
}
}
/**
* <p> Handles an unauthorized response returned by a service provider. </p>
*
* @param request
* @param response
*
* @throws IOException
* @throws ServletException
*/
private void handleUnauthorizedResponse(Request request, Response response) throws IOException, ServletException {
IDPWebRequestUtil webRequestUtil = new IDPWebRequestUtil(request, getIdpConfiguration(), keyManager);
Document samlErrorResponse = null;
String referer = request.getHeader("Referer");
String relayState = request.getParameter(GeneralConstants.RELAY_STATE);
try {
IDPType idpConfiguration = getIdpConfiguration();
samlErrorResponse = webRequestUtil.getErrorResponse(referer, JBossSAMLURIConstants.STATUS_AUTHNFAILED.get(),
getIdentityURL(), idpConfiguration.isSupportsSignature());
WebRequestUtilHolder holder = webRequestUtil.getHolder();
holder.setResponseDoc(samlErrorResponse).setDestination(referer).setRelayState(relayState)
.setAreWeSendingRequest(false).setPrivateKey(null).setSupportSignature(false).setServletResponse(response)
.setErrorResponse(true);
holder.setPostBindingRequested(webRequestUtil.hasSAMLRequestInPostProfile());
if (idpConfiguration.isSupportsSignature()) {
holder.setSupportSignature(true).setPrivateKey(keyManager.getSigningKey());
}
holder.setStrictPostBinding(idpConfiguration.isStrictPostBinding());
webRequestUtil.send(holder);
} catch (GeneralSecurityException e) {
throw new ServletException(e);
}
}
private boolean isUnauthorized(Response response) {
return response.getStatus() == HttpServletResponse.SC_FORBIDDEN;
}
private void invokeNextValve(Request request, Response response) throws IOException, ServletException {
getNext().invoke(request, response);
}
public Principal authenticateSSL(Request request, Response response) throws IOException {
// Retrieve the certificate chain for this client
if (containerLog.isDebugEnabled()) {
containerLog.debug(" Looking up certificates");
}
X509Certificate[] certs = (X509Certificate[]) request.getAttribute(Globals.CERTIFICATES_ATTR);
if ((certs == null) || (certs.length < 1)) {
try {
request.getCoyoteRequest().action(ActionCode.ACTION_REQ_SSL_CERTIFICATE, null);
} catch (IllegalStateException ise) {
// Request body was too large for save buffer
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, sm.getString("authenticator.certificates"));
return null;
}
certs = (X509Certificate[]) request.getAttribute(Globals.CERTIFICATES_ATTR);
}
if ((certs == null) || (certs.length < 1)) {
if (containerLog.isDebugEnabled()) {
containerLog.debug(" No certificates included with this request");
}
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, sm.getString("authenticator.certificates"));
return null;
}
// Authenticate the specified certificate chain
Principal principal = getContext().getRealm().authenticate(certs);
if (principal == null) {
if (containerLog.isDebugEnabled()) {
containerLog.debug(" Realm.authenticate() returned false");
}
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, sm.getString("authenticator.unauthorized"));
return null;
}
return principal;
}
protected void handleSAML11UnsolicitedResponse(Request request, Response response) throws ServletException, IOException {
try {
IDPWebRequestUtil webRequestUtil = new IDPWebRequestUtil(request, getIdpConfiguration(), keyManager);
Principal userPrincipal = request.getPrincipal();
String contextPath = getContextPath();
String target = request.getParameter(JBossSAMLConstants.UNSOLICITED_RESPONSE_TARGET.get());
Session session = request.getSessionInternal();
SAML11AssertionType saml11Assertion = (SAML11AssertionType) session.getNote("SAML11");
if (saml11Assertion == null) {
SAML11ProtocolContext saml11Protocol = new SAML11ProtocolContext();
saml11Protocol.setIssuerID(getIdentityURL());
SAML11SubjectType subject = new SAML11SubjectType();
SAML11SubjectTypeChoice subjectChoice = new SAML11SubjectTypeChoice(new SAML11NameIdentifierType(
userPrincipal.getName()));
subject.setChoice(subjectChoice);
saml11Protocol.setSubjectType(subject);
PicketLinkCoreSTS.instance().issueToken(saml11Protocol);
saml11Assertion = saml11Protocol.getIssuedAssertion();
session.setNote("SAML11", saml11Assertion);
if (AssertionUtil.hasExpired(saml11Assertion)) {
saml11Protocol.setIssuedAssertion(saml11Assertion);
PicketLinkCoreSTS.instance().renewToken(saml11Protocol);
saml11Assertion = saml11Protocol.getIssuedAssertion();
session.setNote("SAML11", saml11Assertion);
}
}
GenericPrincipal genericPrincipal = (GenericPrincipal) userPrincipal;
String[] roles = genericPrincipal.getRoles();
SAML11AttributeStatementType attributeStatement = this.createAttributeStatement(Arrays.asList(roles));
if (attributeStatement != null) {
saml11Assertion.add(attributeStatement);
}
// Send it as SAMLResponse
String id = IDGenerator.create("ID_");
SAML11ResponseType saml11Response = new SAML11ResponseType(id, XMLTimeUtil.getIssueInstant());
saml11Response.add(saml11Assertion);
saml11Response.setStatus(SAML11StatusType.successType());
ByteArrayOutputStream baos = new ByteArrayOutputStream();
SAML11ResponseWriter writer = new SAML11ResponseWriter(StaxUtil.getXMLStreamWriter(baos));
writer.write(saml11Response);
Document samlResponse = org.picketlink.identity.federation.core.saml.v2.util.DocumentUtil
.getDocument(new ByteArrayInputStream(baos.toByteArray()));
WebRequestUtilHolder holder = webRequestUtil.getHolder();
holder.setResponseDoc(samlResponse).setDestination(target).setRelayState("").setAreWeSendingRequest(false)
.setPrivateKey(null).setSupportSignature(false).setServletResponse(response);
String requestedBinding = request.getParameter(JBossSAMLConstants.UNSOLICITED_RESPONSE_SAML_BINDING.get());
if ("POST".equalsIgnoreCase(requestedBinding)) {
holder.setPostBindingRequested(true);
}
if (isEnableAudit()) {
PicketLinkAuditEvent auditEvent = new PicketLinkAuditEvent(AuditLevel.INFO);
auditEvent.setType(PicketLinkAuditEventType.RESPONSE_TO_SP);
auditEvent.setDestination(target);
auditEvent.setWhoIsAuditing(contextPath);
auditHelper.audit(auditEvent);
}
recycle(response);
webRequestUtil.send(holder);
} catch (GeneralSecurityException e) {
logger.samlIDPHandlingSAML11Error(e);
throw new ServletException();
}
}
private boolean isEnableAudit() {
return getConfiguration().isEnableAudit();
}
private void handleSAML2UnsolicitedResponse(Request request, Response response) throws ServletException {
SAML2Request samlRequest = new SAML2Request();
String id = IDGenerator.create("ID_");
String assertionConsumerURL = request.getParameter(JBossSAMLConstants.UNSOLICITED_RESPONSE_TARGET.get());
try {
AuthnRequestType authn = samlRequest
.createAuthnRequestType(id, assertionConsumerURL, getIdentityURL(), assertionConsumerURL);
String requestedBinding = request.getParameter(JBossSAMLConstants.UNSOLICITED_RESPONSE_SAML_BINDING.get());
if ("POST".equalsIgnoreCase(requestedBinding)) {
authn.setProtocolBinding(URI.create(JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get()));
request.setMethod("POST");
} else {
authn.setProtocolBinding(URI.create(JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get()));
}
authn.setUnsolicitedResponse(true);
processSAMLRequestMessage(request, response, authn, true);
} catch (Exception e) {
throw new ServletException("Could not handle SAML 2.0 Unsolicited Response.", e);
}
}
protected void processSAMLRequestMessage(Request request, Response response, RequestAbstractType requestType, boolean ignoreSignatureValidation) throws IOException {
Principal userPrincipal = request.getPrincipal();
Session session = request.getSessionInternal();
Document samlResponse = null;
boolean isErrorResponse = false;
String destination = null;
String destinationQueryStringWithSignature = null;
Boolean requestedPostProfile = null;
String samlRequestMessage = (String) session.getNote(GeneralConstants.SAML_REQUEST_KEY);
String samlRequestMessageBinding = (String) session.getNote(JBossSAMLConstants.BINDING.get());
String relayState = (String) session.getNote(GeneralConstants.RELAY_STATE);
String contextPath = getContextPath();
boolean willSendRequest = false;
String referer = request.getHeader("Referer");
cleanUpSessionNote(request);
// Determine the transport mechanism
boolean isSecure = request.isSecure();
String loginType = determineLoginType(isSecure);
IDPType idpConfiguration = getIdpConfiguration();
IDPWebRequestUtil webRequestUtil = new IDPWebRequestUtil(request, idpConfiguration, keyManager);
if (samlRequestMessageBinding != null && "POST".equals(samlRequestMessageBinding)) {
webRequestUtil.setRedirectProfile(false);
}
SAMLDocumentHolder samlDocumentHolder = null;
SAML2Object samlObject = null;
String responseDestination = referer;
try {
if (requestType == null) {
if (samlRequestMessage == null) {
throw logger.samlIDPValidationCheckFailed();
}
samlDocumentHolder = webRequestUtil.getSAMLDocumentHolder(samlRequestMessage);
} else {
samlDocumentHolder = new SAMLDocumentHolder(requestType);
samlDocumentHolder.setSamlDocument(new SAML2Request().convert(requestType));
}
if (samlDocumentHolder == null) {
return;
}
samlObject = samlDocumentHolder.getSamlObject();
if (!(samlObject instanceof RequestAbstractType)) {
throw logger.wrongTypeError(samlObject.getClass().getName());
}
RequestAbstractType requestAbstractType = (RequestAbstractType) samlObject;
String issuer = requestAbstractType.getIssuer().getValue();
// get the destination attribute for the response
if (requestAbstractType instanceof AuthnRequestType) {
AuthnRequestType authnRequestType = (AuthnRequestType) requestAbstractType;
URI senderURL = authnRequestType.getSenderURL();
if(senderURL != null) {
responseDestination = senderURL.toString();
}
}
IssuerInfoHolder idpIssuer = new IssuerInfoHolder(getIdentityURL());
ProtocolContext protocolContext = new HTTPContext(request, response, getContext().getServletContext());
// Create the request/response
SAML2HandlerRequest saml2HandlerRequest = new DefaultSAML2HandlerRequest(protocolContext, idpIssuer.getIssuer(),
samlDocumentHolder, HANDLER_TYPE.IDP);
saml2HandlerRequest.setRelayState(relayState);
if (StringUtil.isNotNull(loginType)) {
saml2HandlerRequest.addOption(GeneralConstants.LOGIN_TYPE, loginType);
}
String assertionID = (String) session.getSession().getAttribute(GeneralConstants.ASSERTION_ID);
// Set the options on the handler request
Map<String, Object> requestOptions = new HashMap<String, Object>();
Boolean ignoreSignatures = willIgnoreSignatureOfCurrentRequest(issuer);
if (ignoreSignatureValidation) {
ignoreSignatures = ignoreSignatureValidation;
}
requestOptions.put(GeneralConstants.IGNORE_SIGNATURES, ignoreSignatures);
requestOptions.put(GeneralConstants.SP_SSO_METADATA_DESCRIPTOR, spSSOMetadataMap.get(issuer));
requestOptions.put(GeneralConstants.SSO_METADATA_DESCRIPTOR, spSSOMetadataMap.get(issuer));
requestOptions.put(GeneralConstants.ROLE_GENERATOR, roleGenerator);
requestOptions.put(GeneralConstants.CONFIGURATION, idpConfiguration);
requestOptions.put(GeneralConstants.SAML_IDP_STRICT_POST_BINDING, idpConfiguration.isStrictPostBinding());
requestOptions.put(GeneralConstants.SUPPORTS_SIGNATURES, idpConfiguration.isSupportsSignature());
if (assertionID != null) {
requestOptions.put(GeneralConstants.ASSERTION_ID, assertionID);
}
if (this.keyManager != null) {
PublicKey validatingKey = getIssuerPublicKey(request, issuer);
requestOptions.put(GeneralConstants.SENDER_PUBLIC_KEY, validatingKey);
requestOptions.put(GeneralConstants.DECRYPTING_KEY, keyManager.getEncryptionKey());
}
// if this is a SAML AuthnRequest load the roles using the generator.
if (requestAbstractType instanceof AuthnRequestType) {
List<String> roles = roleGenerator.generateRoles(userPrincipal);
session.getSession().setAttribute(GeneralConstants.ROLES_ID, roles);
Set<AttributeStatementType> attribs = this.attribManager.getAttributes((AuthnRequestType) requestAbstractType,
passUserPrincipalToAttributeManager == true
? request.getUserPrincipal()
: userPrincipal);
requestOptions.put(GeneralConstants.ATTRIBUTES, attribs);
}
if (auditHelper != null) {
requestOptions.put(GeneralConstants.AUDIT_HELPER, auditHelper);
requestOptions.put(GeneralConstants.CONTEXT_PATH, contextPath);
}
saml2HandlerRequest.setOptions(requestOptions);
SAML2HandlerResponse saml2HandlerResponse = new DefaultSAML2HandlerResponse();
Set<SAML2Handler> handlers = chain.handlers();
logger.trace("Handlers are=" + handlers);
if (handlers != null) {
try {
if (getConfiguration().getHandlers().isLocking()) {
chainLock.lock();
}
for (SAML2Handler handler : handlers) {
handler.handleRequestType(saml2HandlerRequest, saml2HandlerResponse);
willSendRequest = saml2HandlerResponse.getSendRequest();
}
} finally {
if (getConfiguration().getHandlers().isLocking()) {
chainLock.unlock();
}
}
}
samlResponse = saml2HandlerResponse.getResultingDocument();
relayState = saml2HandlerResponse.getRelayState();
destination = saml2HandlerResponse.getDestination();
requestedPostProfile = saml2HandlerResponse.isPostBindingForResponse();
destinationQueryStringWithSignature = saml2HandlerResponse.getDestinationQueryStringWithSignature();
} catch (Exception e) {
String status = JBossSAMLURIConstants.STATUS_AUTHNFAILED.get();
if (e instanceof IssuerNotTrustedException || e.getCause() instanceof IssuerNotTrustedException) {
status = JBossSAMLURIConstants.STATUS_REQUEST_DENIED.get();
}
logger.samlIDPRequestProcessingError(e);
samlResponse = webRequestUtil.getErrorResponse(responseDestination, status, getIdentityURL(),
idpConfiguration.isSupportsSignature());
isErrorResponse = true;
}
try {
// if the destination is null, probably because some error occur during authentication, use the AuthnRequest
// AssertionConsumerServiceURL as the destination
boolean forceAuthn = false;
if (samlObject instanceof AuthnRequestType) {
AuthnRequestType authRequest = (AuthnRequestType) samlObject;
if (destination == null) {
destination = authRequest.getSenderURL().toASCIIString();
}
forceAuthn = authRequest.isForceAuthn();
}
// if destination is still empty redirect the user to the identity url. If the user is already authenticated he
// will be probably redirected to the idp hosted page.
if (destination == null) {
response.sendRedirect(getIdentityURL());
} else if (samlResponse != null) {
WebRequestUtilHolder holder = webRequestUtil.getHolder();
holder.setResponseDoc(samlResponse).setDestination(destination).setRelayState(relayState)
.setAreWeSendingRequest(willSendRequest).setPrivateKey(null).setSupportSignature(false)
.setErrorResponse(isErrorResponse).setServletResponse(response)
.setDestinationQueryStringWithSignature(destinationQueryStringWithSignature);
holder.setStrictPostBinding(idpConfiguration.isStrictPostBinding());
if (requestedPostProfile != null) {
holder.setPostBindingRequested(requestedPostProfile);
} else {
holder.setPostBindingRequested(webRequestUtil.hasSAMLRequestInPostProfile());
}
if (idpConfiguration.isSupportsSignature()) {
holder.setPrivateKey(keyManager.getSigningKey()).setSupportSignature(true);
}
if (holder.isPostBinding()) {
recycle(response);
}
if (isEnableAudit()) {
PicketLinkAuditEvent auditEvent = new PicketLinkAuditEvent(AuditLevel.INFO);
auditEvent.setType(PicketLinkAuditEventType.RESPONSE_TO_SP);
auditEvent.setDestination(destination);
auditEvent.setWhoIsAuditing(contextPath);
auditHelper.audit(auditEvent);
}
if (forceAuthn) {
session.expire();
}
webRequestUtil.send(holder);
} else if (destination != null) {
response.sendRedirect(destination);
}
} catch (ParsingException e) {
logger.samlAssertionPasingFailed(e);
} catch (GeneralSecurityException e) {
logger.trace("Security Exception:", e);
} catch (Exception e) {
logger.error(e);
}
return;
}
/**
* Returns the PublicKey to be used for the token's signature verification. This key is related with the issuer of the SAML
* message received by the IDP.
*
* @param request
* @param issuer
*
* @return
*
* @throws ProcessingException
* @throws ConfigurationException
*/
private PublicKey getIssuerPublicKey(Request request, String issuer) throws ConfigurationException, ProcessingException {
String issuerHost = null;
PublicKey issuerPublicKey = null;
try {
issuerHost = new URL(issuer).getHost();
} catch (MalformedURLException e) {
logger.trace("Token issuer is not a valid URL: " + issuer, e);
issuerHost = issuer;
}
logger.trace("Trying to find a PK for issuer: " + issuerHost);
try {
issuerPublicKey = CoreConfigUtil.getValidatingKey(keyManager, issuerHost);
} catch (IllegalStateException ise) {
logger.trace("Token issuer is not found for: " + issuer, ise);
}
if (issuerPublicKey == null) {
issuerHost = request.getRemoteAddr();
logger.trace("Trying to find a PK for issuer " + issuerHost);
issuerPublicKey = CoreConfigUtil.getValidatingKey(keyManager, issuerHost);
}
logger.trace("Using Validating Alias=" + issuerHost + " to check signatures.");
return issuerPublicKey;
}
protected void processSAMLResponseMessage(Request request, Response response) throws ServletException, IOException {
Session session = request.getSessionInternal();
SAMLDocumentHolder samlDocumentHolder = null;
SAML2Object samlObject = null;
Document samlResponse = null;
boolean isErrorResponse = false;
String destination = null;
String destinationQueryStringWithSignature = null;
String contextPath = getContextPath();
boolean requestedPostProfile = false;
// Get the SAML Response Message
String samlResponseMessage = (String) session.getNote(GeneralConstants.SAML_RESPONSE_KEY);
String relayState = (String) session.getNote(GeneralConstants.RELAY_STATE);
boolean willSendRequest = false;
String referer = request.getHeader("Referer");
cleanUpSessionNote(request);
IDPType idpConfiguration = getIdpConfiguration();
IDPWebRequestUtil webRequestUtil = new IDPWebRequestUtil(request, idpConfiguration, keyManager);
try {
samlDocumentHolder = webRequestUtil.getSAMLDocumentHolder(samlResponseMessage);
samlObject = samlDocumentHolder.getSamlObject();
if (!(samlObject instanceof StatusResponseType)) {
throw logger.wrongTypeError(samlObject.getClass().getName());
}
StatusResponseType statusResponseType = (StatusResponseType) samlObject;
String issuer = statusResponseType.getIssuer().getValue();
boolean isValid = samlResponseMessage != null;
if (!isValid) {
throw logger.samlIDPValidationCheckFailed();
}
IssuerInfoHolder idpIssuer = new IssuerInfoHolder(getIdentityURL());
ProtocolContext protocolContext = new HTTPContext(request, response, getContext().getServletContext());
// Create the request/response
SAML2HandlerRequest saml2HandlerRequest = new DefaultSAML2HandlerRequest(protocolContext, idpIssuer.getIssuer(),
samlDocumentHolder, HANDLER_TYPE.IDP);
Map<String, Object> options = new HashMap<String, Object>();
if (idpConfiguration.isSupportsSignature() || idpConfiguration.isEncrypt()) {
PublicKey publicKey = getIssuerPublicKey(request, issuer);
options.put(GeneralConstants.SENDER_PUBLIC_KEY, publicKey);
}
options.put(GeneralConstants.SAML_IDP_STRICT_POST_BINDING, idpConfiguration.isStrictPostBinding());
options.put(GeneralConstants.SUPPORTS_SIGNATURES, idpConfiguration.isSupportsSignature());
if (auditHelper != null) {
options.put(GeneralConstants.AUDIT_HELPER, auditHelper);
options.put(GeneralConstants.CONTEXT_PATH, contextPath);
}
saml2HandlerRequest.setOptions(options);
saml2HandlerRequest.setRelayState(relayState);
SAML2HandlerResponse saml2HandlerResponse = new DefaultSAML2HandlerResponse();
Set<SAML2Handler> handlers = chain.handlers();
// the trusted domains is done by a handler
// webRequestUtil.isTrusted(issuer);
if (handlers != null) {
try {
chainLock.lock();
for (SAML2Handler handler : handlers) {
handler.reset();
handler.handleStatusResponseType(saml2HandlerRequest, saml2HandlerResponse);
willSendRequest = saml2HandlerResponse.getSendRequest();
}
} finally {
chainLock.unlock();
}
}
samlResponse = saml2HandlerResponse.getResultingDocument();
relayState = saml2HandlerResponse.getRelayState();
destination = saml2HandlerResponse.getDestination();
requestedPostProfile = saml2HandlerResponse.isPostBindingForResponse();
destinationQueryStringWithSignature = saml2HandlerResponse.getDestinationQueryStringWithSignature();
} catch (Exception e) {
String status = JBossSAMLURIConstants.STATUS_AUTHNFAILED.get();
if (e instanceof IssuerNotTrustedException) {
status = JBossSAMLURIConstants.STATUS_REQUEST_DENIED.get();
}
logger.samlIDPRequestProcessingError(e);
samlResponse = webRequestUtil.getErrorResponse(referer, status, getIdentityURL(),
idpConfiguration.isSupportsSignature());
isErrorResponse = true;
} finally {
try {
if (destination == null) {
throw new ServletException(logger.nullValueError("Destination"));
}
if (samlResponse != null) {
WebRequestUtilHolder holder = webRequestUtil.getHolder();
holder.setResponseDoc(samlResponse).setDestination(destination).setRelayState(relayState)
.setAreWeSendingRequest(willSendRequest).setPrivateKey(null).setSupportSignature(false)
.setErrorResponse(isErrorResponse).setServletResponse(response)
.setPostBindingRequested(requestedPostProfile)
.setDestinationQueryStringWithSignature(destinationQueryStringWithSignature);
/*
* if (requestedPostProfile) holder.setPostBindingRequested(requestedPostProfile); else
* holder.setPostBindingRequested(postProfile);
*/
if (idpConfiguration.isSupportsSignature()) {
holder.setPrivateKey(keyManager.getSigningKey()).setSupportSignature(true);
}
holder.setStrictPostBinding(idpConfiguration.isStrictPostBinding());
if (holder.isPostBinding()) {
recycle(response);
}
if (isEnableAudit()) {
PicketLinkAuditEvent auditEvent = new PicketLinkAuditEvent(AuditLevel.INFO);
auditEvent.setType(PicketLinkAuditEventType.RESPONSE_TO_SP);
auditEvent.setWhoIsAuditing(contextPath);
auditEvent.setDestination(destination);
auditHelper.audit(auditEvent);
}
webRequestUtil.send(holder);
} else {
response.sendRedirect(destination);
}
} catch (ParsingException e) {
logger.samlAssertionPasingFailed(e);
} catch (GeneralSecurityException e) {
logger.trace("Security Exception:", e);
}
}
return;
}
protected void cleanUpSessionNote(Request request) {
Session session = request.getSessionInternal();
/**
* Since the container has finished the authentication, we can retrieve the original saml message as well as any relay
* state from the SP
*/
String samlRequestMessage = (String) session.getNote(GeneralConstants.SAML_REQUEST_KEY);
String samlRequestMesssageBinding = (String) session.getNote(JBossSAMLConstants.BINDING.get());
String samlResponseMessage = (String) session.getNote(GeneralConstants.SAML_RESPONSE_KEY);
String relayState = (String) session.getNote(GeneralConstants.RELAY_STATE);
String signature = (String) session.getNote(GeneralConstants.SAML_SIGNATURE_REQUEST_KEY);
String sigAlg = (String) session.getNote(GeneralConstants.SAML_SIG_ALG_REQUEST_KEY);
if (logger.isTraceEnabled()) {
StringBuilder builder = new StringBuilder();
builder.append("Retrieved saml messages and relay state from session");
builder.append("saml Request message=").append(samlRequestMessage);
builder.append("Binding=").append(samlRequestMesssageBinding);
builder.append("::").append("SAMLResponseMessage=");
builder.append(samlResponseMessage).append(":").append("relay state=").append(relayState);
builder.append("Signature=").append(signature).append("::sigAlg=").append(sigAlg);
logger.trace(builder.toString());
}
if (isNotNull(samlRequestMessage)) {
session.removeNote(GeneralConstants.SAML_REQUEST_KEY);
session.removeNote(JBossSAMLConstants.BINDING.get());
}
if (isNotNull(samlResponseMessage)) {
session.removeNote(GeneralConstants.SAML_RESPONSE_KEY);
}
if (isNotNull(relayState)) {
session.removeNote(GeneralConstants.RELAY_STATE);
}
if (isNotNull(signature)) {
session.removeNote(GeneralConstants.SAML_SIGNATURE_REQUEST_KEY);
}
if (isNotNull(sigAlg)) {
session.removeNote(GeneralConstants.SAML_SIG_ALG_REQUEST_KEY);
}
}
protected void sendErrorResponseToSP(String referrer, Response response, String relayState, IDPWebRequestUtil webRequestUtil)
throws ServletException, IOException, ConfigurationException {
logger.trace("About to send error response to SP:" + referrer);
String contextPath = getContextPath();
IDPType idpConfiguration = getIdpConfiguration();
Document samlResponse = webRequestUtil.getErrorResponse(referrer, JBossSAMLURIConstants.STATUS_RESPONDER.get(),
getIdentityURL(), idpConfiguration.isSupportsSignature());
try {
WebRequestUtilHolder holder = webRequestUtil.getHolder();
holder.setResponseDoc(samlResponse).setDestination(referrer).setRelayState(relayState)
.setAreWeSendingRequest(false).setPrivateKey(null).setSupportSignature(false).setServletResponse(response);
holder.setPostBindingRequested(webRequestUtil.hasSAMLRequestInPostProfile());
if (idpConfiguration.isSupportsSignature()) {
holder.setPrivateKey(keyManager.getSigningKey()).setSupportSignature(true);
}
holder.setStrictPostBinding(idpConfiguration.isStrictPostBinding());
if (holder.isPostBinding()) {
recycle(response);
}
if (isEnableAudit()) {
PicketLinkAuditEvent auditEvent = new PicketLinkAuditEvent(AuditLevel.INFO);
auditEvent.setType(PicketLinkAuditEventType.ERROR_RESPONSE_TO_SP);
auditEvent.setWhoIsAuditing(contextPath);
auditEvent.setDestination(referrer);
auditHelper.audit(auditEvent);
}
webRequestUtil.send(holder);
} catch (ParsingException e1) {
throw new ServletException(e1);
} catch (GeneralSecurityException e) {
throw new ServletException(e);
}
}
/**
* <p> Initializes the {@link IdentityServer}. </p>
*/
protected void initIdentityServer() {
IDPType idpConfiguration = getIdpConfiguration();
// The Identity Server on the servlet context gets set
// in the implementation of IdentityServer
// Create an Identity Server and set it on the context
IdentityServer identityServer = (IdentityServer) getContext().getServletContext().getAttribute(
GeneralConstants.IDENTITY_SERVER);
if (identityServer == null) {
identityServer = new IdentityServer();
getContext().getServletContext().setAttribute(GeneralConstants.IDENTITY_SERVER, identityServer);
if (StringUtil.isNotNull(idpConfiguration.getIdentityParticipantStack())) {
try {
Class<?> clazz = SecurityActions.loadClass(getClass(), idpConfiguration.getIdentityParticipantStack());
if (clazz == null) {
throw logger.classNotLoadedError(idpConfiguration.getIdentityParticipantStack());
}
identityServer.setStack((IdentityParticipantStack) clazz.newInstance());
} catch (Exception e) {
logger.samlIDPUnableToSetParticipantStackUsingDefault(e);
}
}
}
}
/**
* <p> Initialize the Handlers chain. </p>
*
* @throws LifecycleException
*/
protected void initHandlersChain() throws LifecycleException {
try {
Handlers handlers = this.picketLinkConfiguration.getHandlers();
if (handlers == null) {
// Get the handlers
String handlerConfigFileName = GeneralConstants.HANDLER_CONFIG_FILE_LOCATION;
handlers = ConfigurationUtil.getHandlers(getContext().getServletContext().getResourceAsStream(
handlerConfigFileName));
}
// Get the chain from config
String handlerChainClass = handlers.getHandlerChainClass();
SAML2HandlerChain chain;
if (isNullOrEmpty(handlerChainClass)) {
chain = SAML2HandlerChainFactory.createChain();
} else {
try {
chain = SAML2HandlerChainFactory.createChain(handlerChainClass);
} catch (ProcessingException e1) {
throw new LifecycleException(e1);
}
}
chain.addAll(HandlerUtil.getHandlers(handlers));
Map<String, Object> chainConfigOptions = new HashMap<String, Object>();
chainConfigOptions.put(GeneralConstants.ROLE_GENERATOR, roleGenerator);
chainConfigOptions.put(GeneralConstants.CONFIGURATION, getIdpConfiguration());
if (keyManager != null) {
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));
}
}
SAML2HandlerChainConfig handlerChainConfig = new DefaultSAML2HandlerChainConfig(chainConfigOptions);
Set<SAML2Handler> samlHandlers = chain.handlers();
for (SAML2Handler handler : samlHandlers) {
handler.initChainConfig(handlerChainConfig);
}
this.chain = chain;
this.picketLinkConfiguration.setHandlers(handlers);
} catch (Exception e) {
logger.samlHandlerConfigurationError(e);
throw new LifecycleException(e.getLocalizedMessage());
}
}
protected void initKeyManager() throws LifecycleException {
IDPType idpConfiguration = getIdpConfiguration();
if (idpConfiguration.isSupportsSignature() || idpConfiguration.isEncrypt()) {
KeyProviderType keyProvider = idpConfiguration.getKeyProvider();
if (keyProvider == null) {
throw new LifecycleException(
logger.nullValueError("Key Provider is null for context=" + getContext().getName()));
}
TrustKeyManager keyManager;
try {
keyManager = CoreConfigUtil.getTrustKeyManager(keyProvider);
List<AuthPropertyType> authProperties = CoreConfigUtil.getKeyProviderProperties(keyProvider);
keyManager.setAuthProperties(authProperties);
keyManager.setValidatingAlias(keyProvider.getValidatingAlias());
// 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;
}
}
}
} catch (Exception e) {
logger.trustKeyManagerCreationError(e);
throw new LifecycleException(e.getLocalizedMessage());
}
logger.samlIDPSettingCanonicalizationMethod(idpConfiguration.getCanonicalizationMethod());
XMLSignatureUtil.setCanonicalizationMethodType(idpConfiguration.getCanonicalizationMethod());
logger.trace("Key Provider=" + keyProvider.getClassName());
this.keyManager = keyManager;
}
}
/**
* <p> Initializes the IDP configuration. </p>
*/
@SuppressWarnings("deprecation")
protected void initIDPConfiguration() {
InputStream is = null;
if (isNullOrEmpty(this.configFile)) {
is = getContext().getServletContext().getResourceAsStream(CONFIG_FILE_LOCATION);
} else {
try {
is = new FileInputStream(this.configFile);
} catch (FileNotFoundException e) {
throw logger.samlIDPConfigurationError(e);
}
}
PicketLinkType picketLinkType = null;
// Work on the IDP Configuration
if (configProvider != null) {
try {
if (is == null) {
// Try the older version
is = getContext().getServletContext().getResourceAsStream(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.getIDPConfiguration());
} catch (ProcessingException e) {
throw logger.samlIDPConfigurationError(e);
} catch (ParsingException e) {
throw logger.samlIDPConfigurationError(e);
}
}
if (picketLinkType == null) {
if (is != null) {
try {
picketLinkType = ConfigurationUtil.getConfiguration(is);
} catch (ParsingException e) {
logger.trace(e);
logger.samlIDPConfigurationError(e);
}
}
if (is == null) {
// Try the older version
is = getContext().getServletContext().getResourceAsStream(DEPRECATED_CONFIG_FILE_LOCATION);
if (is == null) {
throw logger.configurationFileMissing(DEPRECATED_CONFIG_FILE_LOCATION);
}
try {
picketLinkType = new PicketLinkType();
picketLinkType.setIdpOrSP(ConfigurationUtil.getIDPConfiguration(is));
} catch (ParsingException e) {
logger.samlIDPConfigurationError(e);
}
}
}
//Close the InputStream as we no longer need it
if(is != null){
try {
is.close();
} catch (IOException e) {
//ignore
}
}
IDPType idpConfiguration = (IDPType) picketLinkType.getIdpOrSP();
try {
if (picketLinkType != null) {
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(getContext()
.getServletContext());
auditHelper = new PicketLinkAuditHelper(securityDomainName);
}
}
}
logger.trace("Identity Provider URL=" + idpConfiguration.getIdentityURL());
// Get the attribute manager
String attributeManager = idpConfiguration.getAttributeManager();
if (attributeManager != null && !"".equals(attributeManager)) {
Class<?> clazz = SecurityActions.loadClass(getClass(), attributeManager);
if (clazz == null) {
throw new RuntimeException(logger.classNotLoadedError(attributeManager));
}
AttributeManager delegate = (AttributeManager) clazz.newInstance();
// Add some keys to the attributes
String[] ak = new String[]{"mail", "cn", "commonname", "givenname", "surname", "employeeType", "employeeNumber",
"facsimileTelephoneNumber"};
ArrayList<String> attrList = new ArrayList<String>();
attrList.addAll(Arrays.asList(ak));
if(this.attributeKeys != null) {
attrList.addAll(this.attributeKeys);
}
this.attribManager = new DelegatedAttributeManager(delegate, attrList);
}
// Get the role generator
String roleGeneratorAttribute = idpConfiguration.getRoleGenerator();
if (roleGeneratorAttribute != null && !"".equals(roleGeneratorAttribute)) {
Class<?> clazz = SecurityActions.loadClass(getClass(), roleGeneratorAttribute);
if (clazz == null) {
throw new RuntimeException(logger.classNotLoadedError(roleGeneratorAttribute));
}
roleGenerator = (RoleGenerator) clazz.newInstance();
}
// Read SP Metadata if provided
List<EntityDescriptorType> entityDescriptors = CoreConfigUtil.getMetadataConfiguration(idpConfiguration,
getContext().getServletContext());
if (entityDescriptors != null) {
for (EntityDescriptorType entityDescriptorType : entityDescriptors) {
SPSSODescriptorType spSSODescriptor = CoreConfigUtil.getSPDescriptor(entityDescriptorType);
if (spSSODescriptor != null) {
spSSOMetadataMap.put(entityDescriptorType.getEntityID(), spSSODescriptor);
}
}
}
} catch (Exception e) {
throw logger.samlIDPConfigurationError(e);
}
initHostedURI(idpConfiguration);
this.picketLinkConfiguration = picketLinkType;
}
/**
* Initializes the STS configuration.
*/
protected void initSTSConfiguration() {
// if the sts configuration is present in the picketlink.xml then load it.
if (this.picketLinkConfiguration != null && this.picketLinkConfiguration.getStsType() != null) {
PicketLinkCoreSTS sts = PicketLinkCoreSTS.instance();
sts.initialize(new PicketLinkSTSConfiguration(this.picketLinkConfiguration.getStsType()));
} else {
// Try to load from /WEB-INF/picketlink-sts.xml.
// Ensure that the Core STS has the SAML20 Token Provider
PicketLinkCoreSTS sts = PicketLinkCoreSTS.instance();
// Let us look for a file
String configPath = getContext().getServletContext().getRealPath("/WEB-INF/picketlink-sts.xml");
File stsTokenConfigFile = configPath != null ? new File(configPath) : null;
if (stsTokenConfigFile == null || stsTokenConfigFile.exists() == false) {
logger.samlIDPInstallingDefaultSTSConfig();
sts.installDefaultConfiguration();
} else {
sts.installDefaultConfiguration(stsTokenConfigFile.toURI().toString());
}
}
}
protected String getIdentityURL() {
return getIdpConfiguration().getIdentityURL();
}
protected Context getContext() {
return (Context) getContainer();
}
protected abstract String getContextPath();
protected void recycle(Response response) {
/**
* Since the container finished authentication, it will try to locate index.jsp or index.html. We need to recycle
* whatever is in the response object such that we direct it to the html that is being created as part of the HTTP/POST
* binding
*/
response.recycle();
}
protected String determineLoginType(boolean isSecure) {
String result = JBossSAMLURIConstants.AC_PASSWORD.get();
LoginConfig loginConfig = getContext().getLoginConfig();
if (loginConfig != null) {
String auth = loginConfig.getAuthMethod();
if (StringUtil.isNotNull(auth)) {
if ("CLIENT-CERT".equals(auth)) {
result = JBossSAMLURIConstants.AC_TLS_CLIENT.get();
} else if (isSecure) {
result = JBossSAMLURIConstants.AC_PASSWORD_PROTECTED_TRANSPORT.get();
}
}
}
return result;
}
protected void startPicketLink() throws LifecycleException {
SystemPropertiesUtil.ensure();
//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() {
try {
logger.info("Reloading configuration for " + getContext().getName());
processConfiguration();
} catch (LifecycleException e) {
logger.error(e);
}
}
}, timerInterval, timerInterval);
}
processConfiguration();
}
protected void stopPicketLink() {
if (timer != null) {
timer.cancel();
}
}
private void processConfiguration() throws LifecycleException {
initIDPConfiguration();
initSTSConfiguration();
initKeyManager();
initHandlersChain();
initIdentityServer();
}
/**
* Given a set of roles, create an attribute statement
*
* @param roles
*
* @return
*/
private SAML11AttributeStatementType createAttributeStatement(List<String> roles) {
SAML11AttributeStatementType attrStatement = null;
for (String role : roles) {
if (attrStatement == null) {
attrStatement = new SAML11AttributeStatementType();
}
SAML11AttributeType attr = new SAML11AttributeType("Role", URI.create("urn:picketlink:role"));
attr.add(role);
attrStatement.add(attr);
}
return attrStatement;
}
public void setAuditHelper(PicketLinkAuditHelper auditHelper) {
this.auditHelper = auditHelper;
}
/**
* We will ignore signatures of current SAMLRequest if SP Metadata are provided for current SP and if metadata specifies that
* SAMLRequest is not signed for this SP.
*
* @param spIssuer
*
* @return true if signature is not expected in SAMLRequest and so signature validation should be ignored
*/
private Boolean willIgnoreSignatureOfCurrentRequest(String spIssuer) {
SPSSODescriptorType currentSPMetadata = spSSOMetadataMap.get(spIssuer);
if (currentSPMetadata == null) {
return false;
}
Boolean isRequestSigned = currentSPMetadata.isAuthnRequestsSigned();
logger.trace("Issuer: " + spIssuer + ", isRequestSigned: " + isRequestSigned);
if (isRequestSigned == null) {
isRequestSigned = Boolean.FALSE;
}
return !isRequestSigned;
}
private void initHostedURI(IDPType idpConfiguration) {
String hostedURI = idpConfiguration.getHostedURI();
if (isNullOrEmpty(hostedURI)) {
hostedURI = "/hosted/";
} else if (!hostedURI.contains(".") && !hostedURI.endsWith("/")) {
// make sure the hosted uri have a slash at the end if it points to a directory
hostedURI = hostedURI + "/";
}
idpConfiguration.setHostedURI(hostedURI);
}
private SSLAuthenticator getSSLAuthenticator() {
if (this.sslAuthenticator == null) {
this.sslAuthenticator = new SSLAuthenticator() {
@Override
public Valve getNext() {
return new ValveBase() {
@Override
public void invoke(Request request, Response response) throws IOException, ServletException {
// no-op
}
};
}
};
this.sslAuthenticator.setContainer(getContainer());
try {
this.sslAuthenticator.start();
} catch (LifecycleException e) {
throw new RuntimeException("Error starting SSL authenticator.", e);
}
}
return this.sslAuthenticator;
}
private boolean isAjaxRequest(Request request) {
String requestedWithHeader = request.getHeader(GeneralConstants.HTTP_HEADER_X_REQUESTED_WITH);
return requestedWithHeader != null && "XMLHttpRequest".equalsIgnoreCase(requestedWithHeader);
}
}