KEYCLOAK-6514 Common approach to writing performance tests
This commit is contained in:
parent
b464dc15f2
commit
548ab4f78c
13 changed files with 924 additions and 817 deletions
|
@ -7,7 +7,7 @@ Perform the usual test run:
|
||||||
mvn verify -Pteardown
|
mvn verify -Pteardown
|
||||||
mvn verify -Pprovision
|
mvn verify -Pprovision
|
||||||
mvn verify -Pgenerate-data -Ddataset=100users -Dimport.workers=10 -DhashIterations=100
|
mvn verify -Pgenerate-data -Ddataset=100users -Dimport.workers=10 -DhashIterations=100
|
||||||
mvn verify -Ptest -Ddataset=100users -DrunUsers=200 -DrampUpPeriod=10 -DuserThinkTime=0 -DbadLoginAttempts=1 -DrefreshTokenCount=1 -DnumOfIterations=3
|
mvn verify -Ptest -Ddataset=100users -DrunUsers=200 -DrampUpPeriod=10 -DuserThinkTime=0 -DbadLoginAttempts=1 -DrefreshTokenCount=1 -DsteadyLoadPeriod=10
|
||||||
```
|
```
|
||||||
|
|
||||||
Now analyze the generated simulation.log (adjust LOG_DIR, FROM, and TO):
|
Now analyze the generated simulation.log (adjust LOG_DIR, FROM, and TO):
|
||||||
|
|
|
@ -27,7 +27,7 @@ mvn clean install
|
||||||
# Make sure your Docker daemon is running THEN
|
# Make sure your Docker daemon is running THEN
|
||||||
mvn verify -Pprovision
|
mvn verify -Pprovision
|
||||||
mvn verify -Pgenerate-data -Ddataset=100u -DnumOfWorkers=10 -DhashIterations=100
|
mvn verify -Pgenerate-data -Ddataset=100u -DnumOfWorkers=10 -DhashIterations=100
|
||||||
mvn verify -Ptest -Ddataset=100u -DrunUsers=200 -DrampUpPeriod=10 -DuserThinkTime=0 -DbadLoginAttempts=1 -DrefreshTokenCount=1 -DnumOfIterations=3
|
mvn verify -Ptest -Ddataset=100u -DrunUsers=200 -DrampUpPeriod=10 -DuserThinkTime=0 -DbadLoginAttempts=1 -DrefreshTokenCount=1 -DsteadyLoadPeriod=10
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -139,7 +139,7 @@ Usage: `mvn verify -Ptest[,cluster] [-DtestParameter=value]`.
|
||||||
|
|
||||||
| Parameter | Description | Default Value |
|
| Parameter | Description | Default Value |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `gatling.simulationClass` | Classname of the simulation to be run. | `keycloak.DefaultSimulation` |
|
| `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` |
|
| `dataset` | Name of the dataset to use. (Individual dataset properties can be overridden with `-Ddataset.property=value`.) | `default` |
|
||||||
| `runUsers` | Number of users for the simulation run. | `1` |
|
| `runUsers` | Number of users for the simulation run. | `1` |
|
||||||
| `rampUpPeriod` | Period during which the users will be ramped up. (seconds) | `0` |
|
| `rampUpPeriod` | Period during which the users will be ramped up. (seconds) | `0` |
|
||||||
|
@ -149,7 +149,7 @@ Usage: `mvn verify -Ptest[,cluster] [-DtestParameter=value]`.
|
||||||
| `userThinkTime` | Pause between individual scenario steps. | `5` |
|
| `userThinkTime` | Pause between individual scenario steps. | `5` |
|
||||||
| `refreshTokenPeriod`| Period after which token should be refreshed. | `10` |
|
| `refreshTokenPeriod`| Period after which token should be refreshed. | `10` |
|
||||||
|
|
||||||
#### Addtional Parameters of `keycloak.DefaultSimulation`
|
#### Addtional Parameters of `keycloak.BasicOIDCSimulation`
|
||||||
|
|
||||||
| Parameter | Description | Default Value |
|
| Parameter | Description | Default Value |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
|
@ -159,7 +159,7 @@ Usage: `mvn verify -Ptest[,cluster] [-DtestParameter=value]`.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
`mvn verify -Ptest -Dgatling.simulationClass=keycloak.AdminSimulation -Ddataset=100u -DrunUsers=1 -DsteadyLoadPeriod=30 -DuserThinkTime=0 -DrefreshTokenPeriod=15`
|
`mvn verify -Ptest -Dgatling.simulationClass=keycloak.AdminConsoleSimulation -Ddataset=100u -DrunUsers=1 -DsteadyLoadPeriod=30 -DuserThinkTime=0 -DrefreshTokenPeriod=15`
|
||||||
|
|
||||||
|
|
||||||
## Monitoring
|
## Monitoring
|
||||||
|
@ -246,12 +246,16 @@ 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'.
|
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.
|
Type 'scala' in the search box, and click Install button of the Scala plugin.
|
||||||
|
|
||||||
#### Run DefaultSimulation from IntelliJ
|
#### Run BasicOIDCSimulation from IntelliJ
|
||||||
|
|
||||||
In ProjectExplorer find Engine object (you can use ctrl-N / cmd-O). Right click on class name and select Run or Debug like for
|
Make sure that `performance` maven profile is enabled for IDEA to treat `performance` directory as a project module.
|
||||||
JUnit tests.
|
|
||||||
|
|
||||||
You'll have to create a test profile, and set 'VM options' with -Dkey=value to override default configuration values in TestConfig class.
|
You may also need to rebuild the module in IDEA for scala objects to become available.
|
||||||
|
|
||||||
|
Then find Engine object In ProjectExplorer (you can use ctrl-N / cmd-O). Right click on class name and select Run or Debug as if it was
|
||||||
|
a JUnit tests.
|
||||||
|
|
||||||
|
You'll have to edit a test configuration, and set 'VM options' to a list of -Dkey=value pairs to override default configuration values in TestConfig class.
|
||||||
|
|
||||||
Make sure to set 'Use classpath of module' to 'performance-test'.
|
Make sure to set 'Use classpath of module' to 'performance-test'.
|
||||||
|
|
||||||
|
|
|
@ -93,7 +93,7 @@
|
||||||
<scala-maven-plugin.version>3.2.2</scala-maven-plugin.version>
|
<scala-maven-plugin.version>3.2.2</scala-maven-plugin.version>
|
||||||
<jboss-logging.version>3.3.0.Final</jboss-logging.version>
|
<jboss-logging.version>3.3.0.Final</jboss-logging.version>
|
||||||
|
|
||||||
<gatling.simulationClass>keycloak.DefaultSimulation</gatling.simulationClass>
|
<gatling.simulationClass>keycloak.BasicOIDCSimulation</gatling.simulationClass>
|
||||||
<gatling.skip.run>true</gatling.skip.run>
|
<gatling.skip.run>true</gatling.skip.run>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
|
|
|
@ -68,7 +68,7 @@ public class TestConfig {
|
||||||
public static final long rampDownPeriodStartTime = simulationStartTime + (rampUpPeriod + steadyLoadPeriod) * 1000;
|
public static final long rampDownPeriodStartTime = simulationStartTime + (rampUpPeriod + steadyLoadPeriod) * 1000;
|
||||||
|
|
||||||
//
|
//
|
||||||
// Settings used by DefaultSimulation to control behavior specific to DefaultSimulation
|
// Settings used by BasicOIDCSimulation to control behavior specific to BasicOIDCSimulation
|
||||||
//
|
//
|
||||||
public static final int badLoginAttempts = Integer.getInteger("badLoginAttempts", 0);
|
public static final int badLoginAttempts = Integer.getInteger("badLoginAttempts", 0);
|
||||||
public static final int refreshTokenCount = Integer.getInteger("refreshTokenCount", 0);
|
public static final int refreshTokenCount = Integer.getInteger("refreshTokenCount", 0);
|
||||||
|
|
|
@ -4,8 +4,8 @@ import io.gatling.core.config.GatlingPropertiesBuilder
|
||||||
|
|
||||||
object Engine extends App {
|
object Engine extends App {
|
||||||
|
|
||||||
val sim = classOf[keycloak.DefaultSimulation]
|
val sim = classOf[keycloak.BasicOIDCSimulation]
|
||||||
//val sim = classOf[keycloak.AdminSimulation]
|
//val sim = classOf[keycloak.AdminConsoleSimulation]
|
||||||
|
|
||||||
val props = new GatlingPropertiesBuilder
|
val props = new GatlingPropertiesBuilder
|
||||||
props.dataDirectory(IDEPathHelper.dataDirectory.toString)
|
props.dataDirectory(IDEPathHelper.dataDirectory.toString)
|
||||||
|
|
|
@ -24,6 +24,7 @@ class SimpleExample4 extends Simulation {
|
||||||
.exec(account)
|
.exec(account)
|
||||||
|
|
||||||
setUp(
|
setUp(
|
||||||
|
// rather than starting all 100 users at once, increase the count over a period of 10 seconds
|
||||||
scn.inject(rampUsers(100) over 10).protocols(httpConf)
|
scn.inject(rampUsers(100) over 10).protocols(httpConf)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,638 @@
|
||||||
|
package keycloak
|
||||||
|
|
||||||
|
import io.gatling.core.Predef._
|
||||||
|
import io.gatling.http.Predef._
|
||||||
|
import keycloak.AdminConsoleScenarioBuilder._
|
||||||
|
|
||||||
|
import java.time.ZonedDateTime
|
||||||
|
import java.time.format.DateTimeFormatter
|
||||||
|
|
||||||
|
import io.gatling.core.pause.Normal
|
||||||
|
import io.gatling.http.request.StringBody
|
||||||
|
import org.jboss.perf.util.Util
|
||||||
|
import org.jboss.perf.util.Util.randomUUID
|
||||||
|
import org.keycloak.gatling.Utils.{urlEncodedRoot, urlencode}
|
||||||
|
import org.keycloak.performance.TestConfig
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||||
|
*/
|
||||||
|
|
||||||
|
object AdminConsoleScenarioBuilder {
|
||||||
|
|
||||||
|
val UI_HEADERS = Map(
|
||||||
|
"Accept" -> "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
||||||
|
"Upgrade-Insecure-Requests" -> "1")
|
||||||
|
|
||||||
|
val ACCEPT_JSON = Map("Accept" -> "application/json")
|
||||||
|
val ACCEPT_ALL = Map("Accept" -> "*/*")
|
||||||
|
val AUTHORIZATION = Map("Authorization" -> "Bearer ${accessToken}")
|
||||||
|
|
||||||
|
val APP_URL = "${keycloakServer}/admin/master/console/"
|
||||||
|
val DATE_FMT = DateTimeFormatter.RFC_1123_DATE_TIME
|
||||||
|
|
||||||
|
|
||||||
|
def getRandomUser() : String = {
|
||||||
|
"user_" + (Util.random.nextDouble() * TestConfig.usersPerRealm).toInt
|
||||||
|
}
|
||||||
|
|
||||||
|
def needTokenRefresh(sess: Session): Boolean = {
|
||||||
|
val lastRefresh = sess("accessTokenRefreshTime").as[Long]
|
||||||
|
|
||||||
|
// 5 seconds before expiry is time to refresh
|
||||||
|
lastRefresh + sess("expiresIn").as[String].toInt * 1000 - 5000 < System.currentTimeMillis() ||
|
||||||
|
// or if refreshTokenPeriod is set force refresh even if not necessary
|
||||||
|
(TestConfig.refreshTokenPeriod > 0 &&
|
||||||
|
lastRefresh + TestConfig.refreshTokenPeriod * 1000 < System.currentTimeMillis())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class AdminConsoleScenarioBuilder {
|
||||||
|
|
||||||
|
var chainBuilder = exec(s => {
|
||||||
|
val realm = TestConfig.randomRealmsIterator().next()
|
||||||
|
val serverUrl = TestConfig.serverUrisList.get(0)
|
||||||
|
s.setAll(
|
||||||
|
"keycloakServer" -> serverUrl,
|
||||||
|
"keycloakServerUrlEncoded" -> urlencode(serverUrl),
|
||||||
|
"keycloakServerRootEncoded" -> urlEncodedRoot(serverUrl),
|
||||||
|
"state" -> randomUUID(),
|
||||||
|
"nonce" -> randomUUID(),
|
||||||
|
"randomClientId" -> ("client_" + randomUUID()),
|
||||||
|
"realm" -> realm,
|
||||||
|
"username" -> "admin",
|
||||||
|
"password" -> "admin",
|
||||||
|
"clientId" -> "security-admin-console"
|
||||||
|
)
|
||||||
|
}).exitHereIfFailed
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def thinkPause() : AdminConsoleScenarioBuilder = {
|
||||||
|
chainBuilder = chainBuilder.pause(TestConfig.userThinkTime, Normal(TestConfig.userThinkTime * 0.2))
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
def needTokenRefresh(sess: Session): Boolean = {
|
||||||
|
val lastRefresh = sess("accessTokenRefreshTime").as[Long]
|
||||||
|
|
||||||
|
// 5 seconds before expiry is time to refresh
|
||||||
|
lastRefresh + sess("expiresIn").as[String].toInt * 1000 - 5000 < System.currentTimeMillis() ||
|
||||||
|
// or if refreshTokenPeriod is set force refresh even if not necessary
|
||||||
|
(TestConfig.refreshTokenPeriod > 0 &&
|
||||||
|
lastRefresh + TestConfig.refreshTokenPeriod * 1000 < System.currentTimeMillis())
|
||||||
|
}
|
||||||
|
|
||||||
|
def refreshTokenIfExpired() : AdminConsoleScenarioBuilder = {
|
||||||
|
chainBuilder = chainBuilder
|
||||||
|
.doIf(s => needTokenRefresh(s)) {
|
||||||
|
exec(http("JS Adapter Token - Refresh tokens")
|
||||||
|
.post("/auth/realms/master/protocol/openid-connect/token")
|
||||||
|
.headers(ACCEPT_ALL)
|
||||||
|
.formParam("grant_type", "refresh_token")
|
||||||
|
.formParam("refresh_token", "${refreshToken}")
|
||||||
|
.formParam("client_id", "security-admin-console")
|
||||||
|
.check(status.is(200),
|
||||||
|
jsonPath("$.access_token").saveAs("accessToken"),
|
||||||
|
jsonPath("$.refresh_token").saveAs("refreshToken"),
|
||||||
|
jsonPath("$.expires_in").saveAs("expiresIn"),
|
||||||
|
header("Date").saveAs("tokenTime")))
|
||||||
|
|
||||||
|
.exec(s => {
|
||||||
|
s.set("accessTokenRefreshTime", ZonedDateTime.parse(s("tokenTime").as[String], DATE_FMT).toEpochSecond * 1000)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
def openAdminConsoleHome() : AdminConsoleScenarioBuilder = {
|
||||||
|
chainBuilder = chainBuilder
|
||||||
|
.exec(http("Console Home")
|
||||||
|
.get("/auth/admin/")
|
||||||
|
.headers(UI_HEADERS)
|
||||||
|
.check(status.is(302))
|
||||||
|
.resources(
|
||||||
|
http("Console Redirect")
|
||||||
|
.get("/auth/admin/master/console/")
|
||||||
|
.headers(UI_HEADERS)
|
||||||
|
.check(status.is(200), regex("<link.+\\/resources\\/([^\\/]+).+>").saveAs("resourceVersion")),
|
||||||
|
http("Console REST - Config")
|
||||||
|
.get("/auth/admin/master/console/config")
|
||||||
|
.headers(ACCEPT_JSON)
|
||||||
|
.check(status.is(200))
|
||||||
|
))
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
def loginThroughLoginForm() : AdminConsoleScenarioBuilder = {
|
||||||
|
chainBuilder = chainBuilder
|
||||||
|
.exec(http("JS Adapter Auth - Login Form Redirect")
|
||||||
|
.get("/auth/realms/master/protocol/openid-connect/auth?client_id=security-admin-console&redirect_uri=${keycloakServerUrlEncoded}%2Fadmin%2Fmaster%2Fconsole%2F&state=${state}&nonce=${nonce}&response_mode=fragment&response_type=code&scope=openid")
|
||||||
|
.headers(UI_HEADERS)
|
||||||
|
.check(status.is(200), regex("action=\"([^\"]*)\"").find.transform(_.replaceAll("&", "&")).saveAs("login-form-uri")))
|
||||||
|
.exitHereIfFailed
|
||||||
|
|
||||||
|
// thinkPause
|
||||||
|
thinkPause()
|
||||||
|
|
||||||
|
// Successful login
|
||||||
|
chainBuilder = chainBuilder
|
||||||
|
.exec(http("Login Form - Submit Correct Credentials")
|
||||||
|
.post("${login-form-uri}")
|
||||||
|
.formParam("username", "${username}")
|
||||||
|
.formParam("password", "${password}")
|
||||||
|
.formParam("login", "Log in")
|
||||||
|
.check(status.is(302),
|
||||||
|
header("Location").saveAs("login-redirect"),
|
||||||
|
headerRegex("Location", "code=([^&]+)").saveAs("code")))
|
||||||
|
// TODO store AUTH_SESSION_ID cookie for use with oauth.authorize?
|
||||||
|
.exitHereIfFailed
|
||||||
|
|
||||||
|
.exec(http("Console Redirect")
|
||||||
|
.get("/auth/admin/master/console/")
|
||||||
|
.headers(UI_HEADERS)
|
||||||
|
.check(status.is(200))
|
||||||
|
.resources(
|
||||||
|
http("Console REST - Config")
|
||||||
|
.get("/auth/admin/master/console/config")
|
||||||
|
.headers(ACCEPT_JSON)
|
||||||
|
.check(status.is(200)),
|
||||||
|
|
||||||
|
http("JS Adapter Token - Exchange code for tokens")
|
||||||
|
.post("/auth/realms/master/protocol/openid-connect/token")
|
||||||
|
.headers(ACCEPT_ALL)
|
||||||
|
.formParam("code", "${code}")
|
||||||
|
.formParam("grant_type", "authorization_code")
|
||||||
|
.formParam("client_id", "security-admin-console")
|
||||||
|
.formParam("redirect_uri", APP_URL)
|
||||||
|
.check(status.is(200),
|
||||||
|
jsonPath("$.access_token").saveAs("accessToken"),
|
||||||
|
jsonPath("$.refresh_token").saveAs("refreshToken"),
|
||||||
|
jsonPath("$.expires_in").saveAs("expiresIn"),
|
||||||
|
header("Date").saveAs("tokenTime")),
|
||||||
|
|
||||||
|
http("Console REST - messages.json")
|
||||||
|
.get("/auth/admin/master/console/messages.json?lang=en")
|
||||||
|
.headers(ACCEPT_JSON)
|
||||||
|
.check(status.is(200)),
|
||||||
|
|
||||||
|
// iframe status listener
|
||||||
|
// TODO: properly set Referer
|
||||||
|
http("IFrame Status Init")
|
||||||
|
.get("/auth/realms/master/protocol/openid-connect/login-status-iframe.html/init?client_id=security-admin-console&origin=${keycloakServerRootEncoded}") // ${keycloakServerUrlEncoded}
|
||||||
|
.headers(ACCEPT_ALL) // ++ Map("Referer" -> "/auth/realms/master/protocol/openid-connect/login-status-iframe.html?version=3.3.0.cr1-201708011508") ${resourceVersion}
|
||||||
|
.check(status.is(204))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.exec(s => {
|
||||||
|
// How to not have to duplicate this block of code?
|
||||||
|
s.set("accessTokenRefreshTime", ZonedDateTime.parse(s("tokenTime").as[String], DATE_FMT).toEpochSecond * 1000)
|
||||||
|
})
|
||||||
|
.exec(http("Console REST - whoami")
|
||||||
|
.get("/auth/admin/master/console/whoami")
|
||||||
|
.headers(ACCEPT_JSON ++ AUTHORIZATION)
|
||||||
|
.check(status.is(200)))
|
||||||
|
|
||||||
|
.exec(http("Console REST - realms")
|
||||||
|
.get("/auth/admin/realms")
|
||||||
|
.headers(AUTHORIZATION)
|
||||||
|
.check(status.is(200)))
|
||||||
|
|
||||||
|
.exec(http("Console REST - serverinfo")
|
||||||
|
.get("/auth/admin/serverinfo")
|
||||||
|
.headers(AUTHORIZATION)
|
||||||
|
.check(status.is(200)))
|
||||||
|
|
||||||
|
.exec(http("Console REST - realms")
|
||||||
|
.get("/auth/admin/realms")
|
||||||
|
.headers(AUTHORIZATION)
|
||||||
|
.check(status.is(200)))
|
||||||
|
|
||||||
|
.exec(http("Console REST - ${realm}")
|
||||||
|
.get("/auth/admin/realms/${realm}")
|
||||||
|
.headers(AUTHORIZATION)
|
||||||
|
.check(status.is(200)))
|
||||||
|
|
||||||
|
.exec(http("Console REST - realms")
|
||||||
|
.get("/auth/admin/realms")
|
||||||
|
.headers(AUTHORIZATION)
|
||||||
|
.check(status.is(200)))
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
def openRealmSettings() : AdminConsoleScenarioBuilder = {
|
||||||
|
refreshTokenIfExpired()
|
||||||
|
chainBuilder = chainBuilder.exec(http("Console Realm Settings")
|
||||||
|
.get("/auth/resources/${resourceVersion}/admin/keycloak/partials/realm-detail.html")
|
||||||
|
.headers(UI_HEADERS)
|
||||||
|
.check(status.is(200))
|
||||||
|
.resources(
|
||||||
|
http("Console REST - ${realm}")
|
||||||
|
.get("/auth/admin/realms/${realm}")
|
||||||
|
.headers(AUTHORIZATION)
|
||||||
|
.check(status.is(200)),
|
||||||
|
|
||||||
|
http("Console REST - realms")
|
||||||
|
.get("/auth/admin/realms")
|
||||||
|
.headers(AUTHORIZATION)
|
||||||
|
.check(status.is(200)),
|
||||||
|
|
||||||
|
http("Console REST - realms")
|
||||||
|
.get("/auth/admin/realms")
|
||||||
|
.headers(AUTHORIZATION)
|
||||||
|
.check(status.is(200)),
|
||||||
|
|
||||||
|
http("Console - kc-tabs-realm.html")
|
||||||
|
.get("/auth/resources/${resourceVersion}/admin/keycloak/templates/kc-tabs-realm.html")
|
||||||
|
//.headers(UI_HEADERS ++ Map("Referer" -> "")) // TODO fix referer
|
||||||
|
.headers(UI_HEADERS)
|
||||||
|
.check(status.is(200)),
|
||||||
|
|
||||||
|
http("Console - kc-menu.html")
|
||||||
|
.get("/auth/resources/${resourceVersion}/admin/keycloak/templates/kc-menu.html")
|
||||||
|
//.headers(UI_HEADERS ++ Map("Referer" -> "")) // TODO fix referer
|
||||||
|
.headers(UI_HEADERS)
|
||||||
|
.check(status.is(200)),
|
||||||
|
|
||||||
|
// request fonts for css also set referer
|
||||||
|
http("OpenSans-Semibold-webfont.woff")
|
||||||
|
.get("/auth/resources/${resourceVersion}/admin/keycloak/lib/patternfly/fonts/OpenSans-Semibold-webfont.woff")
|
||||||
|
.headers(UI_HEADERS)
|
||||||
|
.check(status.is(200)),
|
||||||
|
|
||||||
|
http("OpenSans-Bold-webfont.woff")
|
||||||
|
.get("/auth/resources/${resourceVersion}/admin/keycloak/lib/patternfly/fonts/OpenSans-Bold-webfont.woff")
|
||||||
|
.headers(UI_HEADERS)
|
||||||
|
.check(status.is(200)),
|
||||||
|
|
||||||
|
http("OpenSans-Light-webfont.woff")
|
||||||
|
.get("/auth/resources/${resourceVersion}/admin/keycloak/lib/patternfly/fonts/OpenSans-Light-webfont.woff")
|
||||||
|
.headers(UI_HEADERS)
|
||||||
|
.check(status.is(200))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.exitHereIfFailed
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
def openClients() : AdminConsoleScenarioBuilder = {
|
||||||
|
refreshTokenIfExpired()
|
||||||
|
chainBuilder = chainBuilder
|
||||||
|
.exec(http("Console - client-list.html")
|
||||||
|
.get("/auth/resources/${resourceVersion}/admin/keycloak/partials/client-list.html")
|
||||||
|
.headers(UI_HEADERS)
|
||||||
|
.check(status.is(200))
|
||||||
|
.resources(
|
||||||
|
http("Console REST - ${realm}")
|
||||||
|
.get("/auth/admin/realms/${realm}")
|
||||||
|
.headers(AUTHORIZATION)
|
||||||
|
.check(status.is(200)),
|
||||||
|
http("Console REST - realms")
|
||||||
|
.get("/auth/admin/realms")
|
||||||
|
.headers(AUTHORIZATION)
|
||||||
|
.check(status.is(200)),
|
||||||
|
http("Console - kc-paging.html")
|
||||||
|
.get("/auth/resources/${resourceVersion}/admin/keycloak/templates/kc-paging.html")
|
||||||
|
.headers(UI_HEADERS)
|
||||||
|
.check(status.is(200)),
|
||||||
|
http("Console REST - ${realm}/clients")
|
||||||
|
.get("/auth/admin/realms/${realm}/clients?viewableOnly=true")
|
||||||
|
.headers(AUTHORIZATION)
|
||||||
|
.check(status.is(200))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
def openCreateNewClient() : AdminConsoleScenarioBuilder = {
|
||||||
|
refreshTokenIfExpired()
|
||||||
|
chainBuilder = chainBuilder
|
||||||
|
.exec(http("Console - create-client.html")
|
||||||
|
.get("/auth/resources/${resourceVersion}/admin/keycloak/partials/create-client.html")
|
||||||
|
.headers(UI_HEADERS)
|
||||||
|
.check(status.is(200))
|
||||||
|
.resources(
|
||||||
|
http("Console REST - ${realm}/clients")
|
||||||
|
.get("/auth/admin/realms/${realm}/clients")
|
||||||
|
.headers(AUTHORIZATION)
|
||||||
|
.check(status.is(200))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
def submitNewClient() : AdminConsoleScenarioBuilder = {
|
||||||
|
refreshTokenIfExpired()
|
||||||
|
chainBuilder = chainBuilder
|
||||||
|
.exec(http("Console REST - ${realm}/clients POST")
|
||||||
|
.post("/auth/admin/realms/${realm}/clients")
|
||||||
|
.headers(AUTHORIZATION)
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.body(StringBody(
|
||||||
|
"""
|
||||||
|
{"enabled":true,"attributes":{},"redirectUris":[],"clientId":"${randomClientId}","rootUrl":"http://localhost:8081/myapp","protocol":"openid-connect"}
|
||||||
|
""".stripMargin))
|
||||||
|
.check(status.is(201), headerRegex("Location", "\\/([^\\/]+)$").saveAs("idOfClient")))
|
||||||
|
|
||||||
|
.exec(http("Console REST - ${realm}/clients/ID")
|
||||||
|
.get("/auth/admin/realms/${realm}/clients/${idOfClient}")
|
||||||
|
.headers(AUTHORIZATION)
|
||||||
|
.check(status.is(200), bodyString.saveAs("clientJson"))
|
||||||
|
.resources(
|
||||||
|
http("Console REST - ${realm}/clients")
|
||||||
|
.get("/auth/admin/realms/${realm}/clients")
|
||||||
|
.headers(AUTHORIZATION)
|
||||||
|
.check(status.is(200)),
|
||||||
|
|
||||||
|
http("Console REST - ${realm}/client-templates")
|
||||||
|
.get("/auth/admin/realms/${realm}/client-templates")
|
||||||
|
.headers(AUTHORIZATION)
|
||||||
|
.check(status.is(200)),
|
||||||
|
|
||||||
|
http("Console REST - ${realm}")
|
||||||
|
.get("/auth/admin/realms/${realm}")
|
||||||
|
.headers(AUTHORIZATION)
|
||||||
|
.check(status.is(200)),
|
||||||
|
|
||||||
|
http("Console REST - realms")
|
||||||
|
.get("/auth/admin/realms")
|
||||||
|
.headers(AUTHORIZATION)
|
||||||
|
.check(status.is(200))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
def updateClient() : AdminConsoleScenarioBuilder = {
|
||||||
|
refreshTokenIfExpired()
|
||||||
|
chainBuilder = chainBuilder.exec(s => {
|
||||||
|
s.set("updateClientJson", s("clientJson").as[String].replace("\"publicClient\":false", "\"publicClient\":true"))
|
||||||
|
})
|
||||||
|
.exec(http("Console REST - ${realm}/clients/ID PUT")
|
||||||
|
.put("/auth/admin/realms/${realm}/clients/${idOfClient}")
|
||||||
|
.headers(AUTHORIZATION)
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.body(StringBody("${updateClientJson}"))
|
||||||
|
.check(status.is(204)))
|
||||||
|
|
||||||
|
.exec(http("Console REST - ${realm}/clients/ID")
|
||||||
|
.get("/auth/admin/realms/${realm}/clients/${idOfClient}")
|
||||||
|
.headers(AUTHORIZATION)
|
||||||
|
.check(status.is(200), bodyString.saveAs("clientJson"))
|
||||||
|
.resources(
|
||||||
|
http("Console REST - ${realm}/clients")
|
||||||
|
.get("/auth/admin/realms/${realm}/clients")
|
||||||
|
.headers(AUTHORIZATION)
|
||||||
|
.check(status.is(200)),
|
||||||
|
|
||||||
|
http("Console REST - ${realm}/client-templates")
|
||||||
|
.get("/auth/admin/realms/${realm}/client-templates")
|
||||||
|
.headers(AUTHORIZATION)
|
||||||
|
.check(status.is(200)),
|
||||||
|
|
||||||
|
http("Console REST - ${realm}")
|
||||||
|
.get("/auth/admin/realms/${realm}")
|
||||||
|
.headers(AUTHORIZATION)
|
||||||
|
.check(status.is(200)),
|
||||||
|
|
||||||
|
http("Console REST - realms")
|
||||||
|
.get("/auth/admin/realms")
|
||||||
|
.headers(AUTHORIZATION)
|
||||||
|
.check(status.is(200))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
def openClientDetails() : AdminConsoleScenarioBuilder = {
|
||||||
|
refreshTokenIfExpired()
|
||||||
|
chainBuilder = chainBuilder
|
||||||
|
.exec(http("Console - client-detail.html")
|
||||||
|
.get("/auth/resources/${resourceVersion}/admin/keycloak/partials/client-detail.html")
|
||||||
|
.headers(UI_HEADERS)
|
||||||
|
.check(status.is(200))
|
||||||
|
.resources(
|
||||||
|
http("Console REST - ${realm}/client-templates")
|
||||||
|
.get("/auth/admin/realms/${realm}/client-templates")
|
||||||
|
.headers(AUTHORIZATION)
|
||||||
|
.check(status.is(200)),
|
||||||
|
|
||||||
|
http("Console REST - ${realm}")
|
||||||
|
.get("/auth/admin/realms/${realm}")
|
||||||
|
.headers(AUTHORIZATION)
|
||||||
|
.check(status.is(200)),
|
||||||
|
|
||||||
|
http("Console REST - realms")
|
||||||
|
.get("/auth/admin/realms")
|
||||||
|
.headers(AUTHORIZATION)
|
||||||
|
.check(status.is(200)),
|
||||||
|
|
||||||
|
http("Console REST - ${realm}/clients")
|
||||||
|
.get("/auth/admin/realms/${realm}/clients")
|
||||||
|
.headers(AUTHORIZATION)
|
||||||
|
.check(status.is(200)),
|
||||||
|
|
||||||
|
http("Console REST - ${realm}/clients/ID")
|
||||||
|
.get("/auth/admin/realms/${realm}/clients/${idOfClient}")
|
||||||
|
.headers(AUTHORIZATION)
|
||||||
|
.check(status.is(200)),
|
||||||
|
|
||||||
|
http("Console - kc-tabs-client.html")
|
||||||
|
.get("/auth/resources/${resourceVersion}/admin/keycloak/templates/kc-tabs-client.html")
|
||||||
|
.headers(UI_HEADERS)
|
||||||
|
.check(status.is(200))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
def openUsers() : AdminConsoleScenarioBuilder = {
|
||||||
|
refreshTokenIfExpired()
|
||||||
|
chainBuilder = chainBuilder
|
||||||
|
.exec(http("Console - user-list.html")
|
||||||
|
.get("/auth/resources/${resourceVersion}/admin/keycloak/partials/user-list.html")
|
||||||
|
.headers(UI_HEADERS)
|
||||||
|
.check(status.is(200))
|
||||||
|
.resources(
|
||||||
|
http("Console REST - realms")
|
||||||
|
.get("/auth/admin/realms")
|
||||||
|
.headers(AUTHORIZATION)
|
||||||
|
.check(status.is(200)),
|
||||||
|
http("Console REST - ${realm}")
|
||||||
|
.get("/auth/admin/realms/${realm}")
|
||||||
|
.headers(AUTHORIZATION)
|
||||||
|
.check(status.is(200)),
|
||||||
|
http("Console - kc-tabs-users.html")
|
||||||
|
.get("/auth/resources/${resourceVersion}/admin/keycloak/templates/kc-tabs-users.html")
|
||||||
|
.headers(UI_HEADERS)
|
||||||
|
.check(status.is(200))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
def viewAllUsers() : AdminConsoleScenarioBuilder = {
|
||||||
|
refreshTokenIfExpired()
|
||||||
|
chainBuilder = chainBuilder
|
||||||
|
.exec(http("Console REST - ${realm}/users")
|
||||||
|
.get("/auth/admin/realms/${realm}/users?first=0&max=20")
|
||||||
|
.headers(AUTHORIZATION)
|
||||||
|
.check(status.is(200))
|
||||||
|
)
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
def viewTenPagesOfUsers() : AdminConsoleScenarioBuilder = {
|
||||||
|
refreshTokenIfExpired()
|
||||||
|
chainBuilder = chainBuilder
|
||||||
|
.repeat(10, "i") {
|
||||||
|
exec(s => s.set("offset", s("i").as[Int] * 20))
|
||||||
|
.pause(1)
|
||||||
|
.exec(http("Console REST - ${realm}/users?first=${offset}")
|
||||||
|
.get("/auth/admin/realms/${realm}/users?first=${offset}&max=20")
|
||||||
|
.headers(AUTHORIZATION)
|
||||||
|
.check(status.is(200))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
def find20Users() : AdminConsoleScenarioBuilder = {
|
||||||
|
refreshTokenIfExpired()
|
||||||
|
chainBuilder = chainBuilder
|
||||||
|
.exec(http("Console REST - ${realm}/users?first=0&max=20&search=user")
|
||||||
|
.get("/auth/admin/realms/${realm}/users?first=0&max=20&search=user")
|
||||||
|
.headers(AUTHORIZATION)
|
||||||
|
.check(status.is(200))
|
||||||
|
)
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
def findUnlimitedUsers() : AdminConsoleScenarioBuilder = {
|
||||||
|
refreshTokenIfExpired()
|
||||||
|
chainBuilder = chainBuilder
|
||||||
|
.exec(http("Console REST - ${realm}/users?search=user")
|
||||||
|
.get("/auth/admin/realms/${realm}/users?search=user")
|
||||||
|
.headers(AUTHORIZATION)
|
||||||
|
.check(status.is(200))
|
||||||
|
)
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
def findRandomUser() : AdminConsoleScenarioBuilder = {
|
||||||
|
refreshTokenIfExpired()
|
||||||
|
chainBuilder = chainBuilder
|
||||||
|
.exec(s => s.set("randomUsername", getRandomUser()))
|
||||||
|
.exec(http("Console REST - ${realm}/users?first=0&max=20&search=USERNAME")
|
||||||
|
.get("/auth/admin/realms/${realm}/users?first=0&max=20&search=${randomUsername}")
|
||||||
|
.headers(AUTHORIZATION)
|
||||||
|
.check(status.is(200), jsonPath("$[0]['id']").saveAs("userId"))
|
||||||
|
)
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
def openUser() : AdminConsoleScenarioBuilder = {
|
||||||
|
refreshTokenIfExpired()
|
||||||
|
chainBuilder = chainBuilder
|
||||||
|
.exec(http("Console - user-detail.html")
|
||||||
|
.get("/auth/resources/${resourceVersion}/admin/keycloak/partials/user-detail.html")
|
||||||
|
.headers(UI_HEADERS)
|
||||||
|
.check(status.is(200))
|
||||||
|
.resources(
|
||||||
|
|
||||||
|
http("Console REST - realms")
|
||||||
|
.get("/auth/admin/realms")
|
||||||
|
.headers(AUTHORIZATION)
|
||||||
|
.check(status.is(200)),
|
||||||
|
|
||||||
|
http("Console REST - ${realm}")
|
||||||
|
.get("/auth/admin/realms/${realm}")
|
||||||
|
.headers(AUTHORIZATION)
|
||||||
|
.check(status.is(200)),
|
||||||
|
|
||||||
|
http("Console REST - ${realm}/users/ID")
|
||||||
|
.get("/auth/admin/realms/${realm}/users/${userId}")
|
||||||
|
.headers(AUTHORIZATION)
|
||||||
|
.check(status.is(200)),
|
||||||
|
|
||||||
|
http("Console - kc-tabs-user.html")
|
||||||
|
.get("/auth/resources/${resourceVersion}/admin/keycloak/templates/kc-tabs-user.html")
|
||||||
|
.headers(UI_HEADERS)
|
||||||
|
.check(status.is(200)),
|
||||||
|
|
||||||
|
http("Console REST - ${realm}/authentication/required-actions")
|
||||||
|
.get("/auth/admin/realms/${realm}/authentication/required-actions")
|
||||||
|
.headers(AUTHORIZATION)
|
||||||
|
.check(status.is(200)),
|
||||||
|
|
||||||
|
http("Console REST - ${realm}/attack-detection/brute-force/users/ID")
|
||||||
|
.get("/auth/admin/realms/${realm}/attack-detection/brute-force/users/${userId}")
|
||||||
|
.headers(AUTHORIZATION)
|
||||||
|
.check(status.is(200))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
def openUserCredentials() : AdminConsoleScenarioBuilder = {
|
||||||
|
refreshTokenIfExpired()
|
||||||
|
chainBuilder = chainBuilder
|
||||||
|
.exec(http("Console - user-credentials.html")
|
||||||
|
.get("/auth/resources/${resourceVersion}/admin/keycloak/partials/user-credentials.html")
|
||||||
|
.headers(UI_HEADERS)
|
||||||
|
.check(status.is(200))
|
||||||
|
.resources(
|
||||||
|
|
||||||
|
http("Console REST - ${realm}/users/ID")
|
||||||
|
.get("/auth/admin/realms/${realm}/users/${userId}")
|
||||||
|
.headers(AUTHORIZATION)
|
||||||
|
.check(status.is(200)),
|
||||||
|
|
||||||
|
http("Console REST - ${realm}")
|
||||||
|
.get("/auth/admin/realms/${realm}")
|
||||||
|
.headers(AUTHORIZATION)
|
||||||
|
.check(status.is(200)),
|
||||||
|
|
||||||
|
http("Console REST - realms")
|
||||||
|
.get("/auth/admin/realms")
|
||||||
|
.headers(AUTHORIZATION)
|
||||||
|
.check(status.is(200)),
|
||||||
|
|
||||||
|
http("Console REST - ${realm}/authentication/required-actions")
|
||||||
|
.get("/auth/admin/realms/${realm}/authentication/required-actions")
|
||||||
|
.headers(AUTHORIZATION)
|
||||||
|
.check(status.is(200))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
def setTemporaryPassword() : AdminConsoleScenarioBuilder = {
|
||||||
|
refreshTokenIfExpired()
|
||||||
|
chainBuilder = chainBuilder
|
||||||
|
.exec(http("Console REST - ${realm}/users/ID/reset-password PUT")
|
||||||
|
.put("/auth/admin/realms/${realm}/users/${userId}/reset-password")
|
||||||
|
.headers(AUTHORIZATION)
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.body(StringBody("""{"type":"password","value":"testtest","temporary":true}"""))
|
||||||
|
.check(status.is(204)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
def logout() : AdminConsoleScenarioBuilder = {
|
||||||
|
refreshTokenIfExpired()
|
||||||
|
chainBuilder = chainBuilder
|
||||||
|
.exec(http("Browser logout")
|
||||||
|
.get("/auth/realms/master/protocol/openid-connect/logout")
|
||||||
|
.headers(UI_HEADERS)
|
||||||
|
.queryParam("redirect_uri", APP_URL)
|
||||||
|
.check(status.is(302), header("Location").is(APP_URL)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
this
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,12 +1,10 @@
|
||||||
package keycloak
|
package keycloak
|
||||||
|
|
||||||
import io.gatling.core.Predef._
|
import io.gatling.core.Predef._
|
||||||
import io.gatling.core.validation.Validation
|
|
||||||
import io.gatling.http.Predef._
|
import io.gatling.http.Predef._
|
||||||
import org.jboss.perf.util.Util
|
|
||||||
|
import io.gatling.core.validation.Validation
|
||||||
import org.keycloak.performance.TestConfig
|
import org.keycloak.performance.TestConfig
|
||||||
import org.keycloak.gatling.Utils._
|
|
||||||
import SimulationsHelper._
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -14,6 +12,12 @@ import SimulationsHelper._
|
||||||
*/
|
*/
|
||||||
class AdminConsoleSimulation extends Simulation {
|
class AdminConsoleSimulation extends Simulation {
|
||||||
|
|
||||||
|
def rampDownPeriodNotReached(): Validation[Boolean] = {
|
||||||
|
System.currentTimeMillis < TestConfig.rampDownPeriodStartTime
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
println()
|
println()
|
||||||
println("Target server: " + TestConfig.serverUrisList.get(0))
|
println("Target server: " + TestConfig.serverUrisList.get(0))
|
||||||
println()
|
println()
|
||||||
|
@ -31,91 +35,72 @@ class AdminConsoleSimulation extends Simulation {
|
||||||
.acceptLanguageHeader("en-US,en;q=0.5")
|
.acceptLanguageHeader("en-US,en;q=0.5")
|
||||||
.userAgentHeader("Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:54.0) Gecko/20100101 Firefox/54.0")
|
.userAgentHeader("Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:54.0) Gecko/20100101 Firefox/54.0")
|
||||||
|
|
||||||
val adminSession = exec(s => {
|
|
||||||
val realm = TestConfig.randomRealmsIterator().next()
|
|
||||||
val serverUrl = TestConfig.serverUrisList.get(0)
|
|
||||||
s.setAll(
|
|
||||||
"keycloakServer" -> serverUrl,
|
|
||||||
"keycloakServerUrlEncoded" -> urlencode(serverUrl),
|
|
||||||
"keycloakServerRootEncoded" -> urlEncodedRoot(serverUrl),
|
|
||||||
"state" -> Util.randomUUID(),
|
|
||||||
"nonce" -> Util.randomUUID(),
|
|
||||||
"randomClientId" -> ("client_" + Util.randomUUID()),
|
|
||||||
"realm" -> realm,
|
|
||||||
"username" -> "admin",
|
|
||||||
"password" -> "admin",
|
|
||||||
"clientId" -> "security-admin-console"
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
.exitHereIfFailed
|
|
||||||
|
|
||||||
|
val adminSession = new AdminConsoleScenarioBuilder()
|
||||||
.openAdminConsoleHome()
|
.openAdminConsoleHome()
|
||||||
|
.thinkPause()
|
||||||
|
.loginThroughLoginForm()
|
||||||
|
|
||||||
|
.openRealmSettings()
|
||||||
|
|
||||||
.thinkPause()
|
.thinkPause()
|
||||||
.acsim_loginThroughLoginForm()
|
.openClients()
|
||||||
.exitHereIfFailed
|
|
||||||
|
|
||||||
.thinkPause()
|
.thinkPause()
|
||||||
.acsim_openClients()
|
.openCreateNewClient()
|
||||||
|
|
||||||
.thinkPause()
|
.thinkPause()
|
||||||
.acsim_openCreateNewClient()
|
.submitNewClient()
|
||||||
|
|
||||||
.thinkPause()
|
.thinkPause()
|
||||||
.acsim_submitNewClient()
|
.updateClient()
|
||||||
|
|
||||||
.thinkPause()
|
.thinkPause()
|
||||||
.acsim_updateClient()
|
.openClients()
|
||||||
|
|
||||||
.thinkPause()
|
.thinkPause()
|
||||||
.acsim_openClients()
|
.openClientDetails()
|
||||||
|
|
||||||
.thinkPause()
|
.thinkPause()
|
||||||
.acsim_openClientDetails()
|
.openUsers()
|
||||||
|
|
||||||
.thinkPause()
|
.thinkPause()
|
||||||
.acsim_openUsers()
|
.viewAllUsers()
|
||||||
|
|
||||||
.thinkPause()
|
.thinkPause()
|
||||||
.acsim_viewAllUsers()
|
.viewTenPagesOfUsers()
|
||||||
|
|
||||||
.thinkPause()
|
.thinkPause()
|
||||||
.acsim_viewTenPagesOfUsers()
|
.find20Users()
|
||||||
|
|
||||||
.thinkPause()
|
.thinkPause()
|
||||||
.acsim_find20Users()
|
.findUnlimitedUsers()
|
||||||
|
|
||||||
.thinkPause()
|
.thinkPause()
|
||||||
.acsim_findUnlimitedUsers()
|
.findRandomUser()
|
||||||
|
.openUser()
|
||||||
|
|
||||||
.thinkPause()
|
.thinkPause()
|
||||||
.acsim_findRandomUser()
|
.openUserCredentials()
|
||||||
|
|
||||||
.acsim_openUser()
|
|
||||||
|
|
||||||
.thinkPause()
|
.thinkPause()
|
||||||
.acsim_openUserCredentials()
|
.setTemporaryPassword()
|
||||||
|
|
||||||
.thinkPause()
|
.thinkPause()
|
||||||
.acsim_setTemporaryPassword()
|
.logout()
|
||||||
|
|
||||||
.thinkPause()
|
.thinkPause()
|
||||||
.acsim_logOut()
|
|
||||||
|
|
||||||
|
|
||||||
val adminScenario = scenario("AdminConsole")
|
val adminScenario = scenario("AdminConsole")
|
||||||
.asLongAs(s => rampDownPeriodNotReached(), null, TestConfig.rampDownASAP) {
|
.asLongAs(s => rampDownPeriodNotReached(), null, TestConfig.rampDownASAP) {
|
||||||
pace(TestConfig.pace)
|
pace(TestConfig.pace)
|
||||||
adminSession
|
adminSession.chainBuilder
|
||||||
}
|
}
|
||||||
|
|
||||||
setUp(adminScenario
|
setUp(adminScenario
|
||||||
.inject(rampUsers(TestConfig.runUsers) over TestConfig.rampUpPeriod)
|
.inject(rampUsers(TestConfig.runUsers) over TestConfig.rampUpPeriod)
|
||||||
.protocols(httpProtocol))
|
.protocols(httpProtocol))
|
||||||
|
|
||||||
|
|
||||||
def rampDownPeriodNotReached(): Validation[Boolean] = {
|
|
||||||
System.currentTimeMillis < TestConfig.rampDownPeriodStartTime
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,39 +0,0 @@
|
||||||
package keycloak
|
|
||||||
|
|
||||||
import java.time.format.DateTimeFormatter
|
|
||||||
|
|
||||||
import io.gatling.core.Predef._
|
|
||||||
import org.jboss.perf.util.Util
|
|
||||||
import org.keycloak.performance.TestConfig
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
|
||||||
*/
|
|
||||||
object AdminConsoleSimulationHelper {
|
|
||||||
|
|
||||||
val UI_HEADERS = Map(
|
|
||||||
"Accept" -> "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
|
||||||
"Upgrade-Insecure-Requests" -> "1")
|
|
||||||
|
|
||||||
val ACCEPT_JSON = Map("Accept" -> "application/json")
|
|
||||||
val ACCEPT_ALL = Map("Accept" -> "*/*")
|
|
||||||
val AUTHORIZATION = Map("Authorization" -> "Bearer ${accessToken}")
|
|
||||||
|
|
||||||
val APP_URL = "${keycloakServer}/admin/master/console/"
|
|
||||||
val DATE_FMT = DateTimeFormatter.RFC_1123_DATE_TIME
|
|
||||||
|
|
||||||
|
|
||||||
def getRandomUser() : String = {
|
|
||||||
"user_" + (Util.random.nextDouble() * TestConfig.usersPerRealm).toInt
|
|
||||||
}
|
|
||||||
|
|
||||||
def needTokenRefresh(sess: Session): Boolean = {
|
|
||||||
val lastRefresh = sess("accessTokenRefreshTime").as[Long]
|
|
||||||
|
|
||||||
// 5 seconds before expiry is time to refresh
|
|
||||||
lastRefresh + sess("expiresIn").as[String].toInt * 1000 - 5000 < System.currentTimeMillis() ||
|
|
||||||
// or if refreshTokenPeriod is set force refresh even if not necessary
|
|
||||||
(TestConfig.refreshTokenPeriod > 0 &&
|
|
||||||
lastRefresh + TestConfig.refreshTokenPeriod * 1000 < System.currentTimeMillis())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,175 @@
|
||||||
|
package keycloak
|
||||||
|
|
||||||
|
import io.gatling.core.Predef._
|
||||||
|
import io.gatling.http.Predef._
|
||||||
|
import org.keycloak.gatling.Predef._
|
||||||
|
import keycloak.BasicOIDCScenarioBuilder._
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
|
|
||||||
|
import io.gatling.core.pause.Normal
|
||||||
|
import io.gatling.core.session.Session
|
||||||
|
import io.gatling.core.structure.ChainBuilder
|
||||||
|
import io.gatling.core.validation.Validation
|
||||||
|
import org.jboss.perf.util.Util
|
||||||
|
import org.jboss.perf.util.Util.randomUUID
|
||||||
|
import org.keycloak.adapters.spi.HttpFacade.Cookie
|
||||||
|
import org.keycloak.gatling.AuthorizeAction
|
||||||
|
import org.keycloak.performance.TestConfig
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||||
|
*/
|
||||||
|
object BasicOIDCScenarioBuilder {
|
||||||
|
|
||||||
|
val BASE_URL = "${keycloakServer}/realms/${realm}"
|
||||||
|
val LOGIN_ENDPOINT = BASE_URL + "/protocol/openid-connect/auth"
|
||||||
|
val LOGOUT_ENDPOINT = BASE_URL + "/protocol/openid-connect/logout"
|
||||||
|
|
||||||
|
// Specify defaults for http requests
|
||||||
|
val UI_HEADERS = Map(
|
||||||
|
"Accept" -> "text/html,application/xhtml+xml,application/xml",
|
||||||
|
"Accept-Encoding" -> "gzip, deflate",
|
||||||
|
"Accept-Language" -> "en-US,en;q=0.5",
|
||||||
|
"User-Agent" -> "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0")
|
||||||
|
|
||||||
|
val ACCEPT_JSON = Map("Accept" -> "application/json")
|
||||||
|
val ACCEPT_ALL = Map("Accept" -> "*/*")
|
||||||
|
|
||||||
|
def downCounterAboveZero(session: Session, attrName: String): Validation[Boolean] = {
|
||||||
|
val missCounter = session.attributes.get(attrName) match {
|
||||||
|
case Some(result) => result.asInstanceOf[AtomicInteger]
|
||||||
|
case None => new AtomicInteger(0)
|
||||||
|
}
|
||||||
|
missCounter.getAndDecrement() > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
def rampDownPeriodNotReached(): Validation[Boolean] = {
|
||||||
|
System.currentTimeMillis < TestConfig.rampDownPeriodStartTime
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class BasicOIDCScenarioBuilder {
|
||||||
|
|
||||||
|
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 clientInfo = TestConfig.getConfidentialClientsIterator(realm).next()
|
||||||
|
|
||||||
|
AuthorizeAction.init(s)
|
||||||
|
.setAll("keycloakServer" -> TestConfig.serverUrisIterator.next(),
|
||||||
|
"state" -> randomUUID(),
|
||||||
|
"wrongPasswordCount" -> new AtomicInteger(TestConfig.badLoginAttempts),
|
||||||
|
"refreshTokenCount" -> new AtomicInteger(TestConfig.refreshTokenCount),
|
||||||
|
"realm" -> realm,
|
||||||
|
"username" -> userInfo.username,
|
||||||
|
"password" -> userInfo.password,
|
||||||
|
"clientId" -> clientInfo.clientId,
|
||||||
|
"secret" -> clientInfo.secret,
|
||||||
|
"appUrl" -> clientInfo.appUrl
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.exitHereIfFailed
|
||||||
|
|
||||||
|
def thinkPause() : BasicOIDCScenarioBuilder = {
|
||||||
|
chainBuilder = chainBuilder.pause(TestConfig.userThinkTime, Normal(TestConfig.userThinkTime * 0.2))
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
def thinkPause(builder: ChainBuilder) : ChainBuilder = {
|
||||||
|
builder.pause(TestConfig.userThinkTime, Normal(TestConfig.userThinkTime * 0.2))
|
||||||
|
}
|
||||||
|
|
||||||
|
def newThinkPause() : ChainBuilder = {
|
||||||
|
pause(TestConfig.userThinkTime, Normal(TestConfig.userThinkTime * 0.2))
|
||||||
|
}
|
||||||
|
|
||||||
|
def browserOpensLoginPage() : BasicOIDCScenarioBuilder = {
|
||||||
|
chainBuilder = chainBuilder
|
||||||
|
.exec(http("Browser to Log In Endpoint")
|
||||||
|
.get(LOGIN_ENDPOINT)
|
||||||
|
.headers(UI_HEADERS)
|
||||||
|
.queryParam("login", "true")
|
||||||
|
.queryParam("response_type", "code")
|
||||||
|
.queryParam("client_id", "${clientId}")
|
||||||
|
.queryParam("state", "${state}")
|
||||||
|
.queryParam("redirect_uri", "${appUrl}")
|
||||||
|
.check(status.is(200), regex("action=\"([^\"]*)\"").find.transform(_.replaceAll("&", "&")).saveAs("login-form-uri")))
|
||||||
|
// 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
|
||||||
|
.exitHereIfFailed
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
def browserPostsWrongCredentials() : BasicOIDCScenarioBuilder = {
|
||||||
|
chainBuilder = chainBuilder
|
||||||
|
.asLongAs(s => downCounterAboveZero(s, "wrongPasswordCount")) {
|
||||||
|
var c = exec(http("Browser posts wrong credentials")
|
||||||
|
.post("${login-form-uri}")
|
||||||
|
.headers(UI_HEADERS)
|
||||||
|
.formParam("username", "${username}")
|
||||||
|
.formParam("password", _ => Util.randomString(10))
|
||||||
|
.formParam("login", "Log in")
|
||||||
|
.check(status.is(200), regex("action=\"([^\"]*)\"").find.transform(_.replaceAll("&", "&")).saveAs("login-form-uri")))
|
||||||
|
.exitHereIfFailed
|
||||||
|
|
||||||
|
// make sure to call the right version of thinkPause - one that takes chainBuilder as argument
|
||||||
|
// - because this is a nested chainBuilder - not the same as chainBuilder field
|
||||||
|
thinkPause(c)
|
||||||
|
}
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
def browserPostsCorrectCredentials() : BasicOIDCScenarioBuilder = {
|
||||||
|
chainBuilder = chainBuilder
|
||||||
|
.exec(http("Browser posts correct credentials")
|
||||||
|
.post("${login-form-uri}")
|
||||||
|
.headers(UI_HEADERS)
|
||||||
|
.formParam("username", "${username}")
|
||||||
|
.formParam("password", "${password}")
|
||||||
|
.formParam("login", "Log in")
|
||||||
|
.check(status.is(302), header("Location").saveAs("login-redirect")))
|
||||||
|
.exitHereIfFailed
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
def adapterExchangesCodeForTokens() : BasicOIDCScenarioBuilder = {
|
||||||
|
chainBuilder = chainBuilder
|
||||||
|
.exec(oauth("Adapter exchanges code for tokens")
|
||||||
|
.authorize("${login-redirect}",
|
||||||
|
session => List(new Cookie("OAuth_Token_Request_State", session("state").as[String], 0, null, null)))
|
||||||
|
.authServerUrl("${keycloakServer}")
|
||||||
|
.resource("${clientId}")
|
||||||
|
.clientCredentials("${secret}")
|
||||||
|
.realm("${realm}")
|
||||||
|
//.realmKey(Loader.realmRepresentation.getPublicKey)
|
||||||
|
)
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
def refreshTokenSeveralTimes() : BasicOIDCScenarioBuilder = {
|
||||||
|
chainBuilder = chainBuilder
|
||||||
|
.asLongAs(s => downCounterAboveZero(s, "refreshTokenCount")) {
|
||||||
|
// make sure to call newThinkPause rather than thinkPause
|
||||||
|
newThinkPause()
|
||||||
|
.exec(oauth("Adapter refreshes token").refresh())
|
||||||
|
}
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
def logout() : BasicOIDCScenarioBuilder = {
|
||||||
|
chainBuilder = chainBuilder
|
||||||
|
.exec(http("Browser logout")
|
||||||
|
.get(LOGOUT_ENDPOINT)
|
||||||
|
.headers(UI_HEADERS)
|
||||||
|
.queryParam("redirect_uri", "${appUrl}")
|
||||||
|
.check(status.is(302), header("Location").is("${appUrl}")))
|
||||||
|
this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
package keycloak
|
||||||
|
|
||||||
|
import io.gatling.core.Predef._
|
||||||
|
import io.gatling.http.Predef._
|
||||||
|
import keycloak.BasicOIDCScenarioBuilder._
|
||||||
|
|
||||||
|
import org.keycloak.performance.TestConfig
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Radim Vansa <rvansa@redhat.com>
|
||||||
|
* @author Marko Strukelj <mstrukel@redhat.com>
|
||||||
|
*/
|
||||||
|
class BasicOIDCSimulation extends Simulation {
|
||||||
|
|
||||||
|
println()
|
||||||
|
println("Target servers: " + TestConfig.serverUrisList)
|
||||||
|
println()
|
||||||
|
|
||||||
|
println("Using test parameters:\n" + TestConfig.toStringCommonTestParameters);
|
||||||
|
println(" refreshTokenCount: " + TestConfig.refreshTokenCount)
|
||||||
|
println(" badLoginAttempts: " + TestConfig.badLoginAttempts)
|
||||||
|
println()
|
||||||
|
println("Using dataset properties:\n" + TestConfig.toStringDatasetProperties)
|
||||||
|
|
||||||
|
|
||||||
|
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")
|
||||||
|
.asLongAs(s => rampDownPeriodNotReached(), null, TestConfig.rampDownASAP) {
|
||||||
|
pace(TestConfig.pace)
|
||||||
|
userSession.chainBuilder
|
||||||
|
}
|
||||||
|
|
||||||
|
setUp(usersScenario
|
||||||
|
.inject(rampUsers(TestConfig.runUsers) over TestConfig.rampUpPeriod)
|
||||||
|
.protocols(httpDefault))
|
||||||
|
|
||||||
|
}
|
|
@ -1,161 +0,0 @@
|
||||||
package keycloak
|
|
||||||
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
|
||||||
|
|
||||||
import io.gatling.core.Predef._
|
|
||||||
import io.gatling.core.pause.Normal
|
|
||||||
import io.gatling.core.session._
|
|
||||||
import io.gatling.core.validation.Validation
|
|
||||||
import io.gatling.http.Predef._
|
|
||||||
import org.jboss.perf.util.Util
|
|
||||||
import org.keycloak.adapters.spi.HttpFacade.Cookie
|
|
||||||
import org.keycloak.gatling.AuthorizeAction
|
|
||||||
import org.keycloak.gatling.Predef._
|
|
||||||
import org.keycloak.performance.TestConfig
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Radim Vansa <rvansa@redhat.com>
|
|
||||||
* @author Marko Strukelj <mstrukel@redhat.com>
|
|
||||||
*/
|
|
||||||
class DefaultSimulation extends Simulation {
|
|
||||||
|
|
||||||
val BASE_URL = "${keycloakServer}/realms/${realm}"
|
|
||||||
val LOGIN_ENDPOINT = BASE_URL + "/protocol/openid-connect/auth"
|
|
||||||
val LOGOUT_ENDPOINT = BASE_URL + "/protocol/openid-connect/logout"
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
println()
|
|
||||||
println("Target servers: " + TestConfig.serverUrisList)
|
|
||||||
println()
|
|
||||||
|
|
||||||
println("Using test parameters:\n" + TestConfig.toStringCommonTestParameters);
|
|
||||||
println(" refreshTokenCount: " + TestConfig.refreshTokenCount)
|
|
||||||
println(" badLoginAttempts: " + TestConfig.badLoginAttempts)
|
|
||||||
println()
|
|
||||||
println("Using dataset properties:\n" + TestConfig.toStringDatasetProperties)
|
|
||||||
|
|
||||||
|
|
||||||
val httpDefault = http
|
|
||||||
.acceptHeader("application/json")
|
|
||||||
.disableFollowRedirect
|
|
||||||
.inferHtmlResources
|
|
||||||
//.baseURL(SERVER_URI)
|
|
||||||
|
|
||||||
// Specify defaults for http requests
|
|
||||||
val UI_HEADERS = Map(
|
|
||||||
"Accept" -> "text/html,application/xhtml+xml,application/xml",
|
|
||||||
"Accept-Encoding" -> "gzip, deflate",
|
|
||||||
"Accept-Language" -> "en-US,en;q=0.5",
|
|
||||||
"User-Agent" -> "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0")
|
|
||||||
|
|
||||||
val ACCEPT_JSON = Map("Accept" -> "application/json")
|
|
||||||
val ACCEPT_ALL = Map("Accept" -> "*/*")
|
|
||||||
|
|
||||||
val userSession = exec(s => {
|
|
||||||
// initialize session with host, user, client app, login failure ratio ...
|
|
||||||
val realm = TestConfig.randomRealmsIterator().next()
|
|
||||||
val userInfo = TestConfig.getUsersIterator(realm).next()
|
|
||||||
val clientInfo = TestConfig.getConfidentialClientsIterator(realm).next()
|
|
||||||
|
|
||||||
AuthorizeAction.init(s)
|
|
||||||
.setAll("keycloakServer" -> TestConfig.serverUrisIterator.next(),
|
|
||||||
"state" -> Util.randomUUID(),
|
|
||||||
"wrongPasswordCount" -> new AtomicInteger(TestConfig.badLoginAttempts),
|
|
||||||
"refreshTokenCount" -> new AtomicInteger(TestConfig.refreshTokenCount),
|
|
||||||
"realm" -> realm,
|
|
||||||
"username" -> userInfo.username,
|
|
||||||
"password" -> userInfo.password,
|
|
||||||
"clientId" -> clientInfo.clientId,
|
|
||||||
"secret" -> clientInfo.secret,
|
|
||||||
"appUrl" -> clientInfo.appUrl
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.exitHereIfFailed
|
|
||||||
.exec(http("Browser to Log In Endpoint")
|
|
||||||
.get(LOGIN_ENDPOINT)
|
|
||||||
.headers(UI_HEADERS)
|
|
||||||
.queryParam("login", "true")
|
|
||||||
.queryParam("response_type", "code")
|
|
||||||
.queryParam("client_id", "${clientId}")
|
|
||||||
.queryParam("state", "${state}")
|
|
||||||
.queryParam("redirect_uri", "${appUrl}")
|
|
||||||
.check(status.is(200), regex("action=\"([^\"]*)\"").find.transform(_.replaceAll("&", "&")).saveAs("login-form-uri")))
|
|
||||||
.exitHereIfFailed
|
|
||||||
.pause(TestConfig.userThinkTime, Normal(TestConfig.userThinkTime * 0.2))
|
|
||||||
|
|
||||||
.asLongAs(s => downCounterAboveZero(s, "wrongPasswordCount")) {
|
|
||||||
exec(http("Browser posts wrong credentials")
|
|
||||||
.post("${login-form-uri}")
|
|
||||||
.headers(UI_HEADERS)
|
|
||||||
.formParam("username", "${username}")
|
|
||||||
.formParam("password", _ => Util.randomString(10))
|
|
||||||
.formParam("login", "Log in")
|
|
||||||
.check(status.is(200), regex("action=\"([^\"]*)\"").find.transform(_.replaceAll("&", "&")).saveAs("login-form-uri")))
|
|
||||||
.exitHereIfFailed
|
|
||||||
.pause(TestConfig.userThinkTime, Normal(TestConfig.userThinkTime * 0.2))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Successful login
|
|
||||||
.exec(http("Browser posts correct credentials")
|
|
||||||
.post("${login-form-uri}")
|
|
||||||
.headers(UI_HEADERS)
|
|
||||||
.formParam("username", "${username}")
|
|
||||||
.formParam("password", "${password}")
|
|
||||||
.formParam("login", "Log in")
|
|
||||||
.check(status.is(302), header("Location").saveAs("login-redirect")))
|
|
||||||
.exitHereIfFailed
|
|
||||||
|
|
||||||
|
|
||||||
// Now act as client adapter - exchange code for keys
|
|
||||||
.exec(oauth("Adapter exchanges code for tokens")
|
|
||||||
.authorize("${login-redirect}",
|
|
||||||
session => List(new Cookie("OAuth_Token_Request_State", session("state").as[String], 0, null, null)))
|
|
||||||
.authServerUrl("${keycloakServer}")
|
|
||||||
.resource("${clientId}")
|
|
||||||
.clientCredentials("${secret}")
|
|
||||||
.realm("${realm}")
|
|
||||||
//.realmKey(Loader.realmRepresentation.getPublicKey)
|
|
||||||
)
|
|
||||||
|
|
||||||
// Refresh token several times
|
|
||||||
.asLongAs(s => downCounterAboveZero(s, "refreshTokenCount")) {
|
|
||||||
pause(TestConfig.refreshTokenPeriod, Normal(TestConfig.refreshTokenPeriod * 0.2))
|
|
||||||
.exec(oauth("Adapter refreshes token").refresh())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Logout
|
|
||||||
.pause(TestConfig.userThinkTime, Normal(TestConfig.userThinkTime * 0.2))
|
|
||||||
.exec(http("Browser logout")
|
|
||||||
.get(LOGOUT_ENDPOINT)
|
|
||||||
.headers(UI_HEADERS)
|
|
||||||
.queryParam("redirect_uri", "${appUrl}")
|
|
||||||
.check(status.is(302), header("Location").is("${appUrl}")))
|
|
||||||
|
|
||||||
val usersScenario = scenario("users")
|
|
||||||
.asLongAs(s => rampDownPeriodNotReached(), null, TestConfig.rampDownASAP) {
|
|
||||||
pace(TestConfig.pace)
|
|
||||||
userSession
|
|
||||||
}
|
|
||||||
|
|
||||||
setUp(usersScenario
|
|
||||||
.inject(rampUsers(TestConfig.runUsers) over TestConfig.rampUpPeriod)
|
|
||||||
.protocols(httpDefault))
|
|
||||||
|
|
||||||
//
|
|
||||||
// Function definitions
|
|
||||||
//
|
|
||||||
|
|
||||||
def downCounterAboveZero(session: Session, attrName: String): Validation[Boolean] = {
|
|
||||||
val missCounter = session.attributes.get(attrName) match {
|
|
||||||
case Some(result) => result.asInstanceOf[AtomicInteger]
|
|
||||||
case None => new AtomicInteger(0)
|
|
||||||
}
|
|
||||||
missCounter.getAndDecrement() > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
def rampDownPeriodNotReached(): Validation[Boolean] = {
|
|
||||||
System.currentTimeMillis < TestConfig.rampDownPeriodStartTime
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,557 +0,0 @@
|
||||||
package keycloak
|
|
||||||
|
|
||||||
import java.time.ZonedDateTime
|
|
||||||
|
|
||||||
import io.gatling.core.Predef._
|
|
||||||
import io.gatling.http.Predef._
|
|
||||||
|
|
||||||
import io.gatling.core.pause.Normal
|
|
||||||
import io.gatling.core.structure.ChainBuilder
|
|
||||||
import keycloak.AdminConsoleSimulationHelper._
|
|
||||||
|
|
||||||
import org.keycloak.performance.TestConfig
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
|
||||||
*/
|
|
||||||
object SimulationsHelper {
|
|
||||||
|
|
||||||
implicit class SimulationsChainBuilderExtras(val builder: ChainBuilder) {
|
|
||||||
|
|
||||||
def acsim_refreshTokenIfExpired() : ChainBuilder = {
|
|
||||||
builder
|
|
||||||
.doIf(s => needTokenRefresh(s)) {
|
|
||||||
exec(http("JS Adapter Token - Refresh tokens")
|
|
||||||
.post("/auth/realms/master/protocol/openid-connect/token")
|
|
||||||
.headers(ACCEPT_ALL)
|
|
||||||
.formParam("grant_type", "refresh_token")
|
|
||||||
.formParam("refresh_token", "${refreshToken}")
|
|
||||||
.formParam("client_id", "security-admin-console")
|
|
||||||
.check(status.is(200),
|
|
||||||
jsonPath("$.access_token").saveAs("accessToken"),
|
|
||||||
jsonPath("$.refresh_token").saveAs("refreshToken"),
|
|
||||||
jsonPath("$.expires_in").saveAs("expiresIn"),
|
|
||||||
header("Date").saveAs("tokenTime")))
|
|
||||||
|
|
||||||
.exec(s => {
|
|
||||||
s.set("accessTokenRefreshTime", ZonedDateTime.parse(s("tokenTime").as[String], DATE_FMT).toEpochSecond * 1000)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def openAdminConsoleHome() : ChainBuilder = {
|
|
||||||
builder
|
|
||||||
.exec(http("Console Home")
|
|
||||||
.get("/auth/admin/")
|
|
||||||
.headers(UI_HEADERS)
|
|
||||||
.check(status.is(302))
|
|
||||||
.resources(
|
|
||||||
http("Console Redirect")
|
|
||||||
.get("/auth/admin/master/console/")
|
|
||||||
.headers(UI_HEADERS)
|
|
||||||
.check(status.is(200), regex("<link.+\\/resources\\/([^\\/]+).+>").saveAs("resourceVersion")),
|
|
||||||
http("Console REST - Config")
|
|
||||||
.get("/auth/admin/master/console/config")
|
|
||||||
.headers(ACCEPT_JSON)
|
|
||||||
.check(status.is(200))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
def acsim_loginThroughLoginForm() : ChainBuilder = {
|
|
||||||
builder
|
|
||||||
.exec(http("JS Adapter Auth - Login Form Redirect")
|
|
||||||
.get("/auth/realms/master/protocol/openid-connect/auth?client_id=security-admin-console&redirect_uri=${keycloakServerUrlEncoded}%2Fadmin%2Fmaster%2Fconsole%2F&state=${state}&nonce=${nonce}&response_mode=fragment&response_type=code&scope=openid")
|
|
||||||
.headers(UI_HEADERS)
|
|
||||||
.check(status.is(200), regex("action=\"([^\"]*)\"").find.transform(_.replaceAll("&", "&")).saveAs("login-form-uri")))
|
|
||||||
.exitHereIfFailed
|
|
||||||
.thinkPause()
|
|
||||||
// Successful login
|
|
||||||
.exec(http("Login Form - Submit Correct Credentials")
|
|
||||||
.post("${login-form-uri}")
|
|
||||||
.formParam("username", "${username}")
|
|
||||||
.formParam("password", "${password}")
|
|
||||||
.formParam("login", "Log in")
|
|
||||||
.check(status.is(302),
|
|
||||||
header("Location").saveAs("login-redirect"),
|
|
||||||
headerRegex("Location", "code=([^&]+)").saveAs("code")))
|
|
||||||
// TODO store AUTH_SESSION_ID cookie for use with oauth.authorize?
|
|
||||||
.exitHereIfFailed
|
|
||||||
.exec(http("Console Redirect")
|
|
||||||
.get("/auth/admin/master/console/")
|
|
||||||
.headers(UI_HEADERS)
|
|
||||||
.check(status.is(200))
|
|
||||||
.resources(
|
|
||||||
http("Console REST - Config")
|
|
||||||
.get("/auth/admin/master/console/config")
|
|
||||||
.headers(ACCEPT_JSON)
|
|
||||||
.check(status.is(200)),
|
|
||||||
|
|
||||||
http("JS Adapter Token - Exchange code for tokens")
|
|
||||||
.post("/auth/realms/master/protocol/openid-connect/token")
|
|
||||||
.headers(ACCEPT_ALL)
|
|
||||||
.formParam("code", "${code}")
|
|
||||||
.formParam("grant_type", "authorization_code")
|
|
||||||
.formParam("client_id", "security-admin-console")
|
|
||||||
.formParam("redirect_uri", APP_URL)
|
|
||||||
.check(status.is(200),
|
|
||||||
jsonPath("$.access_token").saveAs("accessToken"),
|
|
||||||
jsonPath("$.refresh_token").saveAs("refreshToken"),
|
|
||||||
jsonPath("$.expires_in").saveAs("expiresIn"),
|
|
||||||
header("Date").saveAs("tokenTime")),
|
|
||||||
|
|
||||||
http("Console REST - messages.json")
|
|
||||||
.get("/auth/admin/master/console/messages.json?lang=en")
|
|
||||||
.headers(ACCEPT_JSON)
|
|
||||||
.check(status.is(200)),
|
|
||||||
|
|
||||||
// iframe status listener
|
|
||||||
// TODO: properly set Referer
|
|
||||||
http("IFrame Status Init")
|
|
||||||
.get("/auth/realms/master/protocol/openid-connect/login-status-iframe.html/init?client_id=security-admin-console&origin=${keycloakServerRootEncoded}") // ${keycloakServerUrlEncoded}
|
|
||||||
.headers(ACCEPT_ALL) // ++ Map("Referer" -> "/auth/realms/master/protocol/openid-connect/login-status-iframe.html?version=3.3.0.cr1-201708011508") ${resourceVersion}
|
|
||||||
.check(status.is(204))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.exec(s => {
|
|
||||||
// How to not have to duplicate this block of code?
|
|
||||||
s.set("accessTokenRefreshTime", ZonedDateTime.parse(s("tokenTime").as[String], DATE_FMT).toEpochSecond * 1000)
|
|
||||||
})
|
|
||||||
.exec(http("Console REST - whoami")
|
|
||||||
.get("/auth/admin/master/console/whoami")
|
|
||||||
.headers(ACCEPT_JSON ++ AUTHORIZATION)
|
|
||||||
.check(status.is(200)))
|
|
||||||
|
|
||||||
.exec(http("Console REST - realms")
|
|
||||||
.get("/auth/admin/realms")
|
|
||||||
.headers(AUTHORIZATION)
|
|
||||||
.check(status.is(200)))
|
|
||||||
|
|
||||||
.exec(http("Console REST - serverinfo")
|
|
||||||
.get("/auth/admin/serverinfo")
|
|
||||||
.headers(AUTHORIZATION)
|
|
||||||
.check(status.is(200)))
|
|
||||||
|
|
||||||
.exec(http("Console REST - realms")
|
|
||||||
.get("/auth/admin/realms")
|
|
||||||
.headers(AUTHORIZATION)
|
|
||||||
.check(status.is(200)))
|
|
||||||
|
|
||||||
.exec(http("Console REST - ${realm}")
|
|
||||||
.get("/auth/admin/realms/${realm}")
|
|
||||||
.headers(AUTHORIZATION)
|
|
||||||
.check(status.is(200)))
|
|
||||||
|
|
||||||
.exec(http("Console REST - realms")
|
|
||||||
.get("/auth/admin/realms")
|
|
||||||
.headers(AUTHORIZATION)
|
|
||||||
.check(status.is(200)))
|
|
||||||
|
|
||||||
// DO NOT forget the leading dot, or the wrong ScenarioBuilder will be returned
|
|
||||||
.acsim_openRealmSettings()
|
|
||||||
}
|
|
||||||
|
|
||||||
def acsim_openRealmSettings() : ChainBuilder = {
|
|
||||||
builder
|
|
||||||
.acsim_refreshTokenIfExpired()
|
|
||||||
.exec(http("Console Realm Settings")
|
|
||||||
.get("/auth/resources/${resourceVersion}/admin/keycloak/partials/realm-detail.html")
|
|
||||||
.headers(UI_HEADERS)
|
|
||||||
.check(status.is(200))
|
|
||||||
.resources(
|
|
||||||
http("Console REST - ${realm}")
|
|
||||||
.get("/auth/admin/realms/${realm}")
|
|
||||||
.headers(AUTHORIZATION)
|
|
||||||
.check(status.is(200)),
|
|
||||||
|
|
||||||
http("Console REST - realms")
|
|
||||||
.get("/auth/admin/realms")
|
|
||||||
.headers(AUTHORIZATION)
|
|
||||||
.check(status.is(200)),
|
|
||||||
|
|
||||||
http("Console REST - realms")
|
|
||||||
.get("/auth/admin/realms")
|
|
||||||
.headers(AUTHORIZATION)
|
|
||||||
.check(status.is(200)),
|
|
||||||
|
|
||||||
http("Console - kc-tabs-realm.html")
|
|
||||||
.get("/auth/resources/${resourceVersion}/admin/keycloak/templates/kc-tabs-realm.html")
|
|
||||||
//.headers(UI_HEADERS ++ Map("Referer" -> "")) // TODO fix referer
|
|
||||||
.headers(UI_HEADERS)
|
|
||||||
.check(status.is(200)),
|
|
||||||
|
|
||||||
http("Console - kc-menu.html")
|
|
||||||
.get("/auth/resources/${resourceVersion}/admin/keycloak/templates/kc-menu.html")
|
|
||||||
//.headers(UI_HEADERS ++ Map("Referer" -> "")) // TODO fix referer
|
|
||||||
.headers(UI_HEADERS)
|
|
||||||
.check(status.is(200)),
|
|
||||||
|
|
||||||
// request fonts for css also set referer
|
|
||||||
http("OpenSans-Semibold-webfont.woff")
|
|
||||||
.get("/auth/resources/${resourceVersion}/admin/keycloak/lib/patternfly/fonts/OpenSans-Semibold-webfont.woff")
|
|
||||||
.headers(UI_HEADERS)
|
|
||||||
.check(status.is(200)),
|
|
||||||
|
|
||||||
http("OpenSans-Bold-webfont.woff")
|
|
||||||
.get("/auth/resources/${resourceVersion}/admin/keycloak/lib/patternfly/fonts/OpenSans-Bold-webfont.woff")
|
|
||||||
.headers(UI_HEADERS)
|
|
||||||
.check(status.is(200)),
|
|
||||||
|
|
||||||
http("OpenSans-Light-webfont.woff")
|
|
||||||
.get("/auth/resources/${resourceVersion}/admin/keycloak/lib/patternfly/fonts/OpenSans-Light-webfont.woff")
|
|
||||||
.headers(UI_HEADERS)
|
|
||||||
.check(status.is(200))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
def acsim_openClients() : ChainBuilder = {
|
|
||||||
builder
|
|
||||||
.acsim_refreshTokenIfExpired()
|
|
||||||
.exec(http("Console - client-list.html")
|
|
||||||
.get("/auth/resources/${resourceVersion}/admin/keycloak/partials/client-list.html")
|
|
||||||
.headers(UI_HEADERS)
|
|
||||||
.check(status.is(200))
|
|
||||||
.resources(
|
|
||||||
http("Console REST - ${realm}")
|
|
||||||
.get("/auth/admin/realms/${realm}")
|
|
||||||
.headers(AUTHORIZATION)
|
|
||||||
.check(status.is(200)),
|
|
||||||
http("Console REST - realms")
|
|
||||||
.get("/auth/admin/realms")
|
|
||||||
.headers(AUTHORIZATION)
|
|
||||||
.check(status.is(200)),
|
|
||||||
http("Console - kc-paging.html")
|
|
||||||
.get("/auth/resources/${resourceVersion}/admin/keycloak/templates/kc-paging.html")
|
|
||||||
.headers(UI_HEADERS)
|
|
||||||
.check(status.is(200)),
|
|
||||||
http("Console REST - ${realm}/clients")
|
|
||||||
.get("/auth/admin/realms/${realm}/clients?viewableOnly=true")
|
|
||||||
.headers(AUTHORIZATION)
|
|
||||||
.check(status.is(200))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
def acsim_openCreateNewClient() : ChainBuilder = {
|
|
||||||
builder
|
|
||||||
.acsim_refreshTokenIfExpired()
|
|
||||||
.exec(http("Console - create-client.html")
|
|
||||||
.get("/auth/resources/${resourceVersion}/admin/keycloak/partials/create-client.html")
|
|
||||||
.headers(UI_HEADERS)
|
|
||||||
.check(status.is(200))
|
|
||||||
.resources(
|
|
||||||
http("Console REST - ${realm}/clients")
|
|
||||||
.get("/auth/admin/realms/${realm}/clients")
|
|
||||||
.headers(AUTHORIZATION)
|
|
||||||
.check(status.is(200))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
def acsim_submitNewClient() : ChainBuilder = {
|
|
||||||
builder
|
|
||||||
.acsim_refreshTokenIfExpired()
|
|
||||||
.exec(http("Console REST - ${realm}/clients POST")
|
|
||||||
.post("/auth/admin/realms/${realm}/clients")
|
|
||||||
.headers(AUTHORIZATION)
|
|
||||||
.header("Content-Type", "application/json")
|
|
||||||
.body(StringBody("""
|
|
||||||
{"enabled":true,"attributes":{},"redirectUris":[],"clientId":"${randomClientId}","rootUrl":"http://localhost:8081/myapp","protocol":"openid-connect"}
|
|
||||||
""".stripMargin))
|
|
||||||
.check(status.is(201), headerRegex("Location", "\\/([^\\/]+)$").saveAs("idOfClient")))
|
|
||||||
|
|
||||||
.exec(http("Console REST - ${realm}/clients/ID")
|
|
||||||
.get("/auth/admin/realms/${realm}/clients/${idOfClient}")
|
|
||||||
.headers(AUTHORIZATION)
|
|
||||||
.check(status.is(200), bodyString.saveAs("clientJson"))
|
|
||||||
.resources(
|
|
||||||
http("Console REST - ${realm}/clients")
|
|
||||||
.get("/auth/admin/realms/${realm}/clients")
|
|
||||||
.headers(AUTHORIZATION)
|
|
||||||
.check(status.is(200)),
|
|
||||||
|
|
||||||
http("Console REST - ${realm}/client-templates")
|
|
||||||
.get("/auth/admin/realms/${realm}/client-templates")
|
|
||||||
.headers(AUTHORIZATION)
|
|
||||||
.check(status.is(200)),
|
|
||||||
|
|
||||||
http("Console REST - ${realm}")
|
|
||||||
.get("/auth/admin/realms/${realm}")
|
|
||||||
.headers(AUTHORIZATION)
|
|
||||||
.check(status.is(200)),
|
|
||||||
|
|
||||||
http("Console REST - realms")
|
|
||||||
.get("/auth/admin/realms")
|
|
||||||
.headers(AUTHORIZATION)
|
|
||||||
.check(status.is(200))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
def acsim_updateClient() : ChainBuilder = {
|
|
||||||
builder
|
|
||||||
.acsim_refreshTokenIfExpired()
|
|
||||||
.exec(s => {
|
|
||||||
s.set("updateClientJson", s("clientJson").as[String].replace("\"publicClient\":false", "\"publicClient\":true"))
|
|
||||||
})
|
|
||||||
.exec(http("Console REST - ${realm}/clients/ID PUT")
|
|
||||||
.put("/auth/admin/realms/${realm}/clients/${idOfClient}")
|
|
||||||
.headers(AUTHORIZATION)
|
|
||||||
.header("Content-Type", "application/json")
|
|
||||||
.body(StringBody("${updateClientJson}"))
|
|
||||||
.check(status.is(204)))
|
|
||||||
|
|
||||||
.exec(http("Console REST - ${realm}/clients/ID")
|
|
||||||
.get("/auth/admin/realms/${realm}/clients/${idOfClient}")
|
|
||||||
.headers(AUTHORIZATION)
|
|
||||||
.check(status.is(200), bodyString.saveAs("clientJson"))
|
|
||||||
.resources(
|
|
||||||
http("Console REST - ${realm}/clients")
|
|
||||||
.get("/auth/admin/realms/${realm}/clients")
|
|
||||||
.headers(AUTHORIZATION)
|
|
||||||
.check(status.is(200)),
|
|
||||||
|
|
||||||
http("Console REST - ${realm}/client-templates")
|
|
||||||
.get("/auth/admin/realms/${realm}/client-templates")
|
|
||||||
.headers(AUTHORIZATION)
|
|
||||||
.check(status.is(200)),
|
|
||||||
|
|
||||||
http("Console REST - ${realm}")
|
|
||||||
.get("/auth/admin/realms/${realm}")
|
|
||||||
.headers(AUTHORIZATION)
|
|
||||||
.check(status.is(200)),
|
|
||||||
|
|
||||||
http("Console REST - realms")
|
|
||||||
.get("/auth/admin/realms")
|
|
||||||
.headers(AUTHORIZATION)
|
|
||||||
.check(status.is(200))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
def acsim_openClientDetails() : ChainBuilder = {
|
|
||||||
builder
|
|
||||||
.acsim_refreshTokenIfExpired()
|
|
||||||
.exec(http("Console - client-detail.html")
|
|
||||||
.get("/auth/resources/${resourceVersion}/admin/keycloak/partials/client-detail.html")
|
|
||||||
.headers(UI_HEADERS)
|
|
||||||
.check(status.is(200))
|
|
||||||
.resources(
|
|
||||||
http("Console REST - ${realm}/client-templates")
|
|
||||||
.get("/auth/admin/realms/${realm}/client-templates")
|
|
||||||
.headers(AUTHORIZATION)
|
|
||||||
.check(status.is(200)),
|
|
||||||
|
|
||||||
http("Console REST - ${realm}")
|
|
||||||
.get("/auth/admin/realms/${realm}")
|
|
||||||
.headers(AUTHORIZATION)
|
|
||||||
.check(status.is(200)),
|
|
||||||
|
|
||||||
http("Console REST - realms")
|
|
||||||
.get("/auth/admin/realms")
|
|
||||||
.headers(AUTHORIZATION)
|
|
||||||
.check(status.is(200)),
|
|
||||||
|
|
||||||
http("Console REST - ${realm}/clients")
|
|
||||||
.get("/auth/admin/realms/${realm}/clients")
|
|
||||||
.headers(AUTHORIZATION)
|
|
||||||
.check(status.is(200)),
|
|
||||||
|
|
||||||
http("Console REST - ${realm}/clients/ID")
|
|
||||||
.get("/auth/admin/realms/${realm}/clients/${idOfClient}")
|
|
||||||
.headers(AUTHORIZATION)
|
|
||||||
.check(status.is(200)),
|
|
||||||
|
|
||||||
http("Console - kc-tabs-client.html")
|
|
||||||
.get("/auth/resources/${resourceVersion}/admin/keycloak/templates/kc-tabs-client.html")
|
|
||||||
.headers(UI_HEADERS)
|
|
||||||
.check(status.is(200))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
def acsim_openUsers() : ChainBuilder = {
|
|
||||||
builder
|
|
||||||
.acsim_refreshTokenIfExpired()
|
|
||||||
.exec(http("Console - user-list.html")
|
|
||||||
.get("/auth/resources/${resourceVersion}/admin/keycloak/partials/user-list.html")
|
|
||||||
.headers(UI_HEADERS)
|
|
||||||
.check(status.is(200))
|
|
||||||
.resources(
|
|
||||||
http("Console REST - realms")
|
|
||||||
.get("/auth/admin/realms")
|
|
||||||
.headers(AUTHORIZATION)
|
|
||||||
.check(status.is(200)),
|
|
||||||
http("Console REST - ${realm}")
|
|
||||||
.get("/auth/admin/realms/${realm}")
|
|
||||||
.headers(AUTHORIZATION)
|
|
||||||
.check(status.is(200)),
|
|
||||||
http("Console - kc-tabs-users.html")
|
|
||||||
.get("/auth/resources/${resourceVersion}/admin/keycloak/templates/kc-tabs-users.html")
|
|
||||||
.headers(UI_HEADERS)
|
|
||||||
.check(status.is(200))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
def acsim_viewAllUsers() : ChainBuilder = {
|
|
||||||
builder
|
|
||||||
.acsim_refreshTokenIfExpired()
|
|
||||||
.exec(http("Console REST - ${realm}/users")
|
|
||||||
.get("/auth/admin/realms/${realm}/users?first=0&max=20")
|
|
||||||
.headers(AUTHORIZATION)
|
|
||||||
.check(status.is(200))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
def acsim_viewTenPagesOfUsers() : ChainBuilder = {
|
|
||||||
builder
|
|
||||||
.acsim_refreshTokenIfExpired()
|
|
||||||
.repeat(10, "i") {
|
|
||||||
exec(s => s.set("offset", s("i").as[Int]*20))
|
|
||||||
.pause(1)
|
|
||||||
.exec(http("Console REST - ${realm}/users?first=${offset}")
|
|
||||||
.get("/auth/admin/realms/${realm}/users?first=${offset}&max=20")
|
|
||||||
.headers(AUTHORIZATION)
|
|
||||||
.check(status.is(200))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def acsim_find20Users() : ChainBuilder = {
|
|
||||||
builder
|
|
||||||
.acsim_refreshTokenIfExpired()
|
|
||||||
.exec(http("Console REST - ${realm}/users?first=0&max=20&search=user")
|
|
||||||
.get("/auth/admin/realms/${realm}/users?first=0&max=20&search=user")
|
|
||||||
.headers(AUTHORIZATION)
|
|
||||||
.check(status.is(200))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
def acsim_findUnlimitedUsers() : ChainBuilder = {
|
|
||||||
builder
|
|
||||||
.acsim_refreshTokenIfExpired()
|
|
||||||
.exec(http("Console REST - ${realm}/users?search=user")
|
|
||||||
.get("/auth/admin/realms/${realm}/users?search=user")
|
|
||||||
.headers(AUTHORIZATION)
|
|
||||||
.check(status.is(200))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
def acsim_findRandomUser() : ChainBuilder = {
|
|
||||||
builder
|
|
||||||
.acsim_refreshTokenIfExpired()
|
|
||||||
.exec(s => s.set("randomUsername", AdminConsoleSimulationHelper.getRandomUser()))
|
|
||||||
.exec(http("Console REST - ${realm}/users?first=0&max=20&search=USERNAME")
|
|
||||||
.get("/auth/admin/realms/${realm}/users?first=0&max=20&search=${randomUsername}")
|
|
||||||
.headers(AUTHORIZATION)
|
|
||||||
.check(status.is(200), jsonPath("$[0]['id']").saveAs("userId"))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
def acsim_openUser() : ChainBuilder = {
|
|
||||||
builder
|
|
||||||
.acsim_refreshTokenIfExpired()
|
|
||||||
.exec(http("Console - user-detail.html")
|
|
||||||
.get("/auth/resources/${resourceVersion}/admin/keycloak/partials/user-detail.html")
|
|
||||||
.headers(UI_HEADERS)
|
|
||||||
.check(status.is(200))
|
|
||||||
.resources(
|
|
||||||
|
|
||||||
http("Console REST - realms")
|
|
||||||
.get("/auth/admin/realms")
|
|
||||||
.headers(AUTHORIZATION)
|
|
||||||
.check(status.is(200)),
|
|
||||||
|
|
||||||
http("Console REST - ${realm}")
|
|
||||||
.get("/auth/admin/realms/${realm}")
|
|
||||||
.headers(AUTHORIZATION)
|
|
||||||
.check(status.is(200)),
|
|
||||||
|
|
||||||
http("Console REST - ${realm}/users/ID")
|
|
||||||
.get("/auth/admin/realms/${realm}/users/${userId}")
|
|
||||||
.headers(AUTHORIZATION)
|
|
||||||
.check(status.is(200)),
|
|
||||||
|
|
||||||
http("Console - kc-tabs-user.html")
|
|
||||||
.get("/auth/resources/${resourceVersion}/admin/keycloak/templates/kc-tabs-user.html")
|
|
||||||
.headers(UI_HEADERS)
|
|
||||||
.check(status.is(200)),
|
|
||||||
|
|
||||||
http("Console REST - ${realm}/authentication/required-actions")
|
|
||||||
.get("/auth/admin/realms/${realm}/authentication/required-actions")
|
|
||||||
.headers(AUTHORIZATION)
|
|
||||||
.check(status.is(200)),
|
|
||||||
|
|
||||||
http("Console REST - ${realm}/attack-detection/brute-force/users/ID")
|
|
||||||
.get("/auth/admin/realms/${realm}/attack-detection/brute-force/users/${userId}")
|
|
||||||
.headers(AUTHORIZATION)
|
|
||||||
.check(status.is(200))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
def acsim_openUserCredentials() : ChainBuilder = {
|
|
||||||
builder
|
|
||||||
.acsim_refreshTokenIfExpired()
|
|
||||||
.exec(http("Console - user-credentials.html")
|
|
||||||
.get("/auth/resources/${resourceVersion}/admin/keycloak/partials/user-credentials.html")
|
|
||||||
.headers(UI_HEADERS)
|
|
||||||
.check(status.is(200))
|
|
||||||
.resources(
|
|
||||||
|
|
||||||
http("Console REST - ${realm}/users/ID")
|
|
||||||
.get("/auth/admin/realms/${realm}/users/${userId}")
|
|
||||||
.headers(AUTHORIZATION)
|
|
||||||
.check(status.is(200)),
|
|
||||||
|
|
||||||
http("Console REST - ${realm}")
|
|
||||||
.get("/auth/admin/realms/${realm}")
|
|
||||||
.headers(AUTHORIZATION)
|
|
||||||
.check(status.is(200)),
|
|
||||||
|
|
||||||
http("Console REST - realms")
|
|
||||||
.get("/auth/admin/realms")
|
|
||||||
.headers(AUTHORIZATION)
|
|
||||||
.check(status.is(200)),
|
|
||||||
|
|
||||||
http("Console REST - ${realm}/authentication/required-actions")
|
|
||||||
.get("/auth/admin/realms/${realm}/authentication/required-actions")
|
|
||||||
.headers(AUTHORIZATION)
|
|
||||||
.check(status.is(200))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
def acsim_setTemporaryPassword() : ChainBuilder = {
|
|
||||||
builder
|
|
||||||
.acsim_refreshTokenIfExpired()
|
|
||||||
.exec(http("Console REST - ${realm}/users/ID/reset-password PUT")
|
|
||||||
.put("/auth/admin/realms/${realm}/users/${userId}/reset-password")
|
|
||||||
.headers(AUTHORIZATION)
|
|
||||||
.header("Content-Type", "application/json")
|
|
||||||
.body(StringBody("""{"type":"password","value":"testtest","temporary":true}"""))
|
|
||||||
.check(status.is(204)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
def acsim_logOut() : ChainBuilder = {
|
|
||||||
builder
|
|
||||||
.acsim_refreshTokenIfExpired()
|
|
||||||
.exec(http("Browser logout")
|
|
||||||
.get("/auth/realms/master/protocol/openid-connect/logout")
|
|
||||||
.headers(UI_HEADERS)
|
|
||||||
.queryParam("redirect_uri", APP_URL)
|
|
||||||
.check(status.is(302), header("Location").is(APP_URL)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
def thinkPause() : ChainBuilder = {
|
|
||||||
builder.pause(TestConfig.userThinkTime, Normal(TestConfig.userThinkTime * 0.2))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue