Skip to content

Commit 9f8cc88

Browse files
pjeanjeanmichitux
authored andcommitted
XWIKI-21424: Use CSRF token in the realtime HTML Converter API
* Add a mandatory CSRF token to use RTFrontend.ConvertHTML * Add a page test for RTFrontend.ConvertHTML that checks for the proper use of the CSRF token * Improve URL creation and add Translations.xml (cherry picked from commit 4896712)
1 parent f30d9c6 commit 9f8cc88

File tree

5 files changed

+230
-4
lines changed

5 files changed

+230
-4
lines changed

xwiki-platform-core/xwiki-platform-realtime/xwiki-platform-realtime-ui/pom.xml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,5 +75,27 @@
7575
<version>${project.version}</version>
7676
<scope>runtime</scope>
7777
</dependency>
78+
<!-- Test dependencies. -->
79+
<dependency>
80+
<groupId>org.xwiki.platform</groupId>
81+
<artifactId>xwiki-platform-test-page</artifactId>
82+
<version>${project.version}</version>
83+
<scope>test</scope>
84+
</dependency>
85+
<!-- Provides the component list for RenderingScriptService. -->
86+
<dependency>
87+
<groupId>org.xwiki.platform</groupId>
88+
<artifactId>xwiki-platform-rendering-xwiki</artifactId>
89+
<version>${project.version}</version>
90+
<type>test-jar</type>
91+
<scope>test</scope>
92+
</dependency>
93+
<dependency>
94+
<groupId>org.xwiki.platform</groupId>
95+
<artifactId>xwiki-platform-rendering-configuration-default</artifactId>
96+
<version>${project.version}</version>
97+
<type>test-jar</type>
98+
<scope>test</scope>
99+
</dependency>
78100
</dependencies>
79101
</project>

xwiki-platform-core/xwiki-platform-realtime/xwiki-platform-realtime-ui/src/main/resources/RTFrontend/ConvertHTML.xml

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
2121
-->
2222

23-
<xwikidoc version="1.4" reference="RTFrontend.ConvertHTML" locale="">
23+
<xwikidoc version="1.5" reference="RTFrontend.ConvertHTML" locale="">
2424
<web>RTFrontend</web>
2525
<name>ConvertHTML</name>
2626
<language/>
@@ -52,8 +52,13 @@
5252
'documentReference': $documentReference
5353
})
5454
#if ($xcontext.action == 'get')
55-
## FIXME: We shouldn't depend on CKEditor. This code should work independent of the configured WYSIWYG editor.
56-
$xwiki.getDocument('CKEditor.ContentSheet').getRenderedContent()
55+
## Check that the CSRF token matches the user.
56+
#if (!$services.csrf.isTokenValid($request.form_token))
57+
$response.sendError(403, $services.localization.render('rtfFrontend.convertHtml.invalidCsrfToken'))
58+
#else
59+
## FIXME: We shouldn't depend on CKEditor. This code should work independent of the configured WYSIWYG editor.
60+
$xwiki.getDocument('CKEditor.ContentSheet').getRenderedContent()
61+
#end
5762
#end
5863
{{/velocity}}</content>
5964
</xwikidoc>
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<?xml version="1.1" encoding="UTF-8"?>
2+
3+
<!--
4+
* See the NOTICE file distributed with this work for additional
5+
* information regarding copyright ownership.
6+
*
7+
* This is free software; you can redistribute it and/or modify it
8+
* under the terms of the GNU Lesser General Public License as
9+
* published by the Free Software Foundation; either version 2.1 of
10+
* the License, or (at your option) any later version.
11+
*
12+
* This software is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15+
* Lesser General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU Lesser General Public
18+
* License along with this software; if not, write to the Free
19+
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
20+
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
21+
-->
22+
23+
<xwikidoc version="1.5" reference="XWiki.Realtime.Translations" locale="">
24+
<web>XWiki.Realtime</web>
25+
<name>Translations</name>
26+
<language/>
27+
<defaultLanguage>en</defaultLanguage>
28+
<translation>0</translation>
29+
<creator>xwiki:XWiki.Admin</creator>
30+
<parent>Main.WebHome</parent>
31+
<author>xwiki:XWiki.Admin</author>
32+
<contentAuthor>xwiki:XWiki.Admin</contentAuthor>
33+
<version>1.1</version>
34+
<title/>
35+
<comment/>
36+
<minorEdit>false</minorEdit>
37+
<syntaxId>plain/1.0</syntaxId>
38+
<hidden>true</hidden>
39+
<content>rtfFrontend.convertHtml.invalidCsrfToken=Invalid CSRF Token</content>
40+
<object>
41+
<name>XWiki.Realtime.Translations</name>
42+
<number>0</number>
43+
<className>XWiki.TranslationDocumentClass</className>
44+
<guid>41105c55-62ec-47bc-9dd0-c502cad68de6</guid>
45+
<class>
46+
<name>XWiki.TranslationDocumentClass</name>
47+
<customClass/>
48+
<customMapping/>
49+
<defaultViewSheet/>
50+
<defaultEditSheet/>
51+
<defaultWeb/>
52+
<nameField/>
53+
<validationScript/>
54+
<scope>
55+
<cache>0</cache>
56+
<disabled>0</disabled>
57+
<displayType>select</displayType>
58+
<freeText>forbidden</freeText>
59+
<largeStorage>0</largeStorage>
60+
<multiSelect>0</multiSelect>
61+
<name>scope</name>
62+
<number>1</number>
63+
<prettyName>Scope</prettyName>
64+
<relationalStorage>0</relationalStorage>
65+
<separator> </separator>
66+
<separators>|, </separators>
67+
<size>1</size>
68+
<unmodifiable>0</unmodifiable>
69+
<values>GLOBAL|WIKI|USER|ON_DEMAND</values>
70+
<classType>com.xpn.xwiki.objects.classes.StaticListClass</classType>
71+
</scope>
72+
</class>
73+
<property>
74+
<scope>WIKI</scope>
75+
</property>
76+
</object>
77+
</xwikidoc>
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
/*
2+
* See the NOTICE file distributed with this work for additional
3+
* information regarding copyright ownership.
4+
*
5+
* This is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU Lesser General Public License as
7+
* published by the Free Software Foundation; either version 2.1 of
8+
* the License, or (at your option) any later version.
9+
*
10+
* This software is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13+
* Lesser General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU Lesser General Public
16+
* License along with this software; if not, write to the Free
17+
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18+
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
19+
*/
20+
package org.xwiki.realtime.ui;
21+
22+
import javax.inject.Provider;
23+
24+
import org.jsoup.nodes.Document;
25+
import org.junit.jupiter.api.BeforeEach;
26+
import org.junit.jupiter.api.Test;
27+
import org.xwiki.csrf.script.CSRFTokenScriptService;
28+
import org.xwiki.model.reference.DocumentReference;
29+
import org.xwiki.rendering.RenderingScriptServiceComponentList;
30+
import org.xwiki.rendering.internal.configuration.DefaultRenderingConfigurationComponentList;
31+
import org.xwiki.script.service.ScriptService;
32+
import org.xwiki.test.page.HTML50ComponentList;
33+
import org.xwiki.test.page.PageTest;
34+
import org.xwiki.test.page.XWikiSyntax21ComponentList;
35+
36+
import com.xpn.xwiki.XWikiContext;
37+
import com.xpn.xwiki.user.api.XWikiRightService;
38+
39+
import static org.junit.jupiter.api.Assertions.assertEquals;
40+
import static org.mockito.ArgumentMatchers.anyInt;
41+
import static org.mockito.Mockito.mock;
42+
import static org.mockito.Mockito.never;
43+
import static org.mockito.Mockito.spy;
44+
import static org.mockito.Mockito.verify;
45+
import static org.mockito.Mockito.when;
46+
47+
@RenderingScriptServiceComponentList
48+
@DefaultRenderingConfigurationComponentList
49+
@HTML50ComponentList
50+
@XWikiSyntax21ComponentList
51+
class ConvertHTMLPageTest extends PageTest
52+
{
53+
54+
private static final String WIKI_NAME = "xwiki";
55+
56+
private static final String XWIKI_SPACE = "RTFrontend";
57+
58+
private static final DocumentReference RTF_FRONTEND_CONVERT_HTML =
59+
new DocumentReference(WIKI_NAME, XWIKI_SPACE, "ConvertHTML");
60+
61+
private static final String CSRF_TOKEN = "a0a0a0a0";
62+
63+
private CSRFTokenScriptService tokenService;
64+
65+
@BeforeEach
66+
void setUp() throws Exception
67+
{
68+
// Mock the Token Service to get a consistent CSRF token throughout the tests.
69+
this.tokenService = this.oldcore.getMocker().registerMockComponent(ScriptService.class, "csrf",
70+
CSRFTokenScriptService.class, true);
71+
when(this.tokenService.isTokenValid(CSRF_TOKEN)).thenReturn(true);
72+
73+
this.xwiki.initializeMandatoryDocuments(this.context);
74+
75+
this.context = mock(XWikiContext.class);
76+
Provider<XWikiContext> xcontextProvider =
77+
this.componentManager.registerMockComponent(XWikiContext.TYPE_PROVIDER);
78+
when(xcontextProvider.get()).thenReturn(this.context);
79+
when(this.context.getRequest()).thenReturn(this.request);
80+
when(this.context.getResponse()).thenReturn(this.response);
81+
when(this.context.getWiki()).thenReturn(this.xwiki);
82+
83+
// Fake programming access level to display the complete page.
84+
XWikiRightService rightService = this.oldcore.getMockRightService();
85+
when(this.xwiki.getRightService()).thenReturn(rightService);
86+
when(rightService.hasProgrammingRights(this.context)).thenReturn(true);
87+
this.response = spy(this.response);
88+
when(this.context.getResponse()).thenReturn(this.response);
89+
}
90+
91+
@Test
92+
void checkValidCSRFToken() throws Exception
93+
{
94+
when(this.context.getAction()).thenReturn("get");
95+
this.request.put("text", "Hello");
96+
this.request.put("form_token", CSRF_TOKEN);
97+
Document result = renderHTMLPage(RTF_FRONTEND_CONVERT_HTML);
98+
99+
verify(this.response, never()).setStatus(anyInt());
100+
verify(this.tokenService).isTokenValid(CSRF_TOKEN);
101+
assertEquals("$xwiki.getDocument('CKEditor.ContentSheet').getRenderedContent()",
102+
result.getElementsByTag("body").text());
103+
}
104+
105+
@Test
106+
void checkInvalidCSRFToken() throws Exception
107+
{
108+
String wrongToken = "wrong_token";
109+
when(this.context.getAction()).thenReturn("get");
110+
this.request.put("text", "Hello");
111+
this.request.put("form_token", wrongToken);
112+
Document result = renderHTMLPage(RTF_FRONTEND_CONVERT_HTML);
113+
114+
verify(this.response).sendError(403, "rtfFrontend.convertHtml.invalidCsrfToken");
115+
verify(this.tokenService).isTokenValid(wrongToken);
116+
assertEquals("", result.getElementsByTag("body").text());
117+
}
118+
}

xwiki-platform-core/xwiki-platform-realtime/xwiki-platform-realtime-webjar/src/main/webjar/loader.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,11 @@ define('xwiki-realtime-loader', [
7171
var userReference = xm.userReference ? XWiki.Model.serialize(xm.userReference) : 'xwiki:XWiki.XWikiGuest';
7272
return {
7373
WebsocketURL: realtimeConfig.webSocketURL,
74-
htmlConverterUrl: new XWiki.Document('ConvertHTML', 'RTFrontend').getURL('get', 'xpage=plain&outputSyntax=plain'),
74+
htmlConverterUrl: new XWiki.Document('ConvertHTML', 'RTFrontend').getURL('get', $.param({
75+
'xpage': 'plain',
76+
'outputSyntax': 'plain',
77+
'form_token': document.documentElement.dataset.xwikiFormToken
78+
})),
7579
// userId === <userReference>-encoded(<userName>)%2d<randomNumber>
7680
userName: userReference + '-' + encodeURIComponent(realtimeConfig.user.name + '-').replace(/-/g, '%2d') +
7781
String(Math.random()).substring(2),

0 commit comments

Comments
 (0)