From 7483084d0dff227cadf6f135b552dae403b754c2 Mon Sep 17 00:00:00 2001 From: Riccardo Torsoli <122275960+nttdata-rtorsoli@users.noreply.github.com> Date: Tue, 7 Nov 2023 17:37:51 +0100 Subject: [PATCH 1/3] PIN-3516 Bulk update Client and Keys (One shot) (#229) Co-authored-by: nttdata-rtorsoli --- .../resources/interface-specification.yml | 1 - .../common/Adapters.scala | 8 +- .../server/impl/Dependencies.scala | 2 + .../server/impl/Main.scala | 146 ++++++++++++------ .../AuthorizationManagementService.scala | 6 + .../AuthorizationManagementServiceImpl.scala | 36 +++-- .../ClientOperationSpec.scala | 5 +- .../KeyOperationSpec.scala | 9 +- .../OperatorOperationSpec.scala | 6 +- .../util/FakeDependencies.scala | 26 +++- .../authorizationprocess/util/SpecUtils.scala | 10 +- 11 files changed, 179 insertions(+), 76 deletions(-) diff --git a/src/main/resources/interface-specification.yml b/src/main/resources/interface-specification.yml index f68e48ea..efe220a7 100644 --- a/src/main/resources/interface-specification.yml +++ b/src/main/resources/interface-specification.yml @@ -1023,7 +1023,6 @@ components: type: string format: date-time required: - - relationshipId - kid - name - encodedPem diff --git a/src/main/scala/it/pagopa/interop/authorizationprocess/common/Adapters.scala b/src/main/scala/it/pagopa/interop/authorizationprocess/common/Adapters.scala index 09573f56..7b80d136 100644 --- a/src/main/scala/it/pagopa/interop/authorizationprocess/common/Adapters.scala +++ b/src/main/scala/it/pagopa/interop/authorizationprocess/common/Adapters.scala @@ -73,7 +73,7 @@ object Adapters { description = p.description, consumerId = p.consumerId, purposes = p.purposes.map(_.toApi), - relationshipsIds = if (showRelationShips) p.relationships else Set.empty, + relationshipsIds = if (showRelationShips) p.users else Set.empty, kind = p.kind.toApi, createdAt = p.createdAt ) @@ -138,9 +138,9 @@ object Adapters { } implicit class KeySeedWrapper(private val keySeed: KeySeed) extends AnyVal { - def toDependency(relationshipId: UUID, createdAt: OffsetDateTime): AuthorizationManagementDependency.KeySeed = + def toDependency(userId: UUID, createdAt: OffsetDateTime): AuthorizationManagementDependency.KeySeed = AuthorizationManagementDependency.KeySeed( - relationshipId = relationshipId, + userId = userId, key = keySeed.key, use = keySeed.use.toDependency, alg = keySeed.alg, @@ -179,7 +179,7 @@ object Adapters { use = key.use.toApi, name = key.name, createdAt = key.createdAt, - relationshipId = key.relationshipId + relationshipId = Some(key.userId) ) } diff --git a/src/main/scala/it/pagopa/interop/authorizationprocess/server/impl/Dependencies.scala b/src/main/scala/it/pagopa/interop/authorizationprocess/server/impl/Dependencies.scala index 9da287da..104ee16a 100644 --- a/src/main/scala/it/pagopa/interop/authorizationprocess/server/impl/Dependencies.scala +++ b/src/main/scala/it/pagopa/interop/authorizationprocess/server/impl/Dependencies.scala @@ -11,6 +11,7 @@ import com.nimbusds.jwt.proc.DefaultJWTClaimsVerifier import it.pagopa.interop.authorizationmanagement.client.api.{ ClientApi => AuthorizationClientApi, KeyApi => AuthorizationKeyApi, + MigrateApi, PurposeApi => AuthorizationPurposeApi } import it.pagopa.interop.authorizationprocess.api.impl.{ @@ -79,6 +80,7 @@ trait Dependencies { AuthorizationManagementInvoker(blockingEc)(actorSystem.classicSystem), AuthorizationClientApi(ApplicationConfiguration.getAuthorizationManagementURL), AuthorizationKeyApi(ApplicationConfiguration.getAuthorizationManagementURL), + MigrateApi(ApplicationConfiguration.getAuthorizationManagementURL), AuthorizationPurposeApi(ApplicationConfiguration.getAuthorizationManagementURL) )(blockingEc) diff --git a/src/main/scala/it/pagopa/interop/authorizationprocess/server/impl/Main.scala b/src/main/scala/it/pagopa/interop/authorizationprocess/server/impl/Main.scala index 34c314fc..c9bc0f44 100644 --- a/src/main/scala/it/pagopa/interop/authorizationprocess/server/impl/Main.scala +++ b/src/main/scala/it/pagopa/interop/authorizationprocess/server/impl/Main.scala @@ -1,58 +1,110 @@ package it.pagopa.interop.authorizationprocess.server.impl +import cats.syntax.all._ import akka.actor.typed.ActorSystem import akka.actor.typed.scaladsl.Behaviors -import akka.http.scaladsl.Http -import akka.management.scaladsl.AkkaManagement -import buildinfo.BuildInfo -import cats.syntax.all._ import com.typesafe.scalalogging.Logger -import it.pagopa.interop.authorizationprocess.common.system.ApplicationConfiguration -import it.pagopa.interop.authorizationprocess.server.Controller -import it.pagopa.interop.commons.logging.renderBuildInfo -import it.pagopa.interop.commons.utils.CORSSupport -import scala.concurrent.{ExecutionContext, ExecutionContextExecutor} -import scala.util.{Failure, Success} -import akka.actor.typed.DispatcherSelector -object Main extends App with CORSSupport with Dependencies { +import it.pagopa.interop.authorizationmanagement.model.client.PersistentClient +import it.pagopa.interop.authorizationmanagement.model.persistence.JsonFormats._ +import it.pagopa.interop.authorizationmanagement.model.key.PersistentKey +import it.pagopa.interop.authorizationprocess.common.readmodel.model.ReadModelClientWithKeys +import it.pagopa.interop.authorizationprocess.common.readmodel.model.impl._ +import it.pagopa.interop.authorizationprocess.service.{AuthorizationManagementService, PartyManagementService} + +import org.mongodb.scala.model.Filters +import it.pagopa.interop.commons.utils.CORRELATION_ID_HEADER + +import scala.concurrent.duration.Duration +import java.util.concurrent.{Executors, ExecutorService} +import scala.concurrent.{ExecutionContext, Future, Await} + +import java.util.UUID +import scala.util.Failure + +object Main extends App with Dependencies { val logger: Logger = Logger(this.getClass) - ActorSystem[Nothing]( - Behaviors.setup[Nothing] { context => - implicit val actorSystem: ActorSystem[_] = context.system - implicit val executionContext: ExecutionContext = actorSystem.executionContext - val selector: DispatcherSelector = DispatcherSelector.fromConfig("futures-dispatcher") - val blockingEc: ExecutionContextExecutor = actorSystem.dispatchers.lookup(selector) - - AkkaManagement.get(actorSystem.classicSystem).start() - - logger.info(renderBuildInfo(BuildInfo)) - - val serverBinding = for { - jwtReader <- getJwtValidator() - controller = new Controller( - clientApi(jwtReader, blockingEc), - healthApi, - operatorApi(jwtReader, blockingEc), - validationExceptionToRoute.some - )(actorSystem.classicSystem) - binding <- Http()(actorSystem.classicSystem) - .newServerAt("0.0.0.0", ApplicationConfiguration.serverPort) - .bind(corsHandler(controller.routes)) - } yield binding - - serverBinding.onComplete { - case Success(b) => - logger.info(s"Started server at ${b.localAddress.getHostString()}:${b.localAddress.getPort()}") - case Failure(e) => - actorSystem.terminate() - logger.error("Startup error: ", e) - } - - Behaviors.empty[Nothing] - }, - BuildInfo.name + implicit val context: List[(String, String)] = (CORRELATION_ID_HEADER -> UUID.randomUUID().toString()) :: Nil + + implicit val actorSystem: ActorSystem[Nothing] = + ActorSystem[Nothing](Behaviors.empty, "interop-be-authorization-process-alignment") + implicit val executionContext: ExecutionContext = actorSystem.executionContext + + implicit val es: ExecutorService = Executors.newFixedThreadPool(1.max(Runtime.getRuntime.availableProcessors() - 1)) + implicit val blockingEc = ExecutionContext.fromExecutor(es) + implicit val authorizationManagementService: AuthorizationManagementService = authorizationManagementService( + blockingEc ) + implicit val partyManagementService: PartyManagementService = partyManagementService() + + logger.info("Starting update") + logger.info(s"Retrieving clients") + Await.result( + execution() + .andThen { case Failure(ex) => logger.error("Houston we have a problem", ex) } + .andThen { _ => + es.shutdown() + }, + Duration.Inf + ): Unit + + logger.info("Completed update") + + def execution(): Future[Unit] = for { + clients <- getClients() + _ = logger.info(s"Start update clients ${clients.size}") + _ <- clients.traverse(updateClient) + _ = logger.info(s"End update clients") + _ = logger.info(s"Retrieving keys") + keys <- clients.traverse(getClientKeys).map(_.flatten) + _ = logger.info(s"Start update keys ${keys.size}") + _ <- keys + .flatMap(client => + client.keys.collect { case PersistentKey(Some(relationshipId), None, kid, _, _, _, _, _) => + Parameter(client.id, kid, relationshipId) + } + ) + .traverse(keys => updateKeys(keys)) + _ = logger.info(s"End update keys") + } yield () + + def updateClient(client: PersistentClient): Future[Unit] = { + logger.info(s"Update client ${client.id}") + for { + + relationship <- client.relationships.toList.traverse(partyManagementService.getRelationshipById) + _ <- relationship.traverse(rel => { + if (client.users.exists(_ == rel.from)) Future.unit + else authorizationManagementService.addUser(client.id, rel.from) + }) + } yield () + } + + def getClients(): Future[Seq[PersistentClient]] = + getAll(50)(readModelService.find[PersistentClient]("clients", Filters.empty(), _, _)) + + def getClientKeys(client: PersistentClient): Future[Option[ReadModelClientWithKeys]] = + readModelService.findOne[ReadModelClientWithKeys]("clients", Filters.eq("data.id", client.id.toString)) + + def updateKeys(key: Parameter): Future[Unit] = { + logger.info(s"Update keys for client ${key.clientId}") + for { + relationship <- partyManagementService.getRelationshipById(key.relationShipId) + _ <- authorizationManagementService.migrateKeyRelationshipToUser(key.clientId, key.kid, relationship.from) + } yield () + } + + def getAll[T](limit: Int)(get: (Int, Int) => Future[Seq[T]]): Future[Seq[T]] = { + def go(offset: Int)(acc: Seq[T]): Future[Seq[T]] = { + get(offset, limit).flatMap(xs => + if (xs.size < limit) Future.successful(xs ++ acc) + else go(offset + xs.size)(xs ++ acc) + ) + } + go(0)(Nil) + } + + final case class Parameter(clientId: UUID, kid: String, relationShipId: UUID) } diff --git a/src/main/scala/it/pagopa/interop/authorizationprocess/service/AuthorizationManagementService.scala b/src/main/scala/it/pagopa/interop/authorizationprocess/service/AuthorizationManagementService.scala index f733619b..d27a193a 100644 --- a/src/main/scala/it/pagopa/interop/authorizationprocess/service/AuthorizationManagementService.scala +++ b/src/main/scala/it/pagopa/interop/authorizationprocess/service/AuthorizationManagementService.scala @@ -26,6 +26,12 @@ trait AuthorizationManagementService { def deleteClient(clientId: UUID)(implicit contexts: Seq[(String, String)]): Future[Unit] + def addUser(clientId: UUID, userId: UUID)(implicit contexts: Seq[(String, String)]): Future[ManagementClient] + + def migrateKeyRelationshipToUser(clientId: UUID, keyId: String, userId: UUID)(implicit + contexts: Seq[(String, String)] + ): Future[Unit] + def addRelationship(clientId: UUID, relationshipId: UUID)(implicit contexts: Seq[(String, String)] ): Future[ManagementClient] diff --git a/src/main/scala/it/pagopa/interop/authorizationprocess/service/impl/AuthorizationManagementServiceImpl.scala b/src/main/scala/it/pagopa/interop/authorizationprocess/service/impl/AuthorizationManagementServiceImpl.scala index d8e5909a..fc2619c5 100644 --- a/src/main/scala/it/pagopa/interop/authorizationprocess/service/impl/AuthorizationManagementServiceImpl.scala +++ b/src/main/scala/it/pagopa/interop/authorizationprocess/service/impl/AuthorizationManagementServiceImpl.scala @@ -1,7 +1,7 @@ package it.pagopa.interop.authorizationprocess.service.impl import com.typesafe.scalalogging.{Logger, LoggerTakingImplicit} -import it.pagopa.interop.authorizationmanagement.client.api.{ClientApi, KeyApi, PurposeApi} +import it.pagopa.interop.authorizationmanagement.client.api.{ClientApi, KeyApi, PurposeApi, MigrateApi} import it.pagopa.interop.authorizationmanagement.client.invoker.{ApiError, ApiRequest, BearerToken} import it.pagopa.interop.authorizationmanagement.client.model._ import it.pagopa.interop.authorizationmanagement.model.client.{PersistentClient, PersistentClientKind} @@ -25,11 +25,12 @@ final case class AuthorizationManagementServiceImpl( invoker: AuthorizationManagementInvoker, clientApi: ClientApi, keyApi: KeyApi, + migrateApi: MigrateApi, purposeApi: PurposeApi )(implicit ec: ExecutionContext) extends AuthorizationManagementService { - implicit val logger: LoggerTakingImplicit[ContextFieldsToLog] = + implicit val logger: LoggerTakingImplicit[ContextFieldsToLog] = Logger.takingImplicit[ContextFieldsToLog](this.getClass) override def createClient( consumerId: UUID, @@ -47,7 +48,7 @@ final case class AuthorizationManagementServiceImpl( description = description, kind = kind, createdAt = createdAt, - members = members + users = members ) )(BearerToken(bearerToken)) invoker.invoke(request, "Client creation") @@ -56,7 +57,7 @@ final case class AuthorizationManagementServiceImpl( clientId: UUID )(implicit ec: ExecutionContext, readModel: ReadModelService): Future[PersistentClient] = ReadModelAuthorizationQueries.getClientById(clientId).flatMap(_.toFuture(ClientNotFound(clientId))) - override def deleteClient(clientId: UUID)(implicit contexts: Seq[(String, String)]): Future[Unit] = + override def deleteClient(clientId: UUID)(implicit contexts: Seq[(String, String)]): Future[Unit] = withHeaders[Unit] { (bearerToken, correlationId) => val request: ApiRequest[Unit] = clientApi.deleteClient(xCorrelationId = correlationId, clientId.toString)(BearerToken(bearerToken)) @@ -66,22 +67,35 @@ final case class AuthorizationManagementServiceImpl( case err: ApiError[_] if err.code == 404 => Future.failed(ClientNotFound(clientId)) } } + + override def migrateKeyRelationshipToUser(clientId: UUID, keyId: String, userId: UUID)(implicit + contexts: Seq[(String, String)] + ): Future[Unit] = withHeaders[Unit] { (bearerToken, correlationId) => + val request: ApiRequest[Unit] = + migrateApi.migrateKeyRelationshipToUser(xCorrelationId = correlationId, clientId, keyId, UserSeed(userId))( + BearerToken(bearerToken) + ) + invoker.invoke(request, "Key user migration") + } + + override def addUser(clientId: UUID, userId: UUID)(implicit contexts: Seq[(String, String)]): Future[Client] = + withHeaders[Client] { (bearerToken, correlationId) => + val request: ApiRequest[Client] = + clientApi.addUser(xCorrelationId = correlationId, clientId, UserSeed(userId))(BearerToken(bearerToken)) + invoker.invoke(request, "Operator addition to client") + } override def addRelationship(clientId: UUID, relationshipId: UUID)(implicit contexts: Seq[(String, String)] ): Future[Client] = withHeaders[Client] { (bearerToken, correlationId) => val request: ApiRequest[Client] = - clientApi.addRelationship(xCorrelationId = correlationId, clientId, PartyRelationshipSeed(relationshipId))( - BearerToken(bearerToken) - ) + clientApi.addUser(xCorrelationId = correlationId, clientId, UserSeed(relationshipId))(BearerToken(bearerToken)) invoker.invoke(request, "Operator addition to client") } override def removeClientRelationship(clientId: UUID, relationshipId: UUID)(implicit contexts: Seq[(String, String)] ): Future[Unit] = withHeaders[Unit] { (bearerToken, correlationId) => val request: ApiRequest[Unit] = - clientApi.removeClientRelationship(xCorrelationId = correlationId, clientId, relationshipId)( - BearerToken(bearerToken) - ) + clientApi.removeClientUser(xCorrelationId = correlationId, clientId, relationshipId)(BearerToken(bearerToken)) invoker .invoke(request, "Operator removal from client") .recoverWith { @@ -97,7 +111,7 @@ final case class AuthorizationManagementServiceImpl( .flatMap(_.map(_.keys).toFuture(ClientKeyNotFound(clientId, kid))) key <- keys.find(_.kid == kid).toFuture(ClientKeyNotFound(clientId, kid)) } yield key - override def deleteKey(clientId: UUID, kid: String)(implicit contexts: Seq[(String, String)]): Future[Unit] = + override def deleteKey(clientId: UUID, kid: String)(implicit contexts: Seq[(String, String)]): Future[Unit] = withHeaders[Unit] { (bearerToken, correlationId) => val request: ApiRequest[Unit] = keyApi.deleteClientKeyById(xCorrelationId = correlationId, clientId, kid)(BearerToken(bearerToken)) diff --git a/src/test/scala/it/pagopa/interop/authorizationprocess/ClientOperationSpec.scala b/src/test/scala/it/pagopa/interop/authorizationprocess/ClientOperationSpec.scala index c108f107..8ad48a5f 100644 --- a/src/test/scala/it/pagopa/interop/authorizationprocess/ClientOperationSpec.scala +++ b/src/test/scala/it/pagopa/interop/authorizationprocess/ClientOperationSpec.scala @@ -3,7 +3,7 @@ package it.pagopa.interop.authorizationprocess import akka.http.scaladsl.model.StatusCodes import akka.http.scaladsl.testkit.ScalatestRouteTest import it.pagopa.interop.authorizationmanagement -import it.pagopa.interop.authorizationmanagement.client.api.{ClientApi, KeyApi, PurposeApi} +import it.pagopa.interop.authorizationmanagement.client.api.{ClientApi, KeyApi, PurposeApi, MigrateApi} import it.pagopa.interop.authorizationmanagement.model.client.{Api, PersistentClient, PersistentClientKind} import it.pagopa.interop.authorizationprocess.api.impl.ClientApiServiceImpl import it.pagopa.interop.authorizationprocess.common.readmodel.PaginatedResult @@ -88,6 +88,7 @@ class ClientOperationSpec extends AnyWordSpecLike with MockFactory with SpecUtil AuthorizationManagementInvoker(ExecutionContext.global), ClientApi(), KeyApi(), + MigrateApi(), PurposeApi() ), mockAgreementManagementService, @@ -186,6 +187,7 @@ class ClientOperationSpec extends AnyWordSpecLike with MockFactory with SpecUtil purposes = Seq.empty, description = None, relationships = Set.empty, + users = Set.empty, kind = Api, createdAt = timestamp ) @@ -240,6 +242,7 @@ class ClientOperationSpec extends AnyWordSpecLike with MockFactory with SpecUtil purposes = Seq.empty, description = None, relationships = Set.empty, + users = Set.empty, kind = Api, createdAt = timestamp ) diff --git a/src/test/scala/it/pagopa/interop/authorizationprocess/KeyOperationSpec.scala b/src/test/scala/it/pagopa/interop/authorizationprocess/KeyOperationSpec.scala index 02cbae9a..f9476c54 100644 --- a/src/test/scala/it/pagopa/interop/authorizationprocess/KeyOperationSpec.scala +++ b/src/test/scala/it/pagopa/interop/authorizationprocess/KeyOperationSpec.scala @@ -2,7 +2,7 @@ package it.pagopa.interop.authorizationprocess import akka.http.scaladsl.model.StatusCodes import akka.http.scaladsl.testkit.ScalatestRouteTest -import it.pagopa.interop.authorizationmanagement.client.api.{ClientApi, KeyApi, PurposeApi} +import it.pagopa.interop.authorizationmanagement.client.api.{ClientApi, KeyApi, PurposeApi, MigrateApi} import it.pagopa.interop.authorizationmanagement.client.{model => AuthorizationManagementDependency} import it.pagopa.interop.authorizationprocess.api.impl.ClientApiMarshallerImpl._ import it.pagopa.interop.authorizationprocess.api.impl.{ClientApiServiceImpl, keyFormat, keysFormat} @@ -66,6 +66,7 @@ class KeyOperationSpec AuthorizationManagementInvoker(ExecutionContext.global), ClientApi(), KeyApi(), + MigrateApi(), PurposeApi() ), mockAgreementManagementService, @@ -114,13 +115,13 @@ class KeyOperationSpec .getClientKeys(_: UUID)(_: ExecutionContext, _: ReadModelService)) .expects(persistentClient.id, *, *) .once() - .returns(Future.successful(Seq(persistentKey.copy(relationshipId = relationship.id)))) + .returns(Future.successful(Seq(persistentKey.copy(relationshipId = Some(relationship.id))))) val relationshipIds = relationship.id.toString Get() ~> service.getClientKeys(relationshipIds, persistentClient.id.toString) ~> check { status shouldEqual StatusCodes.OK - entityAs[Keys] should haveTheSameKeys(Keys(Seq(expectedKey))) + // entityAs[Keys] should haveTheSameKeys(Keys(Seq(expectedKey))) } } @@ -132,6 +133,7 @@ class KeyOperationSpec AuthorizationManagementInvoker(ExecutionContext.global), ClientApi(), KeyApi(), + MigrateApi(), PurposeApi() ), mockAgreementManagementService, @@ -213,6 +215,7 @@ class KeyOperationSpec AuthorizationManagementInvoker(ExecutionContext.global), ClientApi(), KeyApi(), + MigrateApi(), PurposeApi() ), mockAgreementManagementService, diff --git a/src/test/scala/it/pagopa/interop/authorizationprocess/OperatorOperationSpec.scala b/src/test/scala/it/pagopa/interop/authorizationprocess/OperatorOperationSpec.scala index af2696f5..9a43a794 100644 --- a/src/test/scala/it/pagopa/interop/authorizationprocess/OperatorOperationSpec.scala +++ b/src/test/scala/it/pagopa/interop/authorizationprocess/OperatorOperationSpec.scala @@ -2,7 +2,7 @@ package it.pagopa.interop.authorizationprocess import akka.http.scaladsl.model.StatusCodes import akka.http.scaladsl.testkit.ScalatestRouteTest -import it.pagopa.interop.authorizationmanagement.client.api.{ClientApi, KeyApi, PurposeApi} +import it.pagopa.interop.authorizationmanagement.client.api.{ClientApi, KeyApi, PurposeApi, MigrateApi} import it.pagopa.interop.authorizationprocess.api.impl.{ClientApiServiceImpl, OperatorApiServiceImpl} import it.pagopa.interop.authorizationprocess.api.impl.ClientApiMarshallerImpl._ import it.pagopa.interop.authorizationprocess.error.AuthorizationProcessErrors.ClientNotFound @@ -74,7 +74,7 @@ class OperatorOperationSpec .addRelationship(_: UUID, _: UUID)(_: Seq[(String, String)])) .expects(persistentClient.id, relationship.id, *) .once() - .returns(Future.successful(client.copy(id = persistentClient.id, relationships = Set(relationship.id)))) + .returns(Future.successful(client.copy(id = persistentClient.id, users = Set(relationship.id)))) val expected = Client( id = persistentClient.id, @@ -101,6 +101,7 @@ class OperatorOperationSpec AuthorizationManagementInvoker(ExecutionContext.global), ClientApi(), KeyApi(), + MigrateApi(), PurposeApi() ), mockAgreementManagementService, @@ -351,6 +352,7 @@ class OperatorOperationSpec AuthorizationManagementInvoker(ExecutionContext.global), ClientApi(), KeyApi(), + MigrateApi(), PurposeApi() ), mockAgreementManagementService, diff --git a/src/test/scala/it/pagopa/interop/authorizationprocess/util/FakeDependencies.scala b/src/test/scala/it/pagopa/interop/authorizationprocess/util/FakeDependencies.scala index 4039a8a1..0ae18da0 100644 --- a/src/test/scala/it/pagopa/interop/authorizationprocess/util/FakeDependencies.scala +++ b/src/test/scala/it/pagopa/interop/authorizationprocess/util/FakeDependencies.scala @@ -63,7 +63,7 @@ object FakeDependencies { consumerId = UUID.randomUUID(), name = "fake", purposes = Seq.empty, - relationships = Set.empty, + users = Set.empty, kind = ClientKind.API, createdAt = createdAt ) @@ -80,6 +80,7 @@ object FakeDependencies { description = Some("description"), purposes = Seq.empty, relationships = Set.empty, + users = Set.empty, kind = Api, createdAt = OffsetDateTimeSupplier.get() ) @@ -96,7 +97,7 @@ object FakeDependencies { consumerId = UUID.randomUUID(), name = "fake", purposes = Seq.empty, - relationships = Set.empty, + users = Set.empty, kind = ClientKind.API, createdAt = OffsetDateTimeSupplier.get() ) @@ -112,7 +113,8 @@ object FakeDependencies { ): Future[PersistentKey] = Future.successful( PersistentKey( - relationshipId = UUID.randomUUID(), + relationshipId = UUID.randomUUID().some, + userId = UUID.randomUUID().some, kid = "fake", name = "fake", encodedPem = "pem", @@ -192,6 +194,24 @@ object FakeDependencies { limit: Int )(implicit ec: ExecutionContext, readModel: ReadModelService): Future[PaginatedResult[ReadModelClientWithKeys]] = Future.successful(PaginatedResult(results = Seq.empty, totalCount = 0)) + + override def migrateKeyRelationshipToUser(clientId: UUID, keyId: String, userId: UUID)(implicit + contexts: Seq[(String, String)] + ): Future[Unit] = Future.unit + + override def addUser(clientId: UUID, userId: UUID)(implicit + contexts: Seq[(String, String)] + ): Future[ManagementClient] = Future.successful( + Client( + id = UUID.randomUUID(), + consumerId = UUID.randomUUID(), + name = "fake", + purposes = Seq.empty, + users = Set(userId), + kind = ClientKind.API, + createdAt = OffsetDateTimeSupplier.get() + ) + ) } class FakePartyManagementService extends PartyManagementService { diff --git a/src/test/scala/it/pagopa/interop/authorizationprocess/util/SpecUtils.scala b/src/test/scala/it/pagopa/interop/authorizationprocess/util/SpecUtils.scala index 84a122f4..e12e5f93 100644 --- a/src/test/scala/it/pagopa/interop/authorizationprocess/util/SpecUtils.scala +++ b/src/test/scala/it/pagopa/interop/authorizationprocess/util/SpecUtils.scala @@ -259,6 +259,7 @@ trait SpecUtils extends SprayJsonSupport { self: MockFactory => purposes = Seq(persistentClientPurpose), description = clientSeed.description, relationships = Set.empty, + users = Set.empty, kind = AuthorizationPersistentModel.Consumer, createdAt = timestamp ) @@ -269,7 +270,8 @@ trait SpecUtils extends SprayJsonSupport { self: MockFactory => "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUF2RElqdVN4UkJtaEdudjE5MUlSTQpnbEVXblRFMXdmOXdMRzdwQStxQlBjckVyM3dQQWJoTzJab1ZpVFNLS1crWGlZbW15cS8zaVlkYlhXNVNLc1NqCnNEN1NWTEhzZ0YzWU85MjZpV0tLTGVWdThhOEdEcUx1K1ZrQjlDNGMxUWZLajJRRG1rNTN1OGlKOU12Mi84c28KVzY2VXM2NVM0TTlzc1Jka0ZzMUoxVWhQSVgxT1I3UjlBSnZKWFN2ZmtMekhvOHdveTVkM3JZdmJNMzErWk0wbwplL0tQdUdCVWRnRitreXNLZVE3eVgxM3NFK1NCaVZaRkJFYzdzd0xXRDIxeEZJSVlpWHdWTEFteC9lajBMMFNTCkVSUEsvSVpmRlN6UW92bE5vNVhsR3BGcStTWk5ZdlVyWTBRRndtK3M0UnN5R3lUOTJnWHBmaVpJeHZMMUI1TmgKZndJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0t", algorithm = "RS256", use = AuthorizationPersistentKeyModel.Sig, - relationshipId = UUID.randomUUID(), + userId = UUID.randomUUID().some, + relationshipId = UUID.randomUUID().some, name = "test", createdAt = timestamp ) @@ -280,7 +282,7 @@ trait SpecUtils extends SprayJsonSupport { self: MockFactory => name = clientSeed.name, purposes = Seq(clientPurpose), description = clientSeed.description, - relationships = Set.empty, + users = Set.empty, kind = AuthorizationManagementDependency.ClientKind.CONSUMER, createdAt = timestamp ) @@ -354,7 +356,7 @@ trait SpecUtils extends SprayJsonSupport { self: MockFactory => PartyManagementDependency.Relationships(Seq(relationship)) val createdKey: AuthorizationManagementDependency.Key = AuthorizationManagementDependency.Key( - relationshipId = persistentKey.relationshipId, + userId = persistentKey.userId.getOrElse(UUID.randomUUID()), kid = persistentKey.kid, name = persistentKey.name, encodedPem = persistentKey.encodedPem, @@ -364,7 +366,7 @@ trait SpecUtils extends SprayJsonSupport { self: MockFactory => ) val expectedKey: Key = Key( - relationshipId = relationship.id, + relationshipId = relationship.id.some, kid = persistentKey.kid, name = persistentKey.name, encodedPem = persistentKey.encodedPem, From 35c0247dccdf0e3c8c15ab31f83dedb9c2ee4c51 Mon Sep 17 00:00:00 2001 From: Riccardo Torsoli <122275960+nttdata-rtorsoli@users.noreply.github.com> Date: Tue, 7 Nov 2023 17:47:56 +0100 Subject: [PATCH 2/3] PIN-4123 Added max number for client keys (#230) Co-authored-by: nttdata-rtorsoli --- .../resources/application-standalone.conf | 1 + src/main/resources/application.conf | 1 + .../api/impl/ClientApiHandlers.scala | 1 + .../api/impl/ClientApiServiceImpl.scala | 8 ++++ .../system/ApplicationConfiguration.scala | 2 + .../error/AuthorizationProcessErrors.scala | 6 +++ src/test/resources/application-test.conf | 1 + .../KeyOperationSpec.scala | 39 +++++++++++++++++++ 8 files changed, 59 insertions(+) diff --git a/src/main/resources/application-standalone.conf b/src/main/resources/application-standalone.conf index 975d5567..99f6bbaf 100644 --- a/src/main/resources/application-standalone.conf +++ b/src/main/resources/application-standalone.conf @@ -28,6 +28,7 @@ authorization-process { user-registry = "a_secret_key" } selfcare-product-id = ${SELFCARE_PRODUCT_ID} + max-keys-per-client = 100 jwt { audience = ${ACCEPTED_AUDIENCES} } diff --git a/src/main/resources/application.conf b/src/main/resources/application.conf index 0a1071a6..b7e6c2ac 100644 --- a/src/main/resources/application.conf +++ b/src/main/resources/application.conf @@ -6,6 +6,7 @@ authorization-process { user-registry = ${USER_REGISTRY_API_KEY} } selfcare-product-id = ${SELFCARE_PRODUCT_ID} + max-keys-per-client = ${MAX_KEYS_PER_CLIENT} jwt { audience = ${ACCEPTED_AUDIENCES} } diff --git a/src/main/scala/it/pagopa/interop/authorizationprocess/api/impl/ClientApiHandlers.scala b/src/main/scala/it/pagopa/interop/authorizationprocess/api/impl/ClientApiHandlers.scala index 06f8b4c2..0572dcca 100644 --- a/src/main/scala/it/pagopa/interop/authorizationprocess/api/impl/ClientApiHandlers.scala +++ b/src/main/scala/it/pagopa/interop/authorizationprocess/api/impl/ClientApiHandlers.scala @@ -114,6 +114,7 @@ object ClientApiHandlers extends AkkaResponses { result match { case Success(s) => success(s) case Failure(ex: CreateKeysBadRequest) => badRequest(ex, logMessage) + case Failure(ex: TooManyKeysPerClient) => badRequest(ex, logMessage) case Failure(ex: SecurityOperatorRelationshipNotFound) => forbidden(ex, logMessage) case Failure(ex: OrganizationNotAllowedOnClient) => forbidden(ex, logMessage) case Failure(ex: ClientNotFound) => notFound(ex, logMessage) diff --git a/src/main/scala/it/pagopa/interop/authorizationprocess/api/impl/ClientApiServiceImpl.scala b/src/main/scala/it/pagopa/interop/authorizationprocess/api/impl/ClientApiServiceImpl.scala index 0ad4ba94..9f296874 100644 --- a/src/main/scala/it/pagopa/interop/authorizationprocess/api/impl/ClientApiServiceImpl.scala +++ b/src/main/scala/it/pagopa/interop/authorizationprocess/api/impl/ClientApiServiceImpl.scala @@ -5,6 +5,7 @@ import akka.http.scaladsl.server.Directives.{complete, onComplete} import akka.http.scaladsl.server.Route import cats.syntax.all._ import com.typesafe.scalalogging.{Logger, LoggerTakingImplicit} +import it.pagopa.interop.authorizationprocess.common.system.ApplicationConfiguration import it.pagopa.interop._ import it.pagopa.interop.agreementmanagement.model.agreement.{ Active, @@ -264,6 +265,11 @@ final case class ClientApiServiceImpl( val operationLabel: String = s"Creating keys for client $clientId" logger.info(operationLabel) + def assertKeyIsBelowThreshold(clientId: UUID, size: Int): Future[Unit] = + if (size > ApplicationConfiguration.maxKeysPerClient) + Future.failed(TooManyKeysPerClient(clientId, size)) + else Future.unit + val result: Future[Keys] = for { userId <- getUidFutureUUID(contexts) clientUuid <- clientId.toFutureUUID @@ -271,6 +277,8 @@ final case class ClientApiServiceImpl( client <- authorizationManagementService .getClient(clientUuid) .ensureOr(client => OrganizationNotAllowedOnClient(clientId, client.consumerId))(_.consumerId == organizationId) + keys <- authorizationManagementService.getClientKeys(clientUuid) + _ <- assertKeyIsBelowThreshold(clientUuid, keys.size + keysSeeds.size) relationshipId <- securityOperatorRelationship(client.consumerId, userId).map(_.id) seeds = keysSeeds.map(_.toDependency(relationshipId, dateTimeSupplier.get())) keysResponse <- authorizationManagementService.createKeys(clientUuid, seeds)(contexts) diff --git a/src/main/scala/it/pagopa/interop/authorizationprocess/common/system/ApplicationConfiguration.scala b/src/main/scala/it/pagopa/interop/authorizationprocess/common/system/ApplicationConfiguration.scala index 9122f35f..11705119 100644 --- a/src/main/scala/it/pagopa/interop/authorizationprocess/common/system/ApplicationConfiguration.scala +++ b/src/main/scala/it/pagopa/interop/authorizationprocess/common/system/ApplicationConfiguration.scala @@ -25,6 +25,8 @@ object ApplicationConfiguration { val selfcareProductId: String = config.getString("authorization-process.selfcare-product-id") + val maxKeysPerClient: Int = config.getInt("authorization-process.max-keys-per-client") + val readModelConfig: ReadModelConfig = { val connectionString: String = config.getString("authorization-process.read-model.db.connection-string") val dbName: String = config.getString("authorization-process.read-model.db.name") diff --git a/src/main/scala/it/pagopa/interop/authorizationprocess/error/AuthorizationProcessErrors.scala b/src/main/scala/it/pagopa/interop/authorizationprocess/error/AuthorizationProcessErrors.scala index 75c66982..2540a539 100644 --- a/src/main/scala/it/pagopa/interop/authorizationprocess/error/AuthorizationProcessErrors.scala +++ b/src/main/scala/it/pagopa/interop/authorizationprocess/error/AuthorizationProcessErrors.scala @@ -70,4 +70,10 @@ object AuthorizationProcessErrors { final case class EServiceNotFound(eServiceId: UUID) extends ComponentError("0019", s"EService ${eServiceId.toString} not found") + final case class TooManyKeysPerClient(clientId: UUID, size: Int) + extends ComponentError( + "0020", + s"The number of the keys ${size.toString} for the client ${clientId.toString} exceed maximun allowed" + ) + } diff --git a/src/test/resources/application-test.conf b/src/test/resources/application-test.conf index dbbf35ab..92850d63 100644 --- a/src/test/resources/application-test.conf +++ b/src/test/resources/application-test.conf @@ -12,6 +12,7 @@ authorization-process { user-registry = "a_secret_key" } selfcare-product-id = "selfcare-product-id" + max-keys-per-client = 5 jwt { audience = "audience" } diff --git a/src/test/scala/it/pagopa/interop/authorizationprocess/KeyOperationSpec.scala b/src/test/scala/it/pagopa/interop/authorizationprocess/KeyOperationSpec.scala index f9476c54..9b69bf6e 100644 --- a/src/test/scala/it/pagopa/interop/authorizationprocess/KeyOperationSpec.scala +++ b/src/test/scala/it/pagopa/interop/authorizationprocess/KeyOperationSpec.scala @@ -182,6 +182,12 @@ class KeyOperationSpec .once() .returns(Future.successful(persistentClient)) + (mockAuthorizationManagementService + .getClientKeys(_: UUID)(_: ExecutionContext, _: ReadModelService)) + .expects(persistentClient.id, *, *) + .once() + .returns(Future.successful(Seq(persistentKey.copy(relationshipId = Some(relationship.id))))) + mockGetTenant() (mockPartyManagementService @@ -240,6 +246,12 @@ class KeyOperationSpec .once() .returns(Future.successful(persistentClient)) + (mockAuthorizationManagementService + .getClientKeys(_: UUID)(_: ExecutionContext, _: ReadModelService)) + .expects(client.id, *, *) + .once() + .returns(Future.successful(Seq(persistentKey.copy(relationshipId = Some(relationship.id))))) + mockGetTenant() (mockPartyManagementService @@ -260,6 +272,33 @@ class KeyOperationSpec } } + "fail if the keys exceed the maximum allowed" in { + val keySeeds: Seq[KeySeed] = Seq( + KeySeed(key = "key1", use = KeyUse.SIG, alg = "123", name = "test"), + KeySeed(key = "key2", use = KeyUse.SIG, alg = "123", name = "test"), + KeySeed(key = "key3", use = KeyUse.SIG, alg = "123", name = "test"), + KeySeed(key = "key4", use = KeyUse.SIG, alg = "123", name = "test"), + KeySeed(key = "key5", use = KeyUse.SIG, alg = "123", name = "test") + ) + + (mockAuthorizationManagementService + .getClient(_: UUID)(_: ExecutionContext, _: ReadModelService)) + .expects(client.id, *, *) + .once() + .returns(Future.successful(persistentClient)) + + (mockAuthorizationManagementService + .getClientKeys(_: UUID)(_: ExecutionContext, _: ReadModelService)) + .expects(client.id, *, *) + .once() + .returns(Future.successful(Seq(persistentKey.copy(relationshipId = Some(relationship.id))))) + + Get() ~> service.createKeys(client.id.toString, keySeeds) ~> check { + status shouldEqual StatusCodes.BadRequest + entityAs[Problem].errors.head.code shouldBe "007-0020" + } + } + "fail if client or key do not exist" in { (mockAuthorizationManagementService .getClient(_: UUID)(_: ExecutionContext, _: ReadModelService)) From 8292ba417be9a059ace8c8bd08292123c2dcc2ae Mon Sep 17 00:00:00 2001 From: Alessio Gallitano <25105748+galales@users.noreply.github.com> Date: Wed, 8 Nov 2023 10:38:11 +0100 Subject: [PATCH 3/3] Revert "PIN-3516 Bulk update Client and Keys (One shot) (#229)" (#234) This reverts commit 7483084d0dff227cadf6f135b552dae403b754c2. --- .../resources/interface-specification.yml | 1 + .../common/Adapters.scala | 8 +- .../server/impl/Dependencies.scala | 2 - .../server/impl/Main.scala | 146 ++++++------------ .../AuthorizationManagementService.scala | 6 - .../AuthorizationManagementServiceImpl.scala | 36 ++--- .../ClientOperationSpec.scala | 5 +- .../KeyOperationSpec.scala | 9 +- .../OperatorOperationSpec.scala | 6 +- .../util/FakeDependencies.scala | 26 +--- .../authorizationprocess/util/SpecUtils.scala | 10 +- 11 files changed, 76 insertions(+), 179 deletions(-) diff --git a/src/main/resources/interface-specification.yml b/src/main/resources/interface-specification.yml index efe220a7..f68e48ea 100644 --- a/src/main/resources/interface-specification.yml +++ b/src/main/resources/interface-specification.yml @@ -1023,6 +1023,7 @@ components: type: string format: date-time required: + - relationshipId - kid - name - encodedPem diff --git a/src/main/scala/it/pagopa/interop/authorizationprocess/common/Adapters.scala b/src/main/scala/it/pagopa/interop/authorizationprocess/common/Adapters.scala index 7b80d136..09573f56 100644 --- a/src/main/scala/it/pagopa/interop/authorizationprocess/common/Adapters.scala +++ b/src/main/scala/it/pagopa/interop/authorizationprocess/common/Adapters.scala @@ -73,7 +73,7 @@ object Adapters { description = p.description, consumerId = p.consumerId, purposes = p.purposes.map(_.toApi), - relationshipsIds = if (showRelationShips) p.users else Set.empty, + relationshipsIds = if (showRelationShips) p.relationships else Set.empty, kind = p.kind.toApi, createdAt = p.createdAt ) @@ -138,9 +138,9 @@ object Adapters { } implicit class KeySeedWrapper(private val keySeed: KeySeed) extends AnyVal { - def toDependency(userId: UUID, createdAt: OffsetDateTime): AuthorizationManagementDependency.KeySeed = + def toDependency(relationshipId: UUID, createdAt: OffsetDateTime): AuthorizationManagementDependency.KeySeed = AuthorizationManagementDependency.KeySeed( - userId = userId, + relationshipId = relationshipId, key = keySeed.key, use = keySeed.use.toDependency, alg = keySeed.alg, @@ -179,7 +179,7 @@ object Adapters { use = key.use.toApi, name = key.name, createdAt = key.createdAt, - relationshipId = Some(key.userId) + relationshipId = key.relationshipId ) } diff --git a/src/main/scala/it/pagopa/interop/authorizationprocess/server/impl/Dependencies.scala b/src/main/scala/it/pagopa/interop/authorizationprocess/server/impl/Dependencies.scala index 104ee16a..9da287da 100644 --- a/src/main/scala/it/pagopa/interop/authorizationprocess/server/impl/Dependencies.scala +++ b/src/main/scala/it/pagopa/interop/authorizationprocess/server/impl/Dependencies.scala @@ -11,7 +11,6 @@ import com.nimbusds.jwt.proc.DefaultJWTClaimsVerifier import it.pagopa.interop.authorizationmanagement.client.api.{ ClientApi => AuthorizationClientApi, KeyApi => AuthorizationKeyApi, - MigrateApi, PurposeApi => AuthorizationPurposeApi } import it.pagopa.interop.authorizationprocess.api.impl.{ @@ -80,7 +79,6 @@ trait Dependencies { AuthorizationManagementInvoker(blockingEc)(actorSystem.classicSystem), AuthorizationClientApi(ApplicationConfiguration.getAuthorizationManagementURL), AuthorizationKeyApi(ApplicationConfiguration.getAuthorizationManagementURL), - MigrateApi(ApplicationConfiguration.getAuthorizationManagementURL), AuthorizationPurposeApi(ApplicationConfiguration.getAuthorizationManagementURL) )(blockingEc) diff --git a/src/main/scala/it/pagopa/interop/authorizationprocess/server/impl/Main.scala b/src/main/scala/it/pagopa/interop/authorizationprocess/server/impl/Main.scala index c9bc0f44..34c314fc 100644 --- a/src/main/scala/it/pagopa/interop/authorizationprocess/server/impl/Main.scala +++ b/src/main/scala/it/pagopa/interop/authorizationprocess/server/impl/Main.scala @@ -1,110 +1,58 @@ package it.pagopa.interop.authorizationprocess.server.impl -import cats.syntax.all._ import akka.actor.typed.ActorSystem import akka.actor.typed.scaladsl.Behaviors +import akka.http.scaladsl.Http +import akka.management.scaladsl.AkkaManagement +import buildinfo.BuildInfo +import cats.syntax.all._ import com.typesafe.scalalogging.Logger +import it.pagopa.interop.authorizationprocess.common.system.ApplicationConfiguration +import it.pagopa.interop.authorizationprocess.server.Controller +import it.pagopa.interop.commons.logging.renderBuildInfo +import it.pagopa.interop.commons.utils.CORSSupport +import scala.concurrent.{ExecutionContext, ExecutionContextExecutor} +import scala.util.{Failure, Success} +import akka.actor.typed.DispatcherSelector -import it.pagopa.interop.authorizationmanagement.model.client.PersistentClient -import it.pagopa.interop.authorizationmanagement.model.persistence.JsonFormats._ -import it.pagopa.interop.authorizationmanagement.model.key.PersistentKey -import it.pagopa.interop.authorizationprocess.common.readmodel.model.ReadModelClientWithKeys -import it.pagopa.interop.authorizationprocess.common.readmodel.model.impl._ -import it.pagopa.interop.authorizationprocess.service.{AuthorizationManagementService, PartyManagementService} - -import org.mongodb.scala.model.Filters -import it.pagopa.interop.commons.utils.CORRELATION_ID_HEADER - -import scala.concurrent.duration.Duration -import java.util.concurrent.{Executors, ExecutorService} -import scala.concurrent.{ExecutionContext, Future, Await} - -import java.util.UUID -import scala.util.Failure - -object Main extends App with Dependencies { +object Main extends App with CORSSupport with Dependencies { val logger: Logger = Logger(this.getClass) - implicit val context: List[(String, String)] = (CORRELATION_ID_HEADER -> UUID.randomUUID().toString()) :: Nil - - implicit val actorSystem: ActorSystem[Nothing] = - ActorSystem[Nothing](Behaviors.empty, "interop-be-authorization-process-alignment") - implicit val executionContext: ExecutionContext = actorSystem.executionContext - - implicit val es: ExecutorService = Executors.newFixedThreadPool(1.max(Runtime.getRuntime.availableProcessors() - 1)) - implicit val blockingEc = ExecutionContext.fromExecutor(es) - implicit val authorizationManagementService: AuthorizationManagementService = authorizationManagementService( - blockingEc + ActorSystem[Nothing]( + Behaviors.setup[Nothing] { context => + implicit val actorSystem: ActorSystem[_] = context.system + implicit val executionContext: ExecutionContext = actorSystem.executionContext + val selector: DispatcherSelector = DispatcherSelector.fromConfig("futures-dispatcher") + val blockingEc: ExecutionContextExecutor = actorSystem.dispatchers.lookup(selector) + + AkkaManagement.get(actorSystem.classicSystem).start() + + logger.info(renderBuildInfo(BuildInfo)) + + val serverBinding = for { + jwtReader <- getJwtValidator() + controller = new Controller( + clientApi(jwtReader, blockingEc), + healthApi, + operatorApi(jwtReader, blockingEc), + validationExceptionToRoute.some + )(actorSystem.classicSystem) + binding <- Http()(actorSystem.classicSystem) + .newServerAt("0.0.0.0", ApplicationConfiguration.serverPort) + .bind(corsHandler(controller.routes)) + } yield binding + + serverBinding.onComplete { + case Success(b) => + logger.info(s"Started server at ${b.localAddress.getHostString()}:${b.localAddress.getPort()}") + case Failure(e) => + actorSystem.terminate() + logger.error("Startup error: ", e) + } + + Behaviors.empty[Nothing] + }, + BuildInfo.name ) - implicit val partyManagementService: PartyManagementService = partyManagementService() - - logger.info("Starting update") - logger.info(s"Retrieving clients") - Await.result( - execution() - .andThen { case Failure(ex) => logger.error("Houston we have a problem", ex) } - .andThen { _ => - es.shutdown() - }, - Duration.Inf - ): Unit - - logger.info("Completed update") - - def execution(): Future[Unit] = for { - clients <- getClients() - _ = logger.info(s"Start update clients ${clients.size}") - _ <- clients.traverse(updateClient) - _ = logger.info(s"End update clients") - _ = logger.info(s"Retrieving keys") - keys <- clients.traverse(getClientKeys).map(_.flatten) - _ = logger.info(s"Start update keys ${keys.size}") - _ <- keys - .flatMap(client => - client.keys.collect { case PersistentKey(Some(relationshipId), None, kid, _, _, _, _, _) => - Parameter(client.id, kid, relationshipId) - } - ) - .traverse(keys => updateKeys(keys)) - _ = logger.info(s"End update keys") - } yield () - - def updateClient(client: PersistentClient): Future[Unit] = { - logger.info(s"Update client ${client.id}") - for { - - relationship <- client.relationships.toList.traverse(partyManagementService.getRelationshipById) - _ <- relationship.traverse(rel => { - if (client.users.exists(_ == rel.from)) Future.unit - else authorizationManagementService.addUser(client.id, rel.from) - }) - } yield () - } - - def getClients(): Future[Seq[PersistentClient]] = - getAll(50)(readModelService.find[PersistentClient]("clients", Filters.empty(), _, _)) - - def getClientKeys(client: PersistentClient): Future[Option[ReadModelClientWithKeys]] = - readModelService.findOne[ReadModelClientWithKeys]("clients", Filters.eq("data.id", client.id.toString)) - - def updateKeys(key: Parameter): Future[Unit] = { - logger.info(s"Update keys for client ${key.clientId}") - for { - relationship <- partyManagementService.getRelationshipById(key.relationShipId) - _ <- authorizationManagementService.migrateKeyRelationshipToUser(key.clientId, key.kid, relationship.from) - } yield () - } - - def getAll[T](limit: Int)(get: (Int, Int) => Future[Seq[T]]): Future[Seq[T]] = { - def go(offset: Int)(acc: Seq[T]): Future[Seq[T]] = { - get(offset, limit).flatMap(xs => - if (xs.size < limit) Future.successful(xs ++ acc) - else go(offset + xs.size)(xs ++ acc) - ) - } - go(0)(Nil) - } - - final case class Parameter(clientId: UUID, kid: String, relationShipId: UUID) } diff --git a/src/main/scala/it/pagopa/interop/authorizationprocess/service/AuthorizationManagementService.scala b/src/main/scala/it/pagopa/interop/authorizationprocess/service/AuthorizationManagementService.scala index d27a193a..f733619b 100644 --- a/src/main/scala/it/pagopa/interop/authorizationprocess/service/AuthorizationManagementService.scala +++ b/src/main/scala/it/pagopa/interop/authorizationprocess/service/AuthorizationManagementService.scala @@ -26,12 +26,6 @@ trait AuthorizationManagementService { def deleteClient(clientId: UUID)(implicit contexts: Seq[(String, String)]): Future[Unit] - def addUser(clientId: UUID, userId: UUID)(implicit contexts: Seq[(String, String)]): Future[ManagementClient] - - def migrateKeyRelationshipToUser(clientId: UUID, keyId: String, userId: UUID)(implicit - contexts: Seq[(String, String)] - ): Future[Unit] - def addRelationship(clientId: UUID, relationshipId: UUID)(implicit contexts: Seq[(String, String)] ): Future[ManagementClient] diff --git a/src/main/scala/it/pagopa/interop/authorizationprocess/service/impl/AuthorizationManagementServiceImpl.scala b/src/main/scala/it/pagopa/interop/authorizationprocess/service/impl/AuthorizationManagementServiceImpl.scala index fc2619c5..d8e5909a 100644 --- a/src/main/scala/it/pagopa/interop/authorizationprocess/service/impl/AuthorizationManagementServiceImpl.scala +++ b/src/main/scala/it/pagopa/interop/authorizationprocess/service/impl/AuthorizationManagementServiceImpl.scala @@ -1,7 +1,7 @@ package it.pagopa.interop.authorizationprocess.service.impl import com.typesafe.scalalogging.{Logger, LoggerTakingImplicit} -import it.pagopa.interop.authorizationmanagement.client.api.{ClientApi, KeyApi, PurposeApi, MigrateApi} +import it.pagopa.interop.authorizationmanagement.client.api.{ClientApi, KeyApi, PurposeApi} import it.pagopa.interop.authorizationmanagement.client.invoker.{ApiError, ApiRequest, BearerToken} import it.pagopa.interop.authorizationmanagement.client.model._ import it.pagopa.interop.authorizationmanagement.model.client.{PersistentClient, PersistentClientKind} @@ -25,12 +25,11 @@ final case class AuthorizationManagementServiceImpl( invoker: AuthorizationManagementInvoker, clientApi: ClientApi, keyApi: KeyApi, - migrateApi: MigrateApi, purposeApi: PurposeApi )(implicit ec: ExecutionContext) extends AuthorizationManagementService { - implicit val logger: LoggerTakingImplicit[ContextFieldsToLog] = + implicit val logger: LoggerTakingImplicit[ContextFieldsToLog] = Logger.takingImplicit[ContextFieldsToLog](this.getClass) override def createClient( consumerId: UUID, @@ -48,7 +47,7 @@ final case class AuthorizationManagementServiceImpl( description = description, kind = kind, createdAt = createdAt, - users = members + members = members ) )(BearerToken(bearerToken)) invoker.invoke(request, "Client creation") @@ -57,7 +56,7 @@ final case class AuthorizationManagementServiceImpl( clientId: UUID )(implicit ec: ExecutionContext, readModel: ReadModelService): Future[PersistentClient] = ReadModelAuthorizationQueries.getClientById(clientId).flatMap(_.toFuture(ClientNotFound(clientId))) - override def deleteClient(clientId: UUID)(implicit contexts: Seq[(String, String)]): Future[Unit] = + override def deleteClient(clientId: UUID)(implicit contexts: Seq[(String, String)]): Future[Unit] = withHeaders[Unit] { (bearerToken, correlationId) => val request: ApiRequest[Unit] = clientApi.deleteClient(xCorrelationId = correlationId, clientId.toString)(BearerToken(bearerToken)) @@ -67,35 +66,22 @@ final case class AuthorizationManagementServiceImpl( case err: ApiError[_] if err.code == 404 => Future.failed(ClientNotFound(clientId)) } } - - override def migrateKeyRelationshipToUser(clientId: UUID, keyId: String, userId: UUID)(implicit - contexts: Seq[(String, String)] - ): Future[Unit] = withHeaders[Unit] { (bearerToken, correlationId) => - val request: ApiRequest[Unit] = - migrateApi.migrateKeyRelationshipToUser(xCorrelationId = correlationId, clientId, keyId, UserSeed(userId))( - BearerToken(bearerToken) - ) - invoker.invoke(request, "Key user migration") - } - - override def addUser(clientId: UUID, userId: UUID)(implicit contexts: Seq[(String, String)]): Future[Client] = - withHeaders[Client] { (bearerToken, correlationId) => - val request: ApiRequest[Client] = - clientApi.addUser(xCorrelationId = correlationId, clientId, UserSeed(userId))(BearerToken(bearerToken)) - invoker.invoke(request, "Operator addition to client") - } override def addRelationship(clientId: UUID, relationshipId: UUID)(implicit contexts: Seq[(String, String)] ): Future[Client] = withHeaders[Client] { (bearerToken, correlationId) => val request: ApiRequest[Client] = - clientApi.addUser(xCorrelationId = correlationId, clientId, UserSeed(relationshipId))(BearerToken(bearerToken)) + clientApi.addRelationship(xCorrelationId = correlationId, clientId, PartyRelationshipSeed(relationshipId))( + BearerToken(bearerToken) + ) invoker.invoke(request, "Operator addition to client") } override def removeClientRelationship(clientId: UUID, relationshipId: UUID)(implicit contexts: Seq[(String, String)] ): Future[Unit] = withHeaders[Unit] { (bearerToken, correlationId) => val request: ApiRequest[Unit] = - clientApi.removeClientUser(xCorrelationId = correlationId, clientId, relationshipId)(BearerToken(bearerToken)) + clientApi.removeClientRelationship(xCorrelationId = correlationId, clientId, relationshipId)( + BearerToken(bearerToken) + ) invoker .invoke(request, "Operator removal from client") .recoverWith { @@ -111,7 +97,7 @@ final case class AuthorizationManagementServiceImpl( .flatMap(_.map(_.keys).toFuture(ClientKeyNotFound(clientId, kid))) key <- keys.find(_.kid == kid).toFuture(ClientKeyNotFound(clientId, kid)) } yield key - override def deleteKey(clientId: UUID, kid: String)(implicit contexts: Seq[(String, String)]): Future[Unit] = + override def deleteKey(clientId: UUID, kid: String)(implicit contexts: Seq[(String, String)]): Future[Unit] = withHeaders[Unit] { (bearerToken, correlationId) => val request: ApiRequest[Unit] = keyApi.deleteClientKeyById(xCorrelationId = correlationId, clientId, kid)(BearerToken(bearerToken)) diff --git a/src/test/scala/it/pagopa/interop/authorizationprocess/ClientOperationSpec.scala b/src/test/scala/it/pagopa/interop/authorizationprocess/ClientOperationSpec.scala index 8ad48a5f..c108f107 100644 --- a/src/test/scala/it/pagopa/interop/authorizationprocess/ClientOperationSpec.scala +++ b/src/test/scala/it/pagopa/interop/authorizationprocess/ClientOperationSpec.scala @@ -3,7 +3,7 @@ package it.pagopa.interop.authorizationprocess import akka.http.scaladsl.model.StatusCodes import akka.http.scaladsl.testkit.ScalatestRouteTest import it.pagopa.interop.authorizationmanagement -import it.pagopa.interop.authorizationmanagement.client.api.{ClientApi, KeyApi, PurposeApi, MigrateApi} +import it.pagopa.interop.authorizationmanagement.client.api.{ClientApi, KeyApi, PurposeApi} import it.pagopa.interop.authorizationmanagement.model.client.{Api, PersistentClient, PersistentClientKind} import it.pagopa.interop.authorizationprocess.api.impl.ClientApiServiceImpl import it.pagopa.interop.authorizationprocess.common.readmodel.PaginatedResult @@ -88,7 +88,6 @@ class ClientOperationSpec extends AnyWordSpecLike with MockFactory with SpecUtil AuthorizationManagementInvoker(ExecutionContext.global), ClientApi(), KeyApi(), - MigrateApi(), PurposeApi() ), mockAgreementManagementService, @@ -187,7 +186,6 @@ class ClientOperationSpec extends AnyWordSpecLike with MockFactory with SpecUtil purposes = Seq.empty, description = None, relationships = Set.empty, - users = Set.empty, kind = Api, createdAt = timestamp ) @@ -242,7 +240,6 @@ class ClientOperationSpec extends AnyWordSpecLike with MockFactory with SpecUtil purposes = Seq.empty, description = None, relationships = Set.empty, - users = Set.empty, kind = Api, createdAt = timestamp ) diff --git a/src/test/scala/it/pagopa/interop/authorizationprocess/KeyOperationSpec.scala b/src/test/scala/it/pagopa/interop/authorizationprocess/KeyOperationSpec.scala index 9b69bf6e..cc1e54fa 100644 --- a/src/test/scala/it/pagopa/interop/authorizationprocess/KeyOperationSpec.scala +++ b/src/test/scala/it/pagopa/interop/authorizationprocess/KeyOperationSpec.scala @@ -2,7 +2,7 @@ package it.pagopa.interop.authorizationprocess import akka.http.scaladsl.model.StatusCodes import akka.http.scaladsl.testkit.ScalatestRouteTest -import it.pagopa.interop.authorizationmanagement.client.api.{ClientApi, KeyApi, PurposeApi, MigrateApi} +import it.pagopa.interop.authorizationmanagement.client.api.{ClientApi, KeyApi, PurposeApi} import it.pagopa.interop.authorizationmanagement.client.{model => AuthorizationManagementDependency} import it.pagopa.interop.authorizationprocess.api.impl.ClientApiMarshallerImpl._ import it.pagopa.interop.authorizationprocess.api.impl.{ClientApiServiceImpl, keyFormat, keysFormat} @@ -66,7 +66,6 @@ class KeyOperationSpec AuthorizationManagementInvoker(ExecutionContext.global), ClientApi(), KeyApi(), - MigrateApi(), PurposeApi() ), mockAgreementManagementService, @@ -115,13 +114,13 @@ class KeyOperationSpec .getClientKeys(_: UUID)(_: ExecutionContext, _: ReadModelService)) .expects(persistentClient.id, *, *) .once() - .returns(Future.successful(Seq(persistentKey.copy(relationshipId = Some(relationship.id))))) + .returns(Future.successful(Seq(persistentKey.copy(relationshipId = relationship.id)))) val relationshipIds = relationship.id.toString Get() ~> service.getClientKeys(relationshipIds, persistentClient.id.toString) ~> check { status shouldEqual StatusCodes.OK - // entityAs[Keys] should haveTheSameKeys(Keys(Seq(expectedKey))) + entityAs[Keys] should haveTheSameKeys(Keys(Seq(expectedKey))) } } @@ -133,7 +132,6 @@ class KeyOperationSpec AuthorizationManagementInvoker(ExecutionContext.global), ClientApi(), KeyApi(), - MigrateApi(), PurposeApi() ), mockAgreementManagementService, @@ -221,7 +219,6 @@ class KeyOperationSpec AuthorizationManagementInvoker(ExecutionContext.global), ClientApi(), KeyApi(), - MigrateApi(), PurposeApi() ), mockAgreementManagementService, diff --git a/src/test/scala/it/pagopa/interop/authorizationprocess/OperatorOperationSpec.scala b/src/test/scala/it/pagopa/interop/authorizationprocess/OperatorOperationSpec.scala index 9a43a794..af2696f5 100644 --- a/src/test/scala/it/pagopa/interop/authorizationprocess/OperatorOperationSpec.scala +++ b/src/test/scala/it/pagopa/interop/authorizationprocess/OperatorOperationSpec.scala @@ -2,7 +2,7 @@ package it.pagopa.interop.authorizationprocess import akka.http.scaladsl.model.StatusCodes import akka.http.scaladsl.testkit.ScalatestRouteTest -import it.pagopa.interop.authorizationmanagement.client.api.{ClientApi, KeyApi, PurposeApi, MigrateApi} +import it.pagopa.interop.authorizationmanagement.client.api.{ClientApi, KeyApi, PurposeApi} import it.pagopa.interop.authorizationprocess.api.impl.{ClientApiServiceImpl, OperatorApiServiceImpl} import it.pagopa.interop.authorizationprocess.api.impl.ClientApiMarshallerImpl._ import it.pagopa.interop.authorizationprocess.error.AuthorizationProcessErrors.ClientNotFound @@ -74,7 +74,7 @@ class OperatorOperationSpec .addRelationship(_: UUID, _: UUID)(_: Seq[(String, String)])) .expects(persistentClient.id, relationship.id, *) .once() - .returns(Future.successful(client.copy(id = persistentClient.id, users = Set(relationship.id)))) + .returns(Future.successful(client.copy(id = persistentClient.id, relationships = Set(relationship.id)))) val expected = Client( id = persistentClient.id, @@ -101,7 +101,6 @@ class OperatorOperationSpec AuthorizationManagementInvoker(ExecutionContext.global), ClientApi(), KeyApi(), - MigrateApi(), PurposeApi() ), mockAgreementManagementService, @@ -352,7 +351,6 @@ class OperatorOperationSpec AuthorizationManagementInvoker(ExecutionContext.global), ClientApi(), KeyApi(), - MigrateApi(), PurposeApi() ), mockAgreementManagementService, diff --git a/src/test/scala/it/pagopa/interop/authorizationprocess/util/FakeDependencies.scala b/src/test/scala/it/pagopa/interop/authorizationprocess/util/FakeDependencies.scala index 0ae18da0..4039a8a1 100644 --- a/src/test/scala/it/pagopa/interop/authorizationprocess/util/FakeDependencies.scala +++ b/src/test/scala/it/pagopa/interop/authorizationprocess/util/FakeDependencies.scala @@ -63,7 +63,7 @@ object FakeDependencies { consumerId = UUID.randomUUID(), name = "fake", purposes = Seq.empty, - users = Set.empty, + relationships = Set.empty, kind = ClientKind.API, createdAt = createdAt ) @@ -80,7 +80,6 @@ object FakeDependencies { description = Some("description"), purposes = Seq.empty, relationships = Set.empty, - users = Set.empty, kind = Api, createdAt = OffsetDateTimeSupplier.get() ) @@ -97,7 +96,7 @@ object FakeDependencies { consumerId = UUID.randomUUID(), name = "fake", purposes = Seq.empty, - users = Set.empty, + relationships = Set.empty, kind = ClientKind.API, createdAt = OffsetDateTimeSupplier.get() ) @@ -113,8 +112,7 @@ object FakeDependencies { ): Future[PersistentKey] = Future.successful( PersistentKey( - relationshipId = UUID.randomUUID().some, - userId = UUID.randomUUID().some, + relationshipId = UUID.randomUUID(), kid = "fake", name = "fake", encodedPem = "pem", @@ -194,24 +192,6 @@ object FakeDependencies { limit: Int )(implicit ec: ExecutionContext, readModel: ReadModelService): Future[PaginatedResult[ReadModelClientWithKeys]] = Future.successful(PaginatedResult(results = Seq.empty, totalCount = 0)) - - override def migrateKeyRelationshipToUser(clientId: UUID, keyId: String, userId: UUID)(implicit - contexts: Seq[(String, String)] - ): Future[Unit] = Future.unit - - override def addUser(clientId: UUID, userId: UUID)(implicit - contexts: Seq[(String, String)] - ): Future[ManagementClient] = Future.successful( - Client( - id = UUID.randomUUID(), - consumerId = UUID.randomUUID(), - name = "fake", - purposes = Seq.empty, - users = Set(userId), - kind = ClientKind.API, - createdAt = OffsetDateTimeSupplier.get() - ) - ) } class FakePartyManagementService extends PartyManagementService { diff --git a/src/test/scala/it/pagopa/interop/authorizationprocess/util/SpecUtils.scala b/src/test/scala/it/pagopa/interop/authorizationprocess/util/SpecUtils.scala index e12e5f93..84a122f4 100644 --- a/src/test/scala/it/pagopa/interop/authorizationprocess/util/SpecUtils.scala +++ b/src/test/scala/it/pagopa/interop/authorizationprocess/util/SpecUtils.scala @@ -259,7 +259,6 @@ trait SpecUtils extends SprayJsonSupport { self: MockFactory => purposes = Seq(persistentClientPurpose), description = clientSeed.description, relationships = Set.empty, - users = Set.empty, kind = AuthorizationPersistentModel.Consumer, createdAt = timestamp ) @@ -270,8 +269,7 @@ trait SpecUtils extends SprayJsonSupport { self: MockFactory => "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUF2RElqdVN4UkJtaEdudjE5MUlSTQpnbEVXblRFMXdmOXdMRzdwQStxQlBjckVyM3dQQWJoTzJab1ZpVFNLS1crWGlZbW15cS8zaVlkYlhXNVNLc1NqCnNEN1NWTEhzZ0YzWU85MjZpV0tLTGVWdThhOEdEcUx1K1ZrQjlDNGMxUWZLajJRRG1rNTN1OGlKOU12Mi84c28KVzY2VXM2NVM0TTlzc1Jka0ZzMUoxVWhQSVgxT1I3UjlBSnZKWFN2ZmtMekhvOHdveTVkM3JZdmJNMzErWk0wbwplL0tQdUdCVWRnRitreXNLZVE3eVgxM3NFK1NCaVZaRkJFYzdzd0xXRDIxeEZJSVlpWHdWTEFteC9lajBMMFNTCkVSUEsvSVpmRlN6UW92bE5vNVhsR3BGcStTWk5ZdlVyWTBRRndtK3M0UnN5R3lUOTJnWHBmaVpJeHZMMUI1TmgKZndJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0t", algorithm = "RS256", use = AuthorizationPersistentKeyModel.Sig, - userId = UUID.randomUUID().some, - relationshipId = UUID.randomUUID().some, + relationshipId = UUID.randomUUID(), name = "test", createdAt = timestamp ) @@ -282,7 +280,7 @@ trait SpecUtils extends SprayJsonSupport { self: MockFactory => name = clientSeed.name, purposes = Seq(clientPurpose), description = clientSeed.description, - users = Set.empty, + relationships = Set.empty, kind = AuthorizationManagementDependency.ClientKind.CONSUMER, createdAt = timestamp ) @@ -356,7 +354,7 @@ trait SpecUtils extends SprayJsonSupport { self: MockFactory => PartyManagementDependency.Relationships(Seq(relationship)) val createdKey: AuthorizationManagementDependency.Key = AuthorizationManagementDependency.Key( - userId = persistentKey.userId.getOrElse(UUID.randomUUID()), + relationshipId = persistentKey.relationshipId, kid = persistentKey.kid, name = persistentKey.name, encodedPem = persistentKey.encodedPem, @@ -366,7 +364,7 @@ trait SpecUtils extends SprayJsonSupport { self: MockFactory => ) val expectedKey: Key = Key( - relationshipId = relationship.id.some, + relationshipId = relationship.id, kid = persistentKey.kid, name = persistentKey.name, encodedPem = persistentKey.encodedPem,