1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/chromeos/policy/enrollment_handler_chromeos.h"
6
7#include "base/bind.h"
8#include "base/command_line.h"
9#include "base/logging.h"
10#include "base/message_loop/message_loop.h"
11#include "chrome/browser/browser_process.h"
12#include "chrome/browser/chromeos/login/enrollment/auto_enrollment_controller.h"
13#include "chrome/browser/chromeos/policy/device_cloud_policy_store_chromeos.h"
14#include "chrome/browser/chromeos/policy/proto/chrome_device_policy.pb.h"
15#include "chrome/browser/chromeos/policy/server_backed_state_keys_broker.h"
16#include "chrome/browser/chromeos/settings/device_oauth2_token_service.h"
17#include "chrome/browser/chromeos/settings/device_oauth2_token_service_factory.h"
18#include "chrome/browser/chromeos/settings/device_settings_service.h"
19#include "chromeos/chromeos_switches.h"
20#include "components/policy/core/common/cloud/cloud_policy_constants.h"
21#include "google_apis/gaia/gaia_urls.h"
22#include "net/http/http_status_code.h"
23
24namespace em = enterprise_management;
25
26namespace policy {
27
28namespace {
29
30// Retry for InstallAttrs initialization every 500ms.
31const int kLockRetryIntervalMs = 500;
32// Maximum time to retry InstallAttrs initialization before we give up.
33const int kLockRetryTimeoutMs = 10 * 60 * 1000;  // 10 minutes.
34
35// Testing token used when the enrollment-skip-robot-auth is set to skip talking
36// to GAIA for an actual token. This is needed to be able to run against the
37// testing DMServer implementations.
38const char kTestingRobotToken[] = "test-token";
39
40}  // namespace
41
42EnrollmentHandlerChromeOS::EnrollmentHandlerChromeOS(
43    DeviceCloudPolicyStoreChromeOS* store,
44    EnterpriseInstallAttributes* install_attributes,
45    ServerBackedStateKeysBroker* state_keys_broker,
46    chromeos::DeviceSettingsService* device_settings_service,
47    scoped_ptr<CloudPolicyClient> client,
48    scoped_refptr<base::SequencedTaskRunner> background_task_runner,
49    const std::string& auth_token,
50    const std::string& client_id,
51    bool is_auto_enrollment,
52    const std::string& requisition,
53    const AllowedDeviceModes& allowed_device_modes,
54    em::PolicyData::ManagementMode management_mode,
55    const EnrollmentCallback& completion_callback)
56    : store_(store),
57      install_attributes_(install_attributes),
58      state_keys_broker_(state_keys_broker),
59      device_settings_service_(device_settings_service),
60      client_(client.Pass()),
61      background_task_runner_(background_task_runner),
62      auth_token_(auth_token),
63      client_id_(client_id),
64      is_auto_enrollment_(is_auto_enrollment),
65      requisition_(requisition),
66      allowed_device_modes_(allowed_device_modes),
67      management_mode_(management_mode),
68      completion_callback_(completion_callback),
69      device_mode_(DEVICE_MODE_NOT_SET),
70      enrollment_step_(STEP_PENDING),
71      lockbox_init_duration_(0),
72      weak_ptr_factory_(this) {
73  CHECK(!client_->is_registered());
74  CHECK_EQ(DM_STATUS_SUCCESS, client_->status());
75  CHECK(management_mode_ == em::PolicyData::ENTERPRISE_MANAGED ||
76        management_mode_ == em::PolicyData::CONSUMER_MANAGED);
77  store_->AddObserver(this);
78  client_->AddObserver(this);
79  client_->AddNamespaceToFetch(PolicyNamespaceKey(
80      dm_protocol::kChromeDevicePolicyType, std::string()));
81}
82
83EnrollmentHandlerChromeOS::~EnrollmentHandlerChromeOS() {
84  Stop();
85  store_->RemoveObserver(this);
86}
87
88void EnrollmentHandlerChromeOS::StartEnrollment() {
89  CHECK_EQ(STEP_PENDING, enrollment_step_);
90  enrollment_step_ = STEP_STATE_KEYS;
91  state_keys_broker_->RequestStateKeys(
92      base::Bind(&EnrollmentHandlerChromeOS::HandleStateKeysResult,
93                 weak_ptr_factory_.GetWeakPtr()));
94}
95
96scoped_ptr<CloudPolicyClient> EnrollmentHandlerChromeOS::ReleaseClient() {
97  Stop();
98  return client_.Pass();
99}
100
101void EnrollmentHandlerChromeOS::OnPolicyFetched(CloudPolicyClient* client) {
102  DCHECK_EQ(client_.get(), client);
103  CHECK_EQ(STEP_POLICY_FETCH, enrollment_step_);
104
105  enrollment_step_ = STEP_VALIDATION;
106
107  // Validate the policy.
108  const em::PolicyFetchResponse* policy = client_->GetPolicyFor(
109      PolicyNamespaceKey(dm_protocol::kChromeDevicePolicyType, std::string()));
110  if (!policy) {
111    ReportResult(EnrollmentStatus::ForFetchError(
112        DM_STATUS_RESPONSE_DECODING_ERROR));
113    return;
114  }
115
116  scoped_ptr<DeviceCloudPolicyValidator> validator(
117      DeviceCloudPolicyValidator::Create(
118          scoped_ptr<em::PolicyFetchResponse>(
119              new em::PolicyFetchResponse(*policy)),
120          background_task_runner_));
121
122  validator->ValidateTimestamp(base::Time(), base::Time::NowFromSystemTime(),
123                               CloudPolicyValidatorBase::TIMESTAMP_REQUIRED);
124
125  // If this is re-enrollment, make sure that the new policy matches the
126  // previously-enrolled domain.
127  std::string domain;
128  if (install_attributes_->IsEnterpriseDevice()) {
129    domain = install_attributes_->GetDomain();
130    validator->ValidateDomain(domain);
131  }
132  validator->ValidateDMToken(client->dm_token(),
133                             CloudPolicyValidatorBase::DM_TOKEN_REQUIRED);
134  validator->ValidatePolicyType(dm_protocol::kChromeDevicePolicyType);
135  validator->ValidatePayload();
136  // If |domain| is empty here, the policy validation code will just use the
137  // domain from the username field in the policy itself to do key validation.
138  // TODO(mnissler): Plumb the enrolling user's username into this object so
139  // we can validate the username on the resulting policy, and use the domain
140  // from that username to validate the key below (http://crbug.com/343074).
141  validator->ValidateInitialKey(GetPolicyVerificationKey(), domain);
142  validator.release()->StartValidation(
143      base::Bind(&EnrollmentHandlerChromeOS::HandlePolicyValidationResult,
144                 weak_ptr_factory_.GetWeakPtr()));
145}
146
147void EnrollmentHandlerChromeOS::OnRegistrationStateChanged(
148    CloudPolicyClient* client) {
149  DCHECK_EQ(client_.get(), client);
150
151  if (enrollment_step_ == STEP_REGISTRATION && client_->is_registered()) {
152    enrollment_step_ = STEP_POLICY_FETCH,
153    device_mode_ = client_->device_mode();
154    if (device_mode_ == DEVICE_MODE_NOT_SET)
155      device_mode_ = DEVICE_MODE_ENTERPRISE;
156    if (!allowed_device_modes_.test(device_mode_)) {
157      LOG(ERROR) << "Bad device mode " << device_mode_;
158      ReportResult(EnrollmentStatus::ForStatus(
159          EnrollmentStatus::STATUS_REGISTRATION_BAD_MODE));
160      return;
161    }
162    client_->FetchPolicy();
163  } else {
164    LOG(FATAL) << "Registration state changed to " << client_->is_registered()
165               << " in step " << enrollment_step_ << ".";
166  }
167}
168
169void EnrollmentHandlerChromeOS::OnClientError(CloudPolicyClient* client) {
170  DCHECK_EQ(client_.get(), client);
171
172  if (enrollment_step_ == STEP_ROBOT_AUTH_FETCH) {
173    LOG(ERROR) << "API authentication code fetch failed: "
174               << client_->status();
175    ReportResult(EnrollmentStatus::ForRobotAuthFetchError(client_->status()));
176  } else if (enrollment_step_ < STEP_POLICY_FETCH) {
177    ReportResult(EnrollmentStatus::ForRegistrationError(client_->status()));
178  } else {
179    ReportResult(EnrollmentStatus::ForFetchError(client_->status()));
180  }
181}
182
183void EnrollmentHandlerChromeOS::OnStoreLoaded(CloudPolicyStore* store) {
184  DCHECK_EQ(store_, store);
185
186  if (enrollment_step_ == STEP_LOADING_STORE) {
187    // If the |store_| wasn't initialized when StartEnrollment() was called,
188    // then StartRegistration() bails silently. This gets registration rolling
189    // again after the store finishes loading.
190    StartRegistration();
191  } else if (enrollment_step_ == STEP_STORE_POLICY) {
192    ReportResult(EnrollmentStatus::ForStatus(EnrollmentStatus::STATUS_SUCCESS));
193  }
194}
195
196void EnrollmentHandlerChromeOS::OnStoreError(CloudPolicyStore* store) {
197  DCHECK_EQ(store_, store);
198  if (enrollment_step_ == STEP_STORE_TOKEN_AND_ID) {
199    // Calling DeviceSettingsService::SetManagementSettings() on a non-
200    // enterprise-managed device will trigger OnStoreError(), as
201    // DeviceCloudPolicyStore listens to all changes on DeviceSettingsService,
202    // and it calls OnStoreError() when the device is not enterprise-managed.
203    return;
204  }
205  ReportResult(EnrollmentStatus::ForStoreError(store_->status(),
206                                               store_->validation_status()));
207}
208
209void EnrollmentHandlerChromeOS::HandleStateKeysResult(
210    const std::vector<std::string>& state_keys, bool /* first_boot */) {
211  CHECK_EQ(STEP_STATE_KEYS, enrollment_step_);
212
213  // Make sure state keys are available if forced re-enrollment is on.
214  if (chromeos::AutoEnrollmentController::GetMode() ==
215      chromeos::AutoEnrollmentController::MODE_FORCED_RE_ENROLLMENT) {
216    client_->SetStateKeysToUpload(state_keys);
217    current_state_key_ = state_keys_broker_->current_state_key();
218    if (state_keys.empty() || current_state_key_.empty()) {
219      ReportResult(
220          EnrollmentStatus::ForStatus(EnrollmentStatus::STATUS_NO_STATE_KEYS));
221      return;
222    }
223  }
224
225  enrollment_step_ = STEP_LOADING_STORE;
226  StartRegistration();
227}
228
229void EnrollmentHandlerChromeOS::StartRegistration() {
230  CHECK_EQ(STEP_LOADING_STORE, enrollment_step_);
231  if (store_->is_initialized()) {
232    enrollment_step_ = STEP_REGISTRATION;
233    client_->Register(em::DeviceRegisterRequest::DEVICE,
234                      auth_token_, client_id_, is_auto_enrollment_,
235                      requisition_, current_state_key_);
236  } else {
237    // Do nothing. StartRegistration() will be called again from OnStoreLoaded()
238    // after the CloudPolicyStore has initialized.
239  }
240}
241
242void EnrollmentHandlerChromeOS::HandlePolicyValidationResult(
243    DeviceCloudPolicyValidator* validator) {
244  CHECK_EQ(STEP_VALIDATION, enrollment_step_);
245  if (validator->success()) {
246    policy_ = validator->policy().Pass();
247    username_ = validator->policy_data()->username();
248    device_id_ = validator->policy_data()->device_id();
249    request_token_ = validator->policy_data()->request_token();
250
251    if (CommandLine::ForCurrentProcess()->HasSwitch(
252            chromeos::switches::kEnterpriseEnrollmentSkipRobotAuth)) {
253      // For test purposes we allow enrollment to succeed without proper robot
254      // account and use the provided value as a token.
255      refresh_token_ = kTestingRobotToken;
256      enrollment_step_ = STEP_LOCK_DEVICE;
257      StartLockDevice();
258      return;
259    }
260
261    enrollment_step_ = STEP_ROBOT_AUTH_FETCH;
262    client_->FetchRobotAuthCodes(auth_token_);
263  } else {
264    ReportResult(EnrollmentStatus::ForValidationError(validator->status()));
265  }
266}
267
268void EnrollmentHandlerChromeOS::OnRobotAuthCodesFetched(
269    CloudPolicyClient* client) {
270  DCHECK_EQ(client_.get(), client);
271  CHECK_EQ(STEP_ROBOT_AUTH_FETCH, enrollment_step_);
272
273  enrollment_step_ = STEP_ROBOT_AUTH_REFRESH;
274
275  gaia::OAuthClientInfo client_info;
276  client_info.client_id = GaiaUrls::GetInstance()->oauth2_chrome_client_id();
277  client_info.client_secret =
278      GaiaUrls::GetInstance()->oauth2_chrome_client_secret();
279  client_info.redirect_uri = "oob";
280
281  // Use the system request context to avoid sending user cookies.
282  gaia_oauth_client_.reset(new gaia::GaiaOAuthClient(
283      g_browser_process->system_request_context()));
284  gaia_oauth_client_->GetTokensFromAuthCode(client_info,
285                                            client->robot_api_auth_code(),
286                                            0 /* max_retries */,
287                                            this);
288}
289
290// GaiaOAuthClient::Delegate callback for OAuth2 refresh token fetched.
291void EnrollmentHandlerChromeOS::OnGetTokensResponse(
292    const std::string& refresh_token,
293    const std::string& access_token,
294    int expires_in_seconds) {
295  CHECK_EQ(STEP_ROBOT_AUTH_REFRESH, enrollment_step_);
296
297  refresh_token_ = refresh_token;
298
299  enrollment_step_ = STEP_LOCK_DEVICE;
300  StartLockDevice();
301}
302
303// GaiaOAuthClient::Delegate
304void EnrollmentHandlerChromeOS::OnRefreshTokenResponse(
305    const std::string& access_token,
306    int expires_in_seconds) {
307  // We never use the code that should trigger this callback.
308  LOG(FATAL) << "Unexpected callback invoked.";
309}
310
311// GaiaOAuthClient::Delegate OAuth2 error when fetching refresh token request.
312void EnrollmentHandlerChromeOS::OnOAuthError() {
313  CHECK_EQ(STEP_ROBOT_AUTH_REFRESH, enrollment_step_);
314  // OnOAuthError is only called if the request is bad (malformed) or the
315  // response is bad (empty access token returned).
316  LOG(ERROR) << "OAuth protocol error while fetching API refresh token.";
317  ReportResult(
318      EnrollmentStatus::ForRobotRefreshFetchError(net::HTTP_BAD_REQUEST));
319}
320
321// GaiaOAuthClient::Delegate network error when fetching refresh token.
322void EnrollmentHandlerChromeOS::OnNetworkError(int response_code) {
323  CHECK_EQ(STEP_ROBOT_AUTH_REFRESH, enrollment_step_);
324  LOG(ERROR) << "Network error while fetching API refresh token: "
325             << response_code;
326  ReportResult(
327      EnrollmentStatus::ForRobotRefreshFetchError(response_code));
328}
329
330void EnrollmentHandlerChromeOS::StartLockDevice() {
331  CHECK_EQ(STEP_LOCK_DEVICE, enrollment_step_);
332  // Since this method is also called directly.
333  weak_ptr_factory_.InvalidateWeakPtrs();
334
335  if (management_mode_ == em::PolicyData::CONSUMER_MANAGED) {
336    // Consumer device enrollment doesn't use install attributes. Instead,
337    // we put the information in the owners settings.
338    enrollment_step_ = STEP_STORE_TOKEN_AND_ID;
339    device_settings_service_->SetManagementSettings(
340        management_mode_, request_token_, device_id_,
341        base::Bind(&EnrollmentHandlerChromeOS::HandleSetManagementSettingsDone,
342                   weak_ptr_factory_.GetWeakPtr()));
343  } else {
344    install_attributes_->LockDevice(
345        username_, device_mode_, device_id_,
346        base::Bind(&EnrollmentHandlerChromeOS::HandleLockDeviceResult,
347                   weak_ptr_factory_.GetWeakPtr()));
348  }
349}
350
351void EnrollmentHandlerChromeOS::HandleSetManagementSettingsDone() {
352  CHECK_EQ(STEP_STORE_TOKEN_AND_ID, enrollment_step_);
353  if (device_settings_service_->status() !=
354      chromeos::DeviceSettingsService::STORE_SUCCESS) {
355    ReportResult(EnrollmentStatus::ForStatus(
356        EnrollmentStatus::STATUS_STORE_TOKEN_AND_ID_FAILED));
357    return;
358  }
359
360  StartStoreRobotAuth();
361}
362
363void EnrollmentHandlerChromeOS::HandleLockDeviceResult(
364    EnterpriseInstallAttributes::LockResult lock_result) {
365  CHECK_EQ(STEP_LOCK_DEVICE, enrollment_step_);
366  switch (lock_result) {
367    case EnterpriseInstallAttributes::LOCK_SUCCESS:
368      StartStoreRobotAuth();
369      break;
370    case EnterpriseInstallAttributes::LOCK_NOT_READY:
371      // We wait up to |kLockRetryTimeoutMs| milliseconds and if it hasn't
372      // succeeded by then show an error to the user and stop the enrollment.
373      if (lockbox_init_duration_ < kLockRetryTimeoutMs) {
374        // InstallAttributes not ready yet, retry later.
375        LOG(WARNING) << "Install Attributes not ready yet will retry in "
376                     << kLockRetryIntervalMs << "ms.";
377        base::MessageLoop::current()->PostDelayedTask(
378            FROM_HERE,
379            base::Bind(&EnrollmentHandlerChromeOS::StartLockDevice,
380                       weak_ptr_factory_.GetWeakPtr()),
381            base::TimeDelta::FromMilliseconds(kLockRetryIntervalMs));
382        lockbox_init_duration_ += kLockRetryIntervalMs;
383      } else {
384        ReportResult(EnrollmentStatus::ForStatus(
385            EnrollmentStatus::STATUS_LOCK_TIMEOUT));
386      }
387      break;
388    case EnterpriseInstallAttributes::LOCK_BACKEND_ERROR:
389      ReportResult(EnrollmentStatus::ForStatus(
390          EnrollmentStatus::STATUS_LOCK_ERROR));
391      break;
392    case EnterpriseInstallAttributes::LOCK_WRONG_USER:
393      LOG(ERROR) << "Enrollment cannot proceed because the InstallAttrs "
394                 << "has been locked already!";
395      ReportResult(EnrollmentStatus::ForStatus(
396          EnrollmentStatus::STATUS_LOCK_WRONG_USER));
397      break;
398  }
399}
400
401void EnrollmentHandlerChromeOS::StartStoreRobotAuth() {
402  // Get the token service so we can store our robot refresh token.
403  enrollment_step_ = STEP_STORE_ROBOT_AUTH;
404  chromeos::DeviceOAuth2TokenServiceFactory::Get()->SetAndSaveRefreshToken(
405      refresh_token_,
406      base::Bind(&EnrollmentHandlerChromeOS::HandleStoreRobotAuthTokenResult,
407                 weak_ptr_factory_.GetWeakPtr()));
408}
409
410void EnrollmentHandlerChromeOS::HandleStoreRobotAuthTokenResult(bool result) {
411  CHECK_EQ(STEP_STORE_ROBOT_AUTH, enrollment_step_);
412
413  if (!result) {
414    LOG(ERROR) << "Failed to store API refresh token.";
415    ReportResult(EnrollmentStatus::ForStatus(
416        EnrollmentStatus::STATUS_ROBOT_REFRESH_STORE_FAILED));
417    return;
418  }
419
420  if (management_mode_ == em::PolicyData::CONSUMER_MANAGED) {
421    // For consumer management enrollment, we don't store the policy.
422    ReportResult(EnrollmentStatus::ForStatus(EnrollmentStatus::STATUS_SUCCESS));
423    return;
424  }
425
426  enrollment_step_ = STEP_STORE_POLICY;
427  store_->InstallInitialPolicy(*policy_);
428}
429
430void EnrollmentHandlerChromeOS::Stop() {
431  if (client_.get())
432    client_->RemoveObserver(this);
433  enrollment_step_ = STEP_FINISHED;
434  weak_ptr_factory_.InvalidateWeakPtrs();
435  completion_callback_.Reset();
436}
437
438void EnrollmentHandlerChromeOS::ReportResult(EnrollmentStatus status) {
439  EnrollmentCallback callback = completion_callback_;
440  Stop();
441
442  if (status.status() != EnrollmentStatus::STATUS_SUCCESS) {
443    LOG(WARNING) << "Enrollment failed: " << status.status()
444                 << ", client: " << status.client_status()
445                 << ", validation: " << status.validation_status()
446                 << ", store: " << status.store_status();
447  }
448
449  if (!callback.is_null())
450    callback.Run(status);
451}
452
453}  // namespace policy
454