Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Wallet Management Error Handling #1248

Merged
merged 1 commit into from
Jul 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import org.hyperledger.identus.iam.authentication.oidc.{
KeycloakConfig,
KeycloakEntity
}
import org.hyperledger.identus.iam.authorization.core.PermissionManagement
import org.hyperledger.identus.iam.authorization.core.PermissionManagementService
import org.hyperledger.identus.iam.authorization.keycloak.admin.KeycloakPermissionManagementService
import org.hyperledger.identus.pollux.vc.jwt.{DidResolver as JwtDidResolver, PrismDidResolver}
import org.hyperledger.identus.shared.crypto.Apollo
Expand Down Expand Up @@ -103,7 +103,7 @@ object AppModule {
)

val keycloakAuthenticatorLayer: RLayer[
AppConfig & WalletManagementService & Client & PermissionManagement.Service[KeycloakEntity],
AppConfig & WalletManagementService & Client & PermissionManagementService[KeycloakEntity],
KeycloakAuthenticator
] =
ZLayer.fromZIO {
Expand All @@ -113,7 +113,7 @@ object AppModule {
if (!isEnabled) KeycloakAuthenticatorImpl.disabled
else
ZLayer.makeSome[
AppConfig & WalletManagementService & Client & PermissionManagement.Service[KeycloakEntity],
AppConfig & WalletManagementService & Client & PermissionManagementService[KeycloakEntity],
KeycloakAuthenticator
](
KeycloakConfig.layer,
Expand All @@ -125,14 +125,14 @@ object AppModule {
}.flatten

val keycloakPermissionManagementLayer
: RLayer[AppConfig & WalletManagementService & Client, PermissionManagement.Service[KeycloakEntity]] = {
: RLayer[AppConfig & WalletManagementService & Client, PermissionManagementService[KeycloakEntity]] = {
ZLayer.fromZIO {
ZIO
.serviceWith[AppConfig](_.agent.authentication.keycloak.enabled)
.map { isEnabled =>
if (!isEnabled) KeycloakPermissionManagementService.disabled
else
ZLayer.makeSome[AppConfig & WalletManagementService & Client, PermissionManagement.Service[KeycloakEntity]](
ZLayer.makeSome[AppConfig & WalletManagementService & Client, PermissionManagementService[KeycloakEntity]](
KeycloakClientImpl.authzClientLayer,
KeycloakClientImpl.layer,
KeycloakConfig.layer,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ package org.hyperledger.identus.iam.authentication.oidc
import org.hyperledger.identus.agent.walletapi.model.EntityRole
import org.hyperledger.identus.iam.authentication.AuthenticationError
import org.hyperledger.identus.iam.authentication.AuthenticationError.AuthenticationMethodNotEnabled
import org.hyperledger.identus.iam.authorization.core.PermissionManagement
import org.hyperledger.identus.iam.authorization.core.PermissionManagement.Error.PermissionNotAvailable
import org.hyperledger.identus.iam.authorization.core.PermissionManagementService
import org.hyperledger.identus.iam.authorization.core.PermissionManagementServiceError.PermissionNotAvailable
import org.hyperledger.identus.shared.models.{WalletAccessContext, WalletAdministrationContext}
import zio.*

Expand All @@ -13,7 +13,7 @@ import java.util.UUID
class KeycloakAuthenticatorImpl(
client: KeycloakClient,
keycloakConfig: KeycloakConfig,
keycloakPermissionService: PermissionManagement.Service[KeycloakEntity],
keycloakPermissionService: PermissionManagementService[KeycloakEntity],
) extends KeycloakAuthenticator {

override def isEnabled: Boolean = keycloakConfig.enabled
Expand Down Expand Up @@ -48,7 +48,7 @@ class KeycloakAuthenticatorImpl(
.listWalletPermissions(entity)
.mapError {
case PermissionNotAvailable(_, msg) => AuthenticationError.InvalidCredentials(msg)
case e => AuthenticationError.UnexpectedError(e.message)
case e => AuthenticationError.UnexpectedError(e.userFacingMessage)
}
.flatMap {
case head +: Nil => ZIO.succeed(head)
Expand All @@ -68,7 +68,7 @@ class KeycloakAuthenticatorImpl(
.listWalletPermissions(entity)
.provide(ZLayer.succeed(WalletAdministrationContext.Admin()))
.mapBoth(
e => AuthenticationError.UnexpectedError(e.message),
e => AuthenticationError.UnexpectedError(e.userFacingMessage),
wallets => WalletAdministrationContext.SelfService(wallets)
)

Expand All @@ -90,7 +90,7 @@ class KeycloakAuthenticatorImpl(

object KeycloakAuthenticatorImpl {
val layer: RLayer[
KeycloakClient & KeycloakConfig & PermissionManagement.Service[KeycloakEntity],
KeycloakClient & KeycloakConfig & PermissionManagementService[KeycloakEntity],
KeycloakAuthenticator
] =
ZLayer.fromFunction(KeycloakAuthenticatorImpl(_, _, _))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,38 @@ package org.hyperledger.identus.iam.authorization

import org.hyperledger.identus.agent.walletapi.model.{BaseEntity, Entity}
import org.hyperledger.identus.iam.authentication.oidc.KeycloakEntity
import org.hyperledger.identus.iam.authorization.core.PermissionManagement
import org.hyperledger.identus.iam.authorization.core.PermissionManagement.Error
import org.hyperledger.identus.iam.authorization.core.{PermissionManagementService, PermissionManagementServiceError}
import org.hyperledger.identus.shared.models.{WalletAdministrationContext, WalletId}
import zio.*

class DefaultPermissionManagementService(
entityPermission: PermissionManagement.Service[Entity],
keycloakPermission: PermissionManagement.Service[KeycloakEntity]
) extends PermissionManagement.Service[BaseEntity] {
entityPermission: PermissionManagementService[Entity],
keycloakPermission: PermissionManagementService[KeycloakEntity]
) extends PermissionManagementService[BaseEntity] {

def grantWalletToUser(walletId: WalletId, entity: BaseEntity): ZIO[WalletAdministrationContext, Error, Unit] = {
def grantWalletToUser(
walletId: WalletId,
entity: BaseEntity
): ZIO[WalletAdministrationContext, PermissionManagementServiceError, Unit] = {
entity match {
case entity: Entity => entityPermission.grantWalletToUser(walletId, entity)
case kcEntity: KeycloakEntity => keycloakPermission.grantWalletToUser(walletId, kcEntity)
}
}

def revokeWalletFromUser(walletId: WalletId, entity: BaseEntity): ZIO[WalletAdministrationContext, Error, Unit] = {
def revokeWalletFromUser(
walletId: WalletId,
entity: BaseEntity
): ZIO[WalletAdministrationContext, PermissionManagementServiceError, Unit] = {
entity match {
case entity: Entity => entityPermission.revokeWalletFromUser(walletId, entity)
case kcEntity: KeycloakEntity => keycloakPermission.revokeWalletFromUser(walletId, kcEntity)
}
}

def listWalletPermissions(entity: BaseEntity): ZIO[WalletAdministrationContext, Error, Seq[WalletId]] = {
def listWalletPermissions(
entity: BaseEntity
): ZIO[WalletAdministrationContext, PermissionManagementServiceError, Seq[WalletId]] = {
entity match {
case entity: Entity => entityPermission.listWalletPermissions(entity)
case kcEntity: KeycloakEntity => keycloakPermission.listWalletPermissions(kcEntity)
Expand All @@ -37,8 +44,8 @@ class DefaultPermissionManagementService(

object DefaultPermissionManagementService {
def layer: URLayer[
PermissionManagement.Service[KeycloakEntity] & PermissionManagement.Service[Entity],
PermissionManagement.Service[BaseEntity]
PermissionManagementService[KeycloakEntity] & PermissionManagementService[Entity],
PermissionManagementService[BaseEntity]
] =
ZLayer.fromFunction(DefaultPermissionManagementService(_, _))
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,45 @@ package org.hyperledger.identus.iam.authorization.core

import org.hyperledger.identus.agent.walletapi.model.Entity
import org.hyperledger.identus.agent.walletapi.service.EntityService
import org.hyperledger.identus.iam.authorization.core.PermissionManagement.Error
import org.hyperledger.identus.iam.authorization.core.PermissionManagement.Error.{ServiceError, WalletNotFoundById}
import org.hyperledger.identus.iam.authorization.core.PermissionManagementServiceError.*
import org.hyperledger.identus.shared.models.{WalletAdministrationContext, WalletId}
import zio.*

import scala.language.implicitConversions

class EntityPermissionManagementService(entityService: EntityService) extends PermissionManagement.Service[Entity] {
class EntityPermissionManagementService(entityService: EntityService) extends PermissionManagementService[Entity] {

override def grantWalletToUser(walletId: WalletId, entity: Entity): ZIO[WalletAdministrationContext, Error, Unit] = {
override def grantWalletToUser(
walletId: WalletId,
entity: Entity
): ZIO[WalletAdministrationContext, PermissionManagementServiceError, Unit] = {
for {
_ <- ZIO
.serviceWith[WalletAdministrationContext](_.isAuthorized(walletId))
.filterOrFail(identity)(Error.WalletNotFoundById(walletId))
.filterOrFail(identity)(WalletNotFoundById(walletId))
_ <- entityService.assignWallet(entity.id, walletId.toUUID).orDieAsUnmanagedFailure
} yield ()
}

override def revokeWalletFromUser(walletId: WalletId, entity: Entity): ZIO[WalletAdministrationContext, Error, Unit] =
ZIO.fail(Error.ServiceError(s"Revoking wallet permission for an Entity is not yet supported."))
override def revokeWalletFromUser(
walletId: WalletId,
entity: Entity
): ZIO[WalletAdministrationContext, PermissionManagementServiceError, Unit] =
ZIO.fail(ServiceError(s"Revoking wallet permission for an Entity is not yet supported."))

override def listWalletPermissions(entity: Entity): ZIO[WalletAdministrationContext, Error, Seq[WalletId]] = {
override def listWalletPermissions(
entity: Entity
): ZIO[WalletAdministrationContext, PermissionManagementServiceError, Seq[WalletId]] = {
val walletId = WalletId.fromUUID(entity.walletId)
ZIO
.serviceWith[WalletAdministrationContext](_.isAuthorized(walletId))
.filterOrFail(identity)(Error.WalletNotFoundById(walletId))
.filterOrFail(identity)(WalletNotFoundById(walletId))
.as(Seq(walletId))
}

}

object EntityPermissionManagementService {
val layer: URLayer[EntityService, PermissionManagement.Service[Entity]] =
val layer: URLayer[EntityService, PermissionManagementService[Entity]] =
ZLayer.fromFunction(EntityPermissionManagementService(_))
}
Original file line number Diff line number Diff line change
@@ -1,39 +1 @@
package org.hyperledger.identus.iam.authorization.core

import org.hyperledger.identus.agent.walletapi.model.BaseEntity
import org.hyperledger.identus.shared.models.{WalletAdministrationContext, WalletId}
import zio.*

import java.util.UUID

object PermissionManagement {
trait Service[E <: BaseEntity] {
def grantWalletToUser(walletId: WalletId, entity: E): ZIO[WalletAdministrationContext, Error, Unit]
def revokeWalletFromUser(walletId: WalletId, entity: E): ZIO[WalletAdministrationContext, Error, Unit]
def listWalletPermissions(entity: E): ZIO[WalletAdministrationContext, Error, Seq[WalletId]]
}

sealed trait Error(val message: String)

object Error {
case class UserNotFoundById(userId: UUID, cause: Option[Throwable] = None)
extends Error(s"User $userId is not found" + cause.map(t => s" Cause: ${t.getMessage}"))
case class WalletNotFoundByUserId(userId: UUID) extends Error(s"Wallet for user $userId is not found")

case class WalletNotFoundById(walletId: WalletId) extends Error(s"Wallet not found by ${walletId.toUUID}")

case class WalletResourceNotFoundById(walletId: WalletId)
extends Error(s"Wallet resource not found by ${walletId.toUUID}")

case class PermissionNotFoundById(userId: UUID, walletId: WalletId, walletResourceId: String)
extends Error(
s"Permission not found by userId: $userId, walletId: ${walletId.toUUID}, walletResourceId: $walletResourceId"
)

case class PermissionNotAvailable(userId: UUID, cause: String) extends Error(cause)

case class UnexpectedError(cause: Throwable) extends Error(cause.getMessage)

case class ServiceError(cause: String) extends Error(cause)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.hyperledger.identus.iam.authorization.core

import org.hyperledger.identus.agent.walletapi.model.BaseEntity
import org.hyperledger.identus.shared.models.{WalletAdministrationContext, WalletId}
import zio.*

trait PermissionManagementService[E <: BaseEntity] {
def grantWalletToUser(
walletId: WalletId,
entity: E
): ZIO[WalletAdministrationContext, PermissionManagementServiceError, Unit]
def revokeWalletFromUser(
walletId: WalletId,
entity: E
): ZIO[WalletAdministrationContext, PermissionManagementServiceError, Unit]
def listWalletPermissions(
entity: E
): ZIO[WalletAdministrationContext, PermissionManagementServiceError, Seq[WalletId]]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package org.hyperledger.identus.iam.authorization.core

import org.hyperledger.identus.shared.models.{Failure, StatusCode, WalletId}

import java.util.UUID

sealed trait PermissionManagementServiceError(
val statusCode: StatusCode,
val userFacingMessage: String
) extends Failure {
override val namespace: String = "PermissionManagementServiceError"
}

object PermissionManagementServiceError {

case class UserNotFoundById(userId: UUID, cause: Option[Throwable] = None)
extends PermissionManagementServiceError(
StatusCode.BadRequest,
s"User $userId is not found" + cause.map(t => s" Cause: ${t.getMessage}")
)

case class WalletNotFoundByUserId(userId: UUID)
extends PermissionManagementServiceError(
StatusCode.BadRequest,
s"Wallet for user $userId is not found"
)

case class WalletNotFoundById(walletId: WalletId)
extends PermissionManagementServiceError(
StatusCode.BadRequest,
s"Wallet not found by ${walletId.toUUID}"
)

case class WalletResourceNotFoundById(walletId: WalletId)
extends PermissionManagementServiceError(
StatusCode.BadRequest,
s"Wallet resource not found by ${walletId.toUUID}"
)

case class PermissionNotFoundById(userId: UUID, walletId: WalletId, walletResourceId: String)
extends PermissionManagementServiceError(
StatusCode.BadRequest,
s"Permission not found by userId: $userId, walletId: ${walletId.toUUID}, walletResourceId: $walletResourceId"
)

case class PermissionNotAvailable(userId: UUID, cause: String)
extends PermissionManagementServiceError(StatusCode.BadRequest, cause)

case class ServiceError(cause: String) extends PermissionManagementServiceError(StatusCode.InternalServerError, cause)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like this one represents the catch-all "unexpected error" case, right? From the caller's perspective, there is no way to recover from this type of failure, so I would remove it and throw a defect in the service implementation instead.

}
Loading
Loading