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