keycloak/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/saml/ArtifactBindingTest.java

1023 lines
61 KiB
Java

package org.keycloak.testsuite.saml;
import com.google.common.base.Charsets;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.util.EntityUtils;
import org.hamcrest.Matchers;
import org.junit.Rule;
import org.junit.Test;
import org.keycloak.adapters.saml.SamlDeployment;
import org.keycloak.dom.saml.v2.SAML2Object;
import org.keycloak.dom.saml.v2.metadata.SPSSODescriptorType;
import org.keycloak.dom.saml.v2.protocol.ArtifactResponseType;
import org.keycloak.dom.saml.v2.protocol.LogoutRequestType;
import org.keycloak.dom.saml.v2.protocol.NameIDMappingResponseType;
import org.keycloak.dom.saml.v2.protocol.ResponseType;
import org.keycloak.dom.saml.v2.protocol.StatusResponseType;
import org.keycloak.models.UserSessionModel;
import org.keycloak.protocol.saml.SamlConfigAttributes;
import org.keycloak.protocol.saml.SamlProtocol;
import org.keycloak.protocol.saml.SamlProtocolUtils;
import org.keycloak.protocol.saml.profile.util.Soap;
import org.keycloak.protocol.saml.util.ArtifactBindingUtils;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.saml.SAML2LogoutResponseBuilder;
import org.keycloak.saml.common.constants.GeneralConstants;
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
import org.keycloak.saml.common.exceptions.ParsingException;
import org.keycloak.saml.common.exceptions.ProcessingException;
import org.keycloak.saml.processing.api.saml.v2.request.SAML2Request;
import org.keycloak.saml.processing.core.parsers.saml.SAMLParser;
import org.keycloak.saml.processing.core.saml.v2.common.SAMLDocumentHolder;
import org.keycloak.saml.processing.core.saml.v2.util.AssertionUtil;
import org.keycloak.sessions.CommonClientSessionModel;
import org.keycloak.testsuite.updaters.ClientAttributeUpdater;
import org.keycloak.testsuite.updaters.RealmAttributeUpdater;
import org.keycloak.testsuite.util.InfinispanTestTimeServiceRule;
import org.keycloak.testsuite.util.SamlClient;
import org.keycloak.testsuite.util.SamlClientBuilder;
import org.keycloak.testsuite.util.SamlUtils;
import org.keycloak.testsuite.util.saml.HandleArtifactStepBuilder;
import org.keycloak.testsuite.util.saml.SamlMessageReceiver;
import org.keycloak.testsuite.util.saml.SessionStateChecker;
import org.keycloak.testsuite.utils.io.IOUtil;
import org.w3c.dom.Document;
import javax.ws.rs.core.Response;
import javax.xml.transform.dom.DOMSource;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static org.awaitility.Awaitility.await;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.isEmptyOrNullString;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
import static org.keycloak.testsuite.util.Matchers.bodyHC;
import static org.keycloak.testsuite.util.Matchers.isSamlLogoutRequest;
import static org.keycloak.testsuite.util.Matchers.isSamlResponse;
import static org.keycloak.testsuite.util.Matchers.isSamlStatusResponse;
import static org.keycloak.testsuite.util.Matchers.statusCodeIsHC;
import static org.keycloak.testsuite.util.SamlClient.Binding.ARTIFACT_RESPONSE;
import static org.keycloak.testsuite.util.SamlClient.Binding.POST;
import static org.keycloak.testsuite.util.SamlClient.Binding.REDIRECT;
import static org.keycloak.testsuite.util.SamlUtils.getSPInstallationDescriptor;
public class ArtifactBindingTest extends AbstractSamlTest {
@Rule
public InfinispanTestTimeServiceRule ispnTestTimeService = new InfinispanTestTimeServiceRule(this);
/************************ LOGIN TESTS ************************/
@Test
public void testArtifactBindingTimesOutAfterCodeToTokenLifespan() throws Exception {
getCleanup().addCleanup(
new RealmAttributeUpdater(adminClient.realm(REALM_NAME))
.setAccessCodeLifespan(1)
.update()
);
SAMLDocumentHolder response = new SamlClientBuilder().authnRequest(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST,
SAML_ASSERTION_CONSUMER_URL_SALES_POST, SamlClient.Binding.POST)
.setProtocolBinding(JBossSAMLURIConstants.SAML_HTTP_ARTIFACT_BINDING.getUri())
.build()
.login().user(bburkeUser).build()
.handleArtifact(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST)
.setBeforeStepChecks(() -> setTimeOffset(1000)) // Move in time before resolving the artifact
.build()
.doNotFollowRedirects()
.executeAndTransform(this::getArtifactResponse);
assertThat(response.getSamlObject(), instanceOf(ArtifactResponseType.class));
ArtifactResponseType artifactResponse = (ArtifactResponseType)response.getSamlObject();
assertThat(artifactResponse, isSamlStatusResponse(JBossSAMLURIConstants.STATUS_SUCCESS));
assertThat(artifactResponse.getAny(), nullValue());
}
@Test
public void testArtifactBindingWithResponseAndAssertionSignature() throws Exception {
SAMLDocumentHolder response = new SamlClientBuilder().authnRequest(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST_ASSERTION_AND_RESPONSE_SIG,
SAML_ASSERTION_CONSUMER_URL_SALES_POST_ASSERTION_AND_RESPONSE_SIG, POST)
.setProtocolBinding(JBossSAMLURIConstants.SAML_HTTP_ARTIFACT_BINDING.getUri())
.signWith(SAML_CLIENT_SALES_POST_SIG_PRIVATE_KEY, SAML_CLIENT_SALES_POST_SIG_PUBLIC_KEY)
.build()
.login()
.user(bburkeUser)
.build()
.handleArtifact(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST_ASSERTION_AND_RESPONSE_SIG)
.signWith(SAML_CLIENT_SALES_POST_SIG_PRIVATE_KEY, SAML_CLIENT_SALES_POST_SIG_PUBLIC_KEY)
.build()
.doNotFollowRedirects()
.executeAndTransform(this::getArtifactResponse);
assertThat(response.getSamlObject(), instanceOf(ArtifactResponseType.class));
ArtifactResponseType artifactResponse = (ArtifactResponseType)response.getSamlObject();
assertThat(artifactResponse, isSamlStatusResponse(JBossSAMLURIConstants.STATUS_SUCCESS));
assertThat(artifactResponse.getAny(), instanceOf(ResponseType.class));
ResponseType samlResponse = (ResponseType)artifactResponse.getAny();
assertThat(samlResponse, isSamlStatusResponse(JBossSAMLURIConstants.STATUS_SUCCESS));
assertThat(samlResponse.getAssertions().get(0).getAssertion().getSignature(), not(nullValue()));
SamlDeployment deployment = SamlUtils.getSamlDeploymentForClient("sales-post-assertion-and-response-sig");
SamlProtocolUtils.verifyDocumentSignature(response.getSamlDocument(), deployment.getIDP().getSignatureValidationKeyLocator()); // Checks the signature of the response as well as the signature of the assertion
}
@Test
public void testArtifactBindingWithEncryptedAssertion() throws Exception {
SAMLDocumentHolder response = new SamlClientBuilder().authnRequest(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST_ENC,
SAML_ASSERTION_CONSUMER_URL_SALES_POST_ENC, POST)
.setProtocolBinding(JBossSAMLURIConstants.SAML_HTTP_ARTIFACT_BINDING.getUri())
.signWith(SAML_CLIENT_SALES_POST_ENC_PRIVATE_KEY, SAML_CLIENT_SALES_POST_ENC_PUBLIC_KEY)
.build()
.login().user(bburkeUser).build()
.handleArtifact(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST_ENC)
.signWith(SAML_CLIENT_SALES_POST_ENC_PRIVATE_KEY, SAML_CLIENT_SALES_POST_ENC_PUBLIC_KEY)
.build()
.doNotFollowRedirects()
.executeAndTransform(ARTIFACT_RESPONSE::extractResponse);
assertThat(response.getSamlObject(), instanceOf(ResponseType.class));
ResponseType loginResponse = (ResponseType) response.getSamlObject();
assertThat(loginResponse, isSamlStatusResponse(JBossSAMLURIConstants.STATUS_SUCCESS));
assertThat(loginResponse.getAssertions().get(0).getAssertion(), nullValue());
assertThat(loginResponse.getAssertions().get(0).getEncryptedAssertion(), not(nullValue()));
SamlDeployment deployment = SamlUtils.getSamlDeploymentForClient("sales-post-enc");
AssertionUtil.decryptAssertion(loginResponse, deployment.getDecryptionKey());
assertThat(loginResponse.getAssertions().get(0).getAssertion(), not(nullValue()));
assertThat(loginResponse.getAssertions().get(0).getEncryptedAssertion(), nullValue());
assertThat(loginResponse.getAssertions().get(0).getAssertion().getIssuer().getValue(), equalTo(getAuthServerRealmBase(REALM_NAME).toString()));
}
@Test
public void testArtifactBindingLoginCheckArtifactWithPost() throws NoSuchAlgorithmException {
String response = new SamlClientBuilder().authnRequest(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST,
SAML_ASSERTION_CONSUMER_URL_SALES_POST, SamlClient.Binding.POST)
.setProtocolBinding(JBossSAMLURIConstants.SAML_HTTP_ARTIFACT_BINDING.getUri())
.build()
.login().user(bburkeUser).build().doNotFollowRedirects().executeAndTransform(resp -> EntityUtils.toString(resp.getEntity()));
assertThat(response, containsString(GeneralConstants.SAML_ARTIFACT_KEY));
Pattern artifactPattern = Pattern.compile("NAME=\"SAMLart\" VALUE=\"((?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=))");
Matcher m = artifactPattern.matcher(response);
assertThat(m.find(), is(true));
String artifactB64 = m.group(1);
assertThat(artifactB64,not(isEmptyOrNullString()));
byte[] artifact = Base64.getDecoder().decode(artifactB64);
assertThat(artifact.length, is(44));
assertThat(artifact[0], is((byte)0));
assertThat(artifact[1], is((byte)4));
assertThat(artifact[2], is((byte)0));
assertThat(artifact[3], is((byte)0));
MessageDigest sha1Digester = MessageDigest.getInstance("SHA-1");
byte[] source = sha1Digester.digest(getAuthServerRealmBase(REALM_NAME).toString().getBytes(Charsets.UTF_8));
for (int i = 0; i < 20; i++) {
assertThat(source[i], is(artifact[i+4]));
}
}
@Test
public void testArtifactBindingLoginFullExchangeWithPost() {
SAMLDocumentHolder response = new SamlClientBuilder().authnRequest(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST,
SAML_ASSERTION_CONSUMER_URL_SALES_POST, SamlClient.Binding.POST)
.setProtocolBinding(JBossSAMLURIConstants.SAML_HTTP_ARTIFACT_BINDING.getUri())
.build()
.login().user(bburkeUser).build().handleArtifact(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST).build()
.doNotFollowRedirects().executeAndTransform(this::getArtifactResponse);
assertThat(response.getSamlObject(), instanceOf(ArtifactResponseType.class));
ArtifactResponseType artifactResponse = (ArtifactResponseType)response.getSamlObject();
assertThat(artifactResponse, isSamlStatusResponse(JBossSAMLURIConstants.STATUS_SUCCESS));
assertThat(artifactResponse.getSignature(), nullValue());
assertThat(artifactResponse.getAny(), instanceOf(ResponseType.class));
assertThat(artifactResponse.getInResponseTo(), not(isEmptyOrNullString()));
ResponseType samlResponse = (ResponseType)artifactResponse.getAny();
assertThat(samlResponse, isSamlStatusResponse(JBossSAMLURIConstants.STATUS_SUCCESS));
}
@Test
public void testArtifactBindingLoginCorrectSignature() {
SAMLDocumentHolder response = new SamlClientBuilder().authnRequest(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST_SIG,
SAML_ASSERTION_CONSUMER_URL_SALES_POST_SIG, SamlClient.Binding.POST)
.setProtocolBinding(JBossSAMLURIConstants.SAML_HTTP_ARTIFACT_BINDING.getUri())
.signWith(SAML_CLIENT_SALES_POST_SIG_PRIVATE_KEY
, SAML_CLIENT_SALES_POST_SIG_PUBLIC_KEY)
.build()
.login().user(bburkeUser).build().handleArtifact(getAuthServerSamlEndpoint(REALM_NAME)
, SAML_CLIENT_ID_SALES_POST_SIG).signWith(SAML_CLIENT_SALES_POST_SIG_PRIVATE_KEY
, SAML_CLIENT_SALES_POST_SIG_PUBLIC_KEY).build()
.doNotFollowRedirects().executeAndTransform(this::getArtifactResponse);
assertThat(response.getSamlObject(), instanceOf(ArtifactResponseType.class));
ArtifactResponseType artifactResponse = (ArtifactResponseType)response.getSamlObject();
assertThat(artifactResponse, isSamlStatusResponse(JBossSAMLURIConstants.STATUS_SUCCESS));
assertThat(artifactResponse.getAny(), instanceOf(ResponseType.class));
assertThat(artifactResponse.getSignature(), not(nullValue()));
ResponseType samlResponse = (ResponseType)artifactResponse.getAny();
assertThat(samlResponse, isSamlStatusResponse(JBossSAMLURIConstants.STATUS_SUCCESS));
assertThat(samlResponse.getAssertions().get(0).getAssertion().getSignature(), nullValue());
}
@Test
public void testArtifactBindingLoginIncorrectSignature() {
SAMLDocumentHolder response = new SamlClientBuilder().authnRequest(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST_SIG,
SAML_ASSERTION_CONSUMER_URL_SALES_POST_SIG, SamlClient.Binding.POST)
.setProtocolBinding(JBossSAMLURIConstants.SAML_HTTP_ARTIFACT_BINDING.getUri())
.signWith(SAML_CLIENT_SALES_POST_SIG_PRIVATE_KEY
, SAML_CLIENT_SALES_POST_SIG_PUBLIC_KEY)
.build()
.login().user(bburkeUser).build()
.handleArtifact(getAuthServerSamlEndpoint(REALM_NAME)
, SAML_CLIENT_ID_SALES_POST_SIG).signWith(SAML_CLIENT_SALES_POST_SIG_EXPIRED_PRIVATE_KEY,
SAML_CLIENT_SALES_POST_SIG_EXPIRED_PUBLIC_KEY)
.build()
.doNotFollowRedirects()
.executeAndTransform(this::getArtifactResponse);
assertThat(response.getSamlObject(), instanceOf(ArtifactResponseType.class));
ArtifactResponseType artifactResponse = (ArtifactResponseType)response.getSamlObject();
assertThat(artifactResponse, isSamlStatusResponse(JBossSAMLURIConstants.STATUS_SUCCESS));
assertThat(artifactResponse.getAny(), nullValue());
assertThat(artifactResponse.getSignature(), not(nullValue()));
}
@Test
public void testArtifactBindingLoginGetArtifactResponseTwice() {
SamlClientBuilder clientBuilder = new SamlClientBuilder();
HandleArtifactStepBuilder handleArtifactBuilder = new HandleArtifactStepBuilder(
getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST, clientBuilder);
SAMLDocumentHolder response= clientBuilder.authnRequest(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST,
SAML_ASSERTION_CONSUMER_URL_SALES_POST, SamlClient.Binding.REDIRECT)
.setProtocolBinding(JBossSAMLURIConstants.SAML_HTTP_ARTIFACT_BINDING.getUri())
.build()
.login().user(bburkeUser).build()
.handleArtifact(handleArtifactBuilder).build()
.processSamlResponse(ARTIFACT_RESPONSE)
.transformObject(ob -> {
assertThat(ob, isSamlResponse(JBossSAMLURIConstants.STATUS_SUCCESS));
return null;
})
.build()
.handleArtifact(handleArtifactBuilder).replayPost(true).build()
.doNotFollowRedirects()
.executeAndTransform(this::getArtifactResponse);
assertThat(response.getSamlObject(), instanceOf(ArtifactResponseType.class));
ArtifactResponseType artifactResponse = (ArtifactResponseType)response.getSamlObject();
assertThat(artifactResponse, isSamlStatusResponse(JBossSAMLURIConstants.STATUS_SUCCESS));
assertThat(artifactResponse.getAny(), nullValue());
}
@Test
public void testArtifactSuccessfulAfterFirstUnsuccessfulRequest() {
SamlClientBuilder clientBuilder = new SamlClientBuilder();
AtomicReference<String> artifact = new AtomicReference<>();
SAMLDocumentHolder response = clientBuilder.authnRequest(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST,
SAML_ASSERTION_CONSUMER_URL_SALES_POST, SamlClient.Binding.POST)
.setProtocolBinding(JBossSAMLURIConstants.SAML_HTTP_ARTIFACT_BINDING.getUri())
.build()
.login().user(bburkeUser).build()
.handleArtifact(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST2) // Wrong issuer
.storeArtifact(artifact)
.build()
.assertResponse(r -> assertThat(r, bodyHC(containsString(JBossSAMLURIConstants.STATUS_REQUEST_DENIED.get()))))
.handleArtifact(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST)
.useArtifact(artifact)
.build()
.executeAndTransform(ARTIFACT_RESPONSE::extractResponse);
assertThat(response.getSamlObject(), isSamlResponse(JBossSAMLURIConstants.STATUS_SUCCESS));
}
@Test
public void testArtifactBindingLoginForceArtifactBinding() {
getCleanup()
.addCleanup(ClientAttributeUpdater.forClient(adminClient, REALM_NAME, SAML_CLIENT_ID_SALES_POST)
.setAttribute(SamlConfigAttributes.SAML_ARTIFACT_BINDING, "true")
.update()
);
SAMLDocumentHolder response = new SamlClientBuilder().authnRequest(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST,
SAML_ASSERTION_CONSUMER_URL_SALES_POST, SamlClient.Binding.POST)
.build()
.login().user(bburkeUser).build()
.handleArtifact(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST).build()
.doNotFollowRedirects()
.executeAndTransform(this::getArtifactResponse);
assertThat(response.getSamlObject(), instanceOf(ArtifactResponseType.class));
ArtifactResponseType artifactResponse = (ArtifactResponseType)response.getSamlObject();
assertThat(artifactResponse, isSamlStatusResponse(JBossSAMLURIConstants.STATUS_SUCCESS));
assertThat(artifactResponse.getAny(), instanceOf(ResponseType.class));
ResponseType samlResponse = (ResponseType)artifactResponse.getAny();
assertThat(samlResponse, isSamlStatusResponse(JBossSAMLURIConstants.STATUS_SUCCESS));
}
@Test
public void testArtifactBindingLoginSignedArtifactResponse() throws Exception {
getCleanup()
.addCleanup(ClientAttributeUpdater.forClient(adminClient, REALM_NAME, SAML_CLIENT_ID_SALES_POST)
.setAttribute(SamlConfigAttributes.SAML_ARTIFACT_BINDING, "true")
.setAttribute(SamlConfigAttributes.SAML_SERVER_SIGNATURE, "true")
.update()
);
SAMLDocumentHolder response = new SamlClientBuilder().authnRequest(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST,
SAML_ASSERTION_CONSUMER_URL_SALES_POST, SamlClient.Binding.POST)
.build()
.login().user(bburkeUser).build()
.handleArtifact(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST).build()
.doNotFollowRedirects()
.executeAndTransform(this::getArtifactResponse);
assertThat(response.getSamlObject(), instanceOf(ArtifactResponseType.class));
ArtifactResponseType artifactResponse = (ArtifactResponseType)response.getSamlObject();
assertThat(artifactResponse, isSamlStatusResponse(JBossSAMLURIConstants.STATUS_SUCCESS));
assertThat(artifactResponse.getSignature(), notNullValue());
assertThat(artifactResponse.getAny(), instanceOf(ResponseType.class));
assertThat(artifactResponse.getInResponseTo(), not(isEmptyOrNullString()));
ResponseType samlResponse = (ResponseType)artifactResponse.getAny();
assertThat(samlResponse, isSamlStatusResponse(JBossSAMLURIConstants.STATUS_SUCCESS));
SamlDeployment deployment = SamlUtils.getSamlDeploymentForClient("sales-post");
SamlProtocolUtils.verifyDocumentSignature(response.getSamlDocument(), deployment.getIDP().getSignatureValidationKeyLocator());
}
@Test
public void testArtifactBindingLoginFullExchangeWithRedirect() {
SAMLDocumentHolder response = new SamlClientBuilder().authnRequest(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST,
SAML_ASSERTION_CONSUMER_URL_SALES_POST, SamlClient.Binding.REDIRECT)
.setProtocolBinding(JBossSAMLURIConstants.SAML_HTTP_ARTIFACT_BINDING.getUri())
.build()
.login().user(bburkeUser).build()
.handleArtifact(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST).verifyRedirect(true).build()
.doNotFollowRedirects()
.executeAndTransform(this::getArtifactResponse);
assertThat(response.getSamlObject(), instanceOf(ArtifactResponseType.class));
ArtifactResponseType artifactResponse = (ArtifactResponseType)response.getSamlObject();
assertThat(artifactResponse, isSamlStatusResponse(JBossSAMLURIConstants.STATUS_SUCCESS));
assertThat(artifactResponse.getAny(), instanceOf(ResponseType.class));
assertThat(artifactResponse.getInResponseTo(), not(isEmptyOrNullString()));
ResponseType samlResponse = (ResponseType)artifactResponse.getAny();
assertThat(samlResponse, isSamlStatusResponse(JBossSAMLURIConstants.STATUS_SUCCESS));
}
@Test
public void testArtifactResponseContainsCorrectInResponseTo(){
SAMLDocumentHolder response = new SamlClientBuilder().authnRequest(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST,
SAML_ASSERTION_CONSUMER_URL_SALES_POST, SamlClient.Binding.POST)
.setProtocolBinding(JBossSAMLURIConstants.SAML_HTTP_ARTIFACT_BINDING.getUri())
.build()
.login().user(bburkeUser).build()
.handleArtifact(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST).setArtifactResolveId("TestId").build()
.doNotFollowRedirects()
.executeAndTransform(this::getArtifactResponse);
assertThat(response.getSamlObject(), instanceOf(ArtifactResponseType.class));
ArtifactResponseType artifactResponse = (ArtifactResponseType)response.getSamlObject();
assertThat(artifactResponse, isSamlStatusResponse(JBossSAMLURIConstants.STATUS_SUCCESS));
assertThat(artifactResponse.getSignature(), nullValue());
assertThat(artifactResponse.getAny(), instanceOf(ResponseType.class));
assertThat(artifactResponse.getInResponseTo(), is("TestId"));
}
/************************ LOGOUT TESTS ************************/
@Test
public void testArtifactBindingLogoutSingleClientCheckArtifact() throws NoSuchAlgorithmException {
getCleanup()
.addCleanup(ClientAttributeUpdater.forClient(adminClient, REALM_NAME, SAML_CLIENT_ID_SALES_POST)
.setAttribute(SamlConfigAttributes.SAML_ARTIFACT_BINDING, "true")
.setAttribute(SamlConfigAttributes.SAML_SERVER_SIGNATURE, "true")
.setAttribute(SamlProtocol.SAML_SINGLE_LOGOUT_SERVICE_URL_ARTIFACT_ATTRIBUTE, "http://url")
.setFrontchannelLogout(true)
.update()
);
String response = new SamlClientBuilder().authnRequest(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST,
SAML_ASSERTION_CONSUMER_URL_SALES_POST, SamlClient.Binding.POST)
.build()
.login().user(bburkeUser).build()
.handleArtifact(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST).build()
.logoutRequest(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST, SamlClient.Binding.POST).build()
.doNotFollowRedirects()
.executeAndTransform(resp -> EntityUtils.toString(resp.getEntity()));
assertThat(response, containsString(GeneralConstants.SAML_ARTIFACT_KEY));
Pattern artifactPattern = Pattern.compile("NAME=\"SAMLart\" VALUE=\"((?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=))");
Matcher m = artifactPattern.matcher(response);
assertThat(true, is(m.find()));
String artifactB64 = m.group(1);
assertThat(artifactB64, not(isEmptyOrNullString()));
byte[] artifact = Base64.getDecoder().decode(artifactB64);
assertThat(artifact.length, is(44));
assertThat(artifact[0], is((byte)0));
assertThat(artifact[1], is((byte)4));
assertThat(artifact[2], is((byte)0));
assertThat(artifact[3], is((byte)0));
MessageDigest sha1Digester = MessageDigest.getInstance("SHA-1");
byte[] source = sha1Digester.digest(getAuthServerRealmBase(REALM_NAME).toString().getBytes(Charsets.UTF_8));
for (int i = 0; i < 20; i++) {
assertThat(source[i], is(artifact[i+4]));
}
}
@Test
public void testArtifactBindingLogoutSingleClientPost() {
getCleanup()
.addCleanup(ClientAttributeUpdater.forClient(adminClient, REALM_NAME, SAML_CLIENT_ID_SALES_POST)
.setAttribute(SamlConfigAttributes.SAML_ARTIFACT_BINDING, "true")
.setAttribute(SamlProtocol.SAML_SINGLE_LOGOUT_SERVICE_URL_ARTIFACT_ATTRIBUTE, "http://url")
.setFrontchannelLogout(true)
.update()
);
SAMLDocumentHolder response = new SamlClientBuilder().authnRequest(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST,
SAML_ASSERTION_CONSUMER_URL_SALES_POST, POST)
.build()
.login().user(bburkeUser).build()
.handleArtifact(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST).build()
.logoutRequest(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST, POST).build()
.handleArtifact(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST).build()
.doNotFollowRedirects()
.executeAndTransform(this::getArtifactResponse);
assertThat(response.getSamlObject(), instanceOf(ArtifactResponseType.class));
ArtifactResponseType artifactResponse = (ArtifactResponseType)response.getSamlObject();
assertThat(artifactResponse, isSamlStatusResponse(JBossSAMLURIConstants.STATUS_SUCCESS));
assertThat(artifactResponse.getSignature(), nullValue());
assertThat(artifactResponse.getAny(), not(instanceOf(ResponseType.class)));
assertThat(artifactResponse.getAny(), not(instanceOf(ArtifactResponseType.class)));
assertThat(artifactResponse.getAny(), not(instanceOf(NameIDMappingResponseType.class)));
assertThat(artifactResponse.getAny(), instanceOf(StatusResponseType.class));
StatusResponseType samlResponse = (StatusResponseType)artifactResponse.getAny();
assertThat(samlResponse, isSamlStatusResponse(JBossSAMLURIConstants.STATUS_SUCCESS));
}
@Test
public void testArtifactBindingLogoutSingleClientRedirect() {
getCleanup()
.addCleanup(ClientAttributeUpdater.forClient(adminClient, REALM_NAME, SAML_CLIENT_ID_SALES_POST)
.setAttribute(SamlConfigAttributes.SAML_ARTIFACT_BINDING, "true")
.setAttribute(SamlProtocol.SAML_SINGLE_LOGOUT_SERVICE_URL_ARTIFACT_ATTRIBUTE, "http://url")
.setFrontchannelLogout(true)
.update()
);
SAMLDocumentHolder response = new SamlClientBuilder().authnRequest(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST,
SAML_ASSERTION_CONSUMER_URL_SALES_POST, REDIRECT)
.setProtocolBinding(JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.getUri())
.build()
.login().user(bburkeUser).build()
.handleArtifact(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST).verifyRedirect(true).build()
.logoutRequest(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST, REDIRECT).build()
.handleArtifact(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST).verifyRedirect(true).build()
.doNotFollowRedirects()
.executeAndTransform(this::getArtifactResponse);
assertThat(response.getSamlObject(), instanceOf(ArtifactResponseType.class));
ArtifactResponseType artifactResponse = (ArtifactResponseType)response.getSamlObject();
assertThat(artifactResponse, isSamlStatusResponse(JBossSAMLURIConstants.STATUS_SUCCESS));
assertThat(artifactResponse.getSignature(), nullValue());
assertThat(artifactResponse.getAny(), not(instanceOf(ResponseType.class)));
assertThat(artifactResponse.getAny(), not(instanceOf(ArtifactResponseType.class)));
assertThat(artifactResponse.getAny(), not(instanceOf(NameIDMappingResponseType.class)));
assertThat(artifactResponse.getAny(), instanceOf(StatusResponseType.class));
StatusResponseType samlResponse = (StatusResponseType)artifactResponse.getAny();
assertThat(samlResponse, isSamlStatusResponse(JBossSAMLURIConstants.STATUS_SUCCESS));
}
@Test
public void testArtifactBindingLogoutTwoClientsPostWithSig() throws Exception {
getCleanup()
.addCleanup(ClientAttributeUpdater.forClient(adminClient, REALM_NAME, SAML_CLIENT_ID_SALES_POST_SIG)
.setAttribute(SamlConfigAttributes.SAML_ARTIFACT_BINDING, "true")
.setAttribute(SamlProtocol.SAML_SINGLE_LOGOUT_SERVICE_URL_ARTIFACT_ATTRIBUTE, "http://url")
.setFrontchannelLogout(true)
.update()
);
SAMLDocumentHolder response = new SamlClientBuilder()
.authnRequest(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST2, SAML_ASSERTION_CONSUMER_URL_SALES_POST2, POST).build()
.login().user(bburkeUser).build()
.processSamlResponse(POST)
.transformObject(this::extractNameIdAndSessionIndexAndTerminate)
.build()
.authnRequest(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST_SIG, SAML_ASSERTION_CONSUMER_URL_SALES_POST_SIG, POST)
.signWith(SAML_CLIENT_SALES_POST_SIG_PRIVATE_KEY, SAML_CLIENT_SALES_POST_SIG_PUBLIC_KEY)
.build()
.login().sso(true).build()
.handleArtifact(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST_SIG)
.signWith(SAML_CLIENT_SALES_POST_SIG_PRIVATE_KEY, SAML_CLIENT_SALES_POST_SIG_PUBLIC_KEY)
.build()
.logoutRequest(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST2, POST)
.nameId(nameIdRef::get)
.sessionIndex(sessionIndexRef::get)
.build()
.handleArtifact(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST_SIG)
.signWith(SAML_CLIENT_SALES_POST_SIG_PRIVATE_KEY, SAML_CLIENT_SALES_POST_SIG_PUBLIC_KEY)
.build()
.doNotFollowRedirects()
.executeAndTransform(this::getArtifactResponse);
assertThat(response.getSamlObject(), instanceOf(ArtifactResponseType.class));
ArtifactResponseType artifactResponse = (ArtifactResponseType)response.getSamlObject();
assertThat(artifactResponse, isSamlStatusResponse(JBossSAMLURIConstants.STATUS_SUCCESS));
assertThat(artifactResponse.getSignature(), notNullValue());
assertThat(artifactResponse.getAny(), instanceOf(LogoutRequestType.class));
SamlDeployment deployment = SamlUtils.getSamlDeploymentForClient("sales-post");
SamlProtocolUtils.verifyDocumentSignature(response.getSamlDocument(), deployment.getIDP().getSignatureValidationKeyLocator());
}
@Test
public void testArtifactBindingLogoutTwoClientsRedirect() {
getCleanup()
.addCleanup(ClientAttributeUpdater.forClient(adminClient, REALM_NAME, SAML_CLIENT_ID_SALES_POST)
.setAttribute(SamlConfigAttributes.SAML_ARTIFACT_BINDING, "true")
.setAttribute(SamlProtocol.SAML_SINGLE_LOGOUT_SERVICE_URL_ARTIFACT_ATTRIBUTE, "http://url")
.setFrontchannelLogout(true)
.update()
)
.addCleanup(ClientAttributeUpdater.forClient(adminClient, REALM_NAME, SAML_CLIENT_ID_SALES_POST2)
.setAttribute(SamlProtocol.SAML_SINGLE_LOGOUT_SERVICE_URL_ARTIFACT_ATTRIBUTE, "http://url")
.setFrontchannelLogout(true)
.update()
);
SAMLDocumentHolder response = new SamlClientBuilder()
.authnRequest(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST2, SAML_ASSERTION_CONSUMER_URL_SALES_POST2, REDIRECT)
.setProtocolBinding(JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.getUri()).build()
.login().user(bburkeUser).build()
.processSamlResponse(REDIRECT)
.transformObject(this::extractNameIdAndSessionIndexAndTerminate)
.build()
.authnRequest(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST,
SAML_ASSERTION_CONSUMER_URL_SALES_POST, REDIRECT).setProtocolBinding(JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.getUri())
.build()
.handleArtifact(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST).verifyRedirect(true).build() // This is a formal step
.logoutRequest(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST2, REDIRECT)
.nameId(nameIdRef::get)
.sessionIndex(sessionIndexRef::get)
.build()
.handleArtifact(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST).verifyRedirect(true).build()
.doNotFollowRedirects()
.executeAndTransform(this::getArtifactResponse);
assertThat(response.getSamlObject(), instanceOf(ArtifactResponseType.class));
ArtifactResponseType artifactResponse = (ArtifactResponseType)response.getSamlObject();
assertThat(artifactResponse.getSignature(), nullValue());
assertThat(artifactResponse, isSamlStatusResponse(JBossSAMLURIConstants.STATUS_SUCCESS));
assertThat(artifactResponse.getAny(), instanceOf(LogoutRequestType.class));
}
@Test
public void testArtifactBindingWithBackchannelLogout() {
try (SamlMessageReceiver backchannelLogoutReceiver = new SamlMessageReceiver(8082);
ClientAttributeUpdater cau = ClientAttributeUpdater.forClient(adminClient, REALM_NAME, SAML_CLIENT_ID_SALES_POST)
.setAttribute(SamlConfigAttributes.SAML_ARTIFACT_BINDING, "true")
.setFrontchannelLogout(false)
.setAttribute(SamlProtocol.SAML_SINGLE_LOGOUT_SERVICE_URL_POST_ATTRIBUTE, backchannelLogoutReceiver.getUrl())
.update()) {
new SamlClientBuilder()
.authnRequest(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST, SAML_ASSERTION_CONSUMER_URL_SALES_POST, POST)
.setProtocolBinding(JBossSAMLURIConstants.SAML_HTTP_ARTIFACT_BINDING.getUri()).build()
.login().user(bburkeUser).build()
.handleArtifact(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST).build()
.authnRequest(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST2, SAML_ASSERTION_CONSUMER_URL_SALES_POST2, POST)
.build()
.followOneRedirect()
.processSamlResponse(POST)
.transformObject(this::extractNameIdAndSessionIndexAndTerminate)
.build()
.execute();
// We need new SamlClient so that logout is not done using cookie -> frontchannel logout
new SamlClientBuilder()
// Initiate logout as SAML_CLIENT_ID_SALES_POST2 and expect backchannel call to SAML_CLIENT_ID_SALES_POST
.logoutRequest(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST2, POST)
.nameId(nameIdRef::get)
.sessionIndex(sessionIndexRef::get)
.build()
.executeAndTransform(r -> {
SAMLDocumentHolder saml2ObjectHolder = POST.extractResponse(r);
assertThat(saml2ObjectHolder.getSamlObject(), isSamlStatusResponse(JBossSAMLURIConstants.STATUS_SUCCESS));
return null;
});
// Check whether logoutReceiver contains correct LogoutRequest
await()
.pollInterval(100, TimeUnit.MILLISECONDS)
.atMost(1, TimeUnit.MINUTES)
.until(backchannelLogoutReceiver::isMessageReceived);
assertThat(backchannelLogoutReceiver.isMessageReceived(), is(true));
SAMLDocumentHolder message = backchannelLogoutReceiver.getSamlDocumentHolder();
assertThat(message.getSamlObject(), isSamlLogoutRequest(backchannelLogoutReceiver.getUrl()));
} catch (Exception e) {
throw new RuntimeException("Cannot run SamlMessageReceiver", e);
}
}
@Test
public void testArtifactResolveWithWrongIssuerFails() {
getCleanup()
.addCleanup(ClientAttributeUpdater.forClient(adminClient, REALM_NAME, SAML_CLIENT_ID_SALES_POST)
.setAttribute(SamlConfigAttributes.SAML_ARTIFACT_BINDING, "true")
.setAttribute(SamlProtocol.SAML_SINGLE_LOGOUT_SERVICE_URL_POST_ATTRIBUTE, "http://url")
.update()
);
new SamlClientBuilder()
.authnRequest(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST, SAML_ASSERTION_CONSUMER_URL_SALES_POST, POST)
.setProtocolBinding(JBossSAMLURIConstants.SAML_HTTP_ARTIFACT_BINDING.getUri()).build()
.login().user(bburkeUser).build()
.handleArtifact(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST2).build() // Wrong issuer
.execute(r -> assertThat(r, bodyHC(containsString(JBossSAMLURIConstants.STATUS_REQUEST_DENIED.get()))));
}
// Won't work with openshift, because openshift wouldn't see ArtifactResolutionService
@Test
public void testSessionStateDuringArtifactBindingLogoutWithOneClient() {
ClientRepresentation salesRep = adminClient.realm(REALM_NAME).clients().findByClientId(SAML_CLIENT_ID_SALES_POST).get(0);
final String clientId = salesRep.getId();
getCleanup()
.addCleanup(ClientAttributeUpdater.forClient(adminClient, REALM_NAME, SAML_CLIENT_ID_SALES_POST)
.setAttribute(SamlConfigAttributes.SAML_ARTIFACT_BINDING, "true")
.setAttribute(SamlProtocol.SAML_SINGLE_LOGOUT_SERVICE_URL_ARTIFACT_ATTRIBUTE, "http://url")
.setFrontchannelLogout(true)
.update()
);
AtomicReference<String> userSessionId = new AtomicReference<>();
SAMLDocumentHolder response = new SamlClientBuilder().authnRequest(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST,
SAML_ASSERTION_CONSUMER_URL_SALES_POST, POST)
.build()
.login().user(bburkeUser)
.build()
.handleArtifact(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST)
.setBeforeStepChecks(new SessionStateChecker(testingClient.server())
.storeUserSessionId(userSessionId)
.expectedState(UserSessionModel.State.LOGGED_IN)
.expectedClientSession(clientId)
.consumeUserSession(userSessionModel -> assertThat(userSessionModel, notNullValue()))
.consumeClientSession(clientId, userSessionModel -> assertThat(userSessionModel, notNullValue())))
.build()
.logoutRequest(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST, POST).build()
.handleArtifact(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST)
.setBeforeStepChecks(new SessionStateChecker(testingClient.server())
.expectedUserSession(userSessionId)
.expectedState(UserSessionModel.State.LOGGED_OUT_UNCONFIRMED)
.expectedNumberOfClientSessions(1)
.expectedAction(clientId, CommonClientSessionModel.Action.LOGGING_OUT))
.setAfterStepChecks(new SessionStateChecker(testingClient.server())
.consumeUserSession(userSessionModel -> assertThat(userSessionModel, nullValue()))
.setUserSessionProvider(session -> userSessionId.get()))
.build()
.doNotFollowRedirects().executeAndTransform(this::getArtifactResponse);
assertThat(response.getSamlObject(), instanceOf(ArtifactResponseType.class));
ArtifactResponseType artifactResponse = (ArtifactResponseType)response.getSamlObject();
assertThat(artifactResponse, isSamlStatusResponse(JBossSAMLURIConstants.STATUS_SUCCESS));
assertThat(artifactResponse.getSignature(), nullValue());
assertThat(artifactResponse.getAny(), not(instanceOf(ResponseType.class)));
assertThat(artifactResponse.getAny(), not(instanceOf(ArtifactResponseType.class)));
assertThat(artifactResponse.getAny(), not(instanceOf(NameIDMappingResponseType.class)));
assertThat(artifactResponse.getAny(), instanceOf(StatusResponseType.class));
StatusResponseType samlResponse = (StatusResponseType)artifactResponse.getAny();
assertThat(samlResponse, isSamlStatusResponse(JBossSAMLURIConstants.STATUS_SUCCESS));
}
// Won't work with openshift, because openshift wouldn't see ArtifactResolutionService
@Test
public void testSessionStateDuringArtifactBindingLogoutWithMoreFrontChannelClients() {
getCleanup()
.addCleanup(ClientAttributeUpdater.forClient(adminClient, REALM_NAME, SAML_CLIENT_ID_SALES_POST)
.setAttribute(SamlConfigAttributes.SAML_ARTIFACT_BINDING, "true")
.setAttribute(SamlProtocol.SAML_SINGLE_LOGOUT_SERVICE_URL_ARTIFACT_ATTRIBUTE, "http://url")
.setFrontchannelLogout(true)
.update()
)
.addCleanup(ClientAttributeUpdater.forClient(adminClient, REALM_NAME, SAML_CLIENT_ID_SALES_POST2)
.setAttribute(SamlConfigAttributes.SAML_ARTIFACT_BINDING, "true")
.setAttribute(SamlProtocol.SAML_SINGLE_LOGOUT_SERVICE_URL_ARTIFACT_ATTRIBUTE, "http://url")
.setFrontchannelLogout(true)
.update()
);
ClientRepresentation salesRep = adminClient.realm(REALM_NAME).clients().findByClientId(SAML_CLIENT_ID_SALES_POST).get(0);
final String salesRepId = salesRep.getId();
ClientRepresentation salesRep2 = adminClient.realm(REALM_NAME).clients().findByClientId(SAML_CLIENT_ID_SALES_POST2).get(0);
final String salesRep2Id = salesRep2.getId();
final AtomicReference<String> userSessionId = new AtomicReference<>();
SAMLDocumentHolder response = new SamlClientBuilder()
// Login first sales_post2 and resolve artifact
.authnRequest(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST2, SAML_ASSERTION_CONSUMER_URL_SALES_POST2, REDIRECT)
.setProtocolBinding(JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.getUri()).build()
.login().user(bburkeUser).build()
.handleArtifact(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST2)
.setBeforeStepChecks(new SessionStateChecker(testingClient.server())
.storeUserSessionId(userSessionId)
.expectedClientSession(salesRep2Id)
.expectedState(UserSessionModel.State.LOGGED_IN)
.expectedNumberOfClientSessions(1)
.consumeUserSession(userSessionModel -> assertThat(userSessionModel, notNullValue()))
.consumeClientSession(salesRep2Id, clientSession -> assertThat(clientSession, notNullValue())))
.verifyRedirect(true)
.build() // This is a formal step
// Login second sales_post and resolved artifact, no login should be needed as user is already logged in
.authnRequest(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST,
SAML_ASSERTION_CONSUMER_URL_SALES_POST, REDIRECT)
.setProtocolBinding(JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.getUri())
.build()
.handleArtifact(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST)
.setBeforeStepChecks(new SessionStateChecker(testingClient.server())
.expectedUserSession(userSessionId)
.expectedState(UserSessionModel.State.LOGGED_IN)
.expectedClientSession(salesRepId)
.expectedNumberOfClientSessions(2)
.expectedAction(salesRep2Id, null)
.expectedAction(salesRepId, null))
.verifyRedirect(true)
.build()
// Initiate logout from sales_post2
.logoutRequest(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST2, REDIRECT)
.build()
// Since sales_post uses frontchannel logout, keycloak should send LogoutRequest to sales_post first
.handleArtifact(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST)
.setBeforeStepChecks(new SessionStateChecker(testingClient.server())
.expectedUserSession(userSessionId)
.expectedState(UserSessionModel.State.LOGGING_OUT)
.expectedClientSession(salesRepId)
.expectedNumberOfClientSessions(2)
.expectedAction(salesRepId, CommonClientSessionModel.Action.LOGGING_OUT)
.expectedAction(salesRep2Id, CommonClientSessionModel.Action.LOGGING_OUT))
.setAfterStepChecks(new SessionStateChecker(testingClient.server())
.setUserSessionProvider(session -> userSessionId.get())
.expectedState(UserSessionModel.State.LOGGING_OUT)
.expectedNumberOfClientSessions(2)
.expectedAction(salesRepId, CommonClientSessionModel.Action.LOGGED_OUT)
.expectedAction(salesRep2Id, CommonClientSessionModel.Action.LOGGING_OUT))
.verifyRedirect(true)
.build()
.doNotFollowRedirects()
// Respond with LogoutResponse so that logout flow can continue with logging out client2
.processSamlResponse(ARTIFACT_RESPONSE)
.transformDocument(doc -> {
// Send LogoutResponse
SAML2Object so = (SAML2Object) SAMLParser.getInstance().parse(new DOMSource(doc));
return new SAML2LogoutResponseBuilder()
.destination(getAuthServerSamlEndpoint(REALM_NAME).toString())
.issuer(SAML_CLIENT_ID_SALES_POST)
.logoutRequestID(((LogoutRequestType) so).getID())
.buildDocument();
})
.targetBinding(REDIRECT)
.targetAttributeSamlResponse()
.targetUri(getAuthServerSamlEndpoint(REALM_NAME))
.build()
// Now Keycloak should finish logout process so it should respond with LogoutResponse
.handleArtifact(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST2)
.verifyRedirect(true)
.setBeforeStepChecks(new SessionStateChecker(testingClient.server())
.expectedUserSession(userSessionId)
.expectedClientSession(salesRep2Id)
.expectedState(UserSessionModel.State.LOGGED_OUT_UNCONFIRMED)
.expectedNumberOfClientSessions(2)
.expectedAction(salesRepId, CommonClientSessionModel.Action.LOGGED_OUT)
.expectedAction(salesRep2Id, CommonClientSessionModel.Action.LOGGING_OUT))
.setAfterStepChecks(new SessionStateChecker(testingClient.server())
.consumeUserSession(userSessionModel -> assertThat(userSessionModel, nullValue()))
.setUserSessionProvider(session -> userSessionId.get()))
.build()
.executeAndTransform(this::getArtifactResponse);
assertThat(response.getSamlObject(), instanceOf(ArtifactResponseType.class));
ArtifactResponseType artifactResponse = (ArtifactResponseType)response.getSamlObject();
assertThat(artifactResponse.getSignature(), nullValue());
assertThat(artifactResponse, isSamlStatusResponse(JBossSAMLURIConstants.STATUS_SUCCESS));
assertThat(artifactResponse.getAny(), instanceOf(StatusResponseType.class));
}
@Test
public void testArtifactBindingIsNotUsedForLogoutWhenLogoutUrlNotSetRedirect() {
getCleanup()
.addCleanup(ClientAttributeUpdater.forClient(adminClient, REALM_NAME, SAML_CLIENT_ID_SALES_POST)
.setAttribute(SamlConfigAttributes.SAML_ARTIFACT_BINDING, "true")
.setAttribute(SamlProtocol.SAML_SINGLE_LOGOUT_SERVICE_URL_REDIRECT_ATTRIBUTE, "http://url")
.setFrontchannelLogout(true)
.update()
);
SAMLDocumentHolder response = new SamlClientBuilder().authnRequest(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST,
SAML_ASSERTION_CONSUMER_URL_SALES_POST, REDIRECT)
.setProtocolBinding(JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.getUri())
.build()
.login().user(bburkeUser).build()
.handleArtifact(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST).verifyRedirect(true).build()
.logoutRequest(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST, REDIRECT).build()
.doNotFollowRedirects()
.executeAndTransform(REDIRECT::extractResponse);
assertThat(response.getSamlObject(), instanceOf(StatusResponseType.class));
StatusResponseType logoutResponse = (StatusResponseType)response.getSamlObject();
assertThat(logoutResponse, isSamlStatusResponse(JBossSAMLURIConstants.STATUS_SUCCESS));
assertThat(logoutResponse.getSignature(), nullValue());
assertThat(logoutResponse, not(instanceOf(ResponseType.class)));
assertThat(logoutResponse, not(instanceOf(ArtifactResponseType.class)));
assertThat(logoutResponse, not(instanceOf(NameIDMappingResponseType.class)));
assertThat(logoutResponse, instanceOf(StatusResponseType.class));
}
@Test
public void testArtifactBindingIsNotUsedForLogoutWhenLogoutUrlNotSetPostTest() {
getCleanup()
.addCleanup(ClientAttributeUpdater.forClient(adminClient, REALM_NAME, SAML_CLIENT_ID_SALES_POST)
.setAttribute(SamlConfigAttributes.SAML_ARTIFACT_BINDING, "true")
.setAttribute(SamlProtocol.SAML_SINGLE_LOGOUT_SERVICE_URL_POST_ATTRIBUTE, "http://url")
.setFrontchannelLogout(true)
.update()
);
SAMLDocumentHolder response = new SamlClientBuilder().authnRequest(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST,
SAML_ASSERTION_CONSUMER_URL_SALES_POST, POST)
.setProtocolBinding(JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.getUri())
.build()
.login().user(bburkeUser).build()
.handleArtifact(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST).build()
.logoutRequest(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST, POST).build()
.doNotFollowRedirects()
.executeAndTransform(POST::extractResponse);
assertThat(response.getSamlObject(), instanceOf(StatusResponseType.class));
StatusResponseType logoutResponse = (StatusResponseType)response.getSamlObject();
assertThat(logoutResponse, isSamlStatusResponse(JBossSAMLURIConstants.STATUS_SUCCESS));
assertThat(logoutResponse.getSignature(), nullValue());
assertThat(logoutResponse, not(instanceOf(ResponseType.class)));
assertThat(logoutResponse, not(instanceOf(ArtifactResponseType.class)));
assertThat(logoutResponse, not(instanceOf(NameIDMappingResponseType.class)));
assertThat(logoutResponse, instanceOf(StatusResponseType.class));
}
private SAMLDocumentHolder getArtifactResponse(CloseableHttpResponse response) throws IOException, ParsingException, ProcessingException {
assertThat(response, statusCodeIsHC(Response.Status.OK));
Document soapBody = extractSoapMessage(response);
return SAML2Request.getSAML2ObjectFromDocument(soapBody);
}
private Document extractSoapMessage(CloseableHttpResponse response) throws IOException {
ByteArrayInputStream bais = new ByteArrayInputStream(EntityUtils.toByteArray(response.getEntity()));
Document soapBody = Soap.extractSoapMessage(bais);
response.close();
return soapBody;
}
/************************ IMPORT CLIENT TESTS ************************/
@Test
public void testImportClientArtifactResolutionSingleServices() {
Document doc = IOUtil.loadXML(ArtifactBindingTest.class.getResourceAsStream("/saml/sp-metadata-artifact-simple.xml"));
ClientRepresentation clientRep = adminClient.realm(REALM_NAME).convertClientDescription(IOUtil.documentToString(doc));
assertThat(clientRep.getAttributes().get(SamlProtocol.SAML_ARTIFACT_RESOLUTION_SERVICE_URL_ATTRIBUTE), is("https://test.keycloak.com/auth/login/epd/callback/soap"));
assertThat(clientRep.getAttributes().get(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_ARTIFACT_ATTRIBUTE), is("https://test.keycloak.com/auth/login/epd/callback/http-artifact"));
}
@Test
public void testImportClientMultipleServices() {
Document doc = IOUtil.loadXML(ArtifactBindingTest.class.getResourceAsStream("/saml/sp-metadata-artifact-multiple.xml"));
ClientRepresentation clientRep = adminClient.realm(REALM_NAME).convertClientDescription(IOUtil.documentToString(doc));
assertThat(clientRep.getAttributes().get(SamlProtocol.SAML_ARTIFACT_RESOLUTION_SERVICE_URL_ATTRIBUTE), is("https://test.keycloak.com/auth/login/epd/callback/soap-1"));
assertThat(clientRep.getAttributes().get(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_ARTIFACT_ATTRIBUTE), Matchers.startsWith("https://test.keycloak.com/auth/login/epd/callback/http-artifact"));
}
@Test
public void testImportClientMultipleServicesWithDefault() {
Document doc = IOUtil.loadXML(ArtifactBindingTest.class.getResourceAsStream("/saml/sp-metadata-artifact-multiple-default.xml"));
ClientRepresentation clientRep = adminClient.realm(REALM_NAME).convertClientDescription(IOUtil.documentToString(doc));
assertThat(clientRep.getAttributes().get(SamlProtocol.SAML_ARTIFACT_RESOLUTION_SERVICE_URL_ATTRIBUTE), is("https://test.keycloak.com/auth/login/epd/callback/soap-9"));
assertThat(clientRep.getAttributes().get(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_ARTIFACT_ATTRIBUTE), Matchers.startsWith("https://test.keycloak.com/auth/login/epd/callback/http-artifact"));
}
@Test
public void testSPMetadataArtifactBindingNotUsedForLogout() throws ParsingException, URISyntaxException {
getCleanup()
.addCleanup(ClientAttributeUpdater.forClient(adminClient, REALM_NAME, SAML_CLIENT_ID_SALES_POST)
.setAttribute(SamlConfigAttributes.SAML_ARTIFACT_BINDING, "true")
.setAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_ARTIFACT_ATTRIBUTE, "http://url.artifact.test")
.setAdminUrl("http://admin.url.test")
.update()
);
SPSSODescriptorType spDescriptor = getSPInstallationDescriptor(adminClient.realm(REALM_NAME).clients(), SAML_CLIENT_ID_SALES_POST);
assertThat(spDescriptor.getAssertionConsumerService().get(0).getBinding(), is(equalTo(JBossSAMLURIConstants.SAML_HTTP_ARTIFACT_BINDING.getUri())));
assertThat(spDescriptor.getAssertionConsumerService().get(0).getLocation(), is(equalTo(new URI("http://url.artifact.test"))));
assertThat(spDescriptor.getSingleLogoutService().get(0).getBinding(), is(equalTo(JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.getUri())));
assertThat(spDescriptor.getSingleLogoutService().get(0).getLocation(), is(equalTo(new URI("http://admin.url.test"))));
}
@Test
public void testSPMetadataArtifactBindingUsedForLogout() throws ParsingException, URISyntaxException {
getCleanup()
.addCleanup(ClientAttributeUpdater.forClient(adminClient, REALM_NAME, SAML_CLIENT_ID_SALES_POST)
.setAttribute(SamlConfigAttributes.SAML_ARTIFACT_BINDING, "true")
.setAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_ARTIFACT_ATTRIBUTE, "http://url.artifact.test")
.setAttribute(SamlProtocol.SAML_SINGLE_LOGOUT_SERVICE_URL_ARTIFACT_ATTRIBUTE, "http://url.artifact.test")
.setAdminUrl("http://admin.url.test")
.update()
);
SPSSODescriptorType spDescriptor = getSPInstallationDescriptor(adminClient.realm(REALM_NAME).clients(), SAML_CLIENT_ID_SALES_POST);
assertThat(spDescriptor.getAssertionConsumerService().get(0).getBinding(), is(equalTo(JBossSAMLURIConstants.SAML_HTTP_ARTIFACT_BINDING.getUri())));
assertThat(spDescriptor.getAssertionConsumerService().get(0).getLocation(), is(equalTo(new URI("http://url.artifact.test"))));
assertThat(spDescriptor.getSingleLogoutService().get(0).getBinding(), is(equalTo(JBossSAMLURIConstants.SAML_HTTP_ARTIFACT_BINDING.getUri())));
assertThat(spDescriptor.getSingleLogoutService().get(0).getLocation(), is(equalTo(new URI("http://url.artifact.test"))));
}
@Test
public void testArtifactBindingIdentifierChangedWhenClientIdChanged() throws IOException {
ClientRepresentation clientRepresentation = adminClient.realm(REALM_NAME)
.clients()
.findByClientId(SAML_CLIENT_ID_SALES_POST)
.get(0);
String oldIdentifier = clientRepresentation.getAttributes().get(SamlConfigAttributes.SAML_ARTIFACT_BINDING_IDENTIFIER);
assertThat(oldIdentifier, notNullValue());
final String newClientId = "new_client_id";
try (ClientAttributeUpdater cau = ClientAttributeUpdater.forClient(adminClient, REALM_NAME, SAML_CLIENT_ID_SALES_POST)
.setClientId(newClientId)
.update()
) {
clientRepresentation = adminClient.realm(REALM_NAME)
.clients()
.findByClientId(newClientId)
.get(0);
String identifier = clientRepresentation.getAttributes().get(SamlConfigAttributes.SAML_ARTIFACT_BINDING_IDENTIFIER);
assertThat(identifier, not(equalTo(oldIdentifier)));
assertThat(identifier, equalTo(ArtifactBindingUtils.computeArtifactBindingIdentifierString(newClientId)));
}
clientRepresentation = adminClient.realm(REALM_NAME)
.clients()
.findByClientId(SAML_CLIENT_ID_SALES_POST)
.get(0);
assertThat(clientRepresentation.getAttributes().get(SamlConfigAttributes.SAML_ARTIFACT_BINDING_IDENTIFIER), equalTo(oldIdentifier));
}
}