Merge pull request #3824 from mhajas/KEYCLOAK-4020

KEYCLOAK-4020 add test for boolean attribute
This commit is contained in:
Pavel Drozd 2017-02-13 10:41:04 +01:00 committed by GitHub
commit 6602123b55
7 changed files with 239 additions and 43 deletions

View file

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

View file

@ -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 {

View file

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

View file

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

View file

@ -38,6 +38,15 @@ public class Matchers {
return new ResponseBodyMatcher(matcher);
}
/**
* 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

View file

@ -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;
@ -106,6 +109,40 @@ public class SamlClient {
return post;
}
@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) { }
}
}
}
}

View file

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