#include "remoting/host/webauthn/remote_webauthn_extension_notifier.h"
#include <optional>
#include <vector>
#include "base/base_paths.h"
#include "base/containers/span.h"
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/no_destructor.h"
#include "base/notimplemented.h"
#include "base/path_service.h"
#include "base/sequence_checker.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "build/build_config.h"
#if BUILDFLAG(IS_LINUX)
#include "base/environment.h"
#include "base/nix/xdg_util.h"
#include "base/strings/string_util.h"
#elif BUILDFLAG(IS_WIN)
#include <windows.h>
#include <knownfolders.h>
#include <shlobj.h>
#include <wtsapi32.h>
#include "base/win/scoped_co_mem.h"
#include "base/win/scoped_handle.h"
#endif
namespace remoting {
namespace {
static constexpr char kExtensionWakeupFileContent[] = "";
std::vector<base::FilePath> GetRemoteStateChangeDirPaths() {
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
constexpr base::FilePath::CharType kStateChangeDirName[] =
FILE_PATH_LITERAL("WebAuthenticationProxyRemoteSessionStateChange");
#endif
std::vector<base::FilePath> dirs;
#if BUILDFLAG(IS_LINUX)
auto env = base::Environment::Create();
base::FilePath base_path;
std::optional<std::string> chrome_config_home_str =
env->GetVar("CHROME_CONFIG_HOME");
if (chrome_config_home_str.has_value() &&
base::IsStringUTF8(chrome_config_home_str.value())) {
base_path = base::FilePath::FromUTF8Unsafe(chrome_config_home_str.value());
} else {
base_path = base::nix::GetXDGDirectory(
env.get(), base::nix::kXdgConfigHomeEnvVar, base::nix::kDotConfigDir);
}
dirs.push_back(base_path.Append("google-chrome").Append(kStateChangeDirName));
dirs.push_back(
base_path.Append("google-chrome-beta").Append(kStateChangeDirName));
dirs.push_back(
base_path.Append("google-chrome-canary").Append(kStateChangeDirName));
dirs.push_back(
base_path.Append("google-chrome-unstable").Append(kStateChangeDirName));
dirs.push_back(base_path.Append("chromium").Append(kStateChangeDirName));
#elif BUILDFLAG(IS_WIN)
constexpr base::FilePath::CharType kUserDataDirName[] =
FILE_PATH_LITERAL("User Data");
HANDLE user_token = nullptr;
if (!WTSQueryUserToken(WTS_CURRENT_SESSION, &user_token)) {
PLOG(ERROR) << "Failed to get current user token";
return dirs;
}
base::win::ScopedHandle scoped_user_token(user_token);
base::win::ScopedCoMem<wchar_t> local_app_data_path_buf;
if (!SUCCEEDED(SHGetKnownFolderPath(FOLDERID_LocalAppData, 0,
scoped_user_token.get(),
&local_app_data_path_buf))) {
PLOG(ERROR) << "SHGetKnownFolderPath failed";
return dirs;
}
base::FilePath base_path = base::FilePath(local_app_data_path_buf.get());
base::FilePath base_path_google = base_path.Append(L"Google");
dirs.push_back(base_path_google.Append(L"Chrome")
.Append(kUserDataDirName)
.Append(kStateChangeDirName));
dirs.push_back(base_path_google.Append(L"Chrome Beta")
.Append(kUserDataDirName)
.Append(kStateChangeDirName));
dirs.push_back(base_path_google.Append(L"Chrome Dev")
.Append(kUserDataDirName)
.Append(kStateChangeDirName));
dirs.push_back(base_path_google.Append(L"Chrome SxS")
.Append(kUserDataDirName)
.Append(kStateChangeDirName));
dirs.push_back(base_path.Append(L"Chromium")
.Append(kUserDataDirName)
.Append(kStateChangeDirName));
#elif BUILDFLAG(IS_MAC)
base::FilePath base_path;
if (!base::PathService::Get(base::DIR_APP_DATA, &base_path)) {
LOG(ERROR) << "Failed to get app data dir";
return dirs;
}
base::FilePath base_path_google = base_path.Append("Google");
dirs.push_back(base_path_google.Append("Chrome").Append(kStateChangeDirName));
dirs.push_back(
base_path_google.Append("Chrome Beta").Append(kStateChangeDirName));
dirs.push_back(
base_path_google.Append("Chrome Canary").Append(kStateChangeDirName));
dirs.push_back(base_path.Append("Chromium").Append(kStateChangeDirName));
#else
NOTIMPLEMENTED();
#endif
return dirs;
}
}
class RemoteWebAuthnExtensionNotifier::Core final {
public:
explicit Core(std::vector<base::FilePath> remote_state_change_dirs);
~Core();
void WakeUpExtension();
private:
SEQUENCE_CHECKER(sequence_checker_);
std::vector<base::FilePath> remote_state_change_dirs_;
base::WeakPtrFactory<Core> weak_factory_{this};
};
RemoteWebAuthnExtensionNotifier::Core::Core(
std::vector<base::FilePath> remote_state_change_dirs)
: remote_state_change_dirs_(std::move(remote_state_change_dirs)) {}
RemoteWebAuthnExtensionNotifier::Core::~Core() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
void RemoteWebAuthnExtensionNotifier::Core::WakeUpExtension() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
for (const base::FilePath& dir : remote_state_change_dirs_) {
if (!base::DirectoryExists(dir)) {
VLOG(1) << "Ignored non-directory path: " << dir;
continue;
}
for (const auto& id : GetRemoteWebAuthnExtensionIds()) {
auto file_path = dir.Append(id);
VLOG(1) << "Writing extension wakeup file: " << file_path;
base::File file(file_path,
base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
file.WriteAtCurrentPos(
base::byte_span_with_nul_from_cstring(kExtensionWakeupFileContent));
file.Flush();
}
}
}
const std::vector<base::FilePath::StringType>&
RemoteWebAuthnExtensionNotifier::GetRemoteWebAuthnExtensionIds() {
static const base::NoDestructor<std::vector<base::FilePath::StringType>> ids({
FILE_PATH_LITERAL("djjmngfglakhkhmgcfdmjalogilepkhd"),
FILE_PATH_LITERAL("inomeogfingihgjfjlpeplalcfajhgai"),
#if !defined(NDEBUG)
FILE_PATH_LITERAL("kbapnajlciffffomeaphfpckfdcfopef"),
FILE_PATH_LITERAL("pbnaomcgbfiofkfobmlhmdobjchjkphi"),
#endif
});
return *ids;
}
RemoteWebAuthnExtensionNotifier::RemoteWebAuthnExtensionNotifier()
: RemoteWebAuthnExtensionNotifier(
GetRemoteStateChangeDirPaths(),
base::ThreadPool::CreateSequencedTaskRunner(
{base::MayBlock(), base::WithBaseSyncPrimitives()})) {}
RemoteWebAuthnExtensionNotifier::RemoteWebAuthnExtensionNotifier(
std::vector<base::FilePath> remote_state_change_dirs,
scoped_refptr<base::SequencedTaskRunner> io_task_runner) {
core_ = base::SequenceBound<Core>(io_task_runner,
std::move(remote_state_change_dirs));
}
RemoteWebAuthnExtensionNotifier::~RemoteWebAuthnExtensionNotifier() {
if (is_wake_up_scheduled_) {
core_.AsyncCall(&Core::WakeUpExtension);
}
}
void RemoteWebAuthnExtensionNotifier::NotifyStateChange() {
if (is_wake_up_scheduled_) {
return;
}
is_wake_up_scheduled_ = true;
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(&RemoteWebAuthnExtensionNotifier::WakeUpExtension,
weak_factory_.GetWeakPtr()));
}
void RemoteWebAuthnExtensionNotifier::WakeUpExtension() {
is_wake_up_scheduled_ = false;
core_.AsyncCall(&Core::WakeUpExtension);
}
}