keycloak-uncached
Changes
saml-core/src/main/java/org/keycloak/saml/processing/api/saml/v2/response/SAML2Response.java 2(+1 -1)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/updaters/IdentityProviderCreator.java 5(+5 -0)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/Matchers.java 5(+3 -2)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/matchers/SamlStatusResponseTypeMatcher.java 37(+29 -8)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/saml/AbstractSamlTest.java 5(+5 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/saml/BrokerTest.java 156(+156 -0)
Details
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/api/saml/v2/request/SAML2Request.java b/saml-core/src/main/java/org/keycloak/saml/processing/api/saml/v2/request/SAML2Request.java
index 5a131d2..8c558d1 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/api/saml/v2/request/SAML2Request.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/api/saml/v2/request/SAML2Request.java
@@ -107,7 +107,7 @@ public class SAML2Request {
// Create a default NameIDPolicy
NameIDPolicyType nameIDPolicy = new NameIDPolicyType();
nameIDPolicy.setAllowCreate(Boolean.TRUE);
- nameIDPolicy.setFormat(URI.create(this.nameIDFormat));
+ nameIDPolicy.setFormat(this.nameIDFormat == null ? null : URI.create(this.nameIDFormat));
authnRequest.setNameIDPolicy(nameIDPolicy);
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/api/saml/v2/response/SAML2Response.java b/saml-core/src/main/java/org/keycloak/saml/processing/api/saml/v2/response/SAML2Response.java
index e9a86b1..ab78fff 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/api/saml/v2/response/SAML2Response.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/api/saml/v2/response/SAML2Response.java
@@ -244,7 +244,7 @@ public class SAML2Response {
// subject -> nameid
NameIDType nameIDType = new NameIDType();
- nameIDType.setFormat(URI.create(idp.getNameIDFormat()));
+ nameIDType.setFormat(idp.getNameIDFormat() == null ? null : URI.create(idp.getNameIDFormat()));
nameIDType.setValue(idp.getNameIDFormatValue());
SubjectType.STSubType subType = new SubjectType.STSubType();
diff --git a/services/src/main/java/org/keycloak/protocol/saml/profile/ecp/SamlEcpProfileService.java b/services/src/main/java/org/keycloak/protocol/saml/profile/ecp/SamlEcpProfileService.java
index c15444b..56b804b 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/profile/ecp/SamlEcpProfileService.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/profile/ecp/SamlEcpProfileService.java
@@ -135,7 +135,7 @@ public class SamlEcpProfileService extends SamlService {
}
@Override
- protected Response buildErrorResponse(AuthenticationSessionModel authSession, JaxrsSAML2BindingBuilder binding, Document document) throws ConfigurationException, ProcessingException, IOException {
+ protected Response buildErrorResponse(boolean isPostBinding, String uri, JaxrsSAML2BindingBuilder binding, Document document) throws ConfigurationException, ProcessingException, IOException {
return Soap.createMessage().addToBody(document).build();
}
diff --git a/services/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java b/services/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
index 49d2e3c..52d271a 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
@@ -165,7 +165,7 @@ public class SamlProtocol implements LoginProtocol {
try {
ClientModel client = authSession.getClient();
- if ("true".equals(client.getAttribute(SAML_IDP_INITIATED_LOGIN))) {
+ if ("true".equals(authSession.getClientNote(SAML_IDP_INITIATED_LOGIN))) {
if (error == Error.CANCELLED_BY_USER) {
UriBuilder builder = RealmsResource.protocolUrl(uriInfo).path(SamlService.class, "idpInitiatedSSO");
Map<String, String> params = new HashMap<>();
@@ -178,39 +178,48 @@ public class SamlProtocol implements LoginProtocol {
return ErrorPage.error(session, authSession, Response.Status.BAD_REQUEST, translateErrorToIdpInitiatedErrorMessage(error));
}
} else {
- SAML2ErrorResponseBuilder builder = new SAML2ErrorResponseBuilder().destination(authSession.getRedirectUri()).issuer(getResponseIssuer(realm)).status(translateErrorToSAMLStatus(error).get());
- try {
- JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder().relayState(authSession.getClientNote(GeneralConstants.RELAY_STATE));
- SamlClient samlClient = new SamlClient(client);
- KeyManager keyManager = session.keys();
- if (samlClient.requiresRealmSignature()) {
- KeyManager.ActiveRsaKey keys = keyManager.getActiveRsaKey(realm);
- String keyName = samlClient.getXmlSigKeyInfoKeyNameTransformer().getKeyName(keys.getKid(), keys.getCertificate());
- String canonicalization = samlClient.getCanonicalizationMethod();
- if (canonicalization != null) {
- binding.canonicalizationMethod(canonicalization);
- }
- binding.signatureAlgorithm(samlClient.getSignatureAlgorithm()).signWith(keyName, keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate()).signDocument();
- }
- // There is no support for encrypting status messages in SAML.
- // Only assertions, attributes, base ID and name ID can be encrypted
- // See Chapter 6 of saml-core-2.0-os.pdf
- Document document = builder.buildDocument();
- return buildErrorResponse(authSession, binding, document);
- } catch (Exception e) {
- return ErrorPage.error(session, authSession, Response.Status.BAD_REQUEST, Messages.FAILED_TO_PROCESS_RESPONSE);
- }
+ return samlErrorMessage(
+ authSession, new SamlClient(client), isPostBinding(authSession),
+ authSession.getRedirectUri(), translateErrorToSAMLStatus(error), authSession.getClientNote(GeneralConstants.RELAY_STATE)
+ );
}
} finally {
new AuthenticationSessionManager(session).removeAuthenticationSession(realm, authSession, true);
}
}
- protected Response buildErrorResponse(AuthenticationSessionModel authSession, JaxrsSAML2BindingBuilder binding, Document document) throws ConfigurationException, ProcessingException, IOException {
- if (isPostBinding(authSession)) {
- return binding.postBinding(document).response(authSession.getRedirectUri());
+ private Response samlErrorMessage(
+ AuthenticationSessionModel authSession, SamlClient samlClient, boolean isPostBinding,
+ String destination, JBossSAMLURIConstants statusDetail, String relayState) {
+ JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder().relayState(relayState);
+ SAML2ErrorResponseBuilder builder = new SAML2ErrorResponseBuilder().destination(destination).issuer(getResponseIssuer(realm)).status(statusDetail.get());
+ KeyManager keyManager = session.keys();
+ if (samlClient.requiresRealmSignature()) {
+ KeyManager.ActiveRsaKey keys = keyManager.getActiveRsaKey(realm);
+ String keyName = samlClient.getXmlSigKeyInfoKeyNameTransformer().getKeyName(keys.getKid(), keys.getCertificate());
+ String canonicalization = samlClient.getCanonicalizationMethod();
+ if (canonicalization != null) {
+ binding.canonicalizationMethod(canonicalization);
+ }
+ binding.signatureAlgorithm(samlClient.getSignatureAlgorithm()).signWith(keyName, keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate()).signDocument();
+ }
+
+ try {
+ // There is no support for encrypting status messages in SAML.
+ // Only assertions, attributes, base ID and name ID can be encrypted
+ // See Chapter 6 of saml-core-2.0-os.pdf
+ Document document = builder.buildDocument();
+ return buildErrorResponse(isPostBinding, destination, binding, document);
+ } catch (Exception e) {
+ return ErrorPage.error(session, authSession, Response.Status.BAD_REQUEST, Messages.FAILED_TO_PROCESS_RESPONSE);
+ }
+ }
+
+ protected Response buildErrorResponse(boolean isPostBinding, String destination, JaxrsSAML2BindingBuilder binding, Document document) throws ConfigurationException, ProcessingException, IOException {
+ if (isPostBinding) {
+ return binding.postBinding(document).response(destination);
} else {
- return binding.redirectBinding(document).response(authSession.getRedirectUri());
+ return binding.redirectBinding(document).response(destination);
}
}
@@ -302,7 +311,11 @@ public class SamlProtocol implements LoginProtocol {
protected String getNameId(String nameIdFormat, CommonClientSessionModel clientSession, UserSessionModel userSession) {
if (nameIdFormat.equals(JBossSAMLURIConstants.NAMEID_FORMAT_EMAIL.get())) {
- return userSession.getUser().getEmail();
+ final String email = userSession.getUser().getEmail();
+ if (email == null) {
+ logger.debugf("E-mail of the user %s has to be set for %s NameIDFormat", userSession.getUser().getUsername(), JBossSAMLURIConstants.NAMEID_FORMAT_EMAIL.get());
+ }
+ return email;
} else if (nameIdFormat.equals(JBossSAMLURIConstants.NAMEID_FORMAT_TRANSIENT.get())) {
// "G-" stands for "generated" Add this for the slight possibility of collisions.
return "G-" + UUID.randomUUID().toString();
@@ -365,6 +378,13 @@ public class SamlProtocol implements LoginProtocol {
String nameIdFormat = getNameIdFormat(samlClient, clientSession);
String nameId = getNameId(nameIdFormat, clientSession, userSession);
+ if (nameId == null) {
+ return samlErrorMessage(
+ null, samlClient, isPostBinding(clientSession),
+ redirectUri, JBossSAMLURIConstants.STATUS_INVALID_NAMEIDPOLICY, relayState
+ );
+ }
+
// save NAME_ID and format in clientSession as they may be persistent or transient or email and not username
// we'll need to send this back on a logout
clientSession.setNote(SAML_NAME_ID, nameId);
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/updaters/IdentityProviderCreator.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/updaters/IdentityProviderCreator.java
index 15c50cf..de3da06 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/updaters/IdentityProviderCreator.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/updaters/IdentityProviderCreator.java
@@ -16,6 +16,7 @@
*/
package org.keycloak.testsuite.updaters;
+import org.keycloak.admin.client.resource.IdentityProviderResource;
import org.keycloak.admin.client.resource.IdentityProvidersResource;
import java.io.Closeable;
import javax.ws.rs.NotFoundException;
@@ -48,6 +49,10 @@ public class IdentityProviderCreator implements Closeable {
return this.resource;
}
+ public IdentityProviderResource identityProvider() {
+ return this.resource().get(alias);
+ }
+
@Override
public void close() throws IOException {
try {
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/Matchers.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/Matchers.java
index 5985431..ffc2de4 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/Matchers.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/Matchers.java
@@ -25,6 +25,7 @@ import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
import org.keycloak.testsuite.util.matchers.*;
import java.net.URI;
+import java.util.Arrays;
import java.util.Map;
import javax.ws.rs.core.Response;
import org.apache.http.HttpResponse;
@@ -147,10 +148,10 @@ public class Matchers {
* @param expectedStatusCode
* @return
*/
- public static <T> Matcher<SAML2Object> isSamlStatusResponse(JBossSAMLURIConstants expectedStatus) {
+ public static <T> Matcher<SAML2Object> isSamlStatusResponse(JBossSAMLURIConstants... expectedStatus) {
return allOf(
instanceOf(StatusResponseType.class),
- new SamlStatusResponseTypeMatcher(is(expectedStatus.getUri()))
+ new SamlStatusResponseTypeMatcher(Arrays.stream(expectedStatus).map(JBossSAMLURIConstants::getUri).toArray(i -> new URI[i]))
);
}
}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/matchers/SamlStatusResponseTypeMatcher.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/matchers/SamlStatusResponseTypeMatcher.java
index d76a6dd..e12f95b 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/matchers/SamlStatusResponseTypeMatcher.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/matchers/SamlStatusResponseTypeMatcher.java
@@ -6,8 +6,11 @@
package org.keycloak.testsuite.util.matchers;
import org.keycloak.dom.saml.v2.SAML2Object;
+import org.keycloak.dom.saml.v2.protocol.StatusCodeType;
import org.keycloak.dom.saml.v2.protocol.StatusResponseType;
import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
import org.hamcrest.*;
import static org.hamcrest.Matchers.*;
@@ -17,28 +20,46 @@ import static org.hamcrest.Matchers.*;
*/
public class SamlStatusResponseTypeMatcher extends BaseMatcher<SAML2Object> {
- private final Matcher<URI> statusMatcher;
+ private final List<Matcher<URI>> statusMatchers;
- public SamlStatusResponseTypeMatcher(URI statusMatcher) {
- this.statusMatcher = is(statusMatcher);
+ public SamlStatusResponseTypeMatcher(URI... statusMatchers) {
+ this.statusMatchers = new ArrayList(statusMatchers.length);
+ for (URI uri : statusMatchers) {
+ this.statusMatchers.add(is(uri));
+ }
}
- public SamlStatusResponseTypeMatcher(Matcher<URI> statusMatcher) {
- this.statusMatcher = statusMatcher;
+ public SamlStatusResponseTypeMatcher(List<Matcher<URI>> statusMatchers) {
+ this.statusMatchers = statusMatchers;
}
@Override
public boolean matches(Object item) {
- return statusMatcher.matches(((StatusResponseType) item).getStatus().getStatusCode().getValue());
+ StatusCodeType statusCode = ((StatusResponseType) item).getStatus().getStatusCode();
+ for (Matcher<URI> statusMatcher : statusMatchers) {
+ if (! statusMatcher.matches(statusCode.getValue())) {
+ return false;
+ }
+ statusCode = statusCode.getStatusCode();
+ }
+ return true;
}
@Override
public void describeMismatch(Object item, Description description) {
- description.appendText("was ").appendValue(((StatusResponseType) item).getStatus().getStatusCode().getValue());
+ StatusCodeType statusCode = ((StatusResponseType) item).getStatus().getStatusCode();
+ description.appendText("was ");
+ while (statusCode != null) {
+ description.appendText("/").appendValue(statusCode.getValue());
+ statusCode = statusCode.getStatusCode();
+ }
}
@Override
public void describeTo(Description description) {
- description.appendText("SAML status response status matches ").appendDescriptionOf(this.statusMatcher);
+ description.appendText("SAML status response status matches ");
+ for (Matcher<URI> statusMatcher : statusMatchers) {
+ description.appendText("/").appendDescriptionOf(statusMatcher);
+ }
}
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/saml/AbstractSamlTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/saml/AbstractSamlTest.java
index 46c29d8..f652c3c 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/saml/AbstractSamlTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/saml/AbstractSamlTest.java
@@ -40,6 +40,7 @@ public abstract class AbstractSamlTest extends AbstractAuthTest {
public static final String SAML_CLIENT_SALES_POST_ENC_PRIVATE_KEY = "MIICXQIBAAKBgQDb7kwJPkGdU34hicplwfp6/WmNcaLh94TSc7Jyr9Undp5pkyLgb0DE7EIE+6kSs4LsqCb8HDkB0nLD5DXbBJFd8n0WGoKstelvtg6FtVJMnwN7k7yZbfkPECWH9zF70VeOo9vbzrApNRnct8ZhH5fbflRB4JMA9L9R+LbURdoSKQIDAQABAoGBANtbZG9bruoSGp2s5zhzLzd4hczT6Jfk3o9hYjzNb5Z60ymN3Z1omXtQAdEiiNHkRdNxK+EM7TcKBfmoJqcaeTkW8cksVEAW23ip8W9/XsLqmbU2mRrJiKa+KQNDSHqJi1VGyimi4DDApcaqRZcaKDFXg2KDr/Qt5JFD/o9IIIPZAkEA+ZENdBIlpbUfkJh6Ln+bUTss/FZ1FsrcPZWu13rChRMrsmXsfzu9kZUWdUeQ2Dj5AoW2Q7L/cqdGXS7Mm5XhcwJBAOGZq9axJY5YhKrsksvYRLhQbStmGu5LG75suF+rc/44sFq+aQM7+oeRr4VY88Mvz7mk4esdfnk7ae+cCazqJvMCQQCx1L1cZw3yfRSn6S6u8XjQMjWE/WpjulujeoRiwPPY9WcesOgLZZtYIH8nRL6ehEJTnMnahbLmlPFbttxPRUanAkA11MtSIVcKzkhp2KV2ipZrPJWwI18NuVJXb+3WtjypTrGWFZVNNkSjkLnHIeCYlJIGhDd8OL9zAiBXEm6kmgLNAkBWAg0tK2hCjvzsaA505gWQb4X56uKWdb0IzN+fOLB3Qt7+fLqbVQNQoNGzqey6B4MoS1fUKAStqdGTFYPG/+9t";
public static final String SAML_CLIENT_SALES_POST_ENC_PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDb7kwJPkGdU34hicplwfp6/WmNcaLh94TSc7Jyr9Undp5pkyLgb0DE7EIE+6kSs4LsqCb8HDkB0nLD5DXbBJFd8n0WGoKstelvtg6FtVJMnwN7k7yZbfkPECWH9zF70VeOo9vbzrApNRnct8ZhH5fbflRB4JMA9L9R+LbURdoSKQIDAQAB";
+ public static final String SAML_BROKER_ALIAS = "saml-broker";
@Override
public void addTestRealms(List<RealmRepresentation> testRealms) {
@@ -61,4 +62,8 @@ public abstract class AbstractSamlTest extends AbstractAuthTest {
.realmBaseUrl(UriBuilder.fromUri(getAuthServerRoot()))
.build(realm);
}
+
+ protected URI getSamlBrokerUrl(String realmName) {
+ return URI.create(getAuthServerRealmBase(realmName).toString() + "/broker/" + SAML_BROKER_ALIAS + "/endpoint");
+ }
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/saml/BrokerTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/saml/BrokerTest.java
new file mode 100644
index 0000000..85809fe
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/saml/BrokerTest.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2018 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.keycloak.admin.client.resource.ClientsResource;
+import org.keycloak.admin.client.resource.RealmResource;
+import org.keycloak.authentication.authenticators.broker.IdpReviewProfileAuthenticatorFactory;
+import org.keycloak.broker.saml.SAMLIdentityProviderConfig;
+import org.keycloak.broker.saml.SAMLIdentityProviderFactory;
+import org.keycloak.dom.saml.v2.SAML2Object;
+import org.keycloak.dom.saml.v2.assertion.AttributeStatementType;
+import org.keycloak.dom.saml.v2.assertion.AttributeStatementType.ASTChoiceType;
+import org.keycloak.dom.saml.v2.assertion.AttributeType;
+import org.keycloak.dom.saml.v2.protocol.AuthnRequestType;
+import org.keycloak.dom.saml.v2.protocol.NameIDPolicyType;
+import org.keycloak.dom.saml.v2.protocol.ResponseType;
+import org.keycloak.models.AuthenticationExecutionModel.Requirement;
+import org.keycloak.representations.idm.AuthenticationExecutionInfoRepresentation;
+import org.keycloak.representations.idm.IdentityProviderRepresentation;
+import org.keycloak.saml.SAML2LoginResponseBuilder;
+import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
+import org.keycloak.saml.common.exceptions.ConfigurationException;
+import org.keycloak.saml.common.exceptions.ProcessingException;
+import org.keycloak.saml.processing.core.saml.v2.common.SAMLDocumentHolder;
+import org.keycloak.testsuite.updaters.IdentityProviderCreator;
+import org.keycloak.testsuite.util.IdentityProviderBuilder;
+import org.keycloak.testsuite.util.SamlClientBuilder;
+import java.io.IOException;
+import java.util.List;
+import java.util.Objects;
+import java.util.UUID;
+import org.junit.Assert;
+import org.junit.Test;
+import static org.junit.Assert.assertThat;
+import static org.keycloak.testsuite.saml.AbstractSamlTest.REALM_NAME;
+import static org.keycloak.testsuite.saml.AbstractSamlTest.SAML_ASSERTION_CONSUMER_URL_SALES_POST;
+import static org.keycloak.testsuite.saml.AbstractSamlTest.SAML_CLIENT_ID_SALES_POST;
+import static org.keycloak.testsuite.util.Matchers.isSamlStatusResponse;
+import static org.keycloak.testsuite.util.SamlClient.Binding.POST;
+import static org.keycloak.testsuite.util.SamlClient.Binding.REDIRECT;
+
+/**
+ *
+ * @author hmlnarik
+ */
+public class BrokerTest extends AbstractSamlTest {
+
+ private IdentityProviderRepresentation addIdentityProvider() {
+ IdentityProviderRepresentation identityProvider = IdentityProviderBuilder.create()
+ .providerId(SAMLIdentityProviderFactory.PROVIDER_ID)
+ .alias(SAML_BROKER_ALIAS)
+ .displayName("SAML")
+ .setAttribute(SAMLIdentityProviderConfig.SINGLE_SIGN_ON_SERVICE_URL, "http://saml.idp/saml")
+ .setAttribute(SAMLIdentityProviderConfig.SINGLE_LOGOUT_SERVICE_URL, "http://saml.idp/saml")
+ .setAttribute(SAMLIdentityProviderConfig.NAME_ID_POLICY_FORMAT, JBossSAMLURIConstants.NAMEID_FORMAT_EMAIL.get())
+ .setAttribute(SAMLIdentityProviderConfig.POST_BINDING_RESPONSE, "false")
+ .setAttribute(SAMLIdentityProviderConfig.POST_BINDING_AUTHN_REQUEST, "false")
+ .setAttribute(SAMLIdentityProviderConfig.BACKCHANNEL_SUPPORTED, "false")
+ .build();
+ return identityProvider;
+ }
+
+ private SAML2Object createAuthnResponse(SAML2Object so) {
+ AuthnRequestType req = (AuthnRequestType) so;
+ try {
+ final ResponseType res = new SAML2LoginResponseBuilder()
+ .requestID(req.getID())
+ .destination(req.getAssertionConsumerServiceURL().toString())
+ .issuer("http://saml.idp/saml")
+ .assertionExpiration(1000000)
+ .subjectExpiration(1000000)
+ .requestIssuer(getAuthServerRealmBase(REALM_NAME).toString())
+ .sessionIndex("idp:" + UUID.randomUUID())
+ .buildModel();
+
+ AttributeStatementType attrStatement = new AttributeStatementType();
+ AttributeType attribute = new AttributeType("mail");
+ attribute.addAttributeValue("v@w.x");
+ attrStatement.addAttribute(new ASTChoiceType(attribute));
+
+ res.getAssertions().get(0).getAssertion().addStatement(attrStatement);
+
+ return res;
+ } catch (ConfigurationException | ProcessingException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+ @Test
+ public void testLogoutPropagatesToSamlIdentityProvider() throws IOException {
+ final RealmResource realm = adminClient.realm(REALM_NAME);
+ final ClientsResource clients = realm.clients();
+
+ AuthenticationExecutionInfoRepresentation reviewProfileAuthenticator = null;
+ String firstBrokerLoginFlowAlias = null;
+ try (IdentityProviderCreator idp = new IdentityProviderCreator(realm, addIdentityProvider())) {
+ IdentityProviderRepresentation idpRepresentation = idp.identityProvider().toRepresentation();
+ firstBrokerLoginFlowAlias = idpRepresentation.getFirstBrokerLoginFlowAlias();
+ List<AuthenticationExecutionInfoRepresentation> executions = realm.flows().getExecutions(firstBrokerLoginFlowAlias);
+ reviewProfileAuthenticator = executions.stream()
+ .filter(ex -> Objects.equals(ex.getProviderId(), IdpReviewProfileAuthenticatorFactory.PROVIDER_ID))
+ .findFirst()
+ .orElseGet(() -> { Assert.fail("Could not find update profile in first broker login flow"); return null; });
+
+ reviewProfileAuthenticator.setRequirement(Requirement.DISABLED.name());
+ realm.flows().updateExecutions(firstBrokerLoginFlowAlias, reviewProfileAuthenticator);
+
+ SAMLDocumentHolder samlResponse = new SamlClientBuilder()
+ .authnRequest(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST, SAML_ASSERTION_CONSUMER_URL_SALES_POST, POST)
+ .transformObject(ar -> {
+ NameIDPolicyType nameIDPolicy = new NameIDPolicyType();
+ nameIDPolicy.setAllowCreate(Boolean.TRUE);
+ nameIDPolicy.setFormat(JBossSAMLURIConstants.NAMEID_FORMAT_EMAIL.getUri());
+
+ ar.setNameIDPolicy(nameIDPolicy);
+ return ar;
+ })
+ .build()
+
+ .login().idp(SAML_BROKER_ALIAS).build()
+
+ // Virtually perform login at IdP (return artificial SAML response)
+ .processSamlResponse(REDIRECT)
+ .transformObject(this::createAuthnResponse)
+ .targetAttributeSamlResponse()
+ .targetUri(getSamlBrokerUrl(REALM_NAME))
+ .build()
+ .followOneRedirect() // first-broker-login
+ .followOneRedirect() // after-first-broker-login
+ .getSamlResponse(POST);
+
+ assertThat(samlResponse.getSamlObject(), isSamlStatusResponse(
+ JBossSAMLURIConstants.STATUS_RESPONDER,
+ JBossSAMLURIConstants.STATUS_INVALID_NAMEIDPOLICY
+ ));
+ } finally {
+ reviewProfileAuthenticator.setRequirement(Requirement.REQUIRED.name());
+ realm.flows().updateExecutions(firstBrokerLoginFlowAlias, reviewProfileAuthenticator);
+ }
+ }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/saml/LogoutTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/saml/LogoutTest.java
index 19881c5..09fb266 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/saml/LogoutTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/saml/LogoutTest.java
@@ -49,7 +49,6 @@ import org.keycloak.testsuite.util.SamlClientBuilder;
import java.io.Closeable;
import java.io.IOException;
-import java.net.URI;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
@@ -79,8 +78,6 @@ public class LogoutTest extends AbstractSamlTest {
private final AtomicReference<NameIDType> nameIdRef = new AtomicReference<>();
private final AtomicReference<String> sessionIndexRef = new AtomicReference<>();
- private static final String SAML_BROKER_ALIAS = "saml-broker";
-
@Before
public void setup() {
salesRep = adminClient.realm(REALM_NAME).clients().findByClientId(SAML_CLIENT_ID_SALES_POST).get(0);
@@ -414,8 +411,4 @@ public class LogoutTest extends AbstractSamlTest {
}
}
- private URI getSamlBrokerUrl(String realmName) {
- return URI.create(getAuthServerRealmBase(realmName).toString() + "/broker/" + SAML_BROKER_ALIAS + "/endpoint");
- }
-
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/saml/SamlConsentTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/saml/SamlConsentTest.java
index c91d19d..ec2aedc 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/saml/SamlConsentTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/saml/SamlConsentTest.java
@@ -1,5 +1,8 @@
package org.keycloak.testsuite.saml;
+import org.keycloak.protocol.saml.SamlClient;
+import org.keycloak.protocol.saml.SamlConfigAttributes;
+import org.keycloak.protocol.saml.SamlProtocol;
import org.junit.Test;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
@@ -32,7 +35,7 @@ public class SamlConsentTest extends AbstractSamlTest {
public void rejectedConsentResponseTest() throws ParsingException, ConfigurationException, ProcessingException {
ClientRepresentation client = adminClient.realm(REALM_NAME)
.clients()
- .findByClientId(SAML_CLIENT_ID_SALES_POST_ENC)
+ .findByClientId(SAML_CLIENT_ID_SALES_POST)
.get(0);
adminClient.realm(REALM_NAME)
@@ -40,14 +43,14 @@ public class SamlConsentTest extends AbstractSamlTest {
.get(client.getId())
.update(ClientBuilder.edit(client)
.consentRequired(true)
- .attribute("saml.encrypt", "false") //remove after RHSSO-797
- .attribute("saml_idp_initiated_sso_url_name", "sales-post-enc")
- .attribute("saml_assertion_consumer_url_post", SAML_ASSERTION_CONSUMER_URL_SALES_POST_ENC + "saml")
+ .attribute(SamlProtocol.SAML_IDP_INITIATED_SSO_URL_NAME, "sales-post")
+ .attribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_POST_ATTRIBUTE, SAML_ASSERTION_CONSUMER_URL_SALES_POST + "saml")
+ .attribute(SamlConfigAttributes.SAML_SERVER_SIGNATURE, "true")
.build());
log.debug("Log in using idp initiated login");
SAMLDocumentHolder documentHolder = new SamlClientBuilder()
- .idpInitiatedLogin(getAuthServerSamlEndpoint(REALM_NAME), "sales-post-enc").build()
+ .authnRequest(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST, SAML_ASSERTION_CONSUMER_URL_SALES_POST, Binding.POST).build()
.login().user(bburkeUser).build()
.consentRequired().approveConsent(false).build()
.getSamlResponse(Binding.POST);