Adding instrumentation to locate the source of jankiness
[chromium-blink-merge.git] / chrome / browser / ui / sync / one_click_signin_helper.cc
blobb4341f010cb4f546bb5e0c499bd34d7e7601aa1d
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/ui/sync/one_click_signin_helper.h"
7 #include <algorithm>
8 #include <functional>
9 #include <utility>
10 #include <vector>
12 #include "base/bind.h"
13 #include "base/callback_forward.h"
14 #include "base/callback_helpers.h"
15 #include "base/compiler_specific.h"
16 #include "base/memory/scoped_ptr.h"
17 #include "base/message_loop/message_loop_proxy.h"
18 #include "base/metrics/field_trial.h"
19 #include "base/metrics/histogram.h"
20 #include "base/prefs/pref_service.h"
21 #include "base/prefs/scoped_user_pref_update.h"
22 #include "base/strings/string_split.h"
23 #include "base/strings/string_util.h"
24 #include "base/strings/utf_string_conversions.h"
25 #include "base/supports_user_data.h"
26 #include "base/values.h"
27 #include "chrome/browser/browser_process.h"
28 #include "chrome/browser/chrome_notification_types.h"
29 #include "chrome/browser/defaults.h"
30 #include "chrome/browser/history/history_service.h"
31 #include "chrome/browser/history/history_service_factory.h"
32 #include "chrome/browser/profiles/profile.h"
33 #include "chrome/browser/profiles/profile_info_cache.h"
34 #include "chrome/browser/profiles/profile_io_data.h"
35 #include "chrome/browser/profiles/profile_manager.h"
36 #include "chrome/browser/search/search.h"
37 #include "chrome/browser/signin/chrome_signin_client.h"
38 #include "chrome/browser/signin/chrome_signin_client_factory.h"
39 #include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
40 #include "chrome/browser/signin/signin_manager_factory.h"
41 #include "chrome/browser/signin/signin_names_io_thread.h"
42 #include "chrome/browser/sync/profile_sync_service.h"
43 #include "chrome/browser/sync/profile_sync_service_factory.h"
44 #include "chrome/browser/tab_contents/tab_util.h"
45 #include "chrome/browser/ui/browser_finder.h"
46 #include "chrome/browser/ui/browser_list.h"
47 #include "chrome/browser/ui/browser_tabstrip.h"
48 #include "chrome/browser/ui/browser_window.h"
49 #include "chrome/browser/ui/chrome_pages.h"
50 #include "chrome/browser/ui/sync/one_click_signin_histogram.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/sync/signin_histogram.h"
54 #include "chrome/browser/ui/tab_modal_confirm_dialog.h"
55 #include "chrome/browser/ui/tab_modal_confirm_dialog_delegate.h"
56 #include "chrome/browser/ui/tabs/tab_strip_model.h"
57 #include "chrome/browser/ui/webui/signin/login_ui_service_factory.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 "chrome/grit/chromium_strings.h"
63 #include "chrome/grit/generated_resources.h"
64 #include "components/autofill/core/common/password_form.h"
65 #include "components/google/core/browser/google_util.h"
66 #include "components/password_manager/core/browser/password_manager.h"
67 #include "components/signin/core/browser/profile_oauth2_token_service.h"
68 #include "components/signin/core/browser/signin_client.h"
69 #include "components/signin/core/browser/signin_error_controller.h"
70 #include "components/signin/core/browser/signin_manager.h"
71 #include "components/signin/core/browser/signin_manager_cookie_helper.h"
72 #include "components/signin/core/common/profile_management_switches.h"
73 #include "components/sync_driver/sync_prefs.h"
74 #include "content/public/browser/browser_thread.h"
75 #include "content/public/browser/navigation_entry.h"
76 #include "content/public/browser/page_navigator.h"
77 #include "content/public/browser/render_frame_host.h"
78 #include "content/public/browser/render_process_host.h"
79 #include "content/public/browser/web_contents.h"
80 #include "content/public/browser/web_contents_delegate.h"
81 #include "content/public/common/frame_navigate_params.h"
82 #include "google_apis/gaia/gaia_auth_util.h"
83 #include "google_apis/gaia/gaia_urls.h"
84 #include "grit/components_strings.h"
85 #include "ipc/ipc_message_macros.h"
86 #include "net/base/url_util.h"
87 #include "net/cookies/cookie_monster.h"
88 #include "net/url_request/url_request.h"
89 #include "ui/base/l10n/l10n_util.h"
90 #include "ui/base/page_transition_types.h"
91 #include "url/gurl.h"
94 namespace {
96 // ConfirmEmailDialogDelegate -------------------------------------------------
98 class ConfirmEmailDialogDelegate : public TabModalConfirmDialogDelegate {
99 public:
100 enum Action {
101 CREATE_NEW_USER,
102 START_SYNC,
103 CLOSE
106 // Callback indicating action performed by the user.
107 typedef base::Callback<void(Action)> Callback;
109 // Ask the user for confirmation before starting to sync.
110 static void AskForConfirmation(content::WebContents* contents,
111 const std::string& last_email,
112 const std::string& email,
113 Callback callback);
115 private:
116 ConfirmEmailDialogDelegate(content::WebContents* contents,
117 const std::string& last_email,
118 const std::string& email,
119 Callback callback);
120 virtual ~ConfirmEmailDialogDelegate();
122 // TabModalConfirmDialogDelegate:
123 virtual base::string16 GetTitle() override;
124 virtual base::string16 GetDialogMessage() override;
125 virtual base::string16 GetAcceptButtonTitle() override;
126 virtual base::string16 GetCancelButtonTitle() override;
127 virtual base::string16 GetLinkText() const override;
128 virtual void OnAccepted() override;
129 virtual void OnCanceled() override;
130 virtual void OnClosed() override;
131 virtual void OnLinkClicked(WindowOpenDisposition disposition) override;
133 std::string last_email_;
134 std::string email_;
135 Callback callback_;
137 // Web contents from which the "Learn more" link should be opened.
138 content::WebContents* web_contents_;
140 DISALLOW_COPY_AND_ASSIGN(ConfirmEmailDialogDelegate);
143 // static
144 void ConfirmEmailDialogDelegate::AskForConfirmation(
145 content::WebContents* contents,
146 const std::string& last_email,
147 const std::string& email,
148 Callback callback) {
149 TabModalConfirmDialog::Create(
150 new ConfirmEmailDialogDelegate(contents, last_email, email,
151 callback), contents);
154 ConfirmEmailDialogDelegate::ConfirmEmailDialogDelegate(
155 content::WebContents* contents,
156 const std::string& last_email,
157 const std::string& email,
158 Callback callback)
159 : TabModalConfirmDialogDelegate(contents),
160 last_email_(last_email),
161 email_(email),
162 callback_(callback),
163 web_contents_(contents) {
166 ConfirmEmailDialogDelegate::~ConfirmEmailDialogDelegate() {
169 base::string16 ConfirmEmailDialogDelegate::GetTitle() {
170 return l10n_util::GetStringUTF16(
171 IDS_ONE_CLICK_SIGNIN_CONFIRM_EMAIL_DIALOG_TITLE);
174 base::string16 ConfirmEmailDialogDelegate::GetDialogMessage() {
175 return l10n_util::GetStringFUTF16(
176 IDS_ONE_CLICK_SIGNIN_CONFIRM_EMAIL_DIALOG_MESSAGE,
177 base::UTF8ToUTF16(last_email_), base::UTF8ToUTF16(email_));
180 base::string16 ConfirmEmailDialogDelegate::GetAcceptButtonTitle() {
181 return l10n_util::GetStringUTF16(
182 IDS_ONE_CLICK_SIGNIN_CONFIRM_EMAIL_DIALOG_OK_BUTTON);
185 base::string16 ConfirmEmailDialogDelegate::GetCancelButtonTitle() {
186 return l10n_util::GetStringUTF16(
187 IDS_ONE_CLICK_SIGNIN_CONFIRM_EMAIL_DIALOG_CANCEL_BUTTON);
190 base::string16 ConfirmEmailDialogDelegate::GetLinkText() const {
191 return l10n_util::GetStringUTF16(IDS_LEARN_MORE);
194 void ConfirmEmailDialogDelegate::OnAccepted() {
195 base::ResetAndReturn(&callback_).Run(CREATE_NEW_USER);
198 void ConfirmEmailDialogDelegate::OnCanceled() {
199 base::ResetAndReturn(&callback_).Run(START_SYNC);
202 void ConfirmEmailDialogDelegate::OnClosed() {
203 base::ResetAndReturn(&callback_).Run(CLOSE);
206 void ConfirmEmailDialogDelegate::OnLinkClicked(
207 WindowOpenDisposition disposition) {
208 content::OpenURLParams params(
209 GURL(chrome::kChromeSyncMergeTroubleshootingURL),
210 content::Referrer(),
211 NEW_POPUP,
212 ui::PAGE_TRANSITION_AUTO_TOPLEVEL,
213 false);
214 // It is guaranteed that |web_contents_| is valid here because when it's
215 // deleted, the dialog is immediately closed and no further action can be
216 // performed.
217 web_contents_->OpenURL(params);
221 // Helpers --------------------------------------------------------------------
223 // Add a specific email to the list of emails rejected for one-click
224 // sign-in, for this profile.
225 void AddEmailToOneClickRejectedList(Profile* profile,
226 const std::string& email) {
227 ListPrefUpdate updater(profile->GetPrefs(),
228 prefs::kReverseAutologinRejectedEmailList);
229 updater->AppendIfNotPresent(new base::StringValue(email));
232 void LogOneClickHistogramValue(int action) {
233 UMA_HISTOGRAM_ENUMERATION("Signin.OneClickActions", action,
234 one_click_signin::HISTOGRAM_MAX);
235 UMA_HISTOGRAM_ENUMERATION("Signin.AllAccessPointActions", action,
236 one_click_signin::HISTOGRAM_MAX);
239 void RedirectToNtpOrAppsPageWithIds(int child_id,
240 int route_id,
241 signin::Source source) {
242 content::WebContents* web_contents = tab_util::GetWebContentsByID(child_id,
243 route_id);
244 if (!web_contents)
245 return;
247 OneClickSigninHelper::RedirectToNtpOrAppsPage(web_contents, source);
250 // Start syncing with the given user information.
251 void StartSync(const OneClickSigninHelper::StartSyncArgs& args,
252 OneClickSigninSyncStarter::StartSyncMode start_mode) {
253 if (start_mode == OneClickSigninSyncStarter::UNDO_SYNC) {
254 LogOneClickHistogramValue(one_click_signin::HISTOGRAM_UNDO);
255 return;
258 // The wrapper deletes itself once it's done.
259 OneClickSigninHelper::SyncStarterWrapper* wrapper =
260 new OneClickSigninHelper::SyncStarterWrapper(args, start_mode);
261 wrapper->Start();
263 int action = one_click_signin::HISTOGRAM_MAX;
264 switch (args.auto_accept) {
265 case OneClickSigninHelper::AUTO_ACCEPT_EXPLICIT:
266 break;
267 case OneClickSigninHelper::AUTO_ACCEPT_ACCEPTED:
268 action =
269 start_mode == OneClickSigninSyncStarter::SYNC_WITH_DEFAULT_SETTINGS ?
270 one_click_signin::HISTOGRAM_AUTO_WITH_DEFAULTS :
271 one_click_signin::HISTOGRAM_AUTO_WITH_ADVANCED;
272 break;
273 case OneClickSigninHelper::AUTO_ACCEPT_CONFIGURE:
274 DCHECK(start_mode == OneClickSigninSyncStarter::CONFIGURE_SYNC_FIRST);
275 action = one_click_signin::HISTOGRAM_AUTO_WITH_ADVANCED;
276 break;
277 default:
278 NOTREACHED() << "Invalid auto_accept: " << args.auto_accept;
279 break;
281 if (action != one_click_signin::HISTOGRAM_MAX)
282 LogOneClickHistogramValue(action);
285 void StartExplicitSync(const OneClickSigninHelper::StartSyncArgs& args,
286 content::WebContents* contents,
287 OneClickSigninSyncStarter::StartSyncMode start_mode,
288 ConfirmEmailDialogDelegate::Action action) {
289 bool enable_inline = !switches::IsEnableWebBasedSignin();
290 if (action == ConfirmEmailDialogDelegate::START_SYNC) {
291 StartSync(args, start_mode);
292 if (!enable_inline) {
293 // Redirect/tab closing for inline flow is handled by the sync callback.
294 OneClickSigninHelper::RedirectToNtpOrAppsPageIfNecessary(
295 contents, args.source);
297 } else {
298 // Perform a redirection to the NTP/Apps page to hide the blank page when
299 // the action is CLOSE or CREATE_NEW_USER. The redirection is useful when
300 // the action is CREATE_NEW_USER because the "Create new user" page might
301 // be opened in a different tab that is already showing settings.
302 if (enable_inline) {
303 // Redirect/tab closing for inline flow is handled by the sync callback.
304 args.callback.Run(OneClickSigninSyncStarter::SYNC_SETUP_FAILURE);
305 } else {
306 // Redirect, but don't do so immediately; otherwise there might be two
307 // nested navigations, which would cause a crash: http://crbug.com/293261
308 // Instead, post a task to the current thread.
309 base::MessageLoopProxy::current()->PostNonNestableTask(
310 FROM_HERE,
311 base::Bind(RedirectToNtpOrAppsPageWithIds,
312 contents->GetRenderProcessHost()->GetID(),
313 contents->GetRoutingID(),
314 args.source));
316 if (action == ConfirmEmailDialogDelegate::CREATE_NEW_USER) {
317 chrome::ShowSettingsSubPage(args.browser,
318 std::string(chrome::kCreateProfileSubPage));
323 void ClearPendingEmailOnIOThread(content::ResourceContext* context) {
324 ProfileIOData* io_data = ProfileIOData::FromResourceContext(context);
325 DCHECK(io_data);
326 io_data->set_reverse_autologin_pending_email(std::string());
329 // Determines the source of the sign in and the continue URL. It's either one
330 // of the known sign-in access points (first run, NTP, Apps page, menu, or
331 // settings) or it's an implicit sign in via another Google property. In the
332 // former case, "service" is also checked to make sure its "chromiumsync".
333 signin::Source GetSigninSource(const GURL& url, GURL* continue_url) {
334 DCHECK(url.is_valid());
335 std::string value;
336 net::GetValueForKeyInQuery(url, "service", &value);
337 bool possibly_an_explicit_signin = value == "chromiumsync";
339 // Find the final continue URL for this sign in. In some cases, Gaia can
340 // continue to itself, with the original continue URL buried under a couple
341 // of layers of indirection. Peel those layers away. The final destination
342 // can also be "IsGaiaSignonRealm" so stop if we get to the end (but be sure
343 // we always extract at least one "continue" value).
344 GURL local_continue_url = signin::GetNextPageURLForPromoURL(url);
345 while (gaia::IsGaiaSignonRealm(local_continue_url.GetOrigin())) {
346 GURL next_continue_url =
347 signin::GetNextPageURLForPromoURL(local_continue_url);
348 if (!next_continue_url.is_valid())
349 break;
350 local_continue_url = next_continue_url;
353 if (continue_url && local_continue_url.is_valid()) {
354 DCHECK(!continue_url->is_valid() || *continue_url == local_continue_url);
355 *continue_url = local_continue_url;
358 return possibly_an_explicit_signin ?
359 signin::GetSourceForPromoURL(local_continue_url) :
360 signin::SOURCE_UNKNOWN;
363 // Returns true if |url| is a valid URL that can occur during the sign in
364 // process. Valid URLs are of the form:
366 // https://accounts.google.{TLD}/...
367 // https://accounts.youtube.com/...
368 // https://accounts.blogger.com/...
370 // All special headers used by one click sign in occur on
371 // https://accounts.google.com URLs. However, the sign in process may redirect
372 // to intermediate Gaia URLs that do not end with .com. For example, an account
373 // that uses SMS 2-factor outside the US may redirect to country specific URLs.
375 // The sign in process may also redirect to youtube and blogger account URLs
376 // so that Gaia acts as a single signon service.
377 bool IsValidGaiaSigninRedirectOrResponseURL(const GURL& url) {
378 std::string hostname = url.host();
379 if (google_util::IsGoogleHostname(hostname, google_util::ALLOW_SUBDOMAIN)) {
380 // Also using IsGaiaSignonRealm() to handle overriding with command line.
381 return gaia::IsGaiaSignonRealm(url.GetOrigin()) ||
382 StartsWithASCII(hostname, "accounts.", false);
385 GURL origin = url.GetOrigin();
386 if (origin == GURL("https://accounts.youtube.com") ||
387 origin == GURL("https://accounts.blogger.com"))
388 return true;
390 return false;
393 // Tells when we are in the process of showing either the signin to chrome page
394 // or the one click sign in to chrome page.
395 // NOTE: This should only be used for logging purposes since it relies on hard
396 // coded URLs that could change.
397 bool AreWeShowingSignin(GURL url, signin::Source source, std::string email) {
398 GURL::Replacements replacements;
399 replacements.ClearQuery();
400 GURL clean_login_url =
401 GaiaUrls::GetInstance()->service_login_url().ReplaceComponents(
402 replacements);
404 return (url.ReplaceComponents(replacements) == clean_login_url &&
405 source != signin::SOURCE_UNKNOWN) ||
406 (IsValidGaiaSigninRedirectOrResponseURL(url) &&
407 url.spec().find("ChromeLoginPrompt") != std::string::npos &&
408 !email.empty());
411 // If profile is valid then get signin scoped device id from signin client.
412 // Otherwise returns empty string.
413 std::string GetSigninScopedDeviceId(Profile* profile) {
414 std::string signin_scoped_device_id;
415 SigninClient* signin_client =
416 profile ? ChromeSigninClientFactory::GetForProfile(profile) : NULL;
417 if (signin_client) {
418 signin_scoped_device_id = signin_client->GetSigninScopedDeviceId();
420 return signin_scoped_device_id;
423 // CurrentHistoryCleaner ------------------------------------------------------
425 // Watch a webcontents and remove URL from the history once loading is complete.
426 // We have to delay the cleaning until the new URL has finished loading because
427 // we're not allowed to remove the last-loaded URL from the history. Objects
428 // of this type automatically self-destruct once they're finished their work.
429 class CurrentHistoryCleaner : public content::WebContentsObserver {
430 public:
431 explicit CurrentHistoryCleaner(content::WebContents* contents);
432 virtual ~CurrentHistoryCleaner();
434 // content::WebContentsObserver:
435 virtual void WebContentsDestroyed() override;
436 virtual void DidCommitProvisionalLoadForFrame(
437 content::RenderFrameHost* render_frame_host,
438 const GURL& url,
439 ui::PageTransition transition_type) override;
441 private:
442 scoped_ptr<content::WebContents> contents_;
443 int history_index_to_remove_;
445 DISALLOW_COPY_AND_ASSIGN(CurrentHistoryCleaner);
448 CurrentHistoryCleaner::CurrentHistoryCleaner(content::WebContents* contents)
449 : WebContentsObserver(contents) {
450 history_index_to_remove_ =
451 web_contents()->GetController().GetLastCommittedEntryIndex();
454 CurrentHistoryCleaner::~CurrentHistoryCleaner() {
457 void CurrentHistoryCleaner::DidCommitProvisionalLoadForFrame(
458 content::RenderFrameHost* render_frame_host,
459 const GURL& url,
460 ui::PageTransition transition_type) {
461 // Return early if this is not top-level navigation.
462 if (render_frame_host->GetParent())
463 return;
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.
486 } // namespace
489 // StartSyncArgs --------------------------------------------------------------
491 OneClickSigninHelper::StartSyncArgs::StartSyncArgs()
492 : profile(NULL),
493 browser(NULL),
494 auto_accept(AUTO_ACCEPT_NONE),
495 web_contents(NULL),
496 confirmation_required(OneClickSigninSyncStarter::NO_CONFIRMATION),
497 source(signin::SOURCE_UNKNOWN) {}
499 OneClickSigninHelper::StartSyncArgs::StartSyncArgs(
500 Profile* profile,
501 Browser* browser,
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)
511 : profile(profile),
512 browser(browser),
513 auto_accept(auto_accept),
514 session_index(session_index),
515 email(email),
516 password(password),
517 refresh_token(refresh_token),
518 web_contents(web_contents),
519 source(source),
520 callback(callback) {
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 // Do not display a status confirmation for re-auth.
526 confirmation_required = OneClickSigninSyncStarter::NO_CONFIRMATION;
527 } else {
528 confirmation_required = OneClickSigninSyncStarter::CONFIRM_AFTER_SIGNIN;
532 OneClickSigninHelper::StartSyncArgs::~StartSyncArgs() {}
534 // SyncStarterWrapper ---------------------------------------------------------
536 OneClickSigninHelper::SyncStarterWrapper::SyncStarterWrapper(
537 const OneClickSigninHelper::StartSyncArgs& args,
538 OneClickSigninSyncStarter::StartSyncMode start_mode)
539 : args_(args), start_mode_(start_mode), weak_pointer_factory_(this) {
540 BrowserList::AddObserver(this);
542 // Cache the parent desktop for the browser, so we can reuse that same
543 // desktop for any UI we want to display.
544 desktop_type_ = args_.browser ? args_.browser->host_desktop_type()
545 : chrome::GetActiveDesktop();
548 OneClickSigninHelper::SyncStarterWrapper::~SyncStarterWrapper() {
549 BrowserList::RemoveObserver(this);
552 void OneClickSigninHelper::SyncStarterWrapper::Start() {
553 if (args_.refresh_token.empty()) {
554 if (args_.password.empty()) {
555 VerifyGaiaCookiesBeforeSignIn();
556 } else {
557 StartSigninOAuthHelper();
559 } else {
560 OnSigninOAuthInformationAvailable(args_.email, args_.email,
561 args_.refresh_token);
565 void
566 OneClickSigninHelper::SyncStarterWrapper::OnSigninOAuthInformationAvailable(
567 const std::string& email,
568 const std::string& display_email,
569 const std::string& refresh_token) {
570 if (!gaia::AreEmailsSame(display_email, args_.email)) {
571 DisplayErrorBubble(
572 GoogleServiceAuthError(
573 GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS).ToString());
574 } else {
575 StartOneClickSigninSyncStarter(email, refresh_token);
578 base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
581 void OneClickSigninHelper::SyncStarterWrapper::OnSigninOAuthInformationFailure(
582 const GoogleServiceAuthError& error) {
583 DisplayErrorBubble(error.ToString());
584 base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
587 void OneClickSigninHelper::SyncStarterWrapper::OnBrowserRemoved(
588 Browser* browser) {
589 if (args_.browser == browser)
590 args_.browser = NULL;
593 void OneClickSigninHelper::SyncStarterWrapper::VerifyGaiaCookiesBeforeSignIn() {
594 scoped_refptr<SigninManagerCookieHelper> cookie_helper(
595 new SigninManagerCookieHelper(
596 args_.profile->GetRequestContext(),
597 content::BrowserThread::GetMessageLoopProxyForThread(
598 content::BrowserThread::UI),
599 content::BrowserThread::GetMessageLoopProxyForThread(
600 content::BrowserThread::IO)));
601 cookie_helper->StartFetchingGaiaCookiesOnUIThread(
602 base::Bind(&SyncStarterWrapper::OnGaiaCookiesFetched,
603 weak_pointer_factory_.GetWeakPtr(),
604 args_.session_index));
607 void OneClickSigninHelper::SyncStarterWrapper::OnGaiaCookiesFetched(
608 const std::string session_index, const net::CookieList& cookie_list) {
609 net::CookieList::const_iterator it;
610 bool success = false;
611 for (it = cookie_list.begin(); it != cookie_list.end(); ++it) {
612 // Make sure the LSID cookie is set on the GAIA host, instead of a super-
613 // domain.
614 if (it->Name() == "LSID") {
615 if (it->IsHostCookie() && it->IsHttpOnly() && it->IsSecure()) {
616 // Found a valid LSID cookie. Continue loop to make sure we don't have
617 // invalid LSID cookies on any super-domain.
618 success = true;
619 } else {
620 success = false;
621 break;
626 if (success) {
627 StartSigninOAuthHelper();
628 } else {
629 DisplayErrorBubble(
630 GoogleServiceAuthError(
631 GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS).ToString());
632 base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
636 void OneClickSigninHelper::SyncStarterWrapper::DisplayErrorBubble(
637 const std::string& error_message) {
638 args_.browser = OneClickSigninSyncStarter::EnsureBrowser(
639 args_.browser, args_.profile, desktop_type_);
640 LoginUIServiceFactory::GetForProfile(args_.profile)->DisplayLoginResult(
641 args_.browser, base::UTF8ToUTF16(error_message));
644 void OneClickSigninHelper::SyncStarterWrapper::StartSigninOAuthHelper() {
645 std::string signin_scoped_device_id = GetSigninScopedDeviceId(args_.profile);
646 signin_oauth_helper_.reset(
647 new SigninOAuthHelper(args_.profile->GetRequestContext(),
648 args_.session_index,
649 signin_scoped_device_id,
650 this));
653 void
654 OneClickSigninHelper::SyncStarterWrapper::StartOneClickSigninSyncStarter(
655 const std::string& email,
656 const std::string& refresh_token) {
657 // The starter deletes itself once it's done.
658 new OneClickSigninSyncStarter(args_.profile, args_.browser,
659 email, args_.password,
660 refresh_token, start_mode_,
661 args_.web_contents,
662 args_.confirmation_required,
663 GURL(),
664 args_.callback);
668 // OneClickSigninHelper -------------------------------------------------------
670 DEFINE_WEB_CONTENTS_USER_DATA_KEY(OneClickSigninHelper);
672 // static
673 const int OneClickSigninHelper::kMaxNavigationsSince = 10;
675 OneClickSigninHelper::OneClickSigninHelper(
676 content::WebContents* web_contents,
677 password_manager::PasswordManager* password_manager)
678 : content::WebContentsObserver(web_contents),
679 showing_signin_(false),
680 auto_accept_(AUTO_ACCEPT_NONE),
681 source_(signin::SOURCE_UNKNOWN),
682 switched_to_advanced_(false),
683 untrusted_navigations_since_signin_visit_(0),
684 untrusted_confirmation_required_(false),
685 do_not_clear_pending_email_(false),
686 do_not_start_sync_for_testing_(false),
687 weak_pointer_factory_(this) {
688 // May be NULL during testing.
689 if (password_manager) {
690 password_manager->AddSubmissionCallback(
691 base::Bind(&OneClickSigninHelper::PasswordSubmitted,
692 weak_pointer_factory_.GetWeakPtr()));
696 OneClickSigninHelper::~OneClickSigninHelper() {}
698 // static
699 void OneClickSigninHelper::LogHistogramValue(
700 signin::Source source, int action) {
701 switch (source) {
702 case signin::SOURCE_START_PAGE:
703 UMA_HISTOGRAM_ENUMERATION("Signin.StartPageActions", action,
704 one_click_signin::HISTOGRAM_MAX);
705 break;
706 case signin::SOURCE_NTP_LINK:
707 UMA_HISTOGRAM_ENUMERATION("Signin.NTPLinkActions", action,
708 one_click_signin::HISTOGRAM_MAX);
709 break;
710 case signin::SOURCE_MENU:
711 UMA_HISTOGRAM_ENUMERATION("Signin.MenuActions", action,
712 one_click_signin::HISTOGRAM_MAX);
713 break;
714 case signin::SOURCE_SETTINGS:
715 UMA_HISTOGRAM_ENUMERATION("Signin.SettingsActions", action,
716 one_click_signin::HISTOGRAM_MAX);
717 break;
718 case signin::SOURCE_EXTENSION_INSTALL_BUBBLE:
719 UMA_HISTOGRAM_ENUMERATION("Signin.ExtensionInstallBubbleActions", action,
720 one_click_signin::HISTOGRAM_MAX);
721 break;
722 case signin::SOURCE_APP_LAUNCHER:
723 UMA_HISTOGRAM_ENUMERATION("Signin.AppLauncherActions", action,
724 one_click_signin::HISTOGRAM_MAX);
725 break;
726 case signin::SOURCE_APPS_PAGE_LINK:
727 UMA_HISTOGRAM_ENUMERATION("Signin.AppsPageLinkActions", action,
728 one_click_signin::HISTOGRAM_MAX);
729 break;
730 case signin::SOURCE_BOOKMARK_BUBBLE:
731 UMA_HISTOGRAM_ENUMERATION("Signin.BookmarkBubbleActions", action,
732 one_click_signin::HISTOGRAM_MAX);
733 break;
734 case signin::SOURCE_AVATAR_BUBBLE_SIGN_IN:
735 UMA_HISTOGRAM_ENUMERATION("Signin.AvatarBubbleActions", action,
736 one_click_signin::HISTOGRAM_MAX);
737 break;
738 case signin::SOURCE_AVATAR_BUBBLE_ADD_ACCOUNT:
739 UMA_HISTOGRAM_ENUMERATION("Signin.AvatarBubbleActions", action,
740 one_click_signin::HISTOGRAM_MAX);
741 break;
742 case signin::SOURCE_DEVICES_PAGE:
743 UMA_HISTOGRAM_ENUMERATION("Signin.DevicesPageActions", action,
744 one_click_signin::HISTOGRAM_MAX);
745 break;
746 case signin::SOURCE_REAUTH:
747 UMA_HISTOGRAM_ENUMERATION("Signin.ReauthActions", action,
748 one_click_signin::HISTOGRAM_MAX);
749 break;
750 default:
751 // This switch statement needs to be updated when the enum Source changes.
752 COMPILE_ASSERT(signin::SOURCE_UNKNOWN == 12,
753 kSourceEnumHasChangedButNotThisSwitchStatement);
754 UMA_HISTOGRAM_ENUMERATION("Signin.UnknownActions", action,
755 one_click_signin::HISTOGRAM_MAX);
757 UMA_HISTOGRAM_ENUMERATION("Signin.AllAccessPointActions", action,
758 one_click_signin::HISTOGRAM_MAX);
761 // static
762 void OneClickSigninHelper::CreateForWebContentsWithPasswordManager(
763 content::WebContents* contents,
764 password_manager::PasswordManager* password_manager) {
765 if (!FromWebContents(contents)) {
766 contents->SetUserData(UserDataKey(),
767 new OneClickSigninHelper(contents, password_manager));
771 // static
772 bool OneClickSigninHelper::CanOffer(content::WebContents* web_contents,
773 CanOfferFor can_offer_for,
774 const std::string& email,
775 std::string* error_message) {
776 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
777 VLOG(1) << "OneClickSigninHelper::CanOffer";
779 if (error_message)
780 error_message->clear();
782 if (!web_contents)
783 return false;
785 if (web_contents->GetBrowserContext()->IsOffTheRecord())
786 return false;
788 Profile* profile =
789 Profile::FromBrowserContext(web_contents->GetBrowserContext());
790 if (!profile)
791 return false;
793 SigninManager* manager =
794 SigninManagerFactory::GetForProfile(profile);
795 if (manager && !manager->IsSigninAllowed())
796 return false;
798 if (can_offer_for == CAN_OFFER_FOR_INTERSTITAL_ONLY &&
799 !profile->GetPrefs()->GetBoolean(prefs::kReverseAutologinEnabled))
800 return false;
802 if (!ChromeSigninClient::ProfileAllowsSigninCookies(profile))
803 return false;
805 if (!email.empty()) {
806 if (!manager)
807 return false;
809 // Make sure this username is not prohibited by policy.
810 if (!manager->IsAllowedUsername(email)) {
811 if (error_message) {
812 error_message->assign(
813 l10n_util::GetStringUTF8(IDS_SYNC_LOGIN_NAME_PROHIBITED));
815 return false;
818 if (can_offer_for != CAN_OFFER_FOR_SECONDARY_ACCOUNT) {
819 // If the signin manager already has an authenticated name, then this is a
820 // re-auth scenario. Make sure the email just signed in corresponds to
821 // the one sign in manager expects.
822 std::string current_email = manager->GetAuthenticatedUsername();
823 const bool same_email = gaia::AreEmailsSame(current_email, email);
824 if (!current_email.empty() && !same_email) {
825 UMA_HISTOGRAM_ENUMERATION("Signin.Reauth",
826 signin::HISTOGRAM_ACCOUNT_MISSMATCH,
827 signin::HISTOGRAM_MAX);
828 if (error_message) {
829 error_message->assign(
830 l10n_util::GetStringFUTF8(IDS_SYNC_WRONG_EMAIL,
831 base::UTF8ToUTF16(current_email)));
833 return false;
836 // If some profile, not just the current one, is already connected to this
837 // account, don't show the infobar.
838 if (g_browser_process && !same_email) {
839 ProfileManager* manager = g_browser_process->profile_manager();
840 if (manager) {
841 ProfileInfoCache& cache = manager->GetProfileInfoCache();
842 for (size_t i = 0; i < cache.GetNumberOfProfiles(); ++i) {
843 std::string current_email =
844 base::UTF16ToUTF8(cache.GetUserNameOfProfileAtIndex(i));
845 if (gaia::AreEmailsSame(email, current_email)) {
846 if (error_message) {
847 error_message->assign(
848 l10n_util::GetStringUTF8(IDS_SYNC_USER_NAME_IN_USE_ERROR));
850 return false;
857 // If email was already rejected by this profile for one-click sign-in.
858 if (can_offer_for == CAN_OFFER_FOR_INTERSTITAL_ONLY) {
859 const base::ListValue* rejected_emails = profile->GetPrefs()->GetList(
860 prefs::kReverseAutologinRejectedEmailList);
861 if (!rejected_emails->empty()) {
862 base::ListValue::const_iterator iter = rejected_emails->Find(
863 base::StringValue(email));
864 if (iter != rejected_emails->end())
865 return false;
870 VLOG(1) << "OneClickSigninHelper::CanOffer: yes we can";
871 return true;
874 // static
875 OneClickSigninHelper::Offer OneClickSigninHelper::CanOfferOnIOThread(
876 net::URLRequest* request,
877 ProfileIOData* io_data) {
878 return CanOfferOnIOThreadImpl(request->url(), request, io_data);
881 // static
882 OneClickSigninHelper::Offer OneClickSigninHelper::CanOfferOnIOThreadImpl(
883 const GURL& url,
884 base::SupportsUserData* request,
885 ProfileIOData* io_data) {
886 if (!gaia::IsGaiaSignonRealm(url.GetOrigin()))
887 return IGNORE_REQUEST;
889 if (!io_data)
890 return DONT_OFFER;
892 // Check for incognito before other parts of the io_data, since those
893 // members may not be initalized.
894 if (io_data->IsOffTheRecord())
895 return DONT_OFFER;
897 if (!io_data->signin_allowed()->GetValue())
898 return DONT_OFFER;
900 if (!io_data->reverse_autologin_enabled()->GetValue())
901 return DONT_OFFER;
903 if (!io_data->google_services_username()->GetValue().empty())
904 return DONT_OFFER;
906 if (!ChromeSigninClient::SettingsAllowSigninCookies(
907 io_data->GetCookieSettings()))
908 return DONT_OFFER;
910 // The checks below depend on chrome already knowing what account the user
911 // signed in with. This happens only after receiving the response containing
912 // the Google-Accounts-SignIn header. Until then, if there is even a chance
913 // that we want to connect the profile, chrome needs to tell Gaia that
914 // it should offer the interstitial. Therefore missing one click data on
915 // the request means can offer is true.
916 const std::string& pending_email = io_data->reverse_autologin_pending_email();
917 if (!pending_email.empty()) {
918 if (!SigninManager::IsUsernameAllowedByPolicy(pending_email,
919 io_data->google_services_username_pattern()->GetValue())) {
920 return DONT_OFFER;
923 std::vector<std::string> rejected_emails =
924 io_data->one_click_signin_rejected_email_list()->GetValue();
925 if (std::count_if(rejected_emails.begin(), rejected_emails.end(),
926 std::bind2nd(std::equal_to<std::string>(),
927 pending_email)) > 0) {
928 return DONT_OFFER;
931 if (io_data->signin_names()->GetEmails().count(
932 base::UTF8ToUTF16(pending_email)) > 0) {
933 return DONT_OFFER;
937 return CAN_OFFER;
940 // static
941 void OneClickSigninHelper::ShowInfoBarIfPossible(net::URLRequest* request,
942 ProfileIOData* io_data,
943 int child_id,
944 int route_id) {
945 std::string google_chrome_signin_value;
946 std::string google_accounts_signin_value;
947 request->GetResponseHeaderByName("Google-Chrome-SignIn",
948 &google_chrome_signin_value);
949 request->GetResponseHeaderByName("Google-Accounts-SignIn",
950 &google_accounts_signin_value);
952 if (!google_accounts_signin_value.empty() ||
953 !google_chrome_signin_value.empty()) {
954 VLOG(1) << "OneClickSigninHelper::ShowInfoBarIfPossible:"
955 << " g-a-s='" << google_accounts_signin_value << "'"
956 << " g-c-s='" << google_chrome_signin_value << "'";
959 if (!gaia::IsGaiaSignonRealm(request->url().GetOrigin()))
960 return;
962 // Parse Google-Accounts-SignIn.
963 base::StringPairs pairs;
964 base::SplitStringIntoKeyValuePairs(google_accounts_signin_value, '=', ',',
965 &pairs);
966 std::string session_index;
967 std::string email;
968 for (size_t i = 0; i < pairs.size(); ++i) {
969 const std::pair<std::string, std::string>& pair = pairs[i];
970 const std::string& key = pair.first;
971 const std::string& value = pair.second;
972 if (key == "email") {
973 base::TrimString(value, "\"", &email);
974 } else if (key == "sessionindex") {
975 session_index = value;
979 // Later in the chain of this request, we'll need to check the email address
980 // in the IO thread (see CanOfferOnIOThread). So save the email address as
981 // user data on the request (only for web-based flow).
982 if (!email.empty())
983 io_data->set_reverse_autologin_pending_email(email);
985 if (!email.empty() || !session_index.empty()) {
986 VLOG(1) << "OneClickSigninHelper::ShowInfoBarIfPossible:"
987 << " email=" << email
988 << " sessionindex=" << session_index;
991 // Parse Google-Chrome-SignIn.
992 AutoAccept auto_accept = AUTO_ACCEPT_NONE;
993 signin::Source source = signin::SOURCE_UNKNOWN;
994 GURL continue_url;
995 std::vector<std::string> tokens;
996 base::SplitString(google_chrome_signin_value, ',', &tokens);
997 for (size_t i = 0; i < tokens.size(); ++i) {
998 const std::string& token = tokens[i];
999 if (token == "accepted") {
1000 auto_accept = AUTO_ACCEPT_ACCEPTED;
1001 } else if (token == "configure") {
1002 auto_accept = AUTO_ACCEPT_CONFIGURE;
1003 } else if (token == "rejected-for-profile") {
1004 auto_accept = AUTO_ACCEPT_REJECTED_FOR_PROFILE;
1008 // If this is an explicit sign in (i.e., first run, NTP, Apps page, menu,
1009 // settings) then force the auto accept type to explicit.
1010 source = GetSigninSource(request->url(), &continue_url);
1011 if (source != signin::SOURCE_UNKNOWN)
1012 auto_accept = AUTO_ACCEPT_EXPLICIT;
1014 if (auto_accept != AUTO_ACCEPT_NONE) {
1015 VLOG(1) << "OneClickSigninHelper::ShowInfoBarIfPossible:"
1016 << " auto_accept=" << auto_accept;
1019 // If |session_index|, |email|, |auto_accept|, and |continue_url| all have
1020 // their default value, don't bother posting a task to the UI thread.
1021 // It will be a noop anyway.
1023 // The two headers above may (but not always) come in different http requests
1024 // so a post to the UI thread is still needed if |auto_accept| is not its
1025 // default value, but |email| and |session_index| are.
1026 if (session_index.empty() && email.empty() &&
1027 auto_accept == AUTO_ACCEPT_NONE && !continue_url.is_valid()) {
1028 return;
1031 content::BrowserThread::PostTask(
1032 content::BrowserThread::UI, FROM_HERE,
1033 base::Bind(&OneClickSigninHelper::ShowInfoBarUIThread, session_index,
1034 email, auto_accept, source, continue_url, child_id, route_id));
1037 // static
1038 void OneClickSigninHelper::LogConfirmHistogramValue(int action) {
1039 UMA_HISTOGRAM_ENUMERATION("Signin.OneClickConfirmation", action,
1040 one_click_signin::HISTOGRAM_CONFIRM_MAX);
1042 // static
1043 void OneClickSigninHelper::ShowInfoBarUIThread(
1044 const std::string& session_index,
1045 const std::string& email,
1046 AutoAccept auto_accept,
1047 signin::Source source,
1048 const GURL& continue_url,
1049 int child_id,
1050 int route_id) {
1051 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
1053 content::WebContents* web_contents = tab_util::GetWebContentsByID(child_id,
1054 route_id);
1055 if (!web_contents)
1056 return;
1058 // TODO(mathp): The appearance of this infobar should be tested using a
1059 // browser_test.
1060 OneClickSigninHelper* helper =
1061 OneClickSigninHelper::FromWebContents(web_contents);
1062 if (!helper)
1063 return;
1065 if (auto_accept != AUTO_ACCEPT_NONE)
1066 helper->auto_accept_ = auto_accept;
1068 if (source != signin::SOURCE_UNKNOWN &&
1069 helper->source_ == signin::SOURCE_UNKNOWN) {
1070 helper->source_ = source;
1073 // Save the email in the one-click signin manager. The manager may
1074 // not exist if the contents is incognito or if the profile is already
1075 // connected to a Google account.
1076 if (!session_index.empty())
1077 helper->session_index_ = session_index;
1079 if (!email.empty())
1080 helper->email_ = email;
1082 CanOfferFor can_offer_for =
1083 (auto_accept != AUTO_ACCEPT_EXPLICIT &&
1084 helper->auto_accept_ != AUTO_ACCEPT_EXPLICIT) ?
1085 CAN_OFFER_FOR_INTERSTITAL_ONLY : CAN_OFFER_FOR_ALL;
1087 std::string error_message;
1089 if (!web_contents || !CanOffer(web_contents, can_offer_for, email,
1090 &error_message)) {
1091 VLOG(1) << "OneClickSigninHelper::ShowInfoBarUIThread: not offering";
1092 // TODO(rogerta): Can we just display our error now instead of keeping it
1093 // around and doing it later?
1094 if (helper && helper->error_message_.empty() && !error_message.empty())
1095 helper->error_message_ = error_message;
1097 return;
1100 // Only allow the dedicated signin process to sign the user into
1101 // Chrome without intervention, because it doesn't load any untrusted
1102 // pages. If at any point an untrusted page is detected, chrome will
1103 // show a modal dialog asking the user to confirm.
1104 Profile* profile =
1105 Profile::FromBrowserContext(web_contents->GetBrowserContext());
1106 SigninClient* signin_client =
1107 profile ? ChromeSigninClientFactory::GetForProfile(profile) : NULL;
1108 helper->untrusted_confirmation_required_ |=
1109 (signin_client && !signin_client->IsSigninProcess(child_id));
1111 if (continue_url.is_valid()) {
1112 // Set |original_continue_url_| if it is currently empty. |continue_url|
1113 // could be modified by gaia pages, thus we need to record the original
1114 // continue url to navigate back to the right page when sync setup is
1115 // complete.
1116 if (helper->original_continue_url_.is_empty())
1117 helper->original_continue_url_ = continue_url;
1118 helper->continue_url_ = continue_url;
1122 // static
1123 void OneClickSigninHelper::RemoveSigninRedirectURLHistoryItem(
1124 content::WebContents* web_contents) {
1125 // Only actually remove the item if it's the blank.html continue url.
1126 if (signin::IsContinueUrlForWebBasedSigninFlow(
1127 web_contents->GetLastCommittedURL())) {
1128 new CurrentHistoryCleaner(web_contents); // will self-destruct when done
1132 // static
1133 bool OneClickSigninHelper::HandleCrossAccountError(
1134 Profile* profile,
1135 const std::string& session_index,
1136 const std::string& email,
1137 const std::string& password,
1138 const std::string& refresh_token,
1139 OneClickSigninHelper::AutoAccept auto_accept,
1140 signin::Source source,
1141 OneClickSigninSyncStarter::StartSyncMode start_mode,
1142 OneClickSigninSyncStarter::Callback sync_callback) {
1143 std::string last_email =
1144 profile->GetPrefs()->GetString(prefs::kGoogleServicesLastUsername);
1146 if (!last_email.empty() && !gaia::AreEmailsSame(last_email, email)) {
1147 // If the new email address is different from the email address that
1148 // just signed in, show a confirmation dialog on top of the current active
1149 // tab.
1151 // No need to display a second confirmation so pass false below.
1152 // TODO(atwilson): Move this into OneClickSigninSyncStarter.
1153 // The tab modal dialog always executes its callback before |contents|
1154 // is deleted.
1155 Browser* browser = chrome::FindLastActiveWithProfile(
1156 profile, chrome::GetActiveDesktop());
1157 content::WebContents* contents =
1158 browser->tab_strip_model()->GetActiveWebContents();
1160 ConfirmEmailDialogDelegate::AskForConfirmation(
1161 contents,
1162 last_email,
1163 email,
1164 base::Bind(
1165 &StartExplicitSync,
1166 StartSyncArgs(profile, browser, auto_accept,
1167 session_index, email, password,
1168 refresh_token,
1169 contents, false /* confirmation_required */, source,
1170 sync_callback),
1171 contents,
1172 start_mode));
1173 return true;
1176 return false;
1179 // static
1180 void OneClickSigninHelper::RedirectToNtpOrAppsPage(
1181 content::WebContents* contents, signin::Source source) {
1182 // Do nothing if a navigation is pending, since this call can be triggered
1183 // from DidStartLoading. This avoids deleting the pending entry while we are
1184 // still navigating to it. See crbug/346632.
1185 if (contents->GetController().GetPendingEntry())
1186 return;
1188 VLOG(1) << "RedirectToNtpOrAppsPage";
1189 // Redirect to NTP/Apps page and display a confirmation bubble
1190 GURL url(source == signin::SOURCE_APPS_PAGE_LINK ?
1191 chrome::kChromeUIAppsURL : chrome::kChromeUINewTabURL);
1192 content::OpenURLParams params(url,
1193 content::Referrer(),
1194 CURRENT_TAB,
1195 ui::PAGE_TRANSITION_AUTO_TOPLEVEL,
1196 false);
1197 contents->OpenURL(params);
1200 // static
1201 void OneClickSigninHelper::RedirectToNtpOrAppsPageIfNecessary(
1202 content::WebContents* contents, signin::Source source) {
1203 if (source != signin::SOURCE_SETTINGS) {
1204 RedirectToNtpOrAppsPage(contents, source);
1208 void OneClickSigninHelper::RedirectToSignin() {
1209 VLOG(1) << "OneClickSigninHelper::RedirectToSignin";
1211 // Extract the existing sounce=X value. Default to "2" if missing.
1212 signin::Source source = signin::GetSourceForPromoURL(continue_url_);
1213 if (source == signin::SOURCE_UNKNOWN)
1214 source = signin::SOURCE_MENU;
1215 GURL page = signin::GetPromoURL(source, false);
1217 content::WebContents* contents = web_contents();
1218 contents->GetController().LoadURL(page,
1219 content::Referrer(),
1220 ui::PAGE_TRANSITION_AUTO_TOPLEVEL,
1221 std::string());
1224 void OneClickSigninHelper::CleanTransientState() {
1225 VLOG(1) << "OneClickSigninHelper::CleanTransientState";
1226 showing_signin_ = false;
1227 email_.clear();
1228 password_.clear();
1229 auto_accept_ = AUTO_ACCEPT_NONE;
1230 source_ = signin::SOURCE_UNKNOWN;
1231 switched_to_advanced_ = false;
1232 continue_url_ = GURL();
1233 untrusted_navigations_since_signin_visit_ = 0;
1234 untrusted_confirmation_required_ = false;
1235 error_message_.clear();
1237 // Post to IO thread to clear pending email.
1238 if (!do_not_clear_pending_email_) {
1239 Profile* profile =
1240 Profile::FromBrowserContext(web_contents()->GetBrowserContext());
1241 content::BrowserThread::PostTask(
1242 content::BrowserThread::IO, FROM_HERE,
1243 base::Bind(&ClearPendingEmailOnIOThread,
1244 base::Unretained(profile->GetResourceContext())));
1248 void OneClickSigninHelper::PasswordSubmitted(
1249 const autofill::PasswordForm& form) {
1250 // We only need to scrape the password for Gaia logins.
1251 if (gaia::IsGaiaSignonRealm(GURL(form.signon_realm))) {
1252 VLOG(1) << "OneClickSigninHelper::DidNavigateAnyFrame: got password";
1253 password_ = base::UTF16ToUTF8(form.password_value);
1257 void OneClickSigninHelper::SetDoNotClearPendingEmailForTesting() {
1258 do_not_clear_pending_email_ = true;
1261 void OneClickSigninHelper::set_do_not_start_sync_for_testing() {
1262 do_not_start_sync_for_testing_ = true;
1265 void OneClickSigninHelper::DidStartNavigationToPendingEntry(
1266 const GURL& url,
1267 content::NavigationController::ReloadType reload_type) {
1268 VLOG(1) << "OneClickSigninHelper::DidStartNavigationToPendingEntry: url=" <<
1269 url.spec();
1270 // If the tab navigates to a new page, and this page is not a valid Gaia
1271 // sign in redirect or reponse, or the expected continue URL, make sure to
1272 // clear the internal state. This is needed to detect navigations in the
1273 // middle of the sign in process that may redirect back to the sign in
1274 // process (see crbug.com/181163 for details).
1275 GURL::Replacements replacements;
1276 replacements.ClearQuery();
1278 if (!IsValidGaiaSigninRedirectOrResponseURL(url) &&
1279 continue_url_.is_valid() &&
1280 url.ReplaceComponents(replacements) !=
1281 continue_url_.ReplaceComponents(replacements)) {
1282 if (++untrusted_navigations_since_signin_visit_ > kMaxNavigationsSince)
1283 CleanTransientState();
1287 void OneClickSigninHelper::DidNavigateMainFrame(
1288 const content::LoadCommittedDetails& details,
1289 const content::FrameNavigateParams& params) {
1290 if (!SigninManager::IsWebBasedSigninFlowURL(params.url)) {
1291 // Make sure the renderer process is no longer considered the trusted
1292 // sign-in process when a navigation to a non-sign-in URL occurs.
1293 Profile* profile =
1294 Profile::FromBrowserContext(web_contents()->GetBrowserContext());
1295 SigninClient* signin_client =
1296 profile ? ChromeSigninClientFactory::GetForProfile(profile) : NULL;
1297 int process_id = web_contents()->GetRenderProcessHost()->GetID();
1298 if (signin_client && signin_client->IsSigninProcess(process_id))
1299 signin_client->ClearSigninProcess();
1301 // If the navigation to a non-sign-in URL hasn't been triggered by the web
1302 // contents, the sign in flow has been aborted and the state must be
1303 // cleaned (crbug.com/269421).
1304 if (!ui::PageTransitionIsWebTriggerable(params.transition) &&
1305 auto_accept_ != AUTO_ACCEPT_NONE) {
1306 CleanTransientState();
1311 void OneClickSigninHelper::DidStopLoading(
1312 content::RenderViewHost* render_view_host) {
1313 // If the user left the sign in process, clear all members.
1314 // TODO(rogerta): might need to allow some youtube URLs.
1315 content::WebContents* contents = web_contents();
1316 const GURL url = contents->GetLastCommittedURL();
1317 Profile* profile =
1318 Profile::FromBrowserContext(contents->GetBrowserContext());
1319 VLOG(1) << "OneClickSigninHelper::DidStopLoading: url=" << url.spec();
1321 if (url.scheme() == content::kChromeUIScheme) {
1322 // Suppresses OneClickSigninHelper on webUI pages to avoid inteference with
1323 // inline signin flows.
1324 VLOG(1) << "OneClickSigninHelper::DidStopLoading: suppressed for url="
1325 << url.spec();
1326 CleanTransientState();
1327 return;
1330 // If an error has already occured during the sign in flow, make sure to
1331 // display it to the user and abort the process. Do this only for
1332 // explicit sign ins.
1333 // TODO(rogerta): Could we move this code back up to ShowInfoBarUIThread()?
1334 if (!error_message_.empty() && auto_accept_ == AUTO_ACCEPT_EXPLICIT) {
1335 VLOG(1) << "OneClickSigninHelper::DidStopLoading: error=" << error_message_;
1336 RemoveSigninRedirectURLHistoryItem(contents);
1337 // After we redirect to NTP, our browser pointer gets corrupted because the
1338 // WebContents have changed, so grab the browser pointer
1339 // before the navigation.
1340 Browser* browser = chrome::FindBrowserWithWebContents(contents);
1342 // Redirect to the landing page and display an error popup.
1343 RedirectToNtpOrAppsPage(web_contents(), source_);
1344 LoginUIServiceFactory::GetForProfile(profile)->
1345 DisplayLoginResult(browser, base::UTF8ToUTF16(error_message_));
1346 CleanTransientState();
1347 return;
1350 if (AreWeShowingSignin(url, source_, email_)) {
1351 if (!showing_signin_) {
1352 if (source_ == signin::SOURCE_UNKNOWN)
1353 LogOneClickHistogramValue(one_click_signin::HISTOGRAM_SHOWN);
1354 else
1355 LogHistogramValue(source_, one_click_signin::HISTOGRAM_SHOWN);
1357 showing_signin_ = true;
1360 // When Gaia finally redirects to the continue URL, Gaia will add some
1361 // extra query parameters. So ignore the parameters when checking to see
1362 // if the user has continued. Sometimes locales will redirect to a country-
1363 // specific TLD so just make sure it's a valid domain instead of comparing
1364 // for an exact match.
1365 GURL::Replacements replacements;
1366 replacements.ClearQuery();
1367 bool google_domain_url = google_util::IsGoogleDomainUrl(
1368 url,
1369 google_util::ALLOW_SUBDOMAIN,
1370 google_util::DISALLOW_NON_STANDARD_PORTS);
1371 const bool continue_url_match =
1372 google_domain_url &&
1373 url.ReplaceComponents(replacements).path() ==
1374 continue_url_.ReplaceComponents(replacements).path();
1375 const bool original_continue_url_match =
1376 google_domain_url &&
1377 url.ReplaceComponents(replacements).path() ==
1378 original_continue_url_.ReplaceComponents(replacements).path();
1380 if (continue_url_match)
1381 RemoveSigninRedirectURLHistoryItem(contents);
1383 // If there is no valid email yet, there is nothing to do. As of M26, the
1384 // password is allowed to be empty, since its no longer required to setup
1385 // sync.
1386 if (email_.empty()) {
1387 VLOG(1) << "OneClickSigninHelper::DidStopLoading: nothing to do";
1388 // Original-url check done because some user actions cans get us to a page
1389 // via a POST instead of a GET (and thus to immediate "cuntinue url") but
1390 // we still want redirects from the "blank.html" landing page to work for
1391 // non-security related redirects like NTP.
1392 // https://code.google.com/p/chromium/issues/detail?id=321938
1393 if (original_continue_url_match) {
1394 if (auto_accept_ == AUTO_ACCEPT_EXPLICIT)
1395 RedirectToSignin();
1396 std::string unused_value;
1397 if (net::GetValueForKeyInQuery(url, "ntp", &unused_value)) {
1398 signin::SetUserSkippedPromo(profile);
1399 RedirectToNtpOrAppsPage(web_contents(), source_);
1401 } else {
1402 if (!IsValidGaiaSigninRedirectOrResponseURL(url) &&
1403 ++untrusted_navigations_since_signin_visit_ > kMaxNavigationsSince) {
1404 CleanTransientState();
1408 return;
1411 if (!continue_url_match && IsValidGaiaSigninRedirectOrResponseURL(url))
1412 return;
1414 // During an explicit sign in, if the user has not yet reached the final
1415 // continue URL, wait for it to arrive. Note that Gaia will add some extra
1416 // query parameters to the continue URL. Ignore them when checking to
1417 // see if the user has continued.
1419 // If this is not an explicit sign in, we don't need to check if we landed
1420 // on the right continue URL. This is important because the continue URL
1421 // may itself lead to a redirect, which means this function will never see
1422 // the continue URL go by.
1423 if (auto_accept_ == AUTO_ACCEPT_EXPLICIT) {
1424 DCHECK(source_ != signin::SOURCE_UNKNOWN);
1425 if (!continue_url_match) {
1426 VLOG(1) << "OneClickSigninHelper::DidStopLoading: invalid url='"
1427 << url.spec()
1428 << "' expected continue url=" << continue_url_;
1429 CleanTransientState();
1430 return;
1433 // In explicit sign ins, the user may have changed the box
1434 // "Let me choose what to sync". This is reflected as a change in the
1435 // source of the continue URL. Make one last check of the current URL
1436 // to see if there is a valid source. If so, it overrides the
1437 // current source.
1439 // If the source was changed to SOURCE_SETTINGS, we want
1440 // OneClickSigninSyncStarter to reuse the current tab to display the
1441 // advanced configuration.
1442 signin::Source source = signin::GetSourceForPromoURL(url);
1443 if (source != source_) {
1444 source_ = source;
1445 switched_to_advanced_ = source == signin::SOURCE_SETTINGS;
1449 Browser* browser = chrome::FindBrowserWithWebContents(contents);
1451 VLOG(1) << "OneClickSigninHelper::DidStopLoading: signin is go."
1452 << " auto_accept=" << auto_accept_
1453 << " source=" << source_;
1455 switch (auto_accept_) {
1456 case AUTO_ACCEPT_NONE:
1457 if (showing_signin_)
1458 LogOneClickHistogramValue(one_click_signin::HISTOGRAM_DISMISSED);
1459 break;
1460 case AUTO_ACCEPT_ACCEPTED:
1461 LogOneClickHistogramValue(one_click_signin::HISTOGRAM_ACCEPTED);
1462 LogOneClickHistogramValue(one_click_signin::HISTOGRAM_WITH_DEFAULTS);
1463 SigninManager::DisableOneClickSignIn(profile->GetPrefs());
1464 // Start syncing with the default settings - prompt the user to sign in
1465 // first.
1466 if (!do_not_start_sync_for_testing_) {
1467 StartSync(
1468 StartSyncArgs(profile, browser, auto_accept_,
1469 session_index_, email_, password_, "",
1470 NULL /* don't force sync setup in same tab */,
1471 true /* confirmation_required */, source_,
1472 CreateSyncStarterCallback()),
1473 OneClickSigninSyncStarter::SYNC_WITH_DEFAULT_SETTINGS);
1475 break;
1476 case AUTO_ACCEPT_CONFIGURE:
1477 LogOneClickHistogramValue(one_click_signin::HISTOGRAM_ACCEPTED);
1478 LogOneClickHistogramValue(one_click_signin::HISTOGRAM_WITH_ADVANCED);
1479 SigninManager::DisableOneClickSignIn(profile->GetPrefs());
1480 // Display the extra confirmation (even in the SAML case) in case this
1481 // was an untrusted renderer.
1482 if (!do_not_start_sync_for_testing_) {
1483 StartSync(
1484 StartSyncArgs(profile, browser, auto_accept_,
1485 session_index_, email_, password_, "",
1486 NULL /* don't force sync setup in same tab */,
1487 true /* confirmation_required */, source_,
1488 CreateSyncStarterCallback()),
1489 OneClickSigninSyncStarter::CONFIGURE_SYNC_FIRST);
1491 break;
1492 case AUTO_ACCEPT_EXPLICIT: {
1493 signin::Source original_source =
1494 signin::GetSourceForPromoURL(original_continue_url_);
1495 if (switched_to_advanced_) {
1496 LogHistogramValue(original_source,
1497 one_click_signin::HISTOGRAM_WITH_ADVANCED);
1498 LogHistogramValue(original_source,
1499 one_click_signin::HISTOGRAM_ACCEPTED);
1500 } else {
1501 LogHistogramValue(source_, one_click_signin::HISTOGRAM_ACCEPTED);
1502 LogHistogramValue(source_, one_click_signin::HISTOGRAM_WITH_DEFAULTS);
1505 // - If sign in was initiated from the NTP or the hotdog menu, sync with
1506 // default settings.
1507 // - If sign in was initiated from the settings page for first time sync
1508 // set up, show the advanced sync settings dialog.
1509 // - If sign in was initiated from the settings page due to a re-auth when
1510 // sync was already setup, simply navigate back to the settings page.
1511 ProfileSyncService* sync_service =
1512 ProfileSyncServiceFactory::GetForProfile(profile);
1513 SigninErrorController* error_controller =
1514 ProfileOAuth2TokenServiceFactory::GetForProfile(profile)->
1515 signin_error_controller();
1517 OneClickSigninSyncStarter::StartSyncMode start_mode =
1518 source_ == signin::SOURCE_SETTINGS ?
1519 (error_controller->HasError() &&
1520 sync_service && sync_service->HasSyncSetupCompleted()) ?
1521 OneClickSigninSyncStarter::SHOW_SETTINGS_WITHOUT_CONFIGURE :
1522 OneClickSigninSyncStarter::CONFIGURE_SYNC_FIRST :
1523 OneClickSigninSyncStarter::SYNC_WITH_DEFAULT_SETTINGS;
1525 if (!HandleCrossAccountError(profile, session_index_, email_, password_,
1526 "", auto_accept_, source_, start_mode,
1527 CreateSyncStarterCallback())) {
1528 if (!do_not_start_sync_for_testing_) {
1529 StartSync(
1530 StartSyncArgs(profile, browser, auto_accept_,
1531 session_index_, email_, password_, "",
1532 contents,
1533 untrusted_confirmation_required_, source_,
1534 CreateSyncStarterCallback()),
1535 start_mode);
1538 // If this explicit sign in is not from settings page/webstore, show
1539 // the NTP/Apps page after sign in completes. In the case of the
1540 // settings page, it will get auto-closed after sync setup. In the case
1541 // of webstore, it will redirect back to webstore.
1542 RedirectToNtpOrAppsPageIfNecessary(web_contents(), source_);
1545 // Observe the sync service if the settings tab requested a gaia sign in,
1546 // so that when sign in and sync setup are successful, we can redirect to
1547 // the correct URL, or auto-close the gaia sign in tab.
1548 if (original_source == signin::SOURCE_SETTINGS) {
1549 // The observer deletes itself once it's done.
1550 new OneClickSigninSyncObserver(contents, original_continue_url_);
1552 break;
1554 case AUTO_ACCEPT_REJECTED_FOR_PROFILE:
1555 AddEmailToOneClickRejectedList(profile, email_);
1556 LogOneClickHistogramValue(one_click_signin::HISTOGRAM_REJECTED);
1557 break;
1558 default:
1559 NOTREACHED() << "Invalid auto_accept=" << auto_accept_;
1560 break;
1563 CleanTransientState();
1566 OneClickSigninSyncStarter::Callback
1567 OneClickSigninHelper::CreateSyncStarterCallback() {
1568 // The callback will only be invoked if this object is still alive when sync
1569 // setup is completed. This is correct because this object is only deleted
1570 // when the web contents that potentially shows a blank page is deleted.
1571 return base::Bind(&OneClickSigninHelper::SyncSetupCompletedCallback,
1572 weak_pointer_factory_.GetWeakPtr());
1575 void OneClickSigninHelper::SyncSetupCompletedCallback(
1576 OneClickSigninSyncStarter::SyncSetupResult result) {
1577 if (result == OneClickSigninSyncStarter::SYNC_SETUP_FAILURE &&
1578 web_contents()) {
1579 GURL current_url = web_contents()->GetVisibleURL();
1581 // If the web contents is showing a blank page and not about to be closed,
1582 // redirect to the NTP or apps page.
1583 if (signin::IsContinueUrlForWebBasedSigninFlow(current_url) &&
1584 !signin::IsAutoCloseEnabledInURL(original_continue_url_)) {
1585 RedirectToNtpOrAppsPage(
1586 web_contents(),
1587 signin::GetSourceForPromoURL(original_continue_url_));