keycloak-aplcache

Details

diff --git a/testsuite/integration-arquillian/test-apps/servlets/src/main/java/org/keycloak/testsuite/adapter/servlet/SendUsernameServlet.java b/testsuite/integration-arquillian/test-apps/servlets/src/main/java/org/keycloak/testsuite/adapter/servlet/SendUsernameServlet.java
index 5d29edb..d02ed75 100755
--- a/testsuite/integration-arquillian/test-apps/servlets/src/main/java/org/keycloak/testsuite/adapter/servlet/SendUsernameServlet.java
+++ b/testsuite/integration-arquillian/test-apps/servlets/src/main/java/org/keycloak/testsuite/adapter/servlet/SendUsernameServlet.java
@@ -85,6 +85,7 @@ public class SendUsernameServlet {
     @Path("getAttributes")
     public Response getSentPrincipal() throws IOException {
         System.out.println("In SendUsername Servlet getSentPrincipal()");
+        sentPrincipal = httpServletRequest.getUserPrincipal();
 
         return Response.ok(getAttributes()).header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_HTML_TYPE + ";charset=UTF-8").build();
 
@@ -190,6 +191,7 @@ public class SendUsernameServlet {
         SamlPrincipal principal = (SamlPrincipal) sentPrincipal;
         String output = "attribute email: " + principal.getAttribute(X500SAMLProfileConstants.EMAIL.get());
         output += "<br /> topAttribute: " + principal.getAttribute("topAttribute");
+        output += "<br /> boolean-attribute: " + principal.getAttribute("boolean-attribute");
         output += "<br /> level2Attribute: " + principal.getAttribute("level2Attribute");
         output += "<br /> group: " + principal.getAttributes("group").toString();
         output += "<br /> friendlyAttribute email: " + principal.getFriendlyAttribute("email");
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 9148ce1..5bdea6e 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
@@ -206,20 +206,25 @@ public class IOUtil {
         return currentElement.getTextContent();
     }
 
-    public static void appendChildInDocument(Document doc, String parentTag, Element node) {
-        NodeList nodes = doc.getElementsByTagName(parentTag);
-        if (nodes.getLength() != 1) {
-            log.warn("Not able or ambiguous to find element: " + parentTag);
+    public static void appendChildInDocument(Document doc, String parentPath, Element node) {
+        String[] pathSegments = parentPath.split("/");
+
+        Element currentElement = (Element) doc.getElementsByTagName(pathSegments[0]).item(0);
+        if (currentElement == null) {
+            log.warn("Not able to find element: " + pathSegments[0] + " in document");
             return;
         }
 
-        Element parentElement = (Element) nodes.item(0);
-        if (parentElement == null) {
-            log.warn("Not able to find element: " + parentTag);
-            return;
+        for (int i = 1; i < pathSegments.length; i++) {
+            currentElement = (Element) currentElement.getElementsByTagName(pathSegments[i]).item(0);
+
+            if (currentElement == null) {
+                log.warn("Not able to find element: " + pathSegments[i] + " in " + pathSegments[i - 1]);
+                return;
+            }
         }
 
-        parentElement.appendChild(node);
+        currentElement.appendChild(node);
     }
 
     public static void execCommand(String command, File dir) throws IOException, InterruptedException {
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLServletsAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLServletsAdapterTest.java
index 1d0b7f7..e728eaa 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLServletsAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLServletsAdapterTest.java
@@ -17,6 +17,13 @@
 
 package org.keycloak.testsuite.adapter.servlet;
 
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+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.impl.client.HttpClientBuilder;
+import org.apache.http.util.EntityUtils;
 import org.jboss.arquillian.container.test.api.Deployment;
 import org.jboss.arquillian.graphene.page.Page;
 import org.jboss.shrinkwrap.api.spec.WebArchive;
@@ -28,10 +35,12 @@ import org.keycloak.admin.client.resource.ProtocolMappersResource;
 import org.keycloak.admin.client.resource.RoleScopeResource;
 import org.keycloak.common.util.KeyUtils;
 import org.keycloak.common.util.PemUtils;
+import org.keycloak.dom.saml.v2.protocol.AuthnRequestType;
 import org.keycloak.keys.Attributes;
 import org.keycloak.keys.KeyProvider;
 import org.keycloak.keys.ImportedRsaKeyProviderFactory;
 import org.keycloak.protocol.saml.SamlConfigAttributes;
+import org.keycloak.protocol.saml.SamlProtocol;
 import org.keycloak.representations.idm.ComponentRepresentation;
 import org.keycloak.protocol.saml.mappers.AttributeStatementHelper;
 import org.keycloak.protocol.saml.mappers.RoleListMapper;
@@ -44,6 +53,9 @@ import org.keycloak.saml.BaseSAML2BindingBuilder;
 import org.keycloak.saml.SAML2ErrorResponseBuilder;
 import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
 import org.keycloak.saml.common.util.XmlKeyInfoKeyNameTransformer;
+import org.keycloak.saml.processing.api.saml.v2.request.SAML2Request;
+import org.keycloak.saml.processing.core.saml.v2.common.SAMLDocumentHolder;
+import org.keycloak.services.resources.RealmsResource;
 import org.keycloak.testsuite.adapter.AbstractServletsAdapterTest;
 import org.keycloak.testsuite.adapter.page.BadAssertionSalesPostSig;
 import org.keycloak.testsuite.adapter.page.BadClientSalesPostSigServlet;
@@ -73,11 +85,12 @@ import org.keycloak.testsuite.auth.page.login.Login;
 import org.keycloak.testsuite.auth.page.login.SAMLIDPInitiatedLogin;
 import org.keycloak.testsuite.page.AbstractPage;
 import org.keycloak.testsuite.util.IOUtil;
-import org.keycloak.testsuite.util.RealmBuilder;
+import org.keycloak.testsuite.util.SamlClient;
 import org.keycloak.testsuite.util.UserBuilder;
 
 import org.openqa.selenium.By;
 import org.w3c.dom.Document;
+import org.w3c.dom.Element;
 import org.xml.sax.SAXException;
 
 import javax.ws.rs.client.Client;
@@ -87,6 +100,8 @@ import javax.ws.rs.client.WebTarget;
 import javax.ws.rs.core.Form;
 import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriBuilderException;
 import javax.xml.XMLConstants;
 import javax.xml.transform.Source;
 import javax.xml.transform.stream.StreamSource;
@@ -107,13 +122,15 @@ import java.util.stream.Collectors;
 import static org.hamcrest.Matchers.*;
 import static org.junit.Assert.*;
 import static org.keycloak.representations.idm.CredentialRepresentation.PASSWORD;
-import static org.keycloak.testsuite.AbstractAuthTest.createUserRepresentation;
 import static org.keycloak.testsuite.admin.ApiUtil.createUserAndResetPasswordWithAdminClient;
 import static org.keycloak.testsuite.admin.Users.setPasswordFor;
 import static org.keycloak.testsuite.auth.page.AuthRealm.SAMLSERVLETDEMO;
 import static org.keycloak.testsuite.util.IOUtil.loadRealm;
 import static org.keycloak.testsuite.util.IOUtil.loadXML;
 import static org.keycloak.testsuite.util.IOUtil.modifyDocElementAttribute;
+import static org.keycloak.testsuite.util.Matchers.bodyHC;
+import static org.keycloak.testsuite.util.Matchers.statusCodeIsHC;
+import static org.keycloak.testsuite.util.SamlClient.login;
 import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith;
 import static org.keycloak.testsuite.util.WaitUtils.waitUntilElement;
 
@@ -965,6 +982,61 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd
         response.close();
     }
 
+    @Test
+    //KEYCLOAK-4020
+    public void testBooleanAttribute() throws Exception {
+        AuthnRequestType req = SamlClient.createLoginRequestDocument("http://localhost:8081/employee2/", getAppServerSamlEndpoint(employee2ServletPage).toString(), getAuthServerSamlEndpoint(SAMLSERVLETDEMO));
+        Document doc = SAML2Request.convert(req);
+
+        SAMLDocumentHolder res = login(bburkeUser, getAuthServerSamlEndpoint(SAMLSERVLETDEMO), doc, null, SamlClient.Binding.POST, SamlClient.Binding.POST);
+        Document responseDoc = res.getSamlDocument();
+
+        Element attribute = responseDoc.createElement("saml:Attribute");
+        attribute.setAttribute("Name", "boolean-attribute");
+        attribute.setAttribute("NameFormat", "urn:oasis:names:tc:SAML:2.0:attrname-format:basic");
+
+        Element attributeValue = responseDoc.createElement("saml:AttributeValue");
+        attributeValue.setAttribute("xmlns:xs", "http://www.w3.org/2001/XMLSchema");
+        attributeValue.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
+        attributeValue.setAttribute("xsi:type", "xs:boolean");
+        attributeValue.setTextContent("true");
+
+        attribute.appendChild(attributeValue);
+        IOUtil.appendChildInDocument(responseDoc, "samlp:Response/saml:Assertion/saml:AttributeStatement", attribute);
+
+        CloseableHttpResponse response = null;
+        try (CloseableHttpClient client = HttpClientBuilder.create().build()) {
+            HttpClientContext context = HttpClientContext.create();
+
+            HttpUriRequest post = SamlClient.Binding.POST.createSamlPostUnsignedRequest(getAppServerSamlEndpoint(employee2ServletPage), null, responseDoc);
+            response = client.execute(post, context);
+            assertThat(response, statusCodeIsHC(Response.Status.FOUND));
+            response.close();
+
+            HttpGet get = new HttpGet(employee2ServletPage.toString() + "/getAttributes");
+            response = client.execute(get);
+            assertThat(response, statusCodeIsHC(Response.Status.OK));
+            assertThat(response, bodyHC(containsString("boolean-attribute: true")));
+        } catch (Exception ex) {
+            throw new RuntimeException(ex);
+        } finally {
+            if (response != null) {
+                EntityUtils.consumeQuietly(response.getEntity());
+                try { response.close(); } catch (IOException ex) { }
+            }
+        }
+    }
+
+    private URI getAuthServerSamlEndpoint(String realm) throws IllegalArgumentException, UriBuilderException {
+        return RealmsResource
+                .protocolUrl(UriBuilder.fromUri(getAuthServerRoot()))
+                .build(realm, SamlProtocol.LOGIN_PROTOCOL);
+    }
+
+    private URI getAppServerSamlEndpoint(SAMLServlet page) throws IllegalArgumentException, UriBuilderException {
+        return UriBuilder.fromPath(page.toString()).path("/saml").build();
+    }
+
     private void validateXMLWithSchema(String xml, String schemaFileName) throws SAXException, IOException {
         URL schemaFile = getClass().getResource(schemaFileName);
 
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 e9b4ac6..3bf08c9 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
@@ -67,38 +67,6 @@ public class AuthnRequestNameIdFormatTest extends AbstractAuthTest {
     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 static SAMLDocumentHolder login(UserRepresentation user, URI samlEndpoint,
-      Document samlRequest, String relayState, Binding requestBinding, Binding expectedResponseBinding) {
-        CloseableHttpResponse response = null;
-        SamlClient.RedirectStrategyWithSwitchableFollowRedirect strategy = new SamlClient.RedirectStrategyWithSwitchableFollowRedirect();
-        try (CloseableHttpClient client = HttpClientBuilder.create().setRedirectStrategy(strategy).build()) {
-            HttpClientContext context = HttpClientContext.create();
-
-            HttpUriRequest post = requestBinding.createSamlRequest(samlEndpoint, relayState, samlRequest);
-            response = client.execute(post, context);
-
-            assertThat(response, statusCodeIsHC(Response.Status.OK));
-            String loginPageText = EntityUtils.toString(response.getEntity(), "UTF-8");
-            response.close();
-
-            assertThat(loginPageText, containsString("login"));
-
-            HttpUriRequest loginRequest = handleLoginPage(user, loginPageText);
-
-            strategy.setRedirectable(false);
-            response = client.execute(loginRequest, context);
-
-            return expectedResponseBinding.extractResponse(response);
-        } catch (Exception ex) {
-            throw new RuntimeException(ex);
-        } finally {
-            if (response != null) {
-                EntityUtils.consumeQuietly(response.getEntity());
-                try { response.close(); } catch (IOException ex) { }
-            }
-        }
-    }
-
     @Override
     public void addTestRealms(List<RealmRepresentation> testRealms) {
         testRealms.add(loadRealm("/adapter-test/keycloak-saml/testsaml.json"));
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/Matchers.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/Matchers.java
index 7ff72a5..f88cfdf 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/Matchers.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/Matchers.java
@@ -39,6 +39,15 @@ public class Matchers {
     }
 
     /**
+     * Matcher on HTTP body of a {@link Response} instance.
+     * @param matcher
+     * @return
+     */
+    public static Matcher<HttpResponse> bodyHC(Matcher<String> matcher) {
+        return new HttpResponseBodyMatcher(matcher);
+    }
+
+    /**
      * Matcher on HTTP status code of a {@link Response} instance.
      * @param matcher
      * @return
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/matchers/HttpResponseBodyMatcher.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/matchers/HttpResponseBodyMatcher.java
new file mode 100644
index 0000000..fa4731e
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/matchers/HttpResponseBodyMatcher.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2016 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.matchers;
+
+import javax.ws.rs.core.Response;
+
+import org.apache.http.HttpResponse;
+import org.apache.http.util.EntityUtils;
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+
+import java.io.IOException;
+
+/**
+ * Matcher for matching status code of {@link Response} instance.
+ * @author hmlnarik
+ */
+public class HttpResponseBodyMatcher extends BaseMatcher<HttpResponse> {
+
+    private final Matcher<String> matcher;
+
+    public HttpResponseBodyMatcher(Matcher<String> matcher) {
+        this.matcher = matcher;
+    }
+
+    @Override
+    public boolean matches(Object item) {
+        try {
+            return (item instanceof HttpResponse) && this.matcher.matches(EntityUtils.toString(((HttpResponse) item).getEntity()));
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public void describeTo(Description description) {
+        description.appendText("response body matches ").appendDescriptionOf(this.matcher);
+    }
+
+}
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 7af34f8..f4066f6 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
@@ -16,6 +16,9 @@
  */
 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;
@@ -107,6 +110,40 @@ public class SamlClient {
             }
 
             @Override
+            public HttpPost createSamlPostUnsignedRequest(URI samlEndpoint, String relayState, Document samlRequest) {
+                HttpPost post = new HttpPost(samlEndpoint);
+
+                List<NameValuePair> parameters = new LinkedList<>();
+
+                try {
+                    parameters.add(
+                            new BasicNameValuePair(GeneralConstants.SAML_RESPONSE_KEY,
+                                    new BaseSAML2BindingBuilder()
+                                            .postBinding(samlRequest)
+                                            .encoded())
+                    );
+                } catch (IOException | ConfigurationException | ProcessingException 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
             public URI getBindingUri() {
                 return URI.create(JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get());
             }
@@ -138,11 +175,17 @@ public class SamlClient {
             public URI getBindingUri() {
                 return URI.create(JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get());
             }
+
+            @Override
+            public HttpUriRequest createSamlPostUnsignedRequest(URI samlEndpoint, String relayState, Document samlRequest) {
+                return null;
+            }
         };
 
         public abstract SAMLDocumentHolder extractResponse(CloseableHttpResponse response) throws IOException;
         public abstract HttpUriRequest createSamlRequest(URI samlEndpoint, String relayState, Document samlRequest);
         public abstract URI getBindingUri();
+        public abstract HttpUriRequest createSamlPostUnsignedRequest(URI samlEndpoint, String relayState, Document samlRequest);
     }
 
     public static class RedirectStrategyWithSwitchableFollowRedirect extends LaxRedirectStrategy {
@@ -263,4 +306,46 @@ public class SamlClient {
         }
     }
 
+    /**
+     * Send request for login form and then login using user param
+     * @param user
+     * @param samlEndpoint
+     * @param samlRequest
+     * @param relayState
+     * @param requestBinding
+     * @param expectedResponseBinding
+     * @return
+     */
+    public static SAMLDocumentHolder login(UserRepresentation user, URI samlEndpoint,
+                                           Document samlRequest, String relayState, Binding requestBinding, Binding expectedResponseBinding) {
+        CloseableHttpResponse response = null;
+        SamlClient.RedirectStrategyWithSwitchableFollowRedirect strategy = new SamlClient.RedirectStrategyWithSwitchableFollowRedirect();
+        try (CloseableHttpClient client = HttpClientBuilder.create().setRedirectStrategy(strategy).build()) {
+            HttpClientContext context = HttpClientContext.create();
+
+            HttpUriRequest post = requestBinding.createSamlRequest(samlEndpoint, relayState, samlRequest);
+            response = client.execute(post, context);
+
+            assertThat(response, statusCodeIsHC(Response.Status.OK));
+            String loginPageText = EntityUtils.toString(response.getEntity(), "UTF-8");
+            response.close();
+
+            assertThat(loginPageText, containsString("login"));
+
+            HttpUriRequest loginRequest = handleLoginPage(user, loginPageText);
+
+            strategy.setRedirectable(false);
+            response = client.execute(loginRequest, context);
+
+            return expectedResponseBinding.extractResponse(response);
+        } catch (Exception ex) {
+            throw new RuntimeException(ex);
+        } finally {
+            if (response != null) {
+                EntityUtils.consumeQuietly(response.getEntity());
+                try { response.close(); } catch (IOException ex) { }
+            }
+        }
+    }
+
 }