blob: 6abe3ecf2a498e94f10ae4b5401d3dbae1fc9fbc [file] [log] [blame]
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/payments/secure_payment_confirmation_browsertest.h"
#include <stdint.h>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/command_line.h"
#include "base/files/file_util.h"
#include "base/memory/ref_counted.h"
#include "base/path_service.h"
#include "base/strings/strcat.h"
#include "base/strings/stringprintf.h"
#include "base/threading/thread_restrictions.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/browser/profiles/profile.h"
#include "components/autofill/core/browser/test_event_waiter.h"
#include "components/keyed_service/core/service_access_type.h"
#include "components/payments/content/payment_manifest_web_data_service.h"
#include "components/payments/core/secure_payment_confirmation_credential.h"
#include "components/webdata_services/web_data_service_wrapper_factory.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "third_party/blink/public/mojom/payments/payment_handler_host.mojom.h"
namespace payments {
void SecurePaymentConfirmationTest::SetUpCommandLine(
base::CommandLine* command_line) {
PaymentRequestPlatformBrowserTestBase::SetUpCommandLine(command_line);
command_line->AppendSwitch(switches::kEnableExperimentalWebPlatformFeatures);
}
void SecurePaymentConfirmationTest::OnAppListReady() {
PaymentRequestPlatformBrowserTestBase::OnAppListReady();
if (confirm_payment_)
ASSERT_TRUE(test_controller()->ConfirmPayment());
}
void SecurePaymentConfirmationTest::OnErrorDisplayed() {
PaymentRequestPlatformBrowserTestBase::OnErrorDisplayed();
if (close_dialog_on_error_)
ASSERT_TRUE(test_controller()->CloseDialog());
}
void SecurePaymentConfirmationTest::OnWebDataServiceRequestDone(
WebDataServiceBase::Handle h,
std::unique_ptr<WDTypedResult> result) {
ASSERT_NE(nullptr, result);
ASSERT_EQ(BOOL_RESULT, result->GetType());
EXPECT_TRUE(static_cast<WDResult<bool>*>(result.get())->GetValue());
database_write_responded_ = true;
}
void SecurePaymentConfirmationTest::ExpectEvent2Histogram(
std::set<JourneyLogger::Event2> events,
int count) {
std::vector<base::Bucket> buckets =
histogram_tester_.GetAllSamples("PaymentRequest.Events2");
ASSERT_EQ(1U, buckets.size());
int64_t expected_events = 0;
for (const JourneyLogger::Event2& event : events) {
expected_events |= static_cast<int>(event);
}
EXPECT_EQ(buckets[0].min, expected_events);
EXPECT_EQ(count, buckets[0].count);
}
// static
std::string SecurePaymentConfirmationTest::GetWebAuthnErrorMessage() {
return "NotAllowedError: The operation either timed out or was not allowed. "
"See: https://www.w3.org/TR/webauthn-2/"
"#sctn-privacy-considerations-client.";
}
namespace {
using Event2 = payments::JourneyLogger::Event2;
std::string GetIconDownloadErrorMessage() {
return "NotSupportedError: The payment method "
"\"secure-payment-confirmation\" is not supported. "
"The \"instrument.icon\" either could not be downloaded or decoded.";
}
// Tests that show() will display the Transaction UX, if there is a matching
// credential.
IN_PROC_BROWSER_TEST_F(SecurePaymentConfirmationTest, Show_TransactionUX) {
test_controller()->SetHasAuthenticator(true);
NavigateTo("a.com", "/secure_payment_confirmation.html");
std::vector<uint8_t> credential_id = {'c', 'r', 'e', 'd'};
std::vector<uint8_t> user_id = {'u', 's', 'e', 'r'};
webdata_services::WebDataServiceWrapperFactory::
GetPaymentManifestWebDataServiceForBrowserContext(
GetActiveWebContents()->GetBrowserContext(),
ServiceAccessType::EXPLICIT_ACCESS)
->AddSecurePaymentConfirmationCredential(
std::make_unique<SecurePaymentConfirmationCredential>(
std::move(credential_id), "a.com", std::move(user_id)),
/*consumer=*/this);
ResetEventWaiterForSingleEvent(TestEvent::kUIDisplayed);
ExecuteScriptAsync(GetActiveWebContents(),
"getSecurePaymentConfirmationStatus()");
WaitForObservedEvent();
EXPECT_TRUE(database_write_responded_);
ASSERT_FALSE(test_controller()->app_descriptions().empty());
EXPECT_EQ(1u, test_controller()->app_descriptions().size());
EXPECT_EQ("display_name_for_instrument",
test_controller()->app_descriptions().front().label);
// As these tests mock out the authenticator, we cannot continue on the
// Transaction UX and instead must cancel out. For tests that test the
// continue flow, see secure_payment_confirmation_authenticator_browsertest.cc
test_controller()->CloseDialog();
EXPECT_EQ(
GetWebAuthnErrorMessage(),
content::EvalJs(GetActiveWebContents(), "getOutstandingStatusPromise()"));
ExpectEvent2Histogram({Event2::kInitiated, Event2::kShown,
Event2::kUserAborted, Event2::kHadInitialFormOfPayment,
Event2::kRequestMethodSecurePaymentConfirmation});
}
// Tests that calling show() on a platform without an authenticator will trigger
// the No Matching Credentials UX.
IN_PROC_BROWSER_TEST_F(SecurePaymentConfirmationTest, Show_NoAuthenticator) {
test_controller()->SetHasAuthenticator(false);
NavigateTo("a.com", "/secure_payment_confirmation.html");
close_dialog_on_error_ = true;
EXPECT_EQ(GetWebAuthnErrorMessage(),
content::EvalJs(GetActiveWebContents(),
"getSecurePaymentConfirmationStatus()"));
ExpectEvent2Histogram({Event2::kInitiated, Event2::kShown,
Event2::kUserAborted, Event2::kNoMatchingCredentials,
Event2::kRequestMethodSecurePaymentConfirmation});
}
// Tests that calling show() with no matching credentials will trigger the No
// Matching Credentials UX.
IN_PROC_BROWSER_TEST_F(SecurePaymentConfirmationTest,
Show_NoMatchingCredential) {
test_controller()->SetHasAuthenticator(true);
NavigateTo("a.com", "/secure_payment_confirmation.html");
close_dialog_on_error_ = true;
EXPECT_EQ(GetWebAuthnErrorMessage(),
content::EvalJs(GetActiveWebContents(),
"getSecurePaymentConfirmationStatus()"));
ExpectEvent2Histogram({Event2::kInitiated, Event2::kShown,
Event2::kUserAborted, Event2::kNoMatchingCredentials,
Event2::kRequestMethodSecurePaymentConfirmation});
}
// Tests that a credential with the correct credential ID but wrong RP ID will
// not match.
IN_PROC_BROWSER_TEST_F(SecurePaymentConfirmationTest,
Show_WrongCredentialRpId) {
test_controller()->SetHasAuthenticator(true);
NavigateTo("a.com", "/secure_payment_confirmation.html");
std::vector<uint8_t> credential_id = {'c', 'r', 'e', 'd'};
std::vector<uint8_t> user_id = {'u', 's', 'e', 'r'};
webdata_services::WebDataServiceWrapperFactory::
GetPaymentManifestWebDataServiceForBrowserContext(
GetActiveWebContents()->GetBrowserContext(),
ServiceAccessType::EXPLICIT_ACCESS)
->AddSecurePaymentConfirmationCredential(
std::make_unique<SecurePaymentConfirmationCredential>(
std::move(credential_id), "relying-party.example",
std::move(user_id)),
/*consumer=*/this);
// getSecurePaymentConfirmationStatus creates a SPC credential with RP ID
// a.com, which doesn't match the stored credential's relying-party.example,
// so the No Matching Credentials dialog will be displayed.
ResetEventWaiterForSingleEvent(TestEvent::kErrorDisplayed);
ExecuteScriptAsync(GetActiveWebContents(),
"getSecurePaymentConfirmationStatus()");
WaitForObservedEvent();
EXPECT_TRUE(database_write_responded_);
EXPECT_TRUE(test_controller()->app_descriptions().empty());
test_controller()->CloseDialog();
ExpectEvent2Histogram({Event2::kInitiated, Event2::kShown,
Event2::kUserAborted, Event2::kNoMatchingCredentials,
Event2::kRequestMethodSecurePaymentConfirmation});
}
// Tests that a failed icon download immediately rejects the show() promise,
// without any browser UI being shown.
IN_PROC_BROWSER_TEST_F(SecurePaymentConfirmationTest, IconDownloadFailure) {
test_controller()->SetHasAuthenticator(true);
NavigateTo("a.com", "/secure_payment_confirmation.html");
// We test both with and without a matching credential, so add a credential
// for the former case.
std::vector<uint8_t> credential_id = {'c', 'r', 'e', 'd'};
std::vector<uint8_t> user_id = {'u', 's', 'e', 'r'};
webdata_services::WebDataServiceWrapperFactory::
GetPaymentManifestWebDataServiceForBrowserContext(
GetActiveWebContents()->GetBrowserContext(),
ServiceAccessType::EXPLICIT_ACCESS)
->AddSecurePaymentConfirmationCredential(
std::make_unique<SecurePaymentConfirmationCredential>(
std::move(credential_id), "relying-party.example",
std::move(user_id)),
/*consumer=*/this);
// canMakePayment does not check for a valid icon, so should return true.
EXPECT_EQ("true",
content::EvalJs(GetActiveWebContents(),
"securePaymentConfirmationCanMakePayment(window."
"location.origin + '/non-existant-icon.png')"));
// The show() promise, however, should reject without showing any UX -
// whether or not a valid credential is passed.
std::string credBase64 = "Y3JlZA==";
EXPECT_EQ(
GetIconDownloadErrorMessage(),
content::EvalJs(GetActiveWebContents(),
content::JsReplace(
"getSecurePaymentConfirmationStatus($1, "
"window.location.origin + '/non-existant-icon.png')",
credBase64)));
std::string invalidCred = "ZGVyYw==";
EXPECT_EQ(
GetIconDownloadErrorMessage(),
content::EvalJs(GetActiveWebContents(),
content::JsReplace(
"getSecurePaymentConfirmationStatus($1, "
"window.location.origin + '/non-existant-icon.png')",
invalidCred)));
// Both scenarios should log the same histogram.
ExpectEvent2Histogram({Event2::kInitiated, Event2::kCouldNotShow,
Event2::kRequestMethodSecurePaymentConfirmation},
/*count=*/2);
}
class SecurePaymentConfirmationDisableDebugTest
: public SecurePaymentConfirmationTest {
public:
SecurePaymentConfirmationDisableDebugTest() {
feature_list_.InitWithFeatures(
/*enabled_features=*/{::features::kSecurePaymentConfirmation},
/*disabled_features=*/{::features::kSecurePaymentConfirmationDebug});
}
private:
base::test::ScopedFeatureList feature_list_;
};
// canMakePayment() and hasEnrolledInstrument() should return false on
// platforms without a compatible authenticator.
IN_PROC_BROWSER_TEST_F(SecurePaymentConfirmationDisableDebugTest,
CanMakePayment_NoAuthenticator) {
test_controller()->SetHasAuthenticator(false);
NavigateTo("a.com", "/secure_payment_confirmation.html");
EXPECT_EQ("false",
content::EvalJs(GetActiveWebContents(),
"securePaymentConfirmationCanMakePayment()"));
EXPECT_EQ("false", content::EvalJs(
GetActiveWebContents(),
"securePaymentConfirmationHasEnrolledInstrument()"));
}
// canMakePayment() and hasEnrolledInstrument() should return true on
// platforms with a compatible authenticator regardless of the presence of
// payment credentials.
IN_PROC_BROWSER_TEST_F(SecurePaymentConfirmationTest,
CanMakePayment_HasAuthenticator) {
test_controller()->SetHasAuthenticator(true);
NavigateTo("a.com", "/secure_payment_confirmation.html");
EXPECT_EQ("true",
content::EvalJs(GetActiveWebContents(),
"securePaymentConfirmationCanMakePayment()"));
EXPECT_EQ("true",
content::EvalJs(GetActiveWebContents(),
"securePaymentConfirmationCanMakePaymentTwice()"));
EXPECT_EQ("true", content::EvalJs(
GetActiveWebContents(),
"securePaymentConfirmationHasEnrolledInstrument()"));
}
// Intentionally do not enable the "SecurePaymentConfirmation" Blink runtime
// feature or the browser-side Finch flag.
class SecurePaymentConfirmationDisabledTest
: public PaymentRequestPlatformBrowserTestBase {
public:
SecurePaymentConfirmationDisabledTest() {
feature_list_.InitWithFeatures(
/*enabled_features=*/{},
/*disabled_features=*/{::features::kSecurePaymentConfirmation});
}
private:
base::test::ScopedFeatureList feature_list_;
};
IN_PROC_BROWSER_TEST_F(SecurePaymentConfirmationDisabledTest,
PaymentMethodNotSupported) {
test_controller()->SetHasAuthenticator(true);
NavigateTo("a.com", "/secure_payment_confirmation.html");
// EvalJs waits for JavaScript promise to resolve.
EXPECT_EQ(
"NotSupportedError: The payment method \"secure-payment-confirmation\" "
"is not supported.",
content::EvalJs(GetActiveWebContents(),
"getSecurePaymentConfirmationStatus()"));
}
IN_PROC_BROWSER_TEST_F(SecurePaymentConfirmationDisabledTest,
CannotMakePayment) {
test_controller()->SetHasAuthenticator(true);
NavigateTo("a.com", "/secure_payment_confirmation.html");
EXPECT_EQ("false",
content::EvalJs(GetActiveWebContents(),
"securePaymentConfirmationCanMakePayment()"));
EXPECT_EQ("false", content::EvalJs(
GetActiveWebContents(),
"securePaymentConfirmationHasEnrolledInstrument()"));
}
// Test that the feature can be disabled by the browser-side Finch flag, even if
// the Blink runtime feature is enabled.
class SecurePaymentConfirmationDisabledByFinchTest
: public PaymentRequestPlatformBrowserTestBase {
public:
SecurePaymentConfirmationDisabledByFinchTest() {
// The feature should get disabled by the feature state despite
// experimental web platform features being enabled.
feature_list_.InitAndDisableFeature(::features::kSecurePaymentConfirmation);
}
void SetUpCommandLine(base::CommandLine* command_line) override {
PaymentRequestPlatformBrowserTestBase::SetUpCommandLine(command_line);
command_line->AppendSwitch(
switches::kEnableExperimentalWebPlatformFeatures);
}
private:
base::test::ScopedFeatureList feature_list_;
};
IN_PROC_BROWSER_TEST_F(SecurePaymentConfirmationDisabledByFinchTest,
PaymentMethodNotSupported) {
test_controller()->SetHasAuthenticator(true);
NavigateTo("a.com", "/secure_payment_confirmation.html");
// EvalJs waits for JavaScript promise to resolve.
EXPECT_EQ(
"NotSupportedError: The payment method \"secure-payment-confirmation\" "
"is not supported.",
content::EvalJs(GetActiveWebContents(),
"getSecurePaymentConfirmationStatus()"));
}
IN_PROC_BROWSER_TEST_F(SecurePaymentConfirmationDisabledByFinchTest,
CannotMakePayment) {
test_controller()->SetHasAuthenticator(true);
NavigateTo("a.com", "/secure_payment_confirmation.html");
EXPECT_EQ("false",
content::EvalJs(GetActiveWebContents(),
"securePaymentConfirmationCanMakePayment()"));
EXPECT_EQ("false", content::EvalJs(
GetActiveWebContents(),
"securePaymentConfirmationHasEnrolledInstrument()"));
}
} // namespace
} // namespace payments