/* * 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.test.identity.federation.web.saml.handlers; import junit.framework.TestCase; import org.picketlink.common.ErrorCodes; import org.picketlink.common.constants.GeneralConstants; import org.picketlink.common.constants.JBossSAMLURIConstants; import org.picketlink.common.exceptions.ProcessingException; import org.picketlink.common.util.DocumentUtil; import org.picketlink.config.federation.IDPType; import org.picketlink.config.federation.SPType; import org.picketlink.identity.federation.api.saml.v2.response.SAML2Response; import org.picketlink.identity.federation.core.parsers.saml.SAMLParser; import org.picketlink.identity.federation.core.saml.v2.common.SAMLDocumentHolder; 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.DefaultSAML2HandlerConfig; 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.SAML2HandlerChainConfig; import org.picketlink.identity.federation.core.saml.v2.interfaces.SAML2HandlerConfig; 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.sts.PicketLinkCoreSTS; import org.picketlink.identity.federation.saml.v2.protocol.AuthnRequestType; import org.picketlink.identity.federation.saml.v2.protocol.ResponseType; import org.picketlink.identity.federation.web.core.HTTPContext; import org.picketlink.identity.federation.web.core.IdentityServer; import org.picketlink.identity.federation.web.core.SessionManager; import org.picketlink.identity.federation.web.handlers.saml2.BaseSAML2Handler; import org.picketlink.identity.federation.web.handlers.saml2.SAML2AuthenticationHandler; import org.picketlink.identity.federation.web.handlers.saml2.SAML2InResponseToVerificationHandler; import org.picketlink.test.identity.federation.web.mock.MockHttpServletRequest; import org.picketlink.test.identity.federation.web.mock.MockHttpServletResponse; import org.picketlink.test.identity.federation.web.mock.MockHttpSession; import org.picketlink.test.identity.federation.web.mock.MockServletContext; import org.w3c.dom.Document; import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSessionListener; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.security.Principal; import java.util.HashMap; import java.util.Map; /** * Unit test the {@link org.picketlink.identity.federation.web.handlers.saml2.SAML2InResponseToVerificationHandler} * * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a> */ public class SAML2InResponseToVerificationHandlerUnitTestCase extends TestCase { private static final String SERVICE_PROVIDER_URL = "http://localhost:8080/sales/"; public void testResponseIdVerification() throws Exception { // 1) CONFIGURATION AND INITIALIZATION OF TEST // Create handlers SAML2AuthenticationHandler authenticationHandler = new SAML2AuthenticationHandler(); SAML2InResponseToVerificationHandler verificationHandler = new SAML2InResponseToVerificationHandler(); // Create configuration for handlers SAML2HandlerChainConfig chainConfig = new DefaultSAML2HandlerChainConfig(); SAML2HandlerConfig handlerConfig = new DefaultSAML2HandlerConfig(); handlerConfig.addParameter(GeneralConstants.NAMEID_FORMAT, JBossSAMLURIConstants.NAMEID_FORMAT_PERSISTENT.get()); handlerConfig.addParameter(SAML2Handler.DISABLE_SENDING_ROLES, "true"); Map<String, Object> chainOptions = new HashMap<String, Object>(); SPType spType = new SPType(); spType.setServiceURL(SERVICE_PROVIDER_URL); chainOptions.put(GeneralConstants.CONFIGURATION, spType); chainOptions.put(GeneralConstants.ROLE_VALIDATOR_IGNORE, "true"); chainConfig.set(chainOptions); // Initialize the handlers authenticationHandler.initChainConfig(chainConfig); authenticationHandler.initHandlerConfig(handlerConfig); verificationHandler.initChainConfig(chainConfig); verificationHandler.initHandlerConfig(handlerConfig); // Create a Protocol Context MockServletContext servletContext = createServletContext(); MockHttpSession session = new MockHttpSession(); session.setServletContext(servletContext); MockHttpServletRequest servletRequest = new MockHttpServletRequest(session, "POST"); MockHttpServletResponse servletResponse = new MockHttpServletResponse(); HTTPContext httpContext = new HTTPContext(servletRequest, servletResponse, servletContext); // Create handler request and response IssuerInfoHolder issuerInfo = new IssuerInfoHolder(SERVICE_PROVIDER_URL); SAML2HandlerRequest request = new DefaultSAML2HandlerRequest(httpContext, issuerInfo.getIssuer(), null, SAML2Handler.HANDLER_TYPE.SP); request.setTypeOfRequestToBeGenerated(SAML2HandlerRequest.GENERATE_REQUEST_TYPE.AUTH); SAML2HandlerResponse response = new DefaultSAML2HandlerResponse(); // 2) GENERATE SAML AUTHENTICATION REQUEST // Generate SAML AuthnRequest with handlers authenticationHandler.generateSAMLRequest(request, response); verificationHandler.generateSAMLRequest(request, response); // Parse document and verify that ID is saved in Http session Document samlReqDoc = response.getResultingDocument(); SAMLParser parser = new SAMLParser(); AuthnRequestType authnRequest = (AuthnRequestType) parser.parse(DocumentUtil.getNodeAsStream(samlReqDoc)); assertEquals(authnRequest.getID(), servletRequest.getSession().getAttribute(GeneralConstants.AUTH_REQUEST_ID)); // 3) SEND SAML AUTHENTICATION REQUEST TO IDP // Generate request and response for IDP SAML2HandlerResponse handlerResponseFromIdp = sendRequestToIdp(authnRequest, samlReqDoc, httpContext, handlerConfig); // Parse SAML response from IDP Document doc2response = handlerResponseFromIdp.getResultingDocument(); assertNotNull(doc2response); String responseString = DocumentUtil.asString(doc2response); // 4) PROCESS SAML RESPONSE FROM IDP. VERIFICATION OF InResponseId SHOULD BE SUCCESSFUL HandlerContext handlerContext = getHandlerRequestAndResponse(httpContext, issuerInfo, responseString); // Assert that ID from session is not null String inResponseIdFromSession = (String) servletRequest.getSession().getAttribute(GeneralConstants.AUTH_REQUEST_ID); assertNotNull(inResponseIdFromSession); // Handle response from IDP authenticationHandler.handleStatusResponseType(handlerContext.request, handlerContext.response); verificationHandler.handleStatusResponseType(handlerContext.request, handlerContext.response); // Verify that Id is not in session anymore. Becaue it was removed by SAML2ResponseIdVerificationHandler assertNull(servletRequest.getSession().getAttribute(GeneralConstants.AUTH_REQUEST_ID)); // 5) CHANGE InResponseId IN SAML RESPONSE. VALIDATION MUST FAIL NOW. // Change InResponseId String responseStringChangedId = responseString.replaceAll("InResponseTo=\"" + inResponseIdFromSession + "\"", "InResponseTo=\"ID_101dcb5e-f432-4f45-87cb-47daff92edef\""); HandlerContext handlerContextChangedId = getHandlerRequestAndResponse(httpContext, issuerInfo, responseStringChangedId); // Set Id to session again as it was removed in previous processing servletRequest.getSession().setAttribute(GeneralConstants.AUTH_REQUEST_ID, inResponseIdFromSession); // Handle response with changed Id. This time it should fail try { authenticationHandler.handleStatusResponseType(handlerContextChangedId.request, handlerContextChangedId.response); verificationHandler.handleStatusResponseType(handlerContextChangedId.request, handlerContextChangedId.response); fail("Verification of InResponseTo should fail."); } catch (ProcessingException pe) { assertEquals(ErrorCodes.AUTHN_REQUEST_ID_VERIFICATION_FAILED, pe.getMessage()); } // 6) REMOVE InResponseId FROM SAML RESPONSE. VALIDATION MUST FAIL NOW. // Remove inResponseId String responseStringRemovedId = responseString.replaceAll("InResponseTo=\"" + inResponseIdFromSession + "\"", ""); HandlerContext handlerContextRemovedId = getHandlerRequestAndResponse(httpContext, issuerInfo, responseStringRemovedId); // Set Id to session again as it was removed in previous processing servletRequest.getSession().setAttribute(GeneralConstants.AUTH_REQUEST_ID, inResponseIdFromSession); // Now handle again response from IDP. This time it should also fail as InResponseTo is null try { authenticationHandler.handleStatusResponseType(handlerContextRemovedId.request, handlerContextRemovedId.response); verificationHandler.handleStatusResponseType(handlerContextRemovedId.request, handlerContextRemovedId.response); fail("Verification of InResponseTo should fail."); } catch (ProcessingException pe) { assertEquals(ErrorCodes.AUTHN_REQUEST_ID_VERIFICATION_FAILED, pe.getMessage()); } } /** * Sending SAML Request to IDP and receiving SAML response. * * @param authnRequest Generated SAML Request object * @param samlReqDoc Document for generated SAML Request object * @param httpContext httpContext * @param handlerConfig handlerConfig * * @return SAML2HandlerResponse after receiving response from IDP * * @throws Exception */ private SAML2HandlerResponse sendRequestToIdp(AuthnRequestType authnRequest, Document samlReqDoc, HTTPContext httpContext, SAML2HandlerConfig handlerConfig) throws Exception { // Generate handler request and handler response for IDP IssuerInfoHolder issuerInfo = new IssuerInfoHolder("http://localhost:8080/idp/"); SAMLDocumentHolder docHolder = new SAMLDocumentHolder(authnRequest, samlReqDoc); SAML2HandlerRequest idpHandlerRequest = new DefaultSAML2HandlerRequest(httpContext, issuerInfo.getIssuer(), docHolder, SAML2Handler.HANDLER_TYPE.IDP); idpHandlerRequest.addOption(GeneralConstants.ASSERTIONS_VALIDITY, 10000l); SAML2HandlerResponse idpHandlerResponse = new DefaultSAML2HandlerResponse(); // Create chainConfig for IDP Map<String, Object> chainOptionsIdp = new HashMap<String, Object>(); IDPType idpType = new IDPType(); chainOptionsIdp.put(GeneralConstants.CONFIGURATION, idpType); chainOptionsIdp.put(GeneralConstants.ROLE_VALIDATOR_IGNORE, "true"); SAML2HandlerChainConfig chainConfigIdp = new DefaultSAML2HandlerChainConfig(chainOptionsIdp); // Create and init handlers for IDP SAML2AuthenticationHandler authenticationHandlerIdp = new SAML2AuthenticationHandler(); SAML2InResponseToVerificationHandler verificationHandlerIdp = new SAML2InResponseToVerificationHandler(); authenticationHandlerIdp.initChainConfig(chainConfigIdp); authenticationHandlerIdp.initHandlerConfig(handlerConfig); verificationHandlerIdp.initChainConfig(chainConfigIdp); verificationHandlerIdp.initHandlerConfig(handlerConfig); HttpSession session = BaseSAML2Handler.getHttpSession(idpHandlerRequest); session.setAttribute(GeneralConstants.PRINCIPAL_ID, new Principal() { public String getName() { return "testPrincipal"; } }); // Init Picketlink Core STS PicketLinkCoreSTS sts = PicketLinkCoreSTS.instance(); sts.installDefaultConfiguration(); // Init identityServer IdentityServer identityServer = new IdentityServer(); httpContext.getServletContext().setAttribute(GeneralConstants.IDENTITY_SERVER, identityServer); // Handle request by IDP authenticationHandlerIdp.handleRequestType(idpHandlerRequest, idpHandlerResponse); verificationHandlerIdp.handleRequestType(idpHandlerRequest, idpHandlerResponse); return idpHandlerResponse; } private ResponseType getResponseTypeFromString(String responseString) throws Exception { InputStream is = new ByteArrayInputStream(responseString.getBytes()); SAML2Response saml2Response = new SAML2Response(); return saml2Response.getResponseType(is); } private HandlerContext getHandlerRequestAndResponse(HTTPContext httpContext, IssuerInfoHolder issuerInfo, String responseString) throws Exception { ResponseType responseType = getResponseTypeFromString(responseString); SAML2Response saml2Response = new SAML2Response(); Document doc = saml2Response.convert(responseType); SAMLDocumentHolder docHolder = new SAMLDocumentHolder(responseType, doc); SAML2HandlerRequest request = new DefaultSAML2HandlerRequest(httpContext, issuerInfo.getIssuer(), docHolder, SAML2Handler.HANDLER_TYPE.SP); SAML2HandlerResponse response = new DefaultSAML2HandlerResponse(); return new HandlerContext(request, response); } private class HandlerContext { private final SAML2HandlerRequest request; private final SAML2HandlerResponse response; private HandlerContext(SAML2HandlerRequest request, SAML2HandlerResponse response) { this.request = request; this.response = response; } } private MockServletContext createServletContext() { MockServletContext mockServletContext = new MockServletContext(); new SessionManager(mockServletContext, new SessionManager.InitializationCallback() { @Override public void registerSessionListener(Class<? extends HttpSessionListener> listener) { } }); return mockServletContext; } }