diff --git a/source/java/org/alfresco/repo/forum/CommentServiceImpl.java b/source/java/org/alfresco/repo/forum/CommentServiceImpl.java index f24411bc0c..1e3071b2f9 100644 --- a/source/java/org/alfresco/repo/forum/CommentServiceImpl.java +++ b/source/java/org/alfresco/repo/forum/CommentServiceImpl.java @@ -613,17 +613,18 @@ public class CommentServiceImpl extends AbstractLifecycleBean implements Comment NodeRef discussableNodeRef = getDiscussableAncestor(commentNodeRef); if (discussableNodeRef.equals(discussableNode)) { - if (!isWorkingCopyOrLocked(discussableNode)) + if (!isWorkingCopyOrLocked(discussableNode) + || isLockOwner(discussableNode)) { canEdit = canEditPermission(commentNodeRef); canDelete = canDeletePermission(commentNodeRef); } } - + Map map = new HashMap<>(2); map.put(CommentService.CAN_EDIT, canEdit); map.put(CommentService.CAN_DELETE, canDelete); - + return map; } @@ -637,12 +638,17 @@ public class CommentServiceImpl extends AbstractLifecycleBean implements Comment boolean isCoordinator = permissionService.hasPermission(commentNodeRef, PermissionService.COORDINATOR) == (AccessStatus.ALLOWED); return (isSiteManager || isCoordinator || currentUser.equals(creator) || currentUser.equals(owner)); } - + private boolean canDeletePermission(NodeRef commentNodeRef) { return permissionService.hasPermission(commentNodeRef, PermissionService.DELETE) == AccessStatus.ALLOWED; } + private boolean isLockOwner(NodeRef nodeRef) + { + return lockService.getLockStatus(nodeRef) == LockStatus.LOCK_OWNER?true:false; + } + private boolean isWorkingCopyOrLocked(NodeRef nodeRef) { boolean isWorkingCopy = false; @@ -667,7 +673,7 @@ public class CommentServiceImpl extends AbstractLifecycleBean implements Comment } return (isWorkingCopy || isLocked); } - + @Override public void onUpdateProperties(NodeRef commentNodeRef, Map before, Map after) { @@ -675,10 +681,11 @@ public class CommentServiceImpl extends AbstractLifecycleBean implements Comment if (discussableNodeRef != null) { if (behaviourFilter.isEnabled(ContentModel.ASPECT_LOCKABLE) - && isWorkingCopyOrLocked(discussableNodeRef)) + && isWorkingCopyOrLocked(discussableNodeRef) + && !isLockOwner(discussableNodeRef)) { List changedProperties = getChangedProperties(before, after); - + // check if comment updated (rather than some other change, eg. addition of lockable aspect only) boolean commentUpdated = false; for (QName changedProperty : changedProperties) @@ -693,7 +700,7 @@ public class CommentServiceImpl extends AbstractLifecycleBean implements Comment } } } - + if (commentUpdated) { throw new NodeLockedException(discussableNodeRef); @@ -741,20 +748,22 @@ public class CommentServiceImpl extends AbstractLifecycleBean implements Comment return results; } - + @Override public void beforeDeleteNode(final NodeRef commentNodeRef) { NodeRef discussableNodeRef = getDiscussableAncestor(commentNodeRef); if (discussableNodeRef != null) { + boolean canDelete = canDeletePermission(commentNodeRef); if (behaviourFilter.isEnabled(ContentModel.ASPECT_LOCKABLE) // eg. delete site (MNT-14671) - && isWorkingCopyOrLocked(discussableNodeRef)) + && isWorkingCopyOrLocked(discussableNodeRef) + && !isLockOwner(discussableNodeRef) + && !canDelete) { throw new NodeLockedException(discussableNodeRef); } - boolean canDelete = canDeletePermission(commentNodeRef); if (! canDelete) { throw new AccessDeniedException("Cannot delete comment"); diff --git a/source/test-java/org/alfresco/repo/forum/CommentsTest.java b/source/test-java/org/alfresco/repo/forum/CommentsTest.java index 4cc6b6e4c3..ce018a91cc 100644 --- a/source/test-java/org/alfresco/repo/forum/CommentsTest.java +++ b/source/test-java/org/alfresco/repo/forum/CommentsTest.java @@ -1,34 +1,35 @@ -/* - * #%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 . - * #L% - */ +/* + * #%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 . + * #L% + */ package org.alfresco.repo.forum; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import java.io.Serializable; import java.util.ArrayList; @@ -41,6 +42,7 @@ import org.alfresco.model.ForumModel; import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.domain.activities.ActivityPostDAO; import org.alfresco.repo.domain.activities.ActivityPostEntity; +import org.alfresco.repo.lock.mem.Lifetime; import org.alfresco.repo.model.Repository; import org.alfresco.repo.policy.BehaviourFilter; import org.alfresco.repo.security.authentication.AuthenticationComponent; @@ -50,6 +52,9 @@ import org.alfresco.repo.security.permissions.impl.ModelDAO; import org.alfresco.repo.security.permissions.impl.PermissionServiceImpl; import org.alfresco.repo.site.SiteModel; import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.service.cmr.lock.LockService; +import org.alfresco.service.cmr.lock.LockType; +import org.alfresco.service.cmr.lock.NodeLockedException; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.ContentData; import org.alfresco.service.cmr.repository.ContentService; @@ -65,6 +70,7 @@ import org.alfresco.service.cmr.site.SiteService; import org.alfresco.service.cmr.site.SiteVisibility; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; +import org.alfresco.util.GUID; import org.alfresco.util.PropertyMap; import org.alfresco.util.test.junitrules.AlfrescoPerson; import org.alfresco.util.test.junitrules.ApplicationContextInit; @@ -92,6 +98,8 @@ public class CommentsTest private static final ApplicationContextInit APP_CONTEXT_INIT = new ApplicationContextInit(); public static final String USER_ONE_NAME = "userone"; + public static final String USER_TWO_NAME = "usertwo"; + public static final String USER_THREE_NAME = "userthree"; public static final AlfrescoPerson TEST_USER1 = new AlfrescoPerson(APP_CONTEXT_INIT, USER_ONE_NAME); // Tie them together in a static Rule Chain @@ -124,6 +132,7 @@ public class CommentsTest private static ActivityPostDAO postDAO; private static PermissionServiceImpl permissionServiceImpl; private static ModelDAO permissionModelDAO; + private static LockService lockService; // These NodeRefs are used by the test methods. private static NodeRef COMPANY_HOME; @@ -147,7 +156,10 @@ public class CommentsTest postDAO = (ActivityPostDAO)APP_CONTEXT_INIT.getApplicationContext().getBean("postDAO"); permissionServiceImpl = (PermissionServiceImpl)APP_CONTEXT_INIT.getApplicationContext().getBean("permissionServiceImpl"); permissionModelDAO = (ModelDAO)APP_CONTEXT_INIT.getApplicationContext().getBean("permissionsModelDAO"); - + + lockService = (LockService)APP_CONTEXT_INIT.getApplicationContext().getBean("lockService"); + + COMPANY_HOME = repositoryHelper.getCompanyHome(); } @@ -160,7 +172,7 @@ public class CommentsTest testDocs = new ArrayList(3); for (int i = 0; i < 3; i++) { - NodeRef testNode = testNodes.createQuickFile(MimetypeMap.MIMETYPE_TEXT_PLAIN, COMPANY_HOME, "testDocInFolder" + i, TEST_USER1.getUsername()); + NodeRef testNode = testNodes.createQuickFile(MimetypeMap.MIMETYPE_TEXT_PLAIN, testFolder, "testDocInFolder_" + GUID.generate() + "_" + i, TEST_USER1.getUsername()); testDocs.add(testNode); } } @@ -168,8 +180,6 @@ public class CommentsTest // MNT-11667 "createComment" method creates activity for users who are not supposed to see the file @Test public void testMNT11667() throws Exception { - final String userTwo = "usertwo"; - try { transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() @@ -179,17 +189,17 @@ public class CommentsTest { authenticationComponent.setCurrentUser(AuthenticationUtil.getAdminUserName()); - createUser(userTwo); + createUser(USER_TWO_NAME); assertTrue(siteService.hasSite(testSite.getShortName())); authenticationComponent.setCurrentUser(USER_ONE_NAME); // invite user to a site with 'Collaborator' role - siteService.setMembership(testSite.getShortName(), userTwo, SiteModel.SITE_COLLABORATOR); + siteService.setMembership(testSite.getShortName(), USER_TWO_NAME, SiteModel.SITE_COLLABORATOR); assertEquals(SiteModel.SITE_MANAGER, siteService.getMembersRole(testSite.getShortName(), USER_ONE_NAME)); - assertEquals(SiteModel.SITE_COLLABORATOR, siteService.getMembersRole(testSite.getShortName(), userTwo)); + assertEquals(SiteModel.SITE_COLLABORATOR, siteService.getMembersRole(testSite.getShortName(), USER_TWO_NAME)); // get container of site NodeRef doclib = siteService.getContainer(testSite.getShortName(), SiteService.DOCUMENT_LIBRARY); @@ -233,7 +243,7 @@ public class CommentsTest assertTrue(permissionServiceImpl.hasPermission(nodeRef, getPermission(PermissionService.WRITE)) == AccessStatus.ALLOWED); assertTrue(permissionServiceImpl.hasPermission(nodeRef, getPermission(PermissionService.DELETE)) == AccessStatus.ALLOWED); - authenticationComponent.setCurrentUser(userTwo); + authenticationComponent.setCurrentUser(USER_TWO_NAME); assertTrue(permissionServiceImpl.hasPermission(nodeRef, getPermission(PermissionService.READ)) == AccessStatus.DENIED); assertTrue(permissionServiceImpl.hasPermission(nodeRef, getPermission(PermissionService.WRITE)) == AccessStatus.DENIED); @@ -248,14 +258,14 @@ public class CommentsTest { authenticationComponent.setCurrentUser(AuthenticationUtil.getAdminUserName()); - if (personService.personExists(userTwo)) + if (personService.personExists(USER_TWO_NAME)) { - personService.deletePerson(userTwo); + personService.deletePerson(USER_TWO_NAME); } - if (authenticationService.authenticationExists(userTwo)) + if (authenticationService.authenticationExists(USER_TWO_NAME)) { - authenticationService.deleteAuthentication(userTwo); + authenticationService.deleteAuthentication(USER_TWO_NAME); } } @@ -396,6 +406,93 @@ public class CommentsTest } }); } + + /** + * REPO-2557, ALF-21907 + */ + @Test public void testAddingCommentOnLockedNode() + { + // create user 2 and 3 + transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + authenticationComponent.setCurrentUser(AuthenticationUtil.getAdminUserName()); + createUser(USER_TWO_NAME); + createUser(USER_THREE_NAME); + siteService.setMembership(testSite.getShortName(), USER_TWO_NAME, SiteModel.SITE_MANAGER); + siteService.setMembership(testSite.getShortName(), USER_THREE_NAME, SiteModel.SITE_MANAGER); + return null; + } + }); + + // switch to user 1 + authenticationComponent.setCurrentUser(USER_ONE_NAME); + + // create some comments + final NodeRef testDoc = testDocs.get(0); + transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + applyComment(testDoc, "Hello 1"); + applyComment(testDoc, "Hello 2"); + return null; + } + }); + + authenticationComponent.setCurrentUser(USER_TWO_NAME); + + // lock node + transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + lockService.lock(testDoc, LockType.WRITE_LOCK, (int) 3600, Lifetime.PERSISTENT, "someInfo"); + return null; + } + }); + + authenticationComponent.setCurrentUser(USER_THREE_NAME); + + try + { + transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + applyComment(testDoc, "Hello 3"); + return null; + } + }); + fail("NodeLockedException not thrown"); + } + catch(NodeLockedException e) + { + // Fails because is not the lock owner + } + + // change to lock owner + authenticationComponent.setCurrentUser(USER_TWO_NAME); + + transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + applyComment(testDoc, "Hello 3"); + return null; + } + }); + + assertCommentCountIs(testDoc, 3); + + authenticationComponent.setCurrentUser(AuthenticationUtil.getAdminUserName()); + } /** * This test method tests that nodes whose commentCount is set to -1 have their commentCounts recalculated.