All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.jackrabbit.oak.security.authorization.permission.PermissionHook Maven / Gradle / Ivy

/*
 * 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.security.authorization.permission;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.Nonnull;

import org.apache.jackrabbit.oak.api.CommitFailedException;
import org.apache.jackrabbit.oak.api.Type;
import org.apache.jackrabbit.oak.core.ImmutableRoot;
import org.apache.jackrabbit.oak.plugins.nodetype.TypePredicate;
import org.apache.jackrabbit.oak.plugins.tree.ImmutableTree;
import org.apache.jackrabbit.oak.spi.commit.CommitInfo;
import org.apache.jackrabbit.oak.spi.commit.PostValidationHook;
import org.apache.jackrabbit.oak.spi.security.authorization.accesscontrol.AccessControlConstants;
import org.apache.jackrabbit.oak.spi.security.authorization.permission.PermissionConstants;
import org.apache.jackrabbit.oak.spi.security.authorization.restriction.Restriction;
import org.apache.jackrabbit.oak.spi.security.authorization.restriction.RestrictionProvider;
import org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeBits;
import org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeBitsProvider;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Objects;
import com.google.common.base.Strings;

import static com.google.common.collect.Iterables.addAll;
import static com.google.common.collect.Sets.newLinkedHashSet;
import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE;
import static org.apache.jackrabbit.JcrConstants.JCR_SYSTEM;
import static org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.EMPTY_NODE;
import static org.apache.jackrabbit.oak.plugins.tree.TreeConstants.OAK_CHILD_ORDER;

/**
 * {@code CommitHook} implementation that processes any modification made to
 * access control content and updates persisted permission store associated
 * with access control related data stored in the repository.
 * 

* The access control entries are grouped by principal and stored below the store root based on the hash value of the * access controllable path. hash collisions are handled by adding subnodes accordingly. *

 *   /jcr:system/rep:permissionStore/workspace-name
 *      /everyone
 *          /552423  [rep:PermissionStore]
 *              /0     [rep:Permissions]
 *              /1     [rep:Permissions]
 *              /c0     [rep:PermissionStore]
 *                  /0      [rep:Permissions]
 *                  /1      [rep:Permissions]
 *                  /2      [rep:Permissions]
 *              /c1     [rep:PermissionStore]
 *                  /0      [rep:Permissions]
 *                  /1      [rep:Permissions]
 *                  /2      [rep:Permissions]
 * 
*/ public class PermissionHook implements PostValidationHook, AccessControlConstants, PermissionConstants { private static final Logger log = LoggerFactory.getLogger(PermissionHook.class); private final RestrictionProvider restrictionProvider; private final String workspaceName; private NodeBuilder permissionRoot; private PrivilegeBitsProvider bitsProvider; private TypePredicate isACL; private TypePredicate isACE; private TypePredicate isGrantACE; private Map modified = new HashMap(); private Map deleted = new HashMap(); public PermissionHook(String workspaceName, RestrictionProvider restrictionProvider) { this.workspaceName = workspaceName; this.restrictionProvider = restrictionProvider; } @Nonnull @Override public NodeState processCommit( NodeState before, NodeState after, CommitInfo info) throws CommitFailedException { NodeBuilder rootAfter = after.builder(); permissionRoot = getPermissionRoot(rootAfter); bitsProvider = new PrivilegeBitsProvider(new ImmutableRoot(after)); isACL = new TypePredicate(after, NT_REP_ACL); isACE = new TypePredicate(after, NT_REP_ACE); isGrantACE = new TypePredicate(after, NT_REP_GRANT_ACE); Diff diff = new Diff(""); after.compareAgainstBaseState(before, diff); apply(); return rootAfter.getNodeState(); } private void apply() { for (Map.Entry entry : deleted.entrySet()) { entry.getValue().remove(); } for (Map.Entry entry : modified.entrySet()) { entry.getValue().update(); } } @Nonnull private NodeBuilder getPermissionRoot(NodeBuilder rootBuilder) { // permission root has been created during workspace initialization return rootBuilder.getChildNode(JCR_SYSTEM).getChildNode(REP_PERMISSION_STORE).getChildNode(workspaceName); } private class Diff extends DefaultNodeStateDiff { private final String parentPath; private Diff(String parentPath) { this.parentPath = parentPath; } @Override public boolean childNodeAdded(String name, NodeState after) { if (NodeStateUtils.isHidden(name)) { // ignore hidden nodes return true; } String path = parentPath + '/' + name; if (isACL.apply(after)) { Acl acl = new Acl(parentPath, name, after); modified.put(acl.accessControlledPath, acl); } else { after.compareAgainstBaseState(EMPTY_NODE, new Diff(path)); } return true; } @Override public boolean childNodeChanged(String name, NodeState before, NodeState after) { if (NodeStateUtils.isHidden(name)) { // ignore hidden nodes return true; } String path = parentPath + '/' + name; if (isACL.apply(before)) { if (isACL.apply(after)) { Acl acl = new Acl(parentPath, name, after); modified.put(acl.accessControlledPath, acl); // also consider to remove the ACL from removed entries of other principals Acl beforeAcl = new Acl(parentPath, name, before); beforeAcl.entries.keySet().removeAll(acl.entries.keySet()); if (!beforeAcl.entries.isEmpty()) { deleted.put(parentPath, beforeAcl); } } else { Acl acl = new Acl(parentPath, name, before); deleted.put(acl.accessControlledPath, acl); } } else if (isACL.apply(after)) { Acl acl = new Acl(parentPath, name, after); modified.put(acl.accessControlledPath, acl); } else { after.compareAgainstBaseState(before, new Diff(path)); } return true; } @Override public boolean childNodeDeleted(String name, NodeState before) { if (NodeStateUtils.isHidden(name)) { // ignore hidden nodes return true; } String path = parentPath + '/' + name; if (isACL.apply(before)) { Acl acl = new Acl(parentPath, name, before); deleted.put(acl.accessControlledPath, acl); } else { EMPTY_NODE.compareAgainstBaseState(before, new Diff(path)); } return true; } } private final class Acl { private final String accessControlledPath; private final String nodeName; private final Map> entries = new HashMap>(); private Acl(String aclPath, String name, @Nonnull NodeState node) { if (name.equals(REP_REPO_POLICY)) { this.accessControlledPath = ""; } else { this.accessControlledPath = aclPath.length() == 0 ? "/" : aclPath; } nodeName = PermissionUtil.getEntryName(accessControlledPath); Set orderedChildNames = newLinkedHashSet(node.getNames(OAK_CHILD_ORDER)); long n = orderedChildNames.size(); if (node.getChildNodeCount(n + 1) > n) { addAll(orderedChildNames, node.getChildNodeNames()); } int index = 0; for (String childName : orderedChildNames) { NodeState ace = node.getChildNode(childName); if (isACE.apply(ace)) { AcEntry entry = new AcEntry(ace, accessControlledPath, index); List list = entries.get(entry.principalName); if (list == null) { list = new ArrayList(); entries.put(entry.principalName, list); } list.add(entry); index++; } } } private void remove() { for (String principalName: entries.keySet()) { if (permissionRoot.hasChildNode(principalName)) { NodeBuilder principalRoot = permissionRoot.getChildNode(principalName); // find the ACL node that for this path and principal NodeBuilder parent = principalRoot.getChildNode(nodeName); if (!parent.exists()) { continue; } // check if the node is the correct one if (PermissionUtil.checkACLPath(parent, accessControlledPath)) { // remove and reconnect child nodes NodeBuilder newParent = null; for (String childName : parent.getChildNodeNames()) { if (childName.charAt(0) != 'c') { continue; } NodeBuilder child = parent.getChildNode(childName); if (newParent == null) { newParent = child; } else { newParent.setChildNode(childName, child.getNodeState()); child.remove(); } } parent.remove(); if (newParent != null) { principalRoot.setChildNode(nodeName, newParent.getNodeState()); } } else { // check if any of the child nodes match for (String childName : parent.getChildNodeNames()) { if (childName.charAt(0) != 'c') { continue; } NodeBuilder child = parent.getChildNode(childName); if (PermissionUtil.checkACLPath(child, accessControlledPath)) { child.remove(); } } } } else { log.error("Unable to remove permission entry {}: Principal root missing.", this); } } } private void update() { for (String principalName: entries.keySet()) { NodeBuilder principalRoot = permissionRoot.child(principalName); if (!principalRoot.hasProperty(JCR_PRIMARYTYPE)) { principalRoot.setProperty(JCR_PRIMARYTYPE, NT_REP_PERMISSION_STORE, Type.NAME); } NodeBuilder parent = principalRoot.child(nodeName); if (!parent.hasProperty(JCR_PRIMARYTYPE)) { parent.setProperty(JCR_PRIMARYTYPE, NT_REP_PERMISSION_STORE, Type.NAME); } // check if current parent already has the correct path if (parent.hasProperty(REP_ACCESS_CONTROLLED_PATH)) { if (!PermissionUtil.checkACLPath(parent, accessControlledPath)) { // hash collision, find a new child NodeBuilder child = null; int idx = 0; for (String childName : parent.getChildNodeNames()) { if (childName.charAt(0) != 'c') { continue; } child = parent.getChildNode(childName); if (PermissionUtil.checkACLPath(child, accessControlledPath)) { break; } child = null; idx++; } while (child == null) { String name = 'c' + String.valueOf(idx++); child = parent.getChildNode(name); if (child.exists()) { child = null; } else { child = parent.child(name); child.setProperty(JCR_PRIMARYTYPE, NT_REP_PERMISSION_STORE, Type.NAME); } } parent = child; parent.setProperty(REP_ACCESS_CONTROLLED_PATH, accessControlledPath); } } else { // new parent parent.setProperty(REP_ACCESS_CONTROLLED_PATH, accessControlledPath); } updateEntries(parent, entries.get(principalName)); } } private void updateEntries(NodeBuilder parent, List list) { // remove old entries for (String childName : parent.getChildNodeNames()) { if (childName.charAt(0) != 'c') { parent.getChildNode(childName).remove(); } } for (AcEntry ace: list) { PermissionEntry.write(parent, ace.isAllow, ace.index, ace.privilegeBits, ace.restrictions); } } } private final class AcEntry { private final String accessControlledPath; private final String principalName; private final PrivilegeBits privilegeBits; private final boolean isAllow; private final Set restrictions; private final int index; private int hashCode = -1; private AcEntry(@Nonnull NodeState node, @Nonnull String accessControlledPath, int index) { this.accessControlledPath = accessControlledPath; this.index = index; principalName = Text.escapeIllegalJcrChars(node.getString(REP_PRINCIPAL_NAME)); privilegeBits = bitsProvider.getBits(node.getNames(REP_PRIVILEGES)); isAllow = isGrantACE.apply(node); restrictions = restrictionProvider.readRestrictions(Strings.emptyToNull(accessControlledPath), new ImmutableTree(node)); } @Override public int hashCode() { if (hashCode == -1) { hashCode = Objects.hashCode(accessControlledPath, principalName, privilegeBits, isAllow, restrictions); } return hashCode; } @Override public boolean equals(Object o) { if (o == this) { return true; } if (o instanceof AcEntry) { AcEntry other = (AcEntry) o; return isAllow == other.isAllow && privilegeBits.equals(other.privilegeBits) && principalName.equals(other.principalName) && accessControlledPath.equals(other.accessControlledPath) && restrictions.equals(other.restrictions); } return false; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append(accessControlledPath); sb.append(';').append(principalName); sb.append(';').append(isAllow ? "allow" : "deny"); sb.append(';').append(bitsProvider.getPrivilegeNames(privilegeBits)); sb.append(';').append(restrictions); return sb.toString(); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy