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.
5 #include "components/signin/core/browser/about_signin_internals.h"
7 #include "base/command_line.h"
9 #include "base/i18n/time_formatting.h"
10 #include "base/logging.h"
11 #include "base/prefs/pref_service.h"
12 #include "base/profiler/scoped_tracker.h"
13 #include "base/strings/stringprintf.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "base/trace_event/trace_event.h"
16 #include "components/pref_registry/pref_registry_syncable.h"
17 #include "components/signin/core/browser/account_tracker_service.h"
18 #include "components/signin/core/browser/profile_oauth2_token_service.h"
19 #include "components/signin/core/browser/signin_client.h"
20 #include "components/signin/core/browser/signin_internals_util.h"
21 #include "components/signin/core/browser/signin_manager.h"
22 #include "components/signin/core/common/profile_management_switches.h"
23 #include "components/signin/core/common/signin_switches.h"
26 using namespace signin_internals_util
;
30 std::string
GetTimeStr(base::Time time
) {
31 return base::UTF16ToUTF8(base::TimeFormatShortDateAndTime(time
));
34 base::ListValue
* AddSection(base::ListValue
* parent_list
,
35 const std::string
& title
) {
36 scoped_ptr
<base::DictionaryValue
> section(new base::DictionaryValue());
37 base::ListValue
* section_contents
= new base::ListValue();
39 section
->SetString("title", title
);
40 section
->Set("data", section_contents
);
41 parent_list
->Append(section
.release());
42 return section_contents
;
45 void AddSectionEntry(base::ListValue
* section_list
,
46 const std::string
& field_name
,
47 const std::string
& field_status
,
48 const std::string
& field_time
= "") {
49 scoped_ptr
<base::DictionaryValue
> entry(new base::DictionaryValue());
50 entry
->SetString("label", field_name
);
51 entry
->SetString("status", field_status
);
52 entry
->SetString("time", field_time
);
53 section_list
->Append(entry
.release());
56 void AddCookieEntry(base::ListValue
* accounts_list
,
57 const std::string
& field_email
,
58 const std::string
& field_gaia_id
,
59 const std::string
& field_valid
) {
60 scoped_ptr
<base::DictionaryValue
> entry(new base::DictionaryValue());
61 entry
->SetString("email", field_email
);
62 entry
->SetString("gaia_id", field_gaia_id
);
63 entry
->SetString("valid", field_valid
);
64 accounts_list
->Append(entry
.release());
67 std::string
SigninStatusFieldToLabel(UntimedSigninStatusField field
) {
75 case UNTIMED_FIELDS_END
:
83 #if !defined (OS_CHROMEOS)
84 std::string
SigninStatusFieldToLabel(TimedSigninStatusField field
) {
86 case AUTHENTICATION_RESULT_RECEIVED
:
87 return "Gaia Authentication Result";
88 case REFRESH_TOKEN_RECEIVED
:
89 return "RefreshToken Received";
91 return "SigninManager Started";
92 case SIGNIN_COMPLETED
:
93 return "SigninManager Completed";
94 case TIMED_FIELDS_END
:
101 #endif // !defined (OS_CHROMEOS)
103 void SetPref(PrefService
* prefs
,
104 TimedSigninStatusField field
,
105 const std::string
& time
,
106 const std::string
& value
) {
107 std::string value_pref
= SigninStatusFieldToString(field
) + ".value";
108 std::string time_pref
= SigninStatusFieldToString(field
) + ".time";
109 prefs
->SetString(value_pref
, value
);
110 prefs
->SetString(time_pref
, time
);
113 void GetPref(PrefService
* prefs
,
114 TimedSigninStatusField field
,
116 std::string
* value
) {
117 std::string value_pref
= SigninStatusFieldToString(field
) + ".value";
118 std::string time_pref
= SigninStatusFieldToString(field
) + ".time";
119 *value
= prefs
->GetString(value_pref
);
120 *time
= prefs
->GetString(time_pref
);
123 void ClearPref(PrefService
* prefs
, TimedSigninStatusField field
) {
124 std::string value_pref
= SigninStatusFieldToString(field
) + ".value";
125 std::string time_pref
= SigninStatusFieldToString(field
) + ".time";
126 prefs
->ClearPref(value_pref
);
127 prefs
->ClearPref(time_pref
);
130 } // anonymous namespace
132 AboutSigninInternals::AboutSigninInternals(
133 ProfileOAuth2TokenService
* token_service
,
134 AccountTrackerService
* account_tracker
,
135 SigninManagerBase
* signin_manager
,
136 SigninErrorController
* signin_error_controller
,
137 GaiaCookieManagerService
* cookie_manager_service
)
138 : token_service_(token_service
),
139 account_tracker_(account_tracker
),
140 signin_manager_(signin_manager
),
142 signin_error_controller_(signin_error_controller
),
143 cookie_manager_service_(cookie_manager_service
) {}
145 AboutSigninInternals::~AboutSigninInternals() {}
148 void AboutSigninInternals::RegisterPrefs(
149 user_prefs::PrefRegistrySyncable
* user_prefs
) {
150 // SigninManager information for about:signin-internals.
152 // TODO(rogerta): leaving untimed fields here for now because legacy
153 // profiles still have these prefs. In three or four version from M43
154 // we can probably remove them.
155 for (int i
= UNTIMED_FIELDS_BEGIN
; i
< UNTIMED_FIELDS_END
; ++i
) {
156 const std::string pref_path
=
157 SigninStatusFieldToString(static_cast<UntimedSigninStatusField
>(i
));
158 user_prefs
->RegisterStringPref(pref_path
.c_str(), std::string());
161 for (int i
= TIMED_FIELDS_BEGIN
; i
< TIMED_FIELDS_END
; ++i
) {
162 const std::string value
=
163 SigninStatusFieldToString(static_cast<TimedSigninStatusField
>(i
)) +
165 const std::string time
=
166 SigninStatusFieldToString(static_cast<TimedSigninStatusField
>(i
)) +
168 user_prefs
->RegisterStringPref(value
.c_str(), std::string());
169 user_prefs
->RegisterStringPref(time
.c_str(), std::string());
173 void AboutSigninInternals::AddSigninObserver(
174 AboutSigninInternals::Observer
* observer
) {
175 signin_observers_
.AddObserver(observer
);
178 void AboutSigninInternals::RemoveSigninObserver(
179 AboutSigninInternals::Observer
* observer
) {
180 signin_observers_
.RemoveObserver(observer
);
183 void AboutSigninInternals::NotifySigninValueChanged(
184 const TimedSigninStatusField
& field
,
185 const std::string
& value
) {
186 unsigned int field_index
= field
- TIMED_FIELDS_BEGIN
;
187 DCHECK(field_index
>= 0 &&
188 field_index
< signin_status_
.timed_signin_fields
.size());
190 Time now
= Time::NowFromSystemTime();
191 std::string time_as_str
=
192 base::UTF16ToUTF8(base::TimeFormatShortDateAndTime(now
));
193 TimedSigninStatusValue
timed_value(value
, time_as_str
);
195 signin_status_
.timed_signin_fields
[field_index
] = timed_value
;
197 // Also persist these values in the prefs.
198 SetPref(client_
->GetPrefs(), field
, value
, time_as_str
);
200 // If the user is restarting a sign in process, clear the fields that are
202 if (field
== AUTHENTICATION_RESULT_RECEIVED
) {
203 ClearPref(client_
->GetPrefs(), REFRESH_TOKEN_RECEIVED
);
204 ClearPref(client_
->GetPrefs(), SIGNIN_STARTED
);
205 ClearPref(client_
->GetPrefs(), SIGNIN_COMPLETED
);
211 void AboutSigninInternals::RefreshSigninPrefs() {
212 // Return if no client exists. Can occur in unit tests.
216 PrefService
* pref_service
= client_
->GetPrefs();
217 for (int i
= TIMED_FIELDS_BEGIN
; i
< TIMED_FIELDS_END
; ++i
) {
218 std::string time_str
;
219 std::string value_str
;
220 GetPref(pref_service
, static_cast<TimedSigninStatusField
>(i
),
221 &time_str
, &value_str
);
222 TimedSigninStatusValue
value(value_str
, time_str
);
223 signin_status_
.timed_signin_fields
[i
- TIMED_FIELDS_BEGIN
] = value
;
226 // TODO(rogerta): Get status and timestamps for oauth2 tokens.
231 void AboutSigninInternals::Initialize(SigninClient
* client
) {
235 RefreshSigninPrefs();
237 signin_error_controller_
->AddObserver(this);
238 signin_manager_
->AddSigninDiagnosticsObserver(this);
239 token_service_
->AddDiagnosticsObserver(this);
240 cookie_manager_service_
->AddObserver(this);
243 void AboutSigninInternals::Shutdown() {
244 signin_error_controller_
->RemoveObserver(this);
245 signin_manager_
->RemoveSigninDiagnosticsObserver(this);
246 token_service_
->RemoveDiagnosticsObserver(this);
247 cookie_manager_service_
->RemoveObserver(this);
250 void AboutSigninInternals::NotifyObservers() {
251 if (!signin_observers_
.might_have_observers())
254 // TODO(robliao): Remove ScopedTracker below once https://crbug.com/422460 is
256 tracked_objects::ScopedTracker
tracking_profile(
257 FROM_HERE_WITH_EXPLICIT_FUNCTION(
258 "422460 AboutSigninInternals::NotifyObservers"));
260 const std::string product_version
= client_
->GetProductVersion();
262 // TODO(robliao): Remove ScopedTracker below once https://crbug.com/422460 is
264 tracked_objects::ScopedTracker
tracking_profile05(
265 FROM_HERE_WITH_EXPLICIT_FUNCTION(
266 "422460 AboutSigninInternals::NotifyObservers 0.5"));
268 scoped_ptr
<base::DictionaryValue
> signin_status_value
=
269 signin_status_
.ToValue(account_tracker_
,
271 signin_error_controller_
,
275 // TODO(robliao): Remove ScopedTracker below once https://crbug.com/422460 is
277 tracked_objects::ScopedTracker
tracking_profile1(
278 FROM_HERE_WITH_EXPLICIT_FUNCTION(
279 "422460 AboutSigninInternals::NotifyObservers1"));
281 FOR_EACH_OBSERVER(AboutSigninInternals::Observer
,
283 OnSigninStateChanged(signin_status_value
.get()));
286 scoped_ptr
<base::DictionaryValue
> AboutSigninInternals::GetSigninStatus() {
287 return signin_status_
.ToValue(account_tracker_
,
289 signin_error_controller_
,
291 client_
->GetProductVersion()).Pass();
294 void AboutSigninInternals::OnAccessTokenRequested(
295 const std::string
& account_id
,
296 const std::string
& consumer_id
,
297 const OAuth2TokenService::ScopeSet
& scopes
) {
298 // TODO(robliao): Remove ScopedTracker below once https://crbug.com/422460 is
300 tracked_objects::ScopedTracker
tracking_profile(
301 FROM_HERE_WITH_EXPLICIT_FUNCTION(
302 "422460 AboutSigninInternals::OnAccessTokenRequested"));
304 TokenInfo
* token
= signin_status_
.FindToken(account_id
, consumer_id
, scopes
);
306 *token
= TokenInfo(consumer_id
, scopes
);
308 token
= new TokenInfo(consumer_id
, scopes
);
309 signin_status_
.token_info_map
[account_id
].push_back(token
);
315 void AboutSigninInternals::OnFetchAccessTokenComplete(
316 const std::string
& account_id
,
317 const std::string
& consumer_id
,
318 const OAuth2TokenService::ScopeSet
& scopes
,
319 GoogleServiceAuthError error
,
320 base::Time expiration_time
) {
321 TokenInfo
* token
= signin_status_
.FindToken(account_id
, consumer_id
, scopes
);
323 DVLOG(1) << "Can't find token: " << account_id
<< ", " << consumer_id
;
327 token
->receive_time
= base::Time::Now();
328 token
->error
= error
;
329 token
->expiration_time
= expiration_time
;
334 void AboutSigninInternals::OnTokenRemoved(
335 const std::string
& account_id
,
336 const OAuth2TokenService::ScopeSet
& scopes
) {
337 for (size_t i
= 0; i
< signin_status_
.token_info_map
[account_id
].size();
339 TokenInfo
* token
= signin_status_
.token_info_map
[account_id
][i
];
340 if (token
->scopes
== scopes
)
346 void AboutSigninInternals::OnRefreshTokenReceived(std::string status
) {
347 NotifySigninValueChanged(REFRESH_TOKEN_RECEIVED
, status
);
350 void AboutSigninInternals::OnAuthenticationResultReceived(std::string status
) {
351 NotifySigninValueChanged(AUTHENTICATION_RESULT_RECEIVED
, status
);
354 void AboutSigninInternals::OnErrorChanged() {
358 void AboutSigninInternals::GoogleSigninFailed(
359 const GoogleServiceAuthError
& error
) {
363 void AboutSigninInternals::GoogleSigninSucceeded(const std::string
& account_id
,
364 const std::string
& username
,
365 const std::string
& password
) {
369 void AboutSigninInternals::GoogleSignedOut(const std::string
& account_id
,
370 const std::string
& username
) {
374 void AboutSigninInternals::OnGaiaAccountsInCookieUpdated(
375 const std::vector
<gaia::ListedAccount
>& gaia_accounts
,
376 const GoogleServiceAuthError
& error
) {
377 if (error
.state() != GoogleServiceAuthError::NONE
)
380 base::DictionaryValue cookie_status
;
381 base::ListValue
* cookie_info
= new base::ListValue();
382 cookie_status
.Set("cookie_info", cookie_info
);
384 for (size_t i
= 0; i
< gaia_accounts
.size(); ++i
) {
385 AddCookieEntry(cookie_info
,
386 gaia_accounts
[i
].raw_email
,
387 gaia_accounts
[i
].gaia_id
,
388 gaia_accounts
[i
].valid
? "Valid" : "Invalid");
391 if (gaia_accounts
.size() == 0) {
392 AddCookieEntry(cookie_info
,
393 "No Accounts Present.",
398 // Update the observers that the cookie's accounts are updated.
399 FOR_EACH_OBSERVER(AboutSigninInternals::Observer
,
401 OnCookieAccountsFetched(&cookie_status
));
404 AboutSigninInternals::TokenInfo::TokenInfo(
405 const std::string
& consumer_id
,
406 const OAuth2TokenService::ScopeSet
& scopes
)
407 : consumer_id(consumer_id
),
409 request_time(base::Time::Now()),
410 error(GoogleServiceAuthError::AuthErrorNone()),
413 AboutSigninInternals::TokenInfo::~TokenInfo() {}
415 bool AboutSigninInternals::TokenInfo::LessThan(const TokenInfo
* a
,
416 const TokenInfo
* b
) {
417 if (a
->request_time
== b
->request_time
) {
418 if (a
->consumer_id
== b
->consumer_id
) {
419 return a
->scopes
< b
->scopes
;
421 return a
->consumer_id
< b
->consumer_id
;
423 return a
->request_time
< b
->request_time
;
426 void AboutSigninInternals::TokenInfo::Invalidate() { removed_
= true; }
428 base::DictionaryValue
* AboutSigninInternals::TokenInfo::ToValue() const {
429 scoped_ptr
<base::DictionaryValue
> token_info(new base::DictionaryValue());
430 token_info
->SetString("service", consumer_id
);
432 std::string scopes_str
;
433 for (OAuth2TokenService::ScopeSet::const_iterator it
= scopes
.begin();
436 scopes_str
+= *it
+ "<br/>";
438 token_info
->SetString("scopes", scopes_str
);
439 token_info
->SetString("request_time", GetTimeStr(request_time
).c_str());
442 token_info
->SetString("status", "Token was revoked.");
443 } else if (!receive_time
.is_null()) {
444 if (error
== GoogleServiceAuthError::AuthErrorNone()) {
445 bool token_expired
= expiration_time
< base::Time::Now();
446 std::string expiration_time_string
= GetTimeStr(expiration_time
);
447 if (expiration_time
.is_null()) {
448 token_expired
= false;
449 expiration_time_string
= "Expiration time not available";
451 std::string status_str
= "";
453 status_str
= "<p style=\"color: #ffffff; background-color: #ff0000\">";
454 base::StringAppendF(&status_str
, "Received token at %s. Expire at %s",
455 GetTimeStr(receive_time
).c_str(),
456 expiration_time_string
.c_str());
458 base::StringAppendF(&status_str
, "</p>");
459 token_info
->SetString("status", status_str
);
461 token_info
->SetString(
463 base::StringPrintf("Failure: %s", error
.ToString().c_str()));
466 token_info
->SetString("status", "Waiting for response");
469 return token_info
.release();
472 AboutSigninInternals::SigninStatus::SigninStatus()
473 : timed_signin_fields(TIMED_FIELDS_COUNT
) {}
475 AboutSigninInternals::SigninStatus::~SigninStatus() {
476 for (TokenInfoMap::iterator it
= token_info_map
.begin();
477 it
!= token_info_map
.end();
479 STLDeleteElements(&it
->second
);
483 AboutSigninInternals::TokenInfo
* AboutSigninInternals::SigninStatus::FindToken(
484 const std::string
& account_id
,
485 const std::string
& consumer_id
,
486 const OAuth2TokenService::ScopeSet
& scopes
) {
487 for (size_t i
= 0; i
< token_info_map
[account_id
].size(); ++i
) {
488 TokenInfo
* tmp
= token_info_map
[account_id
][i
];
489 if (tmp
->consumer_id
== consumer_id
&& tmp
->scopes
== scopes
)
495 scoped_ptr
<base::DictionaryValue
> AboutSigninInternals::SigninStatus::ToValue(
496 AccountTrackerService
* account_tracker
,
497 SigninManagerBase
* signin_manager
,
498 SigninErrorController
* signin_error_controller
,
499 ProfileOAuth2TokenService
* token_service
,
500 const std::string
& product_version
) {
501 // TODO(robliao): Remove ScopedTracker below once https://crbug.com/422460 is
503 tracked_objects::ScopedTracker
tracking_profile1(
504 FROM_HERE_WITH_EXPLICIT_FUNCTION(
505 "422460 AboutSigninInternals::SigninStatus::ToValue1"));
507 scoped_ptr
<base::DictionaryValue
> signin_status(new base::DictionaryValue());
508 base::ListValue
* signin_info
= new base::ListValue();
509 signin_status
->Set("signin_info", signin_info
);
511 // A summary of signin related info first.
512 base::ListValue
* basic_info
= AddSection(signin_info
, "Basic Information");
513 AddSectionEntry(basic_info
, "Chrome Version", product_version
);
514 AddSectionEntry(basic_info
, "Webview Based Signin?",
515 switches::IsEnableWebviewBasedSignin() == true ? "On" : "Off");
516 AddSectionEntry(basic_info
, "New Avatar Menu?",
517 switches::IsNewAvatarMenu() == true ? "On" : "Off");
518 AddSectionEntry(basic_info
, "New Profile Management?",
519 switches::IsNewProfileManagement() == true ? "On" : "Off");
520 AddSectionEntry(basic_info
, "Account Consistency?",
521 switches::IsEnableAccountConsistency() == true ? "On" : "Off");
522 AddSectionEntry(basic_info
, "Signin Status",
523 signin_manager
->IsAuthenticated() ? "Signed In" : "Not Signed In");
525 // TODO(robliao): Remove ScopedTracker below once https://crbug.com/422460 is
527 tracked_objects::ScopedTracker
tracking_profile2(
528 FROM_HERE_WITH_EXPLICIT_FUNCTION(
529 "422460 AboutSigninInternals::SigninStatus::ToValue2"));
531 if (signin_manager
->IsAuthenticated()) {
532 std::string account_id
= signin_manager
->GetAuthenticatedAccountId();
533 AddSectionEntry(basic_info
,
534 SigninStatusFieldToLabel(
535 static_cast<UntimedSigninStatusField
>(ACCOUNT_ID
)),
537 AddSectionEntry(basic_info
,
538 SigninStatusFieldToLabel(
539 static_cast<UntimedSigninStatusField
>(GAIA_ID
)),
540 account_tracker
->GetAccountInfo(account_id
).gaia
);
541 AddSectionEntry(basic_info
,
542 SigninStatusFieldToLabel(
543 static_cast<UntimedSigninStatusField
>(USERNAME
)),
544 signin_manager
->GetAuthenticatedAccountInfo().email
);
545 if (signin_error_controller
->HasError()) {
546 const std::string error_account_id
=
547 signin_error_controller
->error_account_id();
548 const std::string error_username
=
549 account_tracker
->GetAccountInfo(error_account_id
).email
;
550 AddSectionEntry(basic_info
, "Auth Error",
551 signin_error_controller
->auth_error().ToString());
552 AddSectionEntry(basic_info
, "Auth Error Account Id", error_account_id
);
553 AddSectionEntry(basic_info
, "Auth Error Username", error_username
);
555 AddSectionEntry(basic_info
, "Auth Error", "None");
559 #if !defined(OS_CHROMEOS)
560 // TODO(robliao): Remove ScopedTracker below once https://crbug.com/422460 is
562 tracked_objects::ScopedTracker
tracking_profile3(
563 FROM_HERE_WITH_EXPLICIT_FUNCTION(
564 "422460 AboutSigninInternals::SigninStatus::ToValue3"));
566 // Time and status information of the possible sign in types.
567 base::ListValue
* detailed_info
=
568 AddSection(signin_info
, "Last Signin Details");
569 for (int i
= TIMED_FIELDS_BEGIN
; i
< TIMED_FIELDS_END
; ++i
) {
570 const std::string status_field_label
=
571 SigninStatusFieldToLabel(static_cast<TimedSigninStatusField
>(i
));
573 AddSectionEntry(detailed_info
,
575 timed_signin_fields
[i
- TIMED_FIELDS_BEGIN
].first
,
576 timed_signin_fields
[i
- TIMED_FIELDS_BEGIN
].second
);
578 #endif // !defined(OS_CHROMEOS)
580 // TODO(robliao): Remove ScopedTracker below once https://crbug.com/422460 is
582 tracked_objects::ScopedTracker
tracking_profile4(
583 FROM_HERE_WITH_EXPLICIT_FUNCTION(
584 "422460 AboutSigninInternals::SigninStatus::ToValue4"));
586 // Token information for all services.
587 base::ListValue
* token_info
= new base::ListValue();
588 signin_status
->Set("token_info", token_info
);
589 for (TokenInfoMap::iterator it
= token_info_map
.begin();
590 it
!= token_info_map
.end();
592 // TODO(robliao): Remove ScopedTracker below once https://crbug.com/422460
594 tracked_objects::ScopedTracker
tracking_profile41(
595 FROM_HERE_WITH_EXPLICIT_FUNCTION(
596 "422460 AboutSigninInternals::SigninStatus::ToValue41"));
598 base::ListValue
* token_details
= AddSection(token_info
, it
->first
);
600 // TODO(robliao): Remove ScopedTracker below once https://crbug.com/422460
602 tracked_objects::ScopedTracker
tracking_profile42(
603 FROM_HERE_WITH_EXPLICIT_FUNCTION(
604 "422460 AboutSigninInternals::SigninStatus::ToValue42"));
606 std::sort(it
->second
.begin(), it
->second
.end(), TokenInfo::LessThan
);
607 const std::vector
<TokenInfo
*>& tokens
= it
->second
;
609 // TODO(robliao): Remove ScopedTracker below once https://crbug.com/422460
611 tracked_objects::ScopedTracker
tracking_profile43(
612 FROM_HERE_WITH_EXPLICIT_FUNCTION(
613 "422460 AboutSigninInternals::SigninStatus::ToValue43"));
615 for (size_t i
= 0; i
< tokens
.size(); ++i
) {
616 base::DictionaryValue
* token_info
= tokens
[i
]->ToValue();
617 token_details
->Append(token_info
);
621 base::ListValue
* account_info
= new base::ListValue();
622 signin_status
->Set("accountInfo", account_info
);
623 const std::vector
<std::string
>& accounts_in_token_service
=
624 token_service
->GetAccounts();
626 if(accounts_in_token_service
.size() == 0) {
627 base::DictionaryValue
* no_token_entry
= new base::DictionaryValue();
628 no_token_entry
->SetString("accountId", "No token in Token Service.");
629 account_info
->Append(no_token_entry
);
632 for(const std::string
& account_id
: accounts_in_token_service
) {
633 base::DictionaryValue
* entry
= new base::DictionaryValue();
634 entry
->SetString("accountId", account_id
);
635 account_info
->Append(entry
);
638 return signin_status
.Pass();