Package org.apache.jackrabbit.oak.security.authorization.permission

Source Code of org.apache.jackrabbit.oak.security.authorization.permission.CompiledPermissionImpl$EntryIterator

/*
* 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 static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;

import java.security.Principal;
import java.security.acl.Group;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import com.google.common.base.Function;
import com.google.common.base.Objects;
import com.google.common.base.Predicate;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import org.apache.jackrabbit.oak.api.PropertyState;
import org.apache.jackrabbit.oak.api.Tree;
import org.apache.jackrabbit.oak.api.Type;
import org.apache.jackrabbit.oak.commons.PathUtils;
import org.apache.jackrabbit.oak.core.ImmutableTree;
import org.apache.jackrabbit.oak.security.privilege.PrivilegeBits;
import org.apache.jackrabbit.oak.security.privilege.PrivilegeBitsProvider;
import org.apache.jackrabbit.oak.spi.security.authorization.permission.Permissions;
import org.apache.jackrabbit.oak.spi.security.authorization.permission.ReadStatus;
import org.apache.jackrabbit.oak.spi.security.authorization.restriction.RestrictionPattern;
import org.apache.jackrabbit.oak.spi.security.authorization.restriction.RestrictionProvider;
import org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeConstants;
import org.apache.jackrabbit.oak.util.TreeUtil;
import org.apache.jackrabbit.util.Text;

/**
* TODO: WIP
*/
class CompiledPermissionImpl implements CompiledPermissions, PermissionConstants {

    private final Set<Principal> principals;
    private final RestrictionProvider restrictionProvider;
    private final Map<String, ImmutableTree> trees;

    // TODO: merge readPaths with readStatus structure
    private final Set<String> readPaths;

    private PrivilegeBitsProvider bitsProvider;
    private Map<Key, PermissionEntry> repoEntries;
    private Map<Key, PermissionEntry> userEntries;
    private Map<Key, PermissionEntry> groupEntries;

    CompiledPermissionImpl(@Nonnull Set<Principal> principals,
                           @Nonnull ImmutableTree permissionsTree,
                           @Nonnull PrivilegeBitsProvider bitsProvider,
                           @Nonnull RestrictionProvider restrictionProvider,
                           @Nonnull Set<String> readPaths) {
        checkArgument(!principals.isEmpty());
        this.principals = principals;
        this.restrictionProvider = restrictionProvider;
        this.bitsProvider = bitsProvider;
        this.readPaths = readPaths;
        this.trees = new HashMap<String, ImmutableTree>(principals.size());
        buildEntries(permissionsTree);
    }

    void refresh(@Nonnull ImmutableTree permissionsTree,
                 @Nonnull PrivilegeBitsProvider bitsProvider) {
        this.bitsProvider = bitsProvider;
        boolean refresh = false;
        // test if a permission has been added for those principals that didn't have one before
        if (trees.size() != principals.size()) {
            for (Principal principal : principals) {
                if (!trees.containsKey(principal.getName()) && getPrincipalRoot(permissionsTree, principal).exists()) {
                    refresh = true;
                    break;
                }
            }
        }
        // test if any of the trees has been modified in the mean time
        if (!refresh) {
            for (Map.Entry<String, ImmutableTree> entry : trees.entrySet()) {
                ImmutableTree t = entry.getValue();
                ImmutableTree t2 = permissionsTree.getChild(t.getName());
                if (t2.exists() && !t.equals(t2)) {
                    refresh = true;
                    break;
                }
            }
        }

        if (refresh) {
            buildEntries(permissionsTree);
        }
    }

    //------------------------------------------------< CompiledPermissions >---
    @Override
    public ReadStatus getReadStatus(@Nonnull Tree tree, @Nullable PropertyState property) {
        // TODO merge with readstatus
        if (isReadablePath(tree, null)) {
            return ReadStatus.ALLOW_ALL_REGULAR;
        }
        long permission = (property == null) ? Permissions.READ_NODE : Permissions.READ_PROPERTY;
        Iterator<PermissionEntry> it = getEntryIterator(tree, property);
        while (it.hasNext()) {
            PermissionEntry entry = it.next();
            if (entry.readStatus != null) {
                return entry.readStatus;
            } else if (entry.privilegeBits.includesRead(permission)) {
                return (entry.isAllow) ? ReadStatus.ALLOW_THIS : ReadStatus.DENY_THIS;
            }
        }
        return ReadStatus.DENY_THIS;
    }

    @Override
    public boolean isGranted(long permissions) {
        return hasPermissions(repoEntries.values().iterator(), permissions, null, null);
    }

    @Override
    public boolean isGranted(@Nonnull Tree tree, @Nullable PropertyState property, long permissions) {
        return hasPermissions(getEntryIterator(tree, property), permissions, tree, null);
    }

    @Override
    public boolean isGranted(@Nonnull String path, long permissions) {
        return hasPermissions(getEntryIterator(path), permissions, null, path);
    }

    @Override
    public Set<String> getPrivileges(@Nullable Tree tree) {
        return bitsProvider.getPrivilegeNames(getPrivilegeBits(tree));
    }

    @Override
    public boolean hasPrivileges(@Nullable Tree tree, String... privilegeNames) {
        return getPrivilegeBits(tree).includes(bitsProvider.getBits(privilegeNames));
    }

    //------------------------------------------------------------< private >---
    @Nonnull
    private static ImmutableTree getPrincipalRoot(ImmutableTree permissionsTree, Principal principal) {
        return permissionsTree.getChild(Text.escapeIllegalJcrChars(principal.getName()));
    }

    private void buildEntries(@Nonnull ImmutableTree permissionsTree) {
        if (!permissionsTree.exists()) {
            repoEntries = Collections.emptyMap();
            userEntries = Collections.emptyMap();
            groupEntries = Collections.emptyMap();
        } else {
            EntriesBuilder builder = new EntriesBuilder();
            for (Principal principal : principals) {
                ImmutableTree t = getPrincipalRoot(permissionsTree, principal);
                if (t.exists()) {
                    trees.put(principal.getName(), t);
                    builder.addEntries(principal, t, restrictionProvider);
                }
            }
            repoEntries = builder.getRepoEntries();
            userEntries = builder.getUserEntries();
            groupEntries = builder.getGroupEntries();
            buildReadStatus(Iterables.<PermissionEntry>concat(userEntries.values(), groupEntries.values()));
        }
    }

    private static void buildReadStatus(Iterable<PermissionEntry> permissionEntries) {
        // TODO
    }

    private boolean hasPermissions(@Nonnull Iterator<PermissionEntry> entries,
                                   long permissions, @Nullable Tree tree, @Nullable String path) {
        // calculate readable paths if the given permissions includes any read permission.
        boolean isReadable = Permissions.diff(Permissions.READ, permissions) != Permissions.READ && isReadablePath(tree, path);
        if (!entries.hasNext() && !isReadable) {
            return false;
        }

        boolean respectParent = (tree != null || path != null) &&
                (Permissions.includes(permissions, Permissions.ADD_NODE) ||
                Permissions.includes(permissions, Permissions.REMOVE_NODE) ||
                Permissions.includes(permissions, Permissions.MODIFY_CHILD_NODE_COLLECTION));

        long allows = (isReadable) ? Permissions.READ : Permissions.NO_PERMISSION;
        long denies = Permissions.NO_PERMISSION;

        PrivilegeBits allowBits = PrivilegeBits.getInstance();
        if (isReadable) {
            allowBits.add(bitsProvider.getBits(PrivilegeConstants.JCR_READ));
        }
        PrivilegeBits denyBits = PrivilegeBits.getInstance();
        PrivilegeBits parentAllowBits;
        PrivilegeBits parentDenyBits;

        Tree parent;
        String parentPath;

        if (respectParent) {
            parentAllowBits = PrivilegeBits.getInstance();
            parentDenyBits = PrivilegeBits.getInstance();
            parent = (tree != null) ? getParentOrNull(tree) : null;
            parentPath = (path != null) ? Strings.emptyToNull(Text.getRelativeParent(path, 1)) : null;
        } else {
            parentAllowBits = PrivilegeBits.EMPTY;
            parentDenyBits = PrivilegeBits.EMPTY;
            parent = null;
            parentPath = null;
        }

        while (entries.hasNext()) {
            PermissionEntry entry = entries.next();
            if (respectParent && (parent != null || parentPath != null)) {
                boolean matchesParent = (parent != null) ? entry.matches(parent, null) : entry.matches(parentPath);
                if (matchesParent) {
                    if (entry.isAllow) {
                        parentAllowBits.addDifference(entry.privilegeBits, parentDenyBits);
                    } else {
                        parentDenyBits.addDifference(entry.privilegeBits, parentAllowBits);
                    }
                }
            }

            if (entry.isAllow) {
                allowBits.addDifference(entry.privilegeBits, denyBits);
                long ap = PrivilegeBits.calculatePermissions(allowBits, parentAllowBits, true);
                allows |= Permissions.diff(ap, denies);
                if ((allows | ~permissions) == -1) {
                    return true;
                }
            } else {
                denyBits.addDifference(entry.privilegeBits, allowBits);
                long dp = PrivilegeBits.calculatePermissions(denyBits, parentDenyBits, false);
                denies |= Permissions.diff(dp, allows);
                if (Permissions.includes(denies, permissions)) {
                    return false;
                }
            }
        }

        return (allows | ~permissions) == -1;
    }

    private static Tree getParentOrNull(Tree tree) {
        Tree parent = tree.getParent();
        return parent.exists() ? parent : null;
    }

    private PrivilegeBits getPrivilegeBits(@Nullable Tree tree) {
        Iterator<PermissionEntry> entries = (tree == null) ?
                repoEntries.values().iterator() :
                getEntryIterator(tree, null);

        PrivilegeBits allowBits = PrivilegeBits.getInstance();
        PrivilegeBits denyBits = PrivilegeBits.getInstance();

        while (entries.hasNext()) {
            PermissionEntry entry = entries.next();
            if (entry.isAllow) {
                allowBits.addDifference(entry.privilegeBits, denyBits);
            } else {
                denyBits.addDifference(entry.privilegeBits, allowBits);
            }
        }

        // special handling for paths that are always readable
        if (isReadablePath(tree, null)) {
            allowBits.add(bitsProvider.getBits(PrivilegeConstants.JCR_READ));
        }
        return allowBits;
    }

    private Iterator<PermissionEntry> getEntryIterator(@Nonnull Tree tree, @Nullable PropertyState property) {
        return Iterators.concat(new EntryIterator(userEntries, tree, property), new EntryIterator(groupEntries, tree, property));
    }

    private Iterator<PermissionEntry> getEntryIterator(@Nonnull String path) {
        return Iterators.concat(new EntryIterator(userEntries, path), new EntryIterator(groupEntries, path));
    }

    private boolean isReadablePath(@Nullable Tree tree, @Nullable String treePath) {
        if (!readPaths.isEmpty()) {
            String targetPath = (tree != null) ? tree.getPath() : treePath;
            if (targetPath != null) {
                for (String path : readPaths) {
                    if (Text.isDescendantOrEqual(path, targetPath)) {
                        return true;
                    }
                }
            }
        }
        return false;
    }

    private static final class Key implements Comparable<Key> {

        private final String path;
        private final int depth;
        private final long index;

        private Key(Tree tree) {
            path = Strings.emptyToNull(TreeUtil.getString(tree, REP_ACCESS_CONTROLLED_PATH));
            depth = (path == null) ? 0 : PathUtils.getDepth(path);
            index = checkNotNull(tree.getProperty(REP_INDEX).getValue(Type.LONG)).longValue();
        }

        @Override
        public int compareTo(Key key) {
            checkNotNull(key);
            if (Objects.equal(path, key.path)) {
                if (index == key.index) {
                    return 0;
                } else if (index <                                                                                                                                                                                 key.index) {
                    return 1;
                } else {
                    return -1;
                }
            } else {
                if (depth == key.depth) {
                    return path.compareTo(key.path);
                } else {
                    return (depth < key.depth) ? 1 : -1;
                }
            }
        }

        @Override
        public int hashCode() {
            return Objects.hashCode(path, index);
        }

        @Override
        public boolean equals(Object o) {
            if (o == this) {
                return true;
            }
            if (o instanceof Key) {
                Key other = (Key) o;
                return index == other.index && Objects.equal(path, other.path);
            }
            return false;
        }
    }

    private static final class PermissionEntry {

        private final boolean isAllow;
        private final PrivilegeBits privilegeBits;
        private final String path;
        private final RestrictionPattern restriction;

        private ReadStatus readStatus;
        private PermissionEntry next;

        private PermissionEntry(String accessControlledPath, Tree entryTree, RestrictionProvider restrictionsProvider) {
            isAllow = (PREFIX_ALLOW == entryTree.getName().charAt(0));
            privilegeBits = PrivilegeBits.getInstance(entryTree.getProperty(REP_PRIVILEGE_BITS));
            path = accessControlledPath;
            restriction = restrictionsProvider.getPattern(accessControlledPath, entryTree);
        }

        private boolean matches(@Nonnull Tree tree, @Nullable PropertyState property) {
            String treePath = tree.getPath();
            if (Text.isDescendantOrEqual(path, treePath)) {
                return restriction.matches(tree, property);
            } else {
                return false;
            }
        }

        private boolean matches(@Nonnull String treePath) {
            if (Text.isDescendantOrEqual(path, treePath)) {
                return restriction.matches(treePath);
            } else {
                return false;
            }
        }
    }

    /**
     * Collects permission entries for different principals and asserts they are
     * in the correct order for proper and efficient evaluation.
     */
    private static final class EntriesBuilder {

        private ImmutableSortedMap.Builder<Key, PermissionEntry> repoEntries = ImmutableSortedMap.naturalOrder();
        private ImmutableSortedMap.Builder<Key, PermissionEntry> userEntries = ImmutableSortedMap.naturalOrder();
        private ImmutableSortedMap.Builder<Key, PermissionEntry> groupEntries = ImmutableSortedMap.naturalOrder();

        private void addEntries(@Nonnull Principal principal,
                                @Nonnull Tree principalRoot,
                                @Nonnull RestrictionProvider restrictionProvider) {
            for (Tree entryTree : principalRoot.getChildren()) {
                Key key = new Key(entryTree);
                PermissionEntry entry = new PermissionEntry(key.path, entryTree, restrictionProvider);
                if (!entry.privilegeBits.isEmpty()) {
                    if (key.path == null) {
                        repoEntries.put(key, entry);
                    } else if (principal instanceof Group) {
                        groupEntries.put(key, entry);
                    } else {
                        userEntries.put(key, entry);
                    }
                }
            }
        }

        private Map<Key, PermissionEntry> getRepoEntries() {
            return repoEntries.build();
        }

        private Map<Key, PermissionEntry> getUserEntries() {
            return getEntries(userEntries);
        }

        private Map<Key, PermissionEntry> getGroupEntries() {
            return getEntries(groupEntries);
        }

        private static Map<Key, PermissionEntry> getEntries(ImmutableSortedMap.Builder builder) {
            Map<Key, PermissionEntry> entryMap = builder.build();
            Set<Map.Entry<Key, PermissionEntry>> toProcess = new HashSet<Map.Entry<Key, PermissionEntry>>();
            for (Map.Entry<Key, PermissionEntry> entry : entryMap.entrySet()) {
                Key currentKey = entry.getKey();
                Iterator<Map.Entry<Key,PermissionEntry>> it = toProcess.iterator();
                while (it.hasNext()) {
                    Map.Entry<Key,PermissionEntry> before = it.next();
                    Key beforeKey = before.getKey();
                    if (Text.isDescendantOrEqual(currentKey.path, beforeKey.path)) {
                        before.getValue().next = entry.getValue();
                        it.remove();
                    }
                }
                toProcess.add(entry);
            }
            return entryMap;
        }
    }

    private static class EntryIterator implements Iterator<PermissionEntry> {

        private final Iterator<PermissionEntry> it;

        private PermissionEntry latestEntry;

        private EntryIterator(@Nonnull Map<Key, PermissionEntry> entries,
                              @Nonnull final Tree tree, @Nullable final PropertyState property) {
            it = Iterators.transform(
                    Iterators.filter(entries.entrySet().iterator(), new EntryPredicate(tree, property)),
                    new EntryFunction());
        }

        private EntryIterator(@Nonnull Map<Key, PermissionEntry> entries,
                              @Nonnull final String path) {
            it = Iterators.transform(
                    Iterators.filter(entries.entrySet().iterator(), new EntryPredicate(path)),
                    new EntryFunction());
        }

        @Override
        public boolean hasNext() {
            return it.hasNext();
        }

        @Override
        public PermissionEntry next() {
            if (latestEntry != null && latestEntry.next != null) {
                // skip entries on the iterator
                while (it.hasNext()) {
                    if (it.next() == latestEntry.next) {
                        break;
                    }
                }
                latestEntry = latestEntry.next;
            } else {
                latestEntry = it.next();
            }
            return latestEntry;
        }

        @Override
        public void remove() {
            throw new UnsupportedOperationException();
        }
    }

    private static class EntryPredicate implements Predicate<Map.Entry<Key, PermissionEntry>> {

        private final Tree tree;
        private final PropertyState property;
        private final String path;
        private final int depth;

        private EntryPredicate(@Nonnull Tree tree, @Nullable PropertyState property) {
            this.tree = tree;
            this.property = property;
            this.path = tree.getPath();
            this.depth = PathUtils.getDepth(path);
        }

        private EntryPredicate(@Nonnull String path) {
            this.tree = null;
            this.property = null;
            this.path = path;
            this.depth = PathUtils.getDepth(path);
        }

        @Override
        public boolean apply(@Nullable Map.Entry<Key, PermissionEntry> entry) {
            if (entry == null) {
                return false;
            }
            if (depth < entry.getKey().depth) {
                return false;
            } else if (tree != null) {
                return entry.getValue().matches(tree, property);
            } else {
                return entry.getValue().matches(path);
            }
        }
    }

    private static class EntryFunction implements Function<Map.Entry<Key, PermissionEntry>, PermissionEntry> {
        @Override
        public PermissionEntry apply(Map.Entry<Key, PermissionEntry> input) {
            return input.getValue();
        }
    }
}
TOP

Related Classes of org.apache.jackrabbit.oak.security.authorization.permission.CompiledPermissionImpl$EntryIterator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.