text: // Copyright 2014 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include "chrome/browser/chromeos/login/enrollment/auto_enrollment_controller.h"
  
  #include "base/bind.h"
  #include "base/bind_helpers.h"
  #include "base/command_line.h"
  #include "base/logging.h"
  #include "base/strings/string_number_conversions.h"
  #include "chrome/browser/browser_process.h"
  #include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h"
  #include "chrome/browser/chromeos/policy/device_cloud_policy_manager_chromeos.h"
  #include "chrome/browser/chromeos/policy/server_backed_state_keys_broker.h"
  #include "chromeos/chromeos_switches.h"
  #include "components/policy/core/common/cloud/device_management_service.h"
  #include "net/url_request/url_request_context_getter.h"
  
  namespace chromeos {
  
  namespace {
  
  // Returns the int value of the |switch_name| argument, clamped to the [0, 62]
  // interval. Returns 0 if the argument doesn't exist or isn't an int value.
  int GetSanitizedArg(const std::string& switch_name) {
    CommandLine* command_line = CommandLine::ForCurrentProcess();
    if (!command_line->HasSwitch(switch_name))
      return 0;
    std::string value = command_line->GetSwitchValueASCII(switch_name);
    int int_value;
    if (!base::StringToInt(value, &int_value)) {
      LOG(ERROR) << "Switch \"" << switch_name << "\" is not a valid int. "
                 << "Defaulting to 0.";
      return 0;
    }
    if (int_value < 0) {
      LOG(ERROR) << "Switch \"" << switch_name << "\" can't be negative. "
                 << "Using 0";
      return 0;
    }
    if (int_value > policy::AutoEnrollmentClient::kMaximumPower) {
      LOG(ERROR) << "Switch \"" << switch_name << "\" can't be greater than "
                 << policy::AutoEnrollmentClient::kMaximumPower << ". Using "
                 << policy::AutoEnrollmentClient::kMaximumPower;
      return policy::AutoEnrollmentClient::kMaximumPower;
    }
    return int_value;
  }
  
  }  // namespace
  
  const char AutoEnrollmentController::kForcedReEnrollmentAlways[] = "always";
  const char AutoEnrollmentController::kForcedReEnrollmentLegacy[] = "legacy";
  const char AutoEnrollmentController::kForcedReEnrollmentNever[] = "never";
  const char AutoEnrollmentController::kForcedReEnrollmentOfficialBuild[] =
      "official";
  
  AutoEnrollmentController::Mode AutoEnrollmentController::GetMode() {
    CommandLine* command_line = CommandLine::ForCurrentProcess();
  
    if (!command_line->HasSwitch(switches::kEnterpriseEnableForcedReEnrollment))
      return MODE_LEGACY_AUTO_ENROLLMENT;
  
    std::string command_line_mode = command_line->GetSwitchValueASCII(
        switches::kEnterpriseEnableForcedReEnrollment);
    if (command_line_mode == kForcedReEnrollmentAlways) {
      return MODE_FORCED_RE_ENROLLMENT;
    } else if (command_line_mode.empty() ||
               command_line_mode == kForcedReEnrollmentOfficialBuild) {
  #if defined(OFFICIAL_BUILD)
      return MODE_FORCED_RE_ENROLLMENT;
  #else
      return MODE_NONE;
  #endif
    } else if (command_line_mode == kForcedReEnrollmentLegacy) {
      return MODE_LEGACY_AUTO_ENROLLMENT;
    }
  
    return MODE_NONE;
  }
  
  AutoEnrollmentController::AutoEnrollmentController()
      : state_(policy::AUTO_ENROLLMENT_STATE_IDLE),
        client_start_weak_factory_(this) {}
  
  AutoEnrollmentController::~AutoEnrollmentController() {}
  
  void AutoEnrollmentController::Start() {
    // This method is called at the point in the OOBE/login flow at which the
    // auto-enrollment check can start. This happens either after the EULA is
    // accepted, or right after a reboot if the EULA has already been accepted.
  
    // Do not communicate auto-enrollment data to the server if
    // 1. we are running telemetry tests.
    // 2. modulus configuration is not present.
    // 3. Auto-enrollment is disabled via the command line.
  
    CommandLine* command_line = CommandLine::ForCurrentProcess();
    if (command_line->HasSwitch(chromeos::switches::kDisableGaiaServices) ||
        (!command_line->HasSwitch(
             chromeos::switches::kEnterpriseEnrollmentInitialModulus) &&
         !command_line->HasSwitch(
             chromeos::switches::kEnterpriseEnrollmentModulusLimit)) ||
        GetMode() == MODE_NONE) {
      VLOG(1) << "Auto-enrollment disabled.";
      UpdateState(policy::AUTO_ENROLLMENT_STATE_NO_ENROLLMENT);
      return;
    }
  
    // If a client is being created or already existing, bail out.
    if (client_start_weak_factory_.HasWeakPtrs() || client_)
      return;
  
    // Start by checking if the device has already been owned.
    UpdateState(policy::AUTO_ENROLLMENT_STATE_PENDING);
    DeviceSettingsService::Get()->GetOwnershipStatusAsync(
        base::Bind(&AutoEnrollmentController::OnOwnershipStatusCheckDone,
                   client_start_weak_factory_.GetWeakPtr()));
  }
  
  void AutoEnrollmentController::Cancel() {
    if (client_) {
      // Cancelling the |client_| allows it to determine whether
      // its protocol finished before login was complete.
      client_.release()->CancelAndDeleteSoon();
    }
  
    // Make sure to nuke pending |client_| start sequences.
    client_start_weak_factory_.InvalidateWeakPtrs();
  }
  
  void AutoEnrollmentController::Retry() {
    if (client_)
      client_->Retry();
  }
  
  scoped_ptr<AutoEnrollmentController::ProgressCallbackList::Subscription>
  AutoEnrollmentController::RegisterProgressCallback(
      const ProgressCallbackList::CallbackType& callback) {
    return progress_callbacks_.Add(callback);
  }
  
  bool AutoEnrollmentController::ShouldEnrollSilently() {
    return state_ == policy::AUTO_ENROLLMENT_STATE_TRIGGER_ENROLLMENT &&
           GetMode() == MODE_LEGACY_AUTO_ENROLLMENT;
  }
  
  void AutoEnrollmentController::OnOwnershipStatusCheckDone(
      DeviceSettingsService::OwnershipStatus status) {
    if (status != DeviceSettingsService::OWNERSHIP_NONE) {
      // The device is already owned. No need for auto-enrollment checks.
      VLOG(1) << "Device already owned, skipping auto-enrollment check";
      UpdateState(policy::AUTO_ENROLLMENT_STATE_NO_ENROLLMENT);
      return;
    }
  
    // Make sure state keys are available.
    g_browser_process->platform_part()
        ->browser_policy_connector_chromeos()
        ->GetStateKeysBroker()
        ->RequestStateKeys(base::Bind(&AutoEnrollmentController::StartClient,
                                      client_start_weak_factory_.GetWeakPtr()));
  }
  
  void AutoEnrollmentController::StartClient(
      const std::vector<std::string>& state_keys, bool first_boot) {
    policy::BrowserPolicyConnectorChromeOS* connector =
        g_browser_process->platform_part()->browser_policy_connector_chromeos();
    policy::DeviceManagementService* service =
        connector->device_management_service();
    service->ScheduleInitialization(0);
  
    int power_initial = GetSanitizedArg(
        chromeos::switches::kEnterpriseEnrollmentInitialModulus);
    int power_limit = GetSanitizedArg(
        chromeos::switches::kEnterpriseEnrollmentModulusLimit);
    if (power_initial > power_limit) {
      LOG(ERROR) << "Initial auto-enrollment modulus is larger than the limit, "
                 << "clamping to the limit.";
      power_initial = power_limit;
    }
  
    bool retrieve_device_state = false;
    std::string device_id;
    if (GetMode() == MODE_FORCED_RE_ENROLLMENT) {
      retrieve_device_state = true;
      if (!state_keys.empty() && !first_boot)
        device_id = state_keys.front();
    } else {
      device_id = policy::DeviceCloudPolicyManagerChromeOS::GetMachineID();
    }
  
    client_.reset(new policy::AutoEnrollmentClient(
        base::Bind(&AutoEnrollmentController::UpdateState,
                   base::Unretained(this)),
        service,
        g_browser_process->local_state(),
        g_browser_process->system_request_context(),
        device_id,
        retrieve_device_state,
        power_initial,
        power_limit));
  
    VLOG(1) << "Starting auto-enrollment client.";
    client_->Start();
  }
  
  void AutoEnrollmentController::UpdateState(
      policy::AutoEnrollmentState new_state) {
    VLOG(1) << "New state: " << new_state << ".";
    state_ = new_state;
    progress_callbacks_.Notify(state_);
  }
  
  }  // namespace chromeos
  