KEYCLOAK-6708 Fix NPE when email not set for email NameIDFormat

This commit is contained in:
Hynek Mlnarik 2018-07-26 19:00:38 +02:00 committed by Hynek Mlnařík
parent 771d7f1724
commit f43519a16e
11 changed files with 257 additions and 53 deletions

View file

@ -107,7 +107,7 @@ public class SAML2Request {
// Create a default NameIDPolicy // Create a default NameIDPolicy
NameIDPolicyType nameIDPolicy = new NameIDPolicyType(); NameIDPolicyType nameIDPolicy = new NameIDPolicyType();
nameIDPolicy.setAllowCreate(Boolean.TRUE); nameIDPolicy.setAllowCreate(Boolean.TRUE);
nameIDPolicy.setFormat(URI.create(this.nameIDFormat)); nameIDPolicy.setFormat(this.nameIDFormat == null ? null : URI.create(this.nameIDFormat));
authnRequest.setNameIDPolicy(nameIDPolicy); authnRequest.setNameIDPolicy(nameIDPolicy);

View file

@ -244,7 +244,7 @@ public class SAML2Response {
// subject -> nameid // subject -> nameid
NameIDType nameIDType = new NameIDType(); NameIDType nameIDType = new NameIDType();
nameIDType.setFormat(URI.create(idp.getNameIDFormat())); nameIDType.setFormat(idp.getNameIDFormat() == null ? null : URI.create(idp.getNameIDFormat()));
nameIDType.setValue(idp.getNameIDFormatValue()); nameIDType.setValue(idp.getNameIDFormatValue());
SubjectType.STSubType subType = new SubjectType.STSubType(); SubjectType.STSubType subType = new SubjectType.STSubType();

View file

@ -165,7 +165,7 @@ public class SamlProtocol implements LoginProtocol {
try { try {
ClientModel client = authSession.getClient(); 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) { if (error == Error.CANCELLED_BY_USER) {
UriBuilder builder = RealmsResource.protocolUrl(uriInfo).path(SamlService.class, "idpInitiatedSSO"); UriBuilder builder = RealmsResource.protocolUrl(uriInfo).path(SamlService.class, "idpInitiatedSSO");
Map<String, String> params = new HashMap<>(); Map<String, String> params = new HashMap<>();
@ -178,10 +178,21 @@ public class SamlProtocol implements LoginProtocol {
return ErrorPage.error(session, authSession, Response.Status.BAD_REQUEST, translateErrorToIdpInitiatedErrorMessage(error)); return ErrorPage.error(session, authSession, Response.Status.BAD_REQUEST, translateErrorToIdpInitiatedErrorMessage(error));
} }
} else { } else {
SAML2ErrorResponseBuilder builder = new SAML2ErrorResponseBuilder().destination(authSession.getRedirectUri()).issuer(getResponseIssuer(realm)).status(translateErrorToSAMLStatus(error).get()); return samlErrorMessage(
try { authSession, new SamlClient(client), isPostBinding(authSession),
JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder().relayState(authSession.getClientNote(GeneralConstants.RELAY_STATE)); authSession.getRedirectUri(), translateErrorToSAMLStatus(error), authSession.getClientNote(GeneralConstants.RELAY_STATE)
SamlClient samlClient = new SamlClient(client); );
}
} finally {
new AuthenticationSessionManager(session).removeAuthenticationSession(realm, authSession, true);
}
}
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(); KeyManager keyManager = session.keys();
if (samlClient.requiresRealmSignature()) { if (samlClient.requiresRealmSignature()) {
KeyManager.ActiveRsaKey keys = keyManager.getActiveRsaKey(realm); KeyManager.ActiveRsaKey keys = keyManager.getActiveRsaKey(realm);
@ -192,25 +203,23 @@ public class SamlProtocol implements LoginProtocol {
} }
binding.signatureAlgorithm(samlClient.getSignatureAlgorithm()).signWith(keyName, keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate()).signDocument(); binding.signatureAlgorithm(samlClient.getSignatureAlgorithm()).signWith(keyName, keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate()).signDocument();
} }
try {
// There is no support for encrypting status messages in SAML. // There is no support for encrypting status messages in SAML.
// Only assertions, attributes, base ID and name ID can be encrypted // Only assertions, attributes, base ID and name ID can be encrypted
// See Chapter 6 of saml-core-2.0-os.pdf // See Chapter 6 of saml-core-2.0-os.pdf
Document document = builder.buildDocument(); Document document = builder.buildDocument();
return buildErrorResponse(authSession, binding, document); return buildErrorResponse(isPostBinding, destination, binding, document);
} catch (Exception e) { } catch (Exception e) {
return ErrorPage.error(session, authSession, Response.Status.BAD_REQUEST, Messages.FAILED_TO_PROCESS_RESPONSE); return ErrorPage.error(session, authSession, Response.Status.BAD_REQUEST, Messages.FAILED_TO_PROCESS_RESPONSE);
} }
} }
} finally {
new AuthenticationSessionManager(session).removeAuthenticationSession(realm, authSession, true);
}
}
protected Response buildErrorResponse(AuthenticationSessionModel authSession, JaxrsSAML2BindingBuilder binding, Document document) throws ConfigurationException, ProcessingException, IOException { protected Response buildErrorResponse(boolean isPostBinding, String destination, JaxrsSAML2BindingBuilder binding, Document document) throws ConfigurationException, ProcessingException, IOException {
if (isPostBinding(authSession)) { if (isPostBinding) {
return binding.postBinding(document).response(authSession.getRedirectUri()); return binding.postBinding(document).response(destination);
} else { } 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) { protected String getNameId(String nameIdFormat, CommonClientSessionModel clientSession, UserSessionModel userSession) {
if (nameIdFormat.equals(JBossSAMLURIConstants.NAMEID_FORMAT_EMAIL.get())) { 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())) { } else if (nameIdFormat.equals(JBossSAMLURIConstants.NAMEID_FORMAT_TRANSIENT.get())) {
// "G-" stands for "generated" Add this for the slight possibility of collisions. // "G-" stands for "generated" Add this for the slight possibility of collisions.
return "G-" + UUID.randomUUID().toString(); return "G-" + UUID.randomUUID().toString();
@ -365,6 +378,13 @@ public class SamlProtocol implements LoginProtocol {
String nameIdFormat = getNameIdFormat(samlClient, clientSession); String nameIdFormat = getNameIdFormat(samlClient, clientSession);
String nameId = getNameId(nameIdFormat, clientSession, userSession); 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 // 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 // we'll need to send this back on a logout
clientSession.setNote(SAML_NAME_ID, nameId); clientSession.setNote(SAML_NAME_ID, nameId);

View file

@ -135,7 +135,7 @@ public class SamlEcpProfileService extends SamlService {
} }
@Override @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(); return Soap.createMessage().addToBody(document).build();
} }

View file

@ -16,6 +16,7 @@
*/ */
package org.keycloak.testsuite.updaters; package org.keycloak.testsuite.updaters;
import org.keycloak.admin.client.resource.IdentityProviderResource;
import org.keycloak.admin.client.resource.IdentityProvidersResource; import org.keycloak.admin.client.resource.IdentityProvidersResource;
import java.io.Closeable; import java.io.Closeable;
import javax.ws.rs.NotFoundException; import javax.ws.rs.NotFoundException;
@ -48,6 +49,10 @@ public class IdentityProviderCreator implements Closeable {
return this.resource; return this.resource;
} }
public IdentityProviderResource identityProvider() {
return this.resource().get(alias);
}
@Override @Override
public void close() throws IOException { public void close() throws IOException {
try { try {

View file

@ -25,6 +25,7 @@ import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
import org.keycloak.testsuite.util.matchers.*; import org.keycloak.testsuite.util.matchers.*;
import java.net.URI; import java.net.URI;
import java.util.Arrays;
import java.util.Map; import java.util.Map;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import org.apache.http.HttpResponse; import org.apache.http.HttpResponse;
@ -147,10 +148,10 @@ public class Matchers {
* @param expectedStatusCode * @param expectedStatusCode
* @return * @return
*/ */
public static <T> Matcher<SAML2Object> isSamlStatusResponse(JBossSAMLURIConstants expectedStatus) { public static <T> Matcher<SAML2Object> isSamlStatusResponse(JBossSAMLURIConstants... expectedStatus) {
return allOf( return allOf(
instanceOf(StatusResponseType.class), instanceOf(StatusResponseType.class),
new SamlStatusResponseTypeMatcher(is(expectedStatus.getUri())) new SamlStatusResponseTypeMatcher(Arrays.stream(expectedStatus).map(JBossSAMLURIConstants::getUri).toArray(i -> new URI[i]))
); );
} }
} }

View file

@ -6,8 +6,11 @@
package org.keycloak.testsuite.util.matchers; package org.keycloak.testsuite.util.matchers;
import org.keycloak.dom.saml.v2.SAML2Object; import org.keycloak.dom.saml.v2.SAML2Object;
import org.keycloak.dom.saml.v2.protocol.StatusCodeType;
import org.keycloak.dom.saml.v2.protocol.StatusResponseType; import org.keycloak.dom.saml.v2.protocol.StatusResponseType;
import java.net.URI; import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import org.hamcrest.*; import org.hamcrest.*;
import static org.hamcrest.Matchers.*; import static org.hamcrest.Matchers.*;
@ -17,28 +20,46 @@ import static org.hamcrest.Matchers.*;
*/ */
public class SamlStatusResponseTypeMatcher extends BaseMatcher<SAML2Object> { public class SamlStatusResponseTypeMatcher extends BaseMatcher<SAML2Object> {
private final Matcher<URI> statusMatcher; private final List<Matcher<URI>> statusMatchers;
public SamlStatusResponseTypeMatcher(URI statusMatcher) { public SamlStatusResponseTypeMatcher(URI... statusMatchers) {
this.statusMatcher = is(statusMatcher); this.statusMatchers = new ArrayList(statusMatchers.length);
for (URI uri : statusMatchers) {
this.statusMatchers.add(is(uri));
}
} }
public SamlStatusResponseTypeMatcher(Matcher<URI> statusMatcher) { public SamlStatusResponseTypeMatcher(List<Matcher<URI>> statusMatchers) {
this.statusMatcher = statusMatcher; this.statusMatchers = statusMatchers;
} }
@Override @Override
public boolean matches(Object item) { 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 @Override
public void describeMismatch(Object item, Description description) { 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 @Override
public void describeTo(Description description) { 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);
}
} }
} }

View file

@ -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_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_CLIENT_SALES_POST_ENC_PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDb7kwJPkGdU34hicplwfp6/WmNcaLh94TSc7Jyr9Undp5pkyLgb0DE7EIE+6kSs4LsqCb8HDkB0nLD5DXbBJFd8n0WGoKstelvtg6FtVJMnwN7k7yZbfkPECWH9zF70VeOo9vbzrApNRnct8ZhH5fbflRB4JMA9L9R+LbURdoSKQIDAQAB";
public static final String SAML_BROKER_ALIAS = "saml-broker";
@Override @Override
public void addTestRealms(List<RealmRepresentation> testRealms) { public void addTestRealms(List<RealmRepresentation> testRealms) {
@ -61,4 +62,8 @@ public abstract class AbstractSamlTest extends AbstractAuthTest {
.realmBaseUrl(UriBuilder.fromUri(getAuthServerRoot())) .realmBaseUrl(UriBuilder.fromUri(getAuthServerRoot()))
.build(realm); .build(realm);
} }
protected URI getSamlBrokerUrl(String realmName) {
return URI.create(getAuthServerRealmBase(realmName).toString() + "/broker/" + SAML_BROKER_ALIAS + "/endpoint");
}
} }

View file

@ -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);
}
}
}

View file

@ -49,7 +49,6 @@ import org.keycloak.testsuite.util.SamlClientBuilder;
import java.io.Closeable; import java.io.Closeable;
import java.io.IOException; import java.io.IOException;
import java.net.URI;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
@ -79,8 +78,6 @@ public class LogoutTest extends AbstractSamlTest {
private final AtomicReference<NameIDType> nameIdRef = new AtomicReference<>(); private final AtomicReference<NameIDType> nameIdRef = new AtomicReference<>();
private final AtomicReference<String> sessionIndexRef = new AtomicReference<>(); private final AtomicReference<String> sessionIndexRef = new AtomicReference<>();
private static final String SAML_BROKER_ALIAS = "saml-broker";
@Before @Before
public void setup() { public void setup() {
salesRep = adminClient.realm(REALM_NAME).clients().findByClientId(SAML_CLIENT_ID_SALES_POST).get(0); 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");
}
} }

View file

@ -1,5 +1,8 @@
package org.keycloak.testsuite.saml; 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.junit.Test;
import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RealmRepresentation;
@ -32,7 +35,7 @@ public class SamlConsentTest extends AbstractSamlTest {
public void rejectedConsentResponseTest() throws ParsingException, ConfigurationException, ProcessingException { public void rejectedConsentResponseTest() throws ParsingException, ConfigurationException, ProcessingException {
ClientRepresentation client = adminClient.realm(REALM_NAME) ClientRepresentation client = adminClient.realm(REALM_NAME)
.clients() .clients()
.findByClientId(SAML_CLIENT_ID_SALES_POST_ENC) .findByClientId(SAML_CLIENT_ID_SALES_POST)
.get(0); .get(0);
adminClient.realm(REALM_NAME) adminClient.realm(REALM_NAME)
@ -40,14 +43,14 @@ public class SamlConsentTest extends AbstractSamlTest {
.get(client.getId()) .get(client.getId())
.update(ClientBuilder.edit(client) .update(ClientBuilder.edit(client)
.consentRequired(true) .consentRequired(true)
.attribute("saml.encrypt", "false") //remove after RHSSO-797 .attribute(SamlProtocol.SAML_IDP_INITIATED_SSO_URL_NAME, "sales-post")
.attribute("saml_idp_initiated_sso_url_name", "sales-post-enc") .attribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_POST_ATTRIBUTE, SAML_ASSERTION_CONSUMER_URL_SALES_POST + "saml")
.attribute("saml_assertion_consumer_url_post", SAML_ASSERTION_CONSUMER_URL_SALES_POST_ENC + "saml") .attribute(SamlConfigAttributes.SAML_SERVER_SIGNATURE, "true")
.build()); .build());
log.debug("Log in using idp initiated login"); log.debug("Log in using idp initiated login");
SAMLDocumentHolder documentHolder = new SamlClientBuilder() 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() .login().user(bburkeUser).build()
.consentRequired().approveConsent(false).build() .consentRequired().approveConsent(false).build()
.getSamlResponse(Binding.POST); .getSamlResponse(Binding.POST);