From 74508a78845a2503c7a17554542473a2e8921f15 Mon Sep 17 00:00:00 2001 From: Benjamin Voiturier Date: Tue, 18 Jun 2024 11:17:56 +0200 Subject: [PATCH 01/11] chore: refactor entity repository Signed-off-by: Benjamin Voiturier --- ...cloakPermissionManagementServiceSpec.scala | 8 +-- .../walletapi/sql/EntityRepository.scala | 27 ++++--- .../walletapi/sql/JdbcEntityRepository.scala | 72 ++++--------------- .../storage/JdbcEntityRepositorySpec.scala | 53 ++------------ .../identus/shared/db/ContextAwareTask.scala | 8 +-- 5 files changed, 38 insertions(+), 130 deletions(-) diff --git a/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/iam/authorization/keycloak/admin/KeycloakPermissionManagementServiceSpec.scala b/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/iam/authorization/keycloak/admin/KeycloakPermissionManagementServiceSpec.scala index f80edb6281..f73771f91e 100644 --- a/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/iam/authorization/keycloak/admin/KeycloakPermissionManagementServiceSpec.scala +++ b/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/iam/authorization/keycloak/admin/KeycloakPermissionManagementServiceSpec.scala @@ -7,13 +7,7 @@ import org.hyperledger.identus.agent.walletapi.service.{ WalletManagementServiceImpl } import org.hyperledger.identus.agent.walletapi.sql.{JdbcWalletNonSecretStorage, JdbcWalletSecretStorage} -import org.hyperledger.identus.iam.authentication.oidc.{ - KeycloakAuthenticator, - KeycloakAuthenticatorImpl, - KeycloakClient, - KeycloakClientImpl, - KeycloakEntity -} +import org.hyperledger.identus.iam.authentication.oidc.* import org.hyperledger.identus.iam.authentication.AuthenticationError.ResourceNotPermitted import org.hyperledger.identus.iam.authorization.core.PermissionManagement import org.hyperledger.identus.iam.authorization.core.PermissionManagement.Error.{UnexpectedError, WalletNotFoundById} diff --git a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/EntityRepository.scala b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/EntityRepository.scala index e36f9dc389..cb272c33e0 100644 --- a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/EntityRepository.scala +++ b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/EntityRepository.scala @@ -3,59 +3,58 @@ package org.hyperledger.identus.agent.walletapi.sql import io.getquill.* import io.getquill.doobie.DoobieContext import io.getquill.idiom.* -import org.hyperledger.identus.agent.walletapi.model.error.EntityServiceError import org.hyperledger.identus.agent.walletapi.model.Entity -import zio.{IO, ZIO} +import zio.{UIO, URIO, ZIO} import java.time.Instant import java.util.UUID trait EntityRepository { - def insert(entity: Entity): IO[EntityServiceError, Entity] - def getById(id: UUID): IO[EntityServiceError, Entity] - def updateName(entityId: UUID, name: String): IO[EntityServiceError, Unit] - def updateWallet(entityId: UUID, walletId: UUID): IO[EntityServiceError, Unit] - def delete(id: UUID): IO[EntityServiceError, Unit] - def getAll(offset: Int, limit: Int): IO[EntityServiceError, List[Entity]] + def insert(entity: Entity): UIO[Entity] + def getById(id: UUID): UIO[Entity] + def updateName(entityId: UUID, name: String): UIO[Unit] + def updateWallet(entityId: UUID, walletId: UUID): UIO[Unit] + def delete(id: UUID): UIO[Unit] + def getAll(offset: Int, limit: Int): UIO[List[Entity]] } object EntityRepository { - def insert(entity: Entity): ZIO[EntityRepository, EntityServiceError, Entity] = { + def insert(entity: Entity): URIO[EntityRepository, Entity] = { for { repository <- ZIO.service[EntityRepository] insertedEntity <- repository.insert(entity) } yield insertedEntity } - def getById(id: UUID): ZIO[EntityRepository, EntityServiceError, Entity] = { + def getById(id: UUID): URIO[EntityRepository, Entity] = { for { repository <- ZIO.service[EntityRepository] entity <- repository.getById(id) } yield entity } - def updateName(entityId: UUID, name: String): ZIO[EntityRepository, EntityServiceError, Unit] = { + def updateName(entityId: UUID, name: String): URIO[EntityRepository, Unit] = { for { repository <- ZIO.service[EntityRepository] _ <- repository.updateName(entityId, name) } yield () } - def updateWallet(entityId: UUID, walletId: UUID): ZIO[EntityRepository, EntityServiceError, Unit] = { + def updateWallet(entityId: UUID, walletId: UUID): URIO[EntityRepository, Unit] = { for { repository <- ZIO.service[EntityRepository] _ <- repository.updateWallet(entityId, walletId) } yield () } - def delete(id: UUID): ZIO[EntityRepository, EntityServiceError, Unit] = { + def delete(id: UUID): URIO[EntityRepository, Unit] = { for { repository <- ZIO.service[EntityRepository] _ <- repository.delete(id) } yield () } - def getAll(skip: Int, take: Int): ZIO[EntityRepository, EntityServiceError, List[Entity]] = { + def getAll(skip: Int, take: Int): URIO[EntityRepository, List[Entity]] = { for { repository <- ZIO.service[EntityRepository] entities <- repository.getAll(skip, take) diff --git a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/JdbcEntityRepository.scala b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/JdbcEntityRepository.scala index 6e9156955b..e7ab00d1f2 100644 --- a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/JdbcEntityRepository.scala +++ b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/JdbcEntityRepository.scala @@ -3,14 +3,8 @@ package org.hyperledger.identus.agent.walletapi.sql import doobie.* import doobie.implicits.* import org.hyperledger.identus.agent.walletapi.model.error.EntityServiceError -import org.hyperledger.identus.agent.walletapi.model.error.EntityServiceError.{ - EntityAlreadyExists, - EntityNotFound, - EntityStorageError, - EntityWalletNotFound -} import org.hyperledger.identus.agent.walletapi.model.Entity -import org.postgresql.util.PSQLException +import org.hyperledger.identus.shared.db.Implicits.ensureOneAffectedRowOrDie import zio.* import zio.interop.catz.* @@ -18,87 +12,49 @@ import java.util.UUID class JdbcEntityRepository(xa: Transactor[Task]) extends EntityRepository { import EntityStorageSql.* - override def insert(entity: Entity): IO[EntityServiceError, Entity] = { + override def insert(entity: Entity): UIO[Entity] = { EntityStorageSql .insert(model2db(entity)) .transact(xa) - .logError(s"Insert entity failed: $entity") - .mapError { - case sqlException: PSQLException - if sqlException.getMessage.contains("duplicate key value violates unique constraint") => - EntityAlreadyExists(entity.id, sqlException.getMessage) - case sqlException: PSQLException - if sqlException.getMessage - .contains("violates foreign key constraint \"entity_wallet_id_fkey\"") => - EntityWalletNotFound(entity.id, entity.walletId) - case other: Throwable => - EntityStorageError(other.getMessage) - } .map(db2model) + .orDie } - override def getById(id: UUID): IO[EntityServiceError, Entity] = { + override def getById(id: UUID): UIO[Entity] = { EntityStorageSql .getById(id) .transact(xa) .map(_.headOption.map(db2model)) - .logError(s"Get entity by id=$id failed") - .mapError(throwable => EntityStorageError(throwable.getMessage)) - .flatMap( - _.fold[ZIO[Any, EntityServiceError, Entity]](ZIO.fail(EntityNotFound(id, s"Get entity by id=$id failed")))( - ZIO.succeed - ) - ) + .someOrElseZIO(ZIO.dieMessage(s"Entity not found: id=$id")) + .orDie } - override def updateName(entityId: UUID, name: String): IO[EntityServiceError, Unit] = { + override def updateName(entityId: UUID, name: String): UIO[Unit] = { EntityStorageSql .updateName(entityId, name) .transact(xa) - .logError(s"Update entity name=$name by id=$entityId failed") - .mapError(throwable => EntityStorageError(throwable.getMessage)) - .flatMap { updatedCount => - if updatedCount == 1 then ZIO.unit - else ZIO.fail(EntityNotFound(entityId, s"Update entity name=$name by id=$entityId failed")) - } + .ensureOneAffectedRowOrDie } - override def updateWallet(entityId: UUID, walletId: UUID): IO[EntityServiceError, Unit] = { + override def updateWallet(entityId: UUID, walletId: UUID): UIO[Unit] = { EntityStorageSql .updateWallet(entityId, walletId) .transact(xa) - .logError(s"Update entity walletId=$walletId by id=$entityId failed") - .mapError { - case sqlException: PSQLException - if sqlException.getMessage - .contains("violates foreign key constraint \"entity_wallet_id_fkey\"") => - EntityWalletNotFound(entityId, walletId) - case other: Throwable => EntityStorageError(other.getMessage) - } - .flatMap(updatedCount => - if updatedCount == 1 then ZIO.unit - else ZIO.fail(EntityNotFound(entityId, s"Update entity walletId=$walletId by id=$entityId failed")) - ) + .ensureOneAffectedRowOrDie } - override def delete(entityId: UUID): IO[EntityServiceError, Unit] = { + override def delete(entityId: UUID): UIO[Unit] = { EntityStorageSql .delete(entityId) .transact(xa) - .logError(s"Delete entity failed: id=$entityId") - .mapError(throwable => EntityStorageError(throwable.getMessage)) - .flatMap(deletedCount => - if deletedCount == 1 then ZIO.unit - else ZIO.fail(EntityNotFound(entityId, s"Delete entity failed: id=$entityId")) - ) + .ensureOneAffectedRowOrDie } - override def getAll(offset: Index, limit: Index): IO[EntityServiceError, List[Entity]] = { + override def getAll(offset: Index, limit: Index): UIO[List[Entity]] = { EntityStorageSql .getAll(offset, limit) .transact(xa) - .logError("Get all entities failed") - .mapError(throwable => EntityStorageError(throwable.getMessage)) .map(_.map(db2model)) + .orDie } } diff --git a/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/storage/JdbcEntityRepositorySpec.scala b/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/storage/JdbcEntityRepositorySpec.scala index c5a47d8c93..21cc824cb4 100644 --- a/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/storage/JdbcEntityRepositorySpec.scala +++ b/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/storage/JdbcEntityRepositorySpec.scala @@ -1,11 +1,6 @@ package org.hyperledger.identus.agent.walletapi.storage import org.hyperledger.identus.agent.walletapi.model.{Entity, Wallet} -import org.hyperledger.identus.agent.walletapi.model.error.EntityServiceError.{ - EntityAlreadyExists, - EntityNotFound, - EntityWalletNotFound -} import org.hyperledger.identus.agent.walletapi.sql.{EntityRepository, JdbcEntityRepository, JdbcWalletNonSecretStorage} import org.hyperledger.identus.shared.models.WalletId import org.hyperledger.identus.sharedtest.containers.PostgresTestContainerSupport @@ -98,13 +93,7 @@ object JdbcEntityRepositorySpec extends ZIOSpecDefault, PostgresTestContainerSup for { in <- createRandomEntity updated <- EntityRepository.updateName(in.id, "newName").exit - } yield assert(updated)( - fails( - isSubtype[EntityNotFound]( - hasField("message", _.message, containsString(s"Update entity name=newName by id=${in.id} failed")) - ) - ) - ) + } yield assert(updated)(dies(anything)) }, test("update the Entity walletId") { for { @@ -128,13 +117,7 @@ object JdbcEntityRepositorySpec extends ZIOSpecDefault, PostgresTestContainerSup id <- random.nextUUID walletId <- random.nextUUID updated <- EntityRepository.updateWallet(id, walletId).exit - } yield assert(updated)( - fails( - isSubtype[EntityNotFound]( - hasField("message", _.message, containsString(s"Update entity walletId=$walletId by id=$id failed")) - ) - ) - ) + } yield assert(updated)(dies(anything)) }, test("update the Entity walletId by the walletId that does not exist") { for { @@ -147,17 +130,7 @@ object JdbcEntityRepositorySpec extends ZIOSpecDefault, PostgresTestContainerSup entity <- EntityRepository.insert(in) updated <- EntityRepository.updateWallet(entity.id, randomWalletId).exit - } yield assert(updated)( - fails( - isSubtype[EntityWalletNotFound]( - hasField( - "message", - _.message, - containsString(s"Wallet with id:$randomWalletId not found for entity with id:${in.id}") - ) - ) - ) - ) + } yield assert(updated)(dies(anything)) }, ) @@ -177,9 +150,7 @@ object JdbcEntityRepositorySpec extends ZIOSpecDefault, PostgresTestContainerSup _ <- random.setSeed(42L) id <- random.nextUUID get <- EntityRepository.getById(id).exit - } yield assert(get)( - fails(isSubtype[EntityNotFound](hasField("message", _.message, containsString(s"Get entity by id=$id failed")))) - ) + } yield assert(get)(dies(anything)) } ) @@ -218,26 +189,14 @@ object JdbcEntityRepositorySpec extends ZIOSpecDefault, PostgresTestContainerSup _ <- createAndStoreWallet(in) out <- EntityRepository.insert(in) exit <- EntityRepository.insert(in).exit - } yield assert(exit)( - fails(isSubtype[EntityAlreadyExists](hasField("message", _.message, containsString("duplicate key value")))) - ) + } yield assert(exit)(dies(anything)) }, test("create the Entity with the walletId that doesn't exist") { for { in <- createRandomEntity // _ <- createAndStoreWallet(in) - the wallet is not created exit <- EntityRepository.insert(in).exit - } yield assert(exit)( - fails( - isSubtype[EntityWalletNotFound]( - hasField( - "message", - _.message, - containsString(s"Wallet with id:${in.walletId} not found for entity with id:${in.id}") - ) - ) - ) - ) + } yield assert(exit)(dies(anything)) } ) diff --git a/shared/core/src/main/scala/org/hyperledger/identus/shared/db/ContextAwareTask.scala b/shared/core/src/main/scala/org/hyperledger/identus/shared/db/ContextAwareTask.scala index 3b07e0be97..5ffc1e0a90 100644 --- a/shared/core/src/main/scala/org/hyperledger/identus/shared/db/ContextAwareTask.scala +++ b/shared/core/src/main/scala/org/hyperledger/identus/shared/db/ContextAwareTask.scala @@ -14,7 +14,7 @@ trait ContextAware type ContextAwareTask[T] = Task[T] & ContextAware object Errors { - final case class UnexpectedAffectedRow(count: Int) extends RuntimeException(s"Unexpected affected row count: $count") + final case class UnexpectedAffectedRow(count: Long) extends RuntimeException(s"Unexpected affected row count: $count") } object Implicits { @@ -47,10 +47,10 @@ object Implicits { } - extension (ma: RIO[WalletAccessContext, Int]) { - def ensureOneAffectedRowOrDie: URIO[WalletAccessContext, Unit] = ma.flatMap { + extension [R, A: Numeric](ma: ZIO[R, Throwable, A]) { + def ensureOneAffectedRowOrDie: URIO[R, Unit] = ma.flatMap { case 1 => ZIO.unit - case count => ZIO.fail(Errors.UnexpectedAffectedRow(count)) + case count => ZIO.fail(Errors.UnexpectedAffectedRow(summon[Numeric[A]].toLong(count))) }.orDie } From 7b56d27b17a3ec2ccce45f6f4b2bb159bbfb1ebc Mon Sep 17 00:00:00 2001 From: Benjamin Voiturier Date: Tue, 18 Jun 2024 16:00:18 +0200 Subject: [PATCH 02/11] chore: refactor entity service and controller Signed-off-by: Benjamin Voiturier --- .../identus/agent/server/CloudAgentApp.scala | 2 +- .../apikey/ApiKeyAuthenticatorImpl.scala | 8 +-- .../EntityPermissionManagementService.scala | 2 +- .../core/PermissionManagement.scala | 13 ---- .../http/controller/EntityController.scala | 19 +----- .../controller/EntityControllerImpl.scala | 20 +++--- .../model/error/EntityServiceError.scala | 26 +++++--- .../walletapi/service/EntityService.scala | 15 +++-- .../walletapi/service/EntityServiceImpl.scala | 65 ++++++++++--------- .../walletapi/sql/EntityRepository.scala | 1 + .../walletapi/sql/JdbcEntityRepository.scala | 9 ++- 11 files changed, 85 insertions(+), 95 deletions(-) diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/CloudAgentApp.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/CloudAgentApp.scala index 045f097bc5..5820df2c73 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/CloudAgentApp.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/CloudAgentApp.scala @@ -213,7 +213,7 @@ object AgentInitialization { _ <- walletService .createWallet(defaultWallet, seed) .orDieAsUnmanagedFailure - _ <- entityService.create(defaultEntity).mapError(e => Exception(e.message)) + _ <- entityService.create(defaultEntity).orDieAsUnmanagedFailure _ <- apiKeyAuth.add(defaultEntity.id, config.authApiKey).mapError(e => Exception(e.message)) _ <- config.webhookUrl.fold(ZIO.unit) { url => val customHeaders = config.webhookApiKey.fold(Map.empty)(apiKey => Map("Authorization" -> s"Bearer $apiKey")) diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/apikey/ApiKeyAuthenticatorImpl.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/apikey/ApiKeyAuthenticatorImpl.scala index a02c0fc71a..3639f39caf 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/apikey/ApiKeyAuthenticatorImpl.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/apikey/ApiKeyAuthenticatorImpl.scala @@ -59,9 +59,7 @@ case class ApiKeyAuthenticatorImpl( .orDieAsUnmanagedFailure .provide(ZLayer.succeed(WalletAdministrationContext.Admin())) entityToCreate = Entity(name = "Auto provisioned entity", walletId = wallet.id.toUUID) - entity <- entityService - .create(entityToCreate) - .mapError(entityServiceError => AuthenticationRepositoryError.ServiceError(entityServiceError.message)) + entity <- entityService.create(entityToCreate).orDieAsUnmanagedFailure _ <- add(entity.id, apiKey) .mapError(are => AuthenticationRepositoryError.ServiceError(are.message)) } yield entity @@ -76,9 +74,7 @@ case class ApiKeyAuthenticatorImpl( .mapError(cause => AuthenticationRepositoryError.UnexpectedError(cause)) entityId <- repository .getEntityIdByMethodAndSecret(AuthenticationMethodType.ApiKey, secret) - entity <- entityService - .getById(entityId) - .mapError(entityServiceError => AuthenticationRepositoryError.ServiceError(entityServiceError.message)) + entity <- entityService.getById(entityId).orDieAsUnmanagedFailure } yield entity } diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authorization/core/EntityPermissionManagementService.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authorization/core/EntityPermissionManagementService.scala index 8bf36e7e05..6b6f5ebba8 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authorization/core/EntityPermissionManagementService.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authorization/core/EntityPermissionManagementService.scala @@ -16,7 +16,7 @@ class EntityPermissionManagementService(entityService: EntityService) extends Pe _ <- ZIO .serviceWith[WalletAdministrationContext](_.isAuthorized(walletId)) .filterOrFail(identity)(Error.WalletNotFoundById(walletId)) - _ <- entityService.assignWallet(entity.id, walletId.toUUID).mapError[Error](e => e) + _ <- entityService.assignWallet(entity.id, walletId.toUUID).orDieAsUnmanagedFailure } yield () } diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authorization/core/PermissionManagement.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authorization/core/PermissionManagement.scala index 7c0b90cdd8..e4f9f52cd7 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authorization/core/PermissionManagement.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authorization/core/PermissionManagement.scala @@ -1,12 +1,6 @@ package org.hyperledger.identus.iam.authorization.core import org.hyperledger.identus.agent.walletapi.model.error.EntityServiceError -import org.hyperledger.identus.agent.walletapi.model.error.EntityServiceError.{ - EntityAlreadyExists, - EntityNotFound, - EntityStorageError, - EntityWalletNotFound -} import org.hyperledger.identus.agent.walletapi.model.BaseEntity import org.hyperledger.identus.shared.models.{WalletAdministrationContext, WalletId} import zio.* @@ -42,12 +36,5 @@ object PermissionManagement { case class UnexpectedError(cause: Throwable) extends Error(cause.getMessage) case class ServiceError(cause: String) extends Error(cause) - - given Conversion[EntityServiceError, Error] = { - case e: EntityNotFound => UserNotFoundById(e.id) - case e: EntityAlreadyExists => UnexpectedError(Exception(s"Entity with id ${e.id} already exists.")) - case e: EntityStorageError => UnexpectedError(Exception(s"Entity storage error: ${e.message}")) - case e: EntityWalletNotFound => WalletNotFoundById(WalletId.fromUUID(e.walletId)) - } } } diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/entity/http/controller/EntityController.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/entity/http/controller/EntityController.scala index ffde118052..a097c43011 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/entity/http/controller/EntityController.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/entity/http/controller/EntityController.scala @@ -1,8 +1,8 @@ package org.hyperledger.identus.iam.entity.http.controller import org.hyperledger.identus.agent.walletapi.model.error.EntityServiceError -import org.hyperledger.identus.api.http.{ErrorResponse, RequestContext} import org.hyperledger.identus.api.http.model.PaginationInput +import org.hyperledger.identus.api.http.{ErrorResponse, RequestContext} import org.hyperledger.identus.iam.entity.http.model.{CreateEntityRequest, EntityResponse, EntityResponsePage} import zio.* @@ -18,19 +18,4 @@ trait EntityController { def deleteEntity(id: UUID)(implicit rc: RequestContext): IO[ErrorResponse, Unit] def addApiKeyAuth(id: UUID, apiKey: String)(implicit rc: RequestContext): IO[ErrorResponse, Unit] def deleteApiKeyAuth(id: UUID, apiKey: String)(implicit rc: RequestContext): IO[ErrorResponse, Unit] -} - -object EntityController { - def domainToHttpError(error: EntityServiceError): ErrorResponse = { - error match { - case EntityServiceError.EntityStorageError(message: String) => - ErrorResponse.internalServerError("RepositoryError", detail = Option(message)) - case EntityServiceError.EntityNotFound(id, message) => - ErrorResponse.notFound(detail = Option(message)) - case EntityServiceError.EntityAlreadyExists(id, message) => - ErrorResponse.badRequest(detail = Option(message)) - case ewnf: EntityServiceError.EntityWalletNotFound => - ErrorResponse.badRequest(detail = Option(ewnf.message)) - } - } -} +} \ No newline at end of file diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/entity/http/controller/EntityControllerImpl.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/entity/http/controller/EntityControllerImpl.scala index a0a3200bf5..1611980544 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/entity/http/controller/EntityControllerImpl.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/entity/http/controller/EntityControllerImpl.scala @@ -8,10 +8,12 @@ import org.hyperledger.identus.api.http.model.PaginationInput import org.hyperledger.identus.iam.authentication.apikey.ApiKeyAuthenticator import org.hyperledger.identus.iam.authentication.AuthenticationError import org.hyperledger.identus.iam.entity.http.model.{CreateEntityRequest, EntityResponse, EntityResponsePage} +import org.hyperledger.identus.shared.models.Failure import zio.{IO, URLayer, ZLayer} import zio.ZIO.succeed import java.util.UUID +import scala.language.implicitConversions case class EntityControllerImpl(service: EntityService, apiKeyAuthenticator: ApiKeyAuthenticator) extends EntityController { @@ -23,14 +25,14 @@ case class EntityControllerImpl(service: EntityService, apiKeyAuthenticator: Api createdEntity <- service.create(entityToCreate) self = rc.request.uri.addPath(createdEntity.id.toString).toString } yield EntityResponse.fromDomain(createdEntity).withSelf(self) - } mapError (EntityController.domainToHttpError) + } override def getEntity(id: UUID)(implicit rc: RequestContext): IO[ErrorResponse, EntityResponse] = { for { entity <- service.getById(id) self = rc.request.uri.toString } yield EntityResponse.fromDomain(entity).withSelf(self) - } mapError (EntityController.domainToHttpError) + } // TODO: add the missing pagination fields to the response override def getEntities(paginationIn: PaginationInput)(implicit @@ -40,7 +42,7 @@ case class EntityControllerImpl(service: EntityService, apiKeyAuthenticator: Api entities <- service.getAll(paginationIn.offset, paginationIn.limit) self = rc.request.uri.toString } yield EntityResponsePage.fromDomain(entities).withSelf(self) - } mapError (EntityController.domainToHttpError) + } override def updateEntityName(id: UUID, name: String)(implicit rc: RequestContext @@ -50,7 +52,7 @@ case class EntityControllerImpl(service: EntityService, apiKeyAuthenticator: Api updatedEntity <- service.getById(id) self = rc.request.uri.toString } yield EntityResponse.fromDomain(updatedEntity).withSelf(self) - } mapError (EntityController.domainToHttpError) + } override def updateEntityWalletId(id: UUID, walletId: UUID)(implicit rc: RequestContext @@ -60,13 +62,13 @@ case class EntityControllerImpl(service: EntityService, apiKeyAuthenticator: Api updatedEntity <- service.getById(id) self = rc.request.uri.toString } yield EntityResponse.fromDomain(updatedEntity).withSelf(self) - } mapError (EntityController.domainToHttpError) + } override def deleteEntity(id: UUID)(implicit rc: RequestContext): IO[ErrorResponse, Unit] = { for { _ <- service.deleteById(id) } yield () - } mapError (EntityController.domainToHttpError) + } override def addApiKeyAuth(id: UUID, apiKey: String)(implicit rc: RequestContext): IO[ErrorResponse, Unit] = { service @@ -75,8 +77,7 @@ case class EntityControllerImpl(service: EntityService, apiKeyAuthenticator: Api .mapError { case ae: AuthenticationError => ErrorResponse.internalServerError("AuthenticationRepositoryError", detail = Option(ae.message)) - case ese: EntityServiceError => - EntityController.domainToHttpError(ese) + case f: Failure => f } } @@ -87,8 +88,7 @@ case class EntityControllerImpl(service: EntityService, apiKeyAuthenticator: Api .mapError { case ae: AuthenticationError => ErrorResponse.internalServerError("AuthenticationRepositoryError", detail = Option(ae.message)) - case ese: EntityServiceError => - EntityController.domainToHttpError(ese) + case f: Failure => f } } } diff --git a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/model/error/EntityServiceError.scala b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/model/error/EntityServiceError.scala index e2fef2dd9f..15844d2b1f 100644 --- a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/model/error/EntityServiceError.scala +++ b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/model/error/EntityServiceError.scala @@ -1,16 +1,26 @@ package org.hyperledger.identus.agent.walletapi.model.error +import org.hyperledger.identus.shared.models.{Failure, StatusCode} + import java.util.UUID -sealed trait EntityServiceError { - def message: String +sealed trait EntityServiceError( + val statusCode: StatusCode, + val userFacingMessage: String +) extends Failure { + override val namespace: String = "EntityServiceError" } object EntityServiceError { - final case class EntityNotFound(id: UUID, message: String) extends EntityServiceError - final case class EntityAlreadyExists(id: UUID, message: String) extends EntityServiceError - final case class EntityStorageError(message: String) extends EntityServiceError - final case class EntityWalletNotFound(entityId: UUID, walletId: UUID) extends EntityServiceError { - override def message: String = s"Wallet with id:$walletId not found for entity with id:$entityId" - } + final case class EntityNotFound(id: UUID) + extends EntityServiceError( + StatusCode.NotFound, + s"There is no entity matching the given identifier: id=$id" + ) + + final case class WalletNotFound(walletId: UUID) + extends EntityServiceError( + StatusCode.NotFound, + s"There is no wallet matching the given identifier: walletId:$walletId" + ) } diff --git a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/service/EntityService.scala b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/service/EntityService.scala index 11f75a077a..aa60c1ed31 100644 --- a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/service/EntityService.scala +++ b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/service/EntityService.scala @@ -1,21 +1,22 @@ package org.hyperledger.identus.agent.walletapi.service import org.hyperledger.identus.agent.walletapi.model.error.EntityServiceError +import org.hyperledger.identus.agent.walletapi.model.error.EntityServiceError.{EntityNotFound, WalletNotFound} import org.hyperledger.identus.agent.walletapi.model.Entity -import zio.IO +import zio.{IO, UIO} import java.util.UUID trait EntityService { - def create(entity: Entity): IO[EntityServiceError, Entity] + def create(entity: Entity): IO[WalletNotFound, Entity] - def getById(entityId: UUID): IO[EntityServiceError, Entity] + def getById(entityId: UUID): IO[EntityNotFound, Entity] - def getAll(offset: Option[Int], limit: Option[Int]): IO[EntityServiceError, Seq[Entity]] + def getAll(offset: Option[Int], limit: Option[Int]): UIO[Seq[Entity]] - def deleteById(entityId: UUID): IO[EntityServiceError, Unit] + def deleteById(entityId: UUID): IO[EntityNotFound, Unit] - def updateName(entityId: UUID, name: String): IO[EntityServiceError, Unit] + def updateName(entityId: UUID, name: String): IO[EntityNotFound, Unit] - def assignWallet(entityId: UUID, walletId: UUID): IO[EntityServiceError, Unit] + def assignWallet(entityId: UUID, walletId: UUID): IO[EntityNotFound | WalletNotFound, Unit] } diff --git a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/service/EntityServiceImpl.scala b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/service/EntityServiceImpl.scala index d990c6c299..cc3b0170f2 100644 --- a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/service/EntityServiceImpl.scala +++ b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/service/EntityServiceImpl.scala @@ -1,61 +1,64 @@ package org.hyperledger.identus.agent.walletapi.service import org.hyperledger.identus.agent.walletapi.model.error.EntityServiceError +import org.hyperledger.identus.agent.walletapi.model.error.EntityServiceError.{EntityNotFound, WalletNotFound} import org.hyperledger.identus.agent.walletapi.model.Entity import org.hyperledger.identus.agent.walletapi.sql.EntityRepository -import zio.{IO, URLayer, ZIO, ZLayer} +import org.hyperledger.identus.shared.models.{WalletAdministrationContext, WalletId} +import zio.{IO, UIO, URLayer, ZLayer} import java.util.UUID -class EntityServiceImpl(repository: EntityRepository) extends EntityService { - def create(entity: Entity): IO[EntityServiceError, Entity] = { +class EntityServiceImpl(repository: EntityRepository, walletManagementService: WalletManagementService) + extends EntityService { + def create(entity: Entity): IO[WalletNotFound, Entity] = { for { - _ <- repository.insert(entity) - _ <- ZIO.logInfo(s"Entity created: $entity") + _ <- walletManagementService + .findWallet(WalletId.fromUUID(entity.walletId)) + .someOrFail(WalletNotFound(entity.walletId)) + .provide(ZLayer.succeed(WalletAdministrationContext.Admin())) + entity <- repository.insert(entity) } yield entity - } logError ("Entity creation failed") - def getById(entityId: UUID): IO[EntityServiceError, Entity] = { - for { - entity <- repository - .getById(entityId) - .logError(s"Entity retrieval failed for $entityId") - } yield entity } - override def getAll(offset: Option[Int], limit: Option[Int]): IO[EntityServiceError, Seq[Entity]] = { - for { - entities <- repository - .getAll(offset.getOrElse(0), limit.getOrElse(100)) - .logError("Entity retrieval failed") - } yield entities + def getById(entityId: UUID): IO[EntityNotFound, Entity] = { + repository + .findById(entityId) + .someOrFail(EntityNotFound(entityId)) } - def deleteById(entityId: UUID): IO[EntityServiceError, Unit] = { + override def getAll(offset: Option[Int], limit: Option[Int]): UIO[Seq[Entity]] = { + repository.getAll(offset.getOrElse(0), limit.getOrElse(100)) + } + + def deleteById(entityId: UUID): IO[EntityNotFound, Unit] = { for { + _ <- getById(entityId) _ <- repository.delete(entityId) - _ <- ZIO.logInfo(s"Entity deleted: $entityId") } yield () - } logError (s"Entity deletion failed for $entityId") + } - override def updateName(entityId: UUID, name: String): IO[EntityServiceError, Unit] = { + override def updateName(entityId: UUID, name: String): IO[EntityNotFound, Unit] = { for { - _ <- repository - .updateName(entityId, name) - .logError(s"Entity name update failed for $entityId") + _ <- getById(entityId) + _ <- repository.updateName(entityId, name) } yield () } - override def assignWallet(entityId: UUID, walletId: UUID): IO[EntityServiceError, Unit] = { + override def assignWallet(entityId: UUID, walletId: UUID): IO[EntityNotFound | WalletNotFound, Unit] = { for { - _ <- repository - .updateWallet(entityId, walletId) - .logError(s"Entity wallet assignment failed for $entityId") + _ <- walletManagementService + .findWallet(WalletId.fromUUID(walletId)) + .someOrFail(WalletNotFound(walletId)) + .provide(ZLayer.succeed(WalletAdministrationContext.Admin())) + _ <- getById(entityId) + _ <- repository.updateWallet(entityId, walletId) } yield () } } object EntityServiceImpl { - val layer: URLayer[EntityRepository, EntityService] = - ZLayer.fromFunction(new EntityServiceImpl(_)) + val layer: URLayer[EntityRepository & WalletManagementService, EntityService] = + ZLayer.fromFunction(new EntityServiceImpl(_, _)) } diff --git a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/EntityRepository.scala b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/EntityRepository.scala index cb272c33e0..405fb5baab 100644 --- a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/EntityRepository.scala +++ b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/EntityRepository.scala @@ -12,6 +12,7 @@ import java.util.UUID trait EntityRepository { def insert(entity: Entity): UIO[Entity] def getById(id: UUID): UIO[Entity] + def findById(id: UUID): UIO[Option[Entity]] def updateName(entityId: UUID, name: String): UIO[Unit] def updateWallet(entityId: UUID, walletId: UUID): UIO[Unit] def delete(id: UUID): UIO[Unit] diff --git a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/JdbcEntityRepository.scala b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/JdbcEntityRepository.scala index e7ab00d1f2..be4aac96dc 100644 --- a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/JdbcEntityRepository.scala +++ b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/JdbcEntityRepository.scala @@ -2,7 +2,6 @@ package org.hyperledger.identus.agent.walletapi.sql import doobie.* import doobie.implicits.* -import org.hyperledger.identus.agent.walletapi.model.error.EntityServiceError import org.hyperledger.identus.agent.walletapi.model.Entity import org.hyperledger.identus.shared.db.Implicits.ensureOneAffectedRowOrDie import zio.* @@ -28,6 +27,14 @@ class JdbcEntityRepository(xa: Transactor[Task]) extends EntityRepository { .orDie } + override def findById(id: UUID): UIO[Option[Entity]] = { + EntityStorageSql + .getById(id) + .transact(xa) + .map(_.headOption.map(db2model)) + .orDie + } + override def updateName(entityId: UUID, name: String): UIO[Unit] = { EntityStorageSql .updateName(entityId, name) From a7023e66c080cd3540d9776b6ca20b2dee40d2f2 Mon Sep 17 00:00:00 2001 From: Benjamin Voiturier Date: Wed, 19 Jun 2024 13:44:14 +0200 Subject: [PATCH 03/11] chore: make AuthenticationError extend the shared Failure trait Signed-off-by: Benjamin Voiturier --- .../identus/agent/server/CloudAgentApp.scala | 2 +- .../iam/authentication/Authenticator.scala | 47 +++++++++++++++---- .../admin/AdminApiKeyCredentials.scala | 7 ++- .../apikey/ApiKeyAuthenticatorImpl.scala | 2 +- .../iam/authentication/apikey/package.scala | 8 +++- .../authentication/oidc/JwtCredentials.scala | 7 ++- .../controller/EntityControllerImpl.scala | 5 +- .../http/CredentialErrorResponse.scala | 2 +- .../identus/shared/models/Failure.scala | 2 + 9 files changed, 63 insertions(+), 19 deletions(-) diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/CloudAgentApp.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/CloudAgentApp.scala index 5820df2c73..a15014061d 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/CloudAgentApp.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/CloudAgentApp.scala @@ -214,7 +214,7 @@ object AgentInitialization { .createWallet(defaultWallet, seed) .orDieAsUnmanagedFailure _ <- entityService.create(defaultEntity).orDieAsUnmanagedFailure - _ <- apiKeyAuth.add(defaultEntity.id, config.authApiKey).mapError(e => Exception(e.message)) + _ <- apiKeyAuth.add(defaultEntity.id, config.authApiKey).mapError(e => Exception(e.userFacingMessage)) _ <- config.webhookUrl.fold(ZIO.unit) { url => val customHeaders = config.webhookApiKey.fold(Map.empty)(apiKey => Map("Authorization" -> s"Bearer $apiKey")) walletService diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/Authenticator.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/Authenticator.scala index 02bfa8375f..c15c5f8ebc 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/Authenticator.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/Authenticator.scala @@ -2,35 +2,62 @@ package org.hyperledger.identus.iam.authentication import org.hyperledger.identus.agent.walletapi.model.{BaseEntity, Entity, EntityRole} import org.hyperledger.identus.api.http.ErrorResponse -import org.hyperledger.identus.shared.models.{WalletAccessContext, WalletAdministrationContext, WalletId} +import org.hyperledger.identus.shared.models.* import zio.{IO, ZIO, ZLayer} trait Credentials -trait AuthenticationError { - def message: String +trait AuthenticationError( + val statusCode: StatusCode, + val userFacingMessage: String +) extends Failure { + override val namespace: String = "AuthenticationError" } object AuthenticationError { - case class InvalidCredentials(message: String) extends AuthenticationError + case class InvalidCredentials(message: String) + extends AuthenticationError( + StatusCode.Unauthorized, + message + ) - case class AuthenticationMethodNotEnabled(message: String) extends AuthenticationError + case class AuthenticationMethodNotEnabled(message: String) + extends AuthenticationError( + StatusCode.Unauthorized, + message + ) - case class UnexpectedError(message: String) extends AuthenticationError + case class UnexpectedError(message: String) + extends AuthenticationError( + StatusCode.InternalServerError, + message + ) - case class ServiceError(message: String) extends AuthenticationError + case class ServiceError(message: String) + extends AuthenticationError( + StatusCode.InternalServerError, + message + ) - case class ResourceNotPermitted(message: String) extends AuthenticationError + case class ResourceNotPermitted(message: String) + extends AuthenticationError( + StatusCode.Forbidden, + message + ) - case class InvalidRole(message: String) extends AuthenticationError + case class InvalidRole(message: String) + extends AuthenticationError( + StatusCode.Forbidden, + message + ) def toErrorResponse(error: AuthenticationError): ErrorResponse = ErrorResponse( status = sttp.model.StatusCode.Forbidden.code, `type` = "authentication_error", title = "", - detail = Option(error.message) + detail = Option(error.userFacingMessage) ) } diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/admin/AdminApiKeyCredentials.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/admin/AdminApiKeyCredentials.scala index 9903edac58..a2155b0187 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/admin/AdminApiKeyCredentials.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/admin/AdminApiKeyCredentials.scala @@ -1,8 +1,13 @@ package org.hyperledger.identus.iam.authentication.admin import org.hyperledger.identus.iam.authentication.{AuthenticationError, Credentials} +import org.hyperledger.identus.shared.models.StatusCode -case class AdminApiKeyAuthenticationError(message: String) extends AuthenticationError +case class AdminApiKeyAuthenticationError(message: String) + extends AuthenticationError( + StatusCode.Unauthorized, + message + ) object AdminApiKeyAuthenticationError { val invalidAdminApiKey = AdminApiKeyAuthenticationError("Invalid Admin API key in header `x-admin-api-key`") diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/apikey/ApiKeyAuthenticatorImpl.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/apikey/ApiKeyAuthenticatorImpl.scala index 3639f39caf..4ffb2bc4ef 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/apikey/ApiKeyAuthenticatorImpl.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/apikey/ApiKeyAuthenticatorImpl.scala @@ -61,7 +61,7 @@ case class ApiKeyAuthenticatorImpl( entityToCreate = Entity(name = "Auto provisioned entity", walletId = wallet.id.toUUID) entity <- entityService.create(entityToCreate).orDieAsUnmanagedFailure _ <- add(entity.id, apiKey) - .mapError(are => AuthenticationRepositoryError.ServiceError(are.message)) + .mapError(are => AuthenticationRepositoryError.ServiceError(are.userFacingMessage)) } yield entity } diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/apikey/package.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/apikey/package.scala index e1dd6716e7..e7957fb9d3 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/apikey/package.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/apikey/package.scala @@ -1,9 +1,15 @@ package org.hyperledger.identus.iam.authentication +import org.hyperledger.identus.shared.models.StatusCode + package object apikey { case class ApiKeyCredentials(apiKey: Option[String]) extends Credentials - case class ApiKeyAuthenticationError(message: String) extends AuthenticationError + case class ApiKeyAuthenticationError(message: String) + extends AuthenticationError( + StatusCode.Unauthorized, + message + ) object ApiKeyAuthenticationError { val invalidApiKey = ApiKeyAuthenticationError("Invalid `apikey` header provided") diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/oidc/JwtCredentials.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/oidc/JwtCredentials.scala index f8936030ec..f8cb9132d5 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/oidc/JwtCredentials.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/oidc/JwtCredentials.scala @@ -1,10 +1,15 @@ package org.hyperledger.identus.iam.authentication.oidc import org.hyperledger.identus.iam.authentication.{AuthenticationError, Credentials} +import org.hyperledger.identus.shared.models.StatusCode final case class JwtCredentials(token: Option[String]) extends Credentials -final case class JwtAuthenticationError(message: String) extends AuthenticationError +final case class JwtAuthenticationError(message: String) + extends AuthenticationError( + StatusCode.Unauthorized, + message + ) object JwtAuthenticationError { val emptyToken = JwtAuthenticationError("Empty bearer token header provided") diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/entity/http/controller/EntityControllerImpl.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/entity/http/controller/EntityControllerImpl.scala index 1611980544..0688264f24 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/entity/http/controller/EntityControllerImpl.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/entity/http/controller/EntityControllerImpl.scala @@ -1,6 +1,5 @@ package org.hyperledger.identus.iam.entity.http.controller -import org.hyperledger.identus.agent.walletapi.model.error.EntityServiceError import org.hyperledger.identus.agent.walletapi.model.Entity import org.hyperledger.identus.agent.walletapi.service.EntityService import org.hyperledger.identus.api.http.{ErrorResponse, RequestContext} @@ -76,7 +75,7 @@ case class EntityControllerImpl(service: EntityService, apiKeyAuthenticator: Api .flatMap(entity => apiKeyAuthenticator.add(entity.id, apiKey)) .mapError { case ae: AuthenticationError => - ErrorResponse.internalServerError("AuthenticationRepositoryError", detail = Option(ae.message)) + ErrorResponse.internalServerError("AuthenticationRepositoryError", detail = Option(ae.userFacingMessage)) case f: Failure => f } } @@ -87,7 +86,7 @@ case class EntityControllerImpl(service: EntityService, apiKeyAuthenticator: Api .flatMap(entity => apiKeyAuthenticator.delete(entity.id, apiKey)) .mapError { case ae: AuthenticationError => - ErrorResponse.internalServerError("AuthenticationRepositoryError", detail = Option(ae.message)) + ErrorResponse.internalServerError("AuthenticationRepositoryError", detail = Option(ae.userFacingMessage)) case f: Failure => f } } diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/oid4vci/http/CredentialErrorResponse.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/oid4vci/http/CredentialErrorResponse.scala index 972cc7a0ea..b676d63222 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/oid4vci/http/CredentialErrorResponse.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/oid4vci/http/CredentialErrorResponse.scala @@ -55,7 +55,7 @@ object CredentialErrorResponse { case _: InvalidCredentials => CredentialErrorCode.invalid_token case _ => CredentialErrorCode.invalid_request } - CredentialErrorResponse(error, Some(ae.message)) + CredentialErrorResponse(error, Some(ae.userFacingMessage)) } } diff --git a/shared/core/src/main/scala/org/hyperledger/identus/shared/models/Failure.scala b/shared/core/src/main/scala/org/hyperledger/identus/shared/models/Failure.scala index 2488ca4ef2..3a895223f3 100644 --- a/shared/core/src/main/scala/org/hyperledger/identus/shared/models/Failure.scala +++ b/shared/core/src/main/scala/org/hyperledger/identus/shared/models/Failure.scala @@ -24,6 +24,8 @@ sealed class StatusCode(val code: Int) object StatusCode { val BadRequest: StatusCode = StatusCode(400) + val Unauthorized: StatusCode = StatusCode(401) + val Forbidden: StatusCode = StatusCode(403) val NotFound: StatusCode = StatusCode(404) val UnprocessableContent: StatusCode = StatusCode(422) From 7ba614a9c559711f9bdde67d735a6884a17d5e88 Mon Sep 17 00:00:00 2001 From: Benjamin Voiturier Date: Wed, 19 Jun 2024 14:18:30 +0200 Subject: [PATCH 04/11] chore: finalize entity controller refactoring Signed-off-by: Benjamin Voiturier --- .../http/controller/EntityControllerImpl.scala | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/entity/http/controller/EntityControllerImpl.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/entity/http/controller/EntityControllerImpl.scala index 0688264f24..6535fcc799 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/entity/http/controller/EntityControllerImpl.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/entity/http/controller/EntityControllerImpl.scala @@ -5,9 +5,7 @@ import org.hyperledger.identus.agent.walletapi.service.EntityService import org.hyperledger.identus.api.http.{ErrorResponse, RequestContext} import org.hyperledger.identus.api.http.model.PaginationInput import org.hyperledger.identus.iam.authentication.apikey.ApiKeyAuthenticator -import org.hyperledger.identus.iam.authentication.AuthenticationError import org.hyperledger.identus.iam.entity.http.model.{CreateEntityRequest, EntityResponse, EntityResponsePage} -import org.hyperledger.identus.shared.models.Failure import zio.{IO, URLayer, ZLayer} import zio.ZIO.succeed @@ -73,22 +71,12 @@ case class EntityControllerImpl(service: EntityService, apiKeyAuthenticator: Api service .getById(id) .flatMap(entity => apiKeyAuthenticator.add(entity.id, apiKey)) - .mapError { - case ae: AuthenticationError => - ErrorResponse.internalServerError("AuthenticationRepositoryError", detail = Option(ae.userFacingMessage)) - case f: Failure => f - } } override def deleteApiKeyAuth(id: UUID, apiKey: String)(implicit rc: RequestContext): IO[ErrorResponse, Unit] = { service .getById(id) .flatMap(entity => apiKeyAuthenticator.delete(entity.id, apiKey)) - .mapError { - case ae: AuthenticationError => - ErrorResponse.internalServerError("AuthenticationRepositoryError", detail = Option(ae.userFacingMessage)) - case f: Failure => f - } } } From 71a51cfa86e64c5679da7d1f17f0867d21a271f2 Mon Sep 17 00:00:00 2001 From: Benjamin Voiturier Date: Wed, 19 Jun 2024 16:29:13 +0200 Subject: [PATCH 05/11] chore: run scalafmt Signed-off-by: Benjamin Voiturier --- .../identus/iam/entity/http/controller/EntityController.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/entity/http/controller/EntityController.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/entity/http/controller/EntityController.scala index a097c43011..b90dbb67b0 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/entity/http/controller/EntityController.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/entity/http/controller/EntityController.scala @@ -1,8 +1,8 @@ package org.hyperledger.identus.iam.entity.http.controller import org.hyperledger.identus.agent.walletapi.model.error.EntityServiceError -import org.hyperledger.identus.api.http.model.PaginationInput import org.hyperledger.identus.api.http.{ErrorResponse, RequestContext} +import org.hyperledger.identus.api.http.model.PaginationInput import org.hyperledger.identus.iam.entity.http.model.{CreateEntityRequest, EntityResponse, EntityResponsePage} import zio.* @@ -18,4 +18,4 @@ trait EntityController { def deleteEntity(id: UUID)(implicit rc: RequestContext): IO[ErrorResponse, Unit] def addApiKeyAuth(id: UUID, apiKey: String)(implicit rc: RequestContext): IO[ErrorResponse, Unit] def deleteApiKeyAuth(id: UUID, apiKey: String)(implicit rc: RequestContext): IO[ErrorResponse, Unit] -} \ No newline at end of file +} From 5d047aa5343034bea16a03f4c2cd5b636122baa0 Mon Sep 17 00:00:00 2001 From: Benjamin Voiturier Date: Thu, 20 Jun 2024 09:40:41 +0200 Subject: [PATCH 06/11] chore: sstart refactoring authentication repository Signed-off-by: Benjamin Voiturier --- .../iam/authentication/Authenticator.scala | 8 ++++++ .../apikey/ApiKeyAuthenticatorImpl.scala | 12 ++++----- .../apikey/AuthenticationRepository.scala | 9 ++----- .../apikey/JdbcAuthenticationRepository.scala | 26 +++---------------- .../JdbcAuthenticationRepositorySpec.scala | 7 ++--- 5 files changed, 23 insertions(+), 39 deletions(-) diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/Authenticator.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/Authenticator.scala index c15c5f8ebc..3c8c06f438 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/Authenticator.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/Authenticator.scala @@ -2,6 +2,7 @@ package org.hyperledger.identus.iam.authentication import org.hyperledger.identus.agent.walletapi.model.{BaseEntity, Entity, EntityRole} import org.hyperledger.identus.api.http.ErrorResponse +import org.hyperledger.identus.iam.authentication.apikey.AuthenticationMethodType import org.hyperledger.identus.shared.models.* import zio.{IO, ZIO, ZLayer} @@ -22,6 +23,13 @@ object AuthenticationError { message ) + def hide(secret: String) = secret.take(8) + "****" + case class AuthenticationNotFound(authenticationMethodType: AuthenticationMethodType, secret: String) + extends AuthenticationError( + StatusCode.NotFound, + s"Authentication method not found for type:${authenticationMethodType.value} and secret:${hide(secret)}" + ) + case class AuthenticationMethodNotEnabled(message: String) extends AuthenticationError( StatusCode.Unauthorized, diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/apikey/ApiKeyAuthenticatorImpl.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/apikey/ApiKeyAuthenticatorImpl.scala index 4ffb2bc4ef..409605dfa4 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/apikey/ApiKeyAuthenticatorImpl.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/apikey/ApiKeyAuthenticatorImpl.scala @@ -28,13 +28,11 @@ case class ApiKeyAuthenticatorImpl( } else { authenticateBy(apiKey) .catchSome { - case AuthenticationRepositoryError.AuthenticationNotFound(method, secret) + case AuthenticationError.AuthenticationNotFound(method, secret) if apiKeyConfig.autoProvisioning => provisionNewEntity(apiKey) } .mapError { - case AuthenticationRepositoryError.AuthenticationNotFound(method, secret) => - InvalidCredentials("Invalid API key") case AuthenticationRepositoryError.StorageError(cause) => UnexpectedError("Internal error") case AuthenticationRepositoryError.UnexpectedError(cause) => @@ -65,15 +63,15 @@ case class ApiKeyAuthenticatorImpl( } yield entity } - protected[apikey] def authenticateBy(apiKey: String): IO[AuthenticationRepositoryError, Entity] = { + protected[apikey] def authenticateBy(apiKey: String): IO[AuthenticationNotFound, Entity] = { for { saltAndApiKey <- ZIO.succeed(apiKeyConfig.salt + apiKey) secret <- ZIO .fromTry(Try(Sha256Hash.compute(saltAndApiKey.getBytes).hexEncoded)) - .logError("Failed to compute SHA256 hash") - .mapError(cause => AuthenticationRepositoryError.UnexpectedError(cause)) + .orDie entityId <- repository - .getEntityIdByMethodAndSecret(AuthenticationMethodType.ApiKey, secret) + .findEntityIdByMethodAndSecret(AuthenticationMethodType.ApiKey, secret) + .someOrFail(AuthenticationNotFound(AuthenticationMethodType.ApiKey, secret)) entity <- entityService.getById(entityId).orDieAsUnmanagedFailure } yield entity } diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/apikey/AuthenticationRepository.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/apikey/AuthenticationRepository.scala index ffe24acc44..163a7d5155 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/apikey/AuthenticationRepository.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/apikey/AuthenticationRepository.scala @@ -36,10 +36,10 @@ trait AuthenticationRepository { secret: String ): zio.IO[AuthenticationRepositoryError, Unit] - def getEntityIdByMethodAndSecret( + def findEntityIdByMethodAndSecret( amt: AuthenticationMethodType, secret: String - ): zio.IO[AuthenticationRepositoryError, UUID] + ): zio.UIO[Option[UUID]] def findAuthenticationMethodByTypeAndSecret( amt: AuthenticationMethodType, @@ -66,11 +66,6 @@ sealed trait AuthenticationRepositoryError { object AuthenticationRepositoryError { def hide(secret: String) = secret.take(8) + "****" - case class AuthenticationNotFound(authenticationMethodType: AuthenticationMethodType, secret: String) - extends AuthenticationRepositoryError { - def message = - s"Authentication method not found for type:${authenticationMethodType.value} and secret:${hide(secret)}" - } case class AuthenticationCompromised( entityId: UUID, diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/apikey/JdbcAuthenticationRepository.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/apikey/JdbcAuthenticationRepository.scala index 4502fd2492..fb1e979b25 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/apikey/JdbcAuthenticationRepository.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/apikey/JdbcAuthenticationRepository.scala @@ -65,20 +65,15 @@ case class JdbcAuthenticationRepository(xa: Transactor[Task]) extends Authentica } yield result } - override def getEntityIdByMethodAndSecret( + override def findEntityIdByMethodAndSecret( amt: AuthenticationMethodType, secret: String - ): IO[AuthenticationRepositoryError, UUID] = { + ): UIO[Option[UUID]] = { AuthenticationRepositorySql .getEntityIdByMethodAndSecret(amt, secret) .transact(xa) - .logError(s"getEntityIdByMethodAndSecret failed for method: $amt and secret: $secret") - .mapError(AuthenticationRepositoryError.StorageError.apply) - .flatMap( - _.headOption.fold(ZIO.fail(AuthenticationRepositoryError.AuthenticationNotFound(amt, secret)))(entityId => - ZIO.succeed(entityId) - ) - ) + .map(_.headOption) + .orDie } override def findAuthenticationMethodByTypeAndSecret( @@ -117,19 +112,6 @@ case class JdbcAuthenticationRepository(xa: Transactor[Task]) extends Authentica .mapError(AuthenticationRepositoryError.StorageError.apply) .map(_ => ()) } - - def checkDeleted(method: AuthenticationMethodType, secret: String) = { - AuthenticationRepositorySql - .getEntityIdByMethodAndSecret(method, secret) - .transact(xa) - .logError(s"getEntityIdByMethodAndSecret failed for method: $method and secret: $secret") - .mapError(AuthenticationRepositoryError.StorageError.apply) - .flatMap( - _.headOption.fold(ZIO.fail(AuthenticationRepositoryError.AuthenticationNotFound(method, secret)))(entityId => - ZIO.succeed(entityId) - ) - ) - } } object JdbcAuthenticationRepository { diff --git a/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/iam/authentication/apikey/JdbcAuthenticationRepositorySpec.scala b/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/iam/authentication/apikey/JdbcAuthenticationRepositorySpec.scala index bee77e273e..6b71d8296e 100644 --- a/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/iam/authentication/apikey/JdbcAuthenticationRepositorySpec.scala +++ b/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/iam/authentication/apikey/JdbcAuthenticationRepositorySpec.scala @@ -1,6 +1,7 @@ package org.hyperledger.identus.iam.authentication.apikey import org.hyperledger.identus.container.util.MigrationAspects.migrate +import org.hyperledger.identus.iam.authentication.AuthenticationError import org.hyperledger.identus.iam.authentication.apikey.AuthenticationMethodType.ApiKey import org.hyperledger.identus.sharedtest.containers.PostgresTestContainerSupport import zio.test.{TestAspect, ZIOSpecDefault, *} @@ -37,11 +38,11 @@ object JdbcAuthenticationRepositorySpec extends ZIOSpecDefault, PostgresTestCont for { repository <- ZIO.service[AuthenticationRepository] recordId <- repository.insert(entityId, AuthenticationMethodType.ApiKey, secret) - fetchedEntityId <- repository.getEntityIdByMethodAndSecret(AuthenticationMethodType.ApiKey, secret) + fetchedEntityId <- repository.findEntityIdByMethodAndSecret(AuthenticationMethodType.ApiKey, secret) _ <- repository.deleteByMethodAndEntityId(entityId, AuthenticationMethodType.ApiKey) - notFoundEntityId <- repository.getEntityIdByMethodAndSecret(AuthenticationMethodType.ApiKey, secret).flip + notFoundEntityId <- repository.findEntityIdByMethodAndSecret(AuthenticationMethodType.ApiKey, secret).flip } yield assert(entityId)(equalTo(fetchedEntityId)) && - assert(notFoundEntityId)(isSubtype[AuthenticationRepositoryError.AuthenticationNotFound](anything)) + assert(notFoundEntityId)(isSubtype[AuthenticationError.AuthenticationNotFound](anything)) } }, test("insert a similar secret for a different tenant must fail") { From 8f570082d3b3ed1bc5d93b54040a7f1a9cf70201 Mon Sep 17 00:00:00 2001 From: Benjamin Voiturier Date: Thu, 20 Jun 2024 10:32:04 +0200 Subject: [PATCH 07/11] chore: refactor authentication repository - step 2 Signed-off-by: Benjamin Voiturier --- .../iam/authentication/Authenticator.scala | 8 -------- .../apikey/ApiKeyAuthenticatorImpl.scala | 12 +++++------- .../apikey/AuthenticationRepository.scala | 6 +++--- .../apikey/JdbcAuthenticationRepository.scala | 17 +++++++---------- .../JdbcAuthenticationRepositorySpec.scala | 7 +++---- 5 files changed, 18 insertions(+), 32 deletions(-) diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/Authenticator.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/Authenticator.scala index 3c8c06f438..c15c5f8ebc 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/Authenticator.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/Authenticator.scala @@ -2,7 +2,6 @@ package org.hyperledger.identus.iam.authentication import org.hyperledger.identus.agent.walletapi.model.{BaseEntity, Entity, EntityRole} import org.hyperledger.identus.api.http.ErrorResponse -import org.hyperledger.identus.iam.authentication.apikey.AuthenticationMethodType import org.hyperledger.identus.shared.models.* import zio.{IO, ZIO, ZLayer} @@ -23,13 +22,6 @@ object AuthenticationError { message ) - def hide(secret: String) = secret.take(8) + "****" - case class AuthenticationNotFound(authenticationMethodType: AuthenticationMethodType, secret: String) - extends AuthenticationError( - StatusCode.NotFound, - s"Authentication method not found for type:${authenticationMethodType.value} and secret:${hide(secret)}" - ) - case class AuthenticationMethodNotEnabled(message: String) extends AuthenticationError( StatusCode.Unauthorized, diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/apikey/ApiKeyAuthenticatorImpl.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/apikey/ApiKeyAuthenticatorImpl.scala index 409605dfa4..1a28f96dbc 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/apikey/ApiKeyAuthenticatorImpl.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/apikey/ApiKeyAuthenticatorImpl.scala @@ -28,8 +28,7 @@ case class ApiKeyAuthenticatorImpl( } else { authenticateBy(apiKey) .catchSome { - case AuthenticationError.AuthenticationNotFound(method, secret) - if apiKeyConfig.autoProvisioning => + case AuthenticationError.InvalidCredentials(message) if apiKeyConfig.autoProvisioning => provisionNewEntity(apiKey) } .mapError { @@ -41,6 +40,7 @@ case class ApiKeyAuthenticatorImpl( UnexpectedError("Internal error") case AuthenticationRepositoryError.AuthenticationCompromised(entityId, amt, secret) => InvalidCredentials("API key is compromised") + case e: AuthenticationError => e } } } else { @@ -63,7 +63,7 @@ case class ApiKeyAuthenticatorImpl( } yield entity } - protected[apikey] def authenticateBy(apiKey: String): IO[AuthenticationNotFound, Entity] = { + protected[apikey] def authenticateBy(apiKey: String): IO[InvalidCredentials, Entity] = { for { saltAndApiKey <- ZIO.succeed(apiKeyConfig.salt + apiKey) secret <- ZIO @@ -71,7 +71,7 @@ case class ApiKeyAuthenticatorImpl( .orDie entityId <- repository .findEntityIdByMethodAndSecret(AuthenticationMethodType.ApiKey, secret) - .someOrFail(AuthenticationNotFound(AuthenticationMethodType.ApiKey, secret)) + .someOrFail(InvalidCredentials("Invalid API key")) entity <- entityService.getById(entityId).orDieAsUnmanagedFailure } yield entity } @@ -97,9 +97,7 @@ case class ApiKeyAuthenticatorImpl( .fromTry(Try(Sha256Hash.compute(saltAndApiKey.getBytes).hexEncoded)) .logError("Failed to compute SHA256 hash") .mapError(cause => AuthenticationError.UnexpectedError(cause.getMessage)) - _ <- repository - .delete(entityId, AuthenticationMethodType.ApiKey, secret) - .mapError(are => AuthenticationError.UnexpectedError(are.message)) + _ <- repository.delete(entityId, AuthenticationMethodType.ApiKey, secret) } yield () } } diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/apikey/AuthenticationRepository.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/apikey/AuthenticationRepository.scala index 163a7d5155..910cf23bf1 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/apikey/AuthenticationRepository.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/apikey/AuthenticationRepository.scala @@ -44,18 +44,18 @@ trait AuthenticationRepository { def findAuthenticationMethodByTypeAndSecret( amt: AuthenticationMethodType, secret: String - ): zio.IO[AuthenticationRepositoryError, Option[AuthenticationMethod]] + ): zio.UIO[Option[AuthenticationMethod]] def deleteByMethodAndEntityId( entityId: UUID, amt: AuthenticationMethodType - ): zio.IO[AuthenticationRepositoryError, Unit] + ): zio.UIO[Unit] def delete( entityId: UUID, amt: AuthenticationMethodType, secret: String - ): zio.IO[AuthenticationRepositoryError, Unit] + ): zio.UIO[Unit] } //TODO: reconsider the hierarchy of the service and dal layers diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/apikey/JdbcAuthenticationRepository.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/apikey/JdbcAuthenticationRepository.scala index fb1e979b25..d298e59728 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/apikey/JdbcAuthenticationRepository.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/apikey/JdbcAuthenticationRepository.scala @@ -2,6 +2,7 @@ package org.hyperledger.identus.iam.authentication.apikey import doobie.* import doobie.implicits.* +import org.hyperledger.identus.shared.db.Implicits.ensureOneAffectedRowOrDie import org.postgresql.util.PSQLException import zio.* import zio.interop.catz.* @@ -79,38 +80,34 @@ case class JdbcAuthenticationRepository(xa: Transactor[Task]) extends Authentica override def findAuthenticationMethodByTypeAndSecret( amt: AuthenticationMethodType, secret: String - ): IO[AuthenticationRepositoryError, Option[AuthenticationMethod]] = { + ): UIO[Option[AuthenticationMethod]] = { AuthenticationRepositorySql .filterByTypeAndSecret(amt, secret) .transact(xa) - .logError(s"findAuthenticationMethodBySecret failed for secret:$secret") .map(_.headOption) - .mapError(AuthenticationRepositoryError.StorageError.apply) + .orDie } override def deleteByMethodAndEntityId( entityId: UUID, amt: AuthenticationMethodType - ): IO[AuthenticationRepositoryError, Unit] = { + ): UIO[Unit] = { AuthenticationRepositorySql .softDeleteByEntityIdAndType(entityId, amt, Some(OffsetDateTime.now())) .transact(xa) - .logError(s"deleteByMethodAndEntityId failed for method: $amt and entityId: $entityId") - .mapError(AuthenticationRepositoryError.StorageError.apply) .map(_ => ()) + .orDie } override def delete( entityId: UUID, amt: AuthenticationMethodType, secret: String - ): IO[AuthenticationRepositoryError, Unit] = { + ): UIO[Unit] = { AuthenticationRepositorySql .softDeleteBy(entityId, amt, secret, Some(OffsetDateTime.now())) .transact(xa) - .logError(s"deleteByEntityIdAndSecret failed for id: $entityId and secret: $secret") - .mapError(AuthenticationRepositoryError.StorageError.apply) - .map(_ => ()) + .ensureOneAffectedRowOrDie } } diff --git a/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/iam/authentication/apikey/JdbcAuthenticationRepositorySpec.scala b/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/iam/authentication/apikey/JdbcAuthenticationRepositorySpec.scala index 6b71d8296e..92d593337b 100644 --- a/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/iam/authentication/apikey/JdbcAuthenticationRepositorySpec.scala +++ b/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/iam/authentication/apikey/JdbcAuthenticationRepositorySpec.scala @@ -1,7 +1,6 @@ package org.hyperledger.identus.iam.authentication.apikey import org.hyperledger.identus.container.util.MigrationAspects.migrate -import org.hyperledger.identus.iam.authentication.AuthenticationError import org.hyperledger.identus.iam.authentication.apikey.AuthenticationMethodType.ApiKey import org.hyperledger.identus.sharedtest.containers.PostgresTestContainerSupport import zio.test.{TestAspect, ZIOSpecDefault, *} @@ -40,9 +39,9 @@ object JdbcAuthenticationRepositorySpec extends ZIOSpecDefault, PostgresTestCont recordId <- repository.insert(entityId, AuthenticationMethodType.ApiKey, secret) fetchedEntityId <- repository.findEntityIdByMethodAndSecret(AuthenticationMethodType.ApiKey, secret) _ <- repository.deleteByMethodAndEntityId(entityId, AuthenticationMethodType.ApiKey) - notFoundEntityId <- repository.findEntityIdByMethodAndSecret(AuthenticationMethodType.ApiKey, secret).flip - } yield assert(entityId)(equalTo(fetchedEntityId)) && - assert(notFoundEntityId)(isSubtype[AuthenticationError.AuthenticationNotFound](anything)) + notFoundEntityId <- repository.findEntityIdByMethodAndSecret(AuthenticationMethodType.ApiKey, secret) + } yield assert(fetchedEntityId)(isSome(equalTo(entityId))) && + assert(notFoundEntityId)(isNone) } }, test("insert a similar secret for a different tenant must fail") { From 056a840603aa6d38eb930d28c8ca181321fd2f26 Mon Sep 17 00:00:00 2001 From: Benjamin Voiturier Date: Thu, 20 Jun 2024 11:02:03 +0200 Subject: [PATCH 08/11] chore: refactor authentication repository - remove obsolete repo errors Signed-off-by: Benjamin Voiturier --- .../apikey/ApiKeyAuthenticatorImpl.scala | 24 ++++----------- .../apikey/AuthenticationRepository.scala | 30 ++++++++----------- .../apikey/JdbcAuthenticationRepository.scala | 27 +++++++---------- 3 files changed, 29 insertions(+), 52 deletions(-) diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/apikey/ApiKeyAuthenticatorImpl.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/apikey/ApiKeyAuthenticatorImpl.scala index 1a28f96dbc..69300ec439 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/apikey/ApiKeyAuthenticatorImpl.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/apikey/ApiKeyAuthenticatorImpl.scala @@ -6,7 +6,7 @@ import org.hyperledger.identus.iam.authentication.AuthenticationError import org.hyperledger.identus.iam.authentication.AuthenticationError.* import org.hyperledger.identus.shared.crypto.Sha256Hash import org.hyperledger.identus.shared.models.{WalletAdministrationContext, WalletId} -import zio.{IO, URLayer, ZIO, ZLayer} +import zio.{IO, UIO, URLayer, ZIO, ZLayer} import java.util.UUID import scala.language.implicitConversions @@ -31,17 +31,6 @@ case class ApiKeyAuthenticatorImpl( case AuthenticationError.InvalidCredentials(message) if apiKeyConfig.autoProvisioning => provisionNewEntity(apiKey) } - .mapError { - case AuthenticationRepositoryError.StorageError(cause) => - UnexpectedError("Internal error") - case AuthenticationRepositoryError.UnexpectedError(cause) => - UnexpectedError("Internal error") - case AuthenticationRepositoryError.ServiceError(message) => - UnexpectedError("Internal error") - case AuthenticationRepositoryError.AuthenticationCompromised(entityId, amt, secret) => - InvalidCredentials("API key is compromised") - case e: AuthenticationError => e - } } } else { ZIO.fail( @@ -50,7 +39,7 @@ case class ApiKeyAuthenticatorImpl( } } - protected[apikey] def provisionNewEntity(apiKey: String): IO[AuthenticationRepositoryError, Entity] = synchronized { + protected[apikey] def provisionNewEntity(apiKey: String): UIO[Entity] = synchronized { for { wallet <- walletManagementService .createWallet(Wallet("Auto provisioned wallet", WalletId.random)) @@ -59,7 +48,6 @@ case class ApiKeyAuthenticatorImpl( entityToCreate = Entity(name = "Auto provisioned entity", walletId = wallet.id.toUUID) entity <- entityService.create(entityToCreate).orDieAsUnmanagedFailure _ <- add(entity.id, apiKey) - .mapError(are => AuthenticationRepositoryError.ServiceError(are.userFacingMessage)) } yield entity } @@ -76,17 +64,15 @@ case class ApiKeyAuthenticatorImpl( } yield entity } - override def add(entityId: UUID, apiKey: String): IO[AuthenticationError, Unit] = { + override def add(entityId: UUID, apiKey: String): UIO[Unit] = { for { saltAndApiKey <- ZIO.succeed(apiKeyConfig.salt + apiKey) secret <- ZIO .fromTry(Try(Sha256Hash.compute(saltAndApiKey.getBytes).hexEncoded)) - .logError("Failed to compute SHA256 hash") - .mapError(cause => AuthenticationError.UnexpectedError(cause.getMessage)) + .orDie _ <- repository .insert(entityId, AuthenticationMethodType.ApiKey, secret) - .logError(s"Insert operation failed for entityId: $entityId") - .mapError(are => AuthenticationError.UnexpectedError(are.message)) + .orDieAsUnmanagedFailure } yield () } diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/apikey/AuthenticationRepository.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/apikey/AuthenticationRepository.scala index 910cf23bf1..dd85d48399 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/apikey/AuthenticationRepository.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/apikey/AuthenticationRepository.scala @@ -3,6 +3,8 @@ package org.hyperledger.identus.iam.authentication.apikey import io.getquill.* import io.getquill.context.json.PostgresJsonExtensions import io.getquill.doobie.DoobieContext +import org.hyperledger.identus.iam.authentication.apikey.AuthenticationRepositoryError.AuthenticationCompromised +import org.hyperledger.identus.shared.models.{Failure, StatusCode} import zio.{IO, *} import zio.interop.catz.* @@ -34,7 +36,7 @@ trait AuthenticationRepository { entityId: UUID, amt: AuthenticationMethodType, secret: String - ): zio.IO[AuthenticationRepositoryError, Unit] + ): zio.IO[AuthenticationCompromised, Unit] def findEntityIdByMethodAndSecret( amt: AuthenticationMethodType, @@ -59,31 +61,25 @@ trait AuthenticationRepository { } //TODO: reconsider the hierarchy of the service and dal layers -sealed trait AuthenticationRepositoryError { - def message: String +sealed trait AuthenticationRepositoryError( + val statusCode: StatusCode, + val userFacingMessage: String +) extends Failure { + override val namespace: String = "AuthenticationRepositoryError" } object AuthenticationRepositoryError { - def hide(secret: String) = secret.take(8) + "****" + private def hide(secret: String) = secret.take(8) + "****" case class AuthenticationCompromised( entityId: UUID, authenticationMethodType: AuthenticationMethodType, secret: String - ) extends AuthenticationRepositoryError { - def message = - s"Authentication method is compromised for entityId:$entityId, type:${authenticationMethodType.value}, and secret:${hide(secret)}" - } - - case class ServiceError(message: String) extends AuthenticationRepositoryError - case class StorageError(cause: Throwable) extends AuthenticationRepositoryError { - def message = cause.getMessage - } - - case class UnexpectedError(cause: Throwable) extends AuthenticationRepositoryError { - def message = cause.getMessage - } + ) extends AuthenticationRepositoryError( + StatusCode.Unauthorized, + s"Authentication method is compromised for entityId:$entityId, type:${authenticationMethodType.value}, and secret:${hide(secret)}" + ) } object AuthenticationRepositorySql extends DoobieContext.Postgres(SnakeCase) with PostgresJsonExtensions { diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/apikey/JdbcAuthenticationRepository.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/apikey/JdbcAuthenticationRepository.scala index d298e59728..918a0eabfa 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/apikey/JdbcAuthenticationRepository.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/apikey/JdbcAuthenticationRepository.scala @@ -2,6 +2,7 @@ package org.hyperledger.identus.iam.authentication.apikey import doobie.* import doobie.implicits.* +import org.hyperledger.identus.shared.db.Errors import org.hyperledger.identus.shared.db.Implicits.ensureOneAffectedRowOrDie import org.postgresql.util.PSQLException import zio.* @@ -18,27 +19,21 @@ case class JdbcAuthenticationRepository(xa: Transactor[Task]) extends Authentica entityId: UUID, amt: AuthenticationMethodType, secret: String - ): IO[AuthenticationRepositoryError, Unit] = { + ): IO[AuthenticationCompromised, Unit] = { val authenticationMethod = AuthenticationMethod(amt, entityId, secret) AuthenticationRepositorySql .insert(authenticationMethod) .transact(xa) - .map(_ => ()) - .logError( - s"insert failed for entityId: $entityId, authenticationMethodType: $amt, and secret: $secret" - ) - .mapError { + .flatMap { + case 1 => ZIO.unit + case count => ZIO.die(Errors.UnexpectedAffectedRow(count)) + } + .catchAll { case sqlException: PSQLException if sqlException.getMessage .contains("ERROR: duplicate key value violates unique constraint \"unique_type_secret_constraint\"") => - AuthenticationCompromised(entityId, amt, secret) - case otherSqlException: PSQLException => - StorageError(otherSqlException) - case unexpected: Throwable => - UnexpectedError(unexpected) - } - .catchSome { case AuthenticationCompromised(eId, amt, s) => - ensureThatTheApiKeyIsNotCompromised(eId, amt, s) + ensureThatTheApiKeyIsNotCompromised(entityId, amt, secret) + case e => ZIO.die(e) } } @@ -46,9 +41,9 @@ case class JdbcAuthenticationRepository(xa: Transactor[Task]) extends Authentica entityId: UUID, authenticationMethodType: AuthenticationMethodType, secret: String - ): IO[AuthenticationRepositoryError, Unit] = { + ): IO[AuthenticationCompromised, Unit] = { val ac = AuthenticationCompromised(entityId, authenticationMethodType, secret) - val acZIO: IO[AuthenticationRepositoryError, Unit] = ZIO.fail(ac) + val acZIO: IO[AuthenticationCompromised, Unit] = ZIO.fail(ac) for { authRecordOpt <- findAuthenticationMethodByTypeAndSecret(authenticationMethodType, secret) From aab407b9b09c9089d7860461223a1b15ce949ef9 Mon Sep 17 00:00:00 2001 From: Benjamin Voiturier Date: Thu, 20 Jun 2024 11:39:28 +0200 Subject: [PATCH 09/11] chore: use implicit conversion for AuthenticationError to ErrorResponse Signed-off-by: Benjamin Voiturier --- .../identus/agent/server/CloudAgentApp.scala | 2 +- .../iam/authentication/Authenticator.scala | 15 --------------- .../iam/authentication/SecurityLogic.scala | 7 ++----- .../apikey/ApiKeyAuthenticator.scala | 8 ++++---- .../apikey/ApiKeyAuthenticatorImpl.scala | 11 +++++------ .../http/controller/EntityControllerImpl.scala | 5 ++--- .../oid4vci/CredentialIssuerServerEndpoints.scala | 1 - 7 files changed, 14 insertions(+), 35 deletions(-) diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/CloudAgentApp.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/CloudAgentApp.scala index a15014061d..fce659e613 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/CloudAgentApp.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/CloudAgentApp.scala @@ -214,7 +214,7 @@ object AgentInitialization { .createWallet(defaultWallet, seed) .orDieAsUnmanagedFailure _ <- entityService.create(defaultEntity).orDieAsUnmanagedFailure - _ <- apiKeyAuth.add(defaultEntity.id, config.authApiKey).mapError(e => Exception(e.userFacingMessage)) + _ <- apiKeyAuth.add(defaultEntity.id, config.authApiKey) _ <- config.webhookUrl.fold(ZIO.unit) { url => val customHeaders = config.webhookApiKey.fold(Map.empty)(apiKey => Map("Authorization" -> s"Bearer $apiKey")) walletService diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/Authenticator.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/Authenticator.scala index c15c5f8ebc..05781710ea 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/Authenticator.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/Authenticator.scala @@ -1,7 +1,6 @@ package org.hyperledger.identus.iam.authentication import org.hyperledger.identus.agent.walletapi.model.{BaseEntity, Entity, EntityRole} -import org.hyperledger.identus.api.http.ErrorResponse import org.hyperledger.identus.shared.models.* import zio.{IO, ZIO, ZLayer} @@ -34,12 +33,6 @@ object AuthenticationError { message ) - case class ServiceError(message: String) - extends AuthenticationError( - StatusCode.InternalServerError, - message - ) - case class ResourceNotPermitted(message: String) extends AuthenticationError( StatusCode.Forbidden, @@ -51,14 +44,6 @@ object AuthenticationError { StatusCode.Forbidden, message ) - - def toErrorResponse(error: AuthenticationError): ErrorResponse = - ErrorResponse( - status = sttp.model.StatusCode.Forbidden.code, - `type` = "authentication_error", - title = "", - detail = Option(error.userFacingMessage) - ) } trait Authenticator[E <: BaseEntity] { diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/SecurityLogic.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/SecurityLogic.scala index 4443f26de2..19985e2d71 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/SecurityLogic.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/SecurityLogic.scala @@ -9,6 +9,8 @@ import org.hyperledger.identus.iam.authentication.AuthenticationError.Authentica import org.hyperledger.identus.shared.models.{WalletAccessContext, WalletAdministrationContext} import zio.* +import scala.language.implicitConversions + object SecurityLogic { def authenticate[E <: BaseEntity](credentials: Credentials, others: Credentials*)( @@ -31,7 +33,6 @@ object SecurityLogic { case head :: _ => ZIO.fail(head) } } - .mapError(AuthenticationError.toErrorResponse) } def authorizeWalletAccess[E <: BaseEntity]( @@ -39,7 +40,6 @@ object SecurityLogic { )(authorizer: Authorizer[E]): IO[ErrorResponse, WalletAccessContext] = authorizer .authorizeWalletAccess(entity) - .mapError(AuthenticationError.toErrorResponse) def authorizeWalletAccess[E <: BaseEntity](credentials: Credentials, others: Credentials*)( authenticator: Authenticator[E], @@ -62,7 +62,6 @@ object SecurityLogic { )(authorizer: Authorizer[E]): IO[ErrorResponse, WalletAdministrationContext] = authorizer .authorizeWalletAdmin(entity) - .mapError(AuthenticationError.toErrorResponse) def authorizeWalletAdminWith[E <: BaseEntity]( credentials: (AdminApiKeyCredentials, ApiKeyCredentials, JwtCredentials) @@ -89,11 +88,9 @@ object SecurityLogic { .mapError(msg => AuthenticationError.UnexpectedError(s"Unable to retrieve entity role for entity id ${entity.id}. $msg") ) - .mapError(AuthenticationError.toErrorResponse) _ <- ZIO .fail(AuthenticationError.InvalidRole(s"$role role is not permitted. Expected $permittedRole role.")) .when(role != permittedRole) - .mapError(AuthenticationError.toErrorResponse) } yield entity } } diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/apikey/ApiKeyAuthenticator.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/apikey/ApiKeyAuthenticator.scala index 7643e5908f..8d421c994b 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/apikey/ApiKeyAuthenticator.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/apikey/ApiKeyAuthenticator.scala @@ -8,7 +8,7 @@ import org.hyperledger.identus.iam.authentication.{ EntityAuthorizer } import org.hyperledger.identus.iam.authentication.AuthenticationError.* -import zio.{IO, ZIO} +import zio.{IO, UIO, ZIO} import java.util.UUID @@ -35,11 +35,11 @@ trait ApiKeyAuthenticator extends AuthenticatorWithAuthZ[Entity], EntityAuthoriz def isEnabled: Boolean - def authenticate(apiKey: String): IO[AuthenticationError, Entity] + def authenticate(apiKey: String): IO[InvalidCredentials, Entity] - def add(entityId: UUID, apiKey: String): IO[AuthenticationError, Unit] + def add(entityId: UUID, apiKey: String): UIO[Unit] - def delete(entityId: UUID, apiKey: String): IO[AuthenticationError, Unit] + def delete(entityId: UUID, apiKey: String): UIO[Unit] } object ApiKeyAuthenticator { diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/apikey/ApiKeyAuthenticatorImpl.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/apikey/ApiKeyAuthenticatorImpl.scala index 69300ec439..0eb662dbac 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/apikey/ApiKeyAuthenticatorImpl.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/apikey/ApiKeyAuthenticatorImpl.scala @@ -21,21 +21,21 @@ case class ApiKeyAuthenticatorImpl( override def isEnabled: Boolean = apiKeyConfig.enabled - override def authenticate(apiKey: String): IO[AuthenticationError, Entity] = { + override def authenticate(apiKey: String): IO[InvalidCredentials, Entity] = { if (apiKeyConfig.enabled) { if (apiKeyConfig.authenticateAsDefaultUser) { ZIO.succeed(Entity.Default) } else { authenticateBy(apiKey) .catchSome { - case AuthenticationError.InvalidCredentials(message) if apiKeyConfig.autoProvisioning => + case InvalidCredentials(message) if apiKeyConfig.autoProvisioning => provisionNewEntity(apiKey) } } } else { ZIO.fail( AuthenticationMethodNotEnabled(s"Authentication method not enabled: ${AuthenticationMethodType.ApiKey.value}") - ) + ).orDieAsUnmanagedFailure } } @@ -76,13 +76,12 @@ case class ApiKeyAuthenticatorImpl( } yield () } - override def delete(entityId: UUID, apiKey: String): IO[AuthenticationError, Unit] = { + override def delete(entityId: UUID, apiKey: String): UIO[Unit] = { for { saltAndApiKey <- ZIO.succeed(apiKeyConfig.salt + apiKey) secret <- ZIO .fromTry(Try(Sha256Hash.compute(saltAndApiKey.getBytes).hexEncoded)) - .logError("Failed to compute SHA256 hash") - .mapError(cause => AuthenticationError.UnexpectedError(cause.getMessage)) + .orDie _ <- repository.delete(entityId, AuthenticationMethodType.ApiKey, secret) } yield () } diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/entity/http/controller/EntityControllerImpl.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/entity/http/controller/EntityControllerImpl.scala index 6535fcc799..1ea9ee7f08 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/entity/http/controller/EntityControllerImpl.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/entity/http/controller/EntityControllerImpl.scala @@ -62,9 +62,8 @@ case class EntityControllerImpl(service: EntityService, apiKeyAuthenticator: Api } override def deleteEntity(id: UUID)(implicit rc: RequestContext): IO[ErrorResponse, Unit] = { - for { - _ <- service.deleteById(id) - } yield () + service + .deleteById(id) } override def addApiKeyAuth(id: UUID, apiKey: String)(implicit rc: RequestContext): IO[ErrorResponse, Unit] = { diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/oid4vci/CredentialIssuerServerEndpoints.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/oid4vci/CredentialIssuerServerEndpoints.scala index 43d522cf30..59ac0bca57 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/oid4vci/CredentialIssuerServerEndpoints.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/oid4vci/CredentialIssuerServerEndpoints.scala @@ -61,7 +61,6 @@ case class CredentialIssuerServerEndpoints( oid4vciAuthenticatorFactory .make(request.issuerState) .flatMap(_.authenticate(jwt)) - .mapError(AuthenticationError.toErrorResponse) .flatMap { entity => credentialIssuerController .getNonce(rc, request) From c7fea9dbbd1fc6067e45e01126b201d4d8afd2c3 Mon Sep 17 00:00:00 2001 From: Benjamin Voiturier Date: Thu, 20 Jun 2024 11:45:37 +0200 Subject: [PATCH 10/11] chrore: run scalafmt Signed-off-by: Benjamin Voiturier --- .../authentication/apikey/ApiKeyAuthenticatorImpl.scala | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/apikey/ApiKeyAuthenticatorImpl.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/apikey/ApiKeyAuthenticatorImpl.scala index 0eb662dbac..1f3730eb8c 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/apikey/ApiKeyAuthenticatorImpl.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/apikey/ApiKeyAuthenticatorImpl.scala @@ -33,9 +33,11 @@ case class ApiKeyAuthenticatorImpl( } } } else { - ZIO.fail( - AuthenticationMethodNotEnabled(s"Authentication method not enabled: ${AuthenticationMethodType.ApiKey.value}") - ).orDieAsUnmanagedFailure + ZIO + .fail( + AuthenticationMethodNotEnabled(s"Authentication method not enabled: ${AuthenticationMethodType.ApiKey.value}") + ) + .orDieAsUnmanagedFailure } } From 9a562ab7e4d5059bfd3268134c25054ddc84bd0e Mon Sep 17 00:00:00 2001 From: Benjamin Voiturier Date: Thu, 20 Jun 2024 12:14:35 +0200 Subject: [PATCH 11/11] test: fix security logic unit test Signed-off-by: Benjamin Voiturier --- .../identus/iam/authentication/SecurityLogicSpec.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/iam/authentication/SecurityLogicSpec.scala b/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/iam/authentication/SecurityLogicSpec.scala index 0b1d58e0a1..15d3edf922 100644 --- a/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/iam/authentication/SecurityLogicSpec.scala +++ b/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/iam/authentication/SecurityLogicSpec.scala @@ -76,7 +76,7 @@ object SecurityLogicSpec extends ZIOSpecDefault { ApiKeyCredentials(Some("key-3")) )(testAuthenticator(entity)) .exit - } yield assert(exit)(fails(hasField("status", _.status, equalTo(sttp.model.StatusCode.Forbidden.code)))) + } yield assert(exit)(fails(hasField("status", _.status, equalTo(sttp.model.StatusCode.Unauthorized.code)))) }, test("authorizeRole accept if the role is matched") { val tenantentity = Entity("alice", UUID.randomUUID()) @@ -103,8 +103,8 @@ object SecurityLogicSpec extends ZIOSpecDefault { exit2 <- SecurityLogic .authorizeRole(ApiKeyCredentials(Some(adminEntity.id.toString())))(tenantAuth)(EntityRole.Tenant) .exit - } yield assert(exit1)(fails(hasField("status", _.status, equalTo(sttp.model.StatusCode.Forbidden.code)))) && - assert(exit2)(fails(hasField("status", _.status, equalTo(sttp.model.StatusCode.Forbidden.code)))) + } yield assert(exit1)(fails(hasField("status", _.status, equalTo(sttp.model.StatusCode.Unauthorized.code)))) && + assert(exit2)(fails(hasField("status", _.status, equalTo(sttp.model.StatusCode.Unauthorized.code)))) }, test("display first error message that is not MethodNotEnabled error") { val alice = Entity("alice", UUID.randomUUID()) @@ -123,7 +123,7 @@ object SecurityLogicSpec extends ZIOSpecDefault { ) ) .exit - } yield assert(exit)(fails(hasField("status", _.status, equalTo(sttp.model.StatusCode.Forbidden.code)))) && + } yield assert(exit)(fails(hasField("status", _.status, equalTo(sttp.model.StatusCode.Unauthorized.code)))) && assert(exit)(fails(hasField("detail", _.detail, isSome(equalTo("invalid credentials"))))) } )