diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/DeploymentArchiveProcessor.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/DeploymentArchiveProcessor.java index 0e06ee1f33..b45bf842be 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/DeploymentArchiveProcessor.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/DeploymentArchiveProcessor.java @@ -118,12 +118,8 @@ public class DeploymentArchiveProcessor implements ApplicationArchiveProcessor { modifyDocElementAttribute(doc, "SingleLogoutService", "redirectBindingUrl", "8080", System.getProperty("auth.server.http.port")); } - try { - archive.add(new StringAsset(IOUtil.documentToString(doc)), adapterConfigPath); - } catch (TransformerException e) { - log.error("Can't transform document to String"); - throw new RuntimeException(e); - } + archive.add(new StringAsset(IOUtil.documentToString(doc)), adapterConfigPath); + // For running SAML tests it is necessary to have few dependencies on app-server side. // Few of them are not in adapter zip so we need to add them manually here @@ -179,78 +175,68 @@ public class DeploymentArchiveProcessor implements ApplicationArchiveProcessor { String dependency = testClass.getAnnotation(UseServletFilter.class).filterDependency(); ((WebArchive) archive).addAsLibraries(KeycloakDependenciesResolver.resolveDependencies((dependency + ":" + System.getProperty("project.version")))); - try { - Document jbossXmlDoc = loadXML(archive.get(JBOSS_DEPLOYMENT_XML_PATH).getAsset().openStream()); - removeNodeByAttributeValue(jbossXmlDoc, "dependencies", "module", "name", "org.keycloak.keycloak-saml-core"); - removeNodeByAttributeValue(jbossXmlDoc, "dependencies", "module", "name", "org.keycloak.keycloak-adapter-spi"); - archive.add(new StringAsset((documentToString(jbossXmlDoc))), JBOSS_DEPLOYMENT_XML_PATH); - } catch (TransformerException e) { - log.error("Can't transform document to String"); - throw new RuntimeException(e); - } + Document jbossXmlDoc = loadXML(archive.get(JBOSS_DEPLOYMENT_XML_PATH).getAsset().openStream()); + removeNodeByAttributeValue(jbossXmlDoc, "dependencies", "module", "name", "org.keycloak.keycloak-saml-core"); + removeNodeByAttributeValue(jbossXmlDoc, "dependencies", "module", "name", "org.keycloak.keycloak-adapter-spi"); + archive.add(new StringAsset((documentToString(jbossXmlDoc))), JBOSS_DEPLOYMENT_XML_PATH); } protected void modifyWebXml(Archive archive, TestClass testClass) { - try { - Document webXmlDoc = loadXML( - archive.get(WEBXML_PATH).getAsset().openStream()); - if (isTomcatAppServer(testClass.getJavaClass())) { - modifyDocElementValue(webXmlDoc, "auth-method", "KEYCLOAK", "BASIC"); - } - - if (testClass.getJavaClass().isAnnotationPresent(UseServletFilter.class)) { - - addFilterDependencies(archive, testClass); - - //We need to add filter declaration to web.xml - log.info("Adding filter to " + testClass.getAnnotation(UseServletFilter.class).filterClass() + " with mapping " + testClass.getAnnotation(UseServletFilter.class).filterPattern() + " for " + archive.getName()); - - Element filter = webXmlDoc.createElement("filter"); - Element filterName = webXmlDoc.createElement("filter-name"); - Element filterClass = webXmlDoc.createElement("filter-class"); - - filterName.setTextContent(testClass.getAnnotation(UseServletFilter.class).filterName()); - filterClass.setTextContent(testClass.getAnnotation(UseServletFilter.class).filterClass()); - - filter.appendChild(filterName); - filter.appendChild(filterClass); - appendChildInDocument(webXmlDoc, "web-app", filter); - - Element filterMapping = webXmlDoc.createElement("filter-mapping"); - - - Element urlPattern = webXmlDoc.createElement("url-pattern"); - - filterName = webXmlDoc.createElement("filter-name"); - - filterName.setTextContent(testClass.getAnnotation(UseServletFilter.class).filterName()); - urlPattern.setTextContent(getElementTextContent(webXmlDoc, "web-app/security-constraint/web-resource-collection/url-pattern")); - - filterMapping.appendChild(filterName); - filterMapping.appendChild(urlPattern); - - if (!testClass.getAnnotation(UseServletFilter.class).dispatcherType().isEmpty()) { - Element dispatcher = webXmlDoc.createElement("dispatcher"); - dispatcher.setTextContent(testClass.getAnnotation(UseServletFilter.class).dispatcherType()); - filterMapping.appendChild(dispatcher); - } - appendChildInDocument(webXmlDoc, "web-app", filterMapping); - - //finally we need to remove all keycloak related configuration from web.xml - removeElementsFromDoc(webXmlDoc, "web-app", "security-constraint"); - removeElementsFromDoc(webXmlDoc, "web-app", "login-config"); - removeElementsFromDoc(webXmlDoc, "web-app", "security-role"); - - - } - - - archive.add(new StringAsset((documentToString(webXmlDoc))), WEBXML_PATH); - } catch (TransformerException e) { - log.error("Can't transform document to String"); - throw new RuntimeException(e); + Document webXmlDoc = loadXML( + archive.get(WEBXML_PATH).getAsset().openStream()); + if (isTomcatAppServer(testClass.getJavaClass())) { + modifyDocElementValue(webXmlDoc, "auth-method", "KEYCLOAK", "BASIC"); } + + if (testClass.getJavaClass().isAnnotationPresent(UseServletFilter.class)) { + + addFilterDependencies(archive, testClass); + + //We need to add filter declaration to web.xml + log.info("Adding filter to " + testClass.getAnnotation(UseServletFilter.class).filterClass() + " with mapping " + testClass.getAnnotation(UseServletFilter.class).filterPattern() + " for " + archive.getName()); + + Element filter = webXmlDoc.createElement("filter"); + Element filterName = webXmlDoc.createElement("filter-name"); + Element filterClass = webXmlDoc.createElement("filter-class"); + + filterName.setTextContent(testClass.getAnnotation(UseServletFilter.class).filterName()); + filterClass.setTextContent(testClass.getAnnotation(UseServletFilter.class).filterClass()); + + filter.appendChild(filterName); + filter.appendChild(filterClass); + appendChildInDocument(webXmlDoc, "web-app", filter); + + Element filterMapping = webXmlDoc.createElement("filter-mapping"); + + + Element urlPattern = webXmlDoc.createElement("url-pattern"); + + filterName = webXmlDoc.createElement("filter-name"); + + filterName.setTextContent(testClass.getAnnotation(UseServletFilter.class).filterName()); + urlPattern.setTextContent(getElementTextContent(webXmlDoc, "web-app/security-constraint/web-resource-collection/url-pattern")); + + filterMapping.appendChild(filterName); + filterMapping.appendChild(urlPattern); + + if (!testClass.getAnnotation(UseServletFilter.class).dispatcherType().isEmpty()) { + Element dispatcher = webXmlDoc.createElement("dispatcher"); + dispatcher.setTextContent(testClass.getAnnotation(UseServletFilter.class).dispatcherType()); + filterMapping.appendChild(dispatcher); + } + appendChildInDocument(webXmlDoc, "web-app", filterMapping); + + //finally we need to remove all keycloak related configuration from web.xml + removeElementsFromDoc(webXmlDoc, "web-app", "security-constraint"); + removeElementsFromDoc(webXmlDoc, "web-app", "login-config"); + removeElementsFromDoc(webXmlDoc, "web-app", "security-role"); + + + } + + + archive.add(new StringAsset((documentToString(webXmlDoc))), WEBXML_PATH); } } diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/IOUtil.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/IOUtil.java index f11ac070da..ea9281397f 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/IOUtil.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/IOUtil.java @@ -29,6 +29,7 @@ import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; @@ -89,13 +90,18 @@ public class IOUtil { } } - public static String documentToString(Document newDoc) throws TransformerException { - DOMSource domSource = new DOMSource(newDoc); - Transformer transformer = TransformerFactory.newInstance().newTransformer(); - StringWriter sw = new StringWriter(); - StreamResult sr = new StreamResult(sw); - transformer.transform(domSource, sr); - return sw.toString(); + public static String documentToString(Document newDoc) { + try { + DOMSource domSource = new DOMSource(newDoc); + Transformer transformer = TransformerFactory.newInstance().newTransformer(); + StringWriter sw = new StringWriter(); + StreamResult sr = new StreamResult(sw); + transformer.transform(domSource, sr); + return sw.toString(); + } catch (TransformerException e) { + log.error("Can't transform document to String"); + throw new RuntimeException(e); + } } public static void modifyDocElementAttribute(Document doc, String tagName, String attributeName, String regex, String replacement) { diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/saml/AbstractSamlTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/saml/AbstractSamlTest.java new file mode 100644 index 0000000000..016aa05126 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/saml/AbstractSamlTest.java @@ -0,0 +1,45 @@ +package org.keycloak.testsuite.saml; + +import org.keycloak.dom.saml.v2.protocol.AuthnRequestType; +import org.keycloak.protocol.saml.SamlProtocol; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.services.resources.RealmsResource; +import org.keycloak.testsuite.AbstractAuthTest; +import org.keycloak.testsuite.adapter.page.SAMLServlet; +import org.keycloak.testsuite.util.SamlClient; + +import javax.ws.rs.core.UriBuilder; +import javax.ws.rs.core.UriBuilderException; +import java.net.URI; +import java.util.List; + +import static org.keycloak.testsuite.util.IOUtil.loadRealm; + +/** + * @author mhajas + */ +public class AbstractSamlTest extends AbstractAuthTest { + + protected static final String REALM_NAME = "demo"; + + 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_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/"; + + @Override + public void addTestRealms(List testRealms) { + testRealms.add(loadRealm("/adapter-test/keycloak-saml/testsaml.json")); + } + + protected AuthnRequestType createLoginRequestDocument(String issuer, String assertionConsumerURL, String realmName) { + return SamlClient.createLoginRequestDocument(issuer, assertionConsumerURL, getAuthServerSamlEndpoint(realmName)); + } + + protected URI getAuthServerSamlEndpoint(String realm) throws IllegalArgumentException, UriBuilderException { + return RealmsResource + .protocolUrl(UriBuilder.fromUri(getAuthServerRoot())) + .build(realm, SamlProtocol.LOGIN_PROTOCOL); + } +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/saml/AuthnRequestNameIdFormatTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/saml/AuthnRequestNameIdFormatTest.java index 3bf08c9e8c..4f942606d7 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/saml/AuthnRequestNameIdFormatTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/saml/AuthnRequestNameIdFormatTest.java @@ -60,27 +60,7 @@ import static org.keycloak.testsuite.util.Matchers.statusCodeIsHC; * * @author hmlnarik */ -public class AuthnRequestNameIdFormatTest extends AbstractAuthTest { - - private static final String REALM_NAME = "demo"; - - private static final String SAML_ASSERTION_CONSUMER_URL_SALES_POST = "http://localhost:8080/sales-post/"; - private static final String SAML_CLIENT_ID_SALES_POST = "http://localhost:8081/sales-post/"; - - @Override - public void addTestRealms(List testRealms) { - testRealms.add(loadRealm("/adapter-test/keycloak-saml/testsaml.json")); - } - - public AuthnRequestType createLoginRequestDocument(String issuer, String assertionConsumerURL, String realmName) { - return SamlClient.createLoginRequestDocument(issuer, assertionConsumerURL, getAuthServerSamlEndpoint(realmName)); - } - - private URI getAuthServerSamlEndpoint(String realm) throws IllegalArgumentException, UriBuilderException { - return RealmsResource - .protocolUrl(UriBuilder.fromUri(getAuthServerRoot())) - .build(realm, SamlProtocol.LOGIN_PROTOCOL); - } +public class AuthnRequestNameIdFormatTest extends AbstractSamlTest { private void testLoginWithNameIdPolicy(Binding requestBinding, Binding responseBinding, NameIDPolicyType nameIDPolicy, Matcher nameIdMatcher) throws Exception { AuthnRequestType loginRep = createLoginRequestDocument(SAML_CLIENT_ID_SALES_POST, SAML_ASSERTION_CONSUMER_URL_SALES_POST, REALM_NAME); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/saml/ConcurrentAuthnRequestTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/saml/ConcurrentAuthnRequestTest.java index b207c045d9..03980ef4f1 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/saml/ConcurrentAuthnRequestTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/saml/ConcurrentAuthnRequestTest.java @@ -53,12 +53,7 @@ import static org.keycloak.testsuite.util.IOUtil.loadRealm; * @author hmlnarik */ @Ignore -public class ConcurrentAuthnRequestTest extends AbstractAuthTest { - - private static final String REALM_NAME = "demo"; - - private static final String SAML_ASSERTION_CONSUMER_URL_SALES_POST = "http://localhost:8080/sales-post/"; - private static final String SAML_CLIENT_ID_SALES_POST = "http://localhost:8081/sales-post/"; +public class ConcurrentAuthnRequestTest extends AbstractSamlTest { public static final int ITERATIONS = 10000; public static final int CONCURRENT_THREADS = 5; @@ -123,12 +118,6 @@ public class ConcurrentAuthnRequestTest extends AbstractAuthTest { return SamlClient.createLoginRequestDocument(issuer, assertionConsumerURL, getAuthServerSamlEndpoint(realmName)); } - private URI getAuthServerSamlEndpoint(String realm) throws IllegalArgumentException, UriBuilderException { - return RealmsResource - .protocolUrl(UriBuilder.fromUri(getAuthServerRoot())) - .build(realm, SamlProtocol.LOGIN_PROTOCOL); - } - private void testLogin(Binding requestBinding) throws Exception { AuthnRequestType loginRep = createLoginRequestDocument(SAML_CLIENT_ID_SALES_POST, SAML_ASSERTION_CONSUMER_URL_SALES_POST, REALM_NAME); Document samlRequest = SAML2Request.convert(loginRep); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/saml/SamlConsentTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/saml/SamlConsentTest.java new file mode 100644 index 0000000000..1428ac15c9 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/saml/SamlConsentTest.java @@ -0,0 +1,55 @@ +package org.keycloak.testsuite.saml; + +import org.junit.Test; +import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.representations.idm.RealmRepresentation; +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.core.saml.v2.common.SAMLDocumentHolder; +import org.keycloak.testsuite.util.ClientBuilder; +import org.keycloak.testsuite.util.IOUtil; +import org.keycloak.testsuite.util.SamlClient; + +import java.net.URI; +import java.util.List; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.junit.Assert.assertThat; +import static org.keycloak.testsuite.util.IOUtil.loadRealm; +import static org.keycloak.testsuite.util.SamlClient.idpInitiatedLoginWithRequiredConsent; + +/** + * @author mhajas + */ +public class SamlConsentTest extends AbstractSamlTest { + + @Override + public void addTestRealms(List testRealms) { + testRealms.add(loadRealm("/adapter-test/keycloak-saml/testsaml.json")); + } + + @Test + public void signingOfRejectedConsentAssertionTest() throws ParsingException, ConfigurationException, ProcessingException { + ClientRepresentation client = adminClient.realm(REALM_NAME) + .clients() + .findByClientId(SAML_CLIENT_ID_SALES_POST_ENC) + .get(0); + + adminClient.realm(REALM_NAME) + .clients() + .get(client.getId()) + .update(ClientBuilder.edit(client) + .consentRequired(true) + .attribute("saml.encrypt", "false") //remove after RHSSO-797 + .attribute("saml_idp_initiated_sso_url_name", "sales-post-enc") + .attribute("saml_assertion_consumer_url_post", SAML_ASSERTION_CONSUMER_URL_SALES_POST_ENC + "saml") + .build()); + + log.debug("Log in using idp initiated login"); + String idpInitiatedLogin = getAuthServerRoot() + "realms/" + REALM_NAME + "/protocol/saml/clients/sales-post-enc"; + SAMLDocumentHolder documentHolder = idpInitiatedLoginWithRequiredConsent(bburkeUser, URI.create(idpInitiatedLogin), SamlClient.Binding.POST, false); + + assertThat(IOUtil.documentToString(documentHolder.getSamlDocument()), containsString(" attributes = new HashMap(); + Map attributes = rep.getAttributes(); + if (attributes == null) { + attributes = new HashMap<>(); + } attributes.put(name, value); rep.setAttributes(attributes); return this; diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/SamlClient.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/SamlClient.java index 8a278964e4..9eae9a1b06 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/SamlClient.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/SamlClient.java @@ -58,6 +58,7 @@ import org.w3c.dom.Document; import static org.hamcrest.Matchers.*; import static org.junit.Assert.assertThat; import static org.keycloak.testsuite.admin.Users.getPasswordOf; +import static org.keycloak.testsuite.arquillian.AuthServerTestEnricher.getAuthServerContextRoot; import static org.keycloak.testsuite.util.Matchers.*; /** @@ -288,6 +289,58 @@ public class SamlClient { throw new IllegalArgumentException("Invalid login form: " + loginPage); } + /** + * 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". + * @param consentPage + * @param consent + * @return + */ + public static HttpUriRequest handleConsentPage(String consentPage, boolean consent) { + org.jsoup.nodes.Document theLoginPage = Jsoup.parse(consentPage); + + List parameters = new LinkedList<>(); + for (Element form : theLoginPage.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 (Objects.equals(input.id(), "kc-login")) { + if (consent) + parameters.add(new BasicNameValuePair(input.attr("name"), input.attr("value"))); + } else if (Objects.equals(input.id(), "kc-cancel")) { + if (!consent) + parameters.add(new BasicNameValuePair(input.attr("name"), input.attr("value"))); + } else { + parameters.add(new BasicNameValuePair(input.attr("name"), input.val())); + } + } + + if (isPost) { + HttpPost res = new HttpPost(getAuthServerContextRoot() + 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 consent page: " + consentPage); + } + /** * Creates a SAML login request document with the given parameters. See SAML <AuthnRequest> description for more details. * @param issuer @@ -307,7 +360,7 @@ public class SamlClient { } /** - * Send request for login form and then login using user param + * Send request for login form and then login using user param. This method is designed for clients without required consent * @param user * @param samlEndpoint * @param samlRequest @@ -318,6 +371,38 @@ public class SamlClient { */ public static SAMLDocumentHolder login(UserRepresentation user, URI samlEndpoint, Document samlRequest, String relayState, Binding requestBinding, Binding expectedResponseBinding) { + return login(user, samlEndpoint, samlRequest, relayState, requestBinding, expectedResponseBinding, false, true); + } + + /** + * Send request for login form and then login using user param. This method is designed for clients which requires consent + * @param user + * @param samlEndpoint + * @param samlRequest + * @param relayState + * @param requestBinding + * @param expectedResponseBinding + * @return + */ + public static SAMLDocumentHolder loginWithRequiredConsent(UserRepresentation user, URI samlEndpoint, + Document samlRequest, String relayState, Binding requestBinding, Binding expectedResponseBinding, boolean 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. + * @param user + * @param samlEndpoint + * @param samlRequest + * @param relayState + * @param requestBinding + * @param expectedResponseBinding + * @param consentRequired + * @param consent + * @return + */ + public static SAMLDocumentHolder login(UserRepresentation user, URI samlEndpoint, + Document samlRequest, String relayState, Binding requestBinding, Binding expectedResponseBinding, boolean consentRequired, boolean consent) { CloseableHttpResponse response = null; SamlClient.RedirectStrategyWithSwitchableFollowRedirect strategy = new SamlClient.RedirectStrategyWithSwitchableFollowRedirect(); try (CloseableHttpClient client = HttpClientBuilder.create().setRedirectStrategy(strategy).build()) { @@ -334,6 +419,13 @@ public class SamlClient { HttpUriRequest loginRequest = handleLoginPage(user, loginPageText); + if (consentRequired) { + // Client requires consent + response = client.execute(loginRequest, context); + String consentPageText = EntityUtils.toString(response.getEntity(), "UTF-8"); + loginRequest = handleConsentPage(consentPageText, consent); + } + strategy.setRedirectable(false); response = client.execute(loginRequest, context); @@ -349,13 +441,37 @@ public class SamlClient { } /** - * Send request for login form and then login using user param + * Send request for login form and then login using user param for clients which doesn't require consent * @param user * @param idpInitiatedURI * @param expectedResponseBinding * @return */ public static SAMLDocumentHolder idpInitiatedLogin(UserRepresentation user, URI idpInitiatedURI, Binding expectedResponseBinding) { + return idpInitiatedLogin(user, idpInitiatedURI, expectedResponseBinding, false, true); + } + + /** + * Send request for login form and then login using user param. For clients which requires consent + * @param user + * @param idpInitiatedURI + * @param expectedResponseBinding + * @param consent + * @return + */ + public static SAMLDocumentHolder idpInitiatedLoginWithRequiredConsent(UserRepresentation user, URI idpInitiatedURI, Binding expectedResponseBinding, boolean consent) { + return idpInitiatedLogin(user, idpInitiatedURI, expectedResponseBinding, true, consent); + } + + /** + * Send request for login form and then login using user param. Checks whether client requires consent and handle consent page. + * @param user + * @param idpInitiatedURI + * @param expectedResponseBinding + * @param consent + * @return + */ + public static SAMLDocumentHolder idpInitiatedLogin(UserRepresentation user, URI idpInitiatedURI, Binding expectedResponseBinding, boolean consentRequired, boolean consent) { CloseableHttpResponse response = null; SamlClient.RedirectStrategyWithSwitchableFollowRedirect strategy = new SamlClient.RedirectStrategyWithSwitchableFollowRedirect(); try (CloseableHttpClient client = HttpClientBuilder.create().setRedirectStrategy(strategy).build()) { @@ -373,6 +489,13 @@ public class SamlClient { HttpUriRequest loginRequest = handleLoginPage(user, loginPageText); + if (consentRequired) { + // Client requires consent + response = client.execute(loginRequest, context); + String consentPageText = EntityUtils.toString(response.getEntity(), "UTF-8"); + loginRequest = handleConsentPage(consentPageText, consent); + } + strategy.setRedirectable(false); response = client.execute(loginRequest, context);