From 91228aea32e45878084389a511e09ee3b171a111 Mon Sep 17 00:00:00 2001 From: Yurii Shynbuiev Date: Fri, 3 Nov 2023 15:08:36 +0700 Subject: [PATCH] feat(prism-agent): implement the logic to grant and revoke permissions for the user to have access to the wallet Signed-off-by: Yurii Shynbuiev --- infrastructure/shared/docker-compose-demo.yml | 12 +- infrastructure/shared/docker-compose.yml | 11 +- .../core/PermissionManagement.scala | 35 ++++ .../KeycloakPermissionManagementService.scala | 159 +++++++++++++++++- .../keycloak/admin/KeycloakAdminSpec.scala | 21 +-- .../keycloak/admin/KeycloakConfigUtils.scala | 48 ++++++ ...cloakPermissionManagementServiceSpec.scala | 151 +++++++++++++++++ .../containers/KeycloakContainerCustom.scala | 6 +- .../KeycloakTestContainerSupport.scala | 51 +++++- .../sharedtest/containers/PostgresLayer.scala | 6 +- 10 files changed, 462 insertions(+), 38 deletions(-) create mode 100644 prism-agent/service/server/src/main/scala/io/iohk/atala/iam/authorization/core/PermissionManagement.scala create mode 100644 prism-agent/service/server/src/test/scala/io/iohk/atala/iam/authorization/keycloak/admin/KeycloakConfigUtils.scala create mode 100644 prism-agent/service/server/src/test/scala/io/iohk/atala/iam/authorization/keycloak/admin/KeycloakPermissionManagementServiceSpec.scala diff --git a/infrastructure/shared/docker-compose-demo.yml b/infrastructure/shared/docker-compose-demo.yml index 770ff351bb..cb1bdef9c8 100644 --- a/infrastructure/shared/docker-compose-demo.yml +++ b/infrastructure/shared/docker-compose-demo.yml @@ -2,7 +2,6 @@ version: "3.8" services: - db: image: postgres:13 environment: @@ -53,7 +52,16 @@ services: prism-node: condition: service_started healthcheck: - test: ["CMD", "wget", "--timeout=5", "--tries=3", "-O", "/dev/null", "http://prism-agent:8085/_system/health"] + test: + [ + "CMD", + "wget", + "--timeout=5", + "--tries=3", + "-O", + "/dev/null", + "http://prism-agent:8085/_system/health", + ] interval: 30s timeout: 10s retries: 5 diff --git a/infrastructure/shared/docker-compose.yml b/infrastructure/shared/docker-compose.yml index acc00622e3..9681d3e0bb 100644 --- a/infrastructure/shared/docker-compose.yml +++ b/infrastructure/shared/docker-compose.yml @@ -121,7 +121,16 @@ services: vault-server: condition: service_healthy healthcheck: - test: ["CMD", "wget", "--timeout=5", "--tries=3", "-O", "/dev/null", "http://prism-agent:8085/_system/health"] + test: + [ + "CMD", + "wget", + "--timeout=5", + "--tries=3", + "-O", + "/dev/null", + "http://prism-agent:8085/_system/health", + ] interval: 30s timeout: 10s retries: 5 diff --git a/prism-agent/service/server/src/main/scala/io/iohk/atala/iam/authorization/core/PermissionManagement.scala b/prism-agent/service/server/src/main/scala/io/iohk/atala/iam/authorization/core/PermissionManagement.scala new file mode 100644 index 0000000000..d745111ce8 --- /dev/null +++ b/prism-agent/service/server/src/main/scala/io/iohk/atala/iam/authorization/core/PermissionManagement.scala @@ -0,0 +1,35 @@ +package io.iohk.atala.iam.authorization.core + +import io.iohk.atala.shared.models.WalletId +import zio.IO + +import java.util.UUID + +object PermissionManagement { + trait Service { + def grantWalletToUser(walletId: WalletId, userId: UUID): IO[Error, Unit] + def revokeWalletFromUser(walletId: WalletId, userId: UUID): IO[Error, Unit] + } + + trait Error(message: String) + + object Error { + case class UserNotFoundById(userId: UUID, cause: Option[Throwable] = None) + extends Error(s"User $userId is not found" + cause.map(t => s" Cause: ${t.getMessage}")) + case class WalletNotFoundByUserId(userId: UUID) extends Error(s"Wallet for user $userId is not found") + + case class WalletNotFoundById(walletId: WalletId) extends Error(s"Wallet not found by ${walletId.toUUID}") + + case class WalletResourceNotFoundById(walletId: WalletId) + extends Error(s"Wallet resource not found by ${walletId.toUUID}") + + case class PermissionNotFoundById(userId: UUID, walletId: WalletId, walletResourceId: String) + extends Error( + s"Permission not found by userId: $userId, walletId: ${walletId.toUUID}, walletResourceId: $walletResourceId" + ) + + case class UnexpectedError(cause: Throwable) extends Error(cause.getMessage) + + case class ServiceError(message: String) extends Error(message) + } +} diff --git a/prism-agent/service/server/src/main/scala/io/iohk/atala/iam/authorization/keycloak/admin/KeycloakPermissionManagementService.scala b/prism-agent/service/server/src/main/scala/io/iohk/atala/iam/authorization/keycloak/admin/KeycloakPermissionManagementService.scala index eb84271985..334b0d7483 100644 --- a/prism-agent/service/server/src/main/scala/io/iohk/atala/iam/authorization/keycloak/admin/KeycloakPermissionManagementService.scala +++ b/prism-agent/service/server/src/main/scala/io/iohk/atala/iam/authorization/keycloak/admin/KeycloakPermissionManagementService.scala @@ -1,16 +1,163 @@ package io.iohk.atala.iam.authorization.keycloak.admin +import io.iohk.atala.agent.walletapi.service.WalletManagementService import io.iohk.atala.iam.authorization.core.PermissionManagement +import io.iohk.atala.iam.authorization.core.PermissionManagement.Error.* import io.iohk.atala.shared.models.WalletId -import org.keycloak.admin.client.Keycloak -import zio.IO +import org.keycloak.authorization.client.AuthzClient +import org.keycloak.representations.idm.authorization.{ResourceRepresentation, UmaPermissionRepresentation} +import zio.ZIO.* +import zio.ZLayer.* +import zio.{IO, Task, URLayer, ZIO, ZLayer} import java.util.UUID +import scala.jdk.CollectionConverters.* -class KeycloakPermissionManagementService(keycloak: Keycloak) extends PermissionManagement.Service { - override def grantWalletToUser(walletId: WalletId, userId: UUID): IO[PermissionManagement.Error, Unit] = ??? +case class KeycloakPermissionManagementService( + authzClient: AuthzClient, + walletManagementService: WalletManagementService +) extends PermissionManagement.Service { - override def revokeWalletFromUser(walletId: WalletId, userId: UUID): IO[PermissionManagement.Error, Unit] = ??? + private def walletResourceName(walletId: WalletId) = s"wallet-${walletId.toUUID.toString}" - override def getWalletForUser(userId: UUID): IO[PermissionManagement.Error, WalletId] = ??? + private def policyName(userId: String, resourceId: String) = s"user $userId on wallet $resourceId permission" + + override def grantWalletToUser(walletId: WalletId, userId: UUID): IO[PermissionManagement.Error, Unit] = { + for { + walletOpt <- walletManagementService + .getWallet(walletId) + .mapError(wmse => ServiceError(wmse.toThrowable.getMessage)) + + wallet <- ZIO + .fromOption(walletOpt) + .orElseFail(WalletNotFoundById(walletId)) + + walletResourceOpt <- findWalletResource(walletId) + .logError("Error while finding wallet resource") + .mapError(UnexpectedError.apply) + + walletResource <- ZIO + .fromOption(walletResourceOpt) + .orElse(createWalletResource(walletId)) + .logError("Error while creating wallet resource") + .mapError(UnexpectedError.apply) + _ <- ZIO.log(s"Wallet resource created ${walletResource.toString}") + + permission <- createResourcePermission(walletResource.getId, userId.toString) + .mapError(UnexpectedError.apply) + + _ <- ZIO.log(s"Permission created with id ${permission.getId} and name ${permission.getName}") + } yield () + } + + private def permissionDetails(permission: UmaPermissionRepresentation): String = { + s""" + |id: ${permission.getId} + |name: ${permission.getName} + |scopes: ${permission.getScopes.asScala.mkString(", ")} + |users: ${permission.getUsers.asScala.mkString(", ")} + |""".stripMargin + } + + private def createResourcePermission(resourceId: String, userId: String): Task[UmaPermissionRepresentation] = { + val policy = UmaPermissionRepresentation() + policy.setName(policyName(userId, resourceId)) + policy.setUsers(Set(userId).asJava) + + for { + umaPermissionRepresentation <- ZIO.attemptBlocking( + authzClient + .protection() + .policy(resourceId) + .create(policy) + ) + } yield umaPermissionRepresentation + } + + private def findWalletResource(walletId: WalletId): Task[Option[ResourceRepresentation]] = { + for { + walletResourceOrNull <- ZIO.attemptBlocking( + authzClient.protection().resource().findByName(walletResourceName(walletId)) + ) + } yield Option(walletResourceOrNull) + } + + private def createWalletResource(walletId: WalletId): Task[ResourceRepresentation] = { + val walletResource = ResourceRepresentation() + walletResource.setId(walletId.toUUID.toString) + walletResource.setUris(Set(s"/wallets/${walletResourceName(walletId)}").asJava) + walletResource.setName(walletResourceName(walletId)) + walletResource.setOwnerManagedAccess(true) + + for { + _ <- ZIO.log(s"Creating resource for the wallet ${walletId.toUUID.toString}") + response <- ZIO.attemptBlocking( + authzClient + .protection() + .resource() + .create(walletResource) + ) + resource <- ZIO.attemptBlocking( + authzClient + .protection() + .resource() + .findById(walletResource.getId) + ) + _ <- ZIO.log(s"Resource for the wallet created id: ${resource.getId}, name ${resource.getName}") + } yield resource + } + + override def revokeWalletFromUser(walletId: WalletId, userId: UUID): IO[PermissionManagement.Error, Unit] = { + for { + walletResourceOpt <- findWalletResource(walletId) + .logError("Error while finding wallet resource") + .mapError(UnexpectedError.apply) + + walletResource <- ZIO + .fromOption(walletResourceOpt) + .orElseFail(WalletResourceNotFoundById(walletId)) + + permissionOpt <- ZIO + .attemptBlocking( + authzClient + .protection() + .policy(walletResource.getId) + .find( + policyName(userId.toString, walletResource.getId), + null, + 0, + 1 + ) + ) + .map(_.asScala.headOption) + .logError(s"Error while finding permission by name ${policyName(userId.toString, walletResource.getId)}") + .mapError(UnexpectedError.apply) + + permission <- ZIO + .fromOption(permissionOpt) + .orElseFail(PermissionNotFoundById(userId, walletId, walletResource.getId)) + + _ <- ZIO + .attemptBlocking( + authzClient + .protection() + .policy(walletResource.getId) + .delete(permission.getId) + ) + .logError(s"Error while deleting permission ${permission.getId}") + .mapError(UnexpectedError.apply) + + _ <- ZIO.log( + s"Permission ${permission.getId} deleted for user ${userId.toString} and wallet ${walletResource.getId}" + ) + } yield () + } +} + +object KeycloakPermissionManagementService { + val layer: URLayer[ + AuthzClient & WalletManagementService, + PermissionManagement.Service + ] = + ZLayer.fromFunction(KeycloakPermissionManagementService(_, _)) } diff --git a/prism-agent/service/server/src/test/scala/io/iohk/atala/iam/authorization/keycloak/admin/KeycloakAdminSpec.scala b/prism-agent/service/server/src/test/scala/io/iohk/atala/iam/authorization/keycloak/admin/KeycloakAdminSpec.scala index da73e0b619..2f98cb9ef4 100644 --- a/prism-agent/service/server/src/test/scala/io/iohk/atala/iam/authorization/keycloak/admin/KeycloakAdminSpec.scala +++ b/prism-agent/service/server/src/test/scala/io/iohk/atala/iam/authorization/keycloak/admin/KeycloakAdminSpec.scala @@ -3,30 +3,13 @@ package io.iohk.atala.iam.authorization.keycloak.admin import io.iohk.atala.sharedtest.containers.{KeycloakContainerCustom, KeycloakTestContainerSupport} import zio.* import zio.ZIO.* +import zio.test.* import zio.test.Assertion.equalTo import zio.test.TestAspect.* -import zio.test.* import scala.util.Try -object KeycloakAdminSpec extends ZIOSpecDefault with KeycloakTestContainerSupport { - - private def keycloakAdminConfig: RIO[KeycloakContainerCustom, KeycloakAdminConfig] = - for { - keycloakContainer <- ZIO.service[KeycloakContainerCustom] - keycloakAdminConfig = KeycloakAdminConfig( - serverUrl = keycloakContainer.container.getAuthServerUrl, - realm = "master", - username = keycloakContainer.container.getAdminUsername, - password = keycloakContainer.container.getAdminPassword, - clientId = "admin-cli", - clientSecret = Option.empty, - authToken = Option.empty, - scope = Option.empty - ) - } yield keycloakAdminConfig - - val keycloakAdminConfigLayer = ZLayer.fromZIO(keycloakAdminConfig) +object KeycloakAdminSpec extends ZIOSpecDefault with KeycloakTestContainerSupport with KeycloakConfigUtils { override def spec = suite("KeycloakAdminSpec")( test("KeycloakAdmin can be created from the container") { diff --git a/prism-agent/service/server/src/test/scala/io/iohk/atala/iam/authorization/keycloak/admin/KeycloakConfigUtils.scala b/prism-agent/service/server/src/test/scala/io/iohk/atala/iam/authorization/keycloak/admin/KeycloakConfigUtils.scala new file mode 100644 index 0000000000..4beb70ca7e --- /dev/null +++ b/prism-agent/service/server/src/test/scala/io/iohk/atala/iam/authorization/keycloak/admin/KeycloakConfigUtils.scala @@ -0,0 +1,48 @@ +package io.iohk.atala.iam.authorization.keycloak.admin + +import io.iohk.atala.iam.authentication.oidc.KeycloakConfig +import io.iohk.atala.sharedtest.containers.{KeycloakContainerCustom, KeycloakTestContainerSupport} +import zio.* +import zio.ZIO.* +import zio.test.* + +import java.net.URI + +trait KeycloakConfigUtils { + this: KeycloakTestContainerSupport => + + protected def keycloakAdminConfig: RIO[KeycloakContainerCustom, KeycloakAdminConfig] = + for { + keycloakContainer <- ZIO.service[KeycloakContainerCustom] + keycloakAdminConfig = KeycloakAdminConfig( + serverUrl = keycloakContainer.container.getAuthServerUrl, + realm = "master", + username = keycloakContainer.container.getAdminUsername, + password = keycloakContainer.container.getAdminPassword, + clientId = "admin-cli", + clientSecret = Option.empty, + authToken = Option.empty, + scope = Option.empty + ) + } yield keycloakAdminConfig + + protected val keycloakAdminConfigLayer = ZLayer.fromZIO(keycloakAdminConfig) + + protected def keycloakConfigLayer(authUpgradeToRPT: Boolean = true) = + ZLayer.fromZIO { + ZIO.serviceWith[KeycloakContainerCustom] { container => + val host = container.container.getHost() + val port = container.container.getHttpPort() + val url = s"http://${host}:${port}" + KeycloakConfig( + enabled = true, + keycloakUrl = URI(url).toURL(), + realmName = realmName, + clientId = agentClientRepresentation.getClientId(), + clientSecret = agentClientSecret, + autoUpgradeToRPT = authUpgradeToRPT + ) + } + } + +} diff --git a/prism-agent/service/server/src/test/scala/io/iohk/atala/iam/authorization/keycloak/admin/KeycloakPermissionManagementServiceSpec.scala b/prism-agent/service/server/src/test/scala/io/iohk/atala/iam/authorization/keycloak/admin/KeycloakPermissionManagementServiceSpec.scala new file mode 100644 index 0000000000..11b72c79d8 --- /dev/null +++ b/prism-agent/service/server/src/test/scala/io/iohk/atala/iam/authorization/keycloak/admin/KeycloakPermissionManagementServiceSpec.scala @@ -0,0 +1,151 @@ +package io.iohk.atala.iam.authorization.keycloak.admin + +import io.iohk.atala.agent.walletapi.model.{Wallet, WalletSeed} +import io.iohk.atala.agent.walletapi.service.{WalletManagementService, WalletManagementServiceError} +import io.iohk.atala.event.notification.EventNotificationConfig +import io.iohk.atala.iam.authentication.AuthenticationError.ResourceNotPermitted +import io.iohk.atala.iam.authentication.oidc.{KeycloakAuthenticator, KeycloakAuthenticatorImpl, KeycloakClient, KeycloakClientImpl} +import io.iohk.atala.iam.authorization.core.PermissionManagement +import io.iohk.atala.iam.authorization.core.PermissionManagement.Error.WalletNotFoundById +import io.iohk.atala.shared.models.{WalletAccessContext, WalletId} +import io.iohk.atala.sharedtest.containers.{KeycloakContainerCustom, KeycloakTestContainerSupport} +import zio.* +import zio.ZIO.* +import zio.http.Client +import zio.test.* +import zio.test.Assertion.* +import zio.test.TestAspect.* + +import java.util.UUID +import scala.util.Try + +object KeycloakPermissionManagementServiceSpec + extends ZIOSpecDefault + with KeycloakTestContainerSupport + with KeycloakConfigUtils { + + override def spec = suite("KeycloakPermissionManagementServiceSpec")( + successfulCasesSuite, + failureCasesSuite + ) + + val successfulCasesSuite = suite("Successful Cases")( + test("grant wallet access to the user") { + for { + client <- ZIO.service[KeycloakClient] + authenticator <- ZIO.service[KeycloakAuthenticator] + walletService <- ZIO.service[WalletManagementService] + + wallet <- walletService.createWallet(Wallet("test_1")) + + randomId = UUID.randomUUID().toString + username = "user_" + randomId + password = randomId + user <- createUser(username = username, password = password) + + permissionService <- ZIO.service[PermissionManagement.Service] + _ <- permissionService.grantWalletToUser(wallet.id, UUID.fromString(user.getId)) + + token <- client.getAccessToken(username, password).map(_.access_token) + + entity <- authenticator.authenticate(token) + permittedWallet <- authenticator.authorize(entity) + } yield assert(wallet.id)(equalTo(permittedWallet)) + }, + test("revoke the wallet access from the user") { + for { + client <- ZIO.service[KeycloakClient] + authenticator <- ZIO.service[KeycloakAuthenticator] + walletService <- ZIO.service[WalletManagementService] + + wallet <- walletService.createWallet(Wallet("test_2")) + + randomId = UUID.randomUUID().toString + username = "user_" + randomId + password = randomId + user <- createUser(username = username, password = password) + + permissionService <- ZIO.service[PermissionManagement.Service] + _ <- permissionService.grantWalletToUser(wallet.id, UUID.fromString(user.getId)) + + token <- client.getAccessToken(username, password).map(_.access_token) + + entity <- authenticator.authenticate(token) + permittedWallet <- authenticator.authorize(entity) + + _ <- permissionService.revokeWalletFromUser(wallet.id, UUID.fromString(user.getId)) + + token2 <- client.getAccessToken(username, password).map(_.access_token) + entity2 <- authenticator.authenticate(token) + permittedWallet2 <- authenticator.authorize(entity).exit + + } yield assert(permittedWallet2)(fails(isSubtype[ResourceNotPermitted](anything))) + } + ).provide( + Client.default, + keycloakContainerLayer, + keycloakAdminConfigLayer, + KeycloakAdmin.layer, + KeycloakPermissionManagementService.layer, + WalletManagementServiceStub.layer, + KeycloakAuthenticatorImpl.layer, + ZLayer.fromZIO(initializeClient) >>> KeycloakClientImpl.layer ++ KeycloakClientImpl.authzClientLayer, + keycloakConfigLayer() + ) @@ sequential + + val failureCasesSuite = suite("Failure Cases Suite")( + test("grant wallet access to the user with invalid wallet id") { + for { + permissionService <- ZIO.service[PermissionManagement.Service] + exit <- permissionService.grantWalletToUser(WalletId.random, UUID.randomUUID()).exit + } yield assert(exit)(fails(isSubtype[WalletNotFoundById](anything))) + } + ).provide( + Client.default, + keycloakContainerLayer, + keycloakAdminConfigLayer, + KeycloakAdmin.layer, + KeycloakPermissionManagementService.layer, + WalletManagementServiceStub.layer, + KeycloakAuthenticatorImpl.layer, + ZLayer.fromZIO(initializeClient) >>> KeycloakClientImpl.layer ++ KeycloakClientImpl.authzClientLayer, + keycloakConfigLayer() + ) @@ sequential +} + +class WalletManagementServiceStub extends WalletManagementService { + private var wallets: Map[WalletId, Wallet] = Map.empty + override def createWallet(wallet: Wallet, seed: Option[WalletSeed]): IO[WalletManagementServiceError, Wallet] = { + val wallet = Wallet(name = "test") + wallets = wallets + (wallet.id -> wallet) + ZIO.succeed(wallet) + } + + override def getWallet(walletId: WalletId): IO[WalletManagementServiceError, Option[Wallet]] = { + ZIO.succeed(wallets.get(walletId)) + } + + override def getWallets(walletIds: Seq[WalletId]): IO[WalletManagementServiceError, Seq[Wallet]] = { + ZIO.succeed(wallets.filter(w => walletIds.contains(w._1)).values.toSeq) + } + + override def listWallets( + offset: Option[RuntimeFlags], + limit: Option[RuntimeFlags] + ): IO[WalletManagementServiceError, (Seq[Wallet], RuntimeFlags)] = ??? + + override def listWalletNotifications + : ZIO[WalletAccessContext, WalletManagementServiceError, Seq[EventNotificationConfig]] = ??? + + override def createWalletNotification( + config: EventNotificationConfig + ): ZIO[WalletAccessContext, WalletManagementServiceError, EventNotificationConfig] = ??? + + override def deleteWalletNotification( + id: _root_.java.util.UUID + ): ZIO[WalletAccessContext, WalletManagementServiceError, Unit] = ??? +} + +object WalletManagementServiceStub { + val layer = ZLayer.succeed(new WalletManagementServiceStub) +} diff --git a/shared-test/src/main/scala/io/iohk/atala/sharedtest/containers/KeycloakContainerCustom.scala b/shared-test/src/main/scala/io/iohk/atala/sharedtest/containers/KeycloakContainerCustom.scala index aefa20a621..2ed9608fe4 100644 --- a/shared-test/src/main/scala/io/iohk/atala/sharedtest/containers/KeycloakContainerCustom.scala +++ b/shared-test/src/main/scala/io/iohk/atala/sharedtest/containers/KeycloakContainerCustom.scala @@ -2,7 +2,7 @@ package io.iohk.atala.sharedtest.containers import com.dimafeng.testcontainers.SingleContainer import dasniko.testcontainers.keycloak.ExtendableKeycloakContainer -import KeycloakTestContainer.keycloakContainer +import io.iohk.atala.sharedtest.containers.KeycloakTestContainer.keycloakContainer import org.testcontainers.utility.DockerImageName import zio.{TaskLayer, ZIO, ZLayer} @@ -18,10 +18,6 @@ final class KeycloakContainerCustom( if (isOnGithubRunner) super.getContainerId.take(12) else super.getHost } -// override def getMappedPort(originalPort: Int): Integer = { -// if (isOnGithubRunner) 8300 -// else super.getMappedPort(originalPort) -// } } override val container: ExtendableKeycloakContainer[_] = keycloakContainer diff --git a/shared-test/src/main/scala/io/iohk/atala/sharedtest/containers/KeycloakTestContainerSupport.scala b/shared-test/src/main/scala/io/iohk/atala/sharedtest/containers/KeycloakTestContainerSupport.scala index e7c2d74695..8e8fa59309 100644 --- a/shared-test/src/main/scala/io/iohk/atala/sharedtest/containers/KeycloakTestContainerSupport.scala +++ b/shared-test/src/main/scala/io/iohk/atala/sharedtest/containers/KeycloakTestContainerSupport.scala @@ -1,7 +1,13 @@ package io.iohk.atala.sharedtest.containers import org.keycloak.admin.client.Keycloak -import org.keycloak.representations.idm.{ClientRepresentation, RealmRepresentation} +import org.keycloak.representations.idm.{ClientRepresentation, CredentialRepresentation, RealmRepresentation, UserRepresentation} import zio.* +import zio.ZIO.attemptBlocking +import zio.test.TestAspect.beforeAll +import zio.test.TestAspectAtLeastR + +import java.util.UUID +import scala.jdk.CollectionConverters.* type KeycloakAdminClient = Keycloak @@ -15,6 +21,7 @@ trait KeycloakTestContainerSupport { protected val adminClientZIO = ZIO.service[KeycloakAdminClient] protected val realmName = "atala-test" + protected val realmRepresentation = { val rr = new RealmRepresentation() rr.setRealm(realmName) @@ -23,6 +30,7 @@ trait KeycloakTestContainerSupport { } protected val agentClientSecret = "prism-agent-demo-secret" + protected val agentClientRepresentation: ClientRepresentation = { val acr = new ClientRepresentation() acr.setClientId("prism-agent") @@ -49,4 +57,45 @@ trait KeycloakTestContainerSupport { .create(agentClientRepresentation) ) } yield () + + def bootstrapKeycloakRealm = adminClientZIO.flatMap(keycloak => + ZIO.attemptBlocking { + keycloak.realms().create(realmRepresentation) + keycloak.realm(realmName).clients().create(agentClientRepresentation) + () + } + ) + + def bootstrapKeycloakRealmAspect: TestAspectAtLeastR[KeycloakAdminClient] = { + val run = for { + _ <- ZIO.log("Bootstrapping the Keycloak realm...") + _ <- bootstrapKeycloakRealm + _ <- ZIO.log("Bootstrap finished") + } yield () + + beforeAll(run.orDie) + } + + def createUser(username: String, password: String): ZIO[KeycloakAdminClient, Throwable, UserRepresentation] = + val userRepresentation = { + val creds = new CredentialRepresentation() + creds.setTemporary(false) + creds.setValue(password) + + val ur = new UserRepresentation() + ur.setId(UUID.nameUUIDFromBytes(username.getBytes).toString) + ur.setUsername(username) + ur.setEnabled(true) + ur.setCredentials(List(creds).asJava) + ur + } + + for { + adminClient <- adminClientZIO + users = adminClient.realm(realmName).users() + _ <- ZIO.log(s"Creating user ${userRepresentation.getId}") + _ <- attemptBlocking(users.create(userRepresentation)) + createdUser <- attemptBlocking(users.search(username).asScala.head) + _ <- ZIO.log(s"Created user ${createdUser.getId}") + } yield createdUser } diff --git a/shared-test/src/main/scala/io/iohk/atala/sharedtest/containers/PostgresLayer.scala b/shared-test/src/main/scala/io/iohk/atala/sharedtest/containers/PostgresLayer.scala index 4cd5dcfba2..0a7b3912eb 100644 --- a/shared-test/src/main/scala/io/iohk/atala/sharedtest/containers/PostgresLayer.scala +++ b/shared-test/src/main/scala/io/iohk/atala/sharedtest/containers/PostgresLayer.scala @@ -2,10 +2,8 @@ package io.iohk.atala.sharedtest.containers import com.dimafeng.testcontainers.PostgreSQLContainer import doobie.util.transactor.Transactor -import io.iohk.atala.shared.db.ContextAwareTask -import io.iohk.atala.shared.db.DbConfig -import io.iohk.atala.shared.db.TransactorLayer -import io.iohk.atala.shared.test.containers.PostgresTestContainer.postgresContainer +import io.iohk.atala.shared.db.{ContextAwareTask, DbConfig, TransactorLayer} +import io.iohk.atala.sharedtest.containers.PostgresTestContainer.postgresContainer import zio.* object PostgresLayer {