Skip to content
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

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

Merged
merged 5 commits into from
Jul 2, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion bin/templates/project/assets/www/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion bin/templates/project/res/xml/config.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Licensed to the Apache Software Foundation (ASF) under one

import android.net.Uri;

public class Whitelist {
public class AllowList {
private static class URLPattern {
public Pattern scheme;
public Pattern host;
Expand Down Expand Up @@ -92,12 +92,12 @@ public boolean matches(Uri uri) {
}
}

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)
Expand All @@ -111,13 +111,13 @@ public 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+))?(/.*)?");
Expand All @@ -131,10 +131,10 @@ public void addWhiteListEntry(String origin, boolean subdomains) {
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));
}
}
}
Expand All @@ -149,15 +149,15 @@ public void addWhiteListEntry(String origin, boolean subdomains) {
* 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)) {
Expand Down
157 changes: 157 additions & 0 deletions framework/src/org/apache/cordova/AllowListPlugin.java
Original file line number Diff line number Diff line change
@@ -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;
}
}
2 changes: 1 addition & 1 deletion framework/src/org/apache/cordova/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Licensed to the Apache Software Foundation (ASF) under one

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";

Expand Down
9 changes: 9 additions & 0 deletions framework/src/org/apache/cordova/ConfigXmlParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,15 @@ public void parse(Context action) {
return;
}
}

pluginEntries.add(
new PluginEntry(
AllowListPlugin.PLUGIN_NAME,
"org.apache.cordova.AllowListPlugin",
true
)
);

parse(action.getResources().getXml(id));
}

Expand Down
2 changes: 1 addition & 1 deletion framework/src/org/apache/cordova/CordovaWebView.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
6 changes: 3 additions & 3 deletions framework/src/org/apache/cordova/CordovaWebViewImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -218,19 +218,19 @@ public void showWebPage(String url, boolean openExternal, boolean clearHistory,

// 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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -371,10 +371,10 @@ public void clearAuthenticationTokens() {
@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);
}
Expand Down
45 changes: 45 additions & 0 deletions test/androidx/app/src/main/assets/www/allowlist/index.html
Original file line number Diff line number Diff line change
@@ -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>