KEYCLOAK-4160
This commit is contained in:
parent
44c81910bf
commit
b1510c43ff
7 changed files with 200 additions and 76 deletions
|
@ -158,6 +158,22 @@ public class IOUtil {
|
||||||
node.setTextContent(node.getTextContent().replace(regex, replacement));
|
node.setTextContent(node.getTextContent().replace(regex, replacement));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void setDocElementAttributeValue(Document doc, String tagName, String attributeName, String value) {
|
||||||
|
NodeList nodes = doc.getElementsByTagName(tagName);
|
||||||
|
if (nodes.getLength() != 1) {
|
||||||
|
log.warn("Not able or ambiguous to find element: " + tagName);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Element node = (Element) nodes.item(0);
|
||||||
|
if (node == null) {
|
||||||
|
log.warn("Not able to find element: " + tagName);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
node.setAttribute(attributeName, value);
|
||||||
|
}
|
||||||
|
|
||||||
public static void removeElementsFromDoc(Document doc, String parentTag, String removeNode) {
|
public static void removeElementsFromDoc(Document doc, String parentTag, String removeNode) {
|
||||||
NodeList nodes = doc.getElementsByTagName(parentTag);
|
NodeList nodes = doc.getElementsByTagName(parentTag);
|
||||||
if (nodes.getLength() != 1) {
|
if (nodes.getLength() != 1) {
|
||||||
|
|
|
@ -1009,7 +1009,7 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd
|
||||||
try (CloseableHttpClient client = HttpClientBuilder.create().build()) {
|
try (CloseableHttpClient client = HttpClientBuilder.create().build()) {
|
||||||
HttpClientContext context = HttpClientContext.create();
|
HttpClientContext context = HttpClientContext.create();
|
||||||
|
|
||||||
HttpUriRequest post = SamlClient.Binding.POST.createSamlPostUnsignedRequest(getAppServerSamlEndpoint(employee2ServletPage), null, responseDoc);
|
HttpUriRequest post = SamlClient.Binding.POST.createSamlUnsignedResponse(getAppServerSamlEndpoint(employee2ServletPage), null, responseDoc);
|
||||||
response = client.execute(post, context);
|
response = client.execute(post, context);
|
||||||
assertThat(response, statusCodeIsHC(Response.Status.FOUND));
|
assertThat(response, statusCodeIsHC(Response.Status.FOUND));
|
||||||
response.close();
|
response.close();
|
||||||
|
@ -1049,7 +1049,7 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd
|
||||||
HttpClientContext context = HttpClientContext.create();
|
HttpClientContext context = HttpClientContext.create();
|
||||||
|
|
||||||
log.debug("Sending response to SP");
|
log.debug("Sending response to SP");
|
||||||
HttpUriRequest post = SamlClient.Binding.POST.createSamlPostUnsignedRequest(getAppServerSamlEndpoint(salesPostSigEmailServletPage), null, responseDoc);
|
HttpUriRequest post = SamlClient.Binding.POST.createSamlUnsignedResponse(getAppServerSamlEndpoint(salesPostSigEmailServletPage), null, responseDoc);
|
||||||
response = client.execute(post, context);
|
response = client.execute(post, context);
|
||||||
System.out.println(EntityUtils.toString(response.getEntity()));
|
System.out.println(EntityUtils.toString(response.getEntity()));
|
||||||
assertThat(response, statusCodeIsHC(Response.Status.FOUND));
|
assertThat(response, statusCodeIsHC(Response.Status.FOUND));
|
||||||
|
|
|
@ -21,12 +21,17 @@ import static org.keycloak.testsuite.util.IOUtil.loadRealm;
|
||||||
public class AbstractSamlTest extends AbstractAuthTest {
|
public class AbstractSamlTest extends AbstractAuthTest {
|
||||||
|
|
||||||
protected static final String REALM_NAME = "demo";
|
protected 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=";
|
||||||
|
protected 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/";
|
protected 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/";
|
protected static final String SAML_CLIENT_ID_SALES_POST = "http://localhost:8081/sales-post/";
|
||||||
|
|
||||||
protected static final String SAML_ASSERTION_CONSUMER_URL_SALES_POST_ENC = "http://localhost:8080/sales-post-enc/";
|
protected 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/";
|
protected 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";
|
||||||
|
protected static final String SAML_CLIENT_SALES_POST_ENC_PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDb7kwJPkGdU34hicplwfp6/WmNcaLh94TSc7Jyr9Undp5pkyLgb0DE7EIE+6kSs4LsqCb8HDkB0nLD5DXbBJFd8n0WGoKstelvtg6FtVJMnwN7k7yZbfkPECWH9zF70VeOo9vbzrApNRnct8ZhH5fbflRB4JMA9L9R+LbURdoSKQIDAQAB";
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addTestRealms(List<RealmRepresentation> testRealms) {
|
public void addTestRealms(List<RealmRepresentation> testRealms) {
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
package org.keycloak.testsuite.saml;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.keycloak.dom.saml.v2.protocol.AuthnRequestType;
|
||||||
|
import org.keycloak.saml.common.exceptions.ConfigurationException;
|
||||||
|
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.saml.v2.common.SAMLDocumentHolder;
|
||||||
|
import org.keycloak.testsuite.util.SamlClient;
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
|
||||||
|
import static org.hamcrest.CoreMatchers.containsString;
|
||||||
|
import static org.hamcrest.CoreMatchers.not;
|
||||||
|
import static org.junit.Assert.assertThat;
|
||||||
|
import static org.keycloak.testsuite.util.IOUtil.documentToString;
|
||||||
|
import static org.keycloak.testsuite.util.IOUtil.setDocElementAttributeValue;
|
||||||
|
import static org.keycloak.testsuite.util.SamlClient.login;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author mhajas
|
||||||
|
*/
|
||||||
|
public class BasicSamlTest extends AbstractSamlTest {
|
||||||
|
|
||||||
|
// KEYCLOAK-4160
|
||||||
|
@Test
|
||||||
|
public void testPropertyValueInAssertion() throws ParsingException, ConfigurationException, ProcessingException {
|
||||||
|
AuthnRequestType loginRep = createLoginRequestDocument(SAML_CLIENT_ID_SALES_POST, SAML_ASSERTION_CONSUMER_URL_SALES_POST, REALM_NAME);
|
||||||
|
|
||||||
|
Document doc = SAML2Request.convert(loginRep);
|
||||||
|
|
||||||
|
setDocElementAttributeValue(doc, "samlp:AuthnRequest", "ID", "${java.version}" );
|
||||||
|
|
||||||
|
SAMLDocumentHolder document = login(bburkeUser, getAuthServerSamlEndpoint(REALM_NAME), doc, null, SamlClient.Binding.POST, SamlClient.Binding.POST);
|
||||||
|
|
||||||
|
assertThat(documentToString(document.getSamlDocument()), not(containsString("InResponseTo=\"" + System.getProperty("java.version") + "\"")));
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,12 +17,9 @@
|
||||||
package org.keycloak.testsuite.saml;
|
package org.keycloak.testsuite.saml;
|
||||||
|
|
||||||
import org.keycloak.dom.saml.v2.protocol.AuthnRequestType;
|
import org.keycloak.dom.saml.v2.protocol.AuthnRequestType;
|
||||||
import org.keycloak.protocol.saml.SamlProtocol;
|
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
import org.keycloak.representations.idm.UserRepresentation;
|
import org.keycloak.representations.idm.UserRepresentation;
|
||||||
import org.keycloak.saml.processing.api.saml.v2.request.SAML2Request;
|
import org.keycloak.saml.processing.api.saml.v2.request.SAML2Request;
|
||||||
import org.keycloak.services.resources.RealmsResource;
|
|
||||||
import org.keycloak.testsuite.AbstractAuthTest;
|
|
||||||
import org.keycloak.testsuite.util.SamlClient;
|
import org.keycloak.testsuite.util.SamlClient;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -33,8 +30,7 @@ import java.util.List;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import javax.ws.rs.core.UriBuilder;
|
|
||||||
import javax.ws.rs.core.UriBuilderException;
|
|
||||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||||
import org.apache.http.client.methods.HttpUriRequest;
|
import org.apache.http.client.methods.HttpUriRequest;
|
||||||
import org.apache.http.client.protocol.HttpClientContext;
|
import org.apache.http.client.protocol.HttpClientContext;
|
||||||
|
@ -65,7 +61,7 @@ public class ConcurrentAuthnRequestTest extends AbstractSamlTest {
|
||||||
ExecutorService threadPool = Executors.newFixedThreadPool(CONCURRENT_THREADS);
|
ExecutorService threadPool = Executors.newFixedThreadPool(CONCURRENT_THREADS);
|
||||||
|
|
||||||
try (CloseableHttpClient client = HttpClientBuilder.create().setRedirectStrategy(strategy).build()) {
|
try (CloseableHttpClient client = HttpClientBuilder.create().setRedirectStrategy(strategy).build()) {
|
||||||
HttpUriRequest post = requestBinding.createSamlRequest(samlEndpoint, relayState, samlRequest);
|
HttpUriRequest post = requestBinding.createSamlUnsignedRequest(samlEndpoint, relayState, samlRequest);
|
||||||
|
|
||||||
Collection<Callable<Void>> futures = new LinkedList<>();
|
Collection<Callable<Void>> futures = new LinkedList<>();
|
||||||
for (int i = 0; i < ITERATIONS; i ++) {
|
for (int i = 0; i < ITERATIONS; i ++) {
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
package org.keycloak.testsuite.util;
|
||||||
|
|
||||||
|
import org.keycloak.common.util.BouncyIntegration;
|
||||||
|
|
||||||
|
import java.security.KeyFactory;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.PublicKey;
|
||||||
|
import java.security.spec.InvalidKeySpecException;
|
||||||
|
import java.security.spec.PKCS8EncodedKeySpec;
|
||||||
|
import java.security.spec.X509EncodedKeySpec;
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author mhajas
|
||||||
|
*/
|
||||||
|
public class KeyUtils {
|
||||||
|
static {
|
||||||
|
BouncyIntegration.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static PublicKey publicKeyFromString(String key) {
|
||||||
|
try {
|
||||||
|
KeyFactory kf = KeyFactory.getInstance("RSA");
|
||||||
|
byte[] encoded = Base64.getDecoder().decode(key);
|
||||||
|
return kf.generatePublic(new X509EncodedKeySpec(encoded));
|
||||||
|
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PrivateKey privateKeyFromString(String key) {
|
||||||
|
try {
|
||||||
|
KeyFactory kf = KeyFactory.getInstance("RSA");
|
||||||
|
byte[] encoded = Base64.getDecoder().decode(key);
|
||||||
|
return kf.generatePrivate(new PKCS8EncodedKeySpec(encoded));
|
||||||
|
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,53 +16,56 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.testsuite.util;
|
package org.keycloak.testsuite.util;
|
||||||
|
|
||||||
import org.apache.http.client.protocol.HttpClientContext;
|
|
||||||
import org.apache.http.impl.client.CloseableHttpClient;
|
|
||||||
import org.apache.http.impl.client.HttpClientBuilder;
|
|
||||||
import org.keycloak.dom.saml.v2.protocol.AuthnRequestType;
|
|
||||||
import org.keycloak.representations.idm.UserRepresentation;
|
|
||||||
import org.keycloak.saml.BaseSAML2BindingBuilder;
|
|
||||||
import org.keycloak.saml.SAMLRequestParser;
|
|
||||||
import org.keycloak.saml.common.constants.GeneralConstants;
|
|
||||||
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.api.saml.v2.request.SAML2Request;
|
|
||||||
import org.keycloak.saml.processing.core.saml.v2.common.SAMLDocumentHolder;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.UUID;
|
|
||||||
import javax.ws.rs.core.Response;
|
|
||||||
import javax.ws.rs.core.UriBuilder;
|
|
||||||
import org.apache.http.NameValuePair;
|
import org.apache.http.NameValuePair;
|
||||||
import org.apache.http.client.entity.UrlEncodedFormEntity;
|
import org.apache.http.client.entity.UrlEncodedFormEntity;
|
||||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||||
import org.apache.http.client.methods.HttpGet;
|
import org.apache.http.client.methods.HttpGet;
|
||||||
import org.apache.http.client.methods.HttpPost;
|
import org.apache.http.client.methods.HttpPost;
|
||||||
import org.apache.http.client.methods.HttpUriRequest;
|
import org.apache.http.client.methods.HttpUriRequest;
|
||||||
|
import org.apache.http.client.protocol.HttpClientContext;
|
||||||
import org.apache.http.client.utils.URLEncodedUtils;
|
import org.apache.http.client.utils.URLEncodedUtils;
|
||||||
|
import org.apache.http.impl.client.CloseableHttpClient;
|
||||||
|
import org.apache.http.impl.client.HttpClientBuilder;
|
||||||
import org.apache.http.impl.client.LaxRedirectStrategy;
|
import org.apache.http.impl.client.LaxRedirectStrategy;
|
||||||
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.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;
|
||||||
|
import org.keycloak.common.util.KeyUtils;
|
||||||
|
import org.keycloak.dom.saml.v2.protocol.AuthnRequestType;
|
||||||
|
import org.keycloak.representations.idm.UserRepresentation;
|
||||||
|
import org.keycloak.saml.BaseSAML2BindingBuilder;
|
||||||
|
import org.keycloak.saml.SAMLRequestParser;
|
||||||
|
import org.keycloak.saml.SignatureAlgorithm;
|
||||||
|
import org.keycloak.saml.common.constants.GeneralConstants;
|
||||||
|
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.api.saml.v2.request.SAML2Request;
|
||||||
|
import org.keycloak.saml.processing.core.saml.v2.common.SAMLDocumentHolder;
|
||||||
import org.w3c.dom.Document;
|
import org.w3c.dom.Document;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import javax.ws.rs.core.UriBuilder;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.PublicKey;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.*;
|
import static org.hamcrest.Matchers.*;
|
||||||
import static org.junit.Assert.assertThat;
|
import static org.junit.Assert.assertThat;
|
||||||
import static org.keycloak.testsuite.admin.Users.getPasswordOf;
|
import static org.keycloak.testsuite.admin.Users.getPasswordOf;
|
||||||
import static org.keycloak.testsuite.arquillian.AuthServerTestEnricher.getAuthServerContextRoot;
|
import static org.keycloak.testsuite.arquillian.AuthServerTestEnricher.getAuthServerContextRoot;
|
||||||
import static org.keycloak.testsuite.util.Matchers.*;
|
import static org.keycloak.testsuite.util.IOUtil.documentToString;
|
||||||
|
import static org.keycloak.testsuite.util.Matchers.statusCodeIsHC;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* @author hmlnarik
|
* @author hmlnarik
|
||||||
*/
|
*/
|
||||||
public class SamlClient {
|
public class SamlClient {
|
||||||
|
@ -81,45 +84,41 @@ public class SamlClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HttpPost createSamlRequest(URI samlEndpoint, String relayState, Document samlRequest) {
|
public HttpPost createSamlUnsignedRequest(URI samlEndpoint, String relayState, Document samlRequest) {
|
||||||
HttpPost post = new HttpPost(samlEndpoint);
|
return createSamlPostMessage(samlEndpoint, relayState, samlRequest, GeneralConstants.SAML_REQUEST_KEY, null, null);
|
||||||
|
|
||||||
List<NameValuePair> parameters = new LinkedList<>();
|
|
||||||
try {
|
|
||||||
parameters.add(
|
|
||||||
new BasicNameValuePair(GeneralConstants.SAML_REQUEST_KEY,
|
|
||||||
new BaseSAML2BindingBuilder()
|
|
||||||
.postBinding(samlRequest)
|
|
||||||
.encoded())
|
|
||||||
);
|
|
||||||
} catch (ProcessingException | ConfigurationException | IOException ex) {
|
|
||||||
throw new RuntimeException(ex);
|
|
||||||
}
|
|
||||||
if (relayState != null) {
|
|
||||||
parameters.add(new BasicNameValuePair(GeneralConstants.RELAY_STATE, relayState));
|
|
||||||
}
|
|
||||||
|
|
||||||
UrlEncodedFormEntity formEntity;
|
|
||||||
try {
|
|
||||||
formEntity = new UrlEncodedFormEntity(parameters, "UTF-8");
|
|
||||||
} catch (UnsupportedEncodingException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
post.setEntity(formEntity);
|
|
||||||
|
|
||||||
return post;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HttpPost createSamlPostUnsignedRequest(URI samlEndpoint, String relayState, Document samlRequest) {
|
public HttpPost createSamlUnsignedResponse(URI samlEndpoint, String relayState, Document samlRequest) {
|
||||||
|
return createSamlPostMessage(samlEndpoint, relayState, samlRequest, GeneralConstants.SAML_RESPONSE_KEY, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HttpPost createSamlSignedRequest(URI samlEndpoint, String relayState, Document samlRequest, String realmPrivateKey, String realmPublicKey) {
|
||||||
|
return createSamlPostMessage(samlEndpoint, relayState, samlRequest, GeneralConstants.SAML_REQUEST_KEY, realmPrivateKey, realmPublicKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
private HttpPost createSamlPostMessage(URI samlEndpoint, String relayState, Document samlRequest, String messageType, String privateKeyStr, String publicKeyStr) {
|
||||||
HttpPost post = new HttpPost(samlEndpoint);
|
HttpPost post = new HttpPost(samlEndpoint);
|
||||||
|
|
||||||
List<NameValuePair> parameters = new LinkedList<>();
|
List<NameValuePair> parameters = new LinkedList<>();
|
||||||
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
BaseSAML2BindingBuilder binding = new BaseSAML2BindingBuilder();
|
||||||
|
|
||||||
|
if (privateKeyStr != null && publicKeyStr != null) {
|
||||||
|
PrivateKey privateKey = org.keycloak.testsuite.util.KeyUtils.privateKeyFromString(privateKeyStr);
|
||||||
|
PublicKey publicKey = org.keycloak.testsuite.util.KeyUtils.publicKeyFromString(publicKeyStr);
|
||||||
|
binding
|
||||||
|
.signatureAlgorithm(SignatureAlgorithm.RSA_SHA256)
|
||||||
|
.signWith(KeyUtils.createKeyId(privateKey), privateKey, publicKey)
|
||||||
|
.signDocument();
|
||||||
|
}
|
||||||
|
|
||||||
parameters.add(
|
parameters.add(
|
||||||
new BasicNameValuePair(GeneralConstants.SAML_RESPONSE_KEY,
|
new BasicNameValuePair(messageType,
|
||||||
new BaseSAML2BindingBuilder()
|
binding
|
||||||
.postBinding(samlRequest)
|
.postBinding(samlRequest)
|
||||||
.encoded())
|
.encoded())
|
||||||
);
|
);
|
||||||
|
@ -160,12 +159,12 @@ public class SamlClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HttpGet createSamlRequest(URI samlEndpoint, String relayState, Document samlRequest) {
|
public HttpGet createSamlUnsignedRequest(URI samlEndpoint, String relayState, Document samlRequest) {
|
||||||
try {
|
try {
|
||||||
URI requestURI = new BaseSAML2BindingBuilder()
|
URI requestURI = new BaseSAML2BindingBuilder()
|
||||||
.relayState(relayState)
|
.relayState(relayState)
|
||||||
.redirectBinding(samlRequest)
|
.redirectBinding(samlRequest)
|
||||||
.requestURI(samlEndpoint.toString());
|
.requestURI(samlEndpoint.toString());
|
||||||
return new HttpGet(requestURI);
|
return new HttpGet(requestURI);
|
||||||
} catch (ProcessingException | ConfigurationException | IOException ex) {
|
} catch (ProcessingException | ConfigurationException | IOException ex) {
|
||||||
throw new RuntimeException(ex);
|
throw new RuntimeException(ex);
|
||||||
|
@ -178,15 +177,25 @@ public class SamlClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HttpUriRequest createSamlPostUnsignedRequest(URI samlEndpoint, String relayState, Document samlRequest) {
|
public HttpUriRequest createSamlUnsignedResponse(URI samlEndpoint, String relayState, Document samlRequest) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HttpUriRequest createSamlSignedRequest(URI samlEndpoint, String relayState, Document samlRequest, String realmPrivateKey, String realmPublicKey) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public abstract SAMLDocumentHolder extractResponse(CloseableHttpResponse response) throws IOException;
|
public abstract SAMLDocumentHolder extractResponse(CloseableHttpResponse response) throws IOException;
|
||||||
public abstract HttpUriRequest createSamlRequest(URI samlEndpoint, String relayState, Document samlRequest);
|
|
||||||
|
public abstract HttpUriRequest createSamlUnsignedRequest(URI samlEndpoint, String relayState, Document samlRequest);
|
||||||
|
|
||||||
|
public abstract HttpUriRequest createSamlSignedRequest(URI samlEndpoint, String relayState, Document samlRequest, String realmPrivateKey, String realmPublicKey);
|
||||||
|
|
||||||
public abstract URI getBindingUri();
|
public abstract URI getBindingUri();
|
||||||
public abstract HttpUriRequest createSamlPostUnsignedRequest(URI samlEndpoint, String relayState, Document samlRequest);
|
|
||||||
|
public abstract HttpUriRequest createSamlUnsignedResponse(URI samlEndpoint, String relayState, Document samlRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class RedirectStrategyWithSwitchableFollowRedirect extends LaxRedirectStrategy {
|
public static class RedirectStrategyWithSwitchableFollowRedirect extends LaxRedirectStrategy {
|
||||||
|
@ -205,6 +214,7 @@ public class SamlClient {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracts and parses value of SAMLResponse input field of a form present in the given page.
|
* Extracts and parses value of SAMLResponse input field of a form present in the given page.
|
||||||
|
*
|
||||||
* @param responsePage HTML code of the page
|
* @param responsePage HTML code of the page
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
|
@ -220,6 +230,7 @@ public class SamlClient {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracts and parses value of SAMLResponse query parameter from the given URI.
|
* Extracts and parses value of SAMLResponse query parameter from the given URI.
|
||||||
|
*
|
||||||
* @param responseUri
|
* @param responseUri
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
|
@ -240,6 +251,7 @@ public class SamlClient {
|
||||||
/**
|
/**
|
||||||
* 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".
|
||||||
|
*
|
||||||
* @param user
|
* @param user
|
||||||
* @param loginPage
|
* @param loginPage
|
||||||
* @return
|
* @return
|
||||||
|
@ -292,6 +304,7 @@ public class SamlClient {
|
||||||
/**
|
/**
|
||||||
* Prepares a GET/POST request for consent granting . The consent page is expected
|
* Prepares a GET/POST request for consent granting . The consent page is expected
|
||||||
* to have at least input fields with id "kc-login" and "kc-cancel".
|
* to have at least input fields with id "kc-login" and "kc-cancel".
|
||||||
|
*
|
||||||
* @param consentPage
|
* @param consentPage
|
||||||
* @param consent
|
* @param consent
|
||||||
* @return
|
* @return
|
||||||
|
@ -343,6 +356,7 @@ public class SamlClient {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a SAML login request document with the given parameters. See SAML <AuthnRequest> description for more details.
|
* Creates a SAML login request document with the given parameters. See SAML <AuthnRequest> description for more details.
|
||||||
|
*
|
||||||
* @param issuer
|
* @param issuer
|
||||||
* @param assertionConsumerURL
|
* @param assertionConsumerURL
|
||||||
* @param destination
|
* @param destination
|
||||||
|
@ -361,6 +375,7 @@ public class SamlClient {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send request for login form and then login using user param. This method is designed for clients without required consent
|
* Send request for login form and then login using user param. This method is designed for clients without required consent
|
||||||
|
*
|
||||||
* @param user
|
* @param user
|
||||||
* @param samlEndpoint
|
* @param samlEndpoint
|
||||||
* @param samlRequest
|
* @param samlRequest
|
||||||
|
@ -376,6 +391,7 @@ public class SamlClient {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send request for login form and then login using user param. This method is designed for clients which requires consent
|
* Send request for login form and then login using user param. This method is designed for clients which requires consent
|
||||||
|
*
|
||||||
* @param user
|
* @param user
|
||||||
* @param samlEndpoint
|
* @param samlEndpoint
|
||||||
* @param samlRequest
|
* @param samlRequest
|
||||||
|
@ -385,12 +401,13 @@ public class SamlClient {
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public static SAMLDocumentHolder loginWithRequiredConsent(UserRepresentation user, URI samlEndpoint,
|
public static SAMLDocumentHolder loginWithRequiredConsent(UserRepresentation user, URI samlEndpoint,
|
||||||
Document samlRequest, String relayState, Binding requestBinding, Binding expectedResponseBinding, boolean consent) {
|
Document samlRequest, String relayState, Binding requestBinding, Binding expectedResponseBinding, boolean consent) {
|
||||||
return login(user, samlEndpoint, samlRequest, relayState, requestBinding, expectedResponseBinding, true, consent);
|
return login(user, samlEndpoint, samlRequest, relayState, requestBinding, expectedResponseBinding, true, consent);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send request for login form and then login using user param. Check whether client requires consent and handle consent page.
|
* Send request for login form and then login using user param. Check whether client requires consent and handle consent page.
|
||||||
|
*
|
||||||
* @param user
|
* @param user
|
||||||
* @param samlEndpoint
|
* @param samlEndpoint
|
||||||
* @param samlRequest
|
* @param samlRequest
|
||||||
|
@ -408,7 +425,7 @@ public class SamlClient {
|
||||||
try (CloseableHttpClient client = HttpClientBuilder.create().setRedirectStrategy(strategy).build()) {
|
try (CloseableHttpClient client = HttpClientBuilder.create().setRedirectStrategy(strategy).build()) {
|
||||||
HttpClientContext context = HttpClientContext.create();
|
HttpClientContext context = HttpClientContext.create();
|
||||||
|
|
||||||
HttpUriRequest post = requestBinding.createSamlRequest(samlEndpoint, relayState, samlRequest);
|
HttpUriRequest post = requestBinding.createSamlUnsignedRequest(samlEndpoint, relayState, samlRequest);
|
||||||
response = client.execute(post, context);
|
response = client.execute(post, context);
|
||||||
|
|
||||||
assertThat(response, statusCodeIsHC(Response.Status.OK));
|
assertThat(response, statusCodeIsHC(Response.Status.OK));
|
||||||
|
@ -428,20 +445,24 @@ public class SamlClient {
|
||||||
|
|
||||||
strategy.setRedirectable(false);
|
strategy.setRedirectable(false);
|
||||||
response = client.execute(loginRequest, context);
|
response = client.execute(loginRequest, context);
|
||||||
|
|
||||||
return expectedResponseBinding.extractResponse(response);
|
return expectedResponseBinding.extractResponse(response);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
throw new RuntimeException(ex);
|
throw new RuntimeException(ex);
|
||||||
} finally {
|
} finally {
|
||||||
if (response != null) {
|
if (response != null) {
|
||||||
EntityUtils.consumeQuietly(response.getEntity());
|
EntityUtils.consumeQuietly(response.getEntity());
|
||||||
try { response.close(); } catch (IOException ex) { }
|
try {
|
||||||
|
response.close();
|
||||||
|
} catch (IOException ex) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send request for login form and then login using user param for clients which doesn't require consent
|
* Send request for login form and then login using user param for clients which doesn't require consent
|
||||||
|
*
|
||||||
* @param user
|
* @param user
|
||||||
* @param idpInitiatedURI
|
* @param idpInitiatedURI
|
||||||
* @param expectedResponseBinding
|
* @param expectedResponseBinding
|
||||||
|
@ -453,6 +474,7 @@ public class SamlClient {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send request for login form and then login using user param. For clients which requires consent
|
* Send request for login form and then login using user param. For clients which requires consent
|
||||||
|
*
|
||||||
* @param user
|
* @param user
|
||||||
* @param idpInitiatedURI
|
* @param idpInitiatedURI
|
||||||
* @param expectedResponseBinding
|
* @param expectedResponseBinding
|
||||||
|
@ -465,6 +487,7 @@ public class SamlClient {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send request for login form and then login using user param. Checks whether client requires consent and handle consent page.
|
* Send request for login form and then login using user param. Checks whether client requires consent and handle consent page.
|
||||||
|
*
|
||||||
* @param user
|
* @param user
|
||||||
* @param idpInitiatedURI
|
* @param idpInitiatedURI
|
||||||
* @param expectedResponseBinding
|
* @param expectedResponseBinding
|
||||||
|
@ -505,9 +528,13 @@ public class SamlClient {
|
||||||
} finally {
|
} finally {
|
||||||
if (response != null) {
|
if (response != null) {
|
||||||
EntityUtils.consumeQuietly(response.getEntity());
|
EntityUtils.consumeQuietly(response.getEntity());
|
||||||
try { response.close(); } catch (IOException ex) { }
|
try {
|
||||||
|
response.close();
|
||||||
|
} catch (IOException ex) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue