feat(allow-list)!: integrate and refactor core plugin (#1138)

* refactor!: naming to allow list
* feat(allow-list): integrate core plugin
* refactor(allow-list): cleanup
* refactor: drop deprecated launch-external case for CustomConfigParser::handleStartTag
* fix: apply review comments
This commit is contained in:
エリス 2021-07-02 11:52:05 +09:00 committed by GitHub
parent 01569ce71a
commit 015db819ae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 279 additions and 29 deletions

View File

@ -21,7 +21,7 @@
<head>
<!--
Customize this policy to fit your own app's needs. For more guidance, see:
https://github.com/apache/cordova-plugin-whitelist/blob/master/README.md#content-security-policy
https://cordova.apache.org/docs/en/latest/
Some notes:
* gap: is required only on iOS (when using UIWebView) and is needed for JS->native communication
* https://ssl.gstatic.com is required only on Android and is needed for TalkBack to function properly

View File

@ -33,7 +33,7 @@
<!-- <content src="http://mysite.com/myapp.html" /> for external pages -->
<content src="index.html" />
<!-- Whitelist docs: https://github.com/apache/cordova-plugin-whitelist -->
<!-- Allow List docs: https://cordova.apache.org/docs/en/latest/ -->
<access origin="*" />
<!-- Grant certain URLs the ability to launch external applications. This
behaviour is set to match that of Cordova versions before 3.6.0, and

View File

@ -28,7 +28,7 @@ import org.apache.cordova.LOG;
import android.net.Uri;
public class Whitelist {
public class AllowList {
private static class URLPattern {
public Pattern scheme;
public Pattern host;
@ -92,12 +92,12 @@ public class Whitelist {
}
}
private ArrayList<URLPattern> whiteList;
private ArrayList<URLPattern> allowList;
public static final String TAG = "Whitelist";
public static final String TAG = "CordovaAllowList";
public Whitelist() {
this.whiteList = new ArrayList<URLPattern>();
public AllowList() {
this.allowList = new ArrayList<URLPattern>();
}
/* Match patterns (from http://developer.chrome.com/extensions/match_patterns.html)
@ -111,13 +111,13 @@ public class Whitelist {
* the scheme to be omitted for backwards compatibility. (Also host is not required
* to begin with a "*" or "*.".)
*/
public void addWhiteListEntry(String origin, boolean subdomains) {
if (whiteList != null) {
public void addAllowListEntry(String origin, boolean subdomains) {
if (allowList != null) {
try {
// Unlimited access to network resources
if (origin.compareTo("*") == 0) {
LOG.d(TAG, "Unlimited access to network resources");
whiteList = null;
allowList = null;
}
else { // specific access
Pattern parts = Pattern.compile("^((\\*|[A-Za-z-]+):(//)?)?(\\*|((\\*\\.)?[^*/:]+))?(:(\\d+))?(/.*)?");
@ -131,10 +131,10 @@ public class Whitelist {
String path = m.group(9);
if (scheme == null) {
// XXX making it stupid friendly for people who forget to include protocol/SSL
whiteList.add(new URLPattern("http", host, port, path));
whiteList.add(new URLPattern("https", host, port, path));
allowList.add(new URLPattern("http", host, port, path));
allowList.add(new URLPattern("https", host, port, path));
} else {
whiteList.add(new URLPattern(scheme, host, port, path));
allowList.add(new URLPattern(scheme, host, port, path));
}
}
}
@ -149,15 +149,15 @@ public class Whitelist {
* Determine if URL is in approved list of URLs to load.
*
* @param uri
* @return true if wide open or whitelisted
* @return true if wide open or allow listed
*/
public boolean isUrlWhiteListed(String uri) {
// If there is no whitelist, then it's wide open
if (whiteList == null) return true;
public boolean isUrlAllowListed(String uri) {
// If there is no allowList, then it's wide open
if (allowList == null) return true;
Uri parsedUri = Uri.parse(uri);
// Look for match in white list
Iterator<URLPattern> pit = whiteList.iterator();
// Look for match in allow list
Iterator<URLPattern> pit = allowList.iterator();
while (pit.hasNext()) {
URLPattern p = pit.next();
if (p.matches(parsedUri)) {

View File

@ -0,0 +1,157 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.ConfigXmlParser;
import org.apache.cordova.LOG;
import org.apache.cordova.AllowList;
import org.xmlpull.v1.XmlPullParser;
import android.content.Context;
public class AllowListPlugin extends CordovaPlugin {
public static final String PLUGIN_NAME = "CordovaAllowListPlugin";
protected static final String LOG_TAG = "CordovaAllowListPlugin";
private AllowList allowedNavigations;
private AllowList allowedIntents;
private AllowList allowedRequests;
// Used when instantiated via reflection by PluginManager
public AllowListPlugin() { }
// These can be used by embedders to allow Java-configuration of an allow list.
public AllowListPlugin(Context context) {
this(new AllowList(), new AllowList(), null);
new CustomConfigXmlParser().parse(context);
}
public AllowListPlugin(XmlPullParser xmlParser) {
this(new AllowList(), new AllowList(), null);
new CustomConfigXmlParser().parse(xmlParser);
}
public AllowListPlugin(AllowList allowedNavigations, AllowList allowedIntents, AllowList allowedRequests) {
if (allowedRequests == null) {
allowedRequests = new AllowList();
allowedRequests.addAllowListEntry("file:///*", false);
allowedRequests.addAllowListEntry("data:*", false);
}
this.allowedNavigations = allowedNavigations;
this.allowedIntents = allowedIntents;
this.allowedRequests = allowedRequests;
}
@Override
public void pluginInitialize() {
if (this.allowedNavigations == null) {
this.allowedNavigations = new AllowList();
this.allowedIntents = new AllowList();
this.allowedRequests = new AllowList();
new CustomConfigXmlParser().parse(webView.getContext());
}
}
private class CustomConfigXmlParser extends ConfigXmlParser {
@Override
public void handleStartTag(XmlPullParser xml) {
String strNode = xml.getName();
if (strNode.equals("content")) {
String startPage = xml.getAttributeValue(null, "src");
allowedNavigations.addAllowListEntry(startPage, false);
} else if (strNode.equals("allow-navigation")) {
String origin = xml.getAttributeValue(null, "href");
if ("*".equals(origin)) {
allowedNavigations.addAllowListEntry("http://*/*", false);
allowedNavigations.addAllowListEntry("https://*/*", false);
allowedNavigations.addAllowListEntry("data:*", false);
} else {
allowedNavigations.addAllowListEntry(origin, false);
}
} else if (strNode.equals("allow-intent")) {
String origin = xml.getAttributeValue(null, "href");
allowedIntents.addAllowListEntry(origin, false);
} else if (strNode.equals("access")) {
String origin = xml.getAttributeValue(null, "origin");
if (origin != null) {
if ("*".equals(origin)) {
allowedRequests.addAllowListEntry("http://*/*", false);
allowedRequests.addAllowListEntry("https://*/*", false);
} else {
String subdomains = xml.getAttributeValue(null, "subdomains");
allowedRequests.addAllowListEntry(origin, (subdomains != null) && (subdomains.compareToIgnoreCase("true") == 0));
}
}
}
}
@Override
public void handleEndTag(XmlPullParser xml) { }
}
@Override
public Boolean shouldAllowNavigation(String url) {
return this.allowedNavigations.isUrlAllowListed(url)
? true
: null; // default policy
}
@Override
public Boolean shouldAllowRequest(String url) {
return (this.shouldAllowNavigation(url) || this.allowedRequests.isUrlAllowListed(url))
? true
: null; // default policy
}
@Override
public Boolean shouldOpenExternalUrl(String url) {
return (this.allowedIntents.isUrlAllowListed(url))
? true
: null; // default policy
}
public AllowList getAllowedNavigations() {
return this.allowedNavigations;
}
public void setAllowedNavigations(AllowList allowedNavigations) {
this.allowedNavigations = allowedNavigations;
}
public AllowList getAllowedIntents() {
return this.allowedIntents;
}
public void setAllowedIntents(AllowList allowedIntents) {
this.allowedIntents = allowedIntents;
}
public AllowList getAllowedRequests() {
return this.allowedRequests;
}
public void setAllowedRequests(AllowList allowedRequests) {
this.allowedRequests = allowedRequests;
}
}

View File

@ -23,7 +23,7 @@ import java.util.List;
import android.app.Activity;
@Deprecated // Use Whitelist, CordovaPrefences, etc. directly.
@Deprecated // Use AllowList, CordovaPrefences, etc. directly.
public class Config {
private static final String TAG = "Config";

View File

@ -68,6 +68,15 @@ public class ConfigXmlParser {
return;
}
}
pluginEntries.add(
new PluginEntry(
AllowListPlugin.PLUGIN_NAME,
"org.apache.cordova.AllowListPlugin",
true
)
);
parse(action.getResources().getXml(id));
}

View File

@ -94,7 +94,7 @@ public interface CordovaWebView {
/**
* Load the specified URL in the Cordova webview or a new browser instance.
*
* NOTE: If openExternal is false, only whitelisted URLs can be loaded.
* NOTE: If openExternal is false, only allow listed URLs can be loaded.
*
* @param url The url to load.
* @param openExternal Load url in browser instead of Cordova webview.

View File

@ -218,19 +218,19 @@ public class CordovaWebViewImpl implements CordovaWebView {
// If loading into our webview
if (!openExternal) {
// Make sure url is in whitelist
// Make sure url is in allow list
if (pluginManager.shouldAllowNavigation(url)) {
// TODO: What about params?
// Load new URL
loadUrlIntoView(url, true);
return;
} else {
LOG.w(TAG, "showWebPage: Refusing to load URL into webview since it is not in the <allow-navigation> whitelist. URL=" + url);
LOG.w(TAG, "showWebPage: Refusing to load URL into webview since it is not in the <allow-navigation> allow list. URL=" + url);
return;
}
}
if (!pluginManager.shouldOpenExternalUrl(url)) {
LOG.w(TAG, "showWebPage: Refusing to send intent for URL since it is not in the <allow-intent> whitelist. URL=" + url);
LOG.w(TAG, "showWebPage: Refusing to send intent for URL since it is not in the <allow-intent> allow list. URL=" + url);
return;
}

View File

@ -27,7 +27,7 @@
<script type="text/javascript" charset="utf-8" src="../main.js"></script>
</head>
<body onload="init();" id="stage" class="theme">
<h1>Whitelist Page 1</h1>
<h1>Allow List Page 1</h1>
<div id="info">
<h4>Cordova: <span id="cordova"> &nbsp;</span></h4>
<h4>Deviceready: <span id="deviceready"> &nbsp;</span></h4>

View File

@ -27,7 +27,7 @@
<script type="text/javascript" charset="utf-8" src="../main.js"></script>
</head>
<body onload="init();" id="stage" class="theme">
<h1>Whitelist Page 2</h1>
<h1>Allow List Page 2</h1>
<div id="info">
<h4>Cordova: <span id="cordova"> &nbsp;</span></h4>
<h4>Deviceready: <span id="deviceready"> &nbsp;</span></h4>

View File

@ -371,10 +371,10 @@ public class SystemWebViewClient extends WebViewClient {
@SuppressWarnings("deprecation")
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
try {
// Check the against the whitelist and lock out access to the WebView directory
// Check the against the allow list and lock out access to the WebView directory
// Changing this will cause problems for your application
if (!parentEngine.pluginManager.shouldAllowRequest(url)) {
LOG.w(TAG, "URL blocked by whitelist: " + url);
LOG.w(TAG, "URL blocked by allow list: " + url);
// Results in a 404.
return new WebResourceResponse("text/plain", "UTF-8", null);
}

View File

@ -0,0 +1,45 @@
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
<!DOCTYPE HTML>
<html>
<head>
<meta name="viewport" content="width=320, user-scalable=no" />
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<title>Cordova Tests</title>
<link rel="stylesheet" href="../master.css" type="text/css" media="screen" title="no title">
<script type="text/javascript" charset="utf-8" src="../cordova.js"></script>
<script type="text/javascript" charset="utf-8" src="../main.js"></script>
</head>
<body onload="init();" id="stage" class="theme">
<h1>Allow List Page 1</h1>
<div id="info">
<h4>Cordova: <span id="cordova"> &nbsp;</span></h4>
<h4>Deviceready: <span id="deviceready"> &nbsp;</span></h4>
</div>
<div id="info">
Loading Page 2 should be successful.<br>
Loading Page 3 should be in web browser.<br>
Loading Page 2 with target=_blank should be in web browser? <br>
(THIS DOESN'T HAPPEN.) https://issues.apache.org/jira/browse/CB-362
</div>
<a href="index2.html" class="btn large">Page 2</a>
<a href="http://www.google.com" class="btn large">Page 3</a>
<a href="index2.html" class="btn large" target="_blank">Page 2 with target=_blank</a>
</body>
</html>

View File

@ -0,0 +1,39 @@
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
<!DOCTYPE HTML>
<html>
<head>
<meta name="viewport" content="width=320, user-scalable=no" />
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<title>Cordova Tests</title>
<link rel="stylesheet" href="../master.css" type="text/css" media="screen" title="no title">
<script type="text/javascript" charset="utf-8" src="../cordova.js"></script>
<script type="text/javascript" charset="utf-8" src="../main.js"></script>
</head>
<body onload="init();" id="stage" class="theme">
<h1>Allow List Page 2</h1>
<div id="info">
<h4>Cordova: <span id="cordova"> &nbsp;</span></h4>
<h4>Deviceready: <span id="deviceready"> &nbsp;</span></h4>
</div>
<div id="info">
Press "backbutton"
</div>
</body>
</html>

View File

@ -51,6 +51,6 @@
<button class="btn large" onclick="startActivity('htmlnotfound/index.html');">HTML not found</button>
<button class="btn large" onclick="startActivity('iframe/index.html');">IFrame</button>
<button class="btn large" onclick="startActivity('lifecycle/index.html');">Lifecycle</button>
<button class="btn large" onclick="startActivity('whitelist/index.html');">Whitelist</button>
<button class="btn large" onclick="startActivity('allowlist/index.html');">Allow List</button>
</body>
</html>