Home | History | Annotate | Download | only in sync
      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 
     21 class Browser;
     22 class GURL;
     23 class ProfileIOData;
     24 
     25 namespace autofill {
     26 struct PasswordForm;
     27 }
     28 
     29 namespace content {
     30 class WebContents;
     31 struct FrameNavigateParams;
     32 struct LoadCommittedDetails;
     33 }
     34 
     35 namespace net {
     36 class URLRequest;
     37 }
     38 
     39 namespace password_manager {
     40 class 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.
     48 class 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