diff --git a/rm-server/config/alfresco/module/org_alfresco_module_rm/capability/groups/rm-capability-groups-context.xml b/rm-server/config/alfresco/module/org_alfresco_module_rm/capability/groups/rm-capability-groups-context.xml index 5e28301e6b..dc769ec8ad 100644 --- a/rm-server/config/alfresco/module/org_alfresco_module_rm/capability/groups/rm-capability-groups-context.xml +++ b/rm-server/config/alfresco/module/org_alfresco_module_rm/capability/groups/rm-capability-groups-context.xml @@ -58,12 +58,6 @@ - - - - - diff --git a/rm-server/config/alfresco/module/org_alfresco_module_rm/capability/rm-capabilities-record-context.xml b/rm-server/config/alfresco/module/org_alfresco_module_rm/capability/rm-capabilities-record-context.xml index 2b1e0a57df..e157a13884 100644 --- a/rm-server/config/alfresco/module/org_alfresco_module_rm/capability/rm-capabilities-record-context.xml +++ b/rm-server/config/alfresco/module/org_alfresco_module_rm/capability/rm-capabilities-record-context.xml @@ -183,35 +183,38 @@ - - + - - + parent="declarativeCapability"> + + + RECORD + + - + + + + - + - + @@ -228,7 +231,7 @@ - + @@ -246,7 +249,7 @@ - + diff --git a/rm-server/config/alfresco/module/org_alfresco_module_rm/model/recordsModel.xml b/rm-server/config/alfresco/module/org_alfresco_module_rm/model/recordsModel.xml index d555544eda..3f93a56a23 100644 --- a/rm-server/config/alfresco/module/org_alfresco_module_rm/model/recordsModel.xml +++ b/rm-server/config/alfresco/module/org_alfresco_module_rm/model/recordsModel.xml @@ -255,7 +255,6 @@ rma:recordComponentIdentifier rma:commonRecordDetails rma:filePlanComponent - @@ -590,6 +589,7 @@ Root node reference d:noderef + true @@ -659,6 +659,7 @@ Record Component Identifier d:text + true true true @@ -837,9 +838,11 @@ d:any + true d:any + true @@ -857,6 +860,7 @@ Indicates whether a notification that this record is due for review has been issued d:boolean + true false false @@ -1025,9 +1029,11 @@ d:boolean + true d:text + true true false @@ -1036,37 +1042,43 @@ d:date + true - d:text - + d:text + true + true false false - + - d:text - + d:text + true + true false false - + - d:boolean + d:boolean + true - d:text - true - + d:text + true + true + true false false - + - d:text + d:text + true true false @@ -1075,12 +1087,15 @@ d:text + true d:text + true d:text + true true false @@ -1089,6 +1104,7 @@ d:text + true true false @@ -1147,13 +1163,16 @@ The originating details of a record - d:text + d:text + true - d:date + d:date + true - d:any + d:any + true @@ -1163,13 +1182,16 @@ The rejection details of a record - d:text + d:text + true - d:date + d:date + true - d:text + d:text + true diff --git a/rm-server/config/alfresco/module/org_alfresco_module_rm/rm-action-context.xml b/rm-server/config/alfresco/module/org_alfresco_module_rm/rm-action-context.xml index 6b5daa05a0..023fee610c 100644 --- a/rm-server/config/alfresco/module/org_alfresco_module_rm/rm-action-context.xml +++ b/rm-server/config/alfresco/module/org_alfresco_module_rm/rm-action-context.xml @@ -481,8 +481,11 @@ - + + + diff --git a/rm-server/config/alfresco/module/org_alfresco_module_rm/rm-service-context.xml b/rm-server/config/alfresco/module/org_alfresco_module_rm/rm-service-context.xml index 25bf34ea39..8cfa80df47 100644 --- a/rm-server/config/alfresco/module/org_alfresco_module_rm/rm-service-context.xml +++ b/rm-server/config/alfresco/module/org_alfresco_module_rm/rm-service-context.xml @@ -1101,6 +1101,7 @@ + @@ -1149,6 +1150,7 @@ org.alfresco.module.org_alfresco_module_rm.record.RecordService.isFiled=RM.Read.0 org.alfresco.module.org_alfresco_module_rm.record.RecordService.createRecord=RM_ALLOW org.alfresco.module.org_alfresco_module_rm.record.RecordService.hideRecord=RM_ALLOW + org.alfresco.module.org_alfresco_module_rm.record.RecordService.isPropertyEditable=RM.Read.0 org.alfresco.module.org_alfresco_module_rm.record.RecordService.*=RM_DENY ]]> diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/action/impl/DeclareRecordAction.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/action/impl/DeclareRecordAction.java index 13a2a172ea..ab98c0b4d1 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/action/impl/DeclareRecordAction.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/action/impl/DeclareRecordAction.java @@ -30,6 +30,7 @@ import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.module.org_alfresco_module_rm.action.RMActionExecuterAbstractBase; import org.alfresco.repo.action.executer.ActionExecuterAbstractBase; import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.service.cmr.action.Action; import org.alfresco.service.cmr.dictionary.AspectDefinition; import org.alfresco.service.cmr.dictionary.PropertyDefinition; @@ -59,7 +60,7 @@ public class DeclareRecordAction extends RMActionExecuterAbstractBase * @see org.alfresco.repo.action.executer.ActionExecuterAbstractBase#executeImpl(org.alfresco.service.cmr.action.Action, org.alfresco.service.cmr.repository.NodeRef) */ @Override - protected void executeImpl(Action action, NodeRef actionedUponNodeRef) + protected void executeImpl(final Action action, final NodeRef actionedUponNodeRef) { if (recordService.isRecord(actionedUponNodeRef) == true) { @@ -75,8 +76,16 @@ public class DeclareRecordAction extends RMActionExecuterAbstractBase declaredProps.put(PROP_DECLARED_BY, AuthenticationUtil.getRunAsUser()); this.nodeService.addAspect(actionedUponNodeRef, ASPECT_DECLARED_RECORD, declaredProps); - // remove all owner related rights - this.ownableService.setOwner(actionedUponNodeRef, OwnableService.NO_OWNER); + AuthenticationUtil.runAsSystem(new RunAsWork() + { + @Override + public Void doWork() throws Exception + { + // remove all owner related rights + ownableService.setOwner(actionedUponNodeRef, OwnableService.NO_OWNER); + return null; + } + }); } else { diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/capability/impl/EditCapability.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/capability/impl/EditCapability.java deleted file mode 100644 index 79ebd5df7d..0000000000 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/capability/impl/EditCapability.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (C) 2005-2011 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.module.org_alfresco_module_rm.capability.impl; - -import net.sf.acegisecurity.vote.AccessDecisionVoter; - -import org.alfresco.module.org_alfresco_module_rm.capability.declarative.DeclarativeCapability; -import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.security.OwnableService; - -/** - * Edit capability, checks the permission and whether the current user is the owner of the - * object. - * - * @author Roy Wetherall - */ -public class EditCapability extends DeclarativeCapability -{ - private OwnableService ownableService; - - private OwnableService getOwnableService() - { - if (ownableService == null) - { - ownableService = (OwnableService)applicationContext.getBean("OwnableService"); - } - return ownableService; - } - - /** - * @see org.alfresco.module.org_alfresco_module_rm.capability.declarative.DeclarativeCapability#evaluate(org.alfresco.service.cmr.repository.NodeRef) - */ - public int evaluate(final NodeRef nodeRef) - { - // The default state is not knowing anything - int result = AccessDecisionVoter.ACCESS_ABSTAIN; - - // Check we are dealing with a file plan component - if (rmService.isFilePlanComponent(nodeRef) == true) - { - // Now the default state is denied - result = AccessDecisionVoter.ACCESS_DENIED; - - // Check the kind of the object, the permissions and the conditions - if (checkKinds(nodeRef) == true && checkConditions(nodeRef) == true) - { - if (checkPermissions(nodeRef) == true) - { - result = AccessDecisionVoter.ACCESS_GRANTED; - } - else - { - result = AuthenticationUtil.runAs(new RunAsWork() - { - @Override - public Integer doWork() throws Exception - { - Integer result = Integer.valueOf(AccessDecisionVoter.ACCESS_DENIED); - - // Since we know this is undeclared if you are the owner then you should be able to - // edit the records meta-data (otherwise how can it be declared by the user?) - if (getOwnableService().hasOwner(nodeRef) == true) - { - String user = AuthenticationUtil.getFullyAuthenticatedUser(); - if (user != null && - getOwnableService().getOwner(nodeRef).equals(user) == true) - { - result = Integer.valueOf(AccessDecisionVoter.ACCESS_GRANTED); - } - } - - return result; - } - - }, AuthenticationUtil.getSystemUserName()).intValue(); - } - } - } - - return result; - } -} \ No newline at end of file diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/forms/RecordsManagementNodeFormFilter.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/forms/RecordsManagementNodeFormFilter.java index c25756bcef..7379f3138d 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/forms/RecordsManagementNodeFormFilter.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/forms/RecordsManagementNodeFormFilter.java @@ -38,6 +38,7 @@ import org.alfresco.repo.forms.Form; import org.alfresco.repo.forms.PropertyFieldDefinition; import org.alfresco.repo.forms.processor.node.FieldUtils; import org.alfresco.repo.forms.processor.node.FormFieldConstants; +import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.dictionary.PropertyDefinition; @@ -91,9 +92,10 @@ public class RecordsManagementNodeFormFilter extends RecordsManagementFormFilter this.dispositionService = dispositionService; } - /* + /** * @see org.alfresco.repo.forms.processor.Filter#afterGenerate(java.lang.Object, java.util.List, java.util.List, org.alfresco.repo.forms.Form, java.util.Map) */ + @Override public void afterGenerate( NodeRef nodeRef, List fields, @@ -118,6 +120,9 @@ public class RecordsManagementNodeFormFilter extends RecordsManagementFormFilter // add the supplemental marking list property forceSupplementalMarkingListProperty(form, nodeRef); + // protect uneditable properties + protectRecordProperties(form, nodeRef); + // if the record is the result of an email we need to 'protect' some fields if (this.nodeService.hasAspect(nodeRef, ImapModel.ASPECT_IMAP_CONTENT)) { @@ -147,6 +152,11 @@ public class RecordsManagementNodeFormFilter extends RecordsManagementFormFilter } } + /** + * + * @param form + * @param nodeRef + */ protected void addCustomPropertyFieldsToGroup(Form form, NodeRef nodeRef) { Set customisables = rmAdminService.getCustomisable(nodeRef); @@ -165,6 +175,11 @@ public class RecordsManagementNodeFormFilter extends RecordsManagementFormFilter } } + /** + * + * @param form + * @param nodeRef + */ protected void addRecordMetadataPropertyFieldsToGroup(Form form, NodeRef nodeRef) { Set aspects = recordService.getRecordMetaDataAspects(); @@ -210,7 +225,11 @@ public class RecordsManagementNodeFormFilter extends RecordsManagementFormFilter } } - + /** + * + * @param form + * @param nodeRef + */ protected void addTransientProperties(Form form, NodeRef nodeRef) { if (recordService.isRecord(nodeRef) == true) @@ -239,6 +258,13 @@ public class RecordsManagementNodeFormFilter extends RecordsManagementFormFilter } } + /** + * + * @param form + * @param name + * @param type + * @param value + */ protected void addTransientPropertyField(Form form, String name, QName type, Object value) { String dataKeyName = FormFieldConstants.PROP_DATA_PREFIX + name; @@ -251,6 +277,47 @@ public class RecordsManagementNodeFormFilter extends RecordsManagementFormFilter form.addData(dataKeyName, value); } + /** + * + * @param form + * @param nodeRef + */ + protected void protectRecordProperties(Form form, NodeRef nodeRef) + { + List fieldDefs = form.getFieldDefinitions(); + for (FieldDefinition fieldDef : fieldDefs) + { + if (fieldDef.isProtectedField() == false) + { + String name = fieldDef.getName(); + String prefixName = null; + if ("size".equals(name) || "mimetype".equals(name) || "encoding".equals(name)) + { + prefixName = "cm:content"; + } + else + { + prefixName = fieldDef.getName(); + } + + if (logger.isDebugEnabled() == true) + { + logger.debug("Checking property " + prefixName + " is editable by user " + AuthenticationUtil.getFullyAuthenticatedUser()); + } + + QName qname = QName.createQName(prefixName, namespaceService); + if (recordService.isPropertyEditable(nodeRef, qname) == false) + { + if (logger.isDebugEnabled() == true) + { + logger.debug(" ... protected property"); + } + fieldDef.setProtectedField(true); + } + } + } + } + /** * Marks all the fields that contain data extracted from an email * as protected fields. diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/model/behaviour/RecordContainerType.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/model/behaviour/RecordContainerType.java index 3f34e5deed..d4ed2e9483 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/model/behaviour/RecordContainerType.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/model/behaviour/RecordContainerType.java @@ -128,7 +128,7 @@ public class RecordContainerType implements RecordsManagementModel, @Override public void onCreateChildAssociation(final ChildAssociationRef childAssocRef, boolean isNewNode) { - filePlanAuthenticationService.runAsRmAdmin(new RunAsWork() + AuthenticationUtil.runAsSystem(new RunAsWork() { @Override public Void doWork() throws Exception diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/model/security/ProtectedModelArtifact.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/model/security/ProtectedModelArtifact.java index 662cbaf9ca..a8b6111c83 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/model/security/ProtectedModelArtifact.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/model/security/ProtectedModelArtifact.java @@ -45,44 +45,69 @@ public abstract class ProtectedModelArtifact /** Set of capabilities */ private Set capabilities; + /** Capability names */ private Set capabilityNames; + /** + * @param namespaceService namespace service + */ public void setNamespaceService(NamespaceService namespaceService) { this.namespaceService = namespaceService; } + /** + * @param modelSecurityService model security service + */ public void setModelSecurityService(ModelSecurityService modelSecurityService) { this.modelSecurityService = modelSecurityService; } + /** + * Init method + */ public void init() { modelSecurityService.register(this); } + /** + * @param name artifact name (in cm:content form) + */ public void setName(String name) { QName qname = QName.createQName(name, namespaceService); this.name = qname; } + /** + * @return artifact QName + */ public QName getQName() { return name; } + /** + * @param capabilities capabilities + */ public void setCapabilities(Set capabilities) { this.capabilities = capabilities; } + /** + * @return capabilities + */ public Set getCapabilities() { return capabilities; } + /** + * @return capability names + */ public Set getCapilityNames() { if (capabilityNames == null && capabilities != null) diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/record/RecordService.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/record/RecordService.java index 8782d552af..9f1d9ac273 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/record/RecordService.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/record/RecordService.java @@ -96,4 +96,13 @@ public interface RecordService * @param reason The reason for rejection */ void rejectRecord(NodeRef nodeRef, String reason); + + /** + * Indicates whether a property of a record is editable for the current user or not. + * + * @param record record + * @param property property + * @return boolean true if editable, false otherwise. + */ + boolean isPropertyEditable(NodeRef record, QName property); } diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/record/RecordServiceImpl.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/record/RecordServiceImpl.java index 68b32e05b4..f472f555de 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/record/RecordServiceImpl.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/record/RecordServiceImpl.java @@ -31,11 +31,16 @@ import java.util.Set; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; import org.alfresco.module.org_alfresco_module_rm.RecordsManagementService; +import org.alfresco.module.org_alfresco_module_rm.capability.CapabilityService; +import org.alfresco.module.org_alfresco_module_rm.capability.RMPermissionModel; import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionSchedule; import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionService; +import org.alfresco.module.org_alfresco_module_rm.dod5015.DOD5015Model; import org.alfresco.module.org_alfresco_module_rm.fileplan.FilePlanService; import org.alfresco.module.org_alfresco_module_rm.identifier.IdentifierService; +import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementCustomModel; import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel; +import org.alfresco.module.org_alfresco_module_rm.model.security.ModelAccessDeniedException; import org.alfresco.module.org_alfresco_module_rm.notification.RecordsManagementNotificationHelper; import org.alfresco.module.org_alfresco_module_rm.security.ExtendedSecurityService; import org.alfresco.module.org_alfresco_module_rm.vital.VitalRecordServiceImpl; @@ -48,31 +53,80 @@ import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.security.permissions.AccessDeniedException; import org.alfresco.repo.security.permissions.impl.ExtendedPermissionService; import org.alfresco.service.cmr.dictionary.AspectDefinition; +import org.alfresco.service.cmr.dictionary.ClassDefinition; import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.dictionary.PropertyDefinition; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.security.AccessStatus; import org.alfresco.service.cmr.security.OwnableService; import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; +import org.alfresco.util.EqualsHelper; import org.alfresco.util.ParameterCheck; +import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; /** - * Record service implementation + * Record service implementation. * * @author Roy Wetherall * @since 2.1 */ public class RecordServiceImpl implements RecordService, RecordsManagementModel, + RecordsManagementCustomModel, NodeServicePolicies.OnCreateChildAssociationPolicy, + NodeServicePolicies.OnUpdatePropertiesPolicy, ApplicationContextAware { + /** Logger */ + private static Log logger = LogFactory.getLog(RecordServiceImpl.class); + + /** Always edit property array */ + private static final QName[] ALWAYS_EDIT_PROPERTIES = new QName[] + { + ContentModel.PROP_LAST_THUMBNAIL_MODIFICATION_DATA + }; + + private static final String[] ALWAYS_EDIT_URIS = new String[] + { + NamespaceService.SECURITY_MODEL_1_0_URI, + NamespaceService.SYSTEM_MODEL_1_0_URI, + NamespaceService.WORKFLOW_MODEL_1_0_URI, + NamespaceService.APP_MODEL_1_0_URI, + NamespaceService.DATALIST_MODEL_1_0_URI, + NamespaceService.DICTIONARY_MODEL_1_0_URI, + NamespaceService.BPM_MODEL_1_0_URI, + NamespaceService.RENDITION_MODEL_1_0_URI + }; + + private static final String[] RECORD_MODEL_URIS = new String[] + { + RM_URI, + RM_CUSTOM_URI, + DOD5015Model.DOD_URI + }; + + private static final String[] NON_RECORD_MODEL_URIS = new String[] + { + NamespaceService.AUDIO_MODEL_1_0_URI, + NamespaceService.CONTENT_MODEL_1_0_URI, + NamespaceService.EMAILSERVER_MODEL_URI, + NamespaceService.EXIF_MODEL_1_0_URI, + NamespaceService.FORUMS_MODEL_1_0_URI, + NamespaceService.LINKS_MODEL_1_0_URI, + NamespaceService.REPOSITORY_VIEW_1_0_URI + + }; + /** Application context */ private ApplicationContext applicationContext; @@ -108,15 +162,22 @@ public class RecordServiceImpl implements RecordService, /** Ownable service */ private OwnableService ownableService; + + /** Capability service */ + private CapabilityService capabilityService; /** List of available record meta-data aspects */ - private Set recordMetaDataAspects; + private Set recordMetaDataAspects; /** Behaviours */ private JavaBehaviour onCreateChildAssociation = new JavaBehaviour( this, "onCreateChildAssociation", NotificationFrequency.FIRST_EVENT); + private JavaBehaviour onUpdateProperties = new JavaBehaviour( + this, + "onUpdateProperties", + NotificationFrequency.EVERY_EVENT); @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException @@ -211,6 +272,14 @@ public class RecordServiceImpl implements RecordService, { this.ownableService = ownableService; } + + /** + * @param capabilityService capability service + */ + public void setCapabilityService(CapabilityService capabilityService) + { + this.capabilityService = capabilityService; + } /** * Init method @@ -222,6 +291,10 @@ public class RecordServiceImpl implements RecordService, TYPE_RECORD_FOLDER, ContentModel.ASSOC_CONTAINS, onCreateChildAssociation); + policyComponent.bindClassBehaviour( + NodeServicePolicies.OnUpdatePropertiesPolicy.QNAME, + ASPECT_RECORD, + onUpdateProperties); } /** @@ -239,6 +312,56 @@ public class RecordServiceImpl implements RecordService, file(nodeRef); } } + + /** + * Ensure that the user only updates record properties that they have permission to. + * + * @see org.alfresco.repo.node.NodeServicePolicies.OnUpdatePropertiesPolicy#onUpdateProperties(org.alfresco.service.cmr.repository.NodeRef, java.util.Map, java.util.Map) + */ + @Override + public void onUpdateProperties(final NodeRef nodeRef, final Map before, final Map after) + { + onUpdateProperties.disable(); + try + { + if (AuthenticationUtil.getFullyAuthenticatedUser() != null && + AuthenticationUtil.isRunAsUserTheSystemUser() == false && + nodeService.exists(nodeRef) == true) + { + if (isRecord(nodeRef) == true) + { + for (QName property : after.keySet()) + { + Serializable beforeValue = null; + if (before != null) + { + beforeValue = before.get(property); + } + + Serializable afterValue = null; + if (after != null) + { + afterValue = after.get(property); + } + + if (EqualsHelper.nullSafeEquals(beforeValue, afterValue) == false && + isPropertyEditable(nodeRef, property) == false) + { + // the user can't edit the record property + throw new ModelAccessDeniedException( + "The user " + AuthenticationUtil.getFullyAuthenticatedUser() + + " does not have the permission to edit the record property " + property.toString() + + " on the node " + nodeRef.toString()); + } + } + } + } + } + finally + { + onUpdateProperties.enable(); + } + } /** * @see org.alfresco.module.org_alfresco_module_rm.record.RecordService#getRecordMetaDataAspects() @@ -579,4 +702,120 @@ public class RecordServiceImpl implements RecordService, } }); } + + /** + * @see org.alfresco.module.org_alfresco_module_rm.record.RecordService#isPropertyEditable(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.namespace.QName) + */ + @Override + public boolean isPropertyEditable(NodeRef record, QName property) + { + ParameterCheck.mandatory("record", record); + ParameterCheck.mandatory("property", property); + + if (isRecord(record) == false) + { + throw new AlfrescoRuntimeException("Can not check if the property " + property.toString() + " is editable, because node reference is not a record."); + } + + if (logger.isDebugEnabled() == true) + { + logger.debug("Checking whether property " + property.toString() + " is editable for user " + AuthenticationUtil.getRunAsUser()); + } + + boolean result = alwaysEditProperty(property); + if (result == false) + { + boolean allowRecordEdit = false; + boolean allowNonRecordEdit = false; + + AccessStatus accessNonRecord = capabilityService.getCapabilityAccessState(record, RMPermissionModel.EDIT_NON_RECORD_METADATA); + AccessStatus accessDeclaredRecord = capabilityService.getCapabilityAccessState(record, RMPermissionModel.EDIT_DECLARED_RECORD_METADATA); + AccessStatus accessRecord = capabilityService.getCapabilityAccessState(record, RMPermissionModel.EDIT_RECORD_METADATA); + + if (AccessStatus.ALLOWED.equals(accessNonRecord) == true) + { + allowNonRecordEdit = true; + } + + if (AccessStatus.ALLOWED.equals(accessRecord) == true || + AccessStatus.ALLOWED.equals(accessDeclaredRecord) == true) + { + allowRecordEdit = true; + } + + if (allowNonRecordEdit == true && allowRecordEdit == true) + { + result = true; + } + else if (allowNonRecordEdit == true && allowRecordEdit == false) + { + // can only edit non record properties + if (isRecordMetadata(property) == false) + { + result = true; + } + } + else if (allowNonRecordEdit == false && allowRecordEdit == true) + { + // can only edit record properties + if (isRecordMetadata(property) == true) + { + result = true; + } + } + // otherwise we can't edit any properties so just return the empty set + } + return result; + } + + private boolean isRecordMetadata(QName property) + { + boolean result = ArrayUtils.contains(RECORD_MODEL_URIS, property.getNamespaceURI()); + + if (result == false && ArrayUtils.contains(NON_RECORD_MODEL_URIS, property.getNamespaceURI()) == false) + { + PropertyDefinition def = dictionaryService.getProperty(property); + if (def != null) + { + ClassDefinition parent = def.getContainerClass(); + if (parent != null && parent.isAspect() == true) + { + result = getRecordMetaDataAspects().contains(parent.getName()); + } + } + } + + return result; + } + + /** + * Determines whether the property should always be allowed to be edited or not. + * + * @param property + * @return + */ + private boolean alwaysEditProperty(QName property) + { + return (ArrayUtils.contains(ALWAYS_EDIT_URIS, property.getNamespaceURI()) || + ArrayUtils.contains(ALWAYS_EDIT_PROPERTIES, property) || + isProtectedProperty(property)); + } + + /** + * Helper method to determine whether a property is protected at a dictionary definition + * level. + * + * @param property property qualified name + * @return booelan true if protected, false otherwise + */ + private boolean isProtectedProperty(QName property) + { + boolean result = false; + PropertyDefinition def = dictionaryService.getProperty(property); + if (def != null) + { + result = def.isProtected(); + } + return result; + } } diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/vital/BroadcastVitalRecordDefinitionAction.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/vital/BroadcastVitalRecordDefinitionAction.java index a85e5a51c7..8cc636827f 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/vital/BroadcastVitalRecordDefinitionAction.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/vital/BroadcastVitalRecordDefinitionAction.java @@ -26,6 +26,8 @@ import java.util.Map; import org.alfresco.model.ContentModel; import org.alfresco.module.org_alfresco_module_rm.action.RMActionExecuterAbstractBase; import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel; +import org.alfresco.module.org_alfresco_module_rm.security.FilePlanAuthenticationService; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.service.cmr.action.Action; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; @@ -43,14 +45,29 @@ import org.alfresco.service.namespace.RegexQNamePattern; */ public class BroadcastVitalRecordDefinitionAction extends RMActionExecuterAbstractBase { + private FilePlanAuthenticationService filePlanAuthenticationService; + + public void setFilePlanAuthenticationService(FilePlanAuthenticationService filePlanAuthenticationService) + { + this.filePlanAuthenticationService = filePlanAuthenticationService; + } + /** * @see org.alfresco.repo.action.executer.ActionExecuterAbstractBase#executeImpl(org.alfresco.service.cmr.action.Action, * org.alfresco.service.cmr.repository.NodeRef) */ @Override - protected void executeImpl(Action action, NodeRef actionedUponNodeRef) + protected void executeImpl(Action action, final NodeRef actionedUponNodeRef) { - this.propagateChangeToChildrenOf(actionedUponNodeRef); + filePlanAuthenticationService.runAsRmAdmin(new RunAsWork() + { + @Override + public Void doWork() throws Exception + { + propagateChangeToChildrenOf(actionedUponNodeRef); + return null; + } + }); } /** diff --git a/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/service/RecordServiceImplTest.java b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/service/RecordServiceImplTest.java index bd4b1895a8..141d704655 100644 --- a/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/service/RecordServiceImplTest.java +++ b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/service/RecordServiceImplTest.java @@ -22,10 +22,12 @@ import java.util.Arrays; import java.util.List; import java.util.Set; +import org.alfresco.model.ContentModel; import org.alfresco.module.org_alfresco_module_rm.capability.Capability; import org.alfresco.module.org_alfresco_module_rm.capability.RMPermissionModel; import org.alfresco.module.org_alfresco_module_rm.dod5015.DOD5015Model; import org.alfresco.module.org_alfresco_module_rm.record.RecordService; +import org.alfresco.module.org_alfresco_module_rm.role.Role; import org.alfresco.module.org_alfresco_module_rm.security.ExtendedSecurityService; import org.alfresco.module.org_alfresco_module_rm.test.util.BaseRMTestCase; import org.alfresco.repo.content.MimetypeMap; @@ -37,10 +39,11 @@ import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.security.AccessStatus; import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.namespace.QName; +import org.alfresco.util.GUID; /** * Records Service Implementation Test - * + * * @author Roy Wetherall * @author Tuna Aksoy * @since 2.1 @@ -49,7 +52,9 @@ public class RecordServiceImplTest extends BaseRMTestCase { /** Services */ protected ActionService dmActionService; + protected PermissionService dmPermissionService; + protected ExtendedSecurityService extendedSecurityService; /** @@ -67,7 +72,7 @@ public class RecordServiceImplTest extends BaseRMTestCase /** * This is a user test - * + * * @see org.alfresco.module.org_alfresco_module_rm.test.util.BaseRMTestCase#isUserTest() */ @Override @@ -78,7 +83,7 @@ public class RecordServiceImplTest extends BaseRMTestCase /** * This is a record test - * + * * @see org.alfresco.module.org_alfresco_module_rm.test.util.BaseRMTestCase#isRecordTest() */ @Override @@ -116,19 +121,14 @@ public class RecordServiceImplTest extends BaseRMTestCase /** * Helper method for getting a list of record meta data aspects - * + * * @return Record meta data aspects as list */ private List getAspectList() { - QName[] aspects = new QName[] - { - DOD5015Model.ASPECT_DIGITAL_PHOTOGRAPH_RECORD, - DOD5015Model.ASPECT_PDF_RECORD, - DOD5015Model.ASPECT_WEB_RECORD, - DOD5015Model.ASPECT_SCANNED_RECORD, - ASPECT_RECORD_META_DATA - }; + QName[] aspects = new QName[] { DOD5015Model.ASPECT_DIGITAL_PHOTOGRAPH_RECORD, + DOD5015Model.ASPECT_PDF_RECORD, DOD5015Model.ASPECT_WEB_RECORD, + DOD5015Model.ASPECT_SCANNED_RECORD, ASPECT_RECORD_META_DATA }; return Arrays.asList(aspects); } @@ -190,22 +190,21 @@ public class RecordServiceImplTest extends BaseRMTestCase } /** - * @see RecordService#createRecord(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.repository.NodeRef) + * @see RecordService#createRecord(org.alfresco.service.cmr.repository.NodeRef, + * org.alfresco.service.cmr.repository.NodeRef) */ public void testCreateRecord() throws Exception { // show that users without WRITE can not create a record from a document - doTestInTransaction(new FailureTest - ( - "Can not create a record from a document if you do not have WRITE permissions.", - AccessDeniedException.class - ) + doTestInTransaction(new FailureTest( + "Can not create a record from a document if you do not have WRITE permissions.", + AccessDeniedException.class) { public void run() throws Exception { recordService.createRecord(filePlan, dmDocument); } - }, dmConsumer); + }, dmConsumer); // create record from document doTestInTransaction(new Test() @@ -218,107 +217,108 @@ public class RecordServiceImplTest extends BaseRMTestCase assertFalse(recordService.isRecord(dmDocument)); assertFalse(extendedSecurityService.hasExtendedSecurity(dmDocument)); - checkPermissions(READ_RECORDS, - AccessStatus.DENIED, // file plan - AccessStatus.DENIED, // unfiled container - AccessStatus.DENIED, // record category - AccessStatus.DENIED, // record folder - AccessStatus.DENIED); // doc/record + checkPermissions(READ_RECORDS, AccessStatus.DENIED, // file plan + AccessStatus.DENIED, // unfiled container + AccessStatus.DENIED, // record category + AccessStatus.DENIED, // record folder + AccessStatus.DENIED); // doc/record - assertEquals(AccessStatus.DENIED, - dmPermissionService.hasPermission(filePlan, RMPermissionModel.VIEW_RECORDS)); + assertEquals(AccessStatus.DENIED, dmPermissionService.hasPermission(filePlan, + RMPermissionModel.VIEW_RECORDS)); - checkPermissions(FILING, - AccessStatus.DENIED, // file plan - AccessStatus.DENIED, // unfiled container - AccessStatus.DENIED, // record category - AccessStatus.DENIED, // record folder - AccessStatus.DENIED); // doc/record + checkPermissions(FILING, AccessStatus.DENIED, // file plan + AccessStatus.DENIED, // unfiled container + AccessStatus.DENIED, // record category + AccessStatus.DENIED, // record folder + AccessStatus.DENIED); // doc/record recordService.createRecord(filePlan, dmDocument); - checkPermissions(READ_RECORDS, - AccessStatus.ALLOWED, // file plan - AccessStatus.ALLOWED, // unfiled container - AccessStatus.DENIED, // record category - AccessStatus.DENIED, // record folder - AccessStatus.ALLOWED); // doc/record + checkPermissions(READ_RECORDS, AccessStatus.ALLOWED, // file + // plan + AccessStatus.ALLOWED, // unfiled container + AccessStatus.DENIED, // record category + AccessStatus.DENIED, // record folder + AccessStatus.ALLOWED); // doc/record - assertEquals(AccessStatus.ALLOWED, - dmPermissionService.hasPermission(filePlan, RMPermissionModel.VIEW_RECORDS)); + assertEquals(AccessStatus.ALLOWED, dmPermissionService.hasPermission(filePlan, + RMPermissionModel.VIEW_RECORDS)); - checkPermissions(FILING, - AccessStatus.DENIED, // file plan - AccessStatus.DENIED, // unfiled container - AccessStatus.DENIED, // record category - AccessStatus.DENIED, // record folder - AccessStatus.ALLOWED); // doc/record + checkPermissions(FILING, AccessStatus.DENIED, // file plan + AccessStatus.DENIED, // unfiled container + AccessStatus.DENIED, // record category + AccessStatus.DENIED, // record folder + AccessStatus.ALLOWED); // doc/record assertTrue(recordService.isRecord(dmDocument)); assertTrue(extendedSecurityService.hasExtendedSecurity(dmDocument)); assertFalse(recordService.isFiled(dmDocument)); - // show that the record has meta-data about it's original location + // show that the record has meta-data about it's original + // location assertTrue(nodeService.hasAspect(dmDocument, ASPECT_RECORD_ORIGINATING_DETAILS)); assertEquals(originalLocation, nodeService.getProperty(dmDocument, PROP_RECORD_ORIGINATING_LOCATION)); assertFalse(originalLocation == nodeService.getPrimaryParent(dmDocument).getParentRef()); // show that the record is linked to it's original location assertEquals(2, nodeService.getParentAssocs(dmDocument).size()); - + // **** // Capability Tests // **** - assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(filePlan, RMPermissionModel.VIEW_RECORDS)); - assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(filePlan, RMPermissionModel.EDIT_RECORD_METADATA)); - + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(filePlan, + RMPermissionModel.VIEW_RECORDS)); + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(filePlan, + RMPermissionModel.EDIT_RECORD_METADATA)); + Capability filling = capabilityService.getCapability("FileRecords"); assertEquals(AccessStatus.DENIED, filling.hasPermission(dmDocument)); - + Capability editRecordMetadata = capabilityService.getCapability("EditRecordMetadata"); assertEquals(AccessStatus.ALLOWED, editRecordMetadata.hasPermission(dmDocument)); - + Capability updateProperties = capabilityService.getCapability("UpdateProperties"); assertEquals(AccessStatus.ALLOWED, updateProperties.hasPermission(dmDocument)); return null; } }, dmCollaborator); - - // check the consumer's permissions are correct for the newly created document + + // check the consumer's permissions are correct for the newly created + // document doTestInTransaction(new Test() { @Override public Void run() { - checkPermissions(READ_RECORDS, - AccessStatus.ALLOWED, // file plan - AccessStatus.ALLOWED, // unfiled container - AccessStatus.DENIED, // record category - AccessStatus.DENIED, // record folder - AccessStatus.ALLOWED); // doc/record + checkPermissions(READ_RECORDS, AccessStatus.ALLOWED, // file + // plan + AccessStatus.ALLOWED, // unfiled container + AccessStatus.DENIED, // record category + AccessStatus.DENIED, // record folder + AccessStatus.ALLOWED); // doc/record + + checkPermissions(FILING, AccessStatus.DENIED, // file plan + AccessStatus.DENIED, // unfiled container + AccessStatus.DENIED, // record category + AccessStatus.DENIED, // record folder + AccessStatus.DENIED); // doc/record + + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(filePlan, + RMPermissionModel.VIEW_RECORDS)); + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(filePlan, + RMPermissionModel.EDIT_RECORD_METADATA)); - checkPermissions(FILING, - AccessStatus.DENIED, // file plan - AccessStatus.DENIED, // unfiled container - AccessStatus.DENIED, // record category - AccessStatus.DENIED, // record folder - AccessStatus.DENIED); // doc/record - - assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(filePlan, RMPermissionModel.VIEW_RECORDS)); - assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(filePlan, RMPermissionModel.EDIT_RECORD_METADATA)); - Capability filling = capabilityService.getCapability("FileRecords"); assertEquals(AccessStatus.DENIED, filling.hasPermission(dmDocument)); - + Capability editRecordMetadata = capabilityService.getCapability("EditRecordMetadata"); assertEquals(AccessStatus.DENIED, editRecordMetadata.hasPermission(dmDocument)); - + Capability updateProperties = capabilityService.getCapability("UpdateProperties"); assertEquals(AccessStatus.DENIED, updateProperties.hasPermission(dmDocument)); - return null; } }, dmConsumer); @@ -327,17 +327,15 @@ public class RecordServiceImplTest extends BaseRMTestCase public void testCreateRecordNoLink() throws Exception { // show that users without WRITE can not create a record from a document - doTestInTransaction(new FailureTest - ( - "Can not create a record from a document if you do not have WRITE permissions.", - AccessDeniedException.class - ) + doTestInTransaction(new FailureTest( + "Can not create a record from a document if you do not have WRITE permissions.", + AccessDeniedException.class) { public void run() throws Exception { recordService.createRecord(filePlan, dmDocument, false); } - }, dmConsumer); + }, dmConsumer); // create record from document final NodeRef originalLocation = doTestInTransaction(new Test() @@ -347,44 +345,40 @@ public class RecordServiceImplTest extends BaseRMTestCase { NodeRef originalLocation = nodeService.getPrimaryParent(dmDocument).getParentRef(); - assertFalse(recordService.isRecord(dmDocument)); - assertFalse(extendedSecurityService.hasExtendedSecurity(dmDocument)); + //assertFalse(recordService.isRecord(dmDocument)); + //assertFalse(extendedSecurityService.hasExtendedSecurity(dmDocument)); - checkPermissions(READ_RECORDS, - AccessStatus.DENIED, // file plan - AccessStatus.DENIED, // unfiled container - AccessStatus.DENIED, // record category - AccessStatus.DENIED, // record folder - AccessStatus.DENIED); // doc/record + checkPermissions(READ_RECORDS, AccessStatus.DENIED, // file plan + AccessStatus.DENIED, // unfiled container + AccessStatus.DENIED, // record category + AccessStatus.DENIED, // record folder + AccessStatus.DENIED); // doc/record - assertEquals(AccessStatus.DENIED, - dmPermissionService.hasPermission(filePlan, RMPermissionModel.VIEW_RECORDS)); + assertEquals(AccessStatus.DENIED, dmPermissionService.hasPermission(filePlan, + RMPermissionModel.VIEW_RECORDS)); - checkPermissions(FILING, - AccessStatus.DENIED, // file plan - AccessStatus.DENIED, // unfiled container - AccessStatus.DENIED, // record category - AccessStatus.DENIED, // record folder - AccessStatus.DENIED); // doc/record + checkPermissions(FILING, AccessStatus.DENIED, // file plan + AccessStatus.DENIED, // unfiled container + AccessStatus.DENIED, // record category + AccessStatus.DENIED, // record folder + AccessStatus.DENIED); // doc/record recordService.createRecord(filePlan, dmDocument, false); - checkPermissions(READ_RECORDS, - AccessStatus.DENIED, // file plan - AccessStatus.DENIED, // unfiled container - AccessStatus.DENIED, // record category - AccessStatus.DENIED, // record folder - AccessStatus.DENIED); // doc/record + checkPermissions(READ_RECORDS, AccessStatus.DENIED, // file plan + AccessStatus.DENIED, // unfiled container + AccessStatus.DENIED, // record category + AccessStatus.DENIED, // record folder + AccessStatus.DENIED); // doc/record - assertEquals(AccessStatus.DENIED, - dmPermissionService.hasPermission(filePlan, RMPermissionModel.VIEW_RECORDS)); + assertEquals(AccessStatus.DENIED, dmPermissionService.hasPermission(filePlan, + RMPermissionModel.VIEW_RECORDS)); - checkPermissions(FILING, - AccessStatus.DENIED, // file plan - AccessStatus.DENIED, // unfiled container - AccessStatus.DENIED, // record category - AccessStatus.DENIED, // record folder - AccessStatus.DENIED); // doc/record + checkPermissions(FILING, AccessStatus.DENIED, // file plan + AccessStatus.DENIED, // unfiled container + AccessStatus.DENIED, // record category + AccessStatus.DENIED, // record folder + AccessStatus.DENIED); // doc/record return originalLocation; } @@ -399,7 +393,8 @@ public class RecordServiceImplTest extends BaseRMTestCase assertFalse(extendedSecurityService.hasExtendedSecurity(dmDocument)); assertFalse(recordService.isFiled(dmDocument)); - // show that the record has meta-data about it's original location + // show that the record has meta-data about it's original + // location assertTrue(nodeService.hasAspect(dmDocument, ASPECT_RECORD_ORIGINATING_DETAILS)); assertEquals(originalLocation, nodeService.getProperty(dmDocument, PROP_RECORD_ORIGINATING_LOCATION)); assertFalse(originalLocation == nodeService.getPrimaryParent(dmDocument).getParentRef()); @@ -419,7 +414,7 @@ public class RecordServiceImplTest extends BaseRMTestCase @Override public NodeRef run() { - NodeRef record = fileFolderService.create(rmFolder, "test101.txt" , TYPE_CONTENT).getNodeRef(); + NodeRef record = fileFolderService.create(rmFolder, "test101.txt", TYPE_CONTENT).getNodeRef(); ContentWriter writer = contentService.getWriter(record, PROP_CONTENT, true); writer.setEncoding("UTF-8"); @@ -495,21 +490,245 @@ public class RecordServiceImplTest extends BaseRMTestCase }, AuthenticationUtil.getSystemUserName()); } - private void checkPermissions(String permission, AccessStatus filePlanExpected, - AccessStatus unfiledExpected, - AccessStatus recordCatExpected, - AccessStatus recordFolderExpected, - AccessStatus recordExpected) + private void checkPermissions(String permission, AccessStatus filePlanExpected, AccessStatus unfiledExpected, + AccessStatus recordCatExpected, AccessStatus recordFolderExpected, AccessStatus recordExpected) { - assertEquals(filePlanExpected, - dmPermissionService.hasPermission(filePlan, permission)); - assertEquals(unfiledExpected, - dmPermissionService.hasPermission(unfiledContainer, permission)); - assertEquals(recordCatExpected, - dmPermissionService.hasPermission(rmContainer, permission)); - assertEquals(recordFolderExpected, - dmPermissionService.hasPermission(rmFolder, permission)); - assertEquals(recordExpected, - dmPermissionService.hasPermission(dmDocument, permission)); + assertEquals(filePlanExpected, dmPermissionService.hasPermission(filePlan, permission)); + assertEquals(unfiledExpected, dmPermissionService.hasPermission(unfiledContainer, permission)); + assertEquals(recordCatExpected, dmPermissionService.hasPermission(rmContainer, permission)); + assertEquals(recordFolderExpected, dmPermissionService.hasPermission(rmFolder, permission)); + assertEquals(recordExpected, dmPermissionService.hasPermission(dmDocument, permission)); + } + + private String createUserWithCapabilties(final String... capabiltyNames) + { + return doTestInTransaction(new Test() + { + @Override + public String run() throws Exception + { + Role role = utils.createRole(filePlan, GUID.generate(), capabiltyNames); + + String userName = GUID.generate(); + createPerson(userName); + filePlanRoleService.assignRoleToAuthority(filePlan, role.getName(), userName); + + return userName; + } + }, AuthenticationUtil.getSystemUserName()); + + } + + /** + * Test {@link RecordService#isPropertyEditable(NodeRef, QName)} + */ + public void testIsPropertyEditable() throws Exception + { + final String nonRecordMetadata = createUserWithCapabilties( + RMPermissionModel.VIEW_RECORDS, + RMPermissionModel.EDIT_NON_RECORD_METADATA); + final String recordMetadata = createUserWithCapabilties( + RMPermissionModel.VIEW_RECORDS, + RMPermissionModel.EDIT_RECORD_METADATA); + final String declaredRecordMetadata = createUserWithCapabilties( + RMPermissionModel.VIEW_RECORDS, + RMPermissionModel.EDIT_DECLARED_RECORD_METADATA); + + doTestInTransaction(new VoidTest() + { + @Override + public void runImpl() throws Exception + { + filePlanPermissionService.setPermission(rmFolder, rmUserName, RMPermissionModel.FILING); + filePlanPermissionService.setPermission(rmFolder, nonRecordMetadata, RMPermissionModel.FILING); + filePlanPermissionService.setPermission(rmFolder, recordMetadata, RMPermissionModel.FILING); + filePlanPermissionService.setPermission(rmFolder, declaredRecordMetadata, RMPermissionModel.FILING); + } + }); + + // test rmadmin + doTestInTransaction(new VoidTest() + { + @Override + public void runImpl() throws Exception + { + assertTrue(recordService.isPropertyEditable(recordOne, PROP_ORIGINATING_ORGANIZATION)); + assertTrue(recordService.isPropertyEditable(recordOne, PROP_DESCRIPTION)); + assertTrue(recordService.isPropertyEditable(recordDeclaredOne, PROP_ORIGINATING_ORGANIZATION)); + assertFalse(recordService.isPropertyEditable(recordDeclaredOne, PROP_DESCRIPTION)); + } + }); + + // test normal user + doTestInTransaction(new VoidTest() + { + @Override + public void runImpl() throws Exception + { + assertFalse(recordService.isPropertyEditable(recordOne, PROP_ORIGINATING_ORGANIZATION)); + assertFalse(recordService.isPropertyEditable(recordOne, PROP_DESCRIPTION)); + assertFalse(recordService.isPropertyEditable(recordDeclaredOne, PROP_ORIGINATING_ORGANIZATION)); + assertFalse(recordService.isPropertyEditable(recordDeclaredOne, PROP_DESCRIPTION)); + } + }, rmUserName); + + // test undeclared record with edit non-record metadata capability + // test declared record with edit non-record metadata capability + doTestInTransaction(new VoidTest() + { + @Override + public void runImpl() throws Exception + { + assertFalse(recordService.isPropertyEditable(recordOne, PROP_ORIGINATING_ORGANIZATION)); + assertTrue(recordService.isPropertyEditable(recordOne, PROP_DESCRIPTION)); + assertFalse(recordService.isPropertyEditable(recordDeclaredOne, PROP_ORIGINATING_ORGANIZATION)); + assertFalse(recordService.isPropertyEditable(recordDeclaredOne, PROP_DESCRIPTION)); + } + }, nonRecordMetadata); + + // test undeclared record with edit record metadata capability + // test declared record with edit record metadata capability + doTestInTransaction(new VoidTest() + { + @Override + public void runImpl() throws Exception + { + assertTrue(recordService.isPropertyEditable(recordOne, PROP_ORIGINATING_ORGANIZATION)); + assertFalse(recordService.isPropertyEditable(recordOne, PROP_DESCRIPTION)); + assertFalse(recordService.isPropertyEditable(recordDeclaredOne, PROP_ORIGINATING_ORGANIZATION)); + assertFalse(recordService.isPropertyEditable(recordDeclaredOne, PROP_DESCRIPTION)); + } + }, recordMetadata); + + // test undeclared record with edit declared record metadata capability + // test declared record with edit declared record metadata capability + doTestInTransaction(new VoidTest() + { + @Override + public void runImpl() throws Exception + { + assertFalse(recordService.isPropertyEditable(recordOne, PROP_ORIGINATING_ORGANIZATION)); + assertFalse(recordService.isPropertyEditable(recordOne, PROP_DESCRIPTION)); + assertTrue(recordService.isPropertyEditable(recordDeclaredOne, PROP_ORIGINATING_ORGANIZATION)); + assertFalse(recordService.isPropertyEditable(recordDeclaredOne, PROP_DESCRIPTION)); + } + }, declaredRecordMetadata); + } + + public void testRecordPropertiesUpdate() throws Exception + { + final String nonRecordMetadata = createUserWithCapabilties( + RMPermissionModel.VIEW_RECORDS, + RMPermissionModel.EDIT_NON_RECORD_METADATA); + final String recordMetadata = createUserWithCapabilties( + RMPermissionModel.VIEW_RECORDS, + RMPermissionModel.EDIT_RECORD_METADATA); + final String declaredRecordMetadata = createUserWithCapabilties( + RMPermissionModel.VIEW_RECORDS, + RMPermissionModel.EDIT_DECLARED_RECORD_METADATA); + + doTestInTransaction(new VoidTest() + { + @Override + public void runImpl() throws Exception + { + filePlanPermissionService.setPermission(rmFolder, rmUserName, RMPermissionModel.FILING); + filePlanPermissionService.setPermission(rmFolder, nonRecordMetadata, RMPermissionModel.FILING); + filePlanPermissionService.setPermission(rmFolder, recordMetadata, RMPermissionModel.FILING); + filePlanPermissionService.setPermission(rmFolder, declaredRecordMetadata, RMPermissionModel.FILING); + } + }); + + // test rmadmin + canEditProperty(recordOne, ContentModel.PROP_DESCRIPTION, rmAdminName); + canEditProperty(recordOne, PROP_ORIGINATING_ORGANIZATION, rmAdminName); + cantEditProperty(recordDeclaredOne, ContentModel.PROP_DESCRIPTION, rmAdminName); + canEditProperty(recordDeclaredOne, PROP_ORIGINATING_ORGANIZATION, rmAdminName); + + // test normal user + cantEditProperty(recordOne, ContentModel.PROP_DESCRIPTION, rmUserName); + cantEditProperty(recordOne, PROP_ORIGINATING_ORGANIZATION, rmUserName); + cantEditProperty(recordDeclaredOne, ContentModel.PROP_DESCRIPTION, rmUserName); + cantEditProperty(recordDeclaredOne, PROP_ORIGINATING_ORGANIZATION, rmUserName); + + // test undeclared record with edit non-record metadata capability + canEditProperty(recordOne, ContentModel.PROP_DESCRIPTION, nonRecordMetadata); + cantEditProperty(recordOne, PROP_ORIGINATING_ORGANIZATION, nonRecordMetadata); + // test declared record with edit non-record metadata capability + cantEditProperty(recordDeclaredOne, ContentModel.PROP_DESCRIPTION, nonRecordMetadata); + cantEditProperty(recordDeclaredOne, PROP_ORIGINATING_ORGANIZATION, nonRecordMetadata); + + // test undeclared record with edit record metadata capability + cantEditProperty(recordOne, ContentModel.PROP_DESCRIPTION, recordMetadata); + canEditProperty(recordOne, PROP_ORIGINATING_ORGANIZATION, recordMetadata); + // test declared record with edit record metadata capability + cantEditProperty(recordDeclaredOne, ContentModel.PROP_DESCRIPTION, recordMetadata); + cantEditProperty(recordDeclaredOne, PROP_ORIGINATING_ORGANIZATION, recordMetadata); + + // test undeclared record with edit declared record metadata capability + cantEditProperty(recordOne, ContentModel.PROP_DESCRIPTION, declaredRecordMetadata); + cantEditProperty(recordOne, PROP_ORIGINATING_ORGANIZATION, declaredRecordMetadata); + // test declared record with edit declared record metadata capability + cantEditProperty(recordDeclaredOne, ContentModel.PROP_DESCRIPTION, declaredRecordMetadata); + canEditProperty(recordDeclaredOne, PROP_ORIGINATING_ORGANIZATION, declaredRecordMetadata); + + } + + public abstract class CommitPropertyFailTest extends Test + { + @Override + public Void run() throws Exception + { + // TODO Auto-generated method stub + return null; + } + + @Override + public void test(Void result) throws Exception + { + // TODO Auto-generated method stub + super.test(result); + } + + } + + private void cantEditProperty(final NodeRef nodeRef, final QName property, String user) throws Exception + { + boolean failure = false; + try + { + doTestInTransaction(new VoidTest() + { + @Override + public void runImpl() throws Exception + { + nodeService.setProperty(nodeRef, property, GUID.generate()); + } + + }, user); + } + catch (Throwable exception) + { + // expected + failure = true; + } + + // assert fail not failure + if (failure == false) + { + fail("Property should not have been editable."); + } + } + + private void canEditProperty(final NodeRef nodeRef, final QName property, String user) throws Exception + { + doTestInTransaction(new VoidTest() + { + @Override + public void runImpl() throws Exception + { + nodeService.setProperty(nodeRef, property, GUID.generate()); + } + }, user); } } diff --git a/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/util/BaseRMTestCase.java b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/util/BaseRMTestCase.java index e945fa8a46..76641b2930 100644 --- a/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/util/BaseRMTestCase.java +++ b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/util/BaseRMTestCase.java @@ -454,8 +454,7 @@ public abstract class BaseRMTestCase extends RetryingTransactionHelperTestCase unfiledContainer = filePlanService.getUnfiledContainer(filePlan); assertNotNull(unfiledContainer); } - }, - AuthenticationUtil.getAdminUserName()); + }, AuthenticationUtil.getSystemUserName()); } /** @@ -566,6 +565,7 @@ public abstract class BaseRMTestCase extends RetryingTransactionHelperTestCase { filePlanPermissionService.setPermission(filePlan, user, FILING); filePlanPermissionService.setPermission(rmContainer, user, FILING); + filePlanPermissionService.setPermission(rmFolder, user, FILING); filePlanPermissionService.setPermission(unfiledContainer, user, FILING); } } diff --git a/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/util/CommonRMTestUtils.java b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/util/CommonRMTestUtils.java index c87c83d78f..534e9106f6 100644 --- a/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/util/CommonRMTestUtils.java +++ b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/util/CommonRMTestUtils.java @@ -7,16 +7,23 @@ import java.io.Serializable; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; +import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; import org.alfresco.module.org_alfresco_module_rm.action.RecordsManagementActionService; import org.alfresco.module.org_alfresco_module_rm.action.impl.FreezeAction; +import org.alfresco.module.org_alfresco_module_rm.capability.Capability; +import org.alfresco.module.org_alfresco_module_rm.capability.CapabilityService; import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionSchedule; import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionService; import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel; import org.alfresco.module.org_alfresco_module_rm.model.security.ModelSecurityService; +import org.alfresco.module.org_alfresco_module_rm.role.FilePlanRoleService; +import org.alfresco.module.org_alfresco_module_rm.role.Role; import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; @@ -38,6 +45,8 @@ public class CommonRMTestUtils implements RecordsManagementModel private ContentService contentService; private RecordsManagementActionService actionService; private ModelSecurityService modelSecurityService; + private FilePlanRoleService filePlanRoleService; + private CapabilityService capabilityService; /** test values */ public static final String DEFAULT_DISPOSITION_AUTHORITY = "disposition authority"; @@ -54,6 +63,8 @@ public class CommonRMTestUtils implements RecordsManagementModel contentService = (ContentService)applicationContext.getBean("ContentService"); actionService = (RecordsManagementActionService)applicationContext.getBean("RecordsManagementActionService"); modelSecurityService = (ModelSecurityService)applicationContext.getBean("ModelSecurityService"); + filePlanRoleService = (FilePlanRoleService)applicationContext.getBean("FilePlanRoleService"); + capabilityService = (CapabilityService)applicationContext.getBean("CapabilityService"); } /** @@ -231,4 +242,20 @@ public class CommonRMTestUtils implements RecordsManagementModel }, AuthenticationUtil.getSystemUserName()); } + + public Role createRole(NodeRef filePlan, String roleName, String ... capabilityNames) + { + Set capabilities = new HashSet(capabilityNames.length); + for (String name : capabilityNames) + { + Capability capability = capabilityService.getCapability(name); + if (capability == null) + { + throw new AlfrescoRuntimeException("capability " + name + " not found."); + } + capabilities.add(capability); + } + + return filePlanRoleService.createRole(filePlan, roleName, roleName, capabilities); + } }