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