chromium/components/policy/core/common/policy_loader_lacros.cc

// 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 "components/policy/core/common/policy_loader_lacros.h"

#include <stdint.h>

#include <memory>
#include <string>
#include <utility>
#include <vector>

#include "base/check.h"
#include "base/logging.h"
#include "base/memory/weak_ptr.h"
#include "base/syslog_logging.h"
#include "base/task/sequenced_task_runner.h"
#include "chromeos/lacros/lacros_service.h"
#include "chromeos/startup/browser_params_proxy.h"
#include "components/policy/core/common/cloud/affiliation.h"
#include "components/policy/core/common/cloud/cloud_policy_validator.h"
#include "components/policy/core/common/policy_bundle.h"
#include "components/policy/core/common/policy_proto_decoders.h"
#include "components/policy/policy_constants.h"
#include "components/policy/proto/device_management_backend.pb.h"

namespace {

// Remembers if the main user is managed or not.
// Note: This is a pessimistic default (no policies read - false) and
// once the profile is loaded, the value is set and will never change in
// production. The value changes in tests whenever policy data gets overridden.
bool g_is_main_user_managed_ = false;

enterprise_management::PolicyData* MainUserPolicyDataStorage() {
  static enterprise_management::PolicyData policy_data;
  return &policy_data;
}

bool IsManaged(const enterprise_management::PolicyData& policy_data) {
  return policy_data.state() == enterprise_management::PolicyData::ACTIVE;
}

// Returns whether a primary device account for this session is child.
bool IsChildSession() {
  const chromeos::BrowserParamsProxy* init_params =
      chromeos::BrowserParamsProxy::Get();
  return init_params->SessionType() ==
         crosapi::mojom::SessionType::kChildSession;
}

}  // namespace

namespace policy {

PolicyLoaderLacros::PolicyLoaderLacros(
    scoped_refptr<base::SequencedTaskRunner> task_runner,
    PolicyPerProfileFilter per_profile)
    : AsyncPolicyLoader(task_runner, /*periodic_updates=*/false),
      per_profile_(per_profile) {
  const chromeos::BrowserParamsProxy* init_params =
      chromeos::BrowserParamsProxy::Get();
  if (per_profile_ == PolicyPerProfileFilter::kTrue &&
      init_params->DeviceAccountComponentPolicy()) {
    SetComponentPolicy(init_params->DeviceAccountComponentPolicy().value());
  }
  if (!init_params->DeviceAccountPolicy().has_value()) {
    LOG(ERROR) << "No policy data";
    return;
  }
  policy_fetch_response_ = init_params->DeviceAccountPolicy().value();
  last_fetch_timestamp_ =
      base::Time::FromTimeT(init_params->LastPolicyFetchAttemptTimestamp());
}

PolicyLoaderLacros::~PolicyLoaderLacros() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  auto* lacros_service = chromeos::LacrosService::Get();
  if (lacros_service) {
    lacros_service->RemoveObserver(this);
  }
}

void PolicyLoaderLacros::InitOnBackgroundThread() {
  DCHECK(task_runner()->RunsTasksInCurrentSequence());
  DETACH_FROM_SEQUENCE(sequence_checker_);
  // We add this as observer on background thread to avoid a situation when
  // notification comes after the object is destroyed, but not removed from the
  // list yet.
  // TODO(crbug.com/40143748): Set up LacrosService in tests.
  auto* lacros_service = chromeos::LacrosService::Get();
  if (lacros_service) {
    lacros_service->AddObserver(this);
  }
}

PolicyBundle PolicyLoaderLacros::Load() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  PolicyBundle bundle;

  // If per_profile loader is used, apply policy for extensions.
  if (per_profile_ == PolicyPerProfileFilter::kTrue && component_policy_)
    bundle.MergeFrom(*component_policy_);

  if (!policy_fetch_response_ || policy_fetch_response_->empty()) {
    return bundle;
  }

  auto policy = std::make_unique<enterprise_management::PolicyFetchResponse>();
  if (!policy->ParseFromArray(policy_fetch_response_.value().data(),
                              policy_fetch_response_->size())) {
    LOG(ERROR) << "Failed to parse policy data";
    return bundle;
  }
  UserCloudPolicyValidator validator(std::move(policy),
                                     /*background_task_runner=*/nullptr);
  validator.ValidatePayload();
  validator.RunValidation();

  PolicyMap policy_map;
  base::WeakPtr<CloudExternalDataManager> external_data_manager;
  DecodeProtoFields(*(validator.payload()), external_data_manager,
                    PolicySource::POLICY_SOURCE_CLOUD_FROM_ASH,
                    PolicyScope::POLICY_SCOPE_USER, &policy_map, per_profile_);

  // We do not set enterprise defaults for child accounts, because they are
  // consumer users. The same rule is applied to policy in Ash. See
  // UserCloudPolicyManagerAsh.
  if (!IsChildSession()) {
    switch (per_profile_) {
      case PolicyPerProfileFilter::kTrue:
        SetEnterpriseUsersProfileDefaults(&policy_map);
        break;
      case PolicyPerProfileFilter::kFalse:
        SetEnterpriseUsersSystemWideDefaults(&policy_map);
        break;
      case PolicyPerProfileFilter::kAny:
        NOTREACHED_IN_MIGRATION();
    }
  }
  bundle.Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()))
      .MergeFrom(policy_map);

  // Remember if the main profile is managed or not.
  if (per_profile_ == PolicyPerProfileFilter::kFalse) {
    g_is_main_user_managed_ = IsManaged(*validator.policy_data());
    if (g_is_main_user_managed_) {
      *MainUserPolicyDataStorage() = *validator.policy_data();
    }
  }
  policy_data_ = std::move(validator.policy_data());

  return bundle;
}

void PolicyLoaderLacros::OnPolicyUpdated(
    const std::vector<uint8_t>& policy_fetch_response) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  policy_fetch_response_ = policy_fetch_response;
  Reload(true);
}

void PolicyLoaderLacros::OnPolicyFetchAttempt() {
  last_fetch_timestamp_ = base::Time::Now();
}

void PolicyLoaderLacros::OnComponentPolicyUpdated(
    const policy::ComponentPolicyMap& component_policy) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  // The component policy is per_profile=true policy. If Lacros is using
  // secondary profile, that policy is loaded directly from DMServer. In case
  // it is using the device account, there are two PolicyLoaderLacros objects
  // present, and we need to store it only in the object with per_profile:True.
  if (per_profile_ == PolicyPerProfileFilter::kFalse) {
    return;
  }

  SetComponentPolicy(component_policy);
  Reload(true);
}

void PolicyLoaderLacros::SetComponentPolicy(
    const policy::ComponentPolicyMap& component_policy) {
  if (component_policy_) {
    component_policy_->Clear();
  } else {
    component_policy_ = std::make_unique<PolicyBundle>();
  }
  for (auto& policy_pair : component_policy) {
    PolicyMap component_policy_map;
    std::string error;
    // The component policy received from Ash is the JSON data corresponding to
    // the policy for the namespace.
    ParseComponentPolicy(policy_pair.second.GetDict().Clone(),
                         POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD_FROM_ASH,
                         &component_policy_map, &error);
    DCHECK(error.empty());

    // The data is also good; expose the policies.
    component_policy_->Get(policy_pair.first).Swap(&component_policy_map);
  }
}

enterprise_management::PolicyData* PolicyLoaderLacros::GetPolicyData() {
  if (!policy_fetch_response_ || !policy_data_)
    return nullptr;

  return policy_data_.get();
}

// static
bool PolicyLoaderLacros::IsDeviceLocalAccountUser() {
  const chromeos::BrowserParamsProxy* init_params =
      chromeos::BrowserParamsProxy::Get();
  crosapi::mojom::SessionType session_type = init_params->SessionType();
  return session_type == crosapi::mojom::SessionType::kPublicSession ||
         session_type == crosapi::mojom::SessionType::kWebKioskSession ||
         session_type == crosapi::mojom::SessionType::kAppKioskSession;
}

// static
bool PolicyLoaderLacros::IsMainUserManaged() {
  return g_is_main_user_managed_;
}

// static
bool PolicyLoaderLacros::IsMainUserAffiliated() {
  const enterprise_management::PolicyData* policy =
      policy::PolicyLoaderLacros::main_user_policy_data();

  // To align with `DeviceLocalAccountUserBase::IsAffiliated()`, a device local
  // account user is always treated as affiliated.
  if (IsDeviceLocalAccountUser()) {
    return true;
  }

  const auto& device_ids = PolicyLoaderLacros::device_affiliation_ids();
  if (policy && !policy->user_affiliation_ids().empty() &&
      !device_ids.empty()) {
    const auto& user_ids = policy->user_affiliation_ids();
    return policy::IsAffiliated({user_ids.begin(), user_ids.end()},
                                {device_ids.begin(), device_ids.end()});
  }
  return false;
}

// static
const enterprise_management::PolicyData*
PolicyLoaderLacros::main_user_policy_data() {
  return MainUserPolicyDataStorage();
}

// static
void PolicyLoaderLacros::set_main_user_policy_data_for_testing(
    const enterprise_management::PolicyData& policy_data) {
  *MainUserPolicyDataStorage() = policy_data;
  g_is_main_user_managed_ = IsManaged(policy_data);
}

// static
const std::vector<std::string> PolicyLoaderLacros::device_affiliation_ids() {
  const chromeos::BrowserParamsProxy* init_params =
      chromeos::BrowserParamsProxy::Get();
  if (!init_params->DeviceProperties()) {
    return {};
  }
  if (!init_params->DeviceProperties()->device_affiliation_ids.has_value()) {
    return {};
  }
  return init_params->DeviceProperties()->device_affiliation_ids.value();
}

// static
const std::string PolicyLoaderLacros::device_dm_token() {
  const chromeos::BrowserParamsProxy* init_params =
      chromeos::BrowserParamsProxy::Get();
  if (!init_params->DeviceProperties()) {
    return std::string();
  }

  return init_params->DeviceProperties()->device_dm_token;
}

}  // namespace policy