diff --git a/config/alfresco/dao/dao-context.xml b/config/alfresco/dao/dao-context.xml
index 74e81924ab..e48997b2d8 100644
--- a/config/alfresco/dao/dao-context.xml
+++ b/config/alfresco/dao/dao-context.xml
@@ -264,6 +264,7 @@
+
diff --git a/config/alfresco/model/systemModel.xml b/config/alfresco/model/systemModel.xml
index 96b9241172..de54824175 100644
--- a/config/alfresco/model/systemModel.xml
+++ b/config/alfresco/model/systemModel.xml
@@ -396,6 +396,26 @@
+
+
+ Pending fix ACL
+
+
+ Shared Acl To Replace
+ d:long
+ true
+ false
+
+
+
+ Inherit From
+ d:long
+ true
+ false
+
+
+
+
\ No newline at end of file
diff --git a/config/alfresco/public-services-security-context.xml b/config/alfresco/public-services-security-context.xml
index 8a29bd07eb..ec0185059a 100644
--- a/config/alfresco/public-services-security-context.xml
+++ b/config/alfresco/public-services-security-context.xml
@@ -102,8 +102,19 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/config/alfresco/repository.properties b/config/alfresco/repository.properties
index 4a10a1f4e0..ef8b3e5111 100644
--- a/config/alfresco/repository.properties
+++ b/config/alfresco/repository.properties
@@ -1110,6 +1110,20 @@ models.enforceTenantInNamespace=false
# Allowed protocols for links
links.protocosl.white.list=http,https,ftp,mailto
+# Fixed ACLs
+# Required for fixing MNT-15368 - Time Consumed for Updating Folder Permission
+# ADMAccessControlListDAO.setFixedAcls called on a large folder hierarchy will take a long time for its execution.
+# For this reason now method can also be called asynchronously if transaction reaches system.fixedACLs.maxTransactionTime.
+# In this case setFixedAcls method recursion will be stopped and unfinished nodes will be marked with ASPECT_PENDING_FIX_ACL.
+# Pending nodes will be processed by FixedAclUpdater, programmatically called but also configured as a scheduled job.
+system.fixedACLs.maxTransactionTime=10000
+# fixedACLsUpdater - lock time to live
+system.fixedACLsUpdater.lockTTL=10000
+# fixedACLsUpdater - maximum number of nodes to process per execution
+system.fixedACLsUpdater.maxItemBatchSize=100
+# fixedACLsUpdater cron expression - fire at midnight every day
+system.fixedACLsUpdater.cronExpression=0 0 0 * * ?
+
cmis.disable.hidden.leading.period.files=false
#Smart Folders Config Properties
diff --git a/config/alfresco/scheduled-jobs-context.xml b/config/alfresco/scheduled-jobs-context.xml
index 65a92d0587..4445bea1de 100644
--- a/config/alfresco/scheduled-jobs-context.xml
+++ b/config/alfresco/scheduled-jobs-context.xml
@@ -327,4 +327,26 @@
${system.cronJob.startDelayMinutes}
+
+
+
+
+
+ org.alfresco.repo.domain.permissions.FixedAclUpdaterJob
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/source/java/org/alfresco/repo/domain/permissions/ADMAccessControlListDAO.java b/source/java/org/alfresco/repo/domain/permissions/ADMAccessControlListDAO.java
index 7dc7bf4de3..9b95f7f2f2 100644
--- a/source/java/org/alfresco/repo/domain/permissions/ADMAccessControlListDAO.java
+++ b/source/java/org/alfresco/repo/domain/permissions/ADMAccessControlListDAO.java
@@ -18,10 +18,13 @@
*/
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;
@@ -32,11 +35,15 @@ 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;
/**
@@ -47,6 +54,7 @@ import org.springframework.dao.ConcurrencyFailureException;
*/
public class ADMAccessControlListDAO implements AccessControlListDAO
{
+ private static final Log log = LogFactory.getLog(ADMAccessControlListDAO.class);
/**
* The DAO for Nodes.
*/
@@ -56,6 +64,9 @@ public class ADMAccessControlListDAO implements AccessControlListDAO
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)
{
@@ -67,6 +78,11 @@ public class ADMAccessControlListDAO implements AccessControlListDAO
this.aclDaoComponent = aclDaoComponent;
}
+ public void setFixedAclMaxTransactionTime(long fixedAclMaxTransactionTime)
+ {
+ this.fixedAclMaxTransactionTime = fixedAclMaxTransactionTime;
+ }
+
public void setBehaviourFilter(BehaviourFilter behaviourFilter)
{
this.behaviourFilter = behaviourFilter;
@@ -295,16 +311,23 @@ public class ADMAccessControlListDAO implements AccessControlListDAO
}
setAccessControlList(nodeRef, aclId);
}
-
+
public void setAccessControlList(StoreRef storeRef, Acl acl)
{
throw new UnsupportedOperationException();
}
-
+
public List 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 setInheritanceForChildren(NodeRef parent, Long inheritFrom, Long sharedAclToReplace, boolean asyncCall)
{
List changes = new ArrayList();
- setFixedAcls(getNodeIdNotNull(parent), inheritFrom, null, sharedAclToReplace, changes, false);
+ setFixedAcls(getNodeIdNotNull(parent), inheritFrom, null, sharedAclToReplace, changes, false, asyncCall, true);
return changes;
}
@@ -329,29 +352,51 @@ public class ADMAccessControlListDAO implements AccessControlListDAO
*/
public void setFixedAcls(Long nodeId, Long inheritFrom, Long mergeFrom, Long sharedAclToReplace, List 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 null
, 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 fixedAclMaxTransactionTime
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 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
{
- if (set)
- {
- // Lazily retrieve/create the shared ACL
- if (mergeFrom == null)
- {
- mergeFrom = aclDaoComponent.getInheritedAccessControlList(inheritFrom);
- }
- nodeDAO.setNodeAclId(nodeId, mergeFrom);
- }
-
- // update all shared in one shot - recurse later
-
+ // Lazily retrieve/create the shared ACL
if (mergeFrom == null)
{
mergeFrom = aclDaoComponent.getInheritedAccessControlList(inheritFrom);
}
+ if (set)
+ {
+ nodeDAO.setNodeAclId(nodeId, mergeFrom);
+ }
List children = nodeDAO.getPrimaryChildrenAcls(nodeId);
@@ -359,20 +404,18 @@ public class ADMAccessControlListDAO implements AccessControlListDAO
{
nodeDAO.setPrimaryChildrenSharedAclId(nodeId, sharedAclToReplace, mergeFrom);
}
-
+
+ if (!propagateOnChildren)
+ {
+ return;
+ }
for (NodeIdAndAclId child : children)
{
- // Lazily retrieve/create the shared ACL
- if (mergeFrom == null)
- {
- mergeFrom = aclDaoComponent.getInheritedAccessControlList(inheritFrom);
- }
-
Long acl = child.getAclId();
-
+
if (acl == null)
{
- setFixedAcls(child.getId(), inheritFrom, mergeFrom, sharedAclToReplace, changes, false);
+ propagateOnChildren = setFixAclPending(child.getId(), inheritFrom, mergeFrom, sharedAclToReplace, changes, false, asyncCall, propagateOnChildren);
}
else
{
@@ -383,7 +426,7 @@ public class ADMAccessControlListDAO implements AccessControlListDAO
// Already replaced
if(acl.equals(sharedAclToReplace))
{
- setFixedAcls(child.getId(), inheritFrom, mergeFrom, sharedAclToReplace, changes, false);
+ propagateOnChildren = setFixAclPending(child.getId(), inheritFrom, mergeFrom, sharedAclToReplace, changes, false, asyncCall, propagateOnChildren);
}
else
{
@@ -409,7 +452,67 @@ public class ADMAccessControlListDAO implements AccessControlListDAO
}
}
}
+
+ /**
+ * 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 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 aspect = new HashSet<>();
+ aspect.add(ContentModel.ASPECT_PENDING_FIX_ACL);
+ nodeDAO.addNodeAspects(nodeId, aspect);
+ Map 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}
*/
diff --git a/source/java/org/alfresco/repo/domain/permissions/AccessControlListDAO.java b/source/java/org/alfresco/repo/domain/permissions/AccessControlListDAO.java
index 21b90ad2cc..faa809ce69 100644
--- a/source/java/org/alfresco/repo/domain/permissions/AccessControlListDAO.java
+++ b/source/java/org/alfresco/repo/domain/permissions/AccessControlListDAO.java
@@ -76,6 +76,12 @@ public interface AccessControlListDAO
* Update inheritance
*/
public List setInheritanceForChildren(NodeRef parent, Long inheritFrom, Long sharedAclToReplace);
+
+ /**
+ * Set the inheritance on a given node and it's children. If the operation takes
+ * too long and asyncCall parameter set accordingly, fixed ACLs method will be synchronously called.
+ */
+ public List setInheritanceForChildren(NodeRef parent, Long inheritFrom, Long sharedAclToReplace, boolean asyncCall);
public Long getIndirectAcl(NodeRef nodeRef);
diff --git a/source/java/org/alfresco/repo/domain/permissions/FixedAclUpdater.java b/source/java/org/alfresco/repo/domain/permissions/FixedAclUpdater.java
new file mode 100644
index 0000000000..57570df828
--- /dev/null
+++ b/source/java/org/alfresco/repo/domain/permissions/FixedAclUpdater.java
@@ -0,0 +1,303 @@
+/*
+ * Copyright (C) 2005-2016 Alfresco Software Limited.
+ *
+ * This file is part of Alfresco
+ *
+ * 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 .
+ */
+package org.alfresco.repo.domain.permissions;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.alfresco.model.ContentModel;
+import org.alfresco.repo.domain.node.NodeDAO;
+import org.alfresco.repo.domain.node.NodeDAO.NodeRefQueryCallback;
+import org.alfresco.repo.lock.JobLockService;
+import org.alfresco.repo.lock.LockAcquisitionException;
+import org.alfresco.repo.lock.JobLockService.JobLockRefreshCallback;
+import org.alfresco.repo.security.authentication.AuthenticationUtil;
+import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
+import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
+import org.alfresco.repo.transaction.TransactionListenerAdapter;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.namespace.NamespaceService;
+import org.alfresco.service.namespace.QName;
+import org.alfresco.service.transaction.TransactionService;
+import org.alfresco.util.Pair;
+import org.alfresco.util.VmShutdownListener.VmShutdownException;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Finds nodes with ASPECT_PENDING_FIX_ACL aspect and sets fixed ACLs for them
+ *
+ * @author Andreea Dragoi
+ * @since 4.2.7
+ */
+public class FixedAclUpdater extends TransactionListenerAdapter
+{
+ private static final Log log = LogFactory.getLog(FixedAclUpdater.class);
+ private static final Set PENDING_FIX_ACL_ASPECT_PROPS = pendingFixAclAspectProps();
+
+ public static final String FIXED_ACL_ASYNC_REQUIRED_KEY = "FIXED_ACL_ASYNC_REQUIRED";
+ public static final String FIXED_ACL_ASYNC_CALL_KEY = "FIXED_ACL_ASYNC_CALL";
+
+ private JobLockService jobLockService;
+ private TransactionService transactionService;
+ private AccessControlListDAO accessControlListDAO;
+ private NodeDAO nodeDAO;
+ private QName lockQName = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "FixedAclUpdater");
+ private long lockTimeToLive = 10000;
+ private long lockRefreshTime = lockTimeToLive / 2;
+ private int maxItemBatchSize = 100;
+
+ public void setJobLockService(JobLockService jobLockService)
+ {
+ this.jobLockService = jobLockService;
+ }
+
+ public void setNodeDAO(NodeDAO nodeDAO)
+ {
+ this.nodeDAO = nodeDAO;
+ }
+
+ public void setTransactionService(TransactionService transactionService)
+ {
+ this.transactionService = transactionService;
+ }
+
+ public void setAccessControlListDAO(AccessControlListDAO accessControlListDAO)
+ {
+ this.accessControlListDAO = accessControlListDAO;
+ }
+
+ public void setMaxItemBatchSize(int maxItemBatchSize)
+ {
+ this.maxItemBatchSize = maxItemBatchSize;
+ }
+
+ public void setLockTimeToLive(long lockTimeToLive)
+ {
+ this.lockTimeToLive = lockTimeToLive;
+ this.lockRefreshTime = lockTimeToLive / 2;
+ }
+
+ private static Set pendingFixAclAspectProps()
+ {
+ Set props = new HashSet<>();
+ props.add(ContentModel.PROP_SHARED_ACL_TO_REPLACE);
+ props.add(ContentModel.PROP_INHERIT_FROM_ACL);
+ return props;
+ }
+
+ private int findAndUpdateAcl(FixedAclUpdaterJobLockRefreshCallback jobCallback)
+ {
+ final Set aspects = new HashSet<>(1);
+ aspects.add(ContentModel.ASPECT_PENDING_FIX_ACL);
+
+ List nodesToUpdate = getNodesWithAspects(aspects);
+ int processedNodes = 0;
+
+ // loop over results
+ for (final NodeRef nodeRef : nodesToUpdate)
+ {
+ // Check if we have been terminated
+ if (!jobCallback.isActive.get())
+ {
+ if (log.isDebugEnabled())
+ {
+ log.debug(String.format("Processing node failed %s. Job not active", nodeRef));
+ }
+ // terminate
+ break;
+ }
+ if (log.isDebugEnabled())
+ {
+ log.debug(String.format("Processing node %s", nodeRef));
+ }
+ final Long nodeId = nodeDAO.getNodePair(nodeRef).getFirst();
+
+ try
+ {
+ transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback()
+ {
+ public Void execute() throws Throwable
+ {
+ // retrieve acl properties from node
+ Long inheritFrom = (Long) nodeDAO.getNodeProperty(nodeId, ContentModel.PROP_INHERIT_FROM_ACL);
+ Long sharedAclToReplace = (Long) nodeDAO.getNodeProperty(nodeId, ContentModel.PROP_SHARED_ACL_TO_REPLACE);
+
+ // set inheritance using retrieved prop
+ accessControlListDAO.setInheritanceForChildren(nodeRef, inheritFrom, sharedAclToReplace, true);
+
+ nodeDAO.removeNodeAspects(nodeId, aspects);
+ nodeDAO.removeNodeProperties(nodeId, PENDING_FIX_ACL_ASPECT_PROPS);
+
+ if (log.isDebugEnabled())
+ {
+ log.debug(String.format("Node processed ", nodeRef));
+ }
+ return null;
+ }
+ }, false, true);
+ }
+ catch (Exception e)
+ {
+ if (log.isDebugEnabled())
+ {
+ log.debug(String.format("Could not process node ", nodeRef), e);
+ }
+ }
+
+ processedNodes++;
+ }
+
+ if (log.isDebugEnabled())
+ {
+ log.debug(String.format("Nodes found %s; nodes processed %s", nodesToUpdate.size(), processedNodes));
+ }
+
+ return processedNodes;
+ }
+
+ public void execute()
+ {
+ String lockToken = null;
+ FixedAclUpdaterJobLockRefreshCallback callback = new FixedAclUpdaterJobLockRefreshCallback();
+ try
+ {
+ RunAsWork findAndUpdateAclRunAsWork = findAndUpdateAclRunAsWork(callback);
+ lockToken = jobLockService.getLock(lockQName, lockTimeToLive, 0, 1);
+ while (true)
+ {
+ jobLockService.refreshLock(lockToken, lockQName, lockRefreshTime, callback);
+ Integer processed = AuthenticationUtil.runAs(findAndUpdateAclRunAsWork, AuthenticationUtil.getSystemUserName());
+ if (processed.intValue() == 0)
+ {
+ // There is no more to process
+ break;
+ }
+ // There is still more to process, so continue
+ }
+ }
+ catch (LockAcquisitionException e)
+ {
+ // already running
+ }
+ catch (VmShutdownException e)
+ {
+ if (log.isDebugEnabled())
+ {
+ log.debug("FixedAclUpdater aborted");
+ }
+ }
+ finally
+ {
+ callback.isActive.set(false);
+ jobLockService.releaseLock(lockToken, lockQName);
+ }
+ }
+
+ private RunAsWork findAndUpdateAclRunAsWork(final FixedAclUpdaterJobLockRefreshCallback callback)
+ {
+ final RetryingTransactionCallback findAndUpdateAclWork = new RetryingTransactionCallback()
+ {
+ public Integer execute() throws Exception
+ {
+ return findAndUpdateAcl(callback);
+ }
+ };
+
+ // execute as system user to ensure fast, accurate results
+ RunAsWork findAndUpdateAclRunAsWork = new RunAsWork()
+ {
+ @Override
+ public Integer doWork() throws Exception
+ {
+ return transactionService.getRetryingTransactionHelper().doInTransaction(findAndUpdateAclWork, false, true);
+ }
+ };
+ return findAndUpdateAclRunAsWork;
+ }
+
+ private List getNodesWithAspects(final Set aspects)
+ {
+ return transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback>()
+ {
+ @Override
+ public List execute() throws Throwable
+ {
+ GetNodesWithAspectCallback callback = new GetNodesWithAspectCallback();
+ nodeDAO.getNodesWithAspects(aspects, 1L, null, callback);
+ return callback.getNodes();
+ }
+ }, false, true);
+ }
+
+ private class GetNodesWithAspectCallback implements NodeRefQueryCallback
+ {
+ private List nodes = new ArrayList<>();
+
+ @Override
+ public boolean handle(Pair nodePair)
+ {
+ if (nodes.size() < maxItemBatchSize)
+ {
+ nodes.add(nodePair.getSecond());
+ return true;
+ }
+ return false;
+ }
+
+ public List getNodes()
+ {
+ return nodes;
+ }
+ }
+
+ private static class FixedAclUpdaterJobLockRefreshCallback implements JobLockRefreshCallback
+ {
+ public AtomicBoolean isActive = new AtomicBoolean(true);
+
+ @Override
+ public boolean isActive()
+ {
+ return isActive.get();
+ }
+
+ @Override
+ public void lockReleased()
+ {
+ isActive.set(false);
+ }
+ }
+
+ @Override
+ public void afterCommit()
+ {
+ Thread t = new Thread(new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ execute();
+ }
+ });
+ t.start();
+ }
+}
diff --git a/source/java/org/alfresco/repo/domain/permissions/FixedAclUpdaterJob.java b/source/java/org/alfresco/repo/domain/permissions/FixedAclUpdaterJob.java
new file mode 100644
index 0000000000..f9b39272d7
--- /dev/null
+++ b/source/java/org/alfresco/repo/domain/permissions/FixedAclUpdaterJob.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2005-2016 Alfresco Software Limited.
+ *
+ * This file is part of Alfresco
+ *
+ * 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 .
+ */
+package org.alfresco.repo.domain.permissions;
+
+import org.alfresco.error.AlfrescoRuntimeException;
+import org.quartz.Job;
+import org.quartz.JobDataMap;
+import org.quartz.JobExecutionContext;
+import org.quartz.JobExecutionException;
+
+/**
+ * Triggers setFixedAcl for those nodes with ASPECT_PENDING_FIX_ACL
+ *
+ * @author Andreea Dragoi
+ * @since 4.2.7
+ *
+ */
+public class FixedAclUpdaterJob implements Job
+{
+
+ /**
+ * Calls {@link FixedAclUpdater} to do it's work
+ */
+ @Override
+ public void execute(JobExecutionContext context) throws JobExecutionException
+ {
+ JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
+ Object fixedAclUpdaterObject = jobDataMap.get("fixedAclUpdater");
+ if (fixedAclUpdaterObject == null || !(fixedAclUpdaterObject instanceof FixedAclUpdater))
+ {
+ throw new AlfrescoRuntimeException("FixedAclUpdaterJob must contain a valid 'fixedAclUpdater'");
+ }
+ FixedAclUpdater fixedAclUpdater = (FixedAclUpdater)fixedAclUpdaterObject;
+ fixedAclUpdater.execute();
+ }
+}
diff --git a/source/java/org/alfresco/repo/jscript/ScriptNode.java b/source/java/org/alfresco/repo/jscript/ScriptNode.java
index fbbbe9969e..dab44b4624 100644
--- a/source/java/org/alfresco/repo/jscript/ScriptNode.java
+++ b/source/java/org/alfresco/repo/jscript/ScriptNode.java
@@ -1628,6 +1628,18 @@ public class ScriptNode implements Scopeable, NamespacePrefixResolverProvider
this.services.getPermissionService().setInheritParentPermissions(this.nodeRef, inherit);
}
+ /**
+ * Set whether this node should inherit permissions from the parent node. If the operation takes
+ * too long and asyncCall parameter set accordingly, fixed ACLs method will be asynchronously called.
+ *
+ * @param inherit True to inherit parent permissions, false otherwise.
+ * @param asyncCall True if fixed ACLs should be asynchronously set when operation execution takes too long, false otherwise.
+ */
+ public void setInheritsPermissions(boolean inherit, boolean asyncCall)
+ {
+ this.services.getPermissionService().setInheritParentPermissions(this.nodeRef, inherit, asyncCall);
+ }
+
/**
* Apply a permission for ALL users to the node.
*
diff --git a/source/java/org/alfresco/repo/security/permissions/impl/PermissionServiceImpl.java b/source/java/org/alfresco/repo/security/permissions/impl/PermissionServiceImpl.java
index 8a7a8d6749..2967c627a3 100644
--- a/source/java/org/alfresco/repo/security/permissions/impl/PermissionServiceImpl.java
+++ b/source/java/org/alfresco/repo/security/permissions/impl/PermissionServiceImpl.java
@@ -34,6 +34,7 @@ import org.alfresco.model.ContentModel;
import org.alfresco.repo.cache.SimpleCache;
import org.alfresco.repo.domain.permissions.AclDAO;
import org.alfresco.repo.node.db.traitextender.NodeServiceTrait;
+import org.alfresco.repo.domain.permissions.FixedAclUpdater;
import org.alfresco.repo.policy.JavaBehaviour;
import org.alfresco.repo.policy.PolicyComponent;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
@@ -148,6 +149,8 @@ public class PermissionServiceImpl extends AbstractLifecycleBean implements Perm
protected AclDAO aclDaoComponent;
protected PermissionReference allPermissionReference;
+
+ protected FixedAclUpdater fixedAclUpdater;
protected boolean anyDenyDenies = false;
@@ -271,7 +274,12 @@ public class PermissionServiceImpl extends AbstractLifecycleBean implements Perm
{
this.aclDaoComponent = aclDaoComponent;
}
-
+
+ public void setFixedAclUpdater(FixedAclUpdater fixedAclUpdater)
+ {
+ this.fixedAclUpdater = fixedAclUpdater;
+ }
+
/**
* Set the permissions access cache.
*
@@ -1034,6 +1042,31 @@ public class PermissionServiceImpl extends AbstractLifecycleBean implements Perm
permissionsDaoComponent.setInheritParentPermissions(actualRef, inheritParentPermissions);
accessCache.clear();
}
+
+ public void setInheritParentPermissions(NodeRef nodeRef, final boolean inheritParentPermissions, boolean asyncCall)
+ {
+ final NodeRef actualRef = tenantService.getName(nodeRef);
+ if (asyncCall)
+ {
+ //use transaction resource to determine later on in ADMAccessControlListDAO.setFixedAcl if asynchronous call may be required
+ AlfrescoTransactionSupport.bindResource(FixedAclUpdater.FIXED_ACL_ASYNC_CALL_KEY, true);
+ permissionsDaoComponent.setInheritParentPermissions(actualRef, inheritParentPermissions);
+ //check if asynchronous call was required
+ Boolean asyncCallRequired = (Boolean) AlfrescoTransactionSupport.getResource(FixedAclUpdater.FIXED_ACL_ASYNC_REQUIRED_KEY);
+ if (asyncCallRequired != null && asyncCallRequired)
+ {
+ //after transaction is committed FixedAclUpdater will be started in a new thread to process pending nodes
+ AlfrescoTransactionSupport.bindListener(fixedAclUpdater);
+ }
+ }
+ else
+ {
+ //regular method call
+ permissionsDaoComponent.setInheritParentPermissions(actualRef, inheritParentPermissions);
+ }
+
+ accessCache.clear();
+ }
/**
* @see org.alfresco.service.cmr.security.PermissionService#getInheritParentPermissions(org.alfresco.service.cmr.repository.NodeRef)
diff --git a/source/java/org/alfresco/repo/security/permissions/noop/PermissionServiceNOOPImpl.java b/source/java/org/alfresco/repo/security/permissions/noop/PermissionServiceNOOPImpl.java
index 6a80378e59..a9bcfa85c7 100644
--- a/source/java/org/alfresco/repo/security/permissions/noop/PermissionServiceNOOPImpl.java
+++ b/source/java/org/alfresco/repo/security/permissions/noop/PermissionServiceNOOPImpl.java
@@ -114,6 +114,12 @@ public class PermissionServiceNOOPImpl implements PermissionServiceSPI
// Do Nothing.
}
+ @Override
+ public void setInheritParentPermissions(NodeRef nodeRef, boolean inheritParentPermissions, boolean asyncCall)
+ {
+ // Do Nothing.
+ }
+
@Override
public boolean getInheritParentPermissions(NodeRef nodeRef)
{
diff --git a/source/test-java/org/alfresco/repo/domain/permissions/FixedAclUpdaterTest.java b/source/test-java/org/alfresco/repo/domain/permissions/FixedAclUpdaterTest.java
new file mode 100644
index 0000000000..49c8a1e9af
--- /dev/null
+++ b/source/test-java/org/alfresco/repo/domain/permissions/FixedAclUpdaterTest.java
@@ -0,0 +1,403 @@
+/*
+ * Copyright (C) 2005-2016 Alfresco Software Limited.
+ *
+ * This file is part of Alfresco
+ *
+ * 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 .
+ */
+package org.alfresco.repo.domain.permissions;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import junit.framework.TestCase;
+
+import org.alfresco.model.ContentModel;
+import org.alfresco.repo.domain.node.NodeDAO;
+import org.alfresco.repo.domain.node.NodeDAO.NodeRefQueryCallback;
+import org.alfresco.repo.model.Repository;
+import org.alfresco.repo.security.authentication.AuthenticationUtil;
+import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
+import org.alfresco.repo.security.permissions.impl.PermissionsDaoComponent;
+import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
+import org.alfresco.repo.transaction.RetryingTransactionHelper;
+import org.alfresco.repo.transaction.TransactionListenerAdapter;
+import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
+import org.alfresco.service.ServiceRegistry;
+import org.alfresco.service.cmr.model.FileFolderService;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.namespace.QName;
+import org.alfresco.util.ApplicationContextHelper;
+import org.alfresco.util.ArgumentHelper;
+import org.alfresco.util.Pair;
+import org.junit.Test;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ConfigurableApplicationContext;
+
+/**
+ * Test class for {@link FixedAclUpdater}
+ *
+ * @author Andreea Dragoi
+ * @since 4.2.7
+ *
+ */
+public class FixedAclUpdaterTest extends TestCase
+{
+ private ApplicationContext ctx;
+ private RetryingTransactionHelper txnHelper;
+ private FileFolderService fileFolderService;
+ private Repository repository;
+ private FixedAclUpdater fixedAclUpdater;
+ private NodeRef folderNodeRef;
+ private PermissionsDaoComponent permissionsDaoComponent;
+ private NodeDAO nodeDAO;
+
+ @Override
+ public void setUp() throws Exception
+ {
+ ctx = ApplicationContextHelper.getApplicationContext();
+ ServiceRegistry serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY);
+ txnHelper = serviceRegistry.getTransactionService().getRetryingTransactionHelper();
+ fileFolderService = serviceRegistry.getFileFolderService();
+ repository = (Repository) ctx.getBean("repositoryHelper");
+ fixedAclUpdater = (FixedAclUpdater) ctx.getBean("fixedAclUpdater");
+ permissionsDaoComponent = (PermissionsDaoComponent) ctx.getBean("admPermissionsDaoComponent");
+ nodeDAO = (NodeDAO) ctx.getBean("nodeDAO");
+
+ AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getSystemUserName());
+
+ NodeRef home = repository.getCompanyHome();
+ // create a folder hierarchy for which will change permission inheritance
+ int[] filesPerLevel = { 5, 5, 10 };
+ RetryingTransactionCallback cb = createFolderHierchyCallback(home, fileFolderService, "ROOT", filesPerLevel);
+ folderNodeRef = txnHelper.doInTransaction(cb);
+
+ // change setFixedAclMaxTransactionTime to lower value so setInheritParentPermissions on created folder hierarchy require async call
+ setFixedAclMaxTransactionTime(permissionsDaoComponent, home, 50);
+
+ }
+
+ private static void setFixedAclMaxTransactionTime(PermissionsDaoComponent permissionsDaoComponent, NodeRef folderNodeRef,
+ long fixedAclMaxTransactionTime)
+ {
+ if (permissionsDaoComponent instanceof ADMPermissionsDaoComponentImpl)
+ {
+ AccessControlListDAO acldao = ((ADMPermissionsDaoComponentImpl) permissionsDaoComponent).getACLDAO(folderNodeRef);
+ if (acldao instanceof ADMAccessControlListDAO)
+ {
+ ADMAccessControlListDAO admAcLDao = (ADMAccessControlListDAO) acldao;
+ admAcLDao.setFixedAclMaxTransactionTime(fixedAclMaxTransactionTime);
+ }
+ }
+ }
+
+ private static RetryingTransactionCallback createFolderHierchyCallback(final NodeRef root,
+ final FileFolderService fileFolderService, final String rootName, final int[] filesPerLevel)
+ {
+ RetryingTransactionCallback cb = new RetryingTransactionCallback()
+ {
+ @Override
+ public NodeRef execute() throws Throwable
+ {
+ NodeRef parent = createFile(fileFolderService, root, rootName, ContentModel.TYPE_FOLDER);
+ createFolderHierchy(fileFolderService, parent, 0, filesPerLevel);
+ return parent;
+ }
+ };
+ return cb;
+ }
+
+ @Override
+ public void tearDown() throws Exception
+ {
+ // delete created folder hierarchy
+ try
+ {
+ txnHelper.doInTransaction(new RetryingTransactionCallback()
+ {
+ @Override
+ public Void execute() throws Throwable
+ {
+ Set aspect = new HashSet<>();
+ aspect.add(ContentModel.ASPECT_TEMPORARY);
+ nodeDAO.addNodeAspects(nodeDAO.getNodePair(folderNodeRef).getFirst(), aspect);
+ fileFolderService.delete(folderNodeRef);
+ return null;
+ }
+ });
+ }
+ catch (Exception e)
+ {
+ }
+ AuthenticationUtil.clearCurrentSecurityContext();
+ }
+
+ private static NodeRef createFile(FileFolderService fileFolderService, NodeRef parent, String name, QName type)
+ {
+ return fileFolderService.create(parent, name + "_" + System.currentTimeMillis(), type).getNodeRef();
+ }
+
+ /**
+ * Get number of nodes with ASPECT_PENDING_FIX_ACL
+ */
+ private int getNodesCountWithPendingFixedAclAspect()
+ {
+ final Set aspects = new HashSet<>(1);
+ aspects.add(ContentModel.ASPECT_PENDING_FIX_ACL);
+ GetNodesCountWithAspectCallback callback = new GetNodesCountWithAspectCallback();
+ nodeDAO.getNodesWithAspects(aspects, 1L, null, callback);
+ return callback.getNodesNumber();
+ }
+
+ @Test
+ public void testMNT15368()
+ {
+ txnHelper.doInTransaction(new RetryingTransactionCallback()
+ {
+ @Override
+ public Void execute() throws Throwable
+ {
+ // call setInheritParentPermissions on a node that will required async
+ AlfrescoTransactionSupport.bindResource(FixedAclUpdater.FIXED_ACL_ASYNC_CALL_KEY, true);
+ permissionsDaoComponent.setInheritParentPermissions(folderNodeRef, false);
+
+ Boolean asyncCallRequired = (Boolean) AlfrescoTransactionSupport.getResource(FixedAclUpdater.FIXED_ACL_ASYNC_REQUIRED_KEY);
+ if (asyncCallRequired != null && asyncCallRequired)
+ {
+ // check if there are nodes with ASPECT_PENDING_FIX_ACL
+ assertTrue(" No nodes with pending aspect", getNodesCountWithPendingFixedAclAspect() > 0);
+ AlfrescoTransactionSupport.bindListener(new TransactionListenerAdapter()
+ {
+ @Override
+ public void afterCommit()
+ {
+ // start fixedAclUpdater
+ Thread t = new Thread(new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ fixedAclUpdater.execute();
+ }
+ });
+ t.start();
+ try
+ {
+ // wait to finish work
+ t.join();
+ }
+ catch (InterruptedException e)
+ {
+ }
+ }
+ });
+ }
+ return null;
+ }
+ });
+ // check if nodes with ASPECT_PENDING_FIX_ACL are processed
+ assertTrue("Not all nodes were processed", getNodesCountWithPendingFixedAclAspect() == 0);
+
+ }
+
+ private static class GetNodesCountWithAspectCallback implements NodeRefQueryCallback
+ {
+ int nodesNumber = 0;
+
+ @Override
+ public boolean handle(Pair nodePair)
+ {
+ nodesNumber++;
+ return true;
+ }
+
+ public int getNodesNumber()
+ {
+ return nodesNumber;
+ }
+ }
+
+ /**
+ * Creates a level in folder/file hierarchy. Intermediate levels will
+ * contain folders and last ones files
+ *
+ * @param fileFolderService
+ * @param parent
+ * - parent node of the of hierarchy level
+ * @param level
+ * - zero based
+ * @param filesPerLevel
+ * - array containing number of folders/files per level
+ */
+ private static void createFolderHierchy(FileFolderService fileFolderService, NodeRef parent, int level, int[] filesPerLevel)
+ {
+ int levels = filesPerLevel.length;
+ // intermediate level
+ if (level < levels - 1)
+ {
+ int numFiles = filesPerLevel[level];
+ for (int i = 0; i < numFiles; i++)
+ {
+ NodeRef node = createFile(fileFolderService, parent, "LVL" + level + i, ContentModel.TYPE_FOLDER);
+ createFolderHierchy(fileFolderService, node, level + 1, filesPerLevel);
+ }
+ }
+ // last level
+ else if (level == levels - 1)
+ {
+ int numFiles = filesPerLevel[level];
+ for (int i = 0; i < numFiles; i++)
+ {
+ createFile(fileFolderService, parent, "File" + i, ContentModel.TYPE_CONTENT);
+ }
+ }
+ }
+
+ /**
+ * Create a folder hierarchy and start FixedAclUpdater. See {@link #getUsage()} for usage parameters
+ */
+ public static void main(String... args)
+ {
+ ConfigurableApplicationContext ctx = (ConfigurableApplicationContext) ApplicationContextHelper.getApplicationContext();
+ try
+ {
+ run(ctx, args);
+ }
+ catch (Exception e)
+ {
+ System.out.println("Failed to run FixedAclUpdaterTest test");
+ e.printStackTrace();
+ }
+ finally
+ {
+ ctx.close();
+ }
+ }
+
+ public static void run(final ApplicationContext ctx, String... args) throws InterruptedException
+ {
+ ArgumentHelper argHelper = new ArgumentHelper(getUsage(), args);
+ int threadCount = argHelper.getIntegerValue("threads", true, 1, 100);
+ String levels[] = argHelper.getStringValue("filesPerLevel", true, true).split(",");
+ int fixedAclMaxTransactionTime = argHelper.getIntegerValue("fixedAclMaxTransactionTime", true, 1, 10000);
+ final int[] filesPerLevel = new int[levels.length];
+ for (int i = 0; i < levels.length; i++)
+ {
+ filesPerLevel[i] = Integer.parseInt(levels[i]);
+ }
+
+ ServiceRegistry serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY);
+ final RetryingTransactionHelper txnHelper = serviceRegistry.getTransactionService().getRetryingTransactionHelper();
+ final FileFolderService fileFolderService = serviceRegistry.getFileFolderService();
+ Repository repository = (Repository) ctx.getBean("repositoryHelper");
+ final FixedAclUpdater fixedAclUpdater = (FixedAclUpdater) ctx.getBean("fixedAclUpdater");
+ final PermissionsDaoComponent permissionsDaoComponent = (PermissionsDaoComponent) ctx.getBean("admPermissionsDaoComponent");
+
+ AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getSystemUserName());
+
+ NodeRef home = repository.getCompanyHome();
+ final NodeRef root = createFile(fileFolderService, home, "ROOT", ContentModel.TYPE_FOLDER);
+
+ // create a folder hierarchy for which will change permission inheritance
+ Thread[] threads = new Thread[threadCount];
+ for (int i = 0; i < threadCount; i++)
+ {
+ final int index = i;
+ Thread t = new Thread(new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ AuthenticationUtil.runAs(new RunAsWork()
+ {
+ @Override
+ public Void doWork() throws Exception
+ {
+
+ RetryingTransactionCallback cb = createFolderHierchyCallback(root, fileFolderService, "FOLDER" + index, filesPerLevel);
+ txnHelper.doInTransaction(cb);
+ return null;
+ }
+ }, AuthenticationUtil.getSystemUserName());
+ }
+ });
+ t.start();
+ threads[i] = t;
+ }
+ for (int i = 0; i < threads.length; i++)
+ {
+ threads[i].join();
+ }
+
+ setFixedAclMaxTransactionTime(permissionsDaoComponent, home, fixedAclMaxTransactionTime);
+
+ txnHelper.doInTransaction(new RetryingTransactionCallback()
+ {
+ @Override
+ public Void execute() throws Throwable
+ {
+ // call setInheritParentPermissions on a node that will required async
+ AlfrescoTransactionSupport.bindResource(FixedAclUpdater.FIXED_ACL_ASYNC_CALL_KEY, true);
+ final long startTime = System.currentTimeMillis();
+ permissionsDaoComponent.setInheritParentPermissions(root, false);
+
+ Boolean asyncCallRequired = (Boolean) AlfrescoTransactionSupport.getResource(FixedAclUpdater.FIXED_ACL_ASYNC_REQUIRED_KEY);
+ if (asyncCallRequired != null && asyncCallRequired)
+ {
+ // check if there are nodes with ASPECT_PENDING_FIX_ACL
+ AlfrescoTransactionSupport.bindListener(new TransactionListenerAdapter()
+ {
+ @Override
+ public void afterCommit()
+ {
+ long userEndTime = System.currentTimeMillis();
+ // start fixedAclUpdater
+ Thread t = new Thread(new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ fixedAclUpdater.execute();
+ }
+ });
+ t.start();
+ try
+ {
+ // wait to finish work
+ t.join();
+ System.out.println("Backend time " + (System.currentTimeMillis() - startTime));
+ System.out.println("User time " + (userEndTime - startTime));
+ }
+ catch (InterruptedException e)
+ {
+ }
+ }
+ });
+ }
+ return null;
+ }
+ }, false, true);
+ }
+
+ private static String getUsage()
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.append("FixedAclUpdaterTest usage: ").append("\n");
+ sb.append(" FixedAclUpdaterTest --threads= --fixedAclMaxTransactionTime= --filesPerLevel=").append("\n");
+ sb.append(" maxtime: max transaction time for fixed acl ").append("\n");
+ sb.append(" threadcount: number of threads to create the folder hierarchy ").append("\n");
+ sb.append(" levelfiles: number of folders/files per level separated by comma").append("\n");
+ return sb.toString();
+ }
+}
diff --git a/source/test-java/org/alfresco/repo/security/SecurityTestSuite.java b/source/test-java/org/alfresco/repo/security/SecurityTestSuite.java
index c41646363d..0f5461302e 100644
--- a/source/test-java/org/alfresco/repo/security/SecurityTestSuite.java
+++ b/source/test-java/org/alfresco/repo/security/SecurityTestSuite.java
@@ -22,6 +22,7 @@ import junit.framework.JUnit4TestAdapter;
import junit.framework.Test;
import junit.framework.TestSuite;
+import org.alfresco.repo.domain.permissions.FixedAclUpdaterTest;
import org.alfresco.repo.ownable.impl.OwnableServiceTest;
import org.alfresco.repo.security.authentication.AlfrescoSSLSocketFactoryTest;
import org.alfresco.repo.security.authentication.AuthenticationBootstrapTest;
@@ -85,6 +86,8 @@ public class SecurityTestSuite extends TestSuite
suite.addTest(new JUnit4TestAdapter(HomeFolderProviderSynchronizerTest.class));
suite.addTest(new JUnit4TestAdapter(AlfrescoSSLSocketFactoryTest.class));
+ suite.addTestSuite(FixedAclUpdaterTest.class);
+
return suite;
}
}