UserPropertiesManagerImpl.java 13.3 KB
package com.adobe.granite.security.user.internal;

import com.adobe.granite.security.user.UserProperties;
import com.adobe.granite.security.user.UserPropertiesComposite;
import com.adobe.granite.security.user.UserPropertiesFilter;
import com.adobe.granite.security.user.UserPropertiesManager;
import com.adobe.granite.security.user.internal.AuthorizableInfo;
import com.adobe.granite.security.user.internal.UserPropertiesCompositeImpl;
import com.adobe.granite.security.user.internal.UserPropertiesImpl;
import com.adobe.granite.security.user.internal.UserPropertiesServiceImpl;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.jcr.Item;
import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.Property;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.security.AccessControlManager;
import javax.jcr.security.AccessControlPolicy;
import javax.jcr.security.Privilege;
import org.apache.jackrabbit.api.JackrabbitSession;
import org.apache.jackrabbit.api.security.JackrabbitAccessControlList;
import org.apache.jackrabbit.api.security.user.Authorizable;
import org.apache.jackrabbit.api.security.user.Group;
import org.apache.jackrabbit.api.security.user.UserManager;
import org.apache.jackrabbit.commons.JcrUtils;
import org.apache.jackrabbit.commons.jackrabbit.authorization.AccessControlUtils;
import org.apache.jackrabbit.util.Text;
import org.apache.sling.api.resource.ResourceResolver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class UserPropertiesManagerImpl
implements UserPropertiesManager {
    private static final Logger log = LoggerFactory.getLogger(UserPropertiesManagerImpl.class);
    private static final String SLING_URI = "http://sling.apache.org/jcr/sling/1.0";
    private static final String SLING_RESOURCE_TYPE = "resourceType";
    public static final String MIX_GRANITE_RANKING = "granite:Ranking";
    private final Session session;
    private final ResourceResolver resourceResolver;
    private final UserPropertiesServiceImpl service;
    private final String slingResourceType;
    private final Privilege[] readPrivileges;

    public UserPropertiesManagerImpl(Session session, ResourceResolver resourceResolver, UserPropertiesServiceImpl service) throws RepositoryException {
        this.resourceResolver = resourceResolver;
        if (session == null) {
            throw new RepositoryException("Cannot create UserPropertiesManager for 'null' session.");
        }
        this.session = session;
        this.service = service;
        this.slingResourceType = session.getNamespacePrefix("http://sling.apache.org/jcr/sling/1.0") + ":" + "resourceType";
        this.readPrivileges = AccessControlUtils.privilegesFromNames((Session)session, (String[])new String[]{"{http://www.jcp.org/jcr/1.0}read"});
    }

    @Override
    public UserProperties createUserProperties(String authorizableId, String relPath) throws RepositoryException {
        String aPath;
        Authorizable authorizable = ((JackrabbitSession)this.session).getUserManager().getAuthorizable(authorizableId);
        if (authorizable != null && this.session.nodeExists(aPath = authorizable.getPath())) {
            String resourceType;
            Node authorizableNode = this.session.getNode(aPath);
            String upNodeType = this.service.getNodeType(relPath);
            Node upNode = JcrUtils.getOrCreateByPath((Node)authorizableNode, (String)relPath, (boolean)false, (String)null, (String)upNodeType, (boolean)false);
            upNode.addMixin("{http://www.jcp.org/jcr/mix/1.0}title");
            upNode.addMixin("granite:Ranking");
            if (!upNode.isSame((Item)authorizableNode) && (resourceType = this.service.getResourceType(relPath)) != null) {
                upNode.setProperty(this.slingResourceType, resourceType);
            }
            if (UserPropertiesManagerImpl.isValidUserPropertiesPath(aPath, upNode.getPath())) {
                return this.getUserProperties(new AuthorizableInfo(authorizable), upNode);
            }
            throw new RepositoryException("Attempt to create user properties outside of scope of authorizable '" + authorizableId + "'.");
        }
        return null;
    }

    @Override
    public UserProperties getUserProperties(String authorizableId, String relPath) throws RepositoryException {
        AuthorizableInfo info = this.service.getAuthorizableInfo(authorizableId);
        Node n = info == null ? null : this.getNode(info.getPath(), relPath);
        return n == null ? null : this.getUserProperties(info, n);
    }

    @Override
    public UserProperties getUserProperties(Authorizable authorizable, String relPath) throws RepositoryException {
        Node n = this.getNode(authorizable.getPath(), relPath);
        return n == null ? null : this.getUserProperties(new AuthorizableInfo(authorizable), n);
    }

    @Override
    public UserProperties getUserProperties(Node userPropertiesNode) throws RepositoryException {
        String path = userPropertiesNode.getPath();
        AuthorizableInfo info = this.service.getAuthorizableInfoByPath(path);
        if (info != null) {
            return this.getUserProperties(info, userPropertiesNode);
        }
        String msg = "User properties node '" + path + "' is not associated with an authorizable.";
        log.debug(msg);
        throw new RepositoryException(msg);
    }

    @Override
    public UserPropertiesComposite getUserPropertiesComposite(String authorizableId, String[] relPaths) throws RepositoryException {
        if (null == authorizableId || authorizableId.length() == 0) {
            throw new IllegalArgumentException("authorizable ID must not be empty or null");
        }
        if (null == relPaths || relPaths.length == 0) {
            throw new IllegalArgumentException("relPaths must not be empty or null");
        }
        ArrayList<UserProperties> collection = new ArrayList<UserProperties>();
        for (String relPath : relPaths) {
            collection.add(this.getUserProperties(authorizableId, relPath));
        }
        return new UserPropertiesCompositeImpl(authorizableId, collection);
    }

    @Override
    public UserPropertiesComposite getUserPropertiesComposite(String authorizableId, UserPropertiesFilter filter) throws RepositoryException {
        if (null == authorizableId || authorizableId.length() == 0) {
            throw new IllegalArgumentException("authorizable ID must not be empty or null");
        }
        if (null == filter) {
            throw new IllegalArgumentException("filter must not be null");
        }
        Node authorizableNode = this.getNode(this.service.getAuthorizablePath(authorizableId), null);
        ArrayList<UserProperties> userProperties = new ArrayList<UserProperties>();
        this.traverseAndFilterNodes(authorizableNode, filter, userProperties);
        return new UserPropertiesCompositeImpl(authorizableId, userProperties);
    }

    @Nonnull
    @Override
    public UserPropertiesComposite getUserPropertiesComposite(@Nonnull String authorizableId, @Nullable String relRootPath) throws RepositoryException {
        return this.getUserPropertiesComposite(authorizableId, relRootPath, DESCENDING_RANKING_COMPARATOR);
    }

    @Nonnull
    @Override
    public UserPropertiesComposite getUserPropertiesComposite(@Nonnull String authorizableId, @Nullable String relRootPath, @Nonnull Comparator<Node> comparator) throws RepositoryException {
        String authorizablePath = this.service.getAuthorizablePath(authorizableId);
        List<Node> profiles = this.service.listUserPropertiesNodes(authorizableId, relRootPath, this.resourceResolver);
        if (profiles.isEmpty()) {
            return null;
        }
        Collections.sort(profiles, comparator);
        String[] paths = new String[profiles.size()];
        for (int i = 0; i < paths.length; ++i) {
            Node pNode = profiles.get(i);
            paths[i] = pNode.getPath().replace(authorizablePath + '/', "");
        }
        return this.getUserPropertiesComposite(authorizableId, paths);
    }

    protected Node getNode(String authorizablePath, String relPath) throws RepositoryException {
        if (authorizablePath != null && authorizablePath.length() > 0) {
            String userPropertiesPath;
            StringBuilder sb = new StringBuilder(authorizablePath);
            if (relPath != null) {
                sb.append('/').append(relPath);
            }
            if (this.session.nodeExists(userPropertiesPath = sb.toString())) {
                Node n = this.session.getNode(userPropertiesPath);
                if (!UserPropertiesManagerImpl.isValidUserPropertiesPath(authorizablePath, n.getPath())) {
                    throw new RepositoryException("User properties outside of scope of authorizable '" + Text.getName((String)authorizablePath) + "'.");
                }
                return n;
            }
        }
        return null;
    }

    private UserProperties getUserProperties(AuthorizableInfo info, Node userPropertiesNode) {
        return new UserPropertiesImpl(info, userPropertiesNode, this.resourceResolver);
    }

    private static boolean isValidUserPropertiesPath(String authorizablePath, String userPropertiesPath) {
        return Text.isDescendantOrEqual((String)authorizablePath, (String)userPropertiesPath);
    }

    protected void traverseAndFilterNodes(Node node, UserPropertiesFilter filter, Collection<UserProperties> userProperties) throws RepositoryException {
        UserProperties properties = this.getUserProperties(node);
        if (filter.includes(properties)) {
            userProperties.add(properties);
        }
        NodeIterator nodes = node.getNodes();
        while (nodes.hasNext()) {
            this.traverseAndFilterNodes(nodes.nextNode(), filter, userProperties);
        }
    }

    @Override
    public Iterator<UserProperties> getMemberUserProperties(Group group, String relPath, boolean declaredOnly) throws RepositoryException {
        return new UserPropertiesIterator(this.service.getMembers(group, declaredOnly), relPath, false);
    }

    @Override
    public /* varargs */ boolean addReaders(@Nonnull UserProperties userProperties, @Nonnull Principal ... principals) throws RepositoryException {
        return this.setReadable(userProperties, true, principals);
    }

    @Override
    public /* varargs */ boolean removeReaders(@Nonnull UserProperties userProperties, @Nonnull Principal ... principals) throws RepositoryException {
        return this.setReadable(userProperties, false, principals);
    }

    private /* varargs */ boolean setReadable(UserProperties userProperties, boolean isAllow, Principal ... principals) throws RepositoryException {
        Node node = userProperties.getNode();
        String path = node.getPath();
        boolean modified = false;
        JackrabbitAccessControlList acl = AccessControlUtils.getAccessControlList((Session)this.session, (String)path);
        if (acl == null) {
            return modified;
        }
        for (Principal p : principals) {
            modified = modified || acl.addEntry(p, this.readPrivileges, isAllow);
        }
        if (modified) {
            this.session.getAccessControlManager().setPolicy(path, (AccessControlPolicy)acl);
            this.session.save();
        }
        return modified;
    }

    @Override
    public Iterator<UserProperties> getMemberOfUserProperties(String authorizableId, String relPath, boolean declaredOnly) throws RepositoryException {
        return new UserPropertiesIterator(this.service.getMemberOf(authorizableId, declaredOnly), relPath, true);
    }

    private class UserPropertiesIterator
    implements Iterator<UserProperties> {
        private final Iterator<AuthorizableInfo> infoIter;
        private final String relPath;
        private final boolean readableOnly;
        private UserProperties next;

        public UserPropertiesIterator(Iterator<AuthorizableInfo> infoIter, String relPath, boolean readableOnly) {
            this.infoIter = infoIter;
            this.relPath = relPath;
            this.readableOnly = readableOnly;
            this.seek();
        }

        @Override
        public boolean hasNext() {
            return this.next != null;
        }

        @Override
        public UserProperties next() {
            UserProperties ret = this.next;
            this.seek();
            return ret;
        }

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

        private void seek() {
            this.next = null;
            while (this.infoIter.hasNext()) {
                AuthorizableInfo info = this.infoIter.next();
                try {
                    Node n;
                    if (this.readableOnly && !UserPropertiesManagerImpl.this.session.nodeExists(info.getPath()) || (n = UserPropertiesManagerImpl.this.getNode(info.getPath(), this.relPath)) == null) continue;
                    this.next = UserPropertiesManagerImpl.this.getUserProperties(info, n);
                    return;
                }
                catch (RepositoryException e) {
                    log.warn("error retrieving properties of '{}'", (Object)info.getId(), (Object)e);
                    continue;
                }
            }
        }
    }

}