about_signin_internals.cc revision 0529e5d033099cbfc42635f6f6183833b09dff6e
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_constants.h"
22
23using base::Time;
24using namespace signin_internals_util;
25
26namespace {
27
28std::string GetTimeStr(base::Time time) {
29  return base::UTF16ToUTF8(base::TimeFormatShortDateAndTime(time));
30}
31
32base::ListValue* AddSection(base::ListValue* parent_list,
33                            const std::string& title) {
34  scoped_ptr<base::DictionaryValue> section(new base::DictionaryValue());
35  base::ListValue* section_contents = new base::ListValue();
36
37  section->SetString("title", title);
38  section->Set("data", section_contents);
39  parent_list->Append(section.release());
40  return section_contents;
41}
42
43void AddSectionEntry(base::ListValue* section_list,
44                     const std::string& field_name,
45                     const std::string& field_val) {
46  scoped_ptr<base::DictionaryValue> entry(new base::DictionaryValue());
47  entry->SetString("label", field_name);
48  entry->SetString("value", field_val);
49  section_list->Append(entry.release());
50}
51
52std::string SigninStatusFieldToLabel(UntimedSigninStatusField field) {
53  switch (field) {
54    case USERNAME:
55      return "User Id";
56    case UNTIMED_FIELDS_END:
57      NOTREACHED();
58      return std::string();
59  }
60  NOTREACHED();
61  return std::string();
62}
63
64TimedSigninStatusValue SigninStatusFieldToLabel(TimedSigninStatusField field) {
65  switch (field) {
66    case SIGNIN_TYPE:
67      return TimedSigninStatusValue("Type", "Time");
68    case CLIENT_LOGIN_STATUS:
69      return TimedSigninStatusValue("Last OnClientLogin Status",
70                                    "Last OnClientLogin Time");
71    case OAUTH_LOGIN_STATUS:
72      return TimedSigninStatusValue("Last OnOAuthLogin Status",
73                                    "Last OnOAuthLogin Time");
74
75    case GET_USER_INFO_STATUS:
76      return TimedSigninStatusValue("Last OnGetUserInfo Status",
77                                    "Last OnGetUserInfo Time");
78    case UBER_TOKEN_STATUS:
79      return TimedSigninStatusValue("Last OnUberToken Status",
80                                    "Last OnUberToken Time");
81    case MERGE_SESSION_STATUS:
82      return TimedSigninStatusValue("Last OnMergeSession Status",
83                                    "Last OnMergeSession Time");
84    case TIMED_FIELDS_END:
85      NOTREACHED();
86      return TimedSigninStatusValue("Error", std::string());
87  }
88  NOTREACHED();
89  return TimedSigninStatusValue("Error", std::string());
90}
91
92}  // anonymous namespace
93
94AboutSigninInternals::AboutSigninInternals(
95    ProfileOAuth2TokenService* token_service,
96    SigninManagerBase* signin_manager)
97    : token_service_(token_service),
98      signin_manager_(signin_manager),
99      client_(NULL) {}
100
101AboutSigninInternals::~AboutSigninInternals() {}
102
103void AboutSigninInternals::AddSigninObserver(
104    AboutSigninInternals::Observer* observer) {
105  signin_observers_.AddObserver(observer);
106}
107
108void AboutSigninInternals::RemoveSigninObserver(
109    AboutSigninInternals::Observer* observer) {
110  signin_observers_.RemoveObserver(observer);
111}
112
113void AboutSigninInternals::NotifySigninValueChanged(
114    const UntimedSigninStatusField& field,
115    const std::string& value) {
116  unsigned int field_index = field - UNTIMED_FIELDS_BEGIN;
117  DCHECK(field_index >= 0 &&
118         field_index < signin_status_.untimed_signin_fields.size());
119
120  signin_status_.untimed_signin_fields[field_index] = value;
121
122  // Also persist these values in the prefs.
123  const std::string pref_path = SigninStatusFieldToString(field);
124  client_->GetPrefs()->SetString(pref_path.c_str(), value);
125
126  NotifyObservers();
127}
128
129void AboutSigninInternals::NotifySigninValueChanged(
130    const TimedSigninStatusField& field,
131    const std::string& value) {
132  unsigned int field_index = field - TIMED_FIELDS_BEGIN;
133  DCHECK(field_index >= 0 &&
134         field_index < signin_status_.timed_signin_fields.size());
135
136  Time now = Time::NowFromSystemTime();
137  std::string time_as_str =
138      base::UTF16ToUTF8(base::TimeFormatFriendlyDate(now));
139  TimedSigninStatusValue timed_value(value, time_as_str);
140
141  signin_status_.timed_signin_fields[field_index] = timed_value;
142
143  // Also persist these values in the prefs.
144  const std::string value_pref = SigninStatusFieldToString(field) + ".value";
145  const std::string time_pref = SigninStatusFieldToString(field) + ".time";
146  client_->GetPrefs()->SetString(value_pref.c_str(), value);
147  client_->GetPrefs()->SetString(time_pref.c_str(), time_as_str);
148
149  NotifyObservers();
150}
151
152void AboutSigninInternals::RefreshSigninPrefs() {
153  // Return if no client exists. Can occur in unit tests.
154  if (!client_)
155    return;
156
157  PrefService* pref_service = client_->GetPrefs();
158  for (int i = UNTIMED_FIELDS_BEGIN; i < UNTIMED_FIELDS_END; ++i) {
159    const std::string pref_path =
160        SigninStatusFieldToString(static_cast<UntimedSigninStatusField>(i));
161
162    signin_status_.untimed_signin_fields[i - UNTIMED_FIELDS_BEGIN] =
163        pref_service->GetString(pref_path.c_str());
164  }
165  for (int i = TIMED_FIELDS_BEGIN; i < TIMED_FIELDS_END; ++i) {
166    const std::string value_pref =
167        SigninStatusFieldToString(static_cast<TimedSigninStatusField>(i)) +
168        ".value";
169    const std::string time_pref =
170        SigninStatusFieldToString(static_cast<TimedSigninStatusField>(i)) +
171        ".time";
172
173    TimedSigninStatusValue value(pref_service->GetString(value_pref.c_str()),
174                                 pref_service->GetString(time_pref.c_str()));
175    signin_status_.timed_signin_fields[i - TIMED_FIELDS_BEGIN] = value;
176  }
177
178  // TODO(rogerta): Get status and timestamps for oauth2 tokens.
179
180  NotifyObservers();
181}
182
183void AboutSigninInternals::Initialize(SigninClient* client) {
184  DCHECK(!client_);
185  client_ = client;
186
187  RefreshSigninPrefs();
188
189  signin_manager_->AddSigninDiagnosticsObserver(this);
190  token_service_->AddDiagnosticsObserver(this);
191}
192
193void AboutSigninInternals::Shutdown() {
194  signin_manager_->RemoveSigninDiagnosticsObserver(this);
195  token_service_->RemoveDiagnosticsObserver(this);
196}
197
198void AboutSigninInternals::NotifyObservers() {
199  FOR_EACH_OBSERVER(AboutSigninInternals::Observer,
200                    signin_observers_,
201                    OnSigninStateChanged(
202                        signin_status_.ToValue(client_->GetProductVersion())));
203}
204
205scoped_ptr<base::DictionaryValue> AboutSigninInternals::GetSigninStatus() {
206  return signin_status_.ToValue(client_->GetProductVersion()).Pass();
207}
208
209void AboutSigninInternals::OnAccessTokenRequested(
210    const std::string& account_id,
211    const std::string& consumer_id,
212    const OAuth2TokenService::ScopeSet& scopes) {
213  TokenInfo* token = signin_status_.FindToken(account_id, consumer_id, scopes);
214  if (token) {
215    *token = TokenInfo(consumer_id, scopes);
216  } else {
217    token = new TokenInfo(consumer_id, scopes);
218    signin_status_.token_info_map[account_id].push_back(token);
219  }
220
221  NotifyObservers();
222}
223
224void AboutSigninInternals::OnFetchAccessTokenComplete(
225    const std::string& account_id,
226    const std::string& consumer_id,
227    const OAuth2TokenService::ScopeSet& scopes,
228    GoogleServiceAuthError error,
229    base::Time expiration_time) {
230  TokenInfo* token = signin_status_.FindToken(account_id, consumer_id, scopes);
231  if (!token) {
232    DVLOG(1) << "Can't find token: " << account_id << ", " << consumer_id;
233    return;
234  }
235
236  token->receive_time = base::Time::Now();
237  token->error = error;
238  token->expiration_time = expiration_time;
239
240  NotifyObservers();
241}
242
243void AboutSigninInternals::OnTokenRemoved(
244    const std::string& account_id,
245    const OAuth2TokenService::ScopeSet& scopes) {
246  for (size_t i = 0; i < signin_status_.token_info_map[account_id].size();
247       ++i) {
248    TokenInfo* token = signin_status_.token_info_map[account_id][i];
249    if (token->scopes == scopes)
250      token->Invalidate();
251  }
252  NotifyObservers();
253}
254
255AboutSigninInternals::TokenInfo::TokenInfo(
256    const std::string& consumer_id,
257    const OAuth2TokenService::ScopeSet& scopes)
258    : consumer_id(consumer_id),
259      scopes(scopes),
260      request_time(base::Time::Now()),
261      error(GoogleServiceAuthError::AuthErrorNone()),
262      removed_(false) {}
263
264AboutSigninInternals::TokenInfo::~TokenInfo() {}
265
266bool AboutSigninInternals::TokenInfo::LessThan(const TokenInfo* a,
267                                               const TokenInfo* b) {
268  return a->consumer_id < b->consumer_id || a->scopes < b->scopes;
269}
270
271void AboutSigninInternals::TokenInfo::Invalidate() { removed_ = true; }
272
273base::DictionaryValue* AboutSigninInternals::TokenInfo::ToValue() const {
274  scoped_ptr<base::DictionaryValue> token_info(new base::DictionaryValue());
275  token_info->SetString("service", consumer_id);
276
277  std::string scopes_str;
278  for (OAuth2TokenService::ScopeSet::const_iterator it = scopes.begin();
279       it != scopes.end();
280       ++it) {
281    scopes_str += *it + "<br/>";
282  }
283  token_info->SetString("scopes", scopes_str);
284  token_info->SetString("request_time", GetTimeStr(request_time).c_str());
285
286  if (removed_) {
287    token_info->SetString("status", "Token was revoked.");
288  } else if (!receive_time.is_null()) {
289    if (error == GoogleServiceAuthError::AuthErrorNone()) {
290      bool token_expired = expiration_time < base::Time::Now();
291      std::string status_str = "";
292      if (token_expired)
293        status_str = "<p style=\"color: #ffffff; background-color: #ff0000\">";
294      base::StringAppendF(&status_str,
295                          "Received token at %s. Expire at %s",
296                          GetTimeStr(receive_time).c_str(),
297                          GetTimeStr(expiration_time).c_str());
298      if (token_expired)
299        base::StringAppendF(&status_str, "</p>");
300      token_info->SetString("status", status_str);
301    } else {
302      token_info->SetString(
303          "status",
304          base::StringPrintf("Failure: %s", error.ToString().c_str()));
305    }
306  } else {
307    token_info->SetString("status", "Waiting for response");
308  }
309
310  return token_info.release();
311}
312
313AboutSigninInternals::SigninStatus::SigninStatus()
314    : untimed_signin_fields(UNTIMED_FIELDS_COUNT),
315      timed_signin_fields(TIMED_FIELDS_COUNT) {}
316
317AboutSigninInternals::SigninStatus::~SigninStatus() {
318  for (TokenInfoMap::iterator it = token_info_map.begin();
319       it != token_info_map.end();
320       ++it) {
321    STLDeleteElements(&it->second);
322  }
323}
324
325AboutSigninInternals::TokenInfo* AboutSigninInternals::SigninStatus::FindToken(
326    const std::string& account_id,
327    const std::string& consumer_id,
328    const OAuth2TokenService::ScopeSet& scopes) {
329  for (size_t i = 0; i < token_info_map[account_id].size(); ++i) {
330    TokenInfo* tmp = token_info_map[account_id][i];
331    if (tmp->consumer_id == consumer_id && tmp->scopes == scopes)
332      return tmp;
333  }
334  return NULL;
335}
336
337scoped_ptr<base::DictionaryValue> AboutSigninInternals::SigninStatus::ToValue(
338    std::string product_version) {
339  scoped_ptr<base::DictionaryValue> signin_status(new base::DictionaryValue());
340  base::ListValue* signin_info = new base::ListValue();
341  signin_status->Set("signin_info", signin_info);
342
343  // A summary of signin related info first.
344  base::ListValue* basic_info = AddSection(signin_info, "Basic Information");
345  const std::string signin_status_string =
346      untimed_signin_fields[USERNAME - UNTIMED_FIELDS_BEGIN].empty()
347          ? "Not Signed In"
348          : "Signed In";
349  AddSectionEntry(basic_info, "Chrome Version", product_version);
350  AddSectionEntry(basic_info, "Signin Status", signin_status_string);
351  AddSectionEntry(basic_info, "Web Based Signin Enabled?",
352      switches::IsEnableWebBasedSignin() == true ? "True" : "False");
353  AddSectionEntry(basic_info, "New Profile Management Enabled?",
354      switches::IsNewProfileManagement() == true ? "True" : "False");
355  AddSectionEntry(basic_info, "New Avatar Menu Enabled?",
356      switches::IsNewAvatarMenu() == true ? "True" : "False");
357  bool new_avatar_menu_flag =
358      CommandLine::ForCurrentProcess()->HasSwitch(switches::kNewAvatarMenu);
359  AddSectionEntry(basic_info, "New Avatar Menu Flag Set?",
360      new_avatar_menu_flag ? "True" : "False");
361
362  // Only add username.  SID and LSID have moved to tokens section.
363  const std::string field =
364      SigninStatusFieldToLabel(static_cast<UntimedSigninStatusField>(USERNAME));
365  AddSectionEntry(basic_info,
366                  field,
367                  untimed_signin_fields[USERNAME - UNTIMED_FIELDS_BEGIN]);
368
369  // Time and status information of the possible sign in types.
370  base::ListValue* detailed_info =
371      AddSection(signin_info, "Last Signin Details");
372  for (int i = TIMED_FIELDS_BEGIN; i < TIMED_FIELDS_END; ++i) {
373    const std::string value_field =
374        SigninStatusFieldToLabel(static_cast<TimedSigninStatusField>(i)).first;
375    const std::string time_field =
376        SigninStatusFieldToLabel(static_cast<TimedSigninStatusField>(i)).second;
377
378    AddSectionEntry(detailed_info,
379                    value_field,
380                    timed_signin_fields[i - TIMED_FIELDS_BEGIN].first);
381    AddSectionEntry(detailed_info,
382                    time_field,
383                    timed_signin_fields[i - TIMED_FIELDS_BEGIN].second);
384  }
385
386  // Token information for all services.
387  base::ListValue* token_info = new base::ListValue();
388  signin_status->Set("token_info", token_info);
389  for (TokenInfoMap::iterator it = token_info_map.begin();
390       it != token_info_map.end();
391       ++it) {
392    base::ListValue* token_details = AddSection(token_info, it->first);
393
394    std::sort(it->second.begin(), it->second.end(), TokenInfo::LessThan);
395    const std::vector<TokenInfo*>& tokens = it->second;
396    for (size_t i = 0; i < tokens.size(); ++i) {
397      base::DictionaryValue* token_info = tokens[i]->ToValue();
398      token_details->Append(token_info);
399    }
400  }
401
402  return signin_status.Pass();
403}
404