From 95d222348d293b59ead275cad22567289c5396ab Mon Sep 17 00:00:00 2001 From: Marko Strukelj Date: Thu, 24 Mar 2016 16:08:36 +0100 Subject: [PATCH 1/3] KEYCLOAK-2589 Copy AssertEvents to Arquillian testsuite and modify to pull events from admin endpoints --- .../idm/EventRepresentation.java | 33 ++ .../servers/auth-server/jboss/build.xml | 42 ++ .../servers/auth-server/jboss/pom.xml | 93 ++++- .../servers/auth-server/pom.xml | 1 + .../auth-server/services/event-queue/pom.xml | 81 ++++ .../events/AssertEventsServletFilter.java | 66 ++++ .../events/EventsListenerProvider.java | 52 +++ .../events/EventsListenerProviderFactory.java | 58 +++ .../testsuite/events/EventsServer.java | 95 +++++ ...ycloak.events.EventListenerProviderFactory | 35 ++ .../main/module.xml | 30 ++ .../servers/auth-server/services/pom.xml | 36 ++ .../integration-arquillian/tests/base/pom.xml | 5 + .../org/keycloak/testsuite/AssertEvents.java | 365 ++++++++++++++++++ .../admin/event/AbstractEventTest.java | 2 +- .../resources/META-INF/keycloak-server.json | 3 +- .../base/src/test/resources/testrealm.json | 186 +++++++++ .../integration-arquillian/tests/pom.xml | 2 + 18 files changed, 1181 insertions(+), 4 deletions(-) create mode 100644 testsuite/integration-arquillian/servers/auth-server/jboss/build.xml create mode 100644 testsuite/integration-arquillian/servers/auth-server/services/event-queue/pom.xml create mode 100644 testsuite/integration-arquillian/servers/auth-server/services/event-queue/src/main/java/org/keycloak/testsuite/events/AssertEventsServletFilter.java create mode 100644 testsuite/integration-arquillian/servers/auth-server/services/event-queue/src/main/java/org/keycloak/testsuite/events/EventsListenerProvider.java create mode 100644 testsuite/integration-arquillian/servers/auth-server/services/event-queue/src/main/java/org/keycloak/testsuite/events/EventsListenerProviderFactory.java create mode 100644 testsuite/integration-arquillian/servers/auth-server/services/event-queue/src/main/java/org/keycloak/testsuite/events/EventsServer.java create mode 100644 testsuite/integration-arquillian/servers/auth-server/services/event-queue/src/main/resources/META-INF/services/org.keycloak.events.EventListenerProviderFactory create mode 100644 testsuite/integration-arquillian/servers/auth-server/services/event-queue/src/main/resources/org/keycloak/testsuite/integration-arquillian-event-queue/main/module.xml create mode 100644 testsuite/integration-arquillian/servers/auth-server/services/pom.xml create mode 100644 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AssertEvents.java create mode 100644 testsuite/integration-arquillian/tests/base/src/test/resources/testrealm.json diff --git a/core/src/main/java/org/keycloak/representations/idm/EventRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/EventRepresentation.java index 93be310efb..0621b9b040 100644 --- a/core/src/main/java/org/keycloak/representations/idm/EventRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/EventRepresentation.java @@ -105,4 +105,37 @@ public class EventRepresentation { public void setDetails(Map details) { this.details = details; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + EventRepresentation that = (EventRepresentation) o; + + if (time != that.time) return false; + if (type != null ? !type.equals(that.type) : that.type != null) return false; + if (realmId != null ? !realmId.equals(that.realmId) : that.realmId != null) return false; + if (clientId != null ? !clientId.equals(that.clientId) : that.clientId != null) return false; + if (userId != null ? !userId.equals(that.userId) : that.userId != null) return false; + if (sessionId != null ? !sessionId.equals(that.sessionId) : that.sessionId != null) return false; + if (ipAddress != null ? !ipAddress.equals(that.ipAddress) : that.ipAddress != null) return false; + if (error != null ? !error.equals(that.error) : that.error != null) return false; + return !(details != null ? !details.equals(that.details) : that.details != null); + + } + + @Override + public int hashCode() { + int result = (int) (time ^ (time >>> 32)); + result = 31 * result + (type != null ? type.hashCode() : 0); + result = 31 * result + (realmId != null ? realmId.hashCode() : 0); + result = 31 * result + (clientId != null ? clientId.hashCode() : 0); + result = 31 * result + (userId != null ? userId.hashCode() : 0); + result = 31 * result + (sessionId != null ? sessionId.hashCode() : 0); + result = 31 * result + (ipAddress != null ? ipAddress.hashCode() : 0); + result = 31 * result + (error != null ? error.hashCode() : 0); + result = 31 * result + (details != null ? details.hashCode() : 0); + return result; + } } diff --git a/testsuite/integration-arquillian/servers/auth-server/jboss/build.xml b/testsuite/integration-arquillian/servers/auth-server/jboss/build.xml new file mode 100644 index 0000000000..b34fc7d7a3 --- /dev/null +++ b/testsuite/integration-arquillian/servers/auth-server/jboss/build.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/testsuite/integration-arquillian/servers/auth-server/jboss/pom.xml b/testsuite/integration-arquillian/servers/auth-server/jboss/pom.xml index 97a18f1db8..9ee8e9a467 100644 --- a/testsuite/integration-arquillian/servers/auth-server/jboss/pom.xml +++ b/testsuite/integration-arquillian/servers/auth-server/jboss/pom.xml @@ -97,13 +97,102 @@ + + copy-event-queue-provider + generate-resources + + copy + + + + + org.keycloak.testsuite + integration-arquillian-event-queue + ${project.version} + jar + false + ${auth.server.home}/modules/org/keycloak/testsuite/integration-arquillian-event-queue/main + + + + + + install-event-queue-module + generate-resources + + unpack + + + + + org.keycloak.testsuite + integration-arquillian-event-queue + ${project.version} + jar + ${auth.server.home}/modules + **/module.xml + + + + - maven-enforcer-plugin + org.apache.maven.plugins + maven-antrun-plugin + 1.8 + + + inject-into-keycloak-server-json + process-resources + + run + + + + + + + + + + + + + ant-contrib + ant-contrib + 1.0b3 + + + ant + ant + + + + + org.apache.ant + ant-apache-bsf + 1.9.3 + + + org.apache.bsf + bsf-api + 3.1 + + + rhino + js + 1.7R2 + + + org.keycloak + keycloak-core + ${project.version} + + - maven-antrun-plugin + maven-enforcer-plugin org.codehaus.mojo diff --git a/testsuite/integration-arquillian/servers/auth-server/pom.xml b/testsuite/integration-arquillian/servers/auth-server/pom.xml index c54f112511..a3328bdaef 100644 --- a/testsuite/integration-arquillian/servers/auth-server/pom.xml +++ b/testsuite/integration-arquillian/servers/auth-server/pom.xml @@ -30,6 +30,7 @@ Auth Server + services jboss undertow diff --git a/testsuite/integration-arquillian/servers/auth-server/services/event-queue/pom.xml b/testsuite/integration-arquillian/servers/auth-server/services/event-queue/pom.xml new file mode 100644 index 0000000000..ad5e007300 --- /dev/null +++ b/testsuite/integration-arquillian/servers/auth-server/services/event-queue/pom.xml @@ -0,0 +1,81 @@ + + + + + + 4.0.0 + + + org.keycloak.testsuite + integration-arquillian-servers-auth-server-services + 2.0.0.CR1-SNAPSHOT + + + integration-arquillian-event-queue + Auth Server Services - Event Queue + + + + + + + org.keycloak + keycloak-dependencies-server-all + pom + + + + org.keycloak + keycloak-core + + + org.keycloak + keycloak-services + + + io.undertow + undertow-core + + + io.undertow + undertow-servlet + + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + + + src/main/resources + true + + + + diff --git a/testsuite/integration-arquillian/servers/auth-server/services/event-queue/src/main/java/org/keycloak/testsuite/events/AssertEventsServletFilter.java b/testsuite/integration-arquillian/servers/auth-server/services/event-queue/src/main/java/org/keycloak/testsuite/events/AssertEventsServletFilter.java new file mode 100644 index 0000000000..6d0d4ed198 --- /dev/null +++ b/testsuite/integration-arquillian/servers/auth-server/services/event-queue/src/main/java/org/keycloak/testsuite/events/AssertEventsServletFilter.java @@ -0,0 +1,66 @@ +/* + * 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.events; + +import org.keycloak.events.Event; +import org.keycloak.util.JsonSerialization; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.concurrent.BlockingQueue; + +/** + * @author Marko Strukelj + */ +public class AssertEventsServletFilter implements Filter { + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + } + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { + HttpServletRequest req = (HttpServletRequest) servletRequest; + + if ("/event-queue".equals(req.getRequestURI().substring(req.getContextPath().length()))) { + BlockingQueue events = EventsListenerProvider.getInstance(); + HttpServletResponse resp = (HttpServletResponse) servletResponse; + resp.setContentType("application/json"); + + Event event = events.poll(); + if (event != null) { + JsonSerialization.writeValueToStream(servletResponse.getOutputStream(), event); + } else { + resp.setStatus(204); + } + } else { + filterChain.doFilter(servletRequest, servletResponse); + } + } + + @Override + public void destroy() { + } +} diff --git a/testsuite/integration-arquillian/servers/auth-server/services/event-queue/src/main/java/org/keycloak/testsuite/events/EventsListenerProvider.java b/testsuite/integration-arquillian/servers/auth-server/services/event-queue/src/main/java/org/keycloak/testsuite/events/EventsListenerProvider.java new file mode 100644 index 0000000000..a4a1a40051 --- /dev/null +++ b/testsuite/integration-arquillian/servers/auth-server/services/event-queue/src/main/java/org/keycloak/testsuite/events/EventsListenerProvider.java @@ -0,0 +1,52 @@ +/* + * 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.events; + +import org.keycloak.events.Event; +import org.keycloak.events.EventListenerProvider; +import org.keycloak.events.admin.AdminEvent; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; + +/** + * @author Marko Strukelj + */ +public class EventsListenerProvider implements EventListenerProvider { + + private static final BlockingQueue events = new LinkedBlockingQueue(); + + @Override + public void onEvent(Event event) { + events.add(event); + } + + @Override + public void onEvent(AdminEvent event, boolean includeRepresentation) { + // TODO: implement if needed + } + + @Override + public void close() { + + } + + public static BlockingQueue getInstance() { + return events; + } +} diff --git a/testsuite/integration-arquillian/servers/auth-server/services/event-queue/src/main/java/org/keycloak/testsuite/events/EventsListenerProviderFactory.java b/testsuite/integration-arquillian/servers/auth-server/services/event-queue/src/main/java/org/keycloak/testsuite/events/EventsListenerProviderFactory.java new file mode 100644 index 0000000000..aecc01abc8 --- /dev/null +++ b/testsuite/integration-arquillian/servers/auth-server/services/event-queue/src/main/java/org/keycloak/testsuite/events/EventsListenerProviderFactory.java @@ -0,0 +1,58 @@ +/* + * 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.events; + +import org.keycloak.Config; +import org.keycloak.events.EventListenerProvider; +import org.keycloak.events.EventListenerProviderFactory; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; + +/** + * @author Marko Strukelj + */ +public class EventsListenerProviderFactory implements EventListenerProviderFactory { + + private static final EventsListenerProvider INSTANCE = new EventsListenerProvider(); + + private EventsServer server = new EventsServer(); + + @Override + public EventListenerProvider create(KeycloakSession session) { + return INSTANCE; + } + + @Override + public void init(Config.Scope config) { + } + + @Override + public void postInit(KeycloakSessionFactory factory) { + server.start(); + } + + @Override + public void close() { + server.stop(); + } + + @Override + public String getId() { + return "event-queue"; + } +} diff --git a/testsuite/integration-arquillian/servers/auth-server/services/event-queue/src/main/java/org/keycloak/testsuite/events/EventsServer.java b/testsuite/integration-arquillian/servers/auth-server/services/event-queue/src/main/java/org/keycloak/testsuite/events/EventsServer.java new file mode 100644 index 0000000000..0b04ee3530 --- /dev/null +++ b/testsuite/integration-arquillian/servers/auth-server/services/event-queue/src/main/java/org/keycloak/testsuite/events/EventsServer.java @@ -0,0 +1,95 @@ +/* + * 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.events; + +import io.undertow.Undertow; +import io.undertow.server.handlers.PathHandler; +import io.undertow.servlet.Servlets; +import io.undertow.servlet.api.DeploymentInfo; +import io.undertow.servlet.api.DeploymentManager; +import io.undertow.servlet.api.FilterInfo; +import io.undertow.servlet.api.ServletContainer; +import io.undertow.servlet.api.ServletContainer.Factory; + +import javax.servlet.DispatcherType; +import javax.servlet.ServletException; +import java.util.logging.Logger; + +/** + * @author Marko Strukelj + */ +public class EventsServer { + + private static final Logger log = Logger.getLogger(EventsServer.class.getName()); + + private String rootPath = "/"; + private int port; + private Undertow server; + + public EventsServer() { + int eventsPort = Integer.parseInt(System.getProperty("auth.server.events.http.port", "8089")); + int portOffset = Integer.parseInt(System.getProperty("auth.server.port.offset", "0")); + int jbossPortOffset = Integer.parseInt(System.getProperty("jboss.socket.binding.port-offset", "-1")); + + log.fine("Configuration:"); + log.fine(" auth.server.events.http.port: " + eventsPort); + log.fine(" auth.server.port.offset: " + portOffset); + log.fine(" jboss.socket.binding.port-offset: " + jbossPortOffset); + port = eventsPort + (jbossPortOffset != -1 ? jbossPortOffset : portOffset); + } + + public void start() { + + PathHandler root = new PathHandler(); + this.server = Undertow.builder().addHttpListener(port, "localhost").setHandler(root).build(); + this.server.start(); + + ServletContainer container = Factory.newInstance(); + + DeploymentInfo di = new DeploymentInfo(); + di.setClassLoader(getClass().getClassLoader()); + di.setContextPath(rootPath); + di.setDeploymentName("testing-event-queue"); + + FilterInfo filter = Servlets.filter("EventsFilter", AssertEventsServletFilter.class); + di.addFilter(filter); + di.addFilterUrlMapping("EventsFilter", "/event-queue", DispatcherType.REQUEST); + + DeploymentManager manager = container.addDeployment(di); + manager.deploy(); + + try { + root.addPrefixPath(rootPath, manager.start()); + } catch (ServletException e) { + throw new RuntimeException(e); + } + log.info("Started EventsServer on port: " + port); + } + + public void stop() { + this.server.stop(); + } + + public void setRootPath(String rootPath) { + this.rootPath = rootPath; + } + + public void setPort(int port) { + this.port = port; + } +} diff --git a/testsuite/integration-arquillian/servers/auth-server/services/event-queue/src/main/resources/META-INF/services/org.keycloak.events.EventListenerProviderFactory b/testsuite/integration-arquillian/servers/auth-server/services/event-queue/src/main/resources/META-INF/services/org.keycloak.events.EventListenerProviderFactory new file mode 100644 index 0000000000..e995149b93 --- /dev/null +++ b/testsuite/integration-arquillian/servers/auth-server/services/event-queue/src/main/resources/META-INF/services/org.keycloak.events.EventListenerProviderFactory @@ -0,0 +1,35 @@ +# +# 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. +# + +# +# 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. +# + +org.keycloak.testsuite.events.EventsListenerProviderFactory \ No newline at end of file diff --git a/testsuite/integration-arquillian/servers/auth-server/services/event-queue/src/main/resources/org/keycloak/testsuite/integration-arquillian-event-queue/main/module.xml b/testsuite/integration-arquillian/servers/auth-server/services/event-queue/src/main/resources/org/keycloak/testsuite/integration-arquillian-event-queue/main/module.xml new file mode 100644 index 0000000000..19f3d4194c --- /dev/null +++ b/testsuite/integration-arquillian/servers/auth-server/services/event-queue/src/main/resources/org/keycloak/testsuite/integration-arquillian-event-queue/main/module.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/testsuite/integration-arquillian/servers/auth-server/services/pom.xml b/testsuite/integration-arquillian/servers/auth-server/services/pom.xml new file mode 100644 index 0000000000..2d96ac0fba --- /dev/null +++ b/testsuite/integration-arquillian/servers/auth-server/services/pom.xml @@ -0,0 +1,36 @@ + + + + + + org.keycloak.testsuite + integration-arquillian-servers-auth-server + 2.0.0.CR1-SNAPSHOT + + 4.0.0 + + integration-arquillian-servers-auth-server-services + pom + Auth Server Services + + + event-queue + + + diff --git a/testsuite/integration-arquillian/tests/base/pom.xml b/testsuite/integration-arquillian/tests/base/pom.xml index 39d376a9c8..05625a18e1 100644 --- a/testsuite/integration-arquillian/tests/base/pom.xml +++ b/testsuite/integration-arquillian/tests/base/pom.xml @@ -57,6 +57,11 @@ commons-configuration 1.10 + + org.keycloak.testsuite + integration-arquillian-event-queue + ${project.version} + diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AssertEvents.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AssertEvents.java new file mode 100644 index 0000000000..8e4c92ac85 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AssertEvents.java @@ -0,0 +1,365 @@ +/* + * 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; + +import org.apache.commons.io.IOUtils; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.hamcrest.CoreMatchers; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; +import org.junit.Assert; +import org.keycloak.OAuth2Constants; +import org.keycloak.admin.client.resource.RealmResource; +import org.keycloak.authentication.authenticators.client.ClientIdAndSecretAuthenticator; +import org.keycloak.common.util.PemUtils; +import org.keycloak.events.Details; +import org.keycloak.events.EventType; +import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.representations.idm.EventRepresentation; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.representations.idm.UserRepresentation; +import org.keycloak.representations.idm.UserSessionRepresentation; +import org.keycloak.util.JsonSerialization; +import org.keycloak.util.TokenUtil; + +import java.io.IOException; +import java.security.PublicKey; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author Stian Thorgersen + */ +public class AssertEvents { + + static final String DEFAULT_CLIENT_ID = "test-app"; + static final String DEFAULT_IP_ADDRESS = "127.0.0.1"; + static final String DEFAULT_REALM = "test"; + static final String DEFAULT_USERNAME = "test-user@localhost"; + + String defaultRedirectUri = "http://localhost:8081/app/auth"; + String defaultEventsQueueUri = "http://localhost:8092"; + + private RealmResource realmResource; + private RealmRepresentation realmRep; + private AbstractKeycloakTest context; + private PublicKey realmPublicKey; + private UserRepresentation defaultUser; + + public AssertEvents(AbstractKeycloakTest ctx) throws Exception { + context = ctx; + + realmResource = context.adminClient.realms().realm(DEFAULT_REALM); + realmRep = realmResource.toRepresentation(); + String pubKeyString = realmRep.getPublicKey(); + realmPublicKey = PemUtils.decodePublicKey(pubKeyString); + + defaultUser = getUser(DEFAULT_USERNAME); + if (defaultUser == null) { + throw new RuntimeException("Default user does not exist: " + DEFAULT_USERNAME + ". Make sure to add it to your test realm."); + } + + defaultEventsQueueUri = getAuthServerEventsQueueUri(); + } + + String getAuthServerEventsQueueUri() { + int httpPort = Integer.parseInt(System.getProperty("auth.server.event.http.port", "8089")); + int portOffset = Integer.parseInt(System.getProperty("auth.server.port.offset", "0")); + return "http://localhost:" + (httpPort + portOffset); + } + + public EventRepresentation poll() { + EventRepresentation event = fetchNextEvent(); + Assert.assertNotNull("Event expected", event); + + return event; + } + + public void clear() { + realmResource.clearEvents(); + } + + public ExpectedEvent expectRequiredAction(EventType event) { + return expectLogin().event(event).removeDetail(Details.CONSENT).session(isUUID()); + } + + public ExpectedEvent expectLogin() { + return expect(EventType.LOGIN) + .detail(Details.CODE_ID, isCodeId()) + //.detail(Details.USERNAME, DEFAULT_USERNAME) + //.detail(Details.AUTH_METHOD, OIDCLoginProtocol.LOGIN_PROTOCOL) + //.detail(Details.AUTH_TYPE, AuthorizationEndpoint.CODE_AUTH_TYPE) + .detail(Details.REDIRECT_URI, defaultRedirectUri) + .detail(Details.CONSENT, Details.CONSENT_VALUE_NO_CONSENT_REQUIRED) + .session(isUUID()); + } + + public ExpectedEvent expectClientLogin() { + return expect(EventType.CLIENT_LOGIN) + .detail(Details.CODE_ID, isCodeId()) + .detail(Details.CLIENT_AUTH_METHOD, ClientIdAndSecretAuthenticator.PROVIDER_ID) + .detail(Details.GRANT_TYPE, OAuth2Constants.CLIENT_CREDENTIALS) + .removeDetail(Details.CODE_ID) + .session(isUUID()); + } + + public ExpectedEvent expectSocialLogin() { + return expect(EventType.LOGIN) + .detail(Details.CODE_ID, isCodeId()) + .detail(Details.USERNAME, DEFAULT_USERNAME) + .detail(Details.AUTH_METHOD, "form") + .detail(Details.REDIRECT_URI, defaultRedirectUri) + .session(isUUID()); + } + + public ExpectedEvent expectCodeToToken(String codeId, String sessionId) { + return expect(EventType.CODE_TO_TOKEN) + .detail(Details.CODE_ID, codeId) + .detail(Details.TOKEN_ID, isUUID()) + .detail(Details.REFRESH_TOKEN_ID, isUUID()) + .detail(Details.REFRESH_TOKEN_TYPE, TokenUtil.TOKEN_TYPE_REFRESH) + .detail(Details.CLIENT_AUTH_METHOD, ClientIdAndSecretAuthenticator.PROVIDER_ID) + .session(sessionId); + } + + public ExpectedEvent expectRefresh(String refreshTokenId, String sessionId) { + return expect(EventType.REFRESH_TOKEN) + .detail(Details.TOKEN_ID, isUUID()) + .detail(Details.REFRESH_TOKEN_ID, refreshTokenId) + .detail(Details.REFRESH_TOKEN_TYPE, TokenUtil.TOKEN_TYPE_REFRESH) + .detail(Details.UPDATED_REFRESH_TOKEN_ID, isUUID()) + .detail(Details.CLIENT_AUTH_METHOD, ClientIdAndSecretAuthenticator.PROVIDER_ID) + .session(sessionId); + } + + public ExpectedEvent expectLogout(String sessionId) { + return expect(EventType.LOGOUT).client((String) null) + .detail(Details.REDIRECT_URI, defaultRedirectUri) + .session(sessionId); + } + + public ExpectedEvent expectRegister(String username, String email) { + UserRepresentation user = username != null ? getUser(username) : null; + return expect(EventType.REGISTER) + .user(user != null ? user.getId() : null) + .detail(Details.USERNAME, username) + .detail(Details.EMAIL, email) + .detail(Details.REGISTER_METHOD, "form") + .detail(Details.REDIRECT_URI, defaultRedirectUri); + } + + public ExpectedEvent expectAccount(EventType event) { + return expect(event).client("account"); + } + + public ExpectedEvent expect(EventType event) { + return new ExpectedEvent() + .realm(realmRep.getId()) + .client(DEFAULT_CLIENT_ID) + .user(defaultUser.getId()) + .ipAddress(DEFAULT_IP_ADDRESS) + .session((String) null) + .event(event); + } + + UserRepresentation getUser(String username) { + List result = realmResource.users().search(username, null, null, null, 0, 1); + return result.size() > 0 ? result.get(0) : null; + } + + public PublicKey getRealmPublicKey() { + return realmPublicKey; + } + + public class ExpectedEvent { + private EventRepresentation expected = new EventRepresentation(); + private Matcher userId; + private Matcher sessionId; + private HashMap> details; + + public ExpectedEvent realm(RealmRepresentation realm) { + expected.setRealmId(realm.getId()); + return this; + } + + public ExpectedEvent realm(String realmId) { + expected.setRealmId(realmId); + return this; + } + + public ExpectedEvent client(ClientRepresentation client) { + expected.setClientId(client.getClientId()); + return this; + } + + public ExpectedEvent client(String clientId) { + expected.setClientId(clientId); + return this; + } + + public ExpectedEvent user(UserRepresentation user) { + return user(user.getId()); + } + + public ExpectedEvent user(String userId) { + return user(CoreMatchers.equalTo(userId)); + } + + public ExpectedEvent user(Matcher userId) { + this.userId = userId; + return this; + } + + public ExpectedEvent session(UserSessionRepresentation session) { + return session(session.getId()); + } + + public ExpectedEvent session(String sessionId) { + return session(CoreMatchers.equalTo(sessionId)); + } + + public ExpectedEvent session(Matcher sessionId) { + this.sessionId = sessionId; + return this; + } + + public ExpectedEvent ipAddress(String ipAddress) { + expected.setIpAddress(ipAddress); + return this; + } + + public ExpectedEvent event(EventType e) { + expected.setType(e.name()); + return this; + } + + public ExpectedEvent detail(String key, String value) { + return detail(key, CoreMatchers.equalTo(value)); + } + + public ExpectedEvent detail(String key, Matcher matcher) { + if (details == null) { + details = new HashMap>(); + } + details.put(key, matcher); + return this; + } + + public ExpectedEvent removeDetail(String key) { + if (details != null) { + details.remove(key); + } + return this; + } + + public ExpectedEvent clearDetails() { + if (details != null) details.clear(); + return this; + } + + public ExpectedEvent error(String error) { + expected.setError(error); + return this; + } + + public EventRepresentation assertEvent() { + return assertEvent(poll()); + } + + public EventRepresentation assertEvent(EventRepresentation actual) { + if (expected.getError() != null && !expected.getType().toString().endsWith("_ERROR")) { + expected.setType(expected.getType() + "_ERROR"); + } + Assert.assertEquals(expected.getType(), actual.getType()); + Assert.assertEquals(expected.getRealmId(), actual.getRealmId()); + Assert.assertEquals(expected.getClientId(), actual.getClientId()); + Assert.assertEquals(expected.getError(), actual.getError()); + Assert.assertEquals(expected.getIpAddress(), actual.getIpAddress()); + Assert.assertThat(actual.getUserId(), userId); + Assert.assertThat(actual.getSessionId(), sessionId); + + if (details == null || details.isEmpty()) { +// Assert.assertNull(actual.getDetails()); + } else { + Assert.assertNotNull(actual.getDetails()); + for (Map.Entry> d : details.entrySet()) { + String actualValue = actual.getDetails().get(d.getKey()); + if (!actual.getDetails().containsKey(d.getKey())) { + Assert.fail(d.getKey() + " missing"); + } + + Assert.assertThat("Unexpected value for " + d.getKey(), actualValue, d.getValue()); + } + /* + for (String k : actual.getDetails().keySet()) { + if (!details.containsKey(k)) { + Assert.fail(k + " was not expected"); + } + } + */ + } + + return actual; + } + } + + public static Matcher isCodeId() { + return isUUID(); + } + + public static Matcher isUUID() { + return new TypeSafeMatcher() { + @Override + protected boolean matchesSafely(String item) { + return 36 == item.length() && item.charAt(8) == '-' && item.charAt(13) == '-' && item.charAt(18) == '-' && item.charAt(23) == '-'; + } + + @Override + public void describeTo(Description description) { + description.appendText("Not an UUID"); + } + }; + } + + private EventRepresentation fetchNextEvent() { + CloseableHttpClient httpclient = HttpClients.createDefault(); + try { + HttpPost post = new HttpPost(defaultEventsQueueUri + "/event-queue"); + CloseableHttpResponse response = httpclient.execute(post); + if (response.getStatusLine().getStatusCode() != 200) { + throw new RuntimeException("Failed to retrieve event from " + post.getURI() + ": " + response.getStatusLine().toString() + " / " + IOUtils.toString(response.getEntity().getContent())); + } + + return JsonSerialization.readValue(response.getEntity().getContent(), EventRepresentation.class); + } catch (Exception e) { + throw new RuntimeException(e); + } + finally { + try { + httpclient.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/event/AbstractEventTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/event/AbstractEventTest.java index ba50fe7227..597556cea5 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/event/AbstractEventTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/event/AbstractEventTest.java @@ -38,7 +38,7 @@ public abstract class AbstractEventTest extends AbstractAuthTest { configRep.setAdminEventsDetailsEnabled(false); configRep.setAdminEventsEnabled(false); configRep.setEventsEnabled(false); - configRep.setEnabledEventTypes(Collections.EMPTY_LIST); // resets to all types + configRep.setEnabledEventTypes(Collections.emptyList()); // resets to all types saveConfig(); } diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json b/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json index ae0f5f9a07..f5d64a511a 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json @@ -11,7 +11,8 @@ "jboss-logging" : { "success-level": "debug", "error-level": "warn" - } + }, + "event-queue": {} }, "realm": { diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/testrealm.json b/testsuite/integration-arquillian/tests/base/src/test/resources/testrealm.json new file mode 100644 index 0000000000..845adda955 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/testrealm.json @@ -0,0 +1,186 @@ +{ + "id": "test", + "realm": "test", + "enabled": true, + "sslRequired": "external", + "registrationAllowed": true, + "resetPasswordAllowed": true, + "editUsernameAllowed" : true, + "privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=", + "publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB", + "requiredCredentials": [ "password" ], + "defaultRoles": [ "user" ], + "smtpServer": { + "from": "auto@keycloak.org", + "host": "localhost", + "port":"3025" + }, + "users" : [ + { + "username" : "test-user@localhost", + "enabled": true, + "email" : "test-user@localhost", + "firstName": "Tom", + "lastName": "Brady", + "credentials" : [ + { "type" : "password", + "value" : "password" } + ], + "realmRoles": ["user", "offline_access"], + "clientRoles": { + "test-app": [ "customer-user" ], + "account": [ "view-profile", "manage-account" ] + } + }, + { + "username" : "john-doh@localhost", + "enabled": true, + "email" : "john-doh@localhost", + "firstName": "John", + "lastName": "Doh", + "credentials" : [ + { "type" : "password", + "value" : "password" } + ], + "realmRoles": ["user"], + "clientRoles": { + "test-app": [ "customer-user" ], + "account": [ "view-profile", "manage-account" ] + } + }, + { + "username" : "keycloak-user@localhost", + "enabled": true, + "email" : "keycloak-user@localhost", + "credentials" : [ + { "type" : "password", + "value" : "password" } + ], + "realmRoles": ["user"], + "clientRoles": { + "test-app": [ "customer-user" ], + "account": [ "view-profile", "manage-account" ] + } + }, + { + "username" : "topGroupUser", + "enabled": true, + "email" : "top@redhat.com", + "credentials" : [ + { "type" : "password", + "value" : "password" } + ], + "groups": [ + "/topGroup" + ] + }, + { + "username" : "level2GroupUser", + "enabled": true, + "email" : "level2@redhat.com", + "credentials" : [ + { "type" : "password", + "value" : "password" } + ], + "groups": [ + "/topGroup/level2group" + ] + } + ], + "scopeMappings": [ + { + "client": "third-party", + "roles": ["user"] + }, + { + "client": "test-app", + "roles": ["user"] + } + ], + "clients": [ + { + "clientId": "test-app", + "enabled": true, + "baseUrl": "http://localhost:8081/app", + "redirectUris": [ + "http://localhost:8081/app/*" + ], + "adminUrl": "http://localhost:8081/app/logout", + "secret": "password" + }, + { + "clientId" : "third-party", + "enabled": true, + "consentRequired": true, + + "redirectUris": [ + "http://localhost:8081/app/*" + ], + "secret": "password" + } + ], + "roles" : { + "realm" : [ + { + "name": "user", + "description": "Have User privileges" + }, + { + "name": "admin", + "description": "Have Administrator privileges" + } + ], + "client" : { + "test-app" : [ + { + "name": "customer-user", + "description": "Have Customer User privileges" + }, + { + "name": "customer-admin", + "description": "Have Customer Admin privileges" + } + ] + } + + }, + "groups" : [ + { + "name": "topGroup", + "attributes": { + "topAttribute": ["true"] + + }, + "realmRoles": ["user"], + + "subGroups": [ + { + "name": "level2group", + "realmRoles": ["admin"], + "clientRoles": { + "test-app": ["customer-user"] + }, + "attributes": { + "level2Attribute": ["true"] + + } + } + ] + } + ], + + + "clientScopeMappings": { + "test-app": [ + { + "client": "third-party", + "roles": ["customer-user"] + } + ] + }, + + "internationalizationEnabled": true, + "supportedLocales": ["en", "de"], + "defaultLocale": "en", + "eventsListeners": ["jboss-logging", "event-queue"] +} diff --git a/testsuite/integration-arquillian/tests/pom.xml b/testsuite/integration-arquillian/tests/pom.xml index 015f5336dc..2cb09d539d 100644 --- a/testsuite/integration-arquillian/tests/pom.xml +++ b/testsuite/integration-arquillian/tests/pom.xml @@ -48,6 +48,7 @@ 100 8180 + 8089 8543 10090 10099 @@ -140,6 +141,7 @@ ${auth.server.port.offset} ${auth.server.http.port} + ${auth.server.events.http.port} ${auth.server.https.port} ${auth.server.management.port} ${auth.server.management.port.jmx} From 790a068bdb08610992b6a7c05b76ef705195c85f Mon Sep 17 00:00:00 2001 From: Marko Strukelj Date: Thu, 24 Mar 2016 16:21:36 +0100 Subject: [PATCH 2/3] KEYCLOAK-2607 Migrate Group tests to ARQ testsuite --- .../admin/group/AbstractGroupTest.java | 86 ++++++++++ .../admin/group/GroupMappersTest.java | 139 ++++++++++++++++ .../testsuite/admin/group}/GroupTest.java | 157 ++++-------------- 3 files changed, 257 insertions(+), 125 deletions(-) create mode 100644 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/group/AbstractGroupTest.java create mode 100644 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/group/GroupMappersTest.java rename testsuite/{integration/src/test/java/org/keycloak/testsuite/model => integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/group}/GroupTest.java (50%) mode change 100755 => 100644 diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/group/AbstractGroupTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/group/AbstractGroupTest.java new file mode 100644 index 0000000000..264551b531 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/group/AbstractGroupTest.java @@ -0,0 +1,86 @@ +/* + * 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.admin.group; + +import org.junit.Before; +import org.keycloak.OAuth2Constants; +import org.keycloak.RSATokenVerifier; +import org.keycloak.events.Details; +import org.keycloak.jose.jws.JWSInput; +import org.keycloak.jose.jws.crypto.RSAProvider; +import org.keycloak.representations.AccessToken; +import org.keycloak.representations.AccessTokenResponse; +import org.keycloak.representations.RefreshToken; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.testsuite.AbstractKeycloakTest; +import org.keycloak.testsuite.AssertEvents; +import org.keycloak.testsuite.arquillian.AuthServerTestEnricher; + +import java.util.List; + +import static org.keycloak.testsuite.util.IOUtil.loadRealm; + +/** + * @author Marko Strukelj + */ +public abstract class AbstractGroupTest extends AbstractKeycloakTest { + + AssertEvents events; + + @Before + public void initAssertEvents() throws Exception { + events = new AssertEvents(this); + } + + AccessToken login(String login, String clientId, String clientSecret, String userId) throws Exception { + + AccessTokenResponse tokenResponse = oauthClient.getToken("test", clientId, clientSecret, login, "password"); + + String accessToken = tokenResponse.getToken(); + String refreshToken = tokenResponse.getRefreshToken(); + + AccessToken accessTokenRepresentation = RSATokenVerifier.verifyToken(accessToken, events.getRealmPublicKey(), AuthServerTestEnricher.getAuthServerContextRoot() + "/auth/realms/test"); + + JWSInput jws = new JWSInput(refreshToken); + if (!RSAProvider.verify(jws, events.getRealmPublicKey())) { + throw new RuntimeException("Invalid refresh token"); + } + RefreshToken refreshTokenRepresentation = jws.readJsonContent(RefreshToken.class); + + events.expectLogin() + .client(clientId) + .user(userId) + .session(tokenResponse.getSessionState()) + .detail(Details.GRANT_TYPE, OAuth2Constants.PASSWORD) + .detail(Details.TOKEN_ID, accessTokenRepresentation.getId()) + .detail(Details.REFRESH_TOKEN_ID, refreshTokenRepresentation.getId()) + .detail(Details.USERNAME, login) + .removeDetail(Details.CODE_ID) + .removeDetail(Details.REDIRECT_URI) + .removeDetail(Details.CONSENT) + .assertEvent(); + + return accessTokenRepresentation; + } + + RealmRepresentation loadTestRealm(List testRealms) { + RealmRepresentation result = loadRealm("/testrealm.json"); + testRealms.add(result); + return result; + } +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/group/GroupMappersTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/group/GroupMappersTest.java new file mode 100644 index 0000000000..d61a517ce6 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/group/GroupMappersTest.java @@ -0,0 +1,139 @@ +/* + * 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.admin.group; + +import org.junit.Assert; +import org.junit.Test; +import org.keycloak.admin.client.resource.RealmResource; +import org.keycloak.protocol.ProtocolMapperUtils; +import org.keycloak.protocol.oidc.OIDCLoginProtocol; +import org.keycloak.protocol.oidc.mappers.GroupMembershipMapper; +import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper; +import org.keycloak.protocol.oidc.mappers.UserAttributeMapper; +import org.keycloak.provider.ProviderConfigProperty; +import org.keycloak.representations.AccessToken; +import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.representations.idm.ProtocolMapperRepresentation; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.representations.idm.UserRepresentation; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +/** + * @author Marko Strukelj + */ +public class GroupMappersTest extends AbstractGroupTest { + + @Override + public void addTestRealms(List testRealms) { + RealmRepresentation testRealmRep = loadTestRealm(testRealms); + + testRealmRep.setEventsEnabled(true); + + ClientRepresentation client = getClientByAlias(testRealmRep, "test-app"); + Assert.assertNotNull("test-app client exists", client); + + client.setDirectAccessGrantsEnabled(true); + + List mappers = new LinkedList<>(); + ProtocolMapperRepresentation mapper = new ProtocolMapperRepresentation(); + mapper.setName("groups"); + mapper.setProtocolMapper(GroupMembershipMapper.PROVIDER_ID); + mapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL); + mapper.setConsentRequired(false); + Map config = new HashMap<>(); + config.put(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME, "groups"); + config.put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, "true"); + config.put(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, "true"); + mapper.setConfig(config); + mappers.add(mapper); + + mapper = new ProtocolMapperRepresentation(); + mapper.setName("topAttribute"); + mapper.setProtocolMapper(UserAttributeMapper.PROVIDER_ID); + mapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL); + mapper.setConsentRequired(false); + config = new HashMap<>(); + config.put(ProtocolMapperUtils.USER_ATTRIBUTE, "topAttribute"); + config.put(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME, "topAttribute"); + config.put(OIDCAttributeMapperHelper.JSON_TYPE, ProviderConfigProperty.STRING_TYPE); + config.put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, "true"); + config.put(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, "true"); + mapper.setConfig(config); + mappers.add(mapper); + + mapper = new ProtocolMapperRepresentation(); + mapper.setName("level2Attribute"); + mapper.setProtocolMapper(UserAttributeMapper.PROVIDER_ID); + mapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL); + mapper.setConsentRequired(false); + config = new HashMap<>(); + config.put(ProtocolMapperUtils.USER_ATTRIBUTE, "level2Attribute"); + config.put(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME, "level2Attribute"); + config.put(OIDCAttributeMapperHelper.JSON_TYPE, ProviderConfigProperty.STRING_TYPE); + config.put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, "true"); + config.put(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, "true"); + mapper.setConfig(config); + mappers.add(mapper); + + client.setProtocolMappers(mappers); + } + + private ClientRepresentation getClientByAlias(RealmRepresentation testRealmRep, String alias) { + for (ClientRepresentation client: testRealmRep.getClients()) { + if (alias.equals(client.getClientId())) { + return client; + } + } + return null; + } + + @Test + @SuppressWarnings("unchecked") + public void testGroupMappers() throws Exception { + RealmResource realm = adminClient.realms().realm("test"); + { + UserRepresentation user = realm.users().search("topGroupUser", -1, -1).get(0); + + AccessToken token = login(user.getUsername(), "test-app", "password", user.getId()); + Assert.assertTrue(token.getRealmAccess().getRoles().contains("user")); + List groups = (List) token.getOtherClaims().get("groups"); + Assert.assertNotNull(groups); + Assert.assertTrue(groups.size() == 1); + Assert.assertEquals("topGroup", groups.get(0)); + Assert.assertEquals("true", token.getOtherClaims().get("topAttribute")); + } + { + UserRepresentation user = realm.users().search("level2GroupUser", -1, -1).get(0); + + AccessToken token = login(user.getUsername(), "test-app", "password", user.getId()); + Assert.assertTrue(token.getRealmAccess().getRoles().contains("user")); + Assert.assertTrue(token.getRealmAccess().getRoles().contains("admin")); + Assert.assertTrue(token.getResourceAccess("test-app").getRoles().contains("customer-user")); + List groups = (List) token.getOtherClaims().get("groups"); + Assert.assertNotNull(groups); + Assert.assertTrue(groups.size() == 1); + Assert.assertEquals("level2group", groups.get(0)); + Assert.assertEquals("true", token.getOtherClaims().get("topAttribute")); + Assert.assertEquals("true", token.getOtherClaims().get("level2Attribute")); + } + } +} diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/GroupTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/group/GroupTest.java old mode 100755 new mode 100644 similarity index 50% rename from testsuite/integration/src/test/java/org/keycloak/testsuite/model/GroupTest.java rename to testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/group/GroupTest.java index 7cc900bcd3..81d8e6103b --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/GroupTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/group/GroupTest.java @@ -15,89 +15,64 @@ * limitations under the License. */ -package org.keycloak.testsuite.model; +package org.keycloak.testsuite.admin.group; import org.junit.Assert; -import org.junit.ClassRule; -import org.junit.Rule; import org.junit.Test; -import org.keycloak.OAuth2Constants; -import org.keycloak.admin.client.Keycloak; import org.keycloak.admin.client.resource.RealmResource; -import org.keycloak.events.Details; -import org.keycloak.models.ClientModel; -import org.keycloak.models.Constants; -import org.keycloak.models.RealmModel; -import org.keycloak.models.UserCredentialModel; -import org.keycloak.models.UserModel; -import org.keycloak.models.utils.KeycloakModelUtils; -import org.keycloak.protocol.oidc.mappers.GroupMembershipMapper; -import org.keycloak.protocol.oidc.mappers.UserAttributeMapper; -import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.representations.AccessToken; -import org.keycloak.representations.RefreshToken; +import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.representations.idm.GroupRepresentation; +import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RoleRepresentation; import org.keycloak.representations.idm.UserRepresentation; -import org.keycloak.services.managers.RealmManager; -import org.keycloak.testsuite.AssertEvents; -import org.keycloak.testsuite.OAuthClient; -import org.keycloak.testsuite.rule.KeycloakRule; -import org.keycloak.testsuite.rule.WebResource; -import org.keycloak.testsuite.rule.WebRule; -import org.openqa.selenium.WebDriver; import javax.ws.rs.NotFoundException; import javax.ws.rs.core.Response; - import java.util.LinkedList; import java.util.List; +import java.util.UUID; -import static org.junit.Assert.assertEquals; /** - * @author Stian Thorgersen + * @author Marko Strukelj */ -public class GroupTest { +public class GroupTest extends AbstractGroupTest { - @ClassRule - public static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() { - @Override - public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) { - ClientModel app = KeycloakModelUtils.createClient(appRealm, "resource-owner"); - app.setDirectAccessGrantsEnabled(true); - app.setSecret("secret"); + @Override + public void addTestRealms(List testRealms) { + RealmRepresentation testRealmRep = loadTestRealm(testRealms); - app = appRealm.getClientByClientId("test-app"); - app.setDirectAccessGrantsEnabled(true); + testRealmRep.setEventsEnabled(true); - UserModel user = session.users().addUser(appRealm, "direct-login"); - user.setEmail("direct-login@localhost"); - user.setEnabled(true); + List users = testRealmRep.getUsers(); + + UserRepresentation user = new UserRepresentation(); + user.setUsername("direct-login"); + user.setEmail("direct-login@localhost"); + user.setEnabled(true); + List credentials = new LinkedList<>(); + CredentialRepresentation credential = new CredentialRepresentation(); + credential.setType(CredentialRepresentation.PASSWORD); + credential.setValue("password"); + credentials.add(credential); + user.setCredentials(credentials); + users.add(user); - session.users().updateCredential(appRealm, user, UserCredentialModel.password("password")); - keycloak = Keycloak.getInstance("http://localhost:8081/auth", "master", "admin", "admin", Constants.ADMIN_CLI_CLIENT_ID); - } - }); + List clients = testRealmRep.getClients(); - protected static Keycloak keycloak; - - @Rule - public AssertEvents events = new AssertEvents(keycloakRule); - - @Rule - public WebRule webRule = new WebRule(this); - - @WebResource - protected WebDriver driver; - - @WebResource - protected OAuthClient oauth; + ClientRepresentation client = new ClientRepresentation(); + client.setClientId("resource-owner"); + client.setDirectAccessGrantsEnabled(true); + client.setSecret("secret"); + clients.add(client); + } @Test public void createAndTestGroups() throws Exception { - RealmResource realm = keycloak.realms().realm("test"); + RealmResource realm = adminClient.realms().realm("test"); { RoleRepresentation groupRole = new RoleRepresentation(); groupRole.setName("topRole"); @@ -207,73 +182,5 @@ public class GroupTest { realm.removeDefaultGroup(level3Group.getId()); defaultGroups = realm.getDefaultGroups(); Assert.assertEquals(0, defaultGroups.size()); - } - - @Test - public void testGroupMappers() throws Exception { - keycloakRule.update(new KeycloakRule.KeycloakSetup() { - @Override - public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) { - ClientModel app = appRealm.getClientByClientId("test-app"); - app.addProtocolMapper(GroupMembershipMapper.create("groups", "groups", false, null, true, true)); - app.addProtocolMapper(UserAttributeMapper.createClaimMapper("topAttribute", "topAttribute", "topAttribute", ProviderConfigProperty.STRING_TYPE, false, null, true, true, false)); - app.addProtocolMapper(UserAttributeMapper.createClaimMapper("level2Attribute", "level2Attribute", "level2Attribute", ProviderConfigProperty.STRING_TYPE, false, null, true, true, false)); - } - }, "test"); - RealmResource realm = keycloak.realms().realm("test"); - { - UserRepresentation user = realm.users().search("topGroupUser", -1, -1).get(0); - - AccessToken token = login(user.getUsername(), "test-app", "password", user.getId()); - Assert.assertTrue(token.getRealmAccess().getRoles().contains("user")); - List groups = (List) token.getOtherClaims().get("groups"); - Assert.assertNotNull(groups); - Assert.assertTrue(groups.size() == 1); - Assert.assertEquals("topGroup", groups.get(0)); - Assert.assertEquals("true", token.getOtherClaims().get("topAttribute")); - } - { - UserRepresentation user = realm.users().search("level2GroupUser", -1, -1).get(0); - - AccessToken token = login(user.getUsername(), "test-app", "password", user.getId()); - Assert.assertTrue(token.getRealmAccess().getRoles().contains("user")); - Assert.assertTrue(token.getRealmAccess().getRoles().contains("admin")); - Assert.assertTrue(token.getResourceAccess("test-app").getRoles().contains("customer-user")); - List groups = (List) token.getOtherClaims().get("groups"); - Assert.assertNotNull(groups); - Assert.assertTrue(groups.size() == 1); - Assert.assertEquals("level2group", groups.get(0)); - Assert.assertEquals("true", token.getOtherClaims().get("topAttribute")); - Assert.assertEquals("true", token.getOtherClaims().get("level2Attribute")); - } - - } - - protected AccessToken login(String login, String clientId, String clientSecret, String userId) throws Exception { - oauth.clientId(clientId); - - OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest(clientSecret, login, "password"); - - assertEquals(200, response.getStatusCode()); - - AccessToken accessToken = oauth.verifyToken(response.getAccessToken()); - RefreshToken refreshToken = oauth.verifyRefreshToken(response.getRefreshToken()); - - events.expectLogin() - .client(clientId) - .user(userId) - .session(accessToken.getSessionState()) - .detail(Details.GRANT_TYPE, OAuth2Constants.PASSWORD) - .detail(Details.TOKEN_ID, accessToken.getId()) - .detail(Details.REFRESH_TOKEN_ID, refreshToken.getId()) - .detail(Details.USERNAME, login) - .removeDetail(Details.CODE_ID) - .removeDetail(Details.REDIRECT_URI) - .removeDetail(Details.CONSENT) - .assertEvent(); - return accessToken; - } - - } From 76a4db5d547195f060e23ea3fe30680df6057557 Mon Sep 17 00:00:00 2001 From: Marko Strukelj Date: Thu, 24 Mar 2016 17:07:20 +0100 Subject: [PATCH 3/3] KEYCLOAK-2597 Invalid children group location header response --- .../resources/admin/GroupResource.java | 2 +- .../testsuite/admin/group/GroupTest.java | 21 ++++- .../keycloak/testsuite/util/URLAssert.java | 78 ++++++++++++++++++- 3 files changed, 96 insertions(+), 5 deletions(-) diff --git a/services/src/main/java/org/keycloak/services/resources/admin/GroupResource.java b/services/src/main/java/org/keycloak/services/resources/admin/GroupResource.java index e82198517a..0563112fd9 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/GroupResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/GroupResource.java @@ -129,7 +129,7 @@ public class GroupResource { child = realm.createGroup(rep.getName()); updateGroup(rep, child); URI uri = uriInfo.getBaseUriBuilder() - .path(uriInfo.getMatchedURIs().get(1)) + .path(uriInfo.getMatchedURIs().get(2)) .path(child.getId()).build(); builder.status(201).location(uri); adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo).representation(rep).success(); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/group/GroupTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/group/GroupTest.java index 81d8e6103b..e36985936a 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/group/GroupTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/group/GroupTest.java @@ -27,12 +27,16 @@ import org.keycloak.representations.idm.GroupRepresentation; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RoleRepresentation; import org.keycloak.representations.idm.UserRepresentation; +import org.keycloak.testsuite.admin.ApiUtil; +import org.keycloak.testsuite.util.URLAssert; +import org.keycloak.util.JsonSerialization; import javax.ws.rs.NotFoundException; import javax.ws.rs.core.Response; +import java.io.IOException; +import java.net.URI; import java.util.LinkedList; import java.util.List; -import java.util.UUID; /** @@ -107,6 +111,21 @@ public class GroupTest extends AbstractGroupTest { level2Group.setName("level2"); response = realm.groups().group(topGroup.getId()).subGroup(level2Group); response.close(); + + URI location = response.getLocation(); + final String level2Id = ApiUtil.getCreatedId(response); + final GroupRepresentation level2GroupById = realm.groups().group(level2Id).toRepresentation(); + Assert.assertEquals(level2Id, level2GroupById.getId()); + Assert.assertEquals(level2Group.getName(), level2GroupById.getName()); + + URLAssert.assertGetURL(location, adminClient.tokenManager().getAccessTokenString(), new URLAssert.AssertJSONResponseHandler() { + @Override + protected void assertResponseBody(String body) throws IOException { + GroupRepresentation level2 = JsonSerialization.readValue(body, GroupRepresentation.class); + Assert.assertEquals(level2Id, level2.getId()); + } + }); + level2Group = realm.getGroupByPath("/top/level2"); Assert.assertNotNull(level2Group); roles.clear(); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/URLAssert.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/URLAssert.java index c66fd70f66..6ef0bbe321 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/URLAssert.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/URLAssert.java @@ -18,14 +18,27 @@ package org.keycloak.testsuite.util; import javax.ws.rs.core.UriBuilder; + +import org.apache.http.Header; +import org.apache.http.HttpEntity; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.junit.Assert; import org.keycloak.testsuite.page.AbstractPage; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import org.keycloak.testsuite.auth.page.login.PageWithLoginUrl; import org.openqa.selenium.WebDriver; -import org.openqa.selenium.support.ui.ExpectedCondition; -import org.openqa.selenium.support.ui.WebDriverWait; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.StringWriter; +import java.net.URI; +import java.nio.charset.Charset; /** * @@ -104,9 +117,68 @@ public class URLAssert { public static void assertCurrentUrlStartsWithLoginUrlOf(PageWithLoginUrl page) { assertCurrentUrlStartsWithLoginUrlOf(page.getDriver(), page); } - + public static void assertCurrentUrlStartsWithLoginUrlOf(WebDriver driver, PageWithLoginUrl page) { assertCurrentUrlStartsWith(driver, page.getOIDCLoginUrl().toString()); } + public static void assertGetURL(URI url, String accessToken, AssertResponseHandler handler) { + CloseableHttpClient httpclient = HttpClients.createDefault(); + try { + HttpGet get = new HttpGet(url); + get.setHeader("Authorization", "Bearer " + accessToken); + + CloseableHttpResponse response = httpclient.execute(get); + + if (response.getStatusLine().getStatusCode() != 200) { + throw new RuntimeException("Response status error: " + response.getStatusLine().getStatusCode() + ": " + url); + } + + handler.assertResponse(response); + + } catch (Exception e) { + throw new RuntimeException(e); + } + finally { + try { + httpclient.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + public interface AssertResponseHandler { + void assertResponse(CloseableHttpResponse response) throws IOException; + } + + public static abstract class AssertJSONResponseHandler implements AssertResponseHandler { + + @Override + public void assertResponse(CloseableHttpResponse response) throws IOException { + HttpEntity entity = response.getEntity(); + Header contentType = entity.getContentType(); + Assert.assertEquals("application/json", contentType.getValue()); + + char [] buf = new char[8192]; + StringWriter out = new StringWriter(); + Reader in = new InputStreamReader(entity.getContent(), Charset.forName("utf-8")); + int rc = 0; + try { + while ((rc = in.read(buf)) != -1) { + out.write(buf, 0, rc); + } + } finally { + try { + in.close(); + } catch (Exception ignored) {} + + out.close(); + } + + assertResponseBody(out.toString()); + } + + protected abstract void assertResponseBody(String body) throws IOException; + } }