blob: 923927a5a70a032cf00e76a7e7a1e1233d772f1d [file] [log] [blame]
/*
* 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.jackrabbit.oak.core;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import org.apache.jackrabbit.oak.api.PropertyState;
import org.apache.jackrabbit.oak.plugins.memory.MemoryChildNodeEntry;
import org.apache.jackrabbit.oak.plugins.memory.MemoryNodeBuilder;
import org.apache.jackrabbit.oak.spi.security.authorization.permission.TreePermission;
import org.apache.jackrabbit.oak.spi.state.AbstractNodeState;
import org.apache.jackrabbit.oak.spi.state.ChildNodeEntry;
import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
import org.apache.jackrabbit.oak.spi.state.NodeState;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.Iterables.filter;
import static com.google.common.collect.Iterables.transform;
import static java.util.Collections.emptyList;
class SecureNodeState extends AbstractNodeState {
/**
* Underlying node state.
*/
private final NodeState state;
/**
* Tree permissions of this subtree.
*/
private final TreePermission treePermission;
private long childNodeCount = -1;
private long propertyCount = -1;
SecureNodeState(@NotNull NodeState state, @NotNull TreePermission treePermission) {
this.state = checkNotNull(state);
this.treePermission = checkNotNull(treePermission);
}
@Override
public boolean exists() {
return treePermission.canRead();
}
@Override @Nullable
public PropertyState getProperty(@NotNull String name) {
PropertyState property = state.getProperty(name);
if (property != null && treePermission.canRead(property)) {
return property;
} else {
return null;
}
}
@Override
public synchronized long getPropertyCount() {
if (propertyCount == -1) {
if (treePermission.canReadProperties()) {
propertyCount = state.getPropertyCount();
} else {
propertyCount = count(filter(
state.getProperties(),
new ReadablePropertyPredicate()));
}
}
return propertyCount;
}
@Override @NotNull
public Iterable<? extends PropertyState> getProperties() {
if (treePermission.canReadProperties()) {
return state.getProperties();
} else {
return filter(
state.getProperties(),
new ReadablePropertyPredicate());
}
}
@Override
public boolean hasChildNode(@NotNull String name) {
if (!state.hasChildNode(name)) {
return false;
} else if (treePermission.canReadAll()) {
return true;
} else {
NodeState child = state.getChildNode(name);
return treePermission.getChildPermission(name, child).canRead();
}
}
@NotNull
@Override
public NodeState getChildNode(@NotNull String name) {
NodeState child = state.getChildNode(name);
if (child.exists() && !treePermission.canReadAll()) {
ChildNodeEntry entry = new MemoryChildNodeEntry(name, child);
return new WrapChildEntryFunction().apply(entry).getNodeState();
} else {
return child;
}
}
@Override
public synchronized long getChildNodeCount(long max) {
if (childNodeCount == -1) {
long count;
if (treePermission.canReadAll()) {
count = state.getChildNodeCount(max);
} else {
count = super.getChildNodeCount(max);
}
if (count == Long.MAX_VALUE) {
return count;
}
childNodeCount = count;
}
return childNodeCount;
}
@Override @NotNull
public Iterable<? extends ChildNodeEntry> getChildNodeEntries() {
if (treePermission.canReadAll()) {
// everything is readable including ac-content -> no secure wrapper needed
return state.getChildNodeEntries();
} else if (treePermission.canRead()) {
Iterable<ChildNodeEntry> readable = transform(
state.getChildNodeEntries(),
new WrapChildEntryFunction());
return filter(readable, new IterableNodePredicate());
} else {
return emptyList();
}
}
@Override @NotNull
public NodeBuilder builder() {
return new MemoryNodeBuilder(this);
}
//------------------------------------------------------< inner classes >---
/**
* Predicate for testing whether a given property is readable.
*/
private class ReadablePropertyPredicate implements Predicate<PropertyState> {
@Override
public boolean apply(@Nullable PropertyState property) {
return property != null && treePermission.canRead(property);
}
}
/**
* Predicate for testing whether the node state in a child node entry is iterable.
*/
private static class IterableNodePredicate implements Predicate<ChildNodeEntry> {
@Override
public boolean apply(@Nullable ChildNodeEntry input) {
return input != null && input.getNodeState().exists();
}
}
/**
* Function that that adds a security wrapper to node states from
* in child node entries. The {@link IterableNodePredicate} predicate should be
* used on the result to filter out non-existing/iterable child nodes.
* <p>
* Note that the SecureNodeState wrapper is needed only when the child
* or any of its descendants has read access restrictions. Otherwise
* we can optimize access by skipping the security wrapper entirely.
*/
private class WrapChildEntryFunction implements Function<ChildNodeEntry, ChildNodeEntry> {
@NotNull
@Override
public ChildNodeEntry apply(@NotNull ChildNodeEntry input) {
String name = input.getName();
NodeState child = input.getNodeState();
TreePermission childContext = treePermission.getChildPermission(name, child);
SecureNodeState secureChild = new SecureNodeState(child, childContext);
if (child.getChildNodeCount(1) == 0
&& secureChild.treePermission.canRead()
&& secureChild.treePermission.canReadProperties()) {
// Since this is an accessible leaf node whose all properties
// are readable, we don't need the SecureNodeState wrapper
// TODO: A further optimization would be to return the raw
// underlying node state even for non-leaf nodes if we can
// tell in advance that the full subtree is readable. Then
// we also wouldn't need the above getChildNodeCount() call
// that's somewhat expensive on the DocumentMK.
return input;
} else {
return new MemoryChildNodeEntry(name, secureChild);
}
}
}
}