// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromeos/ash/components/browser_context_helper/browser_context_helper.h"
#include <string_view>
#include "ash/constants/ash_features.h"
#include "base/check.h"
#include "base/check_is_test.h"
#include "base/logging.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "chromeos/ash/components/browser_context_helper/annotated_account_id.h"
#include "chromeos/ash/components/browser_context_helper/browser_context_types.h"
#include "components/user_manager/user.h"
#include "components/user_manager/user_manager.h"
#include "content/public/browser/browser_context.h"
namespace ash {
namespace {
// Chrome OS profile directories have custom prefix.
// Profile path format: [user_data_dir]/u-[$hash]
// Ex.: /home/chronos/u-0123456789
constexpr char kBrowserContextDirPrefix[] = "u-";
BrowserContextHelper* g_instance = nullptr;
bool ShouldAddBrowserContextDirPrefix(std::string_view user_id_hash) {
// Do not add profile dir prefix for legacy profile dir and test
// user profile. The reason of not adding prefix for test user profile
// is to keep the promise that TestingProfile::kTestUserProfileDir and
// chrome::kTestUserProfileDir are always in sync. Otherwise,
// TestingProfile::kTestUserProfileDir needs to be dynamically calculated
// based on whether multi profile is enabled or not.
return user_id_hash != BrowserContextHelper::kLegacyBrowserContextDirName &&
user_id_hash != BrowserContextHelper::kTestUserBrowserContextDirName;
}
} // namespace
// static
const char BrowserContextHelper::kLegacyBrowserContextDirName[] = "user";
// static
const char BrowserContextHelper::kTestUserBrowserContextDirName[] = "test-user";
BrowserContextHelper::BrowserContextHelper(std::unique_ptr<Delegate> delegate)
: delegate_(std::move(delegate)) {
DCHECK(!g_instance);
g_instance = this;
}
BrowserContextHelper::~BrowserContextHelper() {
DCHECK_EQ(g_instance, this);
g_instance = nullptr;
}
// static
BrowserContextHelper* BrowserContextHelper::Get() {
DCHECK(g_instance);
return g_instance;
}
// static
std::string BrowserContextHelper::GetUserIdHashFromBrowserContext(
content::BrowserContext* browser_context) {
if (!browser_context) {
return std::string();
}
const std::string dir = browser_context->GetPath().BaseName().value();
// Don't strip prefix if the dir is not supposed to be prefixed.
if (!ShouldAddBrowserContextDirPrefix(dir)) {
return dir;
}
if (!base::StartsWith(dir, kBrowserContextDirPrefix,
base::CompareCase::SENSITIVE)) {
// This happens when creating a TestingProfile in browser_tests.
return std::string();
}
return dir.substr(std::string_view(kBrowserContextDirPrefix).length());
}
content::BrowserContext* BrowserContextHelper::GetBrowserContextByAccountId(
const AccountId& account_id) {
const auto* user = user_manager::UserManager::Get()->FindUser(account_id);
if (!user) {
LOG(WARNING) << "Unable to retrieve user for account_id: " << account_id;
return nullptr;
}
return GetBrowserContextByUser(user);
}
content::BrowserContext* BrowserContextHelper::GetBrowserContextByUser(
const user_manager::User* user) {
DCHECK(user);
if (!user->is_profile_created()) {
return nullptr;
}
content::BrowserContext* browser_context =
UseAnnotatedAccountId()
? delegate_->GetBrowserContextByAccountId(user->GetAccountId())
: delegate_->GetBrowserContextByPath(
GetBrowserContextPathByUserIdHash(user->username_hash()));
// GetBrowserContextByPath() returns a new instance of ProfileImpl,
// but actually its off-the-record profile should be used.
// TODO(hidehiko): Replace this by user->GetType() == kGuest.
if (user_manager::UserManager::Get()->IsLoggedInAsGuest()) {
browser_context =
delegate_->GetOrCreatePrimaryOTRBrowserContext(browser_context);
}
return browser_context;
}
const user_manager::User* BrowserContextHelper::GetUserByBrowserContext(
content::BrowserContext* browser_context) {
if (!IsUserBrowserContext(browser_context)) {
return nullptr;
}
// Use the original browser context, if it is off-the-record one.
browser_context = delegate_->GetOriginalBrowserContext(browser_context);
const AccountId* account_id = AnnotatedAccountId::Get(browser_context);
if (!account_id) {
// TODO(crbug.com/40225390): fix tests to annotate AccountId properly.
LOG(ERROR) << "AccountId is not annotated";
CHECK_IS_TEST();
}
if (UseAnnotatedAccountId()) {
CHECK(account_id);
return user_manager::UserManager::Get()->FindUser(*account_id);
}
const std::string hash = GetUserIdHashFromBrowserContext(browser_context);
// Finds the matching user in logged-in user list since only a logged-in
// user would have a profile.
// TODO(crbug.com/40225390): find user by AccountId, once it is annotated
// to Profile in tests.
auto* user_manager = user_manager::UserManager::Get();
for (const user_manager::User* user : user_manager->GetLoggedInUsers()) {
if (user->username_hash() == hash) {
if (!account_id || *account_id != user->GetAccountId()) {
// TODO(crbug.com/40225390): fix tests to annotate AccountId properly.
LOG(ERROR) << "AccountId is mismatched";
CHECK_IS_TEST();
}
return user;
}
}
return nullptr;
}
// static
std::string BrowserContextHelper::GetUserBrowserContextDirName(
std::string_view user_id_hash) {
CHECK(!user_id_hash.empty());
return ShouldAddBrowserContextDirPrefix(user_id_hash)
? base::StrCat({kBrowserContextDirPrefix, user_id_hash})
: std::string(user_id_hash);
}
base::FilePath BrowserContextHelper::GetBrowserContextPathByUserIdHash(
std::string_view user_id_hash) {
// Fails if Chrome runs with "--login-manager", but not "--login-profile", and
// needs to restart. This might happen if you test Chrome OS on Linux and
// you start a guest session or Chrome crashes. Be sure to add
// "[email protected]"
// to the command line flags.
DCHECK(!user_id_hash.empty())
<< "user_id_hash is empty, probably need to add "
"[email protected] to command line parameters";
return delegate_->GetUserDataDir()->Append(
GetUserBrowserContextDirName(user_id_hash));
}
base::FilePath BrowserContextHelper::GetSigninBrowserContextPath() const {
return delegate_->GetUserDataDir()->Append(kSigninBrowserContextBaseName);
}
content::BrowserContext* BrowserContextHelper::GetSigninBrowserContext() {
content::BrowserContext* browser_context =
delegate_->GetBrowserContextByPath(GetSigninBrowserContextPath());
if (!browser_context) {
return nullptr;
}
return delegate_->GetOrCreatePrimaryOTRBrowserContext(browser_context);
}
content::BrowserContext*
BrowserContextHelper::DeprecatedGetOrCreateSigninBrowserContext() {
content::BrowserContext* browser_context =
delegate_->DeprecatedGetBrowserContext(GetSigninBrowserContextPath());
if (!browser_context) {
return nullptr;
}
return delegate_->GetOrCreatePrimaryOTRBrowserContext(browser_context);
}
base::FilePath BrowserContextHelper::GetLockScreenAppBrowserContextPath()
const {
return delegate_->GetUserDataDir()->Append(
kLockScreenAppBrowserContextBaseName);
}
base::FilePath BrowserContextHelper::GetLockScreenBrowserContextPath() const {
return delegate_->GetUserDataDir()->Append(kLockScreenBrowserContextBaseName);
}
content::BrowserContext* BrowserContextHelper::GetLockScreenBrowserContext() {
content::BrowserContext* browser_context =
delegate_->GetBrowserContextByPath(GetLockScreenBrowserContextPath());
if (!browser_context) {
return nullptr;
}
return delegate_->GetOrCreatePrimaryOTRBrowserContext(browser_context);
}
base::FilePath BrowserContextHelper::GetShimlessRmaAppBrowserContextPath()
const {
return delegate_->GetUserDataDir()->Append(
kShimlessRmaAppBrowserContextBaseName);
}
bool BrowserContextHelper::UseAnnotatedAccountId() {
return base::FeatureList::IsEnabled(ash::features::kUseAnnotatedAccountId) ||
use_annotated_account_id_for_testing_;
}
} // namespace ash