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 944a4f2f10..eb322f1066 100644 --- a/integration/src/test/scala/org/knora/webapi/core/LayersTest.scala +++ b/integration/src/test/scala/org/knora/webapi/core/LayersTest.scala @@ -29,6 +29,7 @@ import org.knora.webapi.responders.v2.ontology.OntologyHelpersLive import org.knora.webapi.routing._ import org.knora.webapi.slice.admin.api._ import org.knora.webapi.slice.admin.api.service.MaintenanceRestService +import org.knora.webapi.slice.admin.api.service.PermissionsRestService import org.knora.webapi.slice.admin.api.service.ProjectADMRestService import org.knora.webapi.slice.admin.api.service.ProjectsADMRestServiceLive import org.knora.webapi.slice.admin.api.service.UsersADMRestServiceLive @@ -77,11 +78,12 @@ object LayersTest { with DspIngestTestContainer with SharedVolumes.Images - type CommonR0 = ActorSystem with AppConfigurations with SipiService with JwtService with StringFormatter + type CommonR0 = ActorSystem with AppConfigurations with JwtService with SipiService with StringFormatter type CommonR = ApiRoutes with AppRouter with Authenticator + with AuthorizationRestService with CacheService with CacheServiceRequestMessageHandler with CardinalityHandler @@ -102,10 +104,12 @@ object LayersTest { with MessageRelay with OntologyCache with OntologyHelpers + with OntologyInferencer with OntologyRepo with OntologyResponderV2 with PermissionUtilADM with PermissionsResponderADM + with PermissionsRestService with PredicateObjectMapper with ProjectADMRestService with ProjectADMService @@ -118,12 +122,10 @@ object LayersTest { with ResourceUtilV2 with ResourcesResponderV2 with RestCardinalityService - with RestPermissionService with RestResourceInfoService with SearchApiRoutes with SearchResponderV2 with SipiResponderADM - with OntologyInferencer with StandoffResponderV2 with StandoffTagUtilV2 with State @@ -139,6 +141,7 @@ object LayersTest { ApiRoutes.layer, AppRouter.layer, AuthenticatorLive.layer, + AuthorizationRestServiceLive.layer, BaseEndpoints.layer, CacheServiceInMemImpl.layer, CacheServiceRequestMessageHandlerLive.layer, @@ -170,7 +173,10 @@ object LayersTest { OntologyRepoLive.layer, OntologyResponderV2Live.layer, PermissionUtilADMLive.layer, + PermissionsEndpoints.layer, + PermissionsEndpointsHandlers.layer, PermissionsResponderADMLive.layer, + PermissionsRestService.layer, PredicateObjectMapper.layer, PredicateRepositoryLive.layer, ProjectADMServiceLive.layer, @@ -187,7 +193,6 @@ object LayersTest { ResourceUtilV2Live.layer, ResourcesResponderV2Live.layer, RestCardinalityServiceLive.layer, - RestPermissionServiceLive.layer, SearchApiRoutes.layer, SearchResponderV2Live.layer, SipiResponderADMLive.layer, diff --git a/integration/src/test/scala/org/knora/webapi/e2e/admin/PermissionsADME2ESpec.scala b/integration/src/test/scala/org/knora/webapi/e2e/admin/PermissionsADME2ESpec.scala index 3fcc8218e0..8a8fe977e3 100644 --- a/integration/src/test/scala/org/knora/webapi/e2e/admin/PermissionsADME2ESpec.scala +++ b/integration/src/test/scala/org/knora/webapi/e2e/admin/PermissionsADME2ESpec.scala @@ -5,10 +5,13 @@ package org.knora.webapi.e2e.admin -import org.apache.pekko +import org.apache.pekko.http.scaladsl.model._ +import org.apache.pekko.http.scaladsl.model.headers.BasicHttpCredentials import spray.json._ import zio.durationInt +import java.net.URLEncoder + import org.knora.webapi.E2ESpec import org.knora.webapi.e2e.ClientTestDataCollector import org.knora.webapi.e2e.TestDataFileContent @@ -18,11 +21,10 @@ import org.knora.webapi.messages.store.triplestoremessages.TriplestoreJsonProtoc import org.knora.webapi.sharedtestdata.SharedOntologyTestDataADM import org.knora.webapi.sharedtestdata.SharedTestDataADM import org.knora.webapi.sharedtestdata.SharedTestDataADM2 +import org.knora.webapi.slice.admin.domain.model.GroupIri +import org.knora.webapi.slice.admin.domain.model.KnoraProject.ProjectIri import org.knora.webapi.util.AkkaHttpUtils -import pekko.http.scaladsl.model._ -import pekko.http.scaladsl.model.headers.BasicHttpCredentials - /** * End-to-End (E2E) test specification for testing the 'v1/permissions' route. * @@ -38,9 +40,10 @@ class PermissionsADME2ESpec extends E2ESpec with TriplestoreJsonProtocol { "The Permissions Route ('admin/permissions')" when { "getting permissions" should { "return a group's administrative permission" in { - - val projectIri = java.net.URLEncoder.encode(SharedTestDataADM2.imagesProjectInfo.id, "utf-8") - val groupIri = java.net.URLEncoder.encode(OntologyConstants.KnoraAdmin.ProjectMember, "utf-8") + val projectIri = + URLEncoder.encode(ProjectIri.unsafeFrom(SharedTestDataADM2.imagesProjectInfo.id).value, "utf-8") + val groupIri = + URLEncoder.encode(GroupIri.unsafeFrom(OntologyConstants.KnoraAdmin.ProjectMember).value, "utf-8") val request = Get(baseApiUrl + s"/admin/permissions/ap/$projectIri/$groupIri") ~> addCredentials( BasicHttpCredentials(SharedTestDataADM.rootUser.email, SharedTestDataADM.testPass) ) @@ -68,7 +71,7 @@ class PermissionsADME2ESpec extends E2ESpec with TriplestoreJsonProtocol { } "return a project's administrative permissions" in { - val projectIri = java.net.URLEncoder.encode(SharedTestDataADM2.imagesProjectInfo.id, "utf-8") + val projectIri = URLEncoder.encode(SharedTestDataADM2.imagesProjectInfo.id, "utf-8") val request = Get(baseApiUrl + s"/admin/permissions/ap/$projectIri") ~> addCredentials( BasicHttpCredentials(SharedTestDataADM.rootUser.email, SharedTestDataADM.testPass) @@ -93,7 +96,7 @@ class PermissionsADME2ESpec extends E2ESpec with TriplestoreJsonProtocol { } "return a project's default object access permissions" in { - val projectIri = java.net.URLEncoder.encode(SharedTestDataADM2.imagesProjectInfo.id, "utf-8") + val projectIri = URLEncoder.encode(SharedTestDataADM2.imagesProjectInfo.id, "utf-8") val request = Get(baseApiUrl + s"/admin/permissions/doap/$projectIri") ~> addCredentials( BasicHttpCredentials(SharedTestDataADM.rootUser.email, SharedTestDataADM.testPass) @@ -118,7 +121,7 @@ class PermissionsADME2ESpec extends E2ESpec with TriplestoreJsonProtocol { } "return a project's all permissions" in { - val projectIri = java.net.URLEncoder.encode(SharedTestDataADM2.imagesProjectInfo.id, "utf-8") + val projectIri = URLEncoder.encode(SharedTestDataADM2.imagesProjectInfo.id, "utf-8") val request = Get(baseApiUrl + s"/admin/permissions/$projectIri") ~> addCredentials( BasicHttpCredentials(SharedTestDataADM.rootUser.email, SharedTestDataADM.testPass) @@ -419,7 +422,7 @@ class PermissionsADME2ESpec extends E2ESpec with TriplestoreJsonProtocol { "updating permissions" should { "change the group of an administrative permission" in { val permissionIri = "http://rdfh.ch/permissions/00FF/buxHAlz8SHuu0FuiLN_tKQ" - val encodedPermissionIri = java.net.URLEncoder.encode(permissionIri, "utf-8") + val encodedPermissionIri = URLEncoder.encode(permissionIri, "utf-8") val newGroupIri = "http://rdfh.ch/groups/00FF/images-reviewer" val updatePermissionGroup = s"""{ @@ -461,7 +464,7 @@ class PermissionsADME2ESpec extends E2ESpec with TriplestoreJsonProtocol { "change the group of a default object access permission" in { val permissionIri = "http://rdfh.ch/permissions/00FF/sdHG20U6RoiwSu8MeAT1vA" - val encodedPermissionIri = java.net.URLEncoder.encode(permissionIri, "utf-8") + val encodedPermissionIri = URLEncoder.encode(permissionIri, "utf-8") val newGroupIri = "http://rdfh.ch/groups/00FF/images-reviewer" val updatePermissionGroup = s"""{ @@ -504,7 +507,7 @@ class PermissionsADME2ESpec extends E2ESpec with TriplestoreJsonProtocol { "change the set of hasPermissions of an administrative permission" in { val permissionIri = "http://rdfh.ch/permissions/00FF/buxHAlz8SHuu0FuiLN_tKQ" - val encodedPermissionIri = java.net.URLEncoder.encode(permissionIri, "utf-8") + val encodedPermissionIri = URLEncoder.encode(permissionIri, "utf-8") val updateHasPermissions = s"""{ | "hasPermissions":[{"additionalInformation":null,"name":"ProjectAdminGroupAllPermission","permissionCode":null}] @@ -549,7 +552,7 @@ class PermissionsADME2ESpec extends E2ESpec with TriplestoreJsonProtocol { "change the set of hasPermissions of a default object access permission" in { val permissionIri = "http://rdfh.ch/permissions/00FF/Q3OMWyFqStGYK8EXmC7KhQ" - val encodedPermissionIri = java.net.URLEncoder.encode(permissionIri, "utf-8") + val encodedPermissionIri = URLEncoder.encode(permissionIri, "utf-8") val updateHasPermissions = s"""{ | "hasPermissions":[{"additionalInformation":"http://www.knora.org/ontology/knora-admin#ProjectMember","name":"D","permissionCode":7}] @@ -600,7 +603,7 @@ class PermissionsADME2ESpec extends E2ESpec with TriplestoreJsonProtocol { "change the resource class of a default object access permission" in { val permissionIri = "http://rdfh.ch/permissions/00FF/Q3OMWyFqStGYK8EXmC7KhQ" - val encodedPermissionIri = java.net.URLEncoder.encode(permissionIri, "utf-8") + val encodedPermissionIri = URLEncoder.encode(permissionIri, "utf-8") val resourceClassIri = SharedOntologyTestDataADM.INCUNABULA_BOOK_RESOURCE_CLASS val updateResourceClass = s"""{ @@ -646,7 +649,7 @@ class PermissionsADME2ESpec extends E2ESpec with TriplestoreJsonProtocol { "change the property of a default object access permission" in { val permissionIri = "http://rdfh.ch/permissions/00FF/Mck2xJDjQ_Oimi_9z4aFaA" - val encodedPermissionIri = java.net.URLEncoder.encode(permissionIri, "utf-8") + val encodedPermissionIri = URLEncoder.encode(permissionIri, "utf-8") val propertyClassIri = SharedOntologyTestDataADM.IMAGES_TITEL_PROPERTY val updateResourceClass = s"""{ @@ -691,7 +694,7 @@ class PermissionsADME2ESpec extends E2ESpec with TriplestoreJsonProtocol { "delete request" should { "erase a defaultObjectAccess permission" in { val permissionIri = customDOAPIri - val encodedPermissionIri = java.net.URLEncoder.encode(permissionIri, "utf-8") + val encodedPermissionIri = URLEncoder.encode(permissionIri, "utf-8") val request = Delete(baseApiUrl + s"/admin/permissions/" + encodedPermissionIri) ~> addCredentials( BasicHttpCredentials(SharedTestDataADM.rootUser.email, SharedTestDataADM.testPass) ) @@ -712,7 +715,7 @@ class PermissionsADME2ESpec extends E2ESpec with TriplestoreJsonProtocol { } "erase an administrative permission" in { val permissionIri = "http://rdfh.ch/permissions/00FF/buxHAlz8SHuu0FuiLN_tKQ" - val encodedPermissionIri = java.net.URLEncoder.encode(permissionIri, "utf-8") + val encodedPermissionIri = URLEncoder.encode(permissionIri, "utf-8") val request = Delete(baseApiUrl + s"/admin/permissions/" + encodedPermissionIri) ~> addCredentials( BasicHttpCredentials(SharedTestDataADM.rootUser.email, SharedTestDataADM.testPass) ) diff --git a/integration/src/test/scala/org/knora/webapi/messages/admin/responder/permissionsmessages/PermissionsMessagesADMSpec.scala b/integration/src/test/scala/org/knora/webapi/messages/admin/responder/permissionsmessages/PermissionsMessagesADMSpec.scala index e8a02e6d60..3ff4755f7f 100644 --- a/integration/src/test/scala/org/knora/webapi/messages/admin/responder/permissionsmessages/PermissionsMessagesADMSpec.scala +++ b/integration/src/test/scala/org/knora/webapi/messages/admin/responder/permissionsmessages/PermissionsMessagesADMSpec.scala @@ -15,9 +15,13 @@ import org.knora.webapi.messages.OntologyConstants import org.knora.webapi.messages.OntologyConstants.KnoraAdmin.AdministrativePermissionAbbreviations import org.knora.webapi.messages.OntologyConstants.KnoraBase.EntityPermissionAbbreviations import org.knora.webapi.messages.admin.responder.permissionsmessages.PermissionsMessagesUtilADM.PermissionTypeAndCodes +import org.knora.webapi.responders.admin.PermissionsResponderADM +import org.knora.webapi.routing.UnsafeZioRun import org.knora.webapi.sharedtestdata.SharedOntologyTestDataADM._ import org.knora.webapi.sharedtestdata.SharedTestDataADM2._ import org.knora.webapi.sharedtestdata._ +import org.knora.webapi.slice.admin.api.service.PermissionsRestService +import org.knora.webapi.util.ZioScalaTestUtil.assertFailsWithA /** * This spec is used to test subclasses of the [[PermissionsResponderRequestADM]] class. @@ -25,39 +29,6 @@ import org.knora.webapi.sharedtestdata._ class PermissionsMessagesADMSpec extends CoreSpec { "Administrative Permission Get Requests" should { - "return 'BadRequest' if the supplied project IRI for AdministrativePermissionsForProjectGetRequestADM is not valid" in { - val projectIri = "invalid-project-IRI" - val caught = intercept[BadRequestException]( - AdministrativePermissionsForProjectGetRequestADM( - projectIri = projectIri, - requestingUser = SharedTestDataADM.imagesUser01, - apiRequestID = UUID.randomUUID() - ) - ) - assert(caught.getMessage === s"Invalid project IRI $projectIri") - } - - "return 'ForbiddenException' if the user requesting AdministrativePermissionsForProjectGetRequestADM is not system or project Admin" in { - val caught = intercept[ForbiddenException]( - AdministrativePermissionsForProjectGetRequestADM( - projectIri = SharedTestDataADM.imagesProjectIri, - requestingUser = SharedTestDataADM.imagesUser02, - apiRequestID = UUID.randomUUID() - ) - ) - assert(caught.getMessage === "Administrative permission can only be queried by system and project admin.") - } - - "return 'ForbiddenException' if the user requesting AdministrativePermissionForProjectGroupGetRequestADM is not system or project Admin" in { - val caught = intercept[ForbiddenException]( - AdministrativePermissionForProjectGroupGetRequestADM( - projectIri = SharedTestDataADM.imagesProjectIri, - groupIri = OntologyConstants.KnoraAdmin.ProjectMember, - requestingUser = SharedTestDataADM.imagesUser02 - ) - ) - assert(caught.getMessage === "Administrative permission can only be queried by system and project admin.") - } "return 'BadRequest' if the supplied permission IRI for AdministrativePermissionForIriGetRequestADM is not valid" in { val permissionIri = "invalid-permission-IRI" @@ -97,69 +68,64 @@ class PermissionsMessagesADMSpec extends CoreSpec { "Administrative Permission Create Requests" should { "return 'BadRequest' if the supplied project IRI for AdministrativePermissionCreateRequestADM is not valid" in { - val forProject = "invalid-project-IRI" - val caught = intercept[BadRequestException]( - AdministrativePermissionCreateRequestADM( - createRequest = CreateAdministrativePermissionAPIRequestADM( - forProject = forProject, + val exit = UnsafeZioRun.run( + PermissionsRestService.createAdministrativePermission( + CreateAdministrativePermissionAPIRequestADM( + forProject = "invalid-project-IRI", forGroup = OntologyConstants.KnoraAdmin.ProjectMember, hasPermissions = Set(PermissionADM.ProjectAdminAllPermission) - ).prepareHasPermissions, - requestingUser = SharedTestDataADM.imagesUser01, - apiRequestID = UUID.randomUUID() + ), + SharedTestDataADM.imagesUser01 ) ) - assert(caught.getMessage === s"Invalid project IRI $forProject") + assertFailsWithA[BadRequestException](exit, "Project IRI is invalid.") } "return 'BadRequest' if the supplied group IRI for AdministrativePermissionCreateRequestADM is not valid" in { val groupIri = "invalid-group-iri" - val caught = intercept[BadRequestException]( - AdministrativePermissionCreateRequestADM( - createRequest = CreateAdministrativePermissionAPIRequestADM( - forProject = SharedTestDataADM.anythingProjectIri, + val exit = UnsafeZioRun.run( + PermissionsRestService.createAdministrativePermission( + CreateAdministrativePermissionAPIRequestADM( + forProject = SharedTestDataADM.imagesProjectIri, forGroup = groupIri, hasPermissions = Set(PermissionADM.ProjectAdminAllPermission) - ).prepareHasPermissions, - requestingUser = SharedTestDataADM.imagesUser01, - apiRequestID = UUID.randomUUID() + ), + SharedTestDataADM.imagesUser01 ) ) - assert(caught.getMessage === s"Invalid group IRI $groupIri") + assertFailsWithA[BadRequestException](exit, s"Invalid group IRI $groupIri") } "return 'BadRequest' if the supplied permission IRI for AdministrativePermissionCreateRequestADM is not valid" in { val permissionIri = "invalid-permission-IRI" - val caught = intercept[BadRequestException]( - AdministrativePermissionCreateRequestADM( - createRequest = CreateAdministrativePermissionAPIRequestADM( + val exit = UnsafeZioRun.run( + PermissionsRestService.createAdministrativePermission( + CreateAdministrativePermissionAPIRequestADM( id = Some(permissionIri), forProject = SharedTestDataADM.imagesProjectIri, forGroup = OntologyConstants.KnoraAdmin.ProjectMember, hasPermissions = Set(PermissionADM.ProjectAdminAllPermission) - ).prepareHasPermissions, - requestingUser = SharedTestDataADM.imagesUser01, - apiRequestID = UUID.randomUUID() + ), + SharedTestDataADM.imagesUser01 ) ) - assert(caught.getMessage === s"Invalid permission IRI: $permissionIri.") + assertFailsWithA[BadRequestException](exit, s"Invalid permission IRI: $permissionIri.") } "throw 'BadRequest' for AdministrativePermissionCreateRequestADM if the supplied permission IRI contains bad UUID version" in { val permissionIRIWithUUIDVersion3 = "http://rdfh.ch/permissions/0001/Ul3IYhDMOQ2fyoVY0ePz0w" - val caught = intercept[BadRequestException]( - AdministrativePermissionCreateRequestADM( - createRequest = CreateAdministrativePermissionAPIRequestADM( + val exit = UnsafeZioRun.run( + PermissionsRestService.createAdministrativePermission( + CreateAdministrativePermissionAPIRequestADM( id = Some(permissionIRIWithUUIDVersion3), forProject = SharedTestDataADM.imagesProjectIri, forGroup = OntologyConstants.KnoraAdmin.ProjectMember, hasPermissions = Set(PermissionADM.ProjectAdminAllPermission) - ).prepareHasPermissions, - requestingUser = SharedTestDataADM.imagesUser01, - apiRequestID = UUID.randomUUID() + ), + SharedTestDataADM.imagesUser01 ) ) - assert(caught.getMessage === IriErrorMessages.UuidVersionInvalid) + assertFailsWithA[BadRequestException](exit, IriErrorMessages.UuidVersionInvalid) } "return 'BadRequest' if the no permissions supplied for AdministrativePermissionCreateRequestADM" in { @@ -171,51 +137,52 @@ class PermissionsMessagesADMSpec extends CoreSpec { permissionCode = None ) ) - val caught = intercept[BadRequestException]( - AdministrativePermissionCreateRequestADM( - createRequest = CreateAdministrativePermissionAPIRequestADM( + val exit = UnsafeZioRun.run( + PermissionsRestService.createAdministrativePermission( + CreateAdministrativePermissionAPIRequestADM( forProject = SharedTestDataADM.imagesProjectIri, forGroup = OntologyConstants.KnoraAdmin.ProjectMember, hasPermissions = hasPermissions - ).prepareHasPermissions, - requestingUser = SharedTestDataADM.imagesUser01, - apiRequestID = UUID.randomUUID() + ), + SharedTestDataADM.imagesUser01 ) ) - assert( - caught.getMessage === s"Invalid value for name parameter of hasPermissions: $invalidName, it should be one of " + + assertFailsWithA[BadRequestException]( + exit, + s"Invalid value for name parameter of hasPermissions: $invalidName, it should be one of " + s"${AdministrativePermissionAbbreviations.toString}" ) } "return 'BadRequest' if the a permissions supplied for AdministrativePermissionCreateRequestADM had invalid name" in { - val caught = intercept[BadRequestException]( - AdministrativePermissionCreateRequestADM( - createRequest = CreateAdministrativePermissionAPIRequestADM( + val exit = UnsafeZioRun.run( + PermissionsRestService.createAdministrativePermission( + CreateAdministrativePermissionAPIRequestADM( forProject = SharedTestDataADM.imagesProjectIri, forGroup = OntologyConstants.KnoraAdmin.ProjectMember, hasPermissions = Set.empty[PermissionADM] - ).prepareHasPermissions, - requestingUser = SharedTestDataADM.imagesUser01, - apiRequestID = UUID.randomUUID() + ), + SharedTestDataADM.imagesUser01 ) ) - assert(caught.getMessage === "Permissions needs to be supplied.") + assertFailsWithA[BadRequestException](exit, "Permissions needs to be supplied.") } "return 'ForbiddenException' if the user requesting AdministrativePermissionCreateRequestADM is not system or project admin" in { - val caught = intercept[ForbiddenException]( - AdministrativePermissionCreateRequestADM( - createRequest = CreateAdministrativePermissionAPIRequestADM( + val exit = UnsafeZioRun.run( + PermissionsRestService.createAdministrativePermission( + CreateAdministrativePermissionAPIRequestADM( forProject = SharedTestDataADM.imagesProjectIri, forGroup = OntologyConstants.KnoraAdmin.ProjectMember, hasPermissions = Set(PermissionADM.ProjectAdminAllPermission) - ).prepareHasPermissions, - requestingUser = SharedTestDataADM.imagesReviewerUser, - apiRequestID = UUID.randomUUID() + ), + SharedTestDataADM.imagesReviewerUser ) ) - assert(caught.getMessage === "A new administrative permission can only be added by system or project admin.") + assertFailsWithA[ForbiddenException]( + exit, + "You are logged in with username 'images-reviewer-user', but only a system administrator or project administrator has permissions for this operation." + ) } } @@ -331,29 +298,6 @@ class PermissionsMessagesADMSpec extends CoreSpec { ) } - "return 'BadRequest' if the supplied project IRI for DefaultObjectAccessPermissionsForProjectGetRequestADM is not valid" in { - val projectIri = "invalid-project-IRI" - val caught = intercept[BadRequestException]( - DefaultObjectAccessPermissionsForProjectGetRequestADM( - projectIri = projectIri, - requestingUser = SharedTestDataADM.imagesUser01, - apiRequestID = UUID.randomUUID() - ) - ) - assert(caught.getMessage === s"Invalid project IRI $projectIri") - } - - "return 'ForbiddenException' if the user requesting DefaultObjectAccessPermissionsForProjectGetRequestADM is not System or project Admin" in { - val caught = intercept[ForbiddenException]( - DefaultObjectAccessPermissionsForProjectGetRequestADM( - projectIri = SharedTestDataADM.imagesProjectIri, - requestingUser = SharedTestDataADM.imagesUser02, - apiRequestID = UUID.randomUUID() - ) - ) - assert(caught.getMessage === "Default object access permissions can only be queried by system and project admin.") - } - "return 'BadRequest' if the supplied permission IRI for DefaultObjectAccessPermissionForIriGetRequestADM is not valid" in { val permissionIri = "invalid-permission-IRI" val caught = intercept[BadRequestException]( @@ -488,108 +432,92 @@ class PermissionsMessagesADMSpec extends CoreSpec { "Default Object Access Permission Create Requests" should { "return 'BadRequest' if the supplied project IRI for DefaultObjectAccessPermissionCreateRequestADM is not valid" in { val forProject = "invalid-project-IRI" - val caught = intercept[BadRequestException]( - DefaultObjectAccessPermissionCreateRequestADM( - createRequest = CreateDefaultObjectAccessPermissionAPIRequestADM( + val exit = UnsafeZioRun.run( + PermissionsRestService.createDefaultObjectAccessPermission( + CreateDefaultObjectAccessPermissionAPIRequestADM( forProject = forProject, forGroup = Some(OntologyConstants.KnoraAdmin.ProjectMember), hasPermissions = Set(PermissionADM.changeRightsPermission(OntologyConstants.KnoraAdmin.ProjectMember)) - ).prepareHasPermissions, - requestingUser = SharedTestDataADM.imagesUser01, - apiRequestID = UUID.randomUUID() + ), + SharedTestDataADM.imagesUser01 ) ) - assert(caught.getMessage === s"Invalid project IRI $forProject") + assertFailsWithA[BadRequestException](exit, s"Project IRI is invalid.") } "return 'BadRequest' if the supplied group IRI for DefaultObjectAccessPermissionCreateRequestADM is not valid" in { val groupIri = "invalid-group-iri" - val caught = intercept[BadRequestException]( - DefaultObjectAccessPermissionCreateRequestADM( - createRequest = CreateDefaultObjectAccessPermissionAPIRequestADM( - forProject = SharedTestDataADM.anythingProjectIri, + val exit = UnsafeZioRun.run( + PermissionsRestService.createDefaultObjectAccessPermission( + CreateDefaultObjectAccessPermissionAPIRequestADM( + forProject = SharedTestDataADM.imagesProjectIri, forGroup = Some(groupIri), hasPermissions = Set(PermissionADM.changeRightsPermission(OntologyConstants.KnoraAdmin.ProjectMember)) - ).prepareHasPermissions, - requestingUser = SharedTestDataADM.imagesUser01, - apiRequestID = UUID.randomUUID() + ), + SharedTestDataADM.imagesUser01 ) ) - assert(caught.getMessage === s"Invalid group IRI $groupIri") + assertFailsWithA[BadRequestException](exit, s"Invalid group IRI $groupIri") } "return 'BadRequest' if the supplied custom permission IRI for DefaultObjectAccessPermissionCreateRequestADM is not valid" in { val permissionIri = "invalid-permission-IRI" - val caught = intercept[BadRequestException]( - DefaultObjectAccessPermissionCreateRequestADM( - createRequest = CreateDefaultObjectAccessPermissionAPIRequestADM( + val exit = UnsafeZioRun.run( + PermissionsRestService.createDefaultObjectAccessPermission( + CreateDefaultObjectAccessPermissionAPIRequestADM( id = Some(permissionIri), - forProject = SharedTestDataADM.anythingProjectIri, + forProject = SharedTestDataADM.imagesProjectIri, forGroup = Some(OntologyConstants.KnoraAdmin.ProjectMember), hasPermissions = Set(PermissionADM.changeRightsPermission(OntologyConstants.KnoraAdmin.ProjectMember)) - ).prepareHasPermissions, - requestingUser = SharedTestDataADM.imagesUser01, - apiRequestID = UUID.randomUUID() + ), + SharedTestDataADM.imagesUser01 ) ) - assert(caught.getMessage === s"Invalid permission IRI: $permissionIri.") + assertFailsWithA[BadRequestException](exit, s"Invalid permission IRI: $permissionIri.") } "throw 'BadRequest' for DefaultObjectAccessPermissionCreateRequestADM if the supplied permission IRI contains bad UUID version" in { val permissionIRIWithUUIDVersion3 = "http://rdfh.ch/permissions/0001/Ul3IYhDMOQ2fyoVY0ePz0w" - val caught = intercept[BadRequestException]( - DefaultObjectAccessPermissionCreateRequestADM( - createRequest = CreateDefaultObjectAccessPermissionAPIRequestADM( + val exit = UnsafeZioRun.run( + PermissionsRestService.createDefaultObjectAccessPermission( + CreateDefaultObjectAccessPermissionAPIRequestADM( id = Some(permissionIRIWithUUIDVersion3), - forProject = SharedTestDataADM.anythingProjectIri, + forProject = SharedTestDataADM.imagesProjectIri, forGroup = Some(OntologyConstants.KnoraAdmin.ProjectMember), hasPermissions = Set(PermissionADM.changeRightsPermission(OntologyConstants.KnoraAdmin.ProjectMember)) - ).prepareHasPermissions, - requestingUser = SharedTestDataADM.imagesUser01, - apiRequestID = UUID.randomUUID() + ), + SharedTestDataADM.imagesUser01 ) ) - assert(caught.getMessage === IriErrorMessages.UuidVersionInvalid) + assertFailsWithA[BadRequestException](exit, IriErrorMessages.UuidVersionInvalid) } "return 'BadRequest' if the no permissions supplied for DefaultObjectAccessPermissionCreateRequestADM" in { - val caught = intercept[BadRequestException]( - DefaultObjectAccessPermissionCreateRequestADM( - createRequest = CreateDefaultObjectAccessPermissionAPIRequestADM( - forProject = SharedTestDataADM.anythingProjectIri, + val exit = UnsafeZioRun.run( + PermissionsRestService.createDefaultObjectAccessPermission( + CreateDefaultObjectAccessPermissionAPIRequestADM( + forProject = SharedTestDataADM.imagesProjectIri, forGroup = Some(SharedTestDataADM.thingSearcherGroup.id), hasPermissions = Set.empty[PermissionADM] - ).prepareHasPermissions, - requestingUser = SharedTestDataADM.anythingAdminUser, - apiRequestID = UUID.randomUUID() + ), + SharedTestDataADM.imagesUser01 ) ) - assert(caught.getMessage === "Permissions needs to be supplied.") + assertFailsWithA[BadRequestException](exit, "Permissions needs to be supplied.") } "not create a DefaultObjectAccessPermission for project and property if hasPermissions set contained permission with invalid name" in { - val invalidName = "invalid" val hasPermissions = Set( PermissionADM( - name = invalidName, + name = "invalid", additionalInformation = Some(OntologyConstants.KnoraAdmin.Creator), permissionCode = Some(8) ) ) - val caught = intercept[BadRequestException]( - DefaultObjectAccessPermissionCreateRequestADM( - createRequest = CreateDefaultObjectAccessPermissionAPIRequestADM( - forProject = SharedTestDataADM.imagesProjectIri, - forProperty = Some(SharedOntologyTestDataADM.IMAGES_TITEL_PROPERTY), - hasPermissions = hasPermissions - ).prepareHasPermissions, - requestingUser = SharedTestDataADM.anythingAdminUser, - apiRequestID = UUID.randomUUID() - ) - ) - assert( - caught.getMessage === - s"Invalid value for name parameter of hasPermissions: $invalidName, it should be one of " + + val exit = UnsafeZioRun.run(PermissionsResponderADM.verifyHasPermissionsDOAP(hasPermissions)) + assertFailsWithA[BadRequestException]( + exit, + "Invalid value for name parameter of hasPermissions: invalid, it should be one of " + s"${EntityPermissionAbbreviations.toString}" ) } @@ -603,20 +531,11 @@ class PermissionsMessagesADMSpec extends CoreSpec { permissionCode = Some(invalidCode) ) ) - val caught = intercept[BadRequestException]( - DefaultObjectAccessPermissionCreateRequestADM( - createRequest = CreateDefaultObjectAccessPermissionAPIRequestADM( - forProject = SharedTestDataADM.imagesProjectIri, - forProperty = Some(SharedOntologyTestDataADM.IMAGES_TITEL_PROPERTY), - hasPermissions = hasPermissions - ).prepareHasPermissions, - requestingUser = SharedTestDataADM.anythingAdminUser, - apiRequestID = UUID.randomUUID() - ) - ) - assert( - caught.getMessage === - s"Invalid value for permissionCode parameter of hasPermissions: $invalidCode, it should be one of " + + + val exit = UnsafeZioRun.run(PermissionsResponderADM.verifyHasPermissionsDOAP(hasPermissions)) + assertFailsWithA[BadRequestException]( + exit, + s"Invalid value for permissionCode parameter of hasPermissions: $invalidCode, it should be one of " + s"${PermissionTypeAndCodes.values.toString}" ) } @@ -631,18 +550,12 @@ class PermissionsMessagesADMSpec extends CoreSpec { permissionCode = Some(code) ) ) - val caught = intercept[BadRequestException]( - DefaultObjectAccessPermissionCreateRequestADM( - createRequest = CreateDefaultObjectAccessPermissionAPIRequestADM( - forProject = SharedTestDataADM.imagesProjectIri, - forProperty = Some(SharedOntologyTestDataADM.IMAGES_TITEL_PROPERTY), - hasPermissions = hasPermissions - ).prepareHasPermissions, - requestingUser = SharedTestDataADM.anythingAdminUser, - apiRequestID = UUID.randomUUID() - ) + + val exit = UnsafeZioRun.run(PermissionsResponderADM.verifyHasPermissionsDOAP(hasPermissions)) + assertFailsWithA[BadRequestException]( + exit, + s"Given permission code $code and permission name $name are not consistent." ) - assert(caught.getMessage === s"Given permission code $code and permission name $name are not consistent.") } "not create a DefaultObjectAccessPermission for project and property if hasPermissions set contained permission without any code or name" in { @@ -654,20 +567,11 @@ class PermissionsMessagesADMSpec extends CoreSpec { permissionCode = None ) ) - val caught = intercept[BadRequestException]( - DefaultObjectAccessPermissionCreateRequestADM( - createRequest = CreateDefaultObjectAccessPermissionAPIRequestADM( - forProject = SharedTestDataADM.imagesProjectIri, - forProperty = Some(SharedOntologyTestDataADM.IMAGES_TITEL_PROPERTY), - hasPermissions = hasPermissions - ).prepareHasPermissions, - requestingUser = SharedTestDataADM.anythingAdminUser, - apiRequestID = UUID.randomUUID() - ) - ) - assert( - caught.getMessage === - s"One of permission code or permission name must be provided for a default object access permission." + + val exit = UnsafeZioRun.run(PermissionsResponderADM.verifyHasPermissionsDOAP(hasPermissions)) + assertFailsWithA[BadRequestException]( + exit, + s"One of permission code or permission name must be provided for a default object access permission." ) } @@ -680,142 +584,108 @@ class PermissionsMessagesADMSpec extends CoreSpec { permissionCode = Some(8) ) ) - val caught = intercept[BadRequestException]( - DefaultObjectAccessPermissionCreateRequestADM( - createRequest = CreateDefaultObjectAccessPermissionAPIRequestADM( - forProject = SharedTestDataADM.imagesProjectIri, - forProperty = Some(SharedOntologyTestDataADM.IMAGES_TITEL_PROPERTY), - hasPermissions = hasPermissions - ).prepareHasPermissions, - requestingUser = SharedTestDataADM.anythingAdminUser, - apiRequestID = UUID.randomUUID() - ) - ) - assert( - caught.getMessage === - s"additionalInformation of a default object access permission type cannot be empty." + val exit = UnsafeZioRun.run(PermissionsResponderADM.verifyHasPermissionsDOAP(hasPermissions)) + assertFailsWithA[BadRequestException]( + exit, + s"additionalInformation of a default object access permission type cannot be empty." ) } "return 'ForbiddenException' if the user requesting DefaultObjectAccessPermissionCreateRequestADM is not system or project Admin" in { - val caught = intercept[ForbiddenException]( - DefaultObjectAccessPermissionCreateRequestADM( - createRequest = CreateDefaultObjectAccessPermissionAPIRequestADM( + val exit = UnsafeZioRun.run( + PermissionsRestService.createDefaultObjectAccessPermission( + CreateDefaultObjectAccessPermissionAPIRequestADM( forProject = SharedTestDataADM.anythingProjectIri, forGroup = Some(SharedTestDataADM.thingSearcherGroup.id), hasPermissions = Set(PermissionADM.restrictedViewPermission(SharedTestDataADM.thingSearcherGroup.id)) - ).prepareHasPermissions, - requestingUser = SharedTestDataADM.anythingUser2, - apiRequestID = UUID.randomUUID() + ), + SharedTestDataADM.anythingUser2 ) ) - assert(caught.getMessage === "A new default object access permission can only be added by a system admin.") + assertFailsWithA[ForbiddenException]( + exit, + "You are logged in with username 'anything.user02', but only a system administrator or project administrator has permissions for this operation." + ) } "return 'BadRequest' if the both group and resource class are supplied for DefaultObjectAccessPermissionCreateRequestADM" in { - val caught = intercept[BadRequestException]( - DefaultObjectAccessPermissionCreateRequestADM( - createRequest = CreateDefaultObjectAccessPermissionAPIRequestADM( + val exit = UnsafeZioRun.run( + PermissionsRestService.createDefaultObjectAccessPermission( + CreateDefaultObjectAccessPermissionAPIRequestADM( forProject = anythingProjectIri, forGroup = Some(OntologyConstants.KnoraAdmin.ProjectMember), forResourceClass = Some(ANYTHING_THING_RESOURCE_CLASS_LocalHost), hasPermissions = Set(PermissionADM.changeRightsPermission(OntologyConstants.KnoraAdmin.ProjectMember)) - ).prepareHasPermissions, - requestingUser = SharedTestDataADM.imagesUser01, - apiRequestID = UUID.randomUUID() + ), + SharedTestDataADM.rootUser ) ) - assert(caught.getMessage === "Not allowed to supply groupIri and resourceClassIri together.") + assertFailsWithA[BadRequestException](exit, "Not allowed to supply groupIri and resourceClassIri together.") } "return 'BadRequest' if the both group and property are supplied for DefaultObjectAccessPermissionCreateRequestADM" in { - val caught = intercept[BadRequestException]( - DefaultObjectAccessPermissionCreateRequestADM( - createRequest = CreateDefaultObjectAccessPermissionAPIRequestADM( + val exit = UnsafeZioRun.run( + PermissionsRestService.createDefaultObjectAccessPermission( + CreateDefaultObjectAccessPermissionAPIRequestADM( forProject = anythingProjectIri, forGroup = Some(OntologyConstants.KnoraAdmin.ProjectMember), forProperty = Some(ANYTHING_HasDate_PROPERTY_LocalHost), hasPermissions = Set(PermissionADM.changeRightsPermission(OntologyConstants.KnoraAdmin.ProjectMember)) - ).prepareHasPermissions, - requestingUser = SharedTestDataADM.imagesUser01, - apiRequestID = UUID.randomUUID() + ), + SharedTestDataADM.rootUser ) ) - assert(caught.getMessage === "Not allowed to supply groupIri and propertyIri together.") + assertFailsWithA[BadRequestException](exit, "Not allowed to supply groupIri and propertyIri together.") } "return 'BadRequest' if propertyIri supplied for DefaultObjectAccessPermissionCreateRequestADM is not valid" in { - val caught = intercept[BadRequestException]( - DefaultObjectAccessPermissionCreateRequestADM( - createRequest = CreateDefaultObjectAccessPermissionAPIRequestADM( + val exit = UnsafeZioRun.run( + PermissionsRestService.createDefaultObjectAccessPermission( + CreateDefaultObjectAccessPermissionAPIRequestADM( forProject = anythingProjectIri, forProperty = Some(SharedTestDataADM.customValueIRI), hasPermissions = Set(PermissionADM.changeRightsPermission(OntologyConstants.KnoraAdmin.ProjectMember)) - ).prepareHasPermissions, - requestingUser = SharedTestDataADM.imagesUser01, - apiRequestID = UUID.randomUUID() + ), + SharedTestDataADM.rootUser ) ) - assert(caught.getMessage === s"Invalid property IRI: ${SharedTestDataADM.customValueIRI}") + assertFailsWithA[BadRequestException](exit, s"Invalid property IRI: ${SharedTestDataADM.customValueIRI}") } "return 'BadRequest' if resourceClassIri supplied for DefaultObjectAccessPermissionCreateRequestADM is not valid" in { - val caught = intercept[BadRequestException]( - DefaultObjectAccessPermissionCreateRequestADM( - createRequest = CreateDefaultObjectAccessPermissionAPIRequestADM( + val exit = UnsafeZioRun.run( + PermissionsRestService.createDefaultObjectAccessPermission( + CreateDefaultObjectAccessPermissionAPIRequestADM( forProject = anythingProjectIri, forResourceClass = Some(ANYTHING_THING_RESOURCE_CLASS_LocalHost), hasPermissions = Set(PermissionADM.changeRightsPermission(OntologyConstants.KnoraAdmin.ProjectMember)) - ).prepareHasPermissions, - requestingUser = SharedTestDataADM.imagesUser01, - apiRequestID = UUID.randomUUID() + ), + SharedTestDataADM.rootUser ) ) - assert(caught.getMessage === s"Invalid resource class IRI: $ANYTHING_THING_RESOURCE_CLASS_LocalHost") + assertFailsWithA[BadRequestException]( + exit, + s"Invalid resource class IRI: $ANYTHING_THING_RESOURCE_CLASS_LocalHost" + ) } "return 'BadRequest' if neither a group, nor a resource class, nor a property is supplied for DefaultObjectAccessPermissionCreateRequestADM" in { - val caught = intercept[BadRequestException]( - DefaultObjectAccessPermissionCreateRequestADM( - createRequest = CreateDefaultObjectAccessPermissionAPIRequestADM( + val exit = UnsafeZioRun.run( + PermissionsRestService.createDefaultObjectAccessPermission( + CreateDefaultObjectAccessPermissionAPIRequestADM( forProject = anythingProjectIri, hasPermissions = Set(PermissionADM.changeRightsPermission(OntologyConstants.KnoraAdmin.ProjectMember)) - ).prepareHasPermissions, - requestingUser = SharedTestDataADM.imagesUser01, - apiRequestID = UUID.randomUUID() + ), + SharedTestDataADM.rootUser ) ) - assert( - caught.getMessage === "Either a group, a resource class, a property, or a combination of resource class and property must be given." + assertFailsWithA[BadRequestException]( + exit, + "Either a group, a resource class, a property, or a combination of resource class and property must be given." ) } } - "get all project permissions" should { - "return 'BadRequest' if the supplied project IRI for PermissionsForProjectGetRequestADM is not valid" in { - val projectIri = "invalid-project-IRI" - val caught = intercept[BadRequestException]( - PermissionsForProjectGetRequestADM( - projectIri = projectIri, - requestingUser = SharedTestDataADM.imagesUser01, - apiRequestID = UUID.randomUUID() - ) - ) - assert(caught.getMessage === s"Invalid project IRI $projectIri") - } - - "return 'ForbiddenException' if the user requesting PermissionsForProjectGetRequestADM is not system or project Admin" in { - val caught = intercept[ForbiddenException]( - PermissionsForProjectGetRequestADM( - projectIri = SharedTestDataADM.imagesProjectIri, - requestingUser = SharedTestDataADM.imagesUser02, - apiRequestID = UUID.randomUUID() - ) - ) - assert(caught.getMessage === "Permissions can only be queried by system and project admin.") - } - } - "querying the user's 'PermissionsDataADM' with 'hasPermissionFor'" should { "return true if the user is allowed to create a resource (root user)" in { @@ -912,129 +782,5 @@ class PermissionsMessagesADMSpec extends CoreSpec { ) assert(caught.getMessage === s"Invalid permission IRI: $permissionIri.") } - - "not update permission group if invalid permission IRI given" in { - val permissionIri = "invalid-permission-iri" - val newGroupIri = SharedTestDataADM.imagesReviewerGroup.id - val caught = intercept[BadRequestException]( - PermissionChangeGroupRequestADM( - permissionIri = permissionIri, - changePermissionGroupRequest = ChangePermissionGroupApiRequestADM(newGroupIri), - requestingUser = SharedTestDataADM.imagesUser02, - apiRequestID = UUID.randomUUID() - ) - ) - assert(caught.getMessage === s"Invalid permission IRI $permissionIri is given.") - } - - "not update permission group if invalid group IRI given" in { - val permissionIri = SharedPermissionsTestData.perm001_d1.iri - val newGroupIri = "invalid-group-iri" - val caught = intercept[BadRequestException]( - PermissionChangeGroupRequestADM( - permissionIri = permissionIri, - changePermissionGroupRequest = ChangePermissionGroupApiRequestADM(newGroupIri), - requestingUser = SharedTestDataADM.imagesUser02, - apiRequestID = UUID.randomUUID() - ) - ) - assert(caught.getMessage === s"Invalid IRI $newGroupIri is given.") - } - - "not update hasPermissions set of a permission if invalid permission IRI given" in { - val permissionIri = "invalid-permission-iri" - val hasPermissions = Set(PermissionADM.ProjectAdminAllPermission) - val caught = intercept[BadRequestException]( - PermissionChangeHasPermissionsRequestADM( - permissionIri = permissionIri, - changePermissionHasPermissionsRequest = ChangePermissionHasPermissionsApiRequestADM(hasPermissions), - requestingUser = SharedTestDataADM.imagesUser02, - apiRequestID = UUID.randomUUID() - ) - ) - assert(caught.getMessage === s"Invalid permission IRI $permissionIri is given.") - } - - "not update hasPermissions set of a permission if invalid empty set given" in { - val permissionIri = SharedPermissionsTestData.perm001_d1.iri - val hasPermissions = Set.empty[PermissionADM] - val caught = intercept[BadRequestException]( - PermissionChangeHasPermissionsRequestADM( - permissionIri = permissionIri, - changePermissionHasPermissionsRequest = ChangePermissionHasPermissionsApiRequestADM(hasPermissions), - requestingUser = SharedTestDataADM.imagesUser02, - apiRequestID = UUID.randomUUID() - ) - ) - assert(caught.getMessage === s"hasPermissions cannot be empty.") - } - - "not update resource class of a doap if invalid permission IRI given" in { - val permissionIri = "invalid-permission-iri" - val resourceClassIri = SharedOntologyTestDataADM.INCUNABULA_BOOK_RESOURCE_CLASS - val caught = intercept[BadRequestException]( - PermissionChangeResourceClassRequestADM( - permissionIri = permissionIri, - changePermissionResourceClassRequest = ChangePermissionResourceClassApiRequestADM(resourceClassIri), - requestingUser = SharedTestDataADM.imagesUser02, - apiRequestID = UUID.randomUUID() - ) - ) - assert(caught.getMessage === s"Invalid permission IRI $permissionIri is given.") - } - - "not update resource class of a doap if invalid resource class IRI is given" in { - val permissionIri = SharedPermissionsTestData.perm001_d1.iri - val resourceClassIri = "invalid-iri" - val caught = intercept[BadRequestException]( - PermissionChangeResourceClassRequestADM( - permissionIri = permissionIri, - changePermissionResourceClassRequest = ChangePermissionResourceClassApiRequestADM(resourceClassIri), - requestingUser = SharedTestDataADM.imagesUser02, - apiRequestID = UUID.randomUUID() - ) - ) - assert(caught.getMessage === s"Invalid resource class IRI $resourceClassIri is given.") - } - - "not update property of a doap if invalid permission IRI given" in { - val permissionIri = "invalid-permission-iri" - val propertyIri = SharedOntologyTestDataADM.IMAGES_TITEL_PROPERTY - val caught = intercept[BadRequestException]( - PermissionChangePropertyRequestADM( - permissionIri = permissionIri, - changePermissionPropertyRequest = ChangePermissionPropertyApiRequestADM(propertyIri), - requestingUser = SharedTestDataADM.imagesUser02, - apiRequestID = UUID.randomUUID() - ) - ) - assert(caught.getMessage === s"Invalid permission IRI $permissionIri is given.") - } - - "not update property of a doap if invalid property IRI is given" in { - val permissionIri = SharedPermissionsTestData.perm001_d1.iri - val propertyIri = "invalid-iri" - val caught = intercept[BadRequestException]( - PermissionChangePropertyRequestADM( - permissionIri = permissionIri, - changePermissionPropertyRequest = ChangePermissionPropertyApiRequestADM(propertyIri), - requestingUser = SharedTestDataADM.imagesUser02, - apiRequestID = UUID.randomUUID() - ) - ) - assert(caught.getMessage === s"Invalid property IRI $propertyIri is given.") - } - - "return 'BadRequest' if the supplied permission IRI for PermissionDeleteRequestADM is not valid" in { - val permissionIri = "invalid-permission-Iri" - val caught = intercept[BadRequestException]( - PermissionDeleteRequestADM( - permissionIri = permissionIri, - requestingUser = SharedTestDataADM.imagesUser01, - apiRequestID = UUID.randomUUID() - ) - ) - assert(caught.getMessage === s"Invalid permission IRI: $permissionIri.") - } } } diff --git a/integration/src/test/scala/org/knora/webapi/responders/admin/GroupsResponderADMSpec.scala b/integration/src/test/scala/org/knora/webapi/responders/admin/GroupsResponderADMSpec.scala index 2b237263c8..e2f2587493 100644 --- a/integration/src/test/scala/org/knora/webapi/responders/admin/GroupsResponderADMSpec.scala +++ b/integration/src/test/scala/org/knora/webapi/responders/admin/GroupsResponderADMSpec.scala @@ -5,7 +5,7 @@ package org.knora.webapi.responders.admin -import org.apache.pekko +import org.apache.pekko.actor.Status.Failure import java.util.UUID @@ -13,18 +13,16 @@ import dsp.errors.BadRequestException import dsp.errors.DuplicateValueException import dsp.errors.NotFoundException import dsp.valueobjects.Group._ -import dsp.valueobjects.Iri._ import dsp.valueobjects.V2 import org.knora.webapi._ import org.knora.webapi.messages.admin.responder.groupsmessages._ import org.knora.webapi.messages.admin.responder.usersmessages.UserInformationTypeADM import org.knora.webapi.messages.store.triplestoremessages.StringLiteralV2 import org.knora.webapi.sharedtestdata.SharedTestDataADM +import org.knora.webapi.slice.admin.domain.model.GroupIri import org.knora.webapi.slice.admin.domain.model.KnoraProject.ProjectIri import org.knora.webapi.util.MutableTestIri -import pekko.actor.Status.Failure - /** * This spec is used to test the messages received by the [[org.knora.webapi.responders.admin.GroupsResponderADMSpec]] actor. */ @@ -107,9 +105,7 @@ class GroupsResponderADMSpec extends CoreSpec { "return a 'DuplicateValueException' if the supplied group name is not unique" in { appActor ! GroupCreateRequestADM( createRequest = GroupCreatePayloadADM( - id = Some( - GroupIri.make(imagesReviewerGroup.id).fold(e => throw e.head, v => v) - ), + id = Some(GroupIri.unsafeFrom(imagesReviewerGroup.id)), name = GroupName.make("NewGroup").fold(e => throw e.head, v => v), descriptions = GroupDescriptions .make(Seq(V2.StringLiteralV2(value = "NewGroupDescription", language = Some("en")))) diff --git a/integration/src/test/scala/org/knora/webapi/responders/admin/PermissionsResponderADMSpec.scala b/integration/src/test/scala/org/knora/webapi/responders/admin/PermissionsResponderADMSpec.scala index 6d3e4a8cdd..120ca1b990 100644 --- a/integration/src/test/scala/org/knora/webapi/responders/admin/PermissionsResponderADMSpec.scala +++ b/integration/src/test/scala/org/knora/webapi/responders/admin/PermissionsResponderADMSpec.scala @@ -5,7 +5,9 @@ package org.knora.webapi.responders.admin -import org.apache.pekko +import org.apache.pekko.actor.Status.Failure +import org.apache.pekko.testkit.ImplicitSender +import zio.NonEmptyChunk import java.util.UUID import scala.collection.Map @@ -26,10 +28,15 @@ import org.knora.webapi.routing.UnsafeZioRun import org.knora.webapi.sharedtestdata.SharedOntologyTestDataADM import org.knora.webapi.sharedtestdata.SharedPermissionsTestData._ import org.knora.webapi.sharedtestdata.SharedTestDataADM +import org.knora.webapi.sharedtestdata.SharedTestDataADM.imagesProjectIri +import org.knora.webapi.sharedtestdata.SharedTestDataADM.imagesUser02 +import org.knora.webapi.sharedtestdata.SharedTestDataADM.incunabulaMemberUser +import org.knora.webapi.sharedtestdata.SharedTestDataADM.normalUser import org.knora.webapi.sharedtestdata.SharedTestDataADM2 - -import pekko.actor.Status.Failure -import pekko.testkit.ImplicitSender +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.PermissionIri +import org.knora.webapi.util.ZioScalaTestUtil.assertFailsWithA /** * This spec is used to test the [[PermissionsResponderADM]] actor. @@ -76,7 +83,7 @@ class PermissionsResponderADMSpec extends CoreSpec with ImplicitSender { appActor ! PermissionDataGetADM( projectIris = SharedTestDataADM2.multiuserUser.projects_info.keys.toSeq, groupIris = SharedTestDataADM2.multiuserUser.groups, - isInProjectAdminGroups = Seq(SharedTestDataADM.incunabulaProjectIri, SharedTestDataADM.imagesProjectIri), + isInProjectAdminGroups = Seq(SharedTestDataADM.incunabulaProjectIri, imagesProjectIri), isInSystemAdminGroup = false, requestingUser = KnoraSystemInstances.Users.SystemUser ) @@ -120,7 +127,7 @@ class PermissionsResponderADMSpec extends CoreSpec with ImplicitSender { appActor ! PermissionDataGetADM( projectIris = SharedTestDataADM2.imagesUser01.projects_info.keys.toSeq, groupIris = SharedTestDataADM2.imagesUser01.groups, - isInProjectAdminGroups = Seq(SharedTestDataADM.imagesProjectIri), + isInProjectAdminGroups = Seq(imagesProjectIri), isInSystemAdminGroup = false, requestingUser = KnoraSystemInstances.Users.SystemUser ) @@ -151,11 +158,8 @@ class PermissionsResponderADMSpec extends CoreSpec with ImplicitSender { } "ask for userAdministrativePermissionsGetADM" should { "return user's administrative permissions (helper method used in queries before)" in { - - val permissionsResponder = getService[PermissionsResponderADM] - val result: Map[IRI, Set[PermissionADM]] = UnsafeZioRun.runOrThrow( - permissionsResponder.userAdministrativePermissionsGetADM( + PermissionsResponderADM.userAdministrativePermissionsGetADM( multiuserUser.permissions.groupsPerProject ) ) @@ -166,25 +170,22 @@ class PermissionsResponderADMSpec extends CoreSpec with ImplicitSender { "ask about administrative permissions " should { "return all AdministrativePermissions for project" in { - appActor ! AdministrativePermissionsForProjectGetRequestADM( - projectIri = SharedTestDataADM.imagesProjectIri, - requestingUser = rootUser, - apiRequestID = UUID.randomUUID() + val result = UnsafeZioRun.runOrThrow( + PermissionsResponderADM.getPermissionsApByProjectIri(imagesProjectIri) ) - expectMsg( - AdministrativePermissionsForProjectGetResponseADM( - Seq(perm002_a1.p, perm002_a3.p, perm002_a2.p) - ) + result shouldEqual AdministrativePermissionsForProjectGetResponseADM( + Seq(perm002_a1.p, perm002_a3.p, perm002_a2.p) ) } "return AdministrativePermission for project and group" in { - appActor ! AdministrativePermissionForProjectGroupGetRequestADM( - projectIri = SharedTestDataADM.imagesProjectIri, - groupIri = OntologyConstants.KnoraAdmin.ProjectMember, - requestingUser = rootUser + val result = UnsafeZioRun.runOrThrow( + PermissionsResponderADM.getPermissionsApByProjectAndGroupIri( + imagesProjectIri, + OntologyConstants.KnoraAdmin.ProjectMember + ) ) - expectMsg(AdministrativePermissionGetResponseADM(perm002_a1.p)) + result shouldEqual AdministrativePermissionGetResponseADM(perm002_a1.p) } "return AdministrativePermission for IRI" in { @@ -214,44 +215,43 @@ class PermissionsResponderADMSpec extends CoreSpec with ImplicitSender { "asked to create an administrative permission" should { "fail and return a 'DuplicateValueException' when permission for project and group combination already exists" in { - appActor ! AdministrativePermissionCreateRequestADM( - createRequest = CreateAdministrativePermissionAPIRequestADM( - forProject = SharedTestDataADM.imagesProjectIri, - forGroup = OntologyConstants.KnoraAdmin.ProjectMember, - hasPermissions = Set(PermissionADM.ProjectResourceCreateAllPermission) - ).prepareHasPermissions, - requestingUser = rootUser, - apiRequestID = UUID.randomUUID() - ) - expectMsg( - Failure( - DuplicateValueException( - s"An administrative permission for project: '${SharedTestDataADM.imagesProjectIri}' and group: '${OntologyConstants.KnoraAdmin.ProjectMember}' combination already exists. " + - s"This permission currently has the scope '${PermissionUtilADM - .formatPermissionADMs(perm002_a1.p.hasPermissions, PermissionType.AP)}'. " + - s"Use its IRI ${perm002_a1.iri} to modify it, if necessary." - ) + val exit = UnsafeZioRun.run( + PermissionsResponderADM.createAdministrativePermission( + CreateAdministrativePermissionAPIRequestADM( + forProject = imagesProjectIri, + forGroup = OntologyConstants.KnoraAdmin.ProjectMember, + hasPermissions = Set(PermissionADM.ProjectResourceCreateAllPermission) + ), + rootUser, + UUID.randomUUID() ) ) + assertFailsWithA[DuplicateValueException]( + exit, + s"An administrative permission for project: '$imagesProjectIri' and group: '${OntologyConstants.KnoraAdmin.ProjectMember}' combination already exists. " + + s"This permission currently has the scope '${PermissionUtilADM + .formatPermissionADMs(perm002_a1.p.hasPermissions, PermissionType.AP)}'. " + + s"Use its IRI ${perm002_a1.iri} to modify it, if necessary." + ) } "create and return an administrative permission with a custom IRI" in { val customIri = "http://rdfh.ch/permissions/0001/24RD7QcoTKqEJKrDBE885Q" - appActor ! AdministrativePermissionCreateRequestADM( - createRequest = CreateAdministrativePermissionAPIRequestADM( - id = Some(customIri), - forProject = SharedTestDataADM.anythingProjectIri, - forGroup = SharedTestDataADM.thingSearcherGroup.id, - hasPermissions = Set(PermissionADM.ProjectResourceCreateAllPermission) - ).prepareHasPermissions, - requestingUser = rootUser, - apiRequestID = UUID.randomUUID() + val actual = UnsafeZioRun.runOrThrow( + PermissionsResponderADM.createAdministrativePermission( + CreateAdministrativePermissionAPIRequestADM( + id = Some(customIri), + forProject = SharedTestDataADM.anythingProjectIri, + forGroup = SharedTestDataADM.thingSearcherGroup.id, + hasPermissions = Set(PermissionADM.ProjectResourceCreateAllPermission) + ), + rootUser, + UUID.randomUUID() + ) ) - val received: AdministrativePermissionCreateResponseADM = - expectMsgType[AdministrativePermissionCreateResponseADM] - assert(received.administrativePermission.iri == customIri) - assert(received.administrativePermission.forProject == SharedTestDataADM.anythingProjectIri) - assert(received.administrativePermission.forGroup == SharedTestDataADM.thingSearcherGroup.id) + assert(actual.administrativePermission.iri == customIri) + assert(actual.administrativePermission.forProject == SharedTestDataADM.anythingProjectIri) + assert(actual.administrativePermission.forGroup == SharedTestDataADM.thingSearcherGroup.id) } "create and return an administrative permission even if irrelevant values were given for name and code of its permission" in { @@ -270,22 +270,22 @@ class PermissionsResponderADMSpec extends CoreSpec with ImplicitSender { permissionCode = None ) ) - appActor ! AdministrativePermissionCreateRequestADM( - createRequest = CreateAdministrativePermissionAPIRequestADM( - id = Some(customIri), - forProject = SharedTestDataADM.anythingProjectIri, - forGroup = OntologyConstants.KnoraAdmin.KnownUser, - hasPermissions = hasPermissions - ).prepareHasPermissions, - requestingUser = rootUser, - apiRequestID = UUID.randomUUID() + val actual = UnsafeZioRun.runOrThrow( + PermissionsResponderADM.createAdministrativePermission( + CreateAdministrativePermissionAPIRequestADM( + id = Some(customIri), + forProject = SharedTestDataADM.anythingProjectIri, + forGroup = OntologyConstants.KnoraAdmin.KnownUser, + hasPermissions = hasPermissions + ), + rootUser, + UUID.randomUUID() + ) ) - val received: AdministrativePermissionCreateResponseADM = - expectMsgType[AdministrativePermissionCreateResponseADM] - assert(received.administrativePermission.iri == customIri) - assert(received.administrativePermission.forGroup == knownUser) - assert(received.administrativePermission.forProject == SharedTestDataADM.anythingProjectIri) - assert(received.administrativePermission.hasPermissions.equals(expectedHasPermissions)) + assert(actual.administrativePermission.iri == customIri) + assert(actual.administrativePermission.forGroup == knownUser) + assert(actual.administrativePermission.forProject == SharedTestDataADM.anythingProjectIri) + assert(actual.administrativePermission.hasPermissions.equals(expectedHasPermissions)) } } @@ -332,17 +332,14 @@ class PermissionsResponderADMSpec extends CoreSpec with ImplicitSender { "ask to query about default object access permissions " should { "return all DefaultObjectAccessPermissions for project" in { - appActor ! DefaultObjectAccessPermissionsForProjectGetRequestADM( - projectIri = SharedTestDataADM.imagesProjectIri, - requestingUser = rootUser, - apiRequestID = UUID.randomUUID() - ) - - expectMsg( - DefaultObjectAccessPermissionsForProjectGetResponseADM( - defaultObjectAccessPermissions = Seq(perm002_d2.p, perm0003_a4.p, perm002_d1.p) + val actual = UnsafeZioRun.runOrThrow( + PermissionsResponderADM.getPermissionsDaopByProjectIri( + ProjectIri.unsafeFrom(imagesProjectIri) ) ) + actual shouldEqual DefaultObjectAccessPermissionsForProjectGetResponseADM( + Seq(perm002_d2.p, perm0003_a4.p, perm002_d1.p) + ) } "return DefaultObjectAccessPermission for IRI" in { @@ -424,39 +421,41 @@ class PermissionsResponderADMSpec extends CoreSpec with ImplicitSender { "ask to create a default object access permission" should { "create a DefaultObjectAccessPermission for project and group" in { - appActor ! DefaultObjectAccessPermissionCreateRequestADM( - createRequest = CreateDefaultObjectAccessPermissionAPIRequestADM( - forProject = SharedTestDataADM.anythingProjectIri, - forGroup = Some(SharedTestDataADM.thingSearcherGroup.id), - hasPermissions = Set(PermissionADM.restrictedViewPermission(SharedTestDataADM.thingSearcherGroup.id)) - ).prepareHasPermissions, - requestingUser = rootUser, - apiRequestID = UUID.randomUUID() + val actual = UnsafeZioRun.runOrThrow( + PermissionsResponderADM.createDefaultObjectAccessPermission( + CreateDefaultObjectAccessPermissionAPIRequestADM( + forProject = SharedTestDataADM.anythingProjectIri, + forGroup = Some(SharedTestDataADM.thingSearcherGroup.id), + hasPermissions = Set(PermissionADM.restrictedViewPermission(SharedTestDataADM.thingSearcherGroup.id)) + ), + rootUser, + UUID.randomUUID() + ) ) - val received: DefaultObjectAccessPermissionCreateResponseADM = - expectMsgType[DefaultObjectAccessPermissionCreateResponseADM] - assert(received.defaultObjectAccessPermission.forProject == SharedTestDataADM.anythingProjectIri) - assert(received.defaultObjectAccessPermission.forGroup.contains(SharedTestDataADM.thingSearcherGroup.id)) + + assert(actual.defaultObjectAccessPermission.forProject == SharedTestDataADM.anythingProjectIri) + assert(actual.defaultObjectAccessPermission.forGroup.contains(SharedTestDataADM.thingSearcherGroup.id)) assert( - received.defaultObjectAccessPermission.hasPermissions - .contains(PermissionADM.restrictedViewPermission(SharedTestDataADM.thingSearcherGroup.id)) + actual.defaultObjectAccessPermission.hasPermissions.contains( + PermissionADM.restrictedViewPermission(SharedTestDataADM.thingSearcherGroup.id) + ) ) } "create a DefaultObjectAccessPermission for project and group with custom IRI" in { val customIri = "http://rdfh.ch/permissions/0001/4PnSvolsTEa86KJ2EG76SQ" - appActor ! DefaultObjectAccessPermissionCreateRequestADM( - createRequest = CreateDefaultObjectAccessPermissionAPIRequestADM( - id = Some(customIri), - forProject = SharedTestDataADM.anythingProjectIri, - forGroup = Some(OntologyConstants.KnoraAdmin.UnknownUser), - hasPermissions = Set(PermissionADM.restrictedViewPermission(OntologyConstants.KnoraAdmin.UnknownUser)) - ).prepareHasPermissions, - requestingUser = rootUser, - apiRequestID = UUID.randomUUID() + val received = UnsafeZioRun.runOrThrow( + PermissionsResponderADM.createDefaultObjectAccessPermission( + createRequest = CreateDefaultObjectAccessPermissionAPIRequestADM( + id = Some(customIri), + forProject = SharedTestDataADM.anythingProjectIri, + forGroup = Some(OntologyConstants.KnoraAdmin.UnknownUser), + hasPermissions = Set(PermissionADM.restrictedViewPermission(OntologyConstants.KnoraAdmin.UnknownUser)) + ), + rootUser, + UUID.randomUUID() + ) ) - val received: DefaultObjectAccessPermissionCreateResponseADM = - expectMsgType[DefaultObjectAccessPermissionCreateResponseADM] assert(received.defaultObjectAccessPermission.iri == customIri) assert(received.defaultObjectAccessPermission.forGroup.contains(unknownUser)) assert(received.defaultObjectAccessPermission.forProject == SharedTestDataADM.anythingProjectIri) @@ -467,151 +466,147 @@ class PermissionsResponderADMSpec extends CoreSpec with ImplicitSender { } "create a DefaultObjectAccessPermission for project and resource class" in { - appActor ! DefaultObjectAccessPermissionCreateRequestADM( - createRequest = CreateDefaultObjectAccessPermissionAPIRequestADM( - forProject = SharedTestDataADM.imagesProjectIri, - forResourceClass = Some(SharedOntologyTestDataADM.IMAGES_BILD_RESOURCE_CLASS), - hasPermissions = Set(PermissionADM.modifyPermission(OntologyConstants.KnoraAdmin.KnownUser)) - ).prepareHasPermissions, - requestingUser = rootUser, - apiRequestID = UUID.randomUUID() + val actual = UnsafeZioRun.runOrThrow( + PermissionsResponderADM.createDefaultObjectAccessPermission( + CreateDefaultObjectAccessPermissionAPIRequestADM( + forProject = imagesProjectIri, + forResourceClass = Some(SharedOntologyTestDataADM.IMAGES_BILD_RESOURCE_CLASS), + hasPermissions = Set(PermissionADM.modifyPermission(OntologyConstants.KnoraAdmin.KnownUser)) + ), + rootUser, + UUID.randomUUID() + ) ) - val received: DefaultObjectAccessPermissionCreateResponseADM = - expectMsgType[DefaultObjectAccessPermissionCreateResponseADM] - assert(received.defaultObjectAccessPermission.forProject == SharedTestDataADM.imagesProjectIri) + assert(actual.defaultObjectAccessPermission.forProject == imagesProjectIri) assert( - received.defaultObjectAccessPermission.forResourceClass + actual.defaultObjectAccessPermission.forResourceClass .contains(SharedOntologyTestDataADM.IMAGES_BILD_RESOURCE_CLASS) ) assert( - received.defaultObjectAccessPermission.hasPermissions + actual.defaultObjectAccessPermission.hasPermissions .contains(PermissionADM.modifyPermission(knownUser)) ) - } "create a DefaultObjectAccessPermission for project and property" in { - appActor ! DefaultObjectAccessPermissionCreateRequestADM( - createRequest = CreateDefaultObjectAccessPermissionAPIRequestADM( - forProject = SharedTestDataADM.imagesProjectIri, - forProperty = Some(SharedOntologyTestDataADM.IMAGES_TITEL_PROPERTY), - hasPermissions = Set(PermissionADM.changeRightsPermission(OntologyConstants.KnoraAdmin.Creator)) - ).prepareHasPermissions, - requestingUser = rootUser, - apiRequestID = UUID.randomUUID() + val actual = UnsafeZioRun.runOrThrow( + PermissionsResponderADM.createDefaultObjectAccessPermission( + CreateDefaultObjectAccessPermissionAPIRequestADM( + forProject = imagesProjectIri, + forProperty = Some(SharedOntologyTestDataADM.IMAGES_TITEL_PROPERTY), + hasPermissions = Set(PermissionADM.changeRightsPermission(OntologyConstants.KnoraAdmin.Creator)) + ), + rootUser, + UUID.randomUUID() + ) ) - val received: DefaultObjectAccessPermissionCreateResponseADM = - expectMsgType[DefaultObjectAccessPermissionCreateResponseADM] - assert(received.defaultObjectAccessPermission.forProject == SharedTestDataADM.imagesProjectIri) + assert(actual.defaultObjectAccessPermission.forProject == imagesProjectIri) assert( - received.defaultObjectAccessPermission.forProperty.contains(SharedOntologyTestDataADM.IMAGES_TITEL_PROPERTY) + actual.defaultObjectAccessPermission.forProperty + .contains(SharedOntologyTestDataADM.IMAGES_TITEL_PROPERTY) ) assert( - received.defaultObjectAccessPermission.hasPermissions + actual.defaultObjectAccessPermission.hasPermissions .contains(PermissionADM.changeRightsPermission(creator)) ) } "fail and return a 'DuplicateValueException' when a doap permission for project and group combination already exists" in { - appActor ! DefaultObjectAccessPermissionCreateRequestADM( - createRequest = CreateDefaultObjectAccessPermissionAPIRequestADM( - forProject = SharedTestDataADM2.incunabulaProjectIri, - forGroup = Some(OntologyConstants.KnoraAdmin.ProjectMember), - hasPermissions = Set(PermissionADM.changeRightsPermission(OntologyConstants.KnoraAdmin.ProjectMember)) - ).prepareHasPermissions, - requestingUser = rootUser, - apiRequestID = UUID.randomUUID() - ) - expectMsg( - Failure( - DuplicateValueException( - s"A default object access permission for project: '${SharedTestDataADM2.incunabulaProjectIri}' and group: '${OntologyConstants.KnoraAdmin.ProjectMember}' " + - "combination already exists. " + - s"This permission currently has the scope '${PermissionUtilADM - .formatPermissionADMs(perm003_d1.p.hasPermissions, PermissionType.OAP)}'. " + - s"Use its IRI ${perm003_d1.iri} to modify it, if necessary." - ) + val exit = UnsafeZioRun.run( + PermissionsResponderADM.createDefaultObjectAccessPermission( + CreateDefaultObjectAccessPermissionAPIRequestADM( + forProject = SharedTestDataADM2.incunabulaProjectIri, + forGroup = Some(OntologyConstants.KnoraAdmin.ProjectMember), + hasPermissions = Set(PermissionADM.changeRightsPermission(OntologyConstants.KnoraAdmin.ProjectMember)) + ), + rootUser, + UUID.randomUUID() ) ) + assertFailsWithA[DuplicateValueException]( + exit, + s"A default object access permission for project: '${SharedTestDataADM2.incunabulaProjectIri}' and group: '${OntologyConstants.KnoraAdmin.ProjectMember}' " + + "combination already exists. " + + s"This permission currently has the scope '${PermissionUtilADM + .formatPermissionADMs(perm003_d1.p.hasPermissions, PermissionType.OAP)}'. " + + s"Use its IRI ${perm003_d1.iri} to modify it, if necessary." + ) } "fail and return a 'DuplicateValueException' when a doap permission for project and resourceClass combination already exists" in { - appActor ! DefaultObjectAccessPermissionCreateRequestADM( - createRequest = CreateDefaultObjectAccessPermissionAPIRequestADM( - forProject = SharedTestDataADM2.incunabulaProjectIri, - forResourceClass = Some(SharedOntologyTestDataADM.INCUNABULA_BOOK_RESOURCE_CLASS), - hasPermissions = Set( - PermissionADM.changeRightsPermission(OntologyConstants.KnoraAdmin.Creator), - PermissionADM.modifyPermission(OntologyConstants.KnoraAdmin.ProjectMember) - ) - ).prepareHasPermissions, - requestingUser = rootUser, - apiRequestID = UUID.randomUUID() - ) - expectMsg( - Failure( - DuplicateValueException( - s"A default object access permission for project: '${SharedTestDataADM2.incunabulaProjectIri}' and resourceClass: '${SharedOntologyTestDataADM.INCUNABULA_BOOK_RESOURCE_CLASS}' " + - "combination already exists. " + - s"This permission currently has the scope '${PermissionUtilADM - .formatPermissionADMs(perm003_d2.p.hasPermissions, PermissionType.OAP)}'. " + - s"Use its IRI ${perm003_d2.iri} to modify it, if necessary." - ) + val exit = UnsafeZioRun.run( + PermissionsResponderADM.createDefaultObjectAccessPermission( + CreateDefaultObjectAccessPermissionAPIRequestADM( + forProject = SharedTestDataADM2.incunabulaProjectIri, + forResourceClass = Some(SharedOntologyTestDataADM.INCUNABULA_BOOK_RESOURCE_CLASS), + hasPermissions = Set( + PermissionADM.changeRightsPermission(OntologyConstants.KnoraAdmin.Creator), + PermissionADM.modifyPermission(OntologyConstants.KnoraAdmin.ProjectMember) + ) + ), + rootUser, + UUID.randomUUID() ) ) + assertFailsWithA[DuplicateValueException]( + exit, + s"A default object access permission for project: '${SharedTestDataADM2.incunabulaProjectIri}' and resourceClass: '${SharedOntologyTestDataADM.INCUNABULA_BOOK_RESOURCE_CLASS}' " + + "combination already exists. " + + s"This permission currently has the scope '${PermissionUtilADM + .formatPermissionADMs(perm003_d2.p.hasPermissions, PermissionType.OAP)}'. " + + s"Use its IRI ${perm003_d2.iri} to modify it, if necessary." + ) } "fail and return a 'DuplicateValueException' when a doap permission for project and property combination already exists" in { - appActor ! DefaultObjectAccessPermissionCreateRequestADM( - createRequest = CreateDefaultObjectAccessPermissionAPIRequestADM( - forProject = SharedTestDataADM2.incunabulaProjectIri, - forProperty = Some(SharedOntologyTestDataADM.INCUNABULA_PartOf_Property), - hasPermissions = Set( - PermissionADM.modifyPermission(OntologyConstants.KnoraAdmin.KnownUser) - ) - ).prepareHasPermissions, - requestingUser = rootUser, - apiRequestID = UUID.randomUUID() - ) - expectMsg( - Failure( - DuplicateValueException( - s"A default object access permission for project: '${SharedTestDataADM2.incunabulaProjectIri}' and property: '${SharedOntologyTestDataADM.INCUNABULA_PartOf_Property}' " + - "combination already exists. " + - s"This permission currently has the scope '${PermissionUtilADM - .formatPermissionADMs(perm003_d4.p.hasPermissions, PermissionType.OAP)}'. " + - s"Use its IRI ${perm003_d4.iri} to modify it, if necessary." - ) + val exit = UnsafeZioRun.run( + PermissionsResponderADM.createDefaultObjectAccessPermission( + CreateDefaultObjectAccessPermissionAPIRequestADM( + forProject = SharedTestDataADM2.incunabulaProjectIri, + forProperty = Some(SharedOntologyTestDataADM.INCUNABULA_PartOf_Property), + hasPermissions = Set( + PermissionADM.modifyPermission(OntologyConstants.KnoraAdmin.KnownUser) + ) + ), + rootUser, + UUID.randomUUID() ) ) + assertFailsWithA[DuplicateValueException]( + exit, + s"A default object access permission for project: '${SharedTestDataADM2.incunabulaProjectIri}' and property: '${SharedOntologyTestDataADM.INCUNABULA_PartOf_Property}' " + + "combination already exists. " + + s"This permission currently has the scope '${PermissionUtilADM + .formatPermissionADMs(perm003_d4.p.hasPermissions, PermissionType.OAP)}'. " + + s"Use its IRI ${perm003_d4.iri} to modify it, if necessary." + ) } "fail and return a 'DuplicateValueException' when a doap permission for project, resource class, and property combination already exists" in { - appActor ! DefaultObjectAccessPermissionCreateRequestADM( - createRequest = CreateDefaultObjectAccessPermissionAPIRequestADM( - forProject = SharedTestDataADM2.incunabulaProjectIri, - forResourceClass = Some(SharedOntologyTestDataADM.INCUNABULA_PAGE_RESOURCE_CLASS), - forProperty = Some(SharedOntologyTestDataADM.INCUNABULA_PartOf_Property), - hasPermissions = Set( - PermissionADM.changeRightsPermission(OntologyConstants.KnoraAdmin.Creator), - PermissionADM.modifyPermission(OntologyConstants.KnoraAdmin.ProjectMember) - ) - ).prepareHasPermissions, - requestingUser = rootUser, - apiRequestID = UUID.randomUUID() - ) - expectMsg( - Failure( - DuplicateValueException( - s"A default object access permission for project: '${SharedTestDataADM2.incunabulaProjectIri}' and resourceClass: '${SharedOntologyTestDataADM.INCUNABULA_PAGE_RESOURCE_CLASS}' " + - s"and property: '${SharedOntologyTestDataADM.INCUNABULA_PartOf_Property}' " + - "combination already exists. " + - s"This permission currently has the scope '${PermissionUtilADM - .formatPermissionADMs(perm003_d5.p.hasPermissions, PermissionType.OAP)}'. " + - s"Use its IRI ${perm003_d5.iri} to modify it, if necessary." - ) + val exit = UnsafeZioRun.run( + PermissionsResponderADM.createDefaultObjectAccessPermission( + CreateDefaultObjectAccessPermissionAPIRequestADM( + forProject = SharedTestDataADM2.incunabulaProjectIri, + forResourceClass = Some(SharedOntologyTestDataADM.INCUNABULA_PAGE_RESOURCE_CLASS), + forProperty = Some(SharedOntologyTestDataADM.INCUNABULA_PartOf_Property), + hasPermissions = Set( + PermissionADM.changeRightsPermission(OntologyConstants.KnoraAdmin.Creator), + PermissionADM.modifyPermission(OntologyConstants.KnoraAdmin.ProjectMember) + ) + ), + rootUser, + UUID.randomUUID() ) ) + assertFailsWithA[DuplicateValueException]( + exit, + s"A default object access permission for project: '${SharedTestDataADM2.incunabulaProjectIri}' and resourceClass: '${SharedOntologyTestDataADM.INCUNABULA_PAGE_RESOURCE_CLASS}' " + + s"and property: '${SharedOntologyTestDataADM.INCUNABULA_PartOf_Property}' " + + "combination already exists. " + + s"This permission currently has the scope '${PermissionUtilADM + .formatPermissionADMs(perm003_d5.p.hasPermissions, PermissionType.OAP)}'. " + + s"Use its IRI ${perm003_d5.iri} to modify it, if necessary." + ) } "create a DefaultObjectAccessPermission for project and property even if name of a permission was missing" in { @@ -622,22 +617,23 @@ class PermissionsResponderADMSpec extends CoreSpec with ImplicitSender { permissionCode = Some(1) ) ) - appActor ! DefaultObjectAccessPermissionCreateRequestADM( - createRequest = CreateDefaultObjectAccessPermissionAPIRequestADM( - forProject = SharedTestDataADM.imagesProjectIri, - forGroup = Some(OntologyConstants.KnoraAdmin.UnknownUser), - hasPermissions = hasPermissions - ).prepareHasPermissions, - requestingUser = rootUser, - apiRequestID = UUID.randomUUID() + val actual = UnsafeZioRun.runOrThrow( + PermissionsResponderADM.createDefaultObjectAccessPermission( + CreateDefaultObjectAccessPermissionAPIRequestADM( + forProject = imagesProjectIri, + forGroup = Some(OntologyConstants.KnoraAdmin.UnknownUser), + hasPermissions = hasPermissions + ), + rootUser, + UUID.randomUUID() + ) ) - val received: DefaultObjectAccessPermissionCreateResponseADM = - expectMsgType[DefaultObjectAccessPermissionCreateResponseADM] - assert(received.defaultObjectAccessPermission.forGroup.contains(unknownUser)) - assert(received.defaultObjectAccessPermission.forProject == SharedTestDataADM.imagesProjectIri) + assert(actual.defaultObjectAccessPermission.forProject == imagesProjectIri) + assert(actual.defaultObjectAccessPermission.forGroup.contains(unknownUser)) assert( - received.defaultObjectAccessPermission.hasPermissions - .contains(PermissionADM.restrictedViewPermission(unknownUser)) + actual.defaultObjectAccessPermission.hasPermissions.contains( + PermissionADM.restrictedViewPermission(unknownUser) + ) ) } @@ -656,42 +652,39 @@ class PermissionsResponderADMSpec extends CoreSpec with ImplicitSender { permissionCode = Some(7) ) ) - appActor ! DefaultObjectAccessPermissionCreateRequestADM( - createRequest = CreateDefaultObjectAccessPermissionAPIRequestADM( - forProject = SharedTestDataADM.imagesProjectIri, - forGroup = Some(OntologyConstants.KnoraAdmin.ProjectAdmin), - hasPermissions = hasPermissions - ).prepareHasPermissions, - requestingUser = rootUser, - apiRequestID = UUID.randomUUID() + val actual = UnsafeZioRun.runOrThrow( + PermissionsResponderADM.createDefaultObjectAccessPermission( + CreateDefaultObjectAccessPermissionAPIRequestADM( + forProject = imagesProjectIri, + forGroup = Some(OntologyConstants.KnoraAdmin.ProjectAdmin), + hasPermissions = hasPermissions + ), + rootUser, + UUID.randomUUID() + ) ) - val received: DefaultObjectAccessPermissionCreateResponseADM = - expectMsgType[DefaultObjectAccessPermissionCreateResponseADM] - assert(received.defaultObjectAccessPermission.forProject == SharedTestDataADM.imagesProjectIri) - assert(received.defaultObjectAccessPermission.forGroup.contains(projectAdmin)) - assert(received.defaultObjectAccessPermission.hasPermissions.equals(expectedPermissions)) + assert(actual.defaultObjectAccessPermission.forProject == imagesProjectIri) + assert(actual.defaultObjectAccessPermission.forGroup.contains(projectAdmin)) + assert(actual.defaultObjectAccessPermission.hasPermissions.equals(expectedPermissions)) } } "ask to get all permissions" should { "return all permissions for 'image' project" in { - appActor ! PermissionsForProjectGetRequestADM( - projectIri = SharedTestDataADM.imagesProjectIri, - requestingUser = rootUser, - apiRequestID = UUID.randomUUID() + val actual = UnsafeZioRun.runOrThrow( + PermissionsResponderADM.getPermissionsByProjectIri(ProjectIri.unsafeFrom(imagesProjectIri)) ) - val received: PermissionsForProjectGetResponseADM = expectMsgType[PermissionsForProjectGetResponseADM] - received.allPermissions.size should be(10) + actual.allPermissions.size should be(10) } "return all permissions for 'incunabula' project" in { - appActor ! PermissionsForProjectGetRequestADM( - projectIri = SharedTestDataADM.incunabulaProjectIri, - requestingUser = rootUser, - apiRequestID = UUID.randomUUID() + val actual = UnsafeZioRun.runOrThrow( + PermissionsResponderADM.getPermissionsByProjectIri( + ProjectIri.unsafeFrom(SharedTestDataADM.incunabulaProjectIri) + ) ) - expectMsg( + actual shouldEqual PermissionsForProjectGetResponseADM(allPermissions = Set( PermissionInfoADM( @@ -724,7 +717,6 @@ class PermissionsResponderADMSpec extends CoreSpec with ImplicitSender { ) ) ) - ) } } @@ -893,92 +885,64 @@ class PermissionsResponderADMSpec extends CoreSpec with ImplicitSender { "ask to update group of a permission" should { "update group of an administrative permission" in { - val permissionIri = "http://rdfh.ch/permissions/00FF/buxHAlz8SHuu0FuiLN_tKQ" - val newGroupIri = "http://rdfh.ch/groups/00FF/images-reviewer" - appActor ! PermissionChangeGroupRequestADM( - permissionIri = permissionIri, - changePermissionGroupRequest = ChangePermissionGroupApiRequestADM( - forGroup = newGroupIri - ), - requestingUser = rootUser, - apiRequestID = UUID.randomUUID() + val permissionIri = PermissionIri.unsafeFrom("http://rdfh.ch/permissions/00FF/buxHAlz8SHuu0FuiLN_tKQ") + val newGroupIri = GroupIri.unsafeFrom("http://rdfh.ch/groups/00FF/images-reviewer") + val actual = UnsafeZioRun.runOrThrow( + PermissionsResponderADM + .updatePermissionsGroup(permissionIri, newGroupIri, rootUser, UUID.randomUUID()) ) - val received: AdministrativePermissionGetResponseADM = expectMsgType[AdministrativePermissionGetResponseADM] - val ap = received.administrativePermission - assert(ap.iri == permissionIri) - assert(ap.forGroup == newGroupIri) + val ap = actual.asInstanceOf[AdministrativePermissionGetResponseADM].administrativePermission + assert(ap.iri == permissionIri.value) + assert(ap.forGroup == newGroupIri.value) } "throw ForbiddenException for PermissionChangeGroupRequestADM if requesting user is not system or project Admin" in { - val permissionIri = "http://rdfh.ch/permissions/00FF/buxHAlz8SHuu0FuiLN_tKQ" - val newGroupIri = "http://rdfh.ch/groups/00FF/images-reviewer" - appActor ! PermissionChangeGroupRequestADM( - permissionIri = permissionIri, - changePermissionGroupRequest = ChangePermissionGroupApiRequestADM( - forGroup = newGroupIri - ), - requestingUser = SharedTestDataADM.imagesUser02, - apiRequestID = UUID.randomUUID() + val permissionIri = PermissionIri.unsafeFrom("http://rdfh.ch/permissions/00FF/buxHAlz8SHuu0FuiLN_tKQ") + val newGroupIri = GroupIri.unsafeFrom("http://rdfh.ch/groups/00FF/images-reviewer") + val exit = UnsafeZioRun.run( + PermissionsResponderADM + .updatePermissionsGroup(permissionIri, newGroupIri, imagesUser02, UUID.randomUUID()) ) - expectMsg( - Failure( - ForbiddenException( - s"Permission $permissionIri can only be queried/updated/deleted by system or project admin." - ) - ) + assertFailsWithA[ForbiddenException]( + exit, + s"Permission ${permissionIri.value} can only be queried/updated/deleted by system or project admin." ) } "update group of a default object access permission" in { - val permissionIri = "http://rdfh.ch/permissions/00FF/Mck2xJDjQ_Oimi_9z4aFaA" - val newGroupIri = "http://rdfh.ch/groups/00FF/images-reviewer" - appActor ! PermissionChangeGroupRequestADM( - permissionIri = permissionIri, - changePermissionGroupRequest = ChangePermissionGroupApiRequestADM( - forGroup = newGroupIri - ), - requestingUser = rootUser, - apiRequestID = UUID.randomUUID() + val permissionIri = PermissionIri.unsafeFrom("http://rdfh.ch/permissions/00FF/Mck2xJDjQ_Oimi_9z4aFaA") + val newGroupIri = GroupIri.unsafeFrom("http://rdfh.ch/groups/00FF/images-reviewer") + val actual = UnsafeZioRun.runOrThrow( + PermissionsResponderADM + .updatePermissionsGroup(permissionIri, newGroupIri, rootUser, UUID.randomUUID()) ) - val received: DefaultObjectAccessPermissionGetResponseADM = - expectMsgType[DefaultObjectAccessPermissionGetResponseADM] - val doap = received.defaultObjectAccessPermission - assert(doap.iri == permissionIri) - assert(doap.forGroup.contains(newGroupIri)) + val doap = actual.asInstanceOf[DefaultObjectAccessPermissionGetResponseADM].defaultObjectAccessPermission + assert(doap.iri == permissionIri.value) + assert(doap.forGroup.contains(newGroupIri.value)) } "update group of a default object access permission, resource class must be deleted" in { - val permissionIri = "http://rdfh.ch/permissions/00FF/sdHG20U6RoiwSu8MeAT1vA" - appActor ! PermissionChangeGroupRequestADM( - permissionIri = permissionIri, - changePermissionGroupRequest = ChangePermissionGroupApiRequestADM( - forGroup = projectMember - ), - requestingUser = rootUser, - apiRequestID = UUID.randomUUID() - ) - val received: DefaultObjectAccessPermissionGetResponseADM = - expectMsgType[DefaultObjectAccessPermissionGetResponseADM] - val doap = received.defaultObjectAccessPermission - assert(doap.iri == permissionIri) + val permissionIri = PermissionIri.unsafeFrom("http://rdfh.ch/permissions/00FF/sdHG20U6RoiwSu8MeAT1vA") + val newGroupIri = GroupIri.unsafeFrom(projectMember) + val actual = UnsafeZioRun.runOrThrow( + PermissionsResponderADM + .updatePermissionsGroup(permissionIri, newGroupIri, rootUser, UUID.randomUUID()) + ) + val doap = actual.asInstanceOf[DefaultObjectAccessPermissionGetResponseADM].defaultObjectAccessPermission + assert(doap.iri == permissionIri.value) assert(doap.forGroup.contains(projectMember)) assert(doap.forResourceClass.isEmpty) } "update group of a default object access permission, property must be deleted" in { - val permissionIri = "http://rdfh.ch/permissions/0000/KMjKHCNQQmC4uHPQwlEexw" - appActor ! PermissionChangeGroupRequestADM( - permissionIri = permissionIri, - changePermissionGroupRequest = ChangePermissionGroupApiRequestADM( - forGroup = projectMember - ), - requestingUser = rootUser, - apiRequestID = UUID.randomUUID() - ) - val received: DefaultObjectAccessPermissionGetResponseADM = - expectMsgType[DefaultObjectAccessPermissionGetResponseADM] - val doap = received.defaultObjectAccessPermission - assert(doap.iri == permissionIri) + val permissionIri = PermissionIri.unsafeFrom("http://rdfh.ch/permissions/0000/KMjKHCNQQmC4uHPQwlEexw") + val newGroupIri = GroupIri.unsafeFrom(projectMember) + val actual = UnsafeZioRun.runOrThrow( + PermissionsResponderADM + .updatePermissionsGroup(permissionIri, newGroupIri, rootUser, UUID.randomUUID()) + ) + val doap = actual.asInstanceOf[DefaultObjectAccessPermissionGetResponseADM].defaultObjectAccessPermission + assert(doap.iri == permissionIri.value) assert(doap.forGroup.contains(projectMember)) assert(doap.forProperty.isEmpty) } @@ -987,63 +951,62 @@ class PermissionsResponderADMSpec extends CoreSpec with ImplicitSender { "ask to update hasPermissions of a permission" should { "throw ForbiddenException for PermissionChangeHasPermissionsRequestADM if requesting user is not system or project Admin" in { val permissionIri = "http://rdfh.ch/permissions/00FF/buxHAlz8SHuu0FuiLN_tKQ" - val hasPermissions = Set(PermissionADM.ProjectResourceCreateAllPermission) - - appActor ! PermissionChangeHasPermissionsRequestADM( - permissionIri = permissionIri, - changePermissionHasPermissionsRequest = ChangePermissionHasPermissionsApiRequestADM( - hasPermissions = hasPermissions - ), - requestingUser = SharedTestDataADM.imagesUser02, - apiRequestID = UUID.randomUUID() - ) - expectMsg( - Failure( - ForbiddenException( - s"Permission $permissionIri can only be queried/updated/deleted by system or project admin." + val hasPermissions = NonEmptyChunk(PermissionADM.ProjectResourceCreateAllPermission) + + val exit = UnsafeZioRun.run( + PermissionsResponderADM + .updatePermissionHasPermissions( + PermissionIri.unsafeFrom(permissionIri), + hasPermissions, + imagesUser02, + UUID.randomUUID() ) - ) + ) + + assertFailsWithA[ForbiddenException]( + exit, + s"Permission $permissionIri can only be queried/updated/deleted by system or project admin." ) } "update hasPermissions of an administrative permission" in { val permissionIri = "http://rdfh.ch/permissions/00FF/buxHAlz8SHuu0FuiLN_tKQ" - val hasPermissions = Set(PermissionADM.ProjectResourceCreateAllPermission) - - appActor ! PermissionChangeHasPermissionsRequestADM( - permissionIri = permissionIri, - changePermissionHasPermissionsRequest = ChangePermissionHasPermissionsApiRequestADM( - hasPermissions = hasPermissions - ), - requestingUser = rootUser, - apiRequestID = UUID.randomUUID() + val hasPermissions = NonEmptyChunk(PermissionADM.ProjectResourceCreateAllPermission) + val actual = UnsafeZioRun.runOrThrow( + PermissionsResponderADM + .updatePermissionHasPermissions( + PermissionIri.unsafeFrom(permissionIri), + hasPermissions, + rootUser, + UUID.randomUUID() + ) ) - val received: AdministrativePermissionGetResponseADM = expectMsgType[AdministrativePermissionGetResponseADM] - val ap = received.administrativePermission + + val ap = actual.asInstanceOf[AdministrativePermissionGetResponseADM].administrativePermission assert(ap.iri == permissionIri) ap.hasPermissions.size should be(1) - assert(ap.hasPermissions.equals(hasPermissions)) + assert(ap.hasPermissions.equals(hasPermissions.toSet)) } "ignore irrelevant parameters given in ChangePermissionHasPermissionsApiRequestADM for an administrative permission" in { val permissionIri = "http://rdfh.ch/permissions/00FF/buxHAlz8SHuu0FuiLN_tKQ" - val hasPermissions = Set( + val hasPermissions = NonEmptyChunk( PermissionADM( name = OntologyConstants.KnoraAdmin.ProjectAdminAllPermission, additionalInformation = Some("aIRI"), permissionCode = Some(1) ) ) - appActor ! PermissionChangeHasPermissionsRequestADM( - permissionIri = permissionIri, - changePermissionHasPermissionsRequest = ChangePermissionHasPermissionsApiRequestADM( - hasPermissions = hasPermissions - ), - requestingUser = rootUser, - apiRequestID = UUID.randomUUID() + val actual = UnsafeZioRun.runOrThrow( + PermissionsResponderADM + .updatePermissionHasPermissions( + PermissionIri.unsafeFrom(permissionIri), + hasPermissions, + rootUser, + UUID.randomUUID() + ) ) - val received: AdministrativePermissionGetResponseADM = expectMsgType[AdministrativePermissionGetResponseADM] - val ap = received.administrativePermission + val ap = actual.asInstanceOf[AdministrativePermissionGetResponseADM].administrativePermission assert(ap.iri == permissionIri) ap.hasPermissions.size should be(1) val expectedSetOfPermissions = Set(PermissionADM.ProjectAdminAllPermission) @@ -1052,30 +1015,30 @@ class PermissionsResponderADMSpec extends CoreSpec with ImplicitSender { "update hasPermissions of a default object access permission" in { val permissionIri = "http://rdfh.ch/permissions/00FF/Q3OMWyFqStGYK8EXmC7KhQ" - val hasPermissions = Set( + val hasPermissions = NonEmptyChunk( PermissionADM.changeRightsPermission(creator), PermissionADM.modifyPermission(projectMember) ) - appActor ! PermissionChangeHasPermissionsRequestADM( - permissionIri = permissionIri, - changePermissionHasPermissionsRequest = ChangePermissionHasPermissionsApiRequestADM( - hasPermissions = hasPermissions - ), - requestingUser = rootUser, - apiRequestID = UUID.randomUUID() + val actual = UnsafeZioRun.runOrThrow( + PermissionsResponderADM + .updatePermissionHasPermissions( + PermissionIri.unsafeFrom(permissionIri), + hasPermissions, + rootUser, + UUID.randomUUID() + ) ) - val received: DefaultObjectAccessPermissionGetResponseADM = - expectMsgType[DefaultObjectAccessPermissionGetResponseADM] - val doap = received.defaultObjectAccessPermission + + val doap = actual.asInstanceOf[DefaultObjectAccessPermissionGetResponseADM].defaultObjectAccessPermission assert(doap.iri == permissionIri) doap.hasPermissions.size should be(2) - assert(doap.hasPermissions.equals(hasPermissions)) + assert(doap.hasPermissions.equals(hasPermissions.toSet)) } "add missing name of the permission, if permissionCode of permission was given in hasPermissions of a default object access permission" in { val permissionIri = "http://rdfh.ch/permissions/00FF/Q3OMWyFqStGYK8EXmC7KhQ" - val hasPermissions = Set( + val hasPermissions = NonEmptyChunk( PermissionADM( name = "", additionalInformation = Some(OntologyConstants.KnoraAdmin.Creator), @@ -1091,24 +1054,23 @@ class PermissionsResponderADMSpec extends CoreSpec with ImplicitSender { ) ) - appActor ! PermissionChangeHasPermissionsRequestADM( - permissionIri = permissionIri, - changePermissionHasPermissionsRequest = ChangePermissionHasPermissionsApiRequestADM( - hasPermissions = hasPermissions - ), - requestingUser = rootUser, - apiRequestID = UUID.randomUUID() + val actual = UnsafeZioRun.runOrThrow( + PermissionsResponderADM + .updatePermissionHasPermissions( + PermissionIri.unsafeFrom(permissionIri), + hasPermissions, + rootUser, + UUID.randomUUID() + ) ) - val received: DefaultObjectAccessPermissionGetResponseADM = - expectMsgType[DefaultObjectAccessPermissionGetResponseADM] - val doap = received.defaultObjectAccessPermission + val doap = actual.asInstanceOf[DefaultObjectAccessPermissionGetResponseADM].defaultObjectAccessPermission assert(doap.iri == permissionIri) assert(doap.hasPermissions.equals(expectedHasPermissions)) } "add missing permissionCode of the permission, if name of permission was given in hasPermissions of a default object access permission" in { val permissionIri = "http://rdfh.ch/permissions/00FF/Q3OMWyFqStGYK8EXmC7KhQ" - val hasPermissions = Set( + val hasPermissions = NonEmptyChunk( PermissionADM( name = OntologyConstants.KnoraBase.DeletePermission, additionalInformation = Some(OntologyConstants.KnoraAdmin.ProjectAdmin), @@ -1123,18 +1085,16 @@ class PermissionsResponderADMSpec extends CoreSpec with ImplicitSender { permissionCode = Some(7) ) ) - - appActor ! PermissionChangeHasPermissionsRequestADM( - permissionIri = permissionIri, - changePermissionHasPermissionsRequest = ChangePermissionHasPermissionsApiRequestADM( - hasPermissions = hasPermissions - ), - requestingUser = rootUser, - apiRequestID = UUID.randomUUID() + val actual = UnsafeZioRun.runOrThrow( + PermissionsResponderADM + .updatePermissionHasPermissions( + PermissionIri.unsafeFrom(permissionIri), + hasPermissions, + rootUser, + UUID.randomUUID() + ) ) - val received: DefaultObjectAccessPermissionGetResponseADM = - expectMsgType[DefaultObjectAccessPermissionGetResponseADM] - val doap = received.defaultObjectAccessPermission + val doap = actual.asInstanceOf[DefaultObjectAccessPermissionGetResponseADM].defaultObjectAccessPermission assert(doap.iri == permissionIri) assert(doap.hasPermissions.equals(expectedHasPermissions)) } @@ -1143,32 +1103,32 @@ class PermissionsResponderADMSpec extends CoreSpec with ImplicitSender { val permissionIri = "http://rdfh.ch/permissions/00FF/Q3OMWyFqStGYK8EXmC7KhQ" val code = 1 val name = OntologyConstants.KnoraBase.DeletePermission - val hasPermissions = Set( + val hasPermissions = NonEmptyChunk( PermissionADM( name = name, additionalInformation = Some(OntologyConstants.KnoraAdmin.ProjectAdmin), permissionCode = Some(code) ) ) - - appActor ! PermissionChangeHasPermissionsRequestADM( - permissionIri = permissionIri, - changePermissionHasPermissionsRequest = ChangePermissionHasPermissionsApiRequestADM( - hasPermissions = hasPermissions - ), - requestingUser = rootUser, - apiRequestID = UUID.randomUUID() + val exit = UnsafeZioRun.run( + PermissionsResponderADM + .updatePermissionHasPermissions( + PermissionIri.unsafeFrom(permissionIri), + hasPermissions, + rootUser, + UUID.randomUUID() + ) ) - expectMsg( - Failure(BadRequestException(s"Given permission code $code and permission name $name are not consistent.")) + assertFailsWithA[BadRequestException]( + exit, + s"Given permission code $code and permission name $name are not consistent." ) - } "not update hasPermissions of a default object access permission, if an invalid name was given for a permission" in { val permissionIri = "http://rdfh.ch/permissions/00FF/Q3OMWyFqStGYK8EXmC7KhQ" val name = "invalidName" - val hasPermissions = Set( + val hasPermissions = NonEmptyChunk( PermissionADM( name = name, additionalInformation = Some(OntologyConstants.KnoraAdmin.ProjectAdmin), @@ -1176,29 +1136,26 @@ class PermissionsResponderADMSpec extends CoreSpec with ImplicitSender { ) ) - appActor ! PermissionChangeHasPermissionsRequestADM( - permissionIri = permissionIri, - changePermissionHasPermissionsRequest = ChangePermissionHasPermissionsApiRequestADM( - hasPermissions = hasPermissions - ), - requestingUser = rootUser, - apiRequestID = UUID.randomUUID() - ) - expectMsg( - Failure( - BadRequestException( - s"Invalid value for name parameter of hasPermissions: $name, it should be one of " + - s"${EntityPermissionAbbreviations.toString}" + val exit = UnsafeZioRun.run( + PermissionsResponderADM + .updatePermissionHasPermissions( + PermissionIri.unsafeFrom(permissionIri), + hasPermissions, + rootUser, + UUID.randomUUID() ) - ) ) - + assertFailsWithA[BadRequestException]( + exit, + s"Invalid value for name parameter of hasPermissions: $name, it should be one of " + + s"${EntityPermissionAbbreviations.toString}" + ) } "not update hasPermissions of a default object access permission, if an invalid code was given for a permission" in { val permissionIri = "http://rdfh.ch/permissions/00FF/Q3OMWyFqStGYK8EXmC7KhQ" val code = 10 - val hasPermissions = Set( + val hasPermissions = NonEmptyChunk( PermissionADM( name = OntologyConstants.KnoraBase.DeletePermission, additionalInformation = Some(OntologyConstants.KnoraAdmin.ProjectAdmin), @@ -1206,28 +1163,25 @@ class PermissionsResponderADMSpec extends CoreSpec with ImplicitSender { ) ) - appActor ! PermissionChangeHasPermissionsRequestADM( - permissionIri = permissionIri, - changePermissionHasPermissionsRequest = ChangePermissionHasPermissionsApiRequestADM( - hasPermissions = hasPermissions - ), - requestingUser = rootUser, - apiRequestID = UUID.randomUUID() - ) - expectMsg( - Failure( - BadRequestException( - s"Invalid value for permissionCode parameter of hasPermissions: $code, it should be one of " + - s"${PermissionTypeAndCodes.values.toString}" + val exit = UnsafeZioRun.run( + PermissionsResponderADM + .updatePermissionHasPermissions( + PermissionIri.unsafeFrom(permissionIri), + hasPermissions, + rootUser, + UUID.randomUUID() ) - ) ) - + assertFailsWithA[BadRequestException]( + exit, + s"Invalid value for permissionCode parameter of hasPermissions: $code, it should be one of " + + s"${PermissionTypeAndCodes.values.toString}" + ) } "not update hasPermissions of a default object access permission, if given name and project code are not consistent" in { val permissionIri = "http://rdfh.ch/permissions/00FF/Q3OMWyFqStGYK8EXmC7KhQ" - val hasPermissions = Set( + val hasPermissions = NonEmptyChunk( PermissionADM( name = "", additionalInformation = Some(OntologyConstants.KnoraAdmin.ProjectAdmin), @@ -1235,22 +1189,19 @@ class PermissionsResponderADMSpec extends CoreSpec with ImplicitSender { ) ) - appActor ! PermissionChangeHasPermissionsRequestADM( - permissionIri = permissionIri, - changePermissionHasPermissionsRequest = ChangePermissionHasPermissionsApiRequestADM( - hasPermissions = hasPermissions - ), - requestingUser = rootUser, - apiRequestID = UUID.randomUUID() - ) - expectMsg( - Failure( - BadRequestException( - s"One of permission code or permission name must be provided for a default object access permission." + val exit = UnsafeZioRun.run( + PermissionsResponderADM + .updatePermissionHasPermissions( + PermissionIri.unsafeFrom(permissionIri), + hasPermissions, + rootUser, + UUID.randomUUID() ) - ) ) - + assertFailsWithA[BadRequestException]( + exit, + s"One of permission code or permission name must be provided for a default object access permission." + ) } } "ask to update resource class of a permission" should { @@ -1258,37 +1209,35 @@ class PermissionsResponderADMSpec extends CoreSpec with ImplicitSender { val permissionIri = "http://rdfh.ch/permissions/00FF/sdHG20U6RoiwSu8MeAT1vA" val resourceClassIri = SharedOntologyTestDataADM.INCUNABULA_PAGE_RESOURCE_CLASS - appActor ! PermissionChangeResourceClassRequestADM( - permissionIri = permissionIri, - changePermissionResourceClassRequest = ChangePermissionResourceClassApiRequestADM( - forResourceClass = resourceClassIri - ), - requestingUser = SharedTestDataADM.incunabulaMemberUser, - apiRequestID = UUID.randomUUID() - ) - expectMsg( - Failure( - ForbiddenException( - s"Permission $permissionIri can only be queried/updated/deleted by system or project admin." + val exit = UnsafeZioRun.run( + PermissionsResponderADM + .updatePermissionResourceClass( + PermissionIri.unsafeFrom(permissionIri), + ChangePermissionResourceClassApiRequestADM(resourceClassIri), + incunabulaMemberUser, + UUID.randomUUID() ) - ) + ) + + assertFailsWithA[ForbiddenException]( + exit, + s"Permission $permissionIri can only be queried/updated/deleted by system or project admin." ) } "update resource class of a default object access permission" in { val permissionIri = "http://rdfh.ch/permissions/00FF/sdHG20U6RoiwSu8MeAT1vA" val resourceClassIri = SharedOntologyTestDataADM.INCUNABULA_PAGE_RESOURCE_CLASS - appActor ! PermissionChangeResourceClassRequestADM( - permissionIri = permissionIri, - changePermissionResourceClassRequest = ChangePermissionResourceClassApiRequestADM( - forResourceClass = resourceClassIri - ), - requestingUser = rootUser, - apiRequestID = UUID.randomUUID() + val actual = UnsafeZioRun.runOrThrow( + PermissionsResponderADM + .updatePermissionResourceClass( + PermissionIri.unsafeFrom(permissionIri), + ChangePermissionResourceClassApiRequestADM(resourceClassIri), + rootUser, + UUID.randomUUID() + ) ) - val received: DefaultObjectAccessPermissionGetResponseADM = - expectMsgType[DefaultObjectAccessPermissionGetResponseADM] - val doap = received.defaultObjectAccessPermission + val doap = actual.asInstanceOf[DefaultObjectAccessPermissionGetResponseADM].defaultObjectAccessPermission assert(doap.iri == permissionIri) assert(doap.forResourceClass.contains(resourceClassIri)) } @@ -1296,18 +1245,16 @@ class PermissionsResponderADMSpec extends CoreSpec with ImplicitSender { "update resource class of a default object access permission, and delete group" in { val permissionIri = "http://rdfh.ch/permissions/00FF/Q3OMWyFqStGYK8EXmC7KhQ" val resourceClassIri = SharedOntologyTestDataADM.INCUNABULA_BOOK_RESOURCE_CLASS - - appActor ! PermissionChangeResourceClassRequestADM( - permissionIri = permissionIri, - changePermissionResourceClassRequest = ChangePermissionResourceClassApiRequestADM( - forResourceClass = resourceClassIri - ), - requestingUser = rootUser, - apiRequestID = UUID.randomUUID() + val actual = UnsafeZioRun.runOrThrow( + PermissionsResponderADM + .updatePermissionResourceClass( + PermissionIri.unsafeFrom(permissionIri), + ChangePermissionResourceClassApiRequestADM(resourceClassIri), + rootUser, + UUID.randomUUID() + ) ) - val received: DefaultObjectAccessPermissionGetResponseADM = - expectMsgType[DefaultObjectAccessPermissionGetResponseADM] - val doap = received.defaultObjectAccessPermission + val doap = actual.asInstanceOf[DefaultObjectAccessPermissionGetResponseADM].defaultObjectAccessPermission assert(doap.iri == permissionIri) assert(doap.forResourceClass.contains(resourceClassIri)) assert(doap.forGroup.isEmpty) @@ -1316,22 +1263,19 @@ class PermissionsResponderADMSpec extends CoreSpec with ImplicitSender { "not update resource class of an administrative permission" in { val permissionIri = "http://rdfh.ch/permissions/00FF/OySsjGn8QSqIpXUiSYnSSQ" val resourceClassIri = SharedOntologyTestDataADM.INCUNABULA_BOOK_RESOURCE_CLASS - - appActor ! PermissionChangeResourceClassRequestADM( - permissionIri = permissionIri, - changePermissionResourceClassRequest = ChangePermissionResourceClassApiRequestADM( - forResourceClass = resourceClassIri - ), - requestingUser = rootUser, - apiRequestID = UUID.randomUUID() - ) - expectMsg( - Failure( - BadRequestException( - s"Permission $permissionIri is of type administrative permission. " + - s"Only a default object access permission defined for a resource class can be updated." + val exit = UnsafeZioRun.run( + PermissionsResponderADM + .updatePermissionResourceClass( + PermissionIri.unsafeFrom(permissionIri), + ChangePermissionResourceClassApiRequestADM(resourceClassIri), + rootUser, + UUID.randomUUID() ) - ) + ) + assertFailsWithA[ForbiddenException]( + exit, + s"Permission $permissionIri is of type administrative permission. " + + s"Only a default object access permission defined for a resource class can be updated." ) } } @@ -1340,58 +1284,53 @@ class PermissionsResponderADMSpec extends CoreSpec with ImplicitSender { val permissionIri = "http://rdfh.ch/permissions/00FF/OySsjGn8QSqIpXUiSYnSSQ" val propertyIri = SharedOntologyTestDataADM.IMAGES_TITEL_PROPERTY - appActor ! PermissionChangePropertyRequestADM( - permissionIri = permissionIri, - changePermissionPropertyRequest = ChangePermissionPropertyApiRequestADM( - forProperty = propertyIri - ), - requestingUser = rootUser, - apiRequestID = UUID.randomUUID() - ) - expectMsg( - Failure( - BadRequestException( - s"Permission $permissionIri is of type administrative permission. " + - s"Only a default object access permission defined for a property can be updated." + val exit = UnsafeZioRun.run( + PermissionsResponderADM + .updatePermissionProperty( + PermissionIri.unsafeFrom(permissionIri), + ChangePermissionPropertyApiRequestADM(propertyIri), + rootUser, + UUID.randomUUID() ) - ) + ) + assertFailsWithA[ForbiddenException]( + exit, + s"Permission $permissionIri is of type administrative permission. " + + s"Only a default object access permission defined for a property can be updated." ) } "throw ForbiddenException for PermissionChangePropertyRequestADM if requesting user is not system or project Admin" in { val permissionIri = "http://rdfh.ch/permissions/0000/KMjKHCNQQmC4uHPQwlEexw" val propertyIri = OntologyConstants.KnoraBase.TextFileValue - appActor ! PermissionChangePropertyRequestADM( - permissionIri = permissionIri, - changePermissionPropertyRequest = ChangePermissionPropertyApiRequestADM( - forProperty = propertyIri - ), - requestingUser = SharedTestDataADM.normalUser, - apiRequestID = UUID.randomUUID() - ) - expectMsg( - Failure( - ForbiddenException( - s"Permission $permissionIri can only be queried/updated/deleted by system or project admin." + val exit = UnsafeZioRun.run( + PermissionsResponderADM + .updatePermissionProperty( + PermissionIri.unsafeFrom(permissionIri), + ChangePermissionPropertyApiRequestADM(propertyIri), + normalUser, + UUID.randomUUID() ) - ) + ) + assertFailsWithA[ForbiddenException]( + exit, + s"Permission $permissionIri can only be queried/updated/deleted by system or project admin." ) } "update property of a default object access permission" in { val permissionIri = "http://rdfh.ch/permissions/0000/KMjKHCNQQmC4uHPQwlEexw" val propertyIri = OntologyConstants.KnoraBase.TextFileValue - appActor ! PermissionChangePropertyRequestADM( - permissionIri = permissionIri, - changePermissionPropertyRequest = ChangePermissionPropertyApiRequestADM( - forProperty = propertyIri - ), - requestingUser = rootUser, - apiRequestID = UUID.randomUUID() + val actual = UnsafeZioRun.runOrThrow( + PermissionsResponderADM + .updatePermissionProperty( + PermissionIri.unsafeFrom(permissionIri), + ChangePermissionPropertyApiRequestADM(propertyIri), + rootUser, + UUID.randomUUID() + ) ) - val received: DefaultObjectAccessPermissionGetResponseADM = - expectMsgType[DefaultObjectAccessPermissionGetResponseADM] - val doap = received.defaultObjectAccessPermission + val doap = actual.asInstanceOf[DefaultObjectAccessPermissionGetResponseADM].defaultObjectAccessPermission assert(doap.iri == permissionIri) assert(doap.forProperty.contains(propertyIri)) } @@ -1400,17 +1339,15 @@ class PermissionsResponderADMSpec extends CoreSpec with ImplicitSender { val permissionIri = "http://rdfh.ch/permissions/00FF/Mck2xJDjQ_Oimi_9z4aFaA" val propertyIri = SharedOntologyTestDataADM.IMAGES_TITEL_PROPERTY - appActor ! PermissionChangePropertyRequestADM( - permissionIri = permissionIri, - changePermissionPropertyRequest = ChangePermissionPropertyApiRequestADM( - forProperty = propertyIri - ), - requestingUser = rootUser, - apiRequestID = UUID.randomUUID() + val actual = UnsafeZioRun.runOrThrow( + PermissionsResponderADM.updatePermissionProperty( + PermissionIri.unsafeFrom(permissionIri), + ChangePermissionPropertyApiRequestADM(propertyIri), + rootUser, + UUID.randomUUID() + ) ) - val received: DefaultObjectAccessPermissionGetResponseADM = - expectMsgType[DefaultObjectAccessPermissionGetResponseADM] - val doap = received.defaultObjectAccessPermission + val doap = actual.asInstanceOf[DefaultObjectAccessPermissionGetResponseADM].defaultObjectAccessPermission assert(doap.iri == permissionIri) assert(doap.forProperty.contains(propertyIri)) assert(doap.forGroup.isEmpty) @@ -1419,40 +1356,33 @@ class PermissionsResponderADMSpec extends CoreSpec with ImplicitSender { "ask to delete a permission" should { "throw BadRequestException if given IRI is not a permission IRI" in { - val permissionIri = "http://rdfh.ch/permissions/00FF/RkVssk8XRVO9hZ3VR5IpLA" - appActor ! PermissionDeleteRequestADM( - permissionIri = permissionIri, - requestingUser = rootUser, - apiRequestID = UUID.randomUUID() + val permissionIri = PermissionIri.unsafeFrom("http://rdfh.ch/permissions/00FF/RkVssk8XRVO9hZ3VR5IpLA") + val exit = UnsafeZioRun.run( + PermissionsResponderADM.deletePermission(permissionIri, rootUser, UUID.randomUUID()) + ) + assertFailsWithA[NotFoundException]( + exit, + s"Permission with given IRI: ${permissionIri.value} not found." ) - expectMsg(Failure(NotFoundException(s"Permission with given IRI: $permissionIri not found."))) } "throw ForbiddenException if user requesting PermissionDeleteResponseADM is not a system or project admin" in { - val permissionIri = "http://rdfh.ch/permissions/00FF/Mck2xJDjQ_Oimi_9z4aFaA" - appActor ! PermissionDeleteRequestADM( - permissionIri = permissionIri, - requestingUser = SharedTestDataADM.imagesUser02, - apiRequestID = UUID.randomUUID() + val permissionIri = PermissionIri.unsafeFrom("http://rdfh.ch/permissions/00FF/Mck2xJDjQ_Oimi_9z4aFaA") + val exit = UnsafeZioRun.run( + PermissionsResponderADM.deletePermission(permissionIri, SharedTestDataADM.imagesUser02, UUID.randomUUID()) ) - expectMsg( - Failure( - ForbiddenException( - s"Permission $permissionIri can only be queried/updated/deleted by system or project admin." - ) - ) + assertFailsWithA[ForbiddenException]( + exit, + s"Permission ${permissionIri.value} can only be queried/updated/deleted by system or project admin." ) } "erase a permission with given IRI" in { - val permissionIri = "http://rdfh.ch/permissions/00FF/Mck2xJDjQ_Oimi_9z4aFaA" - appActor ! PermissionDeleteRequestADM( - permissionIri = permissionIri, - requestingUser = rootUser, - apiRequestID = UUID.randomUUID() + val permissionIri = PermissionIri.unsafeFrom("http://rdfh.ch/permissions/00FF/Mck2xJDjQ_Oimi_9z4aFaA") + val actual = UnsafeZioRun.runOrThrow( + PermissionsResponderADM.deletePermission(permissionIri, rootUser, UUID.randomUUID()) ) - val received: PermissionDeleteResponseADM = expectMsgType[PermissionDeleteResponseADM] - assert(received.deleted) + assert(actual.deleted) } } } diff --git a/integration/src/test/scala/org/knora/webapi/responders/admin/ProjectsResponderADMSpec.scala b/integration/src/test/scala/org/knora/webapi/responders/admin/ProjectsResponderADMSpec.scala index c2c11c9684..46819290ee 100644 --- a/integration/src/test/scala/org/knora/webapi/responders/admin/ProjectsResponderADMSpec.scala +++ b/integration/src/test/scala/org/knora/webapi/responders/admin/ProjectsResponderADMSpec.scala @@ -25,6 +25,7 @@ import org.knora.webapi.messages.admin.responder.permissionsmessages._ import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectIdentifierADM._ import org.knora.webapi.messages.admin.responder.projectsmessages._ import org.knora.webapi.messages.admin.responder.usersmessages.UserInformationTypeADM +import org.knora.webapi.routing.UnsafeZioRun import org.knora.webapi.sharedtestdata.SharedTestDataADM import org.knora.webapi.slice.admin.api.model.ProjectsEndpointsRequests.ProjectCreateRequest import org.knora.webapi.slice.admin.api.model.ProjectsEndpointsRequests.ProjectUpdateRequest @@ -191,15 +192,8 @@ class ProjectsResponderADMSpec extends CoreSpec with ImplicitSender { newProjectIri.set(received.project.id) // Check Administrative Permissions - appActor ! AdministrativePermissionsForProjectGetRequestADM( - projectIri = received.project.id, - requestingUser = rootUser, - apiRequestID = UUID.randomUUID() - ) - - // Check Administrative Permission of ProjectAdmin - val receivedApAdmin: AdministrativePermissionsForProjectGetResponseADM = - expectMsgType[AdministrativePermissionsForProjectGetResponseADM] + val receivedApAdmin = + UnsafeZioRun.runOrThrow(PermissionsResponderADM.getPermissionsApByProjectIri(received.project.id)) val hasAPForProjectAdmin = receivedApAdmin.administrativePermissions.filter { ap: AdministrativePermissionADM => ap.forProject == received.project.id && ap.forGroup == OntologyConstants.KnoraAdmin.ProjectAdmin && @@ -219,13 +213,9 @@ class ProjectsResponderADMSpec extends CoreSpec with ImplicitSender { hasAPForProjectMember.size shouldBe 1 // Check Default Object Access permissions - appActor ! DefaultObjectAccessPermissionsForProjectGetRequestADM( - projectIri = received.project.id, - requestingUser = rootUser, - apiRequestID = UUID.randomUUID() + val receivedDoaps = UnsafeZioRun.runOrThrow( + PermissionsResponderADM.getPermissionsDaopByProjectIri(ProjectIri.unsafeFrom(received.project.id)) ) - val receivedDoaps: DefaultObjectAccessPermissionsForProjectGetResponseADM = - expectMsgType[DefaultObjectAccessPermissionsForProjectGetResponseADM] // Check Default Object Access permission of ProjectAdmin val hasDOAPForProjectAdmin = receivedDoaps.defaultObjectAccessPermissions.filter { diff --git a/integration/src/test/scala/org/knora/webapi/util/ZioScalaTestUtil.scala b/integration/src/test/scala/org/knora/webapi/util/ZioScalaTestUtil.scala index 859f4e5eaa..b650c83138 100644 --- a/integration/src/test/scala/org/knora/webapi/util/ZioScalaTestUtil.scala +++ b/integration/src/test/scala/org/knora/webapi/util/ZioScalaTestUtil.scala @@ -18,4 +18,11 @@ object ZioScalaTestUtil { case Exit.Failure(err) => err.squash shouldBe a[T] case _ => Assertions.fail(s"Expected Exit.Failure with specific T.") } + def assertFailsWithA[T <: Throwable: ClassTag](actual: Exit[Throwable, _], expectedError: String) = actual match { + case Exit.Failure(err) => { + err.squash shouldBe a[T] + err.squash.getMessage shouldEqual (expectedError) + } + case _ => Assertions.fail(s"Expected Exit. Failure with specific T.") + } } diff --git a/webapi/src/main/scala/dsp/valueobjects/Iri.scala b/webapi/src/main/scala/dsp/valueobjects/Iri.scala index 31d5fdc0c5..0dae71519f 100644 --- a/webapi/src/main/scala/dsp/valueobjects/Iri.scala +++ b/webapi/src/main/scala/dsp/valueobjects/Iri.scala @@ -81,14 +81,6 @@ object Iri { def isUserIri(iri: IRI): Boolean = isIri(iri) && iri.startsWith("http://rdfh.ch/users/") - /** - * Returns `true` if an IRI string looks like a Knora group IRI. - * - * @param iri the IRI to be checked. - */ - def isGroupIri(iri: IRI): Boolean = - isIri(iri) && iri.startsWith("http://rdfh.ch/groups/") - /** * Returns `true` if an IRI string looks like a Knora project IRI * @@ -109,14 +101,6 @@ object Iri { isIri(iri) && builtInProjects.contains(iri) } - /** - * Returns `true` if an IRI string looks like a Knora permission IRI. - * - * @param iri the IRI to be checked. - */ - def isPermissionIri(iri: IRI): Boolean = - isIri(iri) && iri.startsWith("http://rdfh.ch/permissions/") - /** * Makes a string safe to be entered in the triplestore by escaping special chars. * @@ -187,29 +171,6 @@ object Iri { /** * GroupIri value object. */ - sealed abstract case class GroupIri private (value: String) extends Iri - object GroupIri { self => - def make(value: String): Validation[Throwable, GroupIri] = - if (value.isEmpty) Validation.fail(BadRequestException(IriErrorMessages.GroupIriMissing)) - else { - val isUuid: Boolean = UuidUtil.hasValidLength(value.split("/").last) - - if (!isGroupIri(value)) - Validation.fail(BadRequestException(IriErrorMessages.GroupIriInvalid)) - else if (isUuid && !UuidUtil.hasSupportedVersion(value)) - Validation.fail(BadRequestException(IriErrorMessages.UuidVersionInvalid)) - else - validateAndEscapeIri(value) - .mapError(_ => BadRequestException(IriErrorMessages.GroupIriInvalid)) - .map(new GroupIri(_) {}) - } - - def make(value: Option[String]): Validation[Throwable, Option[GroupIri]] = - value match { - case Some(v) => self.make(v).map(Some(_)) - case None => Validation.succeed(None) - } - } /** * ListIri value object. @@ -326,8 +287,6 @@ object Iri { } object IriErrorMessages { - val GroupIriMissing = "Group IRI cannot be empty." - val GroupIriInvalid = "Group IRI is invalid." val ListIriMissing = "List IRI cannot be empty." val ListIriInvalid = "List IRI is invalid" val ProjectIriMissing = "Project IRI cannot be empty." 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 1a78842f30..3f1189e7dd 100644 --- a/webapi/src/main/scala/org/knora/webapi/core/LayersLive.scala +++ b/webapi/src/main/scala/org/knora/webapi/core/LayersLive.scala @@ -29,6 +29,7 @@ import org.knora.webapi.responders.v2.ontology.OntologyHelpersLive import org.knora.webapi.routing.* import org.knora.webapi.slice.admin.api.* import org.knora.webapi.slice.admin.api.service.MaintenanceRestService +import org.knora.webapi.slice.admin.api.service.PermissionsRestService import org.knora.webapi.slice.admin.api.service.ProjectADMRestService import org.knora.webapi.slice.admin.api.service.ProjectsADMRestServiceLive import org.knora.webapi.slice.admin.api.service.UsersADMRestServiceLive @@ -70,13 +71,13 @@ object LayersLive { ActorSystem & ApiRoutes & AppConfigurations & AppRouter & Authenticator & CacheService & CacheServiceRequestMessageHandler & CardinalityHandler & CardinalityService & ConstructResponseUtilV2 & ConstructTransformer & GravsearchTypeInspectionRunner & GroupsResponderADM & HttpServer & - IIIFRequestMessageHandler & SipiService & InferenceOptimizationService & IriService & IriConverter & JwtService & + IIIFRequestMessageHandler & InferenceOptimizationService & IriConverter & IriService & JwtService & SipiService & KnoraProjectRepo & ListsResponderADM & ListsResponderV2 & MessageRelay & OntologyCache & OntologyHelpers & - OntologyRepo & OntologyResponderV2 & PermissionUtilADM & PermissionsResponderADM & PredicateObjectMapper & + OntologyRepo & OntologyResponderV2 & PermissionsResponderADM & PermissionsRestService & PermissionUtilADM & PredicateObjectMapper & ProjectADMRestService & ProjectADMService & ProjectExportService & ProjectExportStorageService & ProjectImportService & ProjectsResponderADM & QueryTraverser & RepositoryUpdater & ResourceUtilV2 & - ResourceUtilV2 & ResourcesResponderV2 & RestCardinalityService & RestPermissionService & RestResourceInfoService & - SearchApiRoutes & SearchResponderV2 & SipiResponderADM & OntologyInferencer & StandoffResponderV2 & StandoffTagUtilV2 & State & + AuthorizationRestService & ResourcesResponderV2 & ResourceUtilV2 & RestCardinalityService & RestResourceInfoService & + OntologyInferencer & SearchApiRoutes & SearchResponderV2 & SipiResponderADM & StandoffResponderV2 & StandoffTagUtilV2 & State & StoresResponderADM & StringFormatter & TriplestoreService & UsersResponderADM & ValuesResponderV2 /** @@ -90,6 +91,7 @@ object LayersLive { AppConfig.layer, AppRouter.layer, AuthenticatorLive.layer, + AuthorizationRestServiceLive.layer, BaseEndpoints.layer, CacheServiceInMemImpl.layer, CacheServiceRequestMessageHandlerLive.layer, @@ -122,7 +124,10 @@ object LayersLive { OntologyRepoLive.layer, OntologyResponderV2Live.layer, PermissionUtilADMLive.layer, + PermissionsEndpoints.layer, + PermissionsEndpointsHandlers.layer, PermissionsResponderADMLive.layer, + PermissionsRestService.layer, PredicateObjectMapper.layer, PredicateRepositoryLive.layer, ProjectADMServiceLive.layer, @@ -139,7 +144,6 @@ object LayersLive { ResourceUtilV2Live.layer, ResourcesResponderV2Live.layer, RestCardinalityServiceLive.layer, - RestPermissionServiceLive.layer, RestResourceInfoServiceLive.layer, SearchApiRoutes.layer, SearchResponderV2Live.layer, diff --git a/webapi/src/main/scala/org/knora/webapi/messages/StringFormatter.scala b/webapi/src/main/scala/org/knora/webapi/messages/StringFormatter.scala index 5c39920a12..9b9986fdd2 100644 --- a/webapi/src/main/scala/org/knora/webapi/messages/StringFormatter.scala +++ b/webapi/src/main/scala/org/knora/webapi/messages/StringFormatter.scala @@ -7,7 +7,6 @@ package org.knora.webapi.messages import spray.json.* import zio.ZLayer -import zio.prelude.Validation import java.time.* import java.time.temporal.ChronoField @@ -20,7 +19,6 @@ import scala.util.matching.Regex import dsp.errors.* import dsp.valueobjects.Iri -import dsp.valueobjects.IriErrorMessages import dsp.valueobjects.UuidUtil import org.knora.webapi.* import org.knora.webapi.config.AppConfig @@ -127,7 +125,7 @@ object StringFormatter { /** * The domain name used to construct Knora IRIs. */ - private val IriDomain: String = "rdfh.ch" + val IriDomain: String = "rdfh.ch" /* @@ -624,9 +622,6 @@ class StringFormatter private ( private val ProjectIDPattern: String = """\p{XDigit}{4,4}""" - // A regex for matching a string containing the project ID. - private val ProjectIDRegex: Regex = ("^" + ProjectIDPattern + "$").r - // A regex for the URL path of an API v2 ontology (built-in or project-specific). private val ApiV2OntologyUrlPathRegex: Regex = ( "^" + "/ontology/((" + @@ -1525,27 +1520,6 @@ class StringFormatter private ( case _ => false } - /** - * Given the group IRI, checks if it is in a valid format. - * - * @param iri the group's IRI. - * @return the IRI of the list. - */ - def validateGroupIri(iri: IRI): Validation[ValidationException, IRI] = - if (Iri.isGroupIri(iri)) Validation.succeed(iri) - else Validation.fail(ValidationException(s"Invalid IRI: $iri")) - - /** - * Given the permission IRI, checks if it is in a valid format. - * - * @param iri the permission's IRI. - * @return either the IRI or the error message. - */ - def validatePermissionIri(iri: IRI): Either[String, IRI] = - if (Iri.isPermissionIri(iri) && UuidUtil.hasSupportedVersion(iri)) Right(iri) - else if (Iri.isPermissionIri(iri) && !UuidUtil.hasSupportedVersion(iri)) Left(IriErrorMessages.UuidVersionInvalid) - else Left(s"Invalid permission IRI: $iri.") - /** * Given an email address, checks if it is in a valid format. * @@ -1699,17 +1673,6 @@ class StringFormatter private ( s"http://$IriDomain/projects/$uuid" } - /** - * Creates a new group IRI based on a UUID. - * - * @param shortcode the required project shortcode. - * @return a new group IRI. - */ - def makeRandomGroupIri(shortcode: String): String = { - val knoraGroupUuid = UuidUtil.makeRandomBase64EncodedUuid - s"http://$IriDomain/groups/$shortcode/$knoraGroupUuid" - } - /** * Creates a new person IRI based on a UUID. * @@ -1731,17 +1694,6 @@ class StringFormatter private ( s"http://$IriDomain/lists/$shortcode/$knoraListUuid" } - /** - * Creates a new permission IRI based on a UUID. - * - * @param shortcode the required project shortcode. - * @return the IRI of the permission object. - */ - def makeRandomPermissionIri(shortcode: String): IRI = { - val knoraPermissionUuid = UuidUtil.makeRandomBase64EncodedUuid - s"http://$IriDomain/permissions/$shortcode/$knoraPermissionUuid" - } - /** * Validates a custom value IRI, throwing [[BadRequestException]] if the IRI is not valid. * diff --git a/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/KnoraResponseADM.scala b/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/KnoraResponseADM.scala index 0b3efa62d5..949cdad144 100644 --- a/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/KnoraResponseADM.scala +++ b/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/KnoraResponseADM.scala @@ -11,3 +11,5 @@ import org.knora.webapi.messages.traits.Jsonable * A trait for Knora Admin response messages. Any response message can be converted into JSON. */ trait KnoraResponseADM extends Jsonable + +trait AdminKnoraResponseADM extends KnoraResponseADM diff --git a/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/groupsmessages/GroupsPayloadsADM.scala b/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/groupsmessages/GroupsPayloadsADM.scala index f4174c99a3..389c8bf0f2 100644 --- a/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/groupsmessages/GroupsPayloadsADM.scala +++ b/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/groupsmessages/GroupsPayloadsADM.scala @@ -6,7 +6,7 @@ package org.knora.webapi.messages.admin.responder.groupsmessages import dsp.valueobjects.Group.* -import dsp.valueobjects.Iri.* +import org.knora.webapi.slice.admin.domain.model.GroupIri import org.knora.webapi.slice.admin.domain.model.KnoraProject.ProjectIri /** diff --git a/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/permissionsmessages/PermissionsMessagesADM.scala b/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/permissionsmessages/PermissionsMessagesADM.scala index 5bc798c71a..6d847a4011 100644 --- a/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/permissionsmessages/PermissionsMessagesADM.scala +++ b/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/permissionsmessages/PermissionsMessagesADM.scala @@ -5,7 +5,7 @@ package org.knora.webapi.messages.admin.responder.permissionsmessages -import org.apache.pekko +import org.apache.pekko.http.scaladsl.marshallers.sprayjson.SprayJsonSupport import spray.json.* import java.util.UUID @@ -18,14 +18,13 @@ import org.knora.webapi.core.RelayedMessage import org.knora.webapi.messages.OntologyConstants import org.knora.webapi.messages.ResponderRequest.KnoraRequestADM import org.knora.webapi.messages.StringFormatter -import org.knora.webapi.messages.admin.responder.KnoraResponseADM +import org.knora.webapi.messages.admin.responder.AdminKnoraResponseADM import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectsADMJsonProtocol import org.knora.webapi.messages.store.triplestoremessages.TriplestoreJsonProtocol import org.knora.webapi.messages.traits.Jsonable +import org.knora.webapi.slice.admin.domain.model.PermissionIri import org.knora.webapi.slice.admin.domain.model.User -import pekko.http.scaladsl.marshallers.sprayjson.SprayJsonSupport - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // API requests @@ -43,27 +42,7 @@ case class CreateAdministrativePermissionAPIRequestADM( forGroup: IRI, hasPermissions: Set[PermissionADM] ) extends PermissionsADMJsonProtocol { - implicit protected val sf: StringFormatter = StringFormatter.getInstanceForConstantOntologies - - id match { - case Some(iri) => sf.validatePermissionIri(iri).fold(e => throw BadRequestException(e), v => v) - case None => None - } - def toJsValue: JsValue = createAdministrativePermissionAPIRequestADMFormat.write(this) - - Iri.validateAndEscapeProjectIri(forProject).getOrElse(throw BadRequestException(s"Invalid project IRI $forProject")) - - if (hasPermissions.isEmpty) throw BadRequestException("Permissions needs to be supplied.") - - if (!OntologyConstants.KnoraAdmin.BuiltInGroups.contains(forGroup)) { - sf.validateGroupIri(forGroup).getOrElse(throw BadRequestException(s"Invalid group IRI $forGroup")) - } - - def prepareHasPermissions: CreateAdministrativePermissionAPIRequestADM = - copy( - hasPermissions = PermissionsMessagesUtilADM.verifyHasPermissionsAP(hasPermissions) - ) } /** @@ -84,59 +63,7 @@ case class CreateDefaultObjectAccessPermissionAPIRequestADM( forProperty: Option[IRI] = None, hasPermissions: Set[PermissionADM] ) extends PermissionsADMJsonProtocol { - implicit protected val sf: StringFormatter = StringFormatter.getInstanceForConstantOntologies - - id match { - case Some(iri) => sf.validatePermissionIri(iri).fold(e => throw BadRequestException(e), v => v) - case None => None - } - def toJsValue: JsValue = createDefaultObjectAccessPermissionAPIRequestADMFormat.write(this) - - Iri.validateAndEscapeProjectIri(forProject).getOrElse(throw BadRequestException(s"Invalid project IRI $forProject")) - - forGroup match { - case Some(iri: IRI) => - if (forResourceClass.isDefined) - throw BadRequestException("Not allowed to supply groupIri and resourceClassIri together.") - else if (forProperty.isDefined) - throw BadRequestException("Not allowed to supply groupIri and propertyIri together.") - else { - if (!OntologyConstants.KnoraAdmin.BuiltInGroups.contains(iri)) { - sf.validateGroupIri(iri) - .getOrElse(throw BadRequestException(s"Invalid group IRI ${forGroup.get}")) - } - } - case None => - if (forResourceClass.isEmpty && forProperty.isEmpty) { - throw BadRequestException( - "Either a group, a resource class, a property, or a combination of resource class and property must be given." - ) - } - } - - forResourceClass match { - case Some(iri) => - if (!sf.toSmartIri(iri).isKnoraEntityIri) { - throw BadRequestException(s"Invalid resource class IRI: $iri") - } - case None => None - } - - forProperty match { - case Some(iri) => - if (!sf.toSmartIri(iri).isKnoraEntityIri) { - throw BadRequestException(s"Invalid property IRI: $iri") - } - case None => None - } - - if (hasPermissions.isEmpty) throw BadRequestException("Permissions needs to be supplied.") - - def prepareHasPermissions: CreateDefaultObjectAccessPermissionAPIRequestADM = - copy( - hasPermissions = PermissionsMessagesUtilADM.verifyHasPermissionsDOAP(hasPermissions) - ) } /** @@ -163,12 +90,7 @@ case class ChangePermissionGroupApiRequestADM(forGroup: IRI) extends Permissions */ case class ChangePermissionHasPermissionsApiRequestADM(hasPermissions: Set[PermissionADM]) extends PermissionsADMJsonProtocol { - if (hasPermissions.isEmpty) { - throw BadRequestException(s"hasPermissions cannot be empty.") - } - def toJsValue: JsValue = changePermissionHasPermissionsApiRequestADMFormat.write(this) - } /** @@ -229,138 +151,8 @@ case class PermissionDataGetADM( if (!requestingUser.isSystemUser) throw ForbiddenException("Permission data can only by queried by a SystemUser.") } -/** - * A message that requests all permissions defined inside a project. - * A successful response will be a [[PermissionsForProjectGetResponseADM]]. - * - * @param projectIri the project for which the permissions are queried. - * @param requestingUser the user initiation the request. - * @param apiRequestID the API request ID. - */ -case class PermissionsForProjectGetRequestADM( - projectIri: IRI, - requestingUser: User, - apiRequestID: UUID -) extends PermissionsResponderRequestADM { - Iri - .validateAndEscapeProjectIri(projectIri) - .getOrElse(throw BadRequestException(s"Invalid project IRI $projectIri")) - - // Check user's permission for the operation - if ( - !requestingUser.isSystemAdmin - && !requestingUser.permissions.isProjectAdmin(projectIri) - ) { - // not a system or project admin - throw ForbiddenException("Permissions can only be queried by system and project admin.") - } -} - -/** - * A message that requests update of a permission's group. - * A successful response will be a [[PermissionItemADM]]. - * - * @param permissionIri the IRI of the permission to be updated. - * @param changePermissionGroupRequest the request to update permission's group. - * @param requestingUser the user initiation the request. - * @param apiRequestID the API request ID. - */ -case class PermissionChangeGroupRequestADM( - permissionIri: IRI, - changePermissionGroupRequest: ChangePermissionGroupApiRequestADM, - requestingUser: User, - apiRequestID: UUID -) extends PermissionsResponderRequestADM { - if (!Iri.isPermissionIri(permissionIri)) - throw BadRequestException(s"Invalid permission IRI $permissionIri is given.") -} - -/** - * A message that requests update of a permission's hasPermissions property. - * A successful response will be a [[PermissionItemADM]]. - * - * @param permissionIri the IRI of the permission to be updated. - * @param changePermissionHasPermissionsRequest the request to update hasPermissions. - * @param requestingUser the user initiation the request. - * @param apiRequestID the API request ID. - */ -case class PermissionChangeHasPermissionsRequestADM( - permissionIri: IRI, - changePermissionHasPermissionsRequest: ChangePermissionHasPermissionsApiRequestADM, - requestingUser: User, - apiRequestID: UUID -) extends PermissionsResponderRequestADM { - if (!Iri.isPermissionIri(permissionIri)) - throw BadRequestException(s"Invalid permission IRI $permissionIri is given.") -} - -/** - * A message that requests update of a doap permission's resource class. - * A successful response will be a [[PermissionItemADM]]. - * - * @param permissionIri the IRI of the permission to be updated. - * @param changePermissionResourceClassRequest the request to update permission's resource class. - * @param requestingUser the user initiation the request. - * @param apiRequestID the API request ID. - */ -case class PermissionChangeResourceClassRequestADM( - permissionIri: IRI, - changePermissionResourceClassRequest: ChangePermissionResourceClassApiRequestADM, - requestingUser: User, - apiRequestID: UUID -) extends PermissionsResponderRequestADM { - if (!Iri.isPermissionIri(permissionIri)) - throw BadRequestException(s"Invalid permission IRI $permissionIri is given.") -} - -/** - * A message that requests update of a doap permission's resource class. - * A successful response will be a [[PermissionItemADM]]. - * - * @param permissionIri the IRI of the permission to be updated. - * @param changePermissionPropertyRequest the request to update permission's property. - * @param requestingUser the user initiation the request. - * @param apiRequestID the API request ID. - */ -case class PermissionChangePropertyRequestADM( - permissionIri: IRI, - changePermissionPropertyRequest: ChangePermissionPropertyApiRequestADM, - requestingUser: User, - apiRequestID: UUID -) extends PermissionsResponderRequestADM { - if (!Iri.isPermissionIri(permissionIri)) - throw BadRequestException(s"Invalid permission IRI $permissionIri is given.") -} - // Administrative Permissions -/** - * A message that requests all administrative permissions defined inside a project. - * A successful response will be a [[AdministrativePermissionsForProjectGetResponseADM]]. - * - * @param projectIri the project for which the administrative permissions are queried. - * @param requestingUser the user initiation the request. - * @param apiRequestID the API request ID. - */ -case class AdministrativePermissionsForProjectGetRequestADM( - projectIri: IRI, - requestingUser: User, - apiRequestID: UUID -) extends PermissionsResponderRequestADM { - Iri - .validateAndEscapeProjectIri(projectIri) - .getOrElse(throw BadRequestException(s"Invalid project IRI $projectIri")) - - // Check user's permission for the operation - if ( - !requestingUser.isSystemAdmin - && !requestingUser.permissions.isProjectAdmin(projectIri) - ) { - // not a system or project admin - throw ForbiddenException("Administrative permission can only be queried by system and project admin.") - } -} - /** * A message that requests an administrative permission object identified through his IRI. * A successful response will be a [[AdministrativePermissionGetResponseADM]] object. @@ -374,7 +166,7 @@ case class AdministrativePermissionForIriGetRequestADM( requestingUser: User, apiRequestID: UUID ) extends PermissionsResponderRequestADM { - PermissionsMessagesUtilADM.checkPermissionIri(administrativePermissionIri) + PermissionIri.from(administrativePermissionIri).fold(msg => throw BadRequestException(msg), _ => ()) } /** @@ -402,51 +194,6 @@ case class AdministrativePermissionForProjectGroupGetADM(projectIri: IRI, groupI } } -/** - * A message that requests an administrative permission object identified by project and group. - * A successful response will be an [[AdministrativePermissionGetResponseADM]] object. - * - * @param projectIri - * @param groupIri - * @param requestingUser - */ -case class AdministrativePermissionForProjectGroupGetRequestADM(projectIri: IRI, groupIri: IRI, requestingUser: User) - extends PermissionsResponderRequestADM { - // Check user's permission for the operation - if ( - !requestingUser.isSystemAdmin - && !requestingUser.permissions.isProjectAdmin(projectIri) - && !requestingUser.isSystemUser - ) { - // not a system admin - throw ForbiddenException("Administrative permission can only be queried by system and project admin.") - } -} - -/** - * Create a single [[AdministrativePermissionADM]]. - * - * @param createRequest the API create request payload. - * @param requestingUser the requesting user. - * @param apiRequestID the API request ID. - */ -case class AdministrativePermissionCreateRequestADM( - createRequest: CreateAdministrativePermissionAPIRequestADM, - requestingUser: User, - apiRequestID: UUID -) extends PermissionsResponderRequestADM { - // check if the requesting user is allowed to add the administrative permission - // Allowed are SystemAdmin, ProjectAdmin and SystemUser - if ( - !requestingUser.isSystemAdmin - && !requestingUser.permissions.isProjectAdmin(createRequest.forProject) - && !requestingUser.isSystemUser - ) { - // not a system admin - throw ForbiddenException("A new administrative permission can only be added by system or project admin.") - } -} - // Object Access Permissions /** @@ -482,33 +229,6 @@ case class ObjectAccessPermissionsForValueGetADM(valueIri: IRI, requestingUser: // Default Object Access Permissions -/** - * A message that requests all default object access permissions defined inside a project. - * A successful response will be a list of [[DefaultObjectAccessPermissionADM]]. - * - * @param projectIri the project for which the default object access permissions are queried. - * @param requestingUser the user initiating this request. - * @param apiRequestID the API request ID. - */ -case class DefaultObjectAccessPermissionsForProjectGetRequestADM( - projectIri: IRI, - requestingUser: User, - apiRequestID: UUID -) extends PermissionsResponderRequestADM { - Iri - .validateAndEscapeProjectIri(projectIri) - .getOrElse(throw BadRequestException(s"Invalid project IRI $projectIri")) - - // Check user's permission for the operation - if ( - !requestingUser.isSystemAdmin - && !requestingUser.permissions.isProjectAdmin(projectIri) - ) { - // not a system or project admin - throw ForbiddenException("Default object access permissions can only be queried by system and project admin.") - } -} - /** * A message that requests an object access permission identified by project and either group / resource class / property. * A successful response will be a [[DefaultObjectAccessPermissionGetResponseADM]]. @@ -673,30 +393,6 @@ case class DefaultObjectAccessPermissionsStringForPropertyGetADM( if (targetUser.isAnonymousUser) throw BadRequestException("Anonymous Users are not allowed.") } -/** - * Create a single [[DefaultObjectAccessPermissionADM]]. - * - * @param createRequest the create request. - * @param requestingUser the requesting user. - * @param apiRequestID the API request ID. - */ -case class DefaultObjectAccessPermissionCreateRequestADM( - createRequest: CreateDefaultObjectAccessPermissionAPIRequestADM, - requestingUser: User, - apiRequestID: UUID -) extends PermissionsResponderRequestADM { - // check if the requesting user is allowed to add the default object access permission - // Allowed are SystemAdmin, ProjectAdmin and SystemUser - if ( - !requestingUser.isSystemAdmin - && !requestingUser.permissions.isProjectAdmin(createRequest.forProject) - && !requestingUser.isSystemUser - ) { - // not a system admin - throw ForbiddenException("A new default object access permission can only be added by a system admin.") - } -} - /** * A message that requests a permission (doap or ap) by its IRI. * A successful response will be an [[PermissionGetResponseADM]] object. @@ -709,19 +405,6 @@ case class PermissionByIriGetRequestADM(permissionIri: IRI, requestingUser: User PermissionsMessagesUtilADM.checkPermissionIri(permissionIri) } -/** - * A message that requests deletion of a permission identified through its IRI. - * A successful response will be [[PermissionDeleteResponseADM]] with deleted=true. - * - * @param permissionIri the iri of the permission object. - * @param requestingUser the user initiating the request. - * @param apiRequestID the API request ID. - */ -case class PermissionDeleteRequestADM(permissionIri: IRI, requestingUser: User, apiRequestID: UUID) - extends PermissionsResponderRequestADM { - PermissionsMessagesUtilADM.checkPermissionIri(permissionIri) -} - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Responses @@ -732,7 +415,7 @@ case class PermissionDeleteRequestADM(permissionIri: IRI, requestingUser: User, * @param allPermissions the retrieved sequence of [[PermissionInfoADM]] */ case class PermissionsForProjectGetResponseADM(allPermissions: Set[PermissionInfoADM]) - extends KnoraResponseADM + extends AdminKnoraResponseADM with PermissionsADMJsonProtocol { def toJsValue: JsValue = permissionsForProjectGetResponseADMFormat.write(this) } @@ -745,7 +428,7 @@ case class PermissionsForProjectGetResponseADM(allPermissions: Set[PermissionInf */ case class AdministrativePermissionsForProjectGetResponseADM( administrativePermissions: Seq[AdministrativePermissionADM] -) extends KnoraResponseADM +) extends AdminKnoraResponseADM with PermissionsADMJsonProtocol { def toJsValue: JsValue = administrativePermissionsForProjectGetResponseADMFormat.write(this) } @@ -758,12 +441,12 @@ case class AdministrativePermissionsForProjectGetResponseADM( */ case class DefaultObjectAccessPermissionsForProjectGetResponseADM( defaultObjectAccessPermissions: Seq[DefaultObjectAccessPermissionADM] -) extends KnoraResponseADM +) extends AdminKnoraResponseADM with PermissionsADMJsonProtocol { def toJsValue: JsValue = defaultObjectAccessPermissionsForProjectGetResponseADMFormat.write(this) } -abstract class PermissionGetResponseADM() extends KnoraResponseADM with PermissionsADMJsonProtocol +sealed trait PermissionGetResponseADM extends AdminKnoraResponseADM with PermissionsADMJsonProtocol /** * Represents an answer to a request for getting a default object access permission. @@ -791,7 +474,7 @@ case class AdministrativePermissionGetResponseADM(administrativePermission: Admi * @param administrativePermission the newly created [[AdministrativePermissionADM]]. */ case class AdministrativePermissionCreateResponseADM(administrativePermission: AdministrativePermissionADM) - extends KnoraResponseADM + extends AdminKnoraResponseADM with PermissionsADMJsonProtocol { def toJsValue = administrativePermissionCreateResponseADMFormat.write(this) } @@ -803,7 +486,7 @@ case class AdministrativePermissionCreateResponseADM(administrativePermission: A */ case class DefaultObjectAccessPermissionCreateResponseADM( defaultObjectAccessPermission: DefaultObjectAccessPermissionADM -) extends KnoraResponseADM +) extends AdminKnoraResponseADM with PermissionsADMJsonProtocol { def toJsValue: JsValue = defaultObjectAccessPermissionCreateResponseADMFormat.write(this) } @@ -822,7 +505,7 @@ case class DefaultObjectAccessPermissionsStringResponseADM(permissionLiteral: St * @param deleted status of delete operation. */ case class PermissionDeleteResponseADM(permissionIri: IRI, deleted: Boolean) - extends KnoraResponseADM + extends AdminKnoraResponseADM with PermissionsADMJsonProtocol { def toJsValue: JsValue = permissionDeleteResponseADMFormat.write(this) @@ -1242,6 +925,18 @@ trait PermissionsADMJsonProtocol : RootJsonFormat[DefaultObjectAccessPermissionGetResponseADM] = jsonFormat(DefaultObjectAccessPermissionGetResponseADM, "default_object_access_permission") + implicit val permissionGetResponseADMFormat: RootJsonFormat[PermissionGetResponseADM] = + new RootJsonFormat[PermissionGetResponseADM] { + def write(response: PermissionGetResponseADM): JsValue = + response match { + case admin: AdministrativePermissionGetResponseADM => + administrativePermissionGetResponseADMFormat.write(admin) + case default: DefaultObjectAccessPermissionGetResponseADM => + defaultObjectAccessPermissionGetResponseADMFormat.write(default) + } + def read(json: JsValue): PermissionGetResponseADM = throw new UnsupportedOperationException("Not implemented.") + } + implicit val createAdministrativePermissionAPIRequestADMFormat : RootJsonFormat[CreateAdministrativePermissionAPIRequestADM] = rootFormat( lazyFormat( diff --git a/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/permissionsmessages/PermissionsMessagesUtilADM.scala b/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/permissionsmessages/PermissionsMessagesUtilADM.scala index 2946bf5075..33e4a7033f 100644 --- a/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/permissionsmessages/PermissionsMessagesUtilADM.scala +++ b/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/permissionsmessages/PermissionsMessagesUtilADM.scala @@ -9,7 +9,7 @@ import dsp.errors.BadRequestException import org.knora.webapi.IRI import org.knora.webapi.messages.OntologyConstants.KnoraAdmin.AdministrativePermissionAbbreviations import org.knora.webapi.messages.OntologyConstants.KnoraBase.* -import org.knora.webapi.messages.StringFormatter +import org.knora.webapi.slice.admin.domain.model.PermissionIri /** * Providing helper methods. @@ -28,45 +28,6 @@ object PermissionsMessagesUtilADM { // Helper Methods // //////////////////// - /** - * Validates the parameters of the `hasPermissions` collections of a DOAP. - * - * @param hasPermissions Set of the permissions. - */ - private def validateDOAPHasPermissions(hasPermissions: Set[PermissionADM]): Unit = - hasPermissions.foreach { permission => - if (permission.additionalInformation.isEmpty) { - throw BadRequestException(s"additionalInformation of a default object access permission type cannot be empty.") - } - if (permission.name.nonEmpty && !EntityPermissionAbbreviations.contains(permission.name)) - throw BadRequestException( - s"Invalid value for name parameter of hasPermissions: ${permission.name}, it should be one of " + - s"${EntityPermissionAbbreviations.toString}" - ) - if (permission.permissionCode.nonEmpty) { - val code = permission.permissionCode.get - if (!PermissionTypeAndCodes.values.toSet.contains(code)) { - throw BadRequestException( - s"Invalid value for permissionCode parameter of hasPermissions: $code, it should be one of " + - s"${PermissionTypeAndCodes.values.toString}" - ) - } - } - if (permission.permissionCode.isEmpty && permission.name.isEmpty) { - throw BadRequestException( - s"One of permission code or permission name must be provided for a default object access permission." - ) - } - if (permission.permissionCode.nonEmpty && permission.name.nonEmpty) { - val code = permission.permissionCode.get - if (PermissionTypeAndCodes(permission.name) != code) { - throw BadRequestException( - s"Given permission code $code and permission name ${permission.name} are not consistent." - ) - } - } - } - /** * For administrative permission we only need the name parameter of each PermissionADM given in hasPermissions collection. * This method, validates the content of hasPermissions collection by only keeping the values of name params. @@ -88,39 +49,5 @@ object PermissionsMessagesUtilADM { updatedPermissions } - /** - * For default object access permission, we need to make sure that the value given for the permissionCode matches - * the value of name parameter. - * This method, validates the content of hasPermissions collection by verifying that both permissionCode and name - * indicate the same type of permission. - * - * @param hasPermissions Set of the permissions. - */ - def verifyHasPermissionsDOAP(hasPermissions: Set[PermissionADM]): Set[PermissionADM] = { - validateDOAPHasPermissions(hasPermissions) - hasPermissions.map { permission => - val code: Int = permission.permissionCode match { - case None => PermissionTypeAndCodes(permission.name) - case Some(code) => code - } - val name = permission.name.isEmpty match { - case true => - val nameCodeSet: Option[(String, Int)] = PermissionTypeAndCodes.find { case (_, code) => - code == permission.permissionCode.get - } - nameCodeSet.get._1 - case false => permission.name - } - PermissionADM( - name = name, - additionalInformation = permission.additionalInformation, - permissionCode = Some(code) - ) - } - } - - def checkPermissionIri(iri: IRI): IRI = { - implicit val sf: StringFormatter = StringFormatter.getInstanceForConstantOntologies - sf.validatePermissionIri(iri).fold(e => throw BadRequestException(e), v => v) - } + def checkPermissionIri(iri: IRI): IRI = PermissionIri.from(iri).fold(e => throw BadRequestException(e), _.value) } diff --git a/webapi/src/main/scala/org/knora/webapi/responders/admin/GroupsResponderADM.scala b/webapi/src/main/scala/org/knora/webapi/responders/admin/GroupsResponderADM.scala index c64615aa4a..f541fbcc67 100644 --- a/webapi/src/main/scala/org/knora/webapi/responders/admin/GroupsResponderADM.scala +++ b/webapi/src/main/scala/org/knora/webapi/responders/admin/GroupsResponderADM.scala @@ -35,6 +35,8 @@ import org.knora.webapi.responders.IriLocker import org.knora.webapi.responders.IriService import org.knora.webapi.responders.Responder import org.knora.webapi.slice.admin.AdminConstants +import org.knora.webapi.slice.admin.domain.model.GroupIri +import org.knora.webapi.slice.admin.domain.model.KnoraProject.Shortcode import org.knora.webapi.slice.admin.domain.model.User import org.knora.webapi.store.triplestore.api.TriplestoreService import org.knora.webapi.store.triplestore.api.TriplestoreService.Queries.Ask @@ -375,7 +377,7 @@ final case class GroupsResponderADMLive( customGroupIri: Option[SmartIri] = createRequest.id.map(_.value).map(iri => iri.toSmartIri) groupIri <- iriService.checkOrCreateEntityIri( customGroupIri, - stringFormatter.makeRandomGroupIri(projectADM.shortcode) + GroupIri.makeNew(Shortcode.unsafeFrom(projectADM.shortcode)).value ) /* create the group */ diff --git a/webapi/src/main/scala/org/knora/webapi/responders/admin/PermissionsResponderADM.scala b/webapi/src/main/scala/org/knora/webapi/responders/admin/PermissionsResponderADM.scala index 0e79d59040..8bc5964109 100644 --- a/webapi/src/main/scala/org/knora/webapi/responders/admin/PermissionsResponderADM.scala +++ b/webapi/src/main/scala/org/knora/webapi/responders/admin/PermissionsResponderADM.scala @@ -6,24 +6,28 @@ package org.knora.webapi.responders.admin import com.typesafe.scalalogging.LazyLogging import zio.* +import zio.macros.accessible import java.util.UUID import scala.collection.immutable.Iterable import scala.collection.mutable.ListBuffer import dsp.errors.* +import dsp.valueobjects.Iri import org.knora.webapi.* import org.knora.webapi.config.AppConfig import org.knora.webapi.core.MessageHandler import org.knora.webapi.core.MessageRelay import org.knora.webapi.messages.IriConversions.* import org.knora.webapi.messages.OntologyConstants +import org.knora.webapi.messages.OntologyConstants.KnoraBase.EntityPermissionAbbreviations import org.knora.webapi.messages.ResponderRequest import org.knora.webapi.messages.SmartIri import org.knora.webapi.messages.StringFormatter import org.knora.webapi.messages.admin.responder.groupsmessages.GroupADM import org.knora.webapi.messages.admin.responder.groupsmessages.GroupGetADM import org.knora.webapi.messages.admin.responder.permissionsmessages +import org.knora.webapi.messages.admin.responder.permissionsmessages.PermissionsMessagesUtilADM.PermissionTypeAndCodes import org.knora.webapi.messages.admin.responder.permissionsmessages.* import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectADM import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectGetADM @@ -36,7 +40,13 @@ import org.knora.webapi.responders.IriLocker import org.knora.webapi.responders.IriService import org.knora.webapi.responders.Responder import org.knora.webapi.slice.admin.AdminConstants +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.PermissionIri import org.knora.webapi.slice.admin.domain.model.User +import org.knora.webapi.slice.admin.domain.service.KnoraProjectRepo +import org.knora.webapi.slice.common.api.AuthorizationRestService 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.Construct @@ -47,6 +57,7 @@ import org.knora.webapi.util.ZioHelper /** * Provides information about permissions to other responders. */ +@accessible trait PermissionsResponderADM { /** @@ -57,6 +68,158 @@ trait PermissionsResponderADM { * @return a the user's resulting set of administrative permissions for each project. */ def userAdministrativePermissionsGetADM(groupsPerProject: Map[IRI, Seq[IRI]]): Task[Map[IRI, Set[PermissionADM]]] + + /** + * Adds a new administrative permission (internal use). + * + * @param createRequest the administrative permission to add. + * @param requestingUser the requesting user. + * @param apiRequestID the API request ID. + * @return an optional [[AdministrativePermissionADM]] + */ + def createAdministrativePermission( + createRequest: CreateAdministrativePermissionAPIRequestADM, + requestingUser: User, + apiRequestID: UUID + ): Task[AdministrativePermissionCreateResponseADM] + + /** + * Gets a single administrative permission identified by project and group. + * + * @param projectIri the project. + * @param groupIri the group. + * @return an [[AdministrativePermissionGetResponseADM]] + */ + def getPermissionsApByProjectAndGroupIri( + projectIri: IRI, + groupIri: IRI + ): Task[AdministrativePermissionGetResponseADM] + + /** + * Gets all administrative permissions defined inside a project. + * + * @param projectIRI the IRI of the project. + * @return a list of IRIs of [[AdministrativePermissionADM]] objects. + */ + def getPermissionsApByProjectIri(projectIRI: IRI): Task[AdministrativePermissionsForProjectGetResponseADM] + + /** + * Delete a permission. + * + * @param permissionIri the IRI of the permission. + * @param requestingUser the [[UserADM]] of the requesting user. + * @param apiRequestID the API request ID. + * @return [[PermissionDeleteResponseADM]]. + * fails with an UpdateNotPerformedException if permission was in use and could not be deleted or something else went wrong. + * fails with a NotFoundException if no permission is found for the given IRI. + */ + def deletePermission( + permissionIri: PermissionIri, + requestingUser: User, + apiRequestID: UUID + ): Task[PermissionDeleteResponseADM] + + def createDefaultObjectAccessPermission( + createRequest: CreateDefaultObjectAccessPermissionAPIRequestADM, + user: User, + apiRequestID: UUID + ): Task[DefaultObjectAccessPermissionCreateResponseADM] + + /** + * For default object access permission, we need to make sure that the value given for the permissionCode matches + * the value of name parameter. + * This method, validates the content of hasPermissions collection by verifying that both permissionCode and name + * indicate the same type of permission. + * + * @param hasPermissions Set of the permissions. + */ + def verifyHasPermissionsDOAP(hasPermissions: Set[PermissionADM]): Task[Set[PermissionADM]] + + /** + * Gets all IRI's of all default object access permissions defined inside a project. + * + * @param projectIri the IRI of the project. + * @return a list of IRIs of [[DefaultObjectAccessPermissionADM]] objects. + */ + def getPermissionsDaopByProjectIri( + projectIri: ProjectIri + ): Task[DefaultObjectAccessPermissionsForProjectGetResponseADM] + + /** + * Gets all permissions defined inside a project. + * + * @param projectIri the IRI of the project. + * @return a list of of [[PermissionInfoADM]] objects. + */ + def getPermissionsByProjectIri(projectIri: ProjectIri): Task[PermissionsForProjectGetResponseADM] + + /** + * Update a permission's group + * + * @param permissionIri the IRI of the permission. + * @param groupIri the [[GroupIri]] to change. + * @param requestingUser the [[User]] of the requesting user. + * @param apiRequestID the API request ID. + * @return [[PermissionGetResponseADM]]. + * fails with an UpdateNotPerformedException if something has gone wrong. + */ + def updatePermissionsGroup( + permissionIri: PermissionIri, + groupIri: GroupIri, + requestingUser: User, + apiRequestID: UUID + ): Task[PermissionGetResponseADM] + + /** + * Update a permission's set of hasPermissions. + * + * @param permissionIri the IRI of the permission. + * @param newHasPermissions the request to change hasPermissions. + * @param requestingUser the [[User]] of the requesting user. + * @param apiRequestID the API request ID. + * @return [[PermissionGetResponseADM]]. + * fails with an UpdateNotPerformedException if something has gone wrong. + */ + def updatePermissionHasPermissions( + permissionIri: PermissionIri, + newHasPermissions: NonEmptyChunk[PermissionADM], + requestingUser: User, + apiRequestID: UUID + ): Task[PermissionGetResponseADM] + + /** + * Update a doap permission's resource class. + * + * @param permissionIri the IRI of the permission. + * @param changePermissionResourceClass the request to change hasPermissions. + * @param requestingUser the [[User]] of the requesting user. + * @param apiRequestID the API request ID. + * @return [[PermissionGetResponseADM]]. + * fails with an UpdateNotPerformedException if something has gone wrong. + */ + def updatePermissionResourceClass( + permissionIri: PermissionIri, + changePermissionResourceClass: ChangePermissionResourceClassApiRequestADM, + requestingUser: User, + apiRequestID: UUID + ): Task[PermissionGetResponseADM] + + /** + * Update a doap permission's property. + * + * @param permissionIri the IRI of the permission. + * @param changePermissionPropertyRequest the request to change hasPermissions. + * @param requestingUser the [[User]] of the requesting user. + * @param apiRequestID the API request ID. + * @return [[PermissionGetResponseADM]]. + * fails with an UpdateNotPerformedException if something has gone wrong. + */ + def updatePermissionProperty( + permissionIri: PermissionIri, + changePermissionPropertyRequest: ChangePermissionPropertyApiRequestADM, + requestingUser: User, + apiRequestID: UUID + ): Task[PermissionGetResponseADM] } final case class PermissionsResponderADMLive( @@ -64,6 +227,8 @@ final case class PermissionsResponderADMLive( iriService: IriService, messageRelay: MessageRelay, triplestore: TriplestoreService, + projectRepo: KnoraProjectRepo, + auth: AuthorizationRestService, implicit val stringFormatter: StringFormatter ) extends PermissionsResponderADM with MessageHandler @@ -86,30 +251,14 @@ final case class PermissionsResponderADMLive( _ ) => permissionsDataGetADM(projectIris, groupIris, isInProjectAdminGroup, isInSystemAdminGroup) - case AdministrativePermissionsForProjectGetRequestADM(projectIri, _, _) => - administrativePermissionsForProjectGetRequestADM(projectIri) case AdministrativePermissionForIriGetRequestADM(administrativePermissionIri, requestingUser, _) => administrativePermissionForIriGetRequestADM(administrativePermissionIri, requestingUser) case AdministrativePermissionForProjectGroupGetADM(projectIri, groupIri, _) => administrativePermissionForProjectGroupGetADM(projectIri, groupIri) - case AdministrativePermissionForProjectGroupGetRequestADM(projectIri, groupIri, _) => - administrativePermissionForProjectGroupGetRequestADM(projectIri, groupIri) - case AdministrativePermissionCreateRequestADM( - newAdministrativePermission, - requestingUser, - apiRequestID - ) => - administrativePermissionCreateRequestADM( - newAdministrativePermission.prepareHasPermissions, - requestingUser, - apiRequestID - ) case ObjectAccessPermissionsForResourceGetADM(resourceIri, requestingUser) => objectAccessPermissionsForResourceGetADM(resourceIri, requestingUser) case ObjectAccessPermissionsForValueGetADM(valueIri, requestingUser) => objectAccessPermissionsForValueGetADM(valueIri, requestingUser) - case DefaultObjectAccessPermissionsForProjectGetRequestADM(projectIri, _, _) => - defaultObjectAccessPermissionsForProjectGetRequestADM(projectIri) case DefaultObjectAccessPermissionForIriGetRequestADM( defaultObjectAccessPermissionIri, requestingUser, @@ -151,54 +300,8 @@ final case class PermissionsResponderADMLive( PropertyEntityType, targetUser ) - case DefaultObjectAccessPermissionCreateRequestADM( - createRequest, - _, - apiRequestID - ) => - defaultObjectAccessPermissionCreateRequestADM( - createRequest.prepareHasPermissions, - apiRequestID - ) - case PermissionsForProjectGetRequestADM(projectIri, _, _) => - permissionsForProjectGetRequestADM(projectIri) case PermissionByIriGetRequestADM(permissionIri, requestingUser) => permissionByIriGetRequestADM(permissionIri, requestingUser) - case PermissionChangeGroupRequestADM(permissionIri, changePermissionGroupRequest, requestingUser, apiRequestID) => - permissionGroupChangeRequestADM(permissionIri, changePermissionGroupRequest, requestingUser, apiRequestID) - case PermissionChangeHasPermissionsRequestADM( - permissionIri, - changePermissionHasPermissionsRequest, - requestingUser, - apiRequestID - ) => - permissionHasPermissionsChangeRequestADM( - permissionIri, - changePermissionHasPermissionsRequest, - requestingUser, - apiRequestID - ) - case PermissionChangeResourceClassRequestADM( - permissionIri, - changePermissionResourceClassRequest, - requestingUser, - apiRequestID - ) => - permissionResourceClassChangeRequestADM( - permissionIri, - changePermissionResourceClassRequest, - requestingUser, - apiRequestID - ) - case PermissionChangePropertyRequestADM( - permissionIri, - changePermissionPropertyRequest, - requestingUser, - apiRequestID - ) => - permissionPropertyChangeRequestADM(permissionIri, changePermissionPropertyRequest, requestingUser, apiRequestID) - case PermissionDeleteRequestADM(permissionIri, requestingUser, apiRequestID) => - permissionDeleteRequestADM(permissionIri, requestingUser, apiRequestID) case other => Responder.handleUnexpectedMessage(other, this.getClass.getName) } @@ -453,13 +556,7 @@ final case class PermissionsResponderADMLive( result } - /** - * Gets all administrative permissions defined inside a project. - * - * @param projectIRI the IRI of the project. - * @return a list of IRIs of [[AdministrativePermissionADM]] objects. - */ - private def administrativePermissionsForProjectGetRequestADM(projectIRI: IRI) = + override def getPermissionsApByProjectIri(projectIRI: IRI): Task[AdministrativePermissionsForProjectGetResponseADM] = for { permissionsQueryResponseRows <- triplestore @@ -571,14 +668,7 @@ final case class PermissionsResponderADMLive( } } yield permission - /** - * Gets a single administrative permission identified by project and group. - * - * @param projectIri the project. - * @param groupIri the group. - * @return an [[AdministrativePermissionGetResponseADM]] - */ - private def administrativePermissionForProjectGroupGetRequestADM( + override def getPermissionsApByProjectAndGroupIri( projectIri: IRI, groupIri: IRI ): Task[AdministrativePermissionGetResponseADM] = @@ -593,30 +683,28 @@ final case class PermissionsResponderADMLive( } } yield result - /** - * Adds a new administrative permission (internal use). - * - * @param createRequest the administrative permission to add. - * @param requestingUser the requesting user. - * @param apiRequestID the API request ID. - * @return an optional [[AdministrativePermissionADM]] - */ - private def administrativePermissionCreateRequestADM( + private def validate(req: CreateAdministrativePermissionAPIRequestADM): Task[Unit] = ZIO.attempt { + req.id.foreach(iri => PermissionIri.from(iri).fold(msg => throw BadRequestException(msg), _ => ())) + + ProjectIri.from(req.forProject).fold(msg => throw BadRequestException(msg.head.getMessage), _ => ()) + + if (req.hasPermissions.isEmpty) throw BadRequestException("Permissions needs to be supplied.") + + if (!OntologyConstants.KnoraAdmin.BuiltInGroups.contains(req.forGroup)) { + GroupIri.from(req.forGroup).getOrElse(throw BadRequestException(s"Invalid group IRI ${req.forGroup}")) + } + + PermissionsMessagesUtilADM.verifyHasPermissionsAP(req.hasPermissions) + }.unit + + override def createAdministrativePermission( createRequest: CreateAdministrativePermissionAPIRequestADM, requestingUser: User, apiRequestID: UUID ): Task[AdministrativePermissionCreateResponseADM] = { - logger.debug("administrativePermissionCreateRequestADM") - - /** - * The actual change project task run with an IRI lock. - */ - def createPermissionTask( - createRequest: CreateAdministrativePermissionAPIRequestADM, - requestingUser: User - ): Task[AdministrativePermissionCreateResponseADM] = + val createAdministrativePermissionTask = for { - + _ <- validate(createRequest) // does the permission already exist checkResult <- administrativePermissionForProjectGroupGetADM(createRequest.forProject, createRequest.forGroup) @@ -667,7 +755,7 @@ final case class PermissionsResponderADMLive( customPermissionIri: Option[SmartIri] = createRequest.id.map(iri => iri.toSmartIri) newPermissionIri <- iriService.checkOrCreateEntityIri( customPermissionIri, - stringFormatter.makeRandomPermissionIri(project.shortcode) + PermissionIri.makeNew(Shortcode.unsafeFrom(project.shortcode)).value ) // Create the administrative permission. @@ -686,19 +774,10 @@ final case class PermissionsResponderADMLive( _ <- triplestore.query(Update(createAdministrativePermissionSparql)) // try to retrieve the newly created permission - maybePermission <- - administrativePermissionForIriGetRequestADM( - administrativePermissionIri = newPermissionIri, - requestingUser = requestingUser - ) - newAdminPermission: AdministrativePermissionADM = maybePermission.administrativePermission - } yield AdministrativePermissionCreateResponseADM(administrativePermission = newAdminPermission) + created <- administrativePermissionForIriGetRequestADM(newPermissionIri, requestingUser) + } yield AdministrativePermissionCreateResponseADM(created.administrativePermission) - IriLocker.runWithIriLock( - apiRequestID, - PERMISSIONS_GLOBAL_LOCK_IRI, - createPermissionTask(createRequest, requestingUser) - ) + IriLocker.runWithIriLock(apiRequestID, PERMISSIONS_GLOBAL_LOCK_IRI, createAdministrativePermissionTask) } /////////////////////////////////////////////////////////////////////////// @@ -809,18 +888,12 @@ final case class PermissionsResponderADMLive( // DEFAULT OBJECT ACCESS PERMISSIONS /////////////////////////////////////////////////////////////////////////// - /** - * Gets all IRI's of all default object access permissions defined inside a project. - * - * @param projectIri the IRI of the project. - * @return a list of IRIs of [[DefaultObjectAccessPermissionADM]] objects. - */ - private def defaultObjectAccessPermissionsForProjectGetRequestADM( - projectIri: IRI + override def getPermissionsDaopByProjectIri( + projectIri: ProjectIri ): Task[DefaultObjectAccessPermissionsForProjectGetResponseADM] = for { permissionsQueryResponse <- - triplestore.query(Select(sparql.admin.txt.getDefaultObjectAccessPermissionsForProject(projectIri))) + triplestore.query(Select(sparql.admin.txt.getDefaultObjectAccessPermissionsForProject(projectIri.value))) /* extract response rows */ permissionsQueryResponseRows = permissionsQueryResponse.results.bindings @@ -1389,18 +1462,63 @@ final case class PermissionsResponderADMLive( } } yield result - private def defaultObjectAccessPermissionCreateRequestADM( + private def validate(req: CreateDefaultObjectAccessPermissionAPIRequestADM) = ZIO.attempt { + val sf: StringFormatter = StringFormatter.getInstanceForConstantOntologies + + req.id.foreach(iri => PermissionIri.from(iri).fold(msg => throw BadRequestException(msg), _ => ())) + + Iri + .validateAndEscapeProjectIri(req.forProject) + .getOrElse(throw BadRequestException(s"Invalid project IRI ${req.forProject}")) + + (req.forGroup, req.forResourceClass, req.forProperty) match { + case (None, None, None) => + throw BadRequestException( + "Either a group, a resource class, a property, or a combination of resource class and property must be given." + ) + case (Some(_), Some(_), _) => + throw BadRequestException("Not allowed to supply groupIri and resourceClassIri together.") + case (Some(_), _, Some(_)) => + throw BadRequestException("Not allowed to supply groupIri and propertyIri together.") + case (Some(groupIri), None, None) => + GroupIri.from(groupIri).getOrElse(throw BadRequestException(s"Invalid group IRI $groupIri")) + case (None, resourceClassIriMaybe, propertyIriMaybe) => + resourceClassIriMaybe.foreach { resourceClassIri => + if (!sf.toSmartIri(resourceClassIri).isKnoraEntityIri) { + throw BadRequestException(s"Invalid resource class IRI: $resourceClassIri") + } + } + propertyIriMaybe.foreach { propertyIri => + if (!sf.toSmartIri(propertyIri).isKnoraEntityIri) { + throw BadRequestException(s"Invalid property IRI: $propertyIri") + } + } + case _ => () + } + + if (req.hasPermissions.isEmpty) throw BadRequestException("Permissions needs to be supplied.") + } + + override def createDefaultObjectAccessPermission( createRequest: CreateDefaultObjectAccessPermissionAPIRequestADM, + user: User, apiRequestID: UUID ): Task[DefaultObjectAccessPermissionCreateResponseADM] = { /** * The actual change project task run with an IRI lock. */ - def createPermissionTask( - createRequest: CreateDefaultObjectAccessPermissionAPIRequestADM - ): Task[DefaultObjectAccessPermissionCreateResponseADM] = + val createPermissionTask = for { + _ <- validate(createRequest) + projectIri <- ZIO + .fromEither(ProjectIri.from(createRequest.forProject).toEither) + .mapError(_.map(_.getMessage).mkString(",")) + .mapError(BadRequestException.apply) + project <- projectRepo + .findById(projectIri) + .someOrFail(NotFoundException(s"Project ${projectIri.value} not found")) + _ <- auth.ensureSystemAdminSystemUserOrProjectAdmin(user, project) checkResult <- defaultObjectAccessPermissionGetADM( createRequest.forProject, createRequest.forGroup, @@ -1431,27 +1549,10 @@ final case class PermissionsResponderADMLive( case None => () } - // get project - maybeProject <- - messageRelay - .ask[Option[ProjectADM]]( - ProjectGetADM( - identifier = IriIdentifier - .fromString(createRequest.forProject) - .getOrElseWith(e => throw BadRequestException(e.head.getMessage)) - ) - ) - - // if it doesnt exist then throw an error - project: ProjectADM = - maybeProject.getOrElse( - throw NotFoundException(s"Project '${createRequest.forProject}' not found. Aborting request.") - ) - customPermissionIri: Option[SmartIri] = createRequest.id.map(iri => iri.toSmartIri) newPermissionIri <- iriService.checkOrCreateEntityIri( customPermissionIri, - stringFormatter.makeRandomPermissionIri(project.shortcode) + PermissionIri.makeNew(project.shortcode).value ) // verify group, if any given. // Is a group given that is not a built-in one? @@ -1480,17 +1581,18 @@ final case class PermissionsResponderADMLive( } // Create the default object access permission. + permissions <- verifyHasPermissionsDOAP(createRequest.hasPermissions) createNewDefaultObjectAccessPermissionSparqlString = sparql.admin.txt.createNewDefaultObjectAccessPermission( AdminConstants.permissionsDataNamedGraph.value, permissionIri = newPermissionIri, permissionClassIri = OntologyConstants.KnoraAdmin.DefaultObjectAccessPermission, - projectIri = project.id, + projectIri = project.id.value, maybeGroupIri = maybeGroupIri, maybeResourceClassIri = createRequest.forResourceClass, maybePropertyIri = createRequest.forProperty, permissions = PermissionUtilADM.formatPermissionADMs( - createRequest.hasPermissions, + permissions, PermissionType.OAP ) ) @@ -1515,26 +1617,85 @@ final case class PermissionsResponderADMLive( newDefaultObjectAcessPermission ) - IriLocker.runWithIriLock( - apiRequestID, - PERMISSIONS_GLOBAL_LOCK_IRI, - createPermissionTask(createRequest) - ) + IriLocker.runWithIriLock(apiRequestID, PERMISSIONS_GLOBAL_LOCK_IRI, createPermissionTask) + } + + override def verifyHasPermissionsDOAP(hasPermissions: Set[PermissionADM]): Task[Set[PermissionADM]] = ZIO.attempt { + validateDOAPHasPermissions(hasPermissions) + hasPermissions.map { permission => + val code: Int = permission.permissionCode match { + case None => PermissionTypeAndCodes(permission.name) + case Some(code) => code + } + val name = permission.name.isEmpty match { + case true => + val nameCodeSet: Option[(String, Int)] = PermissionTypeAndCodes.find { case (_, code) => + code == permission.permissionCode.get + } + nameCodeSet.get._1 + case false => permission.name + } + PermissionADM( + name = name, + additionalInformation = permission.additionalInformation, + permissionCode = Some(code) + ) + } } + /** + * Validates the parameters of the `hasPermissions` collections of a DOAP. + * + * @param hasPermissions Set of the permissions. + */ + private def validateDOAPHasPermissions(hasPermissions: Set[PermissionADM]): Unit = + hasPermissions.foreach { permission => + if (permission.additionalInformation.isEmpty) { + throw BadRequestException(s"additionalInformation of a default object access permission type cannot be empty.") + } + if (permission.name.nonEmpty && !EntityPermissionAbbreviations.contains(permission.name)) + throw BadRequestException( + s"Invalid value for name parameter of hasPermissions: ${permission.name}, it should be one of " + + s"${EntityPermissionAbbreviations.toString}" + ) + if (permission.permissionCode.nonEmpty) { + val code = permission.permissionCode.get + if (!PermissionTypeAndCodes.values.toSet.contains(code)) { + throw BadRequestException( + s"Invalid value for permissionCode parameter of hasPermissions: $code, it should be one of " + + s"${PermissionTypeAndCodes.values.toString}" + ) + } + } + if (permission.permissionCode.isEmpty && permission.name.isEmpty) { + throw BadRequestException( + s"One of permission code or permission name must be provided for a default object access permission." + ) + } + if (permission.permissionCode.nonEmpty && permission.name.nonEmpty) { + val code = permission.permissionCode.get + if (PermissionTypeAndCodes(permission.name) != code) { + throw BadRequestException( + s"Given permission code $code and permission name ${permission.name} are not consistent." + ) + } + } + } + /** * Gets all permissions defined inside a project. * - * @param projectIRI the IRI of the project. + * @param projectIri the IRI of the project. * @return a list of of [[PermissionInfoADM]] objects. */ - private def permissionsForProjectGetRequestADM(projectIRI: IRI): Task[PermissionsForProjectGetResponseADM] = + override def getPermissionsByProjectIri(projectIri: ProjectIri): Task[PermissionsForProjectGetResponseADM] = for { - permissionsQueryResponseStatements <- triplestore - .query(Construct(sparql.admin.txt.getProjectPermissions(projectIRI))) - .map(_.statements) + permissionsQueryResponseStatements <- + triplestore + .query(Construct(sparql.admin.txt.getProjectPermissions(projectIri.value))) + .map(_.statements) _ <- ZIO.when(permissionsQueryResponseStatements.isEmpty) { - ZIO.fail(NotFoundException(s"No permission could be found for $projectIRI.")) + ZIO.fail(NotFoundException(s"No permission could be found for ${projectIri.value}.")) } permissionsInfo = permissionsQueryResponseStatements.map { statement => @@ -1542,46 +1703,33 @@ final case class PermissionsResponderADMLive( val (_, permissionType) = statement._2.filter(_._1 == OntologyConstants.Rdf.Type).head PermissionInfoADM(iri = permissionIri, permissionType = permissionType) }.toSet - } yield permissionsmessages.PermissionsForProjectGetResponseADM(permissionsInfo) + } yield PermissionsForProjectGetResponseADM(permissionsInfo) - /** - * Update a permission's group - * - * @param permissionIri the IRI of the permission. - * @param changePermissionGroupRequest the request to change group. - * @param requestingUser the [[User]] of the requesting user. - * @param apiRequestID the API request ID. - * @return [[PermissionGetResponseADM]]. - * fails with an UpdateNotPerformedException if something has gone wrong. - */ - private def permissionGroupChangeRequestADM( - permissionIri: IRI, - changePermissionGroupRequest: ChangePermissionGroupApiRequestADM, + override def updatePermissionsGroup( + permissionIri: PermissionIri, + groupIri: GroupIri, requestingUser: User, apiRequestID: UUID ): Task[PermissionGetResponseADM] = { /* verify that the permission group is updated */ - def verifyPermissionGroupUpdate: Task[PermissionItemADM] = + val verifyPermissionGroupUpdate = for { - updatedPermission <- permissionGetADM( - permissionIri = permissionIri, - requestingUser = requestingUser - ) + updatedPermission <- permissionGetADM(permissionIri.value, requestingUser) _ = updatedPermission match { case ap: AdministrativePermissionADM => - if (ap.forGroup != changePermissionGroupRequest.forGroup) + if (ap.forGroup != groupIri.value) throw UpdateNotPerformedException( - s"The group of permission $permissionIri was not updated. Please report this as a bug." + s"The group of permission ${permissionIri.value} was not updated. Please report this as a bug." ) case doap: DefaultObjectAccessPermissionADM => - if (doap.forGroup.get != changePermissionGroupRequest.forGroup) { + if (doap.forGroup.get != groupIri.value) { throw UpdateNotPerformedException( - s"The group of permission $permissionIri was not updated. Please report this as a bug." + s"The group of permission ${permissionIri.value} was not updated. Please report this as a bug." ) } else { if (doap.forProperty.isDefined || doap.forResourceClass.isDefined) throw UpdateNotPerformedException( - s"The $permissionIri is not correctly updated. Please report this as a bug." + s"The ${permissionIri.value} is not correctly updated. Please report this as a bug." ) } } @@ -1590,23 +1738,16 @@ final case class PermissionsResponderADMLive( /** * The actual task run with an IRI lock. */ - def permissionGroupChangeTask( - permissioniri: IRI, - changePermissionGroupRequest: ChangePermissionGroupApiRequestADM, - requestingUser: User - ): Task[PermissionGetResponseADM] = + val permissionGroupChangeTask: Task[PermissionGetResponseADM] = for { // get permission - permission <- permissionGetADM(permissioniri, requestingUser) + permission <- permissionGetADM(permissionIri.value, requestingUser) response <- permission match { // Is permission an administrative permission? case ap: AdministrativePermissionADM => // Yes. Update the group for { - _ <- updatePermission( - permissionIri = ap.iri, - maybeGroup = Some(changePermissionGroupRequest.forGroup) - ) + _ <- updatePermission(permissionIri = ap.iri, maybeGroup = Some(groupIri.value)) updatedPermission <- verifyPermissionGroupUpdate } yield AdministrativePermissionGetResponseADM( updatedPermission.asInstanceOf[AdministrativePermissionADM] @@ -1615,10 +1756,7 @@ final case class PermissionsResponderADMLive( // No. It is a default object access permission for { // if a doap permission has a group defined, it cannot have either resourceClass or property - _ <- updatePermission( - permissionIri = doap.iri, - maybeGroup = Some(changePermissionGroupRequest.forGroup) - ) + _ <- updatePermission(permissionIri = doap.iri, maybeGroup = Some(groupIri.value)) updatedPermission <- verifyPermissionGroupUpdate } yield DefaultObjectAccessPermissionGetResponseADM( updatedPermission.asInstanceOf[DefaultObjectAccessPermissionADM] @@ -1626,30 +1764,26 @@ final case class PermissionsResponderADMLive( } } yield response - IriLocker.runWithIriLock( - apiRequestID, - permissionIri, - permissionGroupChangeTask(permissionIri, changePermissionGroupRequest, requestingUser) - ) + IriLocker.runWithIriLock(apiRequestID, permissionIri.value, permissionGroupChangeTask) } /** * Update a permission's set of hasPermissions. * * @param permissionIri the IRI of the permission. - * @param changeHasPermissionsRequest the request to change hasPermissions. + * @param newHasPermissions the request to change hasPermissions. * @param requestingUser the [[User]] of the requesting user. * @param apiRequestID the API request ID. * @return [[PermissionGetResponseADM]]. * fails with an UpdateNotPerformedException if something has gone wrong. */ - private def permissionHasPermissionsChangeRequestADM( - permissionIri: IRI, - changeHasPermissionsRequest: ChangePermissionHasPermissionsApiRequestADM, + override def updatePermissionHasPermissions( + permissionIri: PermissionIri, + newHasPermissions: NonEmptyChunk[PermissionADM], requestingUser: User, apiRequestID: UUID ): Task[PermissionGetResponseADM] = { - val permissionIriInternal = permissionIri.toSmartIri.toOntologySchema(InternalSchema).toString + val permissionIriInternal = permissionIri.value.toSmartIri.toOntologySchema(InternalSchema).toString /*Verify that hasPermissions is updated successfully*/ def verifyUpdateOfHasPermissions(expectedPermissions: Set[PermissionADM]): Task[PermissionItemADM] = for { @@ -1675,12 +1809,7 @@ final case class PermissionsResponderADMLive( /** * The actual task run with an IRI lock. */ - def permissionHasPermissionsChangeTask( - permissionIri: IRI, - changeHasPermissionsRequest: ChangePermissionHasPermissionsApiRequestADM, - requestingUser: User - ): Task[PermissionGetResponseADM] = { - val permissionIriInternal = permissionIri.toSmartIri.toOntologySchema(InternalSchema).toString + val permissionHasPermissionsChangeTask = for { // get permission permission <- permissionGetADM(permissionIriInternal, requestingUser) @@ -1689,7 +1818,7 @@ final case class PermissionsResponderADMLive( case ap: AdministrativePermissionADM => // Yes. val verifiedPermissions = - PermissionsMessagesUtilADM.verifyHasPermissionsAP(changeHasPermissionsRequest.hasPermissions) + PermissionsMessagesUtilADM.verifyHasPermissionsAP(newHasPermissions.toSet) for { formattedPermissions <- ZIO.attempt( @@ -1703,11 +1832,8 @@ final case class PermissionsResponderADMLive( ) case doap: DefaultObjectAccessPermissionADM => // No. It is a default object access permission. - val verifiedPermissions = - PermissionsMessagesUtilADM.verifyHasPermissionsDOAP( - changeHasPermissionsRequest.hasPermissions - ) for { + verifiedPermissions <- verifyHasPermissionsDOAP(newHasPermissions.toSet) formattedPermissions <- ZIO.attempt( PermissionUtilADM.formatPermissionADMs(verifiedPermissions, PermissionType.OAP) @@ -1720,17 +1846,12 @@ final case class PermissionsResponderADMLive( ) case _ => throw UpdateNotPerformedException( - s"Permission $permissionIri was not updated. Please report this as a bug." + s"Permission ${permissionIri.value} was not updated. Please report this as a bug." ) } } yield response - } - IriLocker.runWithIriLock( - apiRequestID, - permissionIri, - permissionHasPermissionsChangeTask(permissionIri, changeHasPermissionsRequest, requestingUser) - ) + IriLocker.runWithIriLock(apiRequestID, permissionIri.value, permissionHasPermissionsChangeTask) } /** @@ -1743,56 +1864,54 @@ final case class PermissionsResponderADMLive( * @return [[PermissionGetResponseADM]]. * fails with an UpdateNotPerformedException if something has gone wrong. */ - private def permissionResourceClassChangeRequestADM( - permissionIri: IRI, + override def updatePermissionResourceClass( + permissionIri: PermissionIri, changePermissionResourceClass: ChangePermissionResourceClassApiRequestADM, requestingUser: User, apiRequestID: UUID ): Task[PermissionGetResponseADM] = { - val permissionIriInternal = permissionIri.toSmartIri.toOntologySchema(InternalSchema).toString + val permissionIriInternal = permissionIri.value.toSmartIri.toOntologySchema(InternalSchema).toString /*Verify that resource class of doap is updated successfully*/ - def verifyUpdateOfResourceClass: Task[PermissionItemADM] = + val verifyUpdateOfResourceClass = for { updatedPermission <- permissionGetADM(permissionIriInternal, requestingUser) /*Verify that update was successful*/ - _ = updatedPermission match { - case doap: DefaultObjectAccessPermissionADM => - if (doap.forResourceClass.get != changePermissionResourceClass.forResourceClass) - throw UpdateNotPerformedException( - s"The resource class of ${doap.iri} was not updated. Please report this as a bug." - ) + _ <- ZIO.attempt(updatedPermission match { + case doap: DefaultObjectAccessPermissionADM => + if (doap.forResourceClass.get != changePermissionResourceClass.forResourceClass) + throw UpdateNotPerformedException( + s"The resource class of ${doap.iri} was not updated. Please report this as a bug." + ) - if (doap.forGroup.isDefined) - throw UpdateNotPerformedException( - s"The $permissionIriInternal is not correctly updated. Please report this as a bug." - ) + if (doap.forGroup.isDefined) + throw UpdateNotPerformedException( + s"The $permissionIriInternal is not correctly updated. Please report this as a bug." + ) - case _ => - throw UpdateNotPerformedException( - s"Incorrect permission type returned for $permissionIriInternal. Please report this as a bug." - ) - } + case _ => + throw UpdateNotPerformedException( + s"Incorrect permission type returned for $permissionIriInternal. Please report this as a bug." + ) + }) } yield updatedPermission /** * The actual task run with an IRI lock. */ - def permissionResourceClassChangeTask( - permissionIri: IRI, - changePermissionResourceClass: ChangePermissionResourceClassApiRequestADM, - requestingUser: User - ): Task[PermissionGetResponseADM] = + val permissionResourceClassChangeTask: Task[PermissionGetResponseADM] = for { // get permission - permission <- permissionGetADM(permissionIri, requestingUser) + permission <- permissionGetADM(permissionIri.value, requestingUser) response <- permission match { // Is permission an administrative permission? case ap: AdministrativePermissionADM => // Yes. - throw BadRequestException( - s"Permission ${ap.iri} is of type administrative permission. " + - s"Only a default object access permission defined for a resource class can be updated." + ZIO.fail( + ForbiddenException( + s"Permission ${ap.iri} is of type administrative permission. " + + s"Only a default object access permission defined for a resource class can be updated." + ) ) case doap: DefaultObjectAccessPermissionADM => // No. It is a default object access permission. @@ -1806,79 +1925,65 @@ final case class PermissionsResponderADMLive( updatedPermission.asInstanceOf[DefaultObjectAccessPermissionADM] ) case _ => - throw UpdateNotPerformedException( - s"Permission $permissionIri was not updated. Please report this as a bug." + ZIO.fail( + UpdateNotPerformedException( + s"Permission ${permissionIri.value} was not updated. Please report this as a bug." + ) ) } } yield response - IriLocker.runWithIriLock( - apiRequestID, - permissionIri, - permissionResourceClassChangeTask(permissionIri, changePermissionResourceClass, requestingUser) - ) + IriLocker.runWithIriLock(apiRequestID, permissionIri.value, permissionResourceClassChangeTask) } - /** - * Update a doap permission's property. - * - * @param permissionIri the IRI of the permission. - * @param changePermissionPropertyRequest the request to change hasPermissions. - * @param requestingUser the [[User]] of the requesting user. - * @param apiRequestID the API request ID. - * @return [[PermissionGetResponseADM]]. - * fails with an UpdateNotPerformedException if something has gone wrong. - */ - private def permissionPropertyChangeRequestADM( - permissionIri: IRI, + override def updatePermissionProperty( + permissionIri: PermissionIri, changePermissionPropertyRequest: ChangePermissionPropertyApiRequestADM, requestingUser: User, apiRequestID: UUID ): Task[PermissionGetResponseADM] = { - val permissionIriInternal = permissionIri.toSmartIri.toOntologySchema(InternalSchema).toString + val permissionIriInternal = permissionIri.value.toSmartIri.toOntologySchema(InternalSchema).toString /*Verify that property of doap is updated successfully*/ def verifyUpdateOfProperty: Task[PermissionItemADM] = for { updatedPermission <- permissionGetADM(permissionIriInternal, requestingUser) /*Verify that update was successful*/ - _ = updatedPermission match { - case doap: DefaultObjectAccessPermissionADM => - if (doap.forProperty.get != changePermissionPropertyRequest.forProperty) - throw UpdateNotPerformedException( - s"The property of ${doap.iri} was not updated. Please report this as a bug." - ) + _ <- ZIO.attempt(updatedPermission match { + case doap: DefaultObjectAccessPermissionADM => + if (doap.forProperty.get != changePermissionPropertyRequest.forProperty) + throw UpdateNotPerformedException( + s"The property of ${doap.iri} was not updated. Please report this as a bug." + ) - if (doap.forGroup.isDefined) - throw UpdateNotPerformedException( - s"The $permissionIriInternal is not correctly updated. Please report this as a bug." - ) + if (doap.forGroup.isDefined) + throw UpdateNotPerformedException( + s"The $permissionIriInternal is not correctly updated. Please report this as a bug." + ) - case _ => - throw UpdateNotPerformedException( - s"Incorrect permission type returned for $permissionIriInternal. Please report this as a bug." - ) - } + case _ => + throw UpdateNotPerformedException( + s"Incorrect permission type returned for $permissionIriInternal. Please report this as a bug." + ) + }) } yield updatedPermission /** * The actual task run with an IRI lock. */ - def permissionPropertyChangeTask( - permissionIri: IRI, - changePermissionPropertyRequest: ChangePermissionPropertyApiRequestADM, - requestingUser: User - ): Task[PermissionGetResponseADM] = + val permissionPropertyChangeTask = for { // get permission - permission <- permissionGetADM(permissionIri, requestingUser) + permission <- permissionGetADM(permissionIri.value, requestingUser) response <- permission match { // Is permission an administrative permission? case ap: AdministrativePermissionADM => // Yes. - throw BadRequestException( - s"Permission ${ap.iri} is of type administrative permission. " + - s"Only a default object access permission defined for a property can be updated." + ZIO.fail( + ForbiddenException( + s"Permission ${ap.iri} is of type administrative permission. " + + s"Only a default object access permission defined for a property can be updated." + ) ) case doap: DefaultObjectAccessPermissionADM => // No. It is a default object access permission. @@ -1892,35 +1997,23 @@ final case class PermissionsResponderADMLive( updatedPermission.asInstanceOf[DefaultObjectAccessPermissionADM] ) case _ => - throw UpdateNotPerformedException( - s"Permission $permissionIri was not updated. Please report this as a bug." + ZIO.fail( + UpdateNotPerformedException( + s"Permission $permissionIri was not updated. Please report this as a bug." + ) ) } } yield response - IriLocker.runWithIriLock( - apiRequestID, - permissionIri, - permissionPropertyChangeTask(permissionIri, changePermissionPropertyRequest, requestingUser) - ) + IriLocker.runWithIriLock(apiRequestID, permissionIri.value, permissionPropertyChangeTask) } - /** - * Delete a permission. - * - * @param permissionIri the IRI of the permission. - * @param requestingUser the [[User]] of the requesting user. - * @param apiRequestID the API request ID. - * @return [[PermissionDeleteResponseADM]]. - * fails with an UpdateNotPerformedException if permission was in use and could not be deleted or something else went wrong. - * fails with a NotFoundException if no permission is found for the given IRI. - */ - private def permissionDeleteRequestADM( - permissionIri: IRI, + override def deletePermission( + permissionIri: PermissionIri, requestingUser: User, apiRequestID: UUID ): Task[PermissionDeleteResponseADM] = { - val permissionIriInternal = permissionIri.toSmartIri.toOntologySchema(InternalSchema).toString + val permissionIriInternal = permissionIri.value.toSmartIri.toOntologySchema(InternalSchema).toString def permissionDeleteTask(): Task[PermissionDeleteResponseADM] = for { // check that there is a permission with a given IRI @@ -1929,14 +2022,14 @@ final case class PermissionsResponderADMLive( _ <- ZIO .fail(UpdateNotPerformedException(s"Permission $permissionIriInternal is in use and cannot be deleted.")) - .whenZIO(triplestore.query(Ask(sparql.admin.txt.isEntityUsed(permissionIri)))) + .whenZIO(triplestore.query(Ask(sparql.admin.txt.isEntityUsed(permissionIri.value)))) _ <- deletePermission(permissionIriInternal) sf = StringFormatter.getGeneralInstance - iriExternal = sf.toSmartIri(permissionIri).toOntologySchema(ApiV2Complex).toString + iriExternal = sf.toSmartIri(permissionIri.value).toOntologySchema(ApiV2Complex).toString } yield PermissionDeleteResponseADM(iriExternal, deleted = true) - IriLocker.runWithIriLock(apiRequestID, permissionIri, permissionDeleteTask()) + IriLocker.runWithIriLock(apiRequestID, permissionIri.value, permissionDeleteTask()) } /** @@ -1950,10 +2043,10 @@ final case class PermissionsResponderADMLive( * Checks that requesting user has right for the permission operation * * @param requestingUser the [[User]] of the requesting user. - * @param projectIri the IRI of the project the permission is attached to. - * @param permissionIri the IRI of the permission. + * @param projectIri the IRI of the project the permission is attached to. + * @param permissionIri the IRI of the permission. * - * throws ForbiddenException if the user is not a project or system admin + * throws ForbiddenException if the user is not a project or system admin */ private def verifyUsersRightForOperation(requestingUser: User, projectIri: IRI, permissionIri: IRI): Unit = if (!requestingUser.isSystemAdmin && !requestingUser.permissions.isProjectAdmin(projectIri)) { @@ -2117,16 +2210,18 @@ final case class PermissionsResponderADMLive( object PermissionsResponderADMLive { val layer: URLayer[ - StringFormatter & TriplestoreService & MessageRelay & IriService & AppConfig, + AppConfig & AuthorizationRestService & IriService & KnoraProjectRepo & MessageRelay & StringFormatter & TriplestoreService, PermissionsResponderADMLive ] = ZLayer.fromZIO { for { - config <- ZIO.service[AppConfig] - iriS <- ZIO.service[IriService] + au <- ZIO.service[AuthorizationRestService] + ac <- ZIO.service[AppConfig] + is <- ZIO.service[IriService] + kpr <- ZIO.service[KnoraProjectRepo] mr <- ZIO.service[MessageRelay] ts <- ZIO.service[TriplestoreService] sf <- ZIO.service[StringFormatter] - handler <- mr.subscribe(PermissionsResponderADMLive(config, iriS, mr, ts, sf)) + handler <- mr.subscribe(PermissionsResponderADMLive(ac, is, mr, ts, kpr, au, sf)) } yield handler } } diff --git a/webapi/src/main/scala/org/knora/webapi/responders/admin/ProjectsResponderADM.scala b/webapi/src/main/scala/org/knora/webapi/responders/admin/ProjectsResponderADM.scala index c93d7c19e0..5fe5bf1a8f 100644 --- a/webapi/src/main/scala/org/knora/webapi/responders/admin/ProjectsResponderADM.scala +++ b/webapi/src/main/scala/org/knora/webapi/responders/admin/ProjectsResponderADM.scala @@ -181,6 +181,7 @@ final case class ProjectsResponderADMLive( private val iriService: IriService, private val projectService: ProjectADMService, private val cacheServiceSettings: CacheServiceSettings, + private val permissionsResponderADM: PermissionsResponderADM, implicit private val stringFormatter: StringFormatter ) extends ProjectsResponderADM with MessageHandler @@ -683,69 +684,55 @@ final case class ProjectsResponderADMLive( def createPermissionsForAdminsAndMembersOfNewProject(projectIri: IRI): Task[Unit] = for { // Give the admins of the new project rights for any operation in project level, and rights to create resources. - _ <- messageRelay - .ask[AdministrativePermissionCreateResponseADM]( - AdministrativePermissionCreateRequestADM( - createRequest = CreateAdministrativePermissionAPIRequestADM( - forProject = projectIri, - forGroup = OntologyConstants.KnoraAdmin.ProjectAdmin, - hasPermissions = - Set(PermissionADM.ProjectAdminAllPermission, PermissionADM.ProjectResourceCreateAllPermission) - ).prepareHasPermissions, - requestingUser = requestingUser, - apiRequestID = UUID.randomUUID() - ) - ) + _ <- permissionsResponderADM.createAdministrativePermission( + CreateAdministrativePermissionAPIRequestADM( + forProject = projectIri, + forGroup = OntologyConstants.KnoraAdmin.ProjectAdmin, + hasPermissions = + Set(PermissionADM.ProjectAdminAllPermission, PermissionADM.ProjectResourceCreateAllPermission) + ), + requestingUser, + UUID.randomUUID() + ) // Give the members of the new project rights to create resources. - _ <- messageRelay - .ask[AdministrativePermissionCreateResponseADM]( - AdministrativePermissionCreateRequestADM( - createRequest = CreateAdministrativePermissionAPIRequestADM( - forProject = projectIri, - forGroup = OntologyConstants.KnoraAdmin.ProjectMember, - hasPermissions = Set(PermissionADM.ProjectResourceCreateAllPermission) - ).prepareHasPermissions, - requestingUser = requestingUser, - apiRequestID = UUID.randomUUID() - ) - ) + _ <- permissionsResponderADM.createAdministrativePermission( + CreateAdministrativePermissionAPIRequestADM( + forProject = projectIri, + forGroup = OntologyConstants.KnoraAdmin.ProjectMember, + hasPermissions = Set(PermissionADM.ProjectResourceCreateAllPermission) + ), + requestingUser, + UUID.randomUUID() + ) // Give the admins of the new project rights to change rights, modify, delete, view, // and restricted view of all resources and values that belong to the project. - _ <- messageRelay - .ask[DefaultObjectAccessPermissionCreateResponseADM]( - DefaultObjectAccessPermissionCreateRequestADM( - createRequest = CreateDefaultObjectAccessPermissionAPIRequestADM( - forProject = projectIri, - forGroup = Some(OntologyConstants.KnoraAdmin.ProjectAdmin), - hasPermissions = Set( - PermissionADM.changeRightsPermission(OntologyConstants.KnoraAdmin.ProjectAdmin), - PermissionADM.modifyPermission(OntologyConstants.KnoraAdmin.ProjectMember) - ) - ).prepareHasPermissions, - requestingUser = requestingUser, - apiRequestID = UUID.randomUUID() - ) - ) + groupAdmin <- ZIO.attempt( + CreateDefaultObjectAccessPermissionAPIRequestADM( + forProject = projectIri, + forGroup = Some(OntologyConstants.KnoraAdmin.ProjectAdmin), + hasPermissions = Set( + PermissionADM.changeRightsPermission(OntologyConstants.KnoraAdmin.ProjectAdmin), + PermissionADM.modifyPermission(OntologyConstants.KnoraAdmin.ProjectMember) + ) + ) + ) + _ <- permissionsResponderADM.createDefaultObjectAccessPermission(groupAdmin, requestingUser, UUID.randomUUID()) // Give the members of the new project rights to modify, view, and restricted view of all resources and values // that belong to the project. - _ <- messageRelay - .ask[DefaultObjectAccessPermissionCreateResponseADM]( - DefaultObjectAccessPermissionCreateRequestADM( - createRequest = CreateDefaultObjectAccessPermissionAPIRequestADM( - forProject = projectIri, - forGroup = Some(OntologyConstants.KnoraAdmin.ProjectMember), - hasPermissions = Set( - PermissionADM.changeRightsPermission(OntologyConstants.KnoraAdmin.ProjectAdmin), - PermissionADM.modifyPermission(OntologyConstants.KnoraAdmin.ProjectMember) - ) - ).prepareHasPermissions, - requestingUser = requestingUser, - apiRequestID = UUID.randomUUID() - ) - ) + groupMember <- ZIO.attempt( + CreateDefaultObjectAccessPermissionAPIRequestADM( + forProject = projectIri, + forGroup = Some(OntologyConstants.KnoraAdmin.ProjectMember), + hasPermissions = Set( + PermissionADM.changeRightsPermission(OntologyConstants.KnoraAdmin.ProjectAdmin), + PermissionADM.modifyPermission(OntologyConstants.KnoraAdmin.ProjectMember) + ) + ) + ) + _ <- permissionsResponderADM.createDefaultObjectAccessPermission(groupMember, requestingUser, UUID.randomUUID()) } yield () def projectCreateTask( @@ -888,7 +875,7 @@ final case class ProjectsResponderADMLive( object ProjectsResponderADMLive { val layer: URLayer[ - MessageRelay & TriplestoreService & StringFormatter & ProjectADMService & IriService & AppConfig, + AppConfig & IriService & MessageRelay & PermissionsResponderADM & ProjectADMService & StringFormatter & TriplestoreService, ProjectsResponderADMLive ] = ZLayer.fromZIO { for { @@ -898,7 +885,8 @@ object ProjectsResponderADMLive { sf <- ZIO.service[StringFormatter] ts <- ZIO.service[TriplestoreService] mr <- ZIO.service[MessageRelay] - handler <- mr.subscribe(ProjectsResponderADMLive(ts, mr, iris, ps, c, sf)) + pr <- ZIO.service[PermissionsResponderADM] + handler <- mr.subscribe(ProjectsResponderADMLive(ts, mr, iris, ps, c, pr, sf)) } yield handler } } diff --git a/webapi/src/main/scala/org/knora/webapi/routing/ApiRoutes.scala b/webapi/src/main/scala/org/knora/webapi/routing/ApiRoutes.scala index 71423c2c9b..d8c6b99a2b 100644 --- a/webapi/src/main/scala/org/knora/webapi/routing/ApiRoutes.scala +++ b/webapi/src/main/scala/org/knora/webapi/routing/ApiRoutes.scala @@ -106,7 +106,6 @@ private final case class ApiRoutesImpl( ListsRouteADM(routeData, runtime).makeRoute ~ ListsRouteV2().makeRoute ~ OntologiesRouteV2().makeRoute ~ - PermissionsRouteADM(routeData, runtime).makeRoute ~ RejectingRoute(appConfig, runtime).makeRoute ~ ResourcesRouteV2(appConfig).makeRoute ~ StandoffRouteV2().makeRoute ~ diff --git a/webapi/src/main/scala/org/knora/webapi/routing/RouteUtilADM.scala b/webapi/src/main/scala/org/knora/webapi/routing/RouteUtilADM.scala index d732ea660e..ab0632f3db 100644 --- a/webapi/src/main/scala/org/knora/webapi/routing/RouteUtilADM.scala +++ b/webapi/src/main/scala/org/knora/webapi/routing/RouteUtilADM.scala @@ -6,15 +6,12 @@ package org.knora.webapi.routing import org.apache.pekko.http.scaladsl.model.ContentTypes.`application/json` -import org.apache.pekko.http.scaladsl.model.StatusCodes.Accepted import org.apache.pekko.http.scaladsl.model.StatusCodes.OK import org.apache.pekko.http.scaladsl.model.* import org.apache.pekko.http.scaladsl.server.RequestContext import org.apache.pekko.http.scaladsl.server.RouteResult import org.apache.pekko.util.ByteString import zio.* -import zio.json.EncoderOps -import zio.json.JsonEncoder import java.util.UUID import scala.concurrent.Future @@ -43,7 +40,7 @@ object RouteUtilADM { * @param response the response that should be transformed * @return the transformed [[KnoraResponseADM]] */ - private def transformResponseIntoExternalFormat( + def transformResponseIntoExternalFormat( response: KnoraResponseADM ): ZIO[StringFormatter, Throwable, KnoraResponseADM] = ZIO.serviceWithZIO[StringFormatter] { sf => ZIO.attempt { @@ -108,19 +105,6 @@ object RouteUtilADM { )(implicit runtime: Runtime[R & StringFormatter & MessageRelay]): Future[RouteResult] = UnsafeZioRun.runToFuture(requestTask.flatMap(doRunJsonRoute(_, requestContext))) - /** - * Sends a message to a responder and completes the HTTP request by returning the response as JSON. - * - * @param requestFuture A [[Task]] containing a [[KnoraRequestADM]] message that should be sent to the responder manager. - * @param requestContext The pekko-http [[RequestContext]]. - * @return a [[Future]] containing a [[RouteResult]]. - */ - def runJsonRouteF( - requestFuture: Future[KnoraRequestADM], - requestContext: RequestContext - )(implicit runtime: Runtime[StringFormatter & MessageRelay]): Future[RouteResult] = - UnsafeZioRun.runToFuture(ZIO.fromFuture(_ => requestFuture).flatMap(doRunJsonRoute(_, requestContext))) - def runJsonRoute( request: KnoraRequestADM, requestContext: RequestContext @@ -139,20 +123,14 @@ object RouteUtilADM { for { knoraResponse <- MessageRelay.ask[KnoraResponseADM](request) knoraResponseExternal <- transformResponseIntoExternalFormat(knoraResponse) - } yield okResponse(knoraResponseExternal) - - def okResponse(response: KnoraResponseADM): HttpResponse = okResponse(response.toJsValue.asJsObject.compactPrint) - def okResponse[A](response: A)(implicit encoder: JsonEncoder[A]): HttpResponse = okResponse(response.toJson) - private def okResponse(body: String) = HttpResponse(OK, entity = HttpEntity(`application/json`, ByteString(body))) - def acceptedResponse(body: String) = HttpResponse(Accepted, entity = HttpEntity(`application/json`, ByteString(body))) + } yield HttpResponse( + OK, + entity = HttpEntity(`application/json`, ByteString(knoraResponseExternal.toJsValue.asJsObject.compactPrint)) + ) private def completeContext(ctx: RequestContext, response: HttpResponse): Task[RouteResult] = ZIO.fromFuture(_ => ctx.complete(response)) - def completeContext[R](ctx: RequestContext, response: ZIO[R, Throwable, HttpResponse])(implicit - runtime: Runtime[R] - ): Future[RouteResult] = UnsafeZioRun.runToFuture(response.flatMap(completeContext(ctx, _))) - case class IriUserUuid(iri: IRI, user: User, uuid: UUID) case class IriUser(iri: IRI, user: User) case class UserUuid(user: User, uuid: UUID) diff --git a/webapi/src/main/scala/org/knora/webapi/routing/admin/GroupsRouteADM.scala b/webapi/src/main/scala/org/knora/webapi/routing/admin/GroupsRouteADM.scala index fe2f50d147..52c1afdbec 100644 --- a/webapi/src/main/scala/org/knora/webapi/routing/admin/GroupsRouteADM.scala +++ b/webapi/src/main/scala/org/knora/webapi/routing/admin/GroupsRouteADM.scala @@ -5,14 +5,15 @@ package org.knora.webapi.routing.admin -import org.apache.pekko +import org.apache.pekko.http.scaladsl.server.Directives.* +import org.apache.pekko.http.scaladsl.server.PathMatcher +import org.apache.pekko.http.scaladsl.server.Route import zio.* import zio.prelude.Validation import dsp.errors.BadRequestException import dsp.valueobjects.Group.* import dsp.valueobjects.Iri -import dsp.valueobjects.Iri.* import org.knora.webapi.core.MessageRelay import org.knora.webapi.messages.StringFormatter import org.knora.webapi.messages.admin.responder.groupsmessages.* @@ -21,12 +22,9 @@ import org.knora.webapi.routing.KnoraRoute import org.knora.webapi.routing.KnoraRouteData import org.knora.webapi.routing.RouteUtilADM.* import org.knora.webapi.routing.RouteUtilZ +import org.knora.webapi.slice.admin.domain.model.GroupIri import org.knora.webapi.slice.admin.domain.model.KnoraProject.ProjectIri -import pekko.http.scaladsl.server.Directives.* -import pekko.http.scaladsl.server.PathMatcher -import pekko.http.scaladsl.server.Route - /** * Provides a routing function for API routes that deal with groups. */ @@ -76,7 +74,9 @@ final case class GroupsRouteADM( private def createGroup(): Route = path(groupsBasePath) { post { entity(as[CreateGroupApiRequestADM]) { apiRequest => requestContext => - val id: Validation[Throwable, Option[GroupIri]] = GroupIri.make(apiRequest.id) + val id: Validation[Throwable, Option[GroupIri]] = apiRequest.id + .map(id => Validation.fromEither(GroupIri.from(id).map(Some(_))).mapError(BadRequestException(_))) + .getOrElse(Validation.succeed(None)) val name: Validation[Throwable, GroupName] = GroupName.make(apiRequest.name) val descriptions: Validation[Throwable, GroupDescriptions] = GroupDescriptions.make(apiRequest.descriptions) val project: Validation[Throwable, ProjectIri] = ProjectIri.from(apiRequest.project) diff --git a/webapi/src/main/scala/org/knora/webapi/routing/admin/PermissionsRouteADM.scala b/webapi/src/main/scala/org/knora/webapi/routing/admin/PermissionsRouteADM.scala deleted file mode 100644 index 8057d7a55a..0000000000 --- a/webapi/src/main/scala/org/knora/webapi/routing/admin/PermissionsRouteADM.scala +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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.routing.admin - -import org.apache.pekko -import zio.* - -import org.knora.webapi.core.MessageRelay -import org.knora.webapi.messages.StringFormatter -import org.knora.webapi.routing -import org.knora.webapi.routing.KnoraRoute -import org.knora.webapi.routing.KnoraRouteData -import org.knora.webapi.routing.admin.permissions.* - -import pekko.http.scaladsl.server.Directives.* -import pekko.http.scaladsl.server.Route - -/** - * Provides an pekko-http-routing function for API routes that deal with permissions. - */ -final case class PermissionsRouteADM( - private val routeData: KnoraRouteData, - override protected implicit val runtime: Runtime[routing.Authenticator & StringFormatter & MessageRelay] -) extends KnoraRoute(routeData, runtime) { - private val createPermissionRoute: CreatePermissionRouteADM = CreatePermissionRouteADM(routeData, runtime) - private val getPermissionRoute: GetPermissionsRouteADM = GetPermissionsRouteADM(routeData, runtime) - private val updatePermissionRoute: UpdatePermissionRouteADM = UpdatePermissionRouteADM(routeData, runtime) - private val deletePermissionRoute: DeletePermissionRouteADM = DeletePermissionRouteADM(routeData, runtime) - - override def makeRoute: Route = - createPermissionRoute.makeRoute ~ - getPermissionRoute.makeRoute ~ - updatePermissionRoute.makeRoute ~ - deletePermissionRoute.makeRoute -} diff --git a/webapi/src/main/scala/org/knora/webapi/routing/admin/permissions/CreatePermissionRouteADM.scala b/webapi/src/main/scala/org/knora/webapi/routing/admin/permissions/CreatePermissionRouteADM.scala deleted file mode 100644 index c2eb1df37f..0000000000 --- a/webapi/src/main/scala/org/knora/webapi/routing/admin/permissions/CreatePermissionRouteADM.scala +++ /dev/null @@ -1,65 +0,0 @@ -/* - * 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.routing.admin.permissions - -import org.apache.pekko -import zio.* - -import org.knora.webapi.core.MessageRelay -import org.knora.webapi.messages.StringFormatter -import org.knora.webapi.messages.admin.responder.permissionsmessages.* -import org.knora.webapi.routing.Authenticator -import org.knora.webapi.routing.KnoraRoute -import org.knora.webapi.routing.KnoraRouteData -import org.knora.webapi.routing.RouteUtilADM.* - -import pekko.http.scaladsl.server.Directives.* -import pekko.http.scaladsl.server.PathMatcher -import pekko.http.scaladsl.server.Route - -final case class CreatePermissionRouteADM( - private val routeData: KnoraRouteData, - override protected implicit val runtime: Runtime[Authenticator & StringFormatter & MessageRelay] -) extends KnoraRoute(routeData, runtime) - with PermissionsADMJsonProtocol { - - val permissionsBasePath: PathMatcher[Unit] = PathMatcher("admin" / "permissions") - - /** - * Returns the route. - */ - override def makeRoute: Route = - createAdministrativePermission() ~ - createDefaultObjectAccessPermission() - - /** - * Create a new administrative permission - */ - private def createAdministrativePermission(): Route = - path(permissionsBasePath / "ap") { - post { - entity(as[CreateAdministrativePermissionAPIRequestADM]) { apiRequest => ctx => - val task = getUserUuid(ctx).map(r => AdministrativePermissionCreateRequestADM(apiRequest, r.user, r.uuid)) - runJsonRouteZ(task, ctx) - } - } - } - - /** - * Create default object access permission - */ - private def createDefaultObjectAccessPermission(): Route = - path(permissionsBasePath / "doap") { - post { - entity(as[CreateDefaultObjectAccessPermissionAPIRequestADM]) { apiRequest => requestContext => - val task = getUserUuid(requestContext).map(r => - DefaultObjectAccessPermissionCreateRequestADM(apiRequest, r.user, r.uuid) - ) - runJsonRouteZ(task, requestContext) - } - } - } -} diff --git a/webapi/src/main/scala/org/knora/webapi/routing/admin/permissions/DeletePermissionRouteADM.scala b/webapi/src/main/scala/org/knora/webapi/routing/admin/permissions/DeletePermissionRouteADM.scala deleted file mode 100644 index b932b6e450..0000000000 --- a/webapi/src/main/scala/org/knora/webapi/routing/admin/permissions/DeletePermissionRouteADM.scala +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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.routing.admin.permissions - -import org.apache.pekko -import zio.* - -import org.knora.webapi.core.MessageRelay -import org.knora.webapi.messages.StringFormatter -import org.knora.webapi.messages.admin.responder.permissionsmessages.* -import org.knora.webapi.routing.Authenticator -import org.knora.webapi.routing.KnoraRoute -import org.knora.webapi.routing.KnoraRouteData -import org.knora.webapi.routing.RouteUtilADM.* - -import pekko.http.scaladsl.server.Directives.* -import pekko.http.scaladsl.server.PathMatcher -import pekko.http.scaladsl.server.Route - -final case class DeletePermissionRouteADM( - private val routeData: KnoraRouteData, - override protected implicit val runtime: Runtime[Authenticator & StringFormatter & MessageRelay] -) extends KnoraRoute(routeData, runtime) - with PermissionsADMJsonProtocol { - - val permissionsBasePath: PathMatcher[Unit] = PathMatcher("admin" / "permissions") - - /** - * Returns the route. - */ - override def makeRoute: Route = deletePermission() - - /** - * Delete a permission - */ - private def deletePermission(): Route = - path(permissionsBasePath / Segment) { iri => - delete { ctx => - val task = getUserUuid(ctx).map(r => PermissionDeleteRequestADM(iri, r.user, r.uuid)) - runJsonRouteZ(task, ctx) - } - } -} diff --git a/webapi/src/main/scala/org/knora/webapi/routing/admin/permissions/GetPermissionsRouteADM.scala b/webapi/src/main/scala/org/knora/webapi/routing/admin/permissions/GetPermissionsRouteADM.scala deleted file mode 100644 index 62289d9555..0000000000 --- a/webapi/src/main/scala/org/knora/webapi/routing/admin/permissions/GetPermissionsRouteADM.scala +++ /dev/null @@ -1,76 +0,0 @@ -/* - * 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.routing.admin.permissions - -import org.apache.pekko -import zio.* - -import org.knora.webapi.core.MessageRelay -import org.knora.webapi.messages.StringFormatter -import org.knora.webapi.messages.admin.responder.permissionsmessages.* -import org.knora.webapi.routing.Authenticator -import org.knora.webapi.routing.KnoraRoute -import org.knora.webapi.routing.KnoraRouteData -import org.knora.webapi.routing.RouteUtilADM.* - -import pekko.http.scaladsl.server.Directives.* -import pekko.http.scaladsl.server.PathMatcher -import pekko.http.scaladsl.server.Route - -final case class GetPermissionsRouteADM( - private val routeData: KnoraRouteData, - override protected implicit val runtime: Runtime[Authenticator & StringFormatter & MessageRelay] -) extends KnoraRoute(routeData, runtime) - with PermissionsADMJsonProtocol { - - val permissionsBasePath: PathMatcher[Unit] = PathMatcher("admin" / "permissions") - - /** - * Returns the route. - */ - override def makeRoute: Route = - getAdministrativePermissionForProjectGroup() ~ - getAdministrativePermissionsForProject() ~ - getDefaultObjectAccessPermissionsForProject() ~ - getPermissionsForProject() - - private def getAdministrativePermissionForProjectGroup(): Route = - path(permissionsBasePath / "ap" / Segment / Segment) { (projectIri, groupIri) => - get { requestContext => - val task = Authenticator - .getUserADM(requestContext) - .map(AdministrativePermissionForProjectGroupGetRequestADM(projectIri, groupIri, _)) - runJsonRouteZ(task, requestContext) - } - } - - private def getAdministrativePermissionsForProject(): Route = - path(permissionsBasePath / "ap" / Segment) { projectIri => - get { requestContext => - val task = getUserUuid(requestContext) - .map(r => AdministrativePermissionsForProjectGetRequestADM(projectIri, r.user, r.uuid)) - runJsonRouteZ(task, requestContext) - } - } - - private def getDefaultObjectAccessPermissionsForProject(): Route = - path(permissionsBasePath / "doap" / Segment) { projectIri => - get { requestContext => - val task = getUserUuid(requestContext) - .map(r => DefaultObjectAccessPermissionsForProjectGetRequestADM(projectIri, r.user, r.uuid)) - runJsonRouteZ(task, requestContext) - } - } - - private def getPermissionsForProject(): Route = - path(permissionsBasePath / Segment) { projectIri => - get { requestContext => - val task = getUserUuid(requestContext) - .map(r => PermissionsForProjectGetRequestADM(projectIri, r.user, r.uuid)) - runJsonRouteZ(task, requestContext) - } - } -} diff --git a/webapi/src/main/scala/org/knora/webapi/routing/admin/permissions/UpdatePermissionRouteADM.scala b/webapi/src/main/scala/org/knora/webapi/routing/admin/permissions/UpdatePermissionRouteADM.scala deleted file mode 100644 index cc63bafdff..0000000000 --- a/webapi/src/main/scala/org/knora/webapi/routing/admin/permissions/UpdatePermissionRouteADM.scala +++ /dev/null @@ -1,94 +0,0 @@ -/* - * 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.routing.admin.permissions - -import org.apache.pekko -import zio.* - -import org.knora.webapi.core.MessageRelay -import org.knora.webapi.messages.StringFormatter -import org.knora.webapi.messages.admin.responder.permissionsmessages.* -import org.knora.webapi.routing.Authenticator -import org.knora.webapi.routing.KnoraRoute -import org.knora.webapi.routing.KnoraRouteData -import org.knora.webapi.routing.RouteUtilADM.* - -import pekko.http.scaladsl.server.Directives.* -import pekko.http.scaladsl.server.PathMatcher -import pekko.http.scaladsl.server.Route -final case class UpdatePermissionRouteADM( - private val routeData: KnoraRouteData, - override protected implicit val runtime: Runtime[Authenticator & StringFormatter & MessageRelay] -) extends KnoraRoute(routeData, runtime) - with PermissionsADMJsonProtocol { - - val permissionsBasePath: PathMatcher[Unit] = PathMatcher("admin" / "permissions") - - /** - * Returns the route. - */ - override def makeRoute: Route = - updatePermissionGroup() ~ - updatePermissionHasPermissions() ~ - updatePermissionResourceClass() ~ - updatePermissionProperty() - - /** - * Update a permission's group - */ - private def updatePermissionGroup(): Route = - path(permissionsBasePath / Segment / "group") { iri => - put { - entity(as[ChangePermissionGroupApiRequestADM]) { apiRequest => ctx => - val task = getIriUserUuid(iri, ctx) - .map(r => PermissionChangeGroupRequestADM(r.iri, apiRequest, r.user, r.uuid)) - runJsonRouteZ(task, ctx) - } - } - } - - /** - * Update a permission's set of hasPermissions. - */ - private def updatePermissionHasPermissions(): Route = - path(permissionsBasePath / Segment / "hasPermissions") { iri => - put { - entity(as[ChangePermissionHasPermissionsApiRequestADM]) { apiRequest => requestContext => - val task = getIriUserUuid(iri, requestContext) - .map(r => PermissionChangeHasPermissionsRequestADM(r.iri, apiRequest, r.user, r.uuid)) - runJsonRouteZ(task, requestContext) - } - } - } - - /** - * Update a doap permission by setting it for a new resource class - */ - private def updatePermissionResourceClass(): Route = - path(permissionsBasePath / Segment / "resourceClass") { iri => - put { - entity(as[ChangePermissionResourceClassApiRequestADM]) { apiRequest => requestContext => - val task = getIriUserUuid(iri, requestContext) - .map(r => PermissionChangeResourceClassRequestADM(r.iri, apiRequest, r.user, r.uuid)) - runJsonRouteZ(task, requestContext) - } - } - } - - /** - * Update a doap permission by setting it for a new property class - */ - private def updatePermissionProperty(): Route = - path(permissionsBasePath / Segment / "property") { iri => - put { - entity(as[ChangePermissionPropertyApiRequestADM]) { apiRequest => requestContext => - val task = getIriUserUuid(iri, requestContext) - .map(r => PermissionChangePropertyRequestADM(r.iri, apiRequest, r.user, r.uuid)) - runJsonRouteZ(task, requestContext) - } - } - } -} diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/api/AdminApiRoutes.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/api/AdminApiRoutes.scala index 98005ec9d9..2ae285bf8d 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/admin/api/AdminApiRoutes.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/api/AdminApiRoutes.scala @@ -14,10 +14,11 @@ final case class AdminApiRoutes( maintenance: MaintenanceEndpointsHandlers, project: ProjectsEndpointsHandler, users: UsersEndpointsHandler, + permissions: PermissionsEndpointsHandlers, tapirToPekko: TapirToPekkoInterpreter ) { - private val handlers = maintenance.handlers ++ project.allHanders ++ users.allHanders + private val handlers = maintenance.handlers ++ project.allHanders ++ users.allHanders ++ permissions.allHanders val routes: Seq[Route] = handlers.map(tapirToPekko.toRoute(_)) } diff --git a/webapi/src/main/scala/org/knora/webapi/routing/PathVariables.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/api/AdminPathVariables.scala similarity index 71% rename from webapi/src/main/scala/org/knora/webapi/routing/PathVariables.scala rename to webapi/src/main/scala/org/knora/webapi/slice/admin/api/AdminPathVariables.scala index 324334343f..7c24fcbad1 100644 --- a/webapi/src/main/scala/org/knora/webapi/routing/PathVariables.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/api/AdminPathVariables.scala @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package org.knora.webapi.routing +package org.knora.webapi.slice.admin.api import sttp.tapir.CodecFormat.TextPlain import sttp.tapir.* @@ -12,14 +12,27 @@ import dsp.errors.BadRequestException import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectIdentifierADM.IriIdentifier import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectIdentifierADM.ShortcodeIdentifier import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectIdentifierADM.ShortnameIdentifier +import org.knora.webapi.slice.admin.domain.model.GroupIri +import org.knora.webapi.slice.admin.domain.model.PermissionIri -object PathVariables { +object AdminPathVariables { + + val groupIri: EndpointInput.PathCapture[GroupIri] = + path[GroupIri] + .name("groupIri") + .description("The IRI of a group. Must be URL-encoded.") + .example(GroupIri.unsafeFrom("http://rdfh.ch/groups/00FF/gNdJSNYrTDu2lGpPUs94nQ")) + + val permissionIri: EndpointInput.PathCapture[PermissionIri] = + path[PermissionIri]("permissionIri") + .description("The IRI of a permission. Must be URL-encoded.") + .example(PermissionIri.unsafeFrom("http://rdfh.ch/permissions/00FF/Mck2xJDjQ_Oimi_9z4aFaA")) val projectIri: EndpointInput.PathCapture[IriIdentifier] = path[IriIdentifier] .name("projectIri") .description("The IRI of a project. Must be URL-encoded.") - .example(IriIdentifier.fromString("http://rdfh.ch/projects/0001").fold(e => throw e.head, identity)) + .example(IriIdentifier.unsafeFrom("http://rdfh.ch/projects/0001")) private val projectShortcodeCodec: Codec[String, ShortcodeIdentifier, TextPlain] = Codec.string.mapDecode(str => diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/api/PermissionsEndpoints.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/api/PermissionsEndpoints.scala new file mode 100644 index 0000000000..11acd044e7 --- /dev/null +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/api/PermissionsEndpoints.scala @@ -0,0 +1,101 @@ +/* + * 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.api + +import sttp.tapir.* +import sttp.tapir.generic.auto.* +import sttp.tapir.json.spray.jsonBody as sprayJsonBody +import zio.ZLayer + +import org.knora.webapi.messages.admin.responder.permissionsmessages.AdministrativePermissionCreateResponseADM +import org.knora.webapi.messages.admin.responder.permissionsmessages.AdministrativePermissionGetResponseADM +import org.knora.webapi.messages.admin.responder.permissionsmessages.AdministrativePermissionsForProjectGetResponseADM +import org.knora.webapi.messages.admin.responder.permissionsmessages.ChangePermissionGroupApiRequestADM +import org.knora.webapi.messages.admin.responder.permissionsmessages.ChangePermissionHasPermissionsApiRequestADM +import org.knora.webapi.messages.admin.responder.permissionsmessages.ChangePermissionPropertyApiRequestADM +import org.knora.webapi.messages.admin.responder.permissionsmessages.ChangePermissionResourceClassApiRequestADM +import org.knora.webapi.messages.admin.responder.permissionsmessages.CreateAdministrativePermissionAPIRequestADM +import org.knora.webapi.messages.admin.responder.permissionsmessages.CreateDefaultObjectAccessPermissionAPIRequestADM +import org.knora.webapi.messages.admin.responder.permissionsmessages.DefaultObjectAccessPermissionCreateResponseADM +import org.knora.webapi.messages.admin.responder.permissionsmessages.DefaultObjectAccessPermissionsForProjectGetResponseADM +import org.knora.webapi.messages.admin.responder.permissionsmessages.PermissionDeleteResponseADM +import org.knora.webapi.messages.admin.responder.permissionsmessages.PermissionGetResponseADM +import org.knora.webapi.messages.admin.responder.permissionsmessages.PermissionsADMJsonProtocol +import org.knora.webapi.messages.admin.responder.permissionsmessages.PermissionsForProjectGetResponseADM +import org.knora.webapi.slice.admin.api.AdminPathVariables.groupIri +import org.knora.webapi.slice.admin.api.AdminPathVariables.permissionIri +import org.knora.webapi.slice.admin.api.AdminPathVariables.projectIri +import org.knora.webapi.slice.common.api.BaseEndpoints + +final case class PermissionsEndpoints(base: BaseEndpoints) extends PermissionsADMJsonProtocol { + + private val permissionsBase = "admin" / "permissions" + + val postPermissionsAp = base.securedEndpoint.post + .in(permissionsBase / "ap") + .description("Create a new administrative permission") + .in(sprayJsonBody[CreateAdministrativePermissionAPIRequestADM]) + .out(sprayJsonBody[AdministrativePermissionCreateResponseADM]) + + val getPermissionsApByProjectIri = base.securedEndpoint.get + .in(permissionsBase / "ap" / projectIri) + .description("Get all administrative permissions for a project.") + .out(sprayJsonBody[AdministrativePermissionsForProjectGetResponseADM]) + + val getPermissionsApByProjectAndGroupIri = base.securedEndpoint.get + .in(permissionsBase / "ap" / projectIri / groupIri) + .description("Get all administrative permissions for a project and a group.") + .out(sprayJsonBody[AdministrativePermissionGetResponseADM]) + + val getPermissionsDoapByProjectIri = base.securedEndpoint.get + .in(permissionsBase / "doap" / projectIri) + .description("Get all default object access permissions for a project.") + .out(sprayJsonBody[DefaultObjectAccessPermissionsForProjectGetResponseADM]) + + val getPermissionsByProjectIri = base.securedEndpoint.get + .in(permissionsBase / projectIri) + .description("Get all permissions for a project.") + .out(sprayJsonBody[PermissionsForProjectGetResponseADM]) + + val deletePermission = base.securedEndpoint.delete + .in(permissionsBase / permissionIri) + .description("Delete an permission.") + .out(sprayJsonBody[PermissionDeleteResponseADM]) + + val postPermissionsDoap = base.securedEndpoint.post + .in(permissionsBase / "doap") + .description("Create a new default object access permission") + .in(sprayJsonBody[CreateDefaultObjectAccessPermissionAPIRequestADM]) + .out(sprayJsonBody[DefaultObjectAccessPermissionCreateResponseADM]) + + val putPermissionsProjectIriGroup = base.securedEndpoint.put + .in(permissionsBase / permissionIri / "group") + .description("Update a permission's group") + .in(sprayJsonBody[ChangePermissionGroupApiRequestADM]) + .out(sprayJsonBody[PermissionGetResponseADM]) + + val putPerrmissionsHasPermissions = base.securedEndpoint.put + .in(permissionsBase / permissionIri / "hasPermissions") + .description("Update a permission's set of hasPermissions") + .in(sprayJsonBody[ChangePermissionHasPermissionsApiRequestADM]) + .out(sprayJsonBody[PermissionGetResponseADM]) + + val putPermisssionsResourceClass = base.securedEndpoint.put + .in(permissionsBase / permissionIri / "resourceClass") + .description("Update a permission's resource class") + .in(sprayJsonBody[ChangePermissionResourceClassApiRequestADM]) + .out(sprayJsonBody[PermissionGetResponseADM]) + + val putPermissionsProperty = base.securedEndpoint.put + .in(permissionsBase / permissionIri / "property") + .description("Update a permission's property") + .in(sprayJsonBody[ChangePermissionPropertyApiRequestADM]) + .out(sprayJsonBody[PermissionGetResponseADM]) +} + +object PermissionsEndpoints { + val layer = ZLayer.derive[PermissionsEndpoints] +} diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/api/PermissionsEndpointsHandlers.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/api/PermissionsEndpointsHandlers.scala new file mode 100644 index 0000000000..8557b0f3d1 --- /dev/null +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/api/PermissionsEndpointsHandlers.scala @@ -0,0 +1,158 @@ +/* + * 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.api + +import zio.ZLayer + +import org.knora.webapi.messages.admin.responder.permissionsmessages.AdministrativePermissionCreateResponseADM +import org.knora.webapi.messages.admin.responder.permissionsmessages.AdministrativePermissionGetResponseADM +import org.knora.webapi.messages.admin.responder.permissionsmessages.AdministrativePermissionsForProjectGetResponseADM +import org.knora.webapi.messages.admin.responder.permissionsmessages.ChangePermissionGroupApiRequestADM +import org.knora.webapi.messages.admin.responder.permissionsmessages.ChangePermissionHasPermissionsApiRequestADM +import org.knora.webapi.messages.admin.responder.permissionsmessages.ChangePermissionPropertyApiRequestADM +import org.knora.webapi.messages.admin.responder.permissionsmessages.ChangePermissionResourceClassApiRequestADM +import org.knora.webapi.messages.admin.responder.permissionsmessages.CreateAdministrativePermissionAPIRequestADM +import org.knora.webapi.messages.admin.responder.permissionsmessages.CreateDefaultObjectAccessPermissionAPIRequestADM +import org.knora.webapi.messages.admin.responder.permissionsmessages.DefaultObjectAccessPermissionCreateResponseADM +import org.knora.webapi.messages.admin.responder.permissionsmessages.DefaultObjectAccessPermissionsForProjectGetResponseADM +import org.knora.webapi.messages.admin.responder.permissionsmessages.PermissionDeleteResponseADM +import org.knora.webapi.messages.admin.responder.permissionsmessages.PermissionGetResponseADM +import org.knora.webapi.messages.admin.responder.permissionsmessages.PermissionsForProjectGetResponseADM +import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectIdentifierADM.IriIdentifier +import org.knora.webapi.slice.admin.api.service.PermissionsRestService +import org.knora.webapi.slice.admin.domain.model.GroupIri +import org.knora.webapi.slice.admin.domain.model.PermissionIri +import org.knora.webapi.slice.common.api.HandlerMapper +import org.knora.webapi.slice.common.api.SecuredEndpointAndZioHandler + +final case class PermissionsEndpointsHandlers( + permissionsEndpoints: PermissionsEndpoints, + restService: PermissionsRestService, + mapper: HandlerMapper +) { + + private val postPermissionsApHandler = + SecuredEndpointAndZioHandler[ + CreateAdministrativePermissionAPIRequestADM, + AdministrativePermissionCreateResponseADM + ]( + permissionsEndpoints.postPermissionsAp, + user => { case (request: CreateAdministrativePermissionAPIRequestADM) => + restService.createAdministrativePermission(request, user) + } + ) + + private val getPermissionsApByProjectIriHandler = + SecuredEndpointAndZioHandler[IriIdentifier, AdministrativePermissionsForProjectGetResponseADM]( + permissionsEndpoints.getPermissionsApByProjectIri, + user => { case (projectIri: IriIdentifier) => + restService.getPermissionsApByProjectIri(projectIri.value, user) + } + ) + + private val getPermissionsApByProjectAndGroupIriHandler = + SecuredEndpointAndZioHandler[(IriIdentifier, GroupIri), AdministrativePermissionGetResponseADM]( + permissionsEndpoints.getPermissionsApByProjectAndGroupIri, + user => { case (projectIri: IriIdentifier, groupIri: GroupIri) => + restService.getPermissionsApByProjectAndGroupIri(projectIri.value, groupIri, user) + } + ) + + private val getPermissionsDaopByProjectIriHandler = + SecuredEndpointAndZioHandler[IriIdentifier, DefaultObjectAccessPermissionsForProjectGetResponseADM]( + permissionsEndpoints.getPermissionsDoapByProjectIri, + user => { case (projectIri: IriIdentifier) => + restService.getPermissionsDaopByProjectIri(projectIri.value, user) + } + ) + + private val getPermissionsByProjectIriHandler = + SecuredEndpointAndZioHandler[IriIdentifier, PermissionsForProjectGetResponseADM]( + permissionsEndpoints.getPermissionsByProjectIri, + user => { case (projectIri: IriIdentifier) => + restService.getPermissionsByProjectIri(projectIri.value, user) + } + ) + + private val deletePermissionHandler = + SecuredEndpointAndZioHandler[PermissionIri, PermissionDeleteResponseADM]( + permissionsEndpoints.deletePermission, + user => { case (permissionIri: PermissionIri) => + restService.deletePermission(permissionIri, user) + } + ) + + private val postPermissionsDoapHandler = + SecuredEndpointAndZioHandler[ + CreateDefaultObjectAccessPermissionAPIRequestADM, + DefaultObjectAccessPermissionCreateResponseADM + ]( + permissionsEndpoints.postPermissionsDoap, + user => { case (request: CreateDefaultObjectAccessPermissionAPIRequestADM) => + restService.createDefaultObjectAccessPermission(request, user) + } + ) + + private val putPermissionsProjectIriGroupHandler = + SecuredEndpointAndZioHandler[ + (PermissionIri, ChangePermissionGroupApiRequestADM), + PermissionGetResponseADM + ]( + permissionsEndpoints.putPermissionsProjectIriGroup, + user => { case (permissionIri: PermissionIri, request: ChangePermissionGroupApiRequestADM) => + restService.updatePermissionGroup(permissionIri, request, user) + } + ) + + private val putPermissionsHasPermissionsHandler = + SecuredEndpointAndZioHandler[ + (PermissionIri, ChangePermissionHasPermissionsApiRequestADM), + PermissionGetResponseADM + ]( + permissionsEndpoints.putPerrmissionsHasPermissions, + user => { case (permissionIri: PermissionIri, request: ChangePermissionHasPermissionsApiRequestADM) => + restService.updatePermissionHasPermissions(permissionIri, request, user) + } + ) + + private val putPermissionsResourceClass = + SecuredEndpointAndZioHandler[(PermissionIri, ChangePermissionResourceClassApiRequestADM), PermissionGetResponseADM]( + permissionsEndpoints.putPermisssionsResourceClass, + user => { case (permissionIri: PermissionIri, request: ChangePermissionResourceClassApiRequestADM) => + restService.updatePermissionResourceClass(permissionIri, request, user) + } + ) + + private val putPermissionsProperty = + SecuredEndpointAndZioHandler[(PermissionIri, ChangePermissionPropertyApiRequestADM), PermissionGetResponseADM]( + permissionsEndpoints.putPermissionsProperty, + user => { case (permissionIri: PermissionIri, request: ChangePermissionPropertyApiRequestADM) => + restService.updatePermissionProperty(permissionIri, request, user) + } + ) + + private val securedHandlers = + List( + postPermissionsApHandler, + getPermissionsApByProjectIriHandler, + getPermissionsApByProjectAndGroupIriHandler, + getPermissionsDaopByProjectIriHandler, + getPermissionsByProjectIriHandler, + putPermissionsProjectIriGroupHandler, + putPermissionsHasPermissionsHandler, + putPermissionsProperty, + putPermissionsResourceClass, + deletePermissionHandler, + postPermissionsDoapHandler + ).map(mapper.mapEndpointAndHandler(_)) + + val allHanders = securedHandlers +} + +object PermissionsEndpointsHandlers { + + val layer = ZLayer.derive[PermissionsEndpointsHandlers] +} diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/api/ProjectsEndpoints.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/api/ProjectsEndpoints.scala index da19a596ed..eaffdeb028 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/admin/api/ProjectsEndpoints.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/api/ProjectsEndpoints.scala @@ -15,9 +15,9 @@ import zio.Chunk import zio.ZLayer import org.knora.webapi.messages.admin.responder.projectsmessages.* -import org.knora.webapi.routing.PathVariables.projectIri -import org.knora.webapi.routing.PathVariables.projectShortcode -import org.knora.webapi.routing.PathVariables.projectShortname +import org.knora.webapi.slice.admin.api.AdminPathVariables.projectIri +import org.knora.webapi.slice.admin.api.AdminPathVariables.projectShortcode +import org.knora.webapi.slice.admin.api.AdminPathVariables.projectShortname import org.knora.webapi.slice.admin.api.model.ProjectExportInfoResponse import org.knora.webapi.slice.admin.api.model.ProjectImportResponse import org.knora.webapi.slice.admin.api.model.ProjectsEndpointsRequests.ProjectCreateRequest diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/api/service/MaintenanceRestService.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/api/service/MaintenanceRestService.scala index 6036b4056d..3f273a7473 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/admin/api/service/MaintenanceRestService.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/api/service/MaintenanceRestService.scala @@ -16,10 +16,10 @@ import dsp.errors.BadRequestException import org.knora.webapi.slice.admin.api.model.MaintenanceRequests.ProjectsWithBakfilesReport import org.knora.webapi.slice.admin.domain.model.User import org.knora.webapi.slice.admin.domain.service.MaintenanceService -import org.knora.webapi.slice.common.api.RestPermissionService +import org.knora.webapi.slice.common.api.AuthorizationRestService final case class MaintenanceRestService( - securityService: RestPermissionService, + securityService: AuthorizationRestService, maintenanceService: MaintenanceService ) { diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/api/service/PermissionsRestService.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/api/service/PermissionsRestService.scala new file mode 100644 index 0000000000..cdbd620c5f --- /dev/null +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/api/service/PermissionsRestService.scala @@ -0,0 +1,193 @@ +/* + * 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.api.service + +import zio.NonEmptyChunk +import zio.Random +import zio.Task +import zio.ZIO +import zio.ZLayer + +import dsp.errors.BadRequestException +import dsp.errors.NotFoundException +import org.knora.webapi.messages.admin.responder.permissionsmessages.AdministrativePermissionCreateResponseADM +import org.knora.webapi.messages.admin.responder.permissionsmessages.AdministrativePermissionGetResponseADM +import org.knora.webapi.messages.admin.responder.permissionsmessages.AdministrativePermissionsForProjectGetResponseADM +import org.knora.webapi.messages.admin.responder.permissionsmessages.ChangePermissionGroupApiRequestADM +import org.knora.webapi.messages.admin.responder.permissionsmessages.ChangePermissionHasPermissionsApiRequestADM +import org.knora.webapi.messages.admin.responder.permissionsmessages.ChangePermissionPropertyApiRequestADM +import org.knora.webapi.messages.admin.responder.permissionsmessages.ChangePermissionResourceClassApiRequestADM +import org.knora.webapi.messages.admin.responder.permissionsmessages.CreateAdministrativePermissionAPIRequestADM +import org.knora.webapi.messages.admin.responder.permissionsmessages.CreateDefaultObjectAccessPermissionAPIRequestADM +import org.knora.webapi.messages.admin.responder.permissionsmessages.DefaultObjectAccessPermissionCreateResponseADM +import org.knora.webapi.messages.admin.responder.permissionsmessages.DefaultObjectAccessPermissionsForProjectGetResponseADM +import org.knora.webapi.messages.admin.responder.permissionsmessages.PermissionDeleteResponseADM +import org.knora.webapi.messages.admin.responder.permissionsmessages.PermissionGetResponseADM +import org.knora.webapi.messages.admin.responder.permissionsmessages.PermissionsForProjectGetResponseADM +import org.knora.webapi.responders.admin.PermissionsResponderADM +import org.knora.webapi.slice.admin.domain.model.GroupIri +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 +import org.knora.webapi.slice.admin.domain.model.User +import org.knora.webapi.slice.admin.domain.service.KnoraProjectRepo +import org.knora.webapi.slice.common.api.AuthorizationRestService +import org.knora.webapi.slice.common.api.KnoraResponseRenderer + +final case class PermissionsRestService( + responder: PermissionsResponderADM, + projectRepo: KnoraProjectRepo, + auth: AuthorizationRestService, + format: KnoraResponseRenderer +) { + def createAdministrativePermission( + request: CreateAdministrativePermissionAPIRequestADM, + user: User + ): Task[AdministrativePermissionCreateResponseADM] = + for { + _ <- ensureProjectIriStrExistsAndUserHasAccess(request.forProject, user) + uuid <- Random.nextUUID + result <- responder.createAdministrativePermission(request, user, uuid) + ext <- format.toExternal(result) + } yield ext + + private def ensureProjectIriStrExistsAndUserHasAccess(projectIri: String, user: User): Task[KnoraProject] = + for { + projectIri <- KnoraProject.ProjectIri.from(projectIri).toZIO.mapError(e => BadRequestException(e.getMessage)) + project <- ensureProjectIriExistsAndUserHasAccess(projectIri, user) + } yield project + + private def ensureProjectIriExistsAndUserHasAccess(projectIri: ProjectIri, user: User): Task[KnoraProject] = + projectRepo + .findById(projectIri) + .someOrFail(NotFoundException(s"Project ${projectIri.value} not found")) + .tap(auth.ensureSystemAdminOrProjectAdmin(user, _)) + + def getPermissionsApByProjectIri( + value: ProjectIri, + user: User + ): Task[AdministrativePermissionsForProjectGetResponseADM] = + for { + _ <- ensureProjectIriExistsAndUserHasAccess(value, user) + result <- responder.getPermissionsApByProjectIri(value.value) + ext <- format.toExternal(result) + } yield ext + + def getPermissionsByProjectIri(projectIri: ProjectIri, user: User): Task[PermissionsForProjectGetResponseADM] = + for { + _ <- ensureProjectIriExistsAndUserHasAccess(projectIri, user) + result <- responder.getPermissionsByProjectIri(projectIri) + ext <- format.toExternal(result) + } yield ext + + def deletePermission(permissionIri: PermissionIri, user: User): Task[PermissionDeleteResponseADM] = + for { + _ <- auth.ensureSystemAdmin(user) + uuid <- Random.nextUUID + result <- responder.deletePermission(permissionIri, user, uuid) + ext <- format.toExternal(result) + } yield ext + + def createDefaultObjectAccessPermission( + request: CreateDefaultObjectAccessPermissionAPIRequestADM, + user: User + ): Task[DefaultObjectAccessPermissionCreateResponseADM] = + for { + _ <- ensureProjectIriStrExistsAndUserHasAccess(request.forProject, user) + uuid <- Random.nextUUID + result <- responder.createDefaultObjectAccessPermission(request, user, uuid) + ext <- format.toExternal(result) + } yield ext + + def updatePermissionHasPermissions( + permissionIri: PermissionIri, + request: ChangePermissionHasPermissionsApiRequestADM, + user: User + ): Task[PermissionGetResponseADM] = + for { + _ <- auth.ensureSystemAdmin(user) + uuid <- Random.nextUUID + newHasPermissions <- ZIO + .fromOption(NonEmptyChunk.fromIterableOption(request.hasPermissions)) + .mapBoth(_ => BadRequestException("hasPermissions must not be empty"), identity) + result <- responder.updatePermissionHasPermissions(permissionIri, newHasPermissions, user, uuid) + ext <- format.toExternal(result) + } yield ext + + def updatePermissionProperty( + permissionIri: PermissionIri, + request: ChangePermissionPropertyApiRequestADM, + user: User + ): Task[PermissionGetResponseADM] = + for { + _ <- auth.ensureSystemAdmin(user) + uuid <- Random.nextUUID + result <- responder.updatePermissionProperty(permissionIri, request, user, uuid) + ext <- format.toExternal(result) + } yield ext + + def updatePermissionResourceClass( + permissionIri: PermissionIri, + request: ChangePermissionResourceClassApiRequestADM, + user: User + ): Task[PermissionGetResponseADM] = + for { + _ <- auth.ensureSystemAdmin(user) + uuid <- Random.nextUUID + result <- responder.updatePermissionResourceClass(permissionIri, request, user, uuid) + ext <- format.toExternal(result) + } yield ext + + def updatePermissionGroup( + permissionIri: PermissionIri, + request: ChangePermissionGroupApiRequestADM, + user: User + ): Task[PermissionGetResponseADM] = + for { + _ <- auth.ensureSystemAdmin(user) + groupIri <- ZIO.fromEither(GroupIri.from(request.forGroup)).mapError(BadRequestException(_)) + uuid <- Random.nextUUID + result <- responder.updatePermissionsGroup(permissionIri, groupIri, user, uuid) + ext <- format.toExternal(result) + } yield ext + + def getPermissionsDaopByProjectIri( + projectIri: ProjectIri, + user: User + ): Task[DefaultObjectAccessPermissionsForProjectGetResponseADM] = + for { + _ <- ensureProjectIriExistsAndUserHasAccess(projectIri, user) + result <- responder.getPermissionsDaopByProjectIri(projectIri) + ext <- format.toExternal(result) + } yield ext + + def getPermissionsApByProjectAndGroupIri( + projectIri: ProjectIri, + groupIri: GroupIri, + user: User + ): Task[AdministrativePermissionGetResponseADM] = + for { + _ <- ensureProjectIriExistsAndUserHasAccess(projectIri, user) + result <- responder.getPermissionsApByProjectAndGroupIri(projectIri.value, groupIri.value) + ext <- format.toExternal(result) + } yield ext +} + +object PermissionsRestService { + def createAdministrativePermission( + request: CreateAdministrativePermissionAPIRequestADM, + user: User + ): ZIO[PermissionsRestService, Throwable, AdministrativePermissionCreateResponseADM] = + ZIO.serviceWithZIO(_.createAdministrativePermission(request, user)) + + def createDefaultObjectAccessPermission( + request: CreateDefaultObjectAccessPermissionAPIRequestADM, + user: User + ): ZIO[PermissionsRestService, Throwable, DefaultObjectAccessPermissionCreateResponseADM] = + ZIO.serviceWithZIO(_.createDefaultObjectAccessPermission(request, user)) + + val layer = ZLayer.derive[PermissionsRestService] +} diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/api/service/ProjectsADMRestService.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/api/service/ProjectsADMRestService.scala index 8acf47c64a..062b6e1452 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/admin/api/service/ProjectsADMRestService.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/api/service/ProjectsADMRestService.scala @@ -27,7 +27,7 @@ import org.knora.webapi.slice.admin.domain.model.User import org.knora.webapi.slice.admin.domain.service.KnoraProjectRepo import org.knora.webapi.slice.admin.domain.service.ProjectExportService import org.knora.webapi.slice.admin.domain.service.ProjectImportService -import org.knora.webapi.slice.common.api.RestPermissionService +import org.knora.webapi.slice.common.api.AuthorizationRestService @accessible trait ProjectADMRestService { @@ -80,7 +80,7 @@ final case class ProjectsADMRestServiceLive( projectRepo: KnoraProjectRepo, projectExportService: ProjectExportService, projectImportService: ProjectImportService, - permissionService: RestPermissionService + permissionService: AuthorizationRestService ) extends ProjectADMRestService { /** @@ -172,7 +172,7 @@ final case class ProjectsADMRestServiceLive( def getAllProjectData(id: IriIdentifier, user: User): Task[ProjectDataGetResponseADM] = for { project <- projectRepo.findById(id).some.orElseFail(NotFoundException(s"Project ${id.value} not found.")) - _ <- permissionService.ensureSystemOrProjectAdmin(user, project) + _ <- permissionService.ensureSystemAdminOrProjectAdmin(user, project) result <- projectExportService.exportProjectTriples(project).map(_.toFile.toPath) } yield ProjectDataGetResponseADM(result) @@ -257,7 +257,7 @@ final case class ProjectsADMRestServiceLive( for { size <- ZIO.fromEither(RestrictedViewSize.make(setSizeReq.size)).mapError(BadRequestException(_)) project <- projectRepo.findById(id).someOrFail(NotFoundException(s"Project '${getId(id)}' not found.")) - _ <- permissionService.ensureSystemOrProjectAdmin(user, project) + _ <- permissionService.ensureSystemAdminOrProjectAdmin(user, project) _ <- projectRepo.setProjectRestrictedViewSize(project, size) } yield ProjectRestrictedViewSizeResponseADM(size) @@ -296,7 +296,7 @@ final case class ProjectsADMRestServiceLive( object ProjectsADMRestServiceLive { val layer: URLayer[ - ProjectsResponderADM & KnoraProjectRepo & ProjectExportService & ProjectImportService & RestPermissionService, + ProjectsResponderADM & KnoraProjectRepo & ProjectExportService & ProjectImportService & AuthorizationRestService, ProjectsADMRestServiceLive ] = ZLayer.fromFunction(ProjectsADMRestServiceLive.apply _) } diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/model/GroupIri.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/model/GroupIri.scala new file mode 100644 index 0000000000..9a36eff00a --- /dev/null +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/model/GroupIri.scala @@ -0,0 +1,46 @@ +/* + * 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.model + +import sttp.tapir.Codec +import sttp.tapir.CodecFormat + +import dsp.valueobjects.Iri +import dsp.valueobjects.UuidUtil +import org.knora.webapi.messages.OntologyConstants.KnoraAdmin.BuiltInGroups +import org.knora.webapi.messages.StringFormatter.IriDomain +import org.knora.webapi.slice.admin.domain.model.KnoraProject.Shortcode + +final case class GroupIri private (value: String) extends AnyVal + +object GroupIri { + + implicit val tapirCodec: Codec[String, GroupIri, CodecFormat.TextPlain] = + Codec.string.mapEither(GroupIri.from)(_.value) + + /** + * Creates a new group IRI based on a UUID. + * + * @param shortcode the shortcode of a project the group belongs to. + * @return a new group IRI. + */ + def makeNew(shortcode: Shortcode): GroupIri = + GroupIri.unsafeFrom(s"http://$IriDomain/groups/${shortcode.value}/${UuidUtil.makeRandomBase64EncodedUuid}") + + def unsafeFrom(value: String): GroupIri = from(value).fold(e => throw new IllegalArgumentException(e), identity) + + def from(value: String): Either[String, GroupIri] = value match { + case value if value.isEmpty => + Left("Group IRI cannot be empty.") + case value if !Iri.isIri(value) => + Left("Group IRI is invalid.") + case value if !(value.startsWith("http://rdfh.ch/groups/") || BuiltInGroups.contains(value)) => + Left("Group IRI is invalid.") + case value if UuidUtil.hasValidLength(value.split("/").last) && !UuidUtil.hasSupportedVersion(value) => + Left("Invalid UUID used to create IRI. Only versions 4 and 5 are supported.") + case _ => Right(GroupIri(value)) + } +} diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/model/PermissionIri.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/model/PermissionIri.scala new file mode 100644 index 0000000000..63e8378da0 --- /dev/null +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/model/PermissionIri.scala @@ -0,0 +1,46 @@ +/* + * 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.model + +import sttp.tapir.Codec +import sttp.tapir.CodecFormat + +import dsp.valueobjects.Iri.isIri +import dsp.valueobjects.UuidUtil +import org.knora.webapi.messages.StringFormatter.IriDomain +import org.knora.webapi.slice.admin.domain.model.KnoraProject.Shortcode + +final case class PermissionIri private (value: String) extends AnyVal + +object PermissionIri { + + implicit val tapirCodec: Codec[String, PermissionIri, CodecFormat.TextPlain] = + Codec.string.mapEither(PermissionIri.from)(_.value) + + /** + * Creates a new permission IRI based on a UUID. + * + * @param shortcode the required project shortcode. + * @return the IRI of the permission object. + */ + def makeNew(shortcode: Shortcode): PermissionIri = { + val knoraPermissionUuid = UuidUtil.makeRandomBase64EncodedUuid + unsafeFrom(s"http://$IriDomain/permissions/${shortcode.value}/$knoraPermissionUuid") + } + + def unsafeFrom(value: String): PermissionIri = + from(value).fold(msg => throw new IllegalArgumentException(msg), identity) + + def from(value: String): Either[String, PermissionIri] = { + val isPermissionIri = value.startsWith("http://rdfh.ch/permissions/") && isIri(value) + val hasSupportedVersion = UuidUtil.hasSupportedVersion(value) + (isPermissionIri, hasSupportedVersion) match { + case (true, true) => Right(PermissionIri(value)) + case (true, false) => Left("Invalid UUID used to create IRI. Only versions 4 and 5 are supported.") + case _ => Left(s"Invalid permission IRI: $value.") + } + } +} diff --git a/webapi/src/main/scala/org/knora/webapi/slice/common/api/RestPermissionService.scala b/webapi/src/main/scala/org/knora/webapi/slice/common/api/AuthorizationRestService.scala similarity index 63% rename from webapi/src/main/scala/org/knora/webapi/slice/common/api/RestPermissionService.scala rename to webapi/src/main/scala/org/knora/webapi/slice/common/api/AuthorizationRestService.scala index af3ac97cf7..73bbe1fb68 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/common/api/RestPermissionService.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/common/api/AuthorizationRestService.scala @@ -11,18 +11,20 @@ import zio.macros.accessible import dsp.errors.ForbiddenException import org.knora.webapi.slice.admin.domain.model.KnoraProject import org.knora.webapi.slice.admin.domain.model.User -import org.knora.webapi.slice.common.api.RestPermissionService.isActive -import org.knora.webapi.slice.common.api.RestPermissionService.isSystemAdmin -import org.knora.webapi.slice.common.api.RestPermissionService.isSystemOrProjectAdmin +import org.knora.webapi.slice.common.api.AuthorizationRestService.isActive +import org.knora.webapi.slice.common.api.AuthorizationRestService.isSystemAdmin +import org.knora.webapi.slice.common.api.AuthorizationRestService.isSystemAdminSystemUserOrProjectAdmin +import org.knora.webapi.slice.common.api.AuthorizationRestService.isSystemOrProjectAdmin /** * Provides methods for checking permissions. * This service is used by the REST API services. * All `ensure...` methods fail with a [[ForbiddenException]] if the user is not active or the respective check fails. - * @see [[RestPermissionServiceLive]]. + * + * @see [[AuthorizationRestServiceLive]]. */ @accessible -trait RestPermissionService { +trait AuthorizationRestService { /** * Checks if the user is a system administrator. @@ -43,7 +45,12 @@ trait RestPermissionService { * @return [[Unit]] if the user is active and is a system or project administrator. * Fails with a [[ForbiddenException]] otherwise. */ - def ensureSystemOrProjectAdmin(user: User, project: KnoraProject): IO[ForbiddenException, Unit] + def ensureSystemAdminOrProjectAdmin(user: User, project: KnoraProject): IO[ForbiddenException, Unit] + + def ensureSystemAdminSystemUserOrProjectAdmin( + user: User, + project: KnoraProject + ): IO[ForbiddenException, Unit] } /** @@ -51,15 +58,18 @@ trait RestPermissionService { * All functions are pure. * Functions do not check if the user is active. */ -object RestPermissionService { +object AuthorizationRestService { def isActive(userADM: User): Boolean = userADM.status def isSystemAdmin(user: User): Boolean = user.permissions.isSystemAdmin + def isSystemUser(user: User): Boolean = user.isSystemUser def isProjectAdmin(user: User, project: KnoraProject): Boolean = user.permissions.isProjectAdmin(project.id.value) def isSystemOrProjectAdmin(project: KnoraProject)(userADM: User): Boolean = isSystemAdmin(userADM) || isProjectAdmin(userADM, project) + def isSystemAdminSystemUserOrProjectAdmin(project: KnoraProject)(userADM: User): Boolean = + isSystemUser(userADM) || isSystemAdmin(userADM) || isProjectAdmin(userADM, project) } -final case class RestPermissionServiceLive() extends RestPermissionService { +final case class AuthorizationRestServiceLive() extends AuthorizationRestService { override def ensureSystemAdmin(user: User): IO[ForbiddenException, Unit] = { lazy val msg = s"You are logged in with username '${user.username}', but only a system administrator has permissions for this operation." @@ -78,13 +88,21 @@ final case class RestPermissionServiceLive() extends RestPermissionService { ZIO.fail(ForbiddenException(msg)).unless(isActive(user)).unit } - override def ensureSystemOrProjectAdmin(user: User, project: KnoraProject): IO[ForbiddenException, Unit] = { + override def ensureSystemAdminOrProjectAdmin(user: User, project: KnoraProject): IO[ForbiddenException, Unit] = { lazy val msg = s"You are logged in with username '${user.username}', but only a system administrator or project administrator has permissions for this operation." checkActiveUser(user, isSystemOrProjectAdmin(project), msg) } + override def ensureSystemAdminSystemUserOrProjectAdmin( + user: User, + project: KnoraProject + ): IO[ForbiddenException, Unit] = { + lazy val msg = + s"You are logged in with username '${user.username}', but only a system administrator, system user or project administrator has permissions for this operation." + checkActiveUser(user, isSystemAdminSystemUserOrProjectAdmin(project), msg) + } } -object RestPermissionServiceLive { - val layer: ULayer[RestPermissionService] = ZLayer.fromFunction(RestPermissionServiceLive.apply _) +object AuthorizationRestServiceLive { + val layer: ULayer[AuthorizationRestService] = ZLayer.fromFunction(AuthorizationRestServiceLive.apply _) } diff --git a/webapi/src/main/scala/org/knora/webapi/slice/common/api/KnoraResponseRenderer.scala b/webapi/src/main/scala/org/knora/webapi/slice/common/api/KnoraResponseRenderer.scala index 590c9c33c6..6baafa91b2 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/common/api/KnoraResponseRenderer.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/common/api/KnoraResponseRenderer.scala @@ -14,17 +14,32 @@ import org.knora.webapi.ApiV2Schema import org.knora.webapi.Rendering import org.knora.webapi.SchemaRendering import org.knora.webapi.config.AppConfig +import org.knora.webapi.messages.StringFormatter +import org.knora.webapi.messages.admin.responder.AdminKnoraResponseADM import org.knora.webapi.messages.util.rdf.RdfFormat import org.knora.webapi.messages.v2.responder.KnoraResponseV2 +import org.knora.webapi.routing.RouteUtilADM import org.knora.webapi.slice.common.api.KnoraResponseRenderer.FormatOptions import org.knora.webapi.slice.common.api.KnoraResponseRenderer.RenderedResponse /** * Renders a [[KnoraResponseV2]] as a [[RenderedResponse]] (type alias for a [[String]]) ready to be returned to the client. */ -final class KnoraResponseRenderer(config: AppConfig) { +final class KnoraResponseRenderer(config: AppConfig, stringFormatter: StringFormatter) { def render(response: KnoraResponseV2, opts: FormatOptions): Task[(RenderedResponse, MediaType)] = ZIO.attempt(response.format(opts, config)).map((_, opts.rdfFormat.mediaType)) + + /** + * Transforms all ontology IRIs from an [[AdminKnoraResponseADM]] into their external format. + * + * @param response the response that should be transformed + * @return the transformed [[AdminKnoraResponseADM]] + */ + def toExternal[A <: AdminKnoraResponseADM](response: A): Task[A] = + RouteUtilADM + .transformResponseIntoExternalFormat(response) + .provide(ZLayer.succeed(stringFormatter)) + .mapAttempt(_.asInstanceOf[A]) } object KnoraResponseRenderer { diff --git a/webapi/src/test/scala/dsp/valueobjects/IriSpec.scala b/webapi/src/test/scala/dsp/valueobjects/IriSpec.scala index 77dfd87638..e8e2bdb283 100644 --- a/webapi/src/test/scala/dsp/valueobjects/IriSpec.scala +++ b/webapi/src/test/scala/dsp/valueobjects/IriSpec.scala @@ -43,47 +43,7 @@ object IriSpec extends ZIOSpecDefault { val uuidVersion3 = fromIri(userIriWithUUIDVersion3) val supportedUuid = fromIri(validUserIri) - def spec: Spec[Any, Throwable] = groupIriTest + listIriTest + uuidTest + roleIriTest + userIriTest - - private val groupIriTest = suite("IriSpec - GroupIri")( - test("pass an empty value and return an error") { - assertTrue( - GroupIri.make("") == Validation.fail(BadRequestException(IriErrorMessages.GroupIriMissing)), - GroupIri.make(Some("")) == Validation.fail(BadRequestException(IriErrorMessages.GroupIriMissing)) - ) - }, - test("pass an invalid value and return an error") { - assertTrue( - GroupIri.make(invalidIri) == Validation.fail(BadRequestException(IriErrorMessages.GroupIriInvalid)), - GroupIri.make(Some(invalidIri)) == Validation.fail(BadRequestException(IriErrorMessages.GroupIriInvalid)) - ) - }, - test("pass an invalid IRI containing unsupported UUID version and return an error") { - assertTrue( - GroupIri.make(groupIriWithUUIDVersion3) == Validation.fail( - BadRequestException(IriErrorMessages.UuidVersionInvalid) - ), - GroupIri.make(Some(groupIriWithUUIDVersion3)) == Validation.fail( - BadRequestException(IriErrorMessages.UuidVersionInvalid) - ) - ) - }, - test("pass a valid value and successfully create value object") { - val groupIri = GroupIri.make(validGroupIri) - val maybeGroupIri = GroupIri.make(Some(validGroupIri)) - - (for { - iri <- groupIri - maybeIri <- maybeGroupIri - } yield assertTrue(iri.value == validGroupIri) && - assert(maybeIri)(isSome(equalTo(iri)))).toZIO - }, - test("successfully validate passing None") { - assertTrue( - GroupIri.make(None) == Validation.succeed(None) - ) - } - ) + def spec: Spec[Any, Throwable] = listIriTest + uuidTest + roleIriTest + userIriTest private val listIriTest = suite("IriSpec - ListIri")( test("pass an empty value and return an error") { diff --git a/webapi/src/test/scala/org/knora/webapi/responders/admin/ProjectsServiceLiveSpec.scala b/webapi/src/test/scala/org/knora/webapi/responders/admin/ProjectsServiceLiveSpec.scala index 6125e08095..28dc8eb510 100644 --- a/webapi/src/test/scala/org/knora/webapi/responders/admin/ProjectsServiceLiveSpec.scala +++ b/webapi/src/test/scala/org/knora/webapi/responders/admin/ProjectsServiceLiveSpec.scala @@ -65,7 +65,7 @@ object ProjectsServiceLiveSpec extends ZIOSpecDefault { ZLayer.make[ProjectADMRestService]( ProjectsADMRestServiceLive.layer, exp.toLayer, - org.knora.webapi.slice.common.api.RestPermissionServiceLive.layer, + org.knora.webapi.slice.common.api.AuthorizationRestServiceLive.layer, ProjectExportServiceStub.layer, KnoraProjectRepoInMemory.layer, ProjectImportServiceLive.layer, diff --git a/webapi/src/test/scala/org/knora/webapi/slice/admin/api/service/RestPermissionServiceSpec.scala b/webapi/src/test/scala/org/knora/webapi/slice/admin/api/service/AuthorizationRestServiceSpec.scala similarity index 74% rename from webapi/src/test/scala/org/knora/webapi/slice/admin/api/service/RestPermissionServiceSpec.scala rename to webapi/src/test/scala/org/knora/webapi/slice/admin/api/service/AuthorizationRestServiceSpec.scala index fac857b88a..c4f133fcc9 100644 --- a/webapi/src/test/scala/org/knora/webapi/slice/admin/api/service/RestPermissionServiceSpec.scala +++ b/webapi/src/test/scala/org/knora/webapi/slice/admin/api/service/AuthorizationRestServiceSpec.scala @@ -17,10 +17,10 @@ import org.knora.webapi.messages.OntologyConstants.KnoraAdmin.SystemAdmin import org.knora.webapi.messages.OntologyConstants.KnoraAdmin.SystemProject import org.knora.webapi.messages.admin.responder.permissionsmessages.PermissionsDataADM import org.knora.webapi.slice.admin.domain.model.User -import org.knora.webapi.slice.common.api.RestPermissionService -import org.knora.webapi.slice.common.api.RestPermissionServiceLive +import org.knora.webapi.slice.common.api.AuthorizationRestService +import org.knora.webapi.slice.common.api.AuthorizationRestServiceLive -object RestPermissionServiceSpec extends ZIOSpecDefault { +object AuthorizationRestServiceSpec extends ZIOSpecDefault { private val activeNormalUser = User("http://iri", "username", "email@example.com", "given name", "family name", status = true, "lang") @@ -35,11 +35,11 @@ object RestPermissionServiceSpec extends ZIOSpecDefault { val spec: Spec[Any, ForbiddenException]#ZSpec[Any, ForbiddenException, TestSuccess] = suite("RestPermissionService")( suite("given an inactive system admin")( test("isSystemAdmin should return true") { - assertTrue(RestPermissionService.isSystemAdmin(inactiveSystemAdmin)) + assertTrue(AuthorizationRestService.isSystemAdmin(inactiveSystemAdmin)) }, test("when ensureSystemAdmin fail with a ForbiddenException") { for { - actual <- RestPermissionService.ensureSystemAdmin(inactiveSystemAdmin).exit + actual <- AuthorizationRestService.ensureSystemAdmin(inactiveSystemAdmin).exit } yield assertTrue( actual == Exit.fail(ForbiddenException("The account with username 'username' is not active.")) ) @@ -47,21 +47,21 @@ object RestPermissionServiceSpec extends ZIOSpecDefault { ), suite("given a active system admin")( test("isSystemAdmin should return true") { - assertTrue(RestPermissionService.isSystemAdmin(activeSystemAdmin)) + assertTrue(AuthorizationRestService.isSystemAdmin(activeSystemAdmin)) }, test("when ensureSystemAdmin succeed") { for { - _ <- RestPermissionService.ensureSystemAdmin(activeSystemAdmin) + _ <- AuthorizationRestService.ensureSystemAdmin(activeSystemAdmin) } yield assertCompletes } ), suite("given an inactive normal user")( test("isSystemAdmin should return false") { - assertTrue(!RestPermissionService.isSystemAdmin(inactiveNormalUser)) + assertTrue(!AuthorizationRestService.isSystemAdmin(inactiveNormalUser)) }, test("when ensureSystemAdmin fail with a ForbiddenException") { for { - actual <- RestPermissionService.ensureSystemAdmin(inactiveNormalUser).exit + actual <- AuthorizationRestService.ensureSystemAdmin(inactiveNormalUser).exit } yield assertTrue( actual == Exit.fail(ForbiddenException("The account with username 'username' is not active.")) ) @@ -69,11 +69,11 @@ object RestPermissionServiceSpec extends ZIOSpecDefault { ), suite("given an active normal user")( test("isSystemAdmin should return false") { - assertTrue(!RestPermissionService.isSystemAdmin(activeNormalUser)) + assertTrue(!AuthorizationRestService.isSystemAdmin(activeNormalUser)) }, test("when ensureSystemAdmin fail with a ForbiddenException") { for { - actual <- RestPermissionService.ensureSystemAdmin(activeNormalUser).exit + actual <- AuthorizationRestService.ensureSystemAdmin(activeNormalUser).exit } yield assertTrue( actual == Exit.fail( ForbiddenException( @@ -83,5 +83,5 @@ object RestPermissionServiceSpec extends ZIOSpecDefault { ) } ) - ).provide(RestPermissionServiceLive.layer) + ).provide(AuthorizationRestServiceLive.layer) } diff --git a/webapi/src/test/scala/org/knora/webapi/slice/admin/domain/model/GroupIriSpec.scala b/webapi/src/test/scala/org/knora/webapi/slice/admin/domain/model/GroupIriSpec.scala new file mode 100644 index 0000000000..8bc7c97198 --- /dev/null +++ b/webapi/src/test/scala/org/knora/webapi/slice/admin/domain/model/GroupIriSpec.scala @@ -0,0 +1,36 @@ +/* + * 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.model + +import zio.test.Spec +import zio.test.ZIOSpecDefault +import zio.test.assertTrue + +import dsp.valueobjects.IriSpec.groupIriWithUUIDVersion3 +import dsp.valueobjects.IriSpec.invalidIri + +object GroupIriSpec extends ZIOSpecDefault { + + private val validGroupIri = "http://rdfh.ch/groups/0803/qBCJAdzZSCqC_2snW5Q7Nw" + + override val spec: Spec[Any, Nothing] = suite("GroupIri from should")( + test("pass an empty value and return an error") { + assertTrue(GroupIri.from("") == Left("Group IRI cannot be empty.")) + }, + test("pass an invalid value and return an error") { + assertTrue(GroupIri.from(invalidIri) == Left("Group IRI is invalid.")) + }, + test("pass an invalid IRI containing unsupported UUID version and return an error") { + assertTrue( + GroupIri.from(groupIriWithUUIDVersion3) == + Left("Invalid UUID used to create IRI. Only versions 4 and 5 are supported.") + ) + }, + test("pass a valid value and successfully create value object") { + assertTrue(GroupIri.from(validGroupIri).map(_.value) == Right(validGroupIri)) + } + ) +}