keycloak-aplcache
Changes
testsuite/integration-arquillian/servers/auth-server/services/event-queue/src/main/java/org/keycloak/testsuite/events/AssertEventsServletFilter.java 66(+66 -0)
testsuite/integration-arquillian/servers/auth-server/services/event-queue/src/main/java/org/keycloak/testsuite/events/EventsListenerProvider.java 52(+52 -0)
testsuite/integration-arquillian/servers/auth-server/services/event-queue/src/main/java/org/keycloak/testsuite/events/EventsListenerProviderFactory.java 58(+58 -0)
testsuite/integration-arquillian/servers/auth-server/services/event-queue/src/main/java/org/keycloak/testsuite/events/EventsServer.java 95(+95 -0)
testsuite/integration-arquillian/servers/auth-server/services/event-queue/src/main/resources/META-INF/services/org.keycloak.events.EventListenerProviderFactory 35(+35 -0)
testsuite/integration-arquillian/servers/auth-server/services/event-queue/src/main/resources/org/keycloak/testsuite/integration-arquillian-event-queue/main/module.xml 30(+30 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/event/AbstractEventTest.java 2(+1 -1)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/group/AbstractGroupTest.java 86(+86 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/group/GroupMappersTest.java 139(+139 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/group/GroupTest.java 174(+50 -124)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AssertEvents.java 365(+365 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/URLAssert.java 78(+75 -3)
Details
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 93be310..0621b9b 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<String, String> 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/services/src/main/java/org/keycloak/services/resources/admin/GroupResource.java b/services/src/main/java/org/keycloak/services/resources/admin/GroupResource.java
index e821985..0563112 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/servers/auth-server/jboss/build.xml b/testsuite/integration-arquillian/servers/auth-server/jboss/build.xml
new file mode 100644
index 0000000..b34fc7d
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/auth-server/jboss/build.xml
@@ -0,0 +1,42 @@
+<!--
+ ~ 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.
+ -->
+
+<project name="inject-provider" basedir="." default="inject-provider">
+
+ <scriptdef name="inject-provider" language="javascript" manager="bsf">
+ <attribute name="path"/>
+ <![CDATA[
+ importClass(Packages.java.io.File);
+ importClass(Packages.org.keycloak.util.JsonSerialization);
+
+ path = attributes.get("path");
+ file = new File(path);
+ root = JsonSerialization.mapper.readTree(file);
+
+ // inject provider
+ providers = root.withArray("providers");
+ providers.add("module:org.keycloak.testsuite.integration-arquillian-event-queue");
+
+ // save file
+ JsonSerialization.prettyMapper.writeValue(file, root);
+ ]]>
+ </scriptdef>
+
+ <target name="inject-provider">
+ <inject-provider path="${auth.server.home}/standalone/configuration/keycloak-server.json"/>
+ </target>
+</project>
\ 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 8d23106..c19cc1b 100644
--- a/testsuite/integration-arquillian/servers/auth-server/jboss/pom.xml
+++ b/testsuite/integration-arquillian/servers/auth-server/jboss/pom.xml
@@ -100,13 +100,102 @@
</artifactItems>
</configuration>
</execution>
+ <execution>
+ <id>copy-event-queue-provider</id>
+ <phase>generate-resources</phase>
+ <goals>
+ <goal>copy</goal>
+ </goals>
+ <configuration>
+ <artifactItems>
+ <artifactItem>
+ <groupId>org.keycloak.testsuite</groupId>
+ <artifactId>integration-arquillian-event-queue</artifactId>
+ <version>${project.version}</version>
+ <type>jar</type>
+ <overWrite>false</overWrite>
+ <outputDirectory>${auth.server.home}/modules/org/keycloak/testsuite/integration-arquillian-event-queue/main</outputDirectory>
+ </artifactItem>
+ </artifactItems>
+ </configuration>
+ </execution>
+ <execution>
+ <id>install-event-queue-module</id>
+ <phase>generate-resources</phase>
+ <goals>
+ <goal>unpack</goal>
+ </goals>
+ <configuration>
+ <artifactItems>
+ <artifactItem>
+ <groupId>org.keycloak.testsuite</groupId>
+ <artifactId>integration-arquillian-event-queue</artifactId>
+ <version>${project.version}</version>
+ <type>jar</type>
+ <outputDirectory>${auth.server.home}/modules</outputDirectory>
+ <includes>**/module.xml</includes>
+ </artifactItem>
+ </artifactItems>
+ </configuration>
+ </execution>
</executions>
</plugin>
<plugin>
- <artifactId>maven-enforcer-plugin</artifactId>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-antrun-plugin</artifactId>
+ <version>1.8</version>
+ <executions>
+ <execution>
+ <id>inject-into-keycloak-server-json</id>
+ <phase>process-resources</phase>
+ <goals>
+ <goal>run</goal>
+ </goals>
+ <configuration>
+ <target>
+ <ant antfile="../build.xml" inheritRefs="true">
+ <target name="inject-provider"/>
+ </ant>
+ </target>
+ </configuration>
+ </execution>
+ </executions>
+ <dependencies>
+ <dependency>
+ <groupId>ant-contrib</groupId>
+ <artifactId>ant-contrib</artifactId>
+ <version>1.0b3</version>
+ <exclusions>
+ <exclusion>
+ <groupId>ant</groupId>
+ <artifactId>ant</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.ant</groupId>
+ <artifactId>ant-apache-bsf</artifactId>
+ <version>1.9.3</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.bsf</groupId>
+ <artifactId>bsf-api</artifactId>
+ <version>3.1</version>
+ </dependency>
+ <dependency>
+ <groupId>rhino</groupId>
+ <artifactId>js</artifactId>
+ <version>1.7R2</version>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-core</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ </dependencies>
</plugin>
<plugin>
- <artifactId>maven-antrun-plugin</artifactId>
+ <artifactId>maven-enforcer-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
diff --git a/testsuite/integration-arquillian/servers/auth-server/pom.xml b/testsuite/integration-arquillian/servers/auth-server/pom.xml
index d9f8222..f37234d 100644
--- a/testsuite/integration-arquillian/servers/auth-server/pom.xml
+++ b/testsuite/integration-arquillian/servers/auth-server/pom.xml
@@ -30,6 +30,7 @@
<name>Auth Server</name>
<modules>
+ <module>services</module>
<module>jboss</module>
<module>undertow</module>
</modules>
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 0000000..47ae3e4
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/auth-server/services/event-queue/pom.xml
@@ -0,0 +1,81 @@
+<?xml version="1.0"?>
+<!--
+ ~ 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.
+ -->
+
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.keycloak.testsuite</groupId>
+ <artifactId>integration-arquillian-servers-auth-server-services</artifactId>
+ <version>1.9.2.Final-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>integration-arquillian-event-queue</artifactId>
+ <name>Auth Server Services - Event Queue</name>
+
+ <dependencies>
+
+ <!-- Keycloak deps for tests -->
+
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-dependencies-server-all</artifactId>
+ <type>pom</type>
+ </dependency>
+
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-services</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>io.undertow</groupId>
+ <artifactId>undertow-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>io.undertow</groupId>
+ <artifactId>undertow-servlet</artifactId>
+ </dependency>
+
+ </dependencies>
+
+ <build>
+ <pluginManagement>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <skip>true</skip>
+ </configuration>
+ </plugin>
+ </plugins>
+ </pluginManagement>
+ <resources>
+ <resource>
+ <directory>src/main/resources</directory>
+ <filtering>true</filtering>
+ </resource>
+ </resources>
+ </build>
+</project>
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 0000000..6d0d4ed
--- /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 <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ */
+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<Event> 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 0000000..a4a1a40
--- /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 <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ */
+public class EventsListenerProvider implements EventListenerProvider {
+
+ private static final BlockingQueue<Event> events = new LinkedBlockingQueue<Event>();
+
+ @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<Event> 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 0000000..aecc01a
--- /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 <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ */
+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 0000000..0b04ee3
--- /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 <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ */
+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 0000000..e995149
--- /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 0000000..19f3d41
--- /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 @@
+<!--
+ ~ 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.
+ -->
+
+<module xmlns="urn:jboss:module:1.1" name="org.keycloak.testsuite.integration-arquillian-event-queue">
+ <resources>
+ <resource-root path="integration-arquillian-event-queue-${project.version}.jar"/>
+ </resources>
+ <dependencies>
+ <module name="javax.api"/>
+ <module name="javax.servlet.api"/>
+ <module name="io.undertow.core"/>
+ <module name="io.undertow.servlet"/>
+ <module name="org.keycloak.keycloak-core"/>
+ <module name="org.keycloak.keycloak-server-spi"/>
+ </dependencies>
+</module>
\ 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 0000000..4684ec0
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/auth-server/services/pom.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0"?>
+<!--
+ ~ 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.
+ -->
+
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <parent>
+ <groupId>org.keycloak.testsuite</groupId>
+ <artifactId>integration-arquillian-servers-auth-server</artifactId>
+ <version>1.9.2.Final-SNAPSHOT</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>integration-arquillian-servers-auth-server-services</artifactId>
+ <packaging>pom</packaging>
+ <name>Auth Server Services</name>
+
+ <modules>
+ <module>event-queue</module>
+ </modules>
+
+</project>
diff --git a/testsuite/integration-arquillian/tests/base/pom.xml b/testsuite/integration-arquillian/tests/base/pom.xml
index 662a102..bcec08c 100644
--- a/testsuite/integration-arquillian/tests/base/pom.xml
+++ b/testsuite/integration-arquillian/tests/base/pom.xml
@@ -57,6 +57,11 @@
<artifactId>commons-configuration</artifactId>
<version>1.10</version>
</dependency>
+ <dependency>
+ <groupId>org.keycloak.testsuite</groupId>
+ <artifactId>integration-arquillian-event-queue</artifactId>
+ <version>${project.version}</version>
+ </dependency>
</dependencies>
<build>
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 ba50fe7..597556c 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.<String>emptyList()); // resets to all types
saveConfig();
}
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 0000000..264551b
--- /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 <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ */
+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<RealmRepresentation> 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 0000000..d61a517
--- /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 <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ */
+public class GroupMappersTest extends AbstractGroupTest {
+
+ @Override
+ public void addTestRealms(List<RealmRepresentation> 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<ProtocolMapperRepresentation> mappers = new LinkedList<>();
+ ProtocolMapperRepresentation mapper = new ProtocolMapperRepresentation();
+ mapper.setName("groups");
+ mapper.setProtocolMapper(GroupMembershipMapper.PROVIDER_ID);
+ mapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
+ mapper.setConsentRequired(false);
+ Map<String, String> 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<String> groups = (List<String>) 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<String> groups = (List<String>) 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-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 0000000..8e4c92a
--- /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 <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+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<UserRepresentation> 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<String> userId;
+ private Matcher<String> sessionId;
+ private HashMap<String, Matcher<String>> 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<String> 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<String> 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<String> matcher) {
+ if (details == null) {
+ details = new HashMap<String, Matcher<String>>();
+ }
+ 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<String, Matcher<String>> 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<String> isCodeId() {
+ return isUUID();
+ }
+
+ public static Matcher<String> isUUID() {
+ return new TypeSafeMatcher<String>() {
+ @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/util/URLAssert.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/URLAssert.java
index c66fd70..6ef0bbe 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;
+ }
}
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 ae0f5f9..f5d64a5 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 0000000..845adda
--- /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 2fcd713..727b2ec 100644
--- a/testsuite/integration-arquillian/tests/pom.xml
+++ b/testsuite/integration-arquillian/tests/pom.xml
@@ -48,6 +48,7 @@
<auth.server.port.offset>100</auth.server.port.offset>
<auth.server.http.port>8180</auth.server.http.port>
+ <auth.server.events.http.port>8089</auth.server.events.http.port>
<auth.server.https.port>8543</auth.server.https.port>
<auth.server.management.port>10090</auth.server.management.port>
<auth.server.management.port.jmx>10099</auth.server.management.port.jmx>
@@ -140,6 +141,7 @@
<auth.server.port.offset>${auth.server.port.offset}</auth.server.port.offset>
<auth.server.http.port>${auth.server.http.port}</auth.server.http.port>
+ <auth.server.events.http.port>${auth.server.events.http.port}</auth.server.events.http.port>
<auth.server.https.port>${auth.server.https.port}</auth.server.https.port>
<auth.server.management.port>${auth.server.management.port}</auth.server.management.port>
<auth.server.management.port.jmx>${auth.server.management.port.jmx}</auth.server.management.port.jmx>