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#ifndef CHROME_BROWSER_UI_SYNC_ONE_CLICK_SIGNIN_HELPER_H_
6#define CHROME_BROWSER_UI_SYNC_ONE_CLICK_SIGNIN_HELPER_H_
7
8#include <string>
9
10#include "base/gtest_prod_util.h"
11#include "base/memory/weak_ptr.h"
12#include "chrome/browser/signin/signin_promo.h"
13#include "chrome/browser/sync/profile_sync_service_observer.h"
14#include "chrome/browser/ui/sync/one_click_signin_sync_starter.h"
15#include "components/signin/core/browser/signin_oauth_helper.h"
16#include "content/public/browser/navigation_controller.h"
17#include "content/public/browser/web_contents_observer.h"
18#include "content/public/browser/web_contents_user_data.h"
19#include "google_apis/gaia/google_service_auth_error.h"
20
21class Browser;
22class GURL;
23class ProfileIOData;
24
25namespace autofill {
26struct PasswordForm;
27}
28
29namespace content {
30class WebContents;
31struct FrameNavigateParams;
32struct LoadCommittedDetails;
33}
34
35namespace net {
36class URLRequest;
37}
38
39namespace password_manager {
40class PasswordManager;
41}
42
43// Per-tab one-click signin helper.  When a user signs in to a Google service
44// and the profile is not yet connected to a Google account, will start the
45// process of helping the user connect his profile with one click.  The process
46// begins with an infobar and is followed with a confirmation dialog explaining
47// more about what this means.
48class OneClickSigninHelper
49    : public content::WebContentsObserver,
50      public content::WebContentsUserData<OneClickSigninHelper> {
51 public:
52  // Represents user's decision about sign in process.
53  enum AutoAccept {
54    // User decision not yet known.  Assume cancel.
55    AUTO_ACCEPT_NONE,
56
57    // User has explicitly accepted to sign in.  A bubble is shown with the
58    // option to start sync, configure it first, or abort.
59    AUTO_ACCEPT_ACCEPTED,
60
61    // User has explicitly accepted to sign in, but wants to configure sync
62    // settings before turning it on.
63    AUTO_ACCEPT_CONFIGURE,
64
65    // User has explicitly rejected to sign in.  Furthermore, the user does
66    // not want to be prompted to see the interstitial again in this profile.
67    AUTO_ACCEPT_REJECTED_FOR_PROFILE,
68
69    // This is an explicit sign in from either first run, NTP, wrench menu,
70    // or settings page.  The user will be signed in automatically with sync
71    // enabled using default settings.
72    AUTO_ACCEPT_EXPLICIT
73  };
74
75  // Return value of CanOfferOnIOThread().
76  enum Offer {
77    CAN_OFFER,
78    DONT_OFFER,
79    IGNORE_REQUEST
80  };
81
82  // Argument to CanOffer().
83  enum CanOfferFor {
84    CAN_OFFER_FOR_ALL,
85    CAN_OFFER_FOR_INTERSTITAL_ONLY,
86    CAN_OFFER_FOR_SECONDARY_ACCOUNT
87    // TODO(guohui): needs to handle adding secondary account through
88    // interstitial.
89  };
90
91  // Arguments used with StartSync function.  base::Bind() cannot support too
92  // many args for performance reasons, so they are packaged up into a struct.
93  struct StartSyncArgs {
94    // Default contructor for testing only.
95    StartSyncArgs();
96    StartSyncArgs(Profile* profile,
97                  Browser* browser,
98                  OneClickSigninHelper::AutoAccept auto_accept,
99                  const std::string& session_index,
100                  const std::string& email,
101                  const std::string& password,
102                  const std::string& refresh_token,
103                  content::WebContents* web_contents,
104                  bool untrusted_confirmation_required,
105                  signin::Source source,
106                  OneClickSigninSyncStarter::Callback callback);
107    ~StartSyncArgs();
108
109    Profile* profile;
110    Browser* browser;
111    OneClickSigninHelper::AutoAccept auto_accept;
112    std::string session_index;
113    std::string email;
114    std::string password;
115    std::string refresh_token;
116
117    // Web contents in which the sync setup page should be displayed,
118    // if necessary. Can be NULL.
119    content::WebContents* web_contents;
120
121    OneClickSigninSyncStarter::ConfirmationRequired confirmation_required;
122    signin::Source source;
123    OneClickSigninSyncStarter::Callback callback;
124  };
125
126  // Wrapper to call OneClickSigninSyncStarter after fetching the refresh token
127  // if needed.  Also verifies that the cookies are correct if no password is
128  // specified, and checks that the email from the cookies match the expected
129  // email address.
130  class SyncStarterWrapper : public SigninOAuthHelper::Consumer,
131                             public chrome::BrowserListObserver {
132   public:
133    SyncStarterWrapper(
134        const OneClickSigninHelper::StartSyncArgs& args,
135        OneClickSigninSyncStarter::StartSyncMode start_mode);
136    virtual ~SyncStarterWrapper();
137
138    void Start();
139
140   private:
141    void VerifyGaiaCookiesBeforeSignIn();
142    void OnGaiaCookiesFetched(const std::string session_index,
143                              const net::CookieList& cookie_list);
144
145    // Virtual to be overridden in tests.
146    virtual void DisplayErrorBubble(const std::string& error_message);
147    virtual void StartSigninOAuthHelper();
148    virtual void StartOneClickSigninSyncStarter(
149        const std::string& email,
150        const std::string& refresh_token);
151
152    // Overriden from SigninOAuthHelper::Consumer.
153    virtual void OnSigninOAuthInformationAvailable(
154        const std::string& email,
155        const std::string& display_email,
156        const std::string& refresh_token) OVERRIDE;
157    virtual void OnSigninOAuthInformationFailure(
158        const GoogleServiceAuthError& error) OVERRIDE;
159
160    // Overriden from chrome::BrowserListObserver.
161    virtual void OnBrowserRemoved(Browser* browser) OVERRIDE;
162
163    OneClickSigninHelper::StartSyncArgs args_;
164    chrome::HostDesktopType desktop_type_;
165    OneClickSigninSyncStarter::StartSyncMode start_mode_;
166    scoped_ptr<SigninOAuthHelper> signin_oauth_helper_;
167    base::WeakPtrFactory<SyncStarterWrapper> weak_pointer_factory_;
168
169    DISALLOW_COPY_AND_ASSIGN(SyncStarterWrapper);
170  };
171
172  static void LogHistogramValue(signin::Source source, int action);
173
174  static void CreateForWebContentsWithPasswordManager(
175      content::WebContents* contents,
176      password_manager::PasswordManager* password_manager);
177
178  // Returns true if the one-click signin feature can be offered at this time.
179  // If |email| is not empty, then the profile is checked to see if it's
180  // already connected to a google account or if the user has already rejected
181  // one-click sign-in with this email, in which cases a one click signin
182  // should not be offered.
183  //
184  // If |can_offer_for| is |CAN_OFFER_FOR_INTERSTITAL_ONLY|, then only do the
185  // checks that would affect the interstitial page.  Otherwise, do the checks
186  // that would affect the interstitial and the explicit sign ins.
187  //
188  // Returns in |error_message_id| an explanation as a string resource ID for
189  // why one-clicked cannot be offered.  |error_message_id| is valid only if
190  // the return value is false.  If no explanation is needed, |error_message_id|
191  // may be null.
192  static bool CanOffer(content::WebContents* web_contents,
193                       CanOfferFor can_offer_for,
194                       const std::string& email,
195                       std::string* error_message);
196
197  // Returns true if the one-click signin feature can be offered at this time.
198  // It can be offered if the io_data is not in an incognito window and if the
199  // origin of |url| is a valid Gaia sign in origin.  This function is meant
200  // to called only from the IO thread.
201  static Offer CanOfferOnIOThread(net::URLRequest* request,
202                                  ProfileIOData* io_data);
203
204  // Looks for the Google-Accounts-SignIn response header, and if found,
205  // tries to display an infobar in the tab contents identified by the
206  // child/route id.
207  static void ShowInfoBarIfPossible(net::URLRequest* request,
208                                    ProfileIOData* io_data,
209                                    int child_id,
210                                    int route_id);
211
212  // Handles cross account sign in error. If the supplied |email| does not match
213  // the last signed in email of the current profile, then Chrome will show a
214  // confirmation dialog before starting sync. It returns true if there is a
215  // cross account error, and false otherwise.
216  static bool HandleCrossAccountError(
217      Profile* profile,
218      const std::string& session_index,
219      const std::string& email,
220      const std::string& password,
221      const std::string& refresh_token,
222      OneClickSigninHelper::AutoAccept auto_accept,
223      signin::Source source,
224      OneClickSigninSyncStarter::StartSyncMode start_mode,
225      OneClickSigninSyncStarter::Callback sync_callback);
226
227  static void RedirectToNtpOrAppsPage(
228      content::WebContents* contents, signin::Source source);
229
230  // If the |source| is not settings page/webstore, redirects to
231  // the NTP/Apps page.
232  static void RedirectToNtpOrAppsPageIfNecessary(
233      content::WebContents* contents, signin::Source source);
234
235  // Remove the item currently at the top of the history list if it's
236  // the Gaia redirect URL. Due to limitations of the NavigationController
237  // this cannot be done until a new page becomes "current".
238  static void RemoveSigninRedirectURLHistoryItem(
239      content::WebContents* web_contents);
240
241  static void LogConfirmHistogramValue(int action);
242
243 private:
244  friend class content::WebContentsUserData<OneClickSigninHelper>;
245  friend class OneClickSigninHelperTest;
246  FRIEND_TEST_ALL_PREFIXES(OneClickSigninHelperIncognitoTest,
247                           ShowInfoBarUIThreadIncognito);
248  FRIEND_TEST_ALL_PREFIXES(OneClickSigninHelperTest,
249                           SigninFromWebstoreWithConfigSyncfirst);
250  FRIEND_TEST_ALL_PREFIXES(OneClickSigninHelperTest,
251                           ShowSigninBubbleAfterSigninComplete);
252  FRIEND_TEST_ALL_PREFIXES(OneClickSigninHelperTest, SigninCancelled);
253  FRIEND_TEST_ALL_PREFIXES(OneClickSigninHelperTest, SigninFailed);
254  FRIEND_TEST_ALL_PREFIXES(OneClickSigninHelperTest,
255                           CleanTransientStateOnNavigate);
256  FRIEND_TEST_ALL_PREFIXES(OneClickSigninHelperIOTest, CanOfferOnIOThread);
257  FRIEND_TEST_ALL_PREFIXES(OneClickSigninHelperIOTest,
258                           CanOfferOnIOThreadIncognito);
259  FRIEND_TEST_ALL_PREFIXES(OneClickSigninHelperIOTest,
260                           CanOfferOnIOThreadNoIOData);
261  FRIEND_TEST_ALL_PREFIXES(OneClickSigninHelperIOTest,
262                           CanOfferOnIOThreadBadURL);
263  FRIEND_TEST_ALL_PREFIXES(OneClickSigninHelperIOTest,
264                           CanOfferOnIOThreadDisabled);
265  FRIEND_TEST_ALL_PREFIXES(OneClickSigninHelperIOTest,
266                           CanOfferOnIOThreadSignedIn);
267  FRIEND_TEST_ALL_PREFIXES(OneClickSigninHelperIOTest,
268                           CanOfferOnIOThreadEmailNotAllowed);
269  FRIEND_TEST_ALL_PREFIXES(OneClickSigninHelperIOTest,
270                           CanOfferOnIOThreadEmailAlreadyUsed);
271  FRIEND_TEST_ALL_PREFIXES(OneClickSigninHelperIOTest,
272                           CreateTestProfileIOData);
273  FRIEND_TEST_ALL_PREFIXES(OneClickSigninHelperIOTest,
274                           CanOfferOnIOThreadWithRejectedEmail);
275  FRIEND_TEST_ALL_PREFIXES(OneClickSigninHelperIOTest,
276                           CanOfferOnIOThreadNoSigninCookies);
277  FRIEND_TEST_ALL_PREFIXES(OneClickSigninHelperIOTest,
278                           CanOfferOnIOThreadDisabledByPolicy);
279
280  // Maximum number of navigations away from the set of valid Gaia URLs before
281  // clearing the internal state of the helper.  This is necessary to support
282  // SAML-based accounts, but causes bug crbug.com/181163.
283  static const int kMaxNavigationsSince;
284
285  OneClickSigninHelper(content::WebContents* web_contents,
286                       password_manager::PasswordManager* password_manager);
287
288  virtual ~OneClickSigninHelper();
289
290  // Returns true if the one-click signin feature can be offered at this time.
291  // It can be offered if the io_data is not in an incognito window and if the
292  // origin of |url| is a valid Gaia sign in origin.  This function is meant
293  // to called only from the IO thread.
294  static Offer CanOfferOnIOThreadImpl(const GURL& url,
295                                      base::SupportsUserData* request,
296                                      ProfileIOData* io_data);
297
298  // The portion of ShowInfoBarIfPossible() that needs to run on the UI thread.
299  // |session_index| and |email| are extracted from the Google-Accounts-SignIn
300  // header.  |auto_accept| is extracted from the Google-Chrome-SignIn header.
301  // |source| is used to determine which of the explicit sign in mechanism is
302  // being used.
303  //
304  // |continue_url| is where Gaia will continue to when the sign in process is
305  // done.  For explicit sign ins, this is a URL chrome controls. For one-click
306  // sign in, this could be any google property.  This URL is used to know
307  // when the sign process is over and to collect infomation from the user
308  // entered on the Gaia sign in page (for explicit sign ins).
309  static void ShowInfoBarUIThread(const std::string& session_index,
310                                  const std::string& email,
311                                  AutoAccept auto_accept,
312                                  signin::Source source,
313                                  const GURL& continue_url,
314                                  int child_id,
315                                  int route_id);
316
317  void RedirectToSignin();
318
319  // Clear all data member of the helper, except for the error.
320  void CleanTransientState();
321
322  // Unitests that use a TestingProfile should call this.
323  // Otherwise, clearing the pending e-mail crashes because the code expects
324  // a real ResourceContext rather than the MockResourceContext a
325  // TestingProfile provides.
326  void SetDoNotClearPendingEmailForTesting();
327
328  // In unit tests, disable starting the actual sync.
329  void set_do_not_start_sync_for_testing();
330
331  // Called when password has been submitted.
332  void PasswordSubmitted(const autofill::PasswordForm& form);
333
334  // content::WebContentsObserver overrides.
335  virtual void DidStartNavigationToPendingEntry(
336      const GURL& url,
337      content::NavigationController::ReloadType reload_type) OVERRIDE;
338  virtual void DidNavigateMainFrame(
339      const content::LoadCommittedDetails& details,
340      const content::FrameNavigateParams& params) OVERRIDE;
341  virtual void DidStopLoading(
342      content::RenderViewHost* render_view_host) OVERRIDE;
343
344  OneClickSigninSyncStarter::Callback CreateSyncStarterCallback();
345
346  // Callback invoked when OneClickSigninSyncStarter completes sync setup.
347  void SyncSetupCompletedCallback(
348      OneClickSigninSyncStarter::SyncSetupResult result);
349
350  // Tracks if we are in the process of showing the signin or one click
351  // interstitial page. It's set to true the first time we load one of those
352  // pages and set to false when transient state is cleaned.
353  // Note: This should only be used for logging purposes.
354  bool showing_signin_;
355
356  // Information about the account that has just logged in.
357  std::string session_index_;
358  std::string email_;
359  std::string password_;
360  AutoAccept auto_accept_;
361  signin::Source source_;
362  bool switched_to_advanced_;
363  GURL continue_url_;
364  // The orignal continue URL after sync setup is complete.
365  GURL original_continue_url_;
366  std::string error_message_;
367
368  // Number of navigations since starting a sign in that is outside the
369  // the set of trusted Gaia URLs.  Sign in attempts that include visits to
370  // one more untrusted will cause a modal dialog to appear asking the user
371  // to confirm, similar to the interstitial flow.
372  int untrusted_navigations_since_signin_visit_;
373
374  // Whether a Gaia URL during the sign in process was not handled by the
375  // dedicated sign in process (e.g. SAML login, which redirects to a
376  // non-google-controlled domain).
377  // This is set to true if at least one such URL is detected.
378  bool untrusted_confirmation_required_;
379
380  // Allows unittests to avoid accessing the ResourceContext for clearing a
381  // pending e-mail.
382  bool do_not_clear_pending_email_;
383
384  // Allows unittest to avoid starting sync for real.
385  bool do_not_start_sync_for_testing_;
386
387  base::WeakPtrFactory<OneClickSigninHelper> weak_pointer_factory_;
388
389  DISALLOW_COPY_AND_ASSIGN(OneClickSigninHelper);
390};
391
392#endif  // CHROME_BROWSER_UI_SYNC_ONE_CLICK_SIGNIN_HELPER_H_
393