1// Copyright (c) 2012 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/about_signin_internals.h"
6
7#include "base/command_line.h"
8#include "base/debug/trace_event.h"
9#include "base/hash.h"
10#include "base/i18n/time_formatting.h"
11#include "base/logging.h"
12#include "base/prefs/pref_service.h"
13#include "base/strings/stringprintf.h"
14#include "base/strings/utf_string_conversions.h"
15#include "components/signin/core/browser/profile_oauth2_token_service.h"
16#include "components/signin/core/browser/signin_client.h"
17#include "components/signin/core/browser/signin_internals_util.h"
18#include "components/signin/core/browser/signin_manager.h"
19#include "components/signin/core/common/profile_management_switches.h"
20#include "components/signin/core/common/signin_switches.h"
21#include "google_apis/gaia/gaia_auth_fetcher.h"
22#include "google_apis/gaia/gaia_auth_util.h"
23#include "google_apis/gaia/gaia_constants.h"
24#include "google_apis/gaia/gaia_urls.h"
25#include "net/cookies/canonical_cookie.h"
26
27using base::Time;
28using namespace signin_internals_util;
29
30namespace {
31
32std::string GetTimeStr(base::Time time) {
33  return base::UTF16ToUTF8(base::TimeFormatShortDateAndTime(time));
34}
35
36base::ListValue* AddSection(base::ListValue* parent_list,
37                            const std::string& title) {
38  scoped_ptr<base::DictionaryValue> section(new base::DictionaryValue());
39  base::ListValue* section_contents = new base::ListValue();
40
41  section->SetString("title", title);
42  section->Set("data", section_contents);
43  parent_list->Append(section.release());
44  return section_contents;
45}
46
47void AddSectionEntry(base::ListValue* section_list,
48                     const std::string& field_name,
49                     const std::string& field_status,
50                     const std::string& field_time = "") {
51  scoped_ptr<base::DictionaryValue> entry(new base::DictionaryValue());
52  entry->SetString("label", field_name);
53  entry->SetString("status", field_status);
54  entry->SetString("time", field_time);
55  section_list->Append(entry.release());
56}
57
58void AddCookieEntry(base::ListValue* accounts_list,
59                     const std::string& field_email,
60                     const std::string& field_valid) {
61  scoped_ptr<base::DictionaryValue> entry(new base::DictionaryValue());
62  entry->SetString("email", field_email);
63  entry->SetString("valid", field_valid);
64  accounts_list->Append(entry.release());
65}
66
67std::string SigninStatusFieldToLabel(UntimedSigninStatusField field) {
68  switch (field) {
69    case USERNAME:
70      return "User Id";
71    case UNTIMED_FIELDS_END:
72      NOTREACHED();
73      return std::string();
74  }
75  NOTREACHED();
76  return std::string();
77}
78
79#if !defined (OS_CHROMEOS)
80std::string SigninStatusFieldToLabel(TimedSigninStatusField field) {
81  switch (field) {
82    case SIGNIN_TYPE:
83      return "Type";
84    case AUTHENTICATION_RESULT_RECEIVED:
85      return "Last Authentication Result Received";
86    case REFRESH_TOKEN_RECEIVED:
87      return "Last RefreshToken Received";
88    case GET_USER_INFO_STATUS:
89      return "Last OnGetUserInfo Received";
90    case UBER_TOKEN_STATUS:
91      return "Last OnUberToken Received";
92    case MERGE_SESSION_STATUS:
93      return "Last OnMergeSession Received";
94    case TIMED_FIELDS_END:
95      NOTREACHED();
96      return "Error";
97  }
98  NOTREACHED();
99  return "Error";
100}
101#endif // !defined (OS_CHROMEOS)
102
103}  // anonymous namespace
104
105AboutSigninInternals::AboutSigninInternals(
106    ProfileOAuth2TokenService* token_service,
107    SigninManagerBase* signin_manager)
108    : token_service_(token_service),
109      signin_manager_(signin_manager),
110      client_(NULL) {}
111
112AboutSigninInternals::~AboutSigninInternals() {}
113
114void AboutSigninInternals::AddSigninObserver(
115    AboutSigninInternals::Observer* observer) {
116  signin_observers_.AddObserver(observer);
117}
118
119void AboutSigninInternals::RemoveSigninObserver(
120    AboutSigninInternals::Observer* observer) {
121  signin_observers_.RemoveObserver(observer);
122}
123
124void AboutSigninInternals::NotifySigninValueChanged(
125    const UntimedSigninStatusField& field,
126    const std::string& value) {
127  unsigned int field_index = field - UNTIMED_FIELDS_BEGIN;
128  DCHECK(field_index >= 0 &&
129         field_index < signin_status_.untimed_signin_fields.size());
130
131  signin_status_.untimed_signin_fields[field_index] = value;
132
133  // Also persist these values in the prefs.
134  const std::string pref_path = SigninStatusFieldToString(field);
135  client_->GetPrefs()->SetString(pref_path.c_str(), value);
136
137  NotifyObservers();
138}
139
140void AboutSigninInternals::NotifySigninValueChanged(
141    const TimedSigninStatusField& field,
142    const std::string& value) {
143  unsigned int field_index = field - TIMED_FIELDS_BEGIN;
144  DCHECK(field_index >= 0 &&
145         field_index < signin_status_.timed_signin_fields.size());
146
147  Time now = Time::NowFromSystemTime();
148  std::string time_as_str =
149      base::UTF16ToUTF8(base::TimeFormatShortDateAndTime(now));
150  TimedSigninStatusValue timed_value(value, time_as_str);
151
152  signin_status_.timed_signin_fields[field_index] = timed_value;
153
154  // Also persist these values in the prefs.
155  const std::string value_pref = SigninStatusFieldToString(field) + ".value";
156  const std::string time_pref = SigninStatusFieldToString(field) + ".time";
157  client_->GetPrefs()->SetString(value_pref.c_str(), value);
158  client_->GetPrefs()->SetString(time_pref.c_str(), time_as_str);
159
160  NotifyObservers();
161}
162
163void AboutSigninInternals::RefreshSigninPrefs() {
164  // Since the AboutSigninInternals has a dependency on the SigninManager
165  // (as seen in the AboutSigninInternalsFactory) the SigninManager can have
166  // the AuthenticatedUsername set before AboutSigninInternals can observe it.
167  // For that scenario, read the AuthenticatedUsername if it exists.
168  if (signin_manager_->IsAuthenticated()) {
169    signin_status_.untimed_signin_fields[USERNAME] =
170        signin_manager_->GetAuthenticatedUsername();
171  }
172
173  // Return if no client exists. Can occur in unit tests.
174  if (!client_)
175    return;
176
177  PrefService* pref_service = client_->GetPrefs();
178  for (int i = UNTIMED_FIELDS_BEGIN; i < UNTIMED_FIELDS_END; ++i) {
179    const std::string pref_path =
180        SigninStatusFieldToString(static_cast<UntimedSigninStatusField>(i));
181
182    signin_status_.untimed_signin_fields[i - UNTIMED_FIELDS_BEGIN] =
183        pref_service->GetString(pref_path.c_str());
184  }
185  for (int i = TIMED_FIELDS_BEGIN; i < TIMED_FIELDS_END; ++i) {
186    const std::string value_pref =
187        SigninStatusFieldToString(static_cast<TimedSigninStatusField>(i)) +
188        ".value";
189    const std::string time_pref =
190        SigninStatusFieldToString(static_cast<TimedSigninStatusField>(i)) +
191        ".time";
192
193    TimedSigninStatusValue value(pref_service->GetString(value_pref.c_str()),
194                                 pref_service->GetString(time_pref.c_str()));
195    signin_status_.timed_signin_fields[i - TIMED_FIELDS_BEGIN] = value;
196  }
197
198  // TODO(rogerta): Get status and timestamps for oauth2 tokens.
199
200  NotifyObservers();
201}
202
203void AboutSigninInternals::Initialize(SigninClient* client) {
204  DCHECK(!client_);
205  client_ = client;
206
207  RefreshSigninPrefs();
208
209  signin_manager_->AddSigninDiagnosticsObserver(this);
210  token_service_->AddDiagnosticsObserver(this);
211  cookie_changed_subscription_ = client_->AddCookieChangedCallback(
212     base::Bind(&AboutSigninInternals::OnCookieChanged,
213     base::Unretained(this)));
214}
215
216void AboutSigninInternals::Shutdown() {
217  signin_manager_->RemoveSigninDiagnosticsObserver(this);
218  token_service_->RemoveDiagnosticsObserver(this);
219  cookie_changed_subscription_.reset();
220}
221
222void AboutSigninInternals::NotifyObservers() {
223  scoped_ptr<base::DictionaryValue> signin_status_value =
224      signin_status_.ToValue(client_->GetProductVersion());
225  FOR_EACH_OBSERVER(AboutSigninInternals::Observer,
226                    signin_observers_,
227                    OnSigninStateChanged(signin_status_value.get()));
228}
229
230scoped_ptr<base::DictionaryValue> AboutSigninInternals::GetSigninStatus() {
231  return signin_status_.ToValue(client_->GetProductVersion()).Pass();
232}
233
234void AboutSigninInternals::OnAccessTokenRequested(
235    const std::string& account_id,
236    const std::string& consumer_id,
237    const OAuth2TokenService::ScopeSet& scopes) {
238  TokenInfo* token = signin_status_.FindToken(account_id, consumer_id, scopes);
239  if (token) {
240    *token = TokenInfo(consumer_id, scopes);
241  } else {
242    token = new TokenInfo(consumer_id, scopes);
243    signin_status_.token_info_map[account_id].push_back(token);
244  }
245
246  NotifyObservers();
247}
248
249void AboutSigninInternals::OnFetchAccessTokenComplete(
250    const std::string& account_id,
251    const std::string& consumer_id,
252    const OAuth2TokenService::ScopeSet& scopes,
253    GoogleServiceAuthError error,
254    base::Time expiration_time) {
255  TokenInfo* token = signin_status_.FindToken(account_id, consumer_id, scopes);
256  if (!token) {
257    DVLOG(1) << "Can't find token: " << account_id << ", " << consumer_id;
258    return;
259  }
260
261  token->receive_time = base::Time::Now();
262  token->error = error;
263  token->expiration_time = expiration_time;
264
265  NotifyObservers();
266}
267
268void AboutSigninInternals::OnTokenRemoved(
269    const std::string& account_id,
270    const OAuth2TokenService::ScopeSet& scopes) {
271  for (size_t i = 0; i < signin_status_.token_info_map[account_id].size();
272       ++i) {
273    TokenInfo* token = signin_status_.token_info_map[account_id][i];
274    if (token->scopes == scopes)
275      token->Invalidate();
276  }
277  NotifyObservers();
278}
279
280void AboutSigninInternals::OnRefreshTokenReceived(std::string status) {
281  NotifySigninValueChanged(REFRESH_TOKEN_RECEIVED, status);
282}
283
284void AboutSigninInternals::OnAuthenticationResultReceived(std::string status) {
285  NotifySigninValueChanged(AUTHENTICATION_RESULT_RECEIVED, status);
286}
287
288void AboutSigninInternals::OnCookieChanged(
289    const net::CanonicalCookie* cookie) {
290  if (cookie->Name() == "LSID" &&
291      cookie->Domain() == GaiaUrls::GetInstance()->gaia_url().host() &&
292      cookie->IsSecure() &&
293      cookie->IsHttpOnly()) {
294    GetCookieAccountsAsync();
295  }
296}
297
298void AboutSigninInternals::GetCookieAccountsAsync() {
299  // Don't bother calling /ListAccounts if no one will observe the response.
300  if (!gaia_fetcher_ && signin_observers_.might_have_observers()) {
301    // There is no list account request in flight.
302    gaia_fetcher_.reset(new GaiaAuthFetcher(
303        this, GaiaConstants::kChromeSource, client_->GetURLRequestContext()));
304    gaia_fetcher_->StartListAccounts();
305  }
306}
307
308void AboutSigninInternals::OnListAccountsSuccess(const std::string& data) {
309  gaia_fetcher_.reset();
310
311  // Get account information from response data.
312  std::vector<std::pair<std::string, bool> > gaia_accounts;
313  bool valid_json = gaia::ParseListAccountsData(data, &gaia_accounts);
314  if (!valid_json) {
315    VLOG(1) << "AboutSigninInternals::OnListAccountsSuccess: parsing error";
316  } else {
317    OnListAccountsComplete(gaia_accounts);
318  }
319}
320
321void AboutSigninInternals::OnListAccountsFailure(
322    const GoogleServiceAuthError& error) {
323  gaia_fetcher_.reset();
324  VLOG(1) << "AboutSigninInternals::OnListAccountsFailure:" << error.ToString();
325}
326
327void AboutSigninInternals::OnListAccountsComplete(
328    std::vector<std::pair<std::string, bool> >& gaia_accounts) {
329  base::DictionaryValue signin_status;
330  base::ListValue* cookie_info = new base::ListValue();
331  signin_status.Set("cookie_info", cookie_info);
332
333  for (size_t i = 0; i < gaia_accounts.size(); ++i) {
334    AddCookieEntry(cookie_info,
335                   gaia_accounts[i].first,
336                   gaia_accounts[i].second ? "Valid" : "Invalid");
337  }
338
339  if (gaia_accounts.size() == 0)
340    AddCookieEntry(cookie_info, "No Accounts Present.", "");
341
342  // Update the observers that the cookie's accounts are updated.
343  FOR_EACH_OBSERVER(AboutSigninInternals::Observer,
344                    signin_observers_,
345                    OnCookieAccountsFetched(&signin_status));
346}
347
348AboutSigninInternals::TokenInfo::TokenInfo(
349    const std::string& consumer_id,
350    const OAuth2TokenService::ScopeSet& scopes)
351    : consumer_id(consumer_id),
352      scopes(scopes),
353      request_time(base::Time::Now()),
354      error(GoogleServiceAuthError::AuthErrorNone()),
355      removed_(false) {}
356
357AboutSigninInternals::TokenInfo::~TokenInfo() {}
358
359bool AboutSigninInternals::TokenInfo::LessThan(const TokenInfo* a,
360                                               const TokenInfo* b) {
361  return a->consumer_id < b->consumer_id || a->scopes < b->scopes;
362}
363
364void AboutSigninInternals::TokenInfo::Invalidate() { removed_ = true; }
365
366base::DictionaryValue* AboutSigninInternals::TokenInfo::ToValue() const {
367  scoped_ptr<base::DictionaryValue> token_info(new base::DictionaryValue());
368  token_info->SetString("service", consumer_id);
369
370  std::string scopes_str;
371  for (OAuth2TokenService::ScopeSet::const_iterator it = scopes.begin();
372       it != scopes.end();
373       ++it) {
374    scopes_str += *it + "<br/>";
375  }
376  token_info->SetString("scopes", scopes_str);
377  token_info->SetString("request_time", GetTimeStr(request_time).c_str());
378
379  if (removed_) {
380    token_info->SetString("status", "Token was revoked.");
381  } else if (!receive_time.is_null()) {
382    if (error == GoogleServiceAuthError::AuthErrorNone()) {
383      bool token_expired = expiration_time < base::Time::Now();
384      std::string status_str = "";
385      if (token_expired)
386        status_str = "<p style=\"color: #ffffff; background-color: #ff0000\">";
387      base::StringAppendF(&status_str,
388                          "Received token at %s. Expire at %s",
389                          GetTimeStr(receive_time).c_str(),
390                          GetTimeStr(expiration_time).c_str());
391      if (token_expired)
392        base::StringAppendF(&status_str, "</p>");
393      token_info->SetString("status", status_str);
394    } else {
395      token_info->SetString(
396          "status",
397          base::StringPrintf("Failure: %s", error.ToString().c_str()));
398    }
399  } else {
400    token_info->SetString("status", "Waiting for response");
401  }
402
403  return token_info.release();
404}
405
406AboutSigninInternals::SigninStatus::SigninStatus()
407    : untimed_signin_fields(UNTIMED_FIELDS_COUNT),
408      timed_signin_fields(TIMED_FIELDS_COUNT) {}
409
410AboutSigninInternals::SigninStatus::~SigninStatus() {
411  for (TokenInfoMap::iterator it = token_info_map.begin();
412       it != token_info_map.end();
413       ++it) {
414    STLDeleteElements(&it->second);
415  }
416}
417
418AboutSigninInternals::TokenInfo* AboutSigninInternals::SigninStatus::FindToken(
419    const std::string& account_id,
420    const std::string& consumer_id,
421    const OAuth2TokenService::ScopeSet& scopes) {
422  for (size_t i = 0; i < token_info_map[account_id].size(); ++i) {
423    TokenInfo* tmp = token_info_map[account_id][i];
424    if (tmp->consumer_id == consumer_id && tmp->scopes == scopes)
425      return tmp;
426  }
427  return NULL;
428}
429
430scoped_ptr<base::DictionaryValue> AboutSigninInternals::SigninStatus::ToValue(
431    std::string product_version) {
432  scoped_ptr<base::DictionaryValue> signin_status(new base::DictionaryValue());
433  base::ListValue* signin_info = new base::ListValue();
434  signin_status->Set("signin_info", signin_info);
435
436  // A summary of signin related info first.
437  base::ListValue* basic_info = AddSection(signin_info, "Basic Information");
438  const std::string signin_status_string =
439      untimed_signin_fields[USERNAME - UNTIMED_FIELDS_BEGIN].empty()
440          ? "Not Signed In"
441          : "Signed In";
442  AddSectionEntry(basic_info, "Chrome Version", product_version);
443  AddSectionEntry(basic_info, "Signin Status", signin_status_string);
444  AddSectionEntry(basic_info, "Web Based Signin Enabled?",
445      switches::IsEnableWebBasedSignin() == true ? "True" : "False");
446  AddSectionEntry(basic_info, "New Avatar Menu Enabled?",
447      switches::IsNewAvatarMenu() == true ? "True" : "False");
448  AddSectionEntry(basic_info, "New Profile Management Enabled?",
449      switches::IsNewProfileManagement() == true ? "True" : "False");
450  AddSectionEntry(basic_info, "Account Consistency Enabled?",
451      switches::IsEnableAccountConsistency() == true ? "True" : "False");
452
453  // Only add username.  SID and LSID have moved to tokens section.
454  const std::string field =
455      SigninStatusFieldToLabel(static_cast<UntimedSigninStatusField>(USERNAME));
456  AddSectionEntry(basic_info,
457                  field,
458                  untimed_signin_fields[USERNAME - UNTIMED_FIELDS_BEGIN]);
459
460#if !defined(OS_CHROMEOS)
461  // Time and status information of the possible sign in types.
462  base::ListValue* detailed_info =
463      AddSection(signin_info, "Last Signin Details");
464  for (int i = TIMED_FIELDS_BEGIN; i < TIMED_FIELDS_END; ++i) {
465    const std::string status_field_label =
466        SigninStatusFieldToLabel(static_cast<TimedSigninStatusField>(i));
467
468    AddSectionEntry(detailed_info,
469                    status_field_label,
470                    timed_signin_fields[i - TIMED_FIELDS_BEGIN].first,
471                    timed_signin_fields[i - TIMED_FIELDS_BEGIN].second);
472  }
473#endif // !defined(OS_CHROMEOS)
474
475  // Token information for all services.
476  base::ListValue* token_info = new base::ListValue();
477  signin_status->Set("token_info", token_info);
478  for (TokenInfoMap::iterator it = token_info_map.begin();
479       it != token_info_map.end();
480       ++it) {
481    base::ListValue* token_details = AddSection(token_info, it->first);
482
483    std::sort(it->second.begin(), it->second.end(), TokenInfo::LessThan);
484    const std::vector<TokenInfo*>& tokens = it->second;
485    for (size_t i = 0; i < tokens.size(); ++i) {
486      base::DictionaryValue* token_info = tokens[i]->ToValue();
487      token_details->Append(token_info);
488    }
489  }
490
491  return signin_status.Pass();
492}
493