From 60f3ae5be758ef6c80fadb0abda52a89134912b8 Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 27 Nov 2024 10:37:52 +0100 Subject: [PATCH] Remove the automatic project provisioning implemented for the MOOC (#5255) * Remove the automatic project provisioning implemented for the MOOC --------- Co-authored-by: Simon Dumas --- delta/app/src/main/resources/app.conf | 24 ---- .../nexus/delta/config/AppConfig.scala | 2 - .../nexus/delta/routes/IdentitiesRoutes.scala | 20 +-- .../nexus/delta/wiring/IdentitiesModule.scala | 6 +- .../nexus/delta/wiring/ProjectsModule.scala | 13 +- .../delta/routes/IdentitiesRoutesSpec.scala | 16 +-- .../AutomaticProvisioningConfig.scala | 76 ---------- .../provisioning/ProjectProvisioning.scala | 134 ------------------ .../ProjectProvisioningSpec.scala | 119 ---------------- docs/src/main/paradox/docs/releases/index.md | 3 +- .../docs/releases/v1.6-release-notes.md | 2 - .../docs/running-nexus/configuration/index.md | 15 -- tests/docker/config/delta-postgres.conf | 11 -- .../nexus/tests/iam/IdentitiesSpec.scala | 40 ++---- 14 files changed, 21 insertions(+), 460 deletions(-) delete mode 100644 delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/provisioning/AutomaticProvisioningConfig.scala delete mode 100644 delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/provisioning/ProjectProvisioning.scala delete mode 100644 delta/sdk/src/test/scala/ch/epfl/bluebrain/nexus/delta/sdk/provisioning/ProjectProvisioningSpec.scala diff --git a/delta/app/src/main/resources/app.conf b/delta/app/src/main/resources/app.conf index 0273c5827e..b94e87d930 100644 --- a/delta/app/src/main/resources/app.conf +++ b/delta/app/src/main/resources/app.conf @@ -277,30 +277,6 @@ app { } } - # Configuration for the automatic provisioning of projects. - automatic-provisioning { - enabled = false - # The list of permissions to apply to the owner on the generated project - permissions = ["resources/read", "resources/write", "projects/read"] - description = "Auto provisioned project" - - # configuration of realms for automatic provisioning - # mapping between realm label and organization in which to provision the project - enabled-realms { - realm = "users-realm" - } - api-mappings { - # example API mapping - # documents = "https://bluebrain.github.io/nexus/vocabulary/defaultElasticSearchIndex" - } - # vocab setting for the project - # vocab = "http://example.com/vocab" - # base setting for the project - # base = "http://example.com/base" - # to ban unconstrained resources in a project - enforce-schema = false - } - # Quotas for projects quotas { # flag to enable or disable project quotas diff --git a/delta/app/src/main/scala/ch/epfl/bluebrain/nexus/delta/config/AppConfig.scala b/delta/app/src/main/scala/ch/epfl/bluebrain/nexus/delta/config/AppConfig.scala index e930828e8d..988f200a41 100644 --- a/delta/app/src/main/scala/ch/epfl/bluebrain/nexus/delta/config/AppConfig.scala +++ b/delta/app/src/main/scala/ch/epfl/bluebrain/nexus/delta/config/AppConfig.scala @@ -12,7 +12,6 @@ import ch.epfl.bluebrain.nexus.delta.sdk.model.ServiceAccountConfig import ch.epfl.bluebrain.nexus.delta.sdk.organizations.OrganizationsConfig import ch.epfl.bluebrain.nexus.delta.sdk.permissions.PermissionsConfig import ch.epfl.bluebrain.nexus.delta.sdk.projects.ProjectsConfig -import ch.epfl.bluebrain.nexus.delta.sdk.provisioning.AutomaticProvisioningConfig import ch.epfl.bluebrain.nexus.delta.sdk.quotas.QuotasConfig import ch.epfl.bluebrain.nexus.delta.sdk.realms.RealmsConfig import ch.epfl.bluebrain.nexus.delta.sdk.resolvers.ResolversConfig @@ -43,7 +42,6 @@ final case class AppConfig( organizations: OrganizationsConfig, acls: AclsConfig, projects: ProjectsConfig, - automaticProvisioning: AutomaticProvisioningConfig, quotas: QuotasConfig, resolvers: ResolversConfig, resources: ResourcesConfig, diff --git a/delta/app/src/main/scala/ch/epfl/bluebrain/nexus/delta/routes/IdentitiesRoutes.scala b/delta/app/src/main/scala/ch/epfl/bluebrain/nexus/delta/routes/IdentitiesRoutes.scala index 918f36c35f..1ce2eec7c6 100644 --- a/delta/app/src/main/scala/ch/epfl/bluebrain/nexus/delta/routes/IdentitiesRoutes.scala +++ b/delta/app/src/main/scala/ch/epfl/bluebrain/nexus/delta/routes/IdentitiesRoutes.scala @@ -1,39 +1,30 @@ package ch.epfl.bluebrain.nexus.delta.routes -import akka.http.scaladsl.server.{Directive0, Route} +import akka.http.scaladsl.server.Route import cats.effect.IO -import cats.effect.unsafe.implicits._ import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.context.RemoteContextResolution import ch.epfl.bluebrain.nexus.delta.rdf.utils.JsonKeyOrdering import ch.epfl.bluebrain.nexus.delta.sdk.acls.AclCheck import ch.epfl.bluebrain.nexus.delta.sdk.directives.AuthDirectives import ch.epfl.bluebrain.nexus.delta.sdk.directives.DeltaDirectives._ import ch.epfl.bluebrain.nexus.delta.sdk.identities.Identities -import ch.epfl.bluebrain.nexus.delta.sdk.identities.model.Caller import ch.epfl.bluebrain.nexus.delta.sdk.identities.model.Caller._ import ch.epfl.bluebrain.nexus.delta.sdk.model.BaseUri -import ch.epfl.bluebrain.nexus.delta.sdk.provisioning.ProjectProvisioning /** * The identities routes */ -class IdentitiesRoutes(identities: Identities, aclCheck: AclCheck, projectProvisioning: ProjectProvisioning)(implicit +class IdentitiesRoutes(identities: Identities, aclCheck: AclCheck)(implicit baseUri: BaseUri, cr: RemoteContextResolution, ordering: JsonKeyOrdering ) extends AuthDirectives(identities, aclCheck) { - private def provisionProject(implicit caller: Caller): Directive0 = onSuccess( - projectProvisioning(caller.subject).unsafeToFuture() - ) - def routes: Route = { baseUriPrefix(baseUri.prefix) { (pathPrefix("identities") & pathEndOrSingleSlash) { (extractCaller & get) { implicit caller => - provisionProject.apply { - emit(IO.pure(caller)) - } + emit(IO.pure(caller)) } } } @@ -48,8 +39,7 @@ object IdentitiesRoutes { */ def apply( identities: Identities, - aclCheck: AclCheck, - projectProvisioning: ProjectProvisioning + aclCheck: AclCheck )(implicit baseUri: BaseUri, cr: RemoteContextResolution, ordering: JsonKeyOrdering): Route = - new IdentitiesRoutes(identities, aclCheck, projectProvisioning).routes + new IdentitiesRoutes(identities, aclCheck).routes } diff --git a/delta/app/src/main/scala/ch/epfl/bluebrain/nexus/delta/wiring/IdentitiesModule.scala b/delta/app/src/main/scala/ch/epfl/bluebrain/nexus/delta/wiring/IdentitiesModule.scala index ca34895d1a..4dd70a6b78 100644 --- a/delta/app/src/main/scala/ch/epfl/bluebrain/nexus/delta/wiring/IdentitiesModule.scala +++ b/delta/app/src/main/scala/ch/epfl/bluebrain/nexus/delta/wiring/IdentitiesModule.scala @@ -4,6 +4,7 @@ import cats.effect.{Clock, IO} import ch.epfl.bluebrain.nexus.delta.Main.pluginsMaxPriority import ch.epfl.bluebrain.nexus.delta.config.AppConfig import ch.epfl.bluebrain.nexus.delta.kernel.cache.CacheConfig +import ch.epfl.bluebrain.nexus.delta.kernel.http.HttpClient import ch.epfl.bluebrain.nexus.delta.kernel.utils.ClasspathResourceLoader import ch.epfl.bluebrain.nexus.delta.rdf.Vocabulary.contexts import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.context.{ContextValue, RemoteContextResolution} @@ -12,10 +13,8 @@ import ch.epfl.bluebrain.nexus.delta.routes.IdentitiesRoutes import ch.epfl.bluebrain.nexus.delta.sdk.PriorityRoute import ch.epfl.bluebrain.nexus.delta.sdk.acls.AclCheck import ch.epfl.bluebrain.nexus.delta.sdk.auth.{AuthTokenProvider, OpenIdAuthService} -import ch.epfl.bluebrain.nexus.delta.kernel.http.HttpClient import ch.epfl.bluebrain.nexus.delta.sdk.identities.{Identities, IdentitiesImpl} import ch.epfl.bluebrain.nexus.delta.sdk.model.BaseUri -import ch.epfl.bluebrain.nexus.delta.sdk.provisioning.ProjectProvisioning import ch.epfl.bluebrain.nexus.delta.sdk.realms.Realms import izumi.distage.model.definition.{Id, ModuleDef} @@ -49,11 +48,10 @@ object IdentitiesModule extends ModuleDef { ( identities: Identities, aclCheck: AclCheck, - projectProvisioning: ProjectProvisioning, baseUri: BaseUri, cr: RemoteContextResolution @Id("aggregate"), ordering: JsonKeyOrdering - ) => new IdentitiesRoutes(identities, aclCheck, projectProvisioning)(baseUri, cr, ordering) + ) => new IdentitiesRoutes(identities, aclCheck)(baseUri, cr, ordering) } diff --git a/delta/app/src/main/scala/ch/epfl/bluebrain/nexus/delta/wiring/ProjectsModule.scala b/delta/app/src/main/scala/ch/epfl/bluebrain/nexus/delta/wiring/ProjectsModule.scala index e641615fb5..1913f3c764 100644 --- a/delta/app/src/main/scala/ch/epfl/bluebrain/nexus/delta/wiring/ProjectsModule.scala +++ b/delta/app/src/main/scala/ch/epfl/bluebrain/nexus/delta/wiring/ProjectsModule.scala @@ -9,7 +9,7 @@ import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.context.{ContextValue, RemoteCon import ch.epfl.bluebrain.nexus.delta.rdf.utils.JsonKeyOrdering import ch.epfl.bluebrain.nexus.delta.routes.ProjectsRoutes import ch.epfl.bluebrain.nexus.delta.sdk._ -import ch.epfl.bluebrain.nexus.delta.sdk.acls.{AclCheck, Acls} +import ch.epfl.bluebrain.nexus.delta.sdk.acls.AclCheck import ch.epfl.bluebrain.nexus.delta.sdk.deletion.{ProjectDeletionCoordinator, ProjectDeletionTask} import ch.epfl.bluebrain.nexus.delta.sdk.directives.DeltaSchemeDirectives import ch.epfl.bluebrain.nexus.delta.sdk.fusion.FusionConfig @@ -21,7 +21,6 @@ import ch.epfl.bluebrain.nexus.delta.sdk.organizations.FetchActiveOrganization import ch.epfl.bluebrain.nexus.delta.sdk.projects._ import ch.epfl.bluebrain.nexus.delta.sdk.projects.job.ProjectHealthJob import ch.epfl.bluebrain.nexus.delta.sdk.projects.model._ -import ch.epfl.bluebrain.nexus.delta.sdk.provisioning.ProjectProvisioning import ch.epfl.bluebrain.nexus.delta.sdk.quotas.Quotas import ch.epfl.bluebrain.nexus.delta.sdk.sse.SseEncoder import ch.epfl.bluebrain.nexus.delta.sourcing.Transactors @@ -85,16 +84,6 @@ object ProjectsModule extends ModuleDef { ProjectsStatistics(xas) } - make[ProjectProvisioning].from { - ( - acls: Acls, - projects: Projects, - config: AppConfig, - serviceAccount: ServiceAccount - ) => - ProjectProvisioning(acls, projects, config.automaticProvisioning, serviceAccount) - } - make[FetchContext].from { (mappings: ApiMappingsCollection, xas: Transactors, quotas: Quotas) => FetchContext(mappings.merge, xas, quotas) } diff --git a/delta/app/src/test/scala/ch/epfl/bluebrain/nexus/delta/routes/IdentitiesRoutesSpec.scala b/delta/app/src/test/scala/ch/epfl/bluebrain/nexus/delta/routes/IdentitiesRoutesSpec.scala index c24f9d1bdc..56e57c2ab8 100644 --- a/delta/app/src/test/scala/ch/epfl/bluebrain/nexus/delta/routes/IdentitiesRoutesSpec.scala +++ b/delta/app/src/test/scala/ch/epfl/bluebrain/nexus/delta/routes/IdentitiesRoutesSpec.scala @@ -5,15 +5,12 @@ import akka.http.scaladsl.model.StatusCodes import akka.http.scaladsl.model.headers.{Accept, BasicHttpCredentials, OAuth2BearerToken} import akka.http.scaladsl.server.Directives.handleExceptions import akka.http.scaladsl.server.Route -import cats.effect.{IO, Ref} import ch.epfl.bluebrain.nexus.delta.sdk.acls.AclSimpleCheck import ch.epfl.bluebrain.nexus.delta.sdk.identities.IdentitiesDummy import ch.epfl.bluebrain.nexus.delta.sdk.identities.model.Caller import ch.epfl.bluebrain.nexus.delta.sdk.marshalling.RdfExceptionHandler -import ch.epfl.bluebrain.nexus.delta.sdk.provisioning.ProjectProvisioning import ch.epfl.bluebrain.nexus.delta.sdk.utils.BaseRouteSpec -import ch.epfl.bluebrain.nexus.delta.sourcing.model.Identity -import ch.epfl.bluebrain.nexus.delta.sourcing.model.Identity.{Anonymous, Authenticated, Group, Subject} +import ch.epfl.bluebrain.nexus.delta.sourcing.model.Identity.{Anonymous, Authenticated, Group} class IdentitiesRoutesSpec extends BaseRouteSpec { @@ -23,14 +20,9 @@ class IdentitiesRoutesSpec extends BaseRouteSpec { private val aclCheck = AclSimpleCheck().accepted - private val refSubjects = Ref.unsafe[IO, Set[Subject]](Set.empty[Subject]) - - private val projectProvisioning: ProjectProvisioning = - (subject: Identity.Subject) => refSubjects.update(_ + subject) - private val route = Route.seal( handleExceptions(RdfExceptionHandler.apply) { - IdentitiesRoutes(identities, aclCheck, projectProvisioning) + IdentitiesRoutes(identities, aclCheck) } ) @@ -38,14 +30,12 @@ class IdentitiesRoutesSpec extends BaseRouteSpec { "return forbidden" in { Get("/v1/identities") ~> addCredentials(OAuth2BearerToken("unknown")) ~> route ~> check { status shouldEqual StatusCodes.Unauthorized - refSubjects.get.accepted shouldBe empty } } "return unauthorized" in { Get("/v1/identities") ~> addCredentials(BasicHttpCredentials("fail")) ~> route ~> check { status shouldEqual StatusCodes.Unauthorized - refSubjects.get.accepted shouldBe empty } } @@ -53,7 +43,6 @@ class IdentitiesRoutesSpec extends BaseRouteSpec { Get("/v1/identities") ~> Accept(`*/*`) ~> route ~> check { status shouldEqual StatusCodes.OK response.asJson should equalIgnoreArrayOrder(jsonContentOf("identities/anonymous.json")) - refSubjects.get.accepted should contain(Anonymous) } } @@ -61,7 +50,6 @@ class IdentitiesRoutesSpec extends BaseRouteSpec { Get("/v1/identities") ~> Accept(`*/*`) ~> addCredentials(OAuth2BearerToken("alice")) ~> route ~> check { status shouldEqual StatusCodes.OK response.asJson should equalIgnoreArrayOrder(jsonContentOf("identities/alice.json")) - refSubjects.get.accepted should contain(alice) } } } diff --git a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/provisioning/AutomaticProvisioningConfig.scala b/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/provisioning/AutomaticProvisioningConfig.scala deleted file mode 100644 index 03730a5059..0000000000 --- a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/provisioning/AutomaticProvisioningConfig.scala +++ /dev/null @@ -1,76 +0,0 @@ -package ch.epfl.bluebrain.nexus.delta.sdk.provisioning - -import cats.syntax.all._ -import ch.epfl.bluebrain.nexus.delta.rdf.IriOrBNode.Iri -import ch.epfl.bluebrain.nexus.delta.sdk.permissions.model.Permission -import ch.epfl.bluebrain.nexus.delta.sdk.projects.model.{ApiMappings, PrefixIri, ProjectFields} -import ch.epfl.bluebrain.nexus.delta.sourcing.model.Label -import pureconfig.ConfigReader -import pureconfig.error.CannotConvert - -/** - * Configuration of automatic provisioning of projects. - * - * @param enabled - * flag signalling whether automatic provisioning is enabled - * @param permissions - * the permissions applied to the newly provisioned project - * @param enabledRealms - * the realms for which the provisioning is enabled(map of realm label to organization in which the projects for the - * realm should be created) - * @param fields - * the project configuration - */ -final case class AutomaticProvisioningConfig( - enabled: Boolean, - permissions: Set[Permission], - enabledRealms: Map[Label, Label], - fields: ProjectFields -) - -object AutomaticProvisioningConfig { - - val disabled: AutomaticProvisioningConfig = AutomaticProvisioningConfig( - enabled = false, - permissions = Set.empty, - enabledRealms = Map.empty, - ProjectFields(None, ApiMappings.empty, None, None, false) - ) - - implicit private val permissionConfigReader: ConfigReader[Permission] = - ConfigReader.fromString(str => - Permission(str).leftMap(err => CannotConvert(str, classOf[Permission].getSimpleName, err.getMessage)) - ) - - implicit private val mapReader: ConfigReader[Map[Label, Label]] = Label.labelMapReader[Label] - - implicit private val prefixIriReader: ConfigReader[PrefixIri] = ConfigReader[Iri].emap { iri => - PrefixIri(iri).leftMap { e => CannotConvert(iri.toString, classOf[PrefixIri].getSimpleName, e.getMessage) } - } - - implicit private val apiMappingsReader: ConfigReader[ApiMappings] = ConfigReader[Map[String, Iri]].map(ApiMappings(_)) - - implicit val provisioningConfigReader: ConfigReader[AutomaticProvisioningConfig] = ConfigReader.fromCursor { cursor => - for { - obj <- cursor.asObjectCursor - enabled <- obj.atKey("enabled").flatMap(_.asBoolean) - permissionsCursor <- obj.atKey("permissions") - permissions <- ConfigReader[Set[Permission]].from(permissionsCursor) - enabledRealmsCursor <- obj.atKey("enabled-realms").flatMap(_.asObjectCursor) - enabledRealms <- ConfigReader[Map[Label, Label]].from(enabledRealmsCursor) - description <- obj.atKey("description").flatMap(_.asString) - apiMappingsCursor <- obj.atKey("api-mappings").flatMap(_.asObjectCursor) - apiMappings <- ConfigReader[ApiMappings].from(apiMappingsCursor) - baseCursor = obj.atKeyOrUndefined("base") - base <- ConfigReader[Option[PrefixIri]].from(baseCursor) - vocabCursor = obj.atKeyOrUndefined("vocab") - vocab <- ConfigReader[Option[PrefixIri]].from(vocabCursor) - enforceSchema <- obj.atKey("enforce-schema").flatMap(_.asBoolean) - } yield AutomaticProvisioningConfig( - enabled, - permissions, - enabledRealms, - ProjectFields(Some(description), apiMappings, base, vocab, enforceSchema) - ) - } -} diff --git a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/provisioning/ProjectProvisioning.scala b/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/provisioning/ProjectProvisioning.scala deleted file mode 100644 index 9415312163..0000000000 --- a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/provisioning/ProjectProvisioning.scala +++ /dev/null @@ -1,134 +0,0 @@ -package ch.epfl.bluebrain.nexus.delta.sdk.provisioning - -import cats.effect.IO -import cats.implicits._ -import ch.epfl.bluebrain.nexus.delta.kernel.Logger -import ch.epfl.bluebrain.nexus.delta.kernel.error.FormatError -import ch.epfl.bluebrain.nexus.delta.sdk.acls.Acls -import ch.epfl.bluebrain.nexus.delta.sdk.acls.model.{Acl, AclAddress, AclRejection} -import ch.epfl.bluebrain.nexus.delta.sdk.error.SDKError -import ch.epfl.bluebrain.nexus.delta.sdk.identities.model.ServiceAccount -import ch.epfl.bluebrain.nexus.delta.sdk.projects.Projects -import ch.epfl.bluebrain.nexus.delta.sdk.projects.model.ProjectRejection -import ch.epfl.bluebrain.nexus.delta.sdk.projects.model.ProjectRejection.ProjectAlreadyExists -import ch.epfl.bluebrain.nexus.delta.sourcing.model.Identity.{Subject, User} -import ch.epfl.bluebrain.nexus.delta.sourcing.model.{Label, ProjectRef} - -/** - * Automatic project provisioning for users. - */ -trait ProjectProvisioning { - - /** - * Provision a project for a user - * - * @param subject - * a user to provision a project for - */ - def apply(subject: Subject): IO[Unit] - -} - -object ProjectProvisioning { - - private val logger = Logger[ProjectProvisioning] - - /** - * Rejection signalling that project provisioning failed. - */ - abstract class ProjectProvisioningRejection(reason: String) extends SDKError { - override def getMessage: String = reason - } - - /** - * Rejection signalling that we were not able to crate project label from username. - */ - final case class InvalidProjectLabel(err: FormatError) extends ProjectProvisioningRejection(err.getMessage) - - /** - * Rejection signalling that we were unable to set ACLs for user. - */ - final case class UnableToSetAcls(err: AclRejection) extends ProjectProvisioningRejection(err.reason) - - /** - * Rejection signalling that we were unable to create the project for user. - */ - final case class UnableToCreateProject(err: ProjectRejection) extends ProjectProvisioningRejection(err.reason) - - /** - * Create an instance of [[ProjectProvisioning]] - * @param appendAcls - * how to append acls - * @param projects - * project operations - * @param provisioningConfig - * provisioning configuration - */ - def apply( - appendAcls: Acl => IO[Unit], - projects: Projects, - provisioningConfig: AutomaticProvisioningConfig - ): ProjectProvisioning = new ProjectProvisioning { - - private def provisionOnNotFound( - projectRef: ProjectRef, - user: User, - provisioningConfig: AutomaticProvisioningConfig - ): IO[Unit] = { - val acl = Acl(AclAddress.Project(projectRef), user -> provisioningConfig.permissions) - for { - _ <- logger.info(s"Starting provisioning project for user ${user.subject}") - _ <- appendAcls(acl) - .recoverWith { - case _: AclRejection.IncorrectRev => IO.unit - case _: AclRejection.NothingToBeUpdated => IO.unit - } - .adaptError { case r: AclRejection => UnableToSetAcls(r) } - _ <- projects - .create( - projectRef, - provisioningConfig.fields - )(user) - .void - .recoverWith { case _: ProjectAlreadyExists => IO.unit } - .adaptError { case r: ProjectRejection => UnableToCreateProject(r) } - _ <- logger.info(s"Provisioning project for user ${user.subject} succeeded.") - } yield () - } - - override def apply(subject: Subject): IO[Unit] = subject match { - case user @ User(subject, realm) if provisioningConfig.enabled => - provisioningConfig.enabledRealms.get(realm) match { - case Some(org) => - for { - proj <- - IO.fromEither(Label.sanitized(subject)).adaptError { case e: FormatError => InvalidProjectLabel(e) } - projectRef = ProjectRef(org, proj) - exists <- projects.fetch(projectRef).as(true).handleError(_ => false) - _ <- IO.whenA(!exists)(provisionOnNotFound(projectRef, user, provisioningConfig)) - } yield () - case None => IO.unit - } - case _ => IO.unit - } - } - - /** - * Create an instance of [[ProjectProvisioning]] from an [[Acls]] instance - * @param acls - * an acls instance - * @param projects - * project operations - * @param provisioningConfig - * provisioning configuration - */ - def apply( - acls: Acls, - projects: Projects, - provisioningConfig: AutomaticProvisioningConfig, - serviceAccount: ServiceAccount - ): ProjectProvisioning = { - implicit val serviceAccountSubject: Subject = serviceAccount.subject - apply(acls.append(_, 0).void, projects, provisioningConfig) - } -} diff --git a/delta/sdk/src/test/scala/ch/epfl/bluebrain/nexus/delta/sdk/provisioning/ProjectProvisioningSpec.scala b/delta/sdk/src/test/scala/ch/epfl/bluebrain/nexus/delta/sdk/provisioning/ProjectProvisioningSpec.scala deleted file mode 100644 index 974e401f51..0000000000 --- a/delta/sdk/src/test/scala/ch/epfl/bluebrain/nexus/delta/sdk/provisioning/ProjectProvisioningSpec.scala +++ /dev/null @@ -1,119 +0,0 @@ -package ch.epfl.bluebrain.nexus.delta.sdk.provisioning - -import cats.effect.IO -import ch.epfl.bluebrain.nexus.delta.kernel.utils.UUIDF -import ch.epfl.bluebrain.nexus.delta.rdf.syntax.iriStringContextSyntax -import ch.epfl.bluebrain.nexus.delta.sdk.acls.AclSimpleCheck -import ch.epfl.bluebrain.nexus.delta.sdk.acls.model.AclAddress -import ch.epfl.bluebrain.nexus.delta.sdk.model.BaseUri -import ch.epfl.bluebrain.nexus.delta.sdk.organizations.FetchActiveOrganization -import ch.epfl.bluebrain.nexus.delta.sdk.organizations.model.Organization -import ch.epfl.bluebrain.nexus.delta.sdk.organizations.model.OrganizationRejection.OrganizationNotFound -import ch.epfl.bluebrain.nexus.delta.sdk.permissions.Permissions.resources -import ch.epfl.bluebrain.nexus.delta.sdk.projects.ProjectsImpl -import ch.epfl.bluebrain.nexus.delta.sdk.projects.model._ -import ch.epfl.bluebrain.nexus.delta.sdk.provisioning.ProjectProvisioning.InvalidProjectLabel -import ch.epfl.bluebrain.nexus.delta.sdk.{ConfigFixtures, ScopeInitializer} -import ch.epfl.bluebrain.nexus.delta.sourcing.model.Identity.Subject -import ch.epfl.bluebrain.nexus.delta.sourcing.model.{Identity, Label, ProjectRef} -import ch.epfl.bluebrain.nexus.delta.sourcing.postgres.DoobieScalaTestFixture -import ch.epfl.bluebrain.nexus.testkit.scalatest.ce.{CatsEffectSpec, CatsIOValues} - -import java.util.UUID - -class ProjectProvisioningSpec extends CatsEffectSpec with DoobieScalaTestFixture with ConfigFixtures with CatsIOValues { - - implicit private val baseUri: BaseUri = BaseUri("http://localhost", Label.unsafe("v1")) - - private val uuid = UUID.randomUUID() - implicit private val uuidF: UUIDF = UUIDF.fixed(uuid) - - private val orgUuid = UUID.randomUUID() - - private val usersOrg = Label.unsafe("users-org") - - private val aclCheck: AclSimpleCheck = AclSimpleCheck().accepted - - private def fetchOrg: FetchActiveOrganization = { - case `usersOrg` => IO.pure(Organization(usersOrg, orgUuid, None)) - case other => IO.raiseError(OrganizationNotFound(other)) - } - - private val provisioningConfig = AutomaticProvisioningConfig( - enabled = true, - permissions = Set(resources.read, resources.write), - enabledRealms = Map(Label.unsafe("realm") -> Label.unsafe("users-org")), - ProjectFields( - Some("Auto provisioned project"), - ApiMappings.empty, - Some(PrefixIri.unsafe(iri"http://example.com/base/")), - Some(PrefixIri.unsafe(iri"http://example.com/vocab/")), - enforceSchema = true - ) - ) - - private lazy val projects = ProjectsImpl( - fetchOrg, - _ => IO.unit, - ScopeInitializer.noop, - ApiMappings.empty, - eventLogConfig, - xas, - clock - ) - - private lazy val provisioning = ProjectProvisioning(aclCheck.append, projects, provisioningConfig) - - "Provisioning projects" should { - - "provision project with correct permissions" in { - val subject: Subject = Identity.User("user1######", Label.unsafe("realm")) - val projectLabel = Label.unsafe("user1") - val projectRef = ProjectRef(usersOrg, projectLabel) - provisioning(subject).accepted - projects.fetchProject(projectRef).accepted shouldEqual Project( - projectLabel, - uuid, - usersOrg, - orgUuid, - provisioningConfig.fields.description, - provisioningConfig.fields.apiMappings, - projects.defaultApiMappings, - ProjectBase(provisioningConfig.fields.base.value.value), - provisioningConfig.fields.vocab.value.value, - enforceSchema = provisioningConfig.fields.enforceSchema, - markedForDeletion = false - ) - - provisioningConfig.permissions.foreach { permission => - aclCheck.authorizeFor(AclAddress.Project(projectRef), permission, Set(subject)).accepted shouldEqual true - } - } - - "provision project with even if the ACLs have been set before" in { - val subject: Subject = Identity.User("user2", Label.unsafe("realm")) - val projectLabel = Label.unsafe("user2") - val projectRef = ProjectRef(usersOrg, projectLabel) - aclCheck.append(AclAddress.Project(projectRef), subject -> provisioningConfig.permissions).accepted - provisioning(subject).accepted - projects.fetchProject(ProjectRef(usersOrg, projectLabel)).accepted shouldEqual Project( - projectLabel, - uuid, - usersOrg, - orgUuid, - provisioningConfig.fields.description, - provisioningConfig.fields.apiMappings, - projects.defaultApiMappings, - ProjectBase(provisioningConfig.fields.base.value.value), - provisioningConfig.fields.vocab.value.value, - enforceSchema = provisioningConfig.fields.enforceSchema, - markedForDeletion = false - ) - } - - "fail to provision if it's not possible to sanitize username" in { - val subject: Subject = Identity.User("!!!!!!!######", Label.unsafe("realm")) - provisioning(subject).rejected shouldBe a[InvalidProjectLabel] - } - } -} diff --git a/docs/src/main/paradox/docs/releases/index.md b/docs/src/main/paradox/docs/releases/index.md index 42d5c3c91a..30e8cf4adc 100644 --- a/docs/src/main/paradox/docs/releases/index.md +++ b/docs/src/main/paradox/docs/releases/index.md @@ -32,7 +32,8 @@ The latest stable release is **v1.10.0** released on **17.09.2024**. The items listed below are changes that have been made in this release that break compatibility with previous releases. -- The Remote storage server implementation has been removed +- The Remote storage server implementation has been removed. +- The automatic provisioning of projects has been removed. - The Jira integration plugin has been removed. @@@ diff --git a/docs/src/main/paradox/docs/releases/v1.6-release-notes.md b/docs/src/main/paradox/docs/releases/v1.6-release-notes.md index d84af33459..1d0ab0d68f 100644 --- a/docs/src/main/paradox/docs/releases/v1.6-release-notes.md +++ b/docs/src/main/paradox/docs/releases/v1.6-release-notes.md @@ -82,8 +82,6 @@ The RDF parser allowing to validate incoming data can now be @link:[configured]( When enabled, a dedicated project is created for the current user on its first access to the Nexus platform. -How to enable and configure it is detailed @ref:[here](../running-nexus/configuration/index.md#automatic-project-provisioning) - ### Deletion of projects The deletion of projects and all its enclosed resources is now possible by enabling a configuration flag via a @ref:[dedicated endpoint](../delta/api/projects-api.md#delete). diff --git a/docs/src/main/paradox/docs/running-nexus/configuration/index.md b/docs/src/main/paradox/docs/running-nexus/configuration/index.md index 836f2c308a..aaa8fad847 100644 --- a/docs/src/main/paradox/docs/running-nexus/configuration/index.md +++ b/docs/src/main/paradox/docs/running-nexus/configuration/index.md @@ -152,21 +152,6 @@ Acl provisioning will only run if none is create at the root level. @link:[The `acls.provisioning` section](https://github.com/BlueBrain/nexus/blob/$git.branch$/delta/app/src/main/resources/app.conf){ open=new } of the configuration defines it. - -## Automatic project provisioning - -Automatic project provisioning allows to create a dedicated project for users the first time they connect to Delta that is to -say the first time, they query the project listing endpoints. - -The generated project label will be: - -* The current username where only non-diacritic alphabetic characters (`[a-zA-Z]`), numbers, dashes and underscores will be preserved. -* This resulting string is then truncated to 64 characters if needed. - -This feature can be turned on via the flag `app.automatic-provisioning.enabled`. - -@link:[The `automatic-provisioning` section](https://github.com/BlueBrain/nexus/blob/$git.branch$/delta/app/src/main/resources/app.conf#L197){ open=new } of the configuration defines the project provisioning configuration. - ## Fusion configuration When fetching a resource, Nexus Delta allows to return a redirection to its representation in Fusion by providing `text/html` in the `Accept` header. diff --git a/tests/docker/config/delta-postgres.conf b/tests/docker/config/delta-postgres.conf index f0beedff51..56d8f7436a 100644 --- a/tests/docker/config/delta-postgres.conf +++ b/tests/docker/config/delta-postgres.conf @@ -105,17 +105,6 @@ app { propagation-delay = 3 seconds } } - - automatic-provisioning { - enabled = true - permissions = [ - "resources/read", - "resources/write" - ] - enabled-realms { - internal = "internal" - } - } } plugins { diff --git a/tests/src/test/scala/ch/epfl/bluebrain/nexus/tests/iam/IdentitiesSpec.scala b/tests/src/test/scala/ch/epfl/bluebrain/nexus/tests/iam/IdentitiesSpec.scala index 39269dcde1..051f6c65de 100644 --- a/tests/src/test/scala/ch/epfl/bluebrain/nexus/tests/iam/IdentitiesSpec.scala +++ b/tests/src/test/scala/ch/epfl/bluebrain/nexus/tests/iam/IdentitiesSpec.scala @@ -2,42 +2,20 @@ package ch.epfl.bluebrain.nexus.tests.iam import akka.http.scaladsl.model.StatusCodes import ch.epfl.bluebrain.nexus.tests.HttpClient.tokensMap -import ch.epfl.bluebrain.nexus.tests.Identity.projects.Bojack -import ch.epfl.bluebrain.nexus.tests.iam.types.{Permission, User} -import ch.epfl.bluebrain.nexus.tests.{BaseIntegrationSpec, Identity, Realm} +import ch.epfl.bluebrain.nexus.tests.{BaseIntegrationSpec, Identity} import io.circe.Json class IdentitiesSpec extends BaseIntegrationSpec { - private val internaRealmName = Realm.internal.name - private val provisionedProjectName = Identity.ServiceAccount.name - private val provisionedProject = s"$internaRealmName/$provisionedProjectName" - private val provisionedProjectPermissions = Permission.Resources.list.toSet - "The /identities endpoint" should { - s"return identities of the user and provision a project for him" in { - for { - _ <- createOrg(Identity.ServiceAccount, internaRealmName) - _ <- deltaClient.get[Json]("/identities", Identity.ServiceAccount) { (json, response) => - response.status shouldEqual StatusCodes.OK - json shouldEqual jsonContentOf( - "iam/identities/response.json", - "deltaUri" -> config.deltaUri.toString() - ) - } - // A project should have been provisioned - _ <- deltaClient.get[Json](s"/projects/$provisionedProject", Identity.ServiceAccount) { (_, response) => - response.status shouldEqual StatusCodes.OK - } - // Checking acls has been properly set - _ <- aclDsl.fetch(s"/$provisionedProject", Identity.ServiceAccount) { acls => - acls._total shouldEqual 1 - val acl = acls._results.headOption.value.acl.head - acl.identity shouldEqual User(internaRealmName, Identity.ServiceAccount.name) - acl.permissions shouldEqual provisionedProjectPermissions - } - } yield succeed - + s"return identities of the user" in { + deltaClient.get[Json]("/identities", Identity.ServiceAccount) { (json, response) => + response.status shouldEqual StatusCodes.OK + json shouldEqual jsonContentOf( + "iam/identities/response.json", + "deltaUri" -> config.deltaUri.toString() + ) + } } "return the error for an invalid token" in {