1 // Copyright 2013 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/webui/signin/inline_login_handler_impl.h" 6 7 #include <string> 8 9 #include "base/bind.h" 10 #include "base/strings/string_number_conversions.h" 11 #include "base/strings/string_util.h" 12 #include "base/strings/utf_string_conversions.h" 13 #include "base/values.h" 14 #include "chrome/browser/profiles/profile.h" 15 #include "chrome/browser/signin/about_signin_internals_factory.h" 16 #include "chrome/browser/signin/chrome_signin_client_factory.h" 17 #include "chrome/browser/signin/local_auth.h" 18 #include "chrome/browser/signin/profile_oauth2_token_service_factory.h" 19 #include "chrome/browser/signin/signin_manager_factory.h" 20 #include "chrome/browser/sync/profile_sync_service.h" 21 #include "chrome/browser/sync/profile_sync_service_factory.h" 22 #include "chrome/browser/ui/browser_finder.h" 23 #include "chrome/browser/ui/browser_window.h" 24 #include "chrome/browser/ui/sync/one_click_signin_helper.h" 25 #include "chrome/browser/ui/sync/one_click_signin_histogram.h" 26 #include "chrome/browser/ui/tabs/tab_strip_model.h" 27 #include "chrome/browser/ui/webui/signin/inline_login_ui.h" 28 #include "chrome/browser/ui/webui/signin/login_ui_service.h" 29 #include "chrome/browser/ui/webui/signin/login_ui_service_factory.h" 30 #include "chrome/common/url_constants.h" 31 #include "components/signin/core/browser/about_signin_internals.h" 32 #include "components/signin/core/browser/profile_oauth2_token_service.h" 33 #include "components/signin/core/browser/signin_error_controller.h" 34 #include "components/signin/core/browser/signin_oauth_helper.h" 35 #include "components/signin/core/common/profile_management_switches.h" 36 #include "content/public/browser/storage_partition.h" 37 #include "content/public/browser/web_ui.h" 38 #include "google_apis/gaia/gaia_auth_fetcher.h" 39 #include "google_apis/gaia/gaia_auth_util.h" 40 #include "google_apis/gaia/gaia_constants.h" 41 #include "google_apis/gaia/gaia_urls.h" 42 #include "net/base/url_util.h" 43 44 namespace { 45 46 class InlineSigninHelper : public SigninOAuthHelper::Consumer { 47 public: 48 InlineSigninHelper( 49 base::WeakPtr<InlineLoginHandlerImpl> handler, 50 net::URLRequestContextGetter* getter, 51 Profile* profile, 52 const GURL& current_url, 53 const std::string& email, 54 const std::string& password, 55 const std::string& session_index, 56 const std::string& signin_scoped_device_id, 57 bool choose_what_to_sync, 58 bool confirm_untrusted_signin); 59 60 private: 61 // Overriden from SigninOAuthHelper::Consumer. 62 virtual void OnSigninOAuthInformationAvailable( 63 const std::string& email, 64 const std::string& display_email, 65 const std::string& refresh_token) OVERRIDE; 66 virtual void OnSigninOAuthInformationFailure( 67 const GoogleServiceAuthError& error) OVERRIDE; 68 69 SigninOAuthHelper signin_oauth_helper_; 70 base::WeakPtr<InlineLoginHandlerImpl> handler_; 71 Profile* profile_; 72 GURL current_url_; 73 std::string email_; 74 std::string password_; 75 std::string session_index_; 76 bool choose_what_to_sync_; 77 bool confirm_untrusted_signin_; 78 79 DISALLOW_COPY_AND_ASSIGN(InlineSigninHelper); 80 }; 81 82 InlineSigninHelper::InlineSigninHelper( 83 base::WeakPtr<InlineLoginHandlerImpl> handler, 84 net::URLRequestContextGetter* getter, 85 Profile* profile, 86 const GURL& current_url, 87 const std::string& email, 88 const std::string& password, 89 const std::string& session_index, 90 const std::string& signin_scoped_device_id, 91 bool choose_what_to_sync, 92 bool confirm_untrusted_signin) 93 : signin_oauth_helper_(getter, session_index, signin_scoped_device_id, 94 this), 95 handler_(handler), 96 profile_(profile), 97 current_url_(current_url), 98 email_(email), 99 password_(password), 100 session_index_(session_index), 101 choose_what_to_sync_(choose_what_to_sync), 102 confirm_untrusted_signin_(confirm_untrusted_signin) { 103 DCHECK(profile_); 104 DCHECK(!email_.empty()); 105 } 106 107 void InlineSigninHelper::OnSigninOAuthInformationAvailable( 108 const std::string& email, 109 const std::string& display_email, 110 const std::string& refresh_token) { 111 content::WebContents* contents = NULL; 112 Browser* browser = NULL; 113 if (handler_) { 114 contents = handler_->web_ui()->GetWebContents(); 115 browser = handler_->GetDesktopBrowser(); 116 } 117 118 AboutSigninInternals* about_signin_internals = 119 AboutSigninInternalsFactory::GetForProfile(profile_); 120 about_signin_internals->OnRefreshTokenReceived("Successful"); 121 122 signin::Source source = signin::GetSourceForPromoURL(current_url_); 123 124 std::string primary_email = 125 SigninManagerFactory::GetForProfile(profile_)->GetAuthenticatedUsername(); 126 if (gaia::AreEmailsSame(email, primary_email) && 127 source == signin::SOURCE_REAUTH && 128 switches::IsNewProfileManagement()) { 129 chrome::SetLocalAuthCredentials(profile_, password_); 130 } 131 132 if (source == signin::SOURCE_AVATAR_BUBBLE_ADD_ACCOUNT || 133 source == signin::SOURCE_REAUTH) { 134 ProfileOAuth2TokenServiceFactory::GetForProfile(profile_)-> 135 UpdateCredentials(email, refresh_token); 136 137 if (signin::IsAutoCloseEnabledInURL(current_url_)) { 138 // Close the gaia sign in tab via a task to make sure we aren't in the 139 // middle of any webui handler code. 140 base::MessageLoop::current()->PostTask( 141 FROM_HERE, 142 base::Bind(&InlineLoginHandlerImpl::CloseTab, 143 handler_, 144 signin::ShouldShowAccountManagement(current_url_))); 145 } 146 } else { 147 ProfileSyncService* sync_service = 148 ProfileSyncServiceFactory::GetForProfile(profile_); 149 SigninErrorController* error_controller = 150 ProfileOAuth2TokenServiceFactory::GetForProfile(profile_)-> 151 signin_error_controller(); 152 153 bool is_new_avatar_menu = switches::IsNewAvatarMenu(); 154 155 OneClickSigninSyncStarter::StartSyncMode start_mode; 156 if (source == signin::SOURCE_SETTINGS || choose_what_to_sync_) { 157 bool show_settings_without_configure = 158 error_controller->HasError() && 159 sync_service && 160 sync_service->HasSyncSetupCompleted(); 161 start_mode = show_settings_without_configure ? 162 OneClickSigninSyncStarter::SHOW_SETTINGS_WITHOUT_CONFIGURE : 163 OneClickSigninSyncStarter::CONFIGURE_SYNC_FIRST; 164 } else { 165 start_mode = is_new_avatar_menu ? 166 OneClickSigninSyncStarter::CONFIRM_SYNC_SETTINGS_FIRST : 167 OneClickSigninSyncStarter::SYNC_WITH_DEFAULT_SETTINGS; 168 } 169 170 OneClickSigninSyncStarter::ConfirmationRequired confirmation_required; 171 if (confirm_untrusted_signin_) { 172 confirmation_required = 173 OneClickSigninSyncStarter::CONFIRM_UNTRUSTED_SIGNIN; 174 } else if (is_new_avatar_menu) { 175 confirmation_required = OneClickSigninSyncStarter::CONFIRM_AFTER_SIGNIN; 176 } else { 177 confirmation_required = 178 source == signin::SOURCE_SETTINGS || 179 choose_what_to_sync_ ? 180 OneClickSigninSyncStarter::NO_CONFIRMATION : 181 OneClickSigninSyncStarter::CONFIRM_AFTER_SIGNIN; 182 } 183 184 bool start_signin = 185 !OneClickSigninHelper::HandleCrossAccountError( 186 profile_, "", 187 email, password_, refresh_token, 188 OneClickSigninHelper::AUTO_ACCEPT_EXPLICIT, 189 source, start_mode, 190 base::Bind(&InlineLoginHandlerImpl::SyncStarterCallback, 191 handler_)); 192 if (start_signin) { 193 // Call OneClickSigninSyncStarter to exchange oauth code for tokens. 194 // OneClickSigninSyncStarter will delete itself once the job is done. 195 new OneClickSigninSyncStarter( 196 profile_, browser, 197 email, password_, refresh_token, 198 start_mode, 199 contents, 200 confirmation_required, 201 signin::GetNextPageURLForPromoURL(current_url_), 202 base::Bind(&InlineLoginHandlerImpl::SyncStarterCallback, handler_)); 203 } 204 } 205 206 base::MessageLoop::current()->DeleteSoon(FROM_HERE, this); 207 } 208 209 void InlineSigninHelper::OnSigninOAuthInformationFailure( 210 const GoogleServiceAuthError& error) { 211 if (handler_) 212 handler_->HandleLoginError(error.ToString()); 213 214 AboutSigninInternals* about_signin_internals = 215 AboutSigninInternalsFactory::GetForProfile(profile_); 216 about_signin_internals->OnRefreshTokenReceived("Failure"); 217 218 base::MessageLoop::current()->DeleteSoon(FROM_HERE, this); 219 } 220 221 } // namespace 222 223 InlineLoginHandlerImpl::InlineLoginHandlerImpl() 224 : confirm_untrusted_signin_(false), 225 weak_factory_(this) { 226 } 227 228 InlineLoginHandlerImpl::~InlineLoginHandlerImpl() {} 229 230 bool InlineLoginHandlerImpl::HandleContextMenu( 231 const content::ContextMenuParams& params) { 232 #ifndef NDEBUG 233 return false; 234 #else 235 return true; 236 #endif 237 } 238 239 void InlineLoginHandlerImpl::DidCommitProvisionalLoadForFrame( 240 content::RenderFrameHost* render_frame_host, 241 const GURL& url, 242 ui::PageTransition transition_type) { 243 if (!web_contents()) 244 return; 245 246 // Returns early if this is not a gaia iframe navigation. 247 const GURL kGaiaExtOrigin( 248 "chrome-extension://mfffpogegjflfpflabcdkioaeobkgjik/"); 249 content::RenderFrameHost* gaia_iframe = InlineLoginUI::GetAuthIframe( 250 web_contents(), kGaiaExtOrigin, "signin-frame"); 251 if (render_frame_host != gaia_iframe) 252 return; 253 254 // Loading any untrusted (e.g., HTTP) URLs in the privileged sign-in process 255 // will require confirmation before the sign in takes effect. 256 if (!url.is_empty()) { 257 GURL origin(url.GetOrigin()); 258 if (url.spec() != url::kAboutBlankURL && 259 origin != kGaiaExtOrigin && 260 !gaia::IsGaiaSignonRealm(origin)) { 261 confirm_untrusted_signin_ = true; 262 } 263 } 264 } 265 266 void InlineLoginHandlerImpl::SetExtraInitParams(base::DictionaryValue& params) { 267 params.SetString("service", "chromiumsync"); 268 269 content::WebContents* contents = web_ui()->GetWebContents(); 270 const GURL& current_url = contents->GetURL(); 271 std::string is_constrained; 272 net::GetValueForKeyInQuery(current_url, "constrained", &is_constrained); 273 if (is_constrained == "1") 274 contents->SetDelegate(this); 275 276 content::WebContentsObserver::Observe(contents); 277 278 signin::Source source = signin::GetSourceForPromoURL(current_url); 279 OneClickSigninHelper::LogHistogramValue( 280 source, one_click_signin::HISTOGRAM_SHOWN); 281 } 282 283 void InlineLoginHandlerImpl::CompleteLogin(const base::ListValue* args) { 284 content::WebContents* contents = web_ui()->GetWebContents(); 285 const GURL& current_url = contents->GetURL(); 286 287 const base::DictionaryValue* dict = NULL; 288 args->GetDictionary(0, &dict); 289 290 bool skip_for_now = false; 291 dict->GetBoolean("skipForNow", &skip_for_now); 292 if (skip_for_now) { 293 signin::SetUserSkippedPromo(Profile::FromWebUI(web_ui())); 294 SyncStarterCallback(OneClickSigninSyncStarter::SYNC_SETUP_FAILURE); 295 return; 296 } 297 298 base::string16 email_string16; 299 dict->GetString("email", &email_string16); 300 DCHECK(!email_string16.empty()); 301 std::string email(base::UTF16ToASCII(email_string16)); 302 303 base::string16 password_string16; 304 dict->GetString("password", &password_string16); 305 std::string password(base::UTF16ToASCII(password_string16)); 306 307 // When doing a SAML sign in, this email check may result in a false 308 // positive. This happens when the user types one email address in the 309 // gaia sign in page, but signs in to a different account in the SAML sign in 310 // page. 311 std::string default_email; 312 std::string validate_email; 313 if (net::GetValueForKeyInQuery(current_url, "email", &default_email) && 314 net::GetValueForKeyInQuery(current_url, "validateEmail", 315 &validate_email) && 316 validate_email == "1") { 317 if (!gaia::AreEmailsSame(email, default_email)) { 318 SyncStarterCallback(OneClickSigninSyncStarter::SYNC_SETUP_FAILURE); 319 return; 320 } 321 } 322 323 base::string16 session_index_string16; 324 dict->GetString("sessionIndex", &session_index_string16); 325 std::string session_index = base::UTF16ToASCII(session_index_string16); 326 DCHECK(!session_index.empty()); 327 328 bool choose_what_to_sync = false; 329 dict->GetBoolean("chooseWhatToSync", &choose_what_to_sync); 330 331 signin::Source source = signin::GetSourceForPromoURL(current_url); 332 OneClickSigninHelper::LogHistogramValue( 333 source, one_click_signin::HISTOGRAM_ACCEPTED); 334 bool switch_to_advanced = 335 choose_what_to_sync && (source != signin::SOURCE_SETTINGS); 336 OneClickSigninHelper::LogHistogramValue( 337 source, 338 switch_to_advanced ? one_click_signin::HISTOGRAM_WITH_ADVANCED : 339 one_click_signin::HISTOGRAM_WITH_DEFAULTS); 340 341 OneClickSigninHelper::CanOfferFor can_offer_for = 342 OneClickSigninHelper::CAN_OFFER_FOR_ALL; 343 switch (source) { 344 case signin::SOURCE_AVATAR_BUBBLE_ADD_ACCOUNT: 345 can_offer_for = OneClickSigninHelper::CAN_OFFER_FOR_SECONDARY_ACCOUNT; 346 break; 347 case signin::SOURCE_REAUTH: { 348 std::string primary_username = 349 SigninManagerFactory::GetForProfile( 350 Profile::FromWebUI(web_ui()))->GetAuthenticatedUsername(); 351 if (!gaia::AreEmailsSame(default_email, primary_username)) 352 can_offer_for = OneClickSigninHelper::CAN_OFFER_FOR_SECONDARY_ACCOUNT; 353 break; 354 } 355 default: 356 // No need to change |can_offer_for|. 357 break; 358 } 359 360 std::string error_msg; 361 bool can_offer = OneClickSigninHelper::CanOffer( 362 contents, can_offer_for, email, &error_msg); 363 if (!can_offer) { 364 HandleLoginError(error_msg); 365 return; 366 } 367 368 AboutSigninInternals* about_signin_internals = 369 AboutSigninInternalsFactory::GetForProfile(Profile::FromWebUI(web_ui())); 370 about_signin_internals->OnAuthenticationResultReceived( 371 "GAIA Auth Successful"); 372 373 content::StoragePartition* partition = 374 content::BrowserContext::GetStoragePartitionForSite( 375 contents->GetBrowserContext(), 376 GURL(chrome::kChromeUIChromeSigninURL)); 377 378 SigninClient* signin_client = 379 ChromeSigninClientFactory::GetForProfile(Profile::FromWebUI(web_ui())); 380 std::string signin_scoped_device_id = 381 signin_client->GetSigninScopedDeviceId(); 382 // InlineSigninHelper will delete itself. 383 new InlineSigninHelper(GetWeakPtr(), partition->GetURLRequestContext(), 384 Profile::FromWebUI(web_ui()), current_url, 385 email, password, session_index, 386 signin_scoped_device_id, choose_what_to_sync, 387 confirm_untrusted_signin_); 388 389 web_ui()->CallJavascriptFunction("inline.login.closeDialog"); 390 } 391 392 void InlineLoginHandlerImpl::HandleLoginError(const std::string& error_msg) { 393 SyncStarterCallback(OneClickSigninSyncStarter::SYNC_SETUP_FAILURE); 394 395 Browser* browser = GetDesktopBrowser(); 396 if (browser && !error_msg.empty()) { 397 LoginUIServiceFactory::GetForProfile(Profile::FromWebUI(web_ui()))-> 398 DisplayLoginResult(browser, base::UTF8ToUTF16(error_msg)); 399 } 400 } 401 402 Browser* InlineLoginHandlerImpl::GetDesktopBrowser() { 403 Browser* browser = chrome::FindBrowserWithWebContents( 404 web_ui()->GetWebContents()); 405 if (!browser) { 406 browser = chrome::FindLastActiveWithProfile( 407 Profile::FromWebUI(web_ui()), chrome::GetActiveDesktop()); 408 } 409 return browser; 410 } 411 412 void InlineLoginHandlerImpl::SyncStarterCallback( 413 OneClickSigninSyncStarter::SyncSetupResult result) { 414 content::WebContents* contents = web_ui()->GetWebContents(); 415 416 if (contents->GetController().GetPendingEntry()) { 417 // Do nothing if a navigation is pending, since this call can be triggered 418 // from DidStartLoading. This avoids deleting the pending entry while we are 419 // still navigating to it. See crbug/346632. 420 return; 421 } 422 423 const GURL& current_url = contents->GetLastCommittedURL(); 424 signin::Source source = signin::GetSourceForPromoURL(current_url); 425 bool auto_close = signin::IsAutoCloseEnabledInURL(current_url); 426 427 if (result == OneClickSigninSyncStarter::SYNC_SETUP_FAILURE) { 428 OneClickSigninHelper::RedirectToNtpOrAppsPage(contents, source); 429 } else if (auto_close) { 430 base::MessageLoop::current()->PostTask( 431 FROM_HERE, 432 base::Bind(&InlineLoginHandlerImpl::CloseTab, 433 weak_factory_.GetWeakPtr(), 434 signin::ShouldShowAccountManagement(current_url))); 435 } else { 436 OneClickSigninHelper::RedirectToNtpOrAppsPageIfNecessary(contents, source); 437 } 438 } 439 440 void InlineLoginHandlerImpl::CloseTab(bool show_account_management) { 441 content::WebContents* tab = web_ui()->GetWebContents(); 442 Browser* browser = chrome::FindBrowserWithWebContents(tab); 443 if (browser) { 444 TabStripModel* tab_strip_model = browser->tab_strip_model(); 445 if (tab_strip_model) { 446 int index = tab_strip_model->GetIndexOfWebContents(tab); 447 if (index != TabStripModel::kNoTab) { 448 tab_strip_model->ExecuteContextMenuCommand( 449 index, TabStripModel::CommandCloseTab); 450 } 451 } 452 453 if (show_account_management) { 454 browser->window()->ShowAvatarBubbleFromAvatarButton( 455 BrowserWindow::AVATAR_BUBBLE_MODE_ACCOUNT_MANAGEMENT, 456 signin::ManageAccountsParams()); 457 } 458 } 459 } 460