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