Package org.apache.jackrabbit.oak.security.user

Source Code of org.apache.jackrabbit.oak.security.user.MembershipProvider$MemberReferenceIterator

/*
* 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.user;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import javax.annotation.Nonnull;
import javax.jcr.RepositoryException;

import com.google.common.collect.Iterators;
import org.apache.jackrabbit.oak.api.PropertyState;
import org.apache.jackrabbit.oak.api.Root;
import org.apache.jackrabbit.oak.api.Tree;
import org.apache.jackrabbit.oak.api.Type;
import org.apache.jackrabbit.oak.spi.query.PropertyValues;
import org.apache.jackrabbit.oak.spi.security.ConfigurationParameters;
import org.apache.jackrabbit.oak.spi.security.user.AuthorizableType;
import org.apache.jackrabbit.oak.spi.security.user.util.UserUtil;
import org.apache.jackrabbit.oak.util.AbstractLazyIterator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* {@code MembershipProvider} implementation storing group membership information
* with the {@code Tree} associated with a given {@link org.apache.jackrabbit.api.security.user.Group}.
*
* As of Oak the {@code MembershipProvider} automatically chooses an appropriate storage structure
* depending on the number of group members. If the number of members is low they are stored as
* {@link javax.jcr.PropertyType#WEAKREFERENCE} in the {@link #REP_MEMBERS} multi value property. This is similar to
* Jackrabbit 2.x.
*
* If the number of members is high the {@code MembershipProvider} will create an intermediate node list to reduce the
* size of the multi value properties below a {@link #REP_MEMBERS_LIST} node. The provider will maintain a number of
* sub nodes of type {@link #NT_REP_MEMBER_REFERENCES} that again store the member references in a {@link #REP_MEMBERS}
* property.
*
* Note that the writing of the members is done in {@link MembershipWriter} so that the logic can be re-used by the
* migration code.
*
* The current implementation uses a fixed threshold value of {@link #getMembershipSizeThreshold()} before creating
* {@link #NT_REP_MEMBER_REFERENCES} sub nodes.
*
* Example Group with few members (irrelevant properties excluded):
* <xmp>
     {
         "jcr:primaryType": "rep:Group",
         "rep:principalName": "contributor",
         "rep:members": [
             "429bbd5b-46a6-3c3d-808b-5fd4219d5c4d",
             "ca58c408-fe06-357e-953c-2d23ffe1e096",
             "3ebb1c04-76dd-317e-a9ee-5164182bc390",
             "d3c827d3-4db2-30cc-9c41-0ed8117dbaff",
             "f5777a0b-a933-3b4d-9405-613d8bc39cc7",
             "fdd1547a-b19a-3154-90da-1eae8c2c3504",
             "65c3084e-abfc-3719-8223-72c6cb9a3d6f"
         ]
     }
* </xmp>
*
* Example Group with many members (irrelevant properties excluded):
* <xmp>
     {
         "jcr:primaryType": "rep:Group",
         "rep:principalName": "employees",
         "rep:membersList": {
             "jcr:primaryType": "rep:MemberReferencesList",
             "0": {
                 "jcr:primaryType": "rep:MemberReferences",
                 "rep:members": [
                     "429bbd5b-46a6-3c3d-808b-5fd4219d5c4d",
                     "ca58c408-fe06-357e-953c-2d23ffe1e096",
                     ...
                 ]
             },
             ...
             "341": {
                 "jcr:primaryType": "rep:MemberReferences",
                 "rep:members": [
                     "fdd1547a-b19a-3154-90da-1eae8c2c3504",
                     "65c3084e-abfc-3719-8223-72c6cb9a3d6f",
                     ...
                 ]
             }
         }
     }
* </xmp>
*/
class MembershipProvider extends AuthorizableBaseProvider {

    private static final Logger log = LoggerFactory.getLogger(MembershipProvider.class);

    private final MembershipWriter writer = new MembershipWriter();

    /**
     * Creates a new membership provider
     * @param root the current root
     * @param config the security configuration
     */
    MembershipProvider(Root root, ConfigurationParameters config) {
        super(root, config);
    }

    /**
     * Returns the size of the membership property threshold. This is currently only useful for testing.
     * @return the size of the membership property threshold.
     */
    int getMembershipSizeThreshold() {
        return writer.getMembershipSizeThreshold();
    }

    /**
     * Sets the size of the membership property threshold. This is currently only useful for testing.
     * @param membershipSizeThreshold the size of the membership property threshold
     */
    void setMembershipSizeThreshold(int membershipSizeThreshold) {
        writer.setMembershipSizeThreshold(membershipSizeThreshold);
    }

    /**
     * Returns an iterator over all membership paths of the given authorizable.
     *
     * @param authorizableTree the authorizable tree
     * @param includeInherited {@code true} to include inherited memberships
     * @return an iterator over all membership paths.
     */
    @Nonnull
    Iterator<String> getMembership(Tree authorizableTree, final boolean includeInherited) {
        return getMembership(authorizableTree, includeInherited, new HashSet<String>());
    }

    /**
     * Returns an iterator over all membership paths of the given authorizable.
     *
     * @param authorizableTree the authorizable tree
     * @param includeInherited {@code true} to include inherited memberships
     * @param processedPaths helper set that contains the processed paths
     * @return an iterator over all membership paths.
     */
    @Nonnull
    private Iterator<String> getMembership(Tree authorizableTree, final boolean includeInherited,
                                           final Set<String> processedPaths) {
        final Iterable<String> refPaths = identifierManager.getReferences(
                true, authorizableTree, REP_MEMBERS, NT_REP_MEMBER_REFERENCES
        );

        return new AbstractLazyIterator<String>() {

            private final Iterator<String> references = refPaths.iterator();

            private Iterator<String> parent;

            @Override
            protected String getNext() {
                String next = null;
                while (next == null) {
                    if (parent != null) {
                        // if we have a parent iterator, process it first
                        if (parent.hasNext()) {
                            next = parent.next();
                        } else {
                            parent = null;
                        }
                    } else if (!references.hasNext()) {
                        // if not, check if we have more references to process and abort if not
                        break;
                    } else {
                        // get the next rep:members property path
                        String propPath = references.next();
                        int index = propPath.indexOf('/' + REP_MEMBERS_LIST);
                        if (index < 0) {
                            index = propPath.indexOf('/' + REP_MEMBERS);
                        }
                        if (index > 0) {
                            String groupPath = propPath.substring(0, index);
                            if (processedPaths.add(groupPath)) {
                                // we didn't see this path before, so continue
                                next = groupPath;
                                if (includeInherited) {
                                    // inject a parent iterator of the inherited memberships is needed
                                    Tree group = getByPath(groupPath);
                                    if (UserUtil.isType(group, AuthorizableType.GROUP)) {
                                        parent = getMembership(group, true, processedPaths);
                                    }
                                }
                            }
                        } else {
                            log.debug("Not a membership reference property " + propPath);
                        }
                    }
                }
                return next;
            }
        };
    }

    /**
     * Returns an iterator over all member paths of the given group.
     *
     * @param groupTree the group tree
     * @param authorizableType type of authorizables to filter.
     * @param includeInherited {@code true} to include inherited members
     * @return an iterator over all member paths
     */
    @Nonnull
    Iterator<String> getMembers(@Nonnull Tree groupTree, @Nonnull AuthorizableType authorizableType, boolean includeInherited) {
        return getMembers(groupTree, authorizableType, includeInherited, new HashSet<String>());
    }

    /**
     * Returns an iterator over all member paths of the given group.
     *
     * @param groupTree the group tree
     * @param authorizableType type of authorizables to filter.
     * @param includeInherited {@code true} to include inherited members
     * @param processedRefs helper set that contains the references that are already processed.
     * @return an iterator over all member paths
     */
    @Nonnull
    private Iterator<String> getMembers(@Nonnull final Tree groupTree, @Nonnull final AuthorizableType authorizableType,
                                final boolean includeInherited, @Nonnull final Set<String> processedRefs) {

        return new AbstractLazyIterator<String>() {

            private MemberReferenceIterator references = new MemberReferenceIterator(groupTree, processedRefs);

            private Iterator<String> parent;

            @Override
            protected String getNext() {
                String next = null;
                while (next == null) {
                    // process parent iterators first
                    if (parent != null) {
                        if (parent.hasNext()) {
                            next = parent.next();
                        } else {
                            parent = null;
                        }
                    } else if (!references.hasNext()) {
                        // if there are no more values left, reset the iterator
                        break;
                    } else {
                        String value = references.next();
                        next = identifierManager.getPath(PropertyValues.newWeakReference(value));

                        // filter by authorizable type, and/or get inherited members
                        if (next != null && (includeInherited || authorizableType != AuthorizableType.AUTHORIZABLE)) {
                            Tree auth = getByPath(next);
                            AuthorizableType type = UserUtil.getType(auth);

                            if (includeInherited && type == AuthorizableType.GROUP) {
                                parent = getMembers(auth, authorizableType, true, processedRefs);
                            }
                            if (authorizableType != AuthorizableType.AUTHORIZABLE && type != authorizableType) {
                                next = null;
                            }
                        }
                    }
                }
                return next;
            }
        };
    }

    /**
     * Returns {@code true} if the given {@code groupTree} contains a member with the given {@code authorizableTree}
     *
     * @param groupTree  The new member to be tested for cyclic membership.
     * @param authorizableTree The authorizable to check
     * @param includeInherited {@code true} to also check inherited members
     *
     * @return true if the group has given member.
     */
    boolean isMember(Tree groupTree, Tree authorizableTree, boolean includeInherited) {
        return isMember(groupTree, getContentID(authorizableTree), includeInherited);
    }

    /**
     * Returns {@code true} if the given {@code groupTree} contains a member with the given {@code contentId}
     *
     * @param groupTree  The new member to be tested for cyclic membership.
     * @param contentId The content ID of the group.
     * @param includeInherited {@code true} to also check inherited members
     *
     * @return true if the group has given member.
     */
    boolean isMember(Tree groupTree, String contentId, boolean includeInherited) {
        if (includeInherited) {
            Set<String> refs = new HashSet<String>();
            for (Iterator<String> it = getMembers(groupTree, AuthorizableType.AUTHORIZABLE, includeInherited, refs); it.hasNext();) {
                it.next();
                if (refs.contains(contentId)) {
                    return true;
                }
            }
        } else {
            MemberReferenceIterator refs = new MemberReferenceIterator(groupTree, new HashSet<String>());
            while (refs.hasNext()) {
                if (contentId.equals(refs.next())) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * Adds a new member to the given {@code groupTree}.
     * @param groupTree the group to add the member to
     * @param newMemberTree the tree of the new member
     * @return {@code true} if the member was added
     * @throws RepositoryException if an error occurs
     */
    boolean addMember(Tree groupTree, Tree newMemberTree) throws RepositoryException {
        return writer.addMember(groupTree, getContentID(newMemberTree));
    }

    /**
     * Adds a new member to the given {@code groupTree}.
     * @param groupTree the group to add the member to
     * @param memberContentId the id of the new member
     * @return {@code true} if the member was added
     * @throws RepositoryException if an error occurs
     */
    boolean addMember(Tree groupTree, String memberContentId) throws RepositoryException {
        return writer.addMember(groupTree, memberContentId);
    }

    /**
     * Removes the member from the given group.
     *
     * @param groupTree group to remove the member from
     * @param memberTree member to remove
     * @return {@code true} if the member was removed.
     */
    boolean removeMember(Tree groupTree, Tree memberTree) {
        if (writer.removeMember(groupTree, getContentID(memberTree))) {
            return true;
        } else {
            log.debug("Authorizable {} was not member of {}", memberTree.getName(), groupTree.getName());
            return false;
        }
    }

    /**
     * Iterator that provides member references based on the rep:members properties of a underlying tree iterator.
     */
    private class MemberReferenceIterator extends AbstractLazyIterator<String> {

        private final Set<String> processedRefs;

        private final Iterator<Tree> trees;

        private Iterator<String> propertyValues;

        private MemberReferenceIterator(@Nonnull Tree groupTree, @Nonnull Set<String> processedRefs) {
            this.processedRefs = processedRefs;
            this.trees = Iterators.concat(
                    Iterators.singletonIterator(groupTree),
                    groupTree.getChild(REP_MEMBERS_LIST).getChildren().iterator()
            );
        }

        @Override
        protected String getNext() {
            String next = null;
            while (next == null) {

                if (propertyValues == null) {
                    // check if there are more trees that can provide a rep:members property
                    if (!trees.hasNext()) {
                        // if not, we're done
                        break;
                    }
                    PropertyState property = trees.next().getProperty(REP_MEMBERS);
                    if (property != null) {
                        propertyValues = property.getValue(Type.STRINGS).iterator();
                    }
                } else if (!propertyValues.hasNext()) {
                    // if there are no more values left, reset the iterator
                    propertyValues = null;
                } else {
                    String value = propertyValues.next();
                    if (processedRefs.add(value)) {
                        next = value;
                    }
                }
            }
            return next;
        }
    }
}
TOP

Related Classes of org.apache.jackrabbit.oak.security.user.MembershipProvider$MemberReferenceIterator

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.