/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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.keycloak.testsuite.saml;
import org.jboss.resteasy.util.Base64;
import org.junit.ClassRule;
import org.junit.Test;
import org.keycloak.dom.saml.v2.protocol.ResponseType;
import org.keycloak.dom.saml.v2.protocol.StatusCodeType;
import org.keycloak.dom.saml.v2.protocol.StatusResponseType;
import org.keycloak.saml.common.constants.JBossSAMLConstants;
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
import org.keycloak.saml.common.util.DocumentUtil;
import org.keycloak.saml.processing.core.parsers.saml.SAMLParser;
import org.keycloak.testsuite.samlfilter.SamlAdapterTest;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.Invocation.Builder;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.NewCookie;
import javax.ws.rs.core.Response;
import javax.xml.namespace.QName;
import javax.xml.soap.MessageFactory;
import javax.xml.soap.SOAPHeader;
import javax.xml.soap.SOAPHeaderElement;
import javax.xml.soap.SOAPMessage;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.Iterator;
import java.util.Map;
import static javax.ws.rs.core.Response.Status.OK;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class SamlEcpProfileTest {
protected String APP_SERVER_BASE_URL = "http://localhost:8081";
@ClassRule
public static org.keycloak.testsuite.samlfilter.SamlKeycloakRule keycloakRule = new org.keycloak.testsuite.samlfilter.SamlKeycloakRule() {
@Override
public void initWars() {
ClassLoader classLoader = SamlAdapterTest.class.getClassLoader();
initializeSamlSecuredWar("/keycloak-saml/ecp/ecp-sp", "/ecp-sp", "ecp-sp.war", classLoader);
}
@Override
public String getRealmJson() {
return "/keycloak-saml/ecp/testsamlecp.json";
}
};
@Test
public void testSuccessfulEcpFlow() throws Exception {
Response authnRequestResponse = ClientBuilder.newClient().target(APP_SERVER_BASE_URL + "/ecp-sp/").request()
.header("Accept", "text/html; application/vnd.paos+xml")
.header("PAOS", "ver='urn:liberty:paos:2003-08' ;'urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp'")
.get();
SOAPMessage authnRequestMessage = MessageFactory.newInstance().createMessage(null, new ByteArrayInputStream(authnRequestResponse.readEntity(byte[].class)));
printDocument(authnRequestMessage.getSOAPPart().getContent(), System.out);
Iterator<SOAPHeaderElement> it = authnRequestMessage.getSOAPHeader().<SOAPHeaderElement>getChildElements(new QName("urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp", "Request"));
SOAPHeaderElement ecpRequestHeader = it.next();
NodeList idpList = ecpRequestHeader.getElementsByTagNameNS("urn:oasis:names:tc:SAML:2.0:protocol", "IDPList");
assertEquals("No IDPList returned from Service Provider", 1, idpList.getLength());
NodeList idpEntries = idpList.item(0).getChildNodes();
assertEquals("No IDPEntry returned from Service Provider", 1, idpEntries.getLength());
String singleSignOnService = null;
for (int i = 0; i < idpEntries.getLength(); i++) {
Node item = idpEntries.item(i);
NamedNodeMap attributes = item.getAttributes();
Node location = attributes.getNamedItem("Loc");
singleSignOnService = location.getNodeValue();
}
assertNotNull("Could not obtain SSO Service URL", singleSignOnService);
Document authenticationRequest = authnRequestMessage.getSOAPBody().getFirstChild().getOwnerDocument();
String username = "pedroigor";
String password = "password";
String pair = username + ":" + password;
String authHeader = "Basic " + new String(Base64.encodeBytes(pair.getBytes()));
Response authenticationResponse = ClientBuilder.newClient().target(singleSignOnService).request()
.header(HttpHeaders.AUTHORIZATION, authHeader)
.post(Entity.entity(DocumentUtil.asString(authenticationRequest), "text/xml"));
assertEquals(OK.getStatusCode(), authenticationResponse.getStatus());
SOAPMessage responseMessage = MessageFactory.newInstance().createMessage(null, new ByteArrayInputStream(authenticationResponse.readEntity(byte[].class)));
printDocument(responseMessage.getSOAPPart().getContent(), System.out);
SOAPHeader responseMessageHeaders = responseMessage.getSOAPHeader();
NodeList ecpResponse = responseMessageHeaders.getElementsByTagNameNS(JBossSAMLURIConstants.ECP_PROFILE.get(), JBossSAMLConstants.RESPONSE.get());
assertEquals("No ECP Response", 1, ecpResponse.getLength());
Node samlResponse = responseMessage.getSOAPBody().getFirstChild();
assertNotNull(samlResponse);
ResponseType responseType = (ResponseType) new SAMLParser().parse(samlResponse);
StatusCodeType statusCode = responseType.getStatus().getStatusCode();
assertEquals(statusCode.getValue().toString(), JBossSAMLURIConstants.STATUS_SUCCESS.get());
assertEquals("http://localhost:8081/ecp-sp/", responseType.getDestination());
assertNotNull(responseType.getSignature());
assertEquals(1, responseType.getAssertions().size());
SOAPMessage samlResponseRequest = MessageFactory.newInstance().createMessage();
samlResponseRequest.getSOAPBody().addDocument(responseMessage.getSOAPBody().extractContentAsDocument());
ByteArrayOutputStream os = new ByteArrayOutputStream();
samlResponseRequest.writeTo(os);
Response serviceProviderFinalResponse = ClientBuilder.newClient().target(responseType.getDestination()).request()
.post(Entity.entity(os.toByteArray(), "application/vnd.paos+xml"));
Map<String, NewCookie> cookies = serviceProviderFinalResponse.getCookies();
Builder resourceRequest = ClientBuilder.newClient().target(responseType.getDestination() + "/index.html").request();
for (NewCookie cookie : cookies.values()) {
resourceRequest.cookie(cookie);
}
Response resourceResponse = resourceRequest.get();
assertTrue(resourceResponse.readEntity(String.class).contains("pedroigor"));
}
@Test
public void testInvalidCredentials() throws Exception {
Response authnRequestResponse = ClientBuilder.newClient().target(APP_SERVER_BASE_URL + "/ecp-sp/").request()
.header("Accept", "text/html; application/vnd.paos+xml")
.header("PAOS", "ver='urn:liberty:paos:2003-08' ;'urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp'")
.get();
SOAPMessage authnRequestMessage = MessageFactory.newInstance().createMessage(null, new ByteArrayInputStream(authnRequestResponse.readEntity(byte[].class)));
Iterator<SOAPHeaderElement> it = authnRequestMessage.getSOAPHeader().<SOAPHeaderElement>getChildElements(new QName("urn:liberty:paos:2003-08", "Request"));
it.next();
it = authnRequestMessage.getSOAPHeader().<SOAPHeaderElement>getChildElements(new QName("urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp", "Request"));
SOAPHeaderElement ecpRequestHeader = it.next();
NodeList idpList = ecpRequestHeader.getElementsByTagNameNS("urn:oasis:names:tc:SAML:2.0:protocol", "IDPList");
assertEquals("No IDPList returned from Service Provider", 1, idpList.getLength());
NodeList idpEntries = idpList.item(0).getChildNodes();
assertEquals("No IDPEntry returned from Service Provider", 1, idpEntries.getLength());
String singleSignOnService = null;
for (int i = 0; i < idpEntries.getLength(); i++) {
Node item = idpEntries.item(i);
NamedNodeMap attributes = item.getAttributes();
Node location = attributes.getNamedItem("Loc");
singleSignOnService = location.getNodeValue();
}
assertNotNull("Could not obtain SSO Service URL", singleSignOnService);
Document authenticationRequest = authnRequestMessage.getSOAPBody().getFirstChild().getOwnerDocument();
String username = "pedroigor";
String password = "baspassword";
String pair = username + ":" + password;
String authHeader = "Basic " + new String(Base64.encodeBytes(pair.getBytes()));
Response authenticationResponse = ClientBuilder.newClient().target(singleSignOnService).request()
.header(HttpHeaders.AUTHORIZATION, authHeader)
.post(Entity.entity(DocumentUtil.asString(authenticationRequest), "application/soap+xml"));
assertEquals(OK.getStatusCode(), authenticationResponse.getStatus());
SOAPMessage responseMessage = MessageFactory.newInstance().createMessage(null, new ByteArrayInputStream(authenticationResponse.readEntity(byte[].class)));
Node samlResponse = responseMessage.getSOAPBody().getFirstChild();
assertNotNull(samlResponse);
StatusResponseType responseType = (StatusResponseType) new SAMLParser().parse(samlResponse);
StatusCodeType statusCode = responseType.getStatus().getStatusCode();
assertNotEquals(statusCode.getStatusCode().getValue().toString(), JBossSAMLURIConstants.STATUS_SUCCESS.get());
}
public static void printDocument(Source doc, OutputStream out) throws IOException, TransformerException {
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
transformer.setOutputProperty(OutputKeys.METHOD, "xml");
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
transformer.transform(doc,
new StreamResult(new OutputStreamWriter(out, "UTF-8")));
}
}