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