Skip to content

Commit

Permalink
test: config and keycloak
Browse files Browse the repository at this point in the history
Signed-off-by: Anton Baliasnikov <[email protected]>
  • Loading branch information
Anton Baliasnikov committed Nov 16, 2023
1 parent 87c447a commit 3a9211a
Show file tree
Hide file tree
Showing 15 changed files with 435 additions and 81 deletions.
2 changes: 2 additions & 0 deletions tests/integration-tests/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ dependencies {
// Hoplite for configuration
implementation("com.sksamuel.hoplite:hoplite-core:2.7.5")
implementation("com.sksamuel.hoplite:hoplite-hocon:2.7.5")
// Kotlin compose
testImplementation("org.testcontainers:testcontainers:1.19.1")
}

buildscript {
Expand Down
4 changes: 2 additions & 2 deletions tests/integration-tests/src/test/kotlin/config/AgentConf.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import java.net.URL

data class AgentConf(
val url: URL,
val apikey: String?,
@ConfigAlias("webhook_url") val webhookUrl: URL?,
var apikey: String?,
@ConfigAlias("multi-tenant") val multiTenant: Boolean?,
val init: AgentInitConf?,
)
12 changes: 12 additions & 0 deletions tests/integration-tests/src/test/kotlin/config/AgentInitConf.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package config

import com.sksamuel.hoplite.ConfigAlias

data class AgentInitConf(
val version: String,
@ConfigAlias("http_port") val httpPort: Int,
@ConfigAlias("didcomm_port") val didcommPort: Int,
@ConfigAlias("secret_storage_backend") val secretStorageBackend: String,
@ConfigAlias("auth_enabled") val authEnabled: Boolean,
@ConfigAlias("keycloak_enabled") val keycloakEnabled: Boolean,
)
4 changes: 3 additions & 1 deletion tests/integration-tests/src/test/kotlin/config/Config.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package config

data class Config(
val global: GlobalConf,
val admin: AgentConf,
val issuer: AgentConf,
val holder: AgentConf,
val verifier: AgentConf,
val admin: AgentConf
val agents: List<AgentInitConf>,
val services: ServicesConf
)
6 changes: 2 additions & 4 deletions tests/integration-tests/src/test/kotlin/config/GlobalConf.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ package config
import com.sksamuel.hoplite.ConfigAlias

data class GlobalConf(
@ConfigAlias("auth_required") val authRequired: Boolean,
@ConfigAlias("auth_header") val authHeader: String,
@ConfigAlias("admin_auth_header") val adminAuthHeader: String,
@ConfigAlias("admin_apikey") val adminApiKey: String
@ConfigAlias("auth_header") val authHeader: String = "apikey",
@ConfigAlias("admin_auth_header") val adminAuthHeader: String = "x-admin-api-key",
)
31 changes: 31 additions & 0 deletions tests/integration-tests/src/test/kotlin/config/ServicesConf.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package config

import com.sksamuel.hoplite.ConfigAlias

data class ServicesConf(
@ConfigAlias("prism_node") val prismNode: PrismNodeConf?,
val keycloak: KeycloakConf?,
val vault: VaultConf?,
)

data class PrismNodeConf(
@ConfigAlias("http_port") val httpPort: Int,
val version: String,
)

data class KeycloakConf(
@ConfigAlias("http_port") val httpPort: Int,
val realm: String,
@ConfigAlias("client_id") val clientId: String,
@ConfigAlias("client_secret") val clientSecret: String,
val users: List<KeycloakUser>
)

data class KeycloakUser(
val username: String,
val password: String
)

data class VaultConf(
@ConfigAlias("http_port") val httpPort: Int,
)
221 changes: 171 additions & 50 deletions tests/integration-tests/src/test/kotlin/features/CommonSteps.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,10 @@ package features

import com.sksamuel.hoplite.ConfigLoader
import common.ListenToEvents
import config.AgentConf
import config.Config
import config.*
import features.connection.ConnectionSteps
import features.credentials.IssueCredentialsSteps
import features.did.PublishDidSteps
import features.multitenancy.EventsSteps
import interactions.Get
import io.cucumber.java.AfterAll
import io.cucumber.java.BeforeAll
Expand All @@ -24,50 +22,142 @@ import net.serenitybdd.screenplay.actors.OnStage
import net.serenitybdd.screenplay.rest.abilities.CallAnApi
import org.apache.http.HttpStatus
import org.apache.http.HttpStatus.SC_OK
import java.util.*
import kotlin.random.Random
import org.testcontainers.containers.ComposeContainer
import org.testcontainers.containers.wait.strategy.Wait
import java.io.File

@OptIn(ExperimentalStdlibApi::class)
fun createWalletAndEntity(agentConf: AgentConf) {
val config = ConfigLoader().loadConfigOrThrow<Config>("/tests.conf")
val createWalletResponse = RestAssured
.given().body(
CreateWalletRequest(
name = UUID.randomUUID().toString(),
seed = Random.nextBytes(64).toHexString(),
id = UUID.randomUUID()
)
val environments: MutableList<ComposeContainer> = mutableListOf()

fun initializeVdr(prismNode: PrismNodeConf) {
val vdrEnvironment: ComposeContainer = ComposeContainer(
File("src/test/resources/containers/vdr.yml")
).withEnv(
mapOf(
"PRISM_NODE_VERSION" to prismNode.version,
"PRISM_NODE_PORT" to prismNode.httpPort.toString()
)
.header(config.global.adminAuthHeader, config.global.adminApiKey)
.post("${agentConf.url}/wallets")
.thenReturn()
Ensure.that(createWalletResponse.statusCode).isEqualTo(HttpStatus.SC_CREATED)
val wallet = createWalletResponse.body.jsonPath().getObject("", WalletDetail::class.java)
val tenantResponse = RestAssured
.given().body(
CreateEntityRequest(
name = UUID.randomUUID().toString(),
walletId = wallet.id
)
).waitingFor(
"prism-node", Wait.forLogMessage(".*Server started, listening on.*", 1)
)
environments.add(vdrEnvironment)
vdrEnvironment.start()
}

fun initializeKeycloak(keycloakConf: KeycloakConf) {
val keycloakEnvironment: ComposeContainer = ComposeContainer(
File("src/test/resources/containers/keycloak.yml")
).withEnv(
mapOf(
"KEYCLOAK_HTTP_PORT" to keycloakConf.httpPort.toString(),
)
.header(config.global.adminAuthHeader, config.global.adminApiKey)
.post("${agentConf.url}/iam/entities")
.thenReturn()
Ensure.that(tenantResponse.statusCode).isEqualTo(HttpStatus.SC_CREATED)
val entity = tenantResponse.body.jsonPath().getObject("", EntityResponse::class.java)
val addApiKeyResponse =
).waitingFor(
"keycloak", Wait.forLogMessage(".*Running the server.*", 1)
)
environments.add(keycloakEnvironment)
keycloakEnvironment.start()

// Get admin token
val getAdminTokenResponse =
RestAssured
.given().body("grant_type=password&client_id=admin-cli&username=admin&password=admin")
.contentType("application/x-www-form-urlencoded")
.post("http://localhost:${keycloakConf.httpPort}/realms/master/protocol/openid-connect/token")
.thenReturn()
Ensure.that(getAdminTokenResponse.statusCode).isEqualTo(SC_OK)
val adminToken = getAdminTokenResponse.body.jsonPath().getString("access_token")

// Create realm
val createRealmResponse =
RestAssured
.given().body(
ApiKeyAuthenticationRequest(
entityId = entity.id,
apiKey = agentConf.apikey!!
mapOf(
"realm" to keycloakConf.realm,
"enabled" to true
)
)
.header(config.global.adminAuthHeader, config.global.adminApiKey)
.post("${agentConf.url}/iam/apikey-authentication")
.header("Authorization", "Bearer $adminToken")
.contentType("application/json")
.post("http://localhost:${keycloakConf.httpPort}/admin/realms")
.thenReturn()
Ensure.that(addApiKeyResponse.statusCode).isEqualTo(HttpStatus.SC_CREATED)
val registerIssuerWebhookResponse =
Ensure.that(createRealmResponse.statusCode).isEqualTo(SC_OK)

// Create client
val createClientResponse =
RestAssured
.given().body(
mapOf(
"id" to keycloakConf.clientId,
"directAccessGrantsEnabled" to true,
"authorizationServicesEnabled" to true,
"serviceAccountsEnabled" to true,
"secret" to keycloakConf.clientSecret,
))
.header("Authorization", "Bearer $adminToken")
.contentType("application/json")
.post("http://localhost:${keycloakConf.httpPort}/admin/realms/${keycloakConf.realm}/clients")
.thenReturn()
Ensure.that(createClientResponse.statusCode).isEqualTo(SC_OK)

// Create users
keycloakConf.users.forEach { keycloakUser ->
val createUserResponse =
RestAssured
.given().body(
mapOf(
"id" to keycloakUser.username,
"username" to keycloakUser.username,
"firstName" to keycloakUser.username,
"enabled" to true,
"credentials" to listOf(
mapOf(
"value" to keycloakUser.password,
"temporary" to false
)
)
)
)
.header("Authorization", "Bearer $adminToken")
.contentType("application/json")
.post("http://localhost:${keycloakConf.httpPort}/admin/realms/${keycloakConf.realm}/users")
.thenReturn()
Ensure.that(createUserResponse.statusCode).isEqualTo(SC_OK)
}
}

fun initializeAgent(agentInitConf: AgentInitConf) {
val config = ConfigLoader().loadConfigOrThrow<Config>("/tests.conf")
val agentConfMap: Map<String, String> = mapOf(
"OPEN_ENTERPRISE_AGENT_VERSION" to agentInitConf.version,
"API_KEY_ENABLED" to agentInitConf.authEnabled.toString(),
"AUTH_HEADER" to config.global.authHeader,
"ADMIN_AUTH_HEADER" to config.global.adminAuthHeader,
"AGENT_DIDCOMM_PORT" to agentInitConf.didcommPort.toString(),
"AGENT_HTTP_PORT" to agentInitConf.httpPort.toString(),
"PRISM_NODE_PORT" to if (config.services.prismNode != null)
config.services.prismNode.httpPort.toString() else "",
"SECRET_STORAGE_BACKEND" to agentInitConf.secretStorageBackend,
"VAULT_HTTP_PORT" to if (config.services.vault != null && agentInitConf.secretStorageBackend == "vault")
config.services.vault.httpPort.toString() else "",
"KEYCLOAK_ENABLED" to agentInitConf.keycloakEnabled.toString(),
"KEYCLOAK_HTTP_PORT" to if (config.services.keycloak != null && agentInitConf.keycloakEnabled)
config.services.keycloak.httpPort.toString() else "",
"KEYCLOAK_REALM" to if (config.services.keycloak != null && agentInitConf.keycloakEnabled)
config.services.keycloak.realm else "",
"KEYCLOAK_CLIENT_ID" to if (config.services.keycloak != null && agentInitConf.keycloakEnabled)
config.services.keycloak.clientId else "",
"KEYCLOAK_CLIENT_SECRET" to if (config.services.keycloak != null && agentInitConf.keycloakEnabled)
config.services.keycloak.clientSecret else "",
)
val environment: ComposeContainer = ComposeContainer(
File("src/test/resources/containers/agent.yml")
).withEnv(agentConfMap).waitingFor("open-enterprise-agent", Wait.forHealthcheck())
environments.add(environment)
environment.start()
}

fun initializeWebhook(agentConf: AgentConf) {
val config = ConfigLoader().loadConfigOrThrow<Config>("/tests.conf")
val registerWebhookResponse =
RestAssured
.given().body(
CreateWebhookNotification(
Expand All @@ -77,17 +167,24 @@ fun createWalletAndEntity(agentConf: AgentConf) {
.header(config.global.authHeader, agentConf.apikey)
.post("${agentConf.url}/events/webhooks")
.thenReturn()
Ensure.that(registerIssuerWebhookResponse.statusCode).isEqualTo(HttpStatus.SC_CREATED)
Ensure.that(registerWebhookResponse.statusCode).isEqualTo(HttpStatus.SC_CREATED)
}

fun getKeycloakAuthToken(keycloakConf: KeycloakConf, username: String, password: String): String {
val tokenResponse =
RestAssured
.given().body("grant_type=password&client_id=${keycloakConf.clientId}&client_secret=${keycloakConf.clientSecret}&username=${username}&password=${password}")
.contentType("application/x-www-form-urlencoded")
.post("http://localhost:${keycloakConf.httpPort}/realms/${keycloakConf.realm}/protocol/openid-connect/token")
.thenReturn()
Ensure.that(tokenResponse.statusCode).isEqualTo(SC_OK)
return tokenResponse.body.jsonPath().getString("access_token")
}

@BeforeAll
fun initAgents() {
val cast = Cast()
val config = ConfigLoader().loadConfigOrThrow<Config>("/tests.conf")
cast.actorNamed(
"Admin",
CallAnApi.at(config.admin.url.toExternalForm())
)
cast.actorNamed(
"Acme",
CallAnApi.at(config.issuer.url.toExternalForm()),
Expand All @@ -105,15 +202,36 @@ fun initAgents() {
)
OnStage.setTheStage(cast)

// Create issuer wallet and tenant
if (config.issuer.multiTenant!!) {
createWalletAndEntity(config.issuer)

if (config.services.keycloak != null) {
initializeKeycloak(config.services.keycloak)
cast.actors.forEach { actor ->
when (actor.name) {
"Acme" -> {
actor.remember("KEYCLOAK_BEARER_TOKEN", getKeycloakAuthToken(config.services.keycloak, "Acme", "Acme"))
}
"Bob" -> {
actor.remember("KEYCLOAK_BEARER_TOKEN", getKeycloakAuthToken(config.services.keycloak, "Bob", "Bob"))
}
"Faber" -> {
actor.remember("KEYCLOAK_BEARER_TOKEN", getKeycloakAuthToken(config.services.keycloak, "Faber", "Faber"))
}
}
}
}
// Create verifier wallet
if (config.verifier.multiTenant!!) {
createWalletAndEntity(config.verifier)

if (config.services.prismNode != null) {
initializeVdr(config.services.prismNode)
}
// Initialize the agents
config.agents.forEach { agent ->
initializeAgent(agent)
}

initializeWebhook(config.issuer)
initializeWebhook(config.holder)
initializeWebhook(config.verifier)

cast.actors.forEach { actor ->
when (actor.name) {
"Acme" -> {
Expand All @@ -132,6 +250,9 @@ fun initAgents() {
@AfterAll
fun clearStage() {
OnStage.drawTheCurtain()
environments.forEach { environment ->
environment.stop()
}
}

class CommonSteps {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package interactions

import com.sksamuel.hoplite.ConfigLoader
import config.Config
import io.ktor.util.*
import io.restassured.specification.RequestSpecification
import net.serenitybdd.screenplay.Actor
import net.serenitybdd.screenplay.rest.interactions.RestInteraction
Expand All @@ -13,12 +12,11 @@ abstract class AuthRestInteraction : RestInteraction() {

fun <T : Actor?> specWithAuthHeaders(actor: T): RequestSpecification {
val spec = rest()
if (actor!!.name.toLowerCasePreservingASCIIRules().contains("admin")) {
spec.header(config.global.adminAuthHeader, config.global.adminApiKey)
} else {
if (config.global.authRequired) {
spec.header(config.global.authHeader, actor.recall("AUTH_KEY"))
}
if (config.services.keycloak != null) {
spec.header("Authorization", "Bearer ${actor!!.recall<String>("KEYCLOAK_BEARER_TOKEN")}")
}
if (actor!!.recall<String>("AUTH_KEY") != null) {
spec.header(config.global.authHeader, actor.recall("AUTH_KEY"))
}
return spec
}
Expand Down
Loading

0 comments on commit 3a9211a

Please sign in to comment.