diff --git a/docs/03-endpoints/api-admin/permissions.md b/docs/03-endpoints/api-admin/permissions.md index 3b0e5a1be9..2409441436 100644 --- a/docs/03-endpoints/api-admin/permissions.md +++ b/docs/03-endpoints/api-admin/permissions.md @@ -8,19 +8,20 @@ For an extensive explanation on how DSP permissions are implemented, see [here](../../05-internals/design/api-admin/administration.md#permissions). -| Route | Operations | Explanation | -| ------------------------------------------------------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `/admin/permissions/{projectIri}` | `GET` | [get all permissions of a project](#getting-permissions) | -| `/admin/permissions/ap/{projectIri}` | `GET` | [get all administrative permissions of a project](#getting-permissions) | -| `/admin/permissions/ap/{projectIri}/{groupIri}` | `GET` | [get all administrative permissions of a group](#getting-permissions) | -| `/admin/permissions/doap/{projectIri}` | `GET` | [get all default object access permissions of a project](#getting-permissions) | -| `/admin/permissions/ap` | `POST` | [create a new administrative permission](#creating-new-administrative-permissions) | -| `/admin/permissions/doap` | `POST` | [create a new default object access permission](#creating-new-default-object-access-permissions) | -| `/admin/permissions/{permissionIri}/group` | `PUT` | [update for which group an administrative or default object access permission is used](#updating-a-permissions-group) | -| `/admin/permissions/{permissionIri}/hasPermission` | `PUT` | [update the scope of an administrative or default object access permission](#updating-a-permissions-scope), i.e. what permissions are granted to which group when this permission applies | -| `/admin/permissions/{doap_permissionIri}/resourceClass` | `PUT` | [update for which resource class a default object access permission applies](#updating-a-default-object-access-permissions-resource-class) | -| `/admin/permissions/{doap_permissionIri}/property` | `PUT` | [update for which property a default object access permission applies](#updating-a-default-object-access-permissions-property) | -| `/admin/permissions/{permissionIri}` | `DELETE` | [delete an administrative or default object access permission](#deleting-a-permission) | +| Route | Operations | Explanation | +|-------------------------------------------------------------|------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `/admin/permissions/{projectIri}` | `GET` | [get all permissions of a project](#getting-permissions) | +| `/admin/permissions/ap/{projectIri}` | `GET` | [get all administrative permissions of a project](#getting-permissions) | +| `/admin/permissions/ap/{projectIri}/{groupIri}` | `GET` | [get all administrative permissions of a group](#getting-permissions) | +| `/admin/permissions/doap/{projectIri}` | `GET` | [get all default object access permissions of a project](#getting-permissions) | +| `/admin/permissions/ap` | `POST` | [create a new administrative permission](#creating-new-administrative-permissions) | +| `/admin/permissions/doap` | `POST` | [create a new default object access permission](#creating-new-default-object-access-permissions) | +| `/admin/permissions/doap/{permissionIri}` | `PUT` | [update an existing default object access permission](#updating-an-existing-default-object-access-permission) | +| `/admin/permissions/{permissionIri}/group` | `PUT` | [update for which group an administrative or default object access permission is used](#updating-a-permissions-group) | +| `/admin/permissions/{permissionIri}/hasPermission` | `PUT` | [update the scope of an administrative or default object access permission](#updating-a-permissions-scope), i.e. what permissions are granted to which group when this permission applies | +| `/admin/permissions/{permissionIri}` | `DELETE` | [delete an administrative or default object access permission](#deleting-a-permission) | +| ~~`/admin/permissions/{doap_permissionIri}/resourceClass`~~ | `PUT` | **deprecated**, use `/admin/permissions/doap/{permissionIri}` instead | +| ~~`/admin/permissions/{doap_permissionIri}/property`~~ | `PUT` | **deprecated**, use `/admin/permissions/doap/{permissionIri}` instead | ## Permission Operations @@ -222,8 +223,53 @@ Therefore, it is not possible to create new default object access permissions for the ProjectAdmin and ProjectMember groups of a project. However, the default permissions set for these groups can be modified; see below for more information. +### Updating an existing Default Object Access Permission + +- `PUT: /admin/permissions/doap/` to change the attributes of an existing default object + access permission, identified by its IRI ``. + +This is an example of a request body to update an existing default object access permission: + +```json +{ + "forProperty" : "http://api.dasch.swiss/ontology/00FF/images/v2#hasTitle", + "forResourceClass": "http://api.dasch.swiss/ontology/0803/incunabula/v2#Book", + "forGroup": null, + "hasPermissions": [ + { + "additionalInformation": "http://www.knora.org/ontology/knora-admin#ProjectMember", + "name": "D", + "permissionCode": 7 + } + ] +} +``` + +All attributes of the default object access permission are optional and may be combined. + +!!! warning + Only certain combinations of attributes are allowed. Only exactly one of the following combinations is allowed: + + - `forGroup` + - `forResourceClass` + - `forProperty` + - `forResourceClass` and `forProperty` + +If the combination of attributes is not allowed, the request will fail with a `400 Bad Request` error. +Any valid combination of attributes will replace the existing values. + +If present, the `hasPermissions` attribute must contain all permission types that must be granted. See [a complete description of object access +permission types](../../05-internals/design/api-admin/administration.md#default-object-access-permissions). +This is also described in the [Creating New Default Object Access Permissions](#creating-new-default-object-access-permissions) section. + +The response is the updated default object access permission with its new attributes and is the same as when +[creating a new default object access permission](#creating-new-default-object-access-permissions). + ### Updating a Permission's Group +!!! warning + For Default Object Access Permissions this endpoint is deprecated, use [`PUT: /admin/permissions/doap/`](#updating-an-existing-default-object-access-permission) instead. + - `PUT: /admin/permissions//group` to change the group for which an administrative or a default object access permission, identified by its IRI ``, is defined. The request body must contain the IRI of the new group as below: @@ -242,6 +288,9 @@ the combination of both, the permission will be defined for the newly specified ### Updating a Permission's Scope +!!! note + For Default Object Access Permissions this endpoint is deprecated, use [`PUT: /admin/permissions/doap/`](#updating-an-existing-default-object-access-permission) instead. + - `PUT: /admin/permissions//hasPermissions` to change the scope of permissions assigned to an administrative or a default object access permission identified by it IRI, ``. The request body must contain the new set of permission types as below: @@ -268,38 +317,6 @@ Either the `name` or the `permissionCode` must be present; it is not necessary t The previous permission set is *replaced* by the new permission set. In order to remove a permission for a group entirely, you can provide a new set of permissions, leaving out the permission specification for the group. -### Updating a Default Object Access Permission's Resource Class - -- `PUT: /admin/permissions//resourceClass` to change the resource class for which a default object -access permission, identified by it IRI ``, is defined. This operation is only valid for -updating a default object acceess permission. The IRI of the new resource class must be given in the request body as: - -```json -{ - "forResourceClass": "http://api.dasch.swiss/ontology/0803/incunabula/v2#bild" -} -``` - -Note that if the default object access permission was originally defined for a group, with this operation, the permission -will be defined for the given resource class instead of the group. That means the value of the `forGroup` will -be deleted. - -### Updating a Default Object Access Permission's Property - -- `PUT: /admin/permissions//property` to change the property for which a default object -access permission, identified by it IRI ``, is defined. This operation is only valid for -updating a default object access permission. The IRI of the new property must be given in the request body as: - -```json -{ - "forProperty" :"http://api.dasch.swiss/ontology/00FF/images/v2#titel" -} -``` - -Note that if the default object access permission was originally defined for a group, with this operation, the permission -will be defined for the given property instead of the group. That means the value of the `forGroup` will -be deleted. - ### Deleting a Permission - `DELETE: /admin/permissions/` to delete an administrative, or a default object access permission. The diff --git a/integration/src/test/scala/org/knora/webapi/responders/admin/PermissionsResponderSpec.scala b/integration/src/test/scala/org/knora/webapi/responders/admin/PermissionsResponderSpec.scala index 2043a7cd65..789845ebc7 100644 --- a/integration/src/test/scala/org/knora/webapi/responders/admin/PermissionsResponderSpec.scala +++ b/integration/src/test/scala/org/knora/webapi/responders/admin/PermissionsResponderSpec.scala @@ -28,8 +28,7 @@ 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.SharedTestDataADM.rootUser import org.knora.webapi.sharedtestdata.SharedTestDataADM2 import org.knora.webapi.slice.admin.api.service.PermissionRestService import org.knora.webapi.slice.admin.domain.model.GroupIri @@ -43,7 +42,6 @@ import org.knora.webapi.util.ZioScalaTestUtil.assertFailsWithA * This spec is used to test the [[PermissionsResponder]] actor. */ class PermissionsResponderSpec extends CoreSpec with ImplicitSender { - private val rootUser = SharedTestDataADM.rootUser override lazy val rdfDataObjects: List[RdfDataObject] = List( RdfDataObject( @@ -932,7 +930,7 @@ class PermissionsResponderSpec extends CoreSpec with ImplicitSender { ) assertFailsWithA[BadRequestException]( exit, - s"Given permission code $code and permission name $name are not consistent.", + s"Given permission code '$code' and permission name '$name' are not consistent.", ) } @@ -959,7 +957,7 @@ class PermissionsResponderSpec extends CoreSpec with ImplicitSender { ) assertFailsWithA[BadRequestException]( exit, - s"Invalid value for name parameter of hasPermissions: $name, it should be one of " + + s"Invalid permission token '$name', it should be one of " + s"${Permission.ObjectAccess.allTokens.mkString(", ")}", ) } @@ -987,8 +985,7 @@ class PermissionsResponderSpec extends CoreSpec with ImplicitSender { ) assertFailsWithA[BadRequestException]( exit, - s"Invalid value for permissionCode parameter of hasPermissions: $code, it should be one of " + - s"${Permission.ObjectAccess.allCodes.mkString(", ")}", + s"Invalid permission code '$code', it should be one of " + s"${Permission.ObjectAccess.allCodes.mkString(", ")}", ) } @@ -1014,31 +1011,12 @@ class PermissionsResponderSpec extends CoreSpec with ImplicitSender { ) assertFailsWithA[BadRequestException]( exit, - s"One of permission code or permission name must be provided for a default object access permission.", + s"Invalid permission token '', it should be one of RV, M, V, CR, D", ) } } "ask to update resource class of a permission" should { - "throw ForbiddenException for PermissionChangeResourceClassRequestADM if requesting user is not system or project Admin" in { - val permissionIri = "http://rdfh.ch/permissions/00FF/sdHG20U6RoiwSu8MeAT1vA" - val resourceClassIri = SharedOntologyTestDataADM.INCUNABULA_PAGE_RESOURCE_CLASS - val exit = UnsafeZioRun.run( - permissionsResponder( - _.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 @@ -1048,12 +1026,11 @@ class PermissionsResponderSpec extends CoreSpec with ImplicitSender { _.updatePermissionResourceClass( PermissionIri.unsafeFrom(permissionIri), ChangePermissionResourceClassApiRequestADM(resourceClassIri), - rootUser, UUID.randomUUID(), ), ), ) - val doap = actual.asInstanceOf[DefaultObjectAccessPermissionGetResponseADM].defaultObjectAccessPermission + val doap = actual.defaultObjectAccessPermission assert(doap.iri == permissionIri) assert(doap.forResourceClass.contains(resourceClassIri)) } @@ -1066,12 +1043,11 @@ class PermissionsResponderSpec extends CoreSpec with ImplicitSender { _.updatePermissionResourceClass( PermissionIri.unsafeFrom(permissionIri), ChangePermissionResourceClassApiRequestADM(resourceClassIri), - rootUser, UUID.randomUUID(), ), ), ) - val doap = actual.asInstanceOf[DefaultObjectAccessPermissionGetResponseADM].defaultObjectAccessPermission + val doap = actual.defaultObjectAccessPermission assert(doap.iri == permissionIri) assert(doap.forResourceClass.contains(resourceClassIri)) assert(doap.forGroup.isEmpty) @@ -1085,16 +1061,11 @@ class PermissionsResponderSpec extends CoreSpec with ImplicitSender { _.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.", - ) + assertFailsWithA[NotFoundException](exit, s"DOAP $permissionIri not found.") } } "ask to update property of a permission" should { @@ -1107,35 +1078,11 @@ class PermissionsResponderSpec extends CoreSpec with ImplicitSender { _.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/00FF/T12XnPXxQ42jBMIf6RK1pg" - val propertyIri = OntologyConstants.KnoraBase.TextFileValue - - val exit = UnsafeZioRun.run( - permissionsResponder( - _.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.", - ) + assertFailsWithA[NotFoundException](exit, s"DOAP $permissionIri not found.") } "update property of a default object access permission" in { val permissionIri = "http://rdfh.ch/permissions/00FF/T12XnPXxQ42jBMIf6RK1pg" @@ -1146,12 +1093,11 @@ class PermissionsResponderSpec extends CoreSpec with ImplicitSender { _.updatePermissionProperty( PermissionIri.unsafeFrom(permissionIri), ChangePermissionPropertyApiRequestADM(propertyIri), - rootUser, UUID.randomUUID(), ), ), ) - val doap = actual.asInstanceOf[DefaultObjectAccessPermissionGetResponseADM].defaultObjectAccessPermission + val doap = actual.defaultObjectAccessPermission assert(doap.iri == permissionIri) assert(doap.forProperty.contains(propertyIri)) } @@ -1165,12 +1111,11 @@ class PermissionsResponderSpec extends CoreSpec with ImplicitSender { _.updatePermissionProperty( PermissionIri.unsafeFrom(permissionIri), ChangePermissionPropertyApiRequestADM(propertyIri), - rootUser, UUID.randomUUID(), ), ), ) - val doap = actual.asInstanceOf[DefaultObjectAccessPermissionGetResponseADM].defaultObjectAccessPermission + val doap = actual.defaultObjectAccessPermission assert(doap.iri == permissionIri) assert(doap.forProperty.contains(propertyIri)) assert(doap.forGroup.isEmpty) diff --git a/mkdocs.yml b/mkdocs.yml index a29280e5fa..e1d7a1419a 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -148,3 +148,4 @@ markdown_extensions: custom_checkbox: true - pymdownx.tabbed: alternate_style: true + - pymdownx.tilde diff --git a/webapi/src/main/scala/dsp/errors/Errors.scala b/webapi/src/main/scala/dsp/errors/Errors.scala index a546160271..7f2015080f 100644 --- a/webapi/src/main/scala/dsp/errors/Errors.scala +++ b/webapi/src/main/scala/dsp/errors/Errors.scala @@ -87,6 +87,7 @@ object RequestRejectedException { */ final case class BadRequestException(message: String) extends RequestRejectedException(message) object BadRequestException { + def apply(e: Throwable): BadRequestException = BadRequestException(e.getMessage) def apply(e: UserServiceError): BadRequestException = BadRequestException(e.message) def invalidQueryParamValue(key: String): BadRequestException = BadRequestException(s"Invalid value for query parameter '$key'") 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 93560e96a6..29d5aaa190 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 @@ -98,16 +98,7 @@ object ChangePermissionHasPermissionsApiRequestADM { * * @param forResourceClass the new resource class IRI of the doap permission. */ -case class ChangePermissionResourceClassApiRequestADM(forResourceClass: IRI) { - if (forResourceClass.isEmpty) { - throw BadRequestException(s"Resource class IRI cannot be empty.") - } - Iri - .validateAndEscapeIri(forResourceClass) - .getOrElse( - throw BadRequestException(s"Invalid resource class IRI $forResourceClass is given."), - ) -} +case class ChangePermissionResourceClassApiRequestADM(forResourceClass: String) object ChangePermissionResourceClassApiRequestADM { implicit val codec: JsonCodec[ChangePermissionResourceClassApiRequestADM] = DeriveJsonCodec.gen[ChangePermissionResourceClassApiRequestADM] @@ -118,14 +109,7 @@ object ChangePermissionResourceClassApiRequestADM { * * @param forProperty the new property IRI of the doap permission. */ -case class ChangePermissionPropertyApiRequestADM(forProperty: IRI) { - if (forProperty.isEmpty) { - throw BadRequestException(s"Property IRI cannot be empty.") - } - Iri - .validateAndEscapeIri(forProperty) - .getOrElse(throw BadRequestException(s"Invalid property IRI $forProperty is given.")) -} +case class ChangePermissionPropertyApiRequestADM(forProperty: String) object ChangePermissionPropertyApiRequestADM { implicit val codec: JsonCodec[ChangePermissionPropertyApiRequestADM] = DeriveJsonCodec.gen[ChangePermissionPropertyApiRequestADM] diff --git a/webapi/src/main/scala/org/knora/webapi/responders/admin/PermissionsResponder.scala b/webapi/src/main/scala/org/knora/webapi/responders/admin/PermissionsResponder.scala index e9b7e8adf4..b1f8001916 100644 --- a/webapi/src/main/scala/org/knora/webapi/responders/admin/PermissionsResponder.scala +++ b/webapi/src/main/scala/org/knora/webapi/responders/admin/PermissionsResponder.scala @@ -26,7 +26,9 @@ import org.knora.webapi.messages.util.rdf.VariableResultsRow import org.knora.webapi.responders.IriLocker import org.knora.webapi.responders.IriService import org.knora.webapi.slice.admin.AdminConstants +import org.knora.webapi.slice.admin.api.PermissionEndpointsRequests.ChangeDoapRequest import org.knora.webapi.slice.admin.domain.model.DefaultObjectAccessPermission +import org.knora.webapi.slice.admin.domain.model.DefaultObjectAccessPermission.DefaultObjectAccessPermissionPart import org.knora.webapi.slice.admin.domain.model.DefaultObjectAccessPermission.ForWhat import org.knora.webapi.slice.admin.domain.model.DefaultObjectAccessPermission.ForWhat.Group import org.knora.webapi.slice.admin.domain.model.DefaultObjectAccessPermission.ForWhat.Property @@ -562,25 +564,12 @@ final case class PermissionsResponder( .flatMap(iriService.checkOrCreateEntityIri(_, PermissionIri.makeNew(project.shortcode).value)) .mapBoth(_.getMessage, PermissionIri.unsafeFrom) - groupIri <- ZIO.fromEither(req.forGroup.fold(Right(None))(GroupIri.from(_).map(Some(_)))) - _ <- ZIO.foreachDiscard(groupIri)(groupService.findById(_).orDie.someOrFail("Group not found")) - - resourceClassIri <- - ZIO.foreach(req.forResourceClass)( - iriConverter.asSmartIri(_).mapError(_.getMessage).flatMap(s => ZIO.fromEither(ResourceClassIri.from(s))), - ) - - propertyIri <- - ZIO.foreach(req.forProperty)( - iriConverter.asSmartIri(_).mapError(_.getMessage).flatMap(s => ZIO.fromEither(PropertyIri.from(s))), - ) - _ <- ZIO.foreachDiscard(propertyIri)( - ontologyRepo.findProperty(_).orDie.someOrFail(s"Property $propertyIri not found"), - ) - - forWhat <- ZIO.fromEither(ForWhat.fromIris(groupIri, resourceClassIri, propertyIri)) - _ <- ZIO.fail("Permissions needs to be supplied.").when(req.hasPermissions.isEmpty) - doap <- ZIO.fromEither(DefaultObjectAccessPermission.from(permissionIri, projectIri, forWhat, req.hasPermissions)) + groupIri <- ZIO.foreach(req.forGroup)(checkGroupExists).mapError(_.getMessage) + resourceClassIri <- ZIO.foreach(req.forResourceClass)(checkResourceClassIri).mapError(_.getMessage) + propertyIri <- ZIO.foreach(req.forProperty)(checkPropertyIri).mapError(_.getMessage) + forWhat <- ZIO.fromEither(ForWhat.fromIris(groupIri, resourceClassIri, propertyIri)) + _ <- ZIO.fail("Permissions needs to be supplied.").when(req.hasPermissions.isEmpty) + doap <- ZIO.fromEither(DefaultObjectAccessPermission.from(permissionIri, projectIri, forWhat, req.hasPermissions)) } yield doap def createDefaultObjectAccessPermission( @@ -654,7 +643,7 @@ final case class PermissionsResponder( ) if (permission.permissionCode.nonEmpty) { val code = permission.permissionCode.get - if (Permission.ObjectAccess.from(code).isEmpty) { + if (Permission.ObjectAccess.from(code).isLeft) { throw BadRequestException( s"Invalid value for permissionCode parameter of hasPermissions: $code, it should be one of " + s"${Permission.ObjectAccess.allCodes.mkString(", ")}", @@ -699,6 +688,72 @@ final case class PermissionsResponder( }.toSet } yield PermissionsForProjectGetResponseADM(permissionsInfo) + def updateDoap( + permissionIri: PermissionIri, + req: ChangeDoapRequest, + uuid: UUID, + ): Task[DefaultObjectAccessPermissionGetResponseADM] = + val task = updateDoapInternal(permissionIri, req) + .flatMap(makeExternal) + .map(DefaultObjectAccessPermissionGetResponseADM.apply) + IriLocker.runWithIriLock(uuid, permissionIri.value, task) + + private def updateDoapInternal( + permissionIri: PermissionIri, + req: ChangeDoapRequest, + ): Task[DefaultObjectAccessPermissionADM] = + for { + doap <- + doapService.findById(permissionIri).someOrFail(NotFoundException(s"DOAP ${permissionIri.value} not found.")) + group <- ZIO.foreach(req.forGroup)(checkGroupExists) + resourceClass <- ZIO.foreach(req.forResourceClass)(checkResourceClassIri) + property <- ZIO.foreach(req.forProperty)(checkPropertyIri) + newForWhat <- ZIO + .fromEither(ForWhat.fromIris(group, resourceClass, property)) + .when(req.hasForWhat) + .mapError(BadRequestException.apply) + + newPermissions <- ZIO.foreach(req.hasPermissions)(asDefaultObjectAccessPermissionParts) + + update = doap.copy( + forWhat = newForWhat.getOrElse(doap.forWhat), + permission = newPermissions.getOrElse(doap.permission), + ) + + newDoap <- doapService.save(update) + } yield doapService.asDefaultObjectAccessPermissionADM(newDoap) + + private def checkGroupExists(groupIri: IRI): Task[GroupIri] = for { + gIri <- ZIO.fromEither(GroupIri.from(groupIri)).mapError(BadRequestException.apply) + _ <- groupService.findById(gIri).someOrFail(BadRequestException(s"Group ${groupIri} not found.")) + } yield gIri + + private def checkPropertyIri(propertyIri: IRI): Task[PropertyIri] = for { + smartIri <- iriConverter.asSmartIri(propertyIri).mapError(BadRequestException.apply) + pIri <- ZIO.fromEither(PropertyIri.from(smartIri)).mapError(BadRequestException.apply) + } yield pIri + + private def checkResourceClassIri(resourceClassIri: IRI): Task[ResourceClassIri] = for { + smartIri <- iriConverter.asSmartIri(resourceClassIri).mapError(BadRequestException.apply) + rcIri <- ZIO.fromEither(ResourceClassIri.from(smartIri)).mapError(BadRequestException.apply) + } yield rcIri + + private def asDefaultObjectAccessPermissionParts( + permissions: Set[PermissionADM], + ): IO[BadRequestException, Chunk[DefaultObjectAccessPermissionPart]] = + ZIO + .foreach(permissions)(p => + ZIO + .fromEither(DefaultObjectAccessPermissionPart.from(p)) + .mapError(BadRequestException.apply), + ) + .map(Chunk.fromIterable) + + private def makeExternal(doap: DefaultObjectAccessPermissionADM): Task[DefaultObjectAccessPermissionADM] = for { + forResourceClass <- ZIO.foreach(doap.forResourceClass)(iriConverter.asExternalIri) + forProperty <- ZIO.foreach(doap.forProperty)(iriConverter.asExternalIri) + } yield doap.copy(forResourceClass = forResourceClass, forProperty = forProperty) + def updatePermissionsGroup( permissionIri: PermissionIri, groupIri: GroupIri, @@ -736,26 +791,21 @@ final case class PermissionsResponder( for { // get permission 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(groupIri.value)) - updatedPermission <- verifyPermissionGroupUpdate - } yield AdministrativePermissionGetResponseADM( - updatedPermission.asInstanceOf[AdministrativePermissionADM], - ) - case doap: DefaultObjectAccessPermissionADM => - // 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(groupIri.value)) - updatedPermission <- verifyPermissionGroupUpdate - } yield DefaultObjectAccessPermissionGetResponseADM( - updatedPermission.asInstanceOf[DefaultObjectAccessPermissionADM], - ) - } + response <- + permission match { + // Is permission an administrative permission? + case ap: AdministrativePermissionADM => + // Yes. Update the group + for { + _ <- updatePermission(permissionIri = ap.iri, maybeGroup = Some(groupIri.value)) + updatedPermission <- verifyPermissionGroupUpdate + } yield AdministrativePermissionGetResponseADM( + updatedPermission.asInstanceOf[AdministrativePermissionADM], + ) + case _: DefaultObjectAccessPermissionADM => + updateDoapInternal(permissionIri, ChangeDoapRequest(forGroup = Some(groupIri.value))) + .map(DefaultObjectAccessPermissionGetResponseADM.apply) + } } yield response IriLocker.runWithIriLock(apiRequestID, permissionIri.value, permissionGroupChangeTask) @@ -825,20 +875,10 @@ final case class PermissionsResponder( } yield AdministrativePermissionGetResponseADM( updatedPermission.asInstanceOf[AdministrativePermissionADM], ) - case doap: DefaultObjectAccessPermissionADM => - // No. It is a default object access permission. - for { - verifiedPermissions <- verifyHasPermissionsDOAP(newHasPermissions.toSet) - formattedPermissions <- - ZIO.attempt( - PermissionUtilADM.formatPermissionADMs(verifiedPermissions, PermissionType.OAP), - ) - _ <- - updatePermission(permissionIri = doap.iri, maybeHasPermissions = Some(formattedPermissions)) - updatedPermission <- verifyUpdateOfHasPermissions(verifiedPermissions) - } yield DefaultObjectAccessPermissionGetResponseADM( - updatedPermission.asInstanceOf[DefaultObjectAccessPermissionADM], - ) + case _: DefaultObjectAccessPermissionADM => + val request = ChangeDoapRequest(hasPermissions = Some(newHasPermissions.toSet)) + updateDoapInternal(permissionIri, request) + .map(DefaultObjectAccessPermissionGetResponseADM.apply) case _ => throw UpdateNotPerformedException( s"Permission ${permissionIri.value} was not updated. Please report this as a bug.", @@ -852,158 +892,32 @@ final case class PermissionsResponder( /** * 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 permission the IRI of the permission. + * @param changeRequest the request to change hasPermissions. * @param apiRequestID the API request ID. - * @return [[PermissionGetResponseADM]]. - * fails with an UpdateNotPerformedException if something has gone wrong. + * @return [[DefaultObjectAccessPermissionGetResponseADM]]. */ def updatePermissionResourceClass( - permissionIri: PermissionIri, - changePermissionResourceClass: ChangePermissionResourceClassApiRequestADM, - requestingUser: User, + permission: PermissionIri, + changeRequest: ChangePermissionResourceClassApiRequestADM, apiRequestID: UUID, - ): Task[PermissionGetResponseADM] = { - val permissionIriInternal = - stringFormatter.toSmartIri(permissionIri.value).toOntologySchema(InternalSchema).toString - /*Verify that resource class of doap is updated successfully*/ - val verifyUpdateOfResourceClass = - for { - updatedPermission <- permissionGetADM(permissionIriInternal, requestingUser) - - /*Verify that update was successful*/ - _ <- 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.", - ) - - 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. - */ - val permissionResourceClassChangeTask: Task[PermissionGetResponseADM] = - for { - // get permission - permission <- permissionGetADM(permissionIri.value, requestingUser) - response <- permission match { - // Is permission an administrative permission? - case ap: AdministrativePermissionADM => - // Yes. - 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. - for { - _ <- updatePermission( - permissionIri = doap.iri, - maybeResourceClass = Some(changePermissionResourceClass.forResourceClass), - ) - updatedPermission <- verifyUpdateOfResourceClass - } yield DefaultObjectAccessPermissionGetResponseADM( - updatedPermission.asInstanceOf[DefaultObjectAccessPermissionADM], - ) - case _ => - ZIO.fail( - UpdateNotPerformedException( - s"Permission ${permissionIri.value} was not updated. Please report this as a bug.", - ), - ) - } - } yield response - - IriLocker.runWithIriLock(apiRequestID, permissionIri.value, permissionResourceClassChangeTask) - } + ): Task[DefaultObjectAccessPermissionGetResponseADM] = IriLocker.runWithIriLock( + apiRequestID, + permission.value, + updateDoapInternal(permission, ChangeDoapRequest(forResourceClass = Some(changeRequest.forResourceClass))) + .map(DefaultObjectAccessPermissionGetResponseADM.apply), + ) def updatePermissionProperty( - permissionIri: PermissionIri, - changePermissionPropertyRequest: ChangePermissionPropertyApiRequestADM, - requestingUser: User, + permission: PermissionIri, + changeRequest: ChangePermissionPropertyApiRequestADM, apiRequestID: UUID, - ): Task[PermissionGetResponseADM] = { - val permissionIriInternal = - stringFormatter.toSmartIri(permissionIri.value).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*/ - _ <- 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.", - ) - - 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. - */ - val permissionPropertyChangeTask = - for { - // get permission - permission <- permissionGetADM(permissionIri.value, requestingUser) - response <- permission match { - // Is permission an administrative permission? - case ap: AdministrativePermissionADM => - // Yes. - 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. - for { - _ <- updatePermission( - permissionIri = doap.iri, - maybeProperty = Some(changePermissionPropertyRequest.forProperty), - ) - updatedPermission <- verifyUpdateOfProperty - } yield DefaultObjectAccessPermissionGetResponseADM( - updatedPermission.asInstanceOf[DefaultObjectAccessPermissionADM], - ) - case _ => - ZIO.fail( - UpdateNotPerformedException( - s"Permission $permissionIri was not updated. Please report this as a bug.", - ), - ) - } - } yield response - - IriLocker.runWithIriLock(apiRequestID, permissionIri.value, permissionPropertyChangeTask) - } + ): Task[DefaultObjectAccessPermissionGetResponseADM] = IriLocker.runWithIriLock( + apiRequestID, + permission.value, + updateDoapInternal(permission, ChangeDoapRequest(forProperty = Some(changeRequest.forProperty))) + .map(DefaultObjectAccessPermissionGetResponseADM.apply), + ) def deletePermission( permissionIri: PermissionIri, 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 index f8bc6a838d..afe5eedd6c 100644 --- 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 @@ -6,16 +6,47 @@ package org.knora.webapi.slice.admin.api import sttp.tapir.* +import sttp.tapir.EndpointIO.Example import sttp.tapir.generic.auto.* import sttp.tapir.json.zio.jsonBody import zio.ZLayer +import zio.json.DeriveJsonCodec +import zio.json.JsonCodec +import zio.json.JsonDecoder import org.knora.webapi.messages.admin.responder.permissionsmessages.* import org.knora.webapi.slice.admin.api.AdminPathVariables.groupIriPathVar import org.knora.webapi.slice.admin.api.AdminPathVariables.permissionIri import org.knora.webapi.slice.admin.api.AdminPathVariables.projectIri +import org.knora.webapi.slice.admin.api.PermissionEndpointsRequests.ChangeDoapRequest +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.service.KnoraGroupRepo import org.knora.webapi.slice.common.api.BaseEndpoints +object PermissionEndpointsRequests { + final case class ChangeDoapRequest( + forGroup: Option[String] = None, + forResourceClass: Option[String] = None, + forProperty: Option[String] = None, + hasPermissions: Option[Set[PermissionADM]] = None, + ) { + def isEmpty: Boolean = + forGroup.isEmpty && + forResourceClass.isEmpty && + forProperty.isEmpty && + hasPermissions.isEmpty + def hasForWhat: Boolean = + forGroup.isDefined || + forResourceClass.isDefined || + forProperty.isDefined + } + object ChangeDoapRequest { + import org.knora.webapi.slice.admin.api.Codecs.ZioJsonCodec.projectIri + given JsonCodec[ChangeDoapRequest] = DeriveJsonCodec.gen[ChangeDoapRequest] + } +} + final case class PermissionsEndpoints(base: BaseEndpoints) { private val permissionsBase = "admin" / "permissions" @@ -57,6 +88,43 @@ final case class PermissionsEndpoints(base: BaseEndpoints) { .in(jsonBody[CreateDefaultObjectAccessPermissionAPIRequestADM]) .out(jsonBody[DefaultObjectAccessPermissionCreateResponseADM]) + val putPermissionsDoapForWhat = base.securedEndpoint.put + .in(permissionsBase / "doap" / permissionIri) + .description( + "Update an existing default object access permission. " + + "The request may update the hasPermission and/or any allowed combination of group, resource class and property for the permission.", + ) + .in( + jsonBody[ChangeDoapRequest] + .description( + "Default object access permissions can be only for group, resource class, property or both resource class and property." + + "If an invalid combination is provided, the request will fail with a Bad Request response." + + "The iris for resource class and property must be valid Api V2 complex iris.", + ) + .examples( + List( + Example( + ChangeDoapRequest(Some(KnoraGroupRepo.builtIn.ProjectMember.id.value), None, None, None), + name = Some("For a group"), + summary = None, + description = None, + ), + Example( + ChangeDoapRequest( + None, + Some("http://api.dasch.swiss/ontology/0803/incunabula/v2#bild"), + Some("http://api.dasch.swiss/ontology/0803/incunabula/v2#pagenum"), + None, + ), + name = Some("For a resource class and a property"), + summary = None, + description = None, + ), + ), + ), + ) + .out(jsonBody[DefaultObjectAccessPermissionGetResponseADM]) + val putPermissionsProjectIriGroup = base.securedEndpoint.put .in(permissionsBase / permissionIri / "group") .description("Update a permission's group") @@ -71,15 +139,17 @@ final case class PermissionsEndpoints(base: BaseEndpoints) { val putPermisssionsResourceClass = base.securedEndpoint.put .in(permissionsBase / permissionIri / "resourceClass") - .description("Update a permission's resource class") + .description("Update a DOAP's resource class. Use `PUT /admin/permissions/doap/{permissionIri}` instead.") .in(jsonBody[ChangePermissionResourceClassApiRequestADM]) - .out(jsonBody[PermissionGetResponseADM]) + .out(jsonBody[DefaultObjectAccessPermissionGetResponseADM]) + .deprecated() val putPermissionsProperty = base.securedEndpoint.put .in(permissionsBase / permissionIri / "property") - .description("Update a permission's property") + .description("Update a DAOP's property. Use `PUT /admin/permissions/doap/{permissionIri}` instead.") .in(jsonBody[ChangePermissionPropertyApiRequestADM]) - .out(jsonBody[PermissionGetResponseADM]) + .out(jsonBody[DefaultObjectAccessPermissionGetResponseADM]) + .deprecated() val endpoints: Seq[AnyEndpoint] = Seq( postPermissionsAp, @@ -89,6 +159,7 @@ final case class PermissionsEndpoints(base: BaseEndpoints) { getPermissionsByProjectIri, deletePermission, postPermissionsDoap, + putPermissionsDoapForWhat, putPermissionsProjectIriGroup, putPerrmissionsHasPermissions, putPermisssionsResourceClass, 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 index 42d04e145f..9dfa11fae9 100644 --- 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 @@ -17,10 +17,12 @@ import org.knora.webapi.messages.admin.responder.permissionsmessages.ChangePermi 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.DefaultObjectAccessPermissionGetResponseADM 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.slice.admin.api.PermissionEndpointsRequests.ChangeDoapRequest import org.knora.webapi.slice.admin.api.service.PermissionRestService import org.knora.webapi.slice.admin.domain.model.GroupIri import org.knora.webapi.slice.admin.domain.model.KnoraProject.ProjectIri @@ -96,6 +98,17 @@ final case class PermissionsEndpointsHandlers( }, ) + private val putPermissionsDoapForWhatHandler = + SecuredEndpointHandler[ + (PermissionIri, ChangeDoapRequest), + DefaultObjectAccessPermissionGetResponseADM, + ]( + permissionsEndpoints.putPermissionsDoapForWhat, + user => { case (permissionIri: PermissionIri, request: ChangeDoapRequest) => + restService.updateDoapForWhat(permissionIri, request, user) + }, + ) + private val putPermissionsProjectIriGroupHandler = SecuredEndpointHandler[ (PermissionIri, ChangePermissionGroupApiRequestADM), @@ -119,7 +132,10 @@ final case class PermissionsEndpointsHandlers( ) private val putPermissionsResourceClass = - SecuredEndpointHandler[(PermissionIri, ChangePermissionResourceClassApiRequestADM), PermissionGetResponseADM]( + SecuredEndpointHandler[ + (PermissionIri, ChangePermissionResourceClassApiRequestADM), + DefaultObjectAccessPermissionGetResponseADM, + ]( permissionsEndpoints.putPermisssionsResourceClass, user => { case (permissionIri: PermissionIri, request: ChangePermissionResourceClassApiRequestADM) => restService.updatePermissionResourceClass(permissionIri, request, user) @@ -127,7 +143,10 @@ final case class PermissionsEndpointsHandlers( ) private val putPermissionsProperty = - SecuredEndpointHandler[(PermissionIri, ChangePermissionPropertyApiRequestADM), PermissionGetResponseADM]( + SecuredEndpointHandler[ + (PermissionIri, ChangePermissionPropertyApiRequestADM), + DefaultObjectAccessPermissionGetResponseADM, + ]( permissionsEndpoints.putPermissionsProperty, user => { case (permissionIri: PermissionIri, request: ChangePermissionPropertyApiRequestADM) => restService.updatePermissionProperty(permissionIri, request, user) @@ -141,6 +160,7 @@ final case class PermissionsEndpointsHandlers( getPermissionsApByProjectAndGroupIriHandler, getPermissionsDaopByProjectIriHandler, getPermissionsByProjectIriHandler, + putPermissionsDoapForWhatHandler, putPermissionsProjectIriGroupHandler, putPermissionsHasPermissionsHandler, putPermissionsProperty, diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/api/service/PermissionRestService.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/api/service/PermissionRestService.scala index c08c31cd16..2691a1f1f2 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/admin/api/service/PermissionRestService.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/api/service/PermissionRestService.scala @@ -24,11 +24,13 @@ import org.knora.webapi.messages.admin.responder.permissionsmessages.ChangePermi 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.DefaultObjectAccessPermissionGetResponseADM 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.PermissionsResponder +import org.knora.webapi.slice.admin.api.PermissionEndpointsRequests.ChangeDoapRequest 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 @@ -124,11 +126,11 @@ final case class PermissionRestService( permissionIri: PermissionIri, request: ChangePermissionPropertyApiRequestADM, user: User, - ): Task[PermissionGetResponseADM] = + ): Task[DefaultObjectAccessPermissionGetResponseADM] = for { _ <- auth.ensureSystemAdmin(user) uuid <- Random.nextUUID - result <- responder.updatePermissionProperty(permissionIri, request, user, uuid) + result <- responder.updatePermissionProperty(permissionIri, request, uuid) ext <- format.toExternal(result) } yield ext @@ -136,11 +138,24 @@ final case class PermissionRestService( permissionIri: PermissionIri, request: ChangePermissionResourceClassApiRequestADM, user: User, - ): Task[PermissionGetResponseADM] = + ): Task[DefaultObjectAccessPermissionGetResponseADM] = + for { + _ <- auth.ensureSystemAdmin(user) + uuid <- Random.nextUUID + result <- responder.updatePermissionResourceClass(permissionIri, request, uuid) + ext <- format.toExternal(result) + } yield ext + + def updateDoapForWhat( + iri: PermissionIri, + req: ChangeDoapRequest, + user: User, + ): Task[DefaultObjectAccessPermissionGetResponseADM] = for { _ <- auth.ensureSystemAdmin(user) + _ <- ZIO.fail(BadRequestException("No update provided.")).when(req.isEmpty) uuid <- Random.nextUUID - result <- responder.updatePermissionResourceClass(permissionIri, request, user, uuid) + result <- responder.updateDoap(iri, req, uuid) ext <- format.toExternal(result) } yield ext diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/model/DefaultObjectAccessPermissionRepo.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/model/DefaultObjectAccessPermissionRepo.scala index bb9e88c260..c0120ae524 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/model/DefaultObjectAccessPermissionRepo.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/model/DefaultObjectAccessPermissionRepo.scala @@ -82,8 +82,18 @@ object DefaultObjectAccessPermission { object DefaultObjectAccessPermissionPart { def from(adm: PermissionADM): Either[String, DefaultObjectAccessPermissionPart] = for { - group <- adm.additionalInformation.toRight("No object access code present").flatMap(GroupIri.from) - perm = adm.permissionCode.flatMap(Permission.ObjectAccess.from).getOrElse(Permission.ObjectAccess.Delete) + group <- adm.additionalInformation.toRight("No object access group present").flatMap(GroupIri.from) + perm <- (adm.permissionCode, adm.name) match + case (None, name) => Permission.ObjectAccess.fromToken(name) + case (Some(code), name) if name.nonEmpty => + for { + perm1 <- Permission.ObjectAccess.from(code) + perm2 <- Permission.ObjectAccess.fromToken(name) + p <- if perm1 == perm2 then Right(perm1) + else Left(s"Given permission code '$code' and permission name '$name' are not consistent.") + } yield p + case (Some(code), _) => Permission.ObjectAccess.from(code) + } yield DefaultObjectAccessPermissionPart(perm, NonEmptyChunk(group)) } diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/model/Permission.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/model/Permission.scala index bac17d2332..008552019c 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/model/Permission.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/model/Permission.scala @@ -45,9 +45,14 @@ object Permission { val maxPermission: ObjectAccess = ChangeRights - def from(code: Int): Option[ObjectAccess] = all.find(_.code == code) - - def fromToken(token: String): Option[ObjectAccess] = all.find(_.token == token) + def from(code: Int): Either[String, ObjectAccess] = + all + .find(_.code == code) + .toRight(s"Invalid permission code '$code', it should be one of ${allCodes.mkString(", ")}") + + def fromToken(token: String): Either[String, ObjectAccess] = all + .find(_.token == token) + .toRight(s"Invalid permission token '$token', it should be one of ${allTokens.mkString(", ")}") val all: Set[ObjectAccess] = Set( ObjectAccess.ChangeRights, diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/DefaultObjectAccessPermissionService.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/DefaultObjectAccessPermissionService.scala index 74cdda8c9d..4156138954 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/DefaultObjectAccessPermissionService.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/DefaultObjectAccessPermissionService.scala @@ -21,6 +21,10 @@ import org.knora.webapi.slice.admin.domain.model.PermissionIri final case class DefaultObjectAccessPermissionService( private val repo: DefaultObjectAccessPermissionRepo, ) { + + def findById(permissionIri: PermissionIri): Task[Option[DefaultObjectAccessPermission]] = + repo.findById(permissionIri) + def findByProject(projectIri: ProjectIri): Task[Chunk[DefaultObjectAccessPermission]] = repo.findByProject(projectIri) @@ -31,8 +35,7 @@ final case class DefaultObjectAccessPermissionService( ): Task[DefaultObjectAccessPermission] = repo.save(DefaultObjectAccessPermission(PermissionIri.makeNew(project.shortcode), project.id, forWhat, permission)) - def save(permission: DefaultObjectAccessPermission): Task[DefaultObjectAccessPermission] = - repo.save(permission) + def save(doap: DefaultObjectAccessPermission): Task[DefaultObjectAccessPermission] = repo.save(doap) def findByProjectAndForWhat(projectIri: ProjectIri, forWhat: ForWhat): Task[Option[DefaultObjectAccessPermission]] = repo.findByProjectAndForWhat(projectIri, forWhat) diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/repo/service/DefaultObjectAccessPermissionRepoLive.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/repo/service/DefaultObjectAccessPermissionRepoLive.scala index 9899751beb..4863ed4f2c 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/admin/repo/service/DefaultObjectAccessPermissionRepoLive.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/repo/service/DefaultObjectAccessPermissionRepoLive.scala @@ -108,7 +108,6 @@ object DefaultObjectAccessPermissionRepoLive { val part: Either[String, DefaultObjectAccessPermissionPart] = Permission.ObjectAccess .fromToken(token) - .toRight("No valid Object Access token") .flatMap { permission => Chunk .fromIterable(groups.split(',')) diff --git a/webapi/src/main/scala/org/knora/webapi/slice/resourceinfo/domain/IriConverter.scala b/webapi/src/main/scala/org/knora/webapi/slice/resourceinfo/domain/IriConverter.scala index 5879ae6f1d..9716220530 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/resourceinfo/domain/IriConverter.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/resourceinfo/domain/IriConverter.scala @@ -9,7 +9,6 @@ import zio.Task import zio.ZIO import zio.ZLayer -import org.knora.webapi.ApiV2Complex import org.knora.webapi.InternalSchema import org.knora.webapi.messages.SmartIri import org.knora.webapi.messages.StringFormatter @@ -22,7 +21,8 @@ final case class IriConverter(sf: StringFormatter) { def asInternalSmartIri(iri: InternalIri): Task[SmartIri] = asInternalSmartIri(iri.value) def asInternalSmartIri(iri: SmartIri): Task[SmartIri] = ZIO.attempt(iri.toOntologySchema(InternalSchema)) def asExternalIri(iri: InternalIri): Task[String] = - asInternalSmartIri(iri.value).mapAttempt(_.toOntologySchema(ApiV2Complex)).map(_.toIri) + asInternalSmartIri(iri.value).mapAttempt(_.toComplexSchema).map(_.toIri) + def asExternalIri(iri: String): Task[String] = asSmartIri(iri).mapAttempt(_.toComplexSchema).map(_.toIri) def getOntologyIriFromClassIri(iri: InternalIri): Task[InternalIri] = getOntologySmartIriFromClassIri(iri).mapAttempt(_.toInternalIri) def getOntologySmartIriFromClassIri(iri: InternalIri): Task[SmartIri] =