1 // Copyright 2013 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/ui/webui/chromeos/login/gaia_screen_handler.h" 6 7 #include "base/logging.h" 8 #include "base/metrics/histogram.h" 9 #include "base/strings/utf_string_conversions.h" 10 #include "base/values.h" 11 #include "chrome/browser/browser_process.h" 12 #include "chrome/browser/browser_shutdown.h" 13 #include "chrome/browser/chromeos/login/ui/login_display_host_impl.h" 14 #include "chrome/browser/chromeos/login/ui/user_adding_screen.h" 15 #include "chrome/browser/chromeos/login/users/user_manager.h" 16 #include "chrome/browser/chromeos/profiles/profile_helper.h" 17 #include "chrome/browser/chromeos/settings/cros_settings.h" 18 #include "chrome/browser/io_thread.h" 19 #include "chrome/browser/ui/webui/chromeos/login/signin_screen_handler.h" 20 #include "chromeos/chromeos_switches.h" 21 #include "chromeos/settings/cros_settings_names.h" 22 #include "content/public/browser/browser_thread.h" 23 #include "content/public/browser/render_frame_host.h" 24 #include "google_apis/gaia/gaia_auth_util.h" 25 #include "google_apis/gaia/gaia_switches.h" 26 #include "google_apis/gaia/gaia_urls.h" 27 #include "grit/chromium_strings.h" 28 #include "grit/generated_resources.h" 29 #include "ui/base/l10n/l10n_util.h" 30 31 using content::BrowserThread; 32 33 namespace chromeos { 34 35 namespace { 36 37 const char kJsScreenPath[] = "login.GaiaSigninScreen"; 38 39 void UpdateAuthParams(base::DictionaryValue* params, bool has_users) { 40 CrosSettings* cros_settings = CrosSettings::Get(); 41 bool allow_new_user = true; 42 cros_settings->GetBoolean(kAccountsPrefAllowNewUser, &allow_new_user); 43 bool allow_guest = true; 44 cros_settings->GetBoolean(kAccountsPrefAllowGuest, &allow_guest); 45 // Account creation depends on Guest sign-in (http://crosbug.com/24570). 46 params->SetBoolean("createAccount", allow_new_user && allow_guest); 47 params->SetBoolean("guestSignin", allow_guest); 48 49 // Allow locally managed user creation only if: 50 // 1. Enterprise managed device > is allowed by policy. 51 // 2. Consumer device > owner exists. 52 // 3. New users are allowed by owner. 53 // 4. Supervised users are allowed by owner. 54 bool managed_users_allowed = 55 UserManager::Get()->AreLocallyManagedUsersAllowed(); 56 bool managed_users_can_create = true; 57 int message_id = -1; 58 if (!has_users) { 59 managed_users_can_create = false; 60 message_id = IDS_CREATE_LOCALLY_MANAGED_USER_NO_MANAGER_TEXT; 61 } 62 if (!allow_new_user || !managed_users_allowed) { 63 managed_users_can_create = false; 64 message_id = IDS_CREATE_LOCALLY_MANAGED_USER_CREATION_RESTRICTED_TEXT; 65 } 66 67 params->SetBoolean("managedUsersEnabled", managed_users_allowed); 68 params->SetBoolean("managedUsersCanCreate", managed_users_can_create); 69 if (!managed_users_can_create) { 70 params->SetString("managedUsersRestrictionReason", 71 l10n_util::GetStringUTF16(message_id)); 72 } 73 74 // Now check whether we're in multi-profiles user adding scenario and 75 // disable GAIA right panel features if that's the case. 76 if (UserAddingScreen::Get()->IsRunning()) { 77 params->SetBoolean("createAccount", false); 78 params->SetBoolean("guestSignin", false); 79 params->SetBoolean("managedUsersEnabled", false); 80 } 81 } 82 83 void RecordSAMLScrapingVerificationResultInHistogram(bool success) { 84 UMA_HISTOGRAM_BOOLEAN("ChromeOS.SAML.Scraping.VerificationResult", success); 85 } 86 87 // The Task posted to PostTaskAndReply in StartClearingDnsCache on the IO 88 // thread. 89 void ClearDnsCache(IOThread* io_thread) { 90 DCHECK_CURRENTLY_ON(BrowserThread::IO); 91 if (browser_shutdown::IsTryingToQuit()) 92 return; 93 94 io_thread->ClearHostCache(); 95 } 96 97 } // namespace 98 99 GaiaContext::GaiaContext() 100 : force_reload(false), 101 is_local(false), 102 password_changed(false), 103 show_users(false), 104 use_offline(false), 105 has_users(false) {} 106 107 GaiaScreenHandler::GaiaScreenHandler( 108 const scoped_refptr<NetworkStateInformer>& network_state_informer) 109 : BaseScreenHandler(kJsScreenPath), 110 frame_state_(FRAME_STATE_UNKNOWN), 111 frame_error_(net::OK), 112 network_state_informer_(network_state_informer), 113 dns_cleared_(false), 114 dns_clear_task_running_(false), 115 cookies_cleared_(false), 116 focus_stolen_(false), 117 gaia_silent_load_(false), 118 using_saml_api_(false), 119 test_expects_complete_login_(false), 120 signin_screen_handler_(NULL), 121 weak_factory_(this) { 122 DCHECK(network_state_informer_.get()); 123 } 124 125 GaiaScreenHandler::~GaiaScreenHandler() { 126 } 127 128 void GaiaScreenHandler::LoadGaia(const GaiaContext& context) { 129 base::DictionaryValue params; 130 131 params.SetBoolean("forceReload", context.force_reload); 132 params.SetBoolean("isLocal", context.is_local); 133 params.SetBoolean("passwordChanged", context.password_changed); 134 params.SetBoolean("isShowUsers", context.show_users); 135 params.SetBoolean("useOffline", context.use_offline); 136 params.SetString("email", context.email); 137 138 UpdateAuthParams(¶ms, context.has_users); 139 140 if (!context.use_offline) { 141 const std::string app_locale = g_browser_process->GetApplicationLocale(); 142 if (!app_locale.empty()) 143 params.SetString("hl", app_locale); 144 } else { 145 base::DictionaryValue* localized_strings = new base::DictionaryValue(); 146 localized_strings->SetString( 147 "stringEmail", l10n_util::GetStringUTF16(IDS_LOGIN_OFFLINE_EMAIL)); 148 localized_strings->SetString( 149 "stringPassword", 150 l10n_util::GetStringUTF16(IDS_LOGIN_OFFLINE_PASSWORD)); 151 localized_strings->SetString( 152 "stringSignIn", l10n_util::GetStringUTF16(IDS_LOGIN_OFFLINE_SIGNIN)); 153 localized_strings->SetString( 154 "stringEmptyEmail", 155 l10n_util::GetStringUTF16(IDS_LOGIN_OFFLINE_EMPTY_EMAIL)); 156 localized_strings->SetString( 157 "stringEmptyPassword", 158 l10n_util::GetStringUTF16(IDS_LOGIN_OFFLINE_EMPTY_PASSWORD)); 159 localized_strings->SetString( 160 "stringError", l10n_util::GetStringUTF16(IDS_LOGIN_OFFLINE_ERROR)); 161 params.Set("localizedStrings", localized_strings); 162 } 163 164 CommandLine* command_line = CommandLine::ForCurrentProcess(); 165 166 const GURL gaia_url = 167 command_line->HasSwitch(::switches::kGaiaUrl) 168 ? GURL(command_line->GetSwitchValueASCII(::switches::kGaiaUrl)) 169 : GaiaUrls::GetInstance()->gaia_url(); 170 params.SetString("gaiaUrl", gaia_url.spec()); 171 172 if (command_line->HasSwitch(chromeos::switches::kEnableEmbeddedSignin)) 173 params.SetBoolean("useEmbedded", true); 174 175 frame_state_ = FRAME_STATE_LOADING; 176 CallJS("loadAuthExtension", params); 177 } 178 179 void GaiaScreenHandler::UpdateGaia(const GaiaContext& context) { 180 base::DictionaryValue params; 181 UpdateAuthParams(¶ms, context.has_users); 182 CallJS("updateAuthExtension", params); 183 } 184 185 void GaiaScreenHandler::ReloadGaia() { 186 if (frame_state_ == FRAME_STATE_LOADING) 187 return; 188 NetworkStateInformer::State state = network_state_informer_->state(); 189 if (state != NetworkStateInformer::ONLINE) { 190 LOG(WARNING) << "Skipping reloading of Gaia since " 191 << "network state=" 192 << NetworkStateInformer::StatusString(state); 193 return; 194 } 195 LOG(WARNING) << "Reloading Gaia."; 196 frame_state_ = FRAME_STATE_LOADING; 197 CallJS("doReload"); 198 } 199 200 void GaiaScreenHandler::DeclareLocalizedValues( 201 LocalizedValuesBuilder* builder) { 202 builder->Add("signinScreenTitle", IDS_SIGNIN_SCREEN_TITLE); 203 builder->Add("signinScreenPasswordChanged", 204 IDS_SIGNIN_SCREEN_PASSWORD_CHANGED); 205 builder->Add("createAccount", IDS_CREATE_ACCOUNT_HTML); 206 builder->Add("guestSignin", IDS_BROWSE_WITHOUT_SIGNING_IN_HTML); 207 builder->Add("createLocallyManagedUser", 208 IDS_CREATE_LOCALLY_MANAGED_USER_HTML); 209 builder->Add("createManagedUserFeatureName", 210 IDS_CREATE_LOCALLY_MANAGED_USER_FEATURE_NAME); 211 212 // Strings used by the SAML fatal error dialog. 213 builder->Add("fatalErrorMessageNoEmail", IDS_LOGIN_FATAL_ERROR_NO_EMAIL); 214 builder->Add("fatalErrorMessageNoPassword", 215 IDS_LOGIN_FATAL_ERROR_NO_PASSWORD); 216 builder->Add("fatalErrorMessageVerificationFailed", 217 IDS_LOGIN_FATAL_ERROR_PASSWORD_VERIFICATION); 218 builder->Add("fatalErrorMessageInsecureURL", 219 IDS_LOGIN_FATAL_ERROR_TEXT_INSECURE_URL); 220 builder->Add("fatalErrorInstructions", IDS_LOGIN_FATAL_ERROR_INSTRUCTIONS); 221 builder->Add("fatalErrorDismissButton", IDS_OK); 222 } 223 224 void GaiaScreenHandler::Initialize() { 225 } 226 227 void GaiaScreenHandler::RegisterMessages() { 228 AddCallback("frameLoadingCompleted", 229 &GaiaScreenHandler::HandleFrameLoadingCompleted); 230 AddCallback("completeLogin", &GaiaScreenHandler::HandleCompleteLogin); 231 AddCallback("completeAuthentication", 232 &GaiaScreenHandler::HandleCompleteAuthentication); 233 AddCallback("usingSAMLAPI", &GaiaScreenHandler::HandleUsingSAMLAPI); 234 AddCallback("scrapedPasswordCount", 235 &GaiaScreenHandler::HandleScrapedPasswordCount); 236 AddCallback("scrapedPasswordVerificationFailed", 237 &GaiaScreenHandler::HandleScrapedPasswordVerificationFailed); 238 AddCallback("loginWebuiReady", &GaiaScreenHandler::HandleGaiaUIReady); 239 } 240 241 void GaiaScreenHandler::HandleFrameLoadingCompleted(int status) { 242 const net::Error frame_error = static_cast<net::Error>(-status); 243 if (frame_error == net::ERR_ABORTED) { 244 LOG(WARNING) << "Ignoring Gaia frame error: " << frame_error; 245 return; 246 } 247 frame_error_ = frame_error; 248 if (frame_error == net::OK) { 249 VLOG(1) << "Gaia is loaded"; 250 frame_state_ = FRAME_STATE_LOADED; 251 } else { 252 LOG(WARNING) << "Gaia frame error: " << frame_error_; 253 frame_state_ = FRAME_STATE_ERROR; 254 } 255 256 if (network_state_informer_->state() != NetworkStateInformer::ONLINE) 257 return; 258 if (frame_state_ == FRAME_STATE_LOADED) 259 UpdateState(ErrorScreenActor::ERROR_REASON_UPDATE); 260 else if (frame_state_ == FRAME_STATE_ERROR) 261 UpdateState(ErrorScreenActor::ERROR_REASON_FRAME_ERROR); 262 } 263 264 void GaiaScreenHandler::HandleCompleteAuthentication( 265 const std::string& email, 266 const std::string& password, 267 const std::string& auth_code) { 268 if (!Delegate()) 269 return; 270 Delegate()->SetDisplayEmail(gaia::SanitizeEmail(email)); 271 UserContext user_context(email); 272 user_context.SetKey(Key(password)); 273 user_context.SetAuthCode(auth_code); 274 Delegate()->CompleteLogin(user_context); 275 } 276 277 void GaiaScreenHandler::HandleCompleteLogin(const std::string& typed_email, 278 const std::string& password, 279 bool using_saml) { 280 if (!Delegate()) 281 return; 282 283 if (using_saml && !using_saml_api_) 284 RecordSAMLScrapingVerificationResultInHistogram(true); 285 286 const std::string sanitized_email = gaia::SanitizeEmail(typed_email); 287 Delegate()->SetDisplayEmail(sanitized_email); 288 UserContext user_context(sanitized_email); 289 user_context.SetKey(Key(password)); 290 user_context.SetAuthFlow(using_saml 291 ? UserContext::AUTH_FLOW_GAIA_WITH_SAML 292 : UserContext::AUTH_FLOW_GAIA_WITHOUT_SAML); 293 Delegate()->CompleteLogin(user_context); 294 295 if (test_expects_complete_login_) { 296 VLOG(2) << "Complete test login for " << typed_email 297 << ", requested=" << test_user_; 298 299 test_expects_complete_login_ = false; 300 test_user_.clear(); 301 test_pass_.clear(); 302 } 303 } 304 305 void GaiaScreenHandler::HandleUsingSAMLAPI() { 306 SetSAMLPrincipalsAPIUsed(true); 307 } 308 309 void GaiaScreenHandler::HandleScrapedPasswordCount(int password_count) { 310 SetSAMLPrincipalsAPIUsed(false); 311 // Use a histogram that has 11 buckets, one for each of the values in [0, 9] 312 // and an overflow bucket at the end. 313 UMA_HISTOGRAM_ENUMERATION( 314 "ChromeOS.SAML.Scraping.PasswordCount", std::min(password_count, 10), 11); 315 if (password_count == 0) 316 HandleScrapedPasswordVerificationFailed(); 317 } 318 319 void GaiaScreenHandler::HandleScrapedPasswordVerificationFailed() { 320 RecordSAMLScrapingVerificationResultInHistogram(false); 321 } 322 323 void GaiaScreenHandler::HandleGaiaUIReady() { 324 if (focus_stolen_) { 325 // Set focus to the Gaia page. 326 // TODO(altimofeev): temporary solution, until focus parameters are 327 // implemented on the Gaia side. 328 // Do this only once. Any subsequent call would relod GAIA frame. 329 focus_stolen_ = false; 330 const char code[] = 331 "if (typeof gWindowOnLoad != 'undefined') gWindowOnLoad();"; 332 content::RenderFrameHost* frame = 333 LoginDisplayHostImpl::GetGaiaAuthIframe(web_ui()->GetWebContents()); 334 frame->ExecuteJavaScript(base::ASCIIToUTF16(code)); 335 } 336 if (gaia_silent_load_) { 337 focus_stolen_ = true; 338 // Prevent focus stealing by the Gaia page. 339 // TODO(altimofeev): temporary solution, until focus parameters are 340 // implemented on the Gaia side. 341 const char code[] = 342 "var gWindowOnLoad = window.onload; " 343 "window.onload=function() {};"; 344 content::RenderFrameHost* frame = 345 LoginDisplayHostImpl::GetGaiaAuthIframe(web_ui()->GetWebContents()); 346 frame->ExecuteJavaScript(base::ASCIIToUTF16(code)); 347 348 // As we could miss and window.onload could already be called, restore 349 // focus to current pod (see crbug/175243). 350 DCHECK(signin_screen_handler_); 351 signin_screen_handler_->RefocusCurrentPod(); 352 } 353 HandleFrameLoadingCompleted(0); 354 355 if (test_expects_complete_login_) 356 SubmitLoginFormForTest(); 357 } 358 359 void GaiaScreenHandler::PopulateEmail(const std::string& user_id) { 360 populated_email_ = user_id; 361 } 362 363 void GaiaScreenHandler::PasswordChangedFor(const std::string& user_id) { 364 password_changed_for_.insert(user_id); 365 } 366 367 void GaiaScreenHandler::StartClearingDnsCache() { 368 if (dns_clear_task_running_ || !g_browser_process->io_thread()) 369 return; 370 371 dns_cleared_ = false; 372 BrowserThread::PostTaskAndReply( 373 BrowserThread::IO, 374 FROM_HERE, 375 base::Bind(&ClearDnsCache, g_browser_process->io_thread()), 376 base::Bind(&GaiaScreenHandler::OnDnsCleared, weak_factory_.GetWeakPtr())); 377 dns_clear_task_running_ = true; 378 } 379 380 void GaiaScreenHandler::OnDnsCleared() { 381 DCHECK_CURRENTLY_ON(BrowserThread::UI); 382 dns_clear_task_running_ = false; 383 dns_cleared_ = true; 384 ShowGaiaScreenIfReady(); 385 } 386 387 void GaiaScreenHandler::StartClearingCookies( 388 const base::Closure& on_clear_callback) { 389 cookies_cleared_ = false; 390 ProfileHelper* profile_helper = 391 g_browser_process->platform_part()->profile_helper(); 392 LOG_ASSERT(Profile::FromWebUI(web_ui()) == 393 profile_helper->GetSigninProfile()); 394 profile_helper->ClearSigninProfile( 395 base::Bind(&GaiaScreenHandler::OnCookiesCleared, 396 weak_factory_.GetWeakPtr(), 397 on_clear_callback)); 398 } 399 400 void GaiaScreenHandler::OnCookiesCleared( 401 const base::Closure& on_clear_callback) { 402 DCHECK_CURRENTLY_ON(BrowserThread::UI); 403 cookies_cleared_ = true; 404 on_clear_callback.Run(); 405 } 406 407 void GaiaScreenHandler::ShowSigninScreenForCreds(const std::string& username, 408 const std::string& password) { 409 VLOG(2) << "ShowSigninScreenForCreds for user " << username 410 << ", frame_state=" << FrameState(); 411 412 test_user_ = username; 413 test_pass_ = password; 414 test_expects_complete_login_ = true; 415 416 // Submit login form for test if gaia is ready. If gaia is loading, login 417 // will be attempted in HandleLoginWebuiReady after gaia is ready. Otherwise, 418 // reload gaia then follow the loading case. 419 if (FrameState() == GaiaScreenHandler::FRAME_STATE_LOADED) 420 SubmitLoginFormForTest(); 421 else if (FrameState() != GaiaScreenHandler::FRAME_STATE_LOADING) { 422 DCHECK(signin_screen_handler_); 423 signin_screen_handler_->OnShowAddUser(); 424 } 425 } 426 427 void GaiaScreenHandler::SubmitLoginFormForTest() { 428 VLOG(2) << "Submit login form for test, user=" << test_user_; 429 430 std::string code; 431 code += "document.getElementById('Email').value = '" + test_user_ + "';"; 432 code += "document.getElementById('Passwd').value = '" + test_pass_ + "';"; 433 code += "document.getElementById('signIn').click();"; 434 435 content::RenderFrameHost* frame = 436 LoginDisplayHostImpl::GetGaiaAuthIframe(web_ui()->GetWebContents()); 437 frame->ExecuteJavaScript(base::ASCIIToUTF16(code)); 438 439 // Test properties are cleared in HandleCompleteLogin because the form 440 // submission might fail and login will not be attempted after reloading 441 // if they are cleared here. 442 } 443 444 void GaiaScreenHandler::SetSAMLPrincipalsAPIUsed(bool api_used) { 445 using_saml_api_ = api_used; 446 UMA_HISTOGRAM_BOOLEAN("ChromeOS.SAML.APIUsed", api_used); 447 } 448 449 void GaiaScreenHandler::ShowGaia() { 450 if (gaia_silent_load_ && populated_email_.empty()) { 451 dns_cleared_ = true; 452 cookies_cleared_ = true; 453 ShowGaiaScreenIfReady(); 454 } else { 455 StartClearingDnsCache(); 456 StartClearingCookies(base::Bind(&GaiaScreenHandler::ShowGaiaScreenIfReady, 457 weak_factory_.GetWeakPtr())); 458 } 459 } 460 461 void GaiaScreenHandler::ShowGaiaScreenIfReady() { 462 if (!dns_cleared_ || !cookies_cleared_ || !Delegate()) 463 return; 464 465 std::string active_network_path = network_state_informer_->network_path(); 466 if (gaia_silent_load_ && 467 (network_state_informer_->state() != NetworkStateInformer::ONLINE || 468 gaia_silent_load_network_ != active_network_path)) { 469 // Network has changed. Force Gaia reload. 470 gaia_silent_load_ = false; 471 // Gaia page will be realoded, so focus isn't stolen anymore. 472 focus_stolen_ = false; 473 } 474 475 // Note that LoadAuthExtension clears |populated_email_|. 476 if (populated_email_.empty()) 477 Delegate()->LoadSigninWallpaper(); 478 else 479 Delegate()->LoadWallpaper(populated_email_); 480 481 // Set Least Recently Used input method for the user. 482 if (!populated_email_.empty()) 483 signin_screen_handler_->SetUserInputMethod(populated_email_); 484 485 LoadAuthExtension(!gaia_silent_load_, false, false); 486 signin_screen_handler_->UpdateUIState( 487 SigninScreenHandler::UI_STATE_GAIA_SIGNIN, NULL); 488 489 if (gaia_silent_load_) { 490 // The variable is assigned to false because silently loaded Gaia page was 491 // used. 492 gaia_silent_load_ = false; 493 if (focus_stolen_) 494 HandleGaiaUIReady(); 495 } 496 497 signin_screen_handler_->UpdateState(ErrorScreenActor::ERROR_REASON_UPDATE); 498 } 499 500 void GaiaScreenHandler::MaybePreloadAuthExtension() { 501 LOG(WARNING) << "MaybePreloadAuthExtension() call."; 502 503 // If cookies clearing was initiated or |dns_clear_task_running_| then auth 504 // extension showing has already been initiated and preloading is senseless. 505 if (signin_screen_handler_->ShouldLoadGaia() && 506 !gaia_silent_load_ && 507 !cookies_cleared_ && 508 !dns_clear_task_running_ && 509 network_state_informer_->state() == NetworkStateInformer::ONLINE) { 510 gaia_silent_load_ = true; 511 gaia_silent_load_network_ = network_state_informer_->network_path(); 512 LoadAuthExtension(true, true, false); 513 } 514 } 515 516 void GaiaScreenHandler::LoadAuthExtension(bool force, 517 bool silent_load, 518 bool offline) { 519 GaiaContext context; 520 context.force_reload = force; 521 context.is_local = offline; 522 context.password_changed = !populated_email_.empty() && 523 password_changed_for_.count(populated_email_); 524 context.use_offline = offline; 525 context.email = populated_email_; 526 if (Delegate()) { 527 context.show_users = Delegate()->IsShowUsers(); 528 context.has_users = !Delegate()->GetUsers().empty(); 529 } 530 531 populated_email_.clear(); 532 533 LoadGaia(context); 534 } 535 536 void GaiaScreenHandler::UpdateState(ErrorScreenActor::ErrorReason reason) { 537 if (signin_screen_handler_) 538 signin_screen_handler_->UpdateState(reason); 539 } 540 541 SigninScreenHandlerDelegate* GaiaScreenHandler::Delegate() { 542 DCHECK(signin_screen_handler_); 543 return signin_screen_handler_->delegate_; 544 } 545 546 void GaiaScreenHandler::SetSigninScreenHandler(SigninScreenHandler* handler) { 547 signin_screen_handler_ = handler; 548 } 549 } // namespace chromeos 550