Skip to content

Commit

Permalink
[Lacros] Define available accounts for profiles
Browse files Browse the repository at this point in the history
This CL defines helper functions that (based on AccountProfileMapper)
return the list of accounts
 - available as primary accounts for a new profile, or
 - available as secondary accounts for a given profile.

To this end, AccountProfileMapper is extended to return the complete
mapping.

Bug: 1226076
Change-Id: Idf0e3723b0be49d3aaba7911fe87a75c617daf83
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3269214
Commit-Queue: Jan Krcal <jkrcal@chromium.org>
Reviewed-by: David Roger <droger@chromium.org>
Cr-Commit-Position: refs/heads/main@{#940299}
  • Loading branch information
Jan Krcal authored and Chromium LUCI CQ committed Nov 10, 2021
1 parent fb26faf commit 8ac0c8e
Show file tree
Hide file tree
Showing 7 changed files with 460 additions and 8 deletions.
78 changes: 78 additions & 0 deletions chrome/browser/lacros/account_manager/account_manager_util.cc
Expand Up @@ -4,10 +4,67 @@

#include "chrome/browser/lacros/account_manager/account_manager_util.h"

#include "base/containers/cxx20_erase.h"
#include "base/containers/flat_set.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profiles_state.h"
#include "chrome/browser/signin/signin_features.h"

namespace {

void GetAccountsAvailableAsPrimaryImpl(
ProfileAttributesStorage* storage,
AccountProfileMapper::ListAccountsCallback callback,
const std::map<base::FilePath, std::vector<account_manager::Account>>&
accounts_map) {
// Collect all primary syncing accounts.
std::vector<std::string> syncing_gaia_ids_temp;
for (ProfileAttributesEntry* entry : storage->GetAllProfilesAttributes()) {
// Skip if not syncing.
if (!entry->IsAuthenticated())
continue;
DCHECK(!entry->GetGAIAId().empty());
syncing_gaia_ids_temp.push_back(entry->GetGAIAId());
}
// Insert them all at once to avoid O(N^2) complexity.
base::flat_set<std::string> syncing_gaia_ids(
std::move(syncing_gaia_ids_temp));

std::vector<account_manager::Account> result;
// Copy all accounts into result, except for those syncing.
for (const auto& path_and_list_pair : accounts_map) {
const std::vector<account_manager::Account>& list =
path_and_list_pair.second;

std::copy_if(list.begin(), list.end(), std::back_inserter(result),
[&syncing_gaia_ids](const account_manager::Account& account) {
return !syncing_gaia_ids.contains(account.key.id());
});
}
std::move(callback).Run(result);
}

void GetAccountsAvailableAsSecondaryImpl(
const base::FilePath& profile_path,
AccountProfileMapper::ListAccountsCallback callback,
const std::map<base::FilePath, std::vector<account_manager::Account>>&
accounts_map) {
std::vector<account_manager::Account> result;

// Copy all accounts into result, except for those already in `profile_path`.
for (const auto& path_and_list_pair : accounts_map) {
if (profile_path == path_and_list_pair.first && !profile_path.empty())
continue;

const std::vector<account_manager::Account>& list =
path_and_list_pair.second;
result.insert(result.end(), list.begin(), list.end());
}
std::move(callback).Run(result);
}

} // namespace

bool IsAccountManagerAvailable(const Profile* profile) {
// Account Manager / Mirror is only enabled on Lacros's Main Profile for now.
if (!profile->IsMainProfile())
Expand All @@ -26,3 +83,24 @@ bool IsAccountManagerAvailable(const Profile* profile) {
// Available in all other cases.
return true;
}

void GetAccountsAvailableAsPrimary(
AccountProfileMapper* mapper,
ProfileAttributesStorage* storage,
AccountProfileMapper::ListAccountsCallback callback) {
DCHECK(mapper);
DCHECK(storage);
DCHECK(callback);
mapper->GetAccountsMap(base::BindOnce(&GetAccountsAvailableAsPrimaryImpl,
storage, std::move(callback)));
}

void GetAccountsAvailableAsSecondary(
AccountProfileMapper* mapper,
const base::FilePath& profile_path,
AccountProfileMapper::ListAccountsCallback callback) {
DCHECK(mapper);
DCHECK(callback);
mapper->GetAccountsMap(base::BindOnce(&GetAccountsAvailableAsSecondaryImpl,
profile_path, std::move(callback)));
}
21 changes: 21 additions & 0 deletions chrome/browser/lacros/account_manager/account_manager_util.h
Expand Up @@ -5,8 +5,29 @@
#ifndef CHROME_BROWSER_LACROS_ACCOUNT_MANAGER_ACCOUNT_MANAGER_UTIL_H_
#define CHROME_BROWSER_LACROS_ACCOUNT_MANAGER_ACCOUNT_MANAGER_UTIL_H_

#include "chrome/browser/lacros/account_manager/account_profile_mapper.h"

class Profile;
class ProfileAttributesStorage;

bool IsAccountManagerAvailable(const Profile* profile);

// Lists accounts that are available as primary accounts for a new profile. This
// passes back all accounts in the OS that are not used as syncing accounts in
// some other profile. The accounts are returned in an arbitrary order.
void GetAccountsAvailableAsPrimary(
AccountProfileMapper* mapper,
ProfileAttributesStorage* storage,
AccountProfileMapper::ListAccountsCallback callback);

// Lists accounts that are available as secondary accounts for profile with
// `profile_path`. This passes back all accounts in the OS, excluding the
// accounts that are already present in the given profile. The accounts are
// returned in an arbitrary order. If the profile with `profile_path` contains
// no accounts or does not exist, it returns all accounts in the OS.
void GetAccountsAvailableAsSecondary(
AccountProfileMapper* mapper,
const base::FilePath& profile_path,
AccountProfileMapper::ListAccountsCallback callback);

#endif // CHROME_BROWSER_LACROS_ACCOUNT_MANAGER_ACCOUNT_MANAGER_UTIL_H_
253 changes: 253 additions & 0 deletions chrome/browser/lacros/account_manager/account_manager_util_unittest.cc
@@ -0,0 +1,253 @@
// Copyright 2021 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 "chrome/browser/lacros/account_manager/account_manager_util.h"

#include <algorithm>
#include <vector>

#include "base/callback.h"
#include "base/callback_forward.h"
#include "base/containers/flat_map.h"
#include "base/files/file_path.h"
#include "base/test/mock_callback.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_attributes_entry.h"
#include "chrome/browser/profiles/profile_attributes_storage.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/signin/signin_features.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "components/account_manager_core/account.h"
#include "components/account_manager_core/account_manager_facade.h"
#include "components/account_manager_core/mock_account_manager_facade.h"
#include "content/public/test/browser_task_environment.h"
#include "google_apis/gaia/google_service_auth_error.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

using account_manager::Account;
using account_manager::AccountKey;
using account_manager::AccountManagerFacade;
using testing::Field;
using testing::UnorderedElementsAre;

namespace {

constexpr account_manager::AccountType kGaiaType =
account_manager::AccountType::kGaia;

// Map from profile path to a vector of GaiaIds.
using AccountMapping =
base::flat_map<base::FilePath, base::flat_set<std::string>>;

// Synthetizes a `Account` from a Gaia ID, with a dummy email.
Account AccountFromGaiaID(const std::string& gaia_id) {
AccountKey key(gaia_id, kGaiaType);
return {key, gaia_id + std::string("@gmail.com")};
}

} // namespace

class AccountManagerUtilTest : public testing::Test {
public:
AccountManagerUtilTest()
: scoped_feature_list_(kMultiProfileAccountConsistency),
testing_profile_manager_(TestingBrowserProcess::GetGlobal()) {
CHECK(testing_profile_manager_.SetUp());
main_path_ = GetProfilePath("Default");
ON_CALL(mock_facade_, GetPersistentErrorForAccount)
.WillByDefault(
[](const AccountKey&,
base::OnceCallback<void(const GoogleServiceAuthError&)>
callback) {
std::move(callback).Run(GoogleServiceAuthError::AuthErrorNone());
});
}

~AccountManagerUtilTest() override = default;

ProfileAttributesStorage* attributes_storage() {
return &testing_profile_manager_.profile_manager()
->GetProfileAttributesStorage();
}

account_manager::MockAccountManagerFacade* mock_facade() {
return &mock_facade_;
}

const base::FilePath& main_path() { return main_path_; }

base::FilePath GetProfilePath(const std::string& name) {
return testing_profile_manager_.profiles_dir().AppendASCII(name);
}

std::unique_ptr<AccountProfileMapper> CreateMapper(
const AccountMapping& accounts) {
// Mapper asks the facade for accounts upon construction.
std::vector<Account> accounts_in_facade;
for (const auto& path_accounts_pair : accounts) {
for (const std::string& id : path_accounts_pair.second) {
accounts_in_facade.push_back(AccountFromGaiaID(id));
}
}
ON_CALL(mock_facade_, GetAccounts(testing::_))
.WillByDefault(
[&accounts_in_facade](
base::OnceCallback<void(const std::vector<Account>&)>
callback) { std::move(callback).Run(accounts_in_facade); });
auto mapper = std::make_unique<AccountProfileMapper>(mock_facade(),
attributes_storage());
SetAccountsInStorage(accounts);
return mapper;
}

// Sets the accounts in `ProfileAttributesStorage`. `accounts_map` is a map
// from profile path to a vector of GaiaIds. One of the profiles must be the
// main profile.
void SetAccountsInStorage(const AccountMapping& accounts_map) {
// Clear all profiles.
testing_profile_manager_.DeleteAllTestingProfiles();
// Create new profiles.
for (const auto& path_accounts_pair : accounts_map) {
const base::FilePath path = path_accounts_pair.first;
if (path.empty())
continue; // Account is unassigned.
testing_profile_manager_.CreateTestingProfile(
path.BaseName().MaybeAsASCII());
}
// Import accounts from the map.
ProfileAttributesStorage* storage = attributes_storage();
for (const auto& path_accounts_pair : accounts_map) {
const base::FilePath path = path_accounts_pair.first;
if (path.empty())
continue; // Account is unassigned.
storage->GetProfileAttributesWithPath(path)->SetGaiaIds(
path_accounts_pair.second);
}
}

void SetPrimaryAccountForProfile(const base::FilePath& profile_path,
const std::string& primary_gaia_id) {
ProfileAttributesStorage* storage = attributes_storage();
ProfileAttributesEntry* entry =
storage->GetProfileAttributesWithPath(profile_path);
ASSERT_TRUE(entry);
entry->SetAuthInfo(primary_gaia_id, u"Test",
/*is_consented_primary_account=*/true);
}

private:
base::test::ScopedFeatureList scoped_feature_list_;
content::BrowserTaskEnvironment task_environment_;
testing::NiceMock<account_manager::MockAccountManagerFacade> mock_facade_;
TestingProfileManager testing_profile_manager_;
base::FilePath main_path_;
};

TEST_F(AccountManagerUtilTest, GetAccountsAvailableAsPrimary) {
base::FilePath other_path = GetProfilePath("Other");
std::unique_ptr<AccountProfileMapper> mapper =
CreateMapper({{main_path(), {"A"}}, {other_path, {"B", "C"}}});

base::MockRepeatingCallback<void(const std::vector<Account>&)> mock_callback;

// All the non-syncing accounts are returned.
EXPECT_CALL(mock_callback,
Run(testing::UnorderedElementsAre(
Field(&Account::key, AccountKey{"A", kGaiaType}),
Field(&Account::key, AccountKey{"B", kGaiaType}),
Field(&Account::key, AccountKey{"C", kGaiaType}))));
GetAccountsAvailableAsPrimary(mapper.get(), attributes_storage(),
mock_callback.Get());
testing::Mock::VerifyAndClearExpectations(&mock_callback);

// Check that only the non-syncing accounts are returned.
SetPrimaryAccountForProfile(other_path, "B");
EXPECT_CALL(mock_callback,
Run(testing::UnorderedElementsAre(
Field(&Account::key, AccountKey{"A", kGaiaType}),
Field(&Account::key, AccountKey{"C", kGaiaType}))));
GetAccountsAvailableAsPrimary(mapper.get(), attributes_storage(),
mock_callback.Get());
testing::Mock::VerifyAndClearExpectations(&mock_callback);

SetPrimaryAccountForProfile(main_path(), "A");
EXPECT_CALL(mock_callback, Run(testing::UnorderedElementsAre(Field(
&Account::key, AccountKey{"C", kGaiaType}))));
GetAccountsAvailableAsPrimary(mapper.get(), attributes_storage(),
mock_callback.Get());
}

TEST_F(AccountManagerUtilTest, GetAccountsAvailableAsSecondary) {
base::FilePath second_path = GetProfilePath("Second");
base::FilePath third_path = GetProfilePath("Third");
base::FilePath unassigned = base::FilePath();
std::unique_ptr<AccountProfileMapper> mapper =
CreateMapper({{main_path(), {"A"}},
{second_path, {"B", "C"}},
{third_path, {"D"}},
{unassigned, {"E"}}});

base::MockRepeatingCallback<void(const std::vector<Account>&)> mock_callback;

// Accounts from all other profiles are returned, incl. unassigned.
EXPECT_CALL(mock_callback,
Run(testing::UnorderedElementsAre(
Field(&Account::key, AccountKey{"B", kGaiaType}),
Field(&Account::key, AccountKey{"C", kGaiaType}),
Field(&Account::key, AccountKey{"D", kGaiaType}),
Field(&Account::key, AccountKey{"E", kGaiaType}))));
GetAccountsAvailableAsSecondary(mapper.get(), main_path(),
mock_callback.Get());
testing::Mock::VerifyAndClearExpectations(&mock_callback);

EXPECT_CALL(mock_callback,
Run(testing::UnorderedElementsAre(
Field(&Account::key, AccountKey{"A", kGaiaType}),
Field(&Account::key, AccountKey{"D", kGaiaType}),
Field(&Account::key, AccountKey{"E", kGaiaType}))));
GetAccountsAvailableAsSecondary(mapper.get(), second_path,
mock_callback.Get());
testing::Mock::VerifyAndClearExpectations(&mock_callback);

// Syncing status does not change anything here.
SetPrimaryAccountForProfile(main_path(), "A");
SetPrimaryAccountForProfile(second_path, "B");
EXPECT_CALL(mock_callback,
Run(testing::UnorderedElementsAre(
Field(&Account::key, AccountKey{"B", kGaiaType}),
Field(&Account::key, AccountKey{"C", kGaiaType}),
Field(&Account::key, AccountKey{"D", kGaiaType}),
Field(&Account::key, AccountKey{"E", kGaiaType}))));
GetAccountsAvailableAsSecondary(mapper.get(), main_path(),
mock_callback.Get());
testing::Mock::VerifyAndClearExpectations(&mock_callback);

EXPECT_CALL(mock_callback,
Run(testing::UnorderedElementsAre(
Field(&Account::key, AccountKey{"A", kGaiaType}),
Field(&Account::key, AccountKey{"D", kGaiaType}),
Field(&Account::key, AccountKey{"E", kGaiaType}))));
GetAccountsAvailableAsSecondary(mapper.get(), second_path,
mock_callback.Get());
testing::Mock::VerifyAndClearExpectations(&mock_callback);

// Non existing profile path or empty profile path returns all accounts.
base::FilePath non_existing_path = base::FilePath("Foo");
EXPECT_CALL(mock_callback,
Run(testing::UnorderedElementsAre(
Field(&Account::key, AccountKey{"A", kGaiaType}),
Field(&Account::key, AccountKey{"B", kGaiaType}),
Field(&Account::key, AccountKey{"C", kGaiaType}),
Field(&Account::key, AccountKey{"D", kGaiaType}),
Field(&Account::key, AccountKey{"E", kGaiaType}))))
.Times(2);
GetAccountsAvailableAsSecondary(mapper.get(), non_existing_path,
mock_callback.Get());
GetAccountsAvailableAsSecondary(mapper.get(), base::FilePath(),
mock_callback.Get());
testing::Mock::VerifyAndClearExpectations(&mock_callback);
}

0 comments on commit 8ac0c8e

Please sign in to comment.