blob: dc770865d4e69dad9b28cb76b1f8b9c98f998410 [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 kChallengeBase64[] = "aaaa";
static constexpr char kCredentialIdBase64[] = "cccc";
class MockAuthenticator : public webauthn::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_METHOD1(SetPaymentOptions, void(blink::mojom::PaymentOptionsPtr));
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 webauthn::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(kChallengeBase64, &challenge_bytes_));
ASSERT_TRUE(base::Base64Decode(kCredentialIdBase64, &credential_id_bytes_));
}
mojom::SecurePaymentConfirmationRequestPtr MakeRequest() {
auto request = mojom::SecurePaymentConfirmationRequest::New();
request->challenge =
std::vector<uint8_t>(challenge_bytes_.begin(), challenge_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);
EXPECT_EQ(stringified_details, "{}");
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,
"The operation either timed out or was not allowed. See: "
"https://www.w3.org/TR/webauthn-2/"
"#sctn-privacy-considerations-client.");
on_instrument_details_error_called_ = true;
}
std::u16string label_;
std::unique_ptr<PaymentRequestSpec> spec_;
std::string challenge_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));
std::vector<uint8_t> expected_bytes =
std::vector<uint8_t>(challenge_bytes_.begin(), challenge_bytes_.end());
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