/*
* 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.permissions.query.rewriter;
import java.util.ArrayList;
import java.util.List;
import org.apache.jena.graph.Node;
import org.apache.jena.graph.NodeFactory;
import org.apache.jena.graph.Triple;
import org.apache.jena.permissions.SecuredItem;
import org.apache.jena.permissions.SecurityEvaluator;
import org.apache.jena.permissions.SecurityEvaluator.Action;
import org.apache.jena.shared.AuthenticationRequiredException;
import org.apache.jena.shared.ReadDeniedException;
import org.apache.jena.sparql.algebra.Op;
import org.apache.jena.sparql.algebra.OpVisitor;
import org.apache.jena.sparql.algebra.op.*;
import org.apache.jena.sparql.core.BasicPattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class rewrites the query by examining each operation in the algebra
* returned by the Jena SPARQL parser.
* <p>
* This implementation inserts security evaluator checks where necessary.
* </p>
*/
public class OpRewriter implements OpVisitor {
private static Logger LOG = LoggerFactory.getLogger(OpRewriter.class);
private OpSequence result;
private final Node graphIRI;
private final SecurityEvaluator securityEvaluator;
// if true the restricted data are silently ignored.
// default false
private final boolean silentFail;
/**
* Constructor
*
* @param securityEvaluator
* The security evaluator to use
* @param graphIRI
* The IRI for the default graph.
*/
public OpRewriter(final SecurityEvaluator securityEvaluator,
final Node graphIRI) {
this.securityEvaluator = securityEvaluator;
this.graphIRI = graphIRI;
this.silentFail = false;
reset();
}
/**
* Constructor
*
* @param securityEvaluator
* The security evaluator to use
* @param graphIRI
* The IRI for the default graph.
*/
public OpRewriter(final SecurityEvaluator securityEvaluator,
final String graphIRI) {
this(securityEvaluator, NodeFactory.createURI(graphIRI));
}
/**
* Add the operation to the result.
*
* @param op
* the operation to add.
*/
private void addOp(final Op op) {
result.add(op);
}
/**
* Get the result of the rewrite.
*
* @return the resulting operator
*/
public Op getResult() {
if (result.size() == 0) {
return OpNull.create();
}
if (result.size() == 1) {
return result.get(0);
}
return result;
}
/**
* Register variables.
*
* Registers n as a variable if it is one.
*
* @param n
* the node to check
* @param variables
* the list of variable nodes
* @Return n for chaining.
*/
private Node registerVariables(final Node n, final List<Node> variables) {
if (n.isVariable() && !variables.contains(n)) {
variables.add(n);
}
return n;
}
/**
* Reset the rewriter to the initial state.
*
* @return this rewriter for chaining.
*/
public OpRewriter reset() {
result = OpSequence.create();
return this;
}
/**
* Register all the variables in the triple.
*
* @param t
* the triple to register.
* @param variables
* The list of variables.
* @return t for chaining
*/
private Triple registerBGPTriple(final Triple t, final List<Node> variables) {
registerVariables(t.getSubject(), variables);
registerVariables(t.getPredicate(), variables);
registerVariables(t.getObject(), variables);
return t;
}
/**
* Rewrites the subop of op1 and returns the result.
*
* @param op1
* @return the rewritten op.
*/
private Op rewriteOp1(final Op1 op1) {
final OpRewriter rewriter = new OpRewriter(securityEvaluator, graphIRI);
op1.getSubOp().visit(rewriter);
return rewriter.getResult();
}
/**
* rewrites the left and right parts of the op2 the left part is returned
* the right part is placed in the rewriter
*
* @param op2
* @param rewriter
* @return the rewritten op.
*/
private Op rewriteOp2(final Op2 op2, final OpRewriter rewriter) {
op2.getLeft().visit(rewriter.reset());
final Op left = rewriter.getResult();
op2.getRight().visit(rewriter.reset());
return left;
}
/**
* rewrite source to dest and returns dest
*
* @param source
* @param dest
* @return the rewritten op.
*/
private OpN rewriteOpN(final OpN source, final OpN dest) {
final OpRewriter rewriter = new OpRewriter(securityEvaluator, graphIRI);
for (final Op o : source.getElements()) {
o.visit(rewriter.reset());
dest.add(rewriter.getResult());
}
return dest;
}
/**
* rewrites the subop of assign.
*/
@Override
public void visit(final OpAssign opAssign) {
if (LOG.isDebugEnabled()) {
LOG.debug("Starting visiting OpAssign");
}
addOp(OpAssign.assign(rewriteOp1(opAssign), opAssign.getVarExprList()));
}
@Override
public void visit(final OpBGP opBGP) throws ReadDeniedException,
AuthenticationRequiredException {
if (LOG.isDebugEnabled()) {
LOG.debug("Starting visiting OpBGP");
}
Object principal = securityEvaluator.getPrincipal();
if (!securityEvaluator.evaluate(principal, Action.Read, graphIRI)) {
if (silentFail) {
return;
} else {
throw new ReadDeniedException(
SecuredItem.Util.modelPermissionMsg(graphIRI));
}
}
// if the user can read any triple just add the opBGP
if (securityEvaluator.evaluate(principal, Action.Read, graphIRI,
Triple.ANY)) {
addOp(opBGP);
} else {
// add security filtering to the resulting triples
final List<Triple> newBGP = new ArrayList<Triple>();
final List<Node> variables = new ArrayList<Node>();
// register all variables
for (final Triple t : opBGP.getPattern().getList()) {
newBGP.add(registerBGPTriple(t, variables));
}
// create the security function.
final SecuredFunction secFunc = new SecuredFunction(graphIRI,
securityEvaluator, variables, newBGP);
// create the filter
Op filter = OpFilter.filter(secFunc,
new OpBGP(BasicPattern.wrap(newBGP)));
// add the filter
addOp(filter);
}
}
/**
* Rewrite left and right
*/
@Override
public void visit(final OpConditional opCondition) {
if (LOG.isDebugEnabled()) {
LOG.debug("Starting visiting OpConditional");
}
final OpRewriter rewriter = new OpRewriter(securityEvaluator, graphIRI);
addOp(new OpConditional(rewriteOp2(opCondition, rewriter),
rewriter.getResult()));
}
/**
* returns the dsNames
*/
@Override
public void visit(final OpDatasetNames dsNames) {
if (LOG.isDebugEnabled()) {
LOG.debug("Starting visiting OpDatasetName");
}
addOp(dsNames);
}
/**
* Rewrite left and right
*/
@Override
public void visit(final OpDiff opDiff) {
if (LOG.isDebugEnabled()) {
LOG.debug("Starting visiting OpDiff");
}
final OpRewriter rewriter = new OpRewriter(securityEvaluator, graphIRI);
addOp(OpDiff.create(rewriteOp2(opDiff, rewriter), rewriter.getResult()));
}
/**
* Rewrite sequence elements
*/
@Override
public void visit(final OpDisjunction opDisjunction) {
if (LOG.isDebugEnabled()) {
LOG.debug("Starting visiting OpDisjunction");
}
addOp(rewriteOpN(opDisjunction, OpDisjunction.create()));
}
/**
* rewrites the subop of distinct
*/
@Override
public void visit(final OpDistinct opDistinct) {
if (LOG.isDebugEnabled()) {
LOG.debug("Starting visiting OpDistinct");
}
addOp(new OpDistinct(rewriteOp1(opDistinct)));
}
/**
* Returns the Ext
*/
@Override
public void visit(final OpExt opExt) {
if (LOG.isDebugEnabled()) {
LOG.debug("Starting visiting OpExt");
}
addOp(opExt);
}
/**
* rewrites the subop of extend.
*/
@Override
public void visit(final OpExtend opExtend) {
if (LOG.isDebugEnabled()) {
LOG.debug("Starting visiting OpExtend");
}
addOp(OpExtend.extend(rewriteOp1(opExtend), opExtend.getVarExprList()));
}
/**
* rewrites the subop of filter.
*/
@Override
public void visit(final OpFilter opFilter) {
if (LOG.isDebugEnabled()) {
LOG.debug("Starting visiting OpFilter");
}
addOp(OpFilter.filterBy(opFilter.getExprs(), rewriteOp1(opFilter)));
}
/**
* rewrites the subop of graph.
*/
@Override
public void visit(final OpGraph opGraph) {
if (LOG.isDebugEnabled()) {
LOG.debug("Starting visiting OpGraph");
}
final OpRewriter rewriter = new OpRewriter(securityEvaluator,
opGraph.getNode());
opGraph.getSubOp().visit(rewriter);
addOp(new OpGraph(opGraph.getNode(), rewriter.getResult()));
}
/**
* rewrites the subop of group.
*/
@Override
public void visit(final OpGroup opGroup) {
if (LOG.isDebugEnabled()) {
LOG.debug("Starting visiting OpGroup");
}
addOp(new OpGroup(rewriteOp1(opGroup), opGroup.getGroupVars(),
opGroup.getAggregators()));
}
/**
* Parses the joins and recursively calls the left and right parts
*/
@Override
public void visit(final OpJoin opJoin) {
if (LOG.isDebugEnabled()) {
LOG.debug("Starting visiting OpJoin");
}
final OpRewriter rewriter = new OpRewriter(securityEvaluator, graphIRI);
addOp(OpJoin.create(rewriteOp2(opJoin, rewriter), rewriter.getResult()));
}
/**
* returns the label
*/
@Override
public void visit(final OpLabel opLabel) {
if (LOG.isDebugEnabled()) {
LOG.debug("Starting visiting OpLabel");
}
addOp(opLabel);
}
/**
* Parses the joins and recursively calls the left and right parts
*/
@Override
public void visit(final OpLeftJoin opLeftJoin) {
if (LOG.isDebugEnabled()) {
LOG.debug("Starting visiting OpLeftJoin");
}
final OpRewriter rewriter = new OpRewriter(securityEvaluator, graphIRI);
addOp(OpLeftJoin.create(rewriteOp2(opLeftJoin, rewriter),
rewriter.getResult(), opLeftJoin.getExprs()));
}
/**
* rewrites the subop of list.
*/
@Override
public void visit(final OpList opList) {
if (LOG.isDebugEnabled()) {
LOG.debug("Starting visiting OpList");
}
addOp(new OpList(rewriteOp1(opList)));
}
/**
* Rewrite left and right
*/
@Override
public void visit(final OpMinus opMinus) {
if (LOG.isDebugEnabled()) {
LOG.debug("Starting visiting OpMinus");
}
final OpRewriter rewriter = new OpRewriter(securityEvaluator, graphIRI);
addOp(OpMinus.create(rewriteOp2(opMinus, rewriter),
rewriter.getResult()));
}
/**
* returns the null
*/
@Override
public void visit(final OpNull opNull) {
if (LOG.isDebugEnabled()) {
LOG.debug("Starting visiting OpNull");
}
addOp(opNull);
}
/**
* rewrites the subop of order.
*/
@Override
public void visit(final OpOrder opOrder) {
if (LOG.isDebugEnabled()) {
LOG.debug("Starting visiting OpOrder");
}
addOp(new OpOrder(rewriteOp1(opOrder), opOrder.getConditions()));
}
/**
* Returns the path
*/
@Override
public void visit(final OpPath opPath) {
if (LOG.isDebugEnabled()) {
LOG.debug("Starting visiting OpPath");
}
addOp(opPath);
}
/**
* rewrites the subop of proc.
*/
@Override
public void visit(final OpProcedure opProc) {
if (LOG.isDebugEnabled()) {
LOG.debug("Starting visiting OpProc");
}
if (opProc.getProcId() != null) {
addOp(new OpProcedure(opProc.getProcId(), opProc.getArgs(),
rewriteOp1(opProc)));
} else {
addOp(new OpProcedure(opProc.getURI(), opProc.getArgs(),
rewriteOp1(opProc)));
}
}
/**
* rewrites the subop of project.
*/
@Override
public void visit(final OpProject opProject) {
if (LOG.isDebugEnabled()) {
LOG.debug("Starting visiting OpProject");
}
addOp(new OpProject(rewriteOp1(opProject), opProject.getVars()));
}
/**
* rewrites the subop of propFunc.
*/
@Override
public void visit(final OpPropFunc opPropFunc) {
if (LOG.isDebugEnabled()) {
LOG.debug("Starting visiting OpPropFunc");
}
addOp(new OpPropFunc(opPropFunc.getProperty(),
opPropFunc.getSubjectArgs(), opPropFunc.getObjectArgs(),
rewriteOp1(opPropFunc)));
}
/**
* Returns the quad
*/
@Override
public void visit(final OpQuad opQuad) {
if (LOG.isDebugEnabled()) {
LOG.debug("Starting visiting OpQuad");
}
addOp(opQuad);
}
/**
* Returns the quadpattern
*/
@Override
public void visit(final OpQuadPattern quadPattern) {
if (LOG.isDebugEnabled()) {
LOG.debug("Starting visiting OpQuadPattern");
}
addOp(quadPattern);
}
/**
* rewrites the subop of reduced.
*/
@Override
public void visit(final OpReduced opReduced) {
if (LOG.isDebugEnabled()) {
LOG.debug("Starting visiting OpReduced");
}
addOp(OpReduced.create(rewriteOp1(opReduced)));
}
/**
* Rewrite sequence elements
*/
@Override
public void visit(final OpSequence opSequence) {
if (LOG.isDebugEnabled()) {
LOG.debug("Starting visiting OpSequence");
}
addOp(rewriteOpN(opSequence, OpSequence.create()));
}
/**
* returns the service
*/
@Override
public void visit(final OpService opService) {
if (LOG.isDebugEnabled()) {
LOG.debug("Starting visiting opService");
}
addOp(opService);
}
/**
* rewrites the subop of slice
*
* This also handles the limit case
*/
@Override
public void visit(final OpSlice opSlice) {
if (LOG.isDebugEnabled()) {
LOG.debug("Starting visiting OpSlice");
}
addOp(opSlice);
}
/**
* returns the table
*/
@Override
public void visit(final OpTable opTable) {
if (LOG.isDebugEnabled()) {
LOG.debug("Starting visiting OpTable");
}
addOp(opTable);
}
/**
* rewrites the subop of top.
*/
@Override
public void visit(final OpTopN opTop) {
if (LOG.isDebugEnabled()) {
LOG.debug("Starting visiting OpTop");
}
addOp(new OpTopN(rewriteOp1(opTop), opTop.getLimit(),
opTop.getConditions()));
}
/**
* Converts to BGP
*/
@Override
public void visit(final OpTriple opTriple) {
if (LOG.isDebugEnabled()) {
LOG.debug("Starting visiting OpTriple");
}
visit(opTriple.asBGP());
}
/**
* Rewrite left and right
*/
@Override
public void visit(final OpUnion opUnion) {
if (LOG.isDebugEnabled()) {
LOG.debug("Starting visiting OpUnion");
}
final OpRewriter rewriter = new OpRewriter(securityEvaluator, graphIRI);
addOp(OpUnion.create(rewriteOp2(opUnion, rewriter),
rewriter.getResult()));
}
@Override
public void visit(OpQuadBlock quadBlock) {
if (LOG.isDebugEnabled()) {
LOG.debug("Starting visiting OpQuadBlock");
}
addOp(quadBlock);
}
}