Commit b26faa07 authored by Rouslan Solomakhin's avatar Rouslan Solomakhin Committed by Commit Bot

[Web Payment] Basic Card quota for hasEnrolledInstrument().

The quota for hasEnrolledInstrument() must be based on the requested
autofill data when Basic Card payment method is being used, so the
merchant cannot iterate over all options (requestShipping,
requestPayerEmail, requestPayerName, requestPayerPhone)  and learn about
user's autofill data.

Disabled by default behind "StrictHasEnrolledAutofillInstrument"
feature.

Explainer:
https://gist.github.com/rsolomakhin/d6d242cbb9306864ada5a29de7ab271e

Design (Googlers only):
https://docs.google.com/document/d/1VVggx4cy_QFsSEWtnZ9yhvtI4lNPONA4rnJKH3VV9jQ/preview

Bug: 993390
Change-Id: I4c2070572a43e7423ba43736741a62546fd8fb8d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1757293
Commit-Queue: Rouslan Solomakhin <rouslan@chromium.org>
Reviewed-by: default avatarDanyao Wang <danyao@chromium.org>
Cr-Commit-Position: refs/heads/master@{#688083}
parent 76f1eb03
...@@ -379,6 +379,17 @@ public class PaymentRequestImpl ...@@ -379,6 +379,17 @@ public class PaymentRequestImpl
private OverviewModeBehavior mOverviewModeBehavior; private OverviewModeBehavior mOverviewModeBehavior;
private PaymentHandlerHost mPaymentHandlerHost; private PaymentHandlerHost mPaymentHandlerHost;
/**
* A mapping of the payment method names to the corresponding payment method specific data. If
* STRICT_HAS_ENROLLED_AUTOFILL_INSTRUMENT is enabled, then the key "basic-card-payment-options"
* also maps to the following payment options:
* - requestPayerEmail
* - requestPayerName
* - requestPayerPhone
* - requestShipping
*/
private Map<String, PaymentMethodData> mQueryForQuota;
/** Aborts should only be recorded if the Payment Request was shown to the user. */ /** Aborts should only be recorded if the Payment Request was shown to the user. */
private boolean mShouldRecordAbortReason; private boolean mShouldRecordAbortReason;
...@@ -503,6 +514,7 @@ public class PaymentRequestImpl ...@@ -503,6 +514,7 @@ public class PaymentRequestImpl
Log.d(TAG, ErrorStrings.PROHIBITED_ORIGIN_OR_INVALID_SSL_EXPLANATION); Log.d(TAG, ErrorStrings.PROHIBITED_ORIGIN_OR_INVALID_SSL_EXPLANATION);
// Don't show any UI. Resolve .canMakePayment() with "false". Reject .show() with // Don't show any UI. Resolve .canMakePayment() with "false". Reject .show() with
// "NotSupportedError". // "NotSupportedError".
mQueryForQuota = new HashMap<>();
onAllPaymentAppsCreated(); onAllPaymentAppsCreated();
return; return;
} }
...@@ -518,6 +530,7 @@ public class PaymentRequestImpl ...@@ -518,6 +530,7 @@ public class PaymentRequestImpl
Log.d(TAG, ErrorStrings.PROHIBITED_ORIGIN_OR_INVALID_SSL_EXPLANATION); Log.d(TAG, ErrorStrings.PROHIBITED_ORIGIN_OR_INVALID_SSL_EXPLANATION);
// Don't show any UI. Resolve .canMakePayment() with "false". Reject .show() with // Don't show any UI. Resolve .canMakePayment() with "false". Reject .show() with
// "NotSupportedError". // "NotSupportedError".
mQueryForQuota = new HashMap<>();
onAllPaymentAppsCreated(); onAllPaymentAppsCreated();
return; return;
} }
...@@ -529,6 +542,17 @@ public class PaymentRequestImpl ...@@ -529,6 +542,17 @@ public class PaymentRequestImpl
return; return;
} }
mQueryForQuota = new HashMap<>(mMethodData);
if (mQueryForQuota.containsKey("basic-card")
&& PaymentsExperimentalFeatures.isEnabled(
ChromeFeatureList.STRICT_HAS_ENROLLED_AUTOFILL_INSTRUMENT)) {
PaymentMethodData paymentMethodData = new PaymentMethodData();
paymentMethodData.stringifiedData = String.format(
"{payerEmail:%s,payerName:%s,payerPhone:%s,shipping:%s}", mRequestPayerEmail,
mRequestPayerName, mRequestPayerPhone, mRequestShipping);
mQueryForQuota.put("basic-card-payment-options", paymentMethodData);
}
if (!parseAndValidateDetailsOrDisconnectFromClient(details)) return; if (!parseAndValidateDetailsOrDisconnectFromClient(details)) return;
if (mRawTotal == null) { if (mRawTotal == null) {
...@@ -1995,7 +2019,7 @@ public class PaymentRequestImpl ...@@ -1995,7 +2019,7 @@ public class PaymentRequestImpl
mIsHasEnrolledInstrumentResponsePending = false; mIsHasEnrolledInstrumentResponsePending = false;
if (CanMakePaymentQuery.canQuery(mWebContents, mTopLevelOrigin, mPaymentRequestOrigin, if (CanMakePaymentQuery.canQuery(mWebContents, mTopLevelOrigin, mPaymentRequestOrigin,
mMethodData, mHasEnrolledInstrumentUsesPerMethodQuota)) { mQueryForQuota, mHasEnrolledInstrumentUsesPerMethodQuota)) {
mClient.onHasEnrolledInstrument(response mClient.onHasEnrolledInstrument(response
? HasEnrolledInstrumentQueryResult.HAS_ENROLLED_INSTRUMENT ? HasEnrolledInstrumentQueryResult.HAS_ENROLLED_INSTRUMENT
: HasEnrolledInstrumentQueryResult.HAS_NO_ENROLLED_INSTRUMENT); : HasEnrolledInstrumentQueryResult.HAS_NO_ENROLLED_INSTRUMENT);
......
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/command_line.h"
#include "base/macros.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "chrome/test/base/chrome_test_utils.h"
#include "components/network_session_configurator/common/network_switches.h"
#include "components/payments/core/features.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test_utils.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "testing/gtest/include/gtest/gtest.h"
#if defined(OS_ANDROID)
#include "chrome/test/base/android/android_browser_test.h"
#else
#include "chrome/test/base/in_process_browser_test.h"
#endif
namespace payments {
namespace {
class HasEnrolledInstrumentQueryQuotaTest : public PlatformBrowserTest {
public:
HasEnrolledInstrumentQueryQuotaTest()
: https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
~HasEnrolledInstrumentQueryQuotaTest() override {}
void SetUpCommandLine(base::CommandLine* command_line) override {
// HTTPS server only serves a valid cert for localhost, so this is needed to
// load pages from other hosts without an error.
command_line->AppendSwitch(switches::kIgnoreCertificateErrors);
}
void SetUpOnMainThread() override {
host_resolver()->AddRule("*", "127.0.0.1");
ASSERT_TRUE(https_server_.InitializeAndListen());
content::SetupCrossSiteRedirector(&https_server_);
https_server_.ServeFilesFromSourceDirectory(
"components/test/data/payments");
https_server_.StartAcceptingConnections();
// Cannot use the default localhost hostname, because Chrome turns off the
// quota for localhost and file:/// scheme to ease web development.
ASSERT_TRUE(content::NavigateToURL(
GetActiveWebContents(),
https_server_.GetURL("a.com", "/has_enrolled_instrument.html")));
PlatformBrowserTest::SetUpOnMainThread();
}
content::WebContents* GetActiveWebContents() {
return chrome_test_utils::GetActiveWebContents(this);
}
private:
net::EmbeddedTestServer https_server_;
DISALLOW_COPY_AND_ASSIGN(HasEnrolledInstrumentQueryQuotaTest);
};
IN_PROC_BROWSER_TEST_F(HasEnrolledInstrumentQueryQuotaTest, QueryQuota) {
// Payment options do not trigger query quota when the
// kStrictHasEnrolledAutofillInstrument feature is disabled.
EXPECT_EQ(false,
content::EvalJs(GetActiveWebContents(), "hasEnrolledInstrument()"));
EXPECT_EQ(false,
content::EvalJs(GetActiveWebContents(),
"hasEnrolledInstrument({requestShipping:true})"));
base::test::ScopedFeatureList features;
features.InitAndEnableFeature(features::kStrictHasEnrolledAutofillInstrument);
// Payment options trigger query quota for Basic Card when the
// kStrictHasEnrolledAutofillInstrument feature is enabled.
EXPECT_EQ(false,
content::EvalJs(GetActiveWebContents(), "hasEnrolledInstrument()"));
EXPECT_EQ("NotAllowedError: Exceeded query quota for hasEnrolledInstrument",
content::EvalJs(GetActiveWebContents(),
"hasEnrolledInstrument({requestShipping:true})"));
}
} // namespace
} // namespace payments
...@@ -507,6 +507,7 @@ if (is_android) { ...@@ -507,6 +507,7 @@ if (is_android) {
sources = [ sources = [
"../browser/engagement/important_sites_util_browsertest.cc", "../browser/engagement/important_sites_util_browsertest.cc",
"../browser/payments/has_enrolled_instrument_browsertest.cc", "../browser/payments/has_enrolled_instrument_browsertest.cc",
"../browser/payments/has_enrolled_instrument_query_quota_browsertest.cc",
"../browser/payments/payment_request_can_make_payment_browsertest.cc", "../browser/payments/payment_request_can_make_payment_browsertest.cc",
"../browser/payments/personal_data_manager_test_util.cc", "../browser/payments/personal_data_manager_test_util.cc",
"../browser/payments/personal_data_manager_test_util.h", "../browser/payments/personal_data_manager_test_util.h",
...@@ -1787,6 +1788,7 @@ if (!is_android) { ...@@ -1787,6 +1788,7 @@ if (!is_android) {
if (toolkit_views) { if (toolkit_views) {
sources += [ sources += [
"../browser/payments/has_enrolled_instrument_browsertest.cc", "../browser/payments/has_enrolled_instrument_browsertest.cc",
"../browser/payments/has_enrolled_instrument_query_quota_browsertest.cc",
"../browser/payments/manifest_verifier_browsertest.cc", "../browser/payments/manifest_verifier_browsertest.cc",
"../browser/payments/payment_manifest_parser_browsertest.cc", "../browser/payments/payment_manifest_parser_browsertest.cc",
"../browser/payments/payment_request_can_make_payment_browsertest.cc", "../browser/payments/payment_request_can_make_payment_browsertest.cc",
......
...@@ -713,11 +713,10 @@ void PaymentRequest::CanMakePaymentCallback(bool can_make_payment) { ...@@ -713,11 +713,10 @@ void PaymentRequest::CanMakePaymentCallback(bool can_make_payment) {
void PaymentRequest::HasEnrolledInstrumentCallback( void PaymentRequest::HasEnrolledInstrumentCallback(
bool per_method_quota, bool per_method_quota,
bool has_enrolled_instrument) { bool has_enrolled_instrument) {
if (!spec_ || if (!spec_ || CanMakePaymentQueryFactory::GetInstance()
CanMakePaymentQueryFactory::GetInstance() ->GetForContext(web_contents_->GetBrowserContext())
->GetForContext(web_contents_->GetBrowserContext()) ->CanQuery(top_level_origin_, frame_origin_,
->CanQuery(top_level_origin_, frame_origin_, spec_->query_for_quota(), per_method_quota)) {
spec_->stringified_method_data(), per_method_quota)) {
RespondToHasEnrolledInstrumentQuery(has_enrolled_instrument, RespondToHasEnrolledInstrumentQuery(has_enrolled_instrument,
/*warn_local_development=*/false); /*warn_local_development=*/false);
} else if (UrlUtil::IsLocalDevelopmentUrl(frame_origin_)) { } else if (UrlUtil::IsLocalDevelopmentUrl(frame_origin_)) {
......
...@@ -8,12 +8,15 @@ ...@@ -8,12 +8,15 @@
#include "base/feature_list.h" #include "base/feature_list.h"
#include "base/logging.h" #include "base/logging.h"
#include "base/stl_util.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "components/payments/content/payment_request_converter.h" #include "components/payments/content/payment_request_converter.h"
#include "components/payments/core/features.h" #include "components/payments/core/features.h"
#include "components/payments/core/payment_instrument.h" #include "components/payments/core/payment_instrument.h"
#include "components/payments/core/payment_method_data.h" #include "components/payments/core/payment_method_data.h"
#include "components/payments/core/payment_request_data_util.h" #include "components/payments/core/payment_request_data_util.h"
#include "components/payments/core/payments_experimental_features.h"
#include "components/strings/grit/components_strings.h" #include "components/strings/grit/components_strings.h"
#include "ui/base/l10n/l10n_util.h" #include "ui/base/l10n/l10n_util.h"
...@@ -68,6 +71,10 @@ void PopulateValidatedMethodData( ...@@ -68,6 +71,10 @@ void PopulateValidatedMethodData(
payment_method_identifiers_set, stringified_method_data); payment_method_identifiers_set, stringified_method_data);
} }
std::string ToString(bool value) {
return value ? "true" : "false";
}
} // namespace } // namespace
const char kBasicCardMethodName[] = "basic-card"; const char kBasicCardMethodName[] = "basic-card";
...@@ -100,6 +107,18 @@ PaymentRequestSpec::PaymentRequestSpec( ...@@ -100,6 +107,18 @@ PaymentRequestSpec::PaymentRequestSpec(
&supported_card_networks_set_, &supported_card_types_set_, &supported_card_networks_set_, &supported_card_types_set_,
&url_payment_method_identifiers_, &payment_method_identifiers_set_, &url_payment_method_identifiers_, &payment_method_identifiers_set_,
&stringified_method_data_); &stringified_method_data_);
query_for_quota_ = stringified_method_data_;
if (base::Contains(payment_method_identifiers_set_, "basic-card") &&
PaymentsExperimentalFeatures::IsEnabled(
features::kStrictHasEnrolledAutofillInstrument)) {
query_for_quota_["basic-card-payment-options"] = {
base::ReplaceStringPlaceholders(
"{payerEmail:$1,payerName:$2,payerPhone:$3,shipping:$4}",
{ToString(request_payer_email()), ToString(request_payer_name()),
ToString(request_payer_phone()), ToString(request_shipping())},
nullptr)};
}
} }
PaymentRequestSpec::~PaymentRequestSpec() {} PaymentRequestSpec::~PaymentRequestSpec() {}
......
...@@ -125,6 +125,19 @@ class PaymentRequestSpec : public PaymentOptionsProvider, ...@@ -125,6 +125,19 @@ class PaymentRequestSpec : public PaymentOptionsProvider,
bool request_payer_email() const override; bool request_payer_email() const override;
PaymentShippingType shipping_type() const override; PaymentShippingType shipping_type() const override;
// Returns the query to be used for the quota on hasEnrolledInstrument()
// calls. Generally this returns the payment method identifiers and their
// corresponding data. However, in the case of basic-card with
// kStrictHasEnrolledAutofillInstrument feature enabled, this method also
// returns the following payment options:
// - requestPayerEmail
// - requestPayerName
// - requestPayerPhone
// - requestShipping
const std::map<std::string, std::set<std::string>>& query_for_quota() const {
return query_for_quota_;
}
bool supports_basic_card() const { return !supported_card_networks_.empty(); } bool supports_basic_card() const { return !supported_card_networks_.empty(); }
const std::vector<std::string>& supported_card_networks() const { const std::vector<std::string>& supported_card_networks() const {
...@@ -260,6 +273,16 @@ class PaymentRequestSpec : public PaymentOptionsProvider, ...@@ -260,6 +273,16 @@ class PaymentRequestSpec : public PaymentOptionsProvider,
// payment method specific data. // payment method specific data.
std::map<std::string, std::set<std::string>> stringified_method_data_; std::map<std::string, std::set<std::string>> stringified_method_data_;
// A mapping of the payment method names to the corresponding JSON-stringified
// payment method specific data. If kStrictHasEnrolledAutofillInstrument is
// enabled, then the key "basic-card-payment-options" also maps to the
// following payment options:
// - requestPayerEmail
// - requestPayerName
// - requestPayerPhone
// - requestShipping
std::map<std::string, std::set<std::string>> query_for_quota_;
// The reason why this payment request is waiting for updateWith. // The reason why this payment request is waiting for updateWith.
UpdateReason current_update_reason_; UpdateReason current_update_reason_;
......
...@@ -8,14 +8,20 @@ ...@@ -8,14 +8,20 @@
* Checks the hasEnrolledInstrument() value for 'basic-card' with the given * Checks the hasEnrolledInstrument() value for 'basic-card' with the given
* options. * options.
* @param {PaymentOptions} options - The payment options to use. * @param {PaymentOptions} options - The payment options to use.
* @return {Promise<boolean>} The value of hasEnrolledInstrument(). * @return {Promise<boolean|string>} The boolean value of
* hasEnrolledInstrument() or the error message string.
*/ */
function hasEnrolledInstrument(options) { // eslint-disable-line no-unused-vars async function hasEnrolledInstrument(options) { // eslint-disable-line no-unused-vars,max-len
return new PaymentRequest( try {
[{supportedMethods: 'basic-card'}], { const result =
total: await new PaymentRequest(
{label: 'Total', amount: {currency: 'USD', value: '0.01'}}, [{supportedMethods: 'basic-card'}], {
}, total: {label: 'Total', amount: {currency: 'USD', value: '0.01'}},
options) },
.hasEnrolledInstrument(); options)
.hasEnrolledInstrument();
return result;
} catch (e) {
return e.toString();
}
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment