| // Copyright 2013 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/ui/webui/signin/inline_login_handler_impl.h" |
| |
| #include <string> |
| |
| #include "base/bind.h" |
| #include "base/prefs/pref_service.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/values.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/signin/profile_oauth2_token_service_factory.h" |
| #include "chrome/browser/signin/signin_oauth_helper.h" |
| #include "chrome/browser/signin/signin_promo.h" |
| #include "chrome/browser/sync/profile_sync_service.h" |
| #include "chrome/browser/sync/profile_sync_service_factory.h" |
| #include "chrome/browser/ui/browser_finder.h" |
| #include "chrome/browser/ui/sync/one_click_signin_helper.h" |
| #include "chrome/browser/ui/sync/one_click_signin_histogram.h" |
| #include "chrome/browser/ui/tabs/tab_strip_model.h" |
| #include "chrome/common/pref_names.h" |
| #include "chrome/common/url_constants.h" |
| #include "components/signin/core/browser/profile_oauth2_token_service.h" |
| #include "components/signin/core/browser/signin_error_controller.h" |
| #include "content/public/browser/storage_partition.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/browser/web_ui.h" |
| #include "google_apis/gaia/gaia_auth_fetcher.h" |
| #include "google_apis/gaia/gaia_auth_util.h" |
| #include "google_apis/gaia/gaia_constants.h" |
| #include "google_apis/gaia/gaia_urls.h" |
| #include "net/base/url_util.h" |
| |
| namespace { |
| |
| class InlineSigninHelper : public SigninOAuthHelper, |
| public SigninOAuthHelper::Consumer { |
| public: |
| InlineSigninHelper( |
| base::WeakPtr<InlineLoginHandlerImpl> handler, |
| net::URLRequestContextGetter* getter, |
| Profile* profile, |
| const GURL& current_url, |
| const std::string& email, |
| const std::string& password, |
| const std::string& session_index, |
| bool choose_what_to_sync); |
| |
| private: |
| // Overriden from SigninOAuthHelper::Consumer. |
| virtual void OnSigninOAuthInformationAvailable( |
| const std::string& email, |
| const std::string& display_email, |
| const std::string& refresh_token) OVERRIDE; |
| virtual void OnSigninOAuthInformationFailure( |
| const GoogleServiceAuthError& error) OVERRIDE; |
| |
| base::WeakPtr<InlineLoginHandlerImpl> handler_; |
| Profile* profile_; |
| GURL current_url_; |
| std::string email_; |
| std::string password_; |
| std::string session_index_; |
| bool choose_what_to_sync_; |
| |
| DISALLOW_COPY_AND_ASSIGN(InlineSigninHelper); |
| }; |
| |
| InlineSigninHelper::InlineSigninHelper( |
| base::WeakPtr<InlineLoginHandlerImpl> handler, |
| net::URLRequestContextGetter* getter, |
| Profile* profile, |
| const GURL& current_url, |
| const std::string& email, |
| const std::string& password, |
| const std::string& session_index, |
| bool choose_what_to_sync) |
| : SigninOAuthHelper(getter, session_index, this), |
| handler_(handler), |
| profile_(profile), |
| current_url_(current_url), |
| email_(email), |
| password_(password), |
| session_index_(session_index), |
| choose_what_to_sync_(choose_what_to_sync) { |
| DCHECK(profile_); |
| DCHECK(!email_.empty()); |
| DCHECK(!session_index_.empty()); |
| } |
| |
| void InlineSigninHelper::OnSigninOAuthInformationAvailable( |
| const std::string& email, |
| const std::string& display_email, |
| const std::string& refresh_token) { |
| content::WebContents* contents = NULL; |
| Browser* browser = NULL; |
| if (handler_) { |
| contents = handler_->web_ui()->GetWebContents(); |
| browser = handler_->GetDesktopBrowser(); |
| } |
| |
| signin::Source source = signin::GetSourceForPromoURL(current_url_); |
| if (source == signin::SOURCE_AVATAR_BUBBLE_ADD_ACCOUNT) { |
| ProfileOAuth2TokenServiceFactory::GetForProfile(profile_)-> |
| UpdateCredentials(email, refresh_token); |
| |
| if (signin::IsAutoCloseEnabledInURL(current_url_)) { |
| // Close the gaia sign in tab via a task to make sure we aren't in the |
| // middle of any webui handler code. |
| base::MessageLoop::current()->PostTask( |
| FROM_HERE, |
| base::Bind(&InlineLoginHandlerImpl::CloseTab, |
| handler_)); |
| } |
| } else { |
| ProfileSyncService* sync_service = |
| ProfileSyncServiceFactory::GetForProfile(profile_); |
| SigninErrorController* error_controller = |
| ProfileOAuth2TokenServiceFactory::GetForProfile(profile_)-> |
| signin_error_controller(); |
| OneClickSigninSyncStarter::StartSyncMode start_mode = |
| source == signin::SOURCE_SETTINGS || choose_what_to_sync_ ? |
| (error_controller->HasError() && |
| sync_service && sync_service->HasSyncSetupCompleted()) ? |
| OneClickSigninSyncStarter::SHOW_SETTINGS_WITHOUT_CONFIGURE : |
| OneClickSigninSyncStarter::CONFIGURE_SYNC_FIRST : |
| OneClickSigninSyncStarter::SYNC_WITH_DEFAULT_SETTINGS; |
| OneClickSigninSyncStarter::ConfirmationRequired confirmation_required = |
| source == signin::SOURCE_SETTINGS || |
| source == signin::SOURCE_WEBSTORE_INSTALL || |
| choose_what_to_sync_ ? |
| OneClickSigninSyncStarter::NO_CONFIRMATION : |
| OneClickSigninSyncStarter::CONFIRM_AFTER_SIGNIN; |
| bool start_signin = true; |
| |
| if (source != signin::SOURCE_AVATAR_BUBBLE_ADD_ACCOUNT) { |
| start_signin = !OneClickSigninHelper::HandleCrossAccountError( |
| contents, "", |
| email, password_, refresh_token, |
| OneClickSigninHelper::AUTO_ACCEPT_EXPLICIT, |
| source, start_mode, |
| base::Bind(&InlineLoginHandlerImpl::SyncStarterCallback, |
| handler_)); |
| } |
| |
| if (start_signin) { |
| // Call OneClickSigninSyncStarter to exchange oauth code for tokens. |
| // OneClickSigninSyncStarter will delete itself once the job is done. |
| new OneClickSigninSyncStarter( |
| profile_, browser, |
| email, password_, refresh_token, |
| start_mode, |
| contents, |
| confirmation_required, |
| base::Bind(&InlineLoginHandlerImpl::SyncStarterCallback, handler_)); |
| } |
| } |
| |
| base::MessageLoop::current()->DeleteSoon(FROM_HERE, this); |
| } |
| |
| void InlineSigninHelper::OnSigninOAuthInformationFailure( |
| const GoogleServiceAuthError& error) { |
| if (handler_) |
| handler_->HandleLoginError(error.ToString()); |
| |
| base::MessageLoop::current()->DeleteSoon(FROM_HERE, this); |
| } |
| |
| } // namespace |
| |
| InlineLoginHandlerImpl::InlineLoginHandlerImpl() |
| : weak_factory_(this), |
| choose_what_to_sync_(false), |
| complete_login_triggered_(false) { |
| } |
| |
| InlineLoginHandlerImpl::~InlineLoginHandlerImpl() {} |
| |
| void InlineLoginHandlerImpl::RegisterMessages() { |
| InlineLoginHandler::RegisterMessages(); |
| |
| web_ui()->RegisterMessageCallback("switchToFullTab", |
| base::Bind(&InlineLoginHandlerImpl::HandleSwitchToFullTabMessage, |
| base::Unretained(this))); |
| } |
| |
| void InlineLoginHandlerImpl::SetExtraInitParams(base::DictionaryValue& params) { |
| params.SetInteger("authMode", InlineLoginHandler::kDesktopAuthMode); |
| |
| const GURL& current_url = web_ui()->GetWebContents()->GetURL(); |
| signin::Source source = signin::GetSourceForPromoURL(current_url); |
| DCHECK(source != signin::SOURCE_UNKNOWN); |
| if (source == signin::SOURCE_AVATAR_BUBBLE_ADD_ACCOUNT || |
| source == signin::SOURCE_AVATAR_BUBBLE_SIGN_IN) { |
| // Drop the leading slash in the path. |
| params.SetString("gaiaPath", |
| GaiaUrls::GetInstance()->embedded_signin_url().path().substr(1)); |
| } |
| |
| params.SetString("service", "chromiumsync"); |
| params.SetString("continueUrl", |
| signin::GetLandingURL("source", static_cast<int>(source)).spec()); |
| |
| std::string default_email; |
| if (source != signin::SOURCE_AVATAR_BUBBLE_ADD_ACCOUNT) { |
| default_email = Profile::FromWebUI(web_ui())->GetPrefs()-> |
| GetString(prefs::kGoogleServicesLastUsername); |
| } else { |
| if (!net::GetValueForKeyInQuery(current_url, "email", &default_email)) |
| default_email.clear(); |
| } |
| if (!default_email.empty()) |
| params.SetString("email", default_email); |
| |
| std::string frame_url; |
| net::GetValueForKeyInQuery(current_url, "frameUrl", &frame_url); |
| if (!frame_url.empty()) |
| params.SetString("frameUrl", frame_url); |
| |
| std::string is_constrained; |
| net::GetValueForKeyInQuery(current_url, "constrained", &is_constrained); |
| if (!is_constrained.empty()) |
| params.SetString("constrained", is_constrained); |
| |
| // TODO(rogerta): this needs to be passed on to gaia somehow. |
| std::string read_only_email; |
| net::GetValueForKeyInQuery(current_url, "readOnlyEmail", &read_only_email); |
| if (!read_only_email.empty()) |
| params.SetString("readOnlyEmail", read_only_email); |
| |
| OneClickSigninHelper::LogHistogramValue( |
| source, one_click_signin::HISTOGRAM_SHOWN); |
| } |
| |
| void InlineLoginHandlerImpl::HandleSwitchToFullTabMessage( |
| const base::ListValue* args) { |
| base::string16 url_str; |
| CHECK(args->GetString(0, &url_str)); |
| |
| content::WebContents* web_contents = web_ui()->GetWebContents(); |
| GURL main_frame_url(web_contents->GetURL()); |
| main_frame_url = net::AppendOrReplaceQueryParameter( |
| main_frame_url, "frameUrl", base::UTF16ToASCII(url_str)); |
| chrome::NavigateParams params( |
| Profile::FromWebUI(web_ui()), |
| net::AppendOrReplaceQueryParameter(main_frame_url, "constrained", "0"), |
| content::PAGE_TRANSITION_AUTO_TOPLEVEL); |
| chrome::Navigate(¶ms); |
| |
| web_ui()->CallJavascriptFunction("inline.login.closeDialog"); |
| } |
| |
| void InlineLoginHandlerImpl::CompleteLogin(const base::ListValue* args) { |
| if (complete_login_triggered_) { |
| // Gaia is supposed to trigger CompleteLogin by sending a completelogin |
| // message to Chrome, since Gaia does not always do this, Chrome injects |
| // some code into the Gaia page to handle that. This may result in duplicate |
| // completelogin messages when Gaia does send the message. |
| // TODO(guohui): coordinate with Gaia team to only send the completeLogin |
| // message on Chrome versions that do not inject similar code into Gaia. |
| VLOG(1) << "InlineLoginHandlerImpl::CompleteLogin called more than once"; |
| return; |
| } |
| complete_login_triggered_ = true; |
| |
| content::WebContents* contents = web_ui()->GetWebContents(); |
| const GURL& current_url = contents->GetURL(); |
| |
| const base::DictionaryValue* dict = NULL; |
| args->GetDictionary(0, &dict); |
| |
| bool skip_for_now = false; |
| dict->GetBoolean("skipForNow", &skip_for_now); |
| if (skip_for_now) { |
| signin::SetUserSkippedPromo(Profile::FromWebUI(web_ui())); |
| SyncStarterCallback(OneClickSigninSyncStarter::SYNC_SETUP_FAILURE); |
| return; |
| } |
| |
| base::string16 email; |
| dict->GetString("email", &email); |
| DCHECK(!email.empty()); |
| email_ = base::UTF16ToASCII(email); |
| base::string16 password; |
| dict->GetString("password", &password); |
| password_ = base::UTF16ToASCII(password); |
| |
| // When doing a SAML sign in, this email check may result in a false |
| // positive. This happens when the user types one email address in the |
| // gaia sign in page, but signs in to a different account in the SAML sign in |
| // page. |
| std::string default_email; |
| std::string validate_email; |
| if (net::GetValueForKeyInQuery(current_url, "email", &default_email) && |
| net::GetValueForKeyInQuery(current_url, "validateEmail", |
| &validate_email) && |
| validate_email == "1") { |
| if (!gaia::AreEmailsSame(email_, default_email)) { |
| SyncStarterCallback(OneClickSigninSyncStarter::SYNC_SETUP_FAILURE); |
| return; |
| } |
| } |
| |
| base::string16 session_index; |
| dict->GetString("sessionIndex", &session_index); |
| session_index_ = base::UTF16ToASCII(session_index); |
| DCHECK(!session_index_.empty()); |
| dict->GetBoolean("chooseWhatToSync", &choose_what_to_sync_); |
| |
| signin::Source source = signin::GetSourceForPromoURL(current_url); |
| OneClickSigninHelper::LogHistogramValue( |
| source, one_click_signin::HISTOGRAM_ACCEPTED); |
| bool switch_to_advanced = |
| choose_what_to_sync_ && (source != signin::SOURCE_SETTINGS); |
| OneClickSigninHelper::LogHistogramValue( |
| source, |
| switch_to_advanced ? one_click_signin::HISTOGRAM_WITH_ADVANCED : |
| one_click_signin::HISTOGRAM_WITH_DEFAULTS); |
| |
| OneClickSigninHelper::CanOfferFor can_offer_for = |
| source == signin::SOURCE_AVATAR_BUBBLE_ADD_ACCOUNT ? |
| OneClickSigninHelper::CAN_OFFER_FOR_SECONDARY_ACCOUNT : |
| OneClickSigninHelper::CAN_OFFER_FOR_ALL; |
| std::string error_msg; |
| bool can_offer = OneClickSigninHelper::CanOffer( |
| contents, can_offer_for, email_, &error_msg); |
| if (!can_offer) { |
| HandleLoginError(error_msg); |
| return; |
| } |
| |
| content::StoragePartition* partition = |
| content::BrowserContext::GetStoragePartitionForSite( |
| contents->GetBrowserContext(), |
| GURL(chrome::kChromeUIChromeSigninURL)); |
| |
| // InlineSigninHelper will delete itself. |
| new InlineSigninHelper(GetWeakPtr(), partition->GetURLRequestContext(), |
| Profile::FromWebUI(web_ui()), current_url, |
| email_, password_, session_index_, |
| choose_what_to_sync_); |
| |
| email_.clear(); |
| password_.clear(); |
| session_index_.clear(); |
| web_ui()->CallJavascriptFunction("inline.login.closeDialog"); |
| } |
| |
| void InlineLoginHandlerImpl::HandleLoginError(const std::string& error_msg) { |
| SyncStarterCallback(OneClickSigninSyncStarter::SYNC_SETUP_FAILURE); |
| |
| Browser* browser = GetDesktopBrowser(); |
| if (browser && !error_msg.empty()) { |
| VLOG(1) << "InlineLoginHandlerImpl::HandleLoginError shows error message: " |
| << error_msg; |
| OneClickSigninHelper::ShowSigninErrorBubble(browser, error_msg); |
| } |
| |
| email_.clear(); |
| password_.clear(); |
| session_index_.clear(); |
| } |
| |
| Browser* InlineLoginHandlerImpl::GetDesktopBrowser() { |
| Browser* browser = chrome::FindBrowserWithWebContents( |
| web_ui()->GetWebContents()); |
| if (!browser) { |
| browser = chrome::FindLastActiveWithProfile( |
| Profile::FromWebUI(web_ui()), chrome::GetActiveDesktop()); |
| } |
| return browser; |
| } |
| |
| void InlineLoginHandlerImpl::SyncStarterCallback( |
| OneClickSigninSyncStarter::SyncSetupResult result) { |
| content::WebContents* contents = web_ui()->GetWebContents(); |
| |
| if (contents->GetController().GetPendingEntry()) { |
| // Do nothing if a navigation is pending, since this call can be triggered |
| // from DidStartLoading. This avoids deleting the pending entry while we are |
| // still navigating to it. See crbug/346632. |
| return; |
| } |
| |
| const GURL& current_url = contents->GetLastCommittedURL(); |
| signin::Source source = signin::GetSourceForPromoURL(current_url); |
| DCHECK(source != signin::SOURCE_UNKNOWN); |
| bool auto_close = signin::IsAutoCloseEnabledInURL(current_url); |
| |
| if (result == OneClickSigninSyncStarter::SYNC_SETUP_FAILURE) { |
| OneClickSigninHelper::RedirectToNtpOrAppsPage(contents, source); |
| } else if (auto_close) { |
| base::MessageLoop::current()->PostTask( |
| FROM_HERE, |
| base::Bind(&InlineLoginHandlerImpl::CloseTab, |
| weak_factory_.GetWeakPtr())); |
| } else { |
| OneClickSigninHelper::RedirectToNtpOrAppsPageIfNecessary(contents, source); |
| } |
| } |
| |
| void InlineLoginHandlerImpl::CloseTab() { |
| content::WebContents* tab = web_ui()->GetWebContents(); |
| Browser* browser = chrome::FindBrowserWithWebContents(tab); |
| if (browser) { |
| TabStripModel* tab_strip_model = browser->tab_strip_model(); |
| if (tab_strip_model) { |
| int index = tab_strip_model->GetIndexOfWebContents(tab); |
| if (index != TabStripModel::kNoTab) { |
| tab_strip_model->ExecuteContextMenuCommand( |
| index, TabStripModel::CommandCloseTab); |
| } |
| } |
| } |
| } |