resolve conflict

This commit is contained in:
Bill Burke 2016-03-30 18:23:40 -04:00
commit d1552cd6e8
46 changed files with 2330 additions and 231 deletions

View file

@ -105,4 +105,37 @@ public class EventRepresentation {
public void setDetails(Map<String, String> details) { public void setDetails(Map<String, String> details) {
this.details = 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;
}
} }

View file

@ -129,7 +129,7 @@ public class GroupResource {
child = realm.createGroup(rep.getName()); child = realm.createGroup(rep.getName());
updateGroup(rep, child); updateGroup(rep, child);
URI uri = uriInfo.getBaseUriBuilder() URI uri = uriInfo.getBaseUriBuilder()
.path(uriInfo.getMatchedURIs().get(1)) .path(uriInfo.getMatchedURIs().get(2))
.path(child.getId()).build(); .path(child.getId()).build();
builder.status(201).location(uri); builder.status(201).location(uri);
adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo).representation(rep).success(); adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo).representation(rep).success();

View file

@ -1,33 +1,36 @@
# Keycloak Arquillian Integration Testsuite # Keycloak Arquillian Integration Testsuite
## Overview
For overview see the **Modules Overview** section at the bottom of this README.
## Container Lifecycles ## Container Lifecycles
### Keycloak Auth Server ### Auth Server
There is only one instance of Keycloak server running during a single test run. Keycloak server is automatically started by the testsuite on the `BeforeSuite` event and stopped on `AfterSuite` event.
It is automatically started by Arquillian on the `BeforeSuite` event and stopped `AfterSuite`.
The type of container can be determined by property `-Dauth.server.container`. Default value is `auth-server-undertow`, By default the server runs in embedded Undertow.
other options are: `auth-server-wildfly` and `auth-server-eap7`. The values correspond to Arquillian *container qualifiers* in `arquillian.xml` config file.
**Note 1:** For the non-default options it's necessary to build a corresponding server module prior to running any of the test modules. #### Wildfly/EAP
This can be done by building the server module directly (from `servers/wildfly`/`servers/eap7`),
or by activating `auth-server-wildfly`/`auth-server-eap7` profile when building from the top level module.
**Note 2:** Most server-side configurations are done during the build of the server module Testsuite supports running server on Wildfly/EAP. For this it's necessary to:
and included in the output artifact - which is then consumed by the test modules( if a corresponding profile is activated). - build the project including the `distribution` module
To reflect a change in server config in the test (e.g. a datasource) it's necessary to rebuild the server module after each change. (artifact `keycloak-server-dist`/`-overlay` needs to be available before running the testsuite),
- activate profile `auth-server-wildfly` or `auth-server-eap7`.
#### Migration [More details...](servers/auth-server/README.md)
Migration tests can be enabled by setting `-Dmigrated.auth.server.version` property. Supported versions can be found at the bottom of `tests/pom.xml`.
When enabled, the `AuthServerTestEnricher` class will start and stop the selected migrated instance
*before* the current auth server instance is started.
#### Cluster Setup #### Cluster Setup
Cluster setup can be enabled with profile `auth-server-wildfly-cluster`. The cluster setup for server can be enabled by activating profile `auth-server-cluster`.
(It is also necessary to build the server modules with this profile before running the test. See *Notes 1 and 2* above.)
The cluster setup is not supported for server on Undetow. Profile `auth-server-wildfly` or `auth-server-eap` needs to be activated.
The setup includes:
- a `mod_cluster` load balancer on Wildfly
- two clustered nodes of Keycloak server on Wildfly/EAP
Clustering tests require MULTICAST to be enabled on machine's `loopback` network interface. Clustering tests require MULTICAST to be enabled on machine's `loopback` network interface.
This can be done by running the following commands under root privileges: This can be done by running the following commands under root privileges:
@ -36,20 +39,20 @@ route add -net 224.0.0.0 netmask 240.0.0.0 dev lo
ifconfig lo multicast ifconfig lo multicast
``` ```
### App Servers ### App Servers / Adapter Tests
Lifecycle of application server is always tied to a particular TestClass. Lifecycle of application server is always tied to a particular TestClass.
Each *adapter* test class is annotated by `@AppServerContainer("app-server-*")` annotation Each *adapter* test class is annotated by `@AppServerContainer("app-server-*")` annotation
that links it to a particular Arquillian container in `arquillian.xml`. that links it to a particular Arquillian container in `arquillian.xml`.
The `AppServerTestEnricher` then ensures the server is started before and stopped after all tests methods in the class. The `AppServerTestEnricher` then ensures the server is started during `BeforeClass` event and stopped during `AfterClass` event for that particular test class.
In case the `@AppServerContainer` annotation has no value it's assumed that the application container In case the `@AppServerContainer` annotation has no value it's assumed that the application container
is the same as the auth server container (a relative adapter test scenario). is the same as the auth server container - a "relative" adapter test scenario.
Adapter tests are separated into submodules because different app containers require different configurations The app-servers with installed Keycloak adapter are prepared in `servers/app-server` submodules, activated by `-Papp-server-MODULE`.
(installation of adapter libs, etc.). [More details.](servers/app-server/README.md)
Container entries of app servers are not present in the main `arquillian.xml` in the `base` module.
Each adapter submodule adds it's own entry before it runs the tests. The corresponding adapter test modules are in `tests/other/adapters` submodules, and are activated by the same profiles.
## SuiteContext and TestContext ## SuiteContext and TestContext
@ -130,32 +133,6 @@ It automatically modifies imported test realms and deployments' adapter configs
2. **Bundled** - In the deployed war in `/WEB-INF/keycloak.json`. **Default.** 2. **Bundled** - In the deployed war in `/WEB-INF/keycloak.json`. **Default.**
## Maven Modules Structure
```
integration-arquillian
├──servers
│ ├──wildfly (activated by -Pauth-server-wildfly)
│ └──eap7 (activated by -Pauth-server-eap7)
└──tests (common settings for all test modules)
├──base (custom ARQ extensions + base functional tests)
└──other (common settings for all modules dependent on base)
├──adapters (common settings for all adapter submodules)
│ ├──wildfly (activated by -Papp-server-wildfly)
│ ├──wildfly-relative (activated by -Papp-server-wildfly-relative,auth-server-wildfly)
│ ├──...
│ ├──tomcat (activated by -Papp-server-tomcat)
│ └──karaf (activated by -Papp-server-karaf)
├──console (activated by -Pconsole-ui-tests)
├──mod_auth_mellon (activated by -Pmod_auth_mellon)
├──console_no_users (activated by -Pconsole-ui-no-users-tests)
└──...
```
## Custom Arquillian Extensions ## Custom Arquillian Extensions
Custom extensions are registered in `META-INF/services/org.jboss.arquillian.core.spi.LoadableExtension`. Custom extensions are registered in `META-INF/services/org.jboss.arquillian.core.spi.LoadableExtension`.
@ -173,6 +150,37 @@ Custom extensions are registered in `META-INF/services/org.jboss.arquillian.core
* `DeploymentTargetModifier` - Ensures all test app deployments are targeted at app server containers. * `DeploymentTargetModifier` - Ensures all test app deployments are targeted at app server containers.
* `URLProvider` - Fixes URLs injected by Arquillian Graphene which contain value `127.0.0.1` instead of required `localhost`. * `URLProvider` - Fixes URLs injected by Arquillian Graphene which contain value `127.0.0.1` instead of required `localhost`.
### CustomKarafContainerExtension ## Modules Overview
```
integration-arquillian
├──servers (preconfigured test servers)
│ │
│ ├──auth-server
│ │ ├──jboss (wildfly/eap)
│ │ └──undertow (arq. extension)
│ │
│ ├──app-server
│ │ ├──jboss (wildfly/eap/as)
│ │ ├──tomcat
│ │ └──karaf
│ │
│ └──wildfly-balancer
└──tests (common settings for all test modules)
├──base (custom ARQ extensions + base functional tests)
└──other (common settings for all test modules dependent on base)
├──adapters (common settings for all adapter test modules)
│ ├──jboss
│ ├──tomcat
│ └──karaf
├──console
├──console_no_users
└──...
```
Extension for executing karaf commands after container is started. Used for installation of bundles (test apps and adapter libs).

View file

@ -102,6 +102,7 @@
<modules> <modules>
<module>servers</module> <module>servers</module>
<module>tests</module> <module>tests</module>
<module>test-apps</module>
</modules> </modules>
</project> </project>

View file

@ -1,6 +1,9 @@
# Keycloak Arquillian Integration TestSuite # Keycloak Arquillian Integration TestSuite - Test Servers
[Keycloak Arquillian Integration TestSuite](../README.md) - [Keycloak Arquillian Integration TestSuite](../README.md)
- Keycloak Arquillian Integration TestSuite - Test Servers
- [Keycloak Arquillian Integration TestSuite - Test Servers - Auth Server](auth-server/README.md)
- [Keycloak Arquillian Integration TestSuite - Test Servers - App Servers](app-server/README.md)
## Test Servers ## Test Servers
@ -14,8 +17,6 @@ The artifacts are used by the Arquillian TestSuite.
- EAP 7 - EAP 7
- Undertow - Undertow
[Details...](auth-server/README.md)
### App Server ### App Server
@ -30,7 +31,6 @@ The artifacts are used by the Arquillian TestSuite.
- Tomcat - Tomcat
- Tomcat 7, 8 - Tomcat 7, 8
[Details...](app-server/README.md)
### Load Balancer ### Load Balancer

View file

@ -1,6 +1,9 @@
# Keycloak Arquillian Integration TestSuite - Test Servers # Keycloak Arquillian Integration TestSuite - Test Servers - App Servers
[Up...](../README.md) - [Keycloak Arquillian Integration TestSuite](../../README.md)
- [Keycloak Arquillian Integration TestSuite - Test Servers](../README.md)
- [Keycloak Arquillian Integration TestSuite - Test Servers - Auth Server](../auth-server/README.md)
- Keycloak Arquillian Integration TestSuite - Test Servers - App Servers
## App Server - JBoss ## App Server - JBoss

View file

@ -1,16 +1,21 @@
# Keycloak Arquillian Integration TestSuite - Test Servers # Keycloak Arquillian Integration TestSuite - Test Servers - Auth Server
[Up...](../README.md) - [Keycloak Arquillian Integration TestSuite](../../README.md)
- [Keycloak Arquillian Integration TestSuite - Test Servers](../README.md)
- Keycloak Arquillian Integration TestSuite - Test Servers - Auth Server
- [Keycloak Arquillian Integration TestSuite - Test Servers - App Servers](../app-server/README.md)
## Auth Server - JBoss `auth-server/jboss` ## Auth Server - JBoss
### Modules Common configurations of Keycloak server on JBoss-based container (Wildfly/EAP).
* __`wildfly` Wildfly 10__ ### Submodules
#### `wildfly` Wildfly 10
- Builds keycloak server on top of latest Wildfly. - Builds keycloak server on top of latest Wildfly.
- Activated by __`-Pauth-server-wildfly`__ - Activated by __`-Pauth-server-wildfly`__
* __`eap` EAP 7__ #### `eap` EAP 7
- Builds keycloak server on top of latest EAP. - Builds keycloak server on top of latest EAP.
- Activated by __`-Pauth-server-eap`__ - Activated by __`-Pauth-server-eap`__
- Requires access to product repo. - Requires access to product repo.
@ -39,6 +44,6 @@ Configures in `standalone-ha.xml`:
See profile `auth-server-cluster`. See profile `auth-server-cluster`.
## Auth Server - Undertow `auth-server/undertow` ## Auth Server - Undertow
Arquillian extension for running Keycloak server in embedded Undertow. Arquillian extension for running Keycloak server in embedded Undertow.

View file

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

View file

@ -57,6 +57,9 @@
<unpacked.artifact.version>${auth.server.dist.version}</unpacked.artifact.version> <unpacked.artifact.version>${auth.server.dist.version}</unpacked.artifact.version>
<auth.server.home>${project.build.directory}/unpacked/${auth.server.dist.unpacked.folder.name}</auth.server.home> <auth.server.home>${project.build.directory}/unpacked/${auth.server.dist.unpacked.folder.name}</auth.server.home>
<!--used in profile auth-server-cluster. profile jpa sets this to true-->
<skip.h2.tcp>false</skip.h2.tcp>
</properties> </properties>
<profiles> <profiles>
@ -97,13 +100,102 @@
</artifactItems> </artifactItems>
</configuration> </configuration>
</execution> </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> </executions>
</plugin> </plugin>
<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>
<plugin> <plugin>
<artifactId>maven-antrun-plugin</artifactId> <artifactId>maven-enforcer-plugin</artifactId>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.codehaus.mojo</groupId> <groupId>org.codehaus.mojo</groupId>
@ -268,6 +360,7 @@
<id>jpa</id> <id>jpa</id>
<properties> <properties>
<jdbc.mvn.driver.deployment.dir>${auth.server.home}/modules/system/layers/base/com/${jdbc.mvn.artifactId}/main</jdbc.mvn.driver.deployment.dir> <jdbc.mvn.driver.deployment.dir>${auth.server.home}/modules/system/layers/base/com/${jdbc.mvn.artifactId}/main</jdbc.mvn.driver.deployment.dir>
<skip.h2.tcp>true</skip.h2.tcp>
</properties> </properties>
<build> <build>
<pluginManagement> <pluginManagement>
@ -344,7 +437,7 @@
<!-- create module.xml in modules --> <!-- create module.xml in modules -->
<transformationSet> <transformationSet>
<dir>${auth.server.home}/modules/system/layers/base/com/h2database/h2/main</dir> <dir>${auth.server.home}/modules/system/layers/base/com/h2database/h2/main</dir>
<stylesheet>src/main/resources/module.xsl</stylesheet> <stylesheet>src/main/resources/xslt/module.xsl</stylesheet>
<includes> <includes>
<include>module.xml</include> <include>module.xml</include>
</includes> </includes>
@ -366,6 +459,7 @@
<stylesheet>${common.resources}/datasource.xsl</stylesheet> <stylesheet>${common.resources}/datasource.xsl</stylesheet>
<includes> <includes>
<include>standalone.xml</include> <include>standalone.xml</include>
<include>standalone-ha.xml</include>
</includes> </includes>
<outputDir>${auth.server.home}/standalone/configuration</outputDir> <outputDir>${auth.server.home}/standalone/configuration</outputDir>
<parameters> <parameters>
@ -421,14 +515,13 @@
<artifactId>xml-maven-plugin</artifactId> <artifactId>xml-maven-plugin</artifactId>
<executions> <executions>
<execution> <execution>
<id>configure-wildfly-datasource</id> <id>jpa-h2-tcp</id>
<phase>process-resources</phase> <phase>process-resources</phase>
<goals> <goals>
<goal>transform</goal> <goal>transform</goal>
</goals> </goals>
<configuration> <configuration>
<transformationSets> <transformationSets>
<!-- point KeycloakDS datasource to H2 running on TCP port -->
<transformationSet> <transformationSet>
<dir>${auth.server.home}/standalone/configuration</dir> <dir>${auth.server.home}/standalone/configuration</dir>
<includes> <includes>
@ -447,6 +540,18 @@
</parameter> </parameter>
</parameters> </parameters>
</transformationSet> </transformationSet>
</transformationSets>
<skip>${skip.h2.tcp}</skip>
</configuration>
</execution>
<execution>
<id>keycloak-ispn-caches</id>
<phase>process-resources</phase>
<goals>
<goal>transform</goal>
</goals>
<configuration>
<transformationSets>
<transformationSet> <transformationSet>
<dir>${auth.server.home}/standalone/configuration</dir> <dir>${auth.server.home}/standalone/configuration</dir>
<includes> <includes>

View file

@ -30,6 +30,7 @@
<name>Auth Server</name> <name>Auth Server</name>
<modules> <modules>
<module>services</module>
<module>jboss</module> <module>jboss</module>
<module>undertow</module> <module>undertow</module>
</modules> </modules>

View file

@ -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>2.0.0.CR1-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>

View file

@ -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() {
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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>2.0.0.CR1-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>

View file

@ -0,0 +1,17 @@
Basic JavaScript Example
========================
Start and configure Keycloak
----------------------------
Start Keycloak:
bin/standalone.sh
Open the Keycloak admin console, click on Add Realm, click on 'Choose a JSON file', selct example-realm.json and click Upload.
Deploy the JS Console to Keycloak by running:
mvn install wildfly:deploy
Open the console at http://localhost:8080/js-console and login with username: 'user', and password: 'password'.

View file

@ -0,0 +1,66 @@
{
"realm": "example",
"enabled": true,
"sslRequired": "external",
"registrationAllowed": 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" ],
"users" : [
{
"username" : "user",
"enabled": true,
"email" : "sample-user@example",
"firstName": "Sample",
"lastName": "User",
"credentials" : [
{ "type" : "password",
"value" : "password" }
],
"realmRoles": [ "user" ],
"clientRoles": {
"account": ["view-profile", "manage-account"]
}
}
],
"roles" : {
"realm" : [
{
"name": "user",
"description": "User privileges"
},
{
"name": "admin",
"description": "Administrator privileges"
}
]
},
"scopeMappings": [
{
"client": "js-console",
"roles": ["user"]
}
],
"clients": [
{
"clientId": "js-console",
"enabled": true,
"publicClient": true,
"baseUrl": "/js-console",
"redirectUris": [
"/js-console/*"
],
"webOrigins": [
"http://localhost:8280"
]
}
],
"clientScopeMappings": {
"account": [
{
"client": "js-console",
"roles": ["view-profile"]
}
]
}
}

View file

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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 xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<groupId>org.keycloak.testsuite</groupId>
<artifactId>integration-arquillian-test-apps</artifactId>
<version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>integration-arquillian-test-apps-js-console</artifactId>
<packaging>war</packaging>
<name>JS Console</name>
<description/>
<build>
<finalName>js-console</finalName>
<plugins>
<plugin>
<groupId>org.jboss.as.plugins</groupId>
<artifactId>jboss-as-maven-plugin</artifactId>
<configuration>
<skip>false</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.wildfly.plugins</groupId>
<artifactId>wildfly-maven-plugin</artifactId>
<configuration>
<skip>false</skip>
</configuration>
</plugin>
</plugins>
</build>
</project>

View file

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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.
-->
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<module-name>js-console</module-name>
</web-app>

View file

@ -0,0 +1,156 @@
<!--
~ 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.
-->
<html>
<head>
<script src="http://localhost:8180/auth/js/keycloak.js"></script>
</head>
<body>
<div>
<button onclick="keycloakInit()">Init</button>
<button onclick="keycloak.login()">Login</button>
<button onclick="keycloak.logout()">Logout</button>
<button onclick="keycloak.register()">Register</button>
<button onclick="refreshToken(9999)">Refresh Token</button>
<button onclick="refreshToken(30)">Refresh Token (if <30s validity)</button>
<button onclick="loadProfile()">Get Profile</button>
<button onclick="loadUserInfo()">Get User Info</button>
<button onclick="output(keycloak.tokenParsed)">Show Token</button>
<button onclick="output(keycloak.refreshTokenParsed)">Show Refresh Token</button>
<button onclick="output(keycloak.idTokenParsed)">Show ID Token</button>
<button onclick="showExpires()">Show Expires</button>
<button onclick="output(keycloak)">Show Details</button>
<button onclick="output(keycloak.createLoginUrl())">Show Login URL</button>
<button onclick="output(keycloak.createLogoutUrl())">Show Logout URL</button>
<button onclick="output(keycloak.createRegisterUrl())">Show Register URL</button>
<select id="flowSelect">
<option value="standard">standard</option>
<option value="implicit">implicit</option>
</select>
<select id="responseModeSelect">
<option value="fragment">fragment</option>
<option value="query">query</option>
</select>
</div>
<h2>Result</h2>
<pre style="background-color: #ddd; border: 1px solid #ccc; padding: 10px;" id="output"></pre>
<h2>Events</h2>
<pre style="background-color: #ddd; border: 1px solid #ccc; padding: 10px;" id="events"></pre>
<script>
function loadProfile() {
keycloak.loadUserProfile().success(function(profile) {
output(profile);
}).error(function() {
output('Failed to load profile');
});
}
function loadUserInfo() {
keycloak.loadUserInfo().success(function(userInfo) {
output(userInfo);
}).error(function() {
output('Failed to load user info');
});
}
function refreshToken(minValidity) {
keycloak.updateToken(minValidity).success(function(refreshed) {
if (refreshed) {
output(keycloak.tokenParsed);
} else {
output('Token not refreshed, valid for ' + Math.round(keycloak.tokenParsed.exp + keycloak.timeSkew - new Date().getTime() / 1000) + ' seconds');
}
}).error(function() {
output('Failed to refresh token');
});
}
function showExpires() {
if (!keycloak.tokenParsed) {
output("Not authenticated");
return;
}
var o = 'Token Expires:\t\t' + new Date((keycloak.tokenParsed.exp + keycloak.timeSkew) * 1000).toLocaleString() + '\n';
o += 'Token Expires in:\t' + Math.round(keycloak.tokenParsed.exp + keycloak.timeSkew - new Date().getTime() / 1000) + ' seconds\n';
if (keycloak.refreshTokenParsed) {
o += 'Refresh Token Expires:\t' + new Date((keycloak.refreshTokenParsed.exp + keycloak.timeSkew) * 1000).toLocaleString() + '\n';
o += 'Refresh Expires in:\t' + Math.round(keycloak.refreshTokenParsed.exp + keycloak.timeSkew - new Date().getTime() / 1000) + ' seconds';
}
output(o);
}
function output(data) {
if (typeof data === 'object') {
data = JSON.stringify(data, null, ' ');
}
document.getElementById('output').innerHTML = data;
}
function event(event) {
var e = document.getElementById('events').innerHTML;
document.getElementById('events').innerHTML = new Date().toLocaleString() + "\t" + event + "\n" + e;
}
var keycloak;
function keycloakInit() {
keycloak = Keycloak();
keycloak.onAuthSuccess = function () {
event('Auth Success');
};
keycloak.onAuthError = function () {
event('Auth Error');
};
keycloak.onAuthRefreshSuccess = function () {
event('Auth Refresh Success');
};
keycloak.onAuthRefreshError = function () {
event('Auth Refresh Error');
};
keycloak.onAuthLogout = function () {
event('Auth Logout');
};
keycloak.onTokenExpired = function () {
event('Access token expired.');
};
var initOptions = {flow: document.getElementById("flowSelect").value, responseMode: document.getElementById("responseModeSelect").value}
keycloak.init(initOptions).success(function (authenticated) {
output('Init Success (' + (authenticated ? 'Authenticated' : 'Not Authenticated') + ')');
}).error(function () {
output('Init Error');
});
}
</script>
</body>
</html>

View file

@ -0,0 +1,8 @@
{
"realm" : "example",
"realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url" : "http://localhost:8180/auth",
"ssl-required" : "external",
"resource" : "js-console",
"public-client" : true
}

View file

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>integration-arquillian</artifactId>
<groupId>org.keycloak.testsuite</groupId>
<version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>integration-arquillian-test-apps</artifactId>
<packaging>pom</packaging>
<name>Test apps</name>
<modules>
<module>js-console</module>
<module>test-apps-dist</module>
</modules>
</project>

View file

@ -0,0 +1,39 @@
<!--
~ 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.
-->
<assembly>
<id>test-apps-dist</id>
<formats>
<format>zip</format>
</formats>
<includeBaseDirectory>true</includeBaseDirectory>
<fileSets>
<fileSet>
<directory>target/test-apps</directory>
<outputDirectory></outputDirectory>
<excludes>
<exclude>**/pom.xml.releaseBackup</exclude>
<exclude>**/.svn/**</exclude>
<exclude>**/target/**</exclude>
<exclude>**/*.iml</exclude>
</excludes>
</fileSet>
</fileSets>
</assembly>

View file

@ -0,0 +1,31 @@
<!--
~ 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="test-apps-dist" basedir="." default="all">
<target name="all">
<delete dir="target/test-apps"/>
<copy todir="target/test-apps/js-console" overwrite="true">
<fileset dir="../js-console">
<exclude name="**/target/**"/>
<exclude name="**/*.iml"/>
<exclude name="**/*.unconfigured"/>
<exclude name="**/subsystem-config.xml"/>
</fileset>
</copy>
</target>
</project>

View file

@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>integration-arquillian-test-apps</artifactId>
<groupId>org.keycloak.testsuite</groupId>
<version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>integration-arquillian-test-apps-dist</artifactId>
<name>Test apps distribution</name>
<build>
<finalName>${product.name}-${product.version}-test-apps</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
<configuration>
<skip>false</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.8</version>
<inherited>false</inherited>
<executions>
<execution>
<id>build-test-apps</id>
<goals>
<goal>run</goal>
</goals>
<phase>compile</phase>
<configuration>
<target>
<ant antfile="build.xml" inheritRefs="true">
<target name="all"></target>
</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>
</dependencies>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<id>assemble</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<descriptors>
<descriptor>assembly.xml</descriptor>
</descriptors>
<outputDirectory>target</outputDirectory>
<workDirectory>target/assembly/work</workDirectory>
<appendAssemblyId>false</appendAssemblyId>
</configuration>
</execution>
</executions>
<version>2.5.5</version>
</plugin>
</plugins>
</build>
</project>

View file

@ -57,6 +57,11 @@
<artifactId>commons-configuration</artifactId> <artifactId>commons-configuration</artifactId>
<version>1.10</version> <version>1.10</version>
</dependency> </dependency>
<dependency>
<groupId>org.keycloak.testsuite</groupId>
<artifactId>integration-arquillian-event-queue</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies> </dependencies>
<build> <build>

View file

@ -22,6 +22,7 @@ import org.jboss.arquillian.test.api.ArquillianResource;
import org.keycloak.testsuite.page.AbstractPageWithInjectedUrl; import org.keycloak.testsuite.page.AbstractPageWithInjectedUrl;
import org.openqa.selenium.WebElement; import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy; import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.ui.Select;
import java.net.URL; import java.net.URL;
@ -32,7 +33,7 @@ import java.net.URL;
public class JSConsoleExample extends AbstractPageWithInjectedUrl { public class JSConsoleExample extends AbstractPageWithInjectedUrl {
public static final String DEPLOYMENT_NAME = "js-console-example"; public static final String DEPLOYMENT_NAME = "js-console-example";
public static final String CLIENT_ID = "js-console"; public static final String CLIENT_ID = "integration-arquillian-test-apps-js-console";
@ArquillianResource @ArquillianResource
@OperateOnDeployment(DEPLOYMENT_NAME) @OperateOnDeployment(DEPLOYMENT_NAME)
@ -45,6 +46,8 @@ public class JSConsoleExample extends AbstractPageWithInjectedUrl {
return fixedUrl != null ? fixedUrl : url; return fixedUrl != null ? fixedUrl : url;
} }
@FindBy(xpath = "//button[text() = 'Init']")
private WebElement initButton;
@FindBy(xpath = "//button[text() = 'Login']") @FindBy(xpath = "//button[text() = 'Login']")
private WebElement logInButton; private WebElement logInButton;
@FindBy(xpath = "//button[text() = 'Logout']") @FindBy(xpath = "//button[text() = 'Logout']")
@ -67,6 +70,17 @@ public class JSConsoleExample extends AbstractPageWithInjectedUrl {
@FindBy(xpath = "//button[text() = 'Show Details']") @FindBy(xpath = "//button[text() = 'Show Details']")
private WebElement showDetailsButton; private WebElement showDetailsButton;
@FindBy(id = "flowSelect")
private Select flowSelect;
@FindBy(id = "responseModeSelect")
private Select responseModeSelect;
@FindBy(id = "output")
private WebElement outputArea;
@FindBy(id = "events")
private WebElement eventsArea;
public void logIn() { public void logIn() {
logInButton.click(); logInButton.click();
} }
@ -87,4 +101,23 @@ public class JSConsoleExample extends AbstractPageWithInjectedUrl {
getProfileButton.click(); getProfileButton.click();
} }
public void setFlow(String value) {
flowSelect.selectByValue(value);
}
public void init() {
initButton.click();
}
public void setResponseMode(String value) {
responseModeSelect.selectByValue(value);
}
public String getOutputText() {
return outputArea.getText();
}
public String getEventsText() {
return eventsArea.getText();
}
} }

View file

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

View file

@ -38,6 +38,7 @@ public abstract class AbstractExampleAdapterTest extends AbstractAdapterTest {
public static final String EXAMPLES_HOME; public static final String EXAMPLES_HOME;
public static final String EXAMPLES_VERSION_SUFFIX; public static final String EXAMPLES_VERSION_SUFFIX;
public static final String EXAMPLES_HOME_DIR; public static final String EXAMPLES_HOME_DIR;
public static final String TEST_APPS_HOME_DIR;
public static final String EXAMPLES_WEB_XML; public static final String EXAMPLES_WEB_XML;
static { static {
@ -49,7 +50,13 @@ public abstract class AbstractExampleAdapterTest extends AbstractAdapterTest {
Assert.assertNotNull("Property ${examples.version.suffix} must bet set.", EXAMPLES_VERSION_SUFFIX); Assert.assertNotNull("Property ${examples.version.suffix} must bet set.", EXAMPLES_VERSION_SUFFIX);
System.out.println(EXAMPLES_VERSION_SUFFIX); System.out.println(EXAMPLES_VERSION_SUFFIX);
EXAMPLES_HOME_DIR = EXAMPLES_HOME + "/keycloak-examples-" + EXAMPLES_VERSION_SUFFIX; if (!System.getProperty("unpacked.container.folder.name","").isEmpty()) {
EXAMPLES_HOME_DIR = EXAMPLES_HOME + "/" + System.getProperty("unpacked.container.folder.name","") + "-examples";
TEST_APPS_HOME_DIR = EXAMPLES_HOME + "/" + System.getProperty("unpacked.container.folder.name","") + "-test-apps";
} else {
EXAMPLES_HOME_DIR = EXAMPLES_HOME + "/keycloak-examples-" + EXAMPLES_VERSION_SUFFIX;
TEST_APPS_HOME_DIR = EXAMPLES_HOME + "/Keycloak-" + EXAMPLES_VERSION_SUFFIX + "-test-apps";
}
EXAMPLES_WEB_XML = EXAMPLES_HOME + "/web.xml"; EXAMPLES_WEB_XML = EXAMPLES_HOME + "/web.xml";
} }

View file

@ -73,7 +73,7 @@ public abstract class AbstractJSConsoleExampleAdapterTest extends AbstractExampl
@Override @Override
public void addAdapterTestRealms(List<RealmRepresentation> testRealms) { public void addAdapterTestRealms(List<RealmRepresentation> testRealms) {
RealmRepresentation jsConsoleRealm = loadRealm(new File(EXAMPLES_HOME_DIR + "/js-console/example-realm.json")); RealmRepresentation jsConsoleRealm = loadRealm(new File(TEST_APPS_HOME_DIR + "/js-console/example-realm.json"));
fixClientUrisUsingDeploymentUrl(jsConsoleRealm, fixClientUrisUsingDeploymentUrl(jsConsoleRealm,
JSConsoleExample.CLIENT_ID, jsConsoleExamplePage.buildUri().toASCIIString()); JSConsoleExample.CLIENT_ID, jsConsoleExamplePage.buildUri().toASCIIString());
@ -96,6 +96,7 @@ public abstract class AbstractJSConsoleExampleAdapterTest extends AbstractExampl
pause(1000); pause(1000);
jsConsoleExamplePage.init();
jsConsoleExamplePage.logIn(); jsConsoleExamplePage.logIn();
testRealmLoginPage.form().login("user", "invalid-password"); testRealmLoginPage.form().login("user", "invalid-password");
assertCurrentUrlDoesntStartWith(jsConsoleExamplePage); assertCurrentUrlDoesntStartWith(jsConsoleExamplePage);
@ -105,14 +106,17 @@ public abstract class AbstractJSConsoleExampleAdapterTest extends AbstractExampl
testRealmLoginPage.form().login("user", "password"); testRealmLoginPage.form().login("user", "password");
assertCurrentUrlStartsWith(jsConsoleExamplePage); assertCurrentUrlStartsWith(jsConsoleExamplePage);
assertTrue(driver.getPageSource().contains("Init Success (Authenticated)")); jsConsoleExamplePage.init();
assertTrue(driver.getPageSource().contains("Auth Success")); assertTrue(jsConsoleExamplePage.getOutputText().contains("Init Success (Authenticated)"));
assertTrue(jsConsoleExamplePage.getEventsText().contains("Auth Success"));
pause(1000); pause(1000);
jsConsoleExamplePage.logOut(); jsConsoleExamplePage.logOut();
assertCurrentUrlStartsWith(jsConsoleExamplePage); assertCurrentUrlStartsWith(jsConsoleExamplePage);
assertTrue(driver.getPageSource().contains("Init Success (Not Authenticated)")); jsConsoleExamplePage.init();
assertTrue(jsConsoleExamplePage.getOutputText().contains("Init Success (Not Authenticated)"));
} }
@Test @Test
@ -120,38 +124,41 @@ public abstract class AbstractJSConsoleExampleAdapterTest extends AbstractExampl
jsConsoleExamplePage.navigateTo(); jsConsoleExamplePage.navigateTo();
assertCurrentUrlStartsWith(jsConsoleExamplePage); assertCurrentUrlStartsWith(jsConsoleExamplePage);
jsConsoleExamplePage.init();
jsConsoleExamplePage.refreshToken(); jsConsoleExamplePage.refreshToken();
assertTrue(driver.getPageSource().contains("Failed to refresh token")); assertTrue(jsConsoleExamplePage.getOutputText().contains("Failed to refresh token"));
jsConsoleExamplePage.logIn(); jsConsoleExamplePage.logIn();
testRealmLoginPage.form().login("user", "password"); testRealmLoginPage.form().login("user", "password");
assertCurrentUrlStartsWith(jsConsoleExamplePage); assertCurrentUrlStartsWith(jsConsoleExamplePage);
assertTrue(driver.getPageSource().contains("Auth Success")); jsConsoleExamplePage.init();
assertTrue(jsConsoleExamplePage.getEventsText().contains("Auth Success"));
jsConsoleExamplePage.refreshToken(); jsConsoleExamplePage.refreshToken();
assertTrue(driver.getPageSource().contains("Auth Refresh Success")); assertTrue(jsConsoleExamplePage.getEventsText().contains("Auth Refresh Success"));
} }
@Test @Test
public void testRefreshTokenIfUnder30s() { public void testRefreshTokenIfUnder30s() {
jsConsoleExamplePage.navigateTo(); jsConsoleExamplePage.navigateTo();
assertCurrentUrlStartsWith(jsConsoleExamplePage); assertCurrentUrlStartsWith(jsConsoleExamplePage);
jsConsoleExamplePage.init();
jsConsoleExamplePage.refreshToken(); jsConsoleExamplePage.refreshToken();
assertTrue(driver.getPageSource().contains("Failed to refresh token")); assertTrue(jsConsoleExamplePage.getOutputText().contains("Failed to refresh token"));
jsConsoleExamplePage.logIn(); jsConsoleExamplePage.logIn();
testRealmLoginPage.form().login("user", "password"); testRealmLoginPage.form().login("user", "password");
assertCurrentUrlStartsWith(jsConsoleExamplePage); assertCurrentUrlStartsWith(jsConsoleExamplePage);
assertTrue(driver.getPageSource().contains("Auth Success")); jsConsoleExamplePage.init();
assertTrue(jsConsoleExamplePage.getEventsText().contains("Auth Success"));
jsConsoleExamplePage.refreshTokenIfUnder30s(); jsConsoleExamplePage.refreshTokenIfUnder30s();
assertTrue(driver.getPageSource().contains("Token not refreshed, valid for")); assertTrue(jsConsoleExamplePage.getOutputText().contains("Token not refreshed, valid for"));
pause((TOKEN_LIFESPAN_LEEWAY + 2) * 1000); pause((TOKEN_LIFESPAN_LEEWAY + 2) * 1000);
jsConsoleExamplePage.refreshTokenIfUnder30s(); jsConsoleExamplePage.refreshTokenIfUnder30s();
assertTrue(driver.getPageSource().contains("Auth Refresh Success")); assertTrue(jsConsoleExamplePage.getEventsText().contains("Auth Refresh Success"));
} }
@Test @Test
@ -159,17 +166,18 @@ public abstract class AbstractJSConsoleExampleAdapterTest extends AbstractExampl
jsConsoleExamplePage.navigateTo(); jsConsoleExamplePage.navigateTo();
assertCurrentUrlStartsWith(jsConsoleExamplePage); assertCurrentUrlStartsWith(jsConsoleExamplePage);
jsConsoleExamplePage.init();
jsConsoleExamplePage.getProfile(); jsConsoleExamplePage.getProfile();
assertTrue(driver.getPageSource().contains("Failed to load profile")); assertTrue(jsConsoleExamplePage.getOutputText().contains("Failed to load profile"));
jsConsoleExamplePage.logIn(); jsConsoleExamplePage.logIn();
testRealmLoginPage.form().login("user", "password"); testRealmLoginPage.form().login("user", "password");
assertCurrentUrlStartsWith(jsConsoleExamplePage); assertCurrentUrlStartsWith(jsConsoleExamplePage);
assertTrue(driver.getPageSource().contains("Auth Success")); jsConsoleExamplePage.init();
assertTrue(jsConsoleExamplePage.getEventsText().contains("Auth Success"));
jsConsoleExamplePage.getProfile(); jsConsoleExamplePage.getProfile();
assertTrue(driver.getPageSource().contains("Failed to load profile")); assertTrue(jsConsoleExamplePage.getOutputText().contains("\"username\": \"user\""));
assertTrue(driver.getPageSource().contains("\"username\": \"user\""));
} }
@Test @Test
@ -194,6 +202,7 @@ public abstract class AbstractJSConsoleExampleAdapterTest extends AbstractExampl
testRealmResource().update(realm); testRealmResource().update(realm);
jsConsoleExamplePage.navigateTo(); jsConsoleExamplePage.navigateTo();
jsConsoleExamplePage.init();
jsConsoleExamplePage.logIn(); jsConsoleExamplePage.logIn();
testRealmLoginPage.form().login("user", "password"); testRealmLoginPage.form().login("user", "password");
@ -201,12 +210,14 @@ public abstract class AbstractJSConsoleExampleAdapterTest extends AbstractExampl
assertTrue(oAuthGrantPage.isCurrent()); assertTrue(oAuthGrantPage.isCurrent());
oAuthGrantPage.accept(); oAuthGrantPage.accept();
assertTrue(driver.getPageSource().contains("Init Success (Authenticated)")); jsConsoleExamplePage.init();
assertTrue(jsConsoleExamplePage.getOutputText().contains("Init Success (Authenticated)"));
applicationsPage.navigateTo(); applicationsPage.navigateTo();
applicationsPage.revokeGrantForApplication("js-console"); applicationsPage.revokeGrantForApplication("js-console");
jsConsoleExamplePage.navigateTo(); jsConsoleExamplePage.navigateTo();
jsConsoleExamplePage.init();
jsConsoleExamplePage.logIn(); jsConsoleExamplePage.logIn();
assertTrue(oAuthGrantPage.isCurrent()); assertTrue(oAuthGrantPage.isCurrent());
@ -223,7 +234,7 @@ public abstract class AbstractJSConsoleExampleAdapterTest extends AbstractExampl
resultList.get(0).findElement(By.xpath(".//td[text()='REVOKE_GRANT']")); resultList.get(0).findElement(By.xpath(".//td[text()='REVOKE_GRANT']"));
resultList.get(0).findElement(By.xpath(".//td[text()='Client']/../td[text()='account']")); resultList.get(0).findElement(By.xpath(".//td[text()='Client']/../td[text()='account']"));
resultList.get(0).findElement(By.xpath(".//td[text()='IP Address']/../td[text()='127.0.0.1']")); resultList.get(0).findElement(By.xpath(".//td[text()='IP Address']/../td[text()='127.0.0.1' or text()='0:0:0:0:0:0:0:1']"));
resultList.get(0).findElement(By.xpath(".//td[text()='revoked_client']/../td[text()='js-console']")); resultList.get(0).findElement(By.xpath(".//td[text()='revoked_client']/../td[text()='js-console']"));
loginEventsPage.table().reset(); loginEventsPage.table().reset();
@ -235,9 +246,87 @@ public abstract class AbstractJSConsoleExampleAdapterTest extends AbstractExampl
resultList.get(0).findElement(By.xpath(".//td[text()='LOGIN']")); resultList.get(0).findElement(By.xpath(".//td[text()='LOGIN']"));
resultList.get(0).findElement(By.xpath(".//td[text()='Client']/../td[text()='js-console']")); resultList.get(0).findElement(By.xpath(".//td[text()='Client']/../td[text()='js-console']"));
resultList.get(0).findElement(By.xpath(".//td[text()='IP Address']/../td[text()='127.0.0.1']")); resultList.get(0).findElement(By.xpath(".//td[text()='IP Address']/../td[text()='127.0.0.1' or text()='0:0:0:0:0:0:0:1']"));
resultList.get(0).findElement(By.xpath(".//td[text()='username']/../td[text()='user']")); resultList.get(0).findElement(By.xpath(".//td[text()='username']/../td[text()='user']"));
resultList.get(0).findElement(By.xpath(".//td[text()='consent']/../td[text()='consent_granted']")); resultList.get(0).findElement(By.xpath(".//td[text()='consent']/../td[text()='consent_granted']"));
} }
@Test
public void implicitFlowTest() {
jsConsoleExamplePage.navigateTo();
jsConsoleExamplePage.setFlow("implicit");
jsConsoleExamplePage.init();
jsConsoleExamplePage.logIn();
assertTrue(driver.getPageSource().contains("Implicit flow is disabled for the client"));
setImplicitFlowFroClient();
jsConsoleExamplePage.navigateTo();
jsConsoleExamplePage.init();
jsConsoleExamplePage.logIn();
assertTrue(driver.getPageSource().contains("Standard flow is disabled for the client"));
logInAndInit("implicit");
assertTrue(jsConsoleExamplePage.getOutputText().contains("Init Success (Authenticated)"));
}
@Test
public void implicitFlowQueryTest() {
setImplicitFlowFroClient();
jsConsoleExamplePage.navigateTo();
jsConsoleExamplePage.setFlow("implicit");
jsConsoleExamplePage.setResponseMode("query");
jsConsoleExamplePage.init();
jsConsoleExamplePage.logIn();
assertTrue(driver.getPageSource().contains("Invalid parameter: response_mode"));
}
@Test
public void implicitFlowRefreshTokenTest() {
setImplicitFlowFroClient();
logInAndInit("implicit");
jsConsoleExamplePage.refreshToken();
assertTrue(jsConsoleExamplePage.getOutputText().contains("Failed to refresh token"));
}
@Test
public void implicitFlowOnTokenExpireTest() {
RealmRepresentation realm = testRealmResource().toRepresentation();
realm.setAccessTokenLifespanForImplicitFlow(5);
testRealmResource().update(realm);
setImplicitFlowFroClient();
logInAndInit("implicit");
pause(5000);
assertTrue(jsConsoleExamplePage.getEventsText().contains("Access token expired"));
}
private void setImplicitFlowFroClient() {
ClientResource clientResource = ApiUtil.findClientResourceByClientId(testRealmResource(), "js-console");
ClientRepresentation client = clientResource.toRepresentation();
client.setImplicitFlowEnabled(true);
client.setStandardFlowEnabled(false);
clientResource.update(client);
}
private void logInAndInit(String flow) {
jsConsoleExamplePage.navigateTo();
jsConsoleExamplePage.setFlow(flow);
jsConsoleExamplePage.init();
jsConsoleExamplePage.logIn();
testRealmLoginPage.form().login("user", "password");
jsConsoleExamplePage.setFlow(flow);
jsConsoleExamplePage.init();
}
} }

View file

@ -38,7 +38,7 @@ public abstract class AbstractEventTest extends AbstractAuthTest {
configRep.setAdminEventsDetailsEnabled(false); configRep.setAdminEventsDetailsEnabled(false);
configRep.setAdminEventsEnabled(false); configRep.setAdminEventsEnabled(false);
configRep.setEventsEnabled(false); configRep.setEventsEnabled(false);
configRep.setEnabledEventTypes(Collections.EMPTY_LIST); // resets to all types configRep.setEnabledEventTypes(Collections.<String>emptyList()); // resets to all types
saveConfig(); saveConfig();
} }

View file

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

View file

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

View file

@ -15,86 +15,66 @@
* limitations under the License. * limitations under the License.
*/ */
package org.keycloak.testsuite.model; package org.keycloak.testsuite.admin.group;
import org.junit.Assert; import org.junit.Assert;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.keycloak.OAuth2Constants;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.admin.client.resource.RealmResource; 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.AccessToken;
import org.keycloak.representations.RefreshToken; import org.keycloak.representations.RefreshToken;
import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.GroupRepresentation; import org.keycloak.representations.idm.GroupRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.RoleRepresentation; import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.services.managers.RealmManager; import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.AssertEvents; import org.keycloak.testsuite.util.URLAssert;
import org.keycloak.testsuite.OAuthClient; import org.keycloak.util.JsonSerialization;
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.NotFoundException;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import java.io.IOException;
import java.net.URI;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import static org.junit.Assert.assertEquals;
/** /**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a> * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/ */
public class GroupTest { public class GroupTest extends AbstractGroupTest {
@ClassRule @Override
public static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() { public void addTestRealms(List<RealmRepresentation> testRealms) {
@Override RealmRepresentation testRealmRep = loadTestRealm(testRealms);
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
ClientModel app = KeycloakModelUtils.createClient(appRealm, "resource-owner");
app.setDirectAccessGrantsEnabled(true);
app.setSecret("secret");
app = appRealm.getClientByClientId("test-app"); testRealmRep.setEventsEnabled(true);
app.setDirectAccessGrantsEnabled(true);
UserModel user = session.users().addUser(appRealm, "direct-login"); List<UserRepresentation> users = testRealmRep.getUsers();
user.setEmail("direct-login@localhost");
user.setEnabled(true); UserRepresentation user = new UserRepresentation();
user.setUsername("direct-login");
user.setEmail("direct-login@localhost");
user.setEnabled(true);
List<CredentialRepresentation> 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")); List<ClientRepresentation> clients = testRealmRep.getClients();
keycloak = Keycloak.getInstance("http://localhost:8081/auth", "master", "admin", "admin", Constants.ADMIN_CLI_CLIENT_ID);
}
});
protected static Keycloak keycloak; ClientRepresentation client = new ClientRepresentation();
client.setClientId("resource-owner");
@Rule client.setDirectAccessGrantsEnabled(true);
public AssertEvents events = new AssertEvents(keycloakRule); client.setSecret("secret");
clients.add(client);
@Rule }
public WebRule webRule = new WebRule(this);
@WebResource
protected WebDriver driver;
@WebResource
protected OAuthClient oauth;
/** /**
* KEYCLOAK-2716 * KEYCLOAK-2716
@ -102,7 +82,7 @@ public class GroupTest {
*/ */
@Test @Test
public void testClientRemoveWithClientRoleGroupMapping() throws Exception { public void testClientRemoveWithClientRoleGroupMapping() throws Exception {
RealmResource realm = keycloak.realms().realm("test"); RealmResource realm = adminClient.realms().realm("test");
ClientRepresentation client = new ClientRepresentation(); ClientRepresentation client = new ClientRepresentation();
client.setClientId("foo"); client.setClientId("foo");
client.setRootUrl("http://foo"); client.setRootUrl("http://foo");
@ -127,7 +107,7 @@ public class GroupTest {
@Test @Test
public void createAndTestGroups() throws Exception { public void createAndTestGroups() throws Exception {
RealmResource realm = keycloak.realms().realm("test"); RealmResource realm = adminClient.realms().realm("test");
{ {
RoleRepresentation groupRole = new RoleRepresentation(); RoleRepresentation groupRole = new RoleRepresentation();
groupRole.setName("topRole"); groupRole.setName("topRole");
@ -162,6 +142,21 @@ public class GroupTest {
level2Group.setName("level2"); level2Group.setName("level2");
response = realm.groups().group(topGroup.getId()).subGroup(level2Group); response = realm.groups().group(topGroup.getId()).subGroup(level2Group);
response.close(); 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"); level2Group = realm.getGroupByPath("/top/level2");
Assert.assertNotNull(level2Group); Assert.assertNotNull(level2Group);
roles.clear(); roles.clear();
@ -237,73 +232,5 @@ public class GroupTest {
realm.removeDefaultGroup(level3Group.getId()); realm.removeDefaultGroup(level3Group.getId());
defaultGroups = realm.getDefaultGroups(); defaultGroups = realm.getDefaultGroups();
Assert.assertEquals(0, defaultGroups.size()); 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<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"));
}
}
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;
}
} }

View file

@ -18,14 +18,27 @@
package org.keycloak.testsuite.util; package org.keycloak.testsuite.util;
import javax.ws.rs.core.UriBuilder; 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 org.keycloak.testsuite.page.AbstractPage;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import org.keycloak.testsuite.auth.page.login.PageWithLoginUrl; import org.keycloak.testsuite.auth.page.login.PageWithLoginUrl;
import org.openqa.selenium.WebDriver; 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;
/** /**
* *
@ -109,4 +122,63 @@ public class URLAssert {
assertCurrentUrlStartsWith(driver, page.getOIDCLoginUrl().toString()); 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;
}
} }

View file

@ -11,7 +11,8 @@
"jboss-logging" : { "jboss-logging" : {
"success-level": "debug", "success-level": "debug",
"error-level": "warn" "error-level": "warn"
} },
"event-queue": {}
}, },
"realm": { "realm": {

View file

@ -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"]
}

View file

@ -0,0 +1,13 @@
package org.keycloak.testsuite.adapter.example;
import org.keycloak.testsuite.arquillian.annotation.AdapterLibsLocationProperty;
import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
import org.junit.Ignore;
/**
* @author tkyjovsk
*/
@AppServerContainer("app-server-eap")
//@AdapterLibsLocationProperty("adapter.libs.eap")
public class EAPJSConsoleExampleAdapterTest extends AbstractJSConsoleExampleAdapterTest {
}

View file

@ -1,5 +1,6 @@
package org.keycloak.testsuite.adapter.example; package org.keycloak.testsuite.adapter.example;
import org.keycloak.testsuite.arquillian.annotation.AdapterLibsLocationProperty;
import org.keycloak.testsuite.arquillian.annotation.AppServerContainer; import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
import org.junit.Ignore; import org.junit.Ignore;
/** /**
@ -7,7 +8,6 @@ import org.junit.Ignore;
*/ */
@AppServerContainer("app-server-eap6") @AppServerContainer("app-server-eap6")
//@AdapterLibsLocationProperty("adapter.libs.eap6") //@AdapterLibsLocationProperty("adapter.libs.eap6")
@Ignore //jsconsole example has hardcoded relative path to keycloak.js
public class EAP6JSConsoleExampleAdapterTest extends AbstractJSConsoleExampleAdapterTest { public class EAP6JSConsoleExampleAdapterTest extends AbstractJSConsoleExampleAdapterTest {
} }

View file

@ -0,0 +1,16 @@
package org.keycloak.testsuite.adapter.example;
import org.keycloak.testsuite.arquillian.annotation.AdapterLibsLocationProperty;
import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
/**
*
* @author tkyjovsk
*/
@AppServerContainer("app-server-wildfly")
//@AdapterLibsLocationProperty("adapter.libs.wildfly")
public class WildflyJSConsoleExampleAdapterTest extends AbstractJSConsoleExampleAdapterTest {
}

View file

@ -220,8 +220,8 @@
<type>war</type> <type>war</type>
</artifactItem> </artifactItem>
<artifactItem> <artifactItem>
<groupId>org.keycloak.example.demo</groupId> <groupId>org.keycloak.testsuite</groupId>
<artifactId>js-console</artifactId> <artifactId>integration-arquillian-test-apps-js-console</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
<type>war</type> <type>war</type>
</artifactItem> </artifactItem>
@ -287,6 +287,13 @@
<type>zip</type> <type>zip</type>
<includes>**/*realm.json,**/testsaml.json</includes> <includes>**/*realm.json,**/testsaml.json</includes>
</artifactItem> </artifactItem>
<artifactItem>
<groupId>org.keycloak.testsuite</groupId>
<artifactId>integration-arquillian-test-apps-dist</artifactId>
<version>${project.version}</version>
<type>zip</type>
<includes>**/*realm.json,**/testsaml.json</includes>
</artifactItem>
</artifactItems> </artifactItems>
<outputDirectory>${examples.home}</outputDirectory> <outputDirectory>${examples.home}</outputDirectory>
<overWriteIfNewer>true</overWriteIfNewer> <overWriteIfNewer>true</overWriteIfNewer>

View file

@ -48,6 +48,7 @@
<auth.server.port.offset>100</auth.server.port.offset> <auth.server.port.offset>100</auth.server.port.offset>
<auth.server.http.port>8180</auth.server.http.port> <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.https.port>8543</auth.server.https.port>
<auth.server.management.port>10090</auth.server.management.port> <auth.server.management.port>10090</auth.server.management.port>
<auth.server.management.port.jmx>10099</auth.server.management.port.jmx> <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.port.offset>${auth.server.port.offset}</auth.server.port.offset>
<auth.server.http.port>${auth.server.http.port}</auth.server.http.port> <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.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>${auth.server.management.port}</auth.server.management.port>
<auth.server.management.port.jmx>${auth.server.management.port.jmx}</auth.server.management.port.jmx> <auth.server.management.port.jmx>${auth.server.management.port.jmx}</auth.server.management.port.jmx>