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"
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/google/google_util.h"
31 #include "chrome/browser/history/history_service.h"
32 #include "chrome/browser/history/history_service_factory.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/profile_oauth2_token_service_factory.h"
41 #include "chrome/browser/signin/signin_manager.h"
42 #include "chrome/browser/signin/signin_manager_factory.h"
43 #include "chrome/browser/signin/signin_names_io_thread.h"
44 #include "chrome/browser/sync/profile_sync_service.h"
45 #include "chrome/browser/sync/profile_sync_service_factory.h"
46 #include "chrome/browser/tab_contents/tab_util.h"
47 #include "chrome/browser/ui/browser_finder.h"
48 #include "chrome/browser/ui/browser_list.h"
49 #include "chrome/browser/ui/browser_tabstrip.h"
50 #include "chrome/browser/ui/browser_window.h"
51 #include "chrome/browser/ui/chrome_pages.h"
52 #include "chrome/browser/ui/sync/one_click_signin_histogram.h"
53 #include "chrome/browser/ui/sync/one_click_signin_sync_starter.h"
54 #include "chrome/browser/ui/sync/signin_histogram.h"
55 #include "chrome/browser/ui/tab_modal_confirm_dialog.h"
56 #include "chrome/browser/ui/tab_modal_confirm_dialog_delegate.h"
57 #include "chrome/browser/ui/tabs/tab_strip_model.h"
58 #include "chrome/common/chrome_version_info.h"
59 #include "chrome/common/net/url_util.h"
60 #include "chrome/common/pref_names.h"
61 #include "chrome/common/profile_management_switches.h"
62 #include "chrome/common/url_constants.h"
63 #include "components/autofill/core/common/password_form.h"
64 #include "components/password_manager/core/browser/password_manager.h"
65 #include "components/signin/core/browser/profile_oauth2_token_service.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_cookie_helper.h"
69 #include "components/sync_driver/sync_prefs.h"
70 #include "content/public/browser/browser_thread.h"
71 #include "content/public/browser/navigation_entry.h"
72 #include "content/public/browser/page_navigator.h"
73 #include "content/public/browser/render_process_host.h"
74 #include "content/public/browser/web_contents.h"
75 #include "content/public/browser/web_contents_delegate.h"
76 #include "content/public/browser/web_contents_view.h"
77 #include "content/public/common/frame_navigate_params.h"
78 #include "content/public/common/page_transition_types.h"
79 #include "google_apis/gaia/gaia_auth_util.h"
80 #include "google_apis/gaia/gaia_urls.h"
81 #include "grit/chromium_strings.h"
82 #include "grit/generated_resources.h"
83 #include "grit/theme_resources.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/resource/resource_bundle.h"
95 // ConfirmEmailDialogDelegate -------------------------------------------------
97 class ConfirmEmailDialogDelegate : public TabModalConfirmDialogDelegate {
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,
115 ConfirmEmailDialogDelegate(content::WebContents* contents,
116 const std::string& last_email,
117 const std::string& email,
119 virtual ~ConfirmEmailDialogDelegate();
121 // TabModalConfirmDialogDelegate:
122 virtual base::string16 GetTitle() OVERRIDE;
123 virtual base::string16 GetDialogMessage() OVERRIDE;
124 virtual base::string16 GetAcceptButtonTitle() OVERRIDE;
125 virtual base::string16 GetCancelButtonTitle() OVERRIDE;
126 virtual base::string16 GetLinkText() const OVERRIDE;
127 virtual void OnAccepted() OVERRIDE;
128 virtual void OnCanceled() OVERRIDE;
129 virtual void OnClosed() OVERRIDE;
130 virtual void OnLinkClicked(WindowOpenDisposition disposition) OVERRIDE;
132 std::string last_email_;
136 // Web contents from which the "Learn more" link should be opened.
137 content::WebContents* web_contents_;
139 DISALLOW_COPY_AND_ASSIGN(ConfirmEmailDialogDelegate);
143 void ConfirmEmailDialogDelegate::AskForConfirmation(
144 content::WebContents* contents,
145 const std::string& last_email,
146 const std::string& email,
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,
158 : TabModalConfirmDialogDelegate(contents),
159 last_email_(last_email),
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),
211 content::PAGE_TRANSITION_AUTO_TOPLEVEL,
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
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 void LogOneClickHistogramValue(int action) {
232 UMA_HISTOGRAM_ENUMERATION("Signin.OneClickActions", action,
233 one_click_signin::HISTOGRAM_MAX);
234 UMA_HISTOGRAM_ENUMERATION("Signin.AllAccessPointActions", action,
235 one_click_signin::HISTOGRAM_MAX);
238 void RedirectToNtpOrAppsPageWithIds(int child_id,
240 signin::Source source) {
241 content::WebContents* web_contents = tab_util::GetWebContentsByID(child_id,
246 OneClickSigninHelper::RedirectToNtpOrAppsPage(web_contents, source);
249 // Start syncing with the given user information.
250 void StartSync(const OneClickSigninHelper::StartSyncArgs& args,
251 OneClickSigninSyncStarter::StartSyncMode start_mode) {
252 if (start_mode == OneClickSigninSyncStarter::UNDO_SYNC) {
253 LogOneClickHistogramValue(one_click_signin::HISTOGRAM_UNDO);
257 // The wrapper deletes itself once it's done.
258 OneClickSigninHelper::SyncStarterWrapper* wrapper =
259 new OneClickSigninHelper::SyncStarterWrapper(args, start_mode);
262 int action = one_click_signin::HISTOGRAM_MAX;
263 switch (args.auto_accept) {
264 case OneClickSigninHelper::AUTO_ACCEPT_EXPLICIT:
266 case OneClickSigninHelper::AUTO_ACCEPT_ACCEPTED:
268 start_mode == OneClickSigninSyncStarter::SYNC_WITH_DEFAULT_SETTINGS ?
269 one_click_signin::HISTOGRAM_AUTO_WITH_DEFAULTS :
270 one_click_signin::HISTOGRAM_AUTO_WITH_ADVANCED;
272 case OneClickSigninHelper::AUTO_ACCEPT_CONFIGURE:
273 DCHECK(start_mode == OneClickSigninSyncStarter::CONFIGURE_SYNC_FIRST);
274 action = one_click_signin::HISTOGRAM_AUTO_WITH_ADVANCED;
277 NOTREACHED() << "Invalid auto_accept: " << args.auto_accept;
280 if (action != one_click_signin::HISTOGRAM_MAX)
281 LogOneClickHistogramValue(action);
284 void StartExplicitSync(const OneClickSigninHelper::StartSyncArgs& args,
285 content::WebContents* contents,
286 OneClickSigninSyncStarter::StartSyncMode start_mode,
287 ConfirmEmailDialogDelegate::Action action) {
288 bool enable_inline = !switches::IsEnableWebBasedSignin();
289 if (action == ConfirmEmailDialogDelegate::START_SYNC) {
290 StartSync(args, start_mode);
291 if (!enable_inline) {
292 // Redirect/tab closing for inline flow is handled by the sync callback.
293 OneClickSigninHelper::RedirectToNtpOrAppsPageIfNecessary(
294 contents, args.source);
297 // Perform a redirection to the NTP/Apps page to hide the blank page when
298 // the action is CLOSE or CREATE_NEW_USER. The redirection is useful when
299 // the action is CREATE_NEW_USER because the "Create new user" page might
300 // be opened in a different tab that is already showing settings.
302 // Redirect/tab closing for inline flow is handled by the sync callback.
303 args.callback.Run(OneClickSigninSyncStarter::SYNC_SETUP_FAILURE);
305 // Don't redirect when the visible URL is not a blank page: if the
306 // source is SOURCE_WEBSTORE_INSTALL, |contents| might be showing an app
307 // page that shouldn't be hidden.
309 // If redirecting, don't do so immediately, otherwise there may be 2
310 // nested navigations and a crash would occur (crbug.com/293261). Post
311 // the task to the current thread instead.
312 if (signin::IsContinueUrlForWebBasedSigninFlow(
313 contents->GetVisibleURL())) {
314 base::MessageLoopProxy::current()->PostNonNestableTask(
316 base::Bind(RedirectToNtpOrAppsPageWithIds,
317 contents->GetRenderProcessHost()->GetID(),
318 contents->GetRoutingID(),
322 if (action == ConfirmEmailDialogDelegate::CREATE_NEW_USER) {
323 chrome::ShowSettingsSubPage(args.browser,
324 std::string(chrome::kCreateProfileSubPage));
329 void ClearPendingEmailOnIOThread(content::ResourceContext* context) {
330 ProfileIOData* io_data = ProfileIOData::FromResourceContext(context);
332 io_data->set_reverse_autologin_pending_email(std::string());
335 // Determines the source of the sign in and the continue URL. It's either one
336 // of the known sign-in access points (first run, NTP, Apps page, menu, or
337 // settings) or it's an implicit sign in via another Google property. In the
338 // former case, "service" is also checked to make sure its "chromiumsync".
339 signin::Source GetSigninSource(const GURL& url, GURL* continue_url) {
340 DCHECK(url.is_valid());
342 net::GetValueForKeyInQuery(url, "service", &value);
343 bool possibly_an_explicit_signin = value == "chromiumsync";
345 // Find the final continue URL for this sign in. In some cases, Gaia can
346 // continue to itself, with the original continue URL buried under a couple
347 // of layers of indirection. Peel those layers away. The final destination
348 // can also be "IsGaiaSignonRealm" so stop if we get to the end (but be sure
349 // we always extract at least one "continue" value).
350 GURL local_continue_url = signin::GetNextPageURLForPromoURL(url);
351 while (gaia::IsGaiaSignonRealm(local_continue_url.GetOrigin())) {
352 GURL next_continue_url =
353 signin::GetNextPageURLForPromoURL(local_continue_url);
354 if (!next_continue_url.is_valid())
356 local_continue_url = next_continue_url;
359 if (continue_url && local_continue_url.is_valid()) {
360 DCHECK(!continue_url->is_valid() || *continue_url == local_continue_url);
361 *continue_url = local_continue_url;
364 return possibly_an_explicit_signin ?
365 signin::GetSourceForPromoURL(local_continue_url) :
366 signin::SOURCE_UNKNOWN;
369 // Returns true if |url| is a valid URL that can occur during the sign in
370 // process. Valid URLs are of the form:
372 // https://accounts.google.{TLD}/...
373 // https://accounts.youtube.com/...
374 // https://accounts.blogger.com/...
376 // All special headers used by one click sign in occur on
377 // https://accounts.google.com URLs. However, the sign in process may redirect
378 // to intermediate Gaia URLs that do not end with .com. For example, an account
379 // that uses SMS 2-factor outside the US may redirect to country specific URLs.
381 // The sign in process may also redirect to youtube and blogger account URLs
382 // so that Gaia acts as a single signon service.
383 bool IsValidGaiaSigninRedirectOrResponseURL(const GURL& url) {
384 std::string hostname = url.host();
385 if (google_util::IsGoogleHostname(hostname, google_util::ALLOW_SUBDOMAIN)) {
386 // Also using IsGaiaSignonRealm() to handle overriding with command line.
387 return gaia::IsGaiaSignonRealm(url.GetOrigin()) ||
388 StartsWithASCII(hostname, "accounts.", false);
391 GURL origin = url.GetOrigin();
392 if (origin == GURL("https://accounts.youtube.com") ||
393 origin == GURL("https://accounts.blogger.com"))
399 // Tells when we are in the process of showing either the signin to chrome page
400 // or the one click sign in to chrome page.
401 // NOTE: This should only be used for logging purposes since it relies on hard
402 // coded URLs that could change.
403 bool AreWeShowingSignin(GURL url, signin::Source source, std::string email) {
404 GURL::Replacements replacements;
405 replacements.ClearQuery();
406 GURL clean_login_url =
407 GaiaUrls::GetInstance()->service_login_url().ReplaceComponents(
410 return (url.ReplaceComponents(replacements) == clean_login_url &&
411 source != signin::SOURCE_UNKNOWN) ||
412 (IsValidGaiaSigninRedirectOrResponseURL(url) &&
413 url.spec().find("ChromeLoginPrompt") != std::string::npos &&
417 // CurrentHistoryCleaner ------------------------------------------------------
419 // Watch a webcontents and remove URL from the history once loading is complete.
420 // We have to delay the cleaning until the new URL has finished loading because
421 // we're not allowed to remove the last-loaded URL from the history. Objects
422 // of this type automatically self-destruct once they're finished their work.
423 class CurrentHistoryCleaner : public content::WebContentsObserver {
425 explicit CurrentHistoryCleaner(content::WebContents* contents);
426 virtual ~CurrentHistoryCleaner();
428 // content::WebContentsObserver:
429 virtual void WebContentsDestroyed(content::WebContents* contents) OVERRIDE;
430 virtual void DidCommitProvisionalLoadForFrame(
432 const base::string16& frame_unique_name,
435 content::PageTransition transition_type,
436 content::RenderViewHost* render_view_host) OVERRIDE;
439 scoped_ptr<content::WebContents> contents_;
440 int history_index_to_remove_;
442 DISALLOW_COPY_AND_ASSIGN(CurrentHistoryCleaner);
445 CurrentHistoryCleaner::CurrentHistoryCleaner(content::WebContents* contents)
446 : WebContentsObserver(contents) {
447 history_index_to_remove_ =
448 web_contents()->GetController().GetLastCommittedEntryIndex();
451 CurrentHistoryCleaner::~CurrentHistoryCleaner() {
454 void CurrentHistoryCleaner::DidCommitProvisionalLoadForFrame(
456 const base::string16& frame_unique_name,
459 content::PageTransition transition_type,
460 content::RenderViewHost* render_view_host) {
461 // Return early if this is not top-level navigation.
465 content::NavigationController* nc = &web_contents()->GetController();
466 HistoryService* hs = HistoryServiceFactory::GetForProfile(
467 Profile::FromBrowserContext(web_contents()->GetBrowserContext()),
468 Profile::IMPLICIT_ACCESS);
470 // Have to wait until something else gets added to history before removal.
471 if (history_index_to_remove_ < nc->GetLastCommittedEntryIndex()) {
472 content::NavigationEntry* entry =
473 nc->GetEntryAtIndex(history_index_to_remove_);
474 if (signin::IsContinueUrlForWebBasedSigninFlow(entry->GetURL())) {
475 hs->DeleteURL(entry->GetURL());
476 nc->RemoveEntryAtIndex(history_index_to_remove_);
477 delete this; // Success.
482 void CurrentHistoryCleaner::WebContentsDestroyed(
483 content::WebContents* contents) {
484 delete this; // Failure.
487 void CloseTab(content::WebContents* tab) {
488 content::WebContentsDelegate* tab_delegate = tab->GetDelegate();
490 tab_delegate->CloseContents(tab);
496 // StartSyncArgs --------------------------------------------------------------
498 OneClickSigninHelper::StartSyncArgs::StartSyncArgs()
501 auto_accept(AUTO_ACCEPT_NONE),
503 confirmation_required(OneClickSigninSyncStarter::NO_CONFIRMATION),
504 source(signin::SOURCE_UNKNOWN) {}
506 OneClickSigninHelper::StartSyncArgs::StartSyncArgs(
509 OneClickSigninHelper::AutoAccept auto_accept,
510 const std::string& session_index,
511 const std::string& email,
512 const std::string& password,
513 const std::string& refresh_token,
514 content::WebContents* web_contents,
515 bool untrusted_confirmation_required,
516 signin::Source source,
517 OneClickSigninSyncStarter::Callback callback)
520 auto_accept(auto_accept),
521 session_index(session_index),
524 refresh_token(refresh_token),
525 web_contents(web_contents),
528 DCHECK(session_index.empty() != refresh_token.empty());
529 if (untrusted_confirmation_required) {
530 confirmation_required = OneClickSigninSyncStarter::CONFIRM_UNTRUSTED_SIGNIN;
531 } else if (source == signin::SOURCE_SETTINGS ||
532 source == signin::SOURCE_WEBSTORE_INSTALL) {
533 // Do not display a status confirmation for webstore installs or re-auth.
534 confirmation_required = OneClickSigninSyncStarter::NO_CONFIRMATION;
536 confirmation_required = OneClickSigninSyncStarter::CONFIRM_AFTER_SIGNIN;
540 OneClickSigninHelper::StartSyncArgs::~StartSyncArgs() {}
542 // SyncStarterWrapper ---------------------------------------------------------
544 OneClickSigninHelper::SyncStarterWrapper::SyncStarterWrapper(
545 const OneClickSigninHelper::StartSyncArgs& args,
546 OneClickSigninSyncStarter::StartSyncMode start_mode)
547 : args_(args), start_mode_(start_mode), weak_pointer_factory_(this) {
548 BrowserList::AddObserver(this);
550 // Cache the parent desktop for the browser, so we can reuse that same
551 // desktop for any UI we want to display.
552 desktop_type_ = args_.browser ? args_.browser->host_desktop_type()
553 : chrome::GetActiveDesktop();
556 OneClickSigninHelper::SyncStarterWrapper::~SyncStarterWrapper() {
557 BrowserList::RemoveObserver(this);
560 void OneClickSigninHelper::SyncStarterWrapper::Start() {
561 if (args_.refresh_token.empty()) {
562 if (args_.password.empty()) {
563 VerifyGaiaCookiesBeforeSignIn();
565 StartSigninOAuthHelper();
568 OnSigninOAuthInformationAvailable(args_.email, args_.email,
569 args_.refresh_token);
574 OneClickSigninHelper::SyncStarterWrapper::OnSigninOAuthInformationAvailable(
575 const std::string& email,
576 const std::string& display_email,
577 const std::string& refresh_token) {
578 if (!gaia::AreEmailsSame(display_email, args_.email)) {
580 GoogleServiceAuthError(
581 GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS).ToString());
583 StartOneClickSigninSyncStarter(email, refresh_token);
586 base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
589 void OneClickSigninHelper::SyncStarterWrapper::OnSigninOAuthInformationFailure(
590 const GoogleServiceAuthError& error) {
591 DisplayErrorBubble(error.ToString());
592 base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
595 void OneClickSigninHelper::SyncStarterWrapper::OnBrowserRemoved(
597 if (args_.browser == browser)
598 args_.browser = NULL;
601 void OneClickSigninHelper::SyncStarterWrapper::VerifyGaiaCookiesBeforeSignIn() {
602 scoped_refptr<SigninManagerCookieHelper> cookie_helper(
603 new SigninManagerCookieHelper(
604 args_.profile->GetRequestContext(),
605 content::BrowserThread::GetMessageLoopProxyForThread(
606 content::BrowserThread::UI),
607 content::BrowserThread::GetMessageLoopProxyForThread(
608 content::BrowserThread::IO)));
609 cookie_helper->StartFetchingGaiaCookiesOnUIThread(
610 base::Bind(&SyncStarterWrapper::OnGaiaCookiesFetched,
611 weak_pointer_factory_.GetWeakPtr(),
612 args_.session_index));
615 void OneClickSigninHelper::SyncStarterWrapper::OnGaiaCookiesFetched(
616 const std::string session_index, const net::CookieList& cookie_list) {
617 net::CookieList::const_iterator it;
618 bool success = false;
619 for (it = cookie_list.begin(); it != cookie_list.end(); ++it) {
620 // Make sure the LSID cookie is set on the GAIA host, instead of a super-
622 if (it->Name() == "LSID") {
623 if (it->IsHostCookie() && it->IsHttpOnly() && it->IsSecure()) {
624 // Found a valid LSID cookie. Continue loop to make sure we don't have
625 // invalid LSID cookies on any super-domain.
635 StartSigninOAuthHelper();
638 GoogleServiceAuthError(
639 GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS).ToString());
640 base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
644 void OneClickSigninHelper::SyncStarterWrapper::DisplayErrorBubble(
645 const std::string& error_message) {
646 args_.browser = OneClickSigninSyncStarter::EnsureBrowser(
647 args_.browser, args_.profile, desktop_type_);
648 args_.browser->window()->ShowOneClickSigninBubble(
649 BrowserWindow::ONE_CLICK_SIGNIN_BUBBLE_TYPE_BUBBLE,
650 base::string16(), // No email required - this is not a SAML confirmation.
651 base::UTF8ToUTF16(error_message),
652 // Callback is ignored.
653 BrowserWindow::StartSyncCallback());
656 void OneClickSigninHelper::SyncStarterWrapper::StartSigninOAuthHelper() {
657 signin_oauth_helper_.reset(
658 new SigninOAuthHelper(args_.profile->GetRequestContext(),
659 args_.session_index, this));
663 OneClickSigninHelper::SyncStarterWrapper::StartOneClickSigninSyncStarter(
664 const std::string& email,
665 const std::string& refresh_token) {
666 // The starter deletes itself once it's done.
667 new OneClickSigninSyncStarter(args_.profile, args_.browser,
668 email, args_.password,
669 refresh_token, start_mode_,
671 args_.confirmation_required,
676 // OneClickSigninHelper -------------------------------------------------------
678 DEFINE_WEB_CONTENTS_USER_DATA_KEY(OneClickSigninHelper);
681 const int OneClickSigninHelper::kMaxNavigationsSince = 10;
683 OneClickSigninHelper::OneClickSigninHelper(content::WebContents* web_contents,
684 PasswordManager* password_manager)
685 : content::WebContentsObserver(web_contents),
686 showing_signin_(false),
687 auto_accept_(AUTO_ACCEPT_NONE),
688 source_(signin::SOURCE_UNKNOWN),
689 switched_to_advanced_(false),
690 untrusted_navigations_since_signin_visit_(0),
691 untrusted_confirmation_required_(false),
692 do_not_clear_pending_email_(false),
693 do_not_start_sync_for_testing_(false),
694 weak_pointer_factory_(this) {
695 // May be NULL during testing.
696 if (password_manager) {
697 password_manager->AddSubmissionCallback(
698 base::Bind(&OneClickSigninHelper::PasswordSubmitted,
699 weak_pointer_factory_.GetWeakPtr()));
703 OneClickSigninHelper::~OneClickSigninHelper() {
704 // WebContentsDestroyed() should always be called before the object is
706 DCHECK(!web_contents());
710 void OneClickSigninHelper::LogHistogramValue(
711 signin::Source source, int action) {
713 case signin::SOURCE_START_PAGE:
714 UMA_HISTOGRAM_ENUMERATION("Signin.StartPageActions", action,
715 one_click_signin::HISTOGRAM_MAX);
717 case signin::SOURCE_NTP_LINK:
718 UMA_HISTOGRAM_ENUMERATION("Signin.NTPLinkActions", action,
719 one_click_signin::HISTOGRAM_MAX);
721 case signin::SOURCE_MENU:
722 UMA_HISTOGRAM_ENUMERATION("Signin.MenuActions", action,
723 one_click_signin::HISTOGRAM_MAX);
725 case signin::SOURCE_SETTINGS:
726 UMA_HISTOGRAM_ENUMERATION("Signin.SettingsActions", action,
727 one_click_signin::HISTOGRAM_MAX);
729 case signin::SOURCE_EXTENSION_INSTALL_BUBBLE:
730 UMA_HISTOGRAM_ENUMERATION("Signin.ExtensionInstallBubbleActions", action,
731 one_click_signin::HISTOGRAM_MAX);
733 case signin::SOURCE_WEBSTORE_INSTALL:
734 UMA_HISTOGRAM_ENUMERATION("Signin.WebstoreInstallActions", action,
735 one_click_signin::HISTOGRAM_MAX);
737 case signin::SOURCE_APP_LAUNCHER:
738 UMA_HISTOGRAM_ENUMERATION("Signin.AppLauncherActions", action,
739 one_click_signin::HISTOGRAM_MAX);
741 case signin::SOURCE_APPS_PAGE_LINK:
742 UMA_HISTOGRAM_ENUMERATION("Signin.AppsPageLinkActions", action,
743 one_click_signin::HISTOGRAM_MAX);
745 case signin::SOURCE_BOOKMARK_BUBBLE:
746 UMA_HISTOGRAM_ENUMERATION("Signin.BookmarkBubbleActions", action,
747 one_click_signin::HISTOGRAM_MAX);
749 case signin::SOURCE_AVATAR_BUBBLE_SIGN_IN:
750 UMA_HISTOGRAM_ENUMERATION("Signin.AvatarBubbleActions", action,
751 one_click_signin::HISTOGRAM_MAX);
753 case signin::SOURCE_AVATAR_BUBBLE_ADD_ACCOUNT:
754 UMA_HISTOGRAM_ENUMERATION("Signin.AvatarBubbleActions", action,
755 one_click_signin::HISTOGRAM_MAX);
757 case signin::SOURCE_DEVICES_PAGE:
758 UMA_HISTOGRAM_ENUMERATION("Signin.DevicesPageActions", action,
759 one_click_signin::HISTOGRAM_MAX);
761 // This switch statement needs to be updated when the enum Source changes.
762 COMPILE_ASSERT(signin::SOURCE_UNKNOWN == 12,
763 kSourceEnumHasChangedButNotThisSwitchStatement);
767 UMA_HISTOGRAM_ENUMERATION("Signin.AllAccessPointActions", action,
768 one_click_signin::HISTOGRAM_MAX);
772 void OneClickSigninHelper::CreateForWebContentsWithPasswordManager(
773 content::WebContents* contents,
774 PasswordManager* password_manager) {
775 if (!FromWebContents(contents)) {
776 contents->SetUserData(UserDataKey(),
777 new OneClickSigninHelper(contents, password_manager));
782 bool OneClickSigninHelper::CanOffer(content::WebContents* web_contents,
783 CanOfferFor can_offer_for,
784 const std::string& email,
785 std::string* error_message) {
786 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
787 VLOG(1) << "OneClickSigninHelper::CanOffer";
790 error_message->clear();
795 if (web_contents->GetBrowserContext()->IsOffTheRecord())
799 Profile::FromBrowserContext(web_contents->GetBrowserContext());
803 SigninManager* manager =
804 SigninManagerFactory::GetForProfile(profile);
805 if (manager && !manager->IsSigninAllowed())
808 if (can_offer_for == CAN_OFFER_FOR_INTERSTITAL_ONLY &&
809 !profile->GetPrefs()->GetBoolean(prefs::kReverseAutologinEnabled))
812 if (!ChromeSigninClient::ProfileAllowsSigninCookies(profile))
815 if (!email.empty()) {
819 // Make sure this username is not prohibited by policy.
820 if (!manager->IsAllowedUsername(email)) {
822 error_message->assign(
823 l10n_util::GetStringUTF8(IDS_SYNC_LOGIN_NAME_PROHIBITED));
828 if (can_offer_for != CAN_OFFER_FOR_SECONDARY_ACCOUNT) {
829 // If the signin manager already has an authenticated name, then this is a
830 // re-auth scenario. Make sure the email just signed in corresponds to
831 // the one sign in manager expects.
832 std::string current_email = manager->GetAuthenticatedUsername();
833 const bool same_email = gaia::AreEmailsSame(current_email, email);
834 if (!current_email.empty() && !same_email) {
835 UMA_HISTOGRAM_ENUMERATION("Signin.Reauth",
836 signin::HISTOGRAM_ACCOUNT_MISSMATCH,
837 signin::HISTOGRAM_MAX);
839 error_message->assign(
840 l10n_util::GetStringFUTF8(IDS_SYNC_WRONG_EMAIL,
841 base::UTF8ToUTF16(current_email)));
846 // If some profile, not just the current one, is already connected to this
847 // account, don't show the infobar.
848 if (g_browser_process && !same_email) {
849 ProfileManager* manager = g_browser_process->profile_manager();
851 ProfileInfoCache& cache = manager->GetProfileInfoCache();
852 for (size_t i = 0; i < cache.GetNumberOfProfiles(); ++i) {
853 std::string current_email =
854 base::UTF16ToUTF8(cache.GetUserNameOfProfileAtIndex(i));
855 if (gaia::AreEmailsSame(email, current_email)) {
857 error_message->assign(
858 l10n_util::GetStringUTF8(IDS_SYNC_USER_NAME_IN_USE_ERROR));
867 // If email was already rejected by this profile for one-click sign-in.
868 if (can_offer_for == CAN_OFFER_FOR_INTERSTITAL_ONLY) {
869 const base::ListValue* rejected_emails = profile->GetPrefs()->GetList(
870 prefs::kReverseAutologinRejectedEmailList);
871 if (!rejected_emails->empty()) {
872 base::ListValue::const_iterator iter = rejected_emails->Find(
873 base::StringValue(email));
874 if (iter != rejected_emails->end())
880 VLOG(1) << "OneClickSigninHelper::CanOffer: yes we can";
885 OneClickSigninHelper::Offer OneClickSigninHelper::CanOfferOnIOThread(
886 net::URLRequest* request,
887 ProfileIOData* io_data) {
888 return CanOfferOnIOThreadImpl(request->url(), request, io_data);
892 OneClickSigninHelper::Offer OneClickSigninHelper::CanOfferOnIOThreadImpl(
894 base::SupportsUserData* request,
895 ProfileIOData* io_data) {
896 if (!gaia::IsGaiaSignonRealm(url.GetOrigin()))
897 return IGNORE_REQUEST;
902 // Check for incognito before other parts of the io_data, since those
903 // members may not be initalized.
904 if (io_data->IsOffTheRecord())
907 if (!io_data->signin_allowed()->GetValue())
910 if (!io_data->reverse_autologin_enabled()->GetValue())
913 if (!io_data->google_services_username()->GetValue().empty())
916 if (!ChromeSigninClient::SettingsAllowSigninCookies(
917 io_data->GetCookieSettings()))
920 // The checks below depend on chrome already knowing what account the user
921 // signed in with. This happens only after receiving the response containing
922 // the Google-Accounts-SignIn header. Until then, if there is even a chance
923 // that we want to connect the profile, chrome needs to tell Gaia that
924 // it should offer the interstitial. Therefore missing one click data on
925 // the request means can offer is true.
926 const std::string& pending_email = io_data->reverse_autologin_pending_email();
927 if (!pending_email.empty()) {
928 if (!SigninManager::IsUsernameAllowedByPolicy(pending_email,
929 io_data->google_services_username_pattern()->GetValue())) {
933 std::vector<std::string> rejected_emails =
934 io_data->one_click_signin_rejected_email_list()->GetValue();
935 if (std::count_if(rejected_emails.begin(), rejected_emails.end(),
936 std::bind2nd(std::equal_to<std::string>(),
937 pending_email)) > 0) {
941 if (io_data->signin_names()->GetEmails().count(
942 base::UTF8ToUTF16(pending_email)) > 0) {
951 void OneClickSigninHelper::ShowInfoBarIfPossible(net::URLRequest* request,
952 ProfileIOData* io_data,
955 std::string google_chrome_signin_value;
956 std::string google_accounts_signin_value;
957 request->GetResponseHeaderByName("Google-Chrome-SignIn",
958 &google_chrome_signin_value);
959 request->GetResponseHeaderByName("Google-Accounts-SignIn",
960 &google_accounts_signin_value);
962 if (!google_accounts_signin_value.empty() ||
963 !google_chrome_signin_value.empty()) {
964 VLOG(1) << "OneClickSigninHelper::ShowInfoBarIfPossible:"
965 << " g-a-s='" << google_accounts_signin_value << "'"
966 << " g-c-s='" << google_chrome_signin_value << "'";
969 if (!gaia::IsGaiaSignonRealm(request->url().GetOrigin()))
972 // Parse Google-Accounts-SignIn.
973 std::vector<std::pair<std::string, std::string> > pairs;
974 base::SplitStringIntoKeyValuePairs(google_accounts_signin_value, '=', ',',
976 std::string session_index;
978 for (size_t i = 0; i < pairs.size(); ++i) {
979 const std::pair<std::string, std::string>& pair = pairs[i];
980 const std::string& key = pair.first;
981 const std::string& value = pair.second;
982 if (key == "email") {
983 base::TrimString(value, "\"", &email);
984 } else if (key == "sessionindex") {
985 session_index = value;
989 // Later in the chain of this request, we'll need to check the email address
990 // in the IO thread (see CanOfferOnIOThread). So save the email address as
991 // user data on the request (only for web-based flow).
993 io_data->set_reverse_autologin_pending_email(email);
995 if (!email.empty() || !session_index.empty()) {
996 VLOG(1) << "OneClickSigninHelper::ShowInfoBarIfPossible:"
997 << " email=" << email
998 << " sessionindex=" << session_index;
1001 // Parse Google-Chrome-SignIn.
1002 AutoAccept auto_accept = AUTO_ACCEPT_NONE;
1003 signin::Source source = signin::SOURCE_UNKNOWN;
1005 std::vector<std::string> tokens;
1006 base::SplitString(google_chrome_signin_value, ',', &tokens);
1007 for (size_t i = 0; i < tokens.size(); ++i) {
1008 const std::string& token = tokens[i];
1009 if (token == "accepted") {
1010 auto_accept = AUTO_ACCEPT_ACCEPTED;
1011 } else if (token == "configure") {
1012 auto_accept = AUTO_ACCEPT_CONFIGURE;
1013 } else if (token == "rejected-for-profile") {
1014 auto_accept = AUTO_ACCEPT_REJECTED_FOR_PROFILE;
1018 // If this is an explicit sign in (i.e., first run, NTP, Apps page, menu,
1019 // settings) then force the auto accept type to explicit.
1020 source = GetSigninSource(request->url(), &continue_url);
1021 if (source != signin::SOURCE_UNKNOWN)
1022 auto_accept = AUTO_ACCEPT_EXPLICIT;
1024 if (auto_accept != AUTO_ACCEPT_NONE) {
1025 VLOG(1) << "OneClickSigninHelper::ShowInfoBarIfPossible:"
1026 << " auto_accept=" << auto_accept;
1029 // If |session_index|, |email|, |auto_accept|, and |continue_url| all have
1030 // their default value, don't bother posting a task to the UI thread.
1031 // It will be a noop anyway.
1033 // The two headers above may (but not always) come in different http requests
1034 // so a post to the UI thread is still needed if |auto_accept| is not its
1035 // default value, but |email| and |session_index| are.
1036 if (session_index.empty() && email.empty() &&
1037 auto_accept == AUTO_ACCEPT_NONE && !continue_url.is_valid()) {
1041 content::BrowserThread::PostTask(
1042 content::BrowserThread::UI, FROM_HERE,
1043 base::Bind(&OneClickSigninHelper::ShowInfoBarUIThread, session_index,
1044 email, auto_accept, source, continue_url, child_id, route_id));
1048 void OneClickSigninHelper::LogConfirmHistogramValue(int action) {
1049 UMA_HISTOGRAM_ENUMERATION("Signin.OneClickConfirmation", action,
1050 one_click_signin::HISTOGRAM_CONFIRM_MAX);
1053 void OneClickSigninHelper::ShowInfoBarUIThread(
1054 const std::string& session_index,
1055 const std::string& email,
1056 AutoAccept auto_accept,
1057 signin::Source source,
1058 const GURL& continue_url,
1061 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
1063 content::WebContents* web_contents = tab_util::GetWebContentsByID(child_id,
1068 // TODO(mathp): The appearance of this infobar should be tested using a
1070 OneClickSigninHelper* helper =
1071 OneClickSigninHelper::FromWebContents(web_contents);
1075 if (auto_accept != AUTO_ACCEPT_NONE)
1076 helper->auto_accept_ = auto_accept;
1078 if (source != signin::SOURCE_UNKNOWN &&
1079 helper->source_ == signin::SOURCE_UNKNOWN) {
1080 helper->source_ = source;
1083 // Save the email in the one-click signin manager. The manager may
1084 // not exist if the contents is incognito or if the profile is already
1085 // connected to a Google account.
1086 if (!session_index.empty())
1087 helper->session_index_ = session_index;
1090 helper->email_ = email;
1092 CanOfferFor can_offer_for =
1093 (auto_accept != AUTO_ACCEPT_EXPLICIT &&
1094 helper->auto_accept_ != AUTO_ACCEPT_EXPLICIT) ?
1095 CAN_OFFER_FOR_INTERSTITAL_ONLY : CAN_OFFER_FOR_ALL;
1097 std::string error_message;
1099 if (!web_contents || !CanOffer(web_contents, can_offer_for, email,
1101 VLOG(1) << "OneClickSigninHelper::ShowInfoBarUIThread: not offering";
1102 // TODO(rogerta): Can we just display our error now instead of keeping it
1103 // around and doing it later?
1104 if (helper && helper->error_message_.empty() && !error_message.empty())
1105 helper->error_message_ = error_message;
1110 // Only allow the dedicated signin process to sign the user into
1111 // Chrome without intervention, because it doesn't load any untrusted
1112 // pages. If at any point an untrusted page is detected, chrome will
1113 // show a modal dialog asking the user to confirm.
1115 Profile::FromBrowserContext(web_contents->GetBrowserContext());
1116 ChromeSigninClient* signin_client =
1117 profile ? ChromeSigninClientFactory::GetForProfile(profile) : NULL;
1118 helper->untrusted_confirmation_required_ |=
1119 (signin_client && !signin_client->IsSigninProcess(child_id));
1121 if (continue_url.is_valid()) {
1122 // Set |original_continue_url_| if it is currently empty. |continue_url|
1123 // could be modified by gaia pages, thus we need to record the original
1124 // continue url to navigate back to the right page when sync setup is
1126 if (helper->original_continue_url_.is_empty())
1127 helper->original_continue_url_ = continue_url;
1128 helper->continue_url_ = continue_url;
1133 void OneClickSigninHelper::RemoveSigninRedirectURLHistoryItem(
1134 content::WebContents* web_contents) {
1135 // Only actually remove the item if it's the blank.html continue url.
1136 if (signin::IsContinueUrlForWebBasedSigninFlow(
1137 web_contents->GetLastCommittedURL())) {
1138 new CurrentHistoryCleaner(web_contents); // will self-destruct when done
1143 void OneClickSigninHelper::ShowSigninErrorBubble(Browser* browser,
1144 const std::string& error) {
1145 DCHECK(!error.empty());
1147 browser->window()->ShowOneClickSigninBubble(
1148 BrowserWindow::ONE_CLICK_SIGNIN_BUBBLE_TYPE_BUBBLE,
1149 base::string16(), /* no SAML email */
1150 base::UTF8ToUTF16(error),
1151 // This callback is never invoked.
1152 // TODO(rogerta): Separate out the bubble API so we don't have to pass
1153 // ignored |email| and |callback| params.
1154 BrowserWindow::StartSyncCallback());
1158 bool OneClickSigninHelper::HandleCrossAccountError(
1159 content::WebContents* contents,
1160 const std::string& session_index,
1161 const std::string& email,
1162 const std::string& password,
1163 const std::string& refresh_token,
1164 OneClickSigninHelper::AutoAccept auto_accept,
1165 signin::Source source,
1166 OneClickSigninSyncStarter::StartSyncMode start_mode,
1167 OneClickSigninSyncStarter::Callback sync_callback) {
1169 Profile::FromBrowserContext(contents->GetBrowserContext());
1170 std::string last_email =
1171 profile->GetPrefs()->GetString(prefs::kGoogleServicesLastUsername);
1173 if (!last_email.empty() && !gaia::AreEmailsSame(last_email, email)) {
1174 // If the new email address is different from the email address that
1175 // just signed in, show a confirmation dialog.
1177 // No need to display a second confirmation so pass false below.
1178 // TODO(atwilson): Move this into OneClickSigninSyncStarter.
1179 // The tab modal dialog always executes its callback before |contents|
1181 Browser* browser = chrome::FindBrowserWithWebContents(contents);
1182 ConfirmEmailDialogDelegate::AskForConfirmation(
1188 StartSyncArgs(profile, browser, auto_accept,
1189 session_index, email, password, refresh_token,
1190 contents, false /* confirmation_required */, source,
1201 void OneClickSigninHelper::RedirectToNtpOrAppsPage(
1202 content::WebContents* contents, signin::Source source) {
1203 VLOG(1) << "RedirectToNtpOrAppsPage";
1204 // Redirect to NTP/Apps page and display a confirmation bubble
1205 GURL url(source == signin::SOURCE_APPS_PAGE_LINK ?
1206 chrome::kChromeUIAppsURL : chrome::kChromeUINewTabURL);
1207 content::OpenURLParams params(url,
1208 content::Referrer(),
1210 content::PAGE_TRANSITION_AUTO_TOPLEVEL,
1212 contents->OpenURL(params);
1216 void OneClickSigninHelper::RedirectToNtpOrAppsPageIfNecessary(
1217 content::WebContents* contents, signin::Source source) {
1218 if (source != signin::SOURCE_SETTINGS &&
1219 source != signin::SOURCE_WEBSTORE_INSTALL) {
1220 RedirectToNtpOrAppsPage(contents, source);
1224 void OneClickSigninHelper::RedirectToSignin() {
1225 VLOG(1) << "OneClickSigninHelper::RedirectToSignin";
1227 // Extract the existing sounce=X value. Default to "2" if missing.
1228 signin::Source source = signin::GetSourceForPromoURL(continue_url_);
1229 if (source == signin::SOURCE_UNKNOWN)
1230 source = signin::SOURCE_MENU;
1231 GURL page = signin::GetPromoURL(source, false);
1233 content::WebContents* contents = web_contents();
1234 contents->GetController().LoadURL(page,
1235 content::Referrer(),
1236 content::PAGE_TRANSITION_AUTO_TOPLEVEL,
1240 void OneClickSigninHelper::CleanTransientState() {
1241 VLOG(1) << "OneClickSigninHelper::CleanTransientState";
1242 showing_signin_ = false;
1245 auto_accept_ = AUTO_ACCEPT_NONE;
1246 source_ = signin::SOURCE_UNKNOWN;
1247 switched_to_advanced_ = false;
1248 continue_url_ = GURL();
1249 untrusted_navigations_since_signin_visit_ = 0;
1250 untrusted_confirmation_required_ = false;
1251 error_message_.clear();
1253 // Post to IO thread to clear pending email.
1254 if (!do_not_clear_pending_email_) {
1256 Profile::FromBrowserContext(web_contents()->GetBrowserContext());
1257 content::BrowserThread::PostTask(
1258 content::BrowserThread::IO, FROM_HERE,
1259 base::Bind(&ClearPendingEmailOnIOThread,
1260 base::Unretained(profile->GetResourceContext())));
1264 void OneClickSigninHelper::PasswordSubmitted(
1265 const autofill::PasswordForm& form) {
1266 // We only need to scrape the password for Gaia logins.
1267 if (gaia::IsGaiaSignonRealm(GURL(form.signon_realm))) {
1268 VLOG(1) << "OneClickSigninHelper::DidNavigateAnyFrame: got password";
1269 password_ = base::UTF16ToUTF8(form.password_value);
1273 void OneClickSigninHelper::SetDoNotClearPendingEmailForTesting() {
1274 do_not_clear_pending_email_ = true;
1277 void OneClickSigninHelper::set_do_not_start_sync_for_testing() {
1278 do_not_start_sync_for_testing_ = true;
1281 void OneClickSigninHelper::DidStartNavigationToPendingEntry(
1283 content::NavigationController::ReloadType reload_type) {
1284 VLOG(1) << "OneClickSigninHelper::DidStartNavigationToPendingEntry: url=" <<
1286 // If the tab navigates to a new page, and this page is not a valid Gaia
1287 // sign in redirect or reponse, or the expected continue URL, make sure to
1288 // clear the internal state. This is needed to detect navigations in the
1289 // middle of the sign in process that may redirect back to the sign in
1290 // process (see crbug.com/181163 for details).
1291 GURL::Replacements replacements;
1292 replacements.ClearQuery();
1294 if (!IsValidGaiaSigninRedirectOrResponseURL(url) &&
1295 continue_url_.is_valid() &&
1296 url.ReplaceComponents(replacements) !=
1297 continue_url_.ReplaceComponents(replacements)) {
1298 if (++untrusted_navigations_since_signin_visit_ > kMaxNavigationsSince)
1299 CleanTransientState();
1303 void OneClickSigninHelper::DidNavigateMainFrame(
1304 const content::LoadCommittedDetails& details,
1305 const content::FrameNavigateParams& params) {
1306 if (!SigninManager::IsWebBasedSigninFlowURL(params.url)) {
1307 // Make sure the renderer process is no longer considered the trusted
1308 // sign-in process when a navigation to a non-sign-in URL occurs.
1310 Profile::FromBrowserContext(web_contents()->GetBrowserContext());
1311 ChromeSigninClient* signin_client =
1312 profile ? ChromeSigninClientFactory::GetForProfile(profile) : NULL;
1313 int process_id = web_contents()->GetRenderProcessHost()->GetID();
1314 if (signin_client && signin_client->IsSigninProcess(process_id))
1315 signin_client->ClearSigninProcess();
1317 // If the navigation to a non-sign-in URL hasn't been triggered by the web
1318 // contents, the sign in flow has been aborted and the state must be
1319 // cleaned (crbug.com/269421).
1320 if (!content::PageTransitionIsWebTriggerable(params.transition) &&
1321 auto_accept_ != AUTO_ACCEPT_NONE) {
1322 CleanTransientState();
1327 void OneClickSigninHelper::DidStopLoading(
1328 content::RenderViewHost* render_view_host) {
1329 // If the user left the sign in process, clear all members.
1330 // TODO(rogerta): might need to allow some youtube URLs.
1331 content::WebContents* contents = web_contents();
1332 const GURL url = contents->GetLastCommittedURL();
1334 Profile::FromBrowserContext(contents->GetBrowserContext());
1335 VLOG(1) << "OneClickSigninHelper::DidStopLoading: url=" << url.spec();
1337 if (url.scheme() == content::kChromeUIScheme) {
1338 // Suppresses OneClickSigninHelper on webUI pages to avoid inteference with
1339 // inline signin flows.
1340 VLOG(1) << "OneClickSigninHelper::DidStopLoading: suppressed for url="
1342 CleanTransientState();
1346 // If an error has already occured during the sign in flow, make sure to
1347 // display it to the user and abort the process. Do this only for
1348 // explicit sign ins.
1349 // TODO(rogerta): Could we move this code back up to ShowInfoBarUIThread()?
1350 if (!error_message_.empty() && auto_accept_ == AUTO_ACCEPT_EXPLICIT) {
1351 VLOG(1) << "OneClickSigninHelper::DidStopLoading: error=" << error_message_;
1352 RemoveSigninRedirectURLHistoryItem(contents);
1353 // After we redirect to NTP, our browser pointer gets corrupted because the
1354 // WebContents have changed, so grab the browser pointer
1355 // before the navigation.
1356 Browser* browser = chrome::FindBrowserWithWebContents(contents);
1358 // Redirect to the landing page and display an error popup.
1359 RedirectToNtpOrAppsPage(web_contents(), source_);
1360 ShowSigninErrorBubble(browser, error_message_);
1361 CleanTransientState();
1365 if (AreWeShowingSignin(url, source_, email_)) {
1366 if (!showing_signin_) {
1367 if (source_ == signin::SOURCE_UNKNOWN)
1368 LogOneClickHistogramValue(one_click_signin::HISTOGRAM_SHOWN);
1370 LogHistogramValue(source_, one_click_signin::HISTOGRAM_SHOWN);
1372 showing_signin_ = true;
1375 // When Gaia finally redirects to the continue URL, Gaia will add some
1376 // extra query parameters. So ignore the parameters when checking to see
1377 // if the user has continued. Sometimes locales will redirect to a country-
1378 // specific TLD so just make sure it's a valid domain instead of comparing
1379 // for an exact match.
1380 GURL::Replacements replacements;
1381 replacements.ClearQuery();
1382 bool google_domain_url = google_util::IsGoogleDomainUrl(
1384 google_util::ALLOW_SUBDOMAIN,
1385 google_util::DISALLOW_NON_STANDARD_PORTS);
1386 const bool continue_url_match =
1387 google_domain_url &&
1388 url.ReplaceComponents(replacements).path() ==
1389 continue_url_.ReplaceComponents(replacements).path();
1390 const bool original_continue_url_match =
1391 google_domain_url &&
1392 url.ReplaceComponents(replacements).path() ==
1393 original_continue_url_.ReplaceComponents(replacements).path();
1395 if (continue_url_match)
1396 RemoveSigninRedirectURLHistoryItem(contents);
1398 // If there is no valid email yet, there is nothing to do. As of M26, the
1399 // password is allowed to be empty, since its no longer required to setup
1401 if (email_.empty()) {
1402 VLOG(1) << "OneClickSigninHelper::DidStopLoading: nothing to do";
1403 // Original-url check done because some user actions cans get us to a page
1404 // via a POST instead of a GET (and thus to immediate "cuntinue url") but
1405 // we still want redirects from the "blank.html" landing page to work for
1406 // non-security related redirects like NTP.
1407 // https://code.google.com/p/chromium/issues/detail?id=321938
1408 if (original_continue_url_match) {
1409 if (auto_accept_ == AUTO_ACCEPT_EXPLICIT)
1411 std::string unused_value;
1412 if (net::GetValueForKeyInQuery(url, "ntp", &unused_value)) {
1413 signin::SetUserSkippedPromo(profile);
1414 RedirectToNtpOrAppsPage(web_contents(), source_);
1417 if (!IsValidGaiaSigninRedirectOrResponseURL(url) &&
1418 ++untrusted_navigations_since_signin_visit_ > kMaxNavigationsSince) {
1419 CleanTransientState();
1426 if (!continue_url_match && IsValidGaiaSigninRedirectOrResponseURL(url))
1429 // During an explicit sign in, if the user has not yet reached the final
1430 // continue URL, wait for it to arrive. Note that Gaia will add some extra
1431 // query parameters to the continue URL. Ignore them when checking to
1432 // see if the user has continued.
1434 // If this is not an explicit sign in, we don't need to check if we landed
1435 // on the right continue URL. This is important because the continue URL
1436 // may itself lead to a redirect, which means this function will never see
1437 // the continue URL go by.
1438 if (auto_accept_ == AUTO_ACCEPT_EXPLICIT) {
1439 DCHECK(source_ != signin::SOURCE_UNKNOWN);
1440 if (!continue_url_match) {
1441 VLOG(1) << "OneClickSigninHelper::DidStopLoading: invalid url='"
1443 << "' expected continue url=" << continue_url_;
1444 CleanTransientState();
1448 // In explicit sign ins, the user may have changed the box
1449 // "Let me choose what to sync". This is reflected as a change in the
1450 // source of the continue URL. Make one last check of the current URL
1451 // to see if there is a valid source. If so, it overrides the
1454 // If the source was changed to SOURCE_SETTINGS, we want
1455 // OneClickSigninSyncStarter to reuse the current tab to display the
1456 // advanced configuration.
1457 signin::Source source = signin::GetSourceForPromoURL(url);
1458 if (source != source_) {
1460 switched_to_advanced_ = source == signin::SOURCE_SETTINGS;
1464 Browser* browser = chrome::FindBrowserWithWebContents(contents);
1466 VLOG(1) << "OneClickSigninHelper::DidStopLoading: signin is go."
1467 << " auto_accept=" << auto_accept_
1468 << " source=" << source_;
1470 switch (auto_accept_) {
1471 case AUTO_ACCEPT_NONE:
1472 if (showing_signin_)
1473 LogOneClickHistogramValue(one_click_signin::HISTOGRAM_DISMISSED);
1475 case AUTO_ACCEPT_ACCEPTED:
1476 LogOneClickHistogramValue(one_click_signin::HISTOGRAM_ACCEPTED);
1477 LogOneClickHistogramValue(one_click_signin::HISTOGRAM_WITH_DEFAULTS);
1478 SigninManager::DisableOneClickSignIn(profile);
1479 // Start syncing with the default settings - prompt the user to sign in
1481 if (!do_not_start_sync_for_testing_) {
1483 StartSyncArgs(profile, browser, auto_accept_,
1484 session_index_, email_, password_, "",
1485 NULL /* don't force sync setup in same tab */,
1486 true /* confirmation_required */, source_,
1487 CreateSyncStarterCallback()),
1488 OneClickSigninSyncStarter::SYNC_WITH_DEFAULT_SETTINGS);
1491 case AUTO_ACCEPT_CONFIGURE:
1492 LogOneClickHistogramValue(one_click_signin::HISTOGRAM_ACCEPTED);
1493 LogOneClickHistogramValue(one_click_signin::HISTOGRAM_WITH_ADVANCED);
1494 SigninManager::DisableOneClickSignIn(profile);
1495 // Display the extra confirmation (even in the SAML case) in case this
1496 // was an untrusted renderer.
1497 if (!do_not_start_sync_for_testing_) {
1499 StartSyncArgs(profile, browser, auto_accept_,
1500 session_index_, email_, password_, "",
1501 NULL /* don't force sync setup in same tab */,
1502 true /* confirmation_required */, source_,
1503 CreateSyncStarterCallback()),
1504 OneClickSigninSyncStarter::CONFIGURE_SYNC_FIRST);
1507 case AUTO_ACCEPT_EXPLICIT: {
1508 signin::Source original_source =
1509 signin::GetSourceForPromoURL(original_continue_url_);
1510 if (switched_to_advanced_) {
1511 LogHistogramValue(original_source,
1512 one_click_signin::HISTOGRAM_WITH_ADVANCED);
1513 LogHistogramValue(original_source,
1514 one_click_signin::HISTOGRAM_ACCEPTED);
1516 LogHistogramValue(source_, one_click_signin::HISTOGRAM_ACCEPTED);
1517 LogHistogramValue(source_, one_click_signin::HISTOGRAM_WITH_DEFAULTS);
1520 // - If sign in was initiated from the NTP or the hotdog menu, sync with
1521 // default settings.
1522 // - If sign in was initiated from the settings page for first time sync
1523 // set up, show the advanced sync settings dialog.
1524 // - If sign in was initiated from the settings page due to a re-auth when
1525 // sync was already setup, simply navigate back to the settings page.
1526 ProfileSyncService* sync_service =
1527 ProfileSyncServiceFactory::GetForProfile(profile);
1528 SigninErrorController* error_controller =
1529 ProfileOAuth2TokenServiceFactory::GetForProfile(profile)->
1530 signin_error_controller();
1532 OneClickSigninSyncStarter::StartSyncMode start_mode =
1533 source_ == signin::SOURCE_SETTINGS ?
1534 (error_controller->HasError() &&
1535 sync_service && sync_service->HasSyncSetupCompleted()) ?
1536 OneClickSigninSyncStarter::SHOW_SETTINGS_WITHOUT_CONFIGURE :
1537 OneClickSigninSyncStarter::CONFIGURE_SYNC_FIRST :
1538 OneClickSigninSyncStarter::SYNC_WITH_DEFAULT_SETTINGS;
1540 if (!HandleCrossAccountError(contents, session_index_, email_, password_,
1541 "", auto_accept_, source_, start_mode,
1542 CreateSyncStarterCallback())) {
1543 if (!do_not_start_sync_for_testing_) {
1545 StartSyncArgs(profile, browser, auto_accept_,
1546 session_index_, email_, password_, "",
1548 untrusted_confirmation_required_, source_,
1549 CreateSyncStarterCallback()),
1553 // If this explicit sign in is not from settings page/webstore, show
1554 // the NTP/Apps page after sign in completes. In the case of the
1555 // settings page, it will get auto-closed after sync setup. In the case
1556 // of webstore, it will redirect back to webstore.
1557 RedirectToNtpOrAppsPageIfNecessary(web_contents(), source_);
1560 // Observe the sync service if the Webstore tab or the settings tab
1561 // requested a gaia sign in, so that when sign in and sync setup are
1562 // successful, we can redirect to the correct URL, or auto-close the gaia
1564 if (original_source == signin::SOURCE_SETTINGS ||
1565 (original_source == signin::SOURCE_WEBSTORE_INSTALL &&
1566 source_ == signin::SOURCE_SETTINGS)) {
1567 ProfileSyncService* sync_service =
1568 ProfileSyncServiceFactory::GetForProfile(profile);
1570 sync_service->AddObserver(this);
1574 case AUTO_ACCEPT_REJECTED_FOR_PROFILE:
1575 AddEmailToOneClickRejectedList(profile, email_);
1576 LogOneClickHistogramValue(one_click_signin::HISTOGRAM_REJECTED);
1579 NOTREACHED() << "Invalid auto_accept=" << auto_accept_;
1583 CleanTransientState();
1586 // It is guaranteed that this method is called before the object is deleted.
1587 void OneClickSigninHelper::WebContentsDestroyed(
1588 content::WebContents* contents) {
1590 Profile::FromBrowserContext(contents->GetBrowserContext());
1591 ProfileSyncService* sync_service =
1592 ProfileSyncServiceFactory::GetForProfile(profile);
1594 sync_service->RemoveObserver(this);
1597 void OneClickSigninHelper::OnStateChanged() {
1598 // We only add observer for ProfileSyncService when original_continue_url_ is
1600 DCHECK(!original_continue_url_.is_empty());
1602 content::WebContents* contents = web_contents();
1604 Profile::FromBrowserContext(contents->GetBrowserContext());
1605 ProfileSyncService* sync_service =
1606 ProfileSyncServiceFactory::GetForProfile(profile);
1608 // At this point, the sign in process is complete, and control has been handed
1609 // back to the sync engine. Close the gaia sign in tab if
1610 // |original_continue_url_| contains the |auto_close| parameter. Otherwise,
1611 // wait for sync setup to complete and then navigate to
1612 // |original_continue_url_|.
1613 if (signin::IsAutoCloseEnabledInURL(original_continue_url_)) {
1614 // Close the gaia sign in tab via a task to make sure we aren't in the
1615 // middle of any webui handler code.
1616 base::MessageLoop::current()->PostTask(
1618 base::Bind(&CloseTab, base::Unretained(contents)));
1620 // Sync setup not completed yet.
1621 if (sync_service->FirstSetupInProgress())
1624 if (sync_service->sync_initialized() &&
1625 signin::GetSourceForPromoURL(original_continue_url_)
1626 != signin::SOURCE_SETTINGS) {
1627 contents->GetController().LoadURL(original_continue_url_,
1628 content::Referrer(),
1629 content::PAGE_TRANSITION_AUTO_TOPLEVEL,
1634 // Clears |original_continue_url_| here instead of in CleanTransientState,
1635 // because it is used in OnStateChanged which occurs later.
1636 original_continue_url_ = GURL();
1637 sync_service->RemoveObserver(this);
1640 OneClickSigninSyncStarter::Callback
1641 OneClickSigninHelper::CreateSyncStarterCallback() {
1642 // The callback will only be invoked if this object is still alive when sync
1643 // setup is completed. This is correct because this object is only deleted
1644 // when the web contents that potentially shows a blank page is deleted.
1645 return base::Bind(&OneClickSigninHelper::SyncSetupCompletedCallback,
1646 weak_pointer_factory_.GetWeakPtr());
1649 void OneClickSigninHelper::SyncSetupCompletedCallback(
1650 OneClickSigninSyncStarter::SyncSetupResult result) {
1651 if (result == OneClickSigninSyncStarter::SYNC_SETUP_FAILURE &&
1653 GURL current_url = web_contents()->GetVisibleURL();
1655 // If the web contents is showing a blank page and not about to be closed,
1656 // redirect to the NTP or apps page.
1657 if (signin::IsContinueUrlForWebBasedSigninFlow(current_url) &&
1658 !signin::IsAutoCloseEnabledInURL(original_continue_url_)) {
1659 RedirectToNtpOrAppsPage(
1661 signin::GetSourceForPromoURL(original_continue_url_));