// Copyright (c) 2012 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/ui/webui/chromeos/login/enrollment_screen_handler.h" #include #include "base/bind.h" #include "base/bind_helpers.h" #include "base/logging.h" #include "base/macros.h" #include "base/strings/stringprintf.h" #include "base/values.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/browser_process_platform_part.h" #include "chrome/browser/chromeos/login/error_screens_histogram_helper.h" #include "chrome/browser/chromeos/login/help_app_launcher.h" #include "chrome/browser/chromeos/login/screens/network_error.h" #include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h" #include "chrome/browser/chromeos/policy/enrollment_status_chromeos.h" #include "chrome/browser/ui/webui/chromeos/login/oobe_screen.h" #include "chrome/grit/generated_resources.h" #include "chromeos/network/network_state.h" #include "chromeos/network/network_state_handler.h" #include "components/login/localized_values_builder.h" #include "components/policy/core/browser/cloud/message_util.h" #include "google_apis/gaia/gaia_auth_util.h" #include "google_apis/gaia/gaia_urls.h" #include "google_apis/gaia/google_service_auth_error.h" #include "ui/base/l10n/l10n_util.h" namespace chromeos { namespace { const char kJsScreenPath[] = "login.OAuthEnrollmentScreen"; // Enrollment step names. const char kEnrollmentStepSignin[] = "signin"; const char kEnrollmentStepSuccess[] = "success"; const char kEnrollmentStepWorking[] = "working"; // Enrollment mode constants used in the UI. This needs to be kept in sync with // oobe_screen_oauth_enrollment.js. const char kEnrollmentModeUIForced[] = "forced"; const char kEnrollmentModeUIManual[] = "manual"; const char kEnrollmentModeUIRecovery[] = "recovery"; // Converts |mode| to a mode identifier for the UI. std::string EnrollmentModeToUIMode(policy::EnrollmentConfig::Mode mode) { switch (mode) { case policy::EnrollmentConfig::MODE_NONE: break; case policy::EnrollmentConfig::MODE_MANUAL: case policy::EnrollmentConfig::MODE_MANUAL_REENROLLMENT: case policy::EnrollmentConfig::MODE_LOCAL_ADVERTISED: case policy::EnrollmentConfig::MODE_SERVER_ADVERTISED: return kEnrollmentModeUIManual; case policy::EnrollmentConfig::MODE_LOCAL_FORCED: case policy::EnrollmentConfig::MODE_SERVER_FORCED: return kEnrollmentModeUIForced; case policy::EnrollmentConfig::MODE_RECOVERY: return kEnrollmentModeUIRecovery; } NOTREACHED() << "Bad enrollment mode " << mode; return kEnrollmentModeUIManual; } // Returns network name by service path. std::string GetNetworkName(const std::string& service_path) { const NetworkState* network = NetworkHandler::Get()->network_state_handler()->GetNetworkState( service_path); if (!network) return std::string(); return network->name(); } bool IsBehindCaptivePortal(NetworkStateInformer::State state, NetworkError::ErrorReason reason) { return state == NetworkStateInformer::CAPTIVE_PORTAL || reason == NetworkError::ERROR_REASON_PORTAL_DETECTED; } bool IsProxyError(NetworkStateInformer::State state, NetworkError::ErrorReason reason) { return state == NetworkStateInformer::PROXY_AUTH_REQUIRED || reason == NetworkError::ERROR_REASON_PROXY_AUTH_CANCELLED || reason == NetworkError::ERROR_REASON_PROXY_CONNECTION_FAILED; } } // namespace // EnrollmentScreenHandler, public ------------------------------ EnrollmentScreenHandler::EnrollmentScreenHandler( const scoped_refptr& network_state_informer, NetworkErrorModel* network_error_model) : BaseScreenHandler(kJsScreenPath), controller_(NULL), show_on_init_(false), first_show_(true), observe_network_failure_(false), network_state_informer_(network_state_informer), network_error_model_(network_error_model), histogram_helper_(new ErrorScreensHistogramHelper("Enrollment")), weak_ptr_factory_(this) { set_async_assets_load_id( GetOobeScreenName(OobeScreen::SCREEN_OOBE_ENROLLMENT)); DCHECK(network_state_informer_.get()); DCHECK(network_error_model_); network_state_informer_->AddObserver(this); } EnrollmentScreenHandler::~EnrollmentScreenHandler() { network_state_informer_->RemoveObserver(this); } // EnrollmentScreenHandler, WebUIMessageHandler implementation -- void EnrollmentScreenHandler::RegisterMessages() { AddCallback("oauthEnrollClose", &EnrollmentScreenHandler::HandleClose); AddCallback("oauthEnrollCompleteLogin", &EnrollmentScreenHandler::HandleCompleteLogin); AddCallback("oauthEnrollRetry", &EnrollmentScreenHandler::HandleRetry); AddCallback("frameLoadingCompleted", &EnrollmentScreenHandler::HandleFrameLoadingCompleted); AddCallback("oauthEnrollAttributes", &EnrollmentScreenHandler::HandleDeviceAttributesProvided); AddCallback("oauthEnrollOnLearnMore", &EnrollmentScreenHandler::HandleOnLearnMore); } // EnrollmentScreenHandler // EnrollmentScreenActor implementation ----------------------------------- void EnrollmentScreenHandler::SetParameters( Controller* controller, const policy::EnrollmentConfig& config) { CHECK_NE(policy::EnrollmentConfig::MODE_NONE, config.mode); controller_ = controller; config_ = config; } void EnrollmentScreenHandler::PrepareToShow() { } void EnrollmentScreenHandler::Show() { if (!page_is_ready()) show_on_init_ = true; else DoShow(); } void EnrollmentScreenHandler::Hide() { } void EnrollmentScreenHandler::ShowSigninScreen() { observe_network_failure_ = true; ShowStep(kEnrollmentStepSignin); } void EnrollmentScreenHandler::ShowAttributePromptScreen( const std::string& asset_id, const std::string& location) { CallJS("showAttributePromptStep", asset_id, location); } void EnrollmentScreenHandler::ShowEnrollmentSpinnerScreen() { ShowStep(kEnrollmentStepWorking); } void EnrollmentScreenHandler::ShowAuthError( const GoogleServiceAuthError& error) { switch (error.state()) { case GoogleServiceAuthError::NONE: case GoogleServiceAuthError::CAPTCHA_REQUIRED: case GoogleServiceAuthError::TWO_FACTOR: case GoogleServiceAuthError::HOSTED_NOT_ALLOWED: case GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS: case GoogleServiceAuthError::REQUEST_CANCELED: case GoogleServiceAuthError::UNEXPECTED_SERVICE_RESPONSE: case GoogleServiceAuthError::SERVICE_ERROR: case GoogleServiceAuthError::WEB_LOGIN_REQUIRED: ShowError(IDS_ENTERPRISE_ENROLLMENT_AUTH_FATAL_ERROR, false); return; case GoogleServiceAuthError::USER_NOT_SIGNED_UP: case GoogleServiceAuthError::ACCOUNT_DELETED: case GoogleServiceAuthError::ACCOUNT_DISABLED: ShowError(IDS_ENTERPRISE_ENROLLMENT_AUTH_ACCOUNT_ERROR, true); return; case GoogleServiceAuthError::CONNECTION_FAILED: case GoogleServiceAuthError::SERVICE_UNAVAILABLE: ShowError(IDS_ENTERPRISE_ENROLLMENT_AUTH_NETWORK_ERROR, true); return; case GoogleServiceAuthError::NUM_STATES: break; } NOTREACHED(); } void EnrollmentScreenHandler::ShowOtherError( EnterpriseEnrollmentHelper::OtherError error) { switch (error) { case EnterpriseEnrollmentHelper::OTHER_ERROR_DOMAIN_MISMATCH: ShowError(IDS_ENTERPRISE_ENROLLMENT_STATUS_LOCK_WRONG_USER, true); return; case EnterpriseEnrollmentHelper::OTHER_ERROR_FATAL: ShowError(IDS_ENTERPRISE_ENROLLMENT_FATAL_ENROLLMENT_ERROR, true); return; } NOTREACHED(); } void EnrollmentScreenHandler::ShowEnrollmentStatus( policy::EnrollmentStatus status) { switch (status.status()) { case policy::EnrollmentStatus::STATUS_SUCCESS: ShowStep(kEnrollmentStepSuccess); return; case policy::EnrollmentStatus::STATUS_NO_STATE_KEYS: ShowError(IDS_ENTERPRISE_ENROLLMENT_STATUS_NO_STATE_KEYS, false); return; case policy::EnrollmentStatus::STATUS_REGISTRATION_FAILED: // Some special cases for generating a nicer message that's more helpful. switch (status.client_status()) { case policy::DM_STATUS_SERVICE_MANAGEMENT_NOT_SUPPORTED: ShowError(IDS_ENTERPRISE_ENROLLMENT_ACCOUNT_ERROR, true); break; case policy::DM_STATUS_SERVICE_MISSING_LICENSES: ShowError(IDS_ENTERPRISE_ENROLLMENT_MISSING_LICENSES_ERROR, true); break; case policy::DM_STATUS_SERVICE_DEPROVISIONED: ShowError(IDS_ENTERPRISE_ENROLLMENT_DEPROVISIONED_ERROR, true); break; case policy::DM_STATUS_SERVICE_DOMAIN_MISMATCH: ShowError(IDS_ENTERPRISE_ENROLLMENT_DOMAIN_MISMATCH_ERROR, true); break; default: ShowErrorMessage( l10n_util::GetStringFUTF8( IDS_ENTERPRISE_ENROLLMENT_STATUS_REGISTRATION_FAILED, policy::FormatDeviceManagementStatus(status.client_status())), true); } return; case policy::EnrollmentStatus::STATUS_ROBOT_AUTH_FETCH_FAILED: ShowError(IDS_ENTERPRISE_ENROLLMENT_ROBOT_AUTH_FETCH_FAILED, true); return; case policy::EnrollmentStatus::STATUS_ROBOT_REFRESH_FETCH_FAILED: ShowError(IDS_ENTERPRISE_ENROLLMENT_ROBOT_REFRESH_FETCH_FAILED, true); return; case policy::EnrollmentStatus::STATUS_ROBOT_REFRESH_STORE_FAILED: ShowError(IDS_ENTERPRISE_ENROLLMENT_ROBOT_REFRESH_STORE_FAILED, true); return; case policy::EnrollmentStatus::STATUS_REGISTRATION_BAD_MODE: ShowError(IDS_ENTERPRISE_ENROLLMENT_STATUS_REGISTRATION_BAD_MODE, false); return; case policy::EnrollmentStatus::STATUS_POLICY_FETCH_FAILED: ShowErrorMessage( l10n_util::GetStringFUTF8( IDS_ENTERPRISE_ENROLLMENT_STATUS_POLICY_FETCH_FAILED, policy::FormatDeviceManagementStatus(status.client_status())), true); return; case policy::EnrollmentStatus::STATUS_VALIDATION_FAILED: ShowErrorMessage( l10n_util::GetStringFUTF8( IDS_ENTERPRISE_ENROLLMENT_STATUS_VALIDATION_FAILED, policy::FormatValidationStatus(status.validation_status())), true); return; case policy::EnrollmentStatus::STATUS_LOCK_ERROR: switch (status.lock_status()) { case policy::EnterpriseInstallAttributes::LOCK_SUCCESS: case policy::EnterpriseInstallAttributes::LOCK_NOT_READY: // LOCK_SUCCESS is in contradiction of STATUS_LOCK_ERROR. // LOCK_NOT_READY is transient, if retries are given up, LOCK_TIMEOUT // is reported instead. This piece of code is unreached. LOG(FATAL) << "Invalid lock status."; return; case policy::EnterpriseInstallAttributes::LOCK_TIMEOUT: ShowError(IDS_ENTERPRISE_ENROLLMENT_STATUS_LOCK_TIMEOUT, false); return; case policy::EnterpriseInstallAttributes::LOCK_BACKEND_INVALID: case policy::EnterpriseInstallAttributes::LOCK_ALREADY_LOCKED: case policy::EnterpriseInstallAttributes::LOCK_SET_ERROR: case policy::EnterpriseInstallAttributes::LOCK_FINALIZE_ERROR: case policy::EnterpriseInstallAttributes::LOCK_READBACK_ERROR: ShowError(IDS_ENTERPRISE_ENROLLMENT_STATUS_LOCK_ERROR, false); return; case policy::EnterpriseInstallAttributes::LOCK_WRONG_DOMAIN: ShowError(IDS_ENTERPRISE_ENROLLMENT_STATUS_LOCK_WRONG_USER, true); return; case policy::EnterpriseInstallAttributes::LOCK_WRONG_MODE: ShowError(IDS_ENTERPRISE_ENROLLMENT_STATUS_LOCK_WRONG_MODE, true); return; } NOTREACHED(); return; case policy::EnrollmentStatus::STATUS_STORE_ERROR: ShowErrorMessage( l10n_util::GetStringFUTF8( IDS_ENTERPRISE_ENROLLMENT_STATUS_STORE_ERROR, policy::FormatStoreStatus(status.store_status(), status.validation_status())), true); return; case policy::EnrollmentStatus::STATUS_STORE_TOKEN_AND_ID_FAILED: // This error should not happen for enterprise enrollment. ShowError(IDS_ENTERPRISE_ENROLLMENT_STATUS_STORE_TOKEN_AND_ID_FAILED, true); NOTREACHED(); return; case policy::EnrollmentStatus::STATUS_ATTRIBUTE_UPDATE_FAILED: ShowError(IDS_ENTERPRISE_ENROLLMENT_ATTRIBUTE_ERROR, false); return; } NOTREACHED(); } // EnrollmentScreenHandler BaseScreenHandler implementation ----- void EnrollmentScreenHandler::Initialize() { if (show_on_init_) { Show(); show_on_init_ = false; } } void EnrollmentScreenHandler::DeclareLocalizedValues( ::login::LocalizedValuesBuilder* builder) { builder->Add("oauthEnrollScreenTitle", IDS_ENTERPRISE_ENROLLMENT_SCREEN_TITLE); builder->Add("oauthEnrollRetry", IDS_ENTERPRISE_ENROLLMENT_RETRY); builder->Add("oauthEnrollDone", IDS_ENTERPRISE_ENROLLMENT_DONE); builder->Add("oauthEnrollNextBtn", IDS_OFFLINE_LOGIN_NEXT_BUTTON_TEXT); builder->Add("oauthEnrollSkip", IDS_ENTERPRISE_ENROLLMENT_SKIP); builder->Add("oauthEnrollSuccess", IDS_ENTERPRISE_ENROLLMENT_SUCCESS); builder->Add("oauthEnrollDeviceInformation", IDS_ENTERPRISE_ENROLLMENT_DEVICE_INFORMATION); builder->Add("oauthEnrollExplaneAttributeLink", IDS_ENTERPRISE_ENROLLMENT_EXPLAIN_ATTRIBUTE_LINK); builder->Add("oauthEnrollAttributeExplanation", IDS_ENTERPRISE_ENROLLMENT_ATTRIBUTE_EXPLANATION); builder->Add("oauthEnrollAssetIdLabel", IDS_ENTERPRISE_ENROLLMENT_ASSET_ID_LABEL); builder->Add("oauthEnrollLocationLabel", IDS_ENTERPRISE_ENROLLMENT_LOCATION_LABEL); } bool EnrollmentScreenHandler::IsOnEnrollmentScreen() const { return (GetCurrentScreen() == OobeScreen::SCREEN_OOBE_ENROLLMENT); } bool EnrollmentScreenHandler::IsEnrollmentScreenHiddenByError() const { return (GetCurrentScreen() == OobeScreen::SCREEN_ERROR_MESSAGE && network_error_model_->GetParentScreen() == OobeScreen::SCREEN_OOBE_ENROLLMENT); } void EnrollmentScreenHandler::UpdateState(NetworkError::ErrorReason reason) { UpdateStateInternal(reason, false); } // TODO(rsorokin): This function is mostly copied from SigninScreenHandler and // should be refactored in the future. void EnrollmentScreenHandler::UpdateStateInternal( NetworkError::ErrorReason reason, bool force_update) { if (!force_update && !IsOnEnrollmentScreen() && !IsEnrollmentScreenHiddenByError()) { return; } if (!force_update && !observe_network_failure_) return; NetworkStateInformer::State state = network_state_informer_->state(); const std::string network_path = network_state_informer_->network_path(); const bool is_online = (state == NetworkStateInformer::ONLINE); const bool is_behind_captive_portal = (state == NetworkStateInformer::CAPTIVE_PORTAL); const bool is_frame_error = reason == NetworkError::ERROR_REASON_FRAME_ERROR; LOG(WARNING) << "EnrollmentScreenHandler::UpdateState(): " << "state=" << NetworkStateInformer::StatusString(state) << ", " << "reason=" << NetworkError::ErrorReasonString(reason); if (is_online || !is_behind_captive_portal) network_error_model_->HideCaptivePortal(); if (is_frame_error) { LOG(WARNING) << "Retry page load"; // TODO(rsorokin): Too many consecutive reloads. CallJS("doReload"); } if (!is_online || is_frame_error) SetupAndShowOfflineMessage(state, reason); else HideOfflineMessage(state, reason); } void EnrollmentScreenHandler::SetupAndShowOfflineMessage( NetworkStateInformer::State state, NetworkError::ErrorReason reason) { const std::string network_path = network_state_informer_->network_path(); const bool is_behind_captive_portal = IsBehindCaptivePortal(state, reason); const bool is_proxy_error = IsProxyError(state, reason); const bool is_frame_error = reason == NetworkError::ERROR_REASON_FRAME_ERROR; if (is_proxy_error) { network_error_model_->SetErrorState(NetworkError::ERROR_STATE_PROXY, std::string()); } else if (is_behind_captive_portal) { // Do not bother a user with obsessive captive portal showing. This // check makes captive portal being shown only once: either when error // screen is shown for the first time or when switching from another // error screen (offline, proxy). if (IsOnEnrollmentScreen() || (network_error_model_->GetErrorState() != NetworkError::ERROR_STATE_PORTAL)) { network_error_model_->FixCaptivePortal(); } const std::string network_name = GetNetworkName(network_path); network_error_model_->SetErrorState(NetworkError::ERROR_STATE_PORTAL, network_name); } else if (is_frame_error) { network_error_model_->SetErrorState( NetworkError::ERROR_STATE_AUTH_EXT_TIMEOUT, std::string()); } else { network_error_model_->SetErrorState(NetworkError::ERROR_STATE_OFFLINE, std::string()); } if (GetCurrentScreen() != OobeScreen::SCREEN_ERROR_MESSAGE) { const std::string network_type = network_state_informer_->network_type(); network_error_model_->SetUIState(NetworkError::UI_STATE_SIGNIN); network_error_model_->SetParentScreen(OobeScreen::SCREEN_OOBE_ENROLLMENT); network_error_model_->SetHideCallback(base::Bind( &EnrollmentScreenHandler::DoShow, weak_ptr_factory_.GetWeakPtr())); network_error_model_->Show(); histogram_helper_->OnErrorShow(network_error_model_->GetErrorState()); } } void EnrollmentScreenHandler::HideOfflineMessage( NetworkStateInformer::State state, NetworkError::ErrorReason reason) { if (IsEnrollmentScreenHiddenByError()) network_error_model_->Hide(); histogram_helper_->OnErrorHide(); } // EnrollmentScreenHandler, private ----------------------------- void EnrollmentScreenHandler::HandleClose(const std::string& reason) { DCHECK(controller_); if (reason == "cancel") controller_->OnCancel(); else if (reason == "done") controller_->OnConfirmationClosed(); else NOTREACHED(); } void EnrollmentScreenHandler::HandleCompleteLogin( const std::string& user, const std::string& auth_code) { observe_network_failure_ = false; DCHECK(controller_); controller_->OnLoginDone(gaia::SanitizeEmail(user), auth_code); } void EnrollmentScreenHandler::HandleRetry() { DCHECK(controller_); controller_->OnRetry(); } void EnrollmentScreenHandler::HandleFrameLoadingCompleted() { if (network_state_informer_->state() != NetworkStateInformer::ONLINE) return; UpdateState(NetworkError::ERROR_REASON_UPDATE); } void EnrollmentScreenHandler::HandleDeviceAttributesProvided( const std::string& asset_id, const std::string& location) { controller_->OnDeviceAttributeProvided(asset_id, location); } void EnrollmentScreenHandler::HandleOnLearnMore() { if (!help_app_.get()) help_app_ = new HelpAppLauncher(GetNativeWindow()); help_app_->ShowHelpTopic(HelpAppLauncher::HELP_DEVICE_ATTRIBUTES); } void EnrollmentScreenHandler::ShowStep(const char* step) { CallJS("showStep", std::string(step)); } void EnrollmentScreenHandler::ShowError(int message_id, bool retry) { ShowErrorMessage(l10n_util::GetStringUTF8(message_id), retry); } void EnrollmentScreenHandler::ShowErrorMessage(const std::string& message, bool retry) { CallJS("showError", message, retry); } void EnrollmentScreenHandler::DoShow() { base::DictionaryValue screen_data; screen_data.SetString("gaiaUrl", GaiaUrls::GetInstance()->gaia_url().spec()); screen_data.SetString("clientId", GaiaUrls::GetInstance()->oauth2_chrome_client_id()); screen_data.SetString("enrollment_mode", EnrollmentModeToUIMode(config_.mode)); screen_data.SetString("management_domain", config_.management_domain); const bool cfm = g_browser_process->platform_part() ->browser_policy_connector_chromeos() ->GetDeviceCloudPolicyManager() ->IsRemoraRequisition(); screen_data.SetString("flow", cfm ? "cfm" : "enterprise"); ShowScreenWithData(OobeScreen::SCREEN_OOBE_ENROLLMENT, &screen_data); if (first_show_) { first_show_ = false; UpdateStateInternal(NetworkError::ERROR_REASON_UPDATE, true); } histogram_helper_->OnScreenShow(); } } // namespace chromeos