[Web Payment] Content Security Checker on Android
Before this patch, Web Payment API on Android did not obey Content
Security Policy (CSP) rules when downloading payment method manifests
and web app manifests of payment apps. The manifest downloads are
managed in the browser, while CSP rules are managed in the renderer.
This patch pipes the CSP check through the Android-specific
PaymentRequestClient Mojo connection to the renderer.
After this patch, if chrome://flags/#web-payment-api-csp is set to
"Enabled", then Web Payment API on Android will obey CSP rules when
downloading payment method manifests and web app manifests of payment
apps.
Intent to prototype:
https://groups.google.com/a/chromium.org/g/blink-dev/c/jklZJYcOVyg/m/Gfwa4QQBAwAJ
Bug: 1349091
Change-Id: I7d758c7ca67a9649ba1eb54491e7ad98e623f992
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3892427
Reviewed-by: Daniel Cheng <dcheng@chromium.org>
Reviewed-by: Stephen McGruer <smcgruer@chromium.org>
Commit-Queue: Rouslan Solomakhin <rouslan@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1050323}
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/AndroidPaymentAppFinderTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/AndroidPaymentAppFinderTest.java
index 04328b3..bf59fb7 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/AndroidPaymentAppFinderTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/AndroidPaymentAppFinderTest.java
@@ -16,6 +16,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.chromium.base.Callback;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.CriteriaHelper;
import org.chromium.base.test.util.Feature;
@@ -25,6 +26,7 @@
import org.chromium.chrome.test.util.browser.Features;
import org.chromium.components.payments.AndroidPaymentAppFinder;
import org.chromium.components.payments.AppCreationFailureReason;
+import org.chromium.components.payments.CSPChecker;
import org.chromium.components.payments.PaymentApp;
import org.chromium.components.payments.PaymentAppFactoryDelegate;
import org.chromium.components.payments.PaymentAppFactoryInterface;
@@ -140,6 +142,18 @@
mAllPaymentAppsCreated = true;
}
+ // PaymentAppFactoryDelegate implementation.
+ @Override
+ public CSPChecker getCSPChecker() {
+ return new CSPChecker() {
+ @Override
+ public void allowConnectToSource(GURL url, GURL urlBeforeRedirects,
+ boolean didFollowRedirect, Callback<Boolean> resultCallback) {
+ resultCallback.onResult(/*allow=*/true);
+ }
+ };
+ }
+
// PaymentAppFactoryParams implementation.
@Override
public WebContents getWebContents() {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/AndroidPaymentAppFinderUnitTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/AndroidPaymentAppFinderUnitTest.java
index 732adff..166e4fa7 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/AndroidPaymentAppFinderUnitTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/AndroidPaymentAppFinderUnitTest.java
@@ -36,6 +36,7 @@
import org.chromium.chrome.test.ChromeBrowserTestRule;
import org.chromium.components.payments.AndroidPaymentAppFinder;
import org.chromium.components.payments.AppCreationFailureReason;
+import org.chromium.components.payments.CSPChecker;
import org.chromium.components.payments.PackageManagerDelegate;
import org.chromium.components.payments.PaymentApp;
import org.chromium.components.payments.PaymentAppFactoryDelegate;
@@ -463,7 +464,7 @@
PaymentManifestDownloader downloader = new PaymentManifestDownloader() {
@Override
- public void initialize(WebContents webContents) {}
+ public void initialize(WebContents webContents, CSPChecker cspChecker) {}
@Override
public void downloadPaymentMethodManifest(
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentManifestDownloaderTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentManifestDownloaderTest.java
index 26f55d5..45cea4c 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentManifestDownloaderTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentManifestDownloaderTest.java
@@ -16,6 +16,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.chromium.base.Callback;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.Criteria;
import org.chromium.base.test.util.CriteriaHelper;
@@ -23,6 +24,7 @@
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
+import org.chromium.components.payments.CSPChecker;
import org.chromium.components.payments.PaymentManifestDownloader;
import org.chromium.components.payments.PaymentManifestDownloader.ManifestDownloadCallback;
import org.chromium.net.test.EmbeddedTestServer;
@@ -99,7 +101,14 @@
mActivityTestRule.startMainActivityOnBlankPage();
mServer = EmbeddedTestServer.createAndStartServer(InstrumentationRegistry.getContext());
mActivityTestRule.runOnUiThread((Runnable) () -> {
- mDownloader.initialize(mActivityTestRule.getActivity().getCurrentWebContents());
+ mDownloader.initialize(
+ mActivityTestRule.getActivity().getCurrentWebContents(), new CSPChecker() {
+ @Override
+ public void allowConnectToSource(GURL url, GURL urlBeforeRedirects,
+ boolean didFollowRedirect, Callback<Boolean> resultCallback) {
+ resultCallback.onResult(/*allow=*/true);
+ }
+ });
mTestOrigin = PaymentManifestDownloader.createOpaqueOriginForTest();
});
mDownloadComplete = false;
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentManifestVerifierTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentManifestVerifierTest.java
index 376dadb..9cc3c1e7 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentManifestVerifierTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentManifestVerifierTest.java
@@ -23,6 +23,7 @@
import org.chromium.base.test.BaseJUnit4ClassRunner;
import org.chromium.base.test.util.Batch;
import org.chromium.chrome.test.ChromeBrowserTestRule;
+import org.chromium.components.payments.CSPChecker;
import org.chromium.components.payments.PackageManagerDelegate;
import org.chromium.components.payments.PaymentManifestDownloader;
import org.chromium.components.payments.PaymentManifestParser;
@@ -95,7 +96,7 @@
mDownloader = new PaymentManifestDownloader() {
@Override
- public void initialize(WebContents webContents) {}
+ public void initialize(WebContents webContents, CSPChecker cspChecker) {}
@Override
public void downloadPaymentMethodManifest(
@@ -158,7 +159,7 @@
mMatchingApps, null /* supportedOrigins */,
mWebDataService, new PaymentManifestDownloader() {
@Override
- public void initialize(WebContents webContents) {}
+ public void initialize(WebContents webContents, CSPChecker cspChecker) {}
@Override
public void downloadPaymentMethodManifest(
@@ -183,7 +184,7 @@
mMatchingApps, null /* supportedOrigins */,
mWebDataService, new PaymentManifestDownloader() {
@Override
- public void initialize(WebContents webContents) {}
+ public void initialize(WebContents webContents, CSPChecker cspChecker) {}
@Override
public void downloadPaymentMethodManifest(
diff --git a/chrome/browser/payments/android/payment_app_service_bridge_unittest.cc b/chrome/browser/payments/android/payment_app_service_bridge_unittest.cc
index 3c9a3b9..b528b56 100644
--- a/chrome/browser/payments/android/payment_app_service_bridge_unittest.cc
+++ b/chrome/browser/payments/android/payment_app_service_bridge_unittest.cc
@@ -12,6 +12,7 @@
#include "components/payments/content/android/payment_app_service_bridge.h"
#include "components/payments/content/payment_manifest_web_data_service.h"
#include "components/payments/content/payment_request_spec.h"
+#include "components/payments/core/const_csp_checker.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/navigation_simulator.h"
@@ -97,13 +98,14 @@
mojom::PaymentDetails::New(), std::move(method_data),
/*observer=*/nullptr, /*app_locale=*/"en-US");
+ ConstCSPChecker const_csp_checker(/*allow=*/true);
MockCallback mock_callback;
base::WeakPtr<PaymentAppServiceBridge> bridge =
PaymentAppServiceBridge::Create(
/*number_of_factories=*/3, web_contents_->GetPrimaryMainFrame(),
top_origin_, spec.AsWeakPtr(), /*twa_package_name=*/GetParam(),
- web_data_service_,
- /*is_off_the_record=*/false,
+ web_data_service_, /*is_off_the_record=*/false,
+ const_csp_checker.GetWeakPtr(),
base::BindRepeating(&MockCallback::NotifyCanMakePaymentCalculated,
base::Unretained(&mock_callback)),
base::BindRepeating(&MockCallback::NotifyPaymentAppCreated,
diff --git a/chrome/browser/payments/iframe_csp_browsertest.cc b/chrome/browser/payments/iframe_csp_browsertest.cc
index 76bbafb..d9beb8cb 100644
--- a/chrome/browser/payments/iframe_csp_browsertest.cc
+++ b/chrome/browser/payments/iframe_csp_browsertest.cc
@@ -4,7 +4,6 @@
#include "base/bind.h"
#include "base/test/scoped_feature_list.h"
-#include "build/build_config.h"
#include "chrome/test/payments/payment_request_platform_browsertest_base.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
@@ -124,13 +123,6 @@
{{domain, https_server()}, {subdomain, https_server()}});
std::string payment_method = "https://" + domain + "/redirect/pay";
-// CSP is not being enforced on Android.
-// TODO(crbug.com/1349091): Enforce CSP on Android.
-#if BUILDFLAG(IS_ANDROID)
- EXPECT_EQ(true, content::EvalJs(GetActiveWebContents(),
- content::JsReplace("checkCanMakePayment($1)",
- payment_method)));
-#else
if (WebPaymentAPICSPEnabled()) {
// The test page's CSP denies connections to the redirect destination.
EXPECT_EQ(false,
@@ -146,7 +138,6 @@
content::JsReplace("checkCanMakePayment($1)",
payment_method)));
}
-#endif // BUILDFLAG(IS_ANDROID)
}
INSTANTIATE_TEST_SUITE_P(All, IframeCspTest, ::testing::Bool());
diff --git a/components/payments/content/android/BUILD.gn b/components/payments/content/android/BUILD.gn
index c7b014f..2e72e03 100644
--- a/components/payments/content/android/BUILD.gn
+++ b/components/payments/content/android/BUILD.gn
@@ -12,6 +12,8 @@
"byte_buffer_helper.cc",
"byte_buffer_helper.h",
"can_make_payment_query_android.cc",
+ "csp_checker_android.cc",
+ "csp_checker_android.h",
"currency_formatter_android.cc",
"currency_formatter_android.h",
"error_message_util.cc",
@@ -64,6 +66,7 @@
generate_jni("jni_headers") {
sources = [
+ "java/src/org/chromium/components/payments/CSPCheckerBridge.java",
"java/src/org/chromium/components/payments/CanMakePaymentQuery.java",
"java/src/org/chromium/components/payments/CurrencyFormatter.java",
"java/src/org/chromium/components/payments/ErrorMessageUtil.java",
@@ -155,6 +158,7 @@
"java/src/org/chromium/components/payments/AndroidPaymentAppFinder.java",
"java/src/org/chromium/components/payments/BasicCardUtils.java",
"java/src/org/chromium/components/payments/BrowserPaymentRequest.java",
+ "java/src/org/chromium/components/payments/CSPCheckerBridge.java",
"java/src/org/chromium/components/payments/CanMakePaymentQuery.java",
"java/src/org/chromium/components/payments/CurrencyFormatter.java",
"java/src/org/chromium/components/payments/ErrorMessageUtil.java",
@@ -239,6 +243,7 @@
android_library("minimal_java") {
annotation_processor_deps = [ "//base/android/jni_generator:jni_processor" ]
sources = [
+ "java/src/org/chromium/components/payments/CSPChecker.java",
"java/src/org/chromium/components/payments/MojoStructCollection.java",
"java/src/org/chromium/components/payments/PaymentApp.java",
"java/src/org/chromium/components/payments/PaymentAppFactoryDelegate.java",
@@ -261,6 +266,7 @@
"//third_party/androidx:androidx_annotation_annotation_java",
"//third_party/androidx:androidx_collection_collection_java",
"//third_party/blink/public/mojom:android_mojo_bindings_java",
+ "//url:gurl_java",
"//url:origin_java",
]
srcjar_deps = [
diff --git a/components/payments/content/android/csp_checker_android.cc b/components/payments/content/android/csp_checker_android.cc
new file mode 100644
index 0000000..5e4fd7e
--- /dev/null
+++ b/components/payments/content/android/csp_checker_android.cc
@@ -0,0 +1,76 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/payments/content/android/csp_checker_android.h"
+
+#include "components/payments/content/android/jni_headers/CSPCheckerBridge_jni.h"
+#include "url/android/gurl_android.h"
+
+namespace payments {
+
+CSPCheckerAndroid::CSPCheckerAndroid(
+ const base::android::JavaParamRef<jobject>& jbridge)
+ : jbridge_(jbridge) {}
+
+CSPCheckerAndroid::~CSPCheckerAndroid() = default;
+
+void CSPCheckerAndroid::Destroy(JNIEnv* env) {
+ delete this;
+}
+
+void CSPCheckerAndroid::OnResult(JNIEnv* env,
+ jint callback_id,
+ jboolean result) {
+ auto iter = result_callbacks_.find(callback_id);
+ if (iter == result_callbacks_.end())
+ return;
+
+ base::OnceCallback<void(bool)> callback = std::move(iter->second);
+ result_callbacks_.erase(iter);
+
+ std::move(callback).Run(result);
+}
+
+// static
+base::WeakPtr<CSPCheckerAndroid> CSPCheckerAndroid::GetWeakPtr(
+ jlong native_csp_checker_android) {
+ if (!native_csp_checker_android)
+ return base::WeakPtr<CSPCheckerAndroid>();
+
+ payments::CSPCheckerAndroid* csp_checker_android =
+ reinterpret_cast<payments::CSPCheckerAndroid*>(
+ native_csp_checker_android);
+ if (!csp_checker_android)
+ return base::WeakPtr<CSPCheckerAndroid>();
+
+ return csp_checker_android->weak_ptr_factory_.GetWeakPtr();
+}
+
+void CSPCheckerAndroid::AllowConnectToSource(
+ const GURL& url,
+ const GURL& url_before_redirects,
+ bool did_follow_redirect,
+ base::OnceCallback<void(bool)> result_callback) {
+ JNIEnv* env = base::android::AttachCurrentThread();
+ if (!env)
+ return;
+
+ int callback_id = ++callback_counter_;
+ result_callbacks_.insert(
+ std::make_pair(callback_id, std::move(result_callback)));
+
+ Java_CSPCheckerBridge_allowConnectToSource(
+ env, jbridge_, url::GURLAndroid::FromNativeGURL(env, url),
+ url::GURLAndroid::FromNativeGURL(env, url_before_redirects),
+ did_follow_redirect, callback_id);
+}
+
+// A static free function declared in and invoked directly from Java.
+static jlong JNI_CSPCheckerBridge_CreateNativeCSPChecker(
+ JNIEnv* env,
+ const base::android::JavaParamRef<jobject>& jbridge) {
+ return reinterpret_cast<intptr_t>(new CSPCheckerAndroid(jbridge));
+}
+
+} // namespace payments
diff --git a/components/payments/content/android/csp_checker_android.h b/components/payments/content/android/csp_checker_android.h
new file mode 100644
index 0000000..e14f9ed
--- /dev/null
+++ b/components/payments/content/android/csp_checker_android.h
@@ -0,0 +1,55 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_PAYMENTS_CONTENT_ANDROID_CSP_CHECKER_ANDROID_H_
+#define COMPONENTS_PAYMENTS_CONTENT_ANDROID_CSP_CHECKER_ANDROID_H_
+
+#include <jni.h>
+#include <map>
+
+#include "base/android/scoped_java_ref.h"
+#include "base/callback.h"
+#include "base/memory/weak_ptr.h"
+#include "components/payments/core/csp_checker.h"
+
+namespace payments {
+
+// Forwarding calls to a Java implementation.
+class CSPCheckerAndroid : public CSPChecker {
+ public:
+ explicit CSPCheckerAndroid(
+ const base::android::JavaParamRef<jobject>& jbridge);
+ ~CSPCheckerAndroid() override;
+
+ CSPCheckerAndroid(const CSPCheckerAndroid&) = delete;
+ CSPCheckerAndroid& operator=(const CSPCheckerAndroid&) = delete;
+
+ // Message from Java to destroy this object.
+ void Destroy(JNIEnv* env);
+
+ // Message from Java to return the result.
+ void OnResult(JNIEnv* env, jint result_id, jboolean result);
+
+ // Convert a Java-owned CSPCheckerAndroid* pointer into a weak pointer.
+ static base::WeakPtr<CSPCheckerAndroid> GetWeakPtr(
+ jlong native_csp_checker_android);
+
+ private:
+ // CSPChecker implementation.
+ void AllowConnectToSource(
+ const GURL& url,
+ const GURL& url_before_redirects,
+ bool did_follow_redirect,
+ base::OnceCallback<void(bool)> result_callback) override;
+
+ base::android::ScopedJavaGlobalRef<jobject> jbridge_;
+ std::map<int, base::OnceCallback<void(bool)>> result_callbacks_;
+ int callback_counter_ = 0;
+
+ base::WeakPtrFactory<CSPCheckerAndroid> weak_ptr_factory_{this};
+};
+
+} // namespace payments
+
+#endif // COMPONENTS_PAYMENTS_CONTENT_ANDROID_CSP_CHECKER_ANDROID_H_
diff --git a/components/payments/content/android/java/src/org/chromium/components/payments/AndroidPaymentAppFinder.java b/components/payments/content/android/java/src/org/chromium/components/payments/AndroidPaymentAppFinder.java
index d26b95f..cd0a955 100644
--- a/components/payments/content/android/java/src/org/chromium/components/payments/AndroidPaymentAppFinder.java
+++ b/components/payments/content/android/java/src/org/chromium/components/payments/AndroidPaymentAppFinder.java
@@ -415,7 +415,8 @@
// Initialize the native side of the downloader, once we know that a manifest file needs
// to be downloaded.
if (!mDownloader.isInitialized()) {
- mDownloader.initialize(mFactoryDelegate.getParams().getWebContents());
+ mDownloader.initialize(mFactoryDelegate.getParams().getWebContents(),
+ mFactoryDelegate.getCSPChecker());
}
manifestVerifiers.add(new PaymentManifestVerifier(
diff --git a/components/payments/content/android/java/src/org/chromium/components/payments/CSPChecker.java b/components/payments/content/android/java/src/org/chromium/components/payments/CSPChecker.java
new file mode 100644
index 0000000..ca500c4d
--- /dev/null
+++ b/components/payments/content/android/java/src/org/chromium/components/payments/CSPChecker.java
@@ -0,0 +1,23 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.components.payments;
+
+import org.chromium.base.Callback;
+import org.chromium.url.GURL;
+
+/** Checks the Content-Security-Policy (CSP). */
+public interface CSPChecker {
+ /**
+ * Checks whether CSP connect-src directive allows the given URL. The parameters match
+ * ContentSecurityPolicy::AllowConnectToSource() in:
+ * third_party/blink/renderer/core/frame/csp/content_security_policy.h
+ * @param url The URL to check.
+ * @param urlBeforeRedirects The URL before redirects, if there was a redirect.
+ * @param didFollowRedirect Whether there was a redirect.
+ * @param resultCallback The callback to invoke with the result of the CSP check.
+ */
+ void allowConnectToSource(GURL url, GURL urlBeforeRedirects, boolean didFollowRedirect,
+ Callback<Boolean> resultCallback);
+}
diff --git a/components/payments/content/android/java/src/org/chromium/components/payments/CSPCheckerBridge.java b/components/payments/content/android/java/src/org/chromium/components/payments/CSPCheckerBridge.java
new file mode 100644
index 0000000..8390e30b
--- /dev/null
+++ b/components/payments/content/android/java/src/org/chromium/components/payments/CSPCheckerBridge.java
@@ -0,0 +1,80 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.components.payments;
+
+import androidx.annotation.NonNull;
+
+import org.chromium.base.annotations.CalledByNative;
+import org.chromium.base.annotations.JNINamespace;
+import org.chromium.base.annotations.NativeMethods;
+import org.chromium.url.GURL;
+
+/**
+ * Native bridge for Content-Security-Policy (CSP) checker. Provides access to a Java implementation
+ * of CSP-checking, albeit that implementation pipes over to renderer code.
+ *
+ * The destroy() method must always be called to clean up the native owned object.
+ *
+ * Usage example.
+ * CSPCheckerBridge bridge = new CSPCheckerBridge(cspChecker);
+ * useNativeCSPChecker(bridge.getNativeCSPChecker());
+ * bridge.destroy();
+ */
+@JNINamespace("payments")
+public class CSPCheckerBridge {
+ // Performs the CSP checks.
+ private final CSPChecker mImpl;
+
+ // The native interface for accessing the CSP checker.
+ private long mNativeBridge;
+
+ /**
+ * Initializes the CSP checker bridge.
+ * @param cspChecker The object that will perform the CSP checks.
+ */
+ public CSPCheckerBridge(@NonNull CSPChecker cspChecker) {
+ mImpl = cspChecker;
+ mNativeBridge = CSPCheckerBridgeJni.get().createNativeCSPChecker(this);
+ }
+
+ /** Destroys the CSP checker bridge. Must be called when this class is no longer being used. */
+ public void destroy() {
+ if (mNativeBridge != 0) {
+ CSPCheckerBridgeJni.get().destroy(mNativeBridge);
+ mNativeBridge = 0;
+ }
+ }
+
+ /** @return The native C++ pointer to a CSP checker object. Owned by the Java object. */
+ public long getNativeCSPChecker() {
+ return mNativeBridge;
+ }
+
+ /**
+ * Checks whether CSP connect-src directive allows the given URL. The parameters match
+ * ContentSecurityPolicy::AllowConnectToSource() in:
+ * third_party/blink/renderer/core/frame/csp/content_security_policy.h
+ * @param url The URL to check.
+ * @param urlBeforeRedirects The URL before redirects, if there was a redirect.
+ * @param didFollowRedirect Whether there was a redirect.
+ * @param callbackId The identifier of the callback to invoke with the result of the CSP check.
+ */
+ @CalledByNative
+ public void allowConnectToSource(
+ GURL url, GURL urlBeforeRedirects, boolean didFollowRedirect, int callbackId) {
+ mImpl.allowConnectToSource(url, urlBeforeRedirects, didFollowRedirect, (Boolean result) -> {
+ if (mNativeBridge != 0) {
+ CSPCheckerBridgeJni.get().onResult(mNativeBridge, callbackId, result);
+ }
+ });
+ }
+
+ @NativeMethods
+ public interface Natives {
+ long createNativeCSPChecker(CSPCheckerBridge bridge);
+ void onResult(long nativeCSPCheckerAndroid, int callbackId, boolean result);
+ void destroy(long nativeCSPCheckerAndroid);
+ }
+}
diff --git a/components/payments/content/android/java/src/org/chromium/components/payments/PaymentAppFactoryDelegate.java b/components/payments/content/android/java/src/org/chromium/components/payments/PaymentAppFactoryDelegate.java
index 1574457..473359f 100644
--- a/components/payments/content/android/java/src/org/chromium/components/payments/PaymentAppFactoryDelegate.java
+++ b/components/payments/content/android/java/src/org/chromium/components/payments/PaymentAppFactoryDelegate.java
@@ -4,6 +4,9 @@
package org.chromium.components.payments;
+import org.chromium.base.Callback;
+import org.chromium.url.GURL;
+
/**
* Interface for providing information to a payment app factory and receiving the list of payment
* apps.
@@ -51,4 +54,16 @@
* app is created.
*/
default void setCanMakePaymentEvenWithoutApps() {}
+
+ /** @return The Content-Security-Policy (CSP) checker. */
+ default CSPChecker getCSPChecker() {
+ // TODO(https://crbug.com/1349091): Remove this stub CSP checker.
+ return new CSPChecker() {
+ @Override
+ public void allowConnectToSource(GURL url, GURL urlBeforeRedirects,
+ boolean didFollowRedirect, Callback<Boolean> resultCallback) {
+ resultCallback.onResult(/*allow=*/true);
+ }
+ };
+ }
}
diff --git a/components/payments/content/android/java/src/org/chromium/components/payments/PaymentAppService.java b/components/payments/content/android/java/src/org/chromium/components/payments/PaymentAppService.java
index 315fa2e..6b00caf 100644
--- a/components/payments/content/android/java/src/org/chromium/components/payments/PaymentAppService.java
+++ b/components/payments/content/android/java/src/org/chromium/components/payments/PaymentAppService.java
@@ -140,6 +140,11 @@
mDelegate.onDoneCreatingPaymentApps(PaymentAppService.this);
}
+
+ @Override
+ public CSPChecker getCSPChecker() {
+ return mDelegate.getCSPChecker();
+ }
}
private static Set<PaymentApp> deduplicatePaymentApps(List<PaymentApp> apps) {
diff --git a/components/payments/content/android/java/src/org/chromium/components/payments/PaymentAppServiceBridge.java b/components/payments/content/android/java/src/org/chromium/components/payments/PaymentAppServiceBridge.java
index 809407f..c7b5be9 100644
--- a/components/payments/content/android/java/src/org/chromium/components/payments/PaymentAppServiceBridge.java
+++ b/components/payments/content/android/java/src/org/chromium/components/payments/PaymentAppServiceBridge.java
@@ -46,20 +46,27 @@
delegate.getParams().getRenderFrameHost().getLastCommittedURL(),
SchemeDisplay.SHOW));
- PaymentAppServiceCallback callback = new PaymentAppServiceCallback(delegate);
+ CSPCheckerBridge cspCheckerBridge = new CSPCheckerBridge(delegate.getCSPChecker());
+
+ PaymentAppServiceCallback callback =
+ new PaymentAppServiceCallback(delegate, cspCheckerBridge);
PaymentAppServiceBridgeJni.get().create(delegate.getParams().getRenderFrameHost(),
delegate.getParams().getTopLevelOrigin(), delegate.getParams().getSpec(),
delegate.getParams().getTwaPackageName(), delegate.getParams().getMayCrawl(),
- delegate.getParams().isOffTheRecord(), callback);
+ delegate.getParams().isOffTheRecord(), cspCheckerBridge.getNativeCSPChecker(),
+ callback);
}
/** Handles callbacks from native PaymentAppService. */
public class PaymentAppServiceCallback {
private final PaymentAppFactoryDelegate mDelegate;
+ private final CSPCheckerBridge mCSPCheckerBridge;
- private PaymentAppServiceCallback(PaymentAppFactoryDelegate delegate) {
+ private PaymentAppServiceCallback(
+ PaymentAppFactoryDelegate delegate, CSPCheckerBridge cspCheckerBridge) {
mDelegate = delegate;
+ mCSPCheckerBridge = cspCheckerBridge;
}
@CalledByNative("PaymentAppServiceCallback")
@@ -93,6 +100,7 @@
@CalledByNative("PaymentAppServiceCallback")
private void onDoneCreatingPaymentApps() {
ThreadUtils.assertOnUiThread();
+ mCSPCheckerBridge.destroy();
mDelegate.onDoneCreatingPaymentApps(PaymentAppServiceBridge.this);
}
@@ -122,11 +130,12 @@
* payment apps is allowed.
* @param isOffTheRecord Whether the merchant WebContent's profile is in off-the-record
* mode.
+ * @param nativeCSPCheckerAndroid A C++ native CSPCheckerAndroid* pointer.
* @param callback The callback that receives the discovered payment apps.
*/
void create(RenderFrameHost initiatorRenderFrameHost, String topOrigin,
PaymentRequestSpec spec, String twaPackageName,
boolean mayCrawlForInstallablePaymentApps, boolean isOffTheRecord,
- PaymentAppServiceCallback callback);
+ long nativeCSPCheckerAndroid, PaymentAppServiceCallback callback);
}
}
diff --git a/components/payments/content/android/java/src/org/chromium/components/payments/PaymentManifestDownloader.java b/components/payments/content/android/java/src/org/chromium/components/payments/PaymentManifestDownloader.java
index 1c5723e7..3e81e06 100644
--- a/components/payments/content/android/java/src/org/chromium/components/payments/PaymentManifestDownloader.java
+++ b/components/payments/content/android/java/src/org/chromium/components/payments/PaymentManifestDownloader.java
@@ -55,17 +55,21 @@
}
private long mNativeObject;
+ private CSPCheckerBridge mCSPCheckerBridge;
/**
* Initializes the native downloader.
*
* @param webContents The web contents to use as the context for the downloads. If this goes
* away, pending downloads are cancelled.
+ * @param cspChecker The Content-Security-Policy (CSP) checker.
*/
- public void initialize(WebContents webContents) {
+ public void initialize(WebContents webContents, CSPChecker cspChecker) {
ThreadUtils.assertOnUiThread();
assert mNativeObject == 0;
- mNativeObject = PaymentManifestDownloaderJni.get().init(webContents);
+ mCSPCheckerBridge = new CSPCheckerBridge(cspChecker);
+ mNativeObject = PaymentManifestDownloaderJni.get().init(
+ webContents, mCSPCheckerBridge.getNativeCSPChecker());
}
/** @return Whether the native downloader is initialized. */
@@ -114,6 +118,7 @@
assert mNativeObject != 0;
PaymentManifestDownloaderJni.get().destroy(mNativeObject, PaymentManifestDownloader.this);
mNativeObject = 0;
+ if (mCSPCheckerBridge != null) mCSPCheckerBridge.destroy();
}
/** @return An opaque origin to be used in tests. */
@@ -124,7 +129,7 @@
@NativeMethods
interface Natives {
- long init(WebContents webContents);
+ long init(WebContents webContents, long nativeCSPCheckerAndroid);
void downloadPaymentMethodManifest(long nativePaymentManifestDownloaderAndroid,
PaymentManifestDownloader caller, Origin merchantOrigin, GURL methodName,
ManifestDownloadCallback callback);
diff --git a/components/payments/content/android/java/src/org/chromium/components/payments/PaymentRequestService.java b/components/payments/content/android/java/src/org/chromium/components/payments/PaymentRequestService.java
index f8cb2225..2499a6c 100644
--- a/components/payments/content/android/java/src/org/chromium/components/payments/PaymentRequestService.java
+++ b/components/payments/content/android/java/src/org/chromium/components/payments/PaymentRequestService.java
@@ -69,7 +69,7 @@
implements PaymentAppFactoryDelegate, PaymentAppFactoryParams,
PaymentRequestUpdateEventListener, PaymentApp.AbortCallback,
PaymentApp.InstrumentDetailsCallback, PaymentDetailsConverter.MethodChecker,
- PaymentResponseHelperInterface.PaymentResponseResultCallback {
+ PaymentResponseHelperInterface.PaymentResponseResultCallback, CSPChecker {
private static final String TAG = "PaymentRequestServ";
/**
* Hold the currently showing PaymentRequest. Used to prevent showing more than one
@@ -726,6 +726,15 @@
}
}
+ // Implements CSPChecker:
+ @Override
+ public void allowConnectToSource(GURL url, GURL urlBeforeRedirects, boolean didFollowRedirect,
+ Callback<Boolean> resultCallback) {
+ if (mClient == null) return;
+ mClient.allowConnectToSource(url.toMojom(), urlBeforeRedirects.toMojom(), didFollowRedirect,
+ (allow) -> { resultCallback.onResult(allow); });
+ }
+
/**
* Invokes the given payment app.
* @param paymentApp The payment app to be invoked.
@@ -1159,6 +1168,12 @@
}
}
+ // Implements PaymentAppFactoryDelegate:
+ @Override
+ public CSPChecker getCSPChecker() {
+ return this;
+ }
+
/**
* @param methodDataList A list of PaymentMethodData.
* @return The validated method data, a mapping of method names to its PaymentMethodData(s);
diff --git a/components/payments/content/android/payment_app_service_bridge.cc b/components/payments/content/android/payment_app_service_bridge.cc
index d5d34cb02..379cbd66 100644
--- a/components/payments/content/android/payment_app_service_bridge.cc
+++ b/components/payments/content/android/payment_app_service_bridge.cc
@@ -15,6 +15,7 @@
#include "base/memory/singleton.h"
#include "base/notreached.h"
#include "components/payments/content/android/byte_buffer_helper.h"
+#include "components/payments/content/android/csp_checker_android.h"
#include "components/payments/content/android/jni_headers/PaymentAppServiceBridge_jni.h"
#include "components/payments/content/android/jni_payment_app.h"
#include "components/payments/content/android/payment_request_spec.h"
@@ -95,6 +96,7 @@
// as it is no longer used.
jboolean jmay_crawl_for_installable_payment_apps,
jboolean jis_off_the_record,
+ jlong native_csp_checker_android,
const JavaParamRef<jobject>& jcallback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
@@ -120,6 +122,7 @@
env, jpayment_request_spec),
jtwa_package_name ? ConvertJavaStringToUTF8(env, jtwa_package_name) : "",
web_data_service, jis_off_the_record,
+ payments::CSPCheckerAndroid::GetWeakPtr(native_csp_checker_android),
base::BindOnce(&OnCanMakePaymentCalculated,
ScopedJavaGlobalRef<jobject>(env, jcallback)),
base::BindRepeating(&OnPaymentAppCreated,
@@ -177,6 +180,7 @@
const std::string& twa_package_name,
scoped_refptr<PaymentManifestWebDataService> web_data_service,
bool is_off_the_record,
+ base::WeakPtr<CSPChecker> csp_checker,
CanMakePaymentCalculatedCallback can_make_payment_calculated_callback,
PaymentAppCreatedCallback payment_app_created_callback,
PaymentAppCreationErrorCallback payment_app_creation_error_callback,
@@ -187,7 +191,7 @@
std::unique_ptr<PaymentAppServiceBridge> bridge(new PaymentAppServiceBridge(
number_of_factories, render_frame_host, top_origin, spec,
twa_package_name, std::move(web_data_service), is_off_the_record,
- std::move(can_make_payment_calculated_callback),
+ csp_checker, std::move(can_make_payment_calculated_callback),
std::move(payment_app_created_callback),
std::move(payment_app_creation_error_callback),
std::move(done_creating_payment_apps_callback),
@@ -203,6 +207,7 @@
const std::string& twa_package_name,
scoped_refptr<PaymentManifestWebDataService> web_data_service,
bool is_off_the_record,
+ base::WeakPtr<CSPChecker> csp_checker,
CanMakePaymentCalculatedCallback can_make_payment_calculated_callback,
PaymentAppCreatedCallback payment_app_created_callback,
PaymentAppCreationErrorCallback payment_app_creation_error_callback,
@@ -218,6 +223,7 @@
twa_package_name_(twa_package_name),
payment_manifest_web_data_service_(web_data_service),
is_off_the_record_(is_off_the_record),
+ csp_checker_(csp_checker),
can_make_payment_calculated_callback_(
std::move(can_make_payment_calculated_callback)),
payment_app_created_callback_(std::move(payment_app_created_callback)),
@@ -364,7 +370,7 @@
}
base::WeakPtr<CSPChecker> PaymentAppServiceBridge::GetCSPChecker() {
- return const_csp_checker_.GetWeakPtr();
+ return csp_checker_;
}
} // namespace payments
diff --git a/components/payments/content/android/payment_app_service_bridge.h b/components/payments/content/android/payment_app_service_bridge.h
index 09116d9..15220354 100644
--- a/components/payments/content/android/payment_app_service_bridge.h
+++ b/components/payments/content/android/payment_app_service_bridge.h
@@ -12,7 +12,6 @@
#include "base/callback_forward.h"
#include "base/memory/weak_ptr.h"
#include "components/payments/content/payment_app_factory.h"
-#include "components/payments/core/const_csp_checker.h"
#include "content/public/browser/global_routing_id.h"
#include "url/gurl.h"
#include "url/origin.h"
@@ -50,6 +49,7 @@
const std::string& twa_package_name,
scoped_refptr<PaymentManifestWebDataService> web_data_service,
bool is_off_the_record,
+ base::WeakPtr<CSPChecker> csp_checker,
CanMakePaymentCalculatedCallback can_make_payment_calculated_callback,
PaymentAppCreatedCallback payment_app_created_callback,
PaymentAppCreationErrorCallback payment_app_creation_error_callback,
@@ -106,6 +106,7 @@
const std::string& twa_package_name,
scoped_refptr<PaymentManifestWebDataService> web_data_service,
bool is_off_the_record,
+ base::WeakPtr<CSPChecker> csp_checker,
CanMakePaymentCalculatedCallback can_make_payment_calculated_callback,
PaymentAppCreatedCallback payment_app_created_callback,
PaymentAppCreationErrorCallback payment_app_creation_error_callback,
@@ -123,6 +124,7 @@
payment_manifest_web_data_service_;
bool is_off_the_record_;
std::vector<autofill::AutofillProfile*> dummy_profiles_;
+ base::WeakPtr<CSPChecker> csp_checker_;
CanMakePaymentCalculatedCallback can_make_payment_calculated_callback_;
PaymentAppCreatedCallback payment_app_created_callback_;
@@ -130,9 +132,6 @@
base::OnceClosure done_creating_payment_apps_callback_;
base::RepeatingClosure set_can_make_payment_even_without_apps_callback_;
- // TODO(https://crbug.com/1349091): Check the CSP in the renderer instead.
- ConstCSPChecker const_csp_checker_{/*allow=*/true};
-
base::WeakPtrFactory<PaymentAppServiceBridge> weak_ptr_factory_{this};
};
diff --git a/components/payments/content/android/payment_manifest_downloader_android.cc b/components/payments/content/android/payment_manifest_downloader_android.cc
index 33aa50d2..2539be4 100644
--- a/components/payments/content/android/payment_manifest_downloader_android.cc
+++ b/components/payments/content/android/payment_manifest_downloader_android.cc
@@ -9,6 +9,7 @@
#include "base/android/jni_string.h"
#include "base/android/scoped_java_ref.h"
#include "base/bind.h"
+#include "components/payments/content/android/csp_checker_android.h"
#include "components/payments/content/android/jni_headers/PaymentManifestDownloader_jni.h"
#include "components/payments/content/developer_console_logger.h"
#include "content/public/browser/browser_context.h"
@@ -75,10 +76,9 @@
PaymentManifestDownloaderAndroid::PaymentManifestDownloaderAndroid(
std::unique_ptr<ErrorLogger> log,
+ base::WeakPtr<CSPChecker> csp_checker,
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory)
- : downloader_(std::move(log),
- const_csp_checker_.GetWeakPtr(),
- std::move(url_loader_factory)) {}
+ : downloader_(std::move(log), csp_checker, std::move(url_loader_factory)) {}
PaymentManifestDownloaderAndroid::~PaymentManifestDownloaderAndroid() {}
@@ -118,7 +118,11 @@
// Caller owns the result. Returns 0 on error.
static jlong JNI_PaymentManifestDownloader_Init(
JNIEnv* env,
- const base::android::JavaParamRef<jobject>& jweb_contents) {
+ const base::android::JavaParamRef<jobject>& jweb_contents,
+ jlong native_csp_checker_android) {
+ if (!jweb_contents || !native_csp_checker_android)
+ return 0;
+
content::WebContents* web_contents =
content::WebContents::FromJavaWebContents(jweb_contents);
if (!web_contents)
@@ -126,6 +130,7 @@
return reinterpret_cast<jlong>(new PaymentManifestDownloaderAndroid(
std::make_unique<DeveloperConsoleLogger>(web_contents),
+ payments::CSPCheckerAndroid::GetWeakPtr(native_csp_checker_android),
web_contents->GetBrowserContext()
->GetDefaultStoragePartition()
->GetURLLoaderFactoryForBrowserProcess()));
diff --git a/components/payments/content/android/payment_manifest_downloader_android.h b/components/payments/content/android/payment_manifest_downloader_android.h
index 10e3239..426d2b9d 100644
--- a/components/payments/content/android/payment_manifest_downloader_android.h
+++ b/components/payments/content/android/payment_manifest_downloader_android.h
@@ -10,7 +10,7 @@
#include "base/android/jni_android.h"
#include "base/memory/ref_counted.h"
-#include "components/payments/core/const_csp_checker.h"
+#include "base/memory/weak_ptr.h"
#include "components/payments/core/payment_manifest_downloader.h"
namespace network {
@@ -19,6 +19,7 @@
namespace payments {
+class CSPChecker;
class ErrorLogger;
// Android wrapper for the payment manifest downloader.
@@ -26,6 +27,7 @@
public:
PaymentManifestDownloaderAndroid(
std::unique_ptr<ErrorLogger> log,
+ base::WeakPtr<CSPChecker> csp_checker,
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory);
PaymentManifestDownloaderAndroid(const PaymentManifestDownloaderAndroid&) =
@@ -55,9 +57,6 @@
const base::android::JavaParamRef<jobject>& jcaller);
private:
- // TODO(https://crbug.com/1349091): Check the CSP in the renderer instead.
- ConstCSPChecker const_csp_checker_{/*allow=*/true};
-
PaymentManifestDownloader downloader_;
};
diff --git a/components/payments/core/BUILD.gn b/components/payments/core/BUILD.gn
index aec4125d..a1eba8c 100644
--- a/components/payments/core/BUILD.gn
+++ b/components/payments/core/BUILD.gn
@@ -12,8 +12,6 @@
"android_app_description_tools.h",
"can_make_payment_query.cc",
"can_make_payment_query.h",
- "const_csp_checker.cc",
- "const_csp_checker.h",
"csp_checker.h",
"currency_formatter.cc",
"currency_formatter.h",
@@ -133,6 +131,8 @@
static_library("test_support") {
testonly = true
sources = [
+ "const_csp_checker.cc",
+ "const_csp_checker.h",
"payments_test_util.cc",
"payments_test_util.h",
"test_payment_manifest_downloader.cc",
diff --git a/components/payments/core/const_csp_checker.h b/components/payments/core/const_csp_checker.h
index f82f0c5..9fadda7 100644
--- a/components/payments/core/const_csp_checker.h
+++ b/components/payments/core/const_csp_checker.h
@@ -10,10 +10,8 @@
namespace payments {
-// A stub class for either always allowing or always denying connections,
+// A test-only class for either always allowing or always denying connections,
// depending on the input parameter for the constructor.
-// TODO(crbug.com/1349091): Make this class test-only after production code
-// stops using it.
class ConstCSPChecker : public CSPChecker {
public:
explicit ConstCSPChecker(bool allow);
diff --git a/url/BUILD.gn b/url/BUILD.gn
index e4e0c4e..5e3fcdcb 100644
--- a/url/BUILD.gn
+++ b/url/BUILD.gn
@@ -145,6 +145,7 @@
"//build/android:build_java",
"//third_party/android_deps:com_google_errorprone_error_prone_annotations_java",
"//third_party/androidx:androidx_annotation_annotation_java",
+ "//url/mojom:url_mojom_gurl_java",
]
annotation_processor_deps = [ "//base/android/jni_generator:jni_processor" ]
}
@@ -338,6 +339,7 @@
"//third_party/androidx:androidx_test_runner_java",
"//third_party/junit",
"//third_party/mockito:mockito_java",
+ "//url/mojom:url_mojom_gurl_java",
]
annotation_processor_deps = [ "//base/android/jni_generator:jni_processor" ]
}
diff --git a/url/android/java/src/org/chromium/url/GURL.java b/url/android/java/src/org/chromium/url/GURL.java
index 48c30da..ad552817 100644
--- a/url/android/java/src/org/chromium/url/GURL.java
+++ b/url/android/java/src/org/chromium/url/GURL.java
@@ -22,6 +22,7 @@
import org.chromium.base.task.PostTask;
import org.chromium.base.task.TaskTraits;
import org.chromium.build.annotations.MainDex;
+import org.chromium.url.mojom.Url;
import java.util.Random;
@@ -49,6 +50,9 @@
void run(Throwable throwable);
}
+ // See url::kMaxURLChars in url/url_constants.cc.
+ private static final int MAX_URL_CHARS = 2 * 1024 * 1024;
+
// Right now this is only collecting reports on Canary which has a relatively small population.
private static final int DEBUG_REPORT_PERCENTAGE = 10;
private static ReportDebugThrowableCallback sReportCallback;
@@ -346,6 +350,17 @@
init(gurl.mSpec, gurl.mIsValid, gurl.mParsed);
}
+ /** @return A Mojom representation of this URL. */
+ public Url toMojom() {
+ Url url = new Url();
+ // See url/mojom/url_gurl_mojom_traits.cc.
+ url.url = TextUtils.isEmpty(getPossiblyInvalidSpec())
+ || getPossiblyInvalidSpec().length() > MAX_URL_CHARS || !isValid()
+ ? ""
+ : getPossiblyInvalidSpec();
+ return url;
+ }
+
@NativeMethods
interface Natives {
/**
diff --git a/url/android/javatests/src/org/chromium/url/GURLJavaTest.java b/url/android/javatests/src/org/chromium/url/GURLJavaTest.java
index 2a5fcd3..02913b077 100644
--- a/url/android/javatests/src/org/chromium/url/GURLJavaTest.java
+++ b/url/android/javatests/src/org/chromium/url/GURLJavaTest.java
@@ -287,4 +287,28 @@
Assert.assertTrue(url1.domainIs("www.google.com"));
Assert.assertFalse(url1.domainIs("images.google.com"));
}
+
+ // Tests Mojom conversion.
+ @SmallTest
+ @Test
+ public void testMojomConvertion() {
+ // Valid:
+ Assert.assertEquals(
+ "https://www.google.com/", new GURL("https://www.google.com/").toMojom().url);
+
+ // Null:
+ Assert.assertEquals("", new GURL(null).toMojom().url);
+
+ // Empty:
+ Assert.assertEquals("", new GURL("").toMojom().url);
+
+ // Invalid:
+ Assert.assertEquals("", new GURL(new String(new byte[] {1, 1, 1})).toMojom().url);
+
+ // Too long.
+ Assert.assertEquals("",
+ new GURL("https://www.google.com/".concat("a".repeat(2 * 1024 * 1024)))
+ .toMojom()
+ .url);
+ }
}