KEYCLOAK-5581 Fix SAML identity broker context serialization

This commit is contained in:
Hynek Mlnarik 2017-11-03 18:19:12 +01:00
parent 1ac9bd510b
commit fe2f65daac
12 changed files with 365 additions and 27 deletions

View file

@ -101,7 +101,7 @@ public class SAMLResponseWriter extends BaseWriter {
for (ResponseType.RTChoiceType choiceType : choiceTypes) { for (ResponseType.RTChoiceType choiceType : choiceTypes) {
AssertionType assertion = choiceType.getAssertion(); AssertionType assertion = choiceType.getAssertion();
if (assertion != null) { if (assertion != null) {
assertionWriter.write(assertion); assertionWriter.write(assertion, forceWriteDsigNamespace);
} }
EncryptedAssertionType encryptedAssertion = choiceType.getEncryptedAssertion(); EncryptedAssertionType encryptedAssertion = choiceType.getEncryptedAssertion();

View file

@ -367,7 +367,9 @@ public class XMLSignatureUtil {
public static Document sign(Document doc, String keyName, KeyPair keyPair, String digestMethod, String signatureMethod, String referenceURI, public static Document sign(Document doc, String keyName, KeyPair keyPair, String digestMethod, String signatureMethod, String referenceURI,
X509Certificate x509Certificate, String canonicalizationMethodType) X509Certificate x509Certificate, String canonicalizationMethodType)
throws GeneralSecurityException, MarshalException, XMLSignatureException { throws GeneralSecurityException, MarshalException, XMLSignatureException {
logger.trace("Document to be signed=" + DocumentUtil.asString(doc)); if (logger.isTraceEnabled()) {
logger.trace("Document to be signed=" + DocumentUtil.asString(doc));
}
PrivateKey signingKey = keyPair.getPrivate(); PrivateKey signingKey = keyPair.getPrivate();
PublicKey publicKey = keyPair.getPublic(); PublicKey publicKey = keyPair.getPublic();
@ -398,7 +400,9 @@ public class XMLSignatureUtil {
String referenceURI = dto.getReferenceURI(); String referenceURI = dto.getReferenceURI();
String signatureMethod = dto.getSignatureMethod(); String signatureMethod = dto.getSignatureMethod();
logger.trace("Document to be signed=" + DocumentUtil.asString(doc)); if (logger.isTraceEnabled()) {
logger.trace("Document to be signed=" + DocumentUtil.asString(doc));
}
PrivateKey signingKey = keyPair.getPrivate(); PrivateKey signingKey = keyPair.getPrivate();
PublicKey publicKey = keyPair.getPublic(); PublicKey publicKey = keyPair.getPublic();
@ -407,6 +411,10 @@ public class XMLSignatureUtil {
signImpl(dsc, digestMethod, signatureMethod, referenceURI, keyName, publicKey, dto.getX509Certificate(), canonicalizationMethodType); signImpl(dsc, digestMethod, signatureMethod, referenceURI, keyName, publicKey, dto.getX509Certificate(), canonicalizationMethodType);
if (logger.isTraceEnabled()) {
logger.trace("Signed document=" + DocumentUtil.asString(doc));
}
return doc; return doc;
} }

View file

@ -129,11 +129,16 @@ public class SAMLDataMarshallerTest {
ResponseType response = (ResponseType) parsedObject; ResponseType response = (ResponseType) parsedObject;
SAMLDataMarshaller serializer = new SAMLDataMarshaller(); SAMLDataMarshaller serializer = new SAMLDataMarshaller();
String serialized = serializer.serialize(response.getAssertions().get(0).getAssertion()); String serializedResponse = serializer.serialize(response);
String serializedAssertion = serializer.serialize(response.getAssertions().get(0).getAssertion());
AssertionType deserialized = serializer.deserialize(serialized, AssertionType.class); ResponseType deserializedResponse = serializer.deserialize(serializedResponse, ResponseType.class);
assertThat(deserialized, CoreMatchers.notNullValue()); assertThat(deserializedResponse, CoreMatchers.notNullValue());
assertThat(deserialized.getID(), CoreMatchers.is("id-4r-Xj702KQsM0gJyu3Fqpuwfe-LvDrEcQZpxKrhC")); assertThat(deserializedResponse.getID(), CoreMatchers.is("id-EYgqtumZ-P-Ph7t37f-brUKMwB5MKix0sNjr-0YV"));
AssertionType deserializedAssertion = serializer.deserialize(serializedAssertion, AssertionType.class);
assertThat(deserializedAssertion, CoreMatchers.notNullValue());
assertThat(deserializedAssertion.getID(), CoreMatchers.is("id-4r-Xj702KQsM0gJyu3Fqpuwfe-LvDrEcQZpxKrhC"));
} }
} }
} }

View file

@ -31,13 +31,16 @@ import org.keycloak.testsuite.util.saml.CreateAuthnRequestStepBuilder;
import org.keycloak.testsuite.util.saml.CreateLogoutRequestStepBuilder; import org.keycloak.testsuite.util.saml.CreateLogoutRequestStepBuilder;
import org.keycloak.testsuite.util.saml.IdPInitiatedLoginBuilder; import org.keycloak.testsuite.util.saml.IdPInitiatedLoginBuilder;
import org.keycloak.testsuite.util.saml.LoginBuilder; import org.keycloak.testsuite.util.saml.LoginBuilder;
import org.keycloak.testsuite.util.saml.UpdateProfileBuilder;
import org.keycloak.testsuite.util.saml.ModifySamlResponseStepBuilder; import org.keycloak.testsuite.util.saml.ModifySamlResponseStepBuilder;
import org.keycloak.testsuite.util.saml.RequiredConsentBuilder; import org.keycloak.testsuite.util.saml.RequiredConsentBuilder;
import javax.ws.rs.core.Response.Status;
import org.apache.http.HttpResponse; import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.client.methods.HttpUriRequest;
import org.hamcrest.Matcher; import org.hamcrest.Matcher;
import org.junit.Assert; import org.junit.Assert;
import org.w3c.dom.Document; import org.w3c.dom.Document;
import static org.hamcrest.Matchers.notNullValue;
/** /**
* *
@ -157,6 +160,11 @@ public class SamlClientBuilder {
return addStepBuilder(new LoginBuilder(this)); return addStepBuilder(new LoginBuilder(this));
} }
/** Handles update profile page after login */
public UpdateProfileBuilder updateProfile() {
return addStepBuilder(new UpdateProfileBuilder(this));
}
/** Starts IdP-initiated flow for the given client */ /** Starts IdP-initiated flow for the given client */
public IdPInitiatedLoginBuilder idpInitiatedLogin(URI authServerSamlUrl, String clientId) { public IdPInitiatedLoginBuilder idpInitiatedLogin(URI authServerSamlUrl, String clientId) {
return addStepBuilder(new IdPInitiatedLoginBuilder(authServerSamlUrl, clientId, this)); return addStepBuilder(new IdPInitiatedLoginBuilder(authServerSamlUrl, clientId, this));
@ -191,4 +199,14 @@ public class SamlClientBuilder {
return this; return this;
} }
public SamlClientBuilder followOneRedirect() {
return
doNotFollowRedirects()
.addStep((client, currentURI, currentResponse, context) -> {
Assert.assertThat(currentResponse, Matchers.statusCodeIsHC(Status.FOUND));
Assert.assertThat("Location header not found", currentResponse.getFirstHeader("Location"), notNullValue());
return new HttpGet(currentResponse.getFirstHeader("Location").getValue());
});
}
} }

View file

@ -18,6 +18,7 @@ package org.keycloak.testsuite.util.saml;
import org.keycloak.testsuite.util.SamlClientBuilder; import org.keycloak.testsuite.util.SamlClientBuilder;
import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.admin.Users;
import org.keycloak.testsuite.util.SamlClient.Step; import org.keycloak.testsuite.util.SamlClient.Step;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.net.URI; import java.net.URI;
@ -36,6 +37,7 @@ import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.message.BasicNameValuePair; import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils; import org.apache.http.util.EntityUtils;
import org.hamcrest.Matchers;
import org.jsoup.Jsoup; import org.jsoup.Jsoup;
import org.jsoup.nodes.Element; import org.jsoup.nodes.Element;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
@ -52,6 +54,7 @@ public class LoginBuilder implements Step {
private final SamlClientBuilder clientBuilder; private final SamlClientBuilder clientBuilder;
private UserRepresentation user; private UserRepresentation user;
private boolean sso = false; private boolean sso = false;
private String idpAlias;
public LoginBuilder(SamlClientBuilder clientBuilder) { public LoginBuilder(SamlClientBuilder clientBuilder) {
this.clientBuilder = clientBuilder; this.clientBuilder = clientBuilder;
@ -66,7 +69,7 @@ public class LoginBuilder implements Step {
String loginPageText = EntityUtils.toString(currentResponse.getEntity(), "UTF-8"); String loginPageText = EntityUtils.toString(currentResponse.getEntity(), "UTF-8");
assertThat(loginPageText, containsString("login")); assertThat(loginPageText, containsString("login"));
return handleLoginPage(loginPageText); return handleLoginPage(loginPageText, currentURI);
} }
} }
@ -79,11 +82,29 @@ public class LoginBuilder implements Step {
return this; return this;
} }
public LoginBuilder user(String userName, String password) {
this.user = new UserRepresentation();
this.user.setUsername(userName);
Users.setPasswordFor(user, password);
return this;
}
public LoginBuilder sso(boolean sso) { public LoginBuilder sso(boolean sso) {
this.sso = sso; this.sso = sso;
return this; return this;
} }
/**
* When the step is executed and {@code idpAlias} is not {@code null}, it attempts to find and follow the link to
* identity provider with the given alias.
* @param idpAlias
* @return
*/
public LoginBuilder idp(String idpAlias) {
this.idpAlias = idpAlias;
return this;
}
/** /**
* Prepares a GET/POST request for logging the given user into the given login page. The login page is expected * Prepares a GET/POST request for logging the given user into the given login page. The login page is expected
* to have at least input fields with id "username" and "password". * to have at least input fields with id "username" and "password".
@ -92,7 +113,16 @@ public class LoginBuilder implements Step {
* @param loginPage * @param loginPage
* @return * @return
*/ */
private HttpUriRequest handleLoginPage(String loginPage) { private HttpUriRequest handleLoginPage(String loginPage, URI currentURI) {
if (idpAlias != null) {
org.jsoup.nodes.Document theLoginPage = Jsoup.parse(loginPage);
Element zocialLink = theLoginPage.getElementById("zocial-" + this.idpAlias);
assertThat("Unknown idp: " + this.idpAlias, zocialLink, Matchers.notNullValue());
final String link = zocialLink.attr("href");
assertThat("Invalid idp link: " + this.idpAlias, link, Matchers.notNullValue());
return new HttpGet(currentURI.resolve(link));
}
return handleLoginPage(user, loginPage); return handleLoginPage(user, loginPage);
} }

View file

@ -43,6 +43,7 @@ import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.message.BasicNameValuePair; import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils; import org.apache.http.util.EntityUtils;
import org.hamcrest.Matchers;
import org.jsoup.Jsoup; import org.jsoup.Jsoup;
import org.jsoup.nodes.Element; import org.jsoup.nodes.Element;
import org.jsoup.select.Elements; import org.jsoup.select.Elements;
@ -154,7 +155,9 @@ public class ModifySamlResponseStepBuilder extends SamlDocumentStepBuilder<SAML2
private HttpUriRequest handlePostBinding(CloseableHttpResponse currentResponse) throws Exception { private HttpUriRequest handlePostBinding(CloseableHttpResponse currentResponse) throws Exception {
assertThat(currentResponse, statusCodeIsHC(Status.OK)); assertThat(currentResponse, statusCodeIsHC(Status.OK));
org.jsoup.nodes.Document theResponsePage = Jsoup.parse(EntityUtils.toString(currentResponse.getEntity())); final String htmlBody = EntityUtils.toString(currentResponse.getEntity());
assertThat(htmlBody, Matchers.containsString("SAML"));
org.jsoup.nodes.Document theResponsePage = Jsoup.parse(htmlBody);
Elements samlResponses = theResponsePage.select("input[name=SAMLResponse]"); Elements samlResponses = theResponsePage.select("input[name=SAMLResponse]");
Elements samlRequests = theResponsePage.select("input[name=SAMLRequest]"); Elements samlRequests = theResponsePage.select("input[name=SAMLRequest]");
Elements forms = theResponsePage.select("form"); Elements forms = theResponsePage.select("form");

View file

@ -0,0 +1,146 @@
/*
* Copyright 2017 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.util.saml;
import org.keycloak.testsuite.util.SamlClientBuilder;
import org.keycloak.testsuite.util.SamlClient.Step;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.jboss.logging.Logger;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Element;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertThat;
import static org.keycloak.testsuite.util.Matchers.statusCodeIsHC;
/**
*
* @author hmlnarik
*/
public class UpdateProfileBuilder implements Step {
private final SamlClientBuilder clientBuilder;
private final Map<String, String> parameters = new HashMap<>();
private static final Logger LOG = Logger.getLogger(UpdateProfileBuilder.class);
public UpdateProfileBuilder(SamlClientBuilder clientBuilder) {
this.clientBuilder = clientBuilder;
}
@Override
public HttpUriRequest perform(CloseableHttpClient client, URI currentURI, CloseableHttpResponse currentResponse, HttpClientContext context) throws Exception {
assertThat(currentResponse, statusCodeIsHC(Response.Status.OK));
String loginPageText = EntityUtils.toString(currentResponse.getEntity(), "UTF-8");
assertThat(loginPageText, containsString("Update Account Information"));
return handleUpdateProfile(loginPageText, currentURI);
}
public SamlClientBuilder build() {
return this.clientBuilder;
}
public UpdateProfileBuilder param(String paramName, String paramValue) {
if (paramValue != null) {
this.parameters.put(paramName, paramValue);
} else {
this.parameters.remove(paramName);
}
return this;
}
public UpdateProfileBuilder firstName(String firstName) {
return param("firstName", firstName);
}
public UpdateProfileBuilder lastName(String lastName) {
return param("lastName", lastName);
}
public UpdateProfileBuilder username(String username) {
return param("username", username);
}
public UpdateProfileBuilder email(String email) {
return param("email", email);
}
public HttpUriRequest handleUpdateProfile(String loginPage, URI currentURI) {
org.jsoup.nodes.Document theUpdateProfilePage = Jsoup.parse(loginPage);
Set<String> unusedParams = new HashSet<>(this.parameters.keySet());
List<NameValuePair> parameters = new LinkedList<>();
for (Element form : theUpdateProfilePage.getElementsByTag("form")) {
String method = form.attr("method");
String action = form.attr("action");
boolean isPost = method != null && "post".equalsIgnoreCase(method);
for (Element input : form.getElementsByTag("input")) {
if (this.parameters.containsKey(input.attr("name"))) {
parameters.add(new BasicNameValuePair(input.attr("name"), this.parameters.get(input.attr("name"))));
unusedParams.remove(input.attr("name"));
}
}
if (! unusedParams.isEmpty()) {
LOG.warnf("Unused parameter names at Update Profile page: %s", unusedParams);
}
if (isPost) {
HttpPost res = new HttpPost(action);
UrlEncodedFormEntity formEntity;
try {
formEntity = new UrlEncodedFormEntity(parameters, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
res.setEntity(formEntity);
return res;
} else {
UriBuilder b = UriBuilder.fromPath(action);
for (NameValuePair parameter : parameters) {
b.queryParam(parameter.getName(), parameter.getValue());
}
return new HttpGet(b.build());
}
}
throw new IllegalArgumentException("Invalid update profile form: " + loginPage);
}
}

View file

@ -17,6 +17,8 @@ import org.keycloak.representations.idm.ProtocolMapperRepresentation;
import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.testsuite.arquillian.SuiteContext; import org.keycloak.testsuite.arquillian.SuiteContext;
import org.keycloak.testsuite.saml.AbstractSamlTest;
import org.keycloak.testsuite.util.ClientBuilder;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
@ -122,7 +124,18 @@ public class KcSamlBrokerConfiguration implements BrokerConfiguration {
@Override @Override
public List<ClientRepresentation> createConsumerClients(SuiteContext suiteContext) { public List<ClientRepresentation> createConsumerClients(SuiteContext suiteContext) {
return null; return Arrays.asList(
ClientBuilder.create()
.clientId(AbstractSamlTest.SAML_CLIENT_ID_SALES_POST)
.enabled(true)
.fullScopeEnabled(true)
.protocol(SamlProtocol.LOGIN_PROTOCOL)
.baseUrl("http://localhost:8080/sales-post")
.addRedirectUri("http://localhost:8080/sales-post/*")
.attribute(SamlConfigAttributes.SAML_AUTHNSTATEMENT, SamlProtocol.ATTRIBUTE_TRUE_VALUE)
.attribute(SamlConfigAttributes.SAML_CLIENT_SIGNATURE_ATTRIBUTE, SamlProtocol.ATTRIBUTE_FALSE_VALUE)
.build()
);
} }
@Override @Override

View file

@ -4,7 +4,12 @@ import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import org.keycloak.broker.saml.mappers.AttributeToRoleMapper; import org.keycloak.broker.saml.mappers.AttributeToRoleMapper;
import org.keycloak.broker.saml.mappers.UserAttributeMapper; import org.keycloak.broker.saml.mappers.UserAttributeMapper;
import org.keycloak.protocol.saml.SamlProtocol;
import org.keycloak.representations.idm.IdentityProviderMapperRepresentation; import org.keycloak.representations.idm.IdentityProviderMapperRepresentation;
import org.keycloak.services.resources.RealmsResource;
import java.net.URI;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriBuilderException;
public class KcSamlBrokerTest extends AbstractBrokerTest { public class KcSamlBrokerTest extends AbstractBrokerTest {
@ -35,4 +40,10 @@ public class KcSamlBrokerTest extends AbstractBrokerTest {
return Lists.newArrayList(attrMapper1, attrMapper2); return Lists.newArrayList(attrMapper1, attrMapper2);
} }
protected URI getAuthServerSamlEndpoint(String realm) throws IllegalArgumentException, UriBuilderException {
return RealmsResource
.protocolUrl(UriBuilder.fromUri(getAuthServerRoot()))
.build(realm, SamlProtocol.LOGIN_PROTOCOL);
}
} }

View file

@ -2,24 +2,42 @@ package org.keycloak.testsuite.broker;
import org.keycloak.admin.client.resource.ClientResource; import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.broker.saml.SAMLIdentityProviderConfig; import org.keycloak.broker.saml.SAMLIdentityProviderConfig;
import org.keycloak.dom.saml.v2.protocol.AuthnRequestType;
import org.keycloak.protocol.saml.SamlConfigAttributes; import org.keycloak.protocol.saml.SamlConfigAttributes;
import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation; import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
import org.keycloak.saml.common.util.DocumentUtil;
import org.keycloak.saml.processing.api.saml.v2.request.SAML2Request;
import org.keycloak.saml.processing.core.saml.v2.common.SAMLDocumentHolder;
import org.keycloak.testsuite.arquillian.SuiteContext; import org.keycloak.testsuite.arquillian.SuiteContext;
import org.keycloak.testsuite.saml.AbstractSamlTest;
import org.keycloak.testsuite.updaters.ClientAttributeUpdater; import org.keycloak.testsuite.updaters.ClientAttributeUpdater;
import org.keycloak.testsuite.updaters.IdentityProviderAttributeUpdater; import org.keycloak.testsuite.updaters.IdentityProviderAttributeUpdater;
import org.keycloak.testsuite.util.SamlClient;
import org.keycloak.testsuite.util.SamlClient.Binding;
import org.keycloak.testsuite.util.SamlClientBuilder;
import java.io.Closeable; import java.io.Closeable;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.hamcrest.Matchers; import org.hamcrest.Matchers;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import static org.keycloak.testsuite.broker.BrokerTestConstants.*; import static org.keycloak.testsuite.broker.BrokerTestConstants.*;
import static org.keycloak.testsuite.broker.BrokerTestTools.encodeUrl; import static org.keycloak.testsuite.broker.BrokerTestTools.encodeUrl;
import static org.keycloak.testsuite.util.Matchers.isSamlResponse;
public class KcSamlSignedBrokerTest extends KcSamlBrokerTest { public class KcSamlSignedBrokerTest extends KcSamlBrokerTest {
@ -138,4 +156,80 @@ public class KcSamlSignedBrokerTest extends KcSamlBrokerTest {
errorPage.assertCurrent(); errorPage.assertCurrent();
} }
} }
private Document extractNamespacesToTopLevelElement(Document original) {
HashMap<String, String> namespaces = new HashMap<>();
enumerateAndRemoveNamespaces(original.getDocumentElement(), namespaces);
log.infof("Namespaces: %s", namespaces);
log.infof("Document: %s", DocumentUtil.asString(original));
Element rootNode = original.getDocumentElement();
for (Entry<String, String> me : namespaces.entrySet()) {
rootNode.setAttribute(me.getKey(), me.getValue());
}
log.infof("Updated document: %s", DocumentUtil.asString(original));
return original;
}
private void enumerateAndRemoveNamespaces(Element documentElement, HashMap<String, String> namespaces) {
final NamedNodeMap attrs = documentElement.getAttributes();
if (attrs != null) {
final Set<String> found = new HashSet<>();
for (int i = attrs.getLength() - 1; i >= 0; i--) {
Node item = attrs.item(i);
String nodeName = item.getNodeName();
if (nodeName != null && nodeName.startsWith("xmlns:")) {
namespaces.put(nodeName, item.getNodeValue());
found.add(nodeName);
}
}
found.forEach(documentElement::removeAttribute);
}
NodeList childNodes = documentElement.getChildNodes();
for (int i = 0; i < childNodes.getLength(); i ++) {
Node childNode = childNodes.item(i);
if (childNode instanceof Element) {
enumerateAndRemoveNamespaces((Element) childNode, namespaces);
}
}
}
// KEYCLOAK-5581
@Test
public void loginUserAllNamespacesInTopElement() throws Exception {
AuthnRequestType loginRep = SamlClient.createLoginRequestDocument(AbstractSamlTest.SAML_CLIENT_ID_SALES_POST, AbstractSamlTest.SAML_ASSERTION_CONSUMER_URL_SALES_POST, null);
Document doc = extractNamespacesToTopLevelElement(SAML2Request.convert(loginRep));
SAMLDocumentHolder samlResponse = new SamlClientBuilder()
.authnRequest(getAuthServerSamlEndpoint(bc.consumerRealmName()), doc, Binding.POST).build() // Request to consumer IdP
.login().idp(bc.getIDPAlias()).build()
.processSamlResponse(Binding.POST) // AuthnRequest to producer IdP
.targetAttributeSamlRequest()
.transformDocument(this::extractNamespacesToTopLevelElement)
.build()
.login().user(bc.getUserLogin(), bc.getUserPassword()).build()
.processSamlResponse(Binding.POST) // Response from producer IdP
.transformDocument(this::extractNamespacesToTopLevelElement)
.build()
// first-broker flow
.updateProfile().firstName("a").lastName("b").email(bc.getUserEmail()).username(bc.getUserLogin()).build()
.followOneRedirect()
.getSamlResponse(Binding.POST); // Response from consumer IdP
Assert.assertThat(samlResponse, Matchers.notNullValue());
Assert.assertThat(samlResponse.getSamlObject(), isSamlResponse(JBossSAMLURIConstants.STATUS_SUCCESS));
}
} }

View file

@ -19,26 +19,26 @@ import static org.keycloak.testsuite.util.IOUtil.loadRealm;
*/ */
public abstract class AbstractSamlTest extends AbstractAuthTest { public abstract class AbstractSamlTest extends AbstractAuthTest {
protected static final String REALM_NAME = "demo"; public static final String REALM_NAME = "demo";
protected static final String REALM_PRIVATE_KEY = "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y="; public static final String REALM_PRIVATE_KEY = "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=";
protected static final String REALM_PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB"; public static final String REALM_PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB";
protected static final String SAML_ASSERTION_CONSUMER_URL_SALES_POST = "http://localhost:8080/sales-post/"; public static final String SAML_ASSERTION_CONSUMER_URL_SALES_POST = "http://localhost:8080/sales-post/";
protected static final String SAML_CLIENT_ID_SALES_POST = "http://localhost:8081/sales-post/"; public static final String SAML_CLIENT_ID_SALES_POST = "http://localhost:8081/sales-post/";
protected static final String SAML_ASSERTION_CONSUMER_URL_SALES_POST2 = "http://localhost:8080/sales-post2/"; public static final String SAML_ASSERTION_CONSUMER_URL_SALES_POST2 = "http://localhost:8080/sales-post2/";
protected static final String SAML_CLIENT_ID_SALES_POST2 = "http://localhost:8081/sales-post2/"; public static final String SAML_CLIENT_ID_SALES_POST2 = "http://localhost:8081/sales-post2/";
protected static final String SAML_ASSERTION_CONSUMER_URL_SALES_POST_SIG = "http://localhost:8080/sales-post-sig/"; public static final String SAML_ASSERTION_CONSUMER_URL_SALES_POST_SIG = "http://localhost:8080/sales-post-sig/";
protected static final String SAML_CLIENT_ID_SALES_POST_SIG = "http://localhost:8081/sales-post-sig/"; public static final String SAML_CLIENT_ID_SALES_POST_SIG = "http://localhost:8081/sales-post-sig/";
protected static final String SAML_URL_SALES_POST_SIG = "http://localhost:8080/sales-post-sig/"; public static final String SAML_URL_SALES_POST_SIG = "http://localhost:8080/sales-post-sig/";
protected static final String SAML_CLIENT_SALES_POST_SIG_PRIVATE_KEY = "MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBANUbxrvEY3pkiQNt55zJLKBwN+zKmNQw08ThAmOKzwHfXoK+xlDSFxNMtTKJGkeUdnKzaTfESEcEfKYULUA41y/NnOlvjS0CEsc7Wq0Ce63TSSGMB2NHea4tV0aQz/MwLsbmz2IjAFWHA5CHL5WwacIf3UTOSNnhJUSvnkomjJAlAgMBAAECgYANpO2gb/5+g5lSIuNFYov86bJq8r2+ODIW1OE2Rljioc6HSHeiDRF1JuAjECwikRrUVTBTZbnK8jqY14neJsWAKBzGo+ToaQALsNZ9B91DxxL50K5oVOzw5shAS9TnRjN40+KIXFED4ydq4JRdoqb8+cN+N3i0+Cu7tdm+UaHTAQJBAOwFs3ZwqQEqmv9vmgmIFwFpJm1aIw25gEOf3Hy45GP4bL/j0FQgwcXYRbLE5bPqhw/liLKc1GQ97bVm6zs8SvUCQQDnJZA6TFRMiDjezinE1J4e0v4RupyDniVjbE5ArTK5/FRVkjw4Ny0AqZUEyIIqlTeZlCq45pCJy4a2hymDGVJxAj9gzfXNnmezEsZ//kYvoqHM8lPQhifaeTsigW7tuOf0GPCBw+6uksDnZM0xhZCxOoArBPoMSEbU1pGo1Y2lvhUCQF6E5sBgHAybm53Ich4Rz4LNRqWbSIstrR5F2I3sBRU2kInZXZSjQ1zE+7HUCB4/nFfJ1dp8NdiTCEg1Zw072pECQQDnxyQALmWhQbBTl0tq6CwYf9rZDwBzxuY+CXB8Ky1gOmXwan96KZvV4rK8MQQs6HIiYC/j+5lX3A3zlXTFldaz"; public static final String SAML_CLIENT_SALES_POST_SIG_PRIVATE_KEY = "MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBANUbxrvEY3pkiQNt55zJLKBwN+zKmNQw08ThAmOKzwHfXoK+xlDSFxNMtTKJGkeUdnKzaTfESEcEfKYULUA41y/NnOlvjS0CEsc7Wq0Ce63TSSGMB2NHea4tV0aQz/MwLsbmz2IjAFWHA5CHL5WwacIf3UTOSNnhJUSvnkomjJAlAgMBAAECgYANpO2gb/5+g5lSIuNFYov86bJq8r2+ODIW1OE2Rljioc6HSHeiDRF1JuAjECwikRrUVTBTZbnK8jqY14neJsWAKBzGo+ToaQALsNZ9B91DxxL50K5oVOzw5shAS9TnRjN40+KIXFED4ydq4JRdoqb8+cN+N3i0+Cu7tdm+UaHTAQJBAOwFs3ZwqQEqmv9vmgmIFwFpJm1aIw25gEOf3Hy45GP4bL/j0FQgwcXYRbLE5bPqhw/liLKc1GQ97bVm6zs8SvUCQQDnJZA6TFRMiDjezinE1J4e0v4RupyDniVjbE5ArTK5/FRVkjw4Ny0AqZUEyIIqlTeZlCq45pCJy4a2hymDGVJxAj9gzfXNnmezEsZ//kYvoqHM8lPQhifaeTsigW7tuOf0GPCBw+6uksDnZM0xhZCxOoArBPoMSEbU1pGo1Y2lvhUCQF6E5sBgHAybm53Ich4Rz4LNRqWbSIstrR5F2I3sBRU2kInZXZSjQ1zE+7HUCB4/nFfJ1dp8NdiTCEg1Zw072pECQQDnxyQALmWhQbBTl0tq6CwYf9rZDwBzxuY+CXB8Ky1gOmXwan96KZvV4rK8MQQs6HIiYC/j+5lX3A3zlXTFldaz";
protected static final String SAML_CLIENT_SALES_POST_SIG_PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDVG8a7xGN6ZIkDbeecySygcDfsypjUMNPE4QJjis8B316CvsZQ0hcTTLUyiRpHlHZys2k3xEhHBHymFC1AONcvzZzpb40tAhLHO1qtAnut00khjAdjR3muLVdGkM/zMC7G5s9iIwBVhwOQhy+VsGnCH91EzkjZ4SVEr55KJoyQJQIDAQAB"; public static final String SAML_CLIENT_SALES_POST_SIG_PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDVG8a7xGN6ZIkDbeecySygcDfsypjUMNPE4QJjis8B316CvsZQ0hcTTLUyiRpHlHZys2k3xEhHBHymFC1AONcvzZzpb40tAhLHO1qtAnut00khjAdjR3muLVdGkM/zMC7G5s9iIwBVhwOQhy+VsGnCH91EzkjZ4SVEr55KJoyQJQIDAQAB";
protected static final String SAML_ASSERTION_CONSUMER_URL_SALES_POST_ENC = "http://localhost:8080/sales-post-enc/"; public static final String SAML_ASSERTION_CONSUMER_URL_SALES_POST_ENC = "http://localhost:8080/sales-post-enc/";
protected static final String SAML_CLIENT_ID_SALES_POST_ENC = "http://localhost:8081/sales-post-enc/"; public static final String SAML_CLIENT_ID_SALES_POST_ENC = "http://localhost:8081/sales-post-enc/";
protected 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";
protected 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";
@Override @Override

View file

@ -176,6 +176,16 @@ public class ClientBuilder {
return this; return this;
} }
public ClientBuilder protocol(String protocol) {
rep.setProtocol(protocol);
return this;
}
public ClientBuilder enabled(Boolean enabled) {
rep.setEnabled(enabled);
return this;
}
public ClientBuilder authorizationServicesEnabled(boolean enable) { public ClientBuilder authorizationServicesEnabled(boolean enable) {
rep.setAuthorizationServicesEnabled(enable); rep.setAuthorizationServicesEnabled(enable);
return this; return this;