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.
5 #include "chrome/browser/signin/profile_oauth2_token_service.h"
8 #include "base/message_loop/message_loop.h"
9 #include "base/stl_util.h"
10 #include "base/time/time.h"
11 #include "chrome/browser/chrome_notification_types.h"
12 #include "chrome/browser/profiles/profile.h"
13 #include "chrome/browser/signin/signin_global_error.h"
14 #include "chrome/browser/signin/signin_manager.h"
15 #include "chrome/browser/signin/signin_manager_factory.h"
16 #include "chrome/browser/signin/token_service.h"
17 #include "chrome/browser/signin/token_service_factory.h"
18 #include "chrome/browser/ui/global_error/global_error_service.h"
19 #include "chrome/browser/ui/global_error/global_error_service_factory.h"
20 #include "chrome/browser/webdata/token_web_data.h"
21 #include "content/public/browser/browser_thread.h"
22 #include "content/public/browser/notification_details.h"
23 #include "content/public/browser/notification_source.h"
24 #include "google_apis/gaia/gaia_constants.h"
25 #include "google_apis/gaia/google_service_auth_error.h"
26 #include "net/url_request/url_request_context_getter.h"
30 const char kAccountIdPrefix[] = "AccountId-";
31 const size_t kAccountIdPrefixLength = 10;
33 bool IsLegacyServiceId(const std::string& account_id) {
34 return account_id.compare(0u, kAccountIdPrefixLength, kAccountIdPrefix) != 0;
37 bool IsLegacyRefreshTokenId(const std::string& service_id) {
38 return service_id == GaiaConstants::kGaiaOAuth2LoginRefreshToken;
41 std::string ApplyAccountIdPrefix(const std::string& account_id) {
42 return kAccountIdPrefix + account_id;
45 std::string RemoveAccountIdPrefix(const std::string& prefixed_account_id) {
46 return prefixed_account_id.substr(kAccountIdPrefixLength);
52 ProfileOAuth2TokenService::AccountInfo::AccountInfo(
53 ProfileOAuth2TokenService* token_service,
54 const std::string& account_id,
55 const std::string& refresh_token)
56 : token_service_(token_service),
57 account_id_(account_id),
58 refresh_token_(refresh_token),
59 last_auth_error_(GoogleServiceAuthError::NONE) {
60 DCHECK(token_service_);
61 DCHECK(!account_id_.empty());
62 token_service_->signin_global_error()->AddProvider(this);
65 ProfileOAuth2TokenService::AccountInfo::~AccountInfo() {
66 token_service_->signin_global_error()->RemoveProvider(this);
69 void ProfileOAuth2TokenService::AccountInfo::SetLastAuthError(
70 const GoogleServiceAuthError& error) {
71 if (error.state() != last_auth_error_.state()) {
72 last_auth_error_ = error;
73 token_service_->signin_global_error()->AuthStatusChanged();
77 std::string ProfileOAuth2TokenService::AccountInfo::GetAccountId() const {
81 GoogleServiceAuthError
82 ProfileOAuth2TokenService::AccountInfo::GetAuthStatus() const {
83 return last_auth_error_;
86 ProfileOAuth2TokenService::ProfileOAuth2TokenService()
88 web_data_service_request_(0) {
91 ProfileOAuth2TokenService::~ProfileOAuth2TokenService() {
92 DCHECK(!signin_global_error_.get()) <<
93 "ProfileOAuth2TokenService::Initialize called but not "
94 "ProfileOAuth2TokenService::Shutdown";
97 void ProfileOAuth2TokenService::Initialize(Profile* profile) {
102 signin_global_error_.reset(new SigninGlobalError(profile));
103 GlobalErrorServiceFactory::GetForProfile(profile_)->AddGlobalError(
104 signin_global_error_.get());
106 content::Source<TokenService> token_service_source(
107 TokenServiceFactory::GetForProfile(profile));
109 chrome::NOTIFICATION_TOKENS_CLEARED,
110 token_service_source);
112 chrome::NOTIFICATION_TOKEN_AVAILABLE,
113 token_service_source);
115 chrome::NOTIFICATION_TOKEN_LOADING_FINISHED,
116 token_service_source);
119 void ProfileOAuth2TokenService::Shutdown() {
120 DCHECK(profile_) << "Shutdown() called without matching call to Initialize()";
122 refresh_tokens_.clear();
123 GlobalErrorServiceFactory::GetForProfile(profile_)->RemoveGlobalError(
124 signin_global_error_.get());
125 signin_global_error_.reset();
128 std::string ProfileOAuth2TokenService::GetRefreshToken(
129 const std::string& account_id) {
130 AccountInfoMap::const_iterator iter = refresh_tokens_.find(account_id);
131 if (iter != refresh_tokens_.end())
132 return iter->second->refresh_token();
133 return std::string();
136 net::URLRequestContextGetter* ProfileOAuth2TokenService::GetRequestContext() {
137 return profile_->GetRequestContext();
140 void ProfileOAuth2TokenService::UpdateAuthError(
141 const std::string& account_id,
142 const GoogleServiceAuthError& error) {
143 // Do not report connection errors as these are not actually auth errors.
144 // We also want to avoid masking a "real" auth error just because we
145 // subsequently get a transient network error.
146 if (error.state() == GoogleServiceAuthError::CONNECTION_FAILED)
149 DCHECK_GT(refresh_tokens_.count(account_id), 0u);
150 refresh_tokens_[account_id]->SetLastAuthError(error);
153 void ProfileOAuth2TokenService::Observe(
155 const content::NotificationSource& source,
156 const content::NotificationDetails& details) {
157 const std::string& account_id = GetPrimaryAccountId();
159 case chrome::NOTIFICATION_TOKEN_AVAILABLE: {
160 TokenService::TokenAvailableDetails* tok_details =
161 content::Details<TokenService::TokenAvailableDetails>(details).ptr();
162 if (tok_details->service() ==
163 GaiaConstants::kGaiaOAuth2LoginRefreshToken) {
164 // TODO(fgorski): Work on removing this code altogether in favor of the
165 // upgrade steps invoked by Initialize.
166 // TODO(fgorski): Refresh token received that way is not persisted in
168 CancelRequestsForAccount(account_id);
169 ClearCacheForAccount(account_id);
170 refresh_tokens_[account_id].reset(
171 new AccountInfo(this, account_id, tok_details->token()));
172 UpdateAuthError(account_id, GoogleServiceAuthError::AuthErrorNone());
173 FireRefreshTokenAvailable(account_id);
177 case chrome::NOTIFICATION_TOKENS_CLEARED: {
180 if (!account_id.empty())
181 UpdateAuthError(account_id, GoogleServiceAuthError::AuthErrorNone());
184 case chrome::NOTIFICATION_TOKEN_LOADING_FINISHED:
185 // During startup, if the user is signed in and the OAuth2 refresh token
186 // is empty, flag it as an error by badging the menu. Otherwise, if the
187 // user goes on to set up sync, they will have to make two attempts:
188 // One to surface the OAuth2 error, and a second one after signing in.
189 // See crbug.com/276650.
191 // If |account_id| is not empty, make sure that we have an entry in the
192 // map for it. The entry could be missing if there is a corruption in
193 // the token DB while this profile is connected to an account.
194 if (!account_id.empty() && refresh_tokens_.count(account_id) == 0) {
195 refresh_tokens_[account_id].reset(new AccountInfo(
196 this, account_id, std::string()));
199 if (!account_id.empty() && !RefreshTokenIsAvailable(account_id)) {
200 UpdateAuthError(account_id, GoogleServiceAuthError(
201 GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS));
203 FireRefreshTokensLoaded();
206 NOTREACHED() << "Invalid notification type=" << type;
211 std::string ProfileOAuth2TokenService::GetPrimaryAccountId() {
212 if (profile_->IsManaged())
213 return std::string("SupervisedUser");
215 SigninManagerBase* signin_manager =
216 SigninManagerFactory::GetForProfileIfExists(profile_);
217 // TODO(fgorski): DCHECK(signin_manager) here - it may require update to test
218 // code and the line above (SigninManager might not exist yet).
219 return signin_manager ? signin_manager->GetAuthenticatedUsername()
223 std::vector<std::string> ProfileOAuth2TokenService::GetAccounts() {
224 std::vector<std::string> account_ids;
225 for (AccountInfoMap::const_iterator iter = refresh_tokens_.begin();
226 iter != refresh_tokens_.end(); ++iter) {
227 account_ids.push_back(iter->first);
232 void ProfileOAuth2TokenService::UpdateCredentials(
233 const std::string& account_id,
234 const std::string& refresh_token) {
235 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
236 DCHECK(!account_id.empty());
237 DCHECK(!refresh_token.empty());
239 bool refresh_token_present = refresh_tokens_.count(account_id) > 0;
240 if (!refresh_token_present ||
241 refresh_tokens_[account_id]->refresh_token() != refresh_token) {
242 // If token present, and different from the new one, cancel its requests,
243 // and clear the entries in cache related to that account.
244 if (refresh_token_present) {
245 CancelRequestsForAccount(account_id);
246 ClearCacheForAccount(account_id);
247 refresh_tokens_[account_id]->set_refresh_token(refresh_token);
249 refresh_tokens_[account_id].reset(
250 new AccountInfo(this, account_id, refresh_token));
253 // Save the token in memory and in persistent store.
254 PersistCredentials(account_id, refresh_token);
256 UpdateAuthError(account_id, GoogleServiceAuthError::AuthErrorNone());
257 FireRefreshTokenAvailable(account_id);
258 // TODO(fgorski): Notify diagnostic observers.
262 void ProfileOAuth2TokenService::RevokeCredentials(
263 const std::string& account_id) {
264 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
266 if (refresh_tokens_.count(account_id) > 0) {
267 CancelRequestsForAccount(account_id);
268 ClearCacheForAccount(account_id);
269 refresh_tokens_.erase(account_id);
270 ClearPersistedCredentials(account_id);
271 FireRefreshTokenRevoked(account_id);
273 // TODO(fgorski): Notify diagnostic observers.
277 void ProfileOAuth2TokenService::PersistCredentials(
278 const std::string& account_id,
279 const std::string& refresh_token) {
280 scoped_refptr<TokenWebData> token_web_data =
281 TokenWebData::FromBrowserContext(profile_);
282 if (token_web_data.get()) {
283 token_web_data->SetTokenForService(ApplyAccountIdPrefix(account_id),
288 void ProfileOAuth2TokenService::ClearPersistedCredentials(
289 const std::string& account_id) {
290 scoped_refptr<TokenWebData> token_web_data =
291 TokenWebData::FromBrowserContext(profile_);
292 if (token_web_data.get())
293 token_web_data->RemoveTokenForService(ApplyAccountIdPrefix(account_id));
296 void ProfileOAuth2TokenService::RevokeAllCredentials() {
297 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
300 for (AccountInfoMap::const_iterator iter = refresh_tokens_.begin();
301 iter != refresh_tokens_.end(); ++iter) {
302 FireRefreshTokenRevoked(iter->first);
304 refresh_tokens_.clear();
306 scoped_refptr<TokenWebData> token_web_data =
307 TokenWebData::FromBrowserContext(profile_);
308 if (token_web_data.get())
309 token_web_data->RemoveAllTokens();
311 // TODO(fgorski): Notify diagnostic observers.
314 void ProfileOAuth2TokenService::LoadCredentials() {
315 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
316 DCHECK_EQ(0, web_data_service_request_);
319 refresh_tokens_.clear();
320 scoped_refptr<TokenWebData> token_web_data =
321 TokenWebData::FromBrowserContext(profile_);
322 if (token_web_data.get())
323 web_data_service_request_ = token_web_data->GetAllTokens(this);
326 void ProfileOAuth2TokenService::OnWebDataServiceRequestDone(
327 WebDataServiceBase::Handle handle,
328 const WDTypedResult* result) {
329 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
330 DCHECK_EQ(web_data_service_request_, handle);
331 web_data_service_request_ = 0;
334 DCHECK(result->GetType() == TOKEN_RESULT);
335 const WDResult<std::map<std::string, std::string> > * token_result =
336 static_cast<const WDResult<std::map<std::string, std::string> > * > (
338 LoadAllCredentialsIntoMemory(token_result->GetValue());
342 void ProfileOAuth2TokenService::LoadAllCredentialsIntoMemory(
343 const std::map<std::string, std::string>& db_tokens) {
344 std::string old_login_token;
346 for (std::map<std::string, std::string>::const_iterator iter =
348 iter != db_tokens.end();
350 std::string prefixed_account_id = iter->first;
351 std::string refresh_token = iter->second;
353 if (IsLegacyRefreshTokenId(prefixed_account_id) && !refresh_token.empty())
354 old_login_token = refresh_token;
356 if (IsLegacyServiceId(prefixed_account_id)) {
357 scoped_refptr<TokenWebData> token_web_data =
358 TokenWebData::FromBrowserContext(profile_);
359 if (token_web_data.get())
360 token_web_data->RemoveTokenForService(prefixed_account_id);
362 DCHECK(!refresh_token.empty());
363 std::string account_id = RemoveAccountIdPrefix(prefixed_account_id);
364 refresh_tokens_[account_id].reset(
365 new AccountInfo(this, account_id, refresh_token));
366 FireRefreshTokenAvailable(account_id);
367 // TODO(fgorski): Notify diagnostic observers.
371 if (!old_login_token.empty() &&
372 refresh_tokens_.count(GetPrimaryAccountId()) == 0) {
373 UpdateCredentials(GetPrimaryAccountId(), old_login_token);
376 FireRefreshTokensLoaded();