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

fix(gravsearch): Unify permissions handling with full resource request #1521

Merged
merged 12 commits into from
Dec 5, 2019
12 changes: 5 additions & 7 deletions docs/src/paradox/03-apis/api-v2/query-language.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,13 +183,11 @@ that refer to events that took place within a certain date range.

## Permission Checking

Each matching resource is returned only if the client has permission to
see all the values of that resource that matched the criteria given in
the WHERE clause. If a matching resource contains a value that was
mentioned in the WHERE clause, but the client does not have permission
to see that value, the resource is not returned in the results, and is
instead replaced by a proxy resource called
`knora-api:ForbiddenResource`.
Each matching resource is returned with the values that the user has
permission to see. If the user does not have permission to see a matching
main resource, it is replaced by a proxy resource called
`knora-api:ForbiddenResource`. If a user does not have permission to see
a matching dependent resource, only its IRI is returned.

## Inference

Expand Down

Large diffs are not rendered by default.

102 changes: 102 additions & 0 deletions webapi/_test_data/all_data/anything-data.ttl
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,108 @@
@prefix standoff: <http://www.knora.org/ontology/standoff#> .
@prefix anything: <http://www.knora.org/ontology/0001/anything#> .

<http://rdfh.ch/0001/55UrkgTKR2SEQgnsLWI9mg> a anything:Thing;
knora-base:attachedToUser <http://rdfh.ch/users/9XBCrDV3SRa7kS1WwynB4Q>;
knora-base:attachedToProject <http://rdfh.ch/projects/0001>;
knora-base:hasPermissions "V knora-admin:UnknownUser|M knora-admin:ProjectMember";
knora-base:creationDate "2019-11-29T10:00:00.673298Z"^^xsd:dateTime;
anything:hasOtherThingValue <http://rdfh.ch/0001/55UrkgTKR2SEQgnsLWI9mg/values/bRS6vcbaQxqU-DF0pWhZog>;
anything:hasOtherThing <http://rdfh.ch/0001/IwMDbs0KQsaxSRUTl2cAIQ>;
anything:hasOtherThingValue <http://rdfh.ch/0001/55UrkgTKR2SEQgnsLWI9mg/values/MIbQMDn6T12QMS0GDlEDSg>;
anything:hasOtherThing <http://rdfh.ch/0001/F8L7zPp7TI-4MGJQlCO4Zg>;
anything:hasInteger <http://rdfh.ch/0001/55UrkgTKR2SEQgnsLWI9mg/values/c8zmKe-eRjWMOGIOw-5GyA>;
rdfs:label "thing with hidden thing";
knora-base:isDeleted false .

<http://rdfh.ch/0001/55UrkgTKR2SEQgnsLWI9mg/values/c8zmKe-eRjWMOGIOw-5GyA> a knora-base:IntValue;
knora-base:attachedToUser <http://rdfh.ch/users/9XBCrDV3SRa7kS1WwynB4Q>;
knora-base:hasPermissions "V knora-admin:UnknownUser|M knora-admin:ProjectMember";
knora-base:valueHasUUID "c8zmKe-eRjWMOGIOw-5GyA"^^xsd:string;
knora-base:isDeleted false;
knora-base:valueCreationDate "2019-11-29T10:00:00.673298Z"^^xsd:dateTime;
knora-base:valueHasInteger 123454321;
knora-base:valueHasOrder 0;
knora-base:valueHasComment "visible int value in main resource";
knora-base:valueHasString "123454321" .

<http://rdfh.ch/0001/55UrkgTKR2SEQgnsLWI9mg/values/bRS6vcbaQxqU-DF0pWhZog> a knora-base:LinkValue;
knora-base:valueHasUUID "bRS6vcbaQxqU-DF0pWhZog"^^xsd:string;
rdf:subject <http://rdfh.ch/0001/55UrkgTKR2SEQgnsLWI9mg>;
rdf:predicate anything:hasOtherThing;
rdf:object <http://rdfh.ch/0001/IwMDbs0KQsaxSRUTl2cAIQ>;
knora-base:isDeleted false;
knora-base:attachedToUser <http://rdfh.ch/users/9XBCrDV3SRa7kS1WwynB4Q>;
knora-base:hasPermissions "V knora-admin:UnknownUser|M knora-admin:ProjectMember";
knora-base:valueCreationDate "2019-11-29T10:00:00.673298Z"^^xsd:dateTime;
knora-base:valueHasString "http://rdfh.ch/0001/LOV-6aLYQFW15jwdyS51Yw";
knora-base:valueHasComment "link value pointing to hidden resource";
knora-base:valueHasRefCount "1"^^xsd:int .

<http://rdfh.ch/0001/IwMDbs0KQsaxSRUTl2cAIQ> a anything:Thing;
knora-base:attachedToUser <http://rdfh.ch/users/9XBCrDV3SRa7kS1WwynB4Q>;
knora-base:attachedToProject <http://rdfh.ch/projects/0001>;
knora-base:hasPermissions "M knora-admin:ProjectMember";
knora-base:creationDate "2019-11-29T10:00:00.673298Z"^^xsd:dateTime;
rdfs:label "hidden thing";
knora-base:isDeleted false ;
anything:hasInteger <http://rdfh.ch/0001/IwMDbs0KQsaxSRUTl2cAIQ/values/95r2v2DQSgmID6Kmr2LwHg> .

<http://rdfh.ch/0001/IwMDbs0KQsaxSRUTl2cAIQ/values/95r2v2DQSgmID6Kmr2LwHg> a knora-base:IntValue;
knora-base:attachedToUser <http://rdfh.ch/users/9XBCrDV3SRa7kS1WwynB4Q>;
knora-base:hasPermissions "V knora-admin:UnknownUser|M knora-admin:ProjectMember";
knora-base:valueHasUUID "95r2v2DQSgmID6Kmr2LwHg"^^xsd:string;
knora-base:isDeleted false;
knora-base:valueCreationDate "2019-11-29T10:00:00.673298Z"^^xsd:dateTime;
knora-base:valueHasInteger 123454321;
knora-base:valueHasOrder 0;
knora-base:valueHasComment "visible int value in hidden resource";
knora-base:valueHasString "123454321" .

<http://rdfh.ch/0001/55UrkgTKR2SEQgnsLWI9mg/values/MIbQMDn6T12QMS0GDlEDSg> a knora-base:LinkValue;
knora-base:valueHasUUID "MIbQMDn6T12QMS0GDlEDSg"^^xsd:string;
rdf:subject <http://rdfh.ch/0001/55UrkgTKR2SEQgnsLWI9mg>;
rdf:predicate anything:hasOtherThing;
rdf:object <http://rdfh.ch/0001/F8L7zPp7TI-4MGJQlCO4Zg>;
knora-base:isDeleted false;
knora-base:attachedToUser <http://rdfh.ch/users/9XBCrDV3SRa7kS1WwynB4Q>;
knora-base:hasPermissions "V knora-admin:UnknownUser|M knora-admin:ProjectMember";
knora-base:valueCreationDate "2019-11-29T10:00:00.673298Z"^^xsd:dateTime;
knora-base:valueHasString "http://rdfh.ch/0001/LOV-6aLYQFW15jwdyS51Yw";
knora-base:valueHasComment "link value pointing to visible resource with hidden int values";
knora-base:valueHasRefCount "1"^^xsd:int .

<http://rdfh.ch/0001/F8L7zPp7TI-4MGJQlCO4Zg> a anything:Thing;
knora-base:attachedToUser <http://rdfh.ch/users/9XBCrDV3SRa7kS1WwynB4Q>;
knora-base:attachedToProject <http://rdfh.ch/projects/0001>;
knora-base:hasPermissions "V knora-admin:UnknownUser|M knora-admin:ProjectMember";
knora-base:creationDate "2019-11-29T10:00:00.673298Z"^^xsd:dateTime;
rdfs:label "visible thing with hidden int values";
knora-base:isDeleted false ;
anything:hasInteger <http://rdfh.ch/0001/F8L7zPp7TI-4MGJQlCO4Zg/values/yVTqO37cRkCSvXbFc3vTyw> ;
anything:hasInteger <http://rdfh.ch/0001/F8L7zPp7TI-4MGJQlCO4Zg/values/F2xCr0S2QfWRQxJDWY9L0g> .

<http://rdfh.ch/0001/F8L7zPp7TI-4MGJQlCO4Zg/values/yVTqO37cRkCSvXbFc3vTyw> a knora-base:IntValue;
knora-base:attachedToUser <http://rdfh.ch/users/9XBCrDV3SRa7kS1WwynB4Q>;
knora-base:valueHasUUID "yVTqO37cRkCSvXbFc3vTyw"^^xsd:string;
knora-base:isDeleted false;
knora-base:valueCreationDate "2019-11-29T10:00:00.673298Z"^^xsd:dateTime;
knora-base:valueHasInteger 123454321;
knora-base:valueHasOrder 0;
knora-base:valueHasString "123454321";
knora-base:valueHasComment "first hidden int value in visible resource";
knora-base:hasPermissions "M knora-admin:ProjectMember" .

<http://rdfh.ch/0001/F8L7zPp7TI-4MGJQlCO4Zg/values/F2xCr0S2QfWRQxJDWY9L0g> a knora-base:IntValue;
knora-base:attachedToUser <http://rdfh.ch/users/9XBCrDV3SRa7kS1WwynB4Q>;
knora-base:valueHasUUID "F2xCr0S2QfWRQxJDWY9L0g"^^xsd:string;
knora-base:isDeleted false;
knora-base:valueCreationDate "2019-11-29T10:00:00.673298Z"^^xsd:dateTime;
knora-base:valueHasInteger 543212345;
knora-base:valueHasOrder 0;
knora-base:valueHasString "543212345";
knora-base:valueHasComment "second hidden int value in visible resource";
knora-base:hasPermissions "M knora-admin:ProjectMember" .

<http://rdfh.ch/0001/0C-0L1kORryKzJAJxxRyRQ> a anything:Thing;
knora-base:attachedToUser <http://rdfh.ch/users/9XBCrDV3SRa7kS1WwynB4Q>;
knora-base:attachedToProject <http://rdfh.ch/projects/0001>;
Expand Down
4 changes: 1 addition & 3 deletions webapi/_test_data/all_data/permissions-data.ttl
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
@prefix knora-base: <http://www.knora.org/ontology/knora-base#> .
@prefix knora-admin: <http://www.knora.org/ontology/knora-admin#> .
@prefix incunabula: <http://www.knora.org/ontology/0803/incunabula#> .
@prefix anything: <http://www.knora.org/ontology/0001/anything#> .
@prefix beol: <http://www.knora.org/ontology/0801/beol#> .

##########################################################
Expand Down Expand Up @@ -241,9 +242,6 @@
knora-base:hasPermissions "CR knora-admin:Creator|M knora-admin:ProjectMember|V knora-admin:KnownUser|RV knora-admin:UnknownUser"^^xsd:string .





##########################################################
#
# BEOL Project Permissions
Expand Down
2 changes: 1 addition & 1 deletion webapi/_test_data/ontologies/anything-onto.ttl
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,7 @@

knora-base:objectClassConstraint knora-base:LinkValue .


:thingHasRegion rdf:type owl:ObjectProperty ;

rdfs:subPropertyOf knora-base:hasLinkTo ;
Expand Down Expand Up @@ -620,7 +621,6 @@
"Cosa azzurra"@it ,
"Blue thing"@en .


:hasPictureTitle rdf:type owl:ObjectProperty ;

rdfs:subPropertyOf knora-base:hasValue ;
Expand Down
2 changes: 1 addition & 1 deletion webapi/_test_data/ontologies/incunabula-onto.ttl
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@
rdfs:subPropertyOf knora-base:hasValue ;

rdfs:label "Physische Beschreibung"@de ,
"Phyiscal description"@en ;
"Physical description"@en ;

rdfs:comment """Generelle physische Beschreibung des Objektes wie Material, Grösse etc."""@de ;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -534,17 +534,15 @@ class SearchResponderV2(responderData: ResponderData) extends ResponderWithStand
for {
mainQueryResponse: SparqlExtendedConstructResponse <- (storeManager ? SparqlExtendedConstructRequest(triplestoreSpecificSparql)).mapTo[SparqlExtendedConstructResponse]

// for each main resource, check if all dependent resources and value objects are still present after permission checking
// this ensures that the user has sufficient permissions on the whole graph pattern
queryResultsWithFullGraphPattern: Map[IRI, ConstructResponseUtilV2.ResourceWithValueRdfData] = MainQueryResultProcessor.getMainQueryResultsWithFullGraphPattern(
mainQueryResponse = mainQueryResponse,
dependentResourceIrisPerMainResource = dependentResourceIrisPerMainResource,
valueObjectVarsAndIrisPerMainResource = valueObjectVarsAndIrisPerMainResource,
requestingUser = requestingUser)
// Filter out values that the user doesn't have permission to see.
queryResultsFilteredForPermissions: Map[IRI, ConstructResponseUtilV2.ResourceWithValueRdfData] = ConstructResponseUtilV2.splitMainResourcesAndValueRdfData(
constructQueryResults = mainQueryResponse,
requestingUser = requestingUser
)

// filter out those value objects that the user does not want to be returned by the query (not present in the input query's CONSTRUCT clause)
queryResWithFullGraphPatternOnlyRequestedValues: Map[IRI, ConstructResponseUtilV2.ResourceWithValueRdfData] = MainQueryResultProcessor.getRequestedValuesFromResultsWithFullGraphPattern(
queryResultsWithFullGraphPattern,
queryResultsFilteredForPermissions,
valueObjectVarsAndIrisPerMainResource,
allResourceVariablesFromTypeInspection,
dependentResourceIrisFromTypeInspection,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,78 +201,6 @@ object MainQueryResultProcessor {
ValueObjectVariablesAndValueObjectIris(new ErrorHandlingMap(valueObjVarsAndIris, { key => throw GravsearchException(s"main resource not found: $key") }))
}

/**
* Removes the main resources from the main query's results that the requesting user has insufficient permissions on.
* If the user does not have full permission on the full graph pattern (main resource, dependent resources, value objects)
* then the main resource is excluded completely from the results.
*
* @param mainQueryResponse results returned by the main query.
* @param dependentResourceIrisPerMainResource Iris of dependent resources per main resource.
* @param valueObjectVarsAndIrisPerMainResource variable names and Iris of value objects per main resource.
* @return a Map of main resource Iris and their values.
*/
def getMainQueryResultsWithFullGraphPattern(mainQueryResponse: SparqlExtendedConstructResponse,
dependentResourceIrisPerMainResource: DependentResourcesPerMainResource,
valueObjectVarsAndIrisPerMainResource: ValueObjectVariablesAndValueObjectIris,
requestingUser: UserADM)(implicit stringFormatter: StringFormatter): RdfResources = {

// separate main resources and value objects (dependent resources are nested)
// this method removes resources and values the requesting users has insufficient permissions on (missing view permissions).
val queryResultsSep: RdfResources = ConstructResponseUtilV2.splitMainResourcesAndValueRdfData(constructQueryResults = mainQueryResponse, requestingUser = requestingUser)

queryResultsSep.foldLeft(ConstructResponseUtilV2.emptyRdfResources) {
case (acc: RdfResources, (mainResIri: IRI, values: ResourceWithValueRdfData)) =>

// check for presence of dependent resources: dependentResourceIrisPerMainResource plus the dependent resources whose Iris where provided in the Gravsearch query.
val expectedDependentResources: Set[IRI] = dependentResourceIrisPerMainResource.dependentResourcesPerMainResource(mainResIri) /*++ dependentResourceIrisFromTypeInspection*/
// TODO: https://github.com/dhlab-basel/Knora/issues/924

// println(expectedDependentResources)

// check for presence of value objects: valueObjectIrisPerMainResource
val expectedValueObjects: Set[IRI] = valueObjectVarsAndIrisPerMainResource.valueObjectVariablesAndValueObjectIris(mainResIri).values.flatten.toSet

// value property assertions for the current main resource
val valuePropAssertions: RdfPropertyValues = values.valuePropertyAssertions

// all the IRIs of dependent resources and value objects contained in `valuePropAssertions`
val resAndValueObjIris: MainQueryResultProcessor.ResourceIrisAndValueObjectIris = MainQueryResultProcessor.collectResourceIrisAndValueObjectIrisFromMainQueryResult(valuePropAssertions)

// check if the client has sufficient permissions on all dependent resources present in the graph pattern
val allDependentResources: Boolean = resAndValueObjIris.resourceIris.intersect(expectedDependentResources) == expectedDependentResources

// check if the client has sufficient permissions on all value objects IRIs present in the graph pattern
val allValueObjects: Boolean = resAndValueObjIris.valueObjectIris.intersect(expectedValueObjects) == expectedValueObjects

// println(allValueObjects)

/*println("+++++++++")

println("graph pattern check for " + mainResIri)

println("expected dependent resources: " + expectedDependentResources)

println("all expected dependent resources present: " + allDependentResources)

println("given dependent resources " + resAndValueObjIris.resourceIris)

println("expected value objs: " + expectedValueObjects)

println("given value objs: " + resAndValueObjIris.valueObjectIris)

println("all expected value objects present: " + allValueObjects)*/

if (allDependentResources && allValueObjects) {
// sufficient permissions, include the main resource and its values
acc + (mainResIri -> values)
} else {
// insufficient permissions, skip the resource
acc
}
}

}

/**
* Given the results of the main query, filters out all values that the user did not ask for in the input query,
* i.e that are not present in its CONSTRUCT clause.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,4 +125,4 @@ WHERE {

}
}
LIMIT 50
LIMIT 60
Loading