1 // Copyright 2014 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 "components/signin/core/browser/mutable_profile_oauth2_token_service.h"
6
7 #include "components/signin/core/browser/signin_client.h"
8 #include "components/signin/core/browser/signin_metrics.h"
9 #include "components/signin/core/browser/webdata/token_web_data.h"
10 #include "components/webdata/common/web_data_service_base.h"
11 #include "google_apis/gaia/gaia_auth_fetcher.h"
12 #include "google_apis/gaia/gaia_constants.h"
13 #include "google_apis/gaia/google_service_auth_error.h"
14 #include "google_apis/gaia/oauth2_access_token_fetcher_impl.h"
15 #include "net/url_request/url_request_context_getter.h"
16
17 namespace {
18
19 const char kAccountIdPrefix[] = "AccountId-";
20 const size_t kAccountIdPrefixLength = 10;
21
ApplyAccountIdPrefix(const std::string & account_id)22 std::string ApplyAccountIdPrefix(const std::string& account_id) {
23 return kAccountIdPrefix + account_id;
24 }
25
IsLegacyRefreshTokenId(const std::string & service_id)26 bool IsLegacyRefreshTokenId(const std::string& service_id) {
27 return service_id == GaiaConstants::kGaiaOAuth2LoginRefreshToken;
28 }
29
IsLegacyServiceId(const std::string & account_id)30 bool IsLegacyServiceId(const std::string& account_id) {
31 return account_id.compare(0u, kAccountIdPrefixLength, kAccountIdPrefix) != 0;
32 }
33
RemoveAccountIdPrefix(const std::string & prefixed_account_id)34 std::string RemoveAccountIdPrefix(const std::string& prefixed_account_id) {
35 return prefixed_account_id.substr(kAccountIdPrefixLength);
36 }
37
38 } // namespace
39
40 // This class sends a request to GAIA to revoke the given refresh token from
41 // the server. This is a best effort attempt only. This class deletes itself
42 // when done sucessfully or otherwise.
43 class MutableProfileOAuth2TokenService::RevokeServerRefreshToken
44 : public GaiaAuthConsumer {
45 public:
46 RevokeServerRefreshToken(MutableProfileOAuth2TokenService* token_service,
47 const std::string& account_id);
48 virtual ~RevokeServerRefreshToken();
49
50 private:
51 // GaiaAuthConsumer overrides:
52 virtual void OnOAuth2RevokeTokenCompleted() OVERRIDE;
53
54 MutableProfileOAuth2TokenService* token_service_;
55 GaiaAuthFetcher fetcher_;
56
57 DISALLOW_COPY_AND_ASSIGN(RevokeServerRefreshToken);
58 };
59
60 MutableProfileOAuth2TokenService::
RevokeServerRefreshToken(MutableProfileOAuth2TokenService * token_service,const std::string & refresh_token)61 RevokeServerRefreshToken::RevokeServerRefreshToken(
62 MutableProfileOAuth2TokenService* token_service,
63 const std::string& refresh_token)
64 : token_service_(token_service),
65 fetcher_(this, GaiaConstants::kChromeSource,
66 token_service_->GetRequestContext()) {
67 fetcher_.StartRevokeOAuth2Token(refresh_token);
68 }
69
70 MutableProfileOAuth2TokenService::
~RevokeServerRefreshToken()71 RevokeServerRefreshToken::~RevokeServerRefreshToken() {}
72
73 void MutableProfileOAuth2TokenService::
OnOAuth2RevokeTokenCompleted()74 RevokeServerRefreshToken::OnOAuth2RevokeTokenCompleted() {
75 // |this| pointer will be deleted when removed from the vector, so don't
76 // access any members after call to erase().
77 token_service_->server_revokes_.erase(
78 std::find(token_service_->server_revokes_.begin(),
79 token_service_->server_revokes_.end(),
80 this));
81 }
82
AccountInfo(ProfileOAuth2TokenService * token_service,const std::string & account_id,const std::string & refresh_token)83 MutableProfileOAuth2TokenService::AccountInfo::AccountInfo(
84 ProfileOAuth2TokenService* token_service,
85 const std::string& account_id,
86 const std::string& refresh_token)
87 : token_service_(token_service),
88 account_id_(account_id),
89 refresh_token_(refresh_token),
90 last_auth_error_(GoogleServiceAuthError::NONE) {
91 DCHECK(token_service_);
92 DCHECK(!account_id_.empty());
93 token_service_->signin_error_controller()->AddProvider(this);
94 }
95
~AccountInfo()96 MutableProfileOAuth2TokenService::AccountInfo::~AccountInfo() {
97 token_service_->signin_error_controller()->RemoveProvider(this);
98 }
99
SetLastAuthError(const GoogleServiceAuthError & error)100 void MutableProfileOAuth2TokenService::AccountInfo::SetLastAuthError(
101 const GoogleServiceAuthError& error) {
102 if (error.state() != last_auth_error_.state()) {
103 last_auth_error_ = error;
104 token_service_->signin_error_controller()->AuthStatusChanged();
105 }
106 }
107
108 std::string
GetAccountId() const109 MutableProfileOAuth2TokenService::AccountInfo::GetAccountId() const {
110 return account_id_;
111 }
112
113 std::string
GetUsername() const114 MutableProfileOAuth2TokenService::AccountInfo::GetUsername() const {
115 // TODO(rogerta): when |account_id| becomes the obfuscated gaia id, this
116 // will need to be changed.
117 return account_id_;
118 }
119
120 GoogleServiceAuthError
GetAuthStatus() const121 MutableProfileOAuth2TokenService::AccountInfo::GetAuthStatus() const {
122 return last_auth_error_;
123 }
124
MutableProfileOAuth2TokenService()125 MutableProfileOAuth2TokenService::MutableProfileOAuth2TokenService()
126 : web_data_service_request_(0) {
127 }
128
~MutableProfileOAuth2TokenService()129 MutableProfileOAuth2TokenService::~MutableProfileOAuth2TokenService() {
130 DCHECK(server_revokes_.empty());
131 }
132
Shutdown()133 void MutableProfileOAuth2TokenService::Shutdown() {
134 server_revokes_.clear();
135 CancelWebTokenFetch();
136 CancelAllRequests();
137 refresh_tokens_.clear();
138
139 ProfileOAuth2TokenService::Shutdown();
140 }
141
RefreshTokenIsAvailable(const std::string & account_id) const142 bool MutableProfileOAuth2TokenService::RefreshTokenIsAvailable(
143 const std::string& account_id) const {
144 return !GetRefreshToken(account_id).empty();
145 }
146
GetRefreshToken(const std::string & account_id) const147 std::string MutableProfileOAuth2TokenService::GetRefreshToken(
148 const std::string& account_id) const {
149 AccountInfoMap::const_iterator iter = refresh_tokens_.find(account_id);
150 if (iter != refresh_tokens_.end())
151 return iter->second->refresh_token();
152 return std::string();
153 }
154
155 OAuth2AccessTokenFetcher*
CreateAccessTokenFetcher(const std::string & account_id,net::URLRequestContextGetter * getter,OAuth2AccessTokenConsumer * consumer)156 MutableProfileOAuth2TokenService::CreateAccessTokenFetcher(
157 const std::string& account_id,
158 net::URLRequestContextGetter* getter,
159 OAuth2AccessTokenConsumer* consumer) {
160 std::string refresh_token = GetRefreshToken(account_id);
161 DCHECK(!refresh_token.empty());
162 return new OAuth2AccessTokenFetcherImpl(consumer, getter, refresh_token);
163 }
164
165 net::URLRequestContextGetter*
GetRequestContext()166 MutableProfileOAuth2TokenService::GetRequestContext() {
167 return client()->GetURLRequestContext();
168 }
169
LoadCredentials(const std::string & primary_account_id)170 void MutableProfileOAuth2TokenService::LoadCredentials(
171 const std::string& primary_account_id) {
172 DCHECK(!primary_account_id.empty());
173 DCHECK(loading_primary_account_id_.empty());
174 DCHECK_EQ(0, web_data_service_request_);
175
176 CancelAllRequests();
177 refresh_tokens().clear();
178 loading_primary_account_id_ = primary_account_id;
179 scoped_refptr<TokenWebData> token_web_data = client()->GetDatabase();
180 if (token_web_data.get())
181 web_data_service_request_ = token_web_data->GetAllTokens(this);
182 }
183
OnWebDataServiceRequestDone(WebDataServiceBase::Handle handle,const WDTypedResult * result)184 void MutableProfileOAuth2TokenService::OnWebDataServiceRequestDone(
185 WebDataServiceBase::Handle handle,
186 const WDTypedResult* result) {
187 DCHECK_EQ(web_data_service_request_, handle);
188 web_data_service_request_ = 0;
189
190 if (result) {
191 DCHECK(result->GetType() == TOKEN_RESULT);
192 const WDResult<std::map<std::string, std::string> > * token_result =
193 static_cast<const WDResult<std::map<std::string, std::string> > * > (
194 result);
195 LoadAllCredentialsIntoMemory(token_result->GetValue());
196 }
197
198 // Make sure that we have an entry for |loading_primary_account_id_| in the
199 // map. The entry could be missing if there is a corruption in the token DB
200 // while this profile is connected to an account.
201 DCHECK(!loading_primary_account_id_.empty());
202 if (refresh_tokens().count(loading_primary_account_id_) == 0) {
203 refresh_tokens()[loading_primary_account_id_].reset(
204 new AccountInfo(this, loading_primary_account_id_, std::string()));
205 }
206
207 // If we don't have a refresh token for a known account, signal an error.
208 for (AccountInfoMap::const_iterator i = refresh_tokens_.begin();
209 i != refresh_tokens_.end(); ++i) {
210 if (!RefreshTokenIsAvailable(i->first)) {
211 UpdateAuthError(
212 i->first,
213 GoogleServiceAuthError(
214 GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS));
215 break;
216 }
217 }
218
219 loading_primary_account_id_.clear();
220 }
221
LoadAllCredentialsIntoMemory(const std::map<std::string,std::string> & db_tokens)222 void MutableProfileOAuth2TokenService::LoadAllCredentialsIntoMemory(
223 const std::map<std::string, std::string>& db_tokens) {
224 std::string old_login_token;
225
226 {
227 ScopedBacthChange batch(this);
228
229 for (std::map<std::string, std::string>::const_iterator iter =
230 db_tokens.begin();
231 iter != db_tokens.end();
232 ++iter) {
233 std::string prefixed_account_id = iter->first;
234 std::string refresh_token = iter->second;
235
236 if (IsLegacyRefreshTokenId(prefixed_account_id) && !refresh_token.empty())
237 old_login_token = refresh_token;
238
239 if (IsLegacyServiceId(prefixed_account_id)) {
240 scoped_refptr<TokenWebData> token_web_data = client()->GetDatabase();
241 if (token_web_data.get())
242 token_web_data->RemoveTokenForService(prefixed_account_id);
243 } else {
244 DCHECK(!refresh_token.empty());
245 std::string account_id = RemoveAccountIdPrefix(prefixed_account_id);
246 refresh_tokens()[account_id].reset(
247 new AccountInfo(this, account_id, refresh_token));
248 FireRefreshTokenAvailable(account_id);
249 // TODO(fgorski): Notify diagnostic observers.
250 }
251 }
252
253 if (!old_login_token.empty()) {
254 DCHECK(!loading_primary_account_id_.empty());
255 if (refresh_tokens().count(loading_primary_account_id_) == 0)
256 UpdateCredentials(loading_primary_account_id_, old_login_token);
257 }
258 }
259
260 FireRefreshTokensLoaded();
261 }
262
UpdateAuthError(const std::string & account_id,const GoogleServiceAuthError & error)263 void MutableProfileOAuth2TokenService::UpdateAuthError(
264 const std::string& account_id,
265 const GoogleServiceAuthError& error) {
266 // Do not report connection errors as these are not actually auth errors.
267 // We also want to avoid masking a "real" auth error just because we
268 // subsequently get a transient network error.
269 if (error.state() == GoogleServiceAuthError::CONNECTION_FAILED ||
270 error.state() == GoogleServiceAuthError::SERVICE_UNAVAILABLE)
271 return;
272
273 if (refresh_tokens_.count(account_id) == 0) {
274 // This could happen if the preferences have been corrupted (see
275 // http://crbug.com/321370). In a Debug build that would be a bug, but in a
276 // Release build we want to deal with it gracefully.
277 NOTREACHED();
278 return;
279 }
280 refresh_tokens_[account_id]->SetLastAuthError(error);
281 }
282
GetAccounts()283 std::vector<std::string> MutableProfileOAuth2TokenService::GetAccounts() {
284 std::vector<std::string> account_ids;
285 for (AccountInfoMap::const_iterator iter = refresh_tokens_.begin();
286 iter != refresh_tokens_.end(); ++iter) {
287 account_ids.push_back(iter->first);
288 }
289 return account_ids;
290 }
291
UpdateCredentials(const std::string & account_id,const std::string & refresh_token)292 void MutableProfileOAuth2TokenService::UpdateCredentials(
293 const std::string& account_id,
294 const std::string& refresh_token) {
295 DCHECK(thread_checker_.CalledOnValidThread());
296 DCHECK(!account_id.empty());
297 DCHECK(!refresh_token.empty());
298
299 signin_metrics::LogSigninAddAccount();
300
301 bool refresh_token_present = refresh_tokens_.count(account_id) > 0;
302 if (!refresh_token_present ||
303 refresh_tokens_[account_id]->refresh_token() != refresh_token) {
304 ScopedBacthChange batch(this);
305
306 // If token present, and different from the new one, cancel its requests,
307 // and clear the entries in cache related to that account.
308 if (refresh_token_present) {
309 std::string revoke_reason = refresh_token_present ? "token differs" :
310 "token is missing";
311 LOG(WARNING) << "Revoking refresh token on server. "
312 << "Reason: token update, " << revoke_reason;
313 RevokeCredentialsOnServer(refresh_tokens_[account_id]->refresh_token());
314 CancelRequestsForAccount(account_id);
315 ClearCacheForAccount(account_id);
316 refresh_tokens_[account_id]->set_refresh_token(refresh_token);
317 } else {
318 refresh_tokens_[account_id].reset(
319 new AccountInfo(this, account_id, refresh_token));
320 }
321
322 // Save the token in memory and in persistent store.
323 PersistCredentials(account_id, refresh_token);
324
325 UpdateAuthError(account_id, GoogleServiceAuthError::AuthErrorNone());
326 FireRefreshTokenAvailable(account_id);
327 }
328 }
329
RevokeCredentials(const std::string & account_id)330 void MutableProfileOAuth2TokenService::RevokeCredentials(
331 const std::string& account_id) {
332 DCHECK(thread_checker_.CalledOnValidThread());
333
334 if (refresh_tokens_.count(account_id) > 0) {
335 ScopedBacthChange batch(this);
336 RevokeCredentialsOnServer(refresh_tokens_[account_id]->refresh_token());
337 CancelRequestsForAccount(account_id);
338 ClearCacheForAccount(account_id);
339 refresh_tokens_.erase(account_id);
340 ClearPersistedCredentials(account_id);
341 FireRefreshTokenRevoked(account_id);
342 }
343 }
344
PersistCredentials(const std::string & account_id,const std::string & refresh_token)345 void MutableProfileOAuth2TokenService::PersistCredentials(
346 const std::string& account_id,
347 const std::string& refresh_token) {
348 scoped_refptr<TokenWebData> token_web_data = client()->GetDatabase();
349 if (token_web_data.get()) {
350 token_web_data->SetTokenForService(ApplyAccountIdPrefix(account_id),
351 refresh_token);
352 }
353 }
354
ClearPersistedCredentials(const std::string & account_id)355 void MutableProfileOAuth2TokenService::ClearPersistedCredentials(
356 const std::string& account_id) {
357 scoped_refptr<TokenWebData> token_web_data = client()->GetDatabase();
358 if (token_web_data.get())
359 token_web_data->RemoveTokenForService(ApplyAccountIdPrefix(account_id));
360 }
361
RevokeAllCredentials()362 void MutableProfileOAuth2TokenService::RevokeAllCredentials() {
363 if (!client()->CanRevokeCredentials())
364 return;
365 DCHECK(thread_checker_.CalledOnValidThread());
366
367 ScopedBacthChange batch(this);
368
369 CancelWebTokenFetch();
370 CancelAllRequests();
371 ClearCache();
372 AccountInfoMap tokens = refresh_tokens_;
373 for (AccountInfoMap::iterator i = tokens.begin(); i != tokens.end(); ++i)
374 RevokeCredentials(i->first);
375
376 DCHECK_EQ(0u, refresh_tokens_.size());
377 }
378
RevokeCredentialsOnServer(const std::string & refresh_token)379 void MutableProfileOAuth2TokenService::RevokeCredentialsOnServer(
380 const std::string& refresh_token) {
381 // Keep track or all server revoke requests. This way they can be deleted
382 // before the token service is shutdown and won't outlive the profile.
383 server_revokes_.push_back(
384 new RevokeServerRefreshToken(this, refresh_token));
385 }
386
CancelWebTokenFetch()387 void MutableProfileOAuth2TokenService::CancelWebTokenFetch() {
388 if (web_data_service_request_ != 0) {
389 scoped_refptr<TokenWebData> token_web_data = client()->GetDatabase();
390 DCHECK(token_web_data.get());
391 token_web_data->CancelRequest(web_data_service_request_);
392 web_data_service_request_ = 0;
393 }
394 }
395