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