Skip to content

Commit

Permalink
feat(prism-agent): add multi-tenant wallet self-service capability (#779
Browse files Browse the repository at this point in the history
)

Signed-off-by: Pat Losoponkul <[email protected]>
  • Loading branch information
patlo-iog authored Nov 9, 2023
1 parent 7ad6427 commit f2e74cd
Show file tree
Hide file tree
Showing 52 changed files with 805 additions and 321 deletions.
6 changes: 3 additions & 3 deletions infrastructure/shared/keycloak/init-script.sh
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,8 @@ create_realm $ADMIN_ACCESS_TOKEN
echo "Creating a new prism-agent client ..."
create_client $ADMIN_ACCESS_TOKEN "prism-agent" $PRISM_AGENT_CLIENT_SECRET

echo "Creating a new prism-manage client ..."
create_client $ADMIN_ACCESS_TOKEN "prism-manage" $PRISM_AGENT_CLIENT_SECRET

echo "Creating a new sample user ..."
create_user $ADMIN_ACCESS_TOKEN "alice" "1234"

echo "Creating a new sample user ..."
create_user $ADMIN_ACCESS_TOKEN "bob" "1234"
21 changes: 1 addition & 20 deletions prism-agent/service/server/src/main/resources/application.conf
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ agent {
# autoUpgradeToRPT is used to enable the auto RPT (requesting party token) logic.
# if enabled, normal accessToken can be used to perform permission checks by obtaining RPT from accessToken.
# if disabled, accessToken must be RPT which already include the permission claims.
autoUpgradeToRPT = false
autoUpgradeToRPT = true
autoUpgradeToRPT = ${?KEYCLOAK_UMA_AUTO_UPGRADE_RPT}
}
}
Expand Down Expand Up @@ -219,22 +219,3 @@ agent {
authApiKey = ${?DEFAULT_WALLET_AUTH_API_KEY}
}
}

keycloakAdmin {
serverUrl = "http://localhost:8080/auth",
serverUrl = ${?KEYCLOAK_SERVER_URL}
realm = "master",
realm = ${?KEYCLOAK_REALM}
username = "admin",
username = ${?KEYCLOAK_ADMIN_USERNAME}
password = "admin",
password = ${?KEYCLOAK_ADMIN_PASSWORD}
clientId = "admin-cli",
clientId = ${?KEYCLOAK_ADMIN_CLIENT_ID}
clientSecret = "",
clientSecret = ${?KEYCLOAK_ADMIN_CLIENT_SECRET}
authToken= "",
authToken = ${?KEYCLOAK_ADMIN_AUTH_TOKEN}
scope= ""
scope = ${?KEYCLOAK_ADMIN_SCOPE}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import io.iohk.atala.event.controller.EventControllerImpl
import io.iohk.atala.event.notification.EventNotificationServiceImpl
import io.iohk.atala.iam.authentication.DefaultAuthenticator
import io.iohk.atala.iam.authentication.apikey.JdbcAuthenticationRepository
import io.iohk.atala.iam.authorization.DefaultPermissionManagementService
import io.iohk.atala.iam.authorization.core.EntityPermissionManagementService
import io.iohk.atala.iam.entity.http.controller.{EntityController, EntityControllerImpl}
import io.iohk.atala.iam.wallet.http.controller.WalletManagementControllerImpl
import io.iohk.atala.issue.controller.IssueControllerImpl
Expand Down Expand Up @@ -155,7 +157,10 @@ object MainApp extends ZIOAppDefault {
// authentication
AppModule.builtInAuthenticatorLayer,
AppModule.keycloakAuthenticatorLayer,
AppModule.keycloakPermissionManagementLayer,
DefaultAuthenticator.layer,
DefaultPermissionManagementService.layer,
EntityPermissionManagementService.layer,
// grpc
GrpcModule.prismNodeStubLayer,
// storage
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,21 @@ import io.iohk.atala.iam.authentication.apikey.ApiKeyAuthenticator
import io.iohk.atala.iam.authentication.apikey.ApiKeyAuthenticatorImpl
import io.iohk.atala.iam.authentication.apikey.ApiKeyConfig
import io.iohk.atala.iam.authentication.apikey.AuthenticationRepository
import io.iohk.atala.iam.authentication.oidc.KeycloakAuthenticator
import io.iohk.atala.iam.authentication.oidc.KeycloakAuthenticatorImpl
import io.iohk.atala.iam.authentication.oidc.KeycloakClientImpl
import io.iohk.atala.iam.authentication.oidc.KeycloakConfig
import io.iohk.atala.iam.authentication.oidc.KeycloakEntity
import io.iohk.atala.iam.authorization.core.PermissionManagement
import io.iohk.atala.iam.authorization.keycloak.admin.KeycloakPermissionManagementService
import io.iohk.atala.pollux.vc.jwt.{PrismDidResolver, DidResolver as JwtDidResolver}
import io.iohk.atala.prism.protos.node_api.NodeServiceGrpc
import io.iohk.atala.shared.db.{ContextAwareTask, DbConfig, TransactorLayer}
import org.keycloak.authorization.client.AuthzClient
import zio.*
import zio.config.typesafe.TypesafeConfigSource
import zio.config.{ReadError, read}
import zio.http.Client
import io.iohk.atala.iam.authentication.oidc.KeycloakAuthenticator

object SystemModule {
val configLayer: Layer[ReadError[String], AppConfig] = ZLayer.fromZIO {
Expand Down Expand Up @@ -74,20 +78,45 @@ object AppModule {
ApiKeyAuthenticatorImpl.layer,
)

val keycloakAuthenticatorLayer: RLayer[AppConfig & WalletManagementService & Client, KeycloakAuthenticator] =
val keycloakAuthenticatorLayer: RLayer[
AppConfig & WalletManagementService & Client & PermissionManagement.Service[KeycloakEntity],
KeycloakAuthenticator
] =
ZLayer.fromZIO {
ZIO
.serviceWith[AppConfig](_.agent.authentication.keycloak.enabled)
.map { isEnabled =>
if (!isEnabled) KeycloakAuthenticatorImpl.disabled
else
ZLayer.makeSome[AppConfig & WalletManagementService & Client, KeycloakAuthenticator](
ZLayer.makeSome[
AppConfig & WalletManagementService & Client & PermissionManagement.Service[KeycloakEntity],
KeycloakAuthenticator
](
KeycloakConfig.layer,
KeycloakAuthenticatorImpl.layer,
KeycloakClientImpl.authzClientLayer,
KeycloakClientImpl.layer
)
}
}.flatten

val keycloakPermissionManagementLayer
: RLayer[AppConfig & WalletManagementService & Client, PermissionManagement.Service[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]](
KeycloakClientImpl.authzClientLayer,
KeycloakClientImpl.layer,
KeycloakConfig.layer,
KeycloakPermissionManagementService.layer
)
}
}.flatten
}
}

object GrpcModule {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import io.iohk.atala.pollux.credentialschema.{SchemaRegistryServerEndpoints, Ver
import io.iohk.atala.pollux.vc.jwt.DidResolver as JwtDidResolver
import io.iohk.atala.presentproof.controller.PresentProofServerEndpoints
import io.iohk.atala.resolvers.DIDResolver
import io.iohk.atala.shared.models.WalletAdministrationContext
import io.iohk.atala.shared.models.{HexString, WalletAccessContext, WalletId}
import io.iohk.atala.shared.utils.DurationOps.toMetricsSeconds
import io.iohk.atala.system.controller.SystemServerEndpoints
Expand Down Expand Up @@ -104,6 +105,7 @@ object PrismAgentApp {
.catchAll(e => ZIO.logError(s"error while syncing DID publication state: $e"))
.repeat(Schedule.spaced(10.seconds))
.unit
.provideSomeLayer(ZLayer.succeed(WalletAdministrationContext.Admin()))

}

Expand Down Expand Up @@ -153,6 +155,7 @@ object AgentInitialization {
for {
_ <- validateAppConfig
_ <- initializeDefaultWallet
.provideSomeLayer(ZLayer.succeed(WalletAdministrationContext.Admin()))
} yield ()

private val validateAppConfig =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package io.iohk.atala.agent.server.config

import io.iohk.atala.castor.core.model.did.VerificationRelationship
import io.iohk.atala.iam.authentication.AuthenticationConfig
import io.iohk.atala.iam.authorization.keycloak.admin.KeycloakAdminConfig
import io.iohk.atala.pollux.vc.jwt.*
import io.iohk.atala.shared.db.DbConfig
import zio.config.*
Expand All @@ -18,7 +17,6 @@ final case class AppConfig(
agent: AgentConfig,
connect: ConnectConfig,
prismNode: PrismNodeConfig,
keycloakAdmin: KeycloakAdminConfig
) {
def validate: Either[String, Unit] =
for {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@ import zio.*

class DIDRegistrarServerEndpoints(
didRegistrarController: DIDRegistrarController,
authenticator: Authenticator[BaseEntity] & Authorizer[BaseEntity]
authenticator: Authenticator[BaseEntity],
authorizer: Authorizer[BaseEntity]
) {

private val listManagedDidServerEndpoint: ZServerEndpoint[Any, Any] =
DIDRegistrarEndpoints.listManagedDid
.zServerSecurityLogic(SecurityLogic.authorizeWith(_)(authenticator))
.zServerSecurityLogic(SecurityLogic.authorizeWith(_)(authenticator, authorizer))
.serverLogic { wac =>
{ case (rc, paginationInput) =>
didRegistrarController
Expand All @@ -27,7 +28,7 @@ class DIDRegistrarServerEndpoints(

private val createManagedDidServerEndpoint: ZServerEndpoint[Any, Any] =
DIDRegistrarEndpoints.createManagedDid
.zServerSecurityLogic(SecurityLogic.authorizeWith(_)(authenticator))
.zServerSecurityLogic(SecurityLogic.authorizeWith(_)(authenticator, authorizer))
.serverLogic { wac =>
{ case (rc, createManagedDidRequest) =>
didRegistrarController
Expand All @@ -38,7 +39,7 @@ class DIDRegistrarServerEndpoints(

private val getManagedDidServerEndpoint: ZServerEndpoint[Any, Any] =
DIDRegistrarEndpoints.getManagedDid
.zServerSecurityLogic(SecurityLogic.authorizeWith(_)(authenticator))
.zServerSecurityLogic(SecurityLogic.authorizeWith(_)(authenticator, authorizer))
.serverLogic { wac =>
{ case (rc, did) =>
didRegistrarController
Expand All @@ -49,7 +50,7 @@ class DIDRegistrarServerEndpoints(

private val publishManagedDidServerEndpoint: ZServerEndpoint[Any, Any] =
DIDRegistrarEndpoints.publishManagedDid
.zServerSecurityLogic(SecurityLogic.authorizeWith(_)(authenticator))
.zServerSecurityLogic(SecurityLogic.authorizeWith(_)(authenticator, authorizer))
.serverLogic { wac =>
{ case (rc, did) =>
didRegistrarController
Expand All @@ -60,7 +61,7 @@ class DIDRegistrarServerEndpoints(

private val updateManagedDidServerEndpoint: ZServerEndpoint[Any, Any] =
DIDRegistrarEndpoints.updateManagedDid
.zServerSecurityLogic(SecurityLogic.authorizeWith(_)(authenticator))
.zServerSecurityLogic(SecurityLogic.authorizeWith(_)(authenticator, authorizer))
.serverLogic { wac =>
{ case (rc, did, updateRequest) =>
didRegistrarController
Expand All @@ -71,7 +72,7 @@ class DIDRegistrarServerEndpoints(

private val deactivateManagedDidServerEndpoint: ZServerEndpoint[Any, Any] =
DIDRegistrarEndpoints.deactivateManagedDid
.zServerSecurityLogic(SecurityLogic.authorizeWith(_)(authenticator))
.zServerSecurityLogic(SecurityLogic.authorizeWith(_)(authenticator, authorizer))
.serverLogic { wac =>
{ case (rc, did) =>
didRegistrarController
Expand All @@ -96,7 +97,7 @@ object DIDRegistrarServerEndpoints {
for {
authenticator <- ZIO.service[DefaultAuthenticator]
didRegistrarController <- ZIO.service[DIDRegistrarController]
didRegistrarEndpoints = new DIDRegistrarServerEndpoints(didRegistrarController, authenticator)
didRegistrarEndpoints = new DIDRegistrarServerEndpoints(didRegistrarController, authenticator, authenticator)
} yield didRegistrarEndpoints.all
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,13 @@ import java.util.UUID

class ConnectionServerEndpoints(
connectionController: ConnectionController,
authenticator: Authenticator[BaseEntity] & Authorizer[BaseEntity]
authenticator: Authenticator[BaseEntity],
authorizer: Authorizer[BaseEntity]
) {

private val createConnectionServerEndpoint: ZServerEndpoint[Any, Any] =
createConnection
.zServerSecurityLogic(SecurityLogic.authorizeWith(_)(authenticator))
.zServerSecurityLogic(SecurityLogic.authorizeWith(_)(authenticator, authorizer))
.serverLogic { wac =>
{ case (ctx: RequestContext, request: CreateConnectionRequest) =>
connectionController
Expand All @@ -32,7 +33,7 @@ class ConnectionServerEndpoints(

private val getConnectionServerEndpoint: ZServerEndpoint[Any, Any] =
getConnection
.zServerSecurityLogic(SecurityLogic.authorizeWith(_)(authenticator))
.zServerSecurityLogic(SecurityLogic.authorizeWith(_)(authenticator, authorizer))
.serverLogic { wac =>
{ case (ctx: RequestContext, connectionId: UUID) =>
connectionController
Expand All @@ -43,7 +44,7 @@ class ConnectionServerEndpoints(

private val getConnectionsServerEndpoint: ZServerEndpoint[Any, Any] =
getConnections
.zServerSecurityLogic(SecurityLogic.authorizeWith(_)(authenticator))
.zServerSecurityLogic(SecurityLogic.authorizeWith(_)(authenticator, authorizer))
.serverLogic { wac =>
{ case (ctx: RequestContext, paginationInput: PaginationInput, thid: Option[String]) =>
connectionController
Expand All @@ -54,7 +55,7 @@ class ConnectionServerEndpoints(

private val acceptConnectionInvitationServerEndpoint: ZServerEndpoint[Any, Any] =
acceptConnectionInvitation
.zServerSecurityLogic(SecurityLogic.authorizeWith(_)(authenticator))
.zServerSecurityLogic(SecurityLogic.authorizeWith(_)(authenticator, authorizer))
.serverLogic { wac =>
{ case (ctx: RequestContext, request: AcceptConnectionInvitationRequest) =>
connectionController
Expand All @@ -76,7 +77,7 @@ object ConnectionServerEndpoints {
for {
authenticator <- ZIO.service[DefaultAuthenticator]
connectionController <- ZIO.service[ConnectionController]
connectionEndpoints = new ConnectionServerEndpoints(connectionController, authenticator)
connectionEndpoints = new ConnectionServerEndpoints(connectionController, authenticator, authenticator)
} yield connectionEndpoints.all
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@ import io.iohk.atala.iam.authentication.SecurityLogic

class EventServerEndpoints(
eventController: EventController,
authenticator: Authenticator[BaseEntity] & Authorizer[BaseEntity]
authenticator: Authenticator[BaseEntity],
authorizer: Authorizer[BaseEntity]
) {

val createWebhookNotificationServerEndpoint: ZServerEndpoint[Any, Any] =
EventEndpoints.createWebhookNotification
.zServerSecurityLogic(SecurityLogic.authorizeWith(_)(authenticator))
.zServerSecurityLogic(SecurityLogic.authorizeWith(_)(authenticator, authorizer))
.serverLogic { wac =>
{ case (rc, createWebhook) =>
eventController
Expand All @@ -27,7 +28,7 @@ class EventServerEndpoints(

val listWebhookNotificationServerEndpoint: ZServerEndpoint[Any, Any] =
EventEndpoints.listWebhookNotification
.zServerSecurityLogic(SecurityLogic.authorizeWith(_)(authenticator))
.zServerSecurityLogic(SecurityLogic.authorizeWith(_)(authenticator, authorizer))
.serverLogic { wac => rc =>
eventController
.listWebhookNotifications(rc)
Expand All @@ -36,7 +37,7 @@ class EventServerEndpoints(

val deleteWebhookNotificationServerEndpoint: ZServerEndpoint[Any, Any] =
EventEndpoints.deleteWebhookNotification
.zServerSecurityLogic(SecurityLogic.authorizeWith(_)(authenticator))
.zServerSecurityLogic(SecurityLogic.authorizeWith(_)(authenticator, authorizer))
.serverLogic { wac =>
{ case (rc, id) =>
eventController
Expand All @@ -58,7 +59,7 @@ object EventServerEndpoints {
for {
authenticator <- ZIO.service[DefaultAuthenticator]
eventController <- ZIO.service[EventController]
eventEndpoints = new EventServerEndpoints(eventController, authenticator)
eventEndpoints = new EventServerEndpoints(eventController, authenticator, authenticator)
} yield eventEndpoints.all
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package io.iohk.atala.iam.authentication
import io.iohk.atala.agent.walletapi.model.BaseEntity
import io.iohk.atala.agent.walletapi.model.Entity
import io.iohk.atala.api.http.ErrorResponse
import io.iohk.atala.shared.models.WalletAccessContext
import io.iohk.atala.shared.models.WalletAdministrationContext
import io.iohk.atala.shared.models.WalletId
import zio.{IO, ZIO, ZLayer}

Expand Down Expand Up @@ -43,14 +45,22 @@ trait Authenticator[E <: BaseEntity] {
}

trait Authorizer[E <: BaseEntity] {
def authorize(entity: E): IO[AuthenticationError, WalletId]
def authorize(entity: E): IO[AuthenticationError, WalletAccessContext]
def authorizeWalletAdmin(entity: E): IO[AuthenticationError, WalletAdministrationContext]
}

object EntityAuthorizer extends EntityAuthorizer

trait EntityAuthorizer extends Authorizer[Entity] {
override def authorize(entity: Entity): IO[AuthenticationError, WalletId] =
ZIO.succeed(entity.walletId).map(WalletId.fromUUID)
override def authorize(entity: Entity): IO[AuthenticationError, WalletAccessContext] =
ZIO.succeed(entity.walletId).map(WalletId.fromUUID).map(WalletAccessContext.apply)

override def authorizeWalletAdmin(entity: Entity): IO[AuthenticationError, WalletAdministrationContext] = {
val ctx =
if (entity == Entity.Admin) WalletAdministrationContext.Admin()
else WalletAdministrationContext.SelfService(Seq(WalletId.fromUUID(entity.walletId)))
ZIO.succeed(ctx)
}
}

trait AuthenticatorWithAuthZ[E <: BaseEntity] extends Authenticator[E], Authorizer[E]
Expand All @@ -59,8 +69,10 @@ object DefaultEntityAuthenticator extends AuthenticatorWithAuthZ[BaseEntity] {

override def isEnabled: Boolean = true
override def authenticate(credentials: Credentials): IO[AuthenticationError, BaseEntity] = ZIO.succeed(Entity.Default)
override def authorize(entity: BaseEntity): IO[AuthenticationError, WalletId] =
override def authorize(entity: BaseEntity): IO[AuthenticationError, WalletAccessContext] =
EntityAuthorizer.authorize(Entity.Default)
override def authorizeWalletAdmin(entity: BaseEntity): IO[AuthenticationError, WalletAdministrationContext] =
EntityAuthorizer.authorizeWalletAdmin(Entity.Default)

val layer = ZLayer.apply(ZIO.succeed(DefaultEntityAuthenticator))
}
Loading

0 comments on commit f2e74cd

Please sign in to comment.