ozone: evdev: Sync caps lock LED state to evdev
[chromium-blink-merge.git] / chrome / browser / ui / sync / one_click_signin_helper.cc
blobe72ae55f591a9048e5015fb0c38375171019e666
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.
5 #include "chrome/browser/ui/sync/one_click_signin_helper.h"
7 #include <algorithm>
8 #include <functional>
9 #include <utility>
10 #include <vector>
12 #include "base/bind.h"
13 #include "base/callback_forward.h"
14 #include "base/callback_helpers.h"
15 #include "base/compiler_specific.h"
16 #include "base/memory/scoped_ptr.h"
17 #include "base/message_loop/message_loop_proxy.h"
18 #include "base/metrics/field_trial.h"
19 #include "base/metrics/histogram.h"
20 #include "base/prefs/pref_service.h"
21 #include "base/prefs/scoped_user_pref_update.h"
22 #include "base/strings/string_split.h"
23 #include "base/strings/string_util.h"
24 #include "base/strings/utf_string_conversions.h"
25 #include "base/supports_user_data.h"
26 #include "base/values.h"
27 #include "chrome/browser/browser_process.h"
28 #include "chrome/browser/chrome_notification_types.h"
29 #include "chrome/browser/defaults.h"
30 #include "chrome/browser/history/history_service.h"
31 #include "chrome/browser/history/history_service_factory.h"
32 #include "chrome/browser/password_manager/chrome_password_manager_client.h"
33 #include "chrome/browser/profiles/profile.h"
34 #include "chrome/browser/profiles/profile_info_cache.h"
35 #include "chrome/browser/profiles/profile_io_data.h"
36 #include "chrome/browser/profiles/profile_manager.h"
37 #include "chrome/browser/search/search.h"
38 #include "chrome/browser/signin/chrome_signin_client.h"
39 #include "chrome/browser/signin/chrome_signin_client_factory.h"
40 #include "chrome/browser/signin/signin_error_controller_factory.h"
41 #include "chrome/browser/signin/signin_manager_factory.h"
42 #include "chrome/browser/signin/signin_names_io_thread.h"
43 #include "chrome/browser/sync/profile_sync_service.h"
44 #include "chrome/browser/sync/profile_sync_service_factory.h"
45 #include "chrome/browser/tab_contents/tab_util.h"
46 #include "chrome/browser/ui/browser_finder.h"
47 #include "chrome/browser/ui/browser_list.h"
48 #include "chrome/browser/ui/browser_tabstrip.h"
49 #include "chrome/browser/ui/browser_window.h"
50 #include "chrome/browser/ui/chrome_pages.h"
51 #include "chrome/browser/ui/sync/one_click_signin_sync_observer.h"
52 #include "chrome/browser/ui/sync/one_click_signin_sync_starter.h"
53 #include "chrome/browser/ui/tab_modal_confirm_dialog.h"
54 #include "chrome/browser/ui/tab_modal_confirm_dialog_delegate.h"
55 #include "chrome/browser/ui/tabs/tab_strip_model.h"
56 #include "chrome/browser/ui/webui/signin/login_ui_service_factory.h"
57 #include "chrome/common/chrome_version_info.h"
58 #include "chrome/common/net/url_util.h"
59 #include "chrome/common/pref_names.h"
60 #include "chrome/common/url_constants.h"
61 #include "chrome/grit/chromium_strings.h"
62 #include "chrome/grit/generated_resources.h"
63 #include "components/autofill/core/common/password_form.h"
64 #include "components/google/core/browser/google_util.h"
65 #include "components/password_manager/core/browser/password_manager.h"
66 #include "components/signin/core/browser/signin_client.h"
67 #include "components/signin/core/browser/signin_error_controller.h"
68 #include "components/signin/core/browser/signin_manager.h"
69 #include "components/signin/core/browser/signin_manager_cookie_helper.h"
70 #include "components/signin/core/browser/signin_metrics.h"
71 #include "components/signin/core/common/profile_management_switches.h"
72 #include "components/sync_driver/sync_prefs.h"
73 #include "content/public/browser/browser_thread.h"
74 #include "content/public/browser/navigation_entry.h"
75 #include "content/public/browser/page_navigator.h"
76 #include "content/public/browser/render_frame_host.h"
77 #include "content/public/browser/render_process_host.h"
78 #include "content/public/browser/web_contents.h"
79 #include "content/public/browser/web_contents_delegate.h"
80 #include "content/public/common/frame_navigate_params.h"
81 #include "google_apis/gaia/gaia_auth_util.h"
82 #include "google_apis/gaia/gaia_urls.h"
83 #include "grit/components_strings.h"
84 #include "ipc/ipc_message_macros.h"
85 #include "net/base/url_util.h"
86 #include "net/cookies/cookie_monster.h"
87 #include "net/url_request/url_request.h"
88 #include "ui/base/l10n/l10n_util.h"
89 #include "ui/base/page_transition_types.h"
90 #include "url/gurl.h"
93 namespace {
95 // ConfirmEmailDialogDelegate -------------------------------------------------
97 class ConfirmEmailDialogDelegate : public TabModalConfirmDialogDelegate {
98 public:
99 enum Action {
100 CREATE_NEW_USER,
101 START_SYNC,
102 CLOSE
105 // Callback indicating action performed by the user.
106 typedef base::Callback<void(Action)> Callback;
108 // Ask the user for confirmation before starting to sync.
109 static void AskForConfirmation(content::WebContents* contents,
110 const std::string& last_email,
111 const std::string& email,
112 Callback callback);
114 private:
115 ConfirmEmailDialogDelegate(content::WebContents* contents,
116 const std::string& last_email,
117 const std::string& email,
118 Callback callback);
119 ~ConfirmEmailDialogDelegate() override;
121 // TabModalConfirmDialogDelegate:
122 base::string16 GetTitle() override;
123 base::string16 GetDialogMessage() override;
124 base::string16 GetAcceptButtonTitle() override;
125 base::string16 GetCancelButtonTitle() override;
126 base::string16 GetLinkText() const override;
127 void OnAccepted() override;
128 void OnCanceled() override;
129 void OnClosed() override;
130 void OnLinkClicked(WindowOpenDisposition disposition) override;
132 std::string last_email_;
133 std::string email_;
134 Callback callback_;
136 // Web contents from which the "Learn more" link should be opened.
137 content::WebContents* web_contents_;
139 DISALLOW_COPY_AND_ASSIGN(ConfirmEmailDialogDelegate);
142 // static
143 void ConfirmEmailDialogDelegate::AskForConfirmation(
144 content::WebContents* contents,
145 const std::string& last_email,
146 const std::string& email,
147 Callback callback) {
148 TabModalConfirmDialog::Create(
149 new ConfirmEmailDialogDelegate(contents, last_email, email,
150 callback), contents);
153 ConfirmEmailDialogDelegate::ConfirmEmailDialogDelegate(
154 content::WebContents* contents,
155 const std::string& last_email,
156 const std::string& email,
157 Callback callback)
158 : TabModalConfirmDialogDelegate(contents),
159 last_email_(last_email),
160 email_(email),
161 callback_(callback),
162 web_contents_(contents) {
165 ConfirmEmailDialogDelegate::~ConfirmEmailDialogDelegate() {
168 base::string16 ConfirmEmailDialogDelegate::GetTitle() {
169 return l10n_util::GetStringUTF16(
170 IDS_ONE_CLICK_SIGNIN_CONFIRM_EMAIL_DIALOG_TITLE);
173 base::string16 ConfirmEmailDialogDelegate::GetDialogMessage() {
174 return l10n_util::GetStringFUTF16(
175 IDS_ONE_CLICK_SIGNIN_CONFIRM_EMAIL_DIALOG_MESSAGE,
176 base::UTF8ToUTF16(last_email_), base::UTF8ToUTF16(email_));
179 base::string16 ConfirmEmailDialogDelegate::GetAcceptButtonTitle() {
180 return l10n_util::GetStringUTF16(
181 IDS_ONE_CLICK_SIGNIN_CONFIRM_EMAIL_DIALOG_OK_BUTTON);
184 base::string16 ConfirmEmailDialogDelegate::GetCancelButtonTitle() {
185 return l10n_util::GetStringUTF16(
186 IDS_ONE_CLICK_SIGNIN_CONFIRM_EMAIL_DIALOG_CANCEL_BUTTON);
189 base::string16 ConfirmEmailDialogDelegate::GetLinkText() const {
190 return l10n_util::GetStringUTF16(IDS_LEARN_MORE);
193 void ConfirmEmailDialogDelegate::OnAccepted() {
194 base::ResetAndReturn(&callback_).Run(CREATE_NEW_USER);
197 void ConfirmEmailDialogDelegate::OnCanceled() {
198 base::ResetAndReturn(&callback_).Run(START_SYNC);
201 void ConfirmEmailDialogDelegate::OnClosed() {
202 base::ResetAndReturn(&callback_).Run(CLOSE);
205 void ConfirmEmailDialogDelegate::OnLinkClicked(
206 WindowOpenDisposition disposition) {
207 content::OpenURLParams params(
208 GURL(chrome::kChromeSyncMergeTroubleshootingURL),
209 content::Referrer(),
210 NEW_POPUP,
211 ui::PAGE_TRANSITION_AUTO_TOPLEVEL,
212 false);
213 // It is guaranteed that |web_contents_| is valid here because when it's
214 // deleted, the dialog is immediately closed and no further action can be
215 // performed.
216 web_contents_->OpenURL(params);
220 // Helpers --------------------------------------------------------------------
222 // Add a specific email to the list of emails rejected for one-click
223 // sign-in, for this profile.
224 void AddEmailToOneClickRejectedList(Profile* profile,
225 const std::string& email) {
226 ListPrefUpdate updater(profile->GetPrefs(),
227 prefs::kReverseAutologinRejectedEmailList);
228 updater->AppendIfNotPresent(new base::StringValue(email));
231 // Start syncing with the given user information.
232 void StartSync(const OneClickSigninHelper::StartSyncArgs& args,
233 OneClickSigninSyncStarter::StartSyncMode start_mode) {
234 if (start_mode == OneClickSigninSyncStarter::UNDO_SYNC) {
235 OneClickSigninHelper::LogHistogramValue(signin_metrics::HISTOGRAM_UNDO);
236 return;
239 // The wrapper deletes itself once it's done.
240 OneClickSigninHelper::SyncStarterWrapper* wrapper =
241 new OneClickSigninHelper::SyncStarterWrapper(args, start_mode);
242 wrapper->Start();
244 int action = signin_metrics::HISTOGRAM_MAX;
245 switch (args.auto_accept) {
246 case OneClickSigninHelper::AUTO_ACCEPT_EXPLICIT:
247 break;
248 case OneClickSigninHelper::AUTO_ACCEPT_ACCEPTED:
249 action =
250 start_mode == OneClickSigninSyncStarter::SYNC_WITH_DEFAULT_SETTINGS ?
251 signin_metrics::HISTOGRAM_AUTO_WITH_DEFAULTS :
252 signin_metrics::HISTOGRAM_AUTO_WITH_ADVANCED;
253 break;
254 case OneClickSigninHelper::AUTO_ACCEPT_CONFIGURE:
255 DCHECK(start_mode == OneClickSigninSyncStarter::CONFIGURE_SYNC_FIRST);
256 action = signin_metrics::HISTOGRAM_AUTO_WITH_ADVANCED;
257 break;
258 default:
259 NOTREACHED() << "Invalid auto_accept: " << args.auto_accept;
260 break;
262 if (action != signin_metrics::HISTOGRAM_MAX)
263 OneClickSigninHelper::LogHistogramValue(action);
266 void StartExplicitSync(const OneClickSigninHelper::StartSyncArgs& args,
267 content::WebContents* contents,
268 OneClickSigninSyncStarter::StartSyncMode start_mode,
269 ConfirmEmailDialogDelegate::Action action) {
270 if (action == ConfirmEmailDialogDelegate::START_SYNC) {
271 StartSync(args, start_mode);
272 } else {
273 // Perform a redirection to the NTP/Apps page to hide the blank page when
274 // the action is CLOSE or CREATE_NEW_USER. The redirection is useful when
275 // the action is CREATE_NEW_USER because the "Create new user" page might
276 // be opened in a different tab that is already showing settings.
277 args.callback.Run(OneClickSigninSyncStarter::SYNC_SETUP_FAILURE);
278 if (action == ConfirmEmailDialogDelegate::CREATE_NEW_USER) {
279 chrome::ShowSettingsSubPage(args.browser,
280 std::string(chrome::kCreateProfileSubPage));
285 void ClearPendingEmailOnIOThread(content::ResourceContext* context) {
286 ProfileIOData* io_data = ProfileIOData::FromResourceContext(context);
287 DCHECK(io_data);
288 io_data->set_reverse_autologin_pending_email(std::string());
291 // Determines the source of the sign in and the continue URL. It's either one
292 // of the known sign-in access points (first run, NTP, Apps page, menu, or
293 // settings) or it's an implicit sign in via another Google property. In the
294 // former case, "service" is also checked to make sure its "chromiumsync".
295 signin_metrics::Source GetSigninSource(const GURL& url, GURL* continue_url) {
296 DCHECK(url.is_valid());
297 std::string value;
298 net::GetValueForKeyInQuery(url, "service", &value);
299 bool possibly_an_explicit_signin = value == "chromiumsync";
301 // Find the final continue URL for this sign in. In some cases, Gaia can
302 // continue to itself, with the original continue URL buried under a couple
303 // of layers of indirection. Peel those layers away. The final destination
304 // can also be "IsGaiaSignonRealm" so stop if we get to the end (but be sure
305 // we always extract at least one "continue" value).
306 GURL local_continue_url = signin::GetNextPageURLForPromoURL(url);
307 while (gaia::IsGaiaSignonRealm(local_continue_url.GetOrigin())) {
308 GURL next_continue_url =
309 signin::GetNextPageURLForPromoURL(local_continue_url);
310 if (!next_continue_url.is_valid())
311 break;
312 local_continue_url = next_continue_url;
315 if (continue_url && local_continue_url.is_valid()) {
316 DCHECK(!continue_url->is_valid() || *continue_url == local_continue_url);
317 *continue_url = local_continue_url;
320 return possibly_an_explicit_signin ?
321 signin::GetSourceForPromoURL(local_continue_url) :
322 signin_metrics::SOURCE_UNKNOWN;
325 // Returns true if |url| is a valid URL that can occur during the sign in
326 // process. Valid URLs are of the form:
328 // https://accounts.google.{TLD}/...
329 // https://accounts.youtube.com/...
330 // https://accounts.blogger.com/...
332 // All special headers used by one click sign in occur on
333 // https://accounts.google.com URLs. However, the sign in process may redirect
334 // to intermediate Gaia URLs that do not end with .com. For example, an account
335 // that uses SMS 2-factor outside the US may redirect to country specific URLs.
337 // The sign in process may also redirect to youtube and blogger account URLs
338 // so that Gaia acts as a single signon service.
339 bool IsValidGaiaSigninRedirectOrResponseURL(const GURL& url) {
340 std::string hostname = url.host();
341 if (google_util::IsGoogleHostname(hostname, google_util::ALLOW_SUBDOMAIN)) {
342 // Also using IsGaiaSignonRealm() to handle overriding with command line.
343 return gaia::IsGaiaSignonRealm(url.GetOrigin()) ||
344 StartsWithASCII(hostname, "accounts.", false);
347 GURL origin = url.GetOrigin();
348 if (origin == GURL("https://accounts.youtube.com") ||
349 origin == GURL("https://accounts.blogger.com"))
350 return true;
352 return false;
355 // Tells when we are in the process of showing either the signin to chrome page
356 // or the one click sign in to chrome page.
357 // NOTE: This should only be used for logging purposes since it relies on hard
358 // coded URLs that could change.
359 bool AreWeShowingSignin(GURL url,
360 signin_metrics::Source source,
361 std::string email) {
362 GURL::Replacements replacements;
363 replacements.ClearQuery();
364 GURL clean_login_url =
365 GaiaUrls::GetInstance()->service_login_url().ReplaceComponents(
366 replacements);
368 return (url.ReplaceComponents(replacements) == clean_login_url &&
369 source != signin_metrics::SOURCE_UNKNOWN) ||
370 (IsValidGaiaSigninRedirectOrResponseURL(url) &&
371 url.spec().find("ChromeLoginPrompt") != std::string::npos &&
372 !email.empty());
375 // If profile is valid then get signin scoped device id from signin client.
376 // Otherwise returns empty string.
377 std::string GetSigninScopedDeviceId(Profile* profile) {
378 std::string signin_scoped_device_id;
379 SigninClient* signin_client =
380 profile ? ChromeSigninClientFactory::GetForProfile(profile) : NULL;
381 if (signin_client) {
382 signin_scoped_device_id = signin_client->GetSigninScopedDeviceId();
384 return signin_scoped_device_id;
387 // CurrentHistoryCleaner ------------------------------------------------------
389 // Watch a webcontents and remove URL from the history once loading is complete.
390 // We have to delay the cleaning until the new URL has finished loading because
391 // we're not allowed to remove the last-loaded URL from the history. Objects
392 // of this type automatically self-destruct once they're finished their work.
393 class CurrentHistoryCleaner : public content::WebContentsObserver {
394 public:
395 explicit CurrentHistoryCleaner(content::WebContents* contents);
396 ~CurrentHistoryCleaner() override;
398 // content::WebContentsObserver:
399 void WebContentsDestroyed() override;
400 void DidCommitProvisionalLoadForFrame(
401 content::RenderFrameHost* render_frame_host,
402 const GURL& url,
403 ui::PageTransition transition_type) override;
405 private:
406 scoped_ptr<content::WebContents> contents_;
407 int history_index_to_remove_;
409 DISALLOW_COPY_AND_ASSIGN(CurrentHistoryCleaner);
412 CurrentHistoryCleaner::CurrentHistoryCleaner(content::WebContents* contents)
413 : WebContentsObserver(contents) {
414 history_index_to_remove_ =
415 web_contents()->GetController().GetLastCommittedEntryIndex();
418 CurrentHistoryCleaner::~CurrentHistoryCleaner() {
421 void CurrentHistoryCleaner::DidCommitProvisionalLoadForFrame(
422 content::RenderFrameHost* render_frame_host,
423 const GURL& url,
424 ui::PageTransition transition_type) {
425 // Return early if this is not top-level navigation.
426 if (render_frame_host->GetParent())
427 return;
430 void CurrentHistoryCleaner::WebContentsDestroyed() {
431 delete this; // Failure.
434 } // namespace
437 // StartSyncArgs --------------------------------------------------------------
439 OneClickSigninHelper::StartSyncArgs::StartSyncArgs()
440 : profile(NULL),
441 browser(NULL),
442 auto_accept(AUTO_ACCEPT_NONE),
443 web_contents(NULL),
444 confirmation_required(OneClickSigninSyncStarter::NO_CONFIRMATION),
445 source(signin_metrics::SOURCE_UNKNOWN) {}
447 OneClickSigninHelper::StartSyncArgs::StartSyncArgs(
448 Profile* profile,
449 Browser* browser,
450 OneClickSigninHelper::AutoAccept auto_accept,
451 const std::string& session_index,
452 const std::string& email,
453 const std::string& password,
454 const std::string& refresh_token,
455 content::WebContents* web_contents,
456 bool untrusted_confirmation_required,
457 signin_metrics::Source source,
458 OneClickSigninSyncStarter::Callback callback)
459 : profile(profile),
460 browser(browser),
461 auto_accept(auto_accept),
462 session_index(session_index),
463 email(email),
464 password(password),
465 refresh_token(refresh_token),
466 web_contents(web_contents),
467 source(source),
468 callback(callback) {
469 DCHECK(session_index.empty() != refresh_token.empty());
470 if (untrusted_confirmation_required) {
471 confirmation_required = OneClickSigninSyncStarter::CONFIRM_UNTRUSTED_SIGNIN;
472 } else if (source == signin_metrics::SOURCE_SETTINGS) {
473 // Do not display a status confirmation for re-auth.
474 confirmation_required = OneClickSigninSyncStarter::NO_CONFIRMATION;
475 } else {
476 confirmation_required = OneClickSigninSyncStarter::CONFIRM_AFTER_SIGNIN;
480 OneClickSigninHelper::StartSyncArgs::~StartSyncArgs() {}
482 // SyncStarterWrapper ---------------------------------------------------------
484 OneClickSigninHelper::SyncStarterWrapper::SyncStarterWrapper(
485 const OneClickSigninHelper::StartSyncArgs& args,
486 OneClickSigninSyncStarter::StartSyncMode start_mode)
487 : args_(args), start_mode_(start_mode), weak_pointer_factory_(this) {
488 BrowserList::AddObserver(this);
490 // Cache the parent desktop for the browser, so we can reuse that same
491 // desktop for any UI we want to display.
492 desktop_type_ = args_.browser ? args_.browser->host_desktop_type()
493 : chrome::GetActiveDesktop();
496 OneClickSigninHelper::SyncStarterWrapper::~SyncStarterWrapper() {
497 BrowserList::RemoveObserver(this);
500 void OneClickSigninHelper::SyncStarterWrapper::Start() {
501 if (args_.refresh_token.empty()) {
502 if (args_.password.empty()) {
503 VerifyGaiaCookiesBeforeSignIn();
504 } else {
505 StartSigninOAuthHelper();
507 } else {
508 OnSigninOAuthInformationAvailable(args_.email, args_.email,
509 args_.refresh_token);
513 void
514 OneClickSigninHelper::SyncStarterWrapper::OnSigninOAuthInformationAvailable(
515 const std::string& email,
516 const std::string& display_email,
517 const std::string& refresh_token) {
518 if (!gaia::AreEmailsSame(display_email, args_.email)) {
519 DisplayErrorBubble(
520 GoogleServiceAuthError(
521 GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS).ToString());
522 } else {
523 StartOneClickSigninSyncStarter(email, refresh_token);
526 base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
529 void OneClickSigninHelper::SyncStarterWrapper::OnSigninOAuthInformationFailure(
530 const GoogleServiceAuthError& error) {
531 DisplayErrorBubble(error.ToString());
532 base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
535 void OneClickSigninHelper::SyncStarterWrapper::OnBrowserRemoved(
536 Browser* browser) {
537 if (args_.browser == browser)
538 args_.browser = NULL;
541 void OneClickSigninHelper::SyncStarterWrapper::VerifyGaiaCookiesBeforeSignIn() {
542 scoped_refptr<SigninManagerCookieHelper> cookie_helper(
543 new SigninManagerCookieHelper(
544 args_.profile->GetRequestContext(),
545 content::BrowserThread::GetMessageLoopProxyForThread(
546 content::BrowserThread::UI),
547 content::BrowserThread::GetMessageLoopProxyForThread(
548 content::BrowserThread::IO)));
549 cookie_helper->StartFetchingGaiaCookiesOnUIThread(
550 base::Bind(&SyncStarterWrapper::OnGaiaCookiesFetched,
551 weak_pointer_factory_.GetWeakPtr(),
552 args_.session_index));
555 void OneClickSigninHelper::SyncStarterWrapper::OnGaiaCookiesFetched(
556 const std::string session_index, const net::CookieList& cookie_list) {
557 net::CookieList::const_iterator it;
558 bool success = false;
559 for (it = cookie_list.begin(); it != cookie_list.end(); ++it) {
560 // Make sure the LSID cookie is set on the GAIA host, instead of a super-
561 // domain.
562 if (it->Name() == "LSID") {
563 if (it->IsHostCookie() && it->IsHttpOnly() && it->IsSecure()) {
564 // Found a valid LSID cookie. Continue loop to make sure we don't have
565 // invalid LSID cookies on any super-domain.
566 success = true;
567 } else {
568 success = false;
569 break;
574 if (success) {
575 StartSigninOAuthHelper();
576 } else {
577 DisplayErrorBubble(
578 GoogleServiceAuthError(
579 GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS).ToString());
580 base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
584 void OneClickSigninHelper::SyncStarterWrapper::DisplayErrorBubble(
585 const std::string& error_message) {
586 args_.browser = OneClickSigninSyncStarter::EnsureBrowser(
587 args_.browser, args_.profile, desktop_type_);
588 LoginUIServiceFactory::GetForProfile(args_.profile)->DisplayLoginResult(
589 args_.browser, base::UTF8ToUTF16(error_message));
592 void OneClickSigninHelper::SyncStarterWrapper::StartSigninOAuthHelper() {
593 std::string signin_scoped_device_id = GetSigninScopedDeviceId(args_.profile);
594 signin_oauth_helper_.reset(
595 new SigninOAuthHelper(args_.profile->GetRequestContext(),
596 args_.session_index,
597 signin_scoped_device_id,
598 this));
601 void
602 OneClickSigninHelper::SyncStarterWrapper::StartOneClickSigninSyncStarter(
603 const std::string& email,
604 const std::string& refresh_token) {
605 // The starter deletes itself once it's done.
606 new OneClickSigninSyncStarter(args_.profile, args_.browser,
607 email, args_.password,
608 refresh_token, start_mode_,
609 args_.web_contents,
610 args_.confirmation_required,
611 GURL(),
612 args_.callback);
616 // OneClickSigninHelper -------------------------------------------------------
618 DEFINE_WEB_CONTENTS_USER_DATA_KEY(OneClickSigninHelper);
620 // static
621 const int OneClickSigninHelper::kMaxNavigationsSince = 10;
623 OneClickSigninHelper::OneClickSigninHelper(content::WebContents* web_contents)
624 : content::WebContentsObserver(web_contents),
625 showing_signin_(false),
626 auto_accept_(AUTO_ACCEPT_NONE),
627 source_(signin_metrics::SOURCE_UNKNOWN),
628 switched_to_advanced_(false),
629 untrusted_navigations_since_signin_visit_(0),
630 untrusted_confirmation_required_(false),
631 do_not_clear_pending_email_(false),
632 do_not_start_sync_for_testing_(false),
633 weak_pointer_factory_(this) {
634 ChromePasswordManagerClient* client =
635 ChromePasswordManagerClient::FromWebContents(web_contents);
636 // May be NULL during testing.
637 if (client) {
638 client->GetPasswordManager()->AddSubmissionCallback(
639 base::Bind(&OneClickSigninHelper::PasswordSubmitted,
640 weak_pointer_factory_.GetWeakPtr()));
644 OneClickSigninHelper::~OneClickSigninHelper() {}
646 // static
647 void OneClickSigninHelper::LogHistogramValue(int action) {
648 UMA_HISTOGRAM_ENUMERATION("Signin.AllAccessPointActions", action,
649 signin_metrics::HISTOGRAM_MAX);
652 // static
653 bool OneClickSigninHelper::CanOffer(content::WebContents* web_contents,
654 CanOfferFor can_offer_for,
655 const std::string& email,
656 std::string* error_message) {
657 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
658 VLOG(1) << "OneClickSigninHelper::CanOffer";
660 if (error_message)
661 error_message->clear();
663 if (!web_contents)
664 return false;
666 if (web_contents->GetBrowserContext()->IsOffTheRecord())
667 return false;
669 Profile* profile =
670 Profile::FromBrowserContext(web_contents->GetBrowserContext());
671 if (!profile)
672 return false;
674 SigninManager* manager =
675 SigninManagerFactory::GetForProfile(profile);
676 if (manager && !manager->IsSigninAllowed())
677 return false;
679 if (can_offer_for == CAN_OFFER_FOR_INTERSTITAL_ONLY &&
680 !profile->GetPrefs()->GetBoolean(prefs::kReverseAutologinEnabled))
681 return false;
683 if (!ChromeSigninClient::ProfileAllowsSigninCookies(profile))
684 return false;
686 if (!email.empty()) {
687 if (!manager)
688 return false;
690 // Make sure this username is not prohibited by policy.
691 if (!manager->IsAllowedUsername(email)) {
692 if (error_message) {
693 error_message->assign(
694 l10n_util::GetStringUTF8(IDS_SYNC_LOGIN_NAME_PROHIBITED));
696 return false;
699 if (can_offer_for != CAN_OFFER_FOR_SECONDARY_ACCOUNT) {
700 // If the signin manager already has an authenticated name, then this is a
701 // re-auth scenario. Make sure the email just signed in corresponds to
702 // the one sign in manager expects.
703 std::string current_email = manager->GetAuthenticatedUsername();
704 const bool same_email = gaia::AreEmailsSame(current_email, email);
705 if (!current_email.empty() && !same_email) {
706 UMA_HISTOGRAM_ENUMERATION("Signin.Reauth",
707 signin_metrics::HISTOGRAM_ACCOUNT_MISSMATCH,
708 signin_metrics::HISTOGRAM_MAX);
709 if (error_message) {
710 error_message->assign(
711 l10n_util::GetStringFUTF8(IDS_SYNC_WRONG_EMAIL,
712 base::UTF8ToUTF16(current_email)));
714 return false;
717 // If some profile, not just the current one, is already connected to this
718 // account, don't show the infobar.
719 if (g_browser_process && !same_email) {
720 ProfileManager* manager = g_browser_process->profile_manager();
721 if (manager) {
722 ProfileInfoCache& cache = manager->GetProfileInfoCache();
723 for (size_t i = 0; i < cache.GetNumberOfProfiles(); ++i) {
724 std::string current_email =
725 base::UTF16ToUTF8(cache.GetUserNameOfProfileAtIndex(i));
726 if (gaia::AreEmailsSame(email, current_email)) {
727 if (error_message) {
728 error_message->assign(
729 l10n_util::GetStringUTF8(IDS_SYNC_USER_NAME_IN_USE_ERROR));
731 return false;
738 // If email was already rejected by this profile for one-click sign-in.
739 if (can_offer_for == CAN_OFFER_FOR_INTERSTITAL_ONLY) {
740 const base::ListValue* rejected_emails = profile->GetPrefs()->GetList(
741 prefs::kReverseAutologinRejectedEmailList);
742 if (!rejected_emails->empty()) {
743 base::ListValue::const_iterator iter = rejected_emails->Find(
744 base::StringValue(email));
745 if (iter != rejected_emails->end())
746 return false;
751 VLOG(1) << "OneClickSigninHelper::CanOffer: yes we can";
752 return true;
755 // static
756 OneClickSigninHelper::Offer OneClickSigninHelper::CanOfferOnIOThread(
757 net::URLRequest* request,
758 ProfileIOData* io_data) {
759 return CanOfferOnIOThreadImpl(request->url(), request, io_data);
762 // static
763 OneClickSigninHelper::Offer OneClickSigninHelper::CanOfferOnIOThreadImpl(
764 const GURL& url,
765 base::SupportsUserData* request,
766 ProfileIOData* io_data) {
767 if (!gaia::IsGaiaSignonRealm(url.GetOrigin()))
768 return IGNORE_REQUEST;
770 if (!io_data)
771 return DONT_OFFER;
773 // Check for incognito before other parts of the io_data, since those
774 // members may not be initalized.
775 if (io_data->IsOffTheRecord())
776 return DONT_OFFER;
778 if (!io_data->signin_allowed()->GetValue())
779 return DONT_OFFER;
781 if (!io_data->reverse_autologin_enabled()->GetValue())
782 return DONT_OFFER;
784 if (!io_data->google_services_username()->GetValue().empty())
785 return DONT_OFFER;
787 if (!ChromeSigninClient::SettingsAllowSigninCookies(
788 io_data->GetCookieSettings()))
789 return DONT_OFFER;
791 // The checks below depend on chrome already knowing what account the user
792 // signed in with. This happens only after receiving the response containing
793 // the Google-Accounts-SignIn header. Until then, if there is even a chance
794 // that we want to connect the profile, chrome needs to tell Gaia that
795 // it should offer the interstitial. Therefore missing one click data on
796 // the request means can offer is true.
797 const std::string& pending_email = io_data->reverse_autologin_pending_email();
798 if (!pending_email.empty()) {
799 if (!SigninManager::IsUsernameAllowedByPolicy(pending_email,
800 io_data->google_services_username_pattern()->GetValue())) {
801 return DONT_OFFER;
804 std::vector<std::string> rejected_emails =
805 io_data->one_click_signin_rejected_email_list()->GetValue();
806 if (std::count_if(rejected_emails.begin(), rejected_emails.end(),
807 std::bind2nd(std::equal_to<std::string>(),
808 pending_email)) > 0) {
809 return DONT_OFFER;
812 if (io_data->signin_names()->GetEmails().count(
813 base::UTF8ToUTF16(pending_email)) > 0) {
814 return DONT_OFFER;
818 return CAN_OFFER;
821 // static
822 void OneClickSigninHelper::ShowInfoBarIfPossible(net::URLRequest* request,
823 ProfileIOData* io_data,
824 int child_id,
825 int route_id) {
826 std::string google_chrome_signin_value;
827 std::string google_accounts_signin_value;
828 request->GetResponseHeaderByName("Google-Chrome-SignIn",
829 &google_chrome_signin_value);
830 request->GetResponseHeaderByName("Google-Accounts-SignIn",
831 &google_accounts_signin_value);
833 if (!google_accounts_signin_value.empty() ||
834 !google_chrome_signin_value.empty()) {
835 VLOG(1) << "OneClickSigninHelper::ShowInfoBarIfPossible:"
836 << " g-a-s='" << google_accounts_signin_value << "'"
837 << " g-c-s='" << google_chrome_signin_value << "'";
840 if (!gaia::IsGaiaSignonRealm(request->url().GetOrigin()))
841 return;
843 // Parse Google-Accounts-SignIn.
844 base::StringPairs pairs;
845 base::SplitStringIntoKeyValuePairs(google_accounts_signin_value, '=', ',',
846 &pairs);
847 std::string session_index;
848 std::string email;
849 for (size_t i = 0; i < pairs.size(); ++i) {
850 const std::pair<std::string, std::string>& pair = pairs[i];
851 const std::string& key = pair.first;
852 const std::string& value = pair.second;
853 if (key == "email") {
854 base::TrimString(value, "\"", &email);
855 } else if (key == "sessionindex") {
856 session_index = value;
860 // Later in the chain of this request, we'll need to check the email address
861 // in the IO thread (see CanOfferOnIOThread). So save the email address as
862 // user data on the request (only for web-based flow).
863 if (!email.empty())
864 io_data->set_reverse_autologin_pending_email(email);
866 if (!email.empty() || !session_index.empty()) {
867 VLOG(1) << "OneClickSigninHelper::ShowInfoBarIfPossible:"
868 << " email=" << email
869 << " sessionindex=" << session_index;
872 // Parse Google-Chrome-SignIn.
873 AutoAccept auto_accept = AUTO_ACCEPT_NONE;
874 signin_metrics::Source source = signin_metrics::SOURCE_UNKNOWN;
875 GURL continue_url;
876 std::vector<std::string> tokens;
877 base::SplitString(google_chrome_signin_value, ',', &tokens);
878 for (size_t i = 0; i < tokens.size(); ++i) {
879 const std::string& token = tokens[i];
880 if (token == "accepted") {
881 auto_accept = AUTO_ACCEPT_ACCEPTED;
882 } else if (token == "configure") {
883 auto_accept = AUTO_ACCEPT_CONFIGURE;
884 } else if (token == "rejected-for-profile") {
885 auto_accept = AUTO_ACCEPT_REJECTED_FOR_PROFILE;
889 // If this is an explicit sign in (i.e., first run, NTP, Apps page, menu,
890 // settings) then force the auto accept type to explicit.
891 source = GetSigninSource(request->url(), &continue_url);
892 if (source != signin_metrics::SOURCE_UNKNOWN)
893 auto_accept = AUTO_ACCEPT_EXPLICIT;
895 if (auto_accept != AUTO_ACCEPT_NONE) {
896 VLOG(1) << "OneClickSigninHelper::ShowInfoBarIfPossible:"
897 << " auto_accept=" << auto_accept;
900 // If |session_index|, |email|, |auto_accept|, and |continue_url| all have
901 // their default value, don't bother posting a task to the UI thread.
902 // It will be a noop anyway.
904 // The two headers above may (but not always) come in different http requests
905 // so a post to the UI thread is still needed if |auto_accept| is not its
906 // default value, but |email| and |session_index| are.
907 if (session_index.empty() && email.empty() &&
908 auto_accept == AUTO_ACCEPT_NONE && !continue_url.is_valid()) {
909 return;
912 content::BrowserThread::PostTask(
913 content::BrowserThread::UI, FROM_HERE,
914 base::Bind(&OneClickSigninHelper::ShowInfoBarUIThread, session_index,
915 email, auto_accept, source, continue_url, child_id, route_id));
918 // static
919 void OneClickSigninHelper::LogConfirmHistogramValue(int action) {
920 UMA_HISTOGRAM_ENUMERATION("Signin.OneClickConfirmation", action,
921 signin_metrics::HISTOGRAM_CONFIRM_MAX);
923 // static
924 void OneClickSigninHelper::ShowInfoBarUIThread(
925 const std::string& session_index,
926 const std::string& email,
927 AutoAccept auto_accept,
928 signin_metrics::Source source,
929 const GURL& continue_url,
930 int child_id,
931 int route_id) {
932 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
934 content::WebContents* web_contents = tab_util::GetWebContentsByID(child_id,
935 route_id);
936 if (!web_contents)
937 return;
939 // TODO(mathp): The appearance of this infobar should be tested using a
940 // browser_test.
941 OneClickSigninHelper* helper =
942 OneClickSigninHelper::FromWebContents(web_contents);
943 if (!helper)
944 return;
946 if (auto_accept != AUTO_ACCEPT_NONE)
947 helper->auto_accept_ = auto_accept;
949 if (source != signin_metrics::SOURCE_UNKNOWN &&
950 helper->source_ == signin_metrics::SOURCE_UNKNOWN) {
951 helper->source_ = source;
954 // Save the email in the one-click signin manager. The manager may
955 // not exist if the contents is incognito or if the profile is already
956 // connected to a Google account.
957 if (!session_index.empty())
958 helper->session_index_ = session_index;
960 if (!email.empty())
961 helper->email_ = email;
963 CanOfferFor can_offer_for =
964 (auto_accept != AUTO_ACCEPT_EXPLICIT &&
965 helper->auto_accept_ != AUTO_ACCEPT_EXPLICIT) ?
966 CAN_OFFER_FOR_INTERSTITAL_ONLY : CAN_OFFER_FOR_ALL;
968 std::string error_message;
970 if (!web_contents || !CanOffer(web_contents, can_offer_for, email,
971 &error_message)) {
972 VLOG(1) << "OneClickSigninHelper::ShowInfoBarUIThread: not offering";
973 // TODO(rogerta): Can we just display our error now instead of keeping it
974 // around and doing it later?
975 if (helper && helper->error_message_.empty() && !error_message.empty())
976 helper->error_message_ = error_message;
978 return;
981 // Only allow the dedicated signin process to sign the user into
982 // Chrome without intervention, because it doesn't load any untrusted
983 // pages. If at any point an untrusted page is detected, chrome will
984 // show a modal dialog asking the user to confirm.
985 Profile* profile =
986 Profile::FromBrowserContext(web_contents->GetBrowserContext());
987 SigninClient* signin_client =
988 profile ? ChromeSigninClientFactory::GetForProfile(profile) : NULL;
989 helper->untrusted_confirmation_required_ |=
990 (signin_client && !signin_client->IsSigninProcess(child_id));
992 if (continue_url.is_valid()) {
993 // Set |original_continue_url_| if it is currently empty. |continue_url|
994 // could be modified by gaia pages, thus we need to record the original
995 // continue url to navigate back to the right page when sync setup is
996 // complete.
997 if (helper->original_continue_url_.is_empty())
998 helper->original_continue_url_ = continue_url;
999 helper->continue_url_ = continue_url;
1003 // static
1004 void OneClickSigninHelper::RemoveSigninRedirectURLHistoryItem(
1005 content::WebContents* web_contents) {
1008 // static
1009 bool OneClickSigninHelper::HandleCrossAccountError(
1010 Profile* profile,
1011 const std::string& session_index,
1012 const std::string& email,
1013 const std::string& password,
1014 const std::string& refresh_token,
1015 OneClickSigninHelper::AutoAccept auto_accept,
1016 signin_metrics::Source source,
1017 OneClickSigninSyncStarter::StartSyncMode start_mode,
1018 OneClickSigninSyncStarter::Callback sync_callback) {
1019 std::string last_email =
1020 profile->GetPrefs()->GetString(prefs::kGoogleServicesLastUsername);
1022 if (!last_email.empty() && !gaia::AreEmailsSame(last_email, email)) {
1023 // If the new email address is different from the email address that
1024 // just signed in, show a confirmation dialog on top of the current active
1025 // tab.
1027 // No need to display a second confirmation so pass false below.
1028 // TODO(atwilson): Move this into OneClickSigninSyncStarter.
1029 // The tab modal dialog always executes its callback before |contents|
1030 // is deleted.
1031 Browser* browser = chrome::FindLastActiveWithProfile(
1032 profile, chrome::GetActiveDesktop());
1033 content::WebContents* contents =
1034 browser->tab_strip_model()->GetActiveWebContents();
1036 ConfirmEmailDialogDelegate::AskForConfirmation(
1037 contents,
1038 last_email,
1039 email,
1040 base::Bind(
1041 &StartExplicitSync,
1042 StartSyncArgs(profile, browser, auto_accept,
1043 session_index, email, password,
1044 refresh_token,
1045 contents, false /* confirmation_required */, source,
1046 sync_callback),
1047 contents,
1048 start_mode));
1049 return true;
1052 return false;
1055 // static
1056 void OneClickSigninHelper::RedirectToNtpOrAppsPage(
1057 content::WebContents* contents, signin_metrics::Source source) {
1058 // Do nothing if a navigation is pending, since this call can be triggered
1059 // from DidStartLoading. This avoids deleting the pending entry while we are
1060 // still navigating to it. See crbug/346632.
1061 if (contents->GetController().GetPendingEntry())
1062 return;
1064 VLOG(1) << "RedirectToNtpOrAppsPage";
1065 // Redirect to NTP/Apps page and display a confirmation bubble
1066 GURL url(source == signin_metrics::SOURCE_APPS_PAGE_LINK ?
1067 chrome::kChromeUIAppsURL : chrome::kChromeUINewTabURL);
1068 content::OpenURLParams params(url,
1069 content::Referrer(),
1070 CURRENT_TAB,
1071 ui::PAGE_TRANSITION_AUTO_TOPLEVEL,
1072 false);
1073 contents->OpenURL(params);
1076 // static
1077 void OneClickSigninHelper::RedirectToNtpOrAppsPageIfNecessary(
1078 content::WebContents* contents, signin_metrics::Source source) {
1079 if (source != signin_metrics::SOURCE_SETTINGS) {
1080 RedirectToNtpOrAppsPage(contents, source);
1084 void OneClickSigninHelper::RedirectToSignin() {
1085 VLOG(1) << "OneClickSigninHelper::RedirectToSignin";
1087 // Extract the existing sounce=X value. Default to "2" if missing.
1088 signin_metrics::Source source = signin::GetSourceForPromoURL(continue_url_);
1089 if (source == signin_metrics::SOURCE_UNKNOWN)
1090 source = signin_metrics::SOURCE_MENU;
1091 GURL page = signin::GetPromoURL(source, false);
1093 content::WebContents* contents = web_contents();
1094 contents->GetController().LoadURL(page,
1095 content::Referrer(),
1096 ui::PAGE_TRANSITION_AUTO_TOPLEVEL,
1097 std::string());
1100 void OneClickSigninHelper::CleanTransientState() {
1101 VLOG(1) << "OneClickSigninHelper::CleanTransientState";
1102 showing_signin_ = false;
1103 email_.clear();
1104 password_.clear();
1105 auto_accept_ = AUTO_ACCEPT_NONE;
1106 source_ = signin_metrics::SOURCE_UNKNOWN;
1107 switched_to_advanced_ = false;
1108 continue_url_ = GURL();
1109 untrusted_navigations_since_signin_visit_ = 0;
1110 untrusted_confirmation_required_ = false;
1111 error_message_.clear();
1113 // Post to IO thread to clear pending email.
1114 if (!do_not_clear_pending_email_) {
1115 Profile* profile =
1116 Profile::FromBrowserContext(web_contents()->GetBrowserContext());
1117 content::BrowserThread::PostTask(
1118 content::BrowserThread::IO, FROM_HERE,
1119 base::Bind(&ClearPendingEmailOnIOThread,
1120 base::Unretained(profile->GetResourceContext())));
1124 void OneClickSigninHelper::PasswordSubmitted(
1125 const autofill::PasswordForm& form) {
1126 // We only need to scrape the password for Gaia logins.
1127 if (gaia::IsGaiaSignonRealm(GURL(form.signon_realm))) {
1128 VLOG(1) << "OneClickSigninHelper::DidNavigateAnyFrame: got password";
1129 password_ = base::UTF16ToUTF8(form.password_value);
1133 void OneClickSigninHelper::SetDoNotClearPendingEmailForTesting() {
1134 do_not_clear_pending_email_ = true;
1137 void OneClickSigninHelper::set_do_not_start_sync_for_testing() {
1138 do_not_start_sync_for_testing_ = true;
1141 void OneClickSigninHelper::DidStartNavigationToPendingEntry(
1142 const GURL& url,
1143 content::NavigationController::ReloadType reload_type) {
1144 VLOG(1) << "OneClickSigninHelper::DidStartNavigationToPendingEntry: url=" <<
1145 url.spec();
1146 // If the tab navigates to a new page, and this page is not a valid Gaia
1147 // sign in redirect or reponse, or the expected continue URL, make sure to
1148 // clear the internal state. This is needed to detect navigations in the
1149 // middle of the sign in process that may redirect back to the sign in
1150 // process (see crbug.com/181163 for details).
1151 GURL::Replacements replacements;
1152 replacements.ClearQuery();
1154 if (!IsValidGaiaSigninRedirectOrResponseURL(url) &&
1155 continue_url_.is_valid() &&
1156 url.ReplaceComponents(replacements) !=
1157 continue_url_.ReplaceComponents(replacements)) {
1158 if (++untrusted_navigations_since_signin_visit_ > kMaxNavigationsSince)
1159 CleanTransientState();
1163 void OneClickSigninHelper::DidNavigateMainFrame(
1164 const content::LoadCommittedDetails& details,
1165 const content::FrameNavigateParams& params) {
1166 if (!SigninManager::IsWebBasedSigninFlowURL(params.url)) {
1167 // Make sure the renderer process is no longer considered the trusted
1168 // sign-in process when a navigation to a non-sign-in URL occurs.
1169 Profile* profile =
1170 Profile::FromBrowserContext(web_contents()->GetBrowserContext());
1171 SigninClient* signin_client =
1172 profile ? ChromeSigninClientFactory::GetForProfile(profile) : NULL;
1173 int process_id = web_contents()->GetRenderProcessHost()->GetID();
1174 if (signin_client && signin_client->IsSigninProcess(process_id))
1175 signin_client->ClearSigninProcess();
1177 // If the navigation to a non-sign-in URL hasn't been triggered by the web
1178 // contents, the sign in flow has been aborted and the state must be
1179 // cleaned (crbug.com/269421).
1180 if (!ui::PageTransitionIsWebTriggerable(params.transition) &&
1181 auto_accept_ != AUTO_ACCEPT_NONE) {
1182 CleanTransientState();
1187 void OneClickSigninHelper::DidStopLoading(
1188 content::RenderViewHost* render_view_host) {
1189 // If the user left the sign in process, clear all members.
1190 // TODO(rogerta): might need to allow some youtube URLs.
1191 content::WebContents* contents = web_contents();
1192 const GURL url = contents->GetLastCommittedURL();
1193 Profile* profile =
1194 Profile::FromBrowserContext(contents->GetBrowserContext());
1195 VLOG(1) << "OneClickSigninHelper::DidStopLoading: url=" << url.spec();
1197 if (url.scheme() == content::kChromeUIScheme) {
1198 // Suppresses OneClickSigninHelper on webUI pages to avoid inteference with
1199 // inline signin flows.
1200 VLOG(1) << "OneClickSigninHelper::DidStopLoading: suppressed for url="
1201 << url.spec();
1202 CleanTransientState();
1203 return;
1206 // If an error has already occured during the sign in flow, make sure to
1207 // display it to the user and abort the process. Do this only for
1208 // explicit sign ins.
1209 // TODO(rogerta): Could we move this code back up to ShowInfoBarUIThread()?
1210 if (!error_message_.empty() && auto_accept_ == AUTO_ACCEPT_EXPLICIT) {
1211 VLOG(1) << "OneClickSigninHelper::DidStopLoading: error=" << error_message_;
1212 RemoveSigninRedirectURLHistoryItem(contents);
1213 // After we redirect to NTP, our browser pointer gets corrupted because the
1214 // WebContents have changed, so grab the browser pointer
1215 // before the navigation.
1216 Browser* browser = chrome::FindBrowserWithWebContents(contents);
1218 // Redirect to the landing page and display an error popup.
1219 RedirectToNtpOrAppsPage(web_contents(), source_);
1220 LoginUIServiceFactory::GetForProfile(profile)->
1221 DisplayLoginResult(browser, base::UTF8ToUTF16(error_message_));
1222 CleanTransientState();
1223 return;
1226 if (AreWeShowingSignin(url, source_, email_)) {
1227 if (!showing_signin_)
1228 LogHistogramValue(signin_metrics::HISTOGRAM_SHOWN);
1229 showing_signin_ = true;
1232 // When Gaia finally redirects to the continue URL, Gaia will add some
1233 // extra query parameters. So ignore the parameters when checking to see
1234 // if the user has continued. Sometimes locales will redirect to a country-
1235 // specific TLD so just make sure it's a valid domain instead of comparing
1236 // for an exact match.
1237 GURL::Replacements replacements;
1238 replacements.ClearQuery();
1239 bool google_domain_url = google_util::IsGoogleDomainUrl(
1240 url,
1241 google_util::ALLOW_SUBDOMAIN,
1242 google_util::DISALLOW_NON_STANDARD_PORTS);
1243 const bool continue_url_match =
1244 google_domain_url &&
1245 url.ReplaceComponents(replacements).path() ==
1246 continue_url_.ReplaceComponents(replacements).path();
1247 const bool original_continue_url_match =
1248 google_domain_url &&
1249 url.ReplaceComponents(replacements).path() ==
1250 original_continue_url_.ReplaceComponents(replacements).path();
1252 if (continue_url_match)
1253 RemoveSigninRedirectURLHistoryItem(contents);
1255 // If there is no valid email yet, there is nothing to do. As of M26, the
1256 // password is allowed to be empty, since its no longer required to setup
1257 // sync.
1258 if (email_.empty()) {
1259 VLOG(1) << "OneClickSigninHelper::DidStopLoading: nothing to do";
1260 // Original-url check done because some user actions cans get us to a page
1261 // via a POST instead of a GET (and thus to immediate "cuntinue url") but
1262 // we still want redirects from the "blank.html" landing page to work for
1263 // non-security related redirects like NTP.
1264 // https://code.google.com/p/chromium/issues/detail?id=321938
1265 if (original_continue_url_match) {
1266 if (auto_accept_ == AUTO_ACCEPT_EXPLICIT)
1267 RedirectToSignin();
1268 std::string unused_value;
1269 if (net::GetValueForKeyInQuery(url, "ntp", &unused_value)) {
1270 signin::SetUserSkippedPromo(profile);
1271 RedirectToNtpOrAppsPage(web_contents(), source_);
1273 } else {
1274 if (!IsValidGaiaSigninRedirectOrResponseURL(url) &&
1275 ++untrusted_navigations_since_signin_visit_ > kMaxNavigationsSince) {
1276 CleanTransientState();
1280 return;
1283 if (!continue_url_match && IsValidGaiaSigninRedirectOrResponseURL(url))
1284 return;
1286 // During an explicit sign in, if the user has not yet reached the final
1287 // continue URL, wait for it to arrive. Note that Gaia will add some extra
1288 // query parameters to the continue URL. Ignore them when checking to
1289 // see if the user has continued.
1291 // If this is not an explicit sign in, we don't need to check if we landed
1292 // on the right continue URL. This is important because the continue URL
1293 // may itself lead to a redirect, which means this function will never see
1294 // the continue URL go by.
1295 if (auto_accept_ == AUTO_ACCEPT_EXPLICIT) {
1296 DCHECK(source_ != signin_metrics::SOURCE_UNKNOWN);
1297 if (!continue_url_match) {
1298 VLOG(1) << "OneClickSigninHelper::DidStopLoading: invalid url='"
1299 << url.spec()
1300 << "' expected continue url=" << continue_url_;
1301 CleanTransientState();
1302 return;
1305 // In explicit sign ins, the user may have changed the box
1306 // "Let me choose what to sync". This is reflected as a change in the
1307 // source of the continue URL. Make one last check of the current URL
1308 // to see if there is a valid source. If so, it overrides the
1309 // current source.
1311 // If the source was changed to SOURCE_SETTINGS, we want
1312 // OneClickSigninSyncStarter to reuse the current tab to display the
1313 // advanced configuration.
1314 signin_metrics::Source source = signin::GetSourceForPromoURL(url);
1315 if (source != source_) {
1316 source_ = source;
1317 switched_to_advanced_ = source == signin_metrics::SOURCE_SETTINGS;
1321 Browser* browser = chrome::FindBrowserWithWebContents(contents);
1323 VLOG(1) << "OneClickSigninHelper::DidStopLoading: signin is go."
1324 << " auto_accept=" << auto_accept_
1325 << " source=" << source_;
1327 switch (auto_accept_) {
1328 case AUTO_ACCEPT_NONE:
1329 if (showing_signin_)
1330 LogHistogramValue(signin_metrics::HISTOGRAM_DISMISSED);
1331 break;
1332 case AUTO_ACCEPT_ACCEPTED:
1333 LogHistogramValue(signin_metrics::HISTOGRAM_ACCEPTED);
1334 LogHistogramValue(signin_metrics::HISTOGRAM_WITH_DEFAULTS);
1335 SigninManager::DisableOneClickSignIn(profile->GetPrefs());
1336 // Start syncing with the default settings - prompt the user to sign in
1337 // first.
1338 if (!do_not_start_sync_for_testing_) {
1339 StartSync(
1340 StartSyncArgs(profile, browser, auto_accept_,
1341 session_index_, email_, password_, "",
1342 NULL /* don't force sync setup in same tab */,
1343 true /* confirmation_required */, source_,
1344 OneClickSigninSyncStarter::Callback()),
1345 OneClickSigninSyncStarter::SYNC_WITH_DEFAULT_SETTINGS);
1347 break;
1348 case AUTO_ACCEPT_CONFIGURE:
1349 LogHistogramValue(signin_metrics::HISTOGRAM_ACCEPTED);
1350 LogHistogramValue(signin_metrics::HISTOGRAM_WITH_ADVANCED);
1351 SigninManager::DisableOneClickSignIn(profile->GetPrefs());
1352 // Display the extra confirmation (even in the SAML case) in case this
1353 // was an untrusted renderer.
1354 if (!do_not_start_sync_for_testing_) {
1355 StartSync(
1356 StartSyncArgs(profile, browser, auto_accept_,
1357 session_index_, email_, password_, "",
1358 NULL /* don't force sync setup in same tab */,
1359 true /* confirmation_required */, source_,
1360 OneClickSigninSyncStarter::Callback()),
1361 OneClickSigninSyncStarter::CONFIGURE_SYNC_FIRST);
1363 break;
1364 case AUTO_ACCEPT_EXPLICIT: {
1365 signin_metrics::Source original_source =
1366 signin::GetSourceForPromoURL(original_continue_url_);
1367 if (switched_to_advanced_) {
1368 LogHistogramValue(signin_metrics::HISTOGRAM_WITH_ADVANCED);
1369 LogHistogramValue(signin_metrics::HISTOGRAM_ACCEPTED);
1370 } else {
1371 LogHistogramValue(signin_metrics::HISTOGRAM_ACCEPTED);
1372 LogHistogramValue(signin_metrics::HISTOGRAM_WITH_DEFAULTS);
1375 // - If sign in was initiated from the NTP or the hotdog menu, sync with
1376 // default settings.
1377 // - If sign in was initiated from the settings page for first time sync
1378 // set up, show the advanced sync settings dialog.
1379 // - If sign in was initiated from the settings page due to a re-auth when
1380 // sync was already setup, simply navigate back to the settings page.
1381 ProfileSyncService* sync_service =
1382 ProfileSyncServiceFactory::GetForProfile(profile);
1383 SigninErrorController* error_controller =
1384 SigninErrorControllerFactory::GetForProfile(profile);
1386 OneClickSigninSyncStarter::StartSyncMode start_mode =
1387 source_ == signin_metrics::SOURCE_SETTINGS ?
1388 (error_controller->HasError() &&
1389 sync_service && sync_service->HasSyncSetupCompleted()) ?
1390 OneClickSigninSyncStarter::SHOW_SETTINGS_WITHOUT_CONFIGURE :
1391 OneClickSigninSyncStarter::CONFIGURE_SYNC_FIRST :
1392 OneClickSigninSyncStarter::SYNC_WITH_DEFAULT_SETTINGS;
1394 if (!HandleCrossAccountError(profile, session_index_, email_, password_,
1395 "", auto_accept_, source_, start_mode,
1396 OneClickSigninSyncStarter::Callback())) {
1397 if (!do_not_start_sync_for_testing_) {
1398 StartSync(
1399 StartSyncArgs(profile, browser, auto_accept_,
1400 session_index_, email_, password_, "",
1401 contents,
1402 untrusted_confirmation_required_, source_,
1403 OneClickSigninSyncStarter::Callback()),
1404 start_mode);
1407 // If this explicit sign in is not from settings page/webstore, show
1408 // the NTP/Apps page after sign in completes. In the case of the
1409 // settings page, it will get auto-closed after sync setup. In the case
1410 // of webstore, it will redirect back to webstore.
1411 RedirectToNtpOrAppsPageIfNecessary(web_contents(), source_);
1414 // Observe the sync service if the settings tab requested a gaia sign in,
1415 // so that when sign in and sync setup are successful, we can redirect to
1416 // the correct URL, or auto-close the gaia sign in tab.
1417 if (original_source == signin_metrics::SOURCE_SETTINGS) {
1418 // The observer deletes itself once it's done.
1419 new OneClickSigninSyncObserver(contents, original_continue_url_);
1421 break;
1423 case AUTO_ACCEPT_REJECTED_FOR_PROFILE:
1424 AddEmailToOneClickRejectedList(profile, email_);
1425 LogHistogramValue(signin_metrics::HISTOGRAM_REJECTED);
1426 break;
1427 default:
1428 NOTREACHED() << "Invalid auto_accept=" << auto_accept_;
1429 break;
1432 CleanTransientState();