XWiki Platform Attachment UI vulnerable to cross-site scripting in the move attachment form
Description
XWiki Platform Attachment UI provides a macro to easily upload and select attachments for XWiki Platform, a generic wiki platform. Starting with version 14.0-rc-1 and prior to 14.4-rc-1, it's possible to store JavaScript in an attachment name, which will be executed by anyone trying to move the corresponding attachment. This issue has been patched in XWiki 14.4-rc-1. As a workaround, one may copy moveStep1.vm to webapp/xwiki/templates/moveStep1.vm and replace vulnerable code with code from the patch.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
org.xwiki.platform:xwiki-platform-attachment-uiMaven | >= 14.0-rc-1, < 14.4-rc-1 | 14.4-rc-1 |
Affected products
1- Range: >= 14.0-rc-1, < 14.4-rc-1
Patches
1fbc4bfbae4f6XWIKI-19667: The move attachment form is missing escaping and some translations
9 files changed · +400 −36
xwiki-platform-core/xwiki-platform-attachment/xwiki-platform-attachment-api/src/main/resources/ApplicationResources.properties+3 −0 modified@@ -37,6 +37,9 @@ attachment.move.submit=Submit attachment.move.status.notFound=The requested attachment move status could not be found. attachment.move.status.label=Attachment Move Status attachment.move.status.hint=The following attachment move operation has been started by {0} on {1}. +attachment.move.targetLocation.modal.title=Select New Attachment Location +attachment.move.targetLocation.modal.selectButton=Select +attachment.move.targetLocation.modal.closeButton=Close attachment.job.saveDocument.inPlace=Attachment {0} moved to {1}. attachment.job.saveDocument.source=Attachment moved to {0}. attachment.job.saveDocument.target=Attachment moved from {0}.
xwiki-platform-core/xwiki-platform-attachment/xwiki-platform-attachment-api/src/main/resources/templates/attachment/moveStatus.vm+7 −5 modified@@ -40,9 +40,11 @@ #macro (displayMoveJobStatus $moveJobStatus) #set ($moveJobRequest = $moveJobStatus.request) <div class="xcontent"> - <h2>$services.localization.render('attachment.move.status.label')</h2> - <p class="text-muted small">$services.localization.render('attachment.move.status.hint', - [$xwiki.getUserName($moveJobRequest.userReference), $xwiki.formatDate($moveJobStatus.startDate)])</p> + <h2>$escapetool.xml($services.localization.render('attachment.move.status.label'))</h2> + <p class="text-muted small"> + $escapetool.xml($services.localization.render('attachment.move.status.hint', + [$xwiki.getUserName($moveJobRequest.userReference), $xwiki.formatDate($moveJobStatus.startDate)])) + </p> #displayMoveJobRequest($moveJobRequest) #template('job_macros.vm') #displayJobStatus($moveJobStatus) @@ -58,13 +60,13 @@ #set ($moveJobStatus = $services.job.getJobStatus(['refactoring', 'moveAttachment', $request.moveId])) #if ($moveJobStatus) #if ($xcontext.action == 'get') - #outputMoveJobStatusJSON($moveJobStatus) + #outputmoveJobStatusJSON($moveJobStatus) #else #displayMoveJobStatus($moveJobStatus) #end #else $response.setStatus(404) <div class="box errormessage"> - $services.localization.render('attachment.move.status.notFound') + $escapetool.xml($services.localization.render('attachment.move.status.notFound')) </div> #end \ No newline at end of file
xwiki-platform-core/xwiki-platform-attachment/xwiki-platform-attachment-api/src/main/resources/templates/attachment/moveStep1.vm+44 −18 modified@@ -23,8 +23,11 @@ #set ($discard = $xwiki.ssrx.use('css/attachment/move.css')) #set ($discard = $xwiki.jsrx.use('js/attachment/move.js')) -#set($titleToDisplay = $services.localization.render('attachment.move.title', - [$attachment.name, $escapetool.xml($doc.plainTitle), $doc.getURL()])) +#set($titleToDisplay = $services.localization.render('attachment.move.title', [ + $escapetool.xml($attachment.name), + $escapetool.xml($doc.plainTitle), + $escapetool.xml($doc.getURL()) +])) <div class="xcontent"> #template('contentheader.vm') #template('attachment/refactoring_macros.vm') @@ -63,16 +66,18 @@ ## In the future we'll offer a config option to define the default behavior, see ## http://jira.xwiki.org/browse/XWIKI-13384 #set ($checked = $request.autoRedirect == 'true') - <dt> + <dt class="autoRedirect"> <label> <input type="checkbox" name="autoRedirect" value="true" #if ($checked)checked="checked"#end /> - $services.localization.render('attachment.move.autoRedirect.label') + $escapetool.xml($services.localization.render('attachment.move.autoRedirect.label')) </label> ## The value submitted when the checkbox is not checked, used to preserve the form state. <input type="hidden" name="autoRedirect" value="false" /> </dt> - <dd> - <span class="xHint">$services.localization.render('attachment.move.autoRedirect.hint')</span> + <dd class="autoRedirect"> + <span class="xHint"> + $escapetool.xml($services.localization.render('attachment.move.autoRedirect.hint')) + </span> </dd> </dl> </div> @@ -81,21 +86,32 @@ ##------------ ## Target attachment ##------------ - <dt> - <label for="targetAttachmentName">$services.localization.render('attachment.move.newName.label')</label> - <span class="xHint">$!services.localization.render('attachment.move.newName.hint')</span> + <dt class="targetAttachmentName"> + <label for="targetAttachmentNameTitle"> + $escapetool.xml($services.localization.render('attachment.move.newName.label')) + </label> + <span class="xHint"> + $escapetool.xml($services.localization.render('attachment.move.newName.hint')) + </span> </dt> <dd> - <input type="text" id="targetAttachmentNameTitle" name="targetAttachmentName" value="$escapetool.xml($attachment.name)" + <input type="text" + id="targetAttachmentNameTitle" + name="targetAttachmentName" + value="$escapetool.xml($attachment.name)" class="location-title-field" - placeholder="$!services.localization.render('attachment.move.newName.placeholder')" /> + placeholder="$escapetool.xml($services.localization.render('attachment.move.newName.placeholder'))" /> </dd> ##------------ ## Target Page ##------------ <dt> - <label for="$!{options.id}Title">$services.localization.render('attachment.move.newLocation.label')</label> - <span class="xHint">$!services.localization.render('attachment.move.newLocation.hint')</span> + <label for="targetLocation"> + $escapetool.xml($services.localization.render('attachment.move.newLocation.label')) + </label> + <span class="xHint"> + $escapetool.xml($services.localization.render('attachment.move.newLocation.hint')) + </span> </dt> <dd> #set ($targetRef = $doc.documentReference) @@ -110,7 +126,9 @@ <button type="button" class="close" data-dismiss="modal" aria-label="Close"> <span aria-hidden="true">×</span> </button> - <h5 class="modal-title">Modal title</h5> + <h5 class="modal-title"> + $escapetool.xml($services.localization.render('attachment.move.targetLocation.modal.title')) + </h5> </div> <div class="modal-body"> #template("documentTree_macros.vm") @@ -124,8 +142,12 @@ }) </div> <div class="modal-footer"> - <button type="button" class="btn btn-default" data-dismiss="modal">Close</button> - <button type="button" class="btn btn-primary">Select</button> + <button type="button" class="btn btn-default" data-dismiss="modal"> + $escapetool.xml($services.localization.render('attachment.move.targetLocation.modal.closeButton')) + </button> + <button type="button" class="btn btn-primary"> + $escapetool.xml($services.localization.render('attachment.move.targetLocation.modal.selectButton')) + </button> </div> </div> </div> @@ -150,10 +172,14 @@ </div> <div class="buttons"> <span class="buttonwrapper"> - <input type="submit" value="$services.localization.render('attachment.move.submit')" class="button" /> + <input type="submit" + value="$escapetool.xml($services.localization.render('attachment.move.submit'))" + class="button" /> </span> <span class="buttonwrapper"> - <a class="secondary button" href="$doc.getURL()">$services.localization.render('cancel')</a> + <a class="secondary button" href="$doc.getURL()"> + $escapetool.xml($services.localization.render('cancel')) + </a> </span> </div> </form>
xwiki-platform-core/xwiki-platform-attachment/xwiki-platform-attachment-api/src/main/resources/templates/attachment/move.vm+3 −4 modified@@ -63,7 +63,7 @@ ## Do the move. It's the form in attachment/moveStep1.vm page that calls this page with step=2. #if ("$!targetAttachmentName.trim()" == '' || "$!targetLocation.trim()" == '') $response.setStatus(400) - #error($services.localization.render('attachment.move.emptyName')) + #error($escapetool.xml($services.localization.render('attachment.move.emptyName'))) #template("attachment/moveStep1.vm") #else #template('attachment/refactoring_macros.vm') @@ -75,8 +75,7 @@ #template("attachment/moveStep1.vm") #elseif (!$services.security.authorization.hasAccess('edit', $targetLocation)) $response.setStatus(403) - #error($services.localization.render('attachment.move.targetNotWritable', - [${escapetool.xml($targetLocation)}])) + #error($escapetool.xml($services.localization.render('attachment.move.targetNotWritable'))) #template('attachment/moveStep1.vm') #else #try("moveJobException") @@ -85,7 +84,7 @@ #set ($moveJob = $services.attachment.createMoveJob($moveRequest)) #end #if ("$!moveJobException" != '') - #displayException($services.localization.render('attachment.job.creation.error'), $moveJobException) + #displayException($escapetool.xml($services.localization.render('attachment.job.creation.error')), $moveJobException) #else $response.sendRedirect($doc.getURL($xcontext.action, $escapetool.url({ 'xpage': 'attachment/move',
xwiki-platform-core/xwiki-platform-attachment/xwiki-platform-attachment-api/src/main/resources/templates/attachment/refactoring_macros.vm+8 −4 modified@@ -22,8 +22,12 @@ ### #macro (displaySourceAttachment) <dt> - <label>$services.localization.render('attachment.move.source.label')</label> - <span class="xHint">$services.localization.render('attachment.move.source.hint')</span> + <label> + $escapetool.xml($services.localization.render('attachment.move.source.label')) + </label> + <span class="xHint"> + $escapetool.xml($services.localization.render('attachment.move.source.hint')) + </span> </dt> <dd> #hierarchy($attachment) @@ -41,7 +45,7 @@ <label> #set ($checked = !$request.updateReferences || $request.updateReferences == 'true') <input type="checkbox" name="updateReferences" value="true" #if ($checked)checked="checked"#end /> - $services.localization.render('attachment.move.links.label') + $escapetool.xml($services.localization.render('attachment.move.links.label')) </label> ## The value submitted when the checkbox is not checked, used to preserve the form state. <input type="hidden" name="updateReferences" value="false"/> @@ -50,7 +54,7 @@ #set ($backLinksCount = $services.attachment.backlinksCount($attachment)) #set ($backLinksURL = '') <span class="xHint"> - $services.localization.render('attachment.move.links.hint', [$backLinksCount]) + $escapetool.xml($services.localization.render('attachment.move.links.hint', [$backLinksCount])) </span> </dd> #end
xwiki-platform-core/xwiki-platform-attachment/xwiki-platform-attachment-api/src/test/java/org/xwiki/attachment/MovePageTest.java+15 −5 modified@@ -151,15 +151,25 @@ void submitMoveTargetEditNotAllowed() throws Exception this.request.put("step", "2"); Document render = Jsoup.parse(this.templateManager.render(MOVE_TEMPLATE)); - assertEquals("error: attachment.move.targetNotWritable [Space.Target]", + assertEquals("error: attachment.move.targetNotWritable", render.getElementsByClass("errormessage").get(0).text()); } + @Test + void submitTargetAttachmentNameEmpty() throws Exception + { + this.context.setDoc(this.xwiki.getDocument(new DocumentReference("xwiki", "Space", "Source"), this.context)); + this.request.put("step", "2"); + this.request.put("form_token", "a6DSv7pKWcPargoTvyx2Ww"); + Document render = Jsoup.parse(this.templateManager.render(MOVE_TEMPLATE)); + assertEquals("error: attachment.move.emptyName", render.select(".errormessage").text()); + } + @Test void submitMoveTargetAlreadyExists() throws Exception { DocumentReference sourceDocumentReference = new DocumentReference("xwiki", "Space", "Source"); - DocumentReference targetDocumentReference = new DocumentReference("xwiki", "Space", "Target"); + DocumentReference targetDocumentReference = new DocumentReference("xwiki", "Space", "Target\"'"); this.context.setDoc(this.xwiki.getDocument(sourceDocumentReference, this.context)); // Allow the user to edit the source and target documents. @@ -178,12 +188,12 @@ void submitMoveTargetAlreadyExists() throws Exception this.request.put("sourceAttachmentName", "oldname.txt"); this.request.put("autoRedirect", "true"); this.request.put("targetAttachmentName", ATTACHMENT_NAME); - this.request.put("targetLocation", "Space.Target"); + this.request.put("targetLocation", "Space.Target\"'"); this.request.put("step", "2"); Document render = Jsoup.parse(this.templateManager.render(MOVE_TEMPLATE)); - assertEquals( - "error: attachment.move.alreadyExists [attachment.txt, Space.Target, /xwiki/bin/view/Space/Target]", + assertEquals("error: attachment.move.alreadyExists " + + "[attachment.txt, Space.Target\"', /xwiki/bin/view/Space/Target%22%27]", render.getElementsByClass("errormessage").get(0).text()); } }
xwiki-platform-core/xwiki-platform-attachment/xwiki-platform-attachment-api/src/test/java/org/xwiki/attachment/MoveStatusPagesTest.java+105 −0 added@@ -0,0 +1,105 @@ +/* + * 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 org.xwiki.attachment; + +import java.util.List; + +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.xwiki.job.Request; +import org.xwiki.job.event.status.JobProgress; +import org.xwiki.job.event.status.JobStatus; +import org.xwiki.job.script.JobScriptService; +import org.xwiki.script.service.ScriptService; +import org.xwiki.template.TemplateManager; +import org.xwiki.template.script.TemplateScriptService; +import org.xwiki.test.annotation.ComponentList; +import org.xwiki.test.page.HTML50ComponentList; +import org.xwiki.test.page.PageTest; +import org.xwiki.test.page.XWikiSyntax21ComponentList; +import org.xwiki.xml.internal.html.filter.ControlCharactersFilter; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * Test of the macros provided by {@code moveStatus.vm}. + * + * @version $Id$ + * @since 14.4RC1 + */ +@HTML50ComponentList +@XWikiSyntax21ComponentList +@ComponentList({ + ControlCharactersFilter.class, + TemplateScriptService.class +}) +class MoveStatusPagesTest extends PageTest +{ + private static final String MOVE_STATUS_TEMPLATE = "attachment/moveStatus.vm"; + + private TemplateManager templateManager; + + @Mock + private JobScriptService jobScriptService; + + @BeforeEach + void setUp() throws Exception + { + this.templateManager = this.oldcore.getMocker().getInstance(TemplateManager.class); + this.componentManager.registerComponent(ScriptService.class, "job", this.jobScriptService); + } + + @Test + void renderScriptStatusUnknown() throws Exception + { + this.request.put("moveId", "42"); + Document render = Jsoup.parse(this.templateManager.render(MOVE_STATUS_TEMPLATE)); + assertEquals("attachment.move.status.notFound", render.select(".errormessage").text()); + verify(this.jobScriptService).getJobStatus(List.of("refactoring", "moveAttachment", "42")); + } + + @Test + void renderScriptActionGet() throws Exception + { + JobStatus jobStatus = mock(JobStatus.class); + Request request = mock(Request.class); + JobProgress jobProgress = mock(JobProgress.class); + + when(request.getId()).thenReturn(List.of("rq", "id")); + when(jobProgress.getOffset()).thenReturn(10d); + when(jobStatus.getRequest()).thenReturn(request); + when(jobStatus.getState()).thenReturn(JobStatus.State.RUNNING); + when(jobStatus.getProgress()).thenReturn(jobProgress); + when(this.jobScriptService.getJobStatus(List.of("refactoring", "moveAttachment", "42"))) + .thenReturn(jobStatus); + this.request.put("moveId", "42"); + this.context.setAction("get"); + assertEquals( + "{\"id\":[\"rq\",\"id\"],\"state\":\"RUNNING\",\"progress\":{\"offset\":10.0}," + + "\"log\":{\"offset\":0,\"items\":[]},\"message\":\" \",\"questionTimeLeft\":0}", + this.templateManager.render(MOVE_STATUS_TEMPLATE).trim()); + } +}
xwiki-platform-core/xwiki-platform-attachment/xwiki-platform-attachment-api/src/test/java/org/xwiki/attachment/MoveStep1PageTest.java+93 −0 added@@ -0,0 +1,93 @@ +/* + * 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 org.xwiki.attachment; + +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.xwiki.csrf.script.CSRFTokenScriptService; +import org.xwiki.model.reference.AttachmentReference; +import org.xwiki.model.reference.DocumentReference; +import org.xwiki.script.service.ScriptService; +import org.xwiki.security.script.SecurityScriptServiceComponentList; +import org.xwiki.template.TemplateManager; +import org.xwiki.test.page.PageTest; +import org.xwiki.velocity.VelocityManager; + +import com.xpn.xwiki.doc.XWikiDocument; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.Mockito.when; + +/** + * Test of {@code moveStep1.vm} template. + * + * @version $Id$ + * @since 14.4RC1 + */ +@SecurityScriptServiceComponentList +class MoveStep1PageTest extends PageTest +{ + private static final String MOVE_STEP1_TEMPLATE = "attachment/moveStep1.vm"; + + private static final String ATTACHMENT_NAME = ">a<b>tt</b>achmen\"'t1.txt"; + + private static final DocumentReference DOC_REF = new DocumentReference("xwiki", "Space", "Page"); + + private TemplateManager templateManager; + + @Mock + private CSRFTokenScriptService csrfScriptService; + + private VelocityManager velocityManager; + + @BeforeEach + void setUp() throws Exception + { + this.templateManager = this.oldcore.getMocker().getInstance(TemplateManager.class); + this.velocityManager = this.oldcore.getMocker().getInstance(VelocityManager.class); + this.componentManager.registerComponent(ScriptService.class, "csrf", this.csrfScriptService); + when(this.csrfScriptService.getToken()).thenReturn("csrf_token"); + } + + @Test + void escapedValues() throws Exception + { + XWikiDocument document = this.xwiki.getDocument(DOC_REF, this.context); + this.context.setDoc(document); + + this.velocityManager.getVelocityContext().put("attachment", new AttachmentReference(ATTACHMENT_NAME, + document.getDocumentReference())); + + Document render = Jsoup.parse(this.templateManager.render(MOVE_STEP1_TEMPLATE)); + assertNotNull(render); + assertEquals("cancel", render.select(".buttons .secondary.button").text()); + assertEquals("attachment.move.submit", render.select(".buttons input").attr("value")); + assertEquals("attachment.move.autoRedirect.label", render.select("dt.autoRedirect label").text()); + assertEquals("attachment.move.autoRedirect.hint", render.select("dd.autoRedirect .xHint").text()); + assertEquals("attachment.move.newName.label", render.select("dt.targetAttachmentName label").text()); + assertEquals("attachment.move.newName.hint", render.select("dt.targetAttachmentName .xHint").text()); + assertEquals(ATTACHMENT_NAME, render.select("[name='sourceAttachmentName']").attr("value")); + assertEquals(ATTACHMENT_NAME, render.select("#targetAttachmentNameTitle").attr("value")); + } +}
xwiki-platform-core/xwiki-platform-attachment/xwiki-platform-attachment-api/src/test/java/org/xwiki/attachment/RefactoringMacrosPageTest.java+122 −0 added@@ -0,0 +1,122 @@ +/* + * 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 org.xwiki.attachment; + +import java.util.stream.Stream; + +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.MethodSource; +import org.xwiki.attachment.script.AttachmentScriptService; +import org.xwiki.model.reference.DocumentReference; +import org.xwiki.rendering.syntax.Syntax; +import org.xwiki.script.service.ScriptService; +import org.xwiki.test.annotation.ComponentList; +import org.xwiki.test.page.HTML50ComponentList; +import org.xwiki.test.page.PageTest; +import org.xwiki.test.page.XWikiSyntax21ComponentList; +import org.xwiki.xml.internal.html.filter.ControlCharactersFilter; + +import com.xpn.xwiki.doc.XWikiDocument; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Test of the macros provided by {@code refactoring_macros.vm}. + * + * @version $Id$ + * @since 14.4RC1 + */ +@ComponentList({ ControlCharactersFilter.class }) +@HTML50ComponentList +@XWikiSyntax21ComponentList +class RefactoringMacrosPageTest extends PageTest +{ + public static final DocumentReference DOCUMENT_REFERENCE = new DocumentReference("xwiki", "Space", "Page"); + + @ParameterizedTest + @CsvSource({ + "false,false,true", + "false,true,false", + "true,false,false", + "true,true, false" + }) + void displayAttachmentLinksCheckboxIsHidden(boolean isAdvancedUser, boolean isSuperAdmin, boolean expectHidden) + throws Exception + { + XWikiDocument document = this.xwiki.getDocument(DOCUMENT_REFERENCE, this.context); + document.setContent(String.format("{{velocity}}\n" + + "#template('attachment/refactoring_macros.vm')\n" + + "#set($isAdvancedUser = %s)\n" + + "#set($isSuperAdmin = %s)\n" + + "{{html}}#displayAttachmentLinksCheckbox(){{/html}}\n" + + "{{/velocity}}", + isAdvancedUser, isSuperAdmin)); + document.setSyntax(Syntax.XWIKI_2_1); + this.xwiki.saveDocument(document, this.context); + Document content = Jsoup.parse(document.getRenderedContent(this.context)); + assertEquals(expectHidden, content.select("dt").hasClass("hidden")); + assertEquals(expectHidden, content.select("dd").hasClass("hidden")); + } + + @ParameterizedTest + @MethodSource("displayAttachmentLinksCheckboxSource") + void displayAttachmentLinksCheckbox(String updateReferences, String expectedChecked) + throws Exception + { + AttachmentScriptService attachmentScriptService = mock(AttachmentScriptService.class); + this.componentManager.registerComponent(ScriptService.class, "attachment", attachmentScriptService); + when(attachmentScriptService.backlinksCount(any())).thenReturn(10L); + + XWikiDocument document = this.xwiki.getDocument(DOCUMENT_REFERENCE, this.context); + document.setContent("{{velocity}}\n" + + "#template('attachment/refactoring_macros.vm')\n" + + "{{html}}#displayAttachmentLinksCheckbox(){{/html}}\n" + + "{{/velocity}}"); + document.setSyntax(Syntax.XWIKI_2_1); + this.xwiki.saveDocument(document, this.context); + + this.request.put("updateReferences", updateReferences); + + Document parse = Jsoup.parse(document.getRenderedContent(this.context)); + // The checkbox is checked by default, unless updateReferences is defined to value different than 'true'. + assertEquals(expectedChecked, parse.select("[name='updateReferences']").attr("checked")); + assertEquals("attachment.move.links.hint [10]", parse.select(".xHint").text()); + } + + /** + * @return the arguments for {@link #displayAttachmentLinksCheckbox(String, String)} + */ + private static Stream<Arguments> displayAttachmentLinksCheckboxSource() + { + return Stream.of( + Arguments.of(null, "checked"), + Arguments.of("", ""), + Arguments.of("false", ""), + Arguments.of("true", "checked") + ); + } +}
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
6- github.com/advisories/GHSA-9r9j-57rf-f6vjghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2022-36097ghsaADVISORY
- github.com/xwiki/xwiki-platform/commit/fbc4bfbae4f6ce8109addb281de86a03acdb9277ghsax_refsource_MISCWEB
- github.com/xwiki/xwiki-platform/security/advisories/GHSA-9r9j-57rf-f6vjghsax_refsource_CONFIRMWEB
- jira.xwiki.org/browse/XWIKI-19667ghsax_refsource_MISCWEB
- raw.githubusercontent.com/xwiki/xwiki-platform/xwiki-platform-14.0-rc-1/xwiki-platform-core/xwiki-platform-attachment/xwiki-platform-attachment-api/src/main/resources/templates/attachment/moveStep1.vmghsax_refsource_MISCWEB
News mentions
0No linked articles in our index yet.