From 3be918c377b5ec42e067796ff065160cd55e9a9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Kleinb=C3=B6lting?= Date: Mon, 17 Jun 2024 14:55:36 +0200 Subject: [PATCH 1/9] Inline OntologyResponderV2 trait and add test for deleting an ontology graph during project erasing --- .../org/knora/webapi/ProjectEraseIT.scala | 32 ++++++++++++++++++- .../org/knora/webapi/core/LayersTest.scala | 2 +- .../org/knora/webapi/core/LayersLive.scala | 2 +- .../responders/v2/OntologyResponderV2.scala | 18 +++++------ 4 files changed, 41 insertions(+), 13 deletions(-) diff --git a/integration/src/test/scala/org/knora/webapi/ProjectEraseIT.scala b/integration/src/test/scala/org/knora/webapi/ProjectEraseIT.scala index 7c4a34f557..d34897830c 100644 --- a/integration/src/test/scala/org/knora/webapi/ProjectEraseIT.scala +++ b/integration/src/test/scala/org/knora/webapi/ProjectEraseIT.scala @@ -10,8 +10,12 @@ import zio.test.Spec import zio.test.TestAspect import zio.test.assertTrue +import java.util.UUID import dsp.valueobjects.LanguageCode import org.knora.webapi.messages.store.triplestoremessages.StringLiteralV2 +import org.knora.webapi.messages.util.KnoraSystemInstances +import org.knora.webapi.messages.v2.responder.ontologymessages.CreateOntologyRequestV2 +import org.knora.webapi.responders.v2.OntologyResponderV2 import org.knora.webapi.slice.admin.api.GroupsRequests.GroupCreateRequest import org.knora.webapi.slice.admin.api.UsersEndpoints.Requests.UserCreateRequest import org.knora.webapi.slice.admin.api.model.ProjectsEndpointsRequestsAndResponses.ProjectCreateRequest @@ -38,6 +42,7 @@ import org.knora.webapi.slice.admin.domain.service.KnoraProjectService import org.knora.webapi.slice.admin.domain.service.KnoraUserService import org.knora.webapi.slice.admin.domain.service.ProjectService import org.knora.webapi.slice.resourceinfo.domain.InternalIri +import org.knora.webapi.slice.resourceinfo.domain.IriConverter import org.knora.webapi.store.triplestore.api.TriplestoreService import org.knora.webapi.store.triplestore.api.TriplestoreService.Queries.Ask import org.knora.webapi.store.triplestore.api.TriplestoreService.Queries.Update @@ -108,6 +113,8 @@ object ProjectEraseIT extends E2EZSpec { user <- users(_.addUserToProjectAsAdmin(user, project)) } yield (user, group) + private def doesGraphExist(graphName: InternalIri) = db(_.query(Ask(s"ASK { GRAPH <${graphName.value}> {} }"))) + override def e2eSpec: Spec[ProjectEraseIT.env, Any] = suiteAll(s"The erasing project endpoint ${AdminApiRestClient.projectsShortcodeErasePath}") { @@ -153,7 +160,6 @@ object ProjectEraseIT extends E2EZSpec { ) }, test("when called as root then it should delete the project graph") { - def doesGraphExist(graphName: InternalIri) = db(_.query(Ask(s"ASK { GRAPH <${graphName.value}> {} }"))) for { // given project <- getProject @@ -178,6 +184,30 @@ object ProjectEraseIT extends E2EZSpec { graphDeleted, ) }, + test("when called as root then it should delete the ontology graph") { + for { + project <- getProject + projectSmartIri <- ZIO.serviceWithZIO[IriConverter](_.asSmartIri(project.id.value)) + req = CreateOntologyRequestV2( + "test", + projectSmartIri, + false, + "some label", + None, + UUID.randomUUID(), + KnoraSystemInstances.Users.SystemUser, + ) + onto <- ZIO.serviceWithZIO[OntologyResponderV2](_.createOntology(req)) + ontologyGraphName = onto.ontologies.head.ontologyIri.toInternalIri + ontologyGraphExists <- doesGraphExist(ontologyGraphName) + + // when + erased <- AdminApiRestClient.eraseProjectAsRoot(shortcode) + + // then + graphDeleted <- doesGraphExist(ontologyGraphName).negate + } yield assertTrue(ontologyGraphExists, graphDeleted) + }, ) @@ TestAspect.before(createProject) } } diff --git a/integration/src/test/scala/org/knora/webapi/core/LayersTest.scala b/integration/src/test/scala/org/knora/webapi/core/LayersTest.scala index 868a3881a1..3e5d36e444 100644 --- a/integration/src/test/scala/org/knora/webapi/core/LayersTest.scala +++ b/integration/src/test/scala/org/knora/webapi/core/LayersTest.scala @@ -211,7 +211,7 @@ object LayersTest { CommonR0 & CommonR, CommonR1, ]( - OntologyResponderV2Live.layer, + OntologyResponderV2.layer, StandoffResponderV2.layer, ResourcesResponderV2.layer, RepositoryUpdater.layer, diff --git a/webapi/src/main/scala/org/knora/webapi/core/LayersLive.scala b/webapi/src/main/scala/org/knora/webapi/core/LayersLive.scala index 8262fd2f9d..a9d4236465 100644 --- a/webapi/src/main/scala/org/knora/webapi/core/LayersLive.scala +++ b/webapi/src/main/scala/org/knora/webapi/core/LayersLive.scala @@ -155,7 +155,7 @@ object LayersLive { OntologyCacheLive.layer, OntologyCacheHelpersLive.layer, OntologyRepoLive.layer, - OntologyResponderV2Live.layer, + OntologyResponderV2.layer, OntologyServiceLive.layer, OntologyTriplestoreHelpersLive.layer, PermissionUtilADMLive.layer, diff --git a/webapi/src/main/scala/org/knora/webapi/responders/v2/OntologyResponderV2.scala b/webapi/src/main/scala/org/knora/webapi/responders/v2/OntologyResponderV2.scala index 9c9fe9f368..3e88afe563 100644 --- a/webapi/src/main/scala/org/knora/webapi/responders/v2/OntologyResponderV2.scala +++ b/webapi/src/main/scala/org/knora/webapi/responders/v2/OntologyResponderV2.scala @@ -12,7 +12,6 @@ import zio.ZIO import zio.ZLayer import java.time.Instant - import dsp.constants.SalsahGui import dsp.errors.* import org.knora.webapi.* @@ -65,9 +64,7 @@ import org.knora.webapi.store.triplestore.api.TriplestoreService.Queries.Update * * The API v1 ontology responder, which is read-only, delegates most of its work to this responder. */ -trait OntologyResponderV2 - -final case class OntologyResponderV2Live( +final case class OntologyResponderV2( appConfig: AppConfig, cardinalityHandler: CardinalityHandler, cardinalityService: CardinalityService, @@ -79,8 +76,7 @@ final case class OntologyResponderV2Live( knoraProjectService: KnoraProjectService, triplestoreService: TriplestoreService, )(implicit val stringFormatter: StringFormatter) - extends OntologyResponderV2 - with MessageHandler + extends MessageHandler with LazyLogging { override def isResponsibleFor(message: ResponderRequest): Boolean = message.isInstanceOf[OntologiesResponderRequestV2] @@ -441,7 +437,7 @@ final case class OntologyResponderV2Live( * @param createOntologyRequest the request message. * @return a [[SuccessResponseV2]]. */ - private def createOntology(createOntologyRequest: CreateOntologyRequestV2): Task[ReadOntologyMetadataV2] = { + def createOntology(createOntologyRequest: CreateOntologyRequestV2): Task[ReadOntologyMetadataV2] = { def makeTaskFuture(internalOntologyIri: SmartIri): Task[ReadOntologyMetadataV2] = for { // Make sure the ontology doesn't already exist. @@ -522,7 +518,9 @@ final case class OntologyResponderV2Live( // check if the requesting user is allowed to create an ontology _ <- ZIO.when( - !(requestingUser.permissions.isProjectAdmin(projectIri.toString) || requestingUser.permissions.isSystemAdmin), + !(requestingUser.permissions.isProjectAdmin( + projectIri.toString, + ) || requestingUser.permissions.isSystemAdmin || requestingUser.isSystemUser), ) { val msg = s"A new ontology in the project ${createOntologyRequest.projectIri} can only be created by an admin of that project, or by a system admin." @@ -2984,7 +2982,7 @@ final case class OntologyResponderV2Live( } } -object OntologyResponderV2Live { +object OntologyResponderV2 { val layer: URLayer[ AppConfig & CardinalityHandler & CardinalityService & IriService & KnoraProjectService & MessageRelay & OntologyCache & OntologyTriplestoreHelpers & OntologyCacheHelpers & OntologyRepo & StringFormatter & @@ -3003,7 +3001,7 @@ object OntologyResponderV2Live { or <- ZIO.service[OntologyRepo] sf <- ZIO.service[StringFormatter] ts <- ZIO.service[TriplestoreService] - responder = OntologyResponderV2Live(ac, ch, cs, is, oc, och, oth, or, kr, ts)(sf) + responder = OntologyResponderV2(ac, ch, cs, is, oc, och, oth, or, kr, ts)(sf) _ <- ZIO.serviceWithZIO[MessageRelay](_.subscribe(responder)) } yield responder } From ada50004afdb64b6d2a04aa8b1b06b12b7cf3261 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Kleinb=C3=B6lting?= Date: Mon, 17 Jun 2024 15:27:02 +0200 Subject: [PATCH 2/9] Add test for administrative permissions --- .../org/knora/webapi/ProjectEraseIT.scala | 55 +++++++++++++++---- .../responders/v2/OntologyResponderV2.scala | 1 + .../AdministrativePermissionService.scala | 19 +++++++ .../domain/service/ProjectEraseService.scala | 15 ++--- 4 files changed, 71 insertions(+), 19 deletions(-) diff --git a/integration/src/test/scala/org/knora/webapi/ProjectEraseIT.scala b/integration/src/test/scala/org/knora/webapi/ProjectEraseIT.scala index d34897830c..8e74acc60e 100644 --- a/integration/src/test/scala/org/knora/webapi/ProjectEraseIT.scala +++ b/integration/src/test/scala/org/knora/webapi/ProjectEraseIT.scala @@ -4,6 +4,7 @@ */ package org.knora.webapi +import zio.Chunk import zio.ZIO import zio.http.Status import zio.test.Spec @@ -11,6 +12,8 @@ import zio.test.TestAspect import zio.test.assertTrue import java.util.UUID +import java.util.concurrent.atomic.AtomicInteger + import dsp.valueobjects.LanguageCode import org.knora.webapi.messages.store.triplestoremessages.StringLiteralV2 import org.knora.webapi.messages.util.KnoraSystemInstances @@ -19,6 +22,7 @@ import org.knora.webapi.responders.v2.OntologyResponderV2 import org.knora.webapi.slice.admin.api.GroupsRequests.GroupCreateRequest import org.knora.webapi.slice.admin.api.UsersEndpoints.Requests.UserCreateRequest import org.knora.webapi.slice.admin.api.model.ProjectsEndpointsRequestsAndResponses.ProjectCreateRequest +import org.knora.webapi.slice.admin.domain.model.AdministrativePermissionPart import org.knora.webapi.slice.admin.domain.model.Email import org.knora.webapi.slice.admin.domain.model.FamilyName import org.knora.webapi.slice.admin.domain.model.GivenName @@ -33,10 +37,13 @@ import org.knora.webapi.slice.admin.domain.model.KnoraProject.ProjectIri import org.knora.webapi.slice.admin.domain.model.KnoraProject.Shortcode import org.knora.webapi.slice.admin.domain.model.KnoraProject.Shortname import org.knora.webapi.slice.admin.domain.model.Password +import org.knora.webapi.slice.admin.domain.model.Permission +import org.knora.webapi.slice.admin.domain.model.Permission.Administrative.ProjectResourceCreateAll import org.knora.webapi.slice.admin.domain.model.SystemAdmin import org.knora.webapi.slice.admin.domain.model.UserIri import org.knora.webapi.slice.admin.domain.model.UserStatus import org.knora.webapi.slice.admin.domain.model.Username +import org.knora.webapi.slice.admin.domain.service.AdministrativePermissionService import org.knora.webapi.slice.admin.domain.service.KnoraGroupService import org.knora.webapi.slice.admin.domain.service.KnoraProjectService import org.knora.webapi.slice.admin.domain.service.KnoraUserService @@ -53,6 +60,7 @@ object ProjectEraseIT extends E2EZSpec { private val projects = ZIO.serviceWithZIO[KnoraProjectService] private val groups = ZIO.serviceWithZIO[KnoraGroupService] private val db = ZIO.serviceWithZIO[TriplestoreService] + private val aps = ZIO.serviceWithZIO[AdministrativePermissionService] private def getUser(userIri: UserIri) = users(_.findById(userIri)).someOrFail(Exception(s"Must be present $userIri")) private val shortcode = Shortcode.unsafeFrom("9999") @@ -90,19 +98,25 @@ object ProjectEraseIT extends E2EZSpec { ), ) - private def createGroup(project: KnoraProject) = groups( - _.createGroup( - GroupCreateRequest( - None, - GroupName.unsafeFrom("group"), - GroupDescriptions.unsafeFrom(Seq(StringLiteralV2.unsafeFrom("group description", None))), - project.id, - GroupStatus.active, - GroupSelfJoin.enabled, + private val groupNr: AtomicInteger = new AtomicInteger() + private def createGroup(project: KnoraProject) = { + val nr = groupNr.getAndIncrement() + groups( + _.createGroup( + GroupCreateRequest( + None, + GroupName.unsafeFrom("group" + nr), + GroupDescriptions.unsafeFrom( + Seq(StringLiteralV2.unsafeFrom("group description: " + nr, None)), + ), + project.id, + GroupStatus.active, + GroupSelfJoin.enabled, + ), + project, ), - project, - ), - ) + ) + } private val createUserWithMemberships = for { user <- createUser @@ -208,6 +222,23 @@ object ProjectEraseIT extends E2EZSpec { graphDeleted <- doesGraphExist(ontologyGraphName).negate } yield assertTrue(ontologyGraphExists, graphDeleted) }, + test("when called as root then it should delete administrative permissions") { + val perm = AdministrativePermissionPart.Simple + .from(ProjectResourceCreateAll) + .getOrElse(throw Exception("should not happen")) + for { + project <- getProject + group <- createGroup(project) + ap <- aps(_.create(project, group, Chunk(perm))) + wasPresent <- aps(_.findByGroupAndProject(group.id, project.id)).map(_.nonEmpty) + + // when + erased <- AdminApiRestClient.eraseProjectAsRoot(shortcode) + + // then + wasDeleted <- aps(_.findByGroupAndProject(group.id, project.id)).map(_.isEmpty) + } yield assertTrue(wasPresent, wasDeleted) + }, ) @@ TestAspect.before(createProject) } } diff --git a/webapi/src/main/scala/org/knora/webapi/responders/v2/OntologyResponderV2.scala b/webapi/src/main/scala/org/knora/webapi/responders/v2/OntologyResponderV2.scala index 3e88afe563..278a6c9245 100644 --- a/webapi/src/main/scala/org/knora/webapi/responders/v2/OntologyResponderV2.scala +++ b/webapi/src/main/scala/org/knora/webapi/responders/v2/OntologyResponderV2.scala @@ -12,6 +12,7 @@ import zio.ZIO import zio.ZLayer import java.time.Instant + import dsp.constants.SalsahGui import dsp.errors.* import org.knora.webapi.* diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/AdministrativePermissionService.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/AdministrativePermissionService.scala index 718f25d1d2..c42ca30f79 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/AdministrativePermissionService.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/AdministrativePermissionService.scala @@ -5,17 +5,36 @@ package org.knora.webapi.slice.admin.domain.service +import zio.Chunk import zio.Task import zio.ZLayer import org.knora.webapi.slice.admin.domain.model.AdministrativePermission +import org.knora.webapi.slice.admin.domain.model.AdministrativePermissionPart import org.knora.webapi.slice.admin.domain.model.AdministrativePermissionRepo import org.knora.webapi.slice.admin.domain.model.GroupIri +import org.knora.webapi.slice.admin.domain.model.KnoraGroup +import org.knora.webapi.slice.admin.domain.model.KnoraProject import org.knora.webapi.slice.admin.domain.model.KnoraProject.ProjectIri +import org.knora.webapi.slice.admin.domain.model.PermissionIri final case class AdministrativePermissionService(repo: AdministrativePermissionRepo) { def findByGroupAndProject(groupIri: GroupIri, projectIri: ProjectIri): Task[Option[AdministrativePermission]] = repo.findByGroupAndProject(groupIri, projectIri) + + def create( + project: KnoraProject, + group: KnoraGroup, + permissions: Chunk[AdministrativePermissionPart], + ): Task[AdministrativePermission] = + repo.save( + AdministrativePermission( + PermissionIri.makeNew(project.shortcode), + group.id, + project.id, + permissions, + ), + ) } object AdministrativePermissionService { diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/ProjectEraseService.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/ProjectEraseService.scala index 64ec8f0fce..772a77219d 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/ProjectEraseService.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/ProjectEraseService.scala @@ -77,13 +77,14 @@ final case class ProjectEraseService( ) private def cleanUpPermissions(project: KnoraProject) = for { - ap <- apRepo.findByProject(project) - doap <- doapRepo.findByProject(project) - _ <- ZIO.logInfo( - s"${logPrefix(project)} Removing permissions ap ${mkString(ap.map(_.id))} , doap ${mkString(doap.map(_.id))}", - ) - _ <- apRepo.deleteAll(ap) - _ <- doapRepo.deleteAll(doap) + aps <- apRepo.findByProject(project) + doaps <- doapRepo.findByProject(project) + _ <- + ZIO.logInfo( + s"${logPrefix(project)} Removing permissions ap ${mkString(aps.map(_.id))} , doap ${mkString(doaps.map(_.id))}", + ) + _ <- apRepo.deleteAll(aps) + _ <- doapRepo.deleteAll(doaps) } yield () private def removeOntologyAndDataGraphs(project: KnoraProject) = for { From af13f019391ce904398f2ac6fbdeff931c31556b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Kleinb=C3=B6lting?= Date: Mon, 17 Jun 2024 18:03:31 +0200 Subject: [PATCH 3/9] Add test for daop cleanup --- .../org/knora/webapi/ProjectEraseIT.scala | 37 ++++++-- .../admin/domain/AdminDomainModule.scala | 2 + .../DefaultObjectAccessPermissionRepo.scala | 27 ++++++ ...DefaultObjectAccessPermissionService.scala | 35 +++++++ .../slice/admin/repo/rdf/RdfConversions.scala | 3 + .../slice/admin/repo/rdf/Vocabulary.scala | 2 + ...efaultObjectAccessPermissionRepoLive.scala | 92 ++++++++++++++++--- 7 files changed, 180 insertions(+), 18 deletions(-) create mode 100644 webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/DefaultObjectAccessPermissionService.scala diff --git a/integration/src/test/scala/org/knora/webapi/ProjectEraseIT.scala b/integration/src/test/scala/org/knora/webapi/ProjectEraseIT.scala index 8e74acc60e..ba7ecdef14 100644 --- a/integration/src/test/scala/org/knora/webapi/ProjectEraseIT.scala +++ b/integration/src/test/scala/org/knora/webapi/ProjectEraseIT.scala @@ -5,6 +5,7 @@ package org.knora.webapi import zio.Chunk +import zio.NonEmptyChunk import zio.ZIO import zio.http.Status import zio.test.Spec @@ -23,8 +24,10 @@ import org.knora.webapi.slice.admin.api.GroupsRequests.GroupCreateRequest import org.knora.webapi.slice.admin.api.UsersEndpoints.Requests.UserCreateRequest import org.knora.webapi.slice.admin.api.model.ProjectsEndpointsRequestsAndResponses.ProjectCreateRequest import org.knora.webapi.slice.admin.domain.model.AdministrativePermissionPart +import org.knora.webapi.slice.admin.domain.model.DefaultObjectAccessPermissionPart import org.knora.webapi.slice.admin.domain.model.Email import org.knora.webapi.slice.admin.domain.model.FamilyName +import org.knora.webapi.slice.admin.domain.model.ForWhat.Group import org.knora.webapi.slice.admin.domain.model.GivenName import org.knora.webapi.slice.admin.domain.model.GroupDescriptions import org.knora.webapi.slice.admin.domain.model.GroupIri @@ -44,6 +47,7 @@ import org.knora.webapi.slice.admin.domain.model.UserIri import org.knora.webapi.slice.admin.domain.model.UserStatus import org.knora.webapi.slice.admin.domain.model.Username import org.knora.webapi.slice.admin.domain.service.AdministrativePermissionService +import org.knora.webapi.slice.admin.domain.service.DefaultObjectAccessPermissionService import org.knora.webapi.slice.admin.domain.service.KnoraGroupService import org.knora.webapi.slice.admin.domain.service.KnoraProjectService import org.knora.webapi.slice.admin.domain.service.KnoraUserService @@ -61,6 +65,7 @@ object ProjectEraseIT extends E2EZSpec { private val groups = ZIO.serviceWithZIO[KnoraGroupService] private val db = ZIO.serviceWithZIO[TriplestoreService] private val aps = ZIO.serviceWithZIO[AdministrativePermissionService] + private val doaps = ZIO.serviceWithZIO[DefaultObjectAccessPermissionService] private def getUser(userIri: UserIri) = users(_.findById(userIri)).someOrFail(Exception(s"Must be present $userIri")) private val shortcode = Shortcode.unsafeFrom("9999") @@ -223,13 +228,15 @@ object ProjectEraseIT extends E2EZSpec { } yield assertTrue(ontologyGraphExists, graphDeleted) }, test("when called as root then it should delete administrative permissions") { - val perm = AdministrativePermissionPart.Simple - .from(ProjectResourceCreateAll) - .getOrElse(throw Exception("should not happen")) for { - project <- getProject - group <- createGroup(project) - ap <- aps(_.create(project, group, Chunk(perm))) + project <- getProject + group <- createGroup(project) + perms = Chunk( + AdministrativePermissionPart.Simple + .from(ProjectResourceCreateAll) + .getOrElse(throw Exception("should not happen")), + ) + ap <- aps(_.create(project, group, perms)) wasPresent <- aps(_.findByGroupAndProject(group.id, project.id)).map(_.nonEmpty) // when @@ -239,6 +246,24 @@ object ProjectEraseIT extends E2EZSpec { wasDeleted <- aps(_.findByGroupAndProject(group.id, project.id)).map(_.isEmpty) } yield assertTrue(wasPresent, wasDeleted) }, + test("when called as root then it should delete the default object access permissions") { + for { + project <- getProject + group <- createGroup(project) + perms = Chunk( + DefaultObjectAccessPermissionPart(Permission.ObjectAccess.View, NonEmptyChunk(group.id)), + DefaultObjectAccessPermissionPart(Permission.ObjectAccess.Modify, NonEmptyChunk(group.id)), + ) + doap <- doaps(_.create(project, Group(group.id), perms)) + wasPresent <- doaps(_.findByProject(project.id)).map(_.nonEmpty) + + // when + erased <- AdminApiRestClient.eraseProjectAsRoot(shortcode) + + // then + wasDeleted <- doaps(_.findByProject(project.id)).map(_.isEmpty) + } yield assertTrue(wasPresent, wasDeleted) + }, ) @@ TestAspect.before(createProject) } } diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/AdminDomainModule.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/AdminDomainModule.scala index 46f19e40ad..df97ee9b6c 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/AdminDomainModule.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/AdminDomainModule.scala @@ -41,6 +41,7 @@ object AdminDomainModule { type Provided = // format: off AdministrativePermissionService & + DefaultObjectAccessPermissionService & GroupService & KnoraGroupService & KnoraProjectService & @@ -55,6 +56,7 @@ object AdminDomainModule { val layer = ZLayer.makeSome[Dependencies, Provided]( AdministrativePermissionService.layer, + DefaultObjectAccessPermissionService.layer, GroupService.layer, KnoraGroupService.layer, KnoraProjectService.layer, diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/model/DefaultObjectAccessPermissionRepo.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/model/DefaultObjectAccessPermissionRepo.scala index 53febb89c7..f0473ae676 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/model/DefaultObjectAccessPermissionRepo.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/model/DefaultObjectAccessPermissionRepo.scala @@ -6,17 +6,44 @@ package org.knora.webapi.slice.admin.domain.model import zio.Chunk +import zio.NonEmptyChunk import zio.Task import org.knora.webapi.slice.admin.domain.model.KnoraProject.ProjectIri import org.knora.webapi.slice.admin.repo.service.EntityWithId import org.knora.webapi.slice.common.repo.service.CrudRepository +import org.knora.webapi.slice.resourceinfo.domain.InternalIri final case class DefaultObjectAccessPermission( id: PermissionIri, forProject: ProjectIri, + forWhat: ForWhat, + permission: Chunk[DefaultObjectAccessPermissionPart], ) extends EntityWithId[PermissionIri] +enum ForWhat { + case Group(iri: GroupIri) + case ResourceClass(iri: InternalIri) + case Property(iri: InternalIri) + case ResourceClassAndProperty(resourceClass: InternalIri, property: InternalIri) +} +object ForWhat { + def from( + group: Option[GroupIri], + resourceClass: Option[InternalIri], + property: Option[InternalIri], + ): Either[String, ForWhat] = + (group, resourceClass, property) match { + case (None, Some(rc: InternalIri), Some(p: InternalIri)) => Right(ResourceClassAndProperty(rc, p)) + case (None, None, Some(p: InternalIri)) => Right(Property(p)) + case (None, Some(rc: InternalIri), None) => Right(ResourceClass(rc)) + case (Some(g: GroupIri), None, None) => Right(Group(g)) + case _ => Left(s"Invalid combination of group $group resourceClass $resourceClass and property $property.") + } +} + +final case class DefaultObjectAccessPermissionPart(permission: Permission.ObjectAccess, groups: NonEmptyChunk[GroupIri]) + trait DefaultObjectAccessPermissionRepo extends CrudRepository[DefaultObjectAccessPermission, PermissionIri] { def findByProject(projectIri: ProjectIri): Task[Chunk[DefaultObjectAccessPermission]] diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/DefaultObjectAccessPermissionService.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/DefaultObjectAccessPermissionService.scala new file mode 100644 index 0000000000..67ca54e747 --- /dev/null +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/DefaultObjectAccessPermissionService.scala @@ -0,0 +1,35 @@ +/* + * Copyright © 2021 - 2024 Swiss National Data and Service Center for the Humanities and/or DaSCH Service Platform contributors. + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.knora.webapi.slice.admin.domain.service +import zio.Chunk +import zio.Task +import zio.ZLayer + +import org.knora.webapi.slice.admin.domain.model.DefaultObjectAccessPermission +import org.knora.webapi.slice.admin.domain.model.DefaultObjectAccessPermissionPart +import org.knora.webapi.slice.admin.domain.model.DefaultObjectAccessPermissionRepo +import org.knora.webapi.slice.admin.domain.model.ForWhat +import org.knora.webapi.slice.admin.domain.model.KnoraProject +import org.knora.webapi.slice.admin.domain.model.KnoraProject.ProjectIri +import org.knora.webapi.slice.admin.domain.model.PermissionIri + +final case class DefaultObjectAccessPermissionService( + private val repo: DefaultObjectAccessPermissionRepo, +) { + def findByProject(projectIri: ProjectIri): Task[Chunk[DefaultObjectAccessPermission]] = + repo.findByProject(projectIri) + + def create( + project: KnoraProject, + forWhat: ForWhat, + permission: Chunk[DefaultObjectAccessPermissionPart], + ): Task[DefaultObjectAccessPermission] = + repo.save(DefaultObjectAccessPermission(PermissionIri.makeNew(project.shortcode), project.id, forWhat, permission)) +} + +object DefaultObjectAccessPermissionService { + val layer = ZLayer.derive[DefaultObjectAccessPermissionService] +} diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/repo/rdf/RdfConversions.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/repo/rdf/RdfConversions.scala index a0461ceadf..1b7c9264ab 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/admin/repo/rdf/RdfConversions.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/repo/rdf/RdfConversions.scala @@ -19,12 +19,15 @@ import org.knora.webapi.slice.admin.domain.model.SystemAdmin import org.knora.webapi.slice.admin.domain.model.UserStatus import org.knora.webapi.slice.admin.domain.model.Username import org.knora.webapi.slice.common.repo.rdf.LangString +import org.knora.webapi.slice.resourceinfo.domain.InternalIri object RdfConversions { def withPrefixExpansion[A](f: String => Either[String, A]): String => Either[String, A] = (str: String) => f(str.replace(KnoraAdminPrefix, KnoraAdminPrefixExpansion)) + implicit val internalIri: String => Either[String, InternalIri] = withPrefixExpansion(str => Right(InternalIri(str))) + // Group properties implicit val groupIriConverter: String => Either[String, GroupIri] = withPrefixExpansion(GroupIri.from) diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/repo/rdf/Vocabulary.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/repo/rdf/Vocabulary.scala index c296c41578..7133d976d4 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/admin/repo/rdf/Vocabulary.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/repo/rdf/Vocabulary.scala @@ -58,6 +58,8 @@ object Vocabulary { val DefaultObjectAccessPermission: Iri = Rdf.iri(KnoraAdminPrefixExpansion, "DefaultObjectAccessPermission") val forProject: Iri = Rdf.iri(KnoraAdminPrefixExpansion, "forProject") val forGroup: Iri = Rdf.iri(KnoraAdminPrefixExpansion, "forGroup") + val forProperty: Iri = Rdf.iri(KnoraAdminPrefixExpansion, "forProperty") + val forResourceClass: Iri = Rdf.iri(KnoraAdminPrefixExpansion, "forResourceClass") } object KnoraBase { diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/repo/service/DefaultObjectAccessPermissionRepoLive.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/repo/service/DefaultObjectAccessPermissionRepoLive.scala index 47e20c9d95..a04ede63b3 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/admin/repo/service/DefaultObjectAccessPermissionRepoLive.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/repo/service/DefaultObjectAccessPermissionRepoLive.scala @@ -17,16 +17,27 @@ import zio.ZIO import zio.ZLayer import org.knora.webapi.messages.OntologyConstants.KnoraAdmin +import org.knora.webapi.messages.OntologyConstants.KnoraAdmin.KnoraAdminPrefix +import org.knora.webapi.messages.OntologyConstants.KnoraAdmin.KnoraAdminPrefixExpansion +import org.knora.webapi.messages.OntologyConstants.KnoraBase import org.knora.webapi.slice.admin.AdminConstants.permissionsDataNamedGraph import org.knora.webapi.slice.admin.domain.model.DefaultObjectAccessPermission +import org.knora.webapi.slice.admin.domain.model.DefaultObjectAccessPermissionPart import org.knora.webapi.slice.admin.domain.model.DefaultObjectAccessPermissionRepo +import org.knora.webapi.slice.admin.domain.model.ForWhat +import org.knora.webapi.slice.admin.domain.model.ForWhat.Group +import org.knora.webapi.slice.admin.domain.model.ForWhat.ResourceClass +import org.knora.webapi.slice.admin.domain.model.ForWhat.ResourceClassAndProperty +import org.knora.webapi.slice.admin.domain.model.GroupIri import org.knora.webapi.slice.admin.domain.model.KnoraProject.ProjectIri +import org.knora.webapi.slice.admin.domain.model.Permission import org.knora.webapi.slice.admin.domain.model.PermissionIri import org.knora.webapi.slice.admin.repo.rdf.RdfConversions.* import org.knora.webapi.slice.admin.repo.rdf.Vocabulary import org.knora.webapi.slice.common.repo.rdf.Errors.ConversionError import org.knora.webapi.slice.common.repo.rdf.Errors.RdfError import org.knora.webapi.slice.common.repo.rdf.RdfResource +import org.knora.webapi.slice.resourceinfo.domain.InternalIri import org.knora.webapi.store.triplestore.api.TriplestoreService final case class DefaultObjectAccessPermissionRepoLive( @@ -40,31 +51,88 @@ final case class DefaultObjectAccessPermissionRepoLive( override protected def entityProperties: EntityProperties = EntityProperties( - NonEmptyChunk(Vocabulary.KnoraAdmin.forProject), + NonEmptyChunk(Vocabulary.KnoraAdmin.forProject, Vocabulary.KnoraBase.hasPermissions), + Chunk(Vocabulary.KnoraAdmin.forGroup, Vocabulary.KnoraAdmin.forProperty, Vocabulary.KnoraAdmin.forResourceClass), ) override def findByProject(projectIri: ProjectIri): Task[Chunk[DefaultObjectAccessPermission]] = findAllByTriplePattern(_.has(Vocabulary.KnoraAdmin.forProject, Rdf.iri(projectIri.value))) - - override def save(entity: DefaultObjectAccessPermission): Task[DefaultObjectAccessPermission] = - ZIO.die(UnsupportedOperationException("Mapper not yet fully implemented")) } object DefaultObjectAccessPermissionRepoLive { private val mapper = new RdfEntityMapper[DefaultObjectAccessPermission] { - override def toEntity(resource: RdfResource): IO[RdfError, DefaultObjectAccessPermission] = - for { - id <- resource.iri.flatMap { iri => - ZIO.fromEither(PermissionIri.from(iri.value).left.map(ConversionError.apply)) - } - forProject <- resource.getObjectIrisConvert[ProjectIri](KnoraAdmin.ForProject).map(_.head) - } yield DefaultObjectAccessPermission(id, forProject) + private val permissionsDelimiter = '|' + + override def toEntity(resource: RdfResource): IO[RdfError, DefaultObjectAccessPermission] = for { + id <- resource.iri.flatMap { iri => + ZIO.fromEither(PermissionIri.from(iri.value).left.map(ConversionError.apply)) + } + forProject <- resource.getObjectIrisConvert[ProjectIri](KnoraAdmin.ForProject).map(_.head) + forGroup <- resource.getObjectIrisConvert[GroupIri](KnoraAdmin.ForGroup).map(_.headOption) + forResourceClass <- resource.getObjectIrisConvert[InternalIri](KnoraAdmin.ForResourceClass).map(_.headOption) + forResourceProperty <- resource.getObjectIrisConvert[InternalIri](KnoraAdmin.ForProperty).map(_.headOption) + forWhat <- + ZIO.fromEither(ForWhat.from(forGroup, forResourceClass, forResourceProperty)).mapError(ConversionError.apply) + permissions <- parsePermissions(resource) + } yield DefaultObjectAccessPermission(id, forProject, forWhat, permissions) + + private def parsePermissions(resource: RdfResource) = for { + permissionStr <- resource.getStringLiteralOrFail[String](KnoraBase.HasPermissions)(Right(_)) + parsedPermissions <- parsePermission(permissionStr) + } yield parsedPermissions + + private def parsePermission(permission: String): IO[RdfError, Chunk[DefaultObjectAccessPermissionPart]] = + ZIO + .foreach(Chunk.fromIterable(permission.split(permissionsDelimiter).map(_.trim))) { token => + token.split(' ') match { + case Array(token, groups) => { + val part: Either[String, DefaultObjectAccessPermissionPart] = + Permission.ObjectAccess + .fromToken(token) + .toRight("No valid Object Access token") + .flatMap { permission => + Chunk + .fromIterable(groups.split(',')) + .map(GroupIri.from) + .foldLeft[Either[String, Chunk[GroupIri]]](Right(Chunk.empty)) { + case (Left(as), Left(a)) => Left(as + "; " + a) + case (Left(as), _) => Left(as) + case (_, Left(a)) => Left(a) + case (Right(bs), Right(b)) => Right(bs :+ b) + } + .flatMap(NonEmptyChunk.fromChunk(_).toRight(s"No groupIris found for $permission")) + .map(DefaultObjectAccessPermissionPart(permission, _)) + } + ZIO.fromEither(part).mapError(ConversionError.apply) + } + case _ => ZIO.fail(ConversionError("Invalid hasPermission pattern")) + } + } override def toTriples(entity: DefaultObjectAccessPermission): TriplePattern = { val id = Rdf.iri(entity.id.value) - id.isA(Vocabulary.KnoraAdmin.DefaultObjectAccessPermission) + val pat: TriplePattern = id + .isA(Vocabulary.KnoraAdmin.DefaultObjectAccessPermission) .andHas(Vocabulary.KnoraAdmin.forProject, Rdf.iri(entity.forProject.value)) + .andHas(Vocabulary.KnoraBase.hasPermissions, toStringLiteral(entity.permission)) + + entity.forWhat match { + case ForWhat.Group(g) => pat.andHas(Vocabulary.KnoraAdmin.forGroup, Rdf.iri(g.value)) + case ForWhat.ResourceClass(rc) => pat.andHas(Vocabulary.KnoraAdmin.forResourceClass, Rdf.iri(rc.value)) + case ForWhat.Property(p) => pat.andHas(Vocabulary.KnoraAdmin.forProperty, Rdf.iri(p.value)) + case ForWhat.ResourceClassAndProperty(rc, p) => + pat + .andHas(Vocabulary.KnoraAdmin.forResourceClass, Rdf.iri(rc.value)) + .andHas(Vocabulary.KnoraAdmin.forProperty, Rdf.iri(p.value)) + } + } + + private def toStringLiteral(permissions: Chunk[DefaultObjectAccessPermissionPart]): String = { + def withOutPrefixExpansion(str: String) = str.replace(KnoraAdminPrefixExpansion, KnoraAdminPrefix) + permissions + .map(p => s"${p.permission.token} ${p.groups.map(_.value).map(withOutPrefixExpansion).mkString(",")}") + .mkString(permissionsDelimiter.toString) } } From 274fd4a9cf5f363c96e171ce1f89b72c6920dfb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Kleinb=C3=B6lting?= Date: Tue, 18 Jun 2024 10:30:22 +0200 Subject: [PATCH 4/9] fix: Use knora-admin: prefix for storing permission literal for AdministrativePermission --- .../AdministrativePermissionRepoLive.scala | 9 +- .../service/PermissionLiteralParser.scala | 5 + .../triplestore/api/TriplestoreService.scala | 4 +- ...AdministrativePermissionRepoLiveSpec.scala | 91 +++++++++++++++++++ 4 files changed, 105 insertions(+), 4 deletions(-) create mode 100644 webapi/src/main/scala/org/knora/webapi/slice/admin/repo/service/PermissionLiteralParser.scala create mode 100644 webapi/src/test/scala/org/knora/webapi/slice/admin/repo/service/AdministrativePermissionRepoLiveSpec.scala diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/repo/service/AdministrativePermissionRepoLive.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/repo/service/AdministrativePermissionRepoLive.scala index 2a1e96e6a6..88b3f3e108 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/admin/repo/service/AdministrativePermissionRepoLive.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/repo/service/AdministrativePermissionRepoLive.scala @@ -15,8 +15,9 @@ import zio.NonEmptyChunk import zio.Task import zio.ZIO import zio.ZLayer - import org.knora.webapi.messages.OntologyConstants.KnoraAdmin +import org.knora.webapi.messages.OntologyConstants.KnoraAdmin.KnoraAdminPrefix +import org.knora.webapi.messages.OntologyConstants.KnoraAdmin.KnoraAdminPrefixExpansion import org.knora.webapi.messages.OntologyConstants.KnoraBase import org.knora.webapi.slice.admin.AdminConstants.permissionsDataNamedGraph import org.knora.webapi.slice.admin.domain.model.AdministrativePermission @@ -125,14 +126,16 @@ object AdministrativePermissionRepoLive { .andHas(Vocabulary.KnoraAdmin.forProject, Rdf.iri(entity.forProject.value)) .andHas(Vocabulary.KnoraBase.hasPermissions, toStringLiteral(entity.permissions)) } - private def toStringLiteral(permissions: Chunk[AdministrativePermissionPart]): String = + private def toStringLiteral(permissions: Chunk[AdministrativePermissionPart]): String = { + def useKnoraAdminPrefix(str: String) = str.replace(KnoraAdminPrefixExpansion, KnoraAdminPrefix) permissions.map { case AdministrativePermissionPart.Simple(permission) => permission.token case AdministrativePermissionPart.ResourceCreateRestricted(iris) => s"${Permission.Administrative.ProjectResourceCreateRestricted.token} ${iris.map(_.value).mkString(",")}" case AdministrativePermissionPart.ProjectAdminGroupRestricted(groups) => - s"${Permission.Administrative.ProjectAdminGroupRestricted.token} ${groups.map(_.value).mkString(",")}" + s"${Permission.Administrative.ProjectAdminGroupRestricted.token} ${groups.map(_.value).map(useKnoraAdminPrefix).mkString(",")}" }.mkString(permissionsDelimiter.toString) + } } val layer = ZLayer.succeed(mapper) >>> ZLayer.derive[AdministrativePermissionRepoLive] diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/repo/service/PermissionLiteralParser.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/repo/service/PermissionLiteralParser.scala new file mode 100644 index 0000000000..a714b7e9e3 --- /dev/null +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/repo/service/PermissionLiteralParser.scala @@ -0,0 +1,5 @@ +package org.knora.webapi.slice.admin.repo.service + +final class PermissionLiteralParser { + +} diff --git a/webapi/src/main/scala/org/knora/webapi/store/triplestore/api/TriplestoreService.scala b/webapi/src/main/scala/org/knora/webapi/store/triplestore/api/TriplestoreService.scala index 2f6c001248..acbe1c4824 100644 --- a/webapi/src/main/scala/org/knora/webapi/store/triplestore/api/TriplestoreService.scala +++ b/webapi/src/main/scala/org/knora/webapi/store/triplestore/api/TriplestoreService.scala @@ -8,11 +8,11 @@ package org.knora.webapi.store.triplestore.api import org.eclipse.rdf4j.sparqlbuilder.core.query.ConstructQuery import org.eclipse.rdf4j.sparqlbuilder.core.query.InsertDataQuery import org.eclipse.rdf4j.sparqlbuilder.core.query.ModifyQuery +import org.eclipse.rdf4j.sparqlbuilder.core.query.SelectQuery import play.twirl.api.TxtFormat import zio.* import java.nio.file.Path - import org.knora.webapi.messages.store.triplestoremessages.* import org.knora.webapi.messages.util.rdf.QuadFormat import org.knora.webapi.messages.util.rdf.SparqlSelectResult @@ -57,6 +57,7 @@ trait TriplestoreService { * @return A [[SparqlSelectResult]]. */ def query(sparql: Select): Task[SparqlSelectResult] + final def select(sparql: SelectQuery): Task[SparqlSelectResult] = query(Select(sparql)) /** * Performs a SPARQL update operation, i.e. an INSERT or DELETE query. @@ -169,6 +170,7 @@ object TriplestoreService { case class Select(sparql: String, override val timeout: SparqlTimeout = SparqlTimeout.Standard) extends SparqlQuery object Select { + def apply(sparql: SelectQuery): Select = Select(sparql.getQueryString) def apply(sparql: TxtFormat.Appendable): Select = Select(sparql.toString) def gravsearch(sparql: TxtFormat.Appendable): Select = Select.gravsearch(sparql.toString) diff --git a/webapi/src/test/scala/org/knora/webapi/slice/admin/repo/service/AdministrativePermissionRepoLiveSpec.scala b/webapi/src/test/scala/org/knora/webapi/slice/admin/repo/service/AdministrativePermissionRepoLiveSpec.scala new file mode 100644 index 0000000000..d9911da793 --- /dev/null +++ b/webapi/src/test/scala/org/knora/webapi/slice/admin/repo/service/AdministrativePermissionRepoLiveSpec.scala @@ -0,0 +1,91 @@ +package org.knora.webapi.slice.admin.repo.service + +import org.eclipse.rdf4j.sparqlbuilder.core.SparqlBuilder.`var` as variable +import org.eclipse.rdf4j.sparqlbuilder.core.query.Queries +import org.eclipse.rdf4j.sparqlbuilder.core.query.SelectQuery +import org.eclipse.rdf4j.sparqlbuilder.rdf.Rdf +import org.knora.webapi.messages.OntologyConstants.KnoraAdmin.KnoraAdminPrefix +import org.knora.webapi.messages.OntologyConstants.KnoraAdmin.KnoraAdminPrefixExpansion +import org.knora.webapi.messages.StringFormatter +import org.knora.webapi.slice.admin.domain.model.AdministrativePermission +import org.knora.webapi.slice.admin.domain.model.AdministrativePermissionPart +import org.knora.webapi.slice.admin.domain.model.AdministrativePermissionRepo +import org.knora.webapi.slice.admin.domain.model.GroupIri +import org.knora.webapi.slice.admin.domain.model.KnoraProject.ProjectIri +import org.knora.webapi.slice.admin.domain.model.KnoraProject.Shortcode +import org.knora.webapi.slice.admin.domain.model.Permission.Administrative.* +import org.knora.webapi.slice.admin.domain.model.PermissionIri +import org.knora.webapi.slice.admin.repo.rdf.Vocabulary +import org.knora.webapi.slice.resourceinfo.domain.InternalIri +import org.knora.webapi.store.triplestore.api.TriplestoreService +import org.knora.webapi.store.triplestore.api.TriplestoreServiceInMemory +import zio.Chunk +import zio.ZIO +import zio.test.* + +object AdministrativePermissionRepoLiveSpec extends ZIOSpecDefault { + private val repo = ZIO.serviceWithZIO[AdministrativePermissionRepo] + private val db = ZIO.serviceWithZIO[TriplestoreService] + private val shortcode = Shortcode.unsafeFrom("0001") + private val groupIri = GroupIri.makeNew(shortcode) + private val projectIri = ProjectIri.makeNew + private def permission(permissions: Chunk[AdministrativePermissionPart]) = + AdministrativePermission(PermissionIri.makeNew(shortcode), groupIri, projectIri, permissions) + + private val simpleAdminPermission = permission( + Chunk(AdministrativePermissionPart.Simple.unsafeFrom(ProjectResourceCreateAll)), + ) + private val complexAdminPermission = permission( + Chunk( + AdministrativePermissionPart.ProjectAdminGroupRestricted(Chunk(groupIri, GroupIri.makeNew(shortcode))), + AdministrativePermissionPart.ResourceCreateRestricted( + Chunk(InternalIri("https://example.org/1"), InternalIri("https://example.org/2")), + ), + ), + ) + + val spec = suite("AdministrativePermissionRepoLive")( + test("should save and find") { + for { + saved <- repo(_.save(simpleAdminPermission)) + found <- repo(_.findById(saved.id)) + } yield assertTrue(found.contains(saved), saved == simpleAdminPermission) + }, + test("should handle complex permission parts") { + val expected = complexAdminPermission + for { + saved <- repo(_.save(expected)) + found <- repo(_.findById(saved.id)) + } yield assertTrue(found.contains(saved), saved == expected) + }, + test("should delete") { + val expected = complexAdminPermission + for { + saved <- repo(_.save(expected)) + foundAfterSave <- repo(_.findById(saved.id)).map(_.nonEmpty) + _ <- repo(_.delete(expected)) + notfoundAfterDelete <- repo(_.findById(saved.id)).map(_.isEmpty) + } yield assertTrue(foundAfterSave, notfoundAfterDelete) + }, + test("should write valid permission literal with knora-admin: prefix") { + val expected = permission( + Chunk( + AdministrativePermissionPart.ProjectAdminGroupRestricted( + Chunk(GroupIri.unsafeFrom(KnoraAdminPrefixExpansion + "Creator")), + ), + ), + ) + def query(id: PermissionIri): SelectQuery = { + val hasPermissionsLiteral = variable("lit") + Queries + .SELECT() + .select(hasPermissionsLiteral) + .where(Rdf.iri(id.value).has(Vocabulary.KnoraBase.hasPermissions, hasPermissionsLiteral)) + } + for { + saved <- repo(_.save(expected)) + res <- db(_.select(query(saved.id))) + } yield assertTrue(res.getFirst("lit").head.contains(KnoraAdminPrefix + "Creator")) + }, + ).provide(AdministrativePermissionRepoLive.layer, TriplestoreServiceInMemory.emptyLayer, StringFormatter.test) +} From 4a831e4f841f0e7b3ec72161661ce7f58d51102f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Kleinb=C3=B6lting?= Date: Tue, 18 Jun 2024 11:16:08 +0200 Subject: [PATCH 5/9] fix: Use knora-admin: Prefix in hasPermissions string literal of AdministrativePermission --- .../AdministrativePermissionRepoLive.scala | 1 + .../service/PermissionLiteralParser.scala | 4 +--- .../triplestore/api/TriplestoreService.scala | 1 + ...AdministrativePermissionRepoLiveSpec.scala | 21 +++++++++++++------ 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/repo/service/AdministrativePermissionRepoLive.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/repo/service/AdministrativePermissionRepoLive.scala index 88b3f3e108..3300ac4075 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/admin/repo/service/AdministrativePermissionRepoLive.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/repo/service/AdministrativePermissionRepoLive.scala @@ -15,6 +15,7 @@ import zio.NonEmptyChunk import zio.Task import zio.ZIO import zio.ZLayer + import org.knora.webapi.messages.OntologyConstants.KnoraAdmin import org.knora.webapi.messages.OntologyConstants.KnoraAdmin.KnoraAdminPrefix import org.knora.webapi.messages.OntologyConstants.KnoraAdmin.KnoraAdminPrefixExpansion diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/repo/service/PermissionLiteralParser.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/repo/service/PermissionLiteralParser.scala index a714b7e9e3..5603c6de05 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/admin/repo/service/PermissionLiteralParser.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/repo/service/PermissionLiteralParser.scala @@ -1,5 +1,3 @@ package org.knora.webapi.slice.admin.repo.service -final class PermissionLiteralParser { - -} +final class PermissionLiteralParser {} diff --git a/webapi/src/main/scala/org/knora/webapi/store/triplestore/api/TriplestoreService.scala b/webapi/src/main/scala/org/knora/webapi/store/triplestore/api/TriplestoreService.scala index acbe1c4824..2cfcc5affe 100644 --- a/webapi/src/main/scala/org/knora/webapi/store/triplestore/api/TriplestoreService.scala +++ b/webapi/src/main/scala/org/knora/webapi/store/triplestore/api/TriplestoreService.scala @@ -13,6 +13,7 @@ import play.twirl.api.TxtFormat import zio.* import java.nio.file.Path + import org.knora.webapi.messages.store.triplestoremessages.* import org.knora.webapi.messages.util.rdf.QuadFormat import org.knora.webapi.messages.util.rdf.SparqlSelectResult diff --git a/webapi/src/test/scala/org/knora/webapi/slice/admin/repo/service/AdministrativePermissionRepoLiveSpec.scala b/webapi/src/test/scala/org/knora/webapi/slice/admin/repo/service/AdministrativePermissionRepoLiveSpec.scala index d9911da793..e1cccf6347 100644 --- a/webapi/src/test/scala/org/knora/webapi/slice/admin/repo/service/AdministrativePermissionRepoLiveSpec.scala +++ b/webapi/src/test/scala/org/knora/webapi/slice/admin/repo/service/AdministrativePermissionRepoLiveSpec.scala @@ -4,7 +4,10 @@ import org.eclipse.rdf4j.sparqlbuilder.core.SparqlBuilder.`var` as variable import org.eclipse.rdf4j.sparqlbuilder.core.query.Queries import org.eclipse.rdf4j.sparqlbuilder.core.query.SelectQuery import org.eclipse.rdf4j.sparqlbuilder.rdf.Rdf -import org.knora.webapi.messages.OntologyConstants.KnoraAdmin.KnoraAdminPrefix +import zio.Chunk +import zio.ZIO +import zio.test.* + import org.knora.webapi.messages.OntologyConstants.KnoraAdmin.KnoraAdminPrefixExpansion import org.knora.webapi.messages.StringFormatter import org.knora.webapi.slice.admin.domain.model.AdministrativePermission @@ -19,9 +22,6 @@ import org.knora.webapi.slice.admin.repo.rdf.Vocabulary import org.knora.webapi.slice.resourceinfo.domain.InternalIri import org.knora.webapi.store.triplestore.api.TriplestoreService import org.knora.webapi.store.triplestore.api.TriplestoreServiceInMemory -import zio.Chunk -import zio.ZIO -import zio.test.* object AdministrativePermissionRepoLiveSpec extends ZIOSpecDefault { private val repo = ZIO.serviceWithZIO[AdministrativePermissionRepo] @@ -70,8 +70,13 @@ object AdministrativePermissionRepoLiveSpec extends ZIOSpecDefault { test("should write valid permission literal with knora-admin: prefix") { val expected = permission( Chunk( + AdministrativePermissionPart.Simple.unsafeFrom(ProjectResourceCreateAll), AdministrativePermissionPart.ProjectAdminGroupRestricted( - Chunk(GroupIri.unsafeFrom(KnoraAdminPrefixExpansion + "Creator")), + Chunk( + GroupIri.unsafeFrom(KnoraAdminPrefixExpansion + "Creator"), + GroupIri.unsafeFrom(KnoraAdminPrefixExpansion + "UnknownUser"), + groupIri, + ), ), ), ) @@ -85,7 +90,11 @@ object AdministrativePermissionRepoLiveSpec extends ZIOSpecDefault { for { saved <- repo(_.save(expected)) res <- db(_.select(query(saved.id))) - } yield assertTrue(res.getFirst("lit").head.contains(KnoraAdminPrefix + "Creator")) + } yield assertTrue( + res + .getFirst("lit") + .head == s"ProjectResourceCreateAllPermission|ProjectAdminGroupRestrictedPermission knora-admin:Creator,knora-admin:UnknownUser,${groupIri.value}", + ) }, ).provide(AdministrativePermissionRepoLive.layer, TriplestoreServiceInMemory.emptyLayer, StringFormatter.test) } From 74a3227bc29c36e42fe8306ace03504fb9b154a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Kleinb=C3=B6lting?= Date: Tue, 18 Jun 2024 11:34:06 +0200 Subject: [PATCH 6/9] Move the respective properties of the `DefaultObjectAccessPermission` into the companion object and extract fold method. --- .../org/knora/webapi/ProjectEraseIT.scala | 4 +- .../DefaultObjectAccessPermissionRepo.scala | 49 +++++++++++-------- ...DefaultObjectAccessPermissionService.scala | 4 +- ...efaultObjectAccessPermissionRepoLive.scala | 27 +++++----- 4 files changed, 47 insertions(+), 37 deletions(-) diff --git a/integration/src/test/scala/org/knora/webapi/ProjectEraseIT.scala b/integration/src/test/scala/org/knora/webapi/ProjectEraseIT.scala index ba7ecdef14..dfd542331a 100644 --- a/integration/src/test/scala/org/knora/webapi/ProjectEraseIT.scala +++ b/integration/src/test/scala/org/knora/webapi/ProjectEraseIT.scala @@ -24,10 +24,10 @@ import org.knora.webapi.slice.admin.api.GroupsRequests.GroupCreateRequest import org.knora.webapi.slice.admin.api.UsersEndpoints.Requests.UserCreateRequest import org.knora.webapi.slice.admin.api.model.ProjectsEndpointsRequestsAndResponses.ProjectCreateRequest import org.knora.webapi.slice.admin.domain.model.AdministrativePermissionPart -import org.knora.webapi.slice.admin.domain.model.DefaultObjectAccessPermissionPart +import org.knora.webapi.slice.admin.domain.model.DefaultObjectAccessPermission.DefaultObjectAccessPermissionPart +import org.knora.webapi.slice.admin.domain.model.DefaultObjectAccessPermission.ForWhat.Group import org.knora.webapi.slice.admin.domain.model.Email import org.knora.webapi.slice.admin.domain.model.FamilyName -import org.knora.webapi.slice.admin.domain.model.ForWhat.Group import org.knora.webapi.slice.admin.domain.model.GivenName import org.knora.webapi.slice.admin.domain.model.GroupDescriptions import org.knora.webapi.slice.admin.domain.model.GroupIri diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/model/DefaultObjectAccessPermissionRepo.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/model/DefaultObjectAccessPermissionRepo.scala index f0473ae676..3dcbbec2bd 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/model/DefaultObjectAccessPermissionRepo.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/model/DefaultObjectAccessPermissionRepo.scala @@ -9,6 +9,8 @@ import zio.Chunk import zio.NonEmptyChunk import zio.Task +import org.knora.webapi.slice.admin.domain.model.DefaultObjectAccessPermission.DefaultObjectAccessPermissionPart +import org.knora.webapi.slice.admin.domain.model.DefaultObjectAccessPermission.ForWhat import org.knora.webapi.slice.admin.domain.model.KnoraProject.ProjectIri import org.knora.webapi.slice.admin.repo.service.EntityWithId import org.knora.webapi.slice.common.repo.service.CrudRepository @@ -21,28 +23,33 @@ final case class DefaultObjectAccessPermission( permission: Chunk[DefaultObjectAccessPermissionPart], ) extends EntityWithId[PermissionIri] -enum ForWhat { - case Group(iri: GroupIri) - case ResourceClass(iri: InternalIri) - case Property(iri: InternalIri) - case ResourceClassAndProperty(resourceClass: InternalIri, property: InternalIri) +object DefaultObjectAccessPermission { + enum ForWhat { + case Group(iri: GroupIri) + case ResourceClass(iri: InternalIri) + case Property(iri: InternalIri) + case ResourceClassAndProperty(resourceClass: InternalIri, property: InternalIri) + } + object ForWhat { + def from( + group: Option[GroupIri], + resourceClass: Option[InternalIri], + property: Option[InternalIri], + ): Either[String, ForWhat] = + (group, resourceClass, property) match { + case (None, Some(rc: InternalIri), Some(p: InternalIri)) => Right(ResourceClassAndProperty(rc, p)) + case (None, None, Some(p: InternalIri)) => Right(Property(p)) + case (None, Some(rc: InternalIri), None) => Right(ResourceClass(rc)) + case (Some(g: GroupIri), None, None) => Right(Group(g)) + case _ => Left(s"Invalid combination of group $group resourceClass $resourceClass and property $property.") + } + } + + final case class DefaultObjectAccessPermissionPart( + permission: Permission.ObjectAccess, + groups: NonEmptyChunk[GroupIri], + ) } -object ForWhat { - def from( - group: Option[GroupIri], - resourceClass: Option[InternalIri], - property: Option[InternalIri], - ): Either[String, ForWhat] = - (group, resourceClass, property) match { - case (None, Some(rc: InternalIri), Some(p: InternalIri)) => Right(ResourceClassAndProperty(rc, p)) - case (None, None, Some(p: InternalIri)) => Right(Property(p)) - case (None, Some(rc: InternalIri), None) => Right(ResourceClass(rc)) - case (Some(g: GroupIri), None, None) => Right(Group(g)) - case _ => Left(s"Invalid combination of group $group resourceClass $resourceClass and property $property.") - } -} - -final case class DefaultObjectAccessPermissionPart(permission: Permission.ObjectAccess, groups: NonEmptyChunk[GroupIri]) trait DefaultObjectAccessPermissionRepo extends CrudRepository[DefaultObjectAccessPermission, PermissionIri] { diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/DefaultObjectAccessPermissionService.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/DefaultObjectAccessPermissionService.scala index 67ca54e747..70e5d97062 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/DefaultObjectAccessPermissionService.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/DefaultObjectAccessPermissionService.scala @@ -9,9 +9,9 @@ import zio.Task import zio.ZLayer import org.knora.webapi.slice.admin.domain.model.DefaultObjectAccessPermission -import org.knora.webapi.slice.admin.domain.model.DefaultObjectAccessPermissionPart +import org.knora.webapi.slice.admin.domain.model.DefaultObjectAccessPermission.DefaultObjectAccessPermissionPart +import org.knora.webapi.slice.admin.domain.model.DefaultObjectAccessPermission.ForWhat import org.knora.webapi.slice.admin.domain.model.DefaultObjectAccessPermissionRepo -import org.knora.webapi.slice.admin.domain.model.ForWhat import org.knora.webapi.slice.admin.domain.model.KnoraProject import org.knora.webapi.slice.admin.domain.model.KnoraProject.ProjectIri import org.knora.webapi.slice.admin.domain.model.PermissionIri diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/repo/service/DefaultObjectAccessPermissionRepoLive.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/repo/service/DefaultObjectAccessPermissionRepoLive.scala index a04ede63b3..df5fdab531 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/admin/repo/service/DefaultObjectAccessPermissionRepoLive.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/repo/service/DefaultObjectAccessPermissionRepoLive.scala @@ -22,12 +22,12 @@ import org.knora.webapi.messages.OntologyConstants.KnoraAdmin.KnoraAdminPrefixEx import org.knora.webapi.messages.OntologyConstants.KnoraBase import org.knora.webapi.slice.admin.AdminConstants.permissionsDataNamedGraph import org.knora.webapi.slice.admin.domain.model.DefaultObjectAccessPermission -import org.knora.webapi.slice.admin.domain.model.DefaultObjectAccessPermissionPart +import org.knora.webapi.slice.admin.domain.model.DefaultObjectAccessPermission.DefaultObjectAccessPermissionPart +import org.knora.webapi.slice.admin.domain.model.DefaultObjectAccessPermission.ForWhat +import org.knora.webapi.slice.admin.domain.model.DefaultObjectAccessPermission.ForWhat.Group +import org.knora.webapi.slice.admin.domain.model.DefaultObjectAccessPermission.ForWhat.ResourceClass +import org.knora.webapi.slice.admin.domain.model.DefaultObjectAccessPermission.ForWhat.ResourceClassAndProperty import org.knora.webapi.slice.admin.domain.model.DefaultObjectAccessPermissionRepo -import org.knora.webapi.slice.admin.domain.model.ForWhat -import org.knora.webapi.slice.admin.domain.model.ForWhat.Group -import org.knora.webapi.slice.admin.domain.model.ForWhat.ResourceClass -import org.knora.webapi.slice.admin.domain.model.ForWhat.ResourceClassAndProperty import org.knora.webapi.slice.admin.domain.model.GroupIri import org.knora.webapi.slice.admin.domain.model.KnoraProject.ProjectIri import org.knora.webapi.slice.admin.domain.model.Permission @@ -82,7 +82,14 @@ object DefaultObjectAccessPermissionRepoLive { parsedPermissions <- parsePermission(permissionStr) } yield parsedPermissions - private def parsePermission(permission: String): IO[RdfError, Chunk[DefaultObjectAccessPermissionPart]] = + private def parsePermission(permission: String): IO[RdfError, Chunk[DefaultObjectAccessPermissionPart]] = { + def collectAllValidOrAllErrors(acc: Either[String, Chunk[GroupIri]], next: Either[String, GroupIri]) = + (acc, next) match { + case (Right(vs), Right(v)) => Right(vs :+ v) + case (Left(es), Left(e)) => Left(es + "; " + e) + case (Left(es), _) => Left(es) + case (_, Left(e)) => Left(e) + } ZIO .foreach(Chunk.fromIterable(permission.split(permissionsDelimiter).map(_.trim))) { token => token.split(' ') match { @@ -95,12 +102,7 @@ object DefaultObjectAccessPermissionRepoLive { Chunk .fromIterable(groups.split(',')) .map(GroupIri.from) - .foldLeft[Either[String, Chunk[GroupIri]]](Right(Chunk.empty)) { - case (Left(as), Left(a)) => Left(as + "; " + a) - case (Left(as), _) => Left(as) - case (_, Left(a)) => Left(a) - case (Right(bs), Right(b)) => Right(bs :+ b) - } + .foldLeft[Either[String, Chunk[GroupIri]]](Right(Chunk.empty))(collectAllValidOrAllErrors) .flatMap(NonEmptyChunk.fromChunk(_).toRight(s"No groupIris found for $permission")) .map(DefaultObjectAccessPermissionPart(permission, _)) } @@ -109,6 +111,7 @@ object DefaultObjectAccessPermissionRepoLive { case _ => ZIO.fail(ConversionError("Invalid hasPermission pattern")) } } + } override def toTriples(entity: DefaultObjectAccessPermission): TriplePattern = { val id = Rdf.iri(entity.id.value) From 6d222d5b6c089cc6ffa784a6703dc3dbc379dbb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Kleinb=C3=B6lting?= Date: Tue, 18 Jun 2024 11:45:12 +0200 Subject: [PATCH 7/9] test naming --- .../repo/service/AdministrativePermissionRepoLiveSpec.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapi/src/test/scala/org/knora/webapi/slice/admin/repo/service/AdministrativePermissionRepoLiveSpec.scala b/webapi/src/test/scala/org/knora/webapi/slice/admin/repo/service/AdministrativePermissionRepoLiveSpec.scala index e1cccf6347..6b1dadc5b1 100644 --- a/webapi/src/test/scala/org/knora/webapi/slice/admin/repo/service/AdministrativePermissionRepoLiveSpec.scala +++ b/webapi/src/test/scala/org/knora/webapi/slice/admin/repo/service/AdministrativePermissionRepoLiveSpec.scala @@ -67,7 +67,7 @@ object AdministrativePermissionRepoLiveSpec extends ZIOSpecDefault { notfoundAfterDelete <- repo(_.findById(saved.id)).map(_.isEmpty) } yield assertTrue(foundAfterSave, notfoundAfterDelete) }, - test("should write valid permission literal with knora-admin: prefix") { + test("should write valid permission literal with the 'knora-admin:' prefix") { val expected = permission( Chunk( AdministrativePermissionPart.Simple.unsafeFrom(ProjectResourceCreateAll), From 94d78d2f2d11ca50b88a5d1a621d43edc3b000f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Kleinb=C3=B6lting?= Date: Tue, 18 Jun 2024 12:01:01 +0200 Subject: [PATCH 8/9] add test for DEfaulObjectAccessPermissionRepoLive --- ...ltObjectAccessPermissionRepoLiveSpec.scala | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 webapi/src/test/scala/org/knora/webapi/slice/admin/repo/service/DefaultObjectAccessPermissionRepoLiveSpec.scala diff --git a/webapi/src/test/scala/org/knora/webapi/slice/admin/repo/service/DefaultObjectAccessPermissionRepoLiveSpec.scala b/webapi/src/test/scala/org/knora/webapi/slice/admin/repo/service/DefaultObjectAccessPermissionRepoLiveSpec.scala new file mode 100644 index 0000000000..093c271ddb --- /dev/null +++ b/webapi/src/test/scala/org/knora/webapi/slice/admin/repo/service/DefaultObjectAccessPermissionRepoLiveSpec.scala @@ -0,0 +1,92 @@ +package org.knora.webapi.slice.admin.repo.service + +import org.eclipse.rdf4j.sparqlbuilder.core.SparqlBuilder.`var` as variable +import org.eclipse.rdf4j.sparqlbuilder.core.query.Queries +import org.eclipse.rdf4j.sparqlbuilder.core.query.SelectQuery +import org.eclipse.rdf4j.sparqlbuilder.rdf.Rdf +import zio.Chunk +import zio.NonEmptyChunk +import zio.ZIO +import zio.test.* + +import org.knora.webapi.messages.OntologyConstants.KnoraAdmin.KnoraAdminPrefixExpansion +import org.knora.webapi.messages.StringFormatter +import org.knora.webapi.slice.admin.domain.model.DefaultObjectAccessPermission +import org.knora.webapi.slice.admin.domain.model.DefaultObjectAccessPermission.DefaultObjectAccessPermissionPart +import org.knora.webapi.slice.admin.domain.model.DefaultObjectAccessPermission.ForWhat +import org.knora.webapi.slice.admin.domain.model.DefaultObjectAccessPermissionRepo +import org.knora.webapi.slice.admin.domain.model.GroupIri +import org.knora.webapi.slice.admin.domain.model.KnoraProject.ProjectIri +import org.knora.webapi.slice.admin.domain.model.KnoraProject.Shortcode +import org.knora.webapi.slice.admin.domain.model.Permission.ObjectAccess.* +import org.knora.webapi.slice.admin.domain.model.PermissionIri +import org.knora.webapi.slice.admin.repo.rdf.Vocabulary +import org.knora.webapi.slice.resourceinfo.domain.InternalIri +import org.knora.webapi.store.triplestore.api.TriplestoreService +import org.knora.webapi.store.triplestore.api.TriplestoreServiceInMemory + +object DefaultObjectAccessPermissionRepoLiveSpec extends ZIOSpecDefault { + private val repo = ZIO.serviceWithZIO[DefaultObjectAccessPermissionRepo] + private val db = ZIO.serviceWithZIO[TriplestoreService] + private val shortcode = Shortcode.unsafeFrom("0001") + private val groupIri = GroupIri.makeNew(shortcode) + private val projectIri = ProjectIri.makeNew + + private def permission(forWhat: ForWhat, permissions: Chunk[DefaultObjectAccessPermissionPart]) = + DefaultObjectAccessPermission(PermissionIri.makeNew(shortcode), projectIri, forWhat, permissions) + + private val expected = permission( + ForWhat.ResourceClassAndProperty(InternalIri("https://example.com/rc"), InternalIri("https://example.com/p")), + Chunk( + DefaultObjectAccessPermissionPart(RestrictedView, NonEmptyChunk(groupIri)), + DefaultObjectAccessPermissionPart(View, NonEmptyChunk(GroupIri.makeNew(shortcode), GroupIri.makeNew(shortcode))), + ), + ) + + val spec = suite("AdministrativePermissionRepoLive")( + test("should save and find") { + for { + saved <- repo(_.save(expected)) + found <- repo(_.findById(saved.id)) + } yield assertTrue(found.contains(saved), saved == expected) + }, + test("should delete") { + for { + saved <- repo(_.save(expected)) + foundAfterSave <- repo(_.findById(saved.id)).map(_.nonEmpty) + _ <- repo(_.delete(expected)) + notfoundAfterDelete <- repo(_.findById(saved.id)).map(_.isEmpty) + } yield assertTrue(foundAfterSave, notfoundAfterDelete) + }, + test("should write valid permission literal with the 'knora-admin:' prefix") { + val expected = permission( + ForWhat.Group(groupIri), + Chunk( + DefaultObjectAccessPermissionPart(RestrictedView, NonEmptyChunk(groupIri)), + DefaultObjectAccessPermissionPart( + View, + NonEmptyChunk( + GroupIri.unsafeFrom(KnoraAdminPrefixExpansion + "Creator"), + GroupIri.unsafeFrom(KnoraAdminPrefixExpansion + "UnknownUser"), + ), + ), + ), + ) + def query(id: PermissionIri): SelectQuery = { + val hasPermissionsLiteral = variable("lit") + Queries + .SELECT() + .select(hasPermissionsLiteral) + .where(Rdf.iri(id.value).has(Vocabulary.KnoraBase.hasPermissions, hasPermissionsLiteral)) + } + for { + saved <- repo(_.save(expected)) + res <- db(_.select(query(saved.id))) + } yield assertTrue( + res + .getFirst("lit") + .head == s"RV ${groupIri.value}|V knora-admin:Creator,knora-admin:UnknownUser", + ) + }, + ).provide(DefaultObjectAccessPermissionRepoLive.layer, TriplestoreServiceInMemory.emptyLayer, StringFormatter.test) +} From 646c67286eb75ce1a8d660f73fa29ec0781706a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Kleinb=C3=B6lting?= Date: Tue, 18 Jun 2024 15:51:52 +0200 Subject: [PATCH 9/9] cleanup --- .../slice/admin/repo/service/PermissionLiteralParser.scala | 3 --- .../repo/service/AdministrativePermissionRepoLiveSpec.scala | 5 +++++ .../service/DefaultObjectAccessPermissionRepoLiveSpec.scala | 5 +++++ 3 files changed, 10 insertions(+), 3 deletions(-) delete mode 100644 webapi/src/main/scala/org/knora/webapi/slice/admin/repo/service/PermissionLiteralParser.scala diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/repo/service/PermissionLiteralParser.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/repo/service/PermissionLiteralParser.scala deleted file mode 100644 index 5603c6de05..0000000000 --- a/webapi/src/main/scala/org/knora/webapi/slice/admin/repo/service/PermissionLiteralParser.scala +++ /dev/null @@ -1,3 +0,0 @@ -package org.knora.webapi.slice.admin.repo.service - -final class PermissionLiteralParser {} diff --git a/webapi/src/test/scala/org/knora/webapi/slice/admin/repo/service/AdministrativePermissionRepoLiveSpec.scala b/webapi/src/test/scala/org/knora/webapi/slice/admin/repo/service/AdministrativePermissionRepoLiveSpec.scala index 6b1dadc5b1..fed05be65f 100644 --- a/webapi/src/test/scala/org/knora/webapi/slice/admin/repo/service/AdministrativePermissionRepoLiveSpec.scala +++ b/webapi/src/test/scala/org/knora/webapi/slice/admin/repo/service/AdministrativePermissionRepoLiveSpec.scala @@ -1,3 +1,8 @@ +/* + * Copyright © 2021 - 2024 Swiss National Data and Service Center for the Humanities and/or DaSCH Service Platform contributors. + * SPDX-License-Identifier: Apache-2.0 + */ + package org.knora.webapi.slice.admin.repo.service import org.eclipse.rdf4j.sparqlbuilder.core.SparqlBuilder.`var` as variable diff --git a/webapi/src/test/scala/org/knora/webapi/slice/admin/repo/service/DefaultObjectAccessPermissionRepoLiveSpec.scala b/webapi/src/test/scala/org/knora/webapi/slice/admin/repo/service/DefaultObjectAccessPermissionRepoLiveSpec.scala index 093c271ddb..cbe5e1a1e1 100644 --- a/webapi/src/test/scala/org/knora/webapi/slice/admin/repo/service/DefaultObjectAccessPermissionRepoLiveSpec.scala +++ b/webapi/src/test/scala/org/knora/webapi/slice/admin/repo/service/DefaultObjectAccessPermissionRepoLiveSpec.scala @@ -1,3 +1,8 @@ +/* + * Copyright © 2021 - 2024 Swiss National Data and Service Center for the Humanities and/or DaSCH Service Platform contributors. + * SPDX-License-Identifier: Apache-2.0 + */ + package org.knora.webapi.slice.admin.repo.service import org.eclipse.rdf4j.sparqlbuilder.core.SparqlBuilder.`var` as variable