| // Copyright 2022 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 <tuple> |
| |
| #include "build/build_config.h" |
| |
| // This class is only supported on Windows so far. |
| #if BUILDFLAG(IS_WIN) |
| |
| #include "base/run_loop.h" |
| #include "chrome/browser/webauthn/local_credential_management.h" |
| #include "chrome/test/base/testing_profile.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/sync_preferences/testing_pref_service_syncable.h" |
| #include "content/public/test/browser_task_environment.h" |
| #include "device/fido/test_callback_receiver.h" |
| #include "device/fido/win/fake_webauthn_api.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace { |
| |
| constexpr char kHasPlatformCredentialsPref[] = |
| "webauthn.has_platform_credentials"; |
| constexpr char kRpId[] = "example.com"; |
| constexpr uint8_t kCredId[] = {1, 2, 3, 4}; |
| |
| class LocalCredentialManagementTest : public testing::Test { |
| protected: |
| void SetUp() override { |
| LocalCredentialManagement::RegisterProfilePrefs( |
| profile_.GetTestingPrefService()->registry()); |
| api_.set_supports_silent_discovery(true); |
| } |
| |
| bool HasCredentials() { |
| device::test::TestCallbackReceiver<bool> callback; |
| local_cred_man_.HasCredentials(&profile_, callback.callback()); |
| |
| while (!callback.was_called()) { |
| base::RunLoop().RunUntilIdle(); |
| } |
| return std::get<0>(callback.TakeResult()); |
| } |
| |
| absl::optional<std::vector<device::DiscoverableCredentialMetadata>> |
| Enumerate() { |
| device::test::TestCallbackReceiver< |
| absl::optional<std::vector<device::DiscoverableCredentialMetadata>>> |
| callback; |
| local_cred_man_.Enumerate(&profile_, callback.callback()); |
| |
| while (!callback.was_called()) { |
| base::RunLoop().RunUntilIdle(); |
| } |
| return std::get<0>(callback.TakeResult()); |
| } |
| |
| // A `BrowserTaskEnvironment` needs to be in-scope in order to create a |
| // `TestingProfile`. |
| content::BrowserTaskEnvironment task_environment_; |
| TestingProfile profile_; |
| device::FakeWinWebAuthnApi api_; |
| LocalCredentialManagement local_cred_man_ = {&api_}; |
| }; |
| |
| TEST_F(LocalCredentialManagementTest, NoSupport) { |
| // With no support, `HasCredentials` should return false and `Enumerate` |
| // should return no value. |
| api_.set_supports_silent_discovery(false); |
| |
| EXPECT_FALSE(HasCredentials()); |
| EXPECT_FALSE(profile_.GetPrefs()->GetBoolean(kHasPlatformCredentialsPref)); |
| |
| EXPECT_FALSE(Enumerate().has_value()); |
| } |
| |
| TEST_F(LocalCredentialManagementTest, NoCredentials) { |
| // With support but no credentials `HasCredentials` should still return false, |
| // but `Enumerate` should return an empty list. |
| EXPECT_FALSE(HasCredentials()); |
| EXPECT_FALSE(profile_.GetPrefs()->GetBoolean(kHasPlatformCredentialsPref)); |
| |
| const absl::optional<std::vector<device::DiscoverableCredentialMetadata>> |
| result = Enumerate(); |
| ASSERT_TRUE(result.has_value()); |
| EXPECT_TRUE(result->empty()); |
| } |
| |
| TEST_F(LocalCredentialManagementTest, OneCredential) { |
| // With a credential injected, `HasCredentials` should return true and should |
| // cache that in the profile. Enumerate should return that credential. |
| api_.InjectDiscoverableCredential( |
| kCredId, {kRpId, absl::nullopt, absl::nullopt}, |
| {{1, 2, 3, 4}, absl::nullopt, absl::nullopt, absl::nullopt}); |
| |
| EXPECT_TRUE(HasCredentials()); |
| EXPECT_TRUE(profile_.GetPrefs()->GetBoolean(kHasPlatformCredentialsPref)); |
| |
| const absl::optional<std::vector<device::DiscoverableCredentialMetadata>> |
| result = Enumerate(); |
| ASSERT_TRUE(result.has_value()); |
| ASSERT_EQ(result->size(), 1u); |
| EXPECT_EQ(result->at(0).rp_id, kRpId); |
| } |
| |
| TEST_F(LocalCredentialManagementTest, CacheIsUsed) { |
| // If the cache is set to true then HasCredentials will return true even |
| // though there aren't any credentials. |
| profile_.GetPrefs()->SetBoolean(kHasPlatformCredentialsPref, true); |
| |
| EXPECT_TRUE(HasCredentials()); |
| EXPECT_TRUE(profile_.GetPrefs()->GetBoolean(kHasPlatformCredentialsPref)); |
| |
| const absl::optional<std::vector<device::DiscoverableCredentialMetadata>> |
| result = Enumerate(); |
| ASSERT_TRUE(result.has_value()); |
| EXPECT_TRUE(result->empty()); |
| |
| // Calling `Enumerate` should have updated the cache to reflect the fact that |
| // there aren't any credentials. |
| EXPECT_FALSE(profile_.GetPrefs()->GetBoolean(kHasPlatformCredentialsPref)); |
| } |
| |
| TEST_F(LocalCredentialManagementTest, Sorting) { |
| constexpr uint8_t kCredId1[] = {1}; |
| constexpr uint8_t kCredId2[] = {2}; |
| constexpr uint8_t kCredId3[] = {3}; |
| constexpr uint8_t kCredId4[] = {4}; |
| constexpr uint8_t kCredId5[] = {5}; |
| constexpr uint8_t kCredId6[] = {6}; |
| constexpr uint8_t kCredId7[] = {7}; |
| |
| api_.InjectDiscoverableCredential( |
| kCredId7, {"zzz.de", absl::nullopt, absl::nullopt}, |
| {{1, 2, 3, 4}, "username", absl::nullopt, absl::nullopt}); |
| api_.InjectDiscoverableCredential( |
| kCredId2, {"zzz.de", absl::nullopt, absl::nullopt}, |
| {{1, 2, 3, 4}, "username", absl::nullopt, absl::nullopt}); |
| api_.InjectDiscoverableCredential( |
| kCredId3, {"www.example.co.uk", absl::nullopt, absl::nullopt}, |
| {{1, 2, 3, 4}, "user1", absl::nullopt, absl::nullopt}); |
| api_.InjectDiscoverableCredential( |
| kCredId4, {"foo.www.example.co.uk", absl::nullopt, absl::nullopt}, |
| {{1, 2, 3, 4}, "user1", absl::nullopt, absl::nullopt}); |
| api_.InjectDiscoverableCredential( |
| kCredId5, {"foo.example.co.uk", absl::nullopt, absl::nullopt}, |
| {{1, 2, 3, 4}, "user1", absl::nullopt, absl::nullopt}); |
| api_.InjectDiscoverableCredential( |
| kCredId6, {"aardvark.us", absl::nullopt, absl::nullopt}, |
| {{1, 2, 3, 4}, "username", absl::nullopt, absl::nullopt}); |
| api_.InjectDiscoverableCredential( |
| kCredId1, {"example.co.uk", absl::nullopt, absl::nullopt}, |
| {{1, 2, 3, 4}, "user2", absl::nullopt, absl::nullopt}); |
| |
| const std::vector<device::DiscoverableCredentialMetadata> result = |
| Enumerate().value(); |
| ASSERT_EQ(result.size(), 7u); |
| EXPECT_EQ(result[0].rp_id, "aardvark.us"); |
| // Despite starting with other characters, all entries for example.co.uk |
| // should be sorted together. |
| EXPECT_EQ(result[1].rp_id, "example.co.uk"); |
| EXPECT_EQ(result[2].rp_id, "foo.example.co.uk"); |
| EXPECT_EQ(result[3].rp_id, "www.example.co.uk"); |
| EXPECT_EQ(result[4].rp_id, "foo.www.example.co.uk"); |
| EXPECT_EQ(result[5].rp_id, "zzz.de"); |
| EXPECT_EQ(result[6].rp_id, "zzz.de"); |
| // The two zzz.de entries have the same RP ID and user.name, thus they should |
| // be sorted by credential ID. |
| EXPECT_EQ(result[6].cred_id[0], 7); |
| } |
| |
| } // namespace |
| |
| #endif |