blob: 2289d1c44e28d15b9b77b94cab97140b8c5fde9b [file] [log] [blame]
// Copyright 2013 Google Inc. All Rights Reserved.
//
// 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 com.google.enterprise.adaptor;
import static org.junit.Assert.*;
import com.google.enterprise.adaptor.SamlServiceProvider.AuthnState;
import com.google.enterprise.secmgr.http.HttpClientInterface;
import com.google.enterprise.secmgr.modules.SamlClient;
import com.google.enterprise.secmgr.saml.OpenSamlUtil;
import com.sun.net.httpserver.HttpExchange;
import org.junit.*;
import org.junit.rules.ExpectedException;
import org.opensaml.common.binding.SAMLMessageContext;
import org.opensaml.common.xml.SAMLConstants;
import org.opensaml.saml2.binding.decoding.HTTPRedirectDeflateDecoder;
import org.opensaml.saml2.binding.encoding.HTTPSOAP11Encoder;
import org.opensaml.saml2.core.AuthnRequest;
import org.opensaml.saml2.core.NameID;
import java.io.IOException;
import java.net.URI;
import java.nio.charset.Charset;
import java.util.*;
/**
* Test cases for {@link SamlServiceProvider}.
*/
public class SamlServiceProviderTest {
@Rule
public ExpectedException thrown = ExpectedException.none();
private Charset charset = Charset.forName("UTF-8");
private SessionManager<HttpExchange> sessionManager
= new SessionManager<HttpExchange>(new MockTimeProvider(),
new SessionManager.HttpExchangeClientStore(), 1000, 1000);
private SamlMetadata metadata = new SamlMetadata("localhost", 80,
"thegsa", "http://google.com/enterprise/gsa/security-manager",
"http://google.com/enterprise/gsa/adaptor");
private HttpClientAdapter httpClient = new HttpClientAdapter();
private SamlServiceProvider serviceProvider = new SamlServiceProvider(
sessionManager, metadata, null, httpClient);
private MockHttpExchange ex = new MockHttpExchange("GET", "/",
new MockHttpContext("/"));
private MockHttpExchange exArtifact
= new MockHttpExchange("GET", "/?SAMLart=1234someid5678",
new MockHttpContext("/"));
private MockHttpExchange initialEx
= new MockHttpExchange("GET", "/doc/someid",
new MockHttpContext("/doc/"));
private static final String GOLDEN_ARTIFACT_RESOLVE_REQUEST
= "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<soap11:Envelope "
+ "xmlns:soap11=\"http://schemas.xmlsoap.org/soap/envelope/\">"
+ "<soap11:Body>"
+ "<saml2p:ArtifactResolve "
+ "ID=\"someid\" "
+ "IssueInstant=\"sometime\" "
+ "Version=\"2.0\" "
+ "xmlns:saml2p=\"urn:oasis:names:tc:SAML:2.0:protocol\">"
+ "<saml2:Issuer "
+ "xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\">"
+ "http://google.com/enterprise/gsa/adaptor"
+ "</saml2:Issuer>"
+ "<saml2p:Artifact>"
+ "1234someid5678"
+ "</saml2p:Artifact>"
+ "</saml2p:ArtifactResolve>"
+ "</soap11:Body>"
+ "</soap11:Envelope>";
@BeforeClass
public static void initSaml() {
GsaCommunicationHandler.bootstrapOpenSaml();
}
@Test
public void testHandleAuthenticationNewSession() throws Exception {
String goldenResponse
= "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<soap11:Envelope "
+ "xmlns:soap11=\"http://schemas.xmlsoap.org/soap/envelope/\">"
+ "<soap11:Body>"
+ "<saml2p:AuthnRequest "
+ "Destination=\"https://thegsa/security-manager/samlauthn\" "
+ "ID=\"someid\" "
+ "IsPassive=\"false\" "
+ "IssueInstant=\"sometime\" "
+ "ProviderName=\"GSA Adaptor\" "
+ "Version=\"2.0\" "
+ "xmlns:saml2p=\"urn:oasis:names:tc:SAML:2.0:protocol\">"
+ "<saml2:Issuer "
+ "xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\">"
+ "http://google.com/enterprise/gsa/adaptor"
+ "</saml2:Issuer>"
+ "</saml2p:AuthnRequest>"
+ "</soap11:Body>"
+ "</soap11:Envelope>";
serviceProvider.handleAuthentication(ex);
//
// We have run the method, now we need to look over the results...
//
assertEquals(307, ex.getResponseCode());
URI uri = new URI(ex.getResponseHeaders().getFirst("Location"));
assertEquals("https", uri.getScheme());
assertEquals("thegsa", uri.getHost());
assertEquals("/security-manager/samlauthn", uri.getPath());
assertTrue(!isAuthned(ex));
// Act like we are the receiving end of the communication.
MockHttpExchange remoteEx = new MockHttpExchange("GET", uri.toString(),
new MockHttpContext("/security-manager/samlauthn"));
SAMLMessageContext<AuthnRequest, AuthnRequest, NameID> context
= OpenSamlUtil.makeSamlMessageContext();
OpenSamlUtil.initializeLocalEntity(context, metadata.getPeerEntity(),
metadata.getPeerEntity().getIDPSSODescriptor(SAMLConstants.SAML20P_NS));
context.setInboundMessageTransport(new EnhancedInTransport(remoteEx));
context.setOutboundMessageTransport(
new HttpExchangeOutTransportAdapter(remoteEx));
HTTPRedirectDeflateDecoder decoder = new RedirectDecoder();
decoder.decode(context);
context.setOutboundSAMLMessage(context.getInboundSAMLMessage());
HTTPSOAP11Encoder encoder = new HTTPSOAP11Encoder();
encoder.encode(context);
String response = new String(remoteEx.getResponseBytes(), "UTF-8");
response = massageMessage(response);
assertEquals(goldenResponse, response);
}
@Test
public void testHandleAuthenticationAlreadyAuthned() throws Exception {
// Create a new authenticated session for this HttpExchange.
Session session = sessionManager.getSession(ex, true);
AuthnState authn = new AuthnState();
session.setAttribute(SamlServiceProvider.SESSION_STATE_ATTR_NAME, authn);
AuthnIdentity identity = new AuthnIdentityImpl
.Builder(new UserPrincipal("test")).build();
authn.authenticated(identity, Long.MAX_VALUE);
serviceProvider.handleAuthentication(ex);
// Still should cause them to go through authn
assertEquals(307, ex.getResponseCode());
assertTrue(!isAuthned(ex));
}
@Test
public void testHandleAuthenticationHead() throws Exception {
MockHttpExchange ex = new MockHttpExchange("HEAD", "/",
new MockHttpContext("/"));
serviceProvider.handleAuthentication(ex);
assertEquals(307, ex.getResponseCode());
assertTrue(!isAuthned(ex));
}
@Test
public void testHandleAuthenticationBadMethod() throws Exception {
MockHttpExchange ex = new MockHttpExchange("POST", "/",
new MockHttpContext("/"));
serviceProvider.handleAuthentication(ex);
assertEquals(405, ex.getResponseCode());
}
@Test
public void testAssertionConsumerNormal() throws Exception {
SamlHttpClient httpClient = new SamlHttpClient() {
@Override
protected void handleExchange(ClientExchange ex) {
String body = new String(ex.getRequestBody(), charset);
body = massageMessage(body);
assertEquals(GOLDEN_ARTIFACT_RESOLVE_REQUEST, body);
// Generate valid response.
String issuer = metadata.getPeerEntity().getEntityID();
String recipient = metadata.getLocalEntity()
.getSPSSODescriptor(SAMLConstants.SAML20P_NS)
.getAssertionConsumerServices().get(0).getLocation();
String audience = metadata.getLocalEntity().getEntityID();
String response
= "<SOAP-ENV:Envelope "
+ "xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\">"
+ "<SOAP-ENV:Body>"
+ "<samlp:ArtifactResponse "
+ "xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\" "
+ "xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\" "
+ "ID=\"someid1\" Version=\"2.0\" "
+ "InResponseTo=\"" + samlClient.getRequestId() + "\" "
+ "IssueInstant=\"2010-01-01T01:01:01Z\">"
+ "<Issuer>" + issuer + "</Issuer>"
+ "<samlp:Status>"
+ "<samlp:StatusCode "
+ "Value=\"urn:oasis:names:tc:SAML:2.0:status:Success\"/>"
+ "</samlp:Status>"
+ "<samlp:Response "
+ "ID=\"someid2\" "
+ "Version=\"2.0\" "
+ "IssueInstant=\"2010-01-01T01:01:01Z\">"
+ "<samlp:Status>"
+ "<samlp:StatusCode "
+ "Value=\"urn:oasis:names:tc:SAML:2.0:status:Success\"/>"
+ "</samlp:Status>"
+ "<Assertion "
+ "Version=\"2.0\" "
+ "ID=\"someid3\" "
+ "IssueInstant=\"2010-01-01T01:01:01Z\">"
+ "<Issuer>" + issuer + "</Issuer>"
+ "<Subject>"
+ "<NameID>CN=Polly Hedra</NameID>"
+ "<SubjectConfirmation "
+ "Method=\"urn:oasis:names:tc:SAML:2.0:cm:bearer\">"
+ "<SubjectConfirmationData "
+ "InResponseTo=\"" + samlClient.getRequestId() + "\" "
+ "Recipient=\"" + recipient + "\" "
+ "NotOnOrAfter=\"2030-01-01T01:01:01Z\"/>"
+ "</SubjectConfirmation>"
+ "</Subject>"
+ "<Conditions "
+ "NotBefore=\"2010-01-01T01:01:01Z\">"
+ "<AudienceRestriction>"
+ "<Audience>" + audience + "</Audience>"
+ "</AudienceRestriction>"
+ "</Conditions>"
+ "<AuthnStatement "
+ "AuthnInstant=\"2010-01-01T01:01:01Z\"/>"
+ "</Assertion>"
+ "</samlp:Response>"
+ "</samlp:ArtifactResponse>"
+ "</SOAP-ENV:Body>"
+ "</SOAP-ENV:Envelope>";
ex.setStatusCode(200);
ex.setResponseStream(response.getBytes(charset));
}
};
SamlClient samlClient = createSamlClient(httpClient);
httpClient.setSamlClient(samlClient);
issueRequest(exArtifact, initialEx, samlClient);
serviceProvider.getAssertionConsumer().handle(exArtifact);
assertEquals(303, exArtifact.getResponseCode());
assertTrue(isAuthned(exArtifact));
AuthnIdentity identity = serviceProvider.getUserIdentity(exArtifact);
assertEquals("CN=Polly Hedra", identity.getUser().getName());
assertNull(identity.getGroups());
assertNull(identity.getPassword());
}
@Test
public void testAssertionConsumerNormalWithExtension() throws Exception {
SamlHttpClient httpClient = new SamlHttpClient() {
@Override
protected void handleExchange(ClientExchange ex) {
String body = new String(ex.getRequestBody(), charset);
body = massageMessage(body);
assertEquals(GOLDEN_ARTIFACT_RESOLVE_REQUEST, body);
// Generate valid response.
String issuer = metadata.getPeerEntity().getEntityID();
String recipient = metadata.getLocalEntity()
.getSPSSODescriptor(SAMLConstants.SAML20P_NS)
.getAssertionConsumerServices().get(0).getLocation();
String audience = metadata.getLocalEntity().getEntityID();
String response
= "<SOAP-ENV:Envelope "
+ "xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\">"
+ "<SOAP-ENV:Body>"
+ "<samlp:ArtifactResponse "
+ "xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\" "
+ "xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\" "
+ "ID=\"someid1\" Version=\"2.0\" "
+ "InResponseTo=\"" + samlClient.getRequestId() + "\" "
+ "IssueInstant=\"2010-01-01T01:01:01Z\">"
+ "<Issuer>" + issuer + "</Issuer>"
+ "<samlp:Status>"
+ "<samlp:StatusCode "
+ "Value=\"urn:oasis:names:tc:SAML:2.0:status:Success\"/>"
+ "</samlp:Status>"
+ "<samlp:Response "
+ "ID=\"someid2\" "
+ "Version=\"2.0\" "
+ "IssueInstant=\"2010-01-01T01:01:01Z\">"
+ "<samlp:Status>"
+ "<samlp:StatusCode "
+ "Value=\"urn:oasis:names:tc:SAML:2.0:status:Success\"/>"
+ "</samlp:Status>"
+ "<Assertion "
+ "Version=\"2.0\" "
+ "ID=\"someid3\" "
+ "IssueInstant=\"2010-01-01T01:01:01Z\">"
+ "<Issuer>" + issuer + "</Issuer>"
+ "<Subject>"
+ "<NameID>CN=Polly Hedra</NameID>"
+ "<SubjectConfirmation "
+ "Method=\"urn:oasis:names:tc:SAML:2.0:cm:bearer\">"
+ "<SubjectConfirmationData "
+ "InResponseTo=\"" + samlClient.getRequestId() + "\" "
+ "Recipient=\"" + recipient + "\" "
+ "NotOnOrAfter=\"2030-01-01T01:01:01Z\"/>"
+ "</SubjectConfirmation>"
+ "</Subject>"
+ "<Conditions "
+ "NotBefore=\"2010-01-01T01:01:01Z\">"
+ "<AudienceRestriction>"
+ "<Audience>" + audience + "</Audience>"
+ "</AudienceRestriction>"
+ "</Conditions>"
+ "<AuthnStatement "
+ "AuthnInstant=\"2010-01-01T01:01:01Z\">"
+ "<AuthnContext>"
+ "<AuthnContextClassRef>"
+ "urn:oasis:names:tc:SAML:2.0:ac:classes:InternetProtocolPassword"
+ "</AuthnContextClassRef>"
+ "</AuthnContext>"
+ "</AuthnStatement>"
+ "<AttributeStatement>"
+ "<Attribute Name=\"SecurityManagerState\">"
+ "<AttributeValue>"
+ "{"
+ "\"version\": 1,"
+ "\"timeStamp\": 1330042321589,"
+ "\"sessionState\": {"
+ "\"instructions\": ["
+ "{"
+ "\"operation\": \"ADD_CREDENTIAL\","
+ "\"authority\": "
+ "\"http://google.com/enterprise/gsa/security-manager/Default\","
+ "\"operand\": {"
+ "\"name\": \"CN=Polly Hedra\","
+ "\"typeName\": \"AuthnPrincipal\""
+ "}"
+ "},"
+ "{"
+ "\"operation\": \"ADD_CREDENTIAL\","
+ "\"authority\": "
+ "\"http://google.com/enterprise/gsa/security-manager/Default\","
+ "\"operand\": {"
+ "\"password\": \"p0ck3t\","
+ "\"typeName\": \"CredPassword\""
+ "}"
+ "},"
+ "{"
+ "\"operation\": \"ADD_VERIFICATION\","
+ "\"authority\": "
+ "\"http://google.com/enterprise/gsa/security-manager/adaptor\","
+ "\"operand\": {"
+ "\"status\": \"VERIFIED\","
+ "\"expirationTime\": 1330043521581,"
+ "\"credentials\": ["
+ "{"
+ "\"name\": \"CN=Polly Hedra\","
+ "\"typeName\": \"AuthnPrincipal\""
+ "},"
+ "{"
+ "\"password\": \"p0ck3t\","
+ "\"typeName\": \"CredPassword\""
+ "}"
+ "]"
+ "}"
+ "}"
+ "]"
+ "},"
+ "\"pviCredentials\": {"
+ "\"username\": \"CN=Polly Hedra\","
+ "\"password\": \"p0ck3t\","
+ "\"groups\": [\"group1\", \"pollysGroup\"]"
+ "},"
+ "\"basicCredentials\": {"
+ "\"username\": \"CN=Polly Hedra\","
+ "\"password\": \"p0ck3t\","
+ "\"groups\": [\"group1\", \"pollysGroup\"]"
+ "},"
+ "\"verifiedCredentials\": ["
+ "{"
+ "\"username\": \"CN=Polly Hedra\","
+ "\"password\": \"p0ck3t\","
+ "\"groups\": [\"group1\", \"pollysGroup\"]"
+ "}"
+ "],"
+ "\"connectorCredentials\": [],"
+ "\"cookies\": []"
+ "}"
+ "</AttributeValue>"
+ "</Attribute>"
+ "</AttributeStatement>"
+ "</Assertion>"
+ "</samlp:Response>"
+ "</samlp:ArtifactResponse>"
+ "</SOAP-ENV:Body>"
+ "</SOAP-ENV:Envelope>";
ex.setStatusCode(200);
ex.setResponseStream(response.getBytes(charset));
}
};
SamlClient samlClient = createSamlClient(httpClient);
httpClient.setSamlClient(samlClient);
issueRequest(exArtifact, initialEx, samlClient);
serviceProvider.getAssertionConsumer().handle(exArtifact);
assertEquals(303, exArtifact.getResponseCode());
assertTrue(isAuthned(exArtifact));
AuthnIdentity identity = serviceProvider.getUserIdentity(exArtifact);
assertEquals("CN=Polly Hedra", identity.getUser().getName());
// Make sure that the information from the extensions was parsed out and
// made available for later use.
Set<String> groups = new HashSet<String>();
groups.add("group1");
groups.add("pollysGroup");
assertEquals(GroupPrincipal.makeSet(groups), identity.getGroups());
assertEquals("p0ck3t", identity.getPassword());
}
@Test
public void testAssertionConsumerAuthnFailure() throws Exception {
SamlHttpClient httpClient = new SamlHttpClient() {
@Override
protected void handleExchange(ClientExchange ex) {
String body = new String(ex.getRequestBody(), charset);
body = massageMessage(body);
assertEquals(GOLDEN_ARTIFACT_RESOLVE_REQUEST, body);
// Generate valid response for authn failed.
String issuer = metadata.getPeerEntity().getEntityID();
String response
= "<SOAP-ENV:Envelope "
+ "xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\">"
+ "<SOAP-ENV:Body>"
+ "<samlp:ArtifactResponse "
+ "xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\" "
+ "xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\" "
+ "ID=\"someid1\" Version=\"2.0\" "
+ "InResponseTo=\"" + samlClient.getRequestId() + "\" "
+ "IssueInstant=\"2010-01-01T01:01:01Z\">"
+ "<Issuer>" + issuer + "</Issuer>"
+ "<samlp:Status>"
+ "<samlp:StatusCode "
+ "Value=\"urn:oasis:names:tc:SAML:2.0:status:Success\"/>"
+ "</samlp:Status>"
+ "<samlp:Response "
+ "ID=\"someid2\" "
+ "Version=\"2.0\" "
+ "IssueInstant=\"2010-01-01T01:01:01Z\">"
+ "<samlp:Status>"
+ "<samlp:StatusCode "
+ "Value=\"urn:oasis:names:tc:SAML:2.0:status:AuthnFailed\"/>"
+ "</samlp:Status>"
+ "</samlp:Response>"
+ "</samlp:ArtifactResponse>"
+ "</SOAP-ENV:Body>"
+ "</SOAP-ENV:Envelope>";
ex.setStatusCode(200);
ex.setResponseStream(response.getBytes(charset));
}
};
SamlClient samlClient = createSamlClient(httpClient);
httpClient.setSamlClient(samlClient);
issueRequest(exArtifact, initialEx, samlClient);
serviceProvider.getAssertionConsumer().handle(exArtifact);
assertEquals(403, exArtifact.getResponseCode());
assertTrue(!isAuthned(exArtifact));
}
@Test
public void testAssertionConsumerNoAuthnResponse() throws Exception {
SamlHttpClient httpClient = new SamlHttpClient() {
@Override
protected void handleExchange(ClientExchange ex) {
String body = new String(ex.getRequestBody(), charset);
body = massageMessage(body);
assertEquals(GOLDEN_ARTIFACT_RESOLVE_REQUEST, body);
// Generate valid response for artifact requesting failure.
String issuer = metadata.getPeerEntity().getEntityID();
String response
= "<SOAP-ENV:Envelope "
+ "xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\">"
+ "<SOAP-ENV:Body>"
+ "<samlp:ArtifactResponse "
+ "xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\" "
+ "xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\" "
+ "ID=\"someid1\" Version=\"2.0\" "
+ "InResponseTo=\"" + samlClient.getRequestId() + "\" "
+ "IssueInstant=\"2010-01-01T01:01:01Z\">"
+ "<Issuer>" + issuer + "</Issuer>"
+ "<samlp:Status>"
+ "<samlp:StatusCode "
+ "Value=\"urn:oasis:names:tc:SAML:2.0:status:Requester\"/>"
+ "</samlp:Status>"
+ "</samlp:ArtifactResponse>"
+ "</SOAP-ENV:Body>"
+ "</SOAP-ENV:Envelope>";
ex.setStatusCode(200);
ex.setResponseStream(response.getBytes(charset));
}
};
SamlClient samlClient = createSamlClient(httpClient);
httpClient.setSamlClient(samlClient);
issueRequest(exArtifact, initialEx, samlClient);
serviceProvider.getAssertionConsumer().handle(exArtifact);
assertEquals(403, exArtifact.getResponseCode());
assertTrue(!isAuthned(exArtifact));
}
@Test
public void testAssertionConsumerWrongResponse() throws Exception {
SamlHttpClient httpClient = new SamlHttpClient() {
@Override
protected void handleExchange(ClientExchange ex) {
String body = new String(ex.getRequestBody(), charset);
body = massageMessage(body);
assertEquals(GOLDEN_ARTIFACT_RESOLVE_REQUEST, body);
// Generate valid response for successful authn, but for the wrong
// request.
String issuer = metadata.getPeerEntity().getEntityID();
String recipient = metadata.getLocalEntity()
.getSPSSODescriptor(SAMLConstants.SAML20P_NS)
.getAssertionConsumerServices().get(0).getLocation();
String audience = metadata.getLocalEntity().getEntityID();
String response
= "<SOAP-ENV:Envelope "
+ "xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\">"
+ "<SOAP-ENV:Body>"
+ "<samlp:ArtifactResponse "
+ "xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\" "
+ "xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\" "
+ "ID=\"someid1\" Version=\"2.0\" "
+ "InResponseTo=\"" + samlClient.getRequestId() + "\" "
+ "IssueInstant=\"2010-01-01T01:01:01Z\">"
+ "<Issuer>" + issuer + "</Issuer>"
+ "<samlp:Status>"
+ "<samlp:StatusCode "
+ "Value=\"urn:oasis:names:tc:SAML:2.0:status:Success\"/>"
+ "</samlp:Status>"
+ "<samlp:Response "
+ "ID=\"someid2\" "
+ "Version=\"2.0\" "
+ "IssueInstant=\"2010-01-01T01:01:01Z\">"
+ "<samlp:Status>"
+ "<samlp:StatusCode "
+ "Value=\"urn:oasis:names:tc:SAML:2.0:status:Success\"/>"
+ "</samlp:Status>"
+ "<Assertion "
+ "Version=\"2.0\" "
+ "ID=\"someid3\" "
+ "IssueInstant=\"2010-01-01T01:01:01Z\">"
+ "<Issuer>" + issuer + "</Issuer>"
+ "<Subject>"
+ "<NameID>CN=Polly Hedra</NameID>"
+ "<SubjectConfirmation "
+ "Method=\"urn:oasis:names:tc:SAML:2.0:cm:bearer\">"
+ "<SubjectConfirmationData "
+ "InResponseTo=\"notthis" + samlClient.getRequestId() + "\" "
+ "Recipient=\"" + recipient + "\" "
+ "NotOnOrAfter=\"2030-01-01T01:01:01Z\"/>"
+ "</SubjectConfirmation>"
+ "</Subject>"
+ "<Conditions "
+ "NotBefore=\"2010-01-01T01:01:01Z\">"
+ "<AudienceRestriction>"
+ "<Audience>" + audience + "</Audience>"
+ "</AudienceRestriction>"
+ "</Conditions>"
+ "<AuthnStatement "
+ "AuthnInstant=\"2010-01-01T01:01:01Z\"/>"
+ "</Assertion>"
+ "</samlp:Response>"
+ "</samlp:ArtifactResponse>"
+ "</SOAP-ENV:Body>"
+ "</SOAP-ENV:Envelope>";
ex.setStatusCode(200);
ex.setResponseStream(response.getBytes(charset));
}
};
SamlClient samlClient = createSamlClient(httpClient);
httpClient.setSamlClient(samlClient);
issueRequest(exArtifact, initialEx, samlClient);
serviceProvider.getAssertionConsumer().handle(exArtifact);
assertEquals(403, exArtifact.getResponseCode());
assertTrue(!isAuthned(exArtifact));
}
@Test
public void testAssertionConsumerWrongIssuer() throws Exception {
SamlHttpClient httpClient = new SamlHttpClient() {
@Override
protected void handleExchange(ClientExchange ex) {
String body = new String(ex.getRequestBody(), charset);
body = massageMessage(body);
assertEquals(GOLDEN_ARTIFACT_RESOLVE_REQUEST, body);
// Generate valid response, but from wrong issuer.
String issuer = metadata.getPeerEntity().getEntityID();
String recipient = metadata.getLocalEntity()
.getSPSSODescriptor(SAMLConstants.SAML20P_NS)
.getAssertionConsumerServices().get(0).getLocation();
String audience = metadata.getLocalEntity().getEntityID();
String response
= "<SOAP-ENV:Envelope "
+ "xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\">"
+ "<SOAP-ENV:Body>"
+ "<samlp:ArtifactResponse "
+ "xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\" "
+ "xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\" "
+ "ID=\"someid1\" Version=\"2.0\" "
+ "InResponseTo=\"" + samlClient.getRequestId() + "\" "
+ "IssueInstant=\"2010-01-01T01:01:01Z\">"
+ "<Issuer>" + issuer + "</Issuer>"
+ "<samlp:Status>"
+ "<samlp:StatusCode "
+ "Value=\"urn:oasis:names:tc:SAML:2.0:status:Success\"/>"
+ "</samlp:Status>"
+ "<samlp:Response "
+ "ID=\"someid2\" "
+ "Version=\"2.0\" "
+ "IssueInstant=\"2010-01-01T01:01:01Z\">"
// Add an issuer that is not the expected one.
+ "<Issuer>notthexpected" + issuer + "</Issuer>"
+ "<samlp:Status>"
+ "<samlp:StatusCode "
+ "Value=\"urn:oasis:names:tc:SAML:2.0:status:Success\"/>"
+ "</samlp:Status>"
+ "<Assertion "
+ "Version=\"2.0\" "
+ "ID=\"someid3\" "
+ "IssueInstant=\"2010-01-01T01:01:01Z\">"
+ "<Issuer>" + issuer + "</Issuer>"
+ "<Subject>"
+ "<NameID>CN=Polly Hedra</NameID>"
+ "<SubjectConfirmation "
+ "Method=\"urn:oasis:names:tc:SAML:2.0:cm:bearer\">"
+ "<SubjectConfirmationData "
+ "InResponseTo=\"" + samlClient.getRequestId() + "\" "
+ "Recipient=\"" + recipient + "\" "
+ "NotOnOrAfter=\"2030-01-01T01:01:01Z\"/>"
+ "</SubjectConfirmation>"
+ "</Subject>"
+ "<Conditions "
+ "NotBefore=\"2010-01-01T01:01:01Z\">"
+ "<AudienceRestriction>"
+ "<Audience>" + audience + "</Audience>"
+ "</AudienceRestriction>"
+ "</Conditions>"
+ "<AuthnStatement "
+ "AuthnInstant=\"2010-01-01T01:01:01Z\"/>"
+ "</Assertion>"
+ "</samlp:Response>"
+ "</samlp:ArtifactResponse>"
+ "</SOAP-ENV:Body>"
+ "</SOAP-ENV:Envelope>";
ex.setStatusCode(200);
ex.setResponseStream(response.getBytes(charset));
}
};
SamlClient samlClient = createSamlClient(httpClient);
httpClient.setSamlClient(samlClient);
issueRequest(exArtifact, initialEx, samlClient);
serviceProvider.getAssertionConsumer().handle(exArtifact);
assertEquals(403, exArtifact.getResponseCode());
assertTrue(!isAuthned(exArtifact));
}
@Test
public void testAssertionConsumerAlreadyAuthned() throws Exception {
MockHttpClient httpClient = new MockHttpClient() {
@Override
protected void handleExchange(ClientExchange ex) {
fail("No request should have been issued.");
}
};
SamlClient samlClient = createSamlClient(httpClient);
issueRequest(exArtifact, initialEx, samlClient);
// Authenticate the session.
Session session = sessionManager.getSession(exArtifact, false);
AuthnState authn = (AuthnState) session
.getAttribute(SamlServiceProvider.SESSION_STATE_ATTR_NAME);
AuthnIdentity identity = new AuthnIdentityImpl
.Builder(new UserPrincipal("test")).build();
authn.authenticated(identity, Long.MAX_VALUE);
serviceProvider.getAssertionConsumer().handle(exArtifact);
assertEquals(409, exArtifact.getResponseCode());
assertTrue(isAuthned(exArtifact));
}
@Test
public void testAssertionConsumerNoSession() throws Exception {
serviceProvider.getAssertionConsumer().handle(exArtifact);
assertEquals(409, exArtifact.getResponseCode());
assertTrue(!isAuthned(exArtifact));
}
@Test
public void testAssertionConsumerUnrequestedAuthnResponse() throws Exception {
AuthnState authnState = new AuthnState();
sessionManager.getSession(exArtifact).setAttribute(
SamlServiceProvider.SESSION_STATE_ATTR_NAME, authnState);
serviceProvider.getAssertionConsumer().handle(exArtifact);
assertEquals(500, exArtifact.getResponseCode());
assertTrue(!isAuthned(exArtifact));
}
@Test
public void testAssertionConsumerPost() throws Exception {
MockHttpExchange ex
= new MockHttpExchange("POST", "/?SAMLart=1234someid5678",
new MockHttpContext("/"));
serviceProvider.getAssertionConsumer().handle(ex);
assertEquals(405, ex.getResponseCode());
assertTrue(!isAuthned(ex));
}
private SamlClient createSamlClient(HttpClientInterface httpClient) {
return new SamlClient(metadata.getLocalEntity(), metadata.getPeerEntity(),
"Testing", null, httpClient);
}
private void issueRequest(HttpExchange ex, HttpExchange initialEx,
SamlClient samlClient) throws IOException {
AuthnState authnState = new AuthnState();
authnState.startAttempt(samlClient, ex.getRequestURI());
sessionManager.getSession(ex).setAttribute(
SamlServiceProvider.SESSION_STATE_ATTR_NAME, authnState);
// Used to generate a request id.
samlClient.sendAuthnRequest(new HttpExchangeOutTransportAdapter(initialEx));
}
private boolean isAuthned(HttpExchange ex) {
return serviceProvider.getUserIdentity(ex) != null;
}
private String massageMessage(String message) {
return message.replaceAll("ID=\"[^\"]+\"", "ID=\"someid\"")
.replaceAll("IssueInstant=\"[^\"]+\"", "IssueInstant=\"sometime\"");
}
private static class EnhancedInTransport
extends HttpExchangeInTransportAdapter {
private Map<String, List<String>> parameters;
public EnhancedInTransport(HttpExchange ex) {
super(ex);
parameters = parseParameters();
}
private Map<String, List<String>> parseParameters() {
Map<String, List<String>> params = new HashMap<String, List<String>>();
String query = ex.getRequestURI().getQuery();
if (query == null) {
return params;
}
// This is not fully correct, but good enough for the test case.
for (String param : query.split("&")) {
String[] split = query.split("=", 2);
if (!params.containsKey(split[0])) {
params.put(split[0], new LinkedList<String>());
}
params.get(split[0]).add(split.length == 2 ? split[1] : "");
}
return params;
}
@Override
public String getParameterValue(String name) {
List<String> values = getParameterValues(name);
return values.size() == 0 ? null : values.get(0);
}
@Override
public List<String> getParameterValues(String name) {
if (parameters.containsKey(name)) {
return parameters.get(name);
} else {
return Collections.emptyList();
}
}
public HttpExchange getExchange() {
return ex;
}
}
private static class RedirectDecoder extends HTTPRedirectDeflateDecoder {
@Override
protected String getActualReceiverEndpointURI(
SAMLMessageContext messageContext) {
EnhancedInTransport inTransport = (EnhancedInTransport) messageContext
.getInboundMessageTransport();
String requestUri = inTransport.getExchange().getRequestURI().toString();
// Remove query from URI
return requestUri.split("\\?", 2)[0];
}
}
private abstract static class SamlHttpClient extends MockHttpClient {
protected SamlClient samlClient;
public void setSamlClient(SamlClient samlClient) {
this.samlClient = samlClient;
}
}
}