| // Copyright 2023 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifndef CHROME_BROWSER_SYNC_TEST_INTEGRATION_WEBAUTHN_CREDENTIALS_HELPER_H_ |
| #define CHROME_BROWSER_SYNC_TEST_INTEGRATION_WEBAUTHN_CREDENTIALS_HELPER_H_ |
| |
| #include <vector> |
| |
| #include "base/scoped_observation.h" |
| #include "chrome/browser/sync/test/integration/fake_server_match_status_checker.h" |
| #include "chrome/browser/sync/test/integration/single_client_status_change_checker.h" |
| #include "chrome/browser/sync/test/integration/status_change_checker.h" |
| #include "components/webauthn/core/browser/passkey_model.h" |
| #include "components/webauthn/core/browser/passkey_model_change.h" |
| #include "components/webauthn/core/browser/passkey_sync_bridge.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace sync_pb { |
| class SyncEntity; |
| class WebauthnCredentialSpecifics; |
| } // namespace sync_pb |
| |
| namespace webauthn_credentials_helper { |
| |
| inline constexpr char kTestRpId[] = "example.com"; |
| |
| // Checker to wait until the WEBAUTHN_CREDENTIAL datatype becomes active. |
| class PasskeySyncActiveChecker : public SingleClientStatusChangeChecker { |
| public: |
| explicit PasskeySyncActiveChecker(syncer::SyncServiceImpl* service); |
| ~PasskeySyncActiveChecker() override; |
| |
| // StatusChangeChecker implementation. |
| bool IsExitConditionSatisfied(std::ostream* os) override; |
| }; |
| |
| class LocalPasskeysChangedChecker : public StatusChangeChecker, |
| public webauthn::PasskeyModel::Observer { |
| public: |
| explicit LocalPasskeysChangedChecker(int profile); |
| ~LocalPasskeysChangedChecker() override; |
| |
| // SingleClientStatusChangeChecker: |
| bool IsExitConditionSatisfied(std::ostream* os) override; |
| |
| // webauthn::PasskeyModel::Observer: |
| void OnPasskeysChanged( |
| const std::vector<webauthn::PasskeyModelChange>& changes) override; |
| void OnPasskeyModelShuttingDown() override; |
| void OnPasskeyModelIsReady(bool is_ready) override; |
| |
| private: |
| int profile_; |
| bool satisfied_ = false; |
| base::ScopedObservation<webauthn::PasskeyModel, |
| webauthn::PasskeyModel::Observer> |
| observation_{this}; |
| }; |
| |
| class LocalPasskeysMatchChecker : public StatusChangeChecker, |
| public webauthn::PasskeyModel::Observer { |
| public: |
| using Matcher = |
| testing::Matcher<std::vector<sync_pb::WebauthnCredentialSpecifics>>; |
| |
| LocalPasskeysMatchChecker(int profile, Matcher matcher); |
| ~LocalPasskeysMatchChecker() override; |
| |
| // SingleClientStatusChangeChecker: |
| bool IsExitConditionSatisfied(std::ostream* os) override; |
| |
| // webauthn::PasskeyModel::Observer: |
| void OnPasskeysChanged( |
| const std::vector<webauthn::PasskeyModelChange>& changes) override; |
| void OnPasskeyModelShuttingDown() override; |
| void OnPasskeyModelIsReady(bool is_ready) override; |
| |
| private: |
| const int profile_; |
| const Matcher matcher_; |
| base::ScopedObservation<webauthn::PasskeyModel, |
| webauthn::PasskeyModel::Observer> |
| observation_{this}; |
| }; |
| |
| class ServerPasskeysMatchChecker |
| : public fake_server::FakeServerMatchStatusChecker { |
| public: |
| using Matcher = testing::Matcher<std::vector<sync_pb::SyncEntity>>; |
| |
| explicit ServerPasskeysMatchChecker(Matcher matcher); |
| ~ServerPasskeysMatchChecker() override; |
| |
| // FakeServerMatchStatusChecker: |
| bool IsExitConditionSatisfied(std::ostream* os) override; |
| |
| private: |
| const Matcher matcher_; |
| }; |
| |
| // Observes PasskeyModel changes and waits until the specified list of |
| // {ChangeType, sync_id} pairs is observed. |
| class PasskeyChangeObservationChecker |
| : public StatusChangeChecker, |
| public webauthn::PasskeyModel::Observer { |
| public: |
| using ChangeList = std::vector< |
| std::pair<webauthn::PasskeyModelChange::ChangeType, std::string>>; |
| explicit PasskeyChangeObservationChecker(int profile, |
| ChangeList expected_changes); |
| ~PasskeyChangeObservationChecker() override; |
| |
| // SingleClientStatusChangeChecker: |
| bool IsExitConditionSatisfied(std::ostream* os) override; |
| |
| // webauthn::PasskeyModel::Observer: |
| void OnPasskeysChanged( |
| const std::vector<webauthn::PasskeyModelChange>& changes) override; |
| void OnPasskeyModelShuttingDown() override; |
| void OnPasskeyModelIsReady(bool is_ready) override; |
| |
| private: |
| const int profile_; |
| std::vector<webauthn::PasskeyModelChange> changes_observed_; |
| const ChangeList expected_changes_; |
| base::ScopedObservation<webauthn::PasskeyModel, |
| webauthn::PasskeyModel::Observer> |
| observation_{this}; |
| }; |
| |
| class MockPasskeyModelObserver : public webauthn::PasskeyModel::Observer { |
| public: |
| explicit MockPasskeyModelObserver(webauthn::PasskeyModel* model); |
| ~MockPasskeyModelObserver() override; |
| |
| MOCK_METHOD(void, |
| OnPasskeysChanged, |
| (const std::vector<webauthn::PasskeyModelChange>&), |
| (override)); |
| MOCK_METHOD(void, OnPasskeyModelShuttingDown, (), (override)); |
| MOCK_METHOD(void, OnPasskeyModelIsReady, (bool), (override)); |
| |
| private: |
| base::ScopedObservation<webauthn::PasskeyModel, |
| webauthn::PasskeyModel::Observer> |
| observation_{this}; |
| }; |
| |
| webauthn::PasskeySyncBridge& GetModel(int profile_idx); |
| |
| bool AwaitAllModelsMatch(); |
| |
| // Returns a new WebauthnCredentialSpecifics entity with a random sync ID, |
| // credential ID and user ID, and a fixed RP ID. |
| sync_pb::WebauthnCredentialSpecifics NewPasskey(); |
| |
| // Returns a new WebauthnCredentialSpecifics entity shadowing another one. Sync |
| // ID and credential ID are random, while user ID and RP ID will match the other |
| // credential. |
| sync_pb::WebauthnCredentialSpecifics NewShadowingPasskey( |
| const sync_pb::WebauthnCredentialSpecifics& shadowed); |
| |
| // Tests that a `sync_pb::SyncEntity` has WebauthnCredentialSpecifics with the |
| // given `sync_id`. Use with `ServerPasskeysMatchChecker`. |
| MATCHER_P(EntityHasSyncId, expected_sync_id, "") { |
| return arg.specifics().webauthn_credential().sync_id() == expected_sync_id; |
| } |
| |
| MATCHER_P(EntityHasUsername, expected_username, "") { |
| return arg.specifics().webauthn_credential().user_name() == expected_username; |
| } |
| |
| MATCHER_P(EntityHasDisplayName, expected_display_name, "") { |
| return arg.specifics().webauthn_credential().user_display_name() == |
| expected_display_name; |
| } |
| |
| MATCHER_P(EntityHasHidden, expected_hidden, "") { |
| return arg.specifics().webauthn_credential().hidden() == expected_hidden; |
| } |
| |
| MATCHER(EntityHasCurrentHiddenTime, "") { |
| base::Time hidden_time = base::Time::FromMillisecondsSinceUnixEpoch( |
| arg.specifics().webauthn_credential().hidden_time()); |
| return (base::Time::Now() - hidden_time) < base::Seconds(5); |
| } |
| |
| MATCHER_P(EntityHasLastUsedTime, expected_last_used_time, "") { |
| return arg.specifics() |
| .webauthn_credential() |
| .last_used_time_windows_epoch_micros() == expected_last_used_time; |
| } |
| |
| // Matches a `sync_pb::WebauthnCredentialSpecifics` against another field by |
| // field. |
| MATCHER_P(PasskeySpecificsEq, expected, "") { |
| return arg.sync_id() == expected.sync_id() && |
| arg.credential_id() == expected.credential_id() && |
| arg.rp_id() == expected.rp_id() && |
| std::ranges::equal(arg.newly_shadowed_credential_ids().begin(), |
| arg.newly_shadowed_credential_ids().end(), |
| expected.newly_shadowed_credential_ids().begin(), |
| expected.newly_shadowed_credential_ids().end()) && |
| arg.creation_time() == expected.creation_time() && |
| arg.user_name() == expected.user_name() && |
| arg.user_display_name() == expected.user_display_name() && |
| arg.third_party_payments_support() == |
| expected.third_party_payments_support() && |
| arg.last_used_time_windows_epoch_micros() == |
| expected.last_used_time_windows_epoch_micros() && |
| arg.key_version() == expected.key_version() && |
| arg.has_private_key() == expected.has_private_key() && |
| arg.private_key() == expected.private_key() && |
| arg.has_encrypted() == expected.has_encrypted() && |
| arg.encrypted() == expected.encrypted(); |
| } |
| |
| // Matches the `sync_id` of a `sync_pb::WebauthnCredentialSpecifics`. Use with |
| // `LocalPasskeysMatchChecker`. |
| MATCHER_P(PasskeyHasSyncId, expected_sync_id, "") { |
| return arg.sync_id() == expected_sync_id; |
| } |
| |
| // Matches the `rp_id` of a `sync_pb::WebauthnCredentialSpecifics`. Use with |
| // `LocalPasskeysMatchChecker`. |
| MATCHER_P(PasskeyHasRpId, expected_rp_id, "") { |
| return arg.rp_id() == expected_rp_id; |
| } |
| |
| // Matches the `user_id` of a `sync_pb::WebauthnCredentialSpecifics`. Use with |
| // `LocalPasskeysMatchChecker`. |
| MATCHER_P(PasskeyHasUserId, expected_user_id, "") { |
| return arg.user_id() == expected_user_id; |
| } |
| |
| // Matches the `display_name` of a `sync_pb::WebauthnCredentialSpecifics`. Use |
| // with `LocalPasskeysMatchChecker`. |
| MATCHER_P(PasskeyHasDisplayName, expected_display_name, "") { |
| return arg.user_display_name() == expected_display_name; |
| } |
| |
| } // namespace webauthn_credentials_helper |
| |
| #endif // CHROME_BROWSER_SYNC_TEST_INTEGRATION_WEBAUTHN_CREDENTIALS_HELPER_H_ |