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/signin/core/browser/account_tracker_service.h"
17 #include "components/signin/core/browser/profile_oauth2_token_service.h"
18 #include "components/signin/core/browser/signin_client.h"
19 #include "components/signin/core/browser/signin_internals_util.h"
20 #include "components/signin/core/browser/signin_manager.h"
21 #include "components/signin/core/common/profile_management_switches.h"
22 #include "components/signin/core/common/signin_switches.h"
23 #include "google_apis/gaia/gaia_auth_fetcher.h"
24 #include "google_apis/gaia/gaia_auth_util.h"
25 #include "google_apis/gaia/gaia_constants.h"
26 #include "google_apis/gaia/gaia_urls.h"
27 #include "net/cookies/canonical_cookie.h"
30 using namespace signin_internals_util
;
34 std::string
GetTimeStr(base::Time time
) {
35 return base::UTF16ToUTF8(base::TimeFormatShortDateAndTime(time
));
38 base::ListValue
* AddSection(base::ListValue
* parent_list
,
39 const std::string
& title
) {
40 scoped_ptr
<base::DictionaryValue
> section(new base::DictionaryValue());
41 base::ListValue
* section_contents
= new base::ListValue();
43 section
->SetString("title", title
);
44 section
->Set("data", section_contents
);
45 parent_list
->Append(section
.release());
46 return section_contents
;
49 void AddSectionEntry(base::ListValue
* section_list
,
50 const std::string
& field_name
,
51 const std::string
& field_status
,
52 const std::string
& field_time
= "") {
53 scoped_ptr
<base::DictionaryValue
> entry(new base::DictionaryValue());
54 entry
->SetString("label", field_name
);
55 entry
->SetString("status", field_status
);
56 entry
->SetString("time", field_time
);
57 section_list
->Append(entry
.release());
60 void AddCookieEntry(base::ListValue
* accounts_list
,
61 const std::string
& field_email
,
62 const std::string
& field_valid
) {
63 scoped_ptr
<base::DictionaryValue
> entry(new base::DictionaryValue());
64 entry
->SetString("email", field_email
);
65 entry
->SetString("valid", field_valid
);
66 accounts_list
->Append(entry
.release());
69 std::string
SigninStatusFieldToLabel(UntimedSigninStatusField field
) {
77 case UNTIMED_FIELDS_END
:
85 #if !defined (OS_CHROMEOS)
86 std::string
SigninStatusFieldToLabel(TimedSigninStatusField field
) {
88 case AUTHENTICATION_RESULT_RECEIVED
:
89 return "Gaia Authentication Result";
90 case REFRESH_TOKEN_RECEIVED
:
91 return "RefreshToken Received";
93 return "SigninManager Started";
94 case SIGNIN_COMPLETED
:
95 return "SigninManager Completed";
96 case TIMED_FIELDS_END
:
103 #endif // !defined (OS_CHROMEOS)
105 void SetPref(PrefService
* prefs
,
106 TimedSigninStatusField field
,
107 const std::string
& time
,
108 const std::string
& value
) {
109 std::string value_pref
= SigninStatusFieldToString(field
) + ".value";
110 std::string time_pref
= SigninStatusFieldToString(field
) + ".time";
111 prefs
->SetString(value_pref
, value
);
112 prefs
->SetString(time_pref
, time
);
115 void GetPref(PrefService
* prefs
,
116 TimedSigninStatusField field
,
118 std::string
* value
) {
119 std::string value_pref
= SigninStatusFieldToString(field
) + ".value";
120 std::string time_pref
= SigninStatusFieldToString(field
) + ".time";
121 *value
= prefs
->GetString(value_pref
);
122 *time
= prefs
->GetString(time_pref
);
125 void ClearPref(PrefService
* prefs
, TimedSigninStatusField field
) {
126 std::string value_pref
= SigninStatusFieldToString(field
) + ".value";
127 std::string time_pref
= SigninStatusFieldToString(field
) + ".time";
128 prefs
->ClearPref(value_pref
);
129 prefs
->ClearPref(time_pref
);
132 } // anonymous namespace
134 AboutSigninInternals::AboutSigninInternals(
135 ProfileOAuth2TokenService
* token_service
,
136 AccountTrackerService
* account_tracker
,
137 SigninManagerBase
* signin_manager
)
138 : token_service_(token_service
),
139 account_tracker_(account_tracker
),
140 signin_manager_(signin_manager
),
143 AboutSigninInternals::~AboutSigninInternals() {}
145 void AboutSigninInternals::AddSigninObserver(
146 AboutSigninInternals::Observer
* observer
) {
147 signin_observers_
.AddObserver(observer
);
150 void AboutSigninInternals::RemoveSigninObserver(
151 AboutSigninInternals::Observer
* observer
) {
152 signin_observers_
.RemoveObserver(observer
);
155 void AboutSigninInternals::NotifySigninValueChanged(
156 const TimedSigninStatusField
& field
,
157 const std::string
& value
) {
158 unsigned int field_index
= field
- TIMED_FIELDS_BEGIN
;
159 DCHECK(field_index
>= 0 &&
160 field_index
< signin_status_
.timed_signin_fields
.size());
162 Time now
= Time::NowFromSystemTime();
163 std::string time_as_str
=
164 base::UTF16ToUTF8(base::TimeFormatShortDateAndTime(now
));
165 TimedSigninStatusValue
timed_value(value
, time_as_str
);
167 signin_status_
.timed_signin_fields
[field_index
] = timed_value
;
169 // Also persist these values in the prefs.
170 SetPref(client_
->GetPrefs(), field
, value
, time_as_str
);
172 // If the user is restarting a sign in process, clear the fields that are
174 if (field
== AUTHENTICATION_RESULT_RECEIVED
) {
175 ClearPref(client_
->GetPrefs(), REFRESH_TOKEN_RECEIVED
);
176 ClearPref(client_
->GetPrefs(), SIGNIN_STARTED
);
177 ClearPref(client_
->GetPrefs(), SIGNIN_COMPLETED
);
183 void AboutSigninInternals::RefreshSigninPrefs() {
184 // Return if no client exists. Can occur in unit tests.
188 PrefService
* pref_service
= client_
->GetPrefs();
189 for (int i
= TIMED_FIELDS_BEGIN
; i
< TIMED_FIELDS_END
; ++i
) {
190 std::string time_str
;
191 std::string value_str
;
192 GetPref(pref_service
, static_cast<TimedSigninStatusField
>(i
),
193 &time_str
, &value_str
);
194 TimedSigninStatusValue
value(value_str
, time_str
);
195 signin_status_
.timed_signin_fields
[i
- TIMED_FIELDS_BEGIN
] = value
;
198 // TODO(rogerta): Get status and timestamps for oauth2 tokens.
203 void AboutSigninInternals::Initialize(SigninClient
* client
) {
207 RefreshSigninPrefs();
209 signin_manager_
->AddSigninDiagnosticsObserver(this);
210 token_service_
->AddDiagnosticsObserver(this);
211 cookie_changed_subscription_
= client_
->AddCookieChangedCallback(
212 GaiaUrls::GetInstance()->gaia_url(),
214 base::Bind(&AboutSigninInternals::OnCookieChanged
,
215 base::Unretained(this)));
218 void AboutSigninInternals::Shutdown() {
219 signin_manager_
->RemoveSigninDiagnosticsObserver(this);
220 token_service_
->RemoveDiagnosticsObserver(this);
221 cookie_changed_subscription_
.reset();
224 void AboutSigninInternals::NotifyObservers() {
225 if (!signin_observers_
.might_have_observers())
228 // TODO(robliao): Remove ScopedTracker below once https://crbug.com/422460 is
230 tracked_objects::ScopedTracker
tracking_profile(
231 FROM_HERE_WITH_EXPLICIT_FUNCTION(
232 "422460 AboutSigninInternals::NotifyObservers"));
234 const std::string product_version
= client_
->GetProductVersion();
236 // TODO(robliao): Remove ScopedTracker below once https://crbug.com/422460 is
238 tracked_objects::ScopedTracker
tracking_profile05(
239 FROM_HERE_WITH_EXPLICIT_FUNCTION(
240 "422460 AboutSigninInternals::NotifyObservers 0.5"));
242 scoped_ptr
<base::DictionaryValue
> signin_status_value
=
243 signin_status_
.ToValue(account_tracker_
, signin_manager_
,
246 // TODO(robliao): Remove ScopedTracker below once https://crbug.com/422460 is
248 tracked_objects::ScopedTracker
tracking_profile1(
249 FROM_HERE_WITH_EXPLICIT_FUNCTION(
250 "422460 AboutSigninInternals::NotifyObservers1"));
252 FOR_EACH_OBSERVER(AboutSigninInternals::Observer
,
254 OnSigninStateChanged(signin_status_value
.get()));
257 scoped_ptr
<base::DictionaryValue
> AboutSigninInternals::GetSigninStatus() {
258 return signin_status_
.ToValue(account_tracker_
, signin_manager_
,
259 client_
->GetProductVersion()).Pass();
262 void AboutSigninInternals::OnAccessTokenRequested(
263 const std::string
& account_id
,
264 const std::string
& consumer_id
,
265 const OAuth2TokenService::ScopeSet
& scopes
) {
266 // TODO(robliao): Remove ScopedTracker below once https://crbug.com/422460 is
268 tracked_objects::ScopedTracker
tracking_profile(
269 FROM_HERE_WITH_EXPLICIT_FUNCTION(
270 "422460 AboutSigninInternals::OnAccessTokenRequested"));
272 TokenInfo
* token
= signin_status_
.FindToken(account_id
, consumer_id
, scopes
);
274 *token
= TokenInfo(consumer_id
, scopes
);
276 token
= new TokenInfo(consumer_id
, scopes
);
277 signin_status_
.token_info_map
[account_id
].push_back(token
);
283 void AboutSigninInternals::OnFetchAccessTokenComplete(
284 const std::string
& account_id
,
285 const std::string
& consumer_id
,
286 const OAuth2TokenService::ScopeSet
& scopes
,
287 GoogleServiceAuthError error
,
288 base::Time expiration_time
) {
289 TokenInfo
* token
= signin_status_
.FindToken(account_id
, consumer_id
, scopes
);
291 DVLOG(1) << "Can't find token: " << account_id
<< ", " << consumer_id
;
295 token
->receive_time
= base::Time::Now();
296 token
->error
= error
;
297 token
->expiration_time
= expiration_time
;
302 void AboutSigninInternals::OnTokenRemoved(
303 const std::string
& account_id
,
304 const OAuth2TokenService::ScopeSet
& scopes
) {
305 for (size_t i
= 0; i
< signin_status_
.token_info_map
[account_id
].size();
307 TokenInfo
* token
= signin_status_
.token_info_map
[account_id
][i
];
308 if (token
->scopes
== scopes
)
314 void AboutSigninInternals::OnRefreshTokenReceived(std::string status
) {
315 NotifySigninValueChanged(REFRESH_TOKEN_RECEIVED
, status
);
318 void AboutSigninInternals::OnAuthenticationResultReceived(std::string status
) {
319 NotifySigninValueChanged(AUTHENTICATION_RESULT_RECEIVED
, status
);
322 void AboutSigninInternals::OnCookieChanged(const net::CanonicalCookie
& cookie
,
324 DCHECK_EQ("LSID", cookie
.Name());
325 DCHECK_EQ(GaiaUrls::GetInstance()->gaia_url().host(), cookie
.Domain());
326 if (cookie
.IsSecure() && cookie
.IsHttpOnly()) {
327 GetCookieAccountsAsync();
331 void AboutSigninInternals::GetCookieAccountsAsync() {
332 // Don't bother calling /ListAccounts if no one will observe the response.
333 if (!gaia_fetcher_
&& signin_observers_
.might_have_observers()) {
334 // There is no list account request in flight.
335 gaia_fetcher_
.reset(new GaiaAuthFetcher(
336 this, GaiaConstants::kChromeSource
, client_
->GetURLRequestContext()));
337 gaia_fetcher_
->StartListAccounts();
341 void AboutSigninInternals::OnListAccountsSuccess(const std::string
& data
) {
342 gaia_fetcher_
.reset();
344 // Get account information from response data.
345 std::vector
<std::pair
<std::string
, bool> > gaia_accounts
;
346 bool valid_json
= gaia::ParseListAccountsData(data
, &gaia_accounts
);
348 VLOG(1) << "AboutSigninInternals::OnListAccountsSuccess: parsing error";
350 OnListAccountsComplete(gaia_accounts
);
354 void AboutSigninInternals::OnListAccountsFailure(
355 const GoogleServiceAuthError
& error
) {
356 gaia_fetcher_
.reset();
357 VLOG(1) << "AboutSigninInternals::OnListAccountsFailure:" << error
.ToString();
360 void AboutSigninInternals::GoogleSigninFailed(
361 const GoogleServiceAuthError
& error
) {
365 void AboutSigninInternals::GoogleSigninSucceeded(const std::string
& account_id
,
366 const std::string
& username
,
367 const std::string
& password
) {
371 void AboutSigninInternals::GoogleSignedOut(const std::string
& account_id
,
372 const std::string
& username
) {
376 void AboutSigninInternals::OnListAccountsComplete(
377 std::vector
<std::pair
<std::string
, bool> >& gaia_accounts
) {
378 base::DictionaryValue cookie_status
;
379 base::ListValue
* cookie_info
= new base::ListValue();
380 cookie_status
.Set("cookie_info", cookie_info
);
382 for (size_t i
= 0; i
< gaia_accounts
.size(); ++i
) {
383 AddCookieEntry(cookie_info
,
384 gaia_accounts
[i
].first
,
385 gaia_accounts
[i
].second
? "Valid" : "Invalid");
388 if (gaia_accounts
.size() == 0)
389 AddCookieEntry(cookie_info
, "No Accounts Present.", "");
391 // Update the observers that the cookie's accounts are updated.
392 FOR_EACH_OBSERVER(AboutSigninInternals::Observer
,
394 OnCookieAccountsFetched(&cookie_status
));
397 AboutSigninInternals::TokenInfo::TokenInfo(
398 const std::string
& consumer_id
,
399 const OAuth2TokenService::ScopeSet
& scopes
)
400 : consumer_id(consumer_id
),
402 request_time(base::Time::Now()),
403 error(GoogleServiceAuthError::AuthErrorNone()),
406 AboutSigninInternals::TokenInfo::~TokenInfo() {}
408 bool AboutSigninInternals::TokenInfo::LessThan(const TokenInfo
* a
,
409 const TokenInfo
* b
) {
410 return a
->consumer_id
< b
->consumer_id
||
411 (a
->consumer_id
== b
->consumer_id
&& a
->scopes
< b
->scopes
);
414 void AboutSigninInternals::TokenInfo::Invalidate() { removed_
= true; }
416 base::DictionaryValue
* AboutSigninInternals::TokenInfo::ToValue() const {
417 scoped_ptr
<base::DictionaryValue
> token_info(new base::DictionaryValue());
418 token_info
->SetString("service", consumer_id
);
420 std::string scopes_str
;
421 for (OAuth2TokenService::ScopeSet::const_iterator it
= scopes
.begin();
424 scopes_str
+= *it
+ "<br/>";
426 token_info
->SetString("scopes", scopes_str
);
427 token_info
->SetString("request_time", GetTimeStr(request_time
).c_str());
430 token_info
->SetString("status", "Token was revoked.");
431 } else if (!receive_time
.is_null()) {
432 if (error
== GoogleServiceAuthError::AuthErrorNone()) {
433 bool token_expired
= expiration_time
< base::Time::Now();
434 std::string status_str
= "";
436 status_str
= "<p style=\"color: #ffffff; background-color: #ff0000\">";
437 base::StringAppendF(&status_str
,
438 "Received token at %s. Expire at %s",
439 GetTimeStr(receive_time
).c_str(),
440 GetTimeStr(expiration_time
).c_str());
442 base::StringAppendF(&status_str
, "</p>");
443 token_info
->SetString("status", status_str
);
445 token_info
->SetString(
447 base::StringPrintf("Failure: %s", error
.ToString().c_str()));
450 token_info
->SetString("status", "Waiting for response");
453 return token_info
.release();
456 AboutSigninInternals::SigninStatus::SigninStatus()
457 : timed_signin_fields(TIMED_FIELDS_COUNT
) {}
459 AboutSigninInternals::SigninStatus::~SigninStatus() {
460 for (TokenInfoMap::iterator it
= token_info_map
.begin();
461 it
!= token_info_map
.end();
463 STLDeleteElements(&it
->second
);
467 AboutSigninInternals::TokenInfo
* AboutSigninInternals::SigninStatus::FindToken(
468 const std::string
& account_id
,
469 const std::string
& consumer_id
,
470 const OAuth2TokenService::ScopeSet
& scopes
) {
471 for (size_t i
= 0; i
< token_info_map
[account_id
].size(); ++i
) {
472 TokenInfo
* tmp
= token_info_map
[account_id
][i
];
473 if (tmp
->consumer_id
== consumer_id
&& tmp
->scopes
== scopes
)
479 scoped_ptr
<base::DictionaryValue
> AboutSigninInternals::SigninStatus::ToValue(
480 AccountTrackerService
* account_tracker
,
481 SigninManagerBase
* signin_manager
,
482 const std::string
& product_version
) {
483 // TODO(robliao): Remove ScopedTracker below once https://crbug.com/422460 is
485 tracked_objects::ScopedTracker
tracking_profile1(
486 FROM_HERE_WITH_EXPLICIT_FUNCTION(
487 "422460 AboutSigninInternals::SigninStatus::ToValue1"));
489 scoped_ptr
<base::DictionaryValue
> signin_status(new base::DictionaryValue());
490 base::ListValue
* signin_info
= new base::ListValue();
491 signin_status
->Set("signin_info", signin_info
);
493 // A summary of signin related info first.
494 base::ListValue
* basic_info
= AddSection(signin_info
, "Basic Information");
495 AddSectionEntry(basic_info
, "Chrome Version", product_version
);
496 AddSectionEntry(basic_info
, "Webview Based Signin?",
497 switches::IsEnableWebviewBasedSignin() == true ? "On" : "Off");
498 AddSectionEntry(basic_info
, "New Avatar Menu?",
499 switches::IsNewAvatarMenu() == true ? "On" : "Off");
500 AddSectionEntry(basic_info
, "New Profile Management?",
501 switches::IsNewProfileManagement() == true ? "On" : "Off");
502 AddSectionEntry(basic_info
, "Account Consistency?",
503 switches::IsEnableAccountConsistency() == true ? "On" : "Off");
504 AddSectionEntry(basic_info
, "Signin Status",
505 signin_manager
->IsAuthenticated() ? "Signed In" : "Not Signed In");
507 // TODO(robliao): Remove ScopedTracker below once https://crbug.com/422460 is
509 tracked_objects::ScopedTracker
tracking_profile2(
510 FROM_HERE_WITH_EXPLICIT_FUNCTION(
511 "422460 AboutSigninInternals::SigninStatus::ToValue2"));
513 if (signin_manager
->IsAuthenticated()) {
514 std::string account_id
= signin_manager
->GetAuthenticatedAccountId();
515 AddSectionEntry(basic_info
,
516 SigninStatusFieldToLabel(
517 static_cast<UntimedSigninStatusField
>(ACCOUNT_ID
)),
519 AddSectionEntry(basic_info
,
520 SigninStatusFieldToLabel(
521 static_cast<UntimedSigninStatusField
>(GAIA_ID
)),
522 account_tracker
->GetAccountInfo(account_id
).gaia
);
523 AddSectionEntry(basic_info
,
524 SigninStatusFieldToLabel(
525 static_cast<UntimedSigninStatusField
>(USERNAME
)),
526 signin_manager
->GetAuthenticatedUsername());
529 #if !defined(OS_CHROMEOS)
530 // TODO(robliao): Remove ScopedTracker below once https://crbug.com/422460 is
532 tracked_objects::ScopedTracker
tracking_profile3(
533 FROM_HERE_WITH_EXPLICIT_FUNCTION(
534 "422460 AboutSigninInternals::SigninStatus::ToValue3"));
536 // Time and status information of the possible sign in types.
537 base::ListValue
* detailed_info
=
538 AddSection(signin_info
, "Last Signin Details");
539 for (int i
= TIMED_FIELDS_BEGIN
; i
< TIMED_FIELDS_END
; ++i
) {
540 const std::string status_field_label
=
541 SigninStatusFieldToLabel(static_cast<TimedSigninStatusField
>(i
));
543 AddSectionEntry(detailed_info
,
545 timed_signin_fields
[i
- TIMED_FIELDS_BEGIN
].first
,
546 timed_signin_fields
[i
- TIMED_FIELDS_BEGIN
].second
);
548 #endif // !defined(OS_CHROMEOS)
550 // TODO(robliao): Remove ScopedTracker below once https://crbug.com/422460 is
552 tracked_objects::ScopedTracker
tracking_profile4(
553 FROM_HERE_WITH_EXPLICIT_FUNCTION(
554 "422460 AboutSigninInternals::SigninStatus::ToValue4"));
556 // Token information for all services.
557 base::ListValue
* token_info
= new base::ListValue();
558 signin_status
->Set("token_info", token_info
);
559 for (TokenInfoMap::iterator it
= token_info_map
.begin();
560 it
!= token_info_map
.end();
562 // TODO(robliao): Remove ScopedTracker below once https://crbug.com/422460
564 tracked_objects::ScopedTracker
tracking_profile41(
565 FROM_HERE_WITH_EXPLICIT_FUNCTION(
566 "422460 AboutSigninInternals::SigninStatus::ToValue41"));
568 base::ListValue
* token_details
= AddSection(token_info
, it
->first
);
570 // TODO(robliao): Remove ScopedTracker below once https://crbug.com/422460
572 tracked_objects::ScopedTracker
tracking_profile42(
573 FROM_HERE_WITH_EXPLICIT_FUNCTION(
574 "422460 AboutSigninInternals::SigninStatus::ToValue42"));
576 std::sort(it
->second
.begin(), it
->second
.end(), TokenInfo::LessThan
);
577 const std::vector
<TokenInfo
*>& tokens
= it
->second
;
579 // TODO(robliao): Remove ScopedTracker below once https://crbug.com/422460
581 tracked_objects::ScopedTracker
tracking_profile43(
582 FROM_HERE_WITH_EXPLICIT_FUNCTION(
583 "422460 AboutSigninInternals::SigninStatus::ToValue43"));
585 for (size_t i
= 0; i
< tokens
.size(); ++i
) {
586 base::DictionaryValue
* token_info
= tokens
[i
]->ToValue();
587 token_details
->Append(token_info
);
591 return signin_status
.Pass();