blob: d75687881f4ea9a744073a46e2da9b1051d488f0 [file] [log] [blame]
// Copyright 2020 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 "components/payments/content/secure_payment_confirmation_app.h"
#include <memory>
#include <utility>
#include "base/base64.h"
#include "base/strings/string_piece.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/scoped_feature_list.h"
#include "components/payments/content/payment_request_spec.h"
#include "components/payments/core/method_strings.h"
#include "components/webauthn/core/browser/internal_authenticator.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_features.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/test_browser_context.h"
#include "content/public/test/test_utils.h"
#include "content/public/test/test_web_contents_factory.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/mojom/payments/payment_request.mojom.h"
#include "third_party/blink/public/mojom/webauthn/authenticator.mojom.h"
#include "url/origin.h"
namespace payments {
namespace {
using ::testing::_;
using ::testing::Eq;
static constexpr char kNetworkDataBase64[] = "aaaa";
static constexpr char kCredentialIdBase64[] = "cccc";
class MockAuthenticator : public autofill::InternalAuthenticator {
public:
explicit MockAuthenticator(bool should_succeed)
: web_contents_(web_contents_factory_.CreateWebContents(&context_)),
should_succeed_(should_succeed) {}
~MockAuthenticator() override = default;
MOCK_METHOD1(SetEffectiveOrigin, void(const url::Origin&));
MOCK_METHOD2(
MakeCredential,
void(blink::mojom::PublicKeyCredentialCreationOptionsPtr options,
blink::mojom::Authenticator::MakeCredentialCallback callback));
MOCK_METHOD1(IsUserVerifyingPlatformAuthenticatorAvailable,
void(blink::mojom::Authenticator::
IsUserVerifyingPlatformAuthenticatorAvailableCallback));
MOCK_METHOD0(Cancel, void());
MOCK_METHOD1(VerifyChallenge, void(const std::vector<uint8_t>&));
content::RenderFrameHost* GetRenderFrameHost() override {
return web_contents_->GetMainFrame();
}
// Implements an autofill::InternalAuthenticator method to delegate fields of
// |options| to gmock methods for easier verification.
void GetAssertion(
blink::mojom::PublicKeyCredentialRequestOptionsPtr options,
blink::mojom::Authenticator::GetAssertionCallback callback) override {
VerifyChallenge(options->challenge);
std::move(callback).Run(
should_succeed_ ? blink::mojom::AuthenticatorStatus::SUCCESS
: blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR,
blink::mojom::GetAssertionAuthenticatorResponse::New());
}
content::WebContents* web_contents() { return web_contents_; }
private:
content::BrowserTaskEnvironment task_environment_;
content::TestBrowserContext context_;
content::TestWebContentsFactory web_contents_factory_;
content::WebContents* web_contents_; // Owned by `web_contents_factory_`.
bool should_succeed_;
};
class SecurePaymentConfirmationAppTest : public testing::Test,
public PaymentApp::Delegate {
protected:
SecurePaymentConfirmationAppTest() : label_(u"test instrument") {
mojom::PaymentDetailsPtr details = mojom::PaymentDetails::New();
details->total = mojom::PaymentItem::New();
details->total->amount = mojom::PaymentCurrencyAmount::New();
details->total->amount->currency = "USD";
details->total->amount->value = "1.25";
std::vector<mojom::PaymentMethodDataPtr> method_data;
spec_ = std::make_unique<PaymentRequestSpec>(
mojom::PaymentOptions::New(), std::move(details),
std::move(method_data), /*observer=*/nullptr, /*app_locale=*/"en-US");
}
void SetUp() override {
ASSERT_TRUE(base::Base64Decode(kNetworkDataBase64, &network_data_bytes_));
ASSERT_TRUE(base::Base64Decode(kCredentialIdBase64, &credential_id_bytes_));
}
mojom::SecurePaymentConfirmationRequestPtr MakeRequest() {
auto request = mojom::SecurePaymentConfirmationRequest::New();
request->challenge = std::vector<uint8_t>(network_data_bytes_.begin(),
network_data_bytes_.end());
return request;
}
// PaymentApp::Delegate:
void OnInstrumentDetailsReady(const std::string& method_name,
const std::string& stringified_details,
const PayerData& payer_data) override {
EXPECT_EQ(method_name, methods::kSecurePaymentConfirmation);
if (base::FeatureList::IsEnabled(
features::kSecurePaymentConfirmationAPIV2)) {
EXPECT_EQ(stringified_details, "{}");
} else {
EXPECT_EQ(
stringified_details,
"{\"appid_extension\":false,\"challenge\":\"{\\\"merchantData\\\":{"
"\\\"merchantOrigin\\\":\\\"https://"
"merchant.example\\\",\\\"total\\\":{\\\"currency\\\":\\\"USD\\\","
"\\\"value\\\":\\\"1.25\\\"}},\\\"networkData\\\":\\\"aaaa\\\"}\","
"\"echo_appid_extension\":false,\"echo_prf\":false,\"info\":{},\"prf_"
"not_evaluated\":false,\"prf_results\":{},\"signature\":\"\"}");
}
EXPECT_EQ(payer_data.payer_name, "");
EXPECT_EQ(payer_data.payer_email, "");
EXPECT_EQ(payer_data.payer_phone, "");
EXPECT_TRUE(payer_data.shipping_address.is_null());
EXPECT_EQ(payer_data.selected_shipping_option_id, "");
on_instrument_details_ready_called_ = true;
}
void OnInstrumentDetailsError(const std::string& error_message) override {
EXPECT_EQ(error_message, "Authenticator returned NOT_ALLOWED_ERROR.");
on_instrument_details_error_called_ = true;
}
std::u16string label_;
std::unique_ptr<PaymentRequestSpec> spec_;
std::string network_data_bytes_;
std::string credential_id_bytes_;
bool on_instrument_details_ready_called_ = false;
bool on_instrument_details_error_called_ = false;
base::WeakPtrFactory<SecurePaymentConfirmationAppTest> weak_ptr_factory_{
this};
};
TEST_F(SecurePaymentConfirmationAppTest, Smoke) {
std::vector<uint8_t> credential_id(credential_id_bytes_.begin(),
credential_id_bytes_.end());
auto authenticator =
std::make_unique<MockAuthenticator>(/*should_succeed=*/true);
MockAuthenticator* mock_authenticator = authenticator.get();
content::WebContents* web_contents = authenticator->web_contents();
SecurePaymentConfirmationApp app(
web_contents, "effective_rp.example",
/*Icon=*/std::make_unique<SkBitmap>(), label_, std::move(credential_id),
url::Origin::Create(GURL("https://merchant.example")), spec_->AsWeakPtr(),
MakeRequest(), std::move(authenticator));
EXPECT_CALL(*mock_authenticator, SetEffectiveOrigin(Eq(url::Origin::Create(
GURL("https://effective_rp.example")))));
std::vector<uint8_t> expected_bytes;
if (base::FeatureList::IsEnabled(features::kSecurePaymentConfirmationAPIV2)) {
expected_bytes = std::vector<uint8_t>(network_data_bytes_.begin(),
network_data_bytes_.end());
} else {
// This is the SHA-256 hash of the serialized JSON string:
// {"merchantData":{"merchantOrigin":"https://merchant.example","total":
// {"currency":"USD","value":"1.25"}},"networkData":"aaaa"}
//
// To update the test expectation, open
// components/test/data/payments/secure_payment_confirmation_debug.html in
// a browser and follow the instructions.
expected_bytes = {240, 123, 37, 51, 16, 34, 244, 220, 166, 179, 139,
85, 229, 152, 242, 133, 88, 44, 222, 133, 49, 97,
146, 20, 207, 119, 43, 142, 171, 239, 125, 250};
}
EXPECT_CALL(*mock_authenticator, VerifyChallenge(Eq(expected_bytes)));
app.InvokePaymentApp(/*delegate=*/weak_ptr_factory_.GetWeakPtr());
EXPECT_TRUE(on_instrument_details_ready_called_);
EXPECT_FALSE(on_instrument_details_error_called_);
}
// Test that OnInstrumentDetailsError is called when the authenticator returns
// an error.
TEST_F(SecurePaymentConfirmationAppTest, OnInstrumentDetailsError) {
std::vector<uint8_t> credential_id(credential_id_bytes_.begin(),
credential_id_bytes_.end());
auto authenticator =
std::make_unique<MockAuthenticator>(/*should_succeed=*/false);
content::WebContents* web_contents = authenticator->web_contents();
SecurePaymentConfirmationApp app(
web_contents, "effective_rp.example",
/*Icon=*/std::make_unique<SkBitmap>(), label_, std::move(credential_id),
url::Origin::Create(GURL("https://merchant.example")), spec_->AsWeakPtr(),
MakeRequest(), std::move(authenticator));
app.InvokePaymentApp(/*delegate=*/weak_ptr_factory_.GetWeakPtr());
EXPECT_FALSE(on_instrument_details_ready_called_);
EXPECT_TRUE(on_instrument_details_error_called_);
}
} // namespace
} // namespace payments