Merge pull request #5086 from tkyjovsk/KEYCLOAK-6827

KEYCLOAK-6827 Performance test for user registration
This commit is contained in:
Marko Strukelj 2018-03-22 19:11:05 +01:00 committed by GitHub
commit e4e14cd9f4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 305 additions and 121 deletions

View file

@ -168,22 +168,29 @@ with `server.version` defaulting to `${project.version}` from `pom.xml`.
### Run Tests
Usage: `mvn verify -P test [-Dtest.properties=NAMED_PROPERTY_SET]`. Default property set is `basic-oidc`.
Usage: `mvn verify -P test [-Dtest.properties=NAMED_PROPERTY_SET]`. Default property set is `oidc-login-logout`.
The parameters are loaded from `tests/parameters/test/${test.properties}.properties` file.
Individual properties can be overriden from command line via `-D` params.
To use a custom properties file specify `-Dtest.properties.file=ABSOLUTE_PATH_TO_FILE` instead of `-Dtest.properties`.
When running the tests it is also necessary to define a dataset to use. Usage is described in the section above.
#### Dataset
When running the tests it is necessary to define the dataset to be used.
| Parameter | Description | Default Value |
| --- | --- | --- |
| `dataset` | Name of the dataset to use. Individual parameters can be overriden from CLI. For details see the section above. | `default` |
| `sequentialRealmsFrom` | Use sequential realm iteration starting from specific index. Must be lower than `numOfRealms` parameter from dataset properties. Useful for user registration scenario. | `-1` random iteration |
| `sequentialUsersFrom` | Use sequential user iteration starting from specific index. Must be lower than `usersPerRealm` parameter from dataset properties. Useful for user registration scenario. | `-1` random iteration |
#### Common Test Run Parameters
| Parameter | Description | Default Value |
| --- | --- | --- |
| `gatling.simulationClass` | Classname of the simulation to be run. | `keycloak.BasicOIDCSimulation` |
| `dataset` | Name of the dataset to use. (Individual dataset properties can be overridden with `-Ddataset.property=value`.) | `default` |
| `usersPerSec` | Arrival rate of new users per second. Can be a floating point number. | `1.0` for BasicOIDCSimulation, `0.2` for AdminConsoleSimulation |
| `gatling.simulationClass` | Classname of the simulation to be run. | `keycloak.OIDCLoginAndLogoutSimulation` |
| `usersPerSec` | Arrival rate of new users per second. Can be a floating point number. | `1.0` for OIDCLoginAndLogoutSimulation, `0.2` for AdminConsoleSimulation |
| `rampUpPeriod` | Period during which the users will be ramped up. (seconds) | `15` |
| `warmUpPeriod` | Period with steady number of users intended for the system under test to warm up. (seconds) | `15` |
| `measurementPeriod` | A measurement period after the system is warmed up. (seconds) | `30` |
@ -191,7 +198,7 @@ When running the tests it is also necessary to define a dataset to use. Usage is
| `userThinkTime` | Pause between individual scenario steps. | `5` |
| `refreshTokenPeriod`| Period after which token should be refreshed. | `10` |
#### Test Run Parameters specific to `BasicOIDCSimulation`
#### Test Run Parameters specific to `OIDCLoginAndLogoutSimulation`
| Parameter | Description | Default Value |
| --- | --- | --- |
@ -206,12 +213,30 @@ When running the tests it is also necessary to define a dataset to use. Usage is
- Run test specific test and dataset parameters:
`mvn verify -P test -Dtest.properties=basic-oidc -Ddataset=100u2c`
`mvn verify -P test -Dtest.properties=oidc-login-logout -Ddataset=100u2c`
- Run test with specific test and dataset parameters, overriding some from command line:
`mvn verify -P test -Dtest.properties=admin-console -Ddataset=100u2c -DrampUpPeriod=30 -DwarmUpPeriod=60 -DusersPerSec=0.3`
#### Running `OIDCRegisterAndLogoutSimulation`
Running the user registration simulation requires a different approach to dataset and how it's iterated.
- It requires sequential iteration instead of the default random one.
- In case some users are already registered it requires starting the iteration from a specific index .
##### Example A:
1. Generate dataset with 0 users: `mvn verify -P generate-data -DusersPerRealm=0`
2. Run the registration test:
`mvn verify -P test -D test.properties=oidc-register-logout -DsequentialUsersFrom=0 -DusersPerRealm=<MAX_EXPECTED_REGISTRATIONS>`
##### Example B:
1. Generate or import dataset with 100 users: `mvn verify -P generate-data -Ddataset=100u2c`. This will create 1 realm and users 0-99.
2. Run the registration test starting from user 100:
`mvn verify -P test -D test.properties=oidc-register-logout -DsequentialUsersFrom=100 -DusersPerRealm=<MAX_EXPECTED_REGISTRATIONS>`
## Monitoring
@ -271,7 +296,7 @@ Thus, it's best to download and install [this SDK version](http://scala-lang.org
Open Preferences in IntelliJ. Type 'plugins' in the search box. In the right pane click on 'Install JetBrains plugin'.
Type 'scala' in the search box, and click Install button of the Scala plugin.
#### Run BasicOIDCSimulation from IntelliJ
#### Run OIDCLoginAndLogoutSimulation from IntelliJ
Make sure that `performance` maven profile is enabled for IDEA to treat `performance` directory as a project module.

View file

@ -0,0 +1,23 @@
#provisioner=docker-compose
#deployment=singlenode
# Keycloak Settings
keycloak.scale=1
keycloak.docker.cpusets=1
keycloak.docker.memlimit=2500m
keycloak.jvm.memory=-Xms64m -Xmx2g -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m
keycloak.http.max-connections=50000
keycloak.worker.io-threads=2
keycloak.worker.task-max-threads=16
keycloak.ds.min-pool-size=10
keycloak.ds.max-pool-size=100
keycloak.ds.pool-prefill=true
keycloak.ds.ps-cache-size=100
# Database Settings
db.docker.cpusets=0
db.docker.memlimit=2g
db.max.connections=100
# Monitoring Settings
monitoring.docker.cpusets=0

View file

@ -1,4 +1,4 @@
gatling.simulationClass=keycloak.BasicOIDCSimulation
gatling.simulationClass=keycloak.OIDCLoginAndLogoutSimulation
usersPerSec=1.0
rampUpPeriod=15
warmUpPeriod=15

View file

@ -0,0 +1,10 @@
gatling.simulationClass=keycloak.OIDCRegisterAndLogoutSimulation
usersPerSec=1.0
rampUpPeriod=15
warmUpPeriod=15
measurementPeriod=30
filterResults=false
userThinkTime=0
refreshTokenPeriod=0
refreshTokenCount=1
badLoginAttempts=1

View file

@ -35,7 +35,7 @@
<provisioning.properties>${provisioner}/4cpus/${deployment}</provisioning.properties>
<dataset>2u2c</dataset>
<test.properties>basic-oidc</test.properties>
<test.properties>oidc-login-logout</test.properties>
<provisioning.properties.file>${project.basedir}/parameters/provisioning/${provisioning.properties}.properties</provisioning.properties.file>
<dataset.properties.file>${project.basedir}/parameters/datasets/${dataset}.properties</dataset.properties.file>
@ -57,7 +57,7 @@
<scala-maven-plugin.version>3.2.2</scala-maven-plugin.version>
<jboss-logging.version>3.3.0.Final</jboss-logging.version>
<gatling.simulationClass>keycloak.BasicOIDCSimulation</gatling.simulationClass>
<gatling.simulationClass>keycloak.OIDCLoginAndLogoutSimulation</gatling.simulationClass>
<gatling.skip.run>true</gatling.skip.run>
</properties>

View file

@ -9,6 +9,6 @@ import java.util.List;
*/
public class RealmConfig {
public int accessTokenLifeSpan = 60;
public boolean registrationAllowed = false;
public boolean registrationAllowed = true;
public List<String> requiredCredentials = Collections.unmodifiableList(Arrays.asList("password"));
}

View file

@ -120,9 +120,9 @@ public class RealmsConfigurationBuilder {
UserRepresentation user = new UserRepresentation();
user.setUsername(computeUsername(realmName, j));
user.setEnabled(true);
user.setEmail(user.getUsername() + "@example.com");
user.setFirstName("User" + j);
user.setLastName("O'realm" + i);
user.setEmail(computeEmail(user.getUsername()));
user.setFirstName(computeFirstName(j));
user.setLastName(computeLastName(realmName));
CredentialRepresentation creds = new CredentialRepresentation();
creds.setType("password");
@ -309,6 +309,18 @@ public class RealmsConfigurationBuilder {
return "passOfUser_" + username;
}
static String computeFirstName(int userIdx) {
return "User" + userIdx;
}
static String computeLastName(String realm) {
return "O'" + realm.replace("_", "");
}
static String computeEmail(String username) {
return username + "@example.com";
}

View file

@ -13,6 +13,9 @@ import java.util.concurrent.ThreadLocalRandom;
import static org.keycloak.performance.RealmsConfigurationBuilder.computeAppUrl;
import static org.keycloak.performance.RealmsConfigurationBuilder.computeClientId;
import static org.keycloak.performance.RealmsConfigurationBuilder.computeEmail;
import static org.keycloak.performance.RealmsConfigurationBuilder.computeFirstName;
import static org.keycloak.performance.RealmsConfigurationBuilder.computeLastName;
import static org.keycloak.performance.RealmsConfigurationBuilder.computePassword;
import static org.keycloak.performance.RealmsConfigurationBuilder.computeSecret;
import static org.keycloak.performance.RealmsConfigurationBuilder.computeUsername;
@ -51,6 +54,12 @@ public class TestConfig {
public static final int clientRolesPerUser = Integer.getInteger("clientRolesPerUser", 2);
public static final int clientRolesPerClient = Integer.getInteger("clientRolesPerClient", 2);
// sequential vs random dataset iteration
public static final int sequentialRealmsFrom = Integer.getInteger("sequentialRealmsFrom", -1); // -1 means random iteration
public static final int sequentialUsersFrom = Integer.getInteger("sequentialUsersFrom", -1); // -1 means random iteration
public static final boolean sequentialRealms = sequentialRealmsFrom >= 0;
public static final boolean sequentialUsers = sequentialUsersFrom >= 0;
//
// Settings used by tests to control common test parameters
//
@ -69,7 +78,7 @@ public class TestConfig {
public static final long measurementEndTime = measurementStartTime + measurementPeriod * 1000;
//
// Settings used by BasicOIDCSimulation to control behavior specific to BasicOIDCSimulation
// Settings used by OIDCLoginAndLogoutSimulation to control behavior specific to OIDCLoginAndLogoutSimulation
//
public static final int badLoginAttempts = Integer.getInteger("badLoginAttempts", 0);
public static final int refreshTokenCount = Integer.getInteger("refreshTokenCount", 0);
@ -101,8 +110,12 @@ public class TestConfig {
// Clients iterators by realm
private static final ConcurrentMap<String, Iterator<ClientInfo>> clientsIteratorMap = new ConcurrentHashMap<>();
public static Iterator<String> getRealmsIterator() {
return sequentialRealms ? sequentialRealmsIterator() : randomRealmsIterator();
}
public static Iterator<UserInfo> getUsersIterator(String realm) {
return usersIteratorMap.computeIfAbsent(realm, (k) -> randomUsersIterator(realm));
return usersIteratorMap.computeIfAbsent(realm, (k) -> sequentialUsers ? sequentialUsersIterator(realm) : randomUsersIterator(realm));
}
public static Iterator<ClientInfo> getClientsIterator(String realm) {
@ -140,15 +153,30 @@ public class TestConfig {
}
public static String toStringDatasetProperties() {
return String.format(" numOfRealms: %s\n usersPerRealm: %s\n clientsPerRealm: %s\n realmRoles: %s\n realmRolesPerUser: %s\n clientRolesPerUser: %s\n clientRolesPerClient: %s\n hashIterations: %s",
numOfRealms, usersPerRealm, clientsPerRealm, realmRoles, realmRolesPerUser, clientRolesPerUser, clientRolesPerClient, hashIterations);
return String.format(
" numOfRealms: %s%s\n"
+ " usersPerRealm: %s%s\n"
+ " clientsPerRealm: %s\n"
+ " realmRoles: %s\n"
+ " realmRolesPerUser: %s\n"
+ " clientRolesPerUser: %s\n"
+ " clientRolesPerClient: %s\n"
+ " hashIterations: %s",
numOfRealms, sequentialRealms ? ", sequential iteration starting from " + sequentialRealmsFrom: "",
usersPerRealm, sequentialUsers ? ", sequential iteration starting from " + sequentialUsersFrom: "",
clientsPerRealm,
realmRoles,
realmRolesPerUser,
clientRolesPerUser,
clientRolesPerClient,
hashIterations);
}
public static Iterator<UserInfo> sequentialUsersIterator(final String realm) {
return new Iterator<UserInfo>() {
int idx = 0;
int idx = sequentialUsers ? sequentialUsersFrom : 0;
@Override
public boolean hasNext() {
@ -162,8 +190,14 @@ public class TestConfig {
}
String user = computeUsername(realm, idx);
String firstName= computeFirstName(idx);
idx += 1;
return new UserInfo(user, computePassword(user));
return new UserInfo(user,
computePassword(user),
firstName,
computeLastName(realm),
computeEmail(user)
);
}
};
}
@ -179,8 +213,14 @@ public class TestConfig {
@Override
public UserInfo next() {
String user = computeUsername(realm, ThreadLocalRandom.current().nextInt(usersPerRealm));
return new UserInfo(user, computePassword(user));
int idx = ThreadLocalRandom.current().nextInt(usersPerRealm);
String user = computeUsername(realm, idx);
return new UserInfo(user,
computePassword(user),
computeFirstName(idx),
computeLastName(realm),
computeEmail(user)
);
}
};
}
@ -204,6 +244,29 @@ public class TestConfig {
};
}
public static Iterator<String> sequentialRealmsIterator() {
return new Iterator<String>() {
int idx = sequentialRealms ? sequentialRealmsFrom : 0;
@Override
public boolean hasNext() {
return true;
}
@Override
public String next() {
if (idx >= numOfRealms) {
idx = 0;
}
String realm = "realm_" + idx;
idx += 1;
return realm;
}
};
}
public static Iterator<String> randomRealmsIterator() {
return new Iterator<String>() {
@ -220,13 +283,19 @@ public class TestConfig {
};
}
static void validateConfiguration() {
public static void validateConfiguration() {
if (realmRolesPerUser > realmRoles) {
throw new RuntimeException("Can't have more realmRolesPerUser than there are realmRoles");
}
if (clientRolesPerUser > clientsPerRealm * clientRolesPerClient) {
throw new RuntimeException("Can't have more clientRolesPerUser than there are all client roles (clientsPerRealm * clientRolesPerClient)");
}
if (sequentialRealmsFrom < -1 || sequentialRealmsFrom >= numOfRealms) {
throw new RuntimeException("The folowing condition must be met: (-1 <= sequentialRealmsFrom < numOfRealms).");
}
if (sequentialUsersFrom < -1 || sequentialUsersFrom >= usersPerRealm) {
throw new RuntimeException("The folowing condition must be met: (-1 <= sequentialUsersFrom < usersPerRealm).");
}
}
}

View file

@ -7,9 +7,15 @@ public class UserInfo {
public final String username;
public final String password;
public final String firstName;
public final String lastName;
public final String email;
UserInfo(String username, String password) {
UserInfo(String username, String password, String firstName, String lastName, String email) {
this.username = username;
this.password = password;
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
}
}

View file

@ -4,7 +4,7 @@ import io.gatling.core.config.GatlingPropertiesBuilder
object Engine extends App {
val sim = classOf[keycloak.BasicOIDCSimulation]
val sim = classOf[keycloak.OIDCLoginAndLogoutSimulation]
//val sim = classOf[keycloak.AdminConsoleSimulation]
val props = new GatlingPropertiesBuilder

View file

@ -3,11 +3,6 @@ package keycloak
import io.gatling.core.Predef._
import io.gatling.http.Predef._
import keycloak.CommonScenarioBuilder._
import org.keycloak.performance.TestConfig
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
@ -80,10 +75,6 @@ class AdminConsoleSimulation extends CommonSimulation {
val adminScenario = scenario("AdminConsole").exec(adminSession.chainBuilder)
setUp(adminScenario
.inject(
rampUsersPerSec(0.001) to TestConfig.usersPerSec during(TestConfig.rampUpPeriod) ,
constantUsersPerSec(TestConfig.usersPerSec) during(TestConfig.warmUpPeriod + TestConfig.measurementPeriod)
).protocols(httpProtocol))
setUp(adminScenario.inject(defaultInjectionProfile).protocols(httpProtocol))
}

View file

@ -1,57 +0,0 @@
package keycloak
import io.gatling.core.Predef._
import io.gatling.http.Predef._
import keycloak.CommonScenarioBuilder._
import keycloak.BasicOIDCScenarioBuilder._
import org.keycloak.performance.TestConfig
/**
* @author Radim Vansa &lt;rvansa@redhat.com&gt;
* @author Marko Strukelj &lt;mstrukel@redhat.com&gt;
*/
class BasicOIDCSimulation extends CommonSimulation {
override def printSpecificTestParameters {
println(" refreshTokenCount: " + TestConfig.refreshTokenCount)
println(" badLoginAttempts: " + TestConfig.badLoginAttempts)
}
val httpDefault = http
.acceptHeader("application/json")
.disableFollowRedirect
.inferHtmlResources
val userSession = new BasicOIDCScenarioBuilder()
.browserOpensLoginPage()
.thinkPause()
.browserPostsWrongCredentials()
.browserPostsCorrectCredentials()
// Act as client adapter - exchange code for keys
.adapterExchangesCodeForTokens()
.refreshTokenSeveralTimes()
.thinkPause()
.logout()
.thinkPause()
val usersScenario = scenario("users").exec(userSession.chainBuilder)
setUp(usersScenario.inject(
rampUsersPerSec(0.001) to TestConfig.usersPerSec during(TestConfig.rampUpPeriod),
constantUsersPerSec(TestConfig.usersPerSec) during(TestConfig.warmUpPeriod + TestConfig.measurementPeriod)
).protocols(httpDefault))
// after {
// filterResults(getClass)
// }
}

View file

@ -1,13 +0,0 @@
package keycloak
import io.gatling.core.Predef._
import io.gatling.http.Predef._
import org.keycloak.gatling.Predef._
/**
* @author <a href="mailto:tkyjovsk@redhat.com">Tomas Kyjovsky</a>
*/
object CommonScenarioBuilder {
}

View file

@ -2,10 +2,11 @@ package keycloak
import io.gatling.core.Predef._
import io.gatling.http.Predef._
import keycloak.CommonScenarioBuilder._
import org.keycloak.performance.log.LogProcessor
import io.gatling.core.validation.Validation
import io.gatling.core.controller.inject.InjectionStep
import org.keycloak.performance.TestConfig
@ -24,7 +25,12 @@ abstract class CommonSimulation extends Simulation {
println()
println("Timestamps: \n" + TestConfig.toStringTimestamps)
println()
var defaultInjectionProfile = Array[InjectionStep] (
rampUsersPerSec(0.001) to TestConfig.usersPerSec during(TestConfig.rampUpPeriod),
constantUsersPerSec(TestConfig.usersPerSec) during(TestConfig.warmUpPeriod + TestConfig.measurementPeriod)
)
def printSpecificTestParameters {
// override in subclass
}
@ -33,6 +39,10 @@ abstract class CommonSimulation extends Simulation {
System.currentTimeMillis < TestConfig.measurementEndTime
}
before {
TestConfig.validateConfiguration
}
after {
if (TestConfig.filterResults) {
new LogProcessor(getClass).filterLog(

View file

@ -0,0 +1,21 @@
package keycloak
import io.gatling.core.Predef._
import io.gatling.http.Predef._
import keycloak.OIDCScenarioBuilder._
import org.keycloak.performance.TestConfig
class OIDCLoginAndLogoutSimulation extends CommonSimulation {
override def printSpecificTestParameters {
println(" refreshTokenCount: " + TestConfig.refreshTokenCount)
println(" badLoginAttempts: " + TestConfig.badLoginAttempts)
}
val usersScenario = scenario("Logging-in Users").exec(loginAndLogoutScenario.chainBuilder)
setUp(usersScenario.inject(defaultInjectionProfile).protocols(httpDefault))
}

View file

@ -0,0 +1,21 @@
package keycloak
import io.gatling.core.Predef._
import io.gatling.http.Predef._
import keycloak.OIDCScenarioBuilder._
import org.keycloak.performance.TestConfig
class OIDCRegisterAndLogoutSimulation extends CommonSimulation {
override def printSpecificTestParameters {
println(" refreshTokenCount: " + TestConfig.refreshTokenCount)
println(" badLoginAttempts: " + TestConfig.badLoginAttempts)
}
val usersScenario = scenario("Registering Users").exec(registerAndLogoutScenario.chainBuilder)
setUp(usersScenario.inject(defaultInjectionProfile).protocols(httpDefault))
}

View file

@ -3,7 +3,7 @@ package keycloak
import io.gatling.core.Predef._
import io.gatling.http.Predef._
import org.keycloak.gatling.Predef._
import keycloak.BasicOIDCScenarioBuilder._
import keycloak.OIDCScenarioBuilder._
import java.util.concurrent.atomic.AtomicInteger
@ -21,7 +21,7 @@ import org.keycloak.performance.TestConfig
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
object BasicOIDCScenarioBuilder {
object OIDCScenarioBuilder {
val BASE_URL = "${keycloakServer}/realms/${realm}"
val LOGIN_ENDPOINT = BASE_URL + "/protocol/openid-connect/auth"
@ -44,17 +44,49 @@ object BasicOIDCScenarioBuilder {
}
missCounter.getAndDecrement() > 0
}
val httpDefault = http
.acceptHeader("application/json")
.disableFollowRedirect
.inferHtmlResources
val loginAndLogoutScenario = new OIDCScenarioBuilder()
.browserOpensLoginPage()
.thinkPause()
.browserPostsWrongCredentials()
.browserPostsCorrectCredentials()
// Act as client adapter - exchange code for keys
.adapterExchangesCodeForTokens()
.refreshTokenSeveralTimes()
.thinkPause()
.logout()
.thinkPause()
val registerAndLogoutScenario = new OIDCScenarioBuilder()
.browserOpensLoginPage()
.thinkPause()
.browserOpensRegistrationPage()
.thinkPause()
.browserPostsRegistrationDetails()
.adapterExchangesCodeForTokens()
.thinkPause()
.logout()
.thinkPause()
}
class BasicOIDCScenarioBuilder {
class OIDCScenarioBuilder {
var chainBuilder = exec(s => {
// initialize session with host, user, client app, login failure ratio ...
val realm = TestConfig.randomRealmsIterator().next()
val userInfo = TestConfig.getUsersIterator(realm).next()
val realm = TestConfig.getRealmsIterator().next();
val userInfo = TestConfig.getUsersIterator(realm).next();
val clientInfo = TestConfig.getConfidentialClientsIterator(realm).next()
AuthorizeAction.init(s)
@ -63,6 +95,9 @@ class BasicOIDCScenarioBuilder {
"wrongPasswordCount" -> new AtomicInteger(TestConfig.badLoginAttempts),
"refreshTokenCount" -> new AtomicInteger(TestConfig.refreshTokenCount),
"realm" -> realm,
"firstName" -> userInfo.firstName,
"lastName" -> userInfo.lastName,
"email" -> userInfo.email,
"username" -> userInfo.username,
"password" -> userInfo.password,
"clientId" -> clientInfo.clientId,
@ -72,7 +107,7 @@ class BasicOIDCScenarioBuilder {
})
.exitHereIfFailed
def thinkPause() : BasicOIDCScenarioBuilder = {
def thinkPause() : OIDCScenarioBuilder = {
chainBuilder = chainBuilder.pause(TestConfig.userThinkTime, Normal(TestConfig.userThinkTime * 0.2))
this
}
@ -85,7 +120,7 @@ class BasicOIDCScenarioBuilder {
pause(TestConfig.userThinkTime, Normal(TestConfig.userThinkTime * 0.2))
}
def browserOpensLoginPage() : BasicOIDCScenarioBuilder = {
def browserOpensLoginPage() : OIDCScenarioBuilder = {
chainBuilder = chainBuilder
.exec(http("Browser to Log In Endpoint")
.get(LOGIN_ENDPOINT)
@ -95,7 +130,9 @@ class BasicOIDCScenarioBuilder {
.queryParam("client_id", "${clientId}")
.queryParam("state", "${state}")
.queryParam("redirect_uri", "${appUrl}")
.check(status.is(200), regex("action=\"([^\"]*)\"").find.transform(_.replaceAll("&amp;", "&")).saveAs("login-form-uri")))
.check(status.is(200),
regex("action=\"([^\"]*)\"").find.transform(_.replaceAll("&amp;", "&")).saveAs("login-form-uri"),
regex("href=\"/auth(/realms/[^\"]*/login-actions/registration[^\"]*)\"").find.transform(_.replaceAll("&amp;", "&")).saveAs("registration-link")))
// if already logged in the check will fail with:
// status.find.is(200), but actually found 302
// The reason is that instead of returning the login page we are immediately redirected to the app that requested authentication
@ -103,7 +140,7 @@ class BasicOIDCScenarioBuilder {
this
}
def browserPostsWrongCredentials() : BasicOIDCScenarioBuilder = {
def browserPostsWrongCredentials() : OIDCScenarioBuilder = {
chainBuilder = chainBuilder
.asLongAs(s => downCounterAboveZero(s, "wrongPasswordCount")) {
var c = exec(http("Browser posts wrong credentials")
@ -122,7 +159,7 @@ class BasicOIDCScenarioBuilder {
this
}
def browserPostsCorrectCredentials() : BasicOIDCScenarioBuilder = {
def browserPostsCorrectCredentials() : OIDCScenarioBuilder = {
chainBuilder = chainBuilder
.exec(http("Browser posts correct credentials")
.post("${login-form-uri}")
@ -135,7 +172,36 @@ class BasicOIDCScenarioBuilder {
this
}
def adapterExchangesCodeForTokens() : BasicOIDCScenarioBuilder = {
def browserOpensRegistrationPage() : OIDCScenarioBuilder = {
chainBuilder = chainBuilder
.exec(http("Browser to Registration Endpoint")
.get("${keycloakServer}${registration-link}")
.headers(UI_HEADERS)
.check(
status.is(200),
regex("action=\"([^\"]*)\"").find.transform(_.replaceAll("&amp;", "&")).saveAs("registration-form-uri"))
)
.exitHereIfFailed
this
}
def browserPostsRegistrationDetails() : OIDCScenarioBuilder = {
chainBuilder = chainBuilder
.exec(http("Browser posts registration details")
.post("${registration-form-uri}")
.headers(UI_HEADERS)
.formParam("firstName", "${firstName}")
.formParam("lastName", "${lastName}")
.formParam("email", "${email}")
.formParam("username", "${username}")
.formParam("password", "${password}")
.formParam("password-confirm", "${password}")
.check(status.is(302), header("Location").saveAs("login-redirect")))
.exitHereIfFailed
this
}
def adapterExchangesCodeForTokens() : OIDCScenarioBuilder = {
chainBuilder = chainBuilder
.exec(oauth("Adapter exchanges code for tokens")
.authorize("${login-redirect}",
@ -149,7 +215,7 @@ class BasicOIDCScenarioBuilder {
this
}
def refreshTokenSeveralTimes() : BasicOIDCScenarioBuilder = {
def refreshTokenSeveralTimes() : OIDCScenarioBuilder = {
chainBuilder = chainBuilder
.asLongAs(s => downCounterAboveZero(s, "refreshTokenCount")) {
// make sure to call newThinkPause rather than thinkPause
@ -159,7 +225,7 @@ class BasicOIDCScenarioBuilder {
this
}
def logout() : BasicOIDCScenarioBuilder = {
def logout() : OIDCScenarioBuilder = {
chainBuilder = chainBuilder
.exec(http("Browser logout")
.get(LOGOUT_ENDPOINT)