Skip to content

Commit

Permalink
jena-fuseki-access - Dynamic Graph selection at query time
Browse files Browse the repository at this point in the history
- "#pragma acl.graphs" preamble can be used to specify one or more
  graphs to restriction query exectution to
- The functionality is only enabled if an access:entry for a specific user
  contains just <urn:jena:accessGraphsDynamic>
- Requires context propagation patch (see apache#1291), since query
  is stored in request-specific context
  • Loading branch information
vtermanis committed Jul 11, 2022
1 parent 079c0ba commit 6618f02
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,19 @@
import java.util.function.Function;

import org.apache.jena.atlas.lib.Pair;
import org.apache.jena.atlas.logging.Log;
import org.apache.jena.fuseki.servlets.*;
import org.apache.jena.query.Query;
import org.apache.jena.query.QueryExecution;
import org.apache.jena.sparql.core.*;
import org.apache.jena.sparql.core.DynamicDatasets.DynamicDatasetGraph;
import org.apache.jena.sparql.util.Symbol;

/** A Query {@link ActionService} that inserts a security filter on each query. */
final
public class AccessCtl_SPARQL_QueryDataset extends SPARQL_QueryDataset {
// For storing original query in execution context. (Used for SecurityContextDynamic support.)
private static final Symbol symSecQuery = Symbol.create(VocabSecurity.getURI() + "secCxtQuery");
private final Function<HttpAction, String> requestUser;

public AccessCtl_SPARQL_QueryDataset(Function<HttpAction, String> requestUser) {
Expand All @@ -54,17 +58,20 @@ protected Collection<String> customParams() {
*/
@Override
protected Pair<DatasetGraph, Query> decideDataset(HttpAction action, Query query, String queryStringLog) {
Log.debug(this, "decideDataset");
DatasetGraph dsg = action.getActiveDSG();
if ( ! DataAccessCtl.isAccessControlled(dsg) )
return super.decideDataset(action, query, queryStringLog);


DatasetDescription dsDesc0 = SPARQLProtocol.getDatasetDescription(action, query);
SecurityContext sCxt = DataAccessLib.getSecurityContext(action, dsg, requestUser);
SecurityContext sCxt = getSecurityContext(action, dsg, requestUser);
DatasetGraph dsg2 = dynamicDataset(action, query, dsg, dsDesc0, sCxt);
return Pair.create(dsg2, query);
}

private DatasetGraph dynamicDataset(HttpAction action, Query query, DatasetGraph dsg0, DatasetDescription dsDesc0, SecurityContext sCxt) {
Log.debug(this, "dynamicDataset");
if ( dsDesc0 == null )
return dsg0;
if ( ! ALLOW_FROM )
Expand Down Expand Up @@ -104,8 +111,27 @@ private List<String> mask(List<String> graphURIs, SecurityContext sCxt) {
.collect(toList());
}

// Note: This is called before both decideDataset() and createQueryExecution()
@Override
protected void execute(String queryString, HttpAction action) {
Log.debug(this, "execute");
action.getContext().set(symSecQuery, queryString);
super.execute(queryString, action);
}

private SecurityContext getSecurityContext(HttpAction action, DatasetGraph dsg, Function<HttpAction, String> requestUser) {
SecurityContext sCxt = DataAccessLib.getSecurityContext(action, dsg, requestUser);
if ( sCxt instanceof SecurityContextDynamic ) {
Log.debug(this, "Dynamic mode for user " + requestUser.toString());
String queryString = action.getContext().getAsString(symSecQuery);
return ((SecurityContextDynamic) sCxt).instantiateFromQuery(queryString);
}
return sCxt;
}

@Override
protected QueryExecution createQueryExecution(HttpAction action, Query query, DatasetGraph target) {
Log.debug(this, "createQueryExecution");
if ( ! ALLOW_FROM ) {
if ( target instanceof DynamicDatasetGraph )
// Protocol query/FROM should have been caught by decideDataset
Expand All @@ -120,7 +146,7 @@ protected QueryExecution createQueryExecution(HttpAction action, Query query, Da
if ( ! DataAccessCtl.isAccessControlled(dsg) )
return super.createQueryExecution(action, query, target);

SecurityContext sCxt = DataAccessLib.getSecurityContext(action, dsg, requestUser);
SecurityContext sCxt = getSecurityContext(action, dsg, requestUser);
// A QueryExecution for controlled access
QueryExecution qExec = sCxt.createQueryExecution(action, query, target);
return qExec;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,12 @@ public AuthorizationService open(Assembler a, Resource root, Mode mode) {
});

map.keySet().forEach(u->{
SecurityContext sCxt = new SecurityContextView(map.get(u));
registry.put(u, sCxt);
Collection<Node> visibleGraphs = map.get(u);
if ( visibleGraphs.size() == 1 && visibleGraphs.contains(SecurityContextDynamic.dynamicAccess)) {
registry.put(u, SecurityContext.DYNAMIC);
} else {
registry.put(u, new SecurityContextView(visibleGraphs));
}
});

return registry;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
public interface SecurityContext {
public static final SecurityContext NONE = new SecurityContextAllowNone();
public static final SecurityContext ALL = new SecurityContextAllowAll();
public static final SecurityContext DYNAMIC = new SecurityContextDynamic();
public static SecurityContext ALL_NG(DatasetGraph dsg) {
Collection<Node> names = Iter.toList(dsg.listGraphNodes());
//return new SecurityContextAllowNamedGraphs(dsg);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.apache.jena.fuseki.access;

import java.util.Collection;
import java.util.Collections;
import java.util.function.Predicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.jena.graph.Node;
import org.apache.jena.graph.NodeFactory;
import org.apache.jena.query.QueryExecution;
import org.apache.jena.sparql.core.DatasetGraph;
import org.apache.jena.sparql.core.Quad;

/** A {@link SecurityContext} that provides no access itself but indicates that the set of visible graphs should be
* constructed from a set embedded in the query. Use {@link SecurityContextDynamic#instantiateFromQuery} to create the
* query-specific set of graphs to filter.
* This context is not meant to be used directly by end users: The initial query has a list of visible graphs prepended
* before execution based on use-case specific criteria, with the following format:
*
* #pragma acl.graphs <graph1>|<graph2>|..|<graphN>
*
*/
public class SecurityContextDynamic implements SecurityContext {

public static final Node dynamicAccess = NodeFactory.createURI("urn:jena:accessGraphsDynamic");

private static final Pattern GRAPH_PRAGMA_DELIMITER_PATTERN = Pattern.compile("\\|");
private static final String GRAPH_PRAGMA_PATTERN_GROUP = "graphs";
private static final Pattern GRAPH_PRAGMA_PATTERN = Pattern.compile(
"^" +
// Leading whitespace
"\\s*" +
// For simplicity, expect pragma on first non-whitespace line
"#pragma acl\\.graphs\\h+(?<" + GRAPH_PRAGMA_PATTERN_GROUP + ">\\S*)\\h*\\v" +
// Anything else
".*",
Pattern.DOTALL
);

public SecurityContextDynamic() {}

@Override
public Collection<Node> visibleGraphs() {
return Collections.emptyList();
}

@Override
public boolean visableDefaultGraph() { return false; }

@Override
public Predicate<Quad> predicateQuad() { return q -> false; }

@Override
public void filterTDB(DatasetGraph dsg, QueryExecution qExec) {
Predicate<?> pred = tuple->false;
qExec.getContext().set(GraphFilter.getContextKey(dsg), pred);
}

public SecurityContext instantiateFromQuery(String queryString) {
if ( queryString != null ) {
Matcher m = GRAPH_PRAGMA_PATTERN.matcher(queryString);
if ( m.matches() ) {
String[] graphs = GRAPH_PRAGMA_DELIMITER_PATTERN
.splitAsStream(m.group(GRAPH_PRAGMA_PATTERN_GROUP))
.map(String::trim)
.toArray(String[]::new);
return new SecurityContextView(graphs);
}
}
return new SecurityContextAllowNone();
}
}

0 comments on commit 6618f02

Please sign in to comment.