diff --git a/config/alfresco/comment-services-context.xml b/config/alfresco/comment-services-context.xml
index 19bfeefc38..b44bffa0ba 100644
--- a/config/alfresco/comment-services-context.xml
+++ b/config/alfresco/comment-services-context.xml
@@ -79,6 +79,9 @@
+
+
+
diff --git a/source/java/org/alfresco/repo/forum/CommentService.java b/source/java/org/alfresco/repo/forum/CommentService.java
index 108fd8bbce..93c69d968e 100644
--- a/source/java/org/alfresco/repo/forum/CommentService.java
+++ b/source/java/org/alfresco/repo/forum/CommentService.java
@@ -31,6 +31,8 @@ import org.alfresco.query.PagingRequest;
import org.alfresco.query.PagingResults;
import org.alfresco.service.cmr.repository.NodeRef;
+import java.util.Map;
+
/**
* A service for handling comments.
*
@@ -93,4 +95,16 @@ public interface CommentService
* @param commentNodeRef the node in Share which is being commented on.
*/
void deleteComment(NodeRef commentNodeRef);
+
+ /**
+ * canEdit / canDelete
+ *
+ * @param discussableNode
+ * @param commentNodeRef
+ * @return
+ */
+ Map getCommentPermissions(NodeRef discussableNode, NodeRef commentNodeRef);
+
+ String CAN_EDIT = "canEdit";
+ String CAN_DELETE = "canDelete";
}
diff --git a/source/java/org/alfresco/repo/forum/CommentServiceImpl.java b/source/java/org/alfresco/repo/forum/CommentServiceImpl.java
index e20010ebfa..f29f2ee6cd 100644
--- a/source/java/org/alfresco/repo/forum/CommentServiceImpl.java
+++ b/source/java/org/alfresco/repo/forum/CommentServiceImpl.java
@@ -32,6 +32,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import org.alfresco.model.ContentModel;
import org.alfresco.model.ForumModel;
@@ -42,17 +43,27 @@ import org.alfresco.query.PagingRequest;
import org.alfresco.query.PagingResults;
import org.alfresco.repo.activities.ActivityType;
import org.alfresco.repo.content.MimetypeMap;
+import org.alfresco.repo.node.NodeServicePolicies;
import org.alfresco.repo.node.getchildren.GetChildrenCannedQuery;
import org.alfresco.repo.node.getchildren.GetChildrenCannedQueryFactory;
+import org.alfresco.repo.policy.JavaBehaviour;
+import org.alfresco.repo.policy.PolicyComponent;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
+import org.alfresco.repo.security.permissions.AccessDeniedException;
+import org.alfresco.repo.site.SiteModel;
import org.alfresco.service.cmr.activities.ActivityService;
+import org.alfresco.service.cmr.lock.LockService;
+import org.alfresco.service.cmr.lock.LockStatus;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.ContentData;
+import org.alfresco.service.cmr.repository.ContentIOException;
import org.alfresco.service.cmr.repository.ContentService;
import org.alfresco.service.cmr.repository.ContentWriter;
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.PermissionService;
import org.alfresco.service.cmr.site.SiteService;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
@@ -61,13 +72,16 @@ import org.alfresco.util.registry.NamedObjectRegistry;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.json.simple.JSONObject;
+import org.springframework.context.ApplicationEvent;
+import org.springframework.extensions.surf.util.AbstractLifecycleBean;
/**
* @author Neil Mc Erlean
* @since 4.0
*/
// TODO consolidate this and ScriptCommentService and the implementations of comments.* web scripts.
-public class CommentServiceImpl implements CommentService
+public class CommentServiceImpl extends AbstractLifecycleBean
+ implements CommentService, NodeServicePolicies.BeforeDeleteNodePolicy, NodeServicePolicies.BeforeUpdateNodePolicy
{
private static Log logger = LogFactory.getLog(CommentServiceImpl.class);
@@ -84,6 +98,9 @@ public class CommentServiceImpl implements CommentService
private ContentService contentService;
private ActivityService activityService;
private SiteService siteService;
+ private PolicyComponent policyComponent;
+ private PermissionService permissionService;
+ private LockService lockService;
private NamedObjectRegistry> cannedQueryRegistry;
@@ -112,7 +129,40 @@ public class CommentServiceImpl implements CommentService
this.cannedQueryRegistry = cannedQueryRegistry;
}
- @Override
+ public void setPolicyComponent(PolicyComponent policyComponent)
+ {
+ this.policyComponent = policyComponent;
+ }
+
+ public void setPermissionService(PermissionService permissionService)
+ {
+ this.permissionService = permissionService;
+ }
+
+ public void setLockService(LockService lockService)
+ {
+ this.lockService = lockService;
+ }
+
+ @Override
+ protected void onBootstrap(ApplicationEvent event)
+ {
+ this.policyComponent.bindClassBehaviour(
+ NodeServicePolicies.BeforeUpdateNodePolicy.QNAME,
+ ForumModel.TYPE_POST,
+ new JavaBehaviour(this, "beforeUpdateNode"));
+ this.policyComponent.bindClassBehaviour(
+ NodeServicePolicies.BeforeDeleteNodePolicy.QNAME,
+ ForumModel.TYPE_POST,
+ new JavaBehaviour(this, "beforeDeleteNode"));
+ }
+
+ @Override
+ protected void onShutdown(ApplicationEvent event)
+ {
+ }
+
+ @Override
public NodeRef getDiscussableAncestor(NodeRef descendantNodeRef)
{
// For "Share comments" i.e. fm:post nodes created via the Share commenting UI, the containment structure is as follows:
@@ -126,7 +176,8 @@ public class CommentServiceImpl implements CommentService
// containment tree in a controlled manner.
// We could navigate up looking for the first fm:discussable ancestor, but that might not find the correct node - it could,
// for example, find a parent folder which was discussable.
-
+
+ // TODO review - is this reasonable or should we (also) check for assoc "Comments" ?
NodeRef result = null;
if (nodeService.getType(descendantNodeRef).equals(ForumModel.TYPE_POST))
{
@@ -358,10 +409,21 @@ public class CommentServiceImpl implements CommentService
{
throw new IllegalArgumentException("Node to update is not a comment node.");
}
-
- ContentWriter writer = contentService.getWriter(commentNodeRef, ContentModel.PROP_CONTENT, true);
- writer.setMimetype(MimetypeMap.MIMETYPE_HTML); // TODO should this be set by the caller?
- writer.putContent(comment);
+
+ try
+ {
+ ContentWriter writer = contentService.getWriter(commentNodeRef, ContentModel.PROP_CONTENT, true);
+ writer.setMimetype(MimetypeMap.MIMETYPE_HTML); // TODO should this be set by the caller?
+ writer.putContent(comment);
+ }
+ catch (ContentIOException cioe)
+ {
+ if (cioe.getCause() instanceof AccessDeniedException)
+ {
+ throw (AccessDeniedException)cioe.getCause();
+ }
+ throw cioe;
+ }
if(title != null)
{
@@ -411,4 +473,119 @@ public class CommentServiceImpl implements CommentService
logger.warn("Unable to determine discussable node for the comment with nodeRef " + commentNodeRef + ", not posting an activity");
}
}
+
+ // TODO review against ACE-5437 (eg. introduce CommentInfo as part of the CommentService)
+ public Map getCommentPermissions(NodeRef discussableNode, NodeRef commentNodeRef)
+ {
+ boolean canEdit = false;
+ boolean canDelete = false;
+
+ // belts-and-braces
+ NodeRef discussableNodeRef = getDiscussableAncestor(commentNodeRef);
+ if (discussableNodeRef.equals(discussableNode))
+ {
+ if (!isWorkingCopyOrLocked(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;
+ }
+
+ private boolean canEditPermission(NodeRef commentNodeRef)
+ {
+ String creator = (String)nodeService.getProperty(commentNodeRef, ContentModel.PROP_CREATOR);
+ Serializable owner = nodeService.getProperty(commentNodeRef, ContentModel.PROP_OWNER);
+ String currentUser = AuthenticationUtil.getFullyAuthenticatedUser();
+
+ boolean isSiteManager = permissionService.hasPermission(commentNodeRef, SiteModel.SITE_MANAGER) == (AccessStatus.ALLOWED);
+ 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 isWorkingCopyOrLocked(NodeRef nodeRef)
+ {
+ boolean isWorkingCopy = false;
+ boolean isLocked = false;
+
+ if (nodeRef != null)
+ {
+ Set aspects = nodeService.getAspects(nodeRef);
+
+ isWorkingCopy = aspects.contains(ContentModel.ASPECT_WORKING_COPY);
+ if (!isWorkingCopy)
+ {
+ if (aspects.contains(ContentModel.ASPECT_LOCKABLE))
+ {
+ LockStatus lockStatus = lockService.getLockStatus(nodeRef);
+ if (lockStatus == LockStatus.LOCKED || lockStatus == LockStatus.LOCK_OWNER)
+ {
+ isLocked = true;
+ }
+ }
+ }
+ }
+ return (isWorkingCopy || isLocked);
+ }
+
+ @Override
+ public void beforeUpdateNode(NodeRef commentNodeRef)
+ {
+ if (! canEdit(commentNodeRef))
+ {
+ throw new AccessDeniedException("Cannot edit comment");
+ }
+ }
+
+ protected boolean canEdit(NodeRef commentNodeRef)
+ {
+ boolean canEdit = false;
+
+ NodeRef discussableNodeRef = getDiscussableAncestor(commentNodeRef);
+ if (discussableNodeRef != null)
+ {
+ if (! isWorkingCopyOrLocked(discussableNodeRef))
+ {
+ canEdit = canEditPermission(commentNodeRef);
+ }
+ }
+
+ return canEdit;
+ }
+
+ @Override
+ public void beforeDeleteNode(final NodeRef commentNodeRef)
+ {
+ if (! canDelete(commentNodeRef))
+ {
+ throw new AccessDeniedException("Cannot delete comment");
+ }
+ }
+
+ protected boolean canDelete(NodeRef commentNodeRef)
+ {
+ boolean canDelete = false;
+
+ NodeRef discussableNodeRef = getDiscussableAncestor(commentNodeRef);
+ if (discussableNodeRef != null)
+ {
+ if (! isWorkingCopyOrLocked(discussableNodeRef))
+ {
+ canDelete = canDeletePermission(commentNodeRef);
+ }
+ }
+
+ return canDelete;
+ }
}