/*
* 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.bindings.workflow;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.deploy.LoginConfig;
import org.apache.catalina.valves.ValveBase;
import org.jboss.security.SimplePrincipal;
import org.junit.Test;
import org.picketlink.common.constants.GeneralConstants;
import org.picketlink.common.util.StringUtil;
import org.picketlink.identity.federation.bindings.tomcat.sp.AbstractAccountChooserValve;
import org.picketlink.identity.federation.bindings.tomcat.sp.AccountChooserValve;
import org.picketlink.identity.federation.bindings.tomcat.sp.SPPostFormAuthenticator;
import org.picketlink.identity.federation.web.core.IdentityServer;
import org.picketlink.test.identity.federation.bindings.mock.MockCatalinaContext;
import org.picketlink.test.identity.federation.bindings.mock.MockCatalinaContextClassLoader;
import org.picketlink.test.identity.federation.bindings.mock.MockCatalinaRealm;
import org.picketlink.test.identity.federation.bindings.mock.MockCatalinaRequest;
import org.picketlink.test.identity.federation.bindings.mock.MockCatalinaResponse;
import org.picketlink.test.identity.federation.bindings.mock.MockCatalinaSession;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.URL;
import java.security.Principal;
import java.util.HashMap;
import java.util.Map;
import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertEquals;
/**
* PLINK-344: Account Chooser at the SP
* @author Anil Saldhana
* @since January 21, 2014
*/
public class AccountChooserWorkflowUnitTestCase{
private String profile = "saml2/post";
private ClassLoader tcl = Thread.currentThread().getContextClassLoader();
private String domainName = "MyDomain";
@Test
public void testAccountChoosingforDomainA() throws Exception{
AccountChooserValve accountChooserValve = new AccountChooserValve();
accountChooserValve.setDomainName(domainName);
accountChooserValve.setAccountIDPMapProvider(MyAccountMapProvider.class.getName());
MockCatalinaSession mockCatalinaSession = new MockCatalinaSession();
MockCatalinaContext servletContext = new MockCatalinaContext();
// First we go to the employee application
MockCatalinaContextClassLoader mclSPEmp = setupTCL(profile + "/sp/employee");
Thread.currentThread().setContextClassLoader(mclSPEmp);
SPPostFormAuthenticator spEmpl = new SPPostFormAuthenticator();
MockCatalinaContext context = new MockCatalinaContext();
context.setPath("/employee");
spEmpl.setContainer(context);
spEmpl.testStart();
LoginConfig loginConfig = new LoginConfig();
context.setLoginConfig(loginConfig);
MockCatalinaRealm realm = new MockCatalinaRealm("anil", "test", new Principal() {
public String getName() {
return "anil";
}
});
context.setRealm(realm);
spEmpl.setNext(new NoopValve());
accountChooserValve.setContainer(context);
accountChooserValve.setNext(spEmpl);
accountChooserValve.start();
MockCatalinaRequest catalinaRequest = new MockCatalinaRequest();
catalinaRequest.setSession(mockCatalinaSession);
MockCatalinaResponse catalinaResponse = new MockCatalinaResponse();
catalinaRequest.setResponse(catalinaResponse);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
catalinaResponse.setOutputStream(baos);
accountChooserValve.invoke(catalinaRequest,catalinaResponse);
//Ensure that the account chooser page was set as the forward path
String forwardPath = catalinaRequest.getForwardPath();
if(StringUtil.isNullOrEmpty(forwardPath)){
forwardPath = (String) catalinaRequest.getAttribute("FORWARD_PATH");
}
assertTrue(forwardPath.contains("account"));
//Assume user chose DomainA
catalinaRequest.setParameter(AbstractAccountChooserValve.ACCOUNT_PARAMETER, "DomainA");
accountChooserValve.invoke(catalinaRequest,catalinaResponse);
//Ensure that the NoopValve was called and we need authentication of the user to mimic AuthenticatorBase.invoke
assertEquals(catalinaRequest.getAttribute("NEED_AUTH"), "true");
//Now let us go ahead and authenticate the user
spEmpl.authenticate(catalinaRequest, catalinaResponse, loginConfig);
String spResponse = new String(baos.toByteArray());
//Ensure that the SP is trying to redirect to idp1 which is the equivalent for DomainA
assertTrue(spResponse.contains("http://idp1"));
//Let us assume that the IDP interaction succeeds and we have a principal now
catalinaRequest.setUserPrincipal(new SimplePrincipal("anil"));
//Also ensure catalinaRequest has a SAMLResponse parameter
//This is so that we don't think the user wants to change his IDP choice
catalinaRequest.setParameter(GeneralConstants.SAML_RESPONSE_KEY, "xyz");
//Let us invoke to get back a cookie
accountChooserValve.invoke(catalinaRequest,catalinaResponse);
//Also ensure that we do have a local cookie set by the SP
assertEquals("DomainA", cookieValue(catalinaResponse));
}
@Test
public void testAccountChoosingforDomainB() throws Exception{
AccountChooserValve accountChooserValve = new AccountChooserValve();
accountChooserValve.setDomainName(domainName);
accountChooserValve.setAccountIDPMapProvider(MyAccountMapProvider.class.getName());
MockCatalinaSession mockCatalinaSession = new MockCatalinaSession();
MockCatalinaContext servletContext = new MockCatalinaContext();
// First we go to the employee application
MockCatalinaContextClassLoader mclSPEmp = setupTCL(profile + "/sp/employee");
Thread.currentThread().setContextClassLoader(mclSPEmp);
SPPostFormAuthenticator spEmpl = new SPPostFormAuthenticator();
MockCatalinaContext context = new MockCatalinaContext();
context.setPath("/employee");
spEmpl.setContainer(context);
spEmpl.testStart();
LoginConfig loginConfig = new LoginConfig();
context.setLoginConfig(loginConfig);
MockCatalinaRealm realm = new MockCatalinaRealm("anil", "test", new Principal() {
public String getName() {
return "anil";
}
});
context.setRealm(realm);
spEmpl.setNext(new NoopValve());
accountChooserValve.setContainer(context);
accountChooserValve.setNext(spEmpl);
accountChooserValve.start();
MockCatalinaRequest catalinaRequest = new MockCatalinaRequest();
catalinaRequest.setSession(mockCatalinaSession);
MockCatalinaResponse catalinaResponse = new MockCatalinaResponse();
catalinaRequest.setResponse(catalinaResponse);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
catalinaResponse.setOutputStream(baos);
accountChooserValve.invoke(catalinaRequest,catalinaResponse);
//Ensure that the account chooser page was set as the forward path
String forwardPath = catalinaRequest.getForwardPath();
if(StringUtil.isNullOrEmpty(forwardPath)){
forwardPath = (String) catalinaRequest.getAttribute("FORWARD_PATH");
}
assertTrue(forwardPath.contains("account"));
//Assume user chose DomainB
catalinaRequest.setParameter(AbstractAccountChooserValve.ACCOUNT_PARAMETER, "DomainB");
accountChooserValve.invoke(catalinaRequest,catalinaResponse);
//Ensure that the NoopValve was called and we need authentication of the user to mimic AuthenticatorBase.invoke
assertEquals(catalinaRequest.getAttribute("NEED_AUTH"), "true");
//Now let us go ahead and authenticate the user
spEmpl.authenticate(catalinaRequest, catalinaResponse, loginConfig);
String spResponse = new String(baos.toByteArray());
//Ensure that the SP is trying to redirect to idp2 which is the equivalent for DomainB
assertTrue(spResponse.contains("http://idp2"));
//Let us assume that the IDP interaction succeeds and we have a principal now
catalinaRequest.setUserPrincipal(new SimplePrincipal("anil"));
//Also ensure catalinaRequest has a SAMLResponse parameter
//This is so that we don't think the user wants to change his IDP choice
catalinaRequest.setParameter(GeneralConstants.SAML_RESPONSE_KEY, "xyz");
//Let us invoke to get back a cookie
accountChooserValve.invoke(catalinaRequest,catalinaResponse);
//Also ensure that we do have a local cookie set by the SP
assertEquals("DomainB",cookieValue(catalinaResponse));
}
private MockCatalinaContextClassLoader setupTCL(String resource) {
URL[] urls = new URL[] { tcl.getResource(resource) };
MockCatalinaContextClassLoader mcl = new MockCatalinaContextClassLoader(urls);
mcl.setDelegate(tcl);
mcl.setProfile(resource);
return mcl;
}
// Get the Identity server
private IdentityServer getIdentityServer(HttpSession session) {
IdentityServer server = new IdentityServer();
server.sessionCreated(new HttpSessionEvent(session));
return server;
}
protected String cookieValue(Response response){
Cookie[] cookies = response.getCookies();
if(cookies != null){
for (Cookie cookie : cookies) {
String cookieName = cookie.getName();
if (AbstractAccountChooserValve.ACCOUNT_CHOOSER_COOKIE_NAME.equals(cookieName)) {
// Found cookie
return cookie.getValue();
}
}
}
return null;
}
private class NoopValve extends ValveBase{
@Override
public void invoke(Request request, Response response) throws IOException, ServletException {
MockCatalinaRequest mockCatalinaRequest = (MockCatalinaRequest) request;
mockCatalinaRequest.setAttribute("NEED_AUTH", "true");
}
}
public static class MyAccountMapProvider implements AbstractAccountChooserValve.AccountIDPMapProvider{
@Override
public void setServletContext(ServletContext servletContext) {
}
@Override
public void setClassLoader(ClassLoader classLoader) {
}
@Override
public Map<String, String> getIDPMap() throws IOException {
Map<String,String> map = new HashMap<String, String>();
map.put("DomainA","http://idp1");
map.put("DomainB","http://idp2");
return map;
}
}
}