chromium/chrome/browser/ash/policy/enrollment/enrollment_config.cc

// Copyright 2018 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/browser/ash/policy/enrollment/enrollment_config.h"

#include <optional>
#include <string>
#include <string_view>

#include "ash/constants/ash_switches.h"
#include "base/command_line.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/notreached.h"
#include "base/values.h"
#include "chrome/browser/ash/login/configuration_keys.h"
#include "chrome/browser/ash/login/login_pref_names.h"
#include "chrome/browser/ash/login/oobe_configuration.h"
#include "chrome/browser/ash/login/ui/login_display_host.h"
#include "chrome/browser/ash/login/wizard_context.h"
#include "chrome/browser/ash/policy/core/device_cloud_policy_manager_ash.h"
#include "chrome/browser/ash/policy/enrollment/enrollment_token_provider.h"
#include "chrome/browser/ash/policy/server_backed_state/server_backed_device_state.h"
#include "chrome/browser/ash/settings/device_settings_service.h"
#include "chrome/browser/browser_process.h"
#include "chrome/common/pref_names.h"
#include "chromeos/ash/components/install_attributes/install_attributes.h"
#include "chromeos/ash/components/system/statistics_provider.h"
#include "components/prefs/pref_service.h"

namespace policy {
namespace {

const char kRecoveryHistogram[] = "EnterpriseCheck.EnrollementRecoveryOnBoot";

const char kZeroTouchEnrollmentForced[] = "forced";

// Do not reorder or delete entries because it is used in UMA.
enum class EnrollmentRecoveryOnBootUma {
  kForced = 0,
  kFalseFlag = 1,
  kNoSerialNumber = 2,
  kMaxValue = kNoSerialNumber,
};

std::string GetString(const base::Value::Dict& dict, std::string_view key) {
  const std::string* value = dict.FindString(key);
  return value ? *value : std::string();
}

bool IsEnrollingAfterRollback() {
  auto* login_display_host = ash::LoginDisplayHost::default_host();
  if (!login_display_host) {
    return false;
  }
  const auto* wizard_context = login_display_host->GetWizardContext();
  return wizard_context && ash::IsRollbackFlow(*wizard_context);
}

// Returns the license type to use based on the license type, assigned
// upgrade type and the license packaged from device state.
LicenseType GetLicenseTypeToUse(const std::string license_type,
                                const bool is_license_packaged_with_device,
                                const std::string assigned_upgrade_type) {
  if (license_type == kDeviceStateLicenseTypeEnterprise) {
    return LicenseType::kEnterprise;
  } else if (license_type == kDeviceStateLicenseTypeEducation) {
    return LicenseType::kEducation;
  } else if (license_type == kDeviceStateLicenseTypeTerminal) {
    return LicenseType::kTerminal;
  }

  if (!is_license_packaged_with_device &&
      assigned_upgrade_type == kDeviceStateAssignedUpgradeTypeKiosk) {
    return LicenseType::kTerminal;
  }

  return LicenseType::kNone;
}

std::string_view ToStringView(EnrollmentConfig::Mode mode) {
#define CASE(_name)                   \
  case EnrollmentConfig::Mode::_name: \
    return #_name;

  switch (mode) {
    CASE(MODE_NONE);
    CASE(MODE_MANUAL);
    CASE(MODE_MANUAL_REENROLLMENT);
    CASE(MODE_LOCAL_FORCED);
    CASE(MODE_LOCAL_ADVERTISED);
    CASE(MODE_SERVER_FORCED);
    CASE(MODE_SERVER_ADVERTISED);
    CASE(MODE_RECOVERY);
    CASE(MODE_ATTESTATION);
    CASE(MODE_ATTESTATION_LOCAL_FORCED);
    CASE(MODE_ATTESTATION_SERVER_FORCED);
    CASE(MODE_ATTESTATION_MANUAL_FALLBACK);
    CASE(MODE_INITIAL_SERVER_FORCED);
    CASE(MODE_ATTESTATION_INITIAL_SERVER_FORCED);
    CASE(MODE_ATTESTATION_INITIAL_MANUAL_FALLBACK);
    CASE(MODE_ATTESTATION_ROLLBACK_FORCED);
    CASE(MODE_ATTESTATION_ROLLBACK_MANUAL_FALLBACK);
    CASE(MODE_ENROLLMENT_TOKEN_INITIAL_SERVER_FORCED);
    CASE(MODE_ENROLLMENT_TOKEN_INITIAL_MANUAL_FALLBACK);
  }

  NOTREACHED();
#undef CASE
}

std::string_view ToStringView(EnrollmentConfig::AuthMechanism auth) {
#define CASE(_name)                            \
  case EnrollmentConfig::AuthMechanism::_name: \
    return #_name;

  switch (auth) {
    CASE(AUTH_MECHANISM_INTERACTIVE);
    CASE(AUTH_MECHANISM_ATTESTATION);
    CASE(AUTH_MECHANISM_ATTESTATION_PREFERRED);
    CASE(AUTH_MECHANISM_TOKEN_PREFERRED);
  }

  NOTREACHED();
#undef CASE
}

EnrollmentConfig::AuthMechanism GetPrescribedAuthMechanism(
    PrefService* local_state) {
  // Authentication through the attestation mechanism is controlled by a
  // command line switch that either enables it or forces it (meaning that
  // interactive authentication is disabled).
  base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
  if (!command_line->HasSwitch(
          ash::switches::kEnterpriseEnableZeroTouchEnrollment)) {
    return EnrollmentConfig::AUTH_MECHANISM_INTERACTIVE;
  }

  const std::string value = command_line->GetSwitchValueASCII(
      ash::switches::kEnterpriseEnableZeroTouchEnrollment);
  if (value == kZeroTouchEnrollmentForced) {
    return EnrollmentConfig::AUTH_MECHANISM_ATTESTATION;
  }

  if (value.empty()) {
    // If OOBE is done and we are not enrolled, make sure we only try
    // interactive enrollment.
    if (local_state->GetBoolean(ash::prefs::kOobeComplete)) {
      return EnrollmentConfig::AUTH_MECHANISM_INTERACTIVE;
    }
    return EnrollmentConfig::AUTH_MECHANISM_ATTESTATION_PREFERRED;
  }

  LOG(WARNING) << "Malformed value \"" << value << "\" for switch --"
               << ash::switches::kEnterpriseEnableZeroTouchEnrollment
               << ". Ignoring switch.";
  return EnrollmentConfig::AUTH_MECHANISM_INTERACTIVE;
}

EnrollmentConfig GetPrescribedRecoveryConfig(
    PrefService* local_state,
    const ash::InstallAttributes& install_attributes,
    ash::system::StatisticsProvider* statistics_provider) {
  EnrollmentConfig recovery_config;

  // Regardless what mode is applicable, auth mechanism must be prescribed one.
  recovery_config.auth_mechanism = GetPrescribedAuthMechanism(local_state);

  // Regardless what mode is applicable, the enrollment domain is fixed.
  recovery_config.management_domain = install_attributes.GetDomain();

  if (!local_state->GetBoolean(prefs::kEnrollmentRecoveryRequired)) {
    return recovery_config;
  }

  if (ash::DeviceSettingsService::IsInitialized() &&
      ash::DeviceSettingsService::Get()->HasDmToken()) {
    LOG(WARNING) << "False recovery flag.";
    local_state->ClearPref(::prefs::kEnrollmentRecoveryRequired);
    base::UmaHistogramEnumeration(kRecoveryHistogram,
                                  EnrollmentRecoveryOnBootUma::kFalseFlag);

    return recovery_config;
  }

  LOG(WARNING) << "Enrollment recovery required according to pref.";
  const auto serial_number = statistics_provider->GetMachineID();
  if (!serial_number || serial_number->empty()) {
    LOG(WARNING) << "Postponing recovery because machine id is missing.";
    base::UmaHistogramEnumeration(kRecoveryHistogram,
                                  EnrollmentRecoveryOnBootUma::kNoSerialNumber);
    return recovery_config;
  }

  recovery_config.mode = EnrollmentConfig::MODE_RECOVERY;
  base::UmaHistogramEnumeration(kRecoveryHistogram,
                                EnrollmentRecoveryOnBootUma::kForced);

  return recovery_config;
}

OOBEConfigSource ConvertToOOBEConfigSource(
    const std::string* oobe_config_source) {
  if (!oobe_config_source || oobe_config_source->empty()) {
    return OOBEConfigSource::kNone;
  }
  if (*oobe_config_source == "REMOTE_DEPLOYMENT") {
    return OOBEConfigSource::kRemoteDeployment;
  }
  if (*oobe_config_source == "PACKAGING_TOOL") {
    return OOBEConfigSource::kPackagingTool;
  }
  return OOBEConfigSource::kUnknown;
}

}  // namespace

struct EnrollmentConfig::PrescribedConfig {
  EnrollmentConfig::Mode mode;
  EnrollmentConfig::AuthMechanism auth_mechanism;
  std::string management_domain;
  std::string enrollment_token;
  OOBEConfigSource oobe_config_source;

  static PrescribedConfig GetPrescribedConfig(
      PrefService* local_state,
      ash::system::StatisticsProvider* statistics_provider,
      const base::Value::Dict& device_state,
      const ash::OobeConfiguration* oobe_configuration);
};

// static
EnrollmentConfig::PrescribedConfig
EnrollmentConfig::PrescribedConfig::GetPrescribedConfig(
    PrefService* local_state,
    ash::system::StatisticsProvider* statistics_provider,
    const base::Value::Dict& device_state,
    const ash::OobeConfiguration* oobe_configuration) {
  // Decide enrollment mode. Give precedence to forced variants.
  if (IsEnrollingAfterRollback()) {
    return {.mode = EnrollmentConfig::MODE_ATTESTATION_ROLLBACK_FORCED,
            .auth_mechanism =
                EnrollmentConfig::AUTH_MECHANISM_ATTESTATION_PREFERRED};
  }

  const std::string device_state_mode =
      GetString(device_state, kDeviceStateMode);
  const std::string device_state_management_domain =
      GetString(device_state, kDeviceStateManagementDomain);

  if (device_state_mode == kDeviceStateRestoreModeReEnrollmentEnforced) {
    return {.mode = EnrollmentConfig::MODE_SERVER_FORCED,
            .auth_mechanism = GetPrescribedAuthMechanism(local_state),
            .management_domain = device_state_management_domain};
  }

  if (device_state_mode == kDeviceStateInitialModeEnrollmentEnforced) {
    return {.mode = EnrollmentConfig::MODE_INITIAL_SERVER_FORCED,
            .auth_mechanism = GetPrescribedAuthMechanism(local_state),
            .management_domain = device_state_management_domain};
  }

  if (device_state_mode == kDeviceStateRestoreModeReEnrollmentZeroTouch) {
    return {.mode = EnrollmentConfig::MODE_ATTESTATION_SERVER_FORCED,
            .auth_mechanism =
                EnrollmentConfig::AUTH_MECHANISM_ATTESTATION_PREFERRED,
            .management_domain = device_state_management_domain};
  }

  if (device_state_mode == kDeviceStateInitialModeEnrollmentZeroTouch) {
    return {.mode = EnrollmentConfig::MODE_ATTESTATION_INITIAL_SERVER_FORCED,
            .auth_mechanism =
                EnrollmentConfig::AUTH_MECHANISM_ATTESTATION_PREFERRED,
            .management_domain = device_state_management_domain};
  }

  if (device_state_mode == kDeviceStateInitialModeTokenEnrollment) {
    std::optional<std::string> enrollment_token =
        GetEnrollmentToken(oobe_configuration);
    // TODO(b/329271128): CHECK to ensure enrollment_token always has value
    // after this bug is fixed.
    if (enrollment_token.has_value()) {
      const std::string* oobe_config_source =
          oobe_configuration->configuration().FindString(
              ash::configuration::kSource);
      return {
          .mode = EnrollmentConfig::MODE_ENROLLMENT_TOKEN_INITIAL_SERVER_FORCED,
          .auth_mechanism = EnrollmentConfig::AUTH_MECHANISM_TOKEN_PREFERRED,
          .enrollment_token = std::move(enrollment_token.value()),
          .oobe_config_source = ConvertToOOBEConfigSource(oobe_config_source)};
    } else {
      return {.mode = EnrollmentConfig::MODE_NONE,
              .auth_mechanism = GetPrescribedAuthMechanism(local_state)};
    }
  }

  const bool pref_enrollment_auto_start_present =
      local_state->HasPrefPath(prefs::kDeviceEnrollmentAutoStart);
  const bool pref_enrollment_auto_start =
      local_state->GetBoolean(prefs::kDeviceEnrollmentAutoStart);

  const bool pref_enrollment_can_exit_present =
      local_state->HasPrefPath(prefs::kDeviceEnrollmentCanExit);
  const bool pref_enrollment_can_exit =
      local_state->GetBoolean(prefs::kDeviceEnrollmentCanExit);

  if (pref_enrollment_auto_start_present && pref_enrollment_auto_start &&
      pref_enrollment_can_exit_present && !pref_enrollment_can_exit) {
    return {.mode = EnrollmentConfig::MODE_LOCAL_FORCED,
            .auth_mechanism = GetPrescribedAuthMechanism(local_state)};
  }

  const bool oem_is_managed = ash::system::StatisticsProvider::FlagValueToBool(
      statistics_provider->GetMachineFlag(
          ash::system::kOemIsEnterpriseManagedKey),
      /*default_value=*/false);
  const bool oem_can_exit_enrollment =
      ash::system::StatisticsProvider::FlagValueToBool(
          statistics_provider->GetMachineFlag(
              ash::system::kOemCanExitEnterpriseEnrollmentKey),
          /*default_value=*/true);

  if (oem_is_managed && !oem_can_exit_enrollment) {
    return {.mode = EnrollmentConfig::MODE_LOCAL_FORCED,
            .auth_mechanism = GetPrescribedAuthMechanism(local_state)};
  }

  if (local_state->GetBoolean(ash::prefs::kOobeComplete)) {
    // If OOBE is complete, don't return advertised modes as there's currently
    // no way to make sure advertised enrollment only gets shown once.
    return {.mode = EnrollmentConfig::MODE_NONE,
            .auth_mechanism = GetPrescribedAuthMechanism(local_state)};
  }

  if (device_state_mode == kDeviceStateRestoreModeReEnrollmentRequested) {
    return {.mode = EnrollmentConfig::MODE_SERVER_ADVERTISED,
            .auth_mechanism = GetPrescribedAuthMechanism(local_state),
            .management_domain = device_state_management_domain};
  }

  if (pref_enrollment_auto_start_present && pref_enrollment_auto_start) {
    return {.mode = EnrollmentConfig::MODE_LOCAL_ADVERTISED,
            .auth_mechanism = GetPrescribedAuthMechanism(local_state)};
  }

  if (oem_is_managed) {
    return {.mode = EnrollmentConfig::MODE_LOCAL_ADVERTISED,
            .auth_mechanism = GetPrescribedAuthMechanism(local_state)};
  }

  return {.mode = EnrollmentConfig::MODE_NONE,
          .auth_mechanism = GetPrescribedAuthMechanism(local_state)};
}

struct EnrollmentConfig::PrescribedLicense {
  bool is_license_packaged_with_device;
  EnrollmentConfig::AssignedUpgradeType assigned_upgrade_type;
  LicenseType license_type;

  static PrescribedLicense GetPrescribedLicense(
      const base::Value::Dict& device_state);
};

// static
EnrollmentConfig::PrescribedLicense
EnrollmentConfig::PrescribedLicense::GetPrescribedLicense(
    const base::Value::Dict& device_state) {
  EnrollmentConfig::AssignedUpgradeType assigned_upgrade_type =
      EnrollmentConfig::AssignedUpgradeType::
          kAssignedUpgradeTypeChromeEnterprise;

  const std::string assigned_upgrade_type_str =
      GetString(device_state, kDeviceStateAssignedUpgradeType);

  if (assigned_upgrade_type_str ==
      kDeviceStateAssignedUpgradeTypeChromeEnterprise) {
    assigned_upgrade_type = EnrollmentConfig::AssignedUpgradeType::
        kAssignedUpgradeTypeChromeEnterprise;
  } else if (assigned_upgrade_type_str ==
             kDeviceStateAssignedUpgradeTypeKiosk) {
    assigned_upgrade_type = EnrollmentConfig::AssignedUpgradeType::
        kAssignedUpgradeTypeKioskAndSignage;
  }

  const bool is_license_packaged_with_device =
      device_state.FindBool(kDeviceStatePackagedLicense).value_or(false);
  const std::string license_type_str =
      GetString(device_state, kDeviceStateLicenseType);

  const LicenseType license_type =
      GetLicenseTypeToUse(license_type_str, is_license_packaged_with_device,
                          assigned_upgrade_type_str);

  return {.is_license_packaged_with_device = is_license_packaged_with_device,
          .assigned_upgrade_type = assigned_upgrade_type,
          .license_type = license_type};
}

EnrollmentConfig::EnrollmentConfig() = default;
EnrollmentConfig::EnrollmentConfig(const EnrollmentConfig& other) = default;
EnrollmentConfig::~EnrollmentConfig() = default;

EnrollmentConfig::EnrollmentConfig(PrescribedConfig prescribed_config,
                                   PrescribedLicense prescribed_license)
    : mode(prescribed_config.mode),
      auth_mechanism(prescribed_config.auth_mechanism),
      management_domain(std::move(prescribed_config.management_domain)),
      is_license_packaged_with_device(
          prescribed_license.is_license_packaged_with_device),
      license_type(prescribed_license.license_type),
      assigned_upgrade_type(prescribed_license.assigned_upgrade_type),
      enrollment_token(std::move(prescribed_config.enrollment_token)),
      oobe_config_source(prescribed_config.oobe_config_source) {}

// static
EnrollmentConfig EnrollmentConfig::GetPrescribedEnrollmentConfig() {
  return GetPrescribedEnrollmentConfig(
      g_browser_process->local_state(), *ash::InstallAttributes::Get(),
      ash::system::StatisticsProvider::GetInstance(),
      ash::OobeConfiguration::Get());
}

// static
EnrollmentConfig EnrollmentConfig::GetPrescribedEnrollmentConfig(
    PrefService* local_state,
    const ash::InstallAttributes& install_attributes,
    ash::system::StatisticsProvider* statistics_provider,
    const ash::OobeConfiguration* oobe_configuration) {
  DCHECK(local_state);
  DCHECK(statistics_provider);
  DCHECK(oobe_configuration);
  // If OOBE is done and the device is enrolled, check for need to recover
  // enrollment.
  if (local_state->GetBoolean(ash::prefs::kOobeComplete) &&
      install_attributes.IsCloudManaged()) {
    return GetPrescribedRecoveryConfig(local_state, install_attributes,
                                       statistics_provider);
  }

  const base::Value::Dict& device_state =
      local_state->GetDict(prefs::kServerBackedDeviceState);

  return EnrollmentConfig(
      PrescribedConfig::GetPrescribedConfig(local_state, statistics_provider,
                                            device_state, oobe_configuration),
      PrescribedLicense::GetPrescribedLicense(device_state));
}

// static
EnrollmentConfig::Mode EnrollmentConfig::GetManualFallbackMode(
    EnrollmentConfig::Mode attestation_mode) {
  switch (attestation_mode) {
    case EnrollmentConfig::MODE_ATTESTATION_INITIAL_SERVER_FORCED:
      return EnrollmentConfig::MODE_ATTESTATION_INITIAL_MANUAL_FALLBACK;
    case EnrollmentConfig::MODE_ATTESTATION_SERVER_FORCED:
      return EnrollmentConfig::MODE_ATTESTATION_MANUAL_FALLBACK;
    case EnrollmentConfig::MODE_ATTESTATION_ROLLBACK_FORCED:
      return EnrollmentConfig::MODE_ATTESTATION_ROLLBACK_MANUAL_FALLBACK;
    case EnrollmentConfig::MODE_ENROLLMENT_TOKEN_INITIAL_SERVER_FORCED:
      return EnrollmentConfig::MODE_ENROLLMENT_TOKEN_INITIAL_MANUAL_FALLBACK;
    case EnrollmentConfig::MODE_NONE:
    case EnrollmentConfig::MODE_MANUAL:
    case EnrollmentConfig::MODE_MANUAL_REENROLLMENT:
    case EnrollmentConfig::MODE_LOCAL_FORCED:
    case EnrollmentConfig::MODE_LOCAL_ADVERTISED:
    case EnrollmentConfig::MODE_SERVER_FORCED:
    case EnrollmentConfig::MODE_SERVER_ADVERTISED:
    case EnrollmentConfig::MODE_RECOVERY:
    case EnrollmentConfig::MODE_ATTESTATION:
    case EnrollmentConfig::MODE_ATTESTATION_LOCAL_FORCED:
    case EnrollmentConfig::MODE_ATTESTATION_MANUAL_FALLBACK:
    case EnrollmentConfig::MODE_INITIAL_SERVER_FORCED:
    case EnrollmentConfig::MODE_ATTESTATION_INITIAL_MANUAL_FALLBACK:
    case EnrollmentConfig::MODE_ATTESTATION_ROLLBACK_MANUAL_FALLBACK:
    case EnrollmentConfig::MODE_ENROLLMENT_TOKEN_INITIAL_MANUAL_FALLBACK:
      NOTREACHED_IN_MIGRATION();
  }
  return EnrollmentConfig::MODE_NONE;
}

std::ostream& operator<<(std::ostream& os, const EnrollmentConfig::Mode& mode) {
  return os << ToStringView(mode);
}

std::ostream& operator<<(std::ostream& os,
                         const EnrollmentConfig::AuthMechanism& auth) {
  return os << ToStringView(auth);
}

std::ostream& operator<<(std::ostream& os, const EnrollmentConfig& config) {
  return os << "EnrollmentConfig(" << config.mode << ", "
            << config.auth_mechanism << ")";
}

}  // namespace policy