Fix version setting
This commit is contained in:
parent
5399c20a8c
commit
bd52908df4
31 changed files with 2 additions and 2816 deletions
|
@ -1,10 +1,7 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<module xmlns="urn:jboss:module:1.1" name="org.keycloak.keycloak-model-file">
|
<module xmlns="urn:jboss:module:1.1" name="org.keycloak.keycloak-model-file">
|
||||||
<resources>
|
<resources>
|
||||||
<resource-root path="keycloak-model-file-1.2.0.RC1-SNAPSHOT.jar"/>
|
|
||||||
<!-- Insert resources here -->
|
<!-- Insert resources here -->
|
||||||
</resources>
|
</resources>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
|
|
@ -33,6 +33,7 @@
|
||||||
</build>
|
</build>
|
||||||
<modules>
|
<modules>
|
||||||
<module>admin-client</module>
|
<module>admin-client</module>
|
||||||
|
<module>broker</module>
|
||||||
<module>cors</module>
|
<module>cors</module>
|
||||||
<module>demo-template</module>
|
<module>demo-template</module>
|
||||||
<module>providers</module>
|
<module>providers</module>
|
||||||
|
@ -42,5 +43,6 @@
|
||||||
<module>fuse</module>
|
<module>fuse</module>
|
||||||
<module>kerberos</module>
|
<module>kerberos</module>
|
||||||
<module>themes</module>
|
<module>themes</module>
|
||||||
|
<module>saml</module>
|
||||||
</modules>
|
</modules>
|
||||||
</project>
|
</project>
|
||||||
|
|
|
@ -1,116 +0,0 @@
|
||||||
Keycloak Web Performance Testsuite
|
|
||||||
==================================
|
|
||||||
To run web performance testsuite, you need to:
|
|
||||||
1) Run Keycloak server
|
|
||||||
2) Add some users into your Keycloak
|
|
||||||
3) Run JMeter performance tests
|
|
||||||
|
|
||||||
Keycloak server
|
|
||||||
---------------
|
|
||||||
In this project you can run:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
mvn exec:java -Pkeycloak-perf-server
|
|
||||||
````
|
|
||||||
|
|
||||||
which will execute embedded Undertow server with:
|
|
||||||
* Keycloak server
|
|
||||||
* Performance-tools for mass adding of new users
|
|
||||||
* Simple web application for testing performance
|
|
||||||
It will also automatically import realm "perf-realm" into Keycloak from file src/main/resources/perfrealm.json
|
|
||||||
|
|
||||||
Note that by default it will use in-memory H2 database, which means that all changes (for example all added users) are discarded after server restart. For performance testing, it's recommended to use some database like PostgreSQL or MySQL
|
|
||||||
|
|
||||||
To run server with PostgreSQL you may use command like this (change host,port,dbName and credentials according your DB configuration):
|
|
||||||
```shell
|
|
||||||
mvn exec:java -Pkeycloak-perf-server -Dhibernate.connection.url=jdbc:postgresql://localhost:5432/keycloak_perf -Dhibernate.connection.driver_class=org.postgresql.Driver -Dhibernate.connection.username=postgres -Dhibernate.connection.password=postgres -Dhibernate.connection.autocommit=false
|
|
||||||
````
|
|
||||||
|
|
||||||
To run server with MySQL you may use command like this (change host,port,dbName and credentials according your DB configuration):
|
|
||||||
```shell
|
|
||||||
mvn exec:java -Pkeycloak-perf-server -Dhibernate.connection.url=jdbc:mysql://localhost/keycloak_perf -Dhibernate.connection.driver_class=com.mysql.jdbc.Driver -Dhibernate.connection.username=portal -Dhibernate.connection.password=portal -Dhibernate.connection.autocommit=false
|
|
||||||
````
|
|
||||||
|
|
||||||
To run server with Mongo you may use command like this (change host,port,dbName and credentials according your DB configuration):
|
|
||||||
```shell
|
|
||||||
mvn exec:java -Pkeycloak-perf-server -Dkeycloak.model.provider=mongo -Dkeycloak.model.mongo.db=keycloak-perf
|
|
||||||
````
|
|
||||||
|
|
||||||
To enable cache, you can add additional property:
|
|
||||||
```shell
|
|
||||||
-Dkeycloak.model.cache.provider=simple
|
|
||||||
````
|
|
||||||
|
|
||||||
Adding users
|
|
||||||
-----------------
|
|
||||||
|
|
||||||
Performance test is using users with prefix "user" (so users like "user-0", "user-1", ... , "user-123456" etc). So you first need to add some of these users into your database.
|
|
||||||
|
|
||||||
For checking users count, you can open this URL:
|
|
||||||
```shell
|
|
||||||
http://localhost:8081/keycloak-tools/perf/perf-realm/get-users-count?prefix=user
|
|
||||||
````
|
|
||||||
|
|
||||||
For adding 10000 new users into your database (will start from last added user, so you don't need to explicitly check how many users to create are needed:
|
|
||||||
```shell
|
|
||||||
http://localhost:8081/keycloak-tools/perf/perf-realm/create-available-users?prefix=user&count=10000&batch=100&async=true&roles=role-0,role-1
|
|
||||||
````
|
|
||||||
|
|
||||||
For update role mappings of all users:
|
|
||||||
```shell
|
|
||||||
http://localhost:8081/keycloak-tools/perf/perf-realm/update-all-users?prefix=user&async=true&roles=role-3,perf-app:approle-3,perf-app:approle-4
|
|
||||||
````
|
|
||||||
|
|
||||||
For deleting all users:
|
|
||||||
```shell
|
|
||||||
http://localhost:8081/keycloak-tools/perf/perf-realm/delete-all-users?prefix=user
|
|
||||||
````
|
|
||||||
|
|
||||||
Seeing progress of job for creating/updating/deleting users
|
|
||||||
```shell
|
|
||||||
http://localhost:8081/keycloak-tools/perf/jobs
|
|
||||||
````
|
|
||||||
|
|
||||||
Note that with default H2 are all data automatically cleared after server restart. So it's recommended to use different DB like PostgreSQL or Mongo.
|
|
||||||
|
|
||||||
|
|
||||||
Execute performance test
|
|
||||||
------------------------
|
|
||||||
|
|
||||||
When server is started and some users are created, you can run performance test. It's possible to run it from Command line with:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
mvn verify -Pperformance-test
|
|
||||||
````
|
|
||||||
|
|
||||||
By default, test is using Keycloak on localhost:8081 and 50 concurrent clients (threads) and each client doing 50 test iterations. Each iterations is:
|
|
||||||
- Login user into KC and retrieve code
|
|
||||||
- Exchange code for accessToken
|
|
||||||
- Refresh token 2 times
|
|
||||||
- Logout user
|
|
||||||
|
|
||||||
Each client is using separate username, so actually you need at least 50 users created into your DB (users "user-0", "user-1", ... "user-49" . See above)
|
|
||||||
|
|
||||||
ATM it's possible to adjust behaviour with properties:
|
|
||||||
* host --- Keycloak host ("localhost" by default)
|
|
||||||
* port --- Keycloak port ("8081" by default)
|
|
||||||
* userPrefix --- prefix of users ("user" by default)
|
|
||||||
* concurrentUsers --- Number of concurrent clients (50 by default). RampUp time is configured to start 5 new users each second, so with 50 users are all clients started in like 10 seconds.
|
|
||||||
* iterationsPerUser --- Number of iterations per each client (50 by default). So by default we have 50*50 = 2500 iterations in total per whole test
|
|
||||||
* refreshTokenRequestsPerIteration --- Number of refresh token iterations (2 by default)
|
|
||||||
|
|
||||||
You can change configuration by adding some properties into command line for example to start just with 10 concurrent clients and 10 iterations per client (so just 100 test iterations) you can use:
|
|
||||||
```shell
|
|
||||||
mvn verify -Pperformance-test -DconcurrentUsers=10 -DiterationsPerUser=10
|
|
||||||
````
|
|
||||||
|
|
||||||
After triggering test are results in file target/jmeter/results/aggregatedRequests-durations-<TIMESTAMP>-keycloak_web_perf_test.html
|
|
||||||
|
|
||||||
Execute performance test from JMeter GUI
|
|
||||||
----------------------------------------
|
|
||||||
You can run
|
|
||||||
```shell
|
|
||||||
mvn jmeter:gui
|
|
||||||
````
|
|
||||||
|
|
||||||
and then open file src/test/jmeter/keycloak_web_perf_test.jmx and trigger test from JMeter GUI. It may be good as you can see the progress of whole test during execution.
|
|
|
@ -1,227 +0,0 @@
|
||||||
<?xml version="1.0"?>
|
|
||||||
<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>
|
|
||||||
<artifactId>keycloak-testsuite-pom</artifactId>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<version>1.2.0.RC1-SNAPSHOT</version>
|
|
||||||
<relativePath>../pom.xml</relativePath>
|
|
||||||
</parent>
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
|
|
||||||
<artifactId>keycloak-testsuite-performance-web</artifactId>
|
|
||||||
<name>Keycloak TestSuite for Web Performance</name>
|
|
||||||
<description/>
|
|
||||||
|
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-testsuite-integration</artifactId>
|
|
||||||
<exclusions>
|
|
||||||
<exclusion>
|
|
||||||
<groupId>org.jboss.spec.javax.servlet</groupId>
|
|
||||||
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
|
||||||
</exclusion>
|
|
||||||
</exclusions>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-testsuite-tools</artifactId>
|
|
||||||
<classifier>classes</classifier>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- Needed by undertow -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.jboss.spec.javax.servlet</groupId>
|
|
||||||
<artifactId>jboss-servlet-api_3.1_spec</artifactId>
|
|
||||||
<version>1.0.0.Final</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- Resteasy deps specified here as we want latest version of them -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.jboss.resteasy</groupId>
|
|
||||||
<artifactId>jaxrs-api</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.jboss.resteasy</groupId>
|
|
||||||
<artifactId>resteasy-jaxrs</artifactId>
|
|
||||||
<exclusions>
|
|
||||||
<exclusion>
|
|
||||||
<groupId>log4j</groupId>
|
|
||||||
<artifactId>log4j</artifactId>
|
|
||||||
</exclusion>
|
|
||||||
<exclusion>
|
|
||||||
<groupId>org.slf4j</groupId>
|
|
||||||
<artifactId>slf4j-api</artifactId>
|
|
||||||
</exclusion>
|
|
||||||
<exclusion>
|
|
||||||
<groupId>org.slf4j</groupId>
|
|
||||||
<artifactId>slf4j-simple</artifactId>
|
|
||||||
</exclusion>
|
|
||||||
</exclusions>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.jboss.resteasy</groupId>
|
|
||||||
<artifactId>resteasy-client</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.jboss.resteasy</groupId>
|
|
||||||
<artifactId>resteasy-multipart-provider</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.jboss.resteasy</groupId>
|
|
||||||
<artifactId>resteasy-jackson-provider</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.jboss.resteasy</groupId>
|
|
||||||
<artifactId>resteasy-undertow</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.apache.jmeter</groupId>
|
|
||||||
<artifactId>ApacheJMeter_java</artifactId>
|
|
||||||
<exclusions>
|
|
||||||
<exclusion>
|
|
||||||
<groupId>org.bouncycastle</groupId>
|
|
||||||
<artifactId>bcprov-jdk15on</artifactId>
|
|
||||||
</exclusion>
|
|
||||||
<exclusion>
|
|
||||||
<groupId>org.apache.tika</groupId>
|
|
||||||
<artifactId>tika-parsers</artifactId>
|
|
||||||
</exclusion>
|
|
||||||
</exclusions>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
</dependencies>
|
|
||||||
|
|
||||||
<build>
|
|
||||||
<plugins>
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
|
||||||
<artifactId>maven-deploy-plugin</artifactId>
|
|
||||||
<configuration>
|
|
||||||
<skip>true</skip>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
|
||||||
<artifactId>maven-compiler-plugin</artifactId>
|
|
||||||
<configuration>
|
|
||||||
<source>${maven.compiler.source}</source>
|
|
||||||
<target>${maven.compiler.target}</target>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.codehaus.mojo</groupId>
|
|
||||||
<artifactId>exec-maven-plugin</artifactId>
|
|
||||||
<configuration>
|
|
||||||
<workingDirectory>${project.basedir}</workingDirectory>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
</plugins>
|
|
||||||
</build>
|
|
||||||
|
|
||||||
<profiles>
|
|
||||||
|
|
||||||
<profile>
|
|
||||||
<id>keycloak-perf-server</id>
|
|
||||||
<build>
|
|
||||||
<plugins>
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.codehaus.mojo</groupId>
|
|
||||||
<artifactId>exec-maven-plugin</artifactId>
|
|
||||||
<configuration>
|
|
||||||
<mainClass>org.keycloak.testsuite.performance.web.KeycloakPerfServer</mainClass>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
</plugins>
|
|
||||||
</build>
|
|
||||||
</profile>
|
|
||||||
|
|
||||||
<profile>
|
|
||||||
<id>performance-test</id>
|
|
||||||
|
|
||||||
<properties>
|
|
||||||
<host>localhost</host>
|
|
||||||
<port>8081</port>
|
|
||||||
<userPrefix>user</userPrefix>
|
|
||||||
<concurrentUsers>50</concurrentUsers>
|
|
||||||
<iterationsPerUser>50</iterationsPerUser>
|
|
||||||
<refreshTokenRequestsPerIteration>2</refreshTokenRequestsPerIteration>
|
|
||||||
</properties>
|
|
||||||
|
|
||||||
<build>
|
|
||||||
<plugins>
|
|
||||||
|
|
||||||
<plugin>
|
|
||||||
<groupId>com.lazerycode.jmeter</groupId>
|
|
||||||
<artifactId>jmeter-maven-plugin</artifactId>
|
|
||||||
<configuration>
|
|
||||||
<suppressJMeterOutput>false</suppressJMeterOutput>
|
|
||||||
<propertiesSystem>
|
|
||||||
<host>${host}</host>
|
|
||||||
<port>${port}</port>
|
|
||||||
<userPrefix>${userPrefix}</userPrefix>
|
|
||||||
<concurrentUsers>${concurrentUsers}</concurrentUsers>
|
|
||||||
<iterationsPerUser>${iterationsPerUser}</iterationsPerUser>
|
|
||||||
<refreshTokenRequestsPerIteration>${refreshTokenRequestsPerIteration}</refreshTokenRequestsPerIteration>
|
|
||||||
</propertiesSystem>
|
|
||||||
</configuration>
|
|
||||||
<executions>
|
|
||||||
<execution>
|
|
||||||
<id>jmeter-tests</id>
|
|
||||||
<phase>verify</phase>
|
|
||||||
<goals>
|
|
||||||
<goal>jmeter</goal>
|
|
||||||
</goals>
|
|
||||||
</execution>
|
|
||||||
</executions>
|
|
||||||
<dependencies>
|
|
||||||
</dependencies>
|
|
||||||
</plugin>
|
|
||||||
|
|
||||||
<plugin>
|
|
||||||
<groupId>com.lazerycode.jmeter</groupId>
|
|
||||||
<artifactId>jmeter-analysis-maven-plugin</artifactId>
|
|
||||||
<executions>
|
|
||||||
<execution>
|
|
||||||
<id>jmeter-tests-analyze</id>
|
|
||||||
<phase>verify</phase>
|
|
||||||
<goals>
|
|
||||||
<goal>analyze</goal>
|
|
||||||
</goals>
|
|
||||||
<configuration>
|
|
||||||
<source>${project.build.directory}/jmeter/results/*.jtl</source>
|
|
||||||
<targetDirectory>${project.build.directory}/jmeter/results</targetDirectory>
|
|
||||||
<preserveDirectories>false</preserveDirectories>
|
|
||||||
|
|
||||||
<requestGroups>
|
|
||||||
<requestGroup implementation="com.lazerycode.jmeter.analyzer.config.RequestGroup">
|
|
||||||
<name>aggregatedRequests</name>
|
|
||||||
<pattern>* request</pattern>
|
|
||||||
</requestGroup>
|
|
||||||
<requestGroup implementation="com.lazerycode.jmeter.analyzer.config.RequestGroup">
|
|
||||||
<name>codes</name>
|
|
||||||
<pattern>**/perf-app/perf-servlet?code=*</pattern>
|
|
||||||
</requestGroup>
|
|
||||||
</requestGroups>
|
|
||||||
|
|
||||||
<writers>
|
|
||||||
<com.lazerycode.jmeter.analyzer.writer.SummaryTextToStdOutWriter/>
|
|
||||||
<!--<com.lazerycode.jmeter.analyzer.writer.SummaryTextToFileWriter/>-->
|
|
||||||
<com.lazerycode.jmeter.analyzer.writer.HtmlWriter/>
|
|
||||||
<!--<com.lazerycode.jmeter.analyzer.writer.DetailsToCsvWriter/>-->
|
|
||||||
<com.lazerycode.jmeter.analyzer.writer.DetailsToHtmlWriter/>
|
|
||||||
<com.lazerycode.jmeter.analyzer.writer.ChartWriter/>
|
|
||||||
</writers>
|
|
||||||
</configuration>
|
|
||||||
</execution>
|
|
||||||
</executions>
|
|
||||||
</plugin>
|
|
||||||
|
|
||||||
</plugins>
|
|
||||||
</build>
|
|
||||||
</profile>
|
|
||||||
|
|
||||||
</profiles>
|
|
||||||
|
|
||||||
</project>
|
|
|
@ -1,97 +0,0 @@
|
||||||
package org.keycloak.testsuite.performance.web;
|
|
||||||
|
|
||||||
import io.undertow.server.handlers.resource.ClassPathResourceManager;
|
|
||||||
import io.undertow.servlet.Servlets;
|
|
||||||
import io.undertow.servlet.api.DeploymentInfo;
|
|
||||||
import io.undertow.servlet.api.FilterInfo;
|
|
||||||
import io.undertow.servlet.api.ServletInfo;
|
|
||||||
import org.jboss.resteasy.plugins.server.undertow.UndertowJaxrsServer;
|
|
||||||
import org.jboss.resteasy.spi.ResteasyDeployment;
|
|
||||||
import org.keycloak.services.filters.ClientConnectionFilter;
|
|
||||||
import org.keycloak.services.filters.KeycloakSessionServletFilter;
|
|
||||||
import org.keycloak.test.tools.KeycloakTestApplication;
|
|
||||||
import org.keycloak.testutils.KeycloakServer;
|
|
||||||
|
|
||||||
import javax.servlet.DispatcherType;
|
|
||||||
import java.io.InputStream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
|
||||||
*/
|
|
||||||
public class KeycloakPerfServer {
|
|
||||||
|
|
||||||
private KeycloakServer keycloakServer;
|
|
||||||
|
|
||||||
public static void main(String[] args) throws Throwable {
|
|
||||||
// TODO: should be better than programmatic setup here, but don't copy persistence.xml again...
|
|
||||||
System.setProperty("hibernate.hbm2ddl.auto", "update");
|
|
||||||
|
|
||||||
if (System.getProperty("undertowWorkerThreads") == null) {
|
|
||||||
System.setProperty("undertowWorkerThreads", "256");
|
|
||||||
}
|
|
||||||
|
|
||||||
KeycloakServer keycloakServer = KeycloakServer.bootstrapKeycloakServer(args);
|
|
||||||
System.out.println("Keycloak server bootstrapped");
|
|
||||||
|
|
||||||
KeycloakSessionFactoryHolder.setKeycloakSessionFactory(keycloakServer.getSessionFactory());
|
|
||||||
new KeycloakPerfServer(keycloakServer).start();
|
|
||||||
}
|
|
||||||
|
|
||||||
public KeycloakPerfServer(KeycloakServer keycloakServer) {
|
|
||||||
this.keycloakServer = keycloakServer;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void start() {
|
|
||||||
importPerfRealm();
|
|
||||||
deployPerfTools();
|
|
||||||
deployPerfApp();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void importPerfRealm() {
|
|
||||||
InputStream perfRealmStream = KeycloakPerfServer.class.getClassLoader().getResourceAsStream("perfrealm.json");
|
|
||||||
keycloakServer.importRealm(perfRealmStream);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void deployPerfTools() {
|
|
||||||
ResteasyDeployment deployment = new ResteasyDeployment();
|
|
||||||
deployment.setApplicationClass(KeycloakToolsApplication.class.getName());
|
|
||||||
|
|
||||||
UndertowJaxrsServer server = keycloakServer.getServer();
|
|
||||||
|
|
||||||
DeploymentInfo di = server.undertowDeployment(deployment, "");
|
|
||||||
di.setClassLoader(KeycloakTestApplication.class.getClassLoader());
|
|
||||||
di.setContextPath("/keycloak-tools");
|
|
||||||
di.setDeploymentName("KeycloakTools");
|
|
||||||
|
|
||||||
FilterInfo filter = Servlets.filter("SessionFilter", KeycloakSessionServletFilter.class);
|
|
||||||
di.addFilter(filter);
|
|
||||||
di.addFilterUrlMapping("SessionFilter", "/perf/*", DispatcherType.REQUEST);
|
|
||||||
|
|
||||||
FilterInfo connectionFilter = Servlets.filter("ClientConnectionFilter", ClientConnectionFilter.class);
|
|
||||||
di.addFilter(connectionFilter);
|
|
||||||
di.addFilterUrlMapping("ClientConnectionFilter", "/perf/*", DispatcherType.REQUEST);
|
|
||||||
|
|
||||||
server.deploy(di);
|
|
||||||
|
|
||||||
System.out.println("Keycloak tools deployed");
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void deployPerfApp() {
|
|
||||||
DeploymentInfo deploymentInfo = new DeploymentInfo();
|
|
||||||
deploymentInfo.setClassLoader(getClass().getClassLoader());
|
|
||||||
deploymentInfo.setDeploymentName("PerfApp");
|
|
||||||
deploymentInfo.setContextPath("/perf-app");
|
|
||||||
|
|
||||||
ServletInfo servlet = new ServletInfo("PerfAppServlet", PerfAppServlet.class);
|
|
||||||
servlet.addMapping("/perf-servlet/*");
|
|
||||||
servlet.addInitParam(PerfAppServlet.BASE_URL_INIT_PARAM, "http://" + keycloakServer.getConfig().getHost() + ":" + keycloakServer.getConfig().getPort());
|
|
||||||
|
|
||||||
deploymentInfo.addServlet(servlet);
|
|
||||||
|
|
||||||
deploymentInfo.setResourceManager(new ClassPathResourceManager(getClass().getClassLoader()));
|
|
||||||
|
|
||||||
keycloakServer.getServer().deploy(deploymentInfo);
|
|
||||||
|
|
||||||
System.out.println("PerfApp deployed");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
package org.keycloak.testsuite.performance.web;
|
|
||||||
|
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Static holder to allow sharing ProviderSessionFactory among different JAX-RS applications
|
|
||||||
*
|
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
|
||||||
*/
|
|
||||||
public class KeycloakSessionFactoryHolder {
|
|
||||||
|
|
||||||
private static KeycloakSessionFactory keycloakSessionFactory;
|
|
||||||
|
|
||||||
public static KeycloakSessionFactory getKeycloakSessionFactory() {
|
|
||||||
return keycloakSessionFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setKeycloakSessionFactory(KeycloakSessionFactory keycloakSessionFactory) {
|
|
||||||
KeycloakSessionFactoryHolder.keycloakSessionFactory = keycloakSessionFactory;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,41 +0,0 @@
|
||||||
package org.keycloak.testsuite.performance.web;
|
|
||||||
|
|
||||||
import org.jboss.resteasy.core.Dispatcher;
|
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
|
||||||
import org.keycloak.test.tools.PerfTools;
|
|
||||||
|
|
||||||
import javax.servlet.ServletContext;
|
|
||||||
import javax.ws.rs.core.Application;
|
|
||||||
import javax.ws.rs.core.Context;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Modified version of {@link org.keycloak.test.tools.KeycloakTestApplication}, which shares ProviderSessionFactory with KeycloakApplication
|
|
||||||
*
|
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
|
||||||
*/
|
|
||||||
public class KeycloakToolsApplication extends Application {
|
|
||||||
|
|
||||||
protected KeycloakSessionFactory keycloakSessionFactory;
|
|
||||||
protected Set<Class<?>> classes = new HashSet<Class<?>>();
|
|
||||||
protected Set<Object> singletons = new HashSet<Object>();
|
|
||||||
|
|
||||||
public KeycloakToolsApplication(@Context ServletContext context, @Context Dispatcher dispatcher) {
|
|
||||||
this.keycloakSessionFactory = KeycloakSessionFactoryHolder.getKeycloakSessionFactory();
|
|
||||||
context.setAttribute(KeycloakSessionFactory.class.getName(), this.keycloakSessionFactory);
|
|
||||||
singletons.add(new PerfTools(keycloakSessionFactory));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Set<Class<?>> getClasses() {
|
|
||||||
return classes;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Set<Object> getSingletons() {
|
|
||||||
return singletons;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,373 +0,0 @@
|
||||||
package org.keycloak.testsuite.performance.web;
|
|
||||||
|
|
||||||
import org.apache.commons.io.IOUtils;
|
|
||||||
import org.apache.http.HttpResponse;
|
|
||||||
import org.apache.http.NameValuePair;
|
|
||||||
import org.apache.http.client.HttpClient;
|
|
||||||
import org.apache.http.client.entity.UrlEncodedFormEntity;
|
|
||||||
import org.apache.http.client.methods.HttpGet;
|
|
||||||
import org.apache.http.client.methods.HttpPost;
|
|
||||||
import org.apache.http.impl.client.DefaultHttpClient;
|
|
||||||
import org.apache.http.message.BasicNameValuePair;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
import org.keycloak.OAuth2Constants;
|
|
||||||
import org.keycloak.RSATokenVerifier;
|
|
||||||
import org.keycloak.VerificationException;
|
|
||||||
import org.keycloak.jose.jws.JWSInput;
|
|
||||||
import org.keycloak.jose.jws.crypto.RSAProvider;
|
|
||||||
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
|
|
||||||
import org.keycloak.representations.AccessToken;
|
|
||||||
import org.keycloak.representations.RefreshToken;
|
|
||||||
import org.keycloak.util.BasicAuthHelper;
|
|
||||||
import org.keycloak.util.PemUtils;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.ws.rs.core.UriBuilder;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.security.PublicKey;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TODO: Remove from here and instead merge with org.keycloak.testsuite.OAuthClient
|
|
||||||
*
|
|
||||||
*@author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
|
||||||
*/
|
|
||||||
public class OAuthClient {
|
|
||||||
|
|
||||||
private String baseUrl;
|
|
||||||
|
|
||||||
private String realm = "perf-realm";
|
|
||||||
|
|
||||||
private String responseType = OAuth2Constants.CODE;
|
|
||||||
|
|
||||||
private String grantType = "authorization_code";
|
|
||||||
|
|
||||||
private String clientId = "perf-app";
|
|
||||||
|
|
||||||
private String redirectUri;
|
|
||||||
|
|
||||||
private String state = "123";
|
|
||||||
|
|
||||||
private PublicKey realmPublicKey;
|
|
||||||
|
|
||||||
public OAuthClient(String baseUrl) {
|
|
||||||
try {
|
|
||||||
JSONObject realmJson = new JSONObject(IOUtils.toString(getClass().getResourceAsStream("/perfrealm.json")));
|
|
||||||
realmPublicKey = PemUtils.decodePublicKey(realmJson.getString("publicKey"));
|
|
||||||
|
|
||||||
this.baseUrl = (baseUrl != null) ? baseUrl + "/auth" : "http://localhost:8081/auth";
|
|
||||||
this.redirectUri = baseUrl + "/perf-app/perf-servlet";
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException("Failed to retrieve realm public key", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public AccessTokenResponse doAccessTokenRequest(String code, String password) {
|
|
||||||
HttpClient client = new DefaultHttpClient();
|
|
||||||
HttpPost post = new HttpPost(getAccessTokenUrl());
|
|
||||||
|
|
||||||
List<NameValuePair> parameters = new LinkedList<NameValuePair>();
|
|
||||||
if (grantType != null) {
|
|
||||||
parameters.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, grantType));
|
|
||||||
}
|
|
||||||
if (code != null) {
|
|
||||||
parameters.add(new BasicNameValuePair(OAuth2Constants.CODE, code));
|
|
||||||
}
|
|
||||||
if (redirectUri != null) {
|
|
||||||
parameters.add(new BasicNameValuePair(OAuth2Constants.REDIRECT_URI, redirectUri));
|
|
||||||
}
|
|
||||||
if (clientId != null && password != null) {
|
|
||||||
String authorization = BasicAuthHelper.createHeader(clientId, password);
|
|
||||||
post.setHeader("Authorization", authorization);
|
|
||||||
}
|
|
||||||
else if (clientId != null) {
|
|
||||||
parameters.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ID, clientId));
|
|
||||||
}
|
|
||||||
|
|
||||||
UrlEncodedFormEntity formEntity = null;
|
|
||||||
try {
|
|
||||||
formEntity = new UrlEncodedFormEntity(parameters, "UTF-8");
|
|
||||||
} catch (UnsupportedEncodingException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
post.setEntity(formEntity);
|
|
||||||
|
|
||||||
try {
|
|
||||||
return new AccessTokenResponse(client.execute(post));
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException("Failed to retrieve access token", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public AccessTokenResponse doGrantAccessTokenRequest(String clientSecret, String username, String password) throws Exception {
|
|
||||||
HttpClient client = new DefaultHttpClient();
|
|
||||||
HttpPost post = new HttpPost(getResourceOwnerPasswordCredentialGrantUrl());
|
|
||||||
|
|
||||||
String authorization = BasicAuthHelper.createHeader(clientId, clientSecret);
|
|
||||||
post.setHeader("Authorization", authorization);
|
|
||||||
|
|
||||||
List<NameValuePair> parameters = new LinkedList<NameValuePair>();
|
|
||||||
parameters.add(new BasicNameValuePair("username", username));
|
|
||||||
parameters.add(new BasicNameValuePair("password", password));
|
|
||||||
|
|
||||||
UrlEncodedFormEntity formEntity;
|
|
||||||
try {
|
|
||||||
formEntity = new UrlEncodedFormEntity(parameters, "UTF-8");
|
|
||||||
} catch (UnsupportedEncodingException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
post.setEntity(formEntity);
|
|
||||||
|
|
||||||
return new AccessTokenResponse(client.execute(post));
|
|
||||||
}
|
|
||||||
|
|
||||||
public HttpResponse doLogout(String redirectUri, String sessionState) throws IOException {
|
|
||||||
HttpClient client = new DefaultHttpClient();
|
|
||||||
HttpGet get = new HttpGet(getLogoutUrl(redirectUri, sessionState));
|
|
||||||
|
|
||||||
return client.execute(get);
|
|
||||||
}
|
|
||||||
|
|
||||||
public AccessTokenResponse doRefreshTokenRequest(String refreshToken, String password) {
|
|
||||||
HttpClient client = new DefaultHttpClient();
|
|
||||||
HttpPost post = new HttpPost(getRefreshTokenUrl());
|
|
||||||
|
|
||||||
List<NameValuePair> parameters = new LinkedList<NameValuePair>();
|
|
||||||
if (grantType != null) {
|
|
||||||
parameters.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, grantType));
|
|
||||||
}
|
|
||||||
if (refreshToken != null) {
|
|
||||||
parameters.add(new BasicNameValuePair(OAuth2Constants.REFRESH_TOKEN, refreshToken));
|
|
||||||
}
|
|
||||||
if (clientId != null && password != null) {
|
|
||||||
String authorization = BasicAuthHelper.createHeader(clientId, password);
|
|
||||||
post.setHeader("Authorization", authorization);
|
|
||||||
}
|
|
||||||
else if (clientId != null) {
|
|
||||||
parameters.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ID, clientId));
|
|
||||||
}
|
|
||||||
|
|
||||||
UrlEncodedFormEntity formEntity;
|
|
||||||
try {
|
|
||||||
formEntity = new UrlEncodedFormEntity(parameters, "UTF-8");
|
|
||||||
} catch (UnsupportedEncodingException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
post.setEntity(formEntity);
|
|
||||||
|
|
||||||
try {
|
|
||||||
return new AccessTokenResponse(client.execute(post));
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException("Failed to retrieve access token", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public AccessToken verifyToken(String token) {
|
|
||||||
try {
|
|
||||||
return RSATokenVerifier.verifyToken(token, realmPublicKey, baseUrl + "/" + realm);
|
|
||||||
} catch (VerificationException e) {
|
|
||||||
throw new RuntimeException("Failed to verify token", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void verifyCode(String code) {
|
|
||||||
if (!RSAProvider.verify(new JWSInput(code), realmPublicKey)) {
|
|
||||||
throw new RuntimeException("Failed to verify code");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public RefreshToken verifyRefreshToken(String refreshToken) {
|
|
||||||
try {
|
|
||||||
JWSInput jws = new JWSInput(refreshToken);
|
|
||||||
if (!RSAProvider.verify(jws, realmPublicKey)) {
|
|
||||||
throw new RuntimeException("Invalid refresh token");
|
|
||||||
}
|
|
||||||
return jws.readJsonContent(RefreshToken.class);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException("Invalid refresh token", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getClientId() {
|
|
||||||
return clientId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getRedirectUri() {
|
|
||||||
return redirectUri;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getLoginFormUrl() {
|
|
||||||
UriBuilder b = OIDCLoginProtocolService.authUrl(UriBuilder.fromUri(baseUrl));
|
|
||||||
if (responseType != null) {
|
|
||||||
b.queryParam(OAuth2Constants.RESPONSE_TYPE, responseType);
|
|
||||||
}
|
|
||||||
if (clientId != null) {
|
|
||||||
b.queryParam(OAuth2Constants.CLIENT_ID, clientId);
|
|
||||||
}
|
|
||||||
if (redirectUri != null) {
|
|
||||||
b.queryParam(OAuth2Constants.REDIRECT_URI, redirectUri);
|
|
||||||
}
|
|
||||||
if (state != null) {
|
|
||||||
b.queryParam(OAuth2Constants.STATE, state);
|
|
||||||
}
|
|
||||||
return b.build(realm).toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getAccessTokenUrl() {
|
|
||||||
UriBuilder b = OIDCLoginProtocolService.tokenUrl(UriBuilder.fromUri(baseUrl));
|
|
||||||
return b.build(realm).toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getLogoutUrl(String redirectUri, String sessionState) {
|
|
||||||
UriBuilder b = OIDCLoginProtocolService.logoutUrl(UriBuilder.fromUri(baseUrl));
|
|
||||||
if (redirectUri != null) {
|
|
||||||
b.queryParam(OAuth2Constants.REDIRECT_URI, redirectUri);
|
|
||||||
}
|
|
||||||
if (sessionState != null) {
|
|
||||||
b.queryParam("session_state", sessionState);
|
|
||||||
}
|
|
||||||
return b.build(realm).toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getResourceOwnerPasswordCredentialGrantUrl() {
|
|
||||||
UriBuilder b = OIDCLoginProtocolService.tokenUrl(UriBuilder.fromUri(baseUrl));
|
|
||||||
return b.build(realm).toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getRefreshTokenUrl() {
|
|
||||||
UriBuilder b = OIDCLoginProtocolService.tokenUrl(UriBuilder.fromUri(baseUrl));
|
|
||||||
return b.build(realm).toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public OAuthClient realm(String realm) {
|
|
||||||
this.realm = realm;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
public OAuthClient realmPublicKey(PublicKey key) {
|
|
||||||
this.realmPublicKey = key;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public OAuthClient clientId(String clientId) {
|
|
||||||
this.clientId = clientId;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public OAuthClient redirectUri(String redirectUri) {
|
|
||||||
this.redirectUri = redirectUri;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public OAuthClient responseType(String responseType) {
|
|
||||||
this.responseType = responseType;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public OAuthClient state(String state) {
|
|
||||||
this.state = state;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getRealm() {
|
|
||||||
return realm;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class AuthorizationCodeResponse {
|
|
||||||
|
|
||||||
private String code;
|
|
||||||
private String state;
|
|
||||||
private String error;
|
|
||||||
|
|
||||||
public AuthorizationCodeResponse(OAuthClient client, HttpServletRequest req) {
|
|
||||||
code = req.getParameter(OAuth2Constants.CODE);
|
|
||||||
state = req.getParameter(OAuth2Constants.STATE);
|
|
||||||
error = req.getParameter(OAuth2Constants.ERROR);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getCode() {
|
|
||||||
return code;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getState() {
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getError() {
|
|
||||||
return error;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class AccessTokenResponse {
|
|
||||||
private int statusCode;
|
|
||||||
|
|
||||||
private String accessToken;
|
|
||||||
private String tokenType;
|
|
||||||
private int expiresIn;
|
|
||||||
private String refreshToken;
|
|
||||||
private String idToken;
|
|
||||||
private String sessionState;
|
|
||||||
|
|
||||||
private String error;
|
|
||||||
|
|
||||||
public AccessTokenResponse(HttpResponse response) throws Exception {
|
|
||||||
statusCode = response.getStatusLine().getStatusCode();
|
|
||||||
if (!"application/json".equals(response.getHeaders("Content-Type")[0].getValue())) {
|
|
||||||
throw new RuntimeException("Invalid content type");
|
|
||||||
}
|
|
||||||
|
|
||||||
String s = IOUtils.toString(response.getEntity().getContent());
|
|
||||||
JSONObject responseJson = new JSONObject(s);
|
|
||||||
|
|
||||||
if (statusCode == 200) {
|
|
||||||
accessToken = responseJson.getString("access_token");
|
|
||||||
tokenType = responseJson.getString("token_type");
|
|
||||||
expiresIn = responseJson.getInt("expires_in");
|
|
||||||
idToken = responseJson.optString("id_token");
|
|
||||||
sessionState = responseJson.optString("session-state");
|
|
||||||
|
|
||||||
if (responseJson.has(OAuth2Constants.REFRESH_TOKEN)) {
|
|
||||||
refreshToken = responseJson.getString(OAuth2Constants.REFRESH_TOKEN);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
error = responseJson.getString(OAuth2Constants.ERROR);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getAccessToken() {
|
|
||||||
return accessToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getError() {
|
|
||||||
return error;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getExpiresIn() {
|
|
||||||
return expiresIn;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getStatusCode() {
|
|
||||||
return statusCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getRefreshToken() {
|
|
||||||
return refreshToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getTokenType() {
|
|
||||||
return tokenType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getIdToken() {
|
|
||||||
return idToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getSessionState() {
|
|
||||||
return sessionState;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,153 +0,0 @@
|
||||||
package org.keycloak.testsuite.performance.web;
|
|
||||||
|
|
||||||
import freemarker.cache.ClassTemplateLoader;
|
|
||||||
import freemarker.template.Configuration;
|
|
||||||
import freemarker.template.Template;
|
|
||||||
import freemarker.template.TemplateException;
|
|
||||||
import org.keycloak.constants.AdapterConstants;
|
|
||||||
import org.keycloak.representations.AccessToken;
|
|
||||||
import org.keycloak.representations.RefreshToken;
|
|
||||||
import org.keycloak.util.Time;
|
|
||||||
|
|
||||||
import javax.servlet.ServletException;
|
|
||||||
import javax.servlet.http.HttpServlet;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.StringWriter;
|
|
||||||
import java.io.Writer;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
|
||||||
*/
|
|
||||||
public class PerfAppServlet extends HttpServlet {
|
|
||||||
|
|
||||||
public static final String BASE_URL_INIT_PARAM = "baseUrl";
|
|
||||||
|
|
||||||
private Template indexTemplate;
|
|
||||||
private OAuthClient oauthClient;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void init() throws ServletException {
|
|
||||||
try {
|
|
||||||
Configuration cfg = new Configuration();
|
|
||||||
cfg.setTemplateLoader(new ClassTemplateLoader(getClass(), "/"));
|
|
||||||
indexTemplate = cfg.getTemplate("perf-app-resources/index.ftl");
|
|
||||||
|
|
||||||
String baseUrl = getInitParameter(BASE_URL_INIT_PARAM);
|
|
||||||
oauthClient = new OAuthClient(baseUrl);
|
|
||||||
} catch (IOException ioe) {
|
|
||||||
throw new ServletException(ioe);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
|
||||||
resp.setContentType("text/html");
|
|
||||||
String action = req.getParameter("action");
|
|
||||||
String actionDone = null;
|
|
||||||
|
|
||||||
if (action != null) {
|
|
||||||
if (action.equals("code")) {
|
|
||||||
keycloakLoginRedirect(req, resp);
|
|
||||||
return;
|
|
||||||
} else if (action.equals("exchangeCode")) {
|
|
||||||
exchangeCodeForToken(req, resp);
|
|
||||||
actionDone = "Token retrieved";
|
|
||||||
} else if (action.equals("refresh")) {
|
|
||||||
refreshToken(req, resp);
|
|
||||||
actionDone = "Token refreshed";
|
|
||||||
} else if (action.equals("logout")) {
|
|
||||||
logoutRedirect(req, resp);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String code = req.getParameter("code");
|
|
||||||
if (code != null) {
|
|
||||||
req.getSession().setAttribute("code", code);
|
|
||||||
actionDone = "Code retrieved";
|
|
||||||
}
|
|
||||||
|
|
||||||
String freemarkerRedirect = freemarkerRedirect(req, resp, actionDone);
|
|
||||||
resp.getWriter().println(freemarkerRedirect);
|
|
||||||
resp.getWriter().flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
|
||||||
if (req.getRequestURI().endsWith(AdapterConstants.K_LOGOUT)) {
|
|
||||||
// System.out.println("Logout callback triggered");
|
|
||||||
resp.setStatus(204);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void keycloakLoginRedirect(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
|
||||||
String loginUrl = oauthClient.getLoginFormUrl();
|
|
||||||
resp.sendRedirect(loginUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void exchangeCodeForToken(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
|
||||||
String code = (String)req.getSession().getAttribute("code");
|
|
||||||
OAuthClient.AccessTokenResponse atResponse = oauthClient.doAccessTokenRequest(code, "password");
|
|
||||||
|
|
||||||
updateTokensInSession(req, atResponse);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void refreshToken(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
|
||||||
String refreshToken = (String)req.getSession().getAttribute("refreshToken");
|
|
||||||
OAuthClient.AccessTokenResponse atResponse = oauthClient.doRefreshTokenRequest(refreshToken, "password");
|
|
||||||
|
|
||||||
updateTokensInSession(req, atResponse);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateTokensInSession(HttpServletRequest req, OAuthClient.AccessTokenResponse atResponse) {
|
|
||||||
String accessToken = atResponse.getAccessToken();
|
|
||||||
String refreshToken = atResponse.getRefreshToken();
|
|
||||||
AccessToken accessTokenParsed = oauthClient.verifyToken(accessToken);
|
|
||||||
RefreshToken refreshTokenParsed = oauthClient.verifyRefreshToken(refreshToken);
|
|
||||||
req.getSession().setAttribute("accessToken", accessToken);
|
|
||||||
req.getSession().setAttribute("refreshToken", refreshToken);
|
|
||||||
req.getSession().setAttribute("accessTokenParsed", accessTokenParsed);
|
|
||||||
req.getSession().setAttribute("refreshTokenParsed", refreshTokenParsed);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void logoutRedirect(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
|
||||||
// Invalidate http session
|
|
||||||
req.getSession(false).invalidate();
|
|
||||||
|
|
||||||
String logoutURL = oauthClient.getLogoutUrl(oauthClient.getRedirectUri(), null);
|
|
||||||
resp.sendRedirect(logoutURL);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String freemarkerRedirect(HttpServletRequest req, HttpServletResponse resp, String actionDone) throws ServletException, IOException {
|
|
||||||
AccessToken accessTokenParsed = (AccessToken)req.getSession().getAttribute("accessTokenParsed");
|
|
||||||
RefreshToken refreshTokenParsed = (RefreshToken)req.getSession().getAttribute("refreshTokenParsed");
|
|
||||||
|
|
||||||
Map<String, Object> attributes = new HashMap<String, Object>();
|
|
||||||
attributes.put("requestURI", req.getRequestURI());
|
|
||||||
attributes.put("code", req.getSession().getAttribute("code"));
|
|
||||||
attributes.put("accessToken", req.getSession().getAttribute("accessToken"));
|
|
||||||
attributes.put("refreshToken", req.getSession().getAttribute("refreshToken"));
|
|
||||||
attributes.put("accessTokenParsed", accessTokenParsed);
|
|
||||||
attributes.put("refreshTokenParsed", refreshTokenParsed);
|
|
||||||
attributes.put("actionDone", actionDone);
|
|
||||||
|
|
||||||
if (accessTokenParsed != null) {
|
|
||||||
attributes.put("accessTokenExpiration", Time.toDate(accessTokenParsed.getExpiration()).toString());
|
|
||||||
}
|
|
||||||
if (refreshTokenParsed != null) {
|
|
||||||
attributes.put("refreshTokenExpiration", Time.toDate(refreshTokenParsed.getExpiration()).toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
Writer out = new StringWriter();
|
|
||||||
indexTemplate.process(attributes, out);
|
|
||||||
return out.toString();
|
|
||||||
} catch (TemplateException te) {
|
|
||||||
throw new ServletException(te);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,51 +0,0 @@
|
||||||
<!doctype html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>PerfTest</title>
|
|
||||||
<script>
|
|
||||||
function updateElementWithToken(tokenStr, elementId) {
|
|
||||||
if (tokenStr && tokenStr != "") {
|
|
||||||
var tokenParsed = JSON.stringify(JSON.parse(decodeURIComponent(escape(window.atob( tokenStr.split('.')[1] )))), null, " ");
|
|
||||||
document.getElementById(elementId).innerHTML = tokenParsed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<p><a href="${requestURI}?action=code">Login and get code</a> | <a href="${requestURI}?action=exchangeCode">Exchange code</a> | <a
|
|
||||||
href="${requestURI}?action=refresh">Refresh token</a> | <a href="${requestURI}?action=logout">Logout</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<#if code??>
|
|
||||||
<b>Code Available</b><br>
|
|
||||||
Code=${code} <br>
|
|
||||||
<hr />
|
|
||||||
</#if>
|
|
||||||
|
|
||||||
<#if accessToken??>
|
|
||||||
<b>Access Token Available</b><br>
|
|
||||||
AccessToken=${accessToken} <br>
|
|
||||||
Username=${accessTokenParsed.preferredUsername} <br>
|
|
||||||
SessionState=${accessTokenParsed.sessionState} <br>
|
|
||||||
Expiration=${accessTokenExpiration} <br>
|
|
||||||
<hr />
|
|
||||||
</#if>
|
|
||||||
|
|
||||||
<#if refreshToken??>
|
|
||||||
<b>Refresh token available</b><br>
|
|
||||||
RefreshToken=${refreshToken} <br>
|
|
||||||
Expiration=${refreshTokenExpiration} <br>
|
|
||||||
<hr />
|
|
||||||
</#if>
|
|
||||||
|
|
||||||
<#if actionDone??>
|
|
||||||
RequestAction=${actionDone}
|
|
||||||
<hr />
|
|
||||||
</#if>
|
|
||||||
|
|
||||||
</p>
|
|
||||||
<br><br>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,112 +0,0 @@
|
||||||
{
|
|
||||||
"id": "perf-realm",
|
|
||||||
"realm": "perf-realm",
|
|
||||||
"enabled": true,
|
|
||||||
"sslRequired": "external",
|
|
||||||
"registrationAllowed": true,
|
|
||||||
"resetPasswordAllowed": 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" ],
|
|
||||||
"smtpServer": {
|
|
||||||
"from": "auto@keycloak.org",
|
|
||||||
"host": "localhost",
|
|
||||||
"port":"3025"
|
|
||||||
},
|
|
||||||
"users" : [
|
|
||||||
{
|
|
||||||
"username" : "test@localhost",
|
|
||||||
"enabled": true,
|
|
||||||
"email" : "test@localhost",
|
|
||||||
"credentials" : [
|
|
||||||
{ "type" : "password",
|
|
||||||
"value" : "password" }
|
|
||||||
],
|
|
||||||
"realmRoles": [ "user" ],
|
|
||||||
"applicationRoles": {
|
|
||||||
"perf-app": [ "customer-user" ],
|
|
||||||
"account": [ "view-account", "manage-account" ]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"oauthClients" : [
|
|
||||||
{
|
|
||||||
"name" : "third-party",
|
|
||||||
"enabled": true,
|
|
||||||
"redirectUris": [
|
|
||||||
"/app/*"
|
|
||||||
],
|
|
||||||
"secret": "password"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"scopeMappings": [
|
|
||||||
{
|
|
||||||
"client": "third-party",
|
|
||||||
"roles": ["user"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"client": "perf-app",
|
|
||||||
"roles": [ "role-0", "role-1", "role-2", "role-3", "role-4" ]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"applications": [
|
|
||||||
{
|
|
||||||
"name": "perf-app",
|
|
||||||
"enabled": true,
|
|
||||||
"baseUrl": "/perf-app",
|
|
||||||
"redirectUris": [
|
|
||||||
"/perf-app/*"
|
|
||||||
],
|
|
||||||
"adminUrl": "/perf-app/perf-servlet",
|
|
||||||
"secret": "password"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"roles" : {
|
|
||||||
"realm" : [
|
|
||||||
{
|
|
||||||
"name": "role-0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "role-1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "role-2"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "role-3"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "role-4"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"application" : {
|
|
||||||
"perf-app" : [
|
|
||||||
{
|
|
||||||
"name": "approle-0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "approle-1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "approle-2"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "approle-3"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "approle-4"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
"applicationScopeMappings": {
|
|
||||||
"perf-app": [
|
|
||||||
{
|
|
||||||
"client": "third-party",
|
|
||||||
"roles": ["customer-user"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
#Thu Mar 07 18:46:04 BRT 2013
|
|
||||||
not_in_menu=HTML Parameter Mask,HTTP User Parameter Modifier
|
|
||||||
xml.parser=org.apache.xerces.parsers.SAXParser
|
|
||||||
cookies=cookies
|
|
||||||
wmlParser.className=org.apache.jmeter.protocol.http.parser.RegexpHTMLParser
|
|
||||||
HTTPResponse.parsers=htmlParser wmlParser
|
|
||||||
remote_hosts=127.0.0.1
|
|
||||||
system.properties=system.properties
|
|
||||||
beanshell.server.file=../extras/startup.bsh
|
|
||||||
log_level.jmeter.junit=DEBUG
|
|
||||||
sampleresult.timestamp.start=true
|
|
||||||
jmeter.laf.mac=System
|
|
||||||
log_level.jorphan=INFO
|
|
||||||
classfinder.functions.contain=.functions.
|
|
||||||
user.properties=user.properties
|
|
||||||
wmlParser.types=text/vnd.wap.wml
|
|
||||||
log_level.jmeter=DEBUG
|
|
||||||
classfinder.functions.notContain=.gui.
|
|
||||||
htmlParser.types=text/html application/xhtml+xml application/xml text/xml
|
|
||||||
upgrade_properties=/bin/upgrade.properties
|
|
|
@ -1,413 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<jmeterTestPlan version="1.2" properties="2.5" jmeter="2.10 r1533061">
|
|
||||||
<hashTree>
|
|
||||||
<TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="Test Plan" enabled="true">
|
|
||||||
<stringProp name="TestPlan.comments"></stringProp>
|
|
||||||
<boolProp name="TestPlan.functional_mode">false</boolProp>
|
|
||||||
<boolProp name="TestPlan.serialize_threadgroups">false</boolProp>
|
|
||||||
<elementProp name="TestPlan.user_defined_variables" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
|
|
||||||
<collectionProp name="Arguments.arguments"/>
|
|
||||||
</elementProp>
|
|
||||||
<stringProp name="TestPlan.user_define_classpath"></stringProp>
|
|
||||||
</TestPlan>
|
|
||||||
<hashTree>
|
|
||||||
<Arguments guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
|
|
||||||
<collectionProp name="Arguments.arguments">
|
|
||||||
<elementProp name="host" elementType="Argument">
|
|
||||||
<stringProp name="Argument.name">host</stringProp>
|
|
||||||
<stringProp name="Argument.value">${__P(host, localhost)}</stringProp>
|
|
||||||
<stringProp name="Argument.metadata">=</stringProp>
|
|
||||||
</elementProp>
|
|
||||||
<elementProp name="port" elementType="Argument">
|
|
||||||
<stringProp name="Argument.name">port</stringProp>
|
|
||||||
<stringProp name="Argument.value">${__P(port, 8081)}</stringProp>
|
|
||||||
<stringProp name="Argument.metadata">=</stringProp>
|
|
||||||
</elementProp>
|
|
||||||
<elementProp name="userPrefix" elementType="Argument">
|
|
||||||
<stringProp name="Argument.name">userPrefix</stringProp>
|
|
||||||
<stringProp name="Argument.value">${__P(userPrefix, user)}</stringProp>
|
|
||||||
<stringProp name="Argument.metadata">=</stringProp>
|
|
||||||
</elementProp>
|
|
||||||
<elementProp name="concurrentUsers" elementType="Argument">
|
|
||||||
<stringProp name="Argument.name">concurrentUsers</stringProp>
|
|
||||||
<stringProp name="Argument.value">${__P(concurrentUsers, 50)}</stringProp>
|
|
||||||
<stringProp name="Argument.metadata">=</stringProp>
|
|
||||||
</elementProp>
|
|
||||||
<elementProp name="iterationsPerUser" elementType="Argument">
|
|
||||||
<stringProp name="Argument.name">iterationsPerUser</stringProp>
|
|
||||||
<stringProp name="Argument.value">${__P(iterationsPerUser, 50)}</stringProp>
|
|
||||||
<stringProp name="Argument.metadata">=</stringProp>
|
|
||||||
</elementProp>
|
|
||||||
<elementProp name="refreshTokenRequestsPerIteration" elementType="Argument">
|
|
||||||
<stringProp name="Argument.name">refreshTokenRequestsPerIteration</stringProp>
|
|
||||||
<stringProp name="Argument.value">${__P(refreshTokenRequestsPerIteration, 2)}</stringProp>
|
|
||||||
<stringProp name="Argument.metadata">=</stringProp>
|
|
||||||
</elementProp>
|
|
||||||
</collectionProp>
|
|
||||||
</Arguments>
|
|
||||||
<hashTree/>
|
|
||||||
<CookieManager guiclass="CookiePanel" testclass="CookieManager" testname="HTTP Cookie Manager" enabled="true">
|
|
||||||
<collectionProp name="CookieManager.cookies"/>
|
|
||||||
<boolProp name="CookieManager.clearEachIteration">true</boolProp>
|
|
||||||
<stringProp name="CookieManager.implementation">org.apache.jmeter.protocol.http.control.HC4CookieHandler</stringProp>
|
|
||||||
</CookieManager>
|
|
||||||
<hashTree/>
|
|
||||||
<ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Keycloak test" enabled="true">
|
|
||||||
<stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
|
|
||||||
<elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true">
|
|
||||||
<boolProp name="LoopController.continue_forever">false</boolProp>
|
|
||||||
<stringProp name="LoopController.loops">${iterationsPerUser}</stringProp>
|
|
||||||
</elementProp>
|
|
||||||
<stringProp name="ThreadGroup.num_threads">${concurrentUsers}</stringProp>
|
|
||||||
<stringProp name="ThreadGroup.ramp_time">${__javaScript(${concurrentUsers} / 5)}</stringProp>
|
|
||||||
<longProp name="ThreadGroup.start_time">1403769177000</longProp>
|
|
||||||
<longProp name="ThreadGroup.end_time">1403769177000</longProp>
|
|
||||||
<boolProp name="ThreadGroup.scheduler">false</boolProp>
|
|
||||||
<stringProp name="ThreadGroup.duration"></stringProp>
|
|
||||||
<stringProp name="ThreadGroup.delay"></stringProp>
|
|
||||||
</ThreadGroup>
|
|
||||||
<hashTree>
|
|
||||||
<UserParameters guiclass="UserParametersGui" testclass="UserParameters" testname="User Parameters" enabled="true">
|
|
||||||
<collectionProp name="UserParameters.names">
|
|
||||||
<stringProp name="-265713450">username</stringProp>
|
|
||||||
</collectionProp>
|
|
||||||
<collectionProp name="UserParameters.thread_values">
|
|
||||||
<collectionProp name="-1547483968">
|
|
||||||
<stringProp name="1649970578">user-${__javaScript(${__threadNum}-1)}</stringProp>
|
|
||||||
</collectionProp>
|
|
||||||
</collectionProp>
|
|
||||||
<boolProp name="UserParameters.per_iteration">false</boolProp>
|
|
||||||
</UserParameters>
|
|
||||||
<hashTree/>
|
|
||||||
<ConfigTestElement guiclass="HttpDefaultsGui" testclass="ConfigTestElement" testname="HTTP Request Defaults" enabled="true">
|
|
||||||
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
|
|
||||||
<collectionProp name="Arguments.arguments"/>
|
|
||||||
</elementProp>
|
|
||||||
<stringProp name="HTTPSampler.domain">${host}</stringProp>
|
|
||||||
<stringProp name="HTTPSampler.port">${port}</stringProp>
|
|
||||||
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
|
|
||||||
<stringProp name="HTTPSampler.response_timeout"></stringProp>
|
|
||||||
<stringProp name="HTTPSampler.protocol"></stringProp>
|
|
||||||
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
|
|
||||||
<stringProp name="HTTPSampler.path"></stringProp>
|
|
||||||
<stringProp name="HTTPSampler.concurrentPool">4</stringProp>
|
|
||||||
</ConfigTestElement>
|
|
||||||
<hashTree/>
|
|
||||||
<ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Check 200 response" enabled="true">
|
|
||||||
<collectionProp name="Asserion.test_strings">
|
|
||||||
<stringProp name="49586">200</stringProp>
|
|
||||||
</collectionProp>
|
|
||||||
<stringProp name="TestPlan.comments">Check that status is 200 in each HTTP response</stringProp>
|
|
||||||
<stringProp name="Assertion.test_field">Assertion.response_code</stringProp>
|
|
||||||
<boolProp name="Assertion.assume_success">false</boolProp>
|
|
||||||
<intProp name="Assertion.test_type">8</intProp>
|
|
||||||
</ResponseAssertion>
|
|
||||||
<hashTree/>
|
|
||||||
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Login request" enabled="true">
|
|
||||||
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
|
|
||||||
<collectionProp name="Arguments.arguments">
|
|
||||||
<elementProp name="action" elementType="HTTPArgument">
|
|
||||||
<boolProp name="HTTPArgument.always_encode">false</boolProp>
|
|
||||||
<stringProp name="Argument.value">code</stringProp>
|
|
||||||
<stringProp name="Argument.metadata">=</stringProp>
|
|
||||||
<boolProp name="HTTPArgument.use_equals">true</boolProp>
|
|
||||||
<stringProp name="Argument.name">action</stringProp>
|
|
||||||
</elementProp>
|
|
||||||
</collectionProp>
|
|
||||||
</elementProp>
|
|
||||||
<stringProp name="HTTPSampler.domain"></stringProp>
|
|
||||||
<stringProp name="HTTPSampler.port"></stringProp>
|
|
||||||
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
|
|
||||||
<stringProp name="HTTPSampler.response_timeout"></stringProp>
|
|
||||||
<stringProp name="HTTPSampler.protocol"></stringProp>
|
|
||||||
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
|
|
||||||
<stringProp name="HTTPSampler.path">/perf-app/perf-servlet</stringProp>
|
|
||||||
<stringProp name="HTTPSampler.method">GET</stringProp>
|
|
||||||
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
|
|
||||||
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
|
|
||||||
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
|
|
||||||
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
|
|
||||||
<boolProp name="HTTPSampler.monitor">false</boolProp>
|
|
||||||
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
|
|
||||||
</HTTPSamplerProxy>
|
|
||||||
<hashTree>
|
|
||||||
<ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Assert KC login page" enabled="true">
|
|
||||||
<collectionProp name="Asserion.test_strings">
|
|
||||||
<stringProp name="-2012386511">Log in to perf-realm</stringProp>
|
|
||||||
</collectionProp>
|
|
||||||
<stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
|
|
||||||
<boolProp name="Assertion.assume_success">false</boolProp>
|
|
||||||
<intProp name="Assertion.test_type">2</intProp>
|
|
||||||
</ResponseAssertion>
|
|
||||||
<hashTree/>
|
|
||||||
</hashTree>
|
|
||||||
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Confirm Login request" enabled="true">
|
|
||||||
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
|
|
||||||
<collectionProp name="Arguments.arguments">
|
|
||||||
<elementProp name="username" elementType="HTTPArgument">
|
|
||||||
<boolProp name="HTTPArgument.always_encode">false</boolProp>
|
|
||||||
<stringProp name="Argument.value">${username}</stringProp>
|
|
||||||
<stringProp name="Argument.metadata">=</stringProp>
|
|
||||||
<boolProp name="HTTPArgument.use_equals">true</boolProp>
|
|
||||||
<stringProp name="Argument.name">username</stringProp>
|
|
||||||
</elementProp>
|
|
||||||
<elementProp name="password" elementType="HTTPArgument">
|
|
||||||
<boolProp name="HTTPArgument.always_encode">false</boolProp>
|
|
||||||
<stringProp name="Argument.value">password</stringProp>
|
|
||||||
<stringProp name="Argument.metadata">=</stringProp>
|
|
||||||
<boolProp name="HTTPArgument.use_equals">true</boolProp>
|
|
||||||
<stringProp name="Argument.name">password</stringProp>
|
|
||||||
</elementProp>
|
|
||||||
</collectionProp>
|
|
||||||
</elementProp>
|
|
||||||
<stringProp name="HTTPSampler.domain"></stringProp>
|
|
||||||
<stringProp name="HTTPSampler.port"></stringProp>
|
|
||||||
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
|
|
||||||
<stringProp name="HTTPSampler.response_timeout"></stringProp>
|
|
||||||
<stringProp name="HTTPSampler.protocol"></stringProp>
|
|
||||||
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
|
|
||||||
<stringProp name="HTTPSampler.path">/auth/realms/perf-realm/tokens/auth/request/login?response_type=code&redirect_uri=http%3A%2F%2F${host}%3A${port}%2Fperf-app%2Fperf-servlet&state=123&client_id=perf-app</stringProp>
|
|
||||||
<stringProp name="HTTPSampler.method">POST</stringProp>
|
|
||||||
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
|
|
||||||
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
|
|
||||||
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
|
|
||||||
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
|
|
||||||
<boolProp name="HTTPSampler.monitor">false</boolProp>
|
|
||||||
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
|
|
||||||
</HTTPSamplerProxy>
|
|
||||||
<hashTree>
|
|
||||||
<ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Assert code retrieved" enabled="true">
|
|
||||||
<collectionProp name="Asserion.test_strings">
|
|
||||||
<stringProp name="726495917">RequestAction=Code retrieved</stringProp>
|
|
||||||
</collectionProp>
|
|
||||||
<stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
|
|
||||||
<boolProp name="Assertion.assume_success">false</boolProp>
|
|
||||||
<intProp name="Assertion.test_type">2</intProp>
|
|
||||||
</ResponseAssertion>
|
|
||||||
<hashTree/>
|
|
||||||
<RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Regular Expression Extractor" enabled="true">
|
|
||||||
<stringProp name="RegexExtractor.useHeaders">false</stringProp>
|
|
||||||
<stringProp name="RegexExtractor.refname">code</stringProp>
|
|
||||||
<stringProp name="RegexExtractor.regex">Code=([\w\.]+)</stringProp>
|
|
||||||
<stringProp name="RegexExtractor.template">$1$</stringProp>
|
|
||||||
<stringProp name="RegexExtractor.default"></stringProp>
|
|
||||||
<stringProp name="RegexExtractor.match_number">1</stringProp>
|
|
||||||
</RegexExtractor>
|
|
||||||
<hashTree/>
|
|
||||||
</hashTree>
|
|
||||||
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Exchange code request" enabled="true">
|
|
||||||
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
|
|
||||||
<collectionProp name="Arguments.arguments"/>
|
|
||||||
</elementProp>
|
|
||||||
<stringProp name="HTTPSampler.domain"></stringProp>
|
|
||||||
<stringProp name="HTTPSampler.port"></stringProp>
|
|
||||||
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
|
|
||||||
<stringProp name="HTTPSampler.response_timeout"></stringProp>
|
|
||||||
<stringProp name="HTTPSampler.protocol"></stringProp>
|
|
||||||
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
|
|
||||||
<stringProp name="HTTPSampler.path">/perf-app/perf-servlet?action=exchangeCode</stringProp>
|
|
||||||
<stringProp name="HTTPSampler.method">GET</stringProp>
|
|
||||||
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
|
|
||||||
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
|
|
||||||
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
|
|
||||||
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
|
|
||||||
<boolProp name="HTTPSampler.monitor">false</boolProp>
|
|
||||||
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
|
|
||||||
</HTTPSamplerProxy>
|
|
||||||
<hashTree>
|
|
||||||
<ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Assert token retrieved" enabled="true">
|
|
||||||
<collectionProp name="Asserion.test_strings">
|
|
||||||
<stringProp name="-1001303095">RequestAction=Token retrieved</stringProp>
|
|
||||||
<stringProp name="1027121929">Username=${username}</stringProp>
|
|
||||||
</collectionProp>
|
|
||||||
<stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
|
|
||||||
<boolProp name="Assertion.assume_success">false</boolProp>
|
|
||||||
<intProp name="Assertion.test_type">16</intProp>
|
|
||||||
</ResponseAssertion>
|
|
||||||
<hashTree/>
|
|
||||||
</hashTree>
|
|
||||||
<LoopController guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true">
|
|
||||||
<boolProp name="LoopController.continue_forever">true</boolProp>
|
|
||||||
<stringProp name="LoopController.loops">${refreshTokenRequestsPerIteration}</stringProp>
|
|
||||||
</LoopController>
|
|
||||||
<hashTree>
|
|
||||||
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="RefreshToken request" enabled="true">
|
|
||||||
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
|
|
||||||
<collectionProp name="Arguments.arguments"/>
|
|
||||||
</elementProp>
|
|
||||||
<stringProp name="HTTPSampler.domain"></stringProp>
|
|
||||||
<stringProp name="HTTPSampler.port"></stringProp>
|
|
||||||
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
|
|
||||||
<stringProp name="HTTPSampler.response_timeout"></stringProp>
|
|
||||||
<stringProp name="HTTPSampler.protocol"></stringProp>
|
|
||||||
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
|
|
||||||
<stringProp name="HTTPSampler.path">/perf-app/perf-servlet?action=refresh</stringProp>
|
|
||||||
<stringProp name="HTTPSampler.method">GET</stringProp>
|
|
||||||
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
|
|
||||||
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
|
|
||||||
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
|
|
||||||
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
|
|
||||||
<boolProp name="HTTPSampler.monitor">false</boolProp>
|
|
||||||
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
|
|
||||||
</HTTPSamplerProxy>
|
|
||||||
<hashTree>
|
|
||||||
<ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Assert token refreshed" enabled="true">
|
|
||||||
<collectionProp name="Asserion.test_strings">
|
|
||||||
<stringProp name="-544743205">RequestAction=Token refreshed</stringProp>
|
|
||||||
</collectionProp>
|
|
||||||
<stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
|
|
||||||
<boolProp name="Assertion.assume_success">false</boolProp>
|
|
||||||
<intProp name="Assertion.test_type">16</intProp>
|
|
||||||
</ResponseAssertion>
|
|
||||||
<hashTree/>
|
|
||||||
</hashTree>
|
|
||||||
</hashTree>
|
|
||||||
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Logout request" enabled="true">
|
|
||||||
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
|
|
||||||
<collectionProp name="Arguments.arguments"/>
|
|
||||||
</elementProp>
|
|
||||||
<stringProp name="HTTPSampler.domain"></stringProp>
|
|
||||||
<stringProp name="HTTPSampler.port"></stringProp>
|
|
||||||
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
|
|
||||||
<stringProp name="HTTPSampler.response_timeout"></stringProp>
|
|
||||||
<stringProp name="HTTPSampler.protocol"></stringProp>
|
|
||||||
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
|
|
||||||
<stringProp name="HTTPSampler.path">/perf-app/perf-servlet?action=logout</stringProp>
|
|
||||||
<stringProp name="HTTPSampler.method">GET</stringProp>
|
|
||||||
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
|
|
||||||
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
|
|
||||||
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
|
|
||||||
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
|
|
||||||
<boolProp name="HTTPSampler.monitor">false</boolProp>
|
|
||||||
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
|
|
||||||
</HTTPSamplerProxy>
|
|
||||||
<hashTree>
|
|
||||||
<ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Assert logged out" enabled="true">
|
|
||||||
<collectionProp name="Asserion.test_strings">
|
|
||||||
<stringProp name="275024728">RequestAction=</stringProp>
|
|
||||||
<stringProp name="-1938181625">Username=</stringProp>
|
|
||||||
</collectionProp>
|
|
||||||
<stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
|
|
||||||
<boolProp name="Assertion.assume_success">false</boolProp>
|
|
||||||
<intProp name="Assertion.test_type">20</intProp>
|
|
||||||
</ResponseAssertion>
|
|
||||||
<hashTree/>
|
|
||||||
</hashTree>
|
|
||||||
</hashTree>
|
|
||||||
<ResultCollector guiclass="StatVisualizer" testclass="ResultCollector" testname="Aggregate Report" enabled="true">
|
|
||||||
<boolProp name="ResultCollector.error_logging">false</boolProp>
|
|
||||||
<objProp>
|
|
||||||
<value class="SampleSaveConfiguration">
|
|
||||||
<time>true</time>
|
|
||||||
<latency>true</latency>
|
|
||||||
<timestamp>true</timestamp>
|
|
||||||
<success>true</success>
|
|
||||||
<label>true</label>
|
|
||||||
<code>true</code>
|
|
||||||
<message>true</message>
|
|
||||||
<threadName>true</threadName>
|
|
||||||
<dataType>true</dataType>
|
|
||||||
<encoding>false</encoding>
|
|
||||||
<assertions>true</assertions>
|
|
||||||
<subresults>true</subresults>
|
|
||||||
<responseData>false</responseData>
|
|
||||||
<samplerData>false</samplerData>
|
|
||||||
<xml>false</xml>
|
|
||||||
<fieldNames>false</fieldNames>
|
|
||||||
<responseHeaders>false</responseHeaders>
|
|
||||||
<requestHeaders>false</requestHeaders>
|
|
||||||
<responseDataOnError>false</responseDataOnError>
|
|
||||||
<saveAssertionResultsFailureMessage>false</saveAssertionResultsFailureMessage>
|
|
||||||
<assertionsResultsToSave>0</assertionsResultsToSave>
|
|
||||||
<bytes>true</bytes>
|
|
||||||
</value>
|
|
||||||
</objProp>
|
|
||||||
<objProp>
|
|
||||||
<value class="SampleSaveConfiguration">
|
|
||||||
<time>true</time>
|
|
||||||
<latency>true</latency>
|
|
||||||
<timestamp>true</timestamp>
|
|
||||||
<success>true</success>
|
|
||||||
<label>true</label>
|
|
||||||
<code>true</code>
|
|
||||||
<message>true</message>
|
|
||||||
<threadName>true</threadName>
|
|
||||||
<dataType>true</dataType>
|
|
||||||
<encoding>false</encoding>
|
|
||||||
<assertions>true</assertions>
|
|
||||||
<subresults>true</subresults>
|
|
||||||
<responseData>false</responseData>
|
|
||||||
<samplerData>false</samplerData>
|
|
||||||
<xml>false</xml>
|
|
||||||
<fieldNames>false</fieldNames>
|
|
||||||
<responseHeaders>false</responseHeaders>
|
|
||||||
<requestHeaders>false</requestHeaders>
|
|
||||||
<responseDataOnError>false</responseDataOnError>
|
|
||||||
<saveAssertionResultsFailureMessage>false</saveAssertionResultsFailureMessage>
|
|
||||||
<assertionsResultsToSave>0</assertionsResultsToSave>
|
|
||||||
<bytes>true</bytes>
|
|
||||||
</value>
|
|
||||||
</objProp>
|
|
||||||
<stringProp name="filename"></stringProp>
|
|
||||||
</ResultCollector>
|
|
||||||
<hashTree/>
|
|
||||||
<ResultCollector guiclass="ViewResultsFullVisualizer" testclass="ResultCollector" testname="View Results Tree" enabled="true">
|
|
||||||
<boolProp name="ResultCollector.error_logging">false</boolProp>
|
|
||||||
<objProp>
|
|
||||||
<value class="SampleSaveConfiguration">
|
|
||||||
<time>true</time>
|
|
||||||
<latency>true</latency>
|
|
||||||
<timestamp>true</timestamp>
|
|
||||||
<success>true</success>
|
|
||||||
<label>true</label>
|
|
||||||
<code>true</code>
|
|
||||||
<message>true</message>
|
|
||||||
<threadName>true</threadName>
|
|
||||||
<dataType>true</dataType>
|
|
||||||
<encoding>false</encoding>
|
|
||||||
<assertions>true</assertions>
|
|
||||||
<subresults>true</subresults>
|
|
||||||
<responseData>false</responseData>
|
|
||||||
<samplerData>false</samplerData>
|
|
||||||
<xml>false</xml>
|
|
||||||
<fieldNames>false</fieldNames>
|
|
||||||
<responseHeaders>false</responseHeaders>
|
|
||||||
<requestHeaders>false</requestHeaders>
|
|
||||||
<responseDataOnError>false</responseDataOnError>
|
|
||||||
<saveAssertionResultsFailureMessage>false</saveAssertionResultsFailureMessage>
|
|
||||||
<assertionsResultsToSave>0</assertionsResultsToSave>
|
|
||||||
<bytes>true</bytes>
|
|
||||||
</value>
|
|
||||||
</objProp>
|
|
||||||
<objProp>
|
|
||||||
<value class="SampleSaveConfiguration">
|
|
||||||
<time>true</time>
|
|
||||||
<latency>true</latency>
|
|
||||||
<timestamp>true</timestamp>
|
|
||||||
<success>true</success>
|
|
||||||
<label>true</label>
|
|
||||||
<code>true</code>
|
|
||||||
<message>true</message>
|
|
||||||
<threadName>true</threadName>
|
|
||||||
<dataType>true</dataType>
|
|
||||||
<encoding>false</encoding>
|
|
||||||
<assertions>true</assertions>
|
|
||||||
<subresults>true</subresults>
|
|
||||||
<responseData>false</responseData>
|
|
||||||
<samplerData>false</samplerData>
|
|
||||||
<xml>false</xml>
|
|
||||||
<fieldNames>false</fieldNames>
|
|
||||||
<responseHeaders>false</responseHeaders>
|
|
||||||
<requestHeaders>false</requestHeaders>
|
|
||||||
<responseDataOnError>false</responseDataOnError>
|
|
||||||
<saveAssertionResultsFailureMessage>false</saveAssertionResultsFailureMessage>
|
|
||||||
<assertionsResultsToSave>0</assertionsResultsToSave>
|
|
||||||
<bytes>true</bytes>
|
|
||||||
</value>
|
|
||||||
</objProp>
|
|
||||||
<stringProp name="filename"></stringProp>
|
|
||||||
</ResultCollector>
|
|
||||||
<hashTree/>
|
|
||||||
</hashTree>
|
|
||||||
</hashTree>
|
|
||||||
</jmeterTestPlan>
|
|
|
@ -1,82 +0,0 @@
|
||||||
<?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/maven-v4_0_0.xsd">
|
|
||||||
<parent>
|
|
||||||
<artifactId>keycloak-parent</artifactId>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<version>1.2.0.RC1-SNAPSHOT</version>
|
|
||||||
<relativePath>../../pom.xml</relativePath>
|
|
||||||
</parent>
|
|
||||||
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
<artifactId>keycloak-testsuite-tools</artifactId>
|
|
||||||
<packaging>war</packaging>
|
|
||||||
<name>Keycloak Testsuite Tools</name>
|
|
||||||
<description/>
|
|
||||||
|
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-dependencies-server-all</artifactId>
|
|
||||||
<type>pom</type>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.jboss.resteasy</groupId>
|
|
||||||
<artifactId>jaxrs-api</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.jboss.resteasy</groupId>
|
|
||||||
<artifactId>resteasy-jaxrs</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.jboss.spec.javax.servlet</groupId>
|
|
||||||
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.icegreen</groupId>
|
|
||||||
<artifactId>greenmail</artifactId>
|
|
||||||
<exclusions>
|
|
||||||
<exclusion>
|
|
||||||
<groupId>org.slf4j</groupId>
|
|
||||||
<artifactId>slf4j-api</artifactId>
|
|
||||||
</exclusion>
|
|
||||||
</exclusions>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
|
|
||||||
<build>
|
|
||||||
<finalName>keycloak-tools</finalName>
|
|
||||||
<plugins>
|
|
||||||
<plugin>
|
|
||||||
<artifactId>maven-war-plugin</artifactId>
|
|
||||||
<configuration>
|
|
||||||
<attachClasses>true</attachClasses>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
<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>
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
|
||||||
<artifactId>maven-deploy-plugin</artifactId>
|
|
||||||
<configuration>
|
|
||||||
<skip>true</skip>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
</plugins>
|
|
||||||
</build>
|
|
||||||
</project>
|
|
|
@ -1,21 +0,0 @@
|
||||||
package org.keycloak.test.tools;
|
|
||||||
|
|
||||||
import javax.servlet.ServletContextEvent;
|
|
||||||
import javax.servlet.ServletContextListener;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
|
||||||
*/
|
|
||||||
public class DestroyListener implements ServletContextListener {
|
|
||||||
@Override
|
|
||||||
public void contextInitialized(ServletContextEvent sce) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void contextDestroyed(ServletContextEvent sce) {
|
|
||||||
if (KeycloakTestApplication.mail != null) {
|
|
||||||
KeycloakTestApplication.mail.stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,45 +0,0 @@
|
||||||
package org.keycloak.test.tools;
|
|
||||||
|
|
||||||
import org.jboss.resteasy.core.Dispatcher;
|
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
|
||||||
import org.keycloak.services.resources.KeycloakApplication;
|
|
||||||
|
|
||||||
import javax.servlet.ServletContext;
|
|
||||||
import javax.ws.rs.core.Application;
|
|
||||||
import javax.ws.rs.core.Context;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
|
||||||
*/
|
|
||||||
public class KeycloakTestApplication extends Application {
|
|
||||||
|
|
||||||
protected KeycloakSessionFactory sessionFactory;
|
|
||||||
protected Set<Class<?>> classes = new HashSet<Class<?>>();
|
|
||||||
protected Set<Object> singletons = new HashSet<Object>();
|
|
||||||
|
|
||||||
static Mail mail = new Mail();
|
|
||||||
|
|
||||||
public KeycloakTestApplication(@Context ServletContext context, @Context Dispatcher dispatcher) {
|
|
||||||
KeycloakApplication.loadConfig();
|
|
||||||
|
|
||||||
this.sessionFactory = KeycloakApplication.createSessionFactory();
|
|
||||||
|
|
||||||
context.setAttribute(KeycloakSessionFactory.class.getName(), this.sessionFactory);
|
|
||||||
|
|
||||||
singletons.add(new PerfTools(sessionFactory));
|
|
||||||
singletons.add(mail);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Set<Class<?>> getClasses() {
|
|
||||||
return classes;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Set<Object> getSingletons() {
|
|
||||||
return singletons;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,131 +0,0 @@
|
||||||
package org.keycloak.test.tools;
|
|
||||||
|
|
||||||
import com.icegreen.greenmail.util.GreenMail;
|
|
||||||
import com.icegreen.greenmail.util.ServerSetup;
|
|
||||||
|
|
||||||
import javax.mail.internet.MimeMessage;
|
|
||||||
import javax.ws.rs.GET;
|
|
||||||
import javax.ws.rs.Path;
|
|
||||||
import javax.ws.rs.Produces;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
|
||||||
*/
|
|
||||||
@Path("mail")
|
|
||||||
public class Mail {
|
|
||||||
|
|
||||||
private GreenMail greenMail;
|
|
||||||
|
|
||||||
@GET
|
|
||||||
@Path("status")
|
|
||||||
@Produces("application/json")
|
|
||||||
public synchronized Status status() {
|
|
||||||
return new Status(greenMail != null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@GET
|
|
||||||
@Path("start")
|
|
||||||
@Produces("application/json")
|
|
||||||
public synchronized Status start() {
|
|
||||||
if (greenMail == null) {
|
|
||||||
ServerSetup setup = new ServerSetup(3025, "localhost", "smtp");
|
|
||||||
|
|
||||||
greenMail = new GreenMail(setup);
|
|
||||||
try {
|
|
||||||
greenMail.start();
|
|
||||||
} catch (Throwable t) {
|
|
||||||
greenMail = null;
|
|
||||||
return new Status(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Status(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@GET
|
|
||||||
@Path("stop")
|
|
||||||
@Produces("application/json")
|
|
||||||
public synchronized Status stop() {
|
|
||||||
if (greenMail != null) {
|
|
||||||
greenMail.stop();
|
|
||||||
greenMail = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Status(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@GET
|
|
||||||
@Path("messages")
|
|
||||||
@Produces("application/json")
|
|
||||||
public synchronized List<Message> getMessages() throws Exception {
|
|
||||||
List<Message> messages = new LinkedList<Message>();
|
|
||||||
if (greenMail != null) {
|
|
||||||
for (MimeMessage m : greenMail.getReceivedMessages()) {
|
|
||||||
messages.add(new Message(m));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return messages;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void finalize() throws Throwable {
|
|
||||||
if (greenMail != null) {
|
|
||||||
greenMail.stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Status {
|
|
||||||
|
|
||||||
private boolean started;
|
|
||||||
|
|
||||||
public Status(boolean started) {
|
|
||||||
this.started = started;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isStarted() {
|
|
||||||
return started;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Message {
|
|
||||||
|
|
||||||
private String from;
|
|
||||||
private String to;
|
|
||||||
private String subject;
|
|
||||||
private String body;
|
|
||||||
private Long date;
|
|
||||||
|
|
||||||
public Message(MimeMessage m) throws Exception {
|
|
||||||
from = m.getFrom()[0].toString();
|
|
||||||
to = m.getRecipients(MimeMessage.RecipientType.TO)[0].toString();
|
|
||||||
subject = m.getSubject();
|
|
||||||
body = m.getContent().toString();
|
|
||||||
date = m.getSentDate() != null ? m.getSentDate().getTime() : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getFrom() {
|
|
||||||
return from;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getTo() {
|
|
||||||
return to;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getSubject() {
|
|
||||||
return subject;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getBody() {
|
|
||||||
return body;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Long getDate() {
|
|
||||||
return date;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,288 +0,0 @@
|
||||||
package org.keycloak.test.tools;
|
|
||||||
|
|
||||||
import org.keycloak.exportimport.ExportImportConfig;
|
|
||||||
import org.keycloak.exportimport.ExportImportManager;
|
|
||||||
import org.keycloak.models.KeycloakSession;
|
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
|
||||||
import org.keycloak.models.RealmModel;
|
|
||||||
import org.keycloak.test.tools.jobs.CreateUsersJob;
|
|
||||||
import org.keycloak.test.tools.jobs.DeleteUsersJob;
|
|
||||||
import org.keycloak.test.tools.jobs.UpdateUsersJob;
|
|
||||||
import org.keycloak.test.tools.jobs.UsersJob;
|
|
||||||
import org.keycloak.test.tools.jobs.UsersJobInitializer;
|
|
||||||
|
|
||||||
import javax.ws.rs.GET;
|
|
||||||
import javax.ws.rs.Path;
|
|
||||||
import javax.ws.rs.PathParam;
|
|
||||||
import javax.ws.rs.Produces;
|
|
||||||
import javax.ws.rs.QueryParam;
|
|
||||||
import javax.ws.rs.core.Context;
|
|
||||||
import javax.ws.rs.core.Response;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.CountDownLatch;
|
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
|
||||||
*/
|
|
||||||
@Path("perf")
|
|
||||||
public class PerfTools {
|
|
||||||
|
|
||||||
private ExecutorService executor = Executors.newFixedThreadPool(20);
|
|
||||||
|
|
||||||
private final KeycloakSessionFactory sessionFactory;
|
|
||||||
|
|
||||||
@Context
|
|
||||||
private KeycloakSession session;
|
|
||||||
|
|
||||||
private List<JobRepresentation> jobs = new LinkedList<JobRepresentation>();
|
|
||||||
|
|
||||||
public PerfTools(KeycloakSessionFactory sessionFactory) {
|
|
||||||
this.sessionFactory = sessionFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
@GET
|
|
||||||
@Path("jobs")
|
|
||||||
@Produces("application/json")
|
|
||||||
public List<JobRepresentation> jobs() {
|
|
||||||
return jobs;
|
|
||||||
}
|
|
||||||
|
|
||||||
@GET
|
|
||||||
@Path("delete-jobs")
|
|
||||||
public void deleteJobs() {
|
|
||||||
Iterator<JobRepresentation> itr = jobs.iterator();
|
|
||||||
while(itr.hasNext()) {
|
|
||||||
JobRepresentation j = itr.next();
|
|
||||||
if (j.getError() != null || j.getCount() == j.getTotal()) {
|
|
||||||
itr.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@GET
|
|
||||||
@Path("{realm}/create-users")
|
|
||||||
public void createUsers(@PathParam("realm") String realmName, @QueryParam("count") Integer count,
|
|
||||||
@QueryParam("batch") Integer batch, @QueryParam("start") Integer start, @QueryParam("prefix") String prefix,
|
|
||||||
@QueryParam("async") Boolean async, @QueryParam("roles") String roles) throws InterruptedException {
|
|
||||||
final String[] rolesArray = roles != null ? roles.split(",") : new String[0];
|
|
||||||
|
|
||||||
createAndRunJob(realmName, count, batch, start, prefix, async, "Create users", new UsersJobInitializer() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public UsersJob instantiateJob() {
|
|
||||||
return new CreateUsersJob(rolesArray);
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Same as createUsers, but dynamically compute "start" (Next available user)
|
|
||||||
@GET
|
|
||||||
@Path("{realm}/create-available-users")
|
|
||||||
public void createAvailableUsers(@PathParam("realm") String realmName, @QueryParam("count") Integer count,
|
|
||||||
@QueryParam("batch") Integer batch, @QueryParam("prefix") String prefix,
|
|
||||||
@QueryParam("async") Boolean async, @QueryParam("roles") String roles) throws InterruptedException {
|
|
||||||
int start = getUsersCount(realmName, prefix);
|
|
||||||
createUsers(realmName, count, batch, start, prefix, async, roles);
|
|
||||||
}
|
|
||||||
|
|
||||||
@GET
|
|
||||||
@Path("{realm}/delete-users")
|
|
||||||
public void deleteUsers(@PathParam("realm") String realmName, @QueryParam("count") Integer count,
|
|
||||||
@QueryParam("batch") Integer batch, @QueryParam("start") Integer start, @QueryParam("prefix") String prefix,
|
|
||||||
@QueryParam("async") Boolean async) throws InterruptedException {
|
|
||||||
createAndRunJob(realmName, count, batch, start, prefix, async, "Delete users", new UsersJobInitializer() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public UsersJob instantiateJob() {
|
|
||||||
return new DeleteUsersJob();
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@GET
|
|
||||||
@Path("{realm}/delete-all-users")
|
|
||||||
public void deleteUsers(@PathParam("realm") String realmName, @QueryParam("prefix") String prefix, @QueryParam("async") Boolean async) throws InterruptedException {
|
|
||||||
int count = getUsersCount(realmName, prefix);
|
|
||||||
if (count == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int batch = count / 10;
|
|
||||||
if (batch == 0) {
|
|
||||||
batch = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteUsers(realmName, count, batch, 0, prefix, async);
|
|
||||||
}
|
|
||||||
|
|
||||||
@GET
|
|
||||||
@Path("{realm}/update-users")
|
|
||||||
public void updateUsers(@PathParam("realm") String realmName, @QueryParam("count") Integer count,
|
|
||||||
@QueryParam("batch") Integer batch, @QueryParam("start") Integer start, @QueryParam("prefix") String prefix,
|
|
||||||
@QueryParam("async") Boolean async, @QueryParam("roles") String roles) throws InterruptedException {
|
|
||||||
final String[] rolesArray = roles != null ? roles.split(",") : new String[0];
|
|
||||||
|
|
||||||
createAndRunJob(realmName, count, batch, start, prefix, async, "Update users", new UsersJobInitializer() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public UsersJob instantiateJob() {
|
|
||||||
return new UpdateUsersJob(rolesArray);
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@GET
|
|
||||||
@Path("{realm}/update-all-users")
|
|
||||||
public void updateAllUsers(@PathParam("realm") String realmName, @QueryParam("prefix") String prefix, @QueryParam("async") Boolean async,
|
|
||||||
@QueryParam("roles") String roles) throws InterruptedException {
|
|
||||||
int count = getUsersCount(realmName, prefix);
|
|
||||||
if (count == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int batch = count / 10;
|
|
||||||
if (batch == 0) {
|
|
||||||
batch = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
updateUsers(realmName, count, batch, 0, prefix, async, roles);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@GET
|
|
||||||
@Path("{realm}/get-users-count")
|
|
||||||
public Response getUsersCountReq(@PathParam("realm") String realmName, @QueryParam("prefix") String prefix) {
|
|
||||||
int usersCount = getUsersCount(realmName, prefix);
|
|
||||||
return Response.ok(String.valueOf(usersCount)).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private int getUsersCount(String realmName, String prefix) {
|
|
||||||
RealmModel realm = session.realms().getRealmByName(realmName);
|
|
||||||
|
|
||||||
// TODO: method for count on model
|
|
||||||
if (prefix == null) {
|
|
||||||
return session.users().getUsers(realm).size();
|
|
||||||
} else {
|
|
||||||
return session.users().searchForUser(prefix, realm).size();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void createAndRunJob(String realmName, Integer count, Integer batch, Integer start, String prefix, Boolean async, String jobName, UsersJobInitializer initializer) throws InterruptedException {
|
|
||||||
if (count == null) {
|
|
||||||
count = 1;
|
|
||||||
}
|
|
||||||
if (batch == null) {
|
|
||||||
batch = 1000;
|
|
||||||
}
|
|
||||||
if (start == null) {
|
|
||||||
start = 0;
|
|
||||||
}
|
|
||||||
if (prefix == null) {
|
|
||||||
prefix = String.valueOf(System.currentTimeMillis());
|
|
||||||
}
|
|
||||||
if (async == null) {
|
|
||||||
async = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
int executorsCount = count / batch;
|
|
||||||
if (count % batch > 0) {
|
|
||||||
executorsCount++;
|
|
||||||
}
|
|
||||||
CountDownLatch latch = new CountDownLatch(executorsCount);
|
|
||||||
|
|
||||||
JobRepresentation job = new JobRepresentation(jobName + " " + prefix + "-" + start + " to " + prefix + "-" + (start + count), count);
|
|
||||||
jobs.add(job);
|
|
||||||
|
|
||||||
List<UsersJob> usersJobs = new ArrayList<UsersJob>();
|
|
||||||
for (int s = start; s < (start + count); s += batch) {
|
|
||||||
int c = s + batch <= (start + count) ? batch : (start + count) - s;
|
|
||||||
UsersJob usersJob = initializer.instantiateJob();
|
|
||||||
usersJob.init(job, sessionFactory, realmName, s, c, prefix, latch);
|
|
||||||
usersJobs.add(usersJob);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run executors once all are initialized
|
|
||||||
for (UsersJob usersJob : usersJobs) {
|
|
||||||
executor.submit(usersJob);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!async) {
|
|
||||||
latch.await();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@GET
|
|
||||||
@Path("export")
|
|
||||||
public void export(@QueryParam("dir") String dir) {
|
|
||||||
ExportImportConfig.setAction("export");
|
|
||||||
ExportImportConfig.setProvider("dir");
|
|
||||||
ExportImportConfig.setDir(dir);
|
|
||||||
|
|
||||||
new ExportImportManager().checkExportImport(sessionFactory);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class JobRepresentation {
|
|
||||||
private final String description;
|
|
||||||
private final int total;
|
|
||||||
private AtomicInteger count = new AtomicInteger();
|
|
||||||
private String error;
|
|
||||||
private AtomicLong started = new AtomicLong();
|
|
||||||
private AtomicLong completed = new AtomicLong();
|
|
||||||
|
|
||||||
public JobRepresentation(String description, int total) {
|
|
||||||
this.description = description;
|
|
||||||
this.total = total;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Long getStarted() {
|
|
||||||
long s = started.get();
|
|
||||||
return s != 0 ? s : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Long getCompleted() {
|
|
||||||
long c = completed.get();
|
|
||||||
return c != 0 ? c : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getDescription() {
|
|
||||||
return description;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getTotal() {
|
|
||||||
return total;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getCount() {
|
|
||||||
return count.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void start() {
|
|
||||||
started.compareAndSet(0, System.currentTimeMillis());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void increment() {
|
|
||||||
if (count.incrementAndGet() == total) {
|
|
||||||
completed.set(System.currentTimeMillis());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getError() {
|
|
||||||
return error;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setError(String error) {
|
|
||||||
this.error = error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,48 +0,0 @@
|
||||||
package org.keycloak.test.tools.jobs;
|
|
||||||
|
|
||||||
import org.keycloak.models.ClientModel;
|
|
||||||
import org.keycloak.models.KeycloakSession;
|
|
||||||
import org.keycloak.models.RealmModel;
|
|
||||||
import org.keycloak.models.RoleModel;
|
|
||||||
import org.keycloak.models.UserCredentialModel;
|
|
||||||
import org.keycloak.models.UserModel;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
|
||||||
*/
|
|
||||||
public class CreateUsersJob extends UsersJob {
|
|
||||||
|
|
||||||
private String[] roles;
|
|
||||||
|
|
||||||
public CreateUsersJob(String[] roles) {
|
|
||||||
this.roles = roles;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void before(KeycloakSession session) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void runIteration(KeycloakSession session, RealmModel realm, Map<String, ClientModel> apps, Set<RoleModel> realmRoles, Map<String, Set<RoleModel>> appRoles, int counter) {
|
|
||||||
String username = prefix + "-" + counter;
|
|
||||||
UserModel user = session.users().addUser(realm, username);
|
|
||||||
user.setEnabled(true);
|
|
||||||
user.setFirstName("First");
|
|
||||||
user.setLastName("Last");
|
|
||||||
user.setEmail(username + "@localhost");
|
|
||||||
|
|
||||||
UserCredentialModel password = new UserCredentialModel();
|
|
||||||
password.setType(UserCredentialModel.PASSWORD);
|
|
||||||
password.setValue("password");
|
|
||||||
|
|
||||||
user.updateCredential(password);
|
|
||||||
|
|
||||||
for (String r : roles) {
|
|
||||||
grantRole(user, r, realmRoles, appRoles);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
package org.keycloak.test.tools.jobs;
|
|
||||||
|
|
||||||
import org.keycloak.models.ClientModel;
|
|
||||||
import org.keycloak.models.KeycloakSession;
|
|
||||||
import org.keycloak.models.RealmModel;
|
|
||||||
import org.keycloak.models.RoleModel;
|
|
||||||
import org.keycloak.models.UserModel;
|
|
||||||
import org.keycloak.services.managers.RealmManager;
|
|
||||||
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
|
||||||
*/
|
|
||||||
public class DeleteUsersJob extends UsersJob {
|
|
||||||
|
|
||||||
private Iterator<UserModel> users;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void before(KeycloakSession session) {
|
|
||||||
RealmModel realm = new RealmManager(session).getRealmByName(realmName);
|
|
||||||
|
|
||||||
// TODO: pagination
|
|
||||||
List<UserModel> users = (prefix==null) ? session.users().getUsers(realm) : session.users().searchForUser(prefix, realm);
|
|
||||||
users = users.subList(start, start + count);
|
|
||||||
|
|
||||||
this.users = users.iterator();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void runIteration(KeycloakSession session, RealmModel realm, Map<String, ClientModel> apps, Set<RoleModel> realmRoles, Map<String, Set<RoleModel>> appRoles, int counter) {
|
|
||||||
session.users().removeUser(realm, users.next());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,54 +0,0 @@
|
||||||
package org.keycloak.test.tools.jobs;
|
|
||||||
|
|
||||||
import org.keycloak.models.ClientModel;
|
|
||||||
import org.keycloak.models.KeycloakSession;
|
|
||||||
import org.keycloak.models.RealmModel;
|
|
||||||
import org.keycloak.models.RoleModel;
|
|
||||||
import org.keycloak.models.UserModel;
|
|
||||||
import org.keycloak.services.managers.RealmManager;
|
|
||||||
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
|
||||||
*/
|
|
||||||
public class UpdateUsersJob extends UsersJob {
|
|
||||||
|
|
||||||
private String[] roles;
|
|
||||||
private Iterator<UserModel> users;
|
|
||||||
|
|
||||||
public UpdateUsersJob(String[] roles) {
|
|
||||||
this.roles = roles;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void before(KeycloakSession session) {
|
|
||||||
RealmModel realm = new RealmManager(session).getRealmByName(realmName);
|
|
||||||
|
|
||||||
// TODO: pagination
|
|
||||||
List<UserModel> users = (prefix==null) ? session.users().getUsers(realm) : session.users().searchForUser(prefix, realm);
|
|
||||||
users = users.subList(start, start + count);
|
|
||||||
|
|
||||||
this.users = users.iterator();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void runIteration(KeycloakSession session, RealmModel realm, Map<String, ClientModel> apps, Set<RoleModel> realmRoles, Map<String, Set<RoleModel>> appRoles, int counter) {
|
|
||||||
String username = users.next().getUsername();
|
|
||||||
|
|
||||||
// Remove all role mappings first
|
|
||||||
UserModel user = session.users().getUserByUsername(username, realm);
|
|
||||||
Set<RoleModel> currRoles = user.getRoleMappings();
|
|
||||||
for (RoleModel role : currRoles) {
|
|
||||||
user.deleteRoleMapping(role);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add new roles now
|
|
||||||
for (String r : roles) {
|
|
||||||
grantRole(user, r, realmRoles, appRoles);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,126 +0,0 @@
|
||||||
package org.keycloak.test.tools.jobs;
|
|
||||||
|
|
||||||
import org.keycloak.models.ClientModel;
|
|
||||||
import org.keycloak.models.KeycloakSession;
|
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
|
||||||
import org.keycloak.models.RealmModel;
|
|
||||||
import org.keycloak.models.RoleModel;
|
|
||||||
import org.keycloak.models.UserModel;
|
|
||||||
import org.keycloak.services.managers.RealmManager;
|
|
||||||
import org.keycloak.test.tools.PerfTools;
|
|
||||||
|
|
||||||
import java.io.PrintWriter;
|
|
||||||
import java.io.StringWriter;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.CountDownLatch;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
|
||||||
*/
|
|
||||||
public abstract class UsersJob implements Runnable {
|
|
||||||
|
|
||||||
protected PerfTools.JobRepresentation job;
|
|
||||||
protected KeycloakSessionFactory sessionFactory;
|
|
||||||
protected String realmName;
|
|
||||||
protected int start;
|
|
||||||
protected int count;
|
|
||||||
protected String prefix;
|
|
||||||
protected CountDownLatch latch;
|
|
||||||
|
|
||||||
public void init(PerfTools.JobRepresentation job, KeycloakSessionFactory sessionFactory, String realmName, int start, int count, String prefix, CountDownLatch latch) {
|
|
||||||
this.sessionFactory = sessionFactory;
|
|
||||||
this.realmName = realmName;
|
|
||||||
this.start = start;
|
|
||||||
this.count = count;
|
|
||||||
this.prefix = prefix;
|
|
||||||
this.job = job;
|
|
||||||
this.latch = latch;
|
|
||||||
|
|
||||||
KeycloakSession session = sessionFactory.create();
|
|
||||||
try {
|
|
||||||
session.getTransaction().begin();
|
|
||||||
|
|
||||||
before(session);
|
|
||||||
|
|
||||||
session.getTransaction().commit();
|
|
||||||
} catch (Throwable t) {
|
|
||||||
handleThrowable(t, session);
|
|
||||||
} finally {
|
|
||||||
session.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
job.start();
|
|
||||||
|
|
||||||
KeycloakSession session = sessionFactory.create();
|
|
||||||
try {
|
|
||||||
session.getTransaction().begin();
|
|
||||||
|
|
||||||
RealmModel realm = new RealmManager(session).getRealmByName(realmName);
|
|
||||||
Map<String, ClientModel> apps = realm.getClientNameMap();
|
|
||||||
|
|
||||||
Set<RoleModel> realmRoles = realm.getRoles();
|
|
||||||
Map<String, Set<RoleModel>> appRoles = new HashMap<String, Set<RoleModel>>();
|
|
||||||
for (Map.Entry<String, ClientModel> appEntry : apps.entrySet()) {
|
|
||||||
appRoles.put(appEntry.getKey(), appEntry.getValue().getRoles());
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = start; i < (start + count); i++) {
|
|
||||||
runIteration(session, realm, apps, realmRoles, appRoles, i);
|
|
||||||
job.increment();
|
|
||||||
}
|
|
||||||
|
|
||||||
session.getTransaction().commit();
|
|
||||||
} catch (Throwable t) {
|
|
||||||
handleThrowable(t, session);
|
|
||||||
} finally {
|
|
||||||
latch.countDown();
|
|
||||||
session.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract void before(KeycloakSession keycloakSession);
|
|
||||||
|
|
||||||
protected abstract void runIteration(KeycloakSession session, RealmModel realm, Map<String, ClientModel> apps, Set<RoleModel> realmRoles, Map<String, Set<RoleModel>> appRoles, int counter);
|
|
||||||
|
|
||||||
protected RoleModel findRole(Set<RoleModel> roles, String roleName) {
|
|
||||||
for (RoleModel role : roles) {
|
|
||||||
if (role.getName().equals(roleName)) {
|
|
||||||
return role;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void grantRole(UserModel user, String roleName, Set<RoleModel> realmRoles, Map<String, Set<RoleModel>> appRoles) {
|
|
||||||
if (roleName.indexOf(':') == -1) {
|
|
||||||
// We expect "realmRoleName"
|
|
||||||
RoleModel realmRole = findRole(realmRoles, roleName);
|
|
||||||
user.grantRole(realmRole);
|
|
||||||
} else {
|
|
||||||
// We expect "appName:appRoleName"
|
|
||||||
String[] parts = roleName.split(":");
|
|
||||||
Set<RoleModel> currentAppRoles = appRoles.get(parts[0]);
|
|
||||||
if (currentAppRoles == null) {
|
|
||||||
throw new IllegalStateException("Application '" + parts[0] + "' not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
RoleModel appRole = findRole(currentAppRoles, parts[1]);
|
|
||||||
user.grantRole(appRole);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleThrowable(Throwable t, KeycloakSession session) {
|
|
||||||
StringWriter sw = new StringWriter();
|
|
||||||
t.printStackTrace(new PrintWriter(sw));
|
|
||||||
job.setError(sw.toString());
|
|
||||||
session.getTransaction().rollback();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
package org.keycloak.test.tools.jobs;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
|
||||||
*/
|
|
||||||
public interface UsersJobInitializer {
|
|
||||||
|
|
||||||
UsersJob instantiateJob();
|
|
||||||
}
|
|
|
@ -1,54 +0,0 @@
|
||||||
{
|
|
||||||
"admin": {
|
|
||||||
"realm": "master"
|
|
||||||
},
|
|
||||||
|
|
||||||
"eventsStore": {
|
|
||||||
"provider": "jpa",
|
|
||||||
"jpa": {
|
|
||||||
"exclude-events": [ "REFRESH_TOKEN" ]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
"model": {
|
|
||||||
"provider": "jpa"
|
|
||||||
},
|
|
||||||
|
|
||||||
"userSessions": {
|
|
||||||
"provider" : "mem"
|
|
||||||
},
|
|
||||||
|
|
||||||
"modelCache": {
|
|
||||||
"provider": "${keycloak.model.cache.provider:}"
|
|
||||||
},
|
|
||||||
|
|
||||||
"timer": {
|
|
||||||
"provider": "basic"
|
|
||||||
},
|
|
||||||
|
|
||||||
"theme": {
|
|
||||||
"default": "keycloak",
|
|
||||||
"staticMaxAge": 2592000,
|
|
||||||
"cacheTemplates": true,
|
|
||||||
"cacheThemes": true,
|
|
||||||
"folder": {
|
|
||||||
"dir": "${jboss.server.config.dir}/themes"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
"login": {
|
|
||||||
"provider": "freemarker"
|
|
||||||
},
|
|
||||||
|
|
||||||
"account": {
|
|
||||||
"provider": "freemarker"
|
|
||||||
},
|
|
||||||
|
|
||||||
"email": {
|
|
||||||
"provider": "freemarker"
|
|
||||||
},
|
|
||||||
|
|
||||||
"scheduled": {
|
|
||||||
"interval": 900
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
<jboss-deployment-structure>
|
|
||||||
<deployment>
|
|
||||||
<dependencies>
|
|
||||||
<module name="org.apache.httpcomponents"/>
|
|
||||||
<module name="org.bouncycastle"/>
|
|
||||||
<module name="org.jboss.resteasy.resteasy-jackson-provider" services="import"/>
|
|
||||||
<module name="org.codehaus.jackson.jackson-core-asl"/>
|
|
||||||
<module name="org.codehaus.jackson.jackson-mapper-asl"/>
|
|
||||||
</dependencies>
|
|
||||||
<exclusions>
|
|
||||||
<module name="org.jboss.resteasy.resteasy-jackson2-provider"/>
|
|
||||||
|
|
||||||
<!-- Exclude keycloak modules -->
|
|
||||||
<module name="org.keycloak.keycloak-core" />
|
|
||||||
<module name="org.keycloak.keycloak-adapter-core" />
|
|
||||||
<module name="org.keycloak.keycloak-undertow-adapter" />
|
|
||||||
<module name="org.keycloak.keycloak-as7-adapter" />
|
|
||||||
</exclusions>
|
|
||||||
</deployment>
|
|
||||||
</jboss-deployment-structure>
|
|
|
@ -1,75 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<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>keycloak-tools</module-name>
|
|
||||||
|
|
||||||
<servlet>
|
|
||||||
<servlet-name>Keycloak REST Interface</servlet-name>
|
|
||||||
<servlet-class>org.jboss.resteasy.plugins.server.servlet.HttpServlet30Dispatcher</servlet-class>
|
|
||||||
<init-param>
|
|
||||||
<param-name>javax.ws.rs.Application</param-name>
|
|
||||||
<param-value>org.keycloak.test.tools.KeycloakTestApplication</param-value>
|
|
||||||
</init-param>
|
|
||||||
<init-param>
|
|
||||||
<param-name>resteasy.servlet.mapping.prefix</param-name>
|
|
||||||
<param-value>/</param-value>
|
|
||||||
</init-param>
|
|
||||||
<load-on-startup>1</load-on-startup>
|
|
||||||
<async-supported>true</async-supported>
|
|
||||||
</servlet>
|
|
||||||
|
|
||||||
<welcome-file-list>
|
|
||||||
<welcome-file>index.html</welcome-file>
|
|
||||||
</welcome-file-list>
|
|
||||||
|
|
||||||
<listener>
|
|
||||||
<listener-class>org.keycloak.test.tools.DestroyListener</listener-class>
|
|
||||||
</listener>
|
|
||||||
|
|
||||||
<filter>
|
|
||||||
<filter-name>Keycloak Client Connection Filter</filter-name>
|
|
||||||
<filter-class>org.keycloak.services.filters.ClientConnectionFilter</filter-class>
|
|
||||||
</filter>
|
|
||||||
|
|
||||||
<filter>
|
|
||||||
<filter-name>Keycloak Session Management</filter-name>
|
|
||||||
<filter-class>org.keycloak.services.filters.KeycloakSessionServletFilter</filter-class>
|
|
||||||
</filter>
|
|
||||||
|
|
||||||
<filter-mapping>
|
|
||||||
<filter-name>Keycloak Session Management</filter-name>
|
|
||||||
<url-pattern>/perf/*</url-pattern>
|
|
||||||
</filter-mapping>
|
|
||||||
|
|
||||||
<filter-mapping>
|
|
||||||
<filter-name>Keycloak Client Connection Filter</filter-name>
|
|
||||||
<url-pattern>/perf/*</url-pattern>
|
|
||||||
</filter-mapping>
|
|
||||||
|
|
||||||
<servlet-mapping>
|
|
||||||
<servlet-name>Keycloak REST Interface</servlet-name>
|
|
||||||
<url-pattern>/perf/*</url-pattern>
|
|
||||||
</servlet-mapping>
|
|
||||||
|
|
||||||
<servlet-mapping>
|
|
||||||
<servlet-name>Keycloak REST Interface</servlet-name>
|
|
||||||
<url-pattern>/mail/*</url-pattern>
|
|
||||||
</servlet-mapping>
|
|
||||||
|
|
||||||
<!--
|
|
||||||
|
|
||||||
<security-constraint>
|
|
||||||
<web-resource-collection>
|
|
||||||
<url-pattern>/*</url-pattern>
|
|
||||||
</web-resource-collection>
|
|
||||||
<user-data-constraint>
|
|
||||||
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
|
|
||||||
</user-data-constraint>
|
|
||||||
</security-constraint>
|
|
||||||
-->
|
|
||||||
|
|
||||||
|
|
||||||
</web-app>
|
|
|
@ -1,33 +0,0 @@
|
||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<title>Keycloak Testsuite Tools</title>
|
|
||||||
|
|
||||||
<link rel="stylesheet" href="/auth/admin/master/console/css/styles.css">
|
|
||||||
|
|
||||||
<script src="/auth/admin/master/console/lib/angular/angular.js"></script>
|
|
||||||
<script src="/auth/admin/master/console/lib/angular/angular-resource.js"></script>
|
|
||||||
<script src="/auth/admin/master/console/lib/angular/angular-route.js"></script>
|
|
||||||
|
|
||||||
<script src="js/app.js"></script>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body data-ng-app="keycloak-tools">
|
|
||||||
|
|
||||||
<header class="navbar navbar-default navbar-pf navbar-main header">
|
|
||||||
|
|
||||||
<ul class="nav navbar-nav navbar-primary persistent-secondary ng-scope">
|
|
||||||
<li><a href="#/">Home</a></li>
|
|
||||||
<li><a href="#/perf">Perf</a></li>
|
|
||||||
<li><a href="#/mail">Mail</a></li>
|
|
||||||
</ul>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div style="background-color: #fff; margin: 2em; padding: 2em;" data-ng-view id="view"></div>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
|
|
|
@ -1,74 +0,0 @@
|
||||||
var module = angular.module('keycloak-tools', [ 'ngRoute', 'ngResource' ]);
|
|
||||||
|
|
||||||
module.config([ '$routeProvider', function ($routeProvider) {
|
|
||||||
|
|
||||||
$routeProvider
|
|
||||||
.when('/perf', {
|
|
||||||
templateUrl: 'pages/perf.html',
|
|
||||||
controller: 'PerfCtrl'
|
|
||||||
})
|
|
||||||
.when('/mail', {
|
|
||||||
templateUrl: 'pages/mail.html',
|
|
||||||
controller: 'MailCtrl'
|
|
||||||
})
|
|
||||||
.otherwise({
|
|
||||||
templateUrl: 'pages/home.html'
|
|
||||||
});
|
|
||||||
}]);
|
|
||||||
|
|
||||||
module.filter('reverse', function() {
|
|
||||||
return function(items) {
|
|
||||||
return items.slice().reverse();
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
module.controller('PerfCtrl', function ($scope, $resource) {
|
|
||||||
|
|
||||||
$scope.createUsersData = {
|
|
||||||
realm: 'test',
|
|
||||||
count: 100,
|
|
||||||
start: 0,
|
|
||||||
batch: 25
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.loadJobs = function() {
|
|
||||||
$scope.jobs = $resource('/keycloak-tools/perf/jobs').query();
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.clearJobs = function() {
|
|
||||||
$scope.jobs = $resource('/keycloak-tools/perf/delete-jobs').query({}, function() {
|
|
||||||
$scope.loadJobs();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.createUsers = function() {
|
|
||||||
console.debug($scope.createUsersData);
|
|
||||||
$resource('/keycloak-tools/perf/:realm/create-users').get($scope.createUsersData, function() {
|
|
||||||
$scope.loadJobs();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.loadJobs();
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
module.controller('MailCtrl', function ($scope, $resource) {
|
|
||||||
|
|
||||||
$scope.start = function() {
|
|
||||||
$resource('/keycloak-tools/mail/start').get({}, function(status) {
|
|
||||||
$scope.status = status;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.stop = function() {
|
|
||||||
$resource('/keycloak-tools/mail/stop').get({}, function(status) {
|
|
||||||
$scope.status = status;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
$scope.loadMessages = function() {
|
|
||||||
$scope.messages = $resource('/keycloak-tools/mail/messages').query();
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.status = $resource('/keycloak-tools/mail/status').get();
|
|
||||||
|
|
||||||
});
|
|
|
@ -1 +0,0 @@
|
||||||
Home
|
|
|
@ -1,24 +0,0 @@
|
||||||
<h1>Mail</h1>
|
|
||||||
|
|
||||||
<button data-ng-click="start()" class="btn btn-default" data-ng-show="!status.started">Start</button>
|
|
||||||
<button data-ng-click="stop()" class="btn btn-default" data-ng-show="status.started">Stop</button>
|
|
||||||
<button data-ng-click="loadMessages()" class="btn btn-default" data-ng-show="status.started">Refresh</button>
|
|
||||||
|
|
||||||
<table class="table table-striped table-bordered">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Date</th>
|
|
||||||
<th>To</th>
|
|
||||||
<th>From</th>
|
|
||||||
<th>Subject</th>
|
|
||||||
<th>Body</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tr data-ng-repeat="m in messages|reverse">
|
|
||||||
<td>{{m.date|date:'medium'}}</td>
|
|
||||||
<td>{{m.to}}</td>
|
|
||||||
<td>{{m.from}}</td>
|
|
||||||
<td>{{m.subject}}</td>
|
|
||||||
<td><pre>{{m.body}}</pre></td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
|
@ -1,67 +0,0 @@
|
||||||
<h1>Perf</h1>
|
|
||||||
|
|
||||||
<fieldset>
|
|
||||||
<legend><span class="text">Jobs</span></legend>
|
|
||||||
<button data-ng-click="loadJobs()" class="btn btn-default">Refresh</button>
|
|
||||||
<button data-ng-click="clearJobs()" class="btn btn-default">Clear completed</button>
|
|
||||||
|
|
||||||
<table class="table table-striped table-bordered">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Description</th>
|
|
||||||
<th>Error</th>
|
|
||||||
<th>Count</th>
|
|
||||||
<th>Total</th>
|
|
||||||
<th>Started</th>
|
|
||||||
<th>Completed</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tr data-ng-repeat="j in jobs|reverse">
|
|
||||||
<td>{{j.description}}</td>
|
|
||||||
<td>{{j.error}}</td>
|
|
||||||
<td>{{j.count}}</td>
|
|
||||||
<td>{{j.total}}</td>
|
|
||||||
<td>{{j.started|date:'medium'}}</td>
|
|
||||||
<td>{{j.completed|date:'medium'}}</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<fieldset>
|
|
||||||
<legend><span class="text">Create users</span></legend>
|
|
||||||
|
|
||||||
<div class="alert alert-info"><span class="pficon pficon-info"></span> Create users with username <code><prefix>-<start></code> to <code><prefix>-<start + count></code> and optionally add role mappings for the specified realm roles.</div>
|
|
||||||
|
|
||||||
<form class="form-horizontal">
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="control-label col-sm-1">Realm</label>
|
|
||||||
<input data-ng-model="createUsersData.realm" value="test">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="control-label col-sm-1">Count</label>
|
|
||||||
<input data-ng-model="createUsersData.count" type="number" min="1" max="100000">
|
|
||||||
<span>(number of users to create)</span>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="control-label col-sm-1">Batch</label>
|
|
||||||
<input data-ng-model="createUsersData.batch" type="number" min="1" max="100">
|
|
||||||
<span>(number of users to create in one transaction)</span>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="control-label col-sm-1">Start</label>
|
|
||||||
<input data-ng-model="createUsersData.start" type="number" min="0">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="control-label col-sm-1">Prefix</label>
|
|
||||||
<input data-ng-model="createUsersData.prefix" type="number" min="0">
|
|
||||||
<span>(optional, by default currentTimeMillis is used)</span>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="control-label col-sm-1">Roles</label>
|
|
||||||
<input data-ng-model="createUsersData.roles" type="number" min="0">
|
|
||||||
<span>(comma separated list of realm roles)</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button data-ng-click="createUsers()" class="btn btn-default">Create</button>
|
|
||||||
</form>
|
|
||||||
</fieldset>
|
|
Loading…
Reference in a new issue