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