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 -Pprovision
|
||||
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):
|
||||
|
|
|
@ -27,7 +27,7 @@ mvn clean install
|
|||
# Make sure your Docker daemon is running THEN
|
||||
mvn verify -Pprovision
|
||||
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 |
|
||||
| --- | --- | --- |
|
||||
| `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` |
|
||||
| `runUsers` | Number of users for the simulation run. | `1` |
|
||||
| `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` |
|
||||
| `refreshTokenPeriod`| Period after which token should be refreshed. | `10` |
|
||||
|
||||
#### Addtional Parameters of `keycloak.DefaultSimulation`
|
||||
#### Addtional Parameters of `keycloak.BasicOIDCSimulation`
|
||||
|
||||
| Parameter | Description | Default Value |
|
||||
| --- | --- | --- |
|
||||
|
@ -159,7 +159,7 @@ Usage: `mvn verify -Ptest[,cluster] [-DtestParameter=value]`.
|
|||
|
||||
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
|
||||
|
@ -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'.
|
||||
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
|
||||
JUnit tests.
|
||||
Make sure that `performance` maven profile is enabled for IDEA to treat `performance` directory as a project module.
|
||||
|
||||
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'.
|
||||
|
||||
|
|
|
@ -93,7 +93,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.DefaultSimulation</gatling.simulationClass>
|
||||
<gatling.simulationClass>keycloak.BasicOIDCSimulation</gatling.simulationClass>
|
||||
<gatling.skip.run>true</gatling.skip.run>
|
||||
</properties>
|
||||
|
||||
|
|
|
@ -68,7 +68,7 @@ public class TestConfig {
|
|||
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 refreshTokenCount = Integer.getInteger("refreshTokenCount", 0);
|
||||
|
|
|
@ -4,8 +4,8 @@ import io.gatling.core.config.GatlingPropertiesBuilder
|
|||
|
||||
object Engine extends App {
|
||||
|
||||
val sim = classOf[keycloak.DefaultSimulation]
|
||||
//val sim = classOf[keycloak.AdminSimulation]
|
||||
val sim = classOf[keycloak.BasicOIDCSimulation]
|
||||
//val sim = classOf[keycloak.AdminConsoleSimulation]
|
||||
|
||||
val props = new GatlingPropertiesBuilder
|
||||
props.dataDirectory(IDEPathHelper.dataDirectory.toString)
|
||||
|
|
|
@ -24,6 +24,7 @@ class SimpleExample4 extends Simulation {
|
|||
.exec(account)
|
||||
|
||||
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)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
import io.gatling.core.Predef._
|
||||
import io.gatling.core.validation.Validation
|
||||
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.gatling.Utils._
|
||||
import SimulationsHelper._
|
||||
|
||||
|
||||
/**
|
||||
|
@ -14,6 +12,12 @@ import SimulationsHelper._
|
|||
*/
|
||||
class AdminConsoleSimulation extends Simulation {
|
||||
|
||||
def rampDownPeriodNotReached(): Validation[Boolean] = {
|
||||
System.currentTimeMillis < TestConfig.rampDownPeriodStartTime
|
||||
}
|
||||
|
||||
|
||||
|
||||
println()
|
||||
println("Target server: " + TestConfig.serverUrisList.get(0))
|
||||
println()
|
||||
|
@ -31,91 +35,72 @@ class AdminConsoleSimulation extends Simulation {
|
|||
.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")
|
||||
|
||||
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()
|
||||
.thinkPause()
|
||||
.loginThroughLoginForm()
|
||||
|
||||
.openRealmSettings()
|
||||
|
||||
.thinkPause()
|
||||
.acsim_loginThroughLoginForm()
|
||||
.exitHereIfFailed
|
||||
.openClients()
|
||||
|
||||
.thinkPause()
|
||||
.acsim_openClients()
|
||||
.openCreateNewClient()
|
||||
|
||||
.thinkPause()
|
||||
.acsim_openCreateNewClient()
|
||||
.submitNewClient()
|
||||
|
||||
.thinkPause()
|
||||
.acsim_submitNewClient()
|
||||
.updateClient()
|
||||
|
||||
.thinkPause()
|
||||
.acsim_updateClient()
|
||||
.openClients()
|
||||
|
||||
.thinkPause()
|
||||
.acsim_openClients()
|
||||
.openClientDetails()
|
||||
|
||||
.thinkPause()
|
||||
.acsim_openClientDetails()
|
||||
.openUsers()
|
||||
|
||||
.thinkPause()
|
||||
.acsim_openUsers()
|
||||
.viewAllUsers()
|
||||
|
||||
.thinkPause()
|
||||
.acsim_viewAllUsers()
|
||||
.viewTenPagesOfUsers()
|
||||
|
||||
.thinkPause()
|
||||
.acsim_viewTenPagesOfUsers()
|
||||
.find20Users()
|
||||
|
||||
.thinkPause()
|
||||
.acsim_find20Users()
|
||||
.findUnlimitedUsers()
|
||||
|
||||
.thinkPause()
|
||||
.acsim_findUnlimitedUsers()
|
||||
.findRandomUser()
|
||||
.openUser()
|
||||
|
||||
.thinkPause()
|
||||
.acsim_findRandomUser()
|
||||
|
||||
.acsim_openUser()
|
||||
.openUserCredentials()
|
||||
|
||||
.thinkPause()
|
||||
.acsim_openUserCredentials()
|
||||
.setTemporaryPassword()
|
||||
|
||||
.thinkPause()
|
||||
.acsim_setTemporaryPassword()
|
||||
.logout()
|
||||
|
||||
.thinkPause()
|
||||
.acsim_logOut()
|
||||
|
||||
|
||||
val adminScenario = scenario("AdminConsole")
|
||||
.asLongAs(s => rampDownPeriodNotReached(), null, TestConfig.rampDownASAP) {
|
||||
pace(TestConfig.pace)
|
||||
adminSession
|
||||
adminSession.chainBuilder
|
||||
}
|
||||
|
||||
setUp(adminScenario
|
||||
.inject(rampUsers(TestConfig.runUsers) over TestConfig.rampUpPeriod)
|
||||
.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