| /* |
| * 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); |
| } |
| } |
| } |
| } |