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_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_histogram.h"
52 #include "chrome/browser/ui/sync/one_click_signin_sync_observer.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/url_constants.h"
62 #include "components/autofill/core/common/password_form.h"
63 #include "components/password_manager/core/browser/password_manager.h"
64 #include "components/signin/core/browser/profile_oauth2_token_service.h"
65 #include "components/signin/core/browser/signin_client.h"
66 #include "components/signin/core/browser/signin_error_controller.h"
67 #include "components/signin/core/browser/signin_manager.h"
68 #include "components/signin/core/browser/signin_manager_cookie_helper.h"
69 #include "components/signin/core/common/profile_management_switches.h"
70 #include "components/sync_driver/sync_prefs.h"
71 #include "content/public/browser/browser_thread.h"
72 #include "content/public/browser/navigation_entry.h"
73 #include "content/public/browser/page_navigator.h"
74 #include "content/public/browser/render_process_host.h"
75 #include "content/public/browser/web_contents.h"
76 #include "content/public/browser/web_contents_delegate.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() 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 delete this; // Failure.
489 // StartSyncArgs --------------------------------------------------------------
491 OneClickSigninHelper::StartSyncArgs::StartSyncArgs()
494 auto_accept(AUTO_ACCEPT_NONE
),
496 confirmation_required(OneClickSigninSyncStarter::NO_CONFIRMATION
),
497 source(signin::SOURCE_UNKNOWN
) {}
499 OneClickSigninHelper::StartSyncArgs::StartSyncArgs(
502 OneClickSigninHelper::AutoAccept auto_accept
,
503 const std::string
& session_index
,
504 const std::string
& email
,
505 const std::string
& password
,
506 const std::string
& refresh_token
,
507 content::WebContents
* web_contents
,
508 bool untrusted_confirmation_required
,
509 signin::Source source
,
510 OneClickSigninSyncStarter::Callback callback
)
513 auto_accept(auto_accept
),
514 session_index(session_index
),
517 refresh_token(refresh_token
),
518 web_contents(web_contents
),
521 DCHECK(session_index
.empty() != refresh_token
.empty());
522 if (untrusted_confirmation_required
) {
523 confirmation_required
= OneClickSigninSyncStarter::CONFIRM_UNTRUSTED_SIGNIN
;
524 } else if (source
== signin::SOURCE_SETTINGS
||
525 source
== signin::SOURCE_WEBSTORE_INSTALL
) {
526 // Do not display a status confirmation for webstore installs or re-auth.
527 confirmation_required
= OneClickSigninSyncStarter::NO_CONFIRMATION
;
529 confirmation_required
= OneClickSigninSyncStarter::CONFIRM_AFTER_SIGNIN
;
533 OneClickSigninHelper::StartSyncArgs::~StartSyncArgs() {}
535 // SyncStarterWrapper ---------------------------------------------------------
537 OneClickSigninHelper::SyncStarterWrapper::SyncStarterWrapper(
538 const OneClickSigninHelper::StartSyncArgs
& args
,
539 OneClickSigninSyncStarter::StartSyncMode start_mode
)
540 : args_(args
), start_mode_(start_mode
), weak_pointer_factory_(this) {
541 BrowserList::AddObserver(this);
543 // Cache the parent desktop for the browser, so we can reuse that same
544 // desktop for any UI we want to display.
545 desktop_type_
= args_
.browser
? args_
.browser
->host_desktop_type()
546 : chrome::GetActiveDesktop();
549 OneClickSigninHelper::SyncStarterWrapper::~SyncStarterWrapper() {
550 BrowserList::RemoveObserver(this);
553 void OneClickSigninHelper::SyncStarterWrapper::Start() {
554 if (args_
.refresh_token
.empty()) {
555 if (args_
.password
.empty()) {
556 VerifyGaiaCookiesBeforeSignIn();
558 StartSigninOAuthHelper();
561 OnSigninOAuthInformationAvailable(args_
.email
, args_
.email
,
562 args_
.refresh_token
);
567 OneClickSigninHelper::SyncStarterWrapper::OnSigninOAuthInformationAvailable(
568 const std::string
& email
,
569 const std::string
& display_email
,
570 const std::string
& refresh_token
) {
571 if (!gaia::AreEmailsSame(display_email
, args_
.email
)) {
573 GoogleServiceAuthError(
574 GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS
).ToString());
576 StartOneClickSigninSyncStarter(email
, refresh_token
);
579 base::MessageLoop::current()->DeleteSoon(FROM_HERE
, this);
582 void OneClickSigninHelper::SyncStarterWrapper::OnSigninOAuthInformationFailure(
583 const GoogleServiceAuthError
& error
) {
584 DisplayErrorBubble(error
.ToString());
585 base::MessageLoop::current()->DeleteSoon(FROM_HERE
, this);
588 void OneClickSigninHelper::SyncStarterWrapper::OnBrowserRemoved(
590 if (args_
.browser
== browser
)
591 args_
.browser
= NULL
;
594 void OneClickSigninHelper::SyncStarterWrapper::VerifyGaiaCookiesBeforeSignIn() {
595 scoped_refptr
<SigninManagerCookieHelper
> cookie_helper(
596 new SigninManagerCookieHelper(
597 args_
.profile
->GetRequestContext(),
598 content::BrowserThread::GetMessageLoopProxyForThread(
599 content::BrowserThread::UI
),
600 content::BrowserThread::GetMessageLoopProxyForThread(
601 content::BrowserThread::IO
)));
602 cookie_helper
->StartFetchingGaiaCookiesOnUIThread(
603 base::Bind(&SyncStarterWrapper::OnGaiaCookiesFetched
,
604 weak_pointer_factory_
.GetWeakPtr(),
605 args_
.session_index
));
608 void OneClickSigninHelper::SyncStarterWrapper::OnGaiaCookiesFetched(
609 const std::string session_index
, const net::CookieList
& cookie_list
) {
610 net::CookieList::const_iterator it
;
611 bool success
= false;
612 for (it
= cookie_list
.begin(); it
!= cookie_list
.end(); ++it
) {
613 // Make sure the LSID cookie is set on the GAIA host, instead of a super-
615 if (it
->Name() == "LSID") {
616 if (it
->IsHostCookie() && it
->IsHttpOnly() && it
->IsSecure()) {
617 // Found a valid LSID cookie. Continue loop to make sure we don't have
618 // invalid LSID cookies on any super-domain.
628 StartSigninOAuthHelper();
631 GoogleServiceAuthError(
632 GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS
).ToString());
633 base::MessageLoop::current()->DeleteSoon(FROM_HERE
, this);
637 void OneClickSigninHelper::SyncStarterWrapper::DisplayErrorBubble(
638 const std::string
& error_message
) {
639 args_
.browser
= OneClickSigninSyncStarter::EnsureBrowser(
640 args_
.browser
, args_
.profile
, desktop_type_
);
641 args_
.browser
->window()->ShowOneClickSigninBubble(
642 BrowserWindow::ONE_CLICK_SIGNIN_BUBBLE_TYPE_BUBBLE
,
643 base::string16(), // No email required - this is not a SAML confirmation.
644 base::UTF8ToUTF16(error_message
),
645 // Callback is ignored.
646 BrowserWindow::StartSyncCallback());
649 void OneClickSigninHelper::SyncStarterWrapper::StartSigninOAuthHelper() {
650 signin_oauth_helper_
.reset(
651 new SigninOAuthHelper(args_
.profile
->GetRequestContext(),
652 args_
.session_index
, this));
656 OneClickSigninHelper::SyncStarterWrapper::StartOneClickSigninSyncStarter(
657 const std::string
& email
,
658 const std::string
& refresh_token
) {
659 // The starter deletes itself once it's done.
660 new OneClickSigninSyncStarter(args_
.profile
, args_
.browser
,
661 email
, args_
.password
,
662 refresh_token
, start_mode_
,
664 args_
.confirmation_required
,
670 // OneClickSigninHelper -------------------------------------------------------
672 DEFINE_WEB_CONTENTS_USER_DATA_KEY(OneClickSigninHelper
);
675 const int OneClickSigninHelper::kMaxNavigationsSince
= 10;
677 OneClickSigninHelper::OneClickSigninHelper(
678 content::WebContents
* web_contents
,
679 password_manager::PasswordManager
* password_manager
)
680 : content::WebContentsObserver(web_contents
),
681 showing_signin_(false),
682 auto_accept_(AUTO_ACCEPT_NONE
),
683 source_(signin::SOURCE_UNKNOWN
),
684 switched_to_advanced_(false),
685 untrusted_navigations_since_signin_visit_(0),
686 untrusted_confirmation_required_(false),
687 do_not_clear_pending_email_(false),
688 do_not_start_sync_for_testing_(false),
689 weak_pointer_factory_(this) {
690 // May be NULL during testing.
691 if (password_manager
) {
692 password_manager
->AddSubmissionCallback(
693 base::Bind(&OneClickSigninHelper::PasswordSubmitted
,
694 weak_pointer_factory_
.GetWeakPtr()));
698 OneClickSigninHelper::~OneClickSigninHelper() {}
701 void OneClickSigninHelper::LogHistogramValue(
702 signin::Source source
, int action
) {
704 case signin::SOURCE_START_PAGE
:
705 UMA_HISTOGRAM_ENUMERATION("Signin.StartPageActions", action
,
706 one_click_signin::HISTOGRAM_MAX
);
708 case signin::SOURCE_NTP_LINK
:
709 UMA_HISTOGRAM_ENUMERATION("Signin.NTPLinkActions", action
,
710 one_click_signin::HISTOGRAM_MAX
);
712 case signin::SOURCE_MENU
:
713 UMA_HISTOGRAM_ENUMERATION("Signin.MenuActions", action
,
714 one_click_signin::HISTOGRAM_MAX
);
716 case signin::SOURCE_SETTINGS
:
717 UMA_HISTOGRAM_ENUMERATION("Signin.SettingsActions", action
,
718 one_click_signin::HISTOGRAM_MAX
);
720 case signin::SOURCE_EXTENSION_INSTALL_BUBBLE
:
721 UMA_HISTOGRAM_ENUMERATION("Signin.ExtensionInstallBubbleActions", action
,
722 one_click_signin::HISTOGRAM_MAX
);
724 case signin::SOURCE_WEBSTORE_INSTALL
:
725 UMA_HISTOGRAM_ENUMERATION("Signin.WebstoreInstallActions", action
,
726 one_click_signin::HISTOGRAM_MAX
);
728 case signin::SOURCE_APP_LAUNCHER
:
729 UMA_HISTOGRAM_ENUMERATION("Signin.AppLauncherActions", action
,
730 one_click_signin::HISTOGRAM_MAX
);
732 case signin::SOURCE_APPS_PAGE_LINK
:
733 UMA_HISTOGRAM_ENUMERATION("Signin.AppsPageLinkActions", action
,
734 one_click_signin::HISTOGRAM_MAX
);
736 case signin::SOURCE_BOOKMARK_BUBBLE
:
737 UMA_HISTOGRAM_ENUMERATION("Signin.BookmarkBubbleActions", action
,
738 one_click_signin::HISTOGRAM_MAX
);
740 case signin::SOURCE_AVATAR_BUBBLE_SIGN_IN
:
741 UMA_HISTOGRAM_ENUMERATION("Signin.AvatarBubbleActions", action
,
742 one_click_signin::HISTOGRAM_MAX
);
744 case signin::SOURCE_AVATAR_BUBBLE_ADD_ACCOUNT
:
745 UMA_HISTOGRAM_ENUMERATION("Signin.AvatarBubbleActions", action
,
746 one_click_signin::HISTOGRAM_MAX
);
748 case signin::SOURCE_DEVICES_PAGE
:
749 UMA_HISTOGRAM_ENUMERATION("Signin.DevicesPageActions", action
,
750 one_click_signin::HISTOGRAM_MAX
);
752 case signin::SOURCE_REAUTH
:
753 UMA_HISTOGRAM_ENUMERATION("Signin.ReauthActions", action
,
754 one_click_signin::HISTOGRAM_MAX
);
757 // This switch statement needs to be updated when the enum Source changes.
758 COMPILE_ASSERT(signin::SOURCE_UNKNOWN
== 13,
759 kSourceEnumHasChangedButNotThisSwitchStatement
);
760 UMA_HISTOGRAM_ENUMERATION("Signin.UnknownActions", action
,
761 one_click_signin::HISTOGRAM_MAX
);
763 UMA_HISTOGRAM_ENUMERATION("Signin.AllAccessPointActions", action
,
764 one_click_signin::HISTOGRAM_MAX
);
768 void OneClickSigninHelper::CreateForWebContentsWithPasswordManager(
769 content::WebContents
* contents
,
770 password_manager::PasswordManager
* password_manager
) {
771 if (!FromWebContents(contents
)) {
772 contents
->SetUserData(UserDataKey(),
773 new OneClickSigninHelper(contents
, password_manager
));
778 bool OneClickSigninHelper::CanOffer(content::WebContents
* web_contents
,
779 CanOfferFor can_offer_for
,
780 const std::string
& email
,
781 std::string
* error_message
) {
782 DCHECK_CURRENTLY_ON(content::BrowserThread::UI
);
783 VLOG(1) << "OneClickSigninHelper::CanOffer";
786 error_message
->clear();
791 if (web_contents
->GetBrowserContext()->IsOffTheRecord())
795 Profile::FromBrowserContext(web_contents
->GetBrowserContext());
799 SigninManager
* manager
=
800 SigninManagerFactory::GetForProfile(profile
);
801 if (manager
&& !manager
->IsSigninAllowed())
804 if (can_offer_for
== CAN_OFFER_FOR_INTERSTITAL_ONLY
&&
805 !profile
->GetPrefs()->GetBoolean(prefs::kReverseAutologinEnabled
))
808 if (!ChromeSigninClient::ProfileAllowsSigninCookies(profile
))
811 if (!email
.empty()) {
815 // Make sure this username is not prohibited by policy.
816 if (!manager
->IsAllowedUsername(email
)) {
818 error_message
->assign(
819 l10n_util::GetStringUTF8(IDS_SYNC_LOGIN_NAME_PROHIBITED
));
824 if (can_offer_for
!= CAN_OFFER_FOR_SECONDARY_ACCOUNT
) {
825 // If the signin manager already has an authenticated name, then this is a
826 // re-auth scenario. Make sure the email just signed in corresponds to
827 // the one sign in manager expects.
828 std::string current_email
= manager
->GetAuthenticatedUsername();
829 const bool same_email
= gaia::AreEmailsSame(current_email
, email
);
830 if (!current_email
.empty() && !same_email
) {
831 UMA_HISTOGRAM_ENUMERATION("Signin.Reauth",
832 signin::HISTOGRAM_ACCOUNT_MISSMATCH
,
833 signin::HISTOGRAM_MAX
);
835 error_message
->assign(
836 l10n_util::GetStringFUTF8(IDS_SYNC_WRONG_EMAIL
,
837 base::UTF8ToUTF16(current_email
)));
842 // If some profile, not just the current one, is already connected to this
843 // account, don't show the infobar.
844 if (g_browser_process
&& !same_email
) {
845 ProfileManager
* manager
= g_browser_process
->profile_manager();
847 ProfileInfoCache
& cache
= manager
->GetProfileInfoCache();
848 for (size_t i
= 0; i
< cache
.GetNumberOfProfiles(); ++i
) {
849 std::string current_email
=
850 base::UTF16ToUTF8(cache
.GetUserNameOfProfileAtIndex(i
));
851 if (gaia::AreEmailsSame(email
, current_email
)) {
853 error_message
->assign(
854 l10n_util::GetStringUTF8(IDS_SYNC_USER_NAME_IN_USE_ERROR
));
863 // If email was already rejected by this profile for one-click sign-in.
864 if (can_offer_for
== CAN_OFFER_FOR_INTERSTITAL_ONLY
) {
865 const base::ListValue
* rejected_emails
= profile
->GetPrefs()->GetList(
866 prefs::kReverseAutologinRejectedEmailList
);
867 if (!rejected_emails
->empty()) {
868 base::ListValue::const_iterator iter
= rejected_emails
->Find(
869 base::StringValue(email
));
870 if (iter
!= rejected_emails
->end())
876 VLOG(1) << "OneClickSigninHelper::CanOffer: yes we can";
881 OneClickSigninHelper::Offer
OneClickSigninHelper::CanOfferOnIOThread(
882 net::URLRequest
* request
,
883 ProfileIOData
* io_data
) {
884 return CanOfferOnIOThreadImpl(request
->url(), request
, io_data
);
888 OneClickSigninHelper::Offer
OneClickSigninHelper::CanOfferOnIOThreadImpl(
890 base::SupportsUserData
* request
,
891 ProfileIOData
* io_data
) {
892 if (!gaia::IsGaiaSignonRealm(url
.GetOrigin()))
893 return IGNORE_REQUEST
;
898 // Check for incognito before other parts of the io_data, since those
899 // members may not be initalized.
900 if (io_data
->IsOffTheRecord())
903 if (!io_data
->signin_allowed()->GetValue())
906 if (!io_data
->reverse_autologin_enabled()->GetValue())
909 if (!io_data
->google_services_username()->GetValue().empty())
912 if (!ChromeSigninClient::SettingsAllowSigninCookies(
913 io_data
->GetCookieSettings()))
916 // The checks below depend on chrome already knowing what account the user
917 // signed in with. This happens only after receiving the response containing
918 // the Google-Accounts-SignIn header. Until then, if there is even a chance
919 // that we want to connect the profile, chrome needs to tell Gaia that
920 // it should offer the interstitial. Therefore missing one click data on
921 // the request means can offer is true.
922 const std::string
& pending_email
= io_data
->reverse_autologin_pending_email();
923 if (!pending_email
.empty()) {
924 if (!SigninManager::IsUsernameAllowedByPolicy(pending_email
,
925 io_data
->google_services_username_pattern()->GetValue())) {
929 std::vector
<std::string
> rejected_emails
=
930 io_data
->one_click_signin_rejected_email_list()->GetValue();
931 if (std::count_if(rejected_emails
.begin(), rejected_emails
.end(),
932 std::bind2nd(std::equal_to
<std::string
>(),
933 pending_email
)) > 0) {
937 if (io_data
->signin_names()->GetEmails().count(
938 base::UTF8ToUTF16(pending_email
)) > 0) {
947 void OneClickSigninHelper::ShowInfoBarIfPossible(net::URLRequest
* request
,
948 ProfileIOData
* io_data
,
951 std::string google_chrome_signin_value
;
952 std::string google_accounts_signin_value
;
953 request
->GetResponseHeaderByName("Google-Chrome-SignIn",
954 &google_chrome_signin_value
);
955 request
->GetResponseHeaderByName("Google-Accounts-SignIn",
956 &google_accounts_signin_value
);
958 if (!google_accounts_signin_value
.empty() ||
959 !google_chrome_signin_value
.empty()) {
960 VLOG(1) << "OneClickSigninHelper::ShowInfoBarIfPossible:"
961 << " g-a-s='" << google_accounts_signin_value
<< "'"
962 << " g-c-s='" << google_chrome_signin_value
<< "'";
965 if (!gaia::IsGaiaSignonRealm(request
->url().GetOrigin()))
968 // Parse Google-Accounts-SignIn.
969 std::vector
<std::pair
<std::string
, std::string
> > pairs
;
970 base::SplitStringIntoKeyValuePairs(google_accounts_signin_value
, '=', ',',
972 std::string session_index
;
974 for (size_t i
= 0; i
< pairs
.size(); ++i
) {
975 const std::pair
<std::string
, std::string
>& pair
= pairs
[i
];
976 const std::string
& key
= pair
.first
;
977 const std::string
& value
= pair
.second
;
978 if (key
== "email") {
979 base::TrimString(value
, "\"", &email
);
980 } else if (key
== "sessionindex") {
981 session_index
= value
;
985 // Later in the chain of this request, we'll need to check the email address
986 // in the IO thread (see CanOfferOnIOThread). So save the email address as
987 // user data on the request (only for web-based flow).
989 io_data
->set_reverse_autologin_pending_email(email
);
991 if (!email
.empty() || !session_index
.empty()) {
992 VLOG(1) << "OneClickSigninHelper::ShowInfoBarIfPossible:"
993 << " email=" << email
994 << " sessionindex=" << session_index
;
997 // Parse Google-Chrome-SignIn.
998 AutoAccept auto_accept
= AUTO_ACCEPT_NONE
;
999 signin::Source source
= signin::SOURCE_UNKNOWN
;
1001 std::vector
<std::string
> tokens
;
1002 base::SplitString(google_chrome_signin_value
, ',', &tokens
);
1003 for (size_t i
= 0; i
< tokens
.size(); ++i
) {
1004 const std::string
& token
= tokens
[i
];
1005 if (token
== "accepted") {
1006 auto_accept
= AUTO_ACCEPT_ACCEPTED
;
1007 } else if (token
== "configure") {
1008 auto_accept
= AUTO_ACCEPT_CONFIGURE
;
1009 } else if (token
== "rejected-for-profile") {
1010 auto_accept
= AUTO_ACCEPT_REJECTED_FOR_PROFILE
;
1014 // If this is an explicit sign in (i.e., first run, NTP, Apps page, menu,
1015 // settings) then force the auto accept type to explicit.
1016 source
= GetSigninSource(request
->url(), &continue_url
);
1017 if (source
!= signin::SOURCE_UNKNOWN
)
1018 auto_accept
= AUTO_ACCEPT_EXPLICIT
;
1020 if (auto_accept
!= AUTO_ACCEPT_NONE
) {
1021 VLOG(1) << "OneClickSigninHelper::ShowInfoBarIfPossible:"
1022 << " auto_accept=" << auto_accept
;
1025 // If |session_index|, |email|, |auto_accept|, and |continue_url| all have
1026 // their default value, don't bother posting a task to the UI thread.
1027 // It will be a noop anyway.
1029 // The two headers above may (but not always) come in different http requests
1030 // so a post to the UI thread is still needed if |auto_accept| is not its
1031 // default value, but |email| and |session_index| are.
1032 if (session_index
.empty() && email
.empty() &&
1033 auto_accept
== AUTO_ACCEPT_NONE
&& !continue_url
.is_valid()) {
1037 content::BrowserThread::PostTask(
1038 content::BrowserThread::UI
, FROM_HERE
,
1039 base::Bind(&OneClickSigninHelper::ShowInfoBarUIThread
, session_index
,
1040 email
, auto_accept
, source
, continue_url
, child_id
, route_id
));
1044 void OneClickSigninHelper::LogConfirmHistogramValue(int action
) {
1045 UMA_HISTOGRAM_ENUMERATION("Signin.OneClickConfirmation", action
,
1046 one_click_signin::HISTOGRAM_CONFIRM_MAX
);
1049 void OneClickSigninHelper::ShowInfoBarUIThread(
1050 const std::string
& session_index
,
1051 const std::string
& email
,
1052 AutoAccept auto_accept
,
1053 signin::Source source
,
1054 const GURL
& continue_url
,
1057 DCHECK_CURRENTLY_ON(content::BrowserThread::UI
);
1059 content::WebContents
* web_contents
= tab_util::GetWebContentsByID(child_id
,
1064 // TODO(mathp): The appearance of this infobar should be tested using a
1066 OneClickSigninHelper
* helper
=
1067 OneClickSigninHelper::FromWebContents(web_contents
);
1071 if (auto_accept
!= AUTO_ACCEPT_NONE
)
1072 helper
->auto_accept_
= auto_accept
;
1074 if (source
!= signin::SOURCE_UNKNOWN
&&
1075 helper
->source_
== signin::SOURCE_UNKNOWN
) {
1076 helper
->source_
= source
;
1079 // Save the email in the one-click signin manager. The manager may
1080 // not exist if the contents is incognito or if the profile is already
1081 // connected to a Google account.
1082 if (!session_index
.empty())
1083 helper
->session_index_
= session_index
;
1086 helper
->email_
= email
;
1088 CanOfferFor can_offer_for
=
1089 (auto_accept
!= AUTO_ACCEPT_EXPLICIT
&&
1090 helper
->auto_accept_
!= AUTO_ACCEPT_EXPLICIT
) ?
1091 CAN_OFFER_FOR_INTERSTITAL_ONLY
: CAN_OFFER_FOR_ALL
;
1093 std::string error_message
;
1095 if (!web_contents
|| !CanOffer(web_contents
, can_offer_for
, email
,
1097 VLOG(1) << "OneClickSigninHelper::ShowInfoBarUIThread: not offering";
1098 // TODO(rogerta): Can we just display our error now instead of keeping it
1099 // around and doing it later?
1100 if (helper
&& helper
->error_message_
.empty() && !error_message
.empty())
1101 helper
->error_message_
= error_message
;
1106 // Only allow the dedicated signin process to sign the user into
1107 // Chrome without intervention, because it doesn't load any untrusted
1108 // pages. If at any point an untrusted page is detected, chrome will
1109 // show a modal dialog asking the user to confirm.
1111 Profile::FromBrowserContext(web_contents
->GetBrowserContext());
1112 SigninClient
* signin_client
=
1113 profile
? ChromeSigninClientFactory::GetForProfile(profile
) : NULL
;
1114 helper
->untrusted_confirmation_required_
|=
1115 (signin_client
&& !signin_client
->IsSigninProcess(child_id
));
1117 if (continue_url
.is_valid()) {
1118 // Set |original_continue_url_| if it is currently empty. |continue_url|
1119 // could be modified by gaia pages, thus we need to record the original
1120 // continue url to navigate back to the right page when sync setup is
1122 if (helper
->original_continue_url_
.is_empty())
1123 helper
->original_continue_url_
= continue_url
;
1124 helper
->continue_url_
= continue_url
;
1129 void OneClickSigninHelper::RemoveSigninRedirectURLHistoryItem(
1130 content::WebContents
* web_contents
) {
1131 // Only actually remove the item if it's the blank.html continue url.
1132 if (signin::IsContinueUrlForWebBasedSigninFlow(
1133 web_contents
->GetLastCommittedURL())) {
1134 new CurrentHistoryCleaner(web_contents
); // will self-destruct when done
1139 void OneClickSigninHelper::ShowSigninErrorBubble(Browser
* browser
,
1140 const std::string
& error
) {
1141 DCHECK(!error
.empty());
1143 browser
->window()->ShowOneClickSigninBubble(
1144 BrowserWindow::ONE_CLICK_SIGNIN_BUBBLE_TYPE_BUBBLE
,
1145 base::string16(), /* no SAML email */
1146 base::UTF8ToUTF16(error
),
1147 // This callback is never invoked.
1148 // TODO(rogerta): Separate out the bubble API so we don't have to pass
1149 // ignored |email| and |callback| params.
1150 BrowserWindow::StartSyncCallback());
1154 bool OneClickSigninHelper::HandleCrossAccountError(
1155 content::WebContents
* contents
,
1156 const std::string
& session_index
,
1157 const std::string
& email
,
1158 const std::string
& password
,
1159 const std::string
& refresh_token
,
1160 OneClickSigninHelper::AutoAccept auto_accept
,
1161 signin::Source source
,
1162 OneClickSigninSyncStarter::StartSyncMode start_mode
,
1163 OneClickSigninSyncStarter::Callback sync_callback
) {
1165 Profile::FromBrowserContext(contents
->GetBrowserContext());
1166 std::string last_email
=
1167 profile
->GetPrefs()->GetString(prefs::kGoogleServicesLastUsername
);
1169 if (!last_email
.empty() && !gaia::AreEmailsSame(last_email
, email
)) {
1170 // If the new email address is different from the email address that
1171 // just signed in, show a confirmation dialog.
1173 // No need to display a second confirmation so pass false below.
1174 // TODO(atwilson): Move this into OneClickSigninSyncStarter.
1175 // The tab modal dialog always executes its callback before |contents|
1177 Browser
* browser
= chrome::FindBrowserWithWebContents(contents
);
1178 ConfirmEmailDialogDelegate::AskForConfirmation(
1184 StartSyncArgs(profile
, browser
, auto_accept
,
1185 session_index
, email
, password
, refresh_token
,
1186 contents
, false /* confirmation_required */, source
,
1197 void OneClickSigninHelper::RedirectToNtpOrAppsPage(
1198 content::WebContents
* contents
, signin::Source source
) {
1199 // Do nothing if a navigation is pending, since this call can be triggered
1200 // from DidStartLoading. This avoids deleting the pending entry while we are
1201 // still navigating to it. See crbug/346632.
1202 if (contents
->GetController().GetPendingEntry())
1205 VLOG(1) << "RedirectToNtpOrAppsPage";
1206 // Redirect to NTP/Apps page and display a confirmation bubble
1207 GURL
url(source
== signin::SOURCE_APPS_PAGE_LINK
?
1208 chrome::kChromeUIAppsURL
: chrome::kChromeUINewTabURL
);
1209 content::OpenURLParams
params(url
,
1210 content::Referrer(),
1212 content::PAGE_TRANSITION_AUTO_TOPLEVEL
,
1214 contents
->OpenURL(params
);
1218 void OneClickSigninHelper::RedirectToNtpOrAppsPageIfNecessary(
1219 content::WebContents
* contents
, signin::Source source
) {
1220 if (source
!= signin::SOURCE_SETTINGS
&&
1221 source
!= signin::SOURCE_WEBSTORE_INSTALL
) {
1222 RedirectToNtpOrAppsPage(contents
, source
);
1226 void OneClickSigninHelper::RedirectToSignin() {
1227 VLOG(1) << "OneClickSigninHelper::RedirectToSignin";
1229 // Extract the existing sounce=X value. Default to "2" if missing.
1230 signin::Source source
= signin::GetSourceForPromoURL(continue_url_
);
1231 if (source
== signin::SOURCE_UNKNOWN
)
1232 source
= signin::SOURCE_MENU
;
1233 GURL page
= signin::GetPromoURL(source
, false);
1235 content::WebContents
* contents
= web_contents();
1236 contents
->GetController().LoadURL(page
,
1237 content::Referrer(),
1238 content::PAGE_TRANSITION_AUTO_TOPLEVEL
,
1242 void OneClickSigninHelper::CleanTransientState() {
1243 VLOG(1) << "OneClickSigninHelper::CleanTransientState";
1244 showing_signin_
= false;
1247 auto_accept_
= AUTO_ACCEPT_NONE
;
1248 source_
= signin::SOURCE_UNKNOWN
;
1249 switched_to_advanced_
= false;
1250 continue_url_
= GURL();
1251 untrusted_navigations_since_signin_visit_
= 0;
1252 untrusted_confirmation_required_
= false;
1253 error_message_
.clear();
1255 // Post to IO thread to clear pending email.
1256 if (!do_not_clear_pending_email_
) {
1258 Profile::FromBrowserContext(web_contents()->GetBrowserContext());
1259 content::BrowserThread::PostTask(
1260 content::BrowserThread::IO
, FROM_HERE
,
1261 base::Bind(&ClearPendingEmailOnIOThread
,
1262 base::Unretained(profile
->GetResourceContext())));
1266 void OneClickSigninHelper::PasswordSubmitted(
1267 const autofill::PasswordForm
& form
) {
1268 // We only need to scrape the password for Gaia logins.
1269 if (gaia::IsGaiaSignonRealm(GURL(form
.signon_realm
))) {
1270 VLOG(1) << "OneClickSigninHelper::DidNavigateAnyFrame: got password";
1271 password_
= base::UTF16ToUTF8(form
.password_value
);
1275 void OneClickSigninHelper::SetDoNotClearPendingEmailForTesting() {
1276 do_not_clear_pending_email_
= true;
1279 void OneClickSigninHelper::set_do_not_start_sync_for_testing() {
1280 do_not_start_sync_for_testing_
= true;
1283 void OneClickSigninHelper::DidStartNavigationToPendingEntry(
1285 content::NavigationController::ReloadType reload_type
) {
1286 VLOG(1) << "OneClickSigninHelper::DidStartNavigationToPendingEntry: url=" <<
1288 // If the tab navigates to a new page, and this page is not a valid Gaia
1289 // sign in redirect or reponse, or the expected continue URL, make sure to
1290 // clear the internal state. This is needed to detect navigations in the
1291 // middle of the sign in process that may redirect back to the sign in
1292 // process (see crbug.com/181163 for details).
1293 GURL::Replacements replacements
;
1294 replacements
.ClearQuery();
1296 if (!IsValidGaiaSigninRedirectOrResponseURL(url
) &&
1297 continue_url_
.is_valid() &&
1298 url
.ReplaceComponents(replacements
) !=
1299 continue_url_
.ReplaceComponents(replacements
)) {
1300 if (++untrusted_navigations_since_signin_visit_
> kMaxNavigationsSince
)
1301 CleanTransientState();
1305 void OneClickSigninHelper::DidNavigateMainFrame(
1306 const content::LoadCommittedDetails
& details
,
1307 const content::FrameNavigateParams
& params
) {
1308 if (!SigninManager::IsWebBasedSigninFlowURL(params
.url
)) {
1309 // Make sure the renderer process is no longer considered the trusted
1310 // sign-in process when a navigation to a non-sign-in URL occurs.
1312 Profile::FromBrowserContext(web_contents()->GetBrowserContext());
1313 SigninClient
* signin_client
=
1314 profile
? ChromeSigninClientFactory::GetForProfile(profile
) : NULL
;
1315 int process_id
= web_contents()->GetRenderProcessHost()->GetID();
1316 if (signin_client
&& signin_client
->IsSigninProcess(process_id
))
1317 signin_client
->ClearSigninProcess();
1319 // If the navigation to a non-sign-in URL hasn't been triggered by the web
1320 // contents, the sign in flow has been aborted and the state must be
1321 // cleaned (crbug.com/269421).
1322 if (!content::PageTransitionIsWebTriggerable(params
.transition
) &&
1323 auto_accept_
!= AUTO_ACCEPT_NONE
) {
1324 CleanTransientState();
1329 void OneClickSigninHelper::DidStopLoading(
1330 content::RenderViewHost
* render_view_host
) {
1331 // If the user left the sign in process, clear all members.
1332 // TODO(rogerta): might need to allow some youtube URLs.
1333 content::WebContents
* contents
= web_contents();
1334 const GURL url
= contents
->GetLastCommittedURL();
1336 Profile::FromBrowserContext(contents
->GetBrowserContext());
1337 VLOG(1) << "OneClickSigninHelper::DidStopLoading: url=" << url
.spec();
1339 if (url
.scheme() == content::kChromeUIScheme
) {
1340 // Suppresses OneClickSigninHelper on webUI pages to avoid inteference with
1341 // inline signin flows.
1342 VLOG(1) << "OneClickSigninHelper::DidStopLoading: suppressed for url="
1344 CleanTransientState();
1348 // If an error has already occured during the sign in flow, make sure to
1349 // display it to the user and abort the process. Do this only for
1350 // explicit sign ins.
1351 // TODO(rogerta): Could we move this code back up to ShowInfoBarUIThread()?
1352 if (!error_message_
.empty() && auto_accept_
== AUTO_ACCEPT_EXPLICIT
) {
1353 VLOG(1) << "OneClickSigninHelper::DidStopLoading: error=" << error_message_
;
1354 RemoveSigninRedirectURLHistoryItem(contents
);
1355 // After we redirect to NTP, our browser pointer gets corrupted because the
1356 // WebContents have changed, so grab the browser pointer
1357 // before the navigation.
1358 Browser
* browser
= chrome::FindBrowserWithWebContents(contents
);
1360 // Redirect to the landing page and display an error popup.
1361 RedirectToNtpOrAppsPage(web_contents(), source_
);
1362 ShowSigninErrorBubble(browser
, error_message_
);
1363 CleanTransientState();
1367 if (AreWeShowingSignin(url
, source_
, email_
)) {
1368 if (!showing_signin_
) {
1369 if (source_
== signin::SOURCE_UNKNOWN
)
1370 LogOneClickHistogramValue(one_click_signin::HISTOGRAM_SHOWN
);
1372 LogHistogramValue(source_
, one_click_signin::HISTOGRAM_SHOWN
);
1374 showing_signin_
= true;
1377 // When Gaia finally redirects to the continue URL, Gaia will add some
1378 // extra query parameters. So ignore the parameters when checking to see
1379 // if the user has continued. Sometimes locales will redirect to a country-
1380 // specific TLD so just make sure it's a valid domain instead of comparing
1381 // for an exact match.
1382 GURL::Replacements replacements
;
1383 replacements
.ClearQuery();
1384 bool google_domain_url
= google_util::IsGoogleDomainUrl(
1386 google_util::ALLOW_SUBDOMAIN
,
1387 google_util::DISALLOW_NON_STANDARD_PORTS
);
1388 const bool continue_url_match
=
1389 google_domain_url
&&
1390 url
.ReplaceComponents(replacements
).path() ==
1391 continue_url_
.ReplaceComponents(replacements
).path();
1392 const bool original_continue_url_match
=
1393 google_domain_url
&&
1394 url
.ReplaceComponents(replacements
).path() ==
1395 original_continue_url_
.ReplaceComponents(replacements
).path();
1397 if (continue_url_match
)
1398 RemoveSigninRedirectURLHistoryItem(contents
);
1400 // If there is no valid email yet, there is nothing to do. As of M26, the
1401 // password is allowed to be empty, since its no longer required to setup
1403 if (email_
.empty()) {
1404 VLOG(1) << "OneClickSigninHelper::DidStopLoading: nothing to do";
1405 // Original-url check done because some user actions cans get us to a page
1406 // via a POST instead of a GET (and thus to immediate "cuntinue url") but
1407 // we still want redirects from the "blank.html" landing page to work for
1408 // non-security related redirects like NTP.
1409 // https://code.google.com/p/chromium/issues/detail?id=321938
1410 if (original_continue_url_match
) {
1411 if (auto_accept_
== AUTO_ACCEPT_EXPLICIT
)
1413 std::string unused_value
;
1414 if (net::GetValueForKeyInQuery(url
, "ntp", &unused_value
)) {
1415 signin::SetUserSkippedPromo(profile
);
1416 RedirectToNtpOrAppsPage(web_contents(), source_
);
1419 if (!IsValidGaiaSigninRedirectOrResponseURL(url
) &&
1420 ++untrusted_navigations_since_signin_visit_
> kMaxNavigationsSince
) {
1421 CleanTransientState();
1428 if (!continue_url_match
&& IsValidGaiaSigninRedirectOrResponseURL(url
))
1431 // During an explicit sign in, if the user has not yet reached the final
1432 // continue URL, wait for it to arrive. Note that Gaia will add some extra
1433 // query parameters to the continue URL. Ignore them when checking to
1434 // see if the user has continued.
1436 // If this is not an explicit sign in, we don't need to check if we landed
1437 // on the right continue URL. This is important because the continue URL
1438 // may itself lead to a redirect, which means this function will never see
1439 // the continue URL go by.
1440 if (auto_accept_
== AUTO_ACCEPT_EXPLICIT
) {
1441 DCHECK(source_
!= signin::SOURCE_UNKNOWN
);
1442 if (!continue_url_match
) {
1443 VLOG(1) << "OneClickSigninHelper::DidStopLoading: invalid url='"
1445 << "' expected continue url=" << continue_url_
;
1446 CleanTransientState();
1450 // In explicit sign ins, the user may have changed the box
1451 // "Let me choose what to sync". This is reflected as a change in the
1452 // source of the continue URL. Make one last check of the current URL
1453 // to see if there is a valid source. If so, it overrides the
1456 // If the source was changed to SOURCE_SETTINGS, we want
1457 // OneClickSigninSyncStarter to reuse the current tab to display the
1458 // advanced configuration.
1459 signin::Source source
= signin::GetSourceForPromoURL(url
);
1460 if (source
!= source_
) {
1462 switched_to_advanced_
= source
== signin::SOURCE_SETTINGS
;
1466 Browser
* browser
= chrome::FindBrowserWithWebContents(contents
);
1468 VLOG(1) << "OneClickSigninHelper::DidStopLoading: signin is go."
1469 << " auto_accept=" << auto_accept_
1470 << " source=" << source_
;
1472 switch (auto_accept_
) {
1473 case AUTO_ACCEPT_NONE
:
1474 if (showing_signin_
)
1475 LogOneClickHistogramValue(one_click_signin::HISTOGRAM_DISMISSED
);
1477 case AUTO_ACCEPT_ACCEPTED
:
1478 LogOneClickHistogramValue(one_click_signin::HISTOGRAM_ACCEPTED
);
1479 LogOneClickHistogramValue(one_click_signin::HISTOGRAM_WITH_DEFAULTS
);
1480 SigninManager::DisableOneClickSignIn(profile
->GetPrefs());
1481 // Start syncing with the default settings - prompt the user to sign in
1483 if (!do_not_start_sync_for_testing_
) {
1485 StartSyncArgs(profile
, browser
, auto_accept_
,
1486 session_index_
, email_
, password_
, "",
1487 NULL
/* don't force sync setup in same tab */,
1488 true /* confirmation_required */, source_
,
1489 CreateSyncStarterCallback()),
1490 OneClickSigninSyncStarter::SYNC_WITH_DEFAULT_SETTINGS
);
1493 case AUTO_ACCEPT_CONFIGURE
:
1494 LogOneClickHistogramValue(one_click_signin::HISTOGRAM_ACCEPTED
);
1495 LogOneClickHistogramValue(one_click_signin::HISTOGRAM_WITH_ADVANCED
);
1496 SigninManager::DisableOneClickSignIn(profile
->GetPrefs());
1497 // Display the extra confirmation (even in the SAML case) in case this
1498 // was an untrusted renderer.
1499 if (!do_not_start_sync_for_testing_
) {
1501 StartSyncArgs(profile
, browser
, auto_accept_
,
1502 session_index_
, email_
, password_
, "",
1503 NULL
/* don't force sync setup in same tab */,
1504 true /* confirmation_required */, source_
,
1505 CreateSyncStarterCallback()),
1506 OneClickSigninSyncStarter::CONFIGURE_SYNC_FIRST
);
1509 case AUTO_ACCEPT_EXPLICIT
: {
1510 signin::Source original_source
=
1511 signin::GetSourceForPromoURL(original_continue_url_
);
1512 if (switched_to_advanced_
) {
1513 LogHistogramValue(original_source
,
1514 one_click_signin::HISTOGRAM_WITH_ADVANCED
);
1515 LogHistogramValue(original_source
,
1516 one_click_signin::HISTOGRAM_ACCEPTED
);
1518 LogHistogramValue(source_
, one_click_signin::HISTOGRAM_ACCEPTED
);
1519 LogHistogramValue(source_
, one_click_signin::HISTOGRAM_WITH_DEFAULTS
);
1522 // - If sign in was initiated from the NTP or the hotdog menu, sync with
1523 // default settings.
1524 // - If sign in was initiated from the settings page for first time sync
1525 // set up, show the advanced sync settings dialog.
1526 // - If sign in was initiated from the settings page due to a re-auth when
1527 // sync was already setup, simply navigate back to the settings page.
1528 ProfileSyncService
* sync_service
=
1529 ProfileSyncServiceFactory::GetForProfile(profile
);
1530 SigninErrorController
* error_controller
=
1531 ProfileOAuth2TokenServiceFactory::GetForProfile(profile
)->
1532 signin_error_controller();
1534 OneClickSigninSyncStarter::StartSyncMode start_mode
=
1535 source_
== signin::SOURCE_SETTINGS
?
1536 (error_controller
->HasError() &&
1537 sync_service
&& sync_service
->HasSyncSetupCompleted()) ?
1538 OneClickSigninSyncStarter::SHOW_SETTINGS_WITHOUT_CONFIGURE
:
1539 OneClickSigninSyncStarter::CONFIGURE_SYNC_FIRST
:
1540 OneClickSigninSyncStarter::SYNC_WITH_DEFAULT_SETTINGS
;
1542 if (!HandleCrossAccountError(contents
, session_index_
, email_
, password_
,
1543 "", auto_accept_
, source_
, start_mode
,
1544 CreateSyncStarterCallback())) {
1545 if (!do_not_start_sync_for_testing_
) {
1547 StartSyncArgs(profile
, browser
, auto_accept_
,
1548 session_index_
, email_
, password_
, "",
1550 untrusted_confirmation_required_
, source_
,
1551 CreateSyncStarterCallback()),
1555 // If this explicit sign in is not from settings page/webstore, show
1556 // the NTP/Apps page after sign in completes. In the case of the
1557 // settings page, it will get auto-closed after sync setup. In the case
1558 // of webstore, it will redirect back to webstore.
1559 RedirectToNtpOrAppsPageIfNecessary(web_contents(), source_
);
1562 // Observe the sync service if the Webstore tab or the settings tab
1563 // requested a gaia sign in, so that when sign in and sync setup are
1564 // successful, we can redirect to the correct URL, or auto-close the gaia
1566 if (original_source
== signin::SOURCE_SETTINGS
||
1567 (original_source
== signin::SOURCE_WEBSTORE_INSTALL
&&
1568 source_
== signin::SOURCE_SETTINGS
)) {
1569 // The observer deletes itself once it's done.
1570 new OneClickSigninSyncObserver(contents
, original_continue_url_
);
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 OneClickSigninSyncStarter::Callback
1587 OneClickSigninHelper::CreateSyncStarterCallback() {
1588 // The callback will only be invoked if this object is still alive when sync
1589 // setup is completed. This is correct because this object is only deleted
1590 // when the web contents that potentially shows a blank page is deleted.
1591 return base::Bind(&OneClickSigninHelper::SyncSetupCompletedCallback
,
1592 weak_pointer_factory_
.GetWeakPtr());
1595 void OneClickSigninHelper::SyncSetupCompletedCallback(
1596 OneClickSigninSyncStarter::SyncSetupResult result
) {
1597 if (result
== OneClickSigninSyncStarter::SYNC_SETUP_FAILURE
&&
1599 GURL current_url
= web_contents()->GetVisibleURL();
1601 // If the web contents is showing a blank page and not about to be closed,
1602 // redirect to the NTP or apps page.
1603 if (signin::IsContinueUrlForWebBasedSigninFlow(current_url
) &&
1604 !signin::IsAutoCloseEnabledInURL(original_continue_url_
)) {
1605 RedirectToNtpOrAppsPage(
1607 signin::GetSourceForPromoURL(original_continue_url_
));