-
-
Notifications
You must be signed in to change notification settings - Fork 601
XWIKI-23433: Provide frontend check of links #4645
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -299,6 +299,17 @@ $xwiki.jsfx.use("flamingo$jsExtension", {'forceSkinAction' : true, 'language' : | |
'wysiwyg': true | ||
})## | ||
#end | ||
#if ($services.security.url.isFrontendUrlCheckEnabled()) | ||
<script type="application/json" id="trusted-domains-configuration"> | ||
{ | ||
"trustedDomains": $jsontool.serialize($services.security.url.getTrustedDomains()), | ||
"whitelistedUrls": $jsontool.serialize($services.security.url.getWhitelistedFrontendUrls()), | ||
"confirmationText": $jsontool.serialize($services.localization.render('url.api.followLinkConfirmationText', | ||
['__CURRENT_DOMAIN__', '__EXTERNAL_DOMAIN__'])) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Any reason for not using the standard translations mechanism in JavaScript? If yes, it might be good to add a comment. |
||
} | ||
</script> | ||
$xwiki.jsfx.use('uicomponents/link/link-protection.js')## | ||
#end | ||
## | ||
## Hooks for inserting JavaScript skin extensions | ||
## | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,10 +19,10 @@ | |
*/ | ||
package org.xwiki.url; | ||
|
||
import java.util.Collections; | ||
import java.util.List; | ||
|
||
import org.xwiki.component.annotation.Role; | ||
import org.xwiki.stability.Unstable; | ||
|
||
/** | ||
* Configuration options for the URL module. | ||
|
@@ -60,7 +60,7 @@ default boolean useResourceLastModificationDate() | |
*/ | ||
default List<String> getTrustedDomains() | ||
{ | ||
return Collections.emptyList(); | ||
return List.of(); | ||
} | ||
|
||
/** | ||
|
@@ -88,4 +88,30 @@ default List<String> getTrustedSchemes() | |
{ | ||
return List.of("http", "https", "ftp"); | ||
} | ||
|
||
/** | ||
* @return {@code true} if checks should be done in the frontend when clicking on a link to validate it's driving | ||
* to an authorized domain. This is independent from {@link #isTrustedDomainsEnabled()} which aims at enabling | ||
* checks server side only. | ||
* @since 17.9.0RC1 | ||
*/ | ||
@Unstable | ||
default boolean isFrontendUrlCheckEnabled() | ||
{ | ||
return true; | ||
} | ||
|
||
/** | ||
* Define a list of whitelisted frontend URLs: in case the {@link #isFrontendUrlCheckEnabled()} is enabled, then | ||
* this list can be used to allow specific URLs without asking confirmation from the user, while avoiding to add | ||
* an entire domain in the list of trusted domains. | ||
* | ||
* @return the list of whitelisted frontend URLs | ||
* @since 17.9.0RC1 | ||
*/ | ||
@Unstable | ||
default List<String> getWhitelistedFrontendUrls() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as before, I would prefer "allowed" instead of "white". |
||
{ | ||
return List.of(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,6 +21,7 @@ | |
|
||
import java.net.URI; | ||
import java.net.URISyntaxException; | ||
import java.util.List; | ||
|
||
import javax.inject.Inject; | ||
import javax.inject.Named; | ||
|
@@ -29,6 +30,8 @@ | |
import org.slf4j.Logger; | ||
import org.xwiki.component.annotation.Component; | ||
import org.xwiki.script.service.ScriptService; | ||
import org.xwiki.stability.Unstable; | ||
import org.xwiki.url.URLConfiguration; | ||
import org.xwiki.url.URLSecurityManager; | ||
|
||
/** | ||
|
@@ -46,6 +49,9 @@ public class URLSecurityScriptService implements ScriptService | |
@Inject | ||
private URLSecurityManager urlSecurityManager; | ||
|
||
@Inject | ||
private URLConfiguration urlConfiguration; | ||
|
||
@Inject | ||
private Logger logger; | ||
|
||
|
@@ -71,4 +77,34 @@ public URI parseToSafeURI(String uriRepresentation) throws URISyntaxException, S | |
return null; | ||
} | ||
} | ||
|
||
/** | ||
* @return the list of trusted domains. | ||
* @since 17.9.0RC1 | ||
*/ | ||
@Unstable | ||
public List<String> getTrustedDomains() | ||
{ | ||
return this.urlConfiguration.getTrustedDomains(); | ||
} | ||
|
||
/** | ||
* @return {@code true} if the mechanism to enforce URLs check on frontend is enabled. | ||
* @since 17.9.0RC1 | ||
*/ | ||
@Unstable | ||
public boolean isFrontendUrlCheckEnabled() | ||
{ | ||
return this.urlConfiguration.isFrontendUrlCheckEnabled(); | ||
} | ||
|
||
/** | ||
* @return the list of URLs that are whitelisted to avoid asking confirmation to users when accessing them. | ||
* @since 17.9.0RC1 | ||
*/ | ||
@Unstable | ||
public List<String> getWhitelistedFrontendUrls() | ||
{ | ||
return this.urlConfiguration.getWhitelistedFrontendUrls(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As mentioned before, I would prefer to avoid the term "white" and use "allowed" instead. |
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
# --------------------------------------------------------------------------- | ||
# 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. | ||
# --------------------------------------------------------------------------- | ||
url.api.followLinkConfirmationText=You are about to leave the domain "{0}" to follow a link to "{1}". Are you sure to \ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "Are you sure you want to continue?" sounds better as second sentence for me. |
||
continue? |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,7 +19,6 @@ | |
*/ | ||
package org.xwiki.url.internal; | ||
|
||
import java.util.Collections; | ||
import java.util.List; | ||
|
||
import javax.inject.Inject; | ||
|
@@ -67,7 +66,7 @@ public boolean useResourceLastModificationDate() | |
@Override | ||
public List<String> getTrustedDomains() | ||
{ | ||
return this.configuration.get().getProperty(PREFIX + "trustedDomains", Collections.emptyList()); | ||
return this.configuration.get().getProperty(PREFIX + "trustedDomains", List.of()); | ||
} | ||
|
||
@Override | ||
|
@@ -81,4 +80,16 @@ public List<String> getTrustedSchemes() | |
{ | ||
return this.configuration.get().getProperty(PREFIX + "trustedSchemes", List.of("http", "https", "ftp")); | ||
} | ||
|
||
@Override | ||
public boolean isFrontendUrlCheckEnabled() | ||
{ | ||
return this.configuration.get().getProperty(PREFIX + "frontendUrlCheckEnabled", true); | ||
} | ||
|
||
@Override | ||
public List<String> getWhitelistedFrontendUrls() | ||
{ | ||
return this.configuration.get().getProperty(PREFIX + "whitelistedFrontendUrls", List.of()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as before, I would prefer "allowed" instead of "white". |
||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,69 @@ | ||||||
/* | ||||||
* 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. | ||||||
*/ | ||||||
require(['jquery', 'xwiki-events-bridge'], function($) { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
let protectLinks = function(elements) { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
let trustedDomainConfigElement = $('#trusted-domains-configuration'); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would make this more explicit with
Suggested change
to avoid that somebody creates another element in the document with the same ID - selecting for script elements acts as a protection here as we don't allow creating script elements easily. It's probably unlikely as you would probably still take the first one, and it shouldn't be possible to insert another element before the correct one.
Comment on lines
+22
to
+23
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can't those |
||||||
if (trustedDomainConfigElement.length > 0) { | ||||||
let configuration = JSON.parse(trustedDomainConfigElement.text()); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How do you handle parsing issues? |
||||||
elements.find('a[href]').on('click', function (event) { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder if this event listener shouldn't be registered on
Suggested change
|
||||||
return askIfLinkNotTrusted(event, this, configuration); | ||||||
}); | ||||||
} | ||||||
}; | ||||||
|
||||||
let askIfLinkNotTrusted = function (event, anchor, configuration) { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why assign an anonymous function to a mutable variable?
Suggested change
|
||||||
if (!isAnchorTrustedOomain(anchor, configuration.trustedDomains, configuration.whitelistedUrls)) { | ||||||
let currentHostname = window.location.hostname; | ||||||
let anchorHostname = anchor.hostname; | ||||||
let customizedMessage = configuration.confirmationText | ||||||
.replace('__CURRENT_DOMAIN__', currentHostname) | ||||||
.replace('__EXTERNAL_DOMAIN__', anchorHostname); | ||||||
return confirm(customizedMessage); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm wondering if it wouldn't be nicer to use a custom styled |
||||||
} else { | ||||||
return true; | ||||||
} | ||||||
}; | ||||||
|
||||||
let isAnchorTrustedOomain = function (anchor, trustedDomains, whitelistedUrls) { | ||||||
let currentHostname = window.location.hostname; | ||||||
let anchorHostname = anchor.hostname; | ||||||
if (currentHostname === anchorHostname) { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't there be a check if |
||||||
return true; | ||||||
} else { | ||||||
for (let i = 0; i < whitelistedUrls.length; i++) { | ||||||
if (anchor.href.startsWith(whitelistedUrls[i])) { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the configuration it isn't mentioned that the allowed URLs would be checked as prefix only. |
||||||
return true; | ||||||
} | ||||||
} | ||||||
for (let i = 0; i < trustedDomains.length; i++) { | ||||||
if (anchorHostname.indexOf(trustedDomains[i]) !== -1) { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's not what I would expect how the check should work. This code would trust |
||||||
return true; | ||||||
} | ||||||
} | ||||||
} | ||||||
return false; | ||||||
}; | ||||||
|
||||||
$(document).on('xwiki:dom:updated', function(event, data) { | ||||||
protectLinks($(data.elements)); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As mentioned above, just listening on |
||||||
}); | ||||||
protectLinks($('body')); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't this wait for the document to be ready? |
||||||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1035,6 +1035,20 @@ distribution.automaticStartOnWiki=$xwikiPropertiesAutomaticStartOnWiki | |
#-# The default is: | ||
# url.forceAllowAnyCharacter=true | ||
|
||
#-# [Since 17.9.0RC1] | ||
#-# Allow to enable or disable checks performed when clicking links in the UI based on the list of trusted domains. | ||
#-# | ||
#-# By default this property is set to true: | ||
# url.frontendUrlCheckEnabled=true | ||
|
||
#-# [Since 17.9.0RC1] | ||
#-# Allow to whitelist specific URLs to be accessible from the frontend without asking confirmation, and without | ||
#-# needing to whitelist and entire domain. The expected format is absolute URLs separated by commas, e.g.: | ||
#-# https://github.com/xwiki/xwiki-platform,https://www.xwiki.org/xwiki/bin/view/Main/WebHome | ||
#-# | ||
#-# By default this property is empty: | ||
# url.whitelistedFrontendUrls= | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same comment as above regarding "whitelisted" vs. "allowed". |
||
|
||
#------------------------------------------------------------------------------------- | ||
# Attachment | ||
#------------------------------------------------------------------------------------- | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comment here but I'll also add it to other places: It would be nice to avoid the term "white" and use "allowed" instead as it is much clearer and doesn't have any bad connotations. See also https://en.wikipedia.org/wiki/Whitelist#Controversy_regarding_name