blob: 6e7a9757a39fdbda675b105612519682ab564237 [file] [log] [blame]
// 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