mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2026-09-16 18:13:17 +00:00
[ACS-12679] Implement mechanism to cleanup ACLs (#4384)
This commit is contained in:
+70
@@ -35,6 +35,7 @@ import org.springframework.dao.ConcurrencyFailureException;
|
||||
import org.springframework.extensions.surf.util.ParameterCheck;
|
||||
|
||||
import org.alfresco.error.AlfrescoRuntimeException;
|
||||
import org.alfresco.model.ContentModel;
|
||||
import org.alfresco.repo.cache.SimpleCache;
|
||||
import org.alfresco.repo.cache.TransactionalCache;
|
||||
import org.alfresco.repo.cache.lookup.EntityLookupCache;
|
||||
@@ -42,6 +43,7 @@ import org.alfresco.repo.cache.lookup.EntityLookupCache.EntityLookupCallbackDAO;
|
||||
import org.alfresco.repo.domain.CrcHelper;
|
||||
import org.alfresco.repo.domain.qname.QNameDAO;
|
||||
import org.alfresco.repo.security.permissions.ACEType;
|
||||
import org.alfresco.repo.security.permissions.ACLType;
|
||||
import org.alfresco.repo.security.permissions.PermissionReference;
|
||||
import org.alfresco.repo.security.permissions.impl.SimplePermissionReference;
|
||||
import org.alfresco.service.cmr.security.AccessStatus;
|
||||
@@ -252,6 +254,64 @@ public abstract class AbstractAclCrudDAOImpl implements AclCrudDAO
|
||||
return getLatestAclEntityByGuid(aclGuid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Long> getUnusedAclIds(long afterAclId, int maxResults)
|
||||
{
|
||||
if (maxResults < 1)
|
||||
{
|
||||
throw new IllegalArgumentException("maxResults must be greater than zero");
|
||||
}
|
||||
|
||||
Long sharedAclToReplaceQNameId = getQNameId(ContentModel.PROP_SHARED_ACL_TO_REPLACE);
|
||||
Long inheritFromAclQNameId = getQNameId(ContentModel.PROP_INHERIT_FROM_ACL);
|
||||
return getUnusedAclEntityIds(afterAclId, sharedAclToReplaceQNameId, inheritFromAclQNameId,
|
||||
ACLType.FIXED.getId(), ACLType.GLOBAL.getId(), maxResults);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deleteUnusedAcl(long aclEntityId)
|
||||
{
|
||||
Long sharedAclToReplaceQNameId = getQNameId(ContentModel.PROP_SHARED_ACL_TO_REPLACE);
|
||||
Long inheritFromAclQNameId = getQNameId(ContentModel.PROP_INHERIT_FROM_ACL);
|
||||
if (!isAclEntityUnused(aclEntityId, sharedAclToReplaceQNameId, inheritFromAclQNameId,
|
||||
ACLType.FIXED.getId(), ACLType.GLOBAL.getId()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Acl acl = getAcl(aclEntityId);
|
||||
if (acl == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
List<AclMember> members = getAclMembersByAcl(aclEntityId);
|
||||
List<Long> aceIds = new ArrayList<>(members.size());
|
||||
for (AclMember member : members)
|
||||
{
|
||||
aceIds.add(member.getAceId());
|
||||
}
|
||||
|
||||
deleteAclMembersByAcl(aclEntityId);
|
||||
deleteAcl(aclEntityId);
|
||||
|
||||
for (Long aceId : aceIds)
|
||||
{
|
||||
deleteAceEntityIfUnused(aceId);
|
||||
}
|
||||
if (acl.getAclChangeSetId() != null)
|
||||
{
|
||||
deleteAclChangeSetEntityIfUnused(acl.getAclChangeSetId());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private Long getQNameId(QName qname)
|
||||
{
|
||||
Pair<Long, QName> qnamePair = qnameDAO.getQName(qname);
|
||||
return qnamePair == null ? null : qnamePair.getFirst();
|
||||
}
|
||||
|
||||
public List<Long> getADMNodesByAcl(long aclEntityId, int maxResults)
|
||||
{
|
||||
return getADMNodeEntityIdsByAcl(aclEntityId, maxResults);
|
||||
@@ -373,12 +433,20 @@ public abstract class AbstractAclCrudDAOImpl implements AclCrudDAO
|
||||
|
||||
protected abstract Long getLatestAclEntityByGuid(String aclGuid);
|
||||
|
||||
protected abstract List<Long> getUnusedAclEntityIds(long afterAclId, Long sharedAclToReplaceQNameId, Long inheritFromAclQNameId,
|
||||
int fixedAclType, int globalAclType, int maxResults);
|
||||
|
||||
protected abstract boolean isAclEntityUnused(long aclEntityId, Long sharedAclToReplaceQNameId, Long inheritFromAclQNameId,
|
||||
int fixedAclType, int globalAclType);
|
||||
|
||||
protected abstract int updateAclEntity(AclEntity entity);
|
||||
|
||||
protected abstract int updateAceEntity(AceEntity updatedAceEntity);
|
||||
|
||||
protected abstract int deleteAclEntity(long id);
|
||||
|
||||
protected abstract int deleteAclChangeSetEntityIfUnused(long aclChangeSetEntityId);
|
||||
|
||||
protected abstract List<Long> getADMNodeEntityIdsByAcl(long aclEntityId, int maxResults);
|
||||
|
||||
//
|
||||
@@ -681,6 +749,8 @@ public abstract class AbstractAclCrudDAOImpl implements AclCrudDAO
|
||||
|
||||
protected abstract int deleteAceEntities(List<Long> aceIds);
|
||||
|
||||
protected abstract int deleteAceEntityIfUnused(long aceId);
|
||||
|
||||
//
|
||||
// Permission
|
||||
//
|
||||
|
||||
@@ -63,6 +63,10 @@ public interface AclCrudDAO
|
||||
|
||||
public Long getLatestAclByGuid(String aclGuid);
|
||||
|
||||
List<Long> getUnusedAclIds(long afterAclId, int maxResults);
|
||||
|
||||
boolean deleteUnusedAcl(long aclEntityId);
|
||||
|
||||
public void updateAcl(AclUpdateEntity entity);
|
||||
|
||||
public void deleteAcl(long aclEntityId);
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* #%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.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.alfresco.service.transaction.TransactionService;
|
||||
import org.alfresco.util.PropertyCheck;
|
||||
|
||||
/**
|
||||
* Removes a bounded batch of ACLs that have no repository references.
|
||||
*/
|
||||
public class UnusedAclCleaner
|
||||
{
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(UnusedAclCleaner.class);
|
||||
|
||||
private AclCrudDAO aclCrudDAO;
|
||||
private TransactionService transactionService;
|
||||
private int batchSize = 1000;
|
||||
private boolean enabled = true;
|
||||
|
||||
public void setAclCrudDAO(AclCrudDAO aclCrudDAO)
|
||||
{
|
||||
this.aclCrudDAO = aclCrudDAO;
|
||||
}
|
||||
|
||||
public void setTransactionService(TransactionService transactionService)
|
||||
{
|
||||
this.transactionService = transactionService;
|
||||
}
|
||||
|
||||
public void setBatchSize(int batchSize)
|
||||
{
|
||||
if (batchSize < 1)
|
||||
{
|
||||
throw new IllegalArgumentException("batchSize must be greater than zero");
|
||||
}
|
||||
this.batchSize = batchSize;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled)
|
||||
{
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public void init()
|
||||
{
|
||||
PropertyCheck.mandatory(this, "aclCrudDAO", aclCrudDAO);
|
||||
PropertyCheck.mandatory(this, "transactionService", transactionService);
|
||||
}
|
||||
|
||||
public int execute()
|
||||
{
|
||||
if (!enabled)
|
||||
{
|
||||
LOGGER.debug("Unused ACL cleanup is disabled.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int deleted = transactionService.getRetryingTransactionHelper()
|
||||
.doInTransaction(this::cleanupBatch, false, true);
|
||||
LOGGER.info("Unused ACL cleanup removed {} ACLs.", deleted);
|
||||
return deleted;
|
||||
}
|
||||
|
||||
int cleanupBatch()
|
||||
{
|
||||
List<Long> aclIds = aclCrudDAO.getUnusedAclIds(0, batchSize);
|
||||
int deleted = 0;
|
||||
for (Long aclId : aclIds)
|
||||
{
|
||||
if (aclCrudDAO.deleteUnusedAcl(aclId))
|
||||
{
|
||||
deleted++;
|
||||
}
|
||||
}
|
||||
return deleted;
|
||||
}
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* #%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 org.quartz.JobDataMap;
|
||||
import org.quartz.JobExecutionContext;
|
||||
import org.quartz.JobExecutionException;
|
||||
|
||||
import org.alfresco.error.AlfrescoRuntimeException;
|
||||
import org.alfresco.schedule.AbstractScheduledLockedJob;
|
||||
|
||||
/**
|
||||
* Cluster-safe scheduled entry point for unused ACL cleanup.
|
||||
*/
|
||||
public class UnusedAclCleanupJob extends AbstractScheduledLockedJob
|
||||
{
|
||||
@Override
|
||||
public void executeJob(JobExecutionContext context) throws JobExecutionException
|
||||
{
|
||||
JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
|
||||
Object cleaner = jobDataMap.get("unusedAclCleaner");
|
||||
if (!(cleaner instanceof UnusedAclCleaner))
|
||||
{
|
||||
throw new AlfrescoRuntimeException("UnusedAclCleanupJob must contain a valid 'unusedAclCleaner'");
|
||||
}
|
||||
((UnusedAclCleaner) cleaner).execute();
|
||||
}
|
||||
}
|
||||
+47
@@ -59,6 +59,8 @@ public class AclCrudDAOImpl extends AbstractAclCrudDAOImpl
|
||||
private static final String SELECT_ACL_BY_ID = "alfresco.permissions.select_AclById";
|
||||
private static final String SELECT_ACLS_THAT_INHERIT_FROM_ACL = "alfresco.permissions.select_AclsThatInheritFromAcl";
|
||||
private static final String SELECT_LATEST_ACL_BY_GUID = "alfresco.permissions.select_LatestAclByGuid";
|
||||
private static final String SELECT_UNUSED_ACL_IDS = "alfresco.permissions.select_UnusedAclIds";
|
||||
private static final String SELECT_IS_ACL_UNUSED = "alfresco.permissions.select_IsAclUnused";
|
||||
private static final String SELECT_ADM_NODES_BY_ACL = "alfresco.permissions.select_ADMNodesByAclId";
|
||||
private static final String UPDATE_ACL = "alfresco.permissions.update_Acl";
|
||||
private static final String DELETE_ACL = "alfresco.permissions.delete_Acl";
|
||||
@@ -75,6 +77,7 @@ public class AclCrudDAOImpl extends AbstractAclCrudDAOImpl
|
||||
private static final String UPDATE_ACL_CHANGESET = "alfresco.permissions.update_AclChangeSet";
|
||||
private static final String SELECT_ACL_CHANGESET_BY_ID = "alfresco.permissions.select_AclChangeSetById";
|
||||
private static final String DELETE_ACL_CHANGESET = "alfresco.permissions.delete_AclChangeSet";
|
||||
private static final String DELETE_UNUSED_ACL_CHANGESET = "alfresco.permissions.delete_UnusedAclChangeSet";
|
||||
|
||||
private static final String INSERT_ACE = "alfresco.permissions.insert.insert_Ace";
|
||||
private static final String SELECT_ACE_BY_ID = "alfresco.permissions.select_AceById";
|
||||
@@ -82,6 +85,7 @@ public class AclCrudDAOImpl extends AbstractAclCrudDAOImpl
|
||||
private static final String SELECT_ACES_AND_AUTHORIES_BY_ACL = "alfresco.permissions.select_AcesAndAuthoritiesByAclId";
|
||||
private static final String SELECT_ACE_WITH_NO_CONTEXT = "alfresco.permissions.select_AceWithNoContext";
|
||||
private static final String DELETE_ACES_LIST = "alfresco.permissions.delete_AcesList";
|
||||
private static final String DELETE_UNUSED_ACE = "alfresco.permissions.delete_UnusedAce";
|
||||
private static final String UPDATE_ACE = "alfresco.permissions.update_Ace";
|
||||
|
||||
private static final String INSERT_ACE_CONTEXT = "alfresco.permissions.insert.insert_AceContext";
|
||||
@@ -151,6 +155,33 @@ public class AclCrudDAOImpl extends AbstractAclCrudDAOImpl
|
||||
return template.selectOne(SELECT_LATEST_ACL_BY_GUID, params);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<Long> getUnusedAclEntityIds(long afterAclId, Long sharedAclToReplaceQNameId, Long inheritFromAclQNameId,
|
||||
int fixedAclType, int globalAclType, int maxResults)
|
||||
{
|
||||
Map<String, Object> params = new HashMap<>(5);
|
||||
params.put("afterAclId", afterAclId);
|
||||
params.put("sharedAclToReplaceQNameId", sharedAclToReplaceQNameId);
|
||||
params.put("inheritFromAclQNameId", inheritFromAclQNameId);
|
||||
params.put("fixedAclType", fixedAclType);
|
||||
params.put("globalAclType", globalAclType);
|
||||
|
||||
return template.selectList(SELECT_UNUSED_ACL_IDS, params, new RowBounds(0, maxResults));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isAclEntityUnused(long aclEntityId, Long sharedAclToReplaceQNameId, Long inheritFromAclQNameId,
|
||||
int fixedAclType, int globalAclType)
|
||||
{
|
||||
Map<String, Object> params = new HashMap<>(5);
|
||||
params.put("id", aclEntityId);
|
||||
params.put("sharedAclToReplaceQNameId", sharedAclToReplaceQNameId);
|
||||
params.put("inheritFromAclQNameId", inheritFromAclQNameId);
|
||||
params.put("fixedAclType", fixedAclType);
|
||||
params.put("globalAclType", globalAclType);
|
||||
return template.selectOne(SELECT_IS_ACL_UNUSED, params) != null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
protected List<Long> getADMNodeEntityIdsByAcl(long aclEntityId, int maxResults)
|
||||
@@ -284,6 +315,14 @@ public class AclCrudDAOImpl extends AbstractAclCrudDAOImpl
|
||||
return template.delete(DELETE_ACL_CHANGESET, params);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int deleteAclChangeSetEntityIfUnused(long aclChangeSetEntityId)
|
||||
{
|
||||
Map<String, Object> params = new HashMap<>(1);
|
||||
params.put("id", aclChangeSetEntityId);
|
||||
return template.delete(DELETE_UNUSED_ACL_CHANGESET, params);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int updateChangeSetEntity(Long id, long commitTimeMs)
|
||||
{
|
||||
@@ -361,6 +400,14 @@ public class AclCrudDAOImpl extends AbstractAclCrudDAOImpl
|
||||
return template.delete(DELETE_ACES_LIST, aceEntityIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int deleteAceEntityIfUnused(long aceEntityId)
|
||||
{
|
||||
Map<String, Object> params = new HashMap<>(1);
|
||||
params.put("id", aceEntityId);
|
||||
return template.delete(DELETE_UNUSED_ACE, params);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long createAceContextEntity(AceContextEntity entity)
|
||||
{
|
||||
|
||||
+64
@@ -385,6 +385,52 @@
|
||||
acl.acl_id = ? and acl.latest = ?
|
||||
</select>
|
||||
|
||||
<select id="select_UnusedAclIds" parameterType="map" resultType="long">
|
||||
select
|
||||
acl.id
|
||||
from
|
||||
alf_access_control_list acl
|
||||
where
|
||||
acl.id > #{afterAclId,jdbcType=BIGINT} and
|
||||
acl.type not in (#{fixedAclType,jdbcType=INTEGER}, #{globalAclType,jdbcType=INTEGER}) and
|
||||
not exists (select 1 from alf_node node where node.acl_id = acl.id) and
|
||||
not exists (select 1 from alf_access_control_list inherited_acl where inherited_acl.inherited_acl = acl.id) and
|
||||
not exists (select 1 from alf_access_control_list parent_acl where parent_acl.inherits_from = acl.id) and
|
||||
not exists (
|
||||
select 1
|
||||
from alf_node_properties node_property
|
||||
where node_property.long_value = acl.id and
|
||||
node_property.qname_id in (
|
||||
#{sharedAclToReplaceQNameId,jdbcType=BIGINT},
|
||||
#{inheritFromAclQNameId,jdbcType=BIGINT}
|
||||
)
|
||||
)
|
||||
order by
|
||||
acl.id
|
||||
</select>
|
||||
|
||||
<select id="select_IsAclUnused" parameterType="map" resultType="long">
|
||||
select
|
||||
acl.id
|
||||
from
|
||||
alf_access_control_list acl
|
||||
where
|
||||
acl.id = #{id,jdbcType=BIGINT} and
|
||||
acl.type not in (#{fixedAclType,jdbcType=INTEGER}, #{globalAclType,jdbcType=INTEGER}) and
|
||||
not exists (select 1 from alf_node node where node.acl_id = acl.id) and
|
||||
not exists (select 1 from alf_access_control_list inherited_acl where inherited_acl.inherited_acl = acl.id) and
|
||||
not exists (select 1 from alf_access_control_list parent_acl where parent_acl.inherits_from = acl.id) and
|
||||
not exists (
|
||||
select 1
|
||||
from alf_node_properties node_property
|
||||
where node_property.long_value = acl.id and
|
||||
node_property.qname_id in (
|
||||
#{sharedAclToReplaceQNameId,jdbcType=BIGINT},
|
||||
#{inheritFromAclQNameId,jdbcType=BIGINT}
|
||||
)
|
||||
)
|
||||
</select>
|
||||
|
||||
<select id="select_ADMNodesByAclId" parameterMap="parameter_IdMap" resultType="long">
|
||||
select
|
||||
id
|
||||
@@ -578,6 +624,15 @@
|
||||
</foreach>
|
||||
</delete>
|
||||
|
||||
<delete id="delete_UnusedAce" parameterMap="parameter_IdMap">
|
||||
delete
|
||||
from
|
||||
alf_access_control_entry
|
||||
where
|
||||
id = ? and
|
||||
not exists (select 1 from alf_acl_member member where member.ace_id = alf_access_control_entry.id)
|
||||
</delete>
|
||||
|
||||
<!-- for patch -->
|
||||
<delete id="delete_UnusedAces">
|
||||
delete
|
||||
@@ -632,6 +687,15 @@
|
||||
id = ?
|
||||
</delete>
|
||||
|
||||
<delete id="delete_UnusedAclChangeSet" parameterMap="parameter_IdMap">
|
||||
delete
|
||||
from
|
||||
alf_acl_change_set
|
||||
where
|
||||
id = ? and
|
||||
not exists (select 1 from alf_access_control_list acl where acl.acl_change_set = alf_acl_change_set.id)
|
||||
</delete>
|
||||
|
||||
<delete id="delete_AceContext" parameterMap="parameter_IdMap">
|
||||
delete
|
||||
from
|
||||
|
||||
@@ -128,6 +128,13 @@
|
||||
<property name="policyIgnoreUtil" ref="policyIgnoreUtil"/>
|
||||
</bean>
|
||||
|
||||
<bean id="unusedAclCleaner" class="org.alfresco.repo.domain.permissions.UnusedAclCleaner" init-method="init">
|
||||
<property name="aclCrudDAO" ref="aclCrudDAO"/>
|
||||
<property name="transactionService" ref="transactionService"/>
|
||||
<property name="enabled" value="${system.aclCleanup.enabled}"/>
|
||||
<property name="batchSize" value="${system.aclCleanup.batchSize}"/>
|
||||
</bean>
|
||||
|
||||
<!-- =================== -->
|
||||
<!-- Dynamic Authorities -->
|
||||
<!-- =================== -->
|
||||
|
||||
@@ -1141,6 +1141,11 @@ system.fixedACLsUpdater.maxItems=-1
|
||||
# fixedACLsUpdater - Impose the order by in the query. If false, it may not process all the results but should do the queries faster
|
||||
system.fixedACLsUpdater.orderNodes=true
|
||||
|
||||
# Unused ACL cleanup - enabled by default and limited to one batch per daily execution
|
||||
system.aclCleanup.enabled=true
|
||||
system.aclCleanup.cronExpression=0 0 2 * * ?
|
||||
system.aclCleanup.batchSize=1000
|
||||
|
||||
cmis.disable.hidden.leading.period.files=false
|
||||
|
||||
#Smart Folders Config Properties
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
<ref bean="upgradePasswordHashJobTrigger"/>
|
||||
<ref bean="patchAddUnmovableAspectTrigger"/>
|
||||
<ref bean="fixedAclUpdaterTrigger"/>
|
||||
<ref bean="unusedAclCleanupTrigger"/>
|
||||
</list>
|
||||
</property>
|
||||
</bean>
|
||||
@@ -227,4 +228,21 @@
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
<bean id="unusedAclCleanupTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
|
||||
<property name="cronExpression" value="${system.aclCleanup.cronExpression}"/>
|
||||
<property name="startDelay" value="${system.cronJob.startDelayMilliseconds}"/>
|
||||
<property name="jobDetail">
|
||||
<bean id="unusedAclCleanupJobDetail" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
|
||||
<property name="jobClass" value="org.alfresco.repo.domain.permissions.UnusedAclCleanupJob"/>
|
||||
<property name="jobDataAsMap">
|
||||
<map>
|
||||
<entry key="unusedAclCleaner" value-ref="unusedAclCleaner"/>
|
||||
<entry key="jobLockService" value-ref="jobLockService"/>
|
||||
<entry key="name" value="unusedAclCleanupJob"/>
|
||||
</map>
|
||||
</property>
|
||||
</bean>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
</beans>
|
||||
|
||||
@@ -239,6 +239,8 @@ import org.alfresco.util.testing.category.NonBuildTests;
|
||||
org.alfresco.repo.content.caching.CachingContentStoreTest.class,
|
||||
org.alfresco.repo.content.caching.ContentCacheImplTest.class,
|
||||
org.alfresco.repo.domain.permissions.FixedAclUpdaterUnitTest.class,
|
||||
org.alfresco.repo.domain.permissions.UnusedAclCleanerTest.class,
|
||||
org.alfresco.repo.domain.permissions.UnusedAclCleanupJobTest.class,
|
||||
org.alfresco.repo.domain.propval.PropertyTypeConverterTest.class,
|
||||
org.alfresco.repo.domain.schema.script.ScriptBundleExecutorImplTest.class,
|
||||
org.alfresco.repo.search.MLAnaysisModeExpansionTest.class,
|
||||
|
||||
@@ -26,17 +26,25 @@
|
||||
package org.alfresco.repo.domain.permissions;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.experimental.categories.Category;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
|
||||
import org.alfresco.repo.security.permissions.ACEType;
|
||||
import org.alfresco.repo.security.permissions.ACLType;
|
||||
import org.alfresco.repo.security.permissions.PermissionReference;
|
||||
import org.alfresco.repo.security.permissions.SimpleAccessControlListProperties;
|
||||
import org.alfresco.repo.security.permissions.impl.SimplePermissionReference;
|
||||
import org.alfresco.repo.transaction.RetryingTransactionHelper;
|
||||
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
|
||||
import org.alfresco.service.ServiceRegistry;
|
||||
import org.alfresco.service.cmr.security.AccessStatus;
|
||||
import org.alfresco.service.namespace.NamespaceService;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
import org.alfresco.service.transaction.TransactionService;
|
||||
import org.alfresco.test_category.OwnJVMTestsCategory;
|
||||
@@ -57,7 +65,9 @@ public class AclCrudDAOTest extends TestCase
|
||||
private TransactionService transactionService;
|
||||
private RetryingTransactionHelper txnHelper;
|
||||
private AclCrudDAO aclCrudDAO;
|
||||
private AclDAO aclDAO;
|
||||
|
||||
@Before
|
||||
@Override
|
||||
public void setUp() throws Exception
|
||||
{
|
||||
@@ -66,6 +76,75 @@ public class AclCrudDAOTest extends TestCase
|
||||
txnHelper = transactionService.getRetryingTransactionHelper();
|
||||
|
||||
aclCrudDAO = (AclCrudDAO) ctx.getBean("aclCrudDAO");
|
||||
aclDAO = (AclDAO) ctx.getBean("aclDAO");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetUnusedAclIdsReturnsBoundedIsolatedAcls() throws Exception
|
||||
{
|
||||
List<Long> aclIds = txnHelper.doInTransaction(() -> {
|
||||
SimpleAccessControlListProperties properties = new SimpleAccessControlListProperties();
|
||||
properties.setAclType(ACLType.DEFINING);
|
||||
properties.setInherits(true);
|
||||
properties.setVersioned(false);
|
||||
|
||||
Long definingAclId = aclDAO.createAccessControlList(properties).getId();
|
||||
Long sharedAclId = aclDAO.getInheritedAccessControlList(definingAclId);
|
||||
properties.setInherits(false);
|
||||
properties.setAclType(ACLType.FIXED);
|
||||
aclDAO.createAccessControlList(properties);
|
||||
properties.setAclType(ACLType.GLOBAL);
|
||||
aclDAO.createAccessControlList(properties);
|
||||
properties.setAclType(ACLType.DEFINING);
|
||||
Long firstUnusedAclId = aclDAO.createAccessControlList(properties).getId();
|
||||
Long secondUnusedAclId = aclDAO.createAccessControlList(properties).getId();
|
||||
|
||||
return Arrays.asList(sharedAclId, firstUnusedAclId, secondUnusedAclId);
|
||||
});
|
||||
|
||||
List<Long> unusedAclIds = txnHelper.doInTransaction(() -> aclCrudDAO.getUnusedAclIds(aclIds.get(0), 1), true);
|
||||
|
||||
assertEquals(1, unusedAclIds.size());
|
||||
assertEquals(aclIds.get(1), unusedAclIds.get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteUnusedAclPreservesSharedAceUntilLastReferenceIsRemoved() throws Exception
|
||||
{
|
||||
List<Long> ids = txnHelper.doInTransaction(() -> {
|
||||
SimpleAccessControlListProperties properties = new SimpleAccessControlListProperties();
|
||||
properties.setAclType(ACLType.DEFINING);
|
||||
properties.setInherits(false);
|
||||
properties.setVersioned(false);
|
||||
|
||||
Long firstAclId = aclDAO.createAccessControlList(properties).getId();
|
||||
Long secondAclId = aclDAO.createAccessControlList(properties).getId();
|
||||
Authority authority = aclCrudDAO.getOrCreateAuthority("acl-cleanup-test-user");
|
||||
Permission permission = aclCrudDAO.getOrCreatePermission(
|
||||
SimplePermissionReference.getPermissionReference(
|
||||
QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "aclCleanupTest"), "Read"));
|
||||
Ace ace = aclCrudDAO.getOrCreateAce(permission, authority, ACEType.ALL, AccessStatus.ALLOWED);
|
||||
aclCrudDAO.addAclMembersToAcl(firstAclId, Arrays.asList(ace.getId()), 0);
|
||||
aclCrudDAO.addAclMembersToAcl(secondAclId, Arrays.asList(ace.getId()), 0);
|
||||
|
||||
return Arrays.asList(firstAclId, secondAclId, ace.getId(), aclCrudDAO.getAcl(firstAclId).getAclChangeSetId());
|
||||
});
|
||||
|
||||
assertTrue(txnHelper.doInTransaction(() -> aclCrudDAO.deleteUnusedAcl(ids.get(0))));
|
||||
txnHelper.doInTransaction(() -> {
|
||||
assertNull(aclCrudDAO.getAcl(ids.get(0)));
|
||||
assertNotNull(aclCrudDAO.getAce(ids.get(2)));
|
||||
assertNotNull(aclCrudDAO.getAclChangeSet(ids.get(3)));
|
||||
return null;
|
||||
}, true);
|
||||
|
||||
assertTrue(txnHelper.doInTransaction(() -> aclCrudDAO.deleteUnusedAcl(ids.get(1))));
|
||||
txnHelper.doInTransaction(() -> {
|
||||
assertNull(aclCrudDAO.getAcl(ids.get(1)));
|
||||
assertNull(aclCrudDAO.getAce(ids.get(2)));
|
||||
assertNull(aclCrudDAO.getAclChangeSet(ids.get(3)));
|
||||
return null;
|
||||
}, true);
|
||||
}
|
||||
|
||||
// TODO - alf_access_control_list, alf_acl_member, alf_access_control_entry
|
||||
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* #%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 static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.alfresco.service.transaction.TransactionService;
|
||||
|
||||
public class UnusedAclCleanerTest
|
||||
{
|
||||
private final AclCrudDAO aclCrudDAO = mock(AclCrudDAO.class);
|
||||
|
||||
@Test
|
||||
public void cleanupBatchDeletesAtMostConfiguredCandidates()
|
||||
{
|
||||
UnusedAclCleaner cleaner = new UnusedAclCleaner();
|
||||
cleaner.setAclCrudDAO(aclCrudDAO);
|
||||
cleaner.setBatchSize(2);
|
||||
when(aclCrudDAO.getUnusedAclIds(0, 2)).thenReturn(Arrays.asList(11L, 12L));
|
||||
when(aclCrudDAO.deleteUnusedAcl(11L)).thenReturn(true);
|
||||
when(aclCrudDAO.deleteUnusedAcl(12L)).thenReturn(false);
|
||||
|
||||
assertEquals(1, cleaner.cleanupBatch());
|
||||
verify(aclCrudDAO).deleteUnusedAcl(11L);
|
||||
verify(aclCrudDAO).deleteUnusedAcl(12L);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void disabledCleanerDoesNothing()
|
||||
{
|
||||
UnusedAclCleaner cleaner = new UnusedAclCleaner();
|
||||
cleaner.setAclCrudDAO(aclCrudDAO);
|
||||
cleaner.setTransactionService(mock(TransactionService.class));
|
||||
cleaner.setEnabled(false);
|
||||
|
||||
assertEquals(0, cleaner.execute());
|
||||
verifyNoInteractions(aclCrudDAO);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsInvalidBatchSize()
|
||||
{
|
||||
new UnusedAclCleaner().setBatchSize(0);
|
||||
}
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* #%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 static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.quartz.JobBuilder;
|
||||
import org.quartz.JobDetail;
|
||||
import org.quartz.JobExecutionContext;
|
||||
import org.quartz.JobExecutionException;
|
||||
|
||||
import org.alfresco.error.AlfrescoRuntimeException;
|
||||
|
||||
public class UnusedAclCleanupJobTest
|
||||
{
|
||||
private final JobExecutionContext context = mock(JobExecutionContext.class);
|
||||
private final UnusedAclCleaner cleaner = mock(UnusedAclCleaner.class);
|
||||
private JobDetail jobDetail;
|
||||
|
||||
@Before
|
||||
public void setUp()
|
||||
{
|
||||
jobDetail = JobBuilder.newJob(UnusedAclCleanupJob.class).withIdentity("unusedAclCleanupJob").build();
|
||||
jobDetail.getJobDataMap().put("unusedAclCleaner", cleaner);
|
||||
when(context.getJobDetail()).thenReturn(jobDetail);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void delegatesCleanup() throws JobExecutionException
|
||||
{
|
||||
new UnusedAclCleanupJob().executeJob(context);
|
||||
|
||||
verify(cleaner).execute();
|
||||
}
|
||||
|
||||
@Test(expected = AlfrescoRuntimeException.class)
|
||||
public void rejectsMissingCleaner() throws JobExecutionException
|
||||
{
|
||||
jobDetail.getJobDataMap().remove("unusedAclCleaner");
|
||||
new UnusedAclCleanupJob().executeJob(context);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user