blob: eb98e54980b9306329b1d41818b54ce7aba0d451 [file] [log] [blame]
// Copyright 2025 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/secure_payment_confirmation_credential_finder.h"
#include <memory>
#include <vector>
#include "base/functional/callback_helpers.h"
#include "base/memory/scoped_refptr.h"
#include "base/test/bind.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/mock_callback.h"
#include "base/test/scoped_feature_list.h"
#include "components/payments/content/mock_web_payments_web_data_service.h"
#include "components/payments/core/features.h"
#include "components/payments/core/secure_payment_confirmation_credential.h"
#include "components/webauthn/core/browser/mock_internal_authenticator.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/test_browser_context.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 "url/gurl.h"
#include "url/origin.h"
namespace payments {
namespace {
using ::base::test::RunOnceCallback;
using ::testing::_;
using ::testing::Eq;
using ::testing::Return;
const std::vector<std::vector<uint8_t>> kInputCredentialIds = {
{0x01, 0x02, 0x03},
{0x04, 0x05, 0x06}};
const std::vector<std::vector<uint8_t>> kAvailableCredentialIds = {
{0x04, 0x05, 0x06}};
const std::string kRelyingPartyId = "rp.example";
class SecurePaymentConfirmationCredentialFinderTest : public testing::Test {
protected:
SecurePaymentConfirmationCredentialFinderTest()
: web_contents_(web_contents_factory_.CreateWebContents(&context_)),
mock_authenticator_(
std::make_unique<webauthn::MockInternalAuthenticator>(
web_contents_)),
mock_service_(base::MakeRefCounted<MockWebPaymentsWebDataService>()) {}
// Required for test environment setup.
content::BrowserTaskEnvironment task_environment_;
content::TestBrowserContext context_;
content::TestWebContentsFactory web_contents_factory_;
raw_ptr<content::WebContents> web_contents_;
// Mocks of the underlying authenticator and user database service.
std::unique_ptr<webauthn::MockInternalAuthenticator> mock_authenticator_;
scoped_refptr<MockWebPaymentsWebDataService> mock_service_;
// The class under test.
SecurePaymentConfirmationCredentialFinder credential_finder_;
};
// Tests for the user profile database fetching path.
class SecurePaymentConfirmationCredentialFinderUserDatabaseTest
: public SecurePaymentConfirmationCredentialFinderTest {
protected:
SecurePaymentConfirmationCredentialFinderUserDatabaseTest() {
feature_list_.InitAndDisableFeature(
features::kSecurePaymentConfirmationUseCredentialStoreAPIs);
}
private:
base::test::ScopedFeatureList feature_list_;
};
// Tests that the credential finder uses the database service and that returned
// credentials are propagated to the callback.
TEST_F(SecurePaymentConfirmationCredentialFinderUserDatabaseTest,
ReturnsCredentialsOnSuccess) {
WebDataServiceBase::Handle handle = 1234;
EXPECT_CALL(*mock_service_, GetSecurePaymentConfirmationCredentials(
Eq(kInputCredentialIds), kRelyingPartyId, _))
.WillOnce(Return(handle));
std::optional<
std::vector<std::unique_ptr<SecurePaymentConfirmationCredential>>>
actual_credentials;
auto callback = base::BindLambdaForTesting(
[&actual_credentials](
std::optional<
std::vector<std::unique_ptr<SecurePaymentConfirmationCredential>>>
result) { actual_credentials = std::move(result); });
url::Origin caller_origin = url::Origin::Create(GURL("https://rp.example"));
credential_finder_.GetMatchingCredentials(
kInputCredentialIds, kRelyingPartyId, caller_origin,
/*authenticator=*/nullptr, mock_service_, std::move(callback));
// Simulate the web data service returning the credentials.
std::vector<std::unique_ptr<SecurePaymentConfirmationCredential>> credentials;
credentials.emplace_back(
std::make_unique<SecurePaymentConfirmationCredential>(
kAvailableCredentialIds[0], kRelyingPartyId,
/*user_id=*/std::vector<uint8_t>()));
auto result = std::make_unique<WDResult<
std::vector<std::unique_ptr<SecurePaymentConfirmationCredential>>>>(
SECURE_PAYMENT_CONFIRMATION, std::move(credentials));
credential_finder_.OnWebDataServiceRequestDone(handle, std::move(result));
// The credential finder should have received the credentials and sent them
// back to the callback.
ASSERT_TRUE(actual_credentials.has_value());
ASSERT_EQ(actual_credentials->size(), 1u);
EXPECT_EQ((*actual_credentials)[0]->credential_id,
kAvailableCredentialIds[0]);
EXPECT_EQ((*actual_credentials)[0]->relying_party_id, kRelyingPartyId);
}
// Tests that if the web data service returns a result that is not for SPC, we
// return a std::nullopt.
TEST_F(SecurePaymentConfirmationCredentialFinderUserDatabaseTest,
ReturnsNulloptOnFailure) {
WebDataServiceBase::Handle handle = 1234;
EXPECT_CALL(*mock_service_, GetSecurePaymentConfirmationCredentials(
Eq(kInputCredentialIds), kRelyingPartyId, _))
.WillOnce(Return(handle));
base::MockCallback<SecurePaymentConfirmationCredentialFinder::
SecurePaymentConfirmationCredentialFinderCallback>
mock_callback;
EXPECT_CALL(mock_callback, Run(Eq(std::nullopt)));
url::Origin caller_origin = url::Origin::Create(GURL("https://rp.example"));
credential_finder_.GetMatchingCredentials(
kInputCredentialIds, kRelyingPartyId, caller_origin,
/*authenticator=*/nullptr, mock_service_, mock_callback.Get());
// Simulate the web data service returning a non-SPC result; this should not
// generally happen, but the finder should handle it and return std::nullopt.
auto result = std::make_unique<WDResult<int>>(AUTOFILL_PROFILES_RESULT, 0);
credential_finder_.OnWebDataServiceRequestDone(handle, std::move(result));
}
// Tests for the credential store APIs path.
class SecurePaymentConfirmationCredentialFinderCredentialStoreApisTest
: public SecurePaymentConfirmationCredentialFinderTest {
protected:
SecurePaymentConfirmationCredentialFinderCredentialStoreApisTest() {
ON_CALL(*mock_authenticator_, IsGetMatchingCredentialIdsSupported())
.WillByDefault(Return(true));
}
private:
base::test::ScopedFeatureList feature_list_{
features::kSecurePaymentConfirmationUseCredentialStoreAPIs};
};
// Tests that the credential finder uses the credential store API path, and that
// returned credentials are propagated to the callback.
TEST_F(SecurePaymentConfirmationCredentialFinderCredentialStoreApisTest,
ReturnsCredentialsOnSuccess) {
std::optional<
std::vector<std::unique_ptr<SecurePaymentConfirmationCredential>>>
actual_credentials;
auto callback = base::BindLambdaForTesting(
[&actual_credentials](
std::optional<
std::vector<std::unique_ptr<SecurePaymentConfirmationCredential>>>
result) { actual_credentials = std::move(result); });
EXPECT_CALL(
*mock_authenticator_,
GetMatchingCredentialIds(kRelyingPartyId, Eq(kInputCredentialIds), _, _))
.WillOnce(RunOnceCallback<3>(kAvailableCredentialIds));
url::Origin caller_origin = url::Origin::Create(GURL("https://rp.example"));
credential_finder_.GetMatchingCredentials(
kInputCredentialIds, kRelyingPartyId, caller_origin,
mock_authenticator_.get(), mock_service_, std::move(callback));
// The credential finder should have received the credentials, converted them,
// and sent them back to the callback.
ASSERT_TRUE(actual_credentials.has_value());
ASSERT_EQ(actual_credentials->size(), 1u);
EXPECT_EQ((*actual_credentials)[0]->credential_id,
kAvailableCredentialIds[0]);
EXPECT_EQ((*actual_credentials)[0]->relying_party_id, kRelyingPartyId);
}
// Test that if the credential store APIs are not available, the finder
// returns a std::nullopt to the callback.
TEST_F(SecurePaymentConfirmationCredentialFinderCredentialStoreApisTest,
ReturnsNulloptWhenNotSupported) {
EXPECT_CALL(*mock_authenticator_, IsGetMatchingCredentialIdsSupported())
.WillOnce(Return(false));
EXPECT_CALL(*mock_authenticator_, GetMatchingCredentialIds).Times(0);
base::MockCallback<SecurePaymentConfirmationCredentialFinder::
SecurePaymentConfirmationCredentialFinderCallback>
mock_callback;
EXPECT_CALL(mock_callback, Run(Eq(std::nullopt)));
url::Origin caller_origin = url::Origin::Create(GURL("https://rp.example"));
credential_finder_.GetMatchingCredentials(
kInputCredentialIds, kRelyingPartyId, caller_origin,
mock_authenticator_.get(), mock_service_, mock_callback.Get());
}
TEST_F(
SecurePaymentConfirmationCredentialFinderCredentialStoreApisTest,
CorrectlyCalculatesThirdPartyPaymentRequirement_OriginDifferentFromRpId) {
url::Origin caller_origin = url::Origin::Create(GURL("https://site.example"));
// Because the RP ID is 'rp.example', and our origin is
// 'https://site.example', this is a third-party payment authentication.
EXPECT_CALL(*mock_authenticator_,
GetMatchingCredentialIds(
_, _, /*require_third_party_payment_bit=*/true, _));
credential_finder_.GetMatchingCredentials(
kInputCredentialIds, kRelyingPartyId, caller_origin,
mock_authenticator_.get(), mock_service_, base::DoNothing());
}
TEST_F(SecurePaymentConfirmationCredentialFinderCredentialStoreApisTest,
CorrectlyCalculatesThirdPartyPaymentRequirement_OriginSameAsRpId) {
url::Origin caller_origin = url::Origin::Create(GURL("https://rp.example"));
// Because the RP ID is 'rp.example', and our origin is 'https://rp.example'
// too, this is a first-party payment authentication.
EXPECT_CALL(*mock_authenticator_,
GetMatchingCredentialIds(
_, _, /*require_third_party_payment_bit=*/false, _));
credential_finder_.GetMatchingCredentials(
kInputCredentialIds, kRelyingPartyId, caller_origin,
mock_authenticator_.get(), mock_service_, base::DoNothing());
}
TEST_F(SecurePaymentConfirmationCredentialFinderCredentialStoreApisTest,
CorrectlyCalculatesThirdPartyPaymentRequirement_OriginSameDomainAsRpId) {
url::Origin caller_origin =
url::Origin::Create(GURL("https://subdomain.rp.example"));
// Because the RP ID is 'rp.example', and our origin is
// 'https://subdomain.rp.example' (a registrable-domain-match), this is a
// first-party payment authentication.
EXPECT_CALL(*mock_authenticator_,
GetMatchingCredentialIds(
_, _, /*require_third_party_payment_bit=*/false, _));
credential_finder_.GetMatchingCredentials(
kInputCredentialIds, kRelyingPartyId, caller_origin,
mock_authenticator_.get(), mock_service_, base::DoNothing());
}
} // namespace
} // namespace payments