Skip to content

Commit 29cb81f

Browse files
surlimichituxmanuelleduc
committed
XWIKI-23433: Provide frontend check of links (#4645)
* Add new properties to enable or not frontend link checks and whitelist of links * Provide script services to access the data from the configuration * Add new mechanism allowing to check links when clicking on them based on the configuration * Apply suggestions from code review * Fix naming of allowed urls * Provide integration test * Add proper since information --------- Co-authored-by: Michael Hamann <michael.hamann@xwiki.com> Co-authored-by: Manuel Leduc <manuel.leduc@xwiki.com> (cherry picked from commit 7b5a4f8)
1 parent f9a2c95 commit 29cb81f

File tree

8 files changed

+312
-5
lines changed

8 files changed

+312
-5
lines changed

xwiki-platform-core/xwiki-platform-flamingo/xwiki-platform-flamingo-skin/xwiki-platform-flamingo-skin-resources/src/main/resources/flamingo/javascript.vm

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,15 @@ $xwiki.jsfx.use("flamingo$jsExtension", {'forceSkinAction' : true, 'language' :
299299
'wysiwyg': true
300300
})##
301301
#end
302+
#if ($services.security.url.isFrontendUrlCheckEnabled())
303+
<script type="application/json" id="trusted-domains-configuration">
304+
{
305+
"trustedDomains": $jsontool.serialize($services.security.url.getTrustedDomains()),
306+
"allowedUrls": $jsontool.serialize($services.security.url.getAllowedFrontendUrls())
307+
}
308+
</script>
309+
$xwiki.jsfx.use('uicomponents/link/link-protection.js')##
310+
#end
302311
##
303312
## Hooks for inserting JavaScript skin extensions
304313
##

xwiki-platform-core/xwiki-platform-flamingo/xwiki-platform-flamingo-skin/xwiki-platform-flamingo-skin-test/xwiki-platform-flamingo-skin-test-docker/src/test/it/org/xwiki/flamingo/test/docker/NavigationIT.java

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@
2727
import org.junit.jupiter.api.BeforeEach;
2828
import org.junit.jupiter.api.Order;
2929
import org.junit.jupiter.api.Test;
30+
import org.openqa.selenium.Alert;
31+
import org.openqa.selenium.By;
32+
import org.openqa.selenium.NoAlertPresentException;
3033
import org.xwiki.administration.test.po.AdministrationPage;
3134
import org.xwiki.flamingo.skin.test.po.AttachmentsPane;
3235
import org.xwiki.flamingo.skin.test.po.AttachmentsViewPage;
@@ -35,6 +38,7 @@
3538
import org.xwiki.test.docker.junit5.TestReference;
3639
import org.xwiki.test.docker.junit5.UITest;
3740
import org.xwiki.test.ui.TestUtils;
41+
import org.xwiki.test.ui.XWikiWebDriver;
3842
import org.xwiki.test.ui.po.CommentsTab;
3943
import org.xwiki.test.ui.po.HistoryPane;
4044
import org.xwiki.test.ui.po.InformationPane;
@@ -43,14 +47,18 @@
4347

4448
import static org.junit.jupiter.api.Assertions.assertEquals;
4549
import static org.junit.jupiter.api.Assertions.assertTrue;
50+
import static org.junit.jupiter.api.Assertions.fail;
4651

4752
/**
4853
* Tests related to navigation in the wiki.
4954
*
5055
* @since 11.10
5156
* @version $Id$
5257
*/
53-
@UITest
58+
@UITest(properties = {
59+
"xwikiPropertiesAdditionalProperties=url.trustedDomains=www.xwiki.org,extensions.xwiki.org\n"
60+
+ "url.allowedFrontendUrls=https://github.com/xwiki/xwiki-platform,https://github.com/xwiki/"
61+
})
5462
public class NavigationIT
5563
{
5664
@BeforeAll
@@ -206,4 +214,87 @@ public void simpleBinUrlDoesNotThrowException(TestUtils testUtils)
206214
ViewPage viewPage = new ViewPage();
207215
assertEquals("XWiki - Main - Main", viewPage.getPageTitle());
208216
}
217+
218+
@Order(5)
219+
@Test
220+
void navigationToExternalPages(TestUtils testUtils, TestReference testReference) throws Exception
221+
{
222+
String pageContent = """
223+
[[Internal link>>doc:Navigation.Test]]
224+
[[Google external>>https://www.google.com]]
225+
[[XWiki.org external>>https://www.xwiki.org]]
226+
[[Contrib xwiki>>https://contrib.xwiki.org]]
227+
[[Extensions xwiki>>https://extensions.xwiki.org]]
228+
[[Specific extensions pages>>https://extensions.xwiki.org/bin/view/WebHome]]
229+
[[Github commons>>https://github.com/xwiki/xwiki-commons]]
230+
[[Github XWiki>>https://github.com/xwiki/]]
231+
[[Github platform>>https://github.com/xwiki/xwiki-platform]]
232+
""";
233+
testUtils.rest().savePage(testReference, pageContent, "Test link navigation");
234+
testUtils.rest().savePage(new DocumentReference("xwiki", "Navigation", "Test"), "Test navigation internal "
235+
+ "link", "Navigation test page");
236+
237+
XWikiWebDriver driver = testUtils.getDriver();
238+
testUtils.gotoPage(testReference);
239+
driver.findElementWithoutWaiting(By.linkText("Google external")).click();
240+
Alert alert = driver.switchTo().alert();
241+
assertEquals("You are about to leave the domain \"host.testcontainers.internal\" to follow a link "
242+
+ "to \"www.google.com\". Are you sure you want to continue?", alert.getText());
243+
alert.dismiss();
244+
245+
driver.findElementWithoutWaiting(By.linkText("Internal link")).click();
246+
ViewPage viewPage = new ViewPage();
247+
assertEquals("Test navigation internal link", viewPage.getContent());
248+
249+
testUtils.gotoPage(testReference);
250+
driver.findElementWithoutWaiting(By.linkText("XWiki.org external")).click();
251+
try {
252+
driver.switchTo().alert();
253+
fail("No alert should be present");
254+
} catch (NoAlertPresentException e) {
255+
}
256+
257+
testUtils.gotoPage(testReference);
258+
driver.findElementWithoutWaiting(By.linkText("Contrib xwiki")).click();
259+
alert = driver.switchTo().alert();
260+
assertEquals("You are about to leave the domain \"host.testcontainers.internal\" to follow a link "
261+
+ "to \"contrib.xwiki.org\". Are you sure you want to continue?", alert.getText());
262+
alert.dismiss();
263+
264+
driver.findElementWithoutWaiting(By.linkText("Extensions xwiki")).click();
265+
try {
266+
driver.switchTo().alert();
267+
fail("No alert should be present");
268+
} catch (NoAlertPresentException e) {
269+
}
270+
271+
testUtils.gotoPage(testReference);
272+
driver.findElementWithoutWaiting(By.linkText("Specific extensions pages")).click();
273+
try {
274+
driver.switchTo().alert();
275+
fail("No alert should be present");
276+
} catch (NoAlertPresentException e) {
277+
}
278+
279+
testUtils.gotoPage(testReference);
280+
driver.findElementWithoutWaiting(By.linkText("Github commons")).click();
281+
assertEquals("You are about to leave the domain \"host.testcontainers.internal\" to follow a link "
282+
+ "to \"github.com\". Are you sure you want to continue?", alert.getText());
283+
alert.dismiss();
284+
285+
driver.findElementWithoutWaiting(By.linkText("Github XWiki")).click();
286+
try {
287+
driver.switchTo().alert();
288+
fail("No alert should be present");
289+
} catch (NoAlertPresentException e) {
290+
}
291+
292+
testUtils.gotoPage(testReference);
293+
driver.findElementWithoutWaiting(By.linkText("Github platform")).click();
294+
try {
295+
driver.switchTo().alert();
296+
fail("No alert should be present");
297+
} catch (NoAlertPresentException e) {
298+
}
299+
}
209300
}

xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/src/main/java/org/xwiki/url/URLConfiguration.java

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@
1919
*/
2020
package org.xwiki.url;
2121

22-
import java.util.Collections;
2322
import java.util.List;
2423

2524
import org.xwiki.component.annotation.Role;
25+
import org.xwiki.stability.Unstable;
2626

2727
/**
2828
* Configuration options for the URL module.
@@ -60,7 +60,7 @@ default boolean useResourceLastModificationDate()
6060
*/
6161
default List<String> getTrustedDomains()
6262
{
63-
return Collections.emptyList();
63+
return List.of();
6464
}
6565

6666
/**
@@ -88,4 +88,34 @@ default List<String> getTrustedSchemes()
8888
{
8989
return List.of("http", "https", "ftp");
9090
}
91+
92+
/**
93+
* @return {@code true} if checks should be done in the frontend when clicking on a link to validate it's driving
94+
* to an authorized domain. This is independent from {@link #isTrustedDomainsEnabled()} which aims at enabling
95+
* checks server side only.
96+
* @since 17.9.0RC1
97+
* @since 17.4.6
98+
* @since 16.10.13
99+
*/
100+
@Unstable
101+
default boolean isFrontendUrlCheckEnabled()
102+
{
103+
return true;
104+
}
105+
106+
/**
107+
* Define a list of allowed frontend URLs: in case the {@link #isFrontendUrlCheckEnabled()} is enabled, then
108+
* this list can be used to allow specific URLs without asking confirmation from the user, while avoiding to add
109+
* an entire domain in the list of trusted domains.
110+
*
111+
* @return the list of allowed frontend URLs
112+
* @since 17.9.0RC1
113+
* @since 17.4.6
114+
* @since 16.10.13
115+
*/
116+
@Unstable
117+
default List<String> getAllowedFrontendUrls()
118+
{
119+
return List.of();
120+
}
91121
}

xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-api/src/main/java/org/xwiki/url/script/URLSecurityScriptService.java

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import java.net.URI;
2323
import java.net.URISyntaxException;
24+
import java.util.List;
2425

2526
import javax.inject.Inject;
2627
import javax.inject.Named;
@@ -29,6 +30,8 @@
2930
import org.slf4j.Logger;
3031
import org.xwiki.component.annotation.Component;
3132
import org.xwiki.script.service.ScriptService;
33+
import org.xwiki.stability.Unstable;
34+
import org.xwiki.url.URLConfiguration;
3235
import org.xwiki.url.URLSecurityManager;
3336

3437
/**
@@ -46,6 +49,9 @@ public class URLSecurityScriptService implements ScriptService
4649
@Inject
4750
private URLSecurityManager urlSecurityManager;
4851

52+
@Inject
53+
private URLConfiguration urlConfiguration;
54+
4955
@Inject
5056
private Logger logger;
5157

@@ -71,4 +77,40 @@ public URI parseToSafeURI(String uriRepresentation) throws URISyntaxException, S
7177
return null;
7278
}
7379
}
80+
81+
/**
82+
* @return the list of trusted domains.
83+
* @since 17.9.0RC1
84+
* @since 17.4.6
85+
* @since 16.10.13
86+
*/
87+
@Unstable
88+
public List<String> getTrustedDomains()
89+
{
90+
return this.urlConfiguration.getTrustedDomains();
91+
}
92+
93+
/**
94+
* @return {@code true} if the mechanism to enforce URLs check on frontend is enabled.
95+
* @since 17.9.0RC1
96+
* @since 17.4.6
97+
* @since 16.10.13
98+
*/
99+
@Unstable
100+
public boolean isFrontendUrlCheckEnabled()
101+
{
102+
return this.urlConfiguration.isFrontendUrlCheckEnabled();
103+
}
104+
105+
/**
106+
* @return the list of URLs that are allowed to avoid asking confirmation to users when accessing them.
107+
* @since 17.9.0RC1
108+
* @since 17.4.6
109+
* @since 16.10.13
110+
*/
111+
@Unstable
112+
public List<String> getAllowedFrontendUrls()
113+
{
114+
return this.urlConfiguration.getAllowedFrontendUrls();
115+
}
74116
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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+
url.api.followLinkConfirmationText=You are about to leave the domain "{0}" to follow a link to "{1}". Are you sure \
21+
you want to continue?

xwiki-platform-core/xwiki-platform-url/xwiki-platform-url-default/src/main/java/org/xwiki/url/internal/DefaultURLConfiguration.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
*/
2020
package org.xwiki.url.internal;
2121

22-
import java.util.Collections;
2322
import java.util.List;
2423

2524
import javax.inject.Inject;
@@ -67,7 +66,7 @@ public boolean useResourceLastModificationDate()
6766
@Override
6867
public List<String> getTrustedDomains()
6968
{
70-
return this.configuration.get().getProperty(PREFIX + "trustedDomains", Collections.emptyList());
69+
return this.configuration.get().getProperty(PREFIX + "trustedDomains", List.of());
7170
}
7271

7372
@Override
@@ -81,4 +80,16 @@ public List<String> getTrustedSchemes()
8180
{
8281
return this.configuration.get().getProperty(PREFIX + "trustedSchemes", List.of("http", "https", "ftp"));
8382
}
83+
84+
@Override
85+
public boolean isFrontendUrlCheckEnabled()
86+
{
87+
return this.configuration.get().getProperty(PREFIX + "frontendUrlCheckEnabled", true);
88+
}
89+
90+
@Override
91+
public List<String> getAllowedFrontendUrls()
92+
{
93+
return this.configuration.get().getProperty(PREFIX + "allowedFrontendUrls", List.of());
94+
}
8495
}

0 commit comments

Comments
 (0)