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.
4
5#include "chrome/browser/ui/sync/one_click_signin_sync_starter.h"
6
7#include "base/prefs/pref_service.h"
8#include "base/strings/utf_string_conversions.h"
9#include "chrome/browser/browser_process.h"
10
11#if defined(ENABLE_CONFIGURATION_POLICY)
12#include "chrome/browser/policy/cloud/user_policy_signin_service.h"
13#include "chrome/browser/policy/cloud/user_policy_signin_service_factory.h"
14#endif
15
16#include "chrome/browser/profiles/profile.h"
17#include "chrome/browser/profiles/profile_avatar_icon_util.h"
18#include "chrome/browser/profiles/profile_info_cache.h"
19#include "chrome/browser/profiles/profile_io_data.h"
20#include "chrome/browser/profiles/profile_manager.h"
21#include "chrome/browser/profiles/profile_window.h"
22#include "chrome/browser/signin/signin_manager_factory.h"
23#include "chrome/browser/signin/signin_tracker_factory.h"
24#include "chrome/browser/sync/profile_sync_service.h"
25#include "chrome/browser/sync/profile_sync_service_factory.h"
26#include "chrome/browser/ui/browser.h"
27#include "chrome/browser/ui/browser_dialogs.h"
28#include "chrome/browser/ui/browser_finder.h"
29#include "chrome/browser/ui/browser_list.h"
30#include "chrome/browser/ui/browser_navigator.h"
31#include "chrome/browser/ui/browser_tabstrip.h"
32#include "chrome/browser/ui/browser_window.h"
33#include "chrome/browser/ui/chrome_pages.h"
34#include "chrome/browser/ui/sync/one_click_signin_sync_observer.h"
35#include "chrome/browser/ui/tabs/tab_strip_model.h"
36#include "chrome/browser/ui/webui/signin/login_ui_service.h"
37#include "chrome/browser/ui/webui/signin/login_ui_service_factory.h"
38#include "chrome/browser/ui/webui/signin/profile_signin_confirmation_dialog.h"
39#include "chrome/common/url_constants.h"
40#include "components/signin/core/browser/signin_manager.h"
41#include "components/signin/core/browser/signin_metrics.h"
42#include "components/signin/core/common/profile_management_switches.h"
43#include "components/sync_driver/sync_prefs.h"
44#include "grit/chromium_strings.h"
45#include "grit/generated_resources.h"
46#include "ui/base/l10n/l10n_util.h"
47#include "ui/base/resource/resource_bundle.h"
48
49OneClickSigninSyncStarter::OneClickSigninSyncStarter(
50    Profile* profile,
51    Browser* browser,
52    const std::string& email,
53    const std::string& password,
54    const std::string& refresh_token,
55    StartSyncMode start_mode,
56    content::WebContents* web_contents,
57    ConfirmationRequired confirmation_required,
58    const GURL& continue_url,
59    Callback sync_setup_completed_callback)
60    : content::WebContentsObserver(web_contents),
61      start_mode_(start_mode),
62      desktop_type_(chrome::HOST_DESKTOP_TYPE_NATIVE),
63      confirmation_required_(confirmation_required),
64      continue_url_(continue_url),
65      sync_setup_completed_callback_(sync_setup_completed_callback),
66      weak_pointer_factory_(this) {
67  DCHECK(profile);
68  DCHECK(web_contents || continue_url.is_empty());
69  BrowserList::AddObserver(this);
70
71  Initialize(profile, browser);
72
73  // Policy is enabled, so pass in a callback to do extra policy-related UI
74  // before signin completes.
75  SigninManagerFactory::GetForProfile(profile_)->
76      StartSignInWithRefreshToken(
77          refresh_token, email, password,
78          base::Bind(&OneClickSigninSyncStarter::ConfirmSignin,
79                     weak_pointer_factory_.GetWeakPtr()));
80}
81
82void OneClickSigninSyncStarter::OnBrowserRemoved(Browser* browser) {
83  if (browser == browser_)
84    browser_ = NULL;
85}
86
87OneClickSigninSyncStarter::~OneClickSigninSyncStarter() {
88  BrowserList::RemoveObserver(this);
89}
90
91void OneClickSigninSyncStarter::Initialize(Profile* profile, Browser* browser) {
92  DCHECK(profile);
93  profile_ = profile;
94  browser_ = browser;
95
96  // Cache the parent desktop for the browser, so we can reuse that same
97  // desktop for any UI we want to display.
98  if (browser) {
99    desktop_type_ = browser->host_desktop_type();
100  } else {
101    desktop_type_ = chrome::GetActiveDesktop();
102  }
103
104  signin_tracker_ = SigninTrackerFactory::CreateForProfile(profile_, this);
105
106  // Let the sync service know that setup is in progress so it doesn't start
107  // syncing until the user has finished any configuration.
108  ProfileSyncService* profile_sync_service = GetProfileSyncService();
109  if (profile_sync_service)
110    profile_sync_service->SetSetupInProgress(true);
111
112  // Make sure the syncing is not suppressed, otherwise the SigninManager
113  // will not be able to complete sucessfully.
114  sync_driver::SyncPrefs sync_prefs(profile_->GetPrefs());
115  sync_prefs.SetStartSuppressed(false);
116}
117
118void OneClickSigninSyncStarter::ConfirmSignin(const std::string& oauth_token) {
119  DCHECK(!oauth_token.empty());
120  SigninManager* signin = SigninManagerFactory::GetForProfile(profile_);
121  // If this is a new signin (no authenticated username yet) try loading
122  // policy for this user now, before any signed in services are initialized.
123  if (signin->GetAuthenticatedUsername().empty()) {
124#if defined(ENABLE_CONFIGURATION_POLICY)
125    policy::UserPolicySigninService* policy_service =
126        policy::UserPolicySigninServiceFactory::GetForProfile(profile_);
127    policy_service->RegisterForPolicy(
128        signin->GetUsernameForAuthInProgress(),
129        oauth_token,
130        base::Bind(&OneClickSigninSyncStarter::OnRegisteredForPolicy,
131                   weak_pointer_factory_.GetWeakPtr()));
132    return;
133#else
134    ConfirmAndSignin();
135#endif
136  } else {
137    // The user is already signed in - just tell SigninManager to continue
138    // with its re-auth flow.
139    signin->CompletePendingSignin();
140  }
141}
142
143#if defined(ENABLE_CONFIGURATION_POLICY)
144OneClickSigninSyncStarter::SigninDialogDelegate::SigninDialogDelegate(
145    base::WeakPtr<OneClickSigninSyncStarter> sync_starter)
146  : sync_starter_(sync_starter) {
147}
148
149OneClickSigninSyncStarter::SigninDialogDelegate::~SigninDialogDelegate() {
150}
151
152void OneClickSigninSyncStarter::SigninDialogDelegate::OnCancelSignin() {
153  if (sync_starter_ != NULL)
154    sync_starter_->CancelSigninAndDelete();
155}
156
157void OneClickSigninSyncStarter::SigninDialogDelegate::OnContinueSignin() {
158  if (sync_starter_ != NULL)
159    sync_starter_->LoadPolicyWithCachedCredentials();
160}
161
162void OneClickSigninSyncStarter::SigninDialogDelegate::OnSigninWithNewProfile() {
163  if (sync_starter_ != NULL)
164    sync_starter_->CreateNewSignedInProfile();
165}
166
167void OneClickSigninSyncStarter::OnRegisteredForPolicy(
168    const std::string& dm_token, const std::string& client_id) {
169  SigninManager* signin = SigninManagerFactory::GetForProfile(profile_);
170  // If there's no token for the user (policy registration did not succeed) just
171  // finish signing in.
172  if (dm_token.empty()) {
173    DVLOG(1) << "Policy registration failed";
174    ConfirmAndSignin();
175    return;
176  }
177
178  DVLOG(1) << "Policy registration succeeded: dm_token=" << dm_token;
179
180  // Stash away a copy of our CloudPolicyClient (should not already have one).
181  DCHECK(dm_token_.empty());
182  DCHECK(client_id_.empty());
183  dm_token_ = dm_token;
184  client_id_ = client_id;
185
186  // Allow user to create a new profile before continuing with sign-in.
187  browser_ = EnsureBrowser(browser_, profile_, desktop_type_);
188  content::WebContents* web_contents =
189      browser_->tab_strip_model()->GetActiveWebContents();
190  if (!web_contents) {
191    CancelSigninAndDelete();
192    return;
193  }
194  chrome::ShowProfileSigninConfirmationDialog(
195      browser_,
196      web_contents,
197      profile_,
198      signin->GetUsernameForAuthInProgress(),
199      new SigninDialogDelegate(weak_pointer_factory_.GetWeakPtr()));
200}
201
202void OneClickSigninSyncStarter::LoadPolicyWithCachedCredentials() {
203  DCHECK(!dm_token_.empty());
204  DCHECK(!client_id_.empty());
205  SigninManager* signin = SigninManagerFactory::GetForProfile(profile_);
206  policy::UserPolicySigninService* policy_service =
207      policy::UserPolicySigninServiceFactory::GetForProfile(profile_);
208  policy_service->FetchPolicyForSignedInUser(
209      signin->GetUsernameForAuthInProgress(),
210      dm_token_,
211      client_id_,
212      profile_->GetRequestContext(),
213      base::Bind(&OneClickSigninSyncStarter::OnPolicyFetchComplete,
214                 weak_pointer_factory_.GetWeakPtr()));
215}
216
217void OneClickSigninSyncStarter::OnPolicyFetchComplete(bool success) {
218  // For now, we allow signin to complete even if the policy fetch fails. If
219  // we ever want to change this behavior, we could call
220  // SigninManager::SignOut() here instead.
221  DLOG_IF(ERROR, !success) << "Error fetching policy for user";
222  DVLOG_IF(1, success) << "Policy fetch successful - completing signin";
223  SigninManagerFactory::GetForProfile(profile_)->CompletePendingSignin();
224}
225
226void OneClickSigninSyncStarter::CreateNewSignedInProfile() {
227  SigninManager* signin = SigninManagerFactory::GetForProfile(profile_);
228  DCHECK(!signin->GetUsernameForAuthInProgress().empty());
229  DCHECK(!dm_token_.empty());
230  DCHECK(!client_id_.empty());
231  // Create a new profile and have it call back when done so we can inject our
232  // signin credentials.
233  size_t icon_index = g_browser_process->profile_manager()->
234      GetProfileInfoCache().ChooseAvatarIconIndexForNewProfile();
235  ProfileManager::CreateMultiProfileAsync(
236      base::UTF8ToUTF16(signin->GetUsernameForAuthInProgress()),
237      base::UTF8ToUTF16(profiles::GetDefaultAvatarIconUrl(icon_index)),
238      base::Bind(&OneClickSigninSyncStarter::CompleteInitForNewProfile,
239                 weak_pointer_factory_.GetWeakPtr(), desktop_type_),
240      std::string());
241}
242
243void OneClickSigninSyncStarter::CompleteInitForNewProfile(
244    chrome::HostDesktopType desktop_type,
245    Profile* new_profile,
246    Profile::CreateStatus status) {
247  DCHECK_NE(profile_, new_profile);
248
249  // TODO(atwilson): On error, unregister the client to release the DMToken
250  // and surface a better error for the user.
251  switch (status) {
252    case Profile::CREATE_STATUS_LOCAL_FAIL: {
253      NOTREACHED() << "Error creating new profile";
254      CancelSigninAndDelete();
255      return;
256    }
257    case Profile::CREATE_STATUS_CREATED: {
258      break;
259    }
260    case Profile::CREATE_STATUS_INITIALIZED: {
261      // Wait until the profile is initialized before we transfer credentials.
262      SigninManager* old_signin_manager =
263          SigninManagerFactory::GetForProfile(profile_);
264      SigninManager* new_signin_manager =
265          SigninManagerFactory::GetForProfile(new_profile);
266      DCHECK(!old_signin_manager->GetUsernameForAuthInProgress().empty());
267      DCHECK(old_signin_manager->GetAuthenticatedUsername().empty());
268      DCHECK(new_signin_manager->GetAuthenticatedUsername().empty());
269      DCHECK(!dm_token_.empty());
270      DCHECK(!client_id_.empty());
271
272      // Copy credentials from the old profile to the just-created profile,
273      // and switch over to tracking that profile.
274      new_signin_manager->CopyCredentialsFrom(*old_signin_manager);
275      FinishProfileSyncServiceSetup();
276      Initialize(new_profile, NULL);
277      DCHECK_EQ(profile_, new_profile);
278
279      // We've transferred our credentials to the new profile - notify that
280      // the signin for the original profile was cancelled (must do this after
281      // we have called Initialize() with the new profile, as otherwise this
282      // object will get freed when the signin on the old profile is cancelled.
283      old_signin_manager->SignOut(signin_metrics::TRANSFER_CREDENTIALS);
284
285      // Load policy for the just-created profile - once policy has finished
286      // loading the signin process will complete.
287      LoadPolicyWithCachedCredentials();
288
289      // Open the profile's first window, after all initialization.
290      profiles::FindOrCreateNewWindowForProfile(
291        new_profile,
292        chrome::startup::IS_PROCESS_STARTUP,
293        chrome::startup::IS_FIRST_RUN,
294        desktop_type,
295        false);
296      break;
297    }
298    case Profile::CREATE_STATUS_REMOTE_FAIL:
299    case Profile::CREATE_STATUS_CANCELED:
300    case Profile::MAX_CREATE_STATUS: {
301      NOTREACHED() << "Invalid profile creation status";
302      CancelSigninAndDelete();
303      return;
304    }
305  }
306}
307#endif
308
309void OneClickSigninSyncStarter::CancelSigninAndDelete() {
310  SigninManagerFactory::GetForProfile(profile_)->SignOut(
311      signin_metrics::ABORT_SIGNIN);
312  // The statement above results in a call to SigninFailed() which will free
313  // this object, so do not refer to the OneClickSigninSyncStarter object
314  // after this point.
315}
316
317void OneClickSigninSyncStarter::ConfirmAndSignin() {
318  SigninManager* signin = SigninManagerFactory::GetForProfile(profile_);
319  if (confirmation_required_ == CONFIRM_UNTRUSTED_SIGNIN) {
320    browser_ = EnsureBrowser(browser_, profile_, desktop_type_);
321    // Display a confirmation dialog to the user.
322    browser_->window()->ShowOneClickSigninBubble(
323        BrowserWindow::ONE_CLICK_SIGNIN_BUBBLE_TYPE_SAML_MODAL_DIALOG,
324        base::UTF8ToUTF16(signin->GetUsernameForAuthInProgress()),
325        base::string16(),  // No error message to display.
326        base::Bind(&OneClickSigninSyncStarter::UntrustedSigninConfirmed,
327                   weak_pointer_factory_.GetWeakPtr()));
328  } else {
329    // No confirmation required - just sign in the user.
330    signin->CompletePendingSignin();
331  }
332}
333
334void OneClickSigninSyncStarter::UntrustedSigninConfirmed(
335    StartSyncMode response) {
336  if (response == UNDO_SYNC) {
337    CancelSigninAndDelete();  // This statement frees this object.
338  } else {
339    // If the user clicked the "Advanced" link in the confirmation dialog, then
340    // override the current start_mode_ to bring up the advanced sync settings.
341    if (response == CONFIGURE_SYNC_FIRST)
342      start_mode_ = response;
343    SigninManager* signin = SigninManagerFactory::GetForProfile(profile_);
344    signin->CompletePendingSignin();
345  }
346}
347
348void OneClickSigninSyncStarter::SigninFailed(
349    const GoogleServiceAuthError& error) {
350  if (!sync_setup_completed_callback_.is_null())
351    sync_setup_completed_callback_.Run(SYNC_SETUP_FAILURE);
352
353  FinishProfileSyncServiceSetup();
354  if (confirmation_required_ == CONFIRM_AFTER_SIGNIN) {
355    switch (error.state()) {
356      case GoogleServiceAuthError::SERVICE_UNAVAILABLE:
357        DisplayFinalConfirmationBubble(l10n_util::GetStringUTF16(
358            IDS_SYNC_UNRECOVERABLE_ERROR));
359        break;
360      case GoogleServiceAuthError::REQUEST_CANCELED:
361        // No error notification needed if the user manually cancelled signin.
362        break;
363      default:
364        DisplayFinalConfirmationBubble(l10n_util::GetStringUTF16(
365            IDS_SYNC_ERROR_SIGNING_IN));
366        break;
367    }
368  }
369  delete this;
370}
371
372void OneClickSigninSyncStarter::SigninSuccess() {
373  if (switches::IsEnableWebBasedSignin())
374    MergeSessionComplete(GoogleServiceAuthError(GoogleServiceAuthError::NONE));
375}
376
377void OneClickSigninSyncStarter::MergeSessionComplete(
378    const GoogleServiceAuthError& error) {
379  // Regardless of whether the merge session completed sucessfully or not,
380  // continue with sync starting.
381
382  if (!sync_setup_completed_callback_.is_null())
383    sync_setup_completed_callback_.Run(SYNC_SETUP_SUCCESS);
384
385  switch (start_mode_) {
386    case SYNC_WITH_DEFAULT_SETTINGS: {
387      // Just kick off the sync machine, no need to configure it first.
388      ProfileSyncService* profile_sync_service = GetProfileSyncService();
389      if (profile_sync_service)
390        profile_sync_service->SetSyncSetupCompleted();
391      FinishProfileSyncServiceSetup();
392      if (confirmation_required_ == CONFIRM_AFTER_SIGNIN) {
393        base::string16 message;
394        if (!profile_sync_service) {
395          // Sync is disabled by policy.
396          message = l10n_util::GetStringUTF16(
397              IDS_ONE_CLICK_SIGNIN_BUBBLE_SYNC_DISABLED_MESSAGE);
398        }
399        DisplayFinalConfirmationBubble(message);
400      }
401      break;
402    }
403    case CONFIGURE_SYNC_FIRST:
404      ShowSettingsPage(true);  // Show sync config UI.
405      break;
406    case SHOW_SETTINGS_WITHOUT_CONFIGURE:
407      ShowSettingsPage(false);  // Don't show sync config UI.
408      break;
409    case UNDO_SYNC:
410      NOTREACHED();
411  }
412
413  // Navigate to the |continue_url_| if one is set, unless the user first needs
414  // to configure Sync.
415  if (web_contents() && !continue_url_.is_empty() &&
416      start_mode_ != CONFIGURE_SYNC_FIRST) {
417    LoadContinueUrl();
418  }
419
420  delete this;
421}
422
423void OneClickSigninSyncStarter::DisplayFinalConfirmationBubble(
424    const base::string16& custom_message) {
425  browser_ = EnsureBrowser(browser_, profile_, desktop_type_);
426  browser_->window()->ShowOneClickSigninBubble(
427      BrowserWindow::ONE_CLICK_SIGNIN_BUBBLE_TYPE_BUBBLE,
428      base::string16(),  // No email required - this is not a SAML confirmation.
429      custom_message,
430      // Callback is ignored.
431      BrowserWindow::StartSyncCallback());
432}
433
434// static
435Browser* OneClickSigninSyncStarter::EnsureBrowser(
436    Browser* browser,
437    Profile* profile,
438    chrome::HostDesktopType desktop_type) {
439  if (!browser) {
440    // The user just created a new profile or has closed the browser that
441    // we used previously. Grab the most recently active browser or else
442    // create a new one.
443    browser = chrome::FindLastActiveWithProfile(profile, desktop_type);
444    if (!browser) {
445      browser = new Browser(Browser::CreateParams(profile,
446                                                   desktop_type));
447      chrome::AddTabAt(browser, GURL(), -1, true);
448    }
449    browser->window()->Show();
450  }
451  return browser;
452}
453
454void OneClickSigninSyncStarter::ShowSettingsPage(bool configure_sync) {
455  // Give the user a chance to configure things. We don't clear the
456  // ProfileSyncService::setup_in_progress flag because we don't want sync
457  // to start up until after the configure UI is displayed (the configure UI
458  // will clear the flag when the user is done setting up sync).
459  ProfileSyncService* profile_sync_service = GetProfileSyncService();
460  LoginUIService* login_ui = LoginUIServiceFactory::GetForProfile(profile_);
461  if (login_ui->current_login_ui()) {
462    login_ui->current_login_ui()->FocusUI();
463  } else {
464    browser_ = EnsureBrowser(browser_, profile_, desktop_type_);
465
466    // If the sign in tab is showing the native signin page or the blank page
467    // for web-based flow, and is not about to be closed, use it to show the
468    // settings UI.
469    bool use_same_tab = false;
470    if (web_contents()) {
471      GURL current_url = web_contents()->GetLastCommittedURL();
472      bool is_chrome_signin_url =
473          current_url.GetOrigin().spec() == chrome::kChromeUIChromeSigninURL;
474      bool is_same_profile =
475          Profile::FromBrowserContext(web_contents()->GetBrowserContext()) ==
476          profile_;
477      use_same_tab =
478          (is_chrome_signin_url ||
479           signin::IsContinueUrlForWebBasedSigninFlow(current_url)) &&
480          !signin::IsAutoCloseEnabledInURL(current_url) &&
481          is_same_profile;
482    }
483    if (profile_sync_service) {
484      // Need to navigate to the settings page and display the sync UI.
485      if (use_same_tab) {
486        ShowSettingsPageInWebContents(web_contents(),
487                                      chrome::kSyncSetupSubPage);
488      } else {
489        // If the user is setting up sync for the first time, let them configure
490        // advanced sync settings. However, in the case of re-authentication,
491        // return the user to the settings page without showing any config UI.
492        if (configure_sync) {
493          chrome::ShowSettingsSubPage(browser_, chrome::kSyncSetupSubPage);
494        } else {
495          FinishProfileSyncServiceSetup();
496          chrome::ShowSettings(browser_);
497        }
498      }
499    } else {
500      // Sync is disabled - just display the settings page or redirect to the
501      // |continue_url_|.
502      FinishProfileSyncServiceSetup();
503      if (!use_same_tab)
504        chrome::ShowSettings(browser_);
505      else if (!continue_url_.is_empty())
506        LoadContinueUrl();
507      else
508        ShowSettingsPageInWebContents(web_contents(), std::string());
509    }
510  }
511}
512
513ProfileSyncService* OneClickSigninSyncStarter::GetProfileSyncService() {
514  ProfileSyncService* service = NULL;
515  if (profile_->IsSyncAccessible())
516    service = ProfileSyncServiceFactory::GetForProfile(profile_);
517  return service;
518}
519
520void OneClickSigninSyncStarter::FinishProfileSyncServiceSetup() {
521  ProfileSyncService* service =
522      ProfileSyncServiceFactory::GetForProfile(profile_);
523  if (service)
524    service->SetSetupInProgress(false);
525}
526
527void OneClickSigninSyncStarter::ShowSettingsPageInWebContents(
528    content::WebContents* contents,
529    const std::string& sub_page) {
530  if (!continue_url_.is_empty()) {
531    // The observer deletes itself once it's done.
532    DCHECK(!sub_page.empty());
533    new OneClickSigninSyncObserver(contents, continue_url_);
534  }
535
536  GURL url = chrome::GetSettingsUrl(sub_page);
537  content::OpenURLParams params(url,
538                                content::Referrer(),
539                                CURRENT_TAB,
540                                content::PAGE_TRANSITION_AUTO_TOPLEVEL,
541                                false);
542  contents->OpenURL(params);
543
544  // Activate the tab.
545  Browser* browser = chrome::FindBrowserWithWebContents(contents);
546  int content_index =
547      browser->tab_strip_model()->GetIndexOfWebContents(contents);
548  browser->tab_strip_model()->ActivateTabAt(content_index,
549                                            false /* user_gesture */);
550}
551
552void OneClickSigninSyncStarter::LoadContinueUrl() {
553  web_contents()->GetController().LoadURL(
554      continue_url_,
555      content::Referrer(),
556      content::PAGE_TRANSITION_AUTO_TOPLEVEL,
557      std::string());
558}
559