org.alfresco.repo.domain.permissions.ADMAccessControlListDAO.java Source code

Java tutorial

Introduction

Here is the source code for org.alfresco.repo.domain.permissions.ADMAccessControlListDAO.java

Source

/*
 * #%L
 * Alfresco Repository
 * %%
 * Copyright (C) 2005 - 2016 Alfresco Software Limited
 * %%
 * This file is part of the Alfresco software. 
 * If the software was purchased under a paid Alfresco license, the terms of 
 * the paid license agreement will prevail.  Otherwise, the software is 
 * provided under the following open source license terms:
 * 
 * Alfresco is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Alfresco is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 * #L%
 */
package org.alfresco.repo.domain.permissions;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.alfresco.model.ContentModel;
import org.alfresco.repo.domain.node.NodeDAO;
import org.alfresco.repo.domain.node.NodeIdAndAclId;
import org.alfresco.repo.policy.BehaviourFilter;
import org.alfresco.repo.security.permissions.ACLType;
import org.alfresco.repo.security.permissions.AccessControlList;
import org.alfresco.repo.security.permissions.AccessControlListProperties;
import org.alfresco.repo.security.permissions.SimpleAccessControlListProperties;
import org.alfresco.repo.security.permissions.impl.AclChange;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.InvalidNodeRefException;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.namespace.QName;
import org.alfresco.util.Pair;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.dao.ConcurrencyFailureException;

/**
 * DAO layer for the improved ACL implementation. This layer is responsible for setting ACLs and any cascade behaviour
 * required. It also implements the migration from the old implementation to the new.
 * 
 * @author andyh
 */
public class ADMAccessControlListDAO implements AccessControlListDAO {
    private static final Log log = LogFactory.getLog(ADMAccessControlListDAO.class);
    /**
     * The DAO for Nodes.
     */
    private NodeDAO nodeDAO;

    private AclDAO aclDaoComponent;

    private BehaviourFilter behaviourFilter;
    private boolean preserveAuditableData = true;

    /**maxim transaction time allowed for {@link #setFixedAcls(Long, Long, Long, Long, List, boolean, AsyncCallParameters, boolean)} */
    private long fixedAclMaxTransactionTime = 10 * 1000;

    public void setNodeDAO(NodeDAO nodeDAO) {
        this.nodeDAO = nodeDAO;
    }

    public void setAclDAO(AclDAO aclDaoComponent) {
        this.aclDaoComponent = aclDaoComponent;
    }

    public void setFixedAclMaxTransactionTime(long fixedAclMaxTransactionTime) {
        this.fixedAclMaxTransactionTime = fixedAclMaxTransactionTime;
    }

    public void setBehaviourFilter(BehaviourFilter behaviourFilter) {
        this.behaviourFilter = behaviourFilter;
    }

    public void setPreserveAuditableData(boolean preserveAuditableData) {
        this.preserveAuditableData = preserveAuditableData;
    }

    public boolean isPreserveAuditableData() {
        return preserveAuditableData;
    }

    public void forceCopy(NodeRef nodeRef) {
        // Nothing to do
    }

    private Long getNodeIdNotNull(NodeRef nodeRef) {
        Pair<Long, NodeRef> nodePair = nodeDAO.getNodePair(nodeRef);
        if (nodePair == null) {
            throw new InvalidNodeRefException(nodeRef);
        }
        return nodePair.getFirst();
    }

    public Acl getAccessControlList(NodeRef nodeRef) {
        Long nodeId = getNodeIdNotNull(nodeRef);
        Long aclId = nodeDAO.getNodeAclId(nodeId);
        return aclDaoComponent.getAcl(aclId);
    }

    public Acl getAccessControlList(StoreRef storeRef) {
        return null;
    }

    public Long getIndirectAcl(NodeRef nodeRef) {
        return getAccessControlList(nodeRef).getId();
    }

    public Long getInheritedAcl(NodeRef nodeRef) {
        Pair<Long, NodeRef> nodePair = nodeDAO.getNodePair(nodeRef);
        if (nodePair == null) {
            return null;
        }
        Pair<Long, ChildAssociationRef> parentAssocRefPair = nodeDAO.getPrimaryParentAssoc(nodePair.getFirst());
        if (parentAssocRefPair == null || parentAssocRefPair.getSecond().getParentRef() == null) {
            return null;
        }
        Acl acl = getAccessControlList(parentAssocRefPair.getSecond().getParentRef());
        if (acl != null) {
            return acl.getId();
        } else {
            return null;
        }
    }

    public Map<ACLType, Integer> patchAcls() {
        CounterSet result = new CounterSet();
        List<Pair<Long, StoreRef>> stores = nodeDAO.getStores();

        for (Pair<Long, StoreRef> pair : stores) {
            CounterSet update;
            Long rootNodeId = nodeDAO.getRootNode(pair.getSecond()).getFirst();
            update = fixOldDmAcls(rootNodeId, nodeDAO.getNodeAclId(rootNodeId), (Long) null, true);
            result.add(update);
        }

        HashMap<ACLType, Integer> toReturn = new HashMap<ACLType, Integer>();
        toReturn.put(ACLType.DEFINING, Integer.valueOf(result.get(ACLType.DEFINING).getCounter()));
        toReturn.put(ACLType.FIXED, Integer.valueOf(result.get(ACLType.FIXED).getCounter()));
        toReturn.put(ACLType.GLOBAL, Integer.valueOf(result.get(ACLType.GLOBAL).getCounter()));
        toReturn.put(ACLType.LAYERED, Integer.valueOf(result.get(ACLType.LAYERED).getCounter()));
        toReturn.put(ACLType.OLD, Integer.valueOf(result.get(ACLType.OLD).getCounter()));
        toReturn.put(ACLType.SHARED, Integer.valueOf(result.get(ACLType.SHARED).getCounter()));
        return toReturn;
    }

    private CounterSet fixOldDmAcls(Long nodeId, Long existingNodeAclId, Long inheritedAclId, boolean isRoot) {
        CounterSet result = new CounterSet();

        // If existingNodeAclId is not null and equal to inheritedAclId then we know we have hit a shared ACL we have bulk set 
        // - just carry on in this case - we do not need to get the acl

        Long newDefiningAcl = null;

        if ((existingNodeAclId != null) && (existingNodeAclId.equals(inheritedAclId))) {
            // nothing to do except move into the children
        } else {
            AccessControlList existing = null;
            if (existingNodeAclId != null) {
                existing = aclDaoComponent.getAccessControlList(existingNodeAclId);
            }

            if (existing != null) {
                if (existing.getProperties().getAclType() == ACLType.OLD) {
                    result.increment(ACLType.DEFINING);
                    SimpleAccessControlListProperties properties = new SimpleAccessControlListProperties(
                            aclDaoComponent.getDefaultProperties());
                    properties.setInherits(existing.getProperties().getInherits());

                    Long actuallyInherited = null;
                    if (existing.getProperties().getInherits()) {
                        if (inheritedAclId != null) {
                            actuallyInherited = inheritedAclId;
                        }
                    }
                    Acl newAcl = aclDaoComponent.createAccessControlList(properties, existing.getEntries(),
                            actuallyInherited);
                    newDefiningAcl = newAcl.getId();
                    nodeDAO.setNodeAclId(nodeId, newDefiningAcl);
                } else if (existing.getProperties().getAclType() == ACLType.SHARED) {
                    // nothing to do just cascade into the children - we most likely did a bulk set above.
                    // TODO: Check shared ACL set is correct
                } else {
                    // Already fixed up
                    // TODO: Keep going to check
                    // Check inheritance is correct
                    return result;
                }
            } else {
                // Set default ACL on roots with no settings
                if (isRoot) {
                    result.increment(ACLType.DEFINING);

                    AccessControlListProperties properties = aclDaoComponent.getDefaultProperties();
                    Acl newAcl = aclDaoComponent.createAccessControlList(properties);
                    newDefiningAcl = newAcl.getId();
                    nodeDAO.setNodeAclId(nodeId, newDefiningAcl);
                } else {
                    // Unset - simple inherit
                    nodeDAO.setNodeAclId(nodeId, inheritedAclId);
                }
            }
        }

        Long toInherit = null;
        List<NodeIdAndAclId> children = nodeDAO.getPrimaryChildrenAcls(nodeId);
        if (children.size() > 0) {
            // Only make inherited if required
            if (newDefiningAcl == null) {
                toInherit = inheritedAclId;
            } else {
                toInherit = aclDaoComponent.getInheritedAccessControlList(newDefiningAcl);
            }

        }

        if (children.size() > 0) {
            nodeDAO.setPrimaryChildrenSharedAclId(nodeId, null, toInherit);
        }

        for (NodeIdAndAclId child : children) {
            CounterSet update = fixOldDmAcls(child.getId(), child.getAclId(), toInherit, false);
            result.add(update);
        }

        return result;
    }

    public void setAccessControlList(NodeRef nodeRef, Long aclId) {
        boolean auditableBehaviorWasDisabled = preserveAuditableData
                && behaviourFilter.isEnabled(ContentModel.ASPECT_AUDITABLE);
        if (auditableBehaviorWasDisabled) {
            behaviourFilter.disableBehaviour(ContentModel.ASPECT_AUDITABLE);
        }

        try {
            Long nodeId = getNodeIdNotNull(nodeRef);
            nodeDAO.setNodeAclId(nodeId, aclId);
        } finally {
            if (auditableBehaviorWasDisabled) {
                behaviourFilter.enableBehaviour(ContentModel.ASPECT_AUDITABLE);
            }
        }
    }

    public void setAccessControlList(NodeRef nodeRef, Acl acl) {
        Long aclId = null;
        if (acl != null) {
            aclId = acl.getId();
        }
        setAccessControlList(nodeRef, aclId);
    }

    public void setAccessControlList(StoreRef storeRef, Acl acl) {
        throw new UnsupportedOperationException();
    }

    public List<AclChange> setInheritanceForChildren(NodeRef parent, Long inheritFrom, Long sharedAclToReplace) {
        //check transaction resource to determine if async call may be required 
        boolean asyncCall = AlfrescoTransactionSupport.getResource(FixedAclUpdater.FIXED_ACL_ASYNC_CALL_KEY) == null
                ? false
                : true;
        return setInheritanceForChildren(parent, inheritFrom, sharedAclToReplace, asyncCall);
    }

    public List<AclChange> setInheritanceForChildren(NodeRef parent, Long inheritFrom, Long sharedAclToReplace,
            boolean asyncCall) {
        List<AclChange> changes = new ArrayList<AclChange>();
        setFixedAcls(getNodeIdNotNull(parent), inheritFrom, null, sharedAclToReplace, changes, false, asyncCall,
                true);
        return changes;
    }

    public void updateChangedAcls(NodeRef startingPoint, List<AclChange> changes) {
        // Nothing to do: no nodes change as a result of ACL changes
    }

    /**
     * Support to set a shared ACL on a node and all of its children
     * 
     * @param nodeId
     *            the parent node id
     * @param inheritFrom
     *            the parent node's ACL
     * @param mergeFrom
     *            the shared ACL, if already known. If <code>null</code>, will be retrieved / created lazily
     * @param changes
     *            the list in which to record changes
     * @param set
     *            set the shared ACL on the parent ?
     */
    public void setFixedAcls(Long nodeId, Long inheritFrom, Long mergeFrom, Long sharedAclToReplace,
            List<AclChange> changes, boolean set) {
        setFixedAcls(nodeId, inheritFrom, mergeFrom, sharedAclToReplace, changes, set, false, true);
    }

    /**
     * Support to set a shared ACL on a node and all of its children
     * 
     * @param nodeId
     *            the parent node
     * @param inheritFrom
     *            the parent node's ACL
     * @param mergeFrom
     *            the shared ACL, if already known. If <code>null</code>, will be retrieved / created lazily
     * @param changes
     *            the list in which to record changes
     * @param set
     *            set the shared ACL on the parent ?
     * @param asyncCall
     *            function may require asynchronous call depending the execution time; if time exceeds configured <code>fixedAclMaxTransactionTime</code> value,
     *            recursion is stopped using propagateOnChildren parameter(set on false) and those nodes for which the method execution was not finished 
     *            in the classical way, will have ASPECT_PENDING_FIX_ACL, which will be used in {@link FixedAclUpdater} for later processing
     */
    public void setFixedAcls(Long nodeId, Long inheritFrom, Long mergeFrom, Long sharedAclToReplace,
            List<AclChange> changes, boolean set, boolean asyncCall, boolean propagateOnChildren) {
        if (log.isDebugEnabled()) {
            log.debug(" Set fixed acl for nodeId=" + nodeId + " inheritFrom=" + inheritFrom + " sharedAclToReplace="
                    + sharedAclToReplace + " mergefrom= " + mergeFrom);
        }

        if (nodeId == null) {
            return;
        } else {
            // Lazily retrieve/create the shared ACL
            if (mergeFrom == null) {
                mergeFrom = aclDaoComponent.getInheritedAccessControlList(inheritFrom);
            }

            if (set) {
                nodeDAO.setNodeAclId(nodeId, mergeFrom);
            }

            List<NodeIdAndAclId> children = nodeDAO.getPrimaryChildrenAcls(nodeId);

            if (children.size() > 0) {
                nodeDAO.setPrimaryChildrenSharedAclId(nodeId, sharedAclToReplace, mergeFrom);
            }

            if (!propagateOnChildren) {
                return;
            }
            for (NodeIdAndAclId child : children) {
                Long acl = child.getAclId();

                if (acl == null) {
                    propagateOnChildren = setFixAclPending(child.getId(), inheritFrom, mergeFrom,
                            sharedAclToReplace, changes, false, asyncCall, propagateOnChildren);
                } else {
                    //                    if(acl.equals(mergeFrom))
                    //                    {
                    //                        setFixedAcls(child.getId(), inheritFrom, mergeFrom, sharedAclToReplace, changes, false);
                    //                    }
                    // Already replaced
                    if (acl.equals(sharedAclToReplace)) {
                        propagateOnChildren = setFixAclPending(child.getId(), inheritFrom, mergeFrom,
                                sharedAclToReplace, changes, false, asyncCall, propagateOnChildren);
                    } else {
                        Acl dbAcl = aclDaoComponent.getAcl(acl);
                        if (dbAcl.getAclType() == ACLType.LAYERED) {
                            throw new UnsupportedOperationException();
                        } else if (dbAcl.getAclType() == ACLType.DEFINING) {
                            if (dbAcl.getInherits()) {
                                @SuppressWarnings("unused")
                                List<AclChange> newChanges = aclDaoComponent
                                        .mergeInheritedAccessControlList(mergeFrom, acl);
                            }
                        } else if (dbAcl.getAclType() == ACLType.SHARED) {
                            throw new ConcurrencyFailureException("setFixedAcls: unexpected shared acl: " + dbAcl);
                        }
                    }
                }
            }
        }
    }

    /**
     * If async call required adds ASPECT_PENDING_FIX_ACL aspect to nodes when transactionTime reaches max admitted time
     */
    private boolean setFixAclPending(Long nodeId, Long inheritFrom, Long mergeFrom, Long sharedAclToReplace,
            List<AclChange> changes, boolean set, boolean asyncCall, boolean propagateOnChildren) {
        // check if async call is required
        if (!asyncCall) {
            // make regular method call
            setFixedAcls(nodeId, inheritFrom, mergeFrom, sharedAclToReplace, changes, set, asyncCall,
                    propagateOnChildren);
            return true;
        } else {
            // check transaction time
            long transactionStartTime = AlfrescoTransactionSupport.getTransactionStartTime();
            long transactionTime = System.currentTimeMillis() - transactionStartTime;

            if (transactionTime < fixedAclMaxTransactionTime) {
                // make regular method call if time is under max transaction configured time
                setFixedAcls(nodeId, inheritFrom, mergeFrom, sharedAclToReplace, changes, set, asyncCall,
                        propagateOnChildren);
                return true;
            } else {
                // time exceeded;
                if (nodeDAO.getPrimaryChildrenAcls(nodeId).size() == 0) {
                    // if node is leaf in tree hierarchy call setFixedAcls now as processing with FixedAclUpdater would be more time consuming
                    setFixedAcls(nodeId, inheritFrom, mergeFrom, sharedAclToReplace, changes, set, asyncCall,
                            false);
                } else {
                    // set ASPECT_PENDING_FIX_ACL aspect on node to be later on processed with FixedAclUpdater
                    addFixedAclPendingAspect(nodeId, sharedAclToReplace, inheritFrom);
                    AlfrescoTransactionSupport.bindResource(FixedAclUpdater.FIXED_ACL_ASYNC_REQUIRED_KEY, true);
                }
                // stop propagating on children nodes
                return false;
            }
        }
    }

    private void addFixedAclPendingAspect(Long nodeId, Long sharedAclToReplace, Long inheritFrom) {
        Set<QName> aspect = new HashSet<>();
        aspect.add(ContentModel.ASPECT_PENDING_FIX_ACL);
        nodeDAO.addNodeAspects(nodeId, aspect);
        Map<QName, Serializable> pendingAclProperties = new HashMap<>();
        pendingAclProperties.put(ContentModel.PROP_SHARED_ACL_TO_REPLACE, sharedAclToReplace);
        pendingAclProperties.put(ContentModel.PROP_INHERIT_FROM_ACL, inheritFrom);
        nodeDAO.addNodeProperties(nodeId, pendingAclProperties);
        if (log.isDebugEnabled()) {
            log.debug("Set Fixed Acl Pending : " + nodeId + " " + nodeDAO.getNodePair(nodeId).getSecond());
        }
    }

    /**
     * {@inheritDoc}
     */
    public void updateInheritance(Long childNodeId, Long oldParentAclId, Long newParentAclId) {
        if (oldParentAclId == null) {
            // nothing to do
            return;
        }
        List<AclChange> changes = new ArrayList<AclChange>();

        Long childAclId = nodeDAO.getNodeAclId(childNodeId);
        if (childAclId == null) {
            if (newParentAclId != null) {
                Long newParentSharedAclId = aclDaoComponent.getInheritedAccessControlList(newParentAclId);
                setFixedAcls(childNodeId, newParentSharedAclId, null, null, changes, true);
            }
        }
        Acl acl = aclDaoComponent.getAcl(childAclId);
        if (acl != null && acl.getInherits()) {
            Long oldParentSharedAclId = aclDaoComponent.getInheritedAccessControlList(oldParentAclId);
            Long sharedAclchildInheritsFrom = acl.getInheritsFrom();
            if (childAclId.equals(oldParentSharedAclId)) {
                // child had old shared acl
                if (newParentAclId != null) {
                    Long newParentSharedAclId = aclDaoComponent.getInheritedAccessControlList(newParentAclId);
                    setFixedAcls(childNodeId, newParentSharedAclId, null, childAclId, changes, true);
                }
            } else if (sharedAclchildInheritsFrom == null) {
                // child has defining acl of some form that does not inherit ?
                // Leave alone
            } else if (sharedAclchildInheritsFrom.equals(oldParentSharedAclId)) {
                // child has defining acl and needs to be remerged
                if (acl.getAclType() == ACLType.LAYERED) {
                    throw new UnsupportedOperationException();
                } else if (acl.getAclType() == ACLType.DEFINING) {
                    Long newParentSharedAclId = aclDaoComponent.getInheritedAccessControlList(newParentAclId);
                    @SuppressWarnings("unused")
                    List<AclChange> newChanges = aclDaoComponent
                            .mergeInheritedAccessControlList(newParentSharedAclId, childAclId);
                } else if (acl.getAclType() == ACLType.SHARED) {
                    throw new IllegalStateException();
                }
            } else {
                // the acl does not inherit from a node and does not need to be fixed up
                // Leave alone
            }
        }
    }

    /**
     * 
     * Counter for each type of ACL change
     * @author andyh
     *
     */
    public static class CounterSet extends HashMap<ACLType, Counter> {
        /**
         * 
         */
        private static final long serialVersionUID = -3682278258679211481L;

        CounterSet() {
            super();
            this.put(ACLType.DEFINING, new Counter());
            this.put(ACLType.FIXED, new Counter());
            this.put(ACLType.GLOBAL, new Counter());
            this.put(ACLType.LAYERED, new Counter());
            this.put(ACLType.OLD, new Counter());
            this.put(ACLType.SHARED, new Counter());
        }

        void add(ACLType type, Counter c) {
            Counter counter = get(type);
            counter.add(c.getCounter());
        }

        void increment(ACLType type) {
            Counter counter = get(type);
            counter.increment();
        }

        void add(CounterSet other) {
            add(ACLType.DEFINING, other.get(ACLType.DEFINING));
            add(ACLType.FIXED, other.get(ACLType.FIXED));
            add(ACLType.GLOBAL, other.get(ACLType.GLOBAL));
            add(ACLType.LAYERED, other.get(ACLType.LAYERED));
            add(ACLType.OLD, other.get(ACLType.OLD));
            add(ACLType.SHARED, other.get(ACLType.SHARED));
        }
    }

    /**
     * Simple counter
     * @author andyh
     *
     */
    public static class Counter {
        int counter;

        void increment() {
            counter++;
        }

        int getCounter() {
            return counter;
        }

        void add(int i) {
            counter += i;
        }
    }
}