| // Copyright 2014 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/chromeos/login/signin/oauth2_login_manager.h" |
| |
| #include <utility> |
| #include <vector> |
| |
| #include "base/command_line.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/signin/chrome_signin_client_factory.h" |
| #include "chrome/browser/signin/gaia_cookie_manager_service_factory.h" |
| #include "chrome/browser/signin/profile_oauth2_token_service_factory.h" |
| #include "chrome/browser/signin/signin_manager_factory.h" |
| #include "chromeos/chromeos_switches.h" |
| #include "components/account_id/account_id.h" |
| #include "components/signin/core/browser/profile_oauth2_token_service.h" |
| #include "components/signin/core/browser/signin_client.h" |
| #include "components/signin/core/browser/signin_manager.h" |
| #include "components/user_manager/user_manager.h" |
| #include "google_apis/gaia/gaia_auth_util.h" |
| #include "google_apis/gaia/gaia_urls.h" |
| #include "services/network/public/cpp/shared_url_loader_factory.h" |
| |
| namespace chromeos { |
| |
| OAuth2LoginManager::OAuth2LoginManager(Profile* user_profile) |
| : user_profile_(user_profile), |
| restore_strategy_(RESTORE_FROM_COOKIE_JAR), |
| state_(SESSION_RESTORE_NOT_STARTED) { |
| GetTokenService()->AddObserver(this); |
| |
| // For telemetry, we mark session restore completed to avoid warnings from |
| // MergeSessionThrottle. |
| if (base::CommandLine::ForCurrentProcess()->HasSwitch( |
| chromeos::switches::kDisableGaiaServices)) { |
| SetSessionRestoreState(SESSION_RESTORE_DONE); |
| } |
| } |
| |
| OAuth2LoginManager::~OAuth2LoginManager() {} |
| |
| void OAuth2LoginManager::AddObserver(OAuth2LoginManager::Observer* observer) { |
| observer_list_.AddObserver(observer); |
| } |
| |
| void OAuth2LoginManager::RemoveObserver( |
| OAuth2LoginManager::Observer* observer) { |
| observer_list_.RemoveObserver(observer); |
| } |
| |
| void OAuth2LoginManager::RestoreSession( |
| scoped_refptr<network::SharedURLLoaderFactory> auth_url_loader_factory, |
| SessionRestoreStrategy restore_strategy, |
| const std::string& oauth2_refresh_token, |
| const std::string& oauth2_access_token) { |
| DCHECK(user_profile_); |
| auth_url_loader_factory_ = auth_url_loader_factory; |
| restore_strategy_ = restore_strategy; |
| refresh_token_ = oauth2_refresh_token; |
| oauthlogin_access_token_ = oauth2_access_token; |
| session_restore_start_ = base::Time::Now(); |
| ContinueSessionRestore(); |
| } |
| |
| void OAuth2LoginManager::ContinueSessionRestore() { |
| SetSessionRestoreState(OAuth2LoginManager::SESSION_RESTORE_PREPARING); |
| if (restore_strategy_ == RESTORE_FROM_COOKIE_JAR) { |
| FetchOAuth2Tokens(); |
| return; |
| } |
| |
| // Save passed OAuth2 refresh token. |
| if (restore_strategy_ == RESTORE_FROM_PASSED_OAUTH2_REFRESH_TOKEN) { |
| DCHECK(!refresh_token_.empty()); |
| restore_strategy_ = RESTORE_FROM_SAVED_OAUTH2_REFRESH_TOKEN; |
| StoreOAuth2Token(); |
| return; |
| } |
| |
| DCHECK(restore_strategy_ == RESTORE_FROM_SAVED_OAUTH2_REFRESH_TOKEN); |
| RestoreSessionFromSavedTokens(); |
| } |
| |
| void OAuth2LoginManager::RestoreSessionFromSavedTokens() { |
| // Just return if there is a pending TokenService::LoadCredentials call. |
| // Session restore continues in OnRefreshTokenAvailable when the call |
| // finishes. |
| if (pending_token_service_load_) |
| return; |
| |
| ProfileOAuth2TokenService* token_service = GetTokenService(); |
| const std::string primary_account_id = GetPrimaryAccountId(); |
| if (token_service->RefreshTokenIsAvailable(primary_account_id)) { |
| VLOG(1) << "OAuth2 refresh token is already loaded."; |
| FireRefreshTokensLoaded(); |
| VerifySessionCookies(); |
| } else { |
| VLOG(1) << "Loading OAuth2 refresh token from database."; |
| |
| // Flag user with unknown token status in case there are no saved tokens |
| // and OnRefreshTokenAvailable is not called. Flagging it here would |
| // cause user to go through Gaia in next login to obtain a new refresh |
| // token. |
| user_manager::UserManager::Get()->SaveUserOAuthStatus( |
| AccountId::FromUserEmail(primary_account_id), |
| user_manager::User::OAUTH_TOKEN_STATUS_UNKNOWN); |
| |
| pending_token_service_load_ = true; |
| token_service->LoadCredentials(primary_account_id); |
| } |
| } |
| |
| void OAuth2LoginManager::Stop() { |
| oauth2_token_fetcher_.reset(); |
| login_verifier_.reset(); |
| } |
| |
| bool OAuth2LoginManager::SessionRestoreIsRunning() const { |
| return state_ == SESSION_RESTORE_PREPARING || |
| state_ == SESSION_RESTORE_IN_PROGRESS; |
| } |
| |
| bool OAuth2LoginManager::ShouldBlockTabLoading() const { |
| return SessionRestoreIsRunning(); |
| } |
| |
| void OAuth2LoginManager::OnRefreshTokenAvailable( |
| const std::string& user_email) { |
| VLOG(1) << "OnRefreshTokenAvailable"; |
| |
| if (state_ == SESSION_RESTORE_NOT_STARTED) |
| return; |
| |
| // TODO(fgorski): Once ProfileOAuth2TokenService supports multi-login, make |
| // sure to restore session cookies in the context of the correct user_email. |
| |
| // Do not validate tokens for supervised users, as they don't actually have |
| // oauth2 token. |
| if (user_manager::UserManager::Get()->IsLoggedInAsSupervisedUser()) { |
| VLOG(1) << "Logged in as supervised user, skip token validation."; |
| return; |
| } |
| // Only restore session cookies for the primary account in the profile. |
| if (GetPrimaryAccountId() == user_email) { |
| // The refresh token has changed, so stop any ongoing actions that were |
| // based on the old refresh token. |
| Stop(); |
| |
| // Token is loaded. Undo the flagging before token loading. |
| user_manager::UserManager::Get()->SaveUserOAuthStatus( |
| AccountId::FromUserEmail(user_email), |
| user_manager::User::OAUTH2_TOKEN_STATUS_VALID); |
| |
| pending_token_service_load_ = false; |
| VerifySessionCookies(); |
| } |
| } |
| |
| ProfileOAuth2TokenService* OAuth2LoginManager::GetTokenService() { |
| return ProfileOAuth2TokenServiceFactory::GetForProfile(user_profile_); |
| } |
| |
| std::string OAuth2LoginManager::GetPrimaryAccountId() { |
| SigninManagerBase* signin_manager = |
| SigninManagerFactory::GetForProfile(user_profile_); |
| const std::string primary_account_id = |
| signin_manager->GetAuthenticatedAccountId(); |
| LOG_IF(ERROR, primary_account_id.empty()) << "Primary account id is empty."; |
| return primary_account_id; |
| } |
| |
| void OAuth2LoginManager::StoreOAuth2Token() { |
| UpdateCredentials(GetPrimaryAccountId()); |
| } |
| |
| void OAuth2LoginManager::UpdateCredentials(const std::string& account_id) { |
| DCHECK(!account_id.empty()); |
| DCHECK(!refresh_token_.empty()); |
| // |account_id| is assumed to be already canonicalized if it's an email. |
| GetTokenService()->UpdateCredentials(account_id, refresh_token_); |
| FireRefreshTokensLoaded(); |
| |
| for (auto& observer : observer_list_) |
| observer.OnNewRefreshTokenAvaiable(user_profile_); |
| } |
| |
| void OAuth2LoginManager::FireRefreshTokensLoaded() { |
| // TODO(570218): Figure out the right way to plumb this. |
| GetTokenService()->LoadCredentials(std::string()); |
| } |
| |
| void OAuth2LoginManager::FetchOAuth2Tokens() { |
| DCHECK(auth_url_loader_factory_); |
| if (restore_strategy_ != RESTORE_FROM_COOKIE_JAR) { |
| NOTREACHED(); |
| SetSessionRestoreState(SESSION_RESTORE_FAILED); |
| return; |
| } |
| |
| // If we have authenticated cookie jar, get OAuth1 token first, then fetch |
| // SID/LSID cookies through OAuthLogin call. |
| SigninClient* signin_client = |
| ChromeSigninClientFactory::GetForProfile(user_profile_); |
| std::string signin_scoped_device_id = |
| signin_client->GetSigninScopedDeviceId(); |
| |
| oauth2_token_fetcher_ = |
| std::make_unique<OAuth2TokenFetcher>(this, auth_url_loader_factory_); |
| oauth2_token_fetcher_->StartExchangeFromCookies(std::string(), |
| signin_scoped_device_id); |
| } |
| |
| void OAuth2LoginManager::OnOAuth2TokensAvailable( |
| const GaiaAuthConsumer::ClientOAuthResult& oauth2_tokens) { |
| VLOG(1) << "OAuth2 tokens fetched"; |
| DCHECK(refresh_token_.empty()); |
| refresh_token_.assign(oauth2_tokens.refresh_token); |
| oauthlogin_access_token_ = oauth2_tokens.access_token; |
| StoreOAuth2Token(); |
| } |
| |
| void OAuth2LoginManager::OnOAuth2TokensFetchFailed() { |
| LOG(ERROR) << "OAuth2 tokens fetch failed!"; |
| RecordSessionRestoreOutcome(SESSION_RESTORE_TOKEN_FETCH_FAILED, |
| SESSION_RESTORE_FAILED); |
| } |
| |
| void OAuth2LoginManager::VerifySessionCookies() { |
| DCHECK(!login_verifier_.get()); |
| login_verifier_.reset(new OAuth2LoginVerifier( |
| this, GaiaCookieManagerServiceFactory::GetForProfile(user_profile_), |
| GetPrimaryAccountId(), oauthlogin_access_token_)); |
| |
| if (restore_strategy_ == RESTORE_FROM_SAVED_OAUTH2_REFRESH_TOKEN) { |
| login_verifier_->VerifyUserCookies(); |
| return; |
| } |
| |
| RestoreSessionCookies(); |
| } |
| |
| void OAuth2LoginManager::RestoreSessionCookies() { |
| SetSessionRestoreState(SESSION_RESTORE_IN_PROGRESS); |
| login_verifier_->VerifyProfileTokens(); |
| } |
| |
| void OAuth2LoginManager::Shutdown() { |
| GetTokenService()->RemoveObserver(this); |
| login_verifier_.reset(); |
| oauth2_token_fetcher_.reset(); |
| } |
| |
| void OAuth2LoginManager::OnSessionMergeSuccess() { |
| VLOG(1) << "OAuth2 refresh and/or GAIA token verification succeeded."; |
| RecordSessionRestoreOutcome(SESSION_RESTORE_SUCCESS, SESSION_RESTORE_DONE); |
| } |
| |
| void OAuth2LoginManager::OnSessionMergeFailure(bool connection_error) { |
| LOG(ERROR) << "OAuth2 refresh and GAIA token verification failed!" |
| << " connection_error: " << connection_error; |
| RecordSessionRestoreOutcome(SESSION_RESTORE_MERGE_SESSION_FAILED, |
| connection_error |
| ? SESSION_RESTORE_CONNECTION_FAILED |
| : SESSION_RESTORE_FAILED); |
| } |
| |
| void OAuth2LoginManager::OnListAccountsSuccess( |
| const std::vector<gaia::ListedAccount>& accounts) { |
| MergeVerificationOutcome outcome = POST_MERGE_SUCCESS; |
| // Let's analyze which accounts we see logged in here: |
| std::string user_email = gaia::CanonicalizeEmail(GetPrimaryAccountId()); |
| if (!accounts.empty()) { |
| bool found = false; |
| bool first = true; |
| for (std::vector<gaia::ListedAccount>::const_iterator iter = |
| accounts.begin(); |
| iter != accounts.end(); ++iter) { |
| if (iter->email == user_email) { |
| found = iter->valid; |
| break; |
| } |
| |
| first = false; |
| } |
| |
| if (!found) |
| outcome = POST_MERGE_MISSING_PRIMARY_ACCOUNT; |
| else if (!first) |
| outcome = POST_MERGE_PRIMARY_NOT_FIRST_ACCOUNT; |
| |
| } else { |
| outcome = POST_MERGE_NO_ACCOUNTS; |
| } |
| |
| bool is_pre_merge = (state_ == SESSION_RESTORE_PREPARING); |
| RecordCookiesCheckOutcome(is_pre_merge, outcome); |
| // If the primary account is missing during the initial cookie freshness |
| // check, try to restore GAIA session cookies form the OAuth2 tokens. |
| if (is_pre_merge) { |
| if (outcome != POST_MERGE_SUCCESS && |
| outcome != POST_MERGE_PRIMARY_NOT_FIRST_ACCOUNT) { |
| RestoreSessionCookies(); |
| } else { |
| // We are done with this account, it's GAIA cookies are legit. |
| RecordSessionRestoreOutcome(SESSION_RESTORE_NOT_NEEDED, |
| SESSION_RESTORE_DONE); |
| } |
| } |
| } |
| |
| void OAuth2LoginManager::OnListAccountsFailure(bool connection_error) { |
| bool is_pre_merge = (state_ == SESSION_RESTORE_PREPARING); |
| RecordCookiesCheckOutcome(is_pre_merge, connection_error |
| ? POST_MERGE_CONNECTION_FAILED |
| : POST_MERGE_VERIFICATION_FAILED); |
| if (is_pre_merge) { |
| if (!connection_error) { |
| // If we failed to get account list, our cookies might be stale so we |
| // need to attempt to restore them. |
| RestoreSessionCookies(); |
| } else { |
| RecordSessionRestoreOutcome(SESSION_RESTORE_LISTACCOUNTS_FAILED, |
| SESSION_RESTORE_CONNECTION_FAILED); |
| } |
| } |
| } |
| |
| void OAuth2LoginManager::RecordSessionRestoreOutcome( |
| SessionRestoreOutcome outcome, |
| OAuth2LoginManager::SessionRestoreState state) { |
| UMA_HISTOGRAM_ENUMERATION("OAuth2Login.SessionRestore", outcome, |
| SESSION_RESTORE_COUNT); |
| SetSessionRestoreState(state); |
| } |
| |
| // static |
| void OAuth2LoginManager::RecordCookiesCheckOutcome( |
| bool is_pre_merge, |
| MergeVerificationOutcome outcome) { |
| if (is_pre_merge) { |
| UMA_HISTOGRAM_ENUMERATION("OAuth2Login.PreMergeVerification", outcome, |
| POST_MERGE_COUNT); |
| } else { |
| UMA_HISTOGRAM_ENUMERATION("OAuth2Login.PostMergeVerification", outcome, |
| POST_MERGE_COUNT); |
| } |
| } |
| |
| void OAuth2LoginManager::SetSessionRestoreState( |
| OAuth2LoginManager::SessionRestoreState state) { |
| if (state_ == state) |
| return; |
| |
| state_ = state; |
| if (state == OAuth2LoginManager::SESSION_RESTORE_FAILED) { |
| UMA_HISTOGRAM_TIMES("OAuth2Login.SessionRestoreTimeToFailure", |
| base::Time::Now() - session_restore_start_); |
| } else if (state == OAuth2LoginManager::SESSION_RESTORE_DONE) { |
| UMA_HISTOGRAM_TIMES("OAuth2Login.SessionRestoreTimeToSuccess", |
| base::Time::Now() - session_restore_start_); |
| } |
| |
| for (auto& observer : observer_list_) |
| observer.OnSessionRestoreStateChanged(user_profile_, state_); |
| } |
| |
| void OAuth2LoginManager::SetSessionRestoreStartForTesting( |
| const base::Time& time) { |
| session_restore_start_ = time; |
| } |
| |
| } // namespace chromeos |