/*
* 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.impl;
import java.lang.reflect.Proxy;
import org.apache.commons.collections4.map.LRUMap;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.jena.graph.FrontsTriple;
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.AddDeniedException;
import org.apache.jena.shared.AuthenticationRequiredException;
import org.apache.jena.shared.DeleteDeniedException;
import org.apache.jena.shared.ReadDeniedException;
import org.apache.jena.shared.UpdateDeniedException;
import org.apache.jena.sparql.expr.Expr;
import org.apache.jena.sparql.util.NodeUtils;
import org.apache.jena.util.iterator.ExtendedIterator;
import org.apache.jena.vocabulary.RDF;
/**
* An abstract implementation of SecuredItem that caches security checks.
* <p>
* Security checks are performed at multiple locations. This implementation
* ensures that during a single operation the specific check is only evaluated
* once by caching the result.
* </p>
*
*/
public abstract class SecuredItemImpl implements SecuredItem {
// a key for the secured item.
private class CacheKey implements Comparable<CacheKey> {
private final Action action;
private final Node modelNode;
private final Triple from;
private final Triple to;
private Integer hashCode;
public CacheKey(final Action action, final Node modelNode) {
this(action, modelNode, null, null);
}
public CacheKey(final Action action, final Node modelNode,
final Triple to) {
this(action, modelNode, to, null);
}
public CacheKey(final Action action, final Node modelNode,
final Triple to, final Triple from) {
this.action = action;
this.modelNode = modelNode;
this.to = to;
this.from = from;
}
private int compare(Node n1, Node n2) {
if (Node.ANY.equals(n1)) {
if (Node.ANY.equals(n2)) {
return Expr.CMP_EQUAL;
}
return Expr.CMP_LESS;
}
if (Node.ANY.equals(n2)) {
return Expr.CMP_GREATER;
}
return NodeUtils.compareRDFTerms(n1, n2);
}
private int compare(Triple t1, Triple t2) {
if (t1 == null) {
if (t2 == null) {
return Expr.CMP_EQUAL;
}
return Expr.CMP_LESS;
}
if (t2 == null) {
return Expr.CMP_GREATER;
}
int retval = compare(t1.getSubject(), t2.getSubject());
if (retval == Expr.CMP_EQUAL) {
retval = compare(t1.getPredicate(), t2.getPredicate());
}
if (retval == Expr.CMP_EQUAL) {
retval = compare(t1.getObject(), t2.getObject());
}
return retval;
}
@Override
public int compareTo(final CacheKey other) {
int retval = this.action.compareTo(other.action);
if (retval == Expr.CMP_EQUAL) {
retval = NodeUtils.compareRDFTerms(this.modelNode,
other.modelNode);
}
if (retval == Expr.CMP_EQUAL) {
retval = compare(this.to, other.to);
}
if (retval == Expr.CMP_EQUAL) {
retval = compare(this.from, other.from);
}
return retval;
}
@Override
public boolean equals(final Object o) {
if (o instanceof CacheKey) {
return this.compareTo((CacheKey) o) == 0;
}
return false;
}
@Override
public int hashCode() {
if (hashCode == null) {
hashCode = new HashCodeBuilder().append(action)
.append(modelNode).append(from).append(to).toHashCode();
}
return hashCode;
}
}
// the maximum size of the cache
public static int MAX_CACHE = 100;
// the cache for this thread.
public static final ThreadLocal<LRUMap<CacheKey, Boolean>> CACHE = new ThreadLocal<LRUMap<CacheKey, Boolean>>();
// the number of times this thread has recursively called the constructor.
public static final ThreadLocal<Integer> COUNT = new ThreadLocal<Integer>();
/**
* May Convert a Jena Node object into the SecurityEvaluator.VARIABLE
* instance.
*
* @param jenaNode
* The Jena node to convert.
* @return The Node that represents the jenaNode.
*/
private static Node convert(final Node jenaNode) {
if (jenaNode.isVariable()) {
return SecurityEvaluator.VARIABLE;
}
return jenaNode;
}
/**
* Convert a Jena Triple into a SecTriple.
*
* @param jenaTriple
* The Jena Triple to convert.
* @return The SecTriple that represents the jenaTriple.
*/
private static Triple convert(final Triple jenaTriple) {
if (jenaTriple.getSubject().isVariable()
|| jenaTriple.getPredicate().isVariable()
|| jenaTriple.getObject().isVariable()) {
return new Triple(SecuredItemImpl.convert(jenaTriple.getSubject()),
SecuredItemImpl.convert(jenaTriple.getPredicate()),
SecuredItemImpl.convert(jenaTriple.getObject()));
}
return jenaTriple;
}
/**
* Decrement the number of instances of SecuredItem.
*/
public static void decrementUse() {
final Integer i = SecuredItemImpl.COUNT.get();
if (i == null) {
throw new IllegalStateException("No count on exit");
}
if (i < 1) {
throw new IllegalStateException("No count less than 1");
}
if (i == 1) {
SecuredItemImpl.CACHE.remove();
SecuredItemImpl.COUNT.remove();
} else {
SecuredItemImpl.COUNT.set(i - 1);
}
}
/**
* Increment the number of instances of SecuredItem.
*/
public static void incrementUse() {
final Integer i = SecuredItemImpl.COUNT.get();
if (i == null) {
SecuredItemImpl.CACHE.set(new LRUMap<CacheKey, Boolean>(Math.max(
SecuredItemImpl.MAX_CACHE, 100)));
SecuredItemImpl.COUNT.set(1);
} else {
SecuredItemImpl.COUNT.set(i + 1);
}
}
// the evaluator we are using
private final SecurityEvaluator securityEvaluator;
// the secured node for that names the graph.
private final Node modelNode;
// the item holder that we are evaluating.
private final ItemHolder<?, ?> itemHolder;
/**
* Create the SecuredItemImpl.
*
* @param securedItem
* The securedItem.
* @param holder
* The Item holder for the securedItem.
* @throws IllegalArgumentException
* if securedItem is null or securedItem.getSecurityEvaluator()
* returns null, or the holder is null.
*/
protected SecuredItemImpl(final SecuredItem securedItem,
final ItemHolder<?, ?> holder) {
if (securedItem == null) {
throw new IllegalArgumentException("Secured item may not be null");
}
if (securedItem.getSecurityEvaluator() == null) {
throw new IllegalArgumentException(
"Security evaluator in secured item may not be null");
}
if (holder == null) {
throw new IllegalArgumentException("ItemHolder may not be null");
}
this.securityEvaluator = securedItem.getSecurityEvaluator();
this.modelNode = securedItem.getModelNode();
this.itemHolder = holder;
}
/**
* Create the SecuredItemImpl.
*
* @param securityEvaluator
* the secured evaluator to use.
* @param modelURI
* the URI for the model.
* @param holder
* The holder to use.
* @throws IllegalArgumentException
* if security evaluator is null, modelURI is null or empty, or
* holder is null.
*/
protected SecuredItemImpl(final SecurityEvaluator securityEvaluator,
final String modelURI, final ItemHolder<?, ?> holder) {
if (securityEvaluator == null) {
throw new IllegalArgumentException(
"Security evaluator may not be null");
}
if (StringUtils.isEmpty(modelURI)) {
throw new IllegalArgumentException(
"ModelURI may not be empty or null");
}
if (holder == null) {
throw new IllegalArgumentException("ItemHolder may not be null");
}
this.securityEvaluator = securityEvaluator;
this.modelNode = NodeFactory.createURI(modelURI);
this.itemHolder = holder;
}
@Override
public String toString() throws AuthenticationRequiredException {
if (canRead()) {
return itemHolder.getBaseItem().toString();
}
return super.toString();
}
/**
* get the cached value.
*
* @param key
* The key to look for.
* @return the value of the security check or <code>null</code> if the value
* has not been cached.
*/
private Boolean cacheGet(final CacheKey key) {
final LRUMap<CacheKey, Boolean> cache = SecuredItemImpl.CACHE.get();
return (cache == null) ? null : (Boolean) cache.get(key);
}
/**
* set the cache value.
*
* @param key
* The key to set the value for.
* @param value
* The value to set.
*/
private void cachePut(final CacheKey key, final boolean value) {
final LRUMap<CacheKey, Boolean> cache = SecuredItemImpl.CACHE.get();
if (cache != null) {
cache.put(key, value);
SecuredItemImpl.CACHE.set(cache);
}
}
@Override
public boolean canCreate() throws AuthenticationRequiredException {
final CacheKey key = new CacheKey(Action.Create, modelNode);
Boolean retval = cacheGet(key);
if (retval == null) {
retval = securityEvaluator.evaluate(
securityEvaluator.getPrincipal(), Action.Create, modelNode);
cachePut(key, retval);
}
return retval;
}
@Override
public boolean canCreate(final Triple triple)
throws AuthenticationRequiredException {
Triple t = convert(triple);
final CacheKey key = new CacheKey(Action.Create, modelNode, t);
Boolean retval = cacheGet(key);
if (retval == null) {
retval = securityEvaluator.evaluate(
securityEvaluator.getPrincipal(), Action.Create, modelNode,
t);
cachePut(key, retval);
}
return retval;
}
@Override
public boolean canCreate(final FrontsTriple frontsTriple)
throws AuthenticationRequiredException {
return canCreate(frontsTriple.asTriple());
}
@Override
public boolean canDelete() throws AuthenticationRequiredException {
final CacheKey key = new CacheKey(Action.Delete, modelNode);
Boolean retval = cacheGet(key);
if (retval == null) {
retval = securityEvaluator.evaluate(
securityEvaluator.getPrincipal(), Action.Delete, modelNode);
cachePut(key, retval);
}
return retval;
}
@Override
public boolean canDelete(final Triple triple)
throws AuthenticationRequiredException {
Triple t = convert(triple);
final CacheKey key = new CacheKey(Action.Delete, modelNode, t);
Boolean retval = cacheGet(key);
if (retval == null) {
retval = securityEvaluator.evaluate(
securityEvaluator.getPrincipal(), Action.Delete, modelNode,
t);
cachePut(key, retval);
}
return retval;
}
@Override
public boolean canDelete(final FrontsTriple frontsTriple)
throws AuthenticationRequiredException {
return canDelete(frontsTriple.asTriple());
}
@Override
public boolean canRead() throws AuthenticationRequiredException {
final CacheKey key = new CacheKey(Action.Read, modelNode);
Boolean retval = cacheGet(key);
if (retval == null) {
retval = securityEvaluator.evaluate(
securityEvaluator.getPrincipal(), Action.Read, modelNode);
cachePut(key, retval);
}
return retval;
}
@Override
public boolean canRead(final Triple triple)
throws AuthenticationRequiredException {
Triple t = convert(triple);
final CacheKey key = new CacheKey(Action.Read, modelNode, t);
Boolean retval = cacheGet(key);
if (retval == null) {
retval = securityEvaluator
.evaluate(securityEvaluator.getPrincipal(), Action.Read,
modelNode, t);
cachePut(key, retval);
}
return retval;
}
@Override
public boolean canRead(final FrontsTriple frontsTriple)
throws AuthenticationRequiredException {
return canRead(frontsTriple.asTriple());
}
@Override
public boolean canUpdate() throws AuthenticationRequiredException {
final CacheKey key = new CacheKey(Action.Update, modelNode);
Boolean retval = cacheGet(key);
if (retval == null) {
retval = securityEvaluator.evaluate(
securityEvaluator.getPrincipal(), Action.Update, modelNode);
cachePut(key, retval);
}
return retval;
}
@Override
public boolean canUpdate(final Triple f, final Triple t)
throws AuthenticationRequiredException {
Triple from = convert(f);
Triple to = convert(t);
final CacheKey key = new CacheKey(Action.Update, modelNode, from, to);
Boolean retval = cacheGet(key);
if (retval == null) {
retval = securityEvaluator.evaluateUpdate(
securityEvaluator.getPrincipal(), modelNode, from, to);
cachePut(key, retval);
}
return retval;
}
@Override
public boolean canUpdate(final FrontsTriple from, final FrontsTriple to)
throws AuthenticationRequiredException {
return canUpdate(from.asTriple(), to.asTriple());
}
/**
* check that create on the securedModel is allowed,
*
* @throws AddDeniedException
* on failure
* @throws AuthenticationRequiredException
* if user is not authenticated and is required to be.
*/
protected void checkCreate() throws AddDeniedException,
AuthenticationRequiredException {
if (!canCreate()) {
throw new AddDeniedException(
SecuredItem.Util.modelPermissionMsg(modelNode));
}
}
/**
* check that the triple can be created in the securedModel.,
*
* @throws AddDeniedException
* on failure
* @throws AuthenticationRequiredException
* if user is not authenticated and is required to be.
*/
protected void checkCreate(final Triple t) throws AddDeniedException,
AuthenticationRequiredException {
if (!canCreate(t)) {
throw new AddDeniedException(
SecuredItem.Util.triplePermissionMsg(modelNode), t);
}
}
/**
* check that the statement can be created.
*
* @param s
* The statement.
* @throws AddDeniedException
* on failure
* @throws AuthenticationRequiredException
* if user is not authenticated and is required to be.
*/
protected void checkCreate(final FrontsTriple frontsTriple)
throws AddDeniedException, AuthenticationRequiredException {
checkCreate(frontsTriple.asTriple());
}
/**
* Check that a triple can be reified.
*
* @param uri
* The URI for the reification subject. May be null.
* @param front
* the frontstriple that is to be reified.
* @throws AddDeniedException
* on failure to add triple
* @throws UpdateDeniedException
* if the updates of the graph are not allowed.
* @throws AuthenticationRequiredException
* if user is not authenticated and is required to be.
*/
protected void checkCreateReified(final String uri, final FrontsTriple front)
throws AddDeniedException, UpdateDeniedException,
AuthenticationRequiredException {
checkUpdate();
Triple t = front.asTriple();
final Node n = uri == null ? SecurityEvaluator.FUTURE : NodeFactory
.createURI(uri);
checkCreate(new Triple(n, RDF.subject.asNode(), t.getSubject()));
checkCreate(new Triple(n, RDF.predicate.asNode(), t.getPredicate()));
checkCreate(new Triple(n, RDF.object.asNode(), t.getObject()));
}
/**
* Check that all the triples can be created.
*
* @param FrontsTripleIter
* an iterator of FrontsTriple objects.
* @throws AddDeniedException
* if a triple can not be added.
* @throws AuthenticationRequiredException
* if user is not authenticated and is required to be.
*/
protected void checkCreateFrontsTriples(
final ExtendedIterator<? extends FrontsTriple> FrontsTripleIter)
throws AddDeniedException, AuthenticationRequiredException {
if (!canCreate(Triple.ANY)) {
try {
while (FrontsTripleIter.hasNext()) {
checkCreate(FrontsTripleIter.next());
}
} finally {
FrontsTripleIter.close();
}
}
}
/**
* Check that all the triples can be created.
*
* @param triples
* an iterator of triples.
* @throws AddDeniedException
* if a triple can not be added.
* @throws AuthenticationRequiredException
* if user is not authenticated and is required to be.
*/
protected void checkCreateTriples(final ExtendedIterator<Triple> triples)
throws AddDeniedException, AuthenticationRequiredException {
if (!canCreate(Triple.ANY)) {
try {
while (triples.hasNext()) {
checkCreate(triples.next());
}
} finally {
triples.close();
}
}
}
/**
* check that delete on the securedModel is allowed,
*
* @throws DeleteDeniedException
* on failure
* @throws AuthenticationRequiredException
* if user is not authenticated and is required to be.
*/
protected void checkDelete() throws DeleteDeniedException,
AuthenticationRequiredException {
if (!canDelete()) {
throw new DeleteDeniedException(
SecuredItem.Util.modelPermissionMsg(modelNode));
}
}
/**
* check that the triple can be deleted in the securedModel.,
*
* @param triple
* The triple to check.
* @throws DeleteDeniedException
* on failure
* @throws AuthenticationRequiredException
* if user is not authenticated and is required to be.
*/
protected void checkDelete(final Triple triple)
throws DeleteDeniedException, AuthenticationRequiredException {
if (!canDelete(triple)) {
throw new DeleteDeniedException(
SecuredItem.Util.triplePermissionMsg(modelNode), triple);
}
}
/**
* check that the triple can be deleted in the securedModel.,
*
* @param frontsTriple
* An object fronting the triple to check.
* @throws DeleteDeniedException
* on failure
* @throws AuthenticationRequiredException
* if user is not authenticated and is required to be.
*/
protected void checkDelete(final FrontsTriple frontsTriple)
throws DeleteDeniedException, AuthenticationRequiredException {
checkDelete(frontsTriple.asTriple());
}
/**
* check that the triples can be deleted in the securedModel.,
*
* @param frontsTripleIter
* An iterator of objects fronting triples to check.
* @throws DeleteDeniedException
* on failure
* @throws AuthenticationRequiredException
* if user is not authenticated and is required to be.
*/
protected void checkDeleteFrontsTriples(
final ExtendedIterator<? extends FrontsTriple> frontsTriplesIter)
throws DeleteDeniedException, AuthenticationRequiredException {
if (!canDelete(Triple.ANY)) {
try {
while (frontsTriplesIter.hasNext()) {
checkDelete(frontsTriplesIter.next());
}
} finally {
frontsTriplesIter.close();
}
}
}
/**
* check that the triples can be deleted in the securedModel.,
*
* @param triples
* An iterator of triples to check.
* @throws DeleteDeniedException
* on failure
* @throws AuthenticationRequiredException
* if user is not authenticated and is required to be.
*/
protected void checkDeleteTriples(final ExtendedIterator<Triple> triples)
throws DeleteDeniedException, AuthenticationRequiredException {
if (!canDelete(Triple.ANY)) {
try {
while (triples.hasNext()) {
checkDelete(triples.next());
}
} finally {
triples.close();
}
}
}
/**
* check that read on the securedModel is allowed,
*
* @throws ReadDeniedException
* on failure
* @throws AuthenticationRequiredException
* if user is not authenticated and is required to be.
*/
protected void checkRead() throws ReadDeniedException,
AuthenticationRequiredException {
if (!canRead()) {
throw new ReadDeniedException(
SecuredItem.Util.modelPermissionMsg(modelNode));
}
}
/**
* check that the triple can be read in the securedModel.,
*
* @param triple
* The triple to check.
* @throws ReadDeniedException
* on failure
* @throws AuthenticationRequiredException
* if user is not authenticated and is required to be.
*/
protected void checkRead(final Triple triple) throws ReadDeniedException,
AuthenticationRequiredException {
if (!canRead(triple)) {
throw new ReadDeniedException(
SecuredItem.Util.triplePermissionMsg(modelNode), triple);
}
}
/**
* check that the triple can be read in the securedModel.,
*
* @param frontsTriple
* The object fronting the triple to check.
* @throws ReadDeniedException
* on failure
* @throws AuthenticationRequiredException
* if user is not authenticated and is required to be.
*/
protected void checkRead(final FrontsTriple frontsTriple)
throws ReadDeniedException, AuthenticationRequiredException {
checkRead(frontsTriple.asTriple());
}
/**
* check that the triple can be read in the securedModel.,
*
* @param frontsTripleIter
* The iterator of fronts triple objects to check.
* @throws ReadDeniedException
* on failure
* @throws AuthenticationRequiredException
* if user is not authenticated and is required to be.
*/
protected void checkReadFrontsTriples(
final ExtendedIterator<FrontsTriple> frontsTripleIter)
throws ReadDeniedException, AuthenticationRequiredException {
try {
while (frontsTripleIter.hasNext()) {
checkRead(frontsTripleIter.next());
}
} finally {
frontsTripleIter.close();
}
}
/**
* check that the triple can be read in the securedModel.,
*
* @param triples
* The iterator of triples to check.
* @throws ReadDeniedException
* on failure
* @throws AuthenticationRequiredException
* if user is not authenticated and is required to be.
*/
protected void checkReadTriples(final ExtendedIterator<Triple> triples)
throws ReadDeniedException, AuthenticationRequiredException {
try {
while (triples.hasNext()) {
checkRead(triples.next());
}
} finally {
triples.close();
}
}
/**
* check that update on the securedModel is allowed,
*
* @throws UpdateDeniedException
* on failure
* @throws AuthenticationRequiredException
* if user is not authenticated and is required to be.
*/
protected void checkUpdate() throws UpdateDeniedException,
AuthenticationRequiredException {
if (!canUpdate()) {
throw new UpdateDeniedException(
SecuredItem.Util.modelPermissionMsg(modelNode));
}
}
/**
* check that the triple can be updated in the securedModel.,
*
* @param from
* the starting triple
* @param to
* the final triple.
* @throws UpdateDeniedException
* on failure
* @throws AuthenticationRequiredException
* if user is not authenticated and is required to be.
*/
protected void checkUpdate(final Triple from, final Triple to)
throws UpdateDeniedException, AuthenticationRequiredException {
if (!canUpdate(from, to)) {
throw new UpdateDeniedException(String.format("%s: %s to %s",
SecuredItem.Util.modelPermissionMsg(modelNode), from, to));
}
}
@Override
public boolean equals(final Object o) {
if (Proxy.isProxyClass(o.getClass())) {
return o.equals(itemHolder.getSecuredItem());
} else {
if (o instanceof SecuredItemImpl) {
return itemHolder.getBaseItem().equals(
((SecuredItemImpl) o).getBaseItem());
}
return false;
}
}
@Override
public int hashCode() {
return itemHolder.getBaseItem().hashCode();
}
@Override
public Object getBaseItem() {
return itemHolder.getBaseItem();
}
@Override
public String getModelIRI() {
return modelNode.getURI();
}
/**
* get the name of the model.
*/
@Override
public Node getModelNode() {
return modelNode;
}
@Override
public SecurityEvaluator getSecurityEvaluator() {
return securityEvaluator;
}
@Override
public boolean isEquivalent(final SecuredItem securedItem) {
return SecuredItem.Util.isEquivalent(this, securedItem);
}
}