blob: 24f5206feddbc186248370947b2e62ebcf385d75 [file] [log] [blame]
// 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_