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/account_tracker_service.h"
6
7#include "base/debug/trace_event.h"
8#include "base/prefs/pref_service.h"
9#include "base/prefs/scoped_user_pref_update.h"
10#include "base/strings/utf_string_conversions.h"
11#include "components/signin/core/browser/signin_manager.h"
12#include "components/signin/core/common/signin_pref_names.h"
13#include "google_apis/gaia/gaia_auth_util.h"
14#include "google_apis/gaia/gaia_constants.h"
15#include "google_apis/gaia/gaia_oauth_client.h"
16#include "google_apis/gaia/oauth2_token_service.h"
17#include "net/url_request/url_request_context_getter.h"
18
19namespace {
20
21const char kAccountKeyPath[] = "account_id";
22const char kAccountEmailPath[] = "email";
23const char kAccountGaiaPath[] = "gaia";
24
25}
26
27class AccountInfoFetcher : public OAuth2TokenService::Consumer,
28                           public gaia::GaiaOAuthClient::Delegate {
29 public:
30  AccountInfoFetcher(OAuth2TokenService* token_service,
31                     net::URLRequestContextGetter* request_context_getter,
32                     AccountTrackerService* service,
33                     const std::string& account_id);
34  virtual ~AccountInfoFetcher();
35
36  const std::string& account_id() { return account_id_; }
37
38  void Start();
39
40  // OAuth2TokenService::Consumer implementation.
41  virtual void OnGetTokenSuccess(const OAuth2TokenService::Request* request,
42                                 const std::string& access_token,
43                                 const base::Time& expiration_time) OVERRIDE;
44  virtual void OnGetTokenFailure(const OAuth2TokenService::Request* request,
45                                 const GoogleServiceAuthError& error) OVERRIDE;
46
47  // gaia::GaiaOAuthClient::Delegate implementation.
48  virtual void OnGetUserInfoResponse(
49      scoped_ptr<base::DictionaryValue> user_info) OVERRIDE;
50  virtual void OnOAuthError() OVERRIDE;
51  virtual void OnNetworkError(int response_code) OVERRIDE;
52
53 private:
54  OAuth2TokenService* token_service_;
55  net::URLRequestContextGetter* request_context_getter_;
56  AccountTrackerService* service_;
57  const std::string account_id_;
58
59  scoped_ptr<OAuth2TokenService::Request> login_token_request_;
60  scoped_ptr<gaia::GaiaOAuthClient> gaia_oauth_client_;
61};
62
63AccountInfoFetcher::AccountInfoFetcher(
64    OAuth2TokenService* token_service,
65    net::URLRequestContextGetter* request_context_getter,
66    AccountTrackerService* service,
67    const std::string& account_id)
68    : OAuth2TokenService::Consumer("gaia_account_tracker"),
69      token_service_(token_service),
70      request_context_getter_(request_context_getter),
71      service_(service),
72      account_id_(account_id) {
73  TRACE_EVENT_ASYNC_BEGIN1(
74      "AccountTrackerService", "AccountIdFetcher", this,
75      "account_id", account_id);
76}
77
78AccountInfoFetcher::~AccountInfoFetcher() {
79  TRACE_EVENT_ASYNC_END0("AccountTrackerService", "AccountIdFetcher", this);
80}
81
82void AccountInfoFetcher::Start() {
83  OAuth2TokenService::ScopeSet scopes;
84  scopes.insert(GaiaConstants::kGoogleUserInfoEmail);
85  scopes.insert(GaiaConstants::kGoogleUserInfoProfile);
86  login_token_request_ = token_service_->StartRequest(
87      account_id_, scopes, this);
88}
89
90void AccountInfoFetcher::OnGetTokenSuccess(
91    const OAuth2TokenService::Request* request,
92    const std::string& access_token,
93    const base::Time& expiration_time) {
94  TRACE_EVENT_ASYNC_STEP_PAST0(
95      "AccountTrackerService", "AccountIdFetcher", this, "OnGetTokenSuccess");
96  DCHECK_EQ(request, login_token_request_.get());
97
98  gaia_oauth_client_.reset(new gaia::GaiaOAuthClient(request_context_getter_));
99
100  const int kMaxRetries = 3;
101  gaia_oauth_client_->GetUserInfo(access_token, kMaxRetries, this);
102}
103
104void AccountInfoFetcher::OnGetTokenFailure(
105    const OAuth2TokenService::Request* request,
106    const GoogleServiceAuthError& error) {
107  TRACE_EVENT_ASYNC_STEP_PAST1("AccountTrackerService",
108                               "AccountIdFetcher",
109                               this,
110                               "OnGetTokenFailure",
111                               "google_service_auth_error",
112                               error.ToString());
113  LOG(ERROR) << "OnGetTokenFailure: " << error.ToString();
114  DCHECK_EQ(request, login_token_request_.get());
115  service_->OnUserInfoFetchFailure(this);
116}
117
118void AccountInfoFetcher::OnGetUserInfoResponse(
119    scoped_ptr<base::DictionaryValue> user_info) {
120  TRACE_EVENT_ASYNC_STEP_PAST1("AccountTrackerService",
121                               "AccountIdFetcher",
122                               this,
123                               "OnGetUserInfoResponse",
124                               "account_id",
125                               account_id_);
126  service_->OnUserInfoFetchSuccess(this, user_info.get());
127}
128
129void AccountInfoFetcher::OnOAuthError() {
130  TRACE_EVENT_ASYNC_STEP_PAST0(
131      "AccountTrackerService", "AccountIdFetcher", this, "OnOAuthError");
132  LOG(ERROR) << "OnOAuthError";
133  service_->OnUserInfoFetchFailure(this);
134}
135
136void AccountInfoFetcher::OnNetworkError(int response_code) {
137  TRACE_EVENT_ASYNC_STEP_PAST1("AccountTrackerService",
138                               "AccountIdFetcher",
139                               this,
140                               "OnNetworkError",
141                               "response_code",
142                               response_code);
143  LOG(ERROR) << "OnNetworkError " << response_code;
144  service_->OnUserInfoFetchFailure(this);
145}
146
147
148const char AccountTrackerService::kAccountInfoPref[] = "account_info";
149
150AccountTrackerService::AccountTrackerService()
151    : token_service_(NULL),
152      pref_service_(NULL),
153      shutdown_called_(false) {
154}
155
156AccountTrackerService::~AccountTrackerService() {
157  DCHECK(shutdown_called_);
158}
159
160void AccountTrackerService::Initialize(
161    OAuth2TokenService* token_service,
162    PrefService* pref_service,
163    net::URLRequestContextGetter* request_context_getter) {
164  DCHECK(token_service);
165  DCHECK(!token_service_);
166  DCHECK(pref_service);
167  DCHECK(!pref_service_);
168  token_service_ = token_service;
169  pref_service_ = pref_service;
170  request_context_getter_ = request_context_getter;
171  token_service_->AddObserver(this);
172  LoadFromPrefs();
173  LoadFromTokenService();
174}
175
176void AccountTrackerService::Shutdown() {
177  shutdown_called_ = true;
178  STLDeleteValues(&user_info_requests_);
179  token_service_->RemoveObserver(this);
180}
181
182void AccountTrackerService::AddObserver(Observer* observer) {
183  observer_list_.AddObserver(observer);
184}
185
186void AccountTrackerService::RemoveObserver(Observer* observer) {
187  observer_list_.RemoveObserver(observer);
188}
189
190bool AccountTrackerService::IsAllUserInfoFetched() const {
191  return user_info_requests_.empty();
192}
193
194std::vector<AccountTrackerService::AccountInfo>
195AccountTrackerService::GetAccounts() const {
196  std::vector<AccountInfo> accounts;
197
198  for (std::map<std::string, AccountState>::const_iterator it =
199           accounts_.begin();
200       it != accounts_.end();
201       ++it) {
202    const AccountState& state = it->second;
203    accounts.push_back(state.info);
204  }
205  return accounts;
206}
207
208AccountTrackerService::AccountInfo AccountTrackerService::GetAccountInfo(
209    const std::string& account_id) {
210  if (ContainsKey(accounts_, account_id))
211    return accounts_[account_id].info;
212
213  return AccountInfo();
214}
215
216AccountTrackerService::AccountInfo
217AccountTrackerService::FindAccountInfoByGaiaId(
218    const std::string& gaia_id) {
219  for (std::map<std::string, AccountState>::const_iterator it =
220           accounts_.begin();
221       it != accounts_.end();
222       ++it) {
223    const AccountState& state = it->second;
224    if (state.info.gaia == gaia_id)
225      return state.info;
226  }
227
228  return AccountInfo();
229}
230
231AccountTrackerService::AccountInfo
232AccountTrackerService::FindAccountInfoByEmail(
233    const std::string& email) {
234  for (std::map<std::string, AccountState>::const_iterator it =
235           accounts_.begin();
236       it != accounts_.end();
237       ++it) {
238    const AccountState& state = it->second;
239    if (gaia::AreEmailsSame(state.info.email, email))
240      return state.info;
241  }
242
243  return AccountInfo();
244}
245
246AccountTrackerService::AccountIdMigrationState
247AccountTrackerService::GetMigrationState() {
248  return GetMigrationState(pref_service_);
249}
250
251// static
252AccountTrackerService::AccountIdMigrationState
253AccountTrackerService::GetMigrationState(PrefService* pref_service) {
254  return static_cast<AccountTrackerService::AccountIdMigrationState>(
255      pref_service->GetInteger(prefs::kAccountIdMigrationState));
256}
257
258void AccountTrackerService::OnRefreshTokenAvailable(
259    const std::string& account_id) {
260  TRACE_EVENT1("AccountTrackerService",
261               "AccountTracker::OnRefreshTokenAvailable",
262               "account_id",
263               account_id);
264  DVLOG(1) << "AVAILABLE " << account_id;
265
266  StartTrackingAccount(account_id);
267  AccountState& state = accounts_[account_id];
268
269  if (state.info.gaia.empty())
270    StartFetchingUserInfo(account_id);
271}
272
273void AccountTrackerService::OnRefreshTokenRevoked(
274    const std::string& account_id) {
275  TRACE_EVENT1("AccountTrackerService",
276               "AccountTracker::OnRefreshTokenRevoked",
277               "account_id",
278               account_id);
279
280  DVLOG(1) << "REVOKED " << account_id;
281  StopTrackingAccount(account_id);
282}
283
284void AccountTrackerService::NotifyAccountUpdated(const AccountState& state) {
285  DCHECK(!state.info.gaia.empty());
286  FOR_EACH_OBSERVER(
287      Observer, observer_list_, OnAccountUpdated(state.info));
288}
289
290void AccountTrackerService::NotifyAccountRemoved(const AccountState& state) {
291  DCHECK(!state.info.gaia.empty());
292  FOR_EACH_OBSERVER(
293      Observer, observer_list_, OnAccountRemoved(state.info));
294}
295
296void AccountTrackerService::StartTrackingAccount(
297    const std::string& account_id) {
298  if (!ContainsKey(accounts_, account_id)) {
299    DVLOG(1) << "StartTracking " << account_id;
300    AccountState state;
301    state.info.account_id = account_id;
302    accounts_.insert(make_pair(account_id, state));
303  }
304}
305
306void AccountTrackerService::StopTrackingAccount(const std::string& account_id) {
307  DVLOG(1) << "StopTracking " << account_id;
308  if (ContainsKey(accounts_, account_id)) {
309    AccountState& state = accounts_[account_id];
310    RemoveFromPrefs(state);
311    if (!state.info.gaia.empty())
312      NotifyAccountRemoved(state);
313
314    accounts_.erase(account_id);
315  }
316
317  if (ContainsKey(user_info_requests_, account_id))
318    DeleteFetcher(user_info_requests_[account_id]);
319}
320
321void AccountTrackerService::StartFetchingUserInfo(
322    const std::string& account_id) {
323  if (ContainsKey(user_info_requests_, account_id))
324    DeleteFetcher(user_info_requests_[account_id]);
325
326  DVLOG(1) << "StartFetching " << account_id;
327  AccountInfoFetcher* fetcher =
328      new AccountInfoFetcher(token_service_,
329                             request_context_getter_.get(),
330                             this,
331                             account_id);
332  user_info_requests_[account_id] = fetcher;
333  fetcher->Start();
334}
335
336void AccountTrackerService::OnUserInfoFetchSuccess(
337    AccountInfoFetcher* fetcher,
338    const base::DictionaryValue* user_info) {
339  const std::string& account_id = fetcher->account_id();
340  DCHECK(ContainsKey(accounts_, account_id));
341  AccountState& state = accounts_[account_id];
342
343  std::string gaia_id;
344  std::string email;
345  if (user_info->GetString("id", &gaia_id) &&
346      user_info->GetString("email", &email)) {
347    state.info.gaia = gaia_id;
348    state.info.email = email;
349
350    NotifyAccountUpdated(state);
351    SaveToPrefs(state);
352  }
353  DeleteFetcher(fetcher);
354}
355
356void AccountTrackerService::OnUserInfoFetchFailure(
357    AccountInfoFetcher* fetcher) {
358  LOG(WARNING) << "Failed to get UserInfo for " << fetcher->account_id();
359  DeleteFetcher(fetcher);
360  // TODO(rogerta): figure out when to retry.
361}
362
363void AccountTrackerService::DeleteFetcher(AccountInfoFetcher* fetcher) {
364  DVLOG(1) << "DeleteFetcher " << fetcher->account_id();
365  const std::string& account_id = fetcher->account_id();
366  DCHECK(ContainsKey(user_info_requests_, account_id));
367  DCHECK_EQ(fetcher, user_info_requests_[account_id]);
368  user_info_requests_.erase(account_id);
369  delete fetcher;
370}
371
372void AccountTrackerService::LoadFromPrefs() {
373  const base::ListValue* list = pref_service_->GetList(kAccountInfoPref);
374  for (size_t i = 0; i < list->GetSize(); ++i) {
375    const base::DictionaryValue* dict;
376    if (list->GetDictionary(i, &dict)) {
377      base::string16 value;
378      if (dict->GetString(kAccountKeyPath, &value)) {
379        std::string account_id = base::UTF16ToUTF8(value);
380        StartTrackingAccount(account_id);
381        AccountState& state = accounts_[account_id];
382
383        if (dict->GetString(kAccountGaiaPath, &value))
384          state.info.gaia = base::UTF16ToUTF8(value);
385        if (dict->GetString(kAccountEmailPath, &value))
386          state.info.email = base::UTF16ToUTF8(value);
387
388        if (!state.info.gaia.empty())
389          NotifyAccountUpdated(state);
390      }
391    }
392  }
393}
394
395void AccountTrackerService::SaveToPrefs(const AccountState& state) {
396  if (!pref_service_)
397    return;
398
399  base::DictionaryValue* dict = NULL;
400  base::string16 account_id_16 = base::UTF8ToUTF16(state.info.account_id);
401  ListPrefUpdate update(pref_service_, kAccountInfoPref);
402  for(size_t i = 0; i < update->GetSize(); ++i, dict = NULL) {
403    if (update->GetDictionary(i, &dict)) {
404      base::string16 value;
405      if (dict->GetString(kAccountKeyPath, &value) && value == account_id_16)
406        break;
407    }
408  }
409
410  if (!dict) {
411    dict = new base::DictionaryValue();
412    update->Append(dict);  // |update| takes ownership.
413    dict->SetString(kAccountKeyPath, account_id_16);
414  }
415
416  dict->SetString(kAccountEmailPath, state.info.email);
417  dict->SetString(kAccountGaiaPath, state.info.gaia);
418}
419
420void AccountTrackerService::RemoveFromPrefs(const AccountState& state) {
421  if (!pref_service_)
422    return;
423
424  base::string16 account_id_16 = base::UTF8ToUTF16(state.info.account_id);
425  ListPrefUpdate update(pref_service_, kAccountInfoPref);
426  for(size_t i = 0; i < update->GetSize(); ++i) {
427    base::DictionaryValue* dict = NULL;
428    if (update->GetDictionary(i, &dict)) {
429      base::string16 value;
430      if (dict->GetString(kAccountKeyPath, &value) && value == account_id_16) {
431        update->Remove(i, NULL);
432        break;
433      }
434    }
435  }
436}
437
438void AccountTrackerService::LoadFromTokenService() {
439  std::vector<std::string> accounts = token_service_->GetAccounts();
440  for (std::vector<std::string>::const_iterator it = accounts.begin();
441       it != accounts.end(); ++it) {
442    OnRefreshTokenAvailable(*it);
443  }
444}
445