Merge branch 'master' into feature/RM-7028_Add_Add_To_Hold_ToAudit

This commit is contained in:
Sara Aspery
2019-11-18 14:12:23 +00:00
9 changed files with 153 additions and 69 deletions

View File

@@ -1025,7 +1025,6 @@
<bean id="deleteHold" class="org.alfresco.module.org_alfresco_module_rm.action.impl.DeleteHoldAction" parent="rmAction"> <bean id="deleteHold" class="org.alfresco.module.org_alfresco_module_rm.action.impl.DeleteHoldAction" parent="rmAction">
<property name="auditable" value="false"/> <property name="auditable" value="false"/>
<property name="auditedImmediately" value="true"/>
</bean> </bean>
<bean id="deleteHold_proxy" parent="rmProxyAction" > <bean id="deleteHold_proxy" parent="rmProxyAction" >

View File

@@ -177,6 +177,11 @@
</bean> </bean>
<util:constant id="propThumbnailModification" static-field="org.alfresco.model.ContentModel.PROP_LAST_THUMBNAIL_MODIFICATION_DATA" /> <util:constant id="propThumbnailModification" static-field="org.alfresco.model.ContentModel.PROP_LAST_THUMBNAIL_MODIFICATION_DATA" />
<!-- Defines a list of namespace URIs for properties, which should be always editable for a frozen node-->
<util:list id="frozen_alwaysEditURIs" value-type="java.lang.String">
<value>http://www.alfresco.org/model/system/1.0</value>
</util:list>
<bean name="updateFrozenPropertyCheck" <bean name="updateFrozenPropertyCheck"
class="org.alfresco.module.org_alfresco_module_rm.util.PropertyModificationAllowedCheck"> class="org.alfresco.module.org_alfresco_module_rm.util.PropertyModificationAllowedCheck">
@@ -185,6 +190,7 @@
<ref bean="propThumbnailModification" /> <ref bean="propThumbnailModification" />
</list> </list>
</property> </property>
<property name="editableURIs" ref="frozen_alwaysEditURIs" />
</bean> </bean>
<bean id="rma.vitalRecordDefinition" class="org.alfresco.module.org_alfresco_module_rm.model.rma.aspect.VitalRecordDefinitionAspect" parent="rm.baseBehaviour"> <bean id="rma.vitalRecordDefinition" class="org.alfresco.module.org_alfresco_module_rm.model.rma.aspect.VitalRecordDefinitionAspect" parent="rm.baseBehaviour">

View File

@@ -30,10 +30,11 @@ package org.alfresco.module.org_alfresco_module_rm.audit.event;
import java.io.Serializable; import java.io.Serializable;
import java.util.Map; import java.util.Map;
import org.alfresco.repo.node.NodeServicePolicies;
import org.alfresco.repo.policy.Behaviour.NotificationFrequency;
import org.alfresco.repo.policy.annotation.Behaviour; import org.alfresco.repo.policy.annotation.Behaviour;
import org.alfresco.repo.policy.annotation.BehaviourBean; import org.alfresco.repo.policy.annotation.BehaviourBean;
import org.alfresco.repo.policy.annotation.BehaviourKind; import org.alfresco.repo.policy.annotation.BehaviourKind;
import org.alfresco.repo.policy.Behaviour.NotificationFrequency;
import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.NodeService;
@@ -41,14 +42,18 @@ import org.alfresco.service.namespace.QName;
/** /**
* Create hold audit event. * Create hold audit event.
* This listens to the NodeServicePolicies.OnCreateNodePolicy in order to cover the create hold action from Share
* since that does not call the createHold from HoldService
* *
* @author Sara Aspery * @author Sara Aspery
* @since 3.3 * @since 3.3
*/ */
@BehaviourBean @BehaviourBean
public class CreateHoldAuditEvent extends AuditEvent public class CreateHoldAuditEvent extends AuditEvent implements NodeServicePolicies.OnCreateNodePolicy
{ {
/** Node Service */ /**
* Node Service
*/
private NodeService nodeService; private NodeService nodeService;
/** /**
@@ -62,16 +67,16 @@ public class CreateHoldAuditEvent extends AuditEvent
} }
/** /**
* @param childAssociationRef child association reference * @see org.alfresco.repo.node.NodeServicePolicies.OnCreateNodePolicy#onCreateNode(org.alfresco.service.cmr.repository.ChildAssociationRef)
*/ */
@Override
@Behaviour @Behaviour
( (
kind = BehaviourKind.CLASS, kind = BehaviourKind.CLASS,
type = "rma:hold", type = "rma:hold",
policy = "alf:onCreateNode", notificationFrequency = NotificationFrequency.TRANSACTION_COMMIT
notificationFrequency= NotificationFrequency.TRANSACTION_COMMIT
) )
public void onCreateHold(ChildAssociationRef childAssociationRef) public void onCreateNode(ChildAssociationRef childAssociationRef)
{ {
NodeRef holdNodeRef = childAssociationRef.getChildRef(); NodeRef holdNodeRef = childAssociationRef.getChildRef();

View File

@@ -27,10 +27,12 @@
package org.alfresco.module.org_alfresco_module_rm.audit.event; package org.alfresco.module.org_alfresco_module_rm.audit.event;
import static org.alfresco.repo.policy.Behaviour.NotificationFrequency.EVERY_EVENT;
import java.io.Serializable; import java.io.Serializable;
import java.util.Map; import java.util.Map;
import org.alfresco.module.org_alfresco_module_rm.hold.HoldServicePolicies; import org.alfresco.repo.node.NodeServicePolicies;
import org.alfresco.repo.policy.annotation.Behaviour; import org.alfresco.repo.policy.annotation.Behaviour;
import org.alfresco.repo.policy.annotation.BehaviourBean; import org.alfresco.repo.policy.annotation.BehaviourBean;
import org.alfresco.repo.policy.annotation.BehaviourKind; import org.alfresco.repo.policy.annotation.BehaviourKind;
@@ -38,18 +40,19 @@ import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.QName;
import static org.alfresco.repo.policy.Behaviour.NotificationFrequency.EVERY_EVENT;
/** /**
* Delete hold audit event. * Delete hold audit event.
* This listens to the NodeServicePolicies.BeforeDeleteNodePolicy in order to cover the delete hold using nodes service
* *
* @author Sara Aspery * @author Sara Aspery
* @since 3.3 * @since 3.3
*/ */
@BehaviourBean @BehaviourBean
public class DeleteHoldAuditEvent extends AuditEvent implements HoldServicePolicies.BeforeDeleteHoldPolicy public class DeleteHoldAuditEvent extends AuditEvent implements NodeServicePolicies.BeforeDeleteNodePolicy
{ {
/** Node Service */ /**
* Node Service
*/
private NodeService nodeService; private NodeService nodeService;
/** /**
@@ -63,16 +66,15 @@ public class DeleteHoldAuditEvent extends AuditEvent implements HoldServicePolic
} }
/** /**
* @see org.alfresco.module.org_alfresco_module_rm.hold.HoldServicePolicies.BeforeDeleteHoldPolicy#beforeDeleteHold(org.alfresco.service.cmr.repository.NodeRef) * @see org.alfresco.repo.node.NodeServicePolicies.BeforeDeleteNodePolicy#beforeDeleteNode(org.alfresco.service.cmr.repository.NodeRef)
*/ */
@Override @Override
@Behaviour @Behaviour (
( kind = BehaviourKind.CLASS,
kind = BehaviourKind.CLASS, type = "rma:hold",
type = "rma:hold", notificationFrequency = EVERY_EVENT
notificationFrequency = EVERY_EVENT )
) public void beforeDeleteNode(NodeRef holdNodeRef)
public void beforeDeleteHold(NodeRef holdNodeRef)
{ {
Map<QName, Serializable> auditProperties = HoldUtils.makePropertiesMap(holdNodeRef, nodeService); Map<QName, Serializable> auditProperties = HoldUtils.makePropertiesMap(holdNodeRef, nodeService);
recordsManagementAuditService.auditEvent(holdNodeRef, getName(), auditProperties, null, true, false); recordsManagementAuditService.auditEvent(holdNodeRef, getName(), auditProperties, null, true, false);

View File

@@ -651,7 +651,8 @@ public class HoldServiceImpl extends ServiceBaseImpl
// fire before add to hold policy // fire before add to hold policy
invokeBeforeAddToHold(hold, nodeRef); invokeBeforeAddToHold(hold, nodeRef);
// run as system to ensure we have all the appropriate permissions to perform the manipulations we require // run as system to ensure we have all the appropriate permissions to perform the manipulations we require
authenticationUtil.runAsSystem((RunAsWork<Void>) () -> { authenticationUtil.runAsSystem((RunAsWork<Void>) () ->
{
// gather freeze properties // gather freeze properties
final Map<QName, Serializable> props = new HashMap<>(2); final Map<QName, Serializable> props = new HashMap<>(2);
props.put(PROP_FROZEN_AT, new Date()); props.put(PROP_FROZEN_AT, new Date());
@@ -671,11 +672,11 @@ public class HoldServiceImpl extends ServiceBaseImpl
records.forEach(record -> addFrozenAspect(record, props)); records.forEach(record -> addFrozenAspect(record, props));
} }
// fire on add to hold policy
invokeOnAddToHold(hold, nodeRef);
return null; return null;
}); });
// fire on add to hold policy
invokeOnAddToHold(hold, nodeRef);
} }
} }
} }
@@ -791,6 +792,7 @@ public class HoldServiceImpl extends ServiceBaseImpl
if (!holds.isEmpty()) if (!holds.isEmpty())
{ {
List<NodeRef> removedHolds = new ArrayList<>();
for (final NodeRef hold : holds) for (final NodeRef hold : holds)
{ {
if (!isHold(hold)) if (!isHold(hold))
@@ -807,34 +809,38 @@ public class HoldServiceImpl extends ServiceBaseImpl
if (getHeld(hold).contains(nodeRef)) if (getHeld(hold).contains(nodeRef))
{ {
// fire before remove from hold policy
invokeBeforeRemoveFromHold(hold, nodeRef);
// run as system so we don't run into further permission issues // run as system so we don't run into further permission issues
// we already know we have to have the correct capability to get here // we already know we have to have the correct capability to get here
authenticationUtil.runAsSystem((RunAsWork<Void>) () -> { authenticationUtil.runAsSystem((RunAsWork<Void>) () ->
// fire before remove from hold policy {
invokeBeforeRemoveFromHold(hold, nodeRef);
// remove from hold // remove from hold
//set in transaction cache in order not to trigger update policy when removing the child association //set in transaction cache in order not to trigger update policy when removing the child association
transactionalResourceHelper.getSet("frozen").add(nodeRef); transactionalResourceHelper.getSet("frozen").add(nodeRef);
nodeService.removeChild(hold, nodeRef); nodeService.removeChild(hold, nodeRef);
// audit that the node has been remove from the hold // audit that the node has been removed from the hold
// TODO add details of the hold that the node was removed from // TODO add details of the hold that the node was removed from
recordsManagementAuditService.auditEvent(nodeRef, AUDIT_REMOVE_FROM_HOLD); recordsManagementAuditService.auditEvent(nodeRef, AUDIT_REMOVE_FROM_HOLD);
// fire on remove from hold policy
invokeOnRemoveFromHold(hold, nodeRef);
return null; return null;
}); });
removedHolds.add(hold);
} }
} }
// run as system as we can't be sure if have remove aspect rights on node // run as system as we can't be sure if have remove aspect rights on node
authenticationUtil.runAsSystem((RunAsWork<Void>) () -> { authenticationUtil.runAsSystem((RunAsWork<Void>) () ->
{
removeFreezeAspect(nodeRef, 0); removeFreezeAspect(nodeRef, 0);
return null; return null;
}); });
for (NodeRef removedHold : removedHolds)
{
// fire on remove from hold policy
invokeOnRemoveFromHold(removedHold, nodeRef);
}
} }
} }

View File

@@ -26,10 +26,13 @@
*/ */
package org.alfresco.module.org_alfresco_module_rm.util; package org.alfresco.module.org_alfresco_module_rm.util;
import static java.util.Collections.unmodifiableList;
import java.io.Serializable; import java.io.Serializable;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.QName;
@@ -46,61 +49,98 @@ public class PropertyModificationAllowedCheck
*/ */
private List<QName> whiteList; private List<QName> whiteList;
/**
* List of model URI's for which the properties can be updated
*/
private List<String> editableURIs;
/**
* Getter for list of model URI's
*
* @return return the list of model URI's
*/
private List<String> getEditableURIs()
{
return unmodifiableList(editableURIs);
}
/**
* Setter for list of model URI's
*
* @param editableURIs List<String>
*/
public void setEditableURIs(List<String> editableURIs)
{
this.editableURIs = unmodifiableList(editableURIs);
}
/** /**
* Setter for list of qnames * Setter for list of qnames
*
* @param whiteList List<QName> * @param whiteList List<QName>
*/ */
public void setWhiteList(List<QName> whiteList) public void setWhiteList(List<QName> whiteList)
{ {
this.whiteList = whiteList; this.whiteList = unmodifiableList(whiteList);
} }
/** /**
* Compares the node properties with the requested update to make sure all potential updates are permitted * Compares the node properties with the requested update to make sure all potential updates are permitted
*
* @param before current node properties * @param before current node properties
* @param after updated properties for the node * @param after updated properties for the node
* @return true - if all modified property keys are in the whitelist * @return true - if all modified property keys are in the whitelist or
* in the list of model URI's for which the properties can be modified
*/ */
public boolean check(Map<QName, Serializable> before, Map<QName, Serializable> after) public boolean check(Map<QName, Serializable> before, Map<QName, Serializable> after)
{ {
boolean proceed = true; boolean proceed = true;
HashSet<QName> unionKeys = new HashSet<>(before.keySet()); // Initially check for changes to existing keys and values.
unionKeys.addAll(after.keySet()); for (final Map.Entry<QName, Serializable> entry : before.entrySet())
for (QName key : unionKeys)
{ {
//Check if property has been added or removed final QName key = entry.getKey();
if (!before.containsKey(key) || !after.containsKey(key)) final Serializable beforeValue = entry.getValue();
//check if property has been updated
final boolean modified = after.containsKey(key) && after.get(key) != null
&& !after.get(key).equals(beforeValue);
//check if the property has been emptied or removed
final boolean propertyRemovedEmptied = (after.get(key) == null && beforeValue != null)
|| !after.containsKey(key);
if (modified || propertyRemovedEmptied)
{ {
//Property modified check to see if allowed proceed = allowPropertyUpdate(key);
proceed = whiteList.contains(key);
if (!proceed)
{
break;
}
} }
//Check if property emptied or empty property filled if (!proceed)
if ((before.get(key) == null && after.get(key) != null) ||
(after.get(key) == null && before.get(key) != null))
{ {
//Property modified check to see if allowed return proceed;
proceed = whiteList.contains(key);
if (!proceed)
{
break;
}
} }
//If properties aren't missing or empty check equality }
if (before.get(key) != null && after.get(key) != null && !(after.get(key).equals(before.get(key))))
// Check for new values. Record individual values and group as a single map.
final Set<QName> newKeys = new HashSet<>(after.keySet());
newKeys.removeAll(before.keySet());
for (final QName key : newKeys)
{
proceed = allowPropertyUpdate(key);
if (!proceed)
{ {
//Property modified check to see if allowed break;
proceed = whiteList.contains(key);
if (!proceed)
{
break;
}
} }
} }
return proceed; return proceed;
} }
/**
* Determines whether the property should be allowed to be updated or not.
*
* @param key property
* @return true if property update is allowed
*/
private boolean allowPropertyUpdate(QName key)
{
return whiteList.contains(key) || getEditableURIs().contains(key.getNamespaceURI());
}
} }

View File

@@ -87,7 +87,7 @@ public class CreateHoldAuditEventUnitTest extends BaseUnitTest
@Test @Test
public void testCreateHoldCausesAuditEvent() public void testCreateHoldCausesAuditEvent()
{ {
createHoldAuditEvent.onCreateHold(childAssociationRef); createHoldAuditEvent.onCreateNode(childAssociationRef);
verify(mockedRecordsManagementAuditService, times(1)).auditEvent(eq(holdNodeRef), any(String.class), isNull(Map.class), any(Map.class)); verify(mockedRecordsManagementAuditService, times(1)).auditEvent(eq(holdNodeRef), any(String.class), isNull(Map.class), any(Map.class));
} }
} }

View File

@@ -82,7 +82,7 @@ public class DeleteHoldAuditEventUnitTest extends BaseUnitTest
@Test @Test
public void testDeleteHoldCausesAuditEvent() public void testDeleteHoldCausesAuditEvent()
{ {
deleteHoldAuditEvent.beforeDeleteHold(holdNodeRef); deleteHoldAuditEvent.beforeDeleteNode(holdNodeRef);
verify(mockedRecordsManagementAuditService, times(1)) verify(mockedRecordsManagementAuditService, times(1))
.auditEvent(eq(holdNodeRef), any(String.class), any(Map.class), isNull(Map.class), Matchers.eq(true), Matchers.eq(false)); .auditEvent(eq(holdNodeRef), any(String.class), any(Map.class), isNull(Map.class), Matchers.eq(true), Matchers.eq(false));
} }

View File

@@ -58,6 +58,7 @@ public class PropertyModificationAllowedCheckUnitTest
private QName qName, qName2; private QName qName, qName2;
private List<QName> list; private List<QName> list;
private List<String> editableURIs;
@Mock @Mock
private Serializable serializable, serializable2; private Serializable serializable, serializable2;
@@ -67,13 +68,16 @@ public class PropertyModificationAllowedCheckUnitTest
{ {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
propertyModificationAllowedCheck = new PropertyModificationAllowedCheck(); propertyModificationAllowedCheck = new PropertyModificationAllowedCheck();
before = new HashMap(); before = new HashMap();
after = new HashMap(); after = new HashMap();
qName = QName.createQName("foo","bar"); qName = QName.createQName("foo", "bar");
qName2 = QName.createQName("bar", "foo"); qName2 = QName.createQName("bar", "foo");
before.put(qName, serializable); before.put(qName, serializable);
after.put(qName, serializable2); after.put(qName, serializable2);
list = new ArrayList(); list = new ArrayList();
editableURIs = new ArrayList<>();
propertyModificationAllowedCheck.setWhiteList(list);
propertyModificationAllowedCheck.setEditableURIs(editableURIs);
} }
/** /**
@@ -232,4 +236,26 @@ public class PropertyModificationAllowedCheckUnitTest
after.put(qName, null); after.put(qName, null);
assertTrue(propertyModificationAllowedCheck.check(before, after)); assertTrue(propertyModificationAllowedCheck.check(before, after));
} }
/**
* Test update of a property from the model URI for which properties can be updated
*/
@Test
public void testUpdatePropertyFromAllowedModelURI()
{
editableURIs.add("foo");
propertyModificationAllowedCheck.setEditableURIs(editableURIs);
assertTrue(propertyModificationAllowedCheck.check(before, after));
}
/**
* Test update of a property that is not in the model URI for which properties can be updated
*/
@Test
public void testUpdatePropertyFromNotAllowedModelURI()
{
editableURIs.add("bar");
propertyModificationAllowedCheck.setEditableURIs(editableURIs);
assertFalse(propertyModificationAllowedCheck.check(before, after));
}
} }