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