Chromium Code Reviews| Index: chrome/browser/chromeos/login/oauth_login_manager.cc |
| diff --git a/chrome/browser/chromeos/login/oauth_login_manager.cc b/chrome/browser/chromeos/login/oauth_login_manager.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..68e905a48c9e7a07a4ea6665269b0d92bca76067 |
| --- /dev/null |
| +++ b/chrome/browser/chromeos/login/oauth_login_manager.cc |
| @@ -0,0 +1,483 @@ |
| +// Copyright (c) 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/chromeos/login/oauth_login_manager.h" |
| + |
| +#include "base/command_line.h" |
| +#include "base/metrics/histogram.h" |
| +#include "base/string_util.h" |
| +#include "chrome/browser/browser_process.h" |
| +#include "chrome/browser/chromeos/login/user_manager.h" |
| +#include "chrome/browser/policy/browser_policy_connector.h" |
| +#include "chrome/browser/prefs/pref_service.h" |
| +#include "chrome/browser/profiles/profile.h" |
| +#include "chrome/browser/signin/token_service.h" |
| +#include "chrome/browser/signin/token_service_factory.h" |
| +#include "chrome/common/chrome_notification_types.h" |
| +#include "chrome/common/chrome_switches.h" |
| +#include "chrome/common/pref_names.h" |
| +#include "content/public/browser/notification_observer.h" |
|
Joao da Silva
2013/01/11 16:45:07
Already in .h
zel
2013/01/11 19:51:16
Done.
|
| +#include "content/public/browser/notification_service.h" |
| +#include "google_apis/gaia/gaia_auth_fetcher.h" |
| +#include "google_apis/gaia/gaia_constants.h" |
| +#include "net/url_request/url_request_context_getter.h" |
| + |
| +using content::BrowserThread; |
| + |
| +namespace { |
| + |
| +// OAuth token verification max retry count. |
| +const int kMaxCookieRecoveryAttemptCount = 5; |
| +// OAuth token verification retry delay in milliseconds. |
| +const int kCookieRecoveryRestartDelay = 10000; |
|
Joao da Silva
2013/01/11 16:45:07
These 2 aren't used.
zel
2013/01/11 19:51:16
Done.
|
| + |
| +} // namespace |
| + |
| + |
| +namespace chromeos { |
| + |
| +// OAuthLoginManagerFactory. |
| +OAuthLoginManager* OAuthLoginManagerFactory::Create( |
| + OAuthLoginManager::Delegate* delegate) { |
| + if (CommandLine::ForCurrentProcess()->HasSwitch(::switches::kForceOAuth2)) |
| + return new OAuth2LoginManager(delegate); |
| + |
| + return new OAuth1LoginManager(delegate); |
| +} |
| + |
| +// OAuthLoginManager. |
| + |
| +void OAuthLoginManager::CompleteAuthentication() { |
| + delegate_->OnCompletedAuthentication(user_profile_); |
| + TokenService* token_service = |
| + TokenServiceFactory::GetForProfile(user_profile_); |
| + if (token_service->AreCredentialsValid()) |
| + token_service->StartFetchingTokens(); |
| +} |
| + |
| +OAuthLoginManager::OAuthLoginManager(Delegate* delegate) |
| + : delegate_(delegate), |
| + user_profile_(NULL), |
| + restore_from_auth_cookies_(false), |
| + state_(SESSION_RESTORE_NOT_STARTED) { |
| +} |
| + |
| +// OAuth2LoginManager. |
| + |
| +OAuth2LoginManager::OAuth2LoginManager(OAuthLoginManager::Delegate* delegate) |
| + : OAuthLoginManager(delegate), |
| + loading_reported_(false), |
| + restore_attempt_count_(0) { |
| +} |
| + |
| +void OAuth2LoginManager::RestoreSession( |
| + Profile* user_profile, |
| + net::URLRequestContextGetter* auth_request_context, |
| + bool restore_from_auth_cookies) { |
| + // TODO(zelidrag): Remove eventualy the next line in some future milestone. |
| + RemoveLegacyTokens(); |
| + |
| + user_profile_ = user_profile; |
| + auth_request_context_ = auth_request_context; |
| + state_ = OAuthLoginManager::SESSION_RESTORE_IN_PROGRESS; |
| + |
| + // Reuse the access token fetched by the OAuth2PolicyFetcher, if it was |
| + // used to fetch policies before Profile creation. |
| + if (oauth2_policy_fetcher_.get() && |
| + oauth2_policy_fetcher_->has_oauth2_tokens()) { |
| + VLOG(1) << "Resuming profile creation after fetching policy token"; |
| + refresh_token_ = oauth2_policy_fetcher_->oauth2_tokens().refresh_token; |
| + restore_from_auth_cookies = false; |
| + } |
| + restore_from_auth_cookies_ = restore_from_auth_cookies; |
| + ContinueSessionRestore(); |
| +} |
| + |
| +void OAuth2LoginManager::ContinueSessionRestore() { |
| + if (restore_from_auth_cookies_) { |
| + FetchOAuth2Tokens(); |
| + return; |
| + } |
| + |
| + // Did we already fetch the refresh token (either policy or db)? |
| + if (!refresh_token_.empty()) { |
| + // TODO(zelidrag): Figure out where to stick that refresh_token_ into. |
| + // We probalby need bit more than that. |
| + } |
| + LoadAndVerifyOAuth2Tokens(); |
| +} |
| + |
| + |
| +void OAuth2LoginManager::RestorePolicyTokens( |
| + net::URLRequestContextGetter* auth_request_context) { |
| + oauth2_policy_fetcher_.reset( |
| + new OAuth2PolicyFetcher(auth_request_context, |
| + g_browser_process->system_request_context())); |
| + oauth2_policy_fetcher_->Start(); |
| +} |
| + |
| +void OAuth2LoginManager::Stop() { |
| + oauth2_token_fetcher_.reset(); |
| + login_verifier_.reset(); |
| +} |
| + |
| +TokenService* OAuth2LoginManager::SetupTokenService() { |
| + DCHECK(registrar_.IsEmpty()); |
| + TokenService* token_service = |
| + TokenServiceFactory::GetForProfile(user_profile_); |
| + registrar_.Add(this, |
| + chrome::NOTIFICATION_TOKEN_LOADING_FINISHED, |
| + content::Source<TokenService>(token_service)); |
| + registrar_.Add(this, |
| + chrome::NOTIFICATION_TOKEN_AVAILABLE, |
| + content::Source<TokenService>(token_service)); |
| + registrar_.Add(this, |
| + chrome::NOTIFICATION_TOKEN_REQUEST_FAILED, |
| + content::Source<TokenService>(token_service)); |
| + return token_service; |
| +} |
| + |
| +void OAuth2LoginManager::RemoveLegacyTokens() { |
| + PrefServiceSyncable* prefs = user_profile_->GetPrefs(); |
| + prefs->RegisterStringPref(prefs::kOAuth1Token, |
| + "", |
| + PrefServiceSyncable::UNSYNCABLE_PREF); |
| + prefs->RegisterStringPref(prefs::kOAuth1Secret, |
| + "", |
| + PrefServiceSyncable::UNSYNCABLE_PREF); |
| + prefs->ClearPref(prefs::kOAuth1Token); |
| + prefs->ClearPref(prefs::kOAuth1Secret); |
| + prefs->UnregisterPreference(prefs::kOAuth1Token); |
| + prefs->UnregisterPreference(prefs::kOAuth1Secret); |
| +} |
| + |
| +void OAuth2LoginManager::LoadAndVerifyOAuth2Tokens() { |
| + // If we have no cookies, try to load saved OAuth2 token from TokenService. |
| + TokenService* token_service = SetupTokenService(); |
| + token_service->Initialize(GaiaConstants::kChromeSource, user_profile_); |
| + token_service->LoadTokensFromDB(); |
| +} |
| + |
| +void OAuth2LoginManager::FetchOAuth2Tokens() { |
| + DCHECK(auth_request_context_.get()); |
| + // If we have authenticated cookie jar, get OAuth1 token first, then fetch |
| + // SID/LSID cookies through OAuthLogin call. |
| + oauth2_token_fetcher_.reset( |
| + new OAuth2TokenFetcher(this, auth_request_context_)); |
| + oauth2_token_fetcher_->Start(); |
| +} |
| + |
| +void OAuth2LoginManager::OnOAuth2TokenAvailable( |
| + const GaiaAuthConsumer::ClientLoginResult& gaia_credentials, |
| + const GaiaAuthConsumer::ClientOAuthResult& oauth2_tokens) { |
| + TokenService* token_service = SetupTokenService(); |
| + token_service->UpdateCredentialsWithOAuth2(oauth2_tokens); |
| + token_service->UpdateCredentials(gaia_credentials); |
| + CompleteAuthentication(); |
| +} |
| + |
| +void OAuth2LoginManager::OnOAuth2TokenFetchFailed() { |
| + state_ = OAuthLoginManager::SESSION_RESTORE_DONE; |
| + UserManager::Get()->SaveUserOAuthStatus( |
| + UserManager::Get()->GetLoggedInUser()->email(), |
| + User::OAUTH2_TOKEN_STATUS_INVALID); |
| +} |
| + |
| +void OAuth2LoginManager::Observe( |
| + int type, |
| + const content::NotificationSource& source, |
| + const content::NotificationDetails& details) OVERRIDE { |
| + TokenService* token_service = |
| + TokenServiceFactory::GetForProfile(user_profile_); |
| + switch (type) { |
| + case chrome::NOTIFICATION_TOKEN_LOADING_FINISHED: { |
| + refresh_token_ = token_service->GetOAuth2LoginRefreshToken(); |
| + ReportOAuth2TokensLoaded(); |
| + // Have we started restoring GAIA auth cookies yet? |
| + if (!refresh_token_.empty() && !login_verifier_.get()) |
| + RestoreSessionCookies(); |
| + |
| + break; |
| + } |
| + case chrome::NOTIFICATION_TOKEN_REQUEST_FAILED: { |
| + // TODO(zelidrag): Figure out how to recover from transient errors with |
| + // TokenService class - similar to what we do in RetryOnError() here. |
| + TokenService::TokenAvailableDetails* token_details = |
| + content::Details<TokenService::TokenAvailableDetails>( |
| + details).ptr(); |
| + if (token_details->service() == |
| + GaiaConstants::kGaiaOAuth2LoginRefreshToken) { |
| + UserManager::Get()->SaveUserOAuthStatus( |
| + UserManager::Get()->GetLoggedInUser()->email(), |
| + User::OAUTH2_TOKEN_STATUS_INVALID); |
| + } |
| + break; |
| + } |
| + case chrome::NOTIFICATION_TOKEN_AVAILABLE: { |
| + TokenService::TokenAvailableDetails* token_details = |
| + content::Details<TokenService::TokenAvailableDetails>( |
| + details).ptr(); |
| + if (token_details->service() == |
| + GaiaConstants::kGaiaOAuth2LoginRefreshToken) { |
| + DCHECK(!login_verifier_.get()); |
| + refresh_token_ = token_details->token(); |
| + RestoreSessionCookies(); |
| + } |
| + |
| + break; |
| + } |
| + default: |
| + NOTREACHED(); |
| + break; |
| + } |
| +} |
| + |
| +void OAuth2LoginManager::RestoreSessionCookies() { |
| + VLOG(1) << "Fetched refresh token!"; |
| + DCHECK(!refresh_token_.empty()); |
| + if (!login_verifier_.get()) { |
| + login_verifier_.reset( |
| + new OAuth2LoginVerifier(this, |
| + g_browser_process->system_request_context(), |
| + user_profile_->GetRequestContext())); |
| + } |
| + login_verifier_->VerifyRefreshToken(refresh_token_); |
| +} |
| + |
| +void OAuth2LoginManager::FetchPolicyTokens() { |
|
Joao da Silva
2013/01/11 16:45:07
This is never used. I think it should be called fr
zel
2013/01/11 19:51:16
It's actually just needed in RestoreSessionCookies
|
| + if (!oauth2_policy_fetcher_.get() || oauth2_policy_fetcher_->failed()) { |
| + // Trigger OAuth2 token fetch for user policy. |
| + oauth2_policy_fetcher_.reset( |
| + new OAuth2PolicyFetcher(g_browser_process->system_request_context(), |
| + refresh_token_)); |
| + oauth2_policy_fetcher_->Start(); |
| + } |
| +} |
| + |
| +void OAuth2LoginManager::OnOAuth2LoginVerifierSuccess( |
| + const std::string& sid, |
| + const std::string& lsid, |
| + const std::string& auth) { |
| + state_ = OAuthLoginManager::SESSION_RESTORE_DONE; |
| + UserManager::Get()->SaveUserOAuthStatus( |
| + UserManager::Get()->GetLoggedInUser()->email(), |
| + User::OAUTH2_TOKEN_STATUS_VALID); |
| +} |
| + |
| +void OAuth2LoginManager::OnOAuth2LoginVerifierFaulure() { |
|
Joao da Silva
2013/01/11 16:45:07
*Failure
zel
2013/01/11 19:51:16
Done.
|
| + state_ = OAuthLoginManager::SESSION_RESTORE_DONE; |
| + UserManager::Get()->SaveUserOAuthStatus( |
| + UserManager::Get()->GetLoggedInUser()->email(), |
| + User::OAUTH2_TOKEN_STATUS_INVALID); |
| +} |
| + |
| +void OAuth2LoginManager::ReportOAuth2TokensLoaded() { |
| + VLOG(1) << "Got OAuth2 refresh token!"; |
| + DCHECK(!loading_reported_); |
| + loading_reported_ = true; |
| + if (refresh_token_.empty()) { |
| + state_ = OAuthLoginManager::SESSION_RESTORE_DONE; |
| + UserManager::Get()->SaveUserOAuthStatus( |
| + UserManager::Get()->GetLoggedInUser()->email(), |
| + User::OAUTH2_TOKEN_STATUS_INVALID); |
| + } |
| +} |
| + |
| +// OAuth1LoginManager. |
| +// TODO(zelidrag): Nuke this one once we switch everything to OAuth2. |
| + |
| +OAuth1LoginManager::OAuth1LoginManager( |
| + OAuthLoginManager::Delegate* delegate) |
| + : OAuthLoginManager(delegate) { |
| +} |
| + |
| +void OAuth1LoginManager::RestoreSession( |
| + Profile* user_profile, |
| + net::URLRequestContextGetter* auth_request_context, |
| + bool restore_from_auth_cookies) { |
| + user_profile_ = user_profile; |
| + auth_request_context_ = auth_request_context; |
| + state_ = OAuthLoginManager::SESSION_RESTORE_IN_PROGRESS; |
| + |
| + // Reuse the access token fetched by the PolicyOAuthFetcher, if it was |
| + // used to fetch policies before Profile creation. |
| + if (policy_oauth_fetcher_.get() && |
| + !policy_oauth_fetcher_->oauth1_token().empty()) { |
| + VLOG(1) << "Resuming profile creation after fetching policy token"; |
| + oauth1_token_ = policy_oauth_fetcher_->oauth1_token(); |
| + oauth1_secret_ = policy_oauth_fetcher_->oauth1_secret(); |
| + StoreOAuth1Tokens(); |
| + restore_from_auth_cookies = false; |
| + } |
| + restore_from_auth_cookies_ = restore_from_auth_cookies; |
| + ContinueSessionRestore(); |
| +} |
| + |
| +void OAuth1LoginManager::ContinueSessionRestore() { |
| + // Have we even started session restore? |
| + if (!user_profile_) |
| + return; |
| + |
| + if (restore_from_auth_cookies_) { |
| + DCHECK(auth_request_context_.get()); |
| + // If we don't have it, fetch OAuth1 access token. |
| + // Once we get that, we will kick off individual requests for OAuth2 |
| + // tokens for all our services. |
| + // Use off-the-record profile that was used for this step. It should |
| + // already contain all needed cookies that will let us skip GAIA's user |
| + // authentication UI. |
| + oauth1_token_fetcher_.reset( |
| + new OAuth1TokenFetcher(this, auth_request_context_)); |
| + oauth1_token_fetcher_->Start(); |
| + } else if (ReadOAuth1Tokens()) { |
| + // Verify OAuth access token when we find it in the profile and always if |
| + // if we don't have cookies. |
| + // TODO(xiyuan): Change back to use authenticator to verify token when |
| + // we support Gaia in lock screen. |
| + VerifyOAuth1AccessToken(); |
| + } else { |
| + UserManager::Get()->SaveUserOAuthStatus( |
| + UserManager::Get()->GetLoggedInUser()->email(), |
| + User::OAUTH1_TOKEN_STATUS_INVALID); |
| + } |
| +} |
| + |
| +void OAuth1LoginManager::RestorePolicyTokens( |
| + net::URLRequestContextGetter* auth_request_context) { |
| + DCHECK(!policy_oauth_fetcher_.get()); |
| + policy_oauth_fetcher_.reset( |
| + new PolicyOAuthFetcher(auth_request_context)); |
| + policy_oauth_fetcher_->Start(); |
| +} |
| + |
| +void OAuth1LoginManager::Stop() { |
| + oauth1_token_fetcher_.reset(); |
| + oauth1_login_verifier_.reset(); |
| + state_ = OAuthLoginManager::SESSION_RESTORE_NOT_STARTED; |
| +} |
| + |
| +void OAuth1LoginManager::VerifyOAuth1AccessToken() { |
| + // Kick off verification of OAuth1 access token (via OAuthLogin), this should |
| + // let us fetch credentials that will be used to initialize sync engine. |
| + FetchCredentialsWithOAuth1(); |
| + delegate_->OnFoundStoredTokens(); |
| + FetchPolicyTokens(); |
| +} |
| + |
| +void OAuth1LoginManager::FetchPolicyTokens() { |
| + if (!policy_oauth_fetcher_.get() || policy_oauth_fetcher_->failed()) { |
| + // Trigger oauth token fetch for user policy. |
| + policy_oauth_fetcher_.reset( |
| + new PolicyOAuthFetcher(g_browser_process->system_request_context(), |
| + oauth1_token_, |
| + oauth1_secret_)); |
| + policy_oauth_fetcher_->Start(); |
| + } |
| +} |
| + |
| +void OAuth1LoginManager::FetchCredentialsWithOAuth1() { |
| + oauth1_login_verifier_.reset( |
| + new OAuth1LoginVerifier(this, |
| + user_profile_->GetRequestContext(), |
| + oauth1_token_, |
| + oauth1_secret_, |
| + UserManager::Get()->GetLoggedInUser()->email())); |
| + oauth1_login_verifier_->StartOAuthVerification(); |
| +} |
| + |
| +void OAuth1LoginManager::OnOAuth1AccessTokenAvailable( |
| + const std::string& token, |
| + const std::string& secret) { |
| + oauth1_token_ = token; |
| + oauth1_secret_ = secret; |
| + StoreOAuth1Tokens(); |
| + // Verify OAuth1 token by doing OAuthLogin and fetching credentials. If we |
| + // have just transfered auth cookies out of authenticated cookie jar, there |
| + // is no need to try to mint them from OAuth token again. |
| + VerifyOAuth1AccessToken(); |
| +} |
| + |
| +void OAuth1LoginManager::OnOAuth1AccessTokenFetchFailed() { |
| + // TODO(kochi): Show failure notification UI here? |
| + LOG(ERROR) << "Failed to fetch OAuth1 access token."; |
| + state_ = OAuthLoginManager::SESSION_RESTORE_DONE; |
| + g_browser_process->browser_policy_connector()->RegisterForUserPolicy( |
| + EmptyString()); |
| +} |
| + |
| +void OAuth1LoginManager::OnOAuth1VerificationSucceeded( |
| + const std::string& user_name, const std::string& sid, |
| + const std::string& lsid, const std::string& auth) { |
| + // Kick off sync engine. |
| + GaiaAuthConsumer::ClientLoginResult credentials(sid, lsid, auth, |
| + std::string()); |
| + TokenService* token_service = |
| + TokenServiceFactory::GetForProfile(user_profile_); |
| + token_service->UpdateCredentials(credentials); |
| + CompleteAuthentication(); |
| +} |
| + |
| +void OAuth1LoginManager::OnOAuth1VerificationFailed( |
| + const std::string& user_name) { |
| + state_ = OAuthLoginManager::SESSION_RESTORE_DONE; |
| + UserManager::Get()->SaveUserOAuthStatus(user_name, |
| + User::OAUTH1_TOKEN_STATUS_INVALID); |
| +} |
| + |
| +bool OAuth1LoginManager::ReadOAuth1Tokens() { |
| + // Skip reading oauth token if user does not have a valid status. |
| + if (UserManager::Get()->IsUserLoggedIn() && |
| + UserManager::Get()->GetLoggedInUser()->oauth_token_status() != |
| + User::OAUTH1_TOKEN_STATUS_VALID) { |
| + return false; |
| + } |
| + |
| + PrefService* pref_service = user_profile_->GetPrefs(); |
| + std::string encoded_token = pref_service->GetString(prefs::kOAuth1Token); |
| + std::string encoded_secret = pref_service->GetString(prefs::kOAuth1Secret); |
| + if (!encoded_token.length() || !encoded_secret.length()) |
| + return false; |
| + |
| + std::string decoded_token = |
| + CrosLibrary::Get()->GetCertLibrary()->DecryptToken(encoded_token); |
| + std::string decoded_secret = |
| + CrosLibrary::Get()->GetCertLibrary()->DecryptToken(encoded_secret); |
| + |
| + if (!decoded_token.length() || !decoded_secret.length()) |
| + return false; |
| + |
| + oauth1_token_ = decoded_token; |
| + oauth1_secret_ = decoded_secret; |
| + return true; |
| +} |
| + |
| +void OAuth1LoginManager::StoreOAuth1Tokens() { |
| + DCHECK(!oauth1_token_.empty()); |
| + DCHECK(!oauth1_secret_.empty()); |
| + // First store OAuth1 token + service for the current user profile... |
| + std::string encrypted_token = |
| + CrosLibrary::Get()->GetCertLibrary()->EncryptToken(oauth1_token_); |
| + std::string encrypted_secret = |
| + CrosLibrary::Get()->GetCertLibrary()->EncryptToken(oauth1_secret_); |
| + |
| + PrefService* pref_service = user_profile_->GetPrefs(); |
| + User* user = UserManager::Get()->GetLoggedInUser(); |
| + if (!encrypted_token.empty() && !encrypted_secret.empty()) { |
| + pref_service->SetString(prefs::kOAuth1Token, encrypted_token); |
| + pref_service->SetString(prefs::kOAuth1Secret, encrypted_secret); |
| + |
| + // ...then record the presence of valid OAuth token for this account in |
| + // local state as well. |
| + UserManager::Get()->SaveUserOAuthStatus( |
| + user->email(), User::OAUTH1_TOKEN_STATUS_VALID); |
| + } else { |
| + LOG(WARNING) << "Failed to get OAuth1 token/secret encrypted."; |
| + // Set the OAuth status invalid so that the user will go through full |
| + // GAIA login next time. |
| + UserManager::Get()->SaveUserOAuthStatus( |
| + user->email(), User::OAUTH1_TOKEN_STATUS_INVALID); |
| + } |
| +} |
| + |
| +} // namespace chromeos |