| /* |
| * 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.spi.security.authorization.cug.impl; |
| |
| import java.util.List; |
| import java.util.Set; |
| import com.google.common.collect.Iterables; |
| import com.google.common.collect.Lists; |
| import com.google.common.collect.Sets; |
| import org.apache.jackrabbit.oak.api.CommitFailedException; |
| import org.apache.jackrabbit.oak.api.PropertyState; |
| import org.apache.jackrabbit.oak.api.Type; |
| import org.apache.jackrabbit.oak.commons.PathUtils; |
| import org.apache.jackrabbit.oak.plugins.memory.PropertyBuilder; |
| import org.apache.jackrabbit.oak.spi.commit.CommitInfo; |
| import org.apache.jackrabbit.oak.spi.commit.PostValidationHook; |
| import org.apache.jackrabbit.oak.spi.state.DefaultNodeStateDiff; |
| import org.apache.jackrabbit.oak.spi.state.NodeBuilder; |
| import org.apache.jackrabbit.oak.spi.state.NodeState; |
| import org.apache.jackrabbit.oak.spi.state.NodeStateUtils; |
| import org.apache.jackrabbit.util.Text; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import static org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.EMPTY_NODE; |
| |
| /** |
| * {@code PostValidationHook} implementation responsible for keeping track of |
| * nested CUGs to simplify evaluation. The information about the nested CUGs is |
| * stored in a hidden property associated with the policy node. |
| * |
| * Note, that this hook does _not_ respect the configured supported paths |
| * and keeps track of all CUG policies irrespective of their validity. |
| * Consequently all optimization considering the nested CUG information must |
| * also verify the supported paths. |
| */ |
| class NestedCugHook implements PostValidationHook, CugConstants { |
| |
| /** |
| * logger instance |
| */ |
| private static final Logger log = LoggerFactory.getLogger(NestedCugHook.class); |
| |
| private Set<String> deletedCUGs = Sets.newHashSet(); |
| |
| //-------------------------------------------------< PostValidationHook >--- |
| @NotNull |
| @Override |
| public NodeState processCommit(NodeState before, NodeState after, CommitInfo info) throws CommitFailedException { |
| NodeBuilder builder = after.builder(); |
| after.compareAgainstBaseState(before, new Diff(before, builder)); |
| deletedCUGs.clear(); |
| return builder.getNodeState(); |
| } |
| |
| //-------------------------------------------------------------< Object >--- |
| @Override |
| public String toString() { |
| return "NestedCugHook"; |
| } |
| |
| //------------------------------------------------------------< private >--- |
| |
| private static long addNestedCugPath(@NotNull NodeBuilder parentBuilder, @NotNull NodeBuilder builder, @NotNull String pathWithNewCug) { |
| PropertyState ps = parentBuilder.getProperty(HIDDEN_NESTED_CUGS); |
| PropertyBuilder<String> pb = getHiddenPropertyBuilder(ps); |
| if (ps != null) { |
| List<String> moveToNestedCug = Lists.newArrayList(); |
| for (String p : ps.getValue(Type.STRINGS)) { |
| if (Text.isDescendant(pathWithNewCug, p)) { |
| pb.removeValue(p); |
| moveToNestedCug.add(p); |
| } else if (p.equals(pathWithNewCug)) { |
| // already present with parent -> remove to avoid duplicate entries |
| log.debug("Path of node holding a new nested CUG is already listed with the parent CUG."); |
| pb.removeValue(p); |
| } |
| } |
| if (!moveToNestedCug.isEmpty()) { |
| PropertyBuilder<String> pb2 = getHiddenPropertyBuilder(builder.getProperty(HIDDEN_NESTED_CUGS)); |
| pb2.addValues(moveToNestedCug); |
| builder.setProperty(pb2.getPropertyState()); |
| } |
| } |
| |
| // update the nested-cug property of the parent |
| pb.addValue(pathWithNewCug); |
| parentBuilder.setProperty(pb.getPropertyState()); |
| return pb.count(); |
| } |
| |
| private static int removeNestedCugPath(@NotNull NodeBuilder parentBuilder, @NotNull String toRemove, @NotNull Iterable<String> toReconnect) { |
| PropertyState ps = parentBuilder.getProperty(HIDDEN_NESTED_CUGS); |
| PropertyBuilder<String> pb = getHiddenPropertyBuilder(ps); |
| if (pb.hasValue(toRemove)) { |
| pb.removeValue(toRemove); |
| pb.addValues(toReconnect); |
| if (pb.isEmpty()) { |
| parentBuilder.removeProperty(HIDDEN_NESTED_CUGS); |
| return 0; |
| } else { |
| parentBuilder.setProperty(pb.getPropertyState()); |
| return pb.count(); |
| } |
| } else { |
| log.debug("Parent CUG doesn't contain expected entry for removed nested CUG"); |
| return -1; |
| } |
| } |
| |
| private static PropertyBuilder<String> getHiddenPropertyBuilder(@Nullable PropertyState ps) { |
| return PropertyBuilder.copy(Type.STRING, ps).setName(HIDDEN_NESTED_CUGS).setArray(); |
| } |
| |
| private final class Diff extends DefaultNodeStateDiff { |
| |
| private final Diff parentDiff; |
| private final boolean isRoot; |
| |
| private String path; |
| private NodeState beforeState = null; |
| |
| private NodeBuilder afterBuilder; |
| private boolean afterHoldsCug; |
| |
| private Diff(@NotNull NodeState rootBefore, @NotNull NodeBuilder rootAfter) { |
| parentDiff = null; |
| isRoot = true; |
| path = PathUtils.ROOT_PATH; |
| |
| beforeState = rootBefore; |
| |
| afterBuilder = rootAfter; |
| afterHoldsCug = CugUtil.hasCug(rootAfter); |
| } |
| |
| private Diff(@NotNull Diff parentDiff, @NotNull String name, @Nullable NodeState before, @Nullable NodeBuilder after) { |
| this.parentDiff = parentDiff; |
| isRoot = false; |
| path = PathUtils.concat(parentDiff.path, name); |
| |
| this.beforeState = before; |
| |
| afterBuilder = after; |
| afterHoldsCug = CugUtil.hasCug(after); |
| } |
| |
| @Override |
| public boolean childNodeAdded(String name, NodeState after) { |
| if (!NodeStateUtils.isHidden(name)) { |
| if (CugUtil.definesCug(name, after)) { |
| if (isRoot) { |
| PropertyState alt = afterBuilder.getProperty(HIDDEN_NESTED_CUGS); |
| if (alt != null) { |
| NodeBuilder cugNode = afterBuilder.getChildNode(REP_CUG_POLICY); |
| cugNode.setProperty(alt); |
| afterBuilder.removeProperty(HIDDEN_NESTED_CUGS); |
| afterBuilder.removeProperty(HIDDEN_TOP_CUG_CNT); |
| } |
| } else { |
| Diff diff = parentDiff; |
| while (diff != null) { |
| if (diff.afterHoldsCug) { |
| NodeBuilder cugNode = diff.afterBuilder.getChildNode(REP_CUG_POLICY); |
| addNestedCugPath(cugNode, afterBuilder.getChildNode(REP_CUG_POLICY), path); |
| break; |
| } else if (diff.isRoot) { |
| long cnt = addNestedCugPath(diff.afterBuilder, afterBuilder.getChildNode(name), path); |
| diff.afterBuilder.setProperty(HIDDEN_TOP_CUG_CNT, cnt, Type.LONG); |
| |
| } |
| diff = diff.parentDiff; |
| } |
| } |
| // no need to traverse down the CUG policy node. |
| } else { |
| after.compareAgainstBaseState(EMPTY_NODE, new Diff(this, name, null, afterBuilder.getChildNode(name))); |
| } |
| } |
| return true; |
| } |
| |
| @Override |
| public boolean childNodeChanged(String name, NodeState before, NodeState after) { |
| if (!NodeStateUtils.isHidden(name)) { |
| after.compareAgainstBaseState(before, new Diff(this, name, before, afterBuilder.getChildNode(name))); |
| } |
| return true; |
| } |
| |
| @Override |
| public boolean childNodeDeleted(String name, NodeState before) { |
| if (!NodeStateUtils.isHidden(name)) { |
| if (CugUtil.definesCug(name, before)) { |
| deletedCUGs.add(path); |
| // reconnect information about nested CUGs at a parent if |
| // only the CUG got removed but the whole subtree including CUGs |
| // are still present. |
| Set<String> reconnect = Sets.newHashSet(); |
| if (afterBuilder != null) { |
| for (String nestedCug : before.getStrings(HIDDEN_NESTED_CUGS)) { |
| if (!deletedCUGs.contains(nestedCug)) { |
| String relPath = PathUtils.relativize(path, nestedCug); |
| NodeState ns = NodeStateUtils.getNode(afterBuilder.getNodeState(), relPath); |
| if (CugUtil.hasCug(ns)) { |
| reconnect.add(nestedCug); |
| } |
| } |
| } |
| } |
| if (isRoot) { |
| if (!Iterables.isEmpty(reconnect)) { |
| afterBuilder.setProperty(HIDDEN_NESTED_CUGS, reconnect, Type.STRINGS); |
| afterBuilder.setProperty(HIDDEN_TOP_CUG_CNT, reconnect.size()); |
| } |
| } else { |
| Diff diff = parentDiff; |
| while (diff != null) { |
| if (diff.afterHoldsCug) { |
| // found an existing parent CUG |
| NodeBuilder cugNode = diff.afterBuilder.getChildNode(REP_CUG_POLICY); |
| if (removeNestedCugPath(cugNode, path, reconnect) > -1) { |
| break; |
| } |
| } |
| if (CugUtil.hasCug(diff.beforeState)) { |
| // parent CUG got removed -> no removal/reconnect required if current path is listed. |
| NodeState cugNode = diff.beforeState.getChildNode(REP_CUG_POLICY); |
| PropertyState ps = cugNode.getProperty(HIDDEN_NESTED_CUGS); |
| if (ps != null && Iterables.contains(ps.getValue(Type.STRINGS), path)) { |
| log.debug("Nested cug property containing " + path + " has also been removed; no reconnect required."); |
| break; |
| } |
| } |
| if (diff.isRoot) { |
| long cnt = removeNestedCugPath(diff.afterBuilder, path, reconnect); |
| if (cnt < 0) { |
| log.warn("Failed to updated nested CUG info for path '" + path + "'."); |
| } else if (cnt == 0) { |
| diff.afterBuilder.removeProperty(HIDDEN_TOP_CUG_CNT); |
| } else { |
| diff.afterBuilder.setProperty(HIDDEN_TOP_CUG_CNT, cnt, Type.LONG); |
| } |
| } |
| diff = diff.parentDiff; |
| } |
| } |
| // no need to traverse down the CUG policy node |
| } else { |
| EMPTY_NODE.compareAgainstBaseState(before, new Diff(this, name, before, null)); |
| } |
| } |
| return true; |
| } |
| } |
| } |