mutable_profile_oauth2_token_service.cc revision cedac228d2dd51db4b79ea1e72c7f249408ee061
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
17namespace {
18
19const char kAccountIdPrefix[] = "AccountId-";
20const size_t kAccountIdPrefixLength = 10;
21
22std::string ApplyAccountIdPrefix(const std::string& account_id) {
23  return kAccountIdPrefix + account_id;
24}
25
26bool IsLegacyRefreshTokenId(const std::string& service_id) {
27  return service_id == GaiaConstants::kGaiaOAuth2LoginRefreshToken;
28}
29
30bool IsLegacyServiceId(const std::string& account_id) {
31  return account_id.compare(0u, kAccountIdPrefixLength, kAccountIdPrefix) != 0;
32}
33
34std::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.
43class 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
60MutableProfileOAuth2TokenService::
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
70MutableProfileOAuth2TokenService::
71    RevokeServerRefreshToken::~RevokeServerRefreshToken() {}
72
73void MutableProfileOAuth2TokenService::
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
83MutableProfileOAuth2TokenService::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
96MutableProfileOAuth2TokenService::AccountInfo::~AccountInfo() {
97  token_service_->signin_error_controller()->RemoveProvider(this);
98}
99
100void 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
108std::string
109MutableProfileOAuth2TokenService::AccountInfo::GetAccountId() const {
110  return account_id_;
111}
112
113std::string
114MutableProfileOAuth2TokenService::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
120GoogleServiceAuthError
121MutableProfileOAuth2TokenService::AccountInfo::GetAuthStatus() const {
122  return last_auth_error_;
123}
124
125MutableProfileOAuth2TokenService::MutableProfileOAuth2TokenService()
126    : web_data_service_request_(0)  {
127}
128
129MutableProfileOAuth2TokenService::~MutableProfileOAuth2TokenService() {
130  DCHECK(server_revokes_.empty());
131}
132
133void MutableProfileOAuth2TokenService::Shutdown() {
134  server_revokes_.clear();
135  CancelWebTokenFetch();
136  CancelAllRequests();
137  refresh_tokens_.clear();
138
139  ProfileOAuth2TokenService::Shutdown();
140}
141
142bool MutableProfileOAuth2TokenService::RefreshTokenIsAvailable(
143    const std::string& account_id) const {
144  return !GetRefreshToken(account_id).empty();
145}
146
147std::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
155OAuth2AccessTokenFetcher*
156MutableProfileOAuth2TokenService::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
165net::URLRequestContextGetter*
166MutableProfileOAuth2TokenService::GetRequestContext() {
167  return client()->GetURLRequestContext();
168}
169
170void 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
184void 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
222void MutableProfileOAuth2TokenService::LoadAllCredentialsIntoMemory(
223    const std::map<std::string, std::string>& db_tokens) {
224  std::string old_login_token;
225
226  for (std::map<std::string, std::string>::const_iterator iter =
227           db_tokens.begin();
228       iter != db_tokens.end();
229       ++iter) {
230    std::string prefixed_account_id = iter->first;
231    std::string refresh_token = iter->second;
232
233    if (IsLegacyRefreshTokenId(prefixed_account_id) && !refresh_token.empty())
234      old_login_token = refresh_token;
235
236    if (IsLegacyServiceId(prefixed_account_id)) {
237      scoped_refptr<TokenWebData> token_web_data = client()->GetDatabase();
238      if (token_web_data.get())
239        token_web_data->RemoveTokenForService(prefixed_account_id);
240    } else {
241      DCHECK(!refresh_token.empty());
242      std::string account_id = RemoveAccountIdPrefix(prefixed_account_id);
243      refresh_tokens()[account_id].reset(
244          new AccountInfo(this, account_id, refresh_token));
245      FireRefreshTokenAvailable(account_id);
246      // TODO(fgorski): Notify diagnostic observers.
247    }
248  }
249
250  if (!old_login_token.empty()) {
251    DCHECK(!loading_primary_account_id_.empty());
252    if (refresh_tokens().count(loading_primary_account_id_) == 0)
253      UpdateCredentials(loading_primary_account_id_, old_login_token);
254  }
255
256  FireRefreshTokensLoaded();
257}
258
259void MutableProfileOAuth2TokenService::UpdateAuthError(
260    const std::string& account_id,
261    const GoogleServiceAuthError& error) {
262  // Do not report connection errors as these are not actually auth errors.
263  // We also want to avoid masking a "real" auth error just because we
264  // subsequently get a transient network error.
265  if (error.state() == GoogleServiceAuthError::CONNECTION_FAILED ||
266      error.state() == GoogleServiceAuthError::SERVICE_UNAVAILABLE)
267    return;
268
269#if defined(OS_IOS)
270  // ProfileOauth2TokenService does not manage the refresh tokens on iOS - the
271  // account info on iOS is only used to manage the authentication error state.
272  // Simply add an account info entry with empty refresh token if none exists.
273  if (refresh_tokens_.count(account_id) == 0) {
274      refresh_tokens_[account_id].reset(
275          new AccountInfo(this, account_id, std::string()));
276  }
277#endif
278
279  if (refresh_tokens_.count(account_id) == 0) {
280    // This could happen if the preferences have been corrupted (see
281    // http://crbug.com/321370). In a Debug build that would be a bug, but in a
282    // Release build we want to deal with it gracefully.
283    NOTREACHED();
284    return;
285  }
286  refresh_tokens_[account_id]->SetLastAuthError(error);
287}
288
289std::vector<std::string> MutableProfileOAuth2TokenService::GetAccounts() {
290  std::vector<std::string> account_ids;
291  for (AccountInfoMap::const_iterator iter = refresh_tokens_.begin();
292           iter != refresh_tokens_.end(); ++iter) {
293    account_ids.push_back(iter->first);
294  }
295  return account_ids;
296}
297
298void MutableProfileOAuth2TokenService::UpdateCredentials(
299    const std::string& account_id,
300    const std::string& refresh_token) {
301  DCHECK(thread_checker_.CalledOnValidThread());
302  DCHECK(!account_id.empty());
303  DCHECK(!refresh_token.empty());
304
305  signin_metrics::LogSigninAddAccount();
306
307  bool refresh_token_present = refresh_tokens_.count(account_id) > 0;
308  if (!refresh_token_present ||
309      refresh_tokens_[account_id]->refresh_token() != refresh_token) {
310    // If token present, and different from the new one, cancel its requests,
311    // and clear the entries in cache related to that account.
312    if (refresh_token_present) {
313      std::string revoke_reason = refresh_token_present ? "token differs" :
314                                                          "token is missing";
315      LOG(WARNING) << "Revoking refresh token on server. "
316                   << "Reason: token update, " << revoke_reason;
317      RevokeCredentialsOnServer(refresh_tokens_[account_id]->refresh_token());
318      CancelRequestsForAccount(account_id);
319      ClearCacheForAccount(account_id);
320      refresh_tokens_[account_id]->set_refresh_token(refresh_token);
321    } else {
322      refresh_tokens_[account_id].reset(
323          new AccountInfo(this, account_id, refresh_token));
324    }
325
326    // Save the token in memory and in persistent store.
327    PersistCredentials(account_id, refresh_token);
328
329    UpdateAuthError(account_id, GoogleServiceAuthError::AuthErrorNone());
330    FireRefreshTokenAvailable(account_id);
331  }
332}
333
334void MutableProfileOAuth2TokenService::RevokeCredentials(
335    const std::string& account_id) {
336  DCHECK(thread_checker_.CalledOnValidThread());
337
338  if (refresh_tokens_.count(account_id) > 0) {
339    RevokeCredentialsOnServer(refresh_tokens_[account_id]->refresh_token());
340    CancelRequestsForAccount(account_id);
341    ClearCacheForAccount(account_id);
342    refresh_tokens_.erase(account_id);
343    ClearPersistedCredentials(account_id);
344    FireRefreshTokenRevoked(account_id);
345  }
346}
347
348void MutableProfileOAuth2TokenService::PersistCredentials(
349    const std::string& account_id,
350    const std::string& refresh_token) {
351  scoped_refptr<TokenWebData> token_web_data = client()->GetDatabase();
352  if (token_web_data.get()) {
353    token_web_data->SetTokenForService(ApplyAccountIdPrefix(account_id),
354                                       refresh_token);
355  }
356}
357
358void MutableProfileOAuth2TokenService::ClearPersistedCredentials(
359    const std::string& account_id) {
360  scoped_refptr<TokenWebData> token_web_data = client()->GetDatabase();
361  if (token_web_data.get())
362    token_web_data->RemoveTokenForService(ApplyAccountIdPrefix(account_id));
363}
364
365void MutableProfileOAuth2TokenService::RevokeAllCredentials() {
366  if (!client()->CanRevokeCredentials())
367    return;
368  DCHECK(thread_checker_.CalledOnValidThread());
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
379void 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
387void 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