gaia_screen_handler.cc revision 46d4c2bc3267f3f028f39e7e311b0f89aba2e4fd
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
31using content::BrowserThread;
32
33namespace chromeos {
34
35namespace {
36
37const char kJsScreenPath[] = "login.GaiaSigninScreen";
38
39void 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
83void 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.
89void 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
99GaiaContext::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
107GaiaScreenHandler::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
125GaiaScreenHandler::~GaiaScreenHandler() {
126}
127
128void 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(&params, 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
179void GaiaScreenHandler::UpdateGaia(const GaiaContext& context) {
180  base::DictionaryValue params;
181  UpdateAuthParams(&params, context.has_users);
182  CallJS("updateAuthExtension", params);
183}
184
185void 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
200void 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
224void GaiaScreenHandler::Initialize() {
225}
226
227void 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
241void 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
264void 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
277void 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
305void GaiaScreenHandler::HandleUsingSAMLAPI() {
306  SetSAMLPrincipalsAPIUsed(true);
307}
308
309void 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
319void GaiaScreenHandler::HandleScrapedPasswordVerificationFailed() {
320  RecordSAMLScrapingVerificationResultInHistogram(false);
321}
322
323void 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
359void GaiaScreenHandler::PopulateEmail(const std::string& user_id) {
360  populated_email_ = user_id;
361}
362
363void GaiaScreenHandler::PasswordChangedFor(const std::string& user_id) {
364  password_changed_for_.insert(user_id);
365}
366
367void 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
380void GaiaScreenHandler::OnDnsCleared() {
381  DCHECK_CURRENTLY_ON(BrowserThread::UI);
382  dns_clear_task_running_ = false;
383  dns_cleared_ = true;
384  ShowGaiaScreenIfReady();
385}
386
387void 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
400void 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
407void 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
427void 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
444void GaiaScreenHandler::SetSAMLPrincipalsAPIUsed(bool api_used) {
445  using_saml_api_ = api_used;
446  UMA_HISTOGRAM_BOOLEAN("ChromeOS.SAML.APIUsed", api_used);
447}
448
449void 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
461void 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
500void 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
516void 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
536void GaiaScreenHandler::UpdateState(ErrorScreenActor::ErrorReason reason) {
537  if (signin_screen_handler_)
538    signin_screen_handler_->UpdateState(reason);
539}
540
541SigninScreenHandlerDelegate* GaiaScreenHandler::Delegate() {
542  DCHECK(signin_screen_handler_);
543  return signin_screen_handler_->delegate_;
544}
545
546void GaiaScreenHandler::SetSigninScreenHandler(SigninScreenHandler* handler) {
547  signin_screen_handler_ = handler;
548}
549}  // namespace chromeos
550