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