Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(api-v2): No custom permissions higher than defaults #1337

Merged
merged 11 commits into from
Jun 14, 2019
12 changes: 12 additions & 0 deletions webapi/_test_data/all_data/permissions-data.ttl
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,18 @@
knora-base:hasPermissions "ProjectResourceCreateRestrictedPermission http://www.knora.org/ontology/00FF/images#bild,http://www.knora.org/ontology/00FF/images#bildformat"^^xsd:string .


### Default Object Access Permissions on images-reviewer Group
<http://rdfh.ch/permissions/00FF/a4>

rdf:type knora-admin:DefaultObjectAccessPermission ;

knora-admin:forProject <http://rdfh.ch/projects/00FF> ;

knora-admin:forGroup <http://rdfh.ch/groups/00FF/images-reviewer> ;

knora-base:hasPermissions "D knora-admin:Creator"^^xsd:string .


### Default Object Access Permissions on ProjectMember Group
<http://rdfh.ch/permissions/00FF/d1>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ object ResourceUtilV2 {
*/
def checkResourcePermission(resourceInfo: ReadResourceV2, permissionNeeded: EntityPermission, requestingUser: UserADM): Unit = {
val maybeUserPermission: Option[EntityPermission] = PermissionUtilADM.getUserPermissionADM(
entityIri = resourceInfo.resourceIri,
entityCreator = resourceInfo.attachedToUser,
entityProject = resourceInfo.projectADM.id,
entityPermissionLiteral = resourceInfo.permissions,
Expand All @@ -76,7 +75,6 @@ object ResourceUtilV2 {
*/
def checkValuePermission(resourceInfo: ReadResourceV2, valueInfo: ReadValueV2, permissionNeeded: EntityPermission, requestingUser: UserADM): Unit = {
val maybeUserPermission: Option[EntityPermission] = PermissionUtilADM.getUserPermissionADM(
entityIri = valueInfo.valueIri,
entityCreator = valueInfo.attachedToUser,
entityProject = resourceInfo.projectADM.id,
entityPermissionLiteral = valueInfo.permissions,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import akka.pattern._
import akka.stream.ActorMaterializer
import org.knora.webapi._
import org.knora.webapi.messages.admin.responder.permissionsmessages.{DefaultObjectAccessPermissionsStringForResourceClassGetADM, DefaultObjectAccessPermissionsStringResponseADM, ResourceCreateOperation}
import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectADM
import org.knora.webapi.messages.admin.responder.usersmessages.UserADM
import org.knora.webapi.messages.store.sipimessages.{SipiGetTextFileRequest, SipiGetTextFileResponse}
import org.knora.webapi.messages.store.triplestoremessages._
Expand All @@ -43,7 +44,7 @@ import org.knora.webapi.responders.{IriLocker, ResponderData}
import org.knora.webapi.twirl.SparqlTemplateResourceToCreate
import org.knora.webapi.util.ConstructResponseUtilV2.{MappingAndXSLTransformation, ResourceWithValueRdfData}
import org.knora.webapi.util.IriConversions._
import org.knora.webapi.util.PermissionUtilADM.{DeletePermission, ModifyPermission}
import org.knora.webapi.util.PermissionUtilADM.{AGreaterThanB, DeletePermission, ModifyPermission, PermissionComparisonResult}
import org.knora.webapi.util._
import org.knora.webapi.util.date.CalendarNameGregorian
import org.knora.webapi.util.standoff.StandoffTagUtilV2
Expand Down Expand Up @@ -508,11 +509,40 @@ class ResourcesResponderV2(responderData: ResponderData) extends ResponderWithSt
// permissions are not provided.

resourcePermissions: String <- internalCreateResource.permissions match {
case Some(permissionStr) => PermissionUtilADM.validatePermissions(permissionLiteral = permissionStr, responderManager = responderManager)
case Some(permissionStr) =>
for {
validatedCustomPermissions: String <- PermissionUtilADM.validatePermissions(permissionLiteral = permissionStr, responderManager = responderManager)

// Is the requesting user a system admin, or an admin of this project?
_ = if (!(requestingUser.permissions.isProjectAdmin(internalCreateResource.projectADM.id) || requestingUser.permissions.isSystemAdmin)) {

// No. Make sure they don't give themselves higher permissions than they would get from the default permissions.

val permissionComparisonResult: PermissionComparisonResult = PermissionUtilADM.comparePermissionsADM(
entityCreator = requestingUser.id,
entityProject = internalCreateResource.projectADM.id,
permissionLiteralA = validatedCustomPermissions,
permissionLiteralB = defaultResourcePermissions,
requestingUser = requestingUser
)

if (permissionComparisonResult == AGreaterThanB) {
throw ForbiddenException(s"${resourceIDForErrorMsg}The specified permissions would give the resource's creator a higher permission on the resource than the default permissions")
}
}
} yield validatedCustomPermissions


case None => FastFuture.successful(defaultResourcePermissions)
}

valuesWithValidatedPermissions: Map[SmartIri, Seq[GenerateSparqlForValueInNewResourceV2]] <- validateAndFormatValuePermissions(internalCreateResource.values, defaultPropertyPermissions)
valuesWithValidatedPermissions: Map[SmartIri, Seq[GenerateSparqlForValueInNewResourceV2]] <- validateAndFormatValuePermissions(
project = internalCreateResource.projectADM,
values = internalCreateResource.values,
defaultPropertyPermissions = defaultPropertyPermissions,
resourceIDForErrorMsg = resourceIDForErrorMsg,
requestingUser = requestingUser
)

// Ask the values responder for SPARQL for generating the values.
sparqlForValuesResponse: GenerateSparqlToCreateMultipleValuesResponseV2 <- (responderManager ?
Expand Down Expand Up @@ -728,13 +758,20 @@ class ResourcesResponderV2(responderData: ResponderData) extends ResponderWithSt
* Given a map of property IRIs to values to be created in a new resource, validates and reformats any custom
* permissions in the values, and sets all value permissions to defaults if custom permissions are not provided.
*
* @param project the project in which the resource is to be created.
* @param values the values whose permissions are to be validated.
* @param defaultPropertyPermissions a map of property IRIs to default permissions.
* @param resourceIDForErrorMsg a string that can be prepended to an error message to specify the client's
* ID for the containing resource, if provided.
* @param requestingUser the user making the request.
* @return a map of property IRIs to sequences of [[GenerateSparqlForValueInNewResourceV2]], in which
* all permissions have been validated and defined.
*/
private def validateAndFormatValuePermissions(values: Map[SmartIri, Seq[CreateValueInNewResourceV2]],
defaultPropertyPermissions: Map[SmartIri, String]): Future[Map[SmartIri, Seq[GenerateSparqlForValueInNewResourceV2]]] = {
private def validateAndFormatValuePermissions(project: ProjectADM,
values: Map[SmartIri, Seq[CreateValueInNewResourceV2]],
defaultPropertyPermissions: Map[SmartIri, String],
resourceIDForErrorMsg: String,
requestingUser: UserADM): Future[Map[SmartIri, Seq[GenerateSparqlForValueInNewResourceV2]]] = {
val propertyValuesWithValidatedPermissionsFutures: Map[SmartIri, Seq[Future[GenerateSparqlForValueInNewResourceV2]]] = values.map {
case (propertyIri: SmartIri, valuesToCreate: Seq[CreateValueInNewResourceV2]) =>
val validatedPermissionFutures: Seq[Future[GenerateSparqlForValueInNewResourceV2]] = valuesToCreate.map {
Expand All @@ -743,16 +780,30 @@ class ResourcesResponderV2(responderData: ResponderData) extends ResponderWithSt
valueToCreate.permissions match {
case Some(permissionStr: String) =>
// Yes. Validate and reformat them.
val validatedPermissionFuture: Future[String] = PermissionUtilADM.validatePermissions(permissionLiteral = permissionStr, responderManager = responderManager)

// Make a future in which the value has the reformatted permissions.
validatedPermissionFuture.map {
validatedPermissions: String =>
GenerateSparqlForValueInNewResourceV2(
valueContent = valueToCreate.valueContent,
permissions = validatedPermissions
for {
validatedCustomPermissions <- PermissionUtilADM.validatePermissions(permissionLiteral = permissionStr, responderManager = responderManager)

// Is the requesting user a system admin, or an admin of this project?
_ = if (!(requestingUser.permissions.isProjectAdmin(project.id) || requestingUser.permissions.isSystemAdmin)) {

// No. Make sure they don't give themselves higher permissions than they would get from the default permissions.

val permissionComparisonResult: PermissionComparisonResult = PermissionUtilADM.comparePermissionsADM(
entityCreator = requestingUser.id,
entityProject = project.id,
permissionLiteralA = validatedCustomPermissions,
permissionLiteralB = defaultPropertyPermissions(propertyIri),
requestingUser = requestingUser
)
}

if (permissionComparisonResult == AGreaterThanB) {
throw ForbiddenException(s"${resourceIDForErrorMsg}The specified value permissions would give a value's creator a higher permission on the value than the default permissions")
}
}
} yield GenerateSparqlForValueInNewResourceV2(
valueContent = valueToCreate.valueContent,
permissions = validatedCustomPermissions
)

case None =>
// No. Use the default permissions.
Expand Down Expand Up @@ -1481,7 +1532,6 @@ class ResourcesResponderV2(responderData: ResponderData) extends ResponderWithSt
node: QueryResultNode =>
// Filter out the nodes that the user doesn't have permission to see.
PermissionUtilADM.getUserPermissionADM(
entityIri = node.nodeIri,
entityCreator = node.nodeCreator,
entityProject = node.nodeProject,
entityPermissionLiteral = node.nodePermissions,
Expand Down Expand Up @@ -1522,7 +1572,6 @@ class ResourcesResponderV2(responderData: ResponderData) extends ResponderWithSt
// nodes.
val hasPermission: Boolean = visibleNodeIris.contains(edge.sourceNodeIri) && visibleNodeIris.contains(edge.targetNodeIri) &&
PermissionUtilADM.getUserPermissionADM(
entityIri = edge.linkValueIri,
entityCreator = edge.linkValueCreator,
entityProject = edge.sourceNodeProject,
entityPermissionLiteral = edge.linkValuePermissions,
Expand Down Expand Up @@ -1616,7 +1665,6 @@ class ResourcesResponderV2(responderData: ResponderData) extends ResponderWithSt

// Make sure the user has permission to see the start node.
_ = if (PermissionUtilADM.getUserPermissionADM(
entityIri = startNode.nodeIri,
entityCreator = startNode.nodeCreator,
entityProject = startNode.nodeProject,
entityPermissionLiteral = startNode.nodePermissions,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import org.knora.webapi.responders.v2.search.gravsearch.GravsearchParser
import org.knora.webapi.responders.{IriLocker, Responder, ResponderData}
import org.knora.webapi.twirl.SparqlTemplateLinkUpdate
import org.knora.webapi.util.IriConversions._
import org.knora.webapi.util.PermissionUtilADM.{ChangeRightsPermission, DeletePermission, EntityPermission, ModifyPermission}
import org.knora.webapi.util.PermissionUtilADM._
import org.knora.webapi.util.{PermissionUtilADM, SmartIri}

import scala.concurrent.Future
Expand Down Expand Up @@ -195,21 +195,44 @@ class ValuesResponderV2(responderData: ResponderData) extends Responder(responde
case _ => FastFuture.successful(())
}

// Get the default permissions for the new value.
defaultValuePermissions: String <- ResourceUtilV2.getDefaultValuePermissions(
projectIri = resourceInfo.projectADM.id,
resourceClassIri = resourceInfo.resourceClassIri,
propertyIri = submittedInternalPropertyIri,
requestingUser = createValueRequest.requestingUser,
responderManager = responderManager
)

// Did the user submit permissions for the new value?
newValuePermissionLiteral <- createValueRequest.createValue.permissions match {
case Some(permissions) =>
case Some(permissions: String) =>
// Yes. Validate them.
PermissionUtilADM.validatePermissions(permissionLiteral = permissions, responderManager = responderManager)
for {
validatedCustomPermissions <- PermissionUtilADM.validatePermissions(permissionLiteral = permissions, responderManager = responderManager)

// Is the requesting user a system admin, or an admin of this project?
_ = if (!(createValueRequest.requestingUser.permissions.isProjectAdmin(createValueRequest.requestingUser.id) || createValueRequest.requestingUser.permissions.isSystemAdmin)) {

// No. Make sure they don't give themselves higher permissions than they would get from the default permissions.

val permissionComparisonResult: PermissionComparisonResult = PermissionUtilADM.comparePermissionsADM(
entityCreator = createValueRequest.requestingUser.id,
entityProject = resourceInfo.projectADM.id,
permissionLiteralA = validatedCustomPermissions,
permissionLiteralB = defaultValuePermissions,
requestingUser = createValueRequest.requestingUser
)

if (permissionComparisonResult == AGreaterThanB) {
throw ForbiddenException(s"The specified value permissions would give a value's creator a higher permission on the value than the default permissions")
}
}
} yield validatedCustomPermissions

case None =>
// No. Get default permissions for the new value.
ResourceUtilV2.getDefaultValuePermissions(
projectIri = resourceInfo.projectADM.id,
resourceClassIri = resourceInfo.resourceClassIri,
propertyIri = submittedInternalPropertyIri,
requestingUser = createValueRequest.requestingUser,
responderManager = responderManager
)
// No. Use the default permissions.
FastFuture.successful(defaultValuePermissions)
}

dataNamedGraph: IRI = stringFormatter.projectDataNamedGraphV2(resourceInfo.projectADM)
Expand Down Expand Up @@ -733,7 +756,7 @@ class ValuesResponderV2(responderData: ResponderData) extends Responder(responde
// on the value, they need ChangeRightsPermission, otherwise they need ModifyPermission.

currentPermissionsParsed: Map[EntityPermission, Set[IRI]] = PermissionUtilADM.parsePermissions(currentValue.permissions)
newPermissionsParsed: Map[EntityPermission, Set[IRI]] = PermissionUtilADM.parsePermissions(newValueVersionPermissionLiteral, { permissionLiteral: String => throw BadRequestException(s"Invalid permission literal: $permissionLiteral") })
newPermissionsParsed: Map[EntityPermission, Set[IRI]] = PermissionUtilADM.parsePermissions(newValueVersionPermissionLiteral, { permissionLiteral: String => throw AssertionException(s"Invalid permission literal: $permissionLiteral") })

permissionNeeded = if (newPermissionsParsed != currentPermissionsParsed) {
ChangeRightsPermission
Expand Down
Loading