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 ccount error, and false otherwise. 216 static bool HandleCrossAccountError( 217 content::WebContents* contents, 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 static void ShowSigninErrorBubble(Browser* browser, const std::string& error); 236 237 // Remove the item currently at the top of the history list if it's 238 // the Gaia redirect URL. Due to limitations of the NavigationController 239 // this cannot be done until a new page becomes "current". 240 static void RemoveSigninRedirectURLHistoryItem( 241 content::WebContents* web_contents); 242 243 static void LogConfirmHistogramValue(int action); 244 245 private: 246 friend class content::WebContentsUserData<OneClickSigninHelper>; 247 friend class OneClickSigninHelperTest; 248 FRIEND_TEST_ALL_PREFIXES(OneClickSigninHelperIncognitoTest, 249 ShowInfoBarUIThreadIncognito); 250 FRIEND_TEST_ALL_PREFIXES(OneClickSigninHelperTest, 251 SigninFromWebstoreWithConfigSyncfirst); 252 FRIEND_TEST_ALL_PREFIXES(OneClickSigninHelperTest, 253 ShowSigninBubbleAfterSigninComplete); 254 FRIEND_TEST_ALL_PREFIXES(OneClickSigninHelperTest, SigninCancelled); 255 FRIEND_TEST_ALL_PREFIXES(OneClickSigninHelperTest, SigninFailed); 256 FRIEND_TEST_ALL_PREFIXES(OneClickSigninHelperTest, 257 CleanTransientStateOnNavigate); 258 FRIEND_TEST_ALL_PREFIXES(OneClickSigninHelperIOTest, CanOfferOnIOThread); 259 FRIEND_TEST_ALL_PREFIXES(OneClickSigninHelperIOTest, 260 CanOfferOnIOThreadIncognito); 261 FRIEND_TEST_ALL_PREFIXES(OneClickSigninHelperIOTest, 262 CanOfferOnIOThreadNoIOData); 263 FRIEND_TEST_ALL_PREFIXES(OneClickSigninHelperIOTest, 264 CanOfferOnIOThreadBadURL); 265 FRIEND_TEST_ALL_PREFIXES(OneClickSigninHelperIOTest, 266 CanOfferOnIOThreadDisabled); 267 FRIEND_TEST_ALL_PREFIXES(OneClickSigninHelperIOTest, 268 CanOfferOnIOThreadSignedIn); 269 FRIEND_TEST_ALL_PREFIXES(OneClickSigninHelperIOTest, 270 CanOfferOnIOThreadEmailNotAllowed); 271 FRIEND_TEST_ALL_PREFIXES(OneClickSigninHelperIOTest, 272 CanOfferOnIOThreadEmailAlreadyUsed); 273 FRIEND_TEST_ALL_PREFIXES(OneClickSigninHelperIOTest, 274 CreateTestProfileIOData); 275 FRIEND_TEST_ALL_PREFIXES(OneClickSigninHelperIOTest, 276 CanOfferOnIOThreadWithRejectedEmail); 277 FRIEND_TEST_ALL_PREFIXES(OneClickSigninHelperIOTest, 278 CanOfferOnIOThreadNoSigninCookies); 279 FRIEND_TEST_ALL_PREFIXES(OneClickSigninHelperIOTest, 280 CanOfferOnIOThreadDisabledByPolicy); 281 282 // Maximum number of navigations away from the set of valid Gaia URLs before 283 // clearing the internal state of the helper. This is necessary to support 284 // SAML-based accounts, but causes bug crbug.com/181163. 285 static const int kMaxNavigationsSince; 286 287 OneClickSigninHelper(content::WebContents* web_contents, 288 password_manager::PasswordManager* password_manager); 289 290 virtual ~OneClickSigninHelper(); 291 292 // Returns true if the one-click signin feature can be offered at this time. 293 // It can be offered if the io_data is not in an incognito window and if the 294 // origin of |url| is a valid Gaia sign in origin. This function is meant 295 // to called only from the IO thread. 296 static Offer CanOfferOnIOThreadImpl(const GURL& url, 297 base::SupportsUserData* request, 298 ProfileIOData* io_data); 299 300 // The portion of ShowInfoBarIfPossible() that needs to run on the UI thread. 301 // |session_index| and |email| are extracted from the Google-Accounts-SignIn 302 // header. |auto_accept| is extracted from the Google-Chrome-SignIn header. 303 // |source| is used to determine which of the explicit sign in mechanism is 304 // being used. 305 // 306 // |continue_url| is where Gaia will continue to when the sign in process is 307 // done. For explicit sign ins, this is a URL chrome controls. For one-click 308 // sign in, this could be any google property. This URL is used to know 309 // when the sign process is over and to collect infomation from the user 310 // entered on the Gaia sign in page (for explicit sign ins). 311 static void ShowInfoBarUIThread(const std::string& session_index, 312 const std::string& email, 313 AutoAccept auto_accept, 314 signin::Source source, 315 const GURL& continue_url, 316 int child_id, 317 int route_id); 318 319 void RedirectToSignin(); 320 321 // Clear all data member of the helper, except for the error. 322 void CleanTransientState(); 323 324 // Unitests that use a TestingProfile should call this. 325 // Otherwise, clearing the pending e-mail crashes because the code expects 326 // a real ResourceContext rather than the MockResourceContext a 327 // TestingProfile provides. 328 void SetDoNotClearPendingEmailForTesting(); 329 330 // In unit tests, disable starting the actual sync. 331 void set_do_not_start_sync_for_testing(); 332 333 // Called when password has been submitted. 334 void PasswordSubmitted(const autofill::PasswordForm& form); 335 336 // content::WebContentsObserver overrides. 337 virtual void DidStartNavigationToPendingEntry( 338 const GURL& url, 339 content::NavigationController::ReloadType reload_type) OVERRIDE; 340 virtual void DidNavigateMainFrame( 341 const content::LoadCommittedDetails& details, 342 const content::FrameNavigateParams& params) OVERRIDE; 343 virtual void DidStopLoading( 344 content::RenderViewHost* render_view_host) OVERRIDE; 345 346 OneClickSigninSyncStarter::Callback CreateSyncStarterCallback(); 347 348 // Callback invoked when OneClickSigninSyncStarter completes sync setup. 349 void SyncSetupCompletedCallback( 350 OneClickSigninSyncStarter::SyncSetupResult result); 351 352 // Tracks if we are in the process of showing the signin or one click 353 // interstitial page. It's set to true the first time we load one of those 354 // pages and set to false when transient state is cleaned. 355 // Note: This should only be used for logging purposes. 356 bool showing_signin_; 357 358 // Information about the account that has just logged in. 359 std::string session_index_; 360 std::string email_; 361 std::string password_; 362 AutoAccept auto_accept_; 363 signin::Source source_; 364 bool switched_to_advanced_; 365 GURL continue_url_; 366 // The orignal continue URL after sync setup is complete. 367 GURL original_continue_url_; 368 std::string error_message_; 369 370 // Number of navigations since starting a sign in that is outside the 371 // the set of trusted Gaia URLs. Sign in attempts that include visits to 372 // one more untrusted will cause a modal dialog to appear asking the user 373 // to confirm, similar to the interstitial flow. 374 int untrusted_navigations_since_signin_visit_; 375 376 // Whether a Gaia URL during the sign in process was not handled by the 377 // dedicated sign in process (e.g. SAML login, which redirects to a 378 // non-google-controlled domain). 379 // This is set to true if at least one such URL is detected. 380 bool untrusted_confirmation_required_; 381 382 // Allows unittests to avoid accessing the ResourceContext for clearing a 383 // pending e-mail. 384 bool do_not_clear_pending_email_; 385 386 // Allows unittest to avoid starting sync for real. 387 bool do_not_start_sync_for_testing_; 388 389 base::WeakPtrFactory<OneClickSigninHelper> weak_pointer_factory_; 390 391 DISALLOW_COPY_AND_ASSIGN(OneClickSigninHelper); 392 }; 393 394 #endif // CHROME_BROWSER_UI_SYNC_ONE_CLICK_SIGNIN_HELPER_H_ 395