Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

XWIKI-21730: Delete own comments should not require edit rights on page #2836

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ $xwiki.ssfx.use('uicomponents/viewers/comments.css', true)
## This variable should be true only for an authenticate user that matches the comment author.
## We don't allow guests to be edit or delete comments because we can't know if they are the comment author.
#set ($isUserComment = $commentAuthor != '' && $services.model.resolveDocument($commentAuthor, 'user') == $xcontext.userReference)
#set ($hasCommentEditionRight = $hasAdmin || $isUserComment)
<div id="xwikicomment_${comment.number}" class="xwikicomment #if($comment.getProperty('author').value == $doc.creator) commentByCreator#end#if($isAnnotation) annotation#end">
<div class="commentavatar">#if("$!comment.replyto" == '')#largeUserAvatar($commentAuthor)#{else}#mediumUserAvatar($commentAuthor)#end</div>
<div class="commentheader">
Expand All @@ -177,14 +178,14 @@ $xwiki.ssfx.use('uicomponents/viewers/comments.css', true)
<div class="btn-group commenttools">
#if($xwiki.hasAccessLevel('comment'))
<a class="commentreply btn btn-default btn-xs" rel="nofollow" href="$xredirect.replaceAll('&?replyto=\d++', '')&amp;replyto=${comment.number}#xwikicomment_${comment.number}" title="$services.localization.render('core.viewers.comments.reply')"#if("$!replyTo" == "${comment.number}") style="display: none;"#end>$services.icon.renderHTML('comment')</a></span>
#if($hasAdmin || $isUserComment)
#if($hasCommentEditionRight)
<a class="edit btn btn-default btn-xs" rel="nofollow" href="$doc.getURL('view', "viewer=comments&amp;number=${comment.number}&amp;xredirect=$doc.getURL('view')")" title="$services.localization.render('core.viewers.comments.edit')">$services.icon.renderHTML('pencil')</a>
#end
#end
<a class="permalink btn btn-default btn-xs" data-toggle="modal" data-target="#permalinkModal" rel="nofollow"
href="$doc.getURL('view', 'viewer=comments')#xwikicomment_${comment.number}"
title="$services.localization.render('core.viewers.comments.permalink')">$services.icon.renderHTML('link')</a>
#if ($hasAdmin || ($hasEdit && $isUserComment))
#if ($hasCommentEditionRight)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm I'm not so sure about that change... Yeah I know I commented to have consistency in an early review :)
Have you tested if it actually works to edit a comment for a page in which the comment user doesn't have edit right?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the indentation change on purpose ?

## If a remote URL is provided, content will be loaded into .modal-content because of bootstrap.
## By providing an anchor this behavior is stoped, without altering the URL functionality.
#set ($queryString = $escapetool.url({
Expand All @@ -193,7 +194,7 @@ $xwiki.ssfx.use('uicomponents/viewers/comments.css', true)
'classid': $comment.number,
'xredirect': $doc.getURL('view')
}))
#set ($deleteURL = $xwiki.getURL($doc.fullName, 'objectremove', $queryString, "xwikicomment_${comment.number}"))
#set ($deleteURL = $xwiki.getURL($doc.fullName, 'commentdelete', $queryString, "xwikicomment_${comment.number}"))
<a class="delete btn btn-default btn-xs " data-toggle="modal" data-target="#deleteModal" rel="nofollow"
href="$deleteURL" title="$services.localization.render('core.viewers.comments.delete')">
$services.icon.renderHTML('trash')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ public String getRight(String action)
actionMap.put("reset", "delete");
actionMap.put("commentadd", "comment");
actionMap.put("commentsave", "comment");
actionMap.put("commentdelete", "comment");
actionMap.put("register", "register");
actionMap.put("redirect", "view");
actionMap.put("admin", "admin");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
/*
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
* This 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 2.1 of
* the License, or (at your option) any later version.
*
* This software 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 this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package com.xpn.xwiki.web;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm normally we shouldn't introduce new stuff in this package, we should instead put stuff in org.xwiki... package, even in oldcore... Now it would make this class far from the other actions, so I'm not sure here...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • We need a new action for the map in XWikiCachingRightService. This means that we cannot just reuse another action.
  • All actions are already here.
  • The code I used in this class is very similar to ObjectRemoveAction. It's mostly oldcore quality code. It'd probably need even more quality improvements to be anywhere else and I don't think it's worth spending that much time on rewriting it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's worth spending that much time on rewriting it.

Note that I was only talking about moving that class to another package. Now if it's using some package private stuff it's making this more complex indeed.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Honestly it does not make much sense to have CommentAddAction here and CommentDeleteAction elsewhere so +1 to keep it here.


import java.io.IOException;

import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import javax.script.ScriptContext;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xwiki.component.annotation.Component;
import org.xwiki.model.reference.DocumentReference;

import com.xpn.xwiki.XWiki;
import com.xpn.xwiki.XWikiContext;
import com.xpn.xwiki.XWikiException;
import com.xpn.xwiki.doc.XWikiDocument;
import com.xpn.xwiki.objects.BaseObject;

import org.xwiki.user.CurrentUserReference;
import org.xwiki.user.UserReference;
import org.xwiki.user.UserReferenceResolver;

/**
* Action used to remove a comment from a page, requires comment right but not edit right. Note that this class is
* largely inspired by ObjectRemoveAction and Comment
*
* @version $Id$
* @since 17.0.0RC1
*/
@Component
@Named("commentdelete")
Copy link
Member

@tmortagne tmortagne Jan 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing critical but commentremove would feel more consistent with objectremove, especially since it's reusing ObjectRemoveForm.

@Singleton
public class CommentDeleteAction extends XWikiAction
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A test class should be provided too.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a test for this class.

For both the test and the new Java class, I made sure to reformat the code with XWiki standard on Intellij Idea
I successfully passed mvn clean install -f xwiki-platform-core/xwiki-platform-oldcore -Pquality. All that was reported by SonarLint on both of the files are the @MockComponent that are not used in the class , which is a false positive. They are needed for our test class to work properly.

Added the test class in 1579ded 👍

Copy link
Member

@tmortagne tmortagne Jan 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might worth moving most of this class in some AbstractObjectRemoveAction that would also be extended by ObjectRemoveAction (to me it seems the main different is that CommentRemoveAction would indicate a hardcoded class to AbstractObjectRemoveAction instead of using the one provided by ObjectRemoveForm).

{
private static final String FAIL_MESSAGE = "failed";

private static final Logger LOGGER = LoggerFactory.getLogger(CommentDeleteAction.class);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a component, so you can inject a Logger (you find this kind of stuff in other actions because they used to not be components, but for new code let's do things by the book).


@Inject
private UserReferenceResolver<CurrentUserReference> currentUserResolver;

@Override
protected Class<? extends XWikiForm> getFormClass()
{
return ObjectRemoveForm.class;
}

protected BaseObject getObject(XWikiDocument doc, XWikiContext context)
{
ObjectRemoveForm form = (ObjectRemoveForm) context.getForm();
BaseObject obj = null;

String className = form.getClassName();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Manipulating the class name does not make sense for this action, since it's always about comments (or only to return an error if someone tried to be clever and use this action to delete another type of xobject with just comment right).

int classId = form.getClassId();
String attributeName = "message";
if (StringUtils.isBlank(className)) {
getCurrentScriptContext().setAttribute(attributeName,
localizePlainOrReturnKey("platform.core.action.commentRemove.noClassnameSpecified"),
ScriptContext.ENGINE_SCOPE);
} else if (classId < 0) {
getCurrentScriptContext().setAttribute(attributeName,
localizePlainOrReturnKey("platform.core.action.commentRemove.noCommentSpecified"),
ScriptContext.ENGINE_SCOPE);
} else {
// Comment class reference
DocumentReference commentClass = new DocumentReference(context.getWikiId(), XWiki.SYSTEM_SPACE,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you need to create a DocumentReference ?

XWikiDocument.COMMENTSCLASS_REFERENCE.getName());
obj = doc.getXObject(commentClass, classId);
if (obj == null) {
getCurrentScriptContext().setAttribute(attributeName,
localizePlainOrReturnKey("platform.core.action.commentRemove.invalidComment"),
ScriptContext.ENGINE_SCOPE);
}
}
return obj;
}

@Override
public boolean action(XWikiContext context) throws XWikiException
{
// CSRF prevention
if (!csrfTokenCheck(context)) {
return false;
}

XWiki xwiki = context.getWiki();
XWikiResponse response = context.getResponse();
XWikiDocument doc = context.getDoc();
DocumentReference userReference = context.getUserReference();

// We need to clone this document first, since a cached storage would return the same object for the
// following requests, so concurrent request might get a partially modified object, or worse, if an error
// occurs during the save, the cached object will not reflect the actual document at all.
doc = doc.clone();

BaseObject obj = getObject(doc, context);
if (obj == null) {
return true;
}

doc.removeXObject(obj);
UserReference currentUserReference = this.currentUserResolver.resolve(CurrentUserReference.INSTANCE);
doc.getAuthors().setEffectiveMetadataAuthor(currentUserReference);

String changeComment = localizePlainOrReturnKey("core.comment.deleteComment");

// Make sure the user is allowed to make this modification
context.getWiki().checkSavingDocument(userReference, doc, changeComment, true, context);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And this check is working if you don't have edit right on the doc?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From what I can understand of this code I reused from ObjectRemoveAction, this does not check the rights of the current user but the state of the document (whether it's in a saveable state or not). I'm retesting things manually to make sure everything is alright.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I finally figured it out. The authorisation to do this action is handled at the level of XWikiCachingRightService. This is also the reason why we need an independant action even thought it's pretty much the same as object deletion. From what I understand, one action can only be mapped to one right. Setting the other object editions to depend on the Comment right is wrong, we need to separate this into two actions.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can confirm this. I forgot to put the security .jar on my local instance and the action wouldn't go through (with edit rights disabled but comment rights enabled). Updating it as expected fixed the backend permission issue.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm retesting things manually to make sure everything is alright.

Except for an oldcore exception that's unrelated to this PR, builds are okay.


xwiki.saveDocument(doc, changeComment, true, context);

if (Utils.isAjaxRequest(context)) {
response.setStatus(HttpServletResponse.SC_NO_CONTENT);
response.setContentLength(0);
} else {
// forward to edit
String redirect = Utils.getRedirect("edit", context);
sendRedirect(response, redirect);
}
return false;
}

@Override
public String render(XWikiContext context) throws XWikiException
{
if (Utils.isAjaxRequest(context)) {
XWikiResponse response = context.getResponse();
response.setStatus(HttpServletResponse.SC_CONFLICT);
response.setContentType("text/plain");
try {
response.getWriter().print(FAIL_MESSAGE);
} catch (IOException e) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing to handle the exception?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that most parts are the same as ObjectRemoveAction. This empty exception handle has been in this other file since 2008 so it seems like it's not a huge problem.
This is bad quality I agree.
I added a log similar to what was done in XWikiAction.java

LOGGER.error("Failed to send error response to AJAX save and continue request.", e);

Addressed in 767daac 👍

LOGGER.error("Failed to send error response to AJAX comment delete request.", e);
}
return null;
} else {
return "error";
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xwiki.component.annotation.Component;
import org.xwiki.model.reference.DocumentReference;

Expand All @@ -41,6 +43,8 @@
@Singleton
public class ObjectRemoveAction extends XWikiAction
{
private static final String FAIL_MESSAGE = "failed";
private static final Logger LOGGER = LoggerFactory.getLogger(ObjectRemoveAction.class);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment as above about injected Loggers.

@Override
protected Class<? extends XWikiForm> getFormClass()
{
Expand Down Expand Up @@ -124,9 +128,9 @@ public String render(XWikiContext context) throws XWikiException
response.setStatus(HttpServletResponse.SC_CONFLICT);
response.setContentType("text/plain");
try {
response.getWriter().write("failed");
response.setContentLength(6);
response.getWriter().print(FAIL_MESSAGE);
} catch (IOException e) {
LOGGER.error("Failed to send error response to AJAX comment delete request.", e);
}
return null;
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -950,6 +950,7 @@ core.comment.tooltip=Enter a brief description of your changes
core.comment.prompt=Enter a brief description of your changes
core.comment.addComment=Added comment
core.comment.editComment=Edited comment
core.comment.deleteComment=Deleted comment
core.comment.addObject=Added object
core.comment.updateObject=Updated object
core.comment.deleteObject=Deleted object
Expand Down Expand Up @@ -2187,6 +2188,9 @@ platform.core.invalidUrl=This is not a valid URL
platform.core.action.objectRemove.noClassnameSpecified=No object type specified.
platform.core.action.objectRemove.noObjectSpecified=No object specified.
platform.core.action.objectRemove.invalidObject=Invalid object specified.
platform.core.action.commentRemove.noClassnameSpecified=No object type specified.
platform.core.action.commentRemove.noCommentSpecified=No comment specified.
platform.core.action.commentRemove.invalidComment=Invalid comment specified.
platform.core.action.deleteAttachment.noAttachment=This attachment does not exist.
core.action.deleteAttachment.failed=Failed to delete attachment {0}
core.action.upload.failure=Failed to upload {0,choice,0#files|1#one file|1<{0} files}.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ com.xpn.xwiki.web.AttachAction
com.xpn.xwiki.web.CancelAction
com.xpn.xwiki.web.CommentAddAction
com.xpn.xwiki.web.CommentSaveAction
com.xpn.xwiki.web.CommentDeleteAction
com.xpn.xwiki.web.CreateAction
com.xpn.xwiki.web.DeleteAction
com.xpn.xwiki.web.DeleteAttachmentAction
Expand Down
Loading