// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/credential_provider/gaiacp/user_policies_manager.h"
#include <limits>
#include <string_view>
#include "base/files/file.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/path_service.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "base/win/registry.h"
#include "chrome/credential_provider/common/gcp_strings.h"
#include "chrome/credential_provider/gaiacp/gcp_utils.h"
#include "chrome/credential_provider/gaiacp/gcpw_strings.h"
#include "chrome/credential_provider/gaiacp/internet_availability_checker.h"
#include "chrome/credential_provider/gaiacp/logging.h"
#include "chrome/credential_provider/gaiacp/os_user_manager.h"
#include "chrome/credential_provider/gaiacp/reg_utils.h"
#include "chrome/credential_provider/gaiacp/win_http_url_fetcher.h"
namespace credential_provider {
namespace {
// HTTP endpoint on the GCPW service to fetch user policies.
const char kUserIdUrlPlaceholder[] = "{user_id}";
const char kGcpwServiceFetchUserPoliciesPath[] = "/v1/users/{user_id}/policies";
const char kGcpwServiceFetchUserPoliciesQueryTemplate[] =
"?device_resource_id=%s&dm_token=%s";
// Default timeout when trying to make requests to the GCPW service.
const base::TimeDelta kDefaultFetchPoliciesRequestTimeout =
base::Milliseconds(5000);
// Path elements for the path where the policies are stored on disk.
constexpr wchar_t kGcpwPoliciesDirectory[] = L"Policies";
constexpr wchar_t kGcpwUserPolicyFileName[] = L"PolicyFetchResponse";
// Maximum number of retries if a HTTP call to the backend fails.
constexpr unsigned int kMaxNumHttpRetries = 1;
// Registry key to control whether cloud policies feature is enabled.
const wchar_t kCloudPoliciesEnabledRegKey[] = L"cloud_policies_enabled";
// Name of the key in the server response whose value contains the user
// policies.
const char kPolicyFetchResponseKeyName[] = "policies";
// The period of refreshing cloud policies.
const base::TimeDelta kCloudPoliciesExecutionPeriod = base::Hours(1);
// True when cloud policies feature is enabled.
bool g_cloud_policies_enabled = false;
// Creates the URL used to fetch the policies from the backend based on the
// credential present (OAuth vs DM token) for authentication.
GURL GetFetchUserPoliciesUrl(const std::wstring& sid,
bool has_access_token,
const std::wstring& device_resource_id,
const std::wstring& dm_token) {
GURL gcpw_service_url = GetGcpwServiceUrl();
std::wstring user_id;
HRESULT status = GetIdFromSid(sid.c_str(), &user_id);
if (FAILED(status)) {
LOGFN(ERROR) << "Could not get user id from sid " << sid;
return GURL();
}
std::string user_policies_path(kGcpwServiceFetchUserPoliciesPath);
std::string placeholder(kUserIdUrlPlaceholder);
user_policies_path.replace(user_policies_path.find(placeholder),
placeholder.size(), base::WideToUTF8(user_id));
if (!has_access_token) {
if (device_resource_id.empty() || dm_token.empty()) {
LOGFN(ERROR) << "Either device id or dm token empty when no access token "
"present for "
<< sid;
return GURL();
}
std::string device_resource_id_value = base::WideToUTF8(device_resource_id);
std::string dm_token_value = base::WideToUTF8(dm_token);
std::string query_suffix = base::StringPrintf(
kGcpwServiceFetchUserPoliciesQueryTemplate,
device_resource_id_value.c_str(), dm_token_value.c_str());
user_policies_path += query_suffix;
}
return gcpw_service_url.Resolve(user_policies_path);
}
// Defines a task that is called by the ESA to perform the policy fetch
// operation.
class UserPoliciesFetchTask : public extension::Task {
public:
static std::unique_ptr<extension::Task> Create() {
std::unique_ptr<extension::Task> esa_task(new UserPoliciesFetchTask());
return esa_task;
}
// ESA calls this to retrieve a configuration for the task execution. Return
// the 1 hour period for the user policies fetch.
extension::Config GetConfig() final {
extension::Config config;
config.execution_period = kCloudPoliciesExecutionPeriod;
return config;
}
// ESA calls this to set all the user-device contexts for the execution of the
// task.
HRESULT SetContext(const std::vector<extension::UserDeviceContext>& c) final {
context_ = c;
return S_OK;
}
// ESA calls execute function to perform the actual task.
HRESULT Execute() final {
HRESULT task_status = S_OK;
for (const auto& c : context_) {
HRESULT hr =
UserPoliciesManager::Get()->FetchAndStoreCloudUserPolicies(c);
if (FAILED(hr)) {
LOGFN(ERROR) << "Failed fetching policies for " << c.user_sid
<< ". hr=" << putHR(hr);
task_status = hr;
}
}
return task_status;
}
private:
std::vector<extension::UserDeviceContext> context_;
};
} // namespace
// static
UserPoliciesManager* UserPoliciesManager::Get() {
return *GetInstanceStorage();
}
// static
UserPoliciesManager** UserPoliciesManager::GetInstanceStorage() {
static UserPoliciesManager instance;
static UserPoliciesManager* instance_storage = &instance;
return &instance_storage;
}
// static
extension::TaskCreator UserPoliciesManager::GetFetchPoliciesTaskCreator() {
return base::BindRepeating(&UserPoliciesFetchTask::Create);
}
UserPoliciesManager::UserPoliciesManager() : fetch_status_(S_OK) {
g_cloud_policies_enabled =
GetGlobalFlagOrDefault(kCloudPoliciesEnabledRegKey, 1) == 1;
}
UserPoliciesManager::~UserPoliciesManager() = default;
bool UserPoliciesManager::CloudPoliciesEnabled() const {
return g_cloud_policies_enabled;
}
GURL UserPoliciesManager::GetGcpwServiceUserPoliciesUrl(
const std::wstring& sid) {
return GetFetchUserPoliciesUrl(sid, true, L"", L"");
}
GURL UserPoliciesManager::GetGcpwServiceUserPoliciesUrl(
const std::wstring& sid,
const std::wstring& device_resource_id,
const std::wstring& dm_token) {
return GetFetchUserPoliciesUrl(sid, false, device_resource_id, dm_token);
}
HRESULT UserPoliciesManager::FetchAndStoreCloudUserPolicies(
const extension::UserDeviceContext& context) {
return FetchAndStorePolicies(
context.user_sid,
GetGcpwServiceUserPoliciesUrl(
context.user_sid, context.device_resource_id, context.dm_token),
std::string());
}
HRESULT UserPoliciesManager::FetchAndStoreCloudUserPolicies(
const std::wstring& sid,
const std::string& access_token) {
if (access_token.empty()) {
LOGFN(ERROR) << "Access token not specified";
return (fetch_status_ = E_FAIL);
}
return FetchAndStorePolicies(sid, GetGcpwServiceUserPoliciesUrl(sid),
access_token);
}
HRESULT UserPoliciesManager::FetchAndStorePolicies(
const std::wstring& sid,
GURL user_policies_url,
const std::string& access_token) {
fetch_status_ = E_FAIL;
if (!user_policies_url.is_valid()) {
LOGFN(ERROR) << "Invalid user policies fetch URL specified.";
return (fetch_status_ = E_FAIL);
}
// Make the fetch policies HTTP request.
std::optional<base::Value> request_result;
HRESULT hr = WinHttpUrlFetcher::BuildRequestAndFetchResultFromHttpService(
user_policies_url, access_token, {}, {},
kDefaultFetchPoliciesRequestTimeout, kMaxNumHttpRetries, &request_result);
if (FAILED(hr)) {
LOGFN(ERROR) << "BuildRequestAndFetchResultFromHttpService hr="
<< putHR(hr);
return (fetch_status_ = hr);
}
std::string policy_data;
if (request_result && request_result->is_dict()) {
if (!base::JSONWriter::Write(*request_result, &policy_data)) {
LOGFN(ERROR) << "base::JSONWriter::Write failed";
return (fetch_status_ = E_FAIL);
}
} else {
LOGFN(ERROR) << "Failed to parse policy response!";
return (fetch_status_ = E_FAIL);
}
uint32_t open_flags = base::File::FLAG_CREATE_ALWAYS |
base::File::FLAG_WRITE |
base::File::FLAG_WIN_EXCLUSIVE_WRITE;
std::unique_ptr<base::File> policy_file = GetOpenedFileForUser(
sid, open_flags, kGcpwPoliciesDirectory, kGcpwUserPolicyFileName);
if (!policy_file) {
return (fetch_status_ = E_FAIL);
}
int num_bytes_written =
policy_file->Write(0, policy_data.c_str(), policy_data.size());
policy_file.reset();
if (size_t(num_bytes_written) != policy_data.size()) {
LOGFN(ERROR) << "Failed writing policy data to file! Only "
<< num_bytes_written << " bytes written out of "
<< policy_data.size();
return (fetch_status_ = E_FAIL);
}
base::Time fetch_time = base::Time::Now();
std::wstring fetch_time_millis = base::NumberToWString(
fetch_time.ToDeltaSinceWindowsEpoch().InMilliseconds());
// Store the fetch time so we know whether a refresh is needed.
SetUserProperty(sid, kLastUserPolicyRefreshTimeRegKey, fetch_time_millis);
return (fetch_status_ = S_OK);
}
bool UserPoliciesManager::GetUserPolicies(const std::wstring& sid,
UserPolicies* user_policies) const {
DCHECK(user_policies);
uint32_t open_flags = base::File::FLAG_OPEN | base::File::FLAG_READ;
std::unique_ptr<base::File> policy_file = GetOpenedFileForUser(
sid, open_flags, kGcpwPoliciesDirectory, kGcpwUserPolicyFileName);
if (!policy_file) {
return false;
}
std::vector<char> buffer(policy_file->GetLength());
policy_file->Read(0, buffer.data(), buffer.size());
policy_file.reset();
std::optional<base::Value> policy_data =
base::JSONReader::Read(std::string_view(buffer.data(), buffer.size()),
base::JSON_ALLOW_TRAILING_COMMAS);
if (!policy_data || !policy_data->is_dict()) {
LOGFN(ERROR) << "Failed to read policy data from file!";
return false;
}
const base::Value::Dict* policies =
policy_data->GetDict().FindDict(kPolicyFetchResponseKeyName);
if (!policies) {
LOGFN(ERROR) << "User policies not found!";
return false;
}
// Override policies with those we just read.
*user_policies = UserPolicies::FromValue(*policies);
return true;
}
bool UserPoliciesManager::IsUserPolicyStaleOrMissing(
const std::wstring& sid) const {
UserPolicies user_policies;
if (!GetUserPolicies(sid, &user_policies)) {
LOGFN(VERBOSE) << "User policy file doesn't exist";
return true;
}
// if the policy file exists but is stale, check the internet connection and
// try to fetch the new policy file. If there's no internet connection, will
// return false and GCPW will continue to use the stale policy information.
if (GetTimeDeltaSinceLastFetch(sid, kLastUserPolicyRefreshTimeRegKey) >
kMaxTimeDeltaSinceLastUserPolicyRefresh) {
if (!InternetAvailabilityChecker::Get()->HasInternetConnection()) {
LOGFN(VERBOSE)
<< "There's no internet connection to update stale policy file.";
return false;
}
LOGFN(VERBOSE) << "User policy file is stale";
return true;
}
return false;
}
void UserPoliciesManager::SetCloudPoliciesEnabledForTesting(bool value) {
g_cloud_policies_enabled = value;
}
HRESULT UserPoliciesManager::GetLastFetchStatusForTesting() const {
return fetch_status_;
}
void UserPoliciesManager::SetFakesForTesting(FakesForTesting* fakes) {
DCHECK(fakes);
WinHttpUrlFetcher::SetCreatorForTesting(
fakes->fake_win_http_url_fetcher_creator);
if (fakes->os_user_manager_for_testing) {
OSUserManager::SetInstanceForTesting(fakes->os_user_manager_for_testing);
}
if (fakes->internet_availability_checker_for_testing) {
InternetAvailabilityChecker::SetInstanceForTesting(
fakes->internet_availability_checker_for_testing);
}
}
} // namespace credential_provider