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"
8 #include "base/debug/trace_event.h"
10 #include "base/i18n/time_formatting.h"
11 #include "base/logging.h"
12 #include "base/prefs/pref_service.h"
13 #include "base/profiler/scoped_tracker.h"
14 #include "base/strings/stringprintf.h"
15 #include "base/strings/utf_string_conversions.h"
16 #include "components/signin/core/browser/profile_oauth2_token_service.h"
17 #include "components/signin/core/browser/signin_client.h"
18 #include "components/signin/core/browser/signin_internals_util.h"
19 #include "components/signin/core/browser/signin_manager.h"
20 #include "components/signin/core/common/profile_management_switches.h"
21 #include "components/signin/core/common/signin_switches.h"
22 #include "google_apis/gaia/gaia_auth_fetcher.h"
23 #include "google_apis/gaia/gaia_auth_util.h"
24 #include "google_apis/gaia/gaia_constants.h"
25 #include "google_apis/gaia/gaia_urls.h"
26 #include "net/cookies/canonical_cookie.h"
29 using namespace signin_internals_util
;
33 std::string
GetTimeStr(base::Time time
) {
34 return base::UTF16ToUTF8(base::TimeFormatShortDateAndTime(time
));
37 base::ListValue
* AddSection(base::ListValue
* parent_list
,
38 const std::string
& title
) {
39 scoped_ptr
<base::DictionaryValue
> section(new base::DictionaryValue());
40 base::ListValue
* section_contents
= new base::ListValue();
42 section
->SetString("title", title
);
43 section
->Set("data", section_contents
);
44 parent_list
->Append(section
.release());
45 return section_contents
;
48 void AddSectionEntry(base::ListValue
* section_list
,
49 const std::string
& field_name
,
50 const std::string
& field_status
,
51 const std::string
& field_time
= "") {
52 scoped_ptr
<base::DictionaryValue
> entry(new base::DictionaryValue());
53 entry
->SetString("label", field_name
);
54 entry
->SetString("status", field_status
);
55 entry
->SetString("time", field_time
);
56 section_list
->Append(entry
.release());
59 void AddCookieEntry(base::ListValue
* accounts_list
,
60 const std::string
& field_email
,
61 const std::string
& field_valid
) {
62 scoped_ptr
<base::DictionaryValue
> entry(new base::DictionaryValue());
63 entry
->SetString("email", field_email
);
64 entry
->SetString("valid", field_valid
);
65 accounts_list
->Append(entry
.release());
68 std::string
SigninStatusFieldToLabel(UntimedSigninStatusField field
) {
72 case UNTIMED_FIELDS_END
:
80 #if !defined (OS_CHROMEOS)
81 std::string
SigninStatusFieldToLabel(TimedSigninStatusField field
) {
85 case AUTHENTICATION_RESULT_RECEIVED
:
86 return "Last Authentication Result Received";
87 case REFRESH_TOKEN_RECEIVED
:
88 return "Last RefreshToken Received";
89 case GET_USER_INFO_STATUS
:
90 return "Last OnGetUserInfo Received";
91 case UBER_TOKEN_STATUS
:
92 return "Last OnUberToken Received";
93 case MERGE_SESSION_STATUS
:
94 return "Last OnMergeSession Received";
95 case TIMED_FIELDS_END
:
102 #endif // !defined (OS_CHROMEOS)
104 } // anonymous namespace
106 AboutSigninInternals::AboutSigninInternals(
107 ProfileOAuth2TokenService
* token_service
,
108 SigninManagerBase
* signin_manager
)
109 : token_service_(token_service
),
110 signin_manager_(signin_manager
),
113 AboutSigninInternals::~AboutSigninInternals() {}
115 void AboutSigninInternals::AddSigninObserver(
116 AboutSigninInternals::Observer
* observer
) {
117 signin_observers_
.AddObserver(observer
);
120 void AboutSigninInternals::RemoveSigninObserver(
121 AboutSigninInternals::Observer
* observer
) {
122 signin_observers_
.RemoveObserver(observer
);
125 void AboutSigninInternals::NotifySigninValueChanged(
126 const UntimedSigninStatusField
& field
,
127 const std::string
& value
) {
128 unsigned int field_index
= field
- UNTIMED_FIELDS_BEGIN
;
129 DCHECK(field_index
>= 0 &&
130 field_index
< signin_status_
.untimed_signin_fields
.size());
132 signin_status_
.untimed_signin_fields
[field_index
] = value
;
134 // Also persist these values in the prefs.
135 const std::string pref_path
= SigninStatusFieldToString(field
);
136 client_
->GetPrefs()->SetString(pref_path
.c_str(), value
);
141 void AboutSigninInternals::NotifySigninValueChanged(
142 const TimedSigninStatusField
& field
,
143 const std::string
& value
) {
144 unsigned int field_index
= field
- TIMED_FIELDS_BEGIN
;
145 DCHECK(field_index
>= 0 &&
146 field_index
< signin_status_
.timed_signin_fields
.size());
148 Time now
= Time::NowFromSystemTime();
149 std::string time_as_str
=
150 base::UTF16ToUTF8(base::TimeFormatShortDateAndTime(now
));
151 TimedSigninStatusValue
timed_value(value
, time_as_str
);
153 signin_status_
.timed_signin_fields
[field_index
] = timed_value
;
155 // Also persist these values in the prefs.
156 const std::string value_pref
= SigninStatusFieldToString(field
) + ".value";
157 const std::string time_pref
= SigninStatusFieldToString(field
) + ".time";
158 client_
->GetPrefs()->SetString(value_pref
.c_str(), value
);
159 client_
->GetPrefs()->SetString(time_pref
.c_str(), time_as_str
);
164 void AboutSigninInternals::RefreshSigninPrefs() {
165 // Since the AboutSigninInternals has a dependency on the SigninManager
166 // (as seen in the AboutSigninInternalsFactory) the SigninManager can have
167 // the AuthenticatedUsername set before AboutSigninInternals can observe it.
168 // For that scenario, read the AuthenticatedUsername if it exists.
169 if (signin_manager_
->IsAuthenticated()) {
170 signin_status_
.untimed_signin_fields
[USERNAME
] =
171 signin_manager_
->GetAuthenticatedUsername();
174 // Return if no client exists. Can occur in unit tests.
178 PrefService
* pref_service
= client_
->GetPrefs();
179 for (int i
= UNTIMED_FIELDS_BEGIN
; i
< UNTIMED_FIELDS_END
; ++i
) {
180 const std::string pref_path
=
181 SigninStatusFieldToString(static_cast<UntimedSigninStatusField
>(i
));
183 signin_status_
.untimed_signin_fields
[i
- UNTIMED_FIELDS_BEGIN
] =
184 pref_service
->GetString(pref_path
.c_str());
186 for (int i
= TIMED_FIELDS_BEGIN
; i
< TIMED_FIELDS_END
; ++i
) {
187 const std::string value_pref
=
188 SigninStatusFieldToString(static_cast<TimedSigninStatusField
>(i
)) +
190 const std::string time_pref
=
191 SigninStatusFieldToString(static_cast<TimedSigninStatusField
>(i
)) +
194 TimedSigninStatusValue
value(pref_service
->GetString(value_pref
.c_str()),
195 pref_service
->GetString(time_pref
.c_str()));
196 signin_status_
.timed_signin_fields
[i
- TIMED_FIELDS_BEGIN
] = value
;
199 // TODO(rogerta): Get status and timestamps for oauth2 tokens.
204 void AboutSigninInternals::Initialize(SigninClient
* client
) {
208 RefreshSigninPrefs();
210 signin_manager_
->AddSigninDiagnosticsObserver(this);
211 token_service_
->AddDiagnosticsObserver(this);
212 cookie_changed_subscription_
= client_
->AddCookieChangedCallback(
213 GaiaUrls::GetInstance()->gaia_url(),
215 base::Bind(&AboutSigninInternals::OnCookieChanged
,
216 base::Unretained(this)));
219 void AboutSigninInternals::Shutdown() {
220 signin_manager_
->RemoveSigninDiagnosticsObserver(this);
221 token_service_
->RemoveDiagnosticsObserver(this);
222 cookie_changed_subscription_
.reset();
225 void AboutSigninInternals::NotifyObservers() {
226 // TODO(vadimt): Remove ScopedTracker below once crbug.com/422460 is fixed.
227 tracked_objects::ScopedTracker
tracking_profile(
228 FROM_HERE_WITH_EXPLICIT_FUNCTION(
229 "422460 AboutSigninInternals::NotifyObservers"));
231 scoped_ptr
<base::DictionaryValue
> signin_status_value
=
232 signin_status_
.ToValue(client_
->GetProductVersion());
234 tracked_objects::ScopedTracker
tracking_profile1(
235 FROM_HERE_WITH_EXPLICIT_FUNCTION(
236 "422460 AboutSigninInternals::NotifyObservers1"));
238 FOR_EACH_OBSERVER(AboutSigninInternals::Observer
,
240 OnSigninStateChanged(signin_status_value
.get()));
243 scoped_ptr
<base::DictionaryValue
> AboutSigninInternals::GetSigninStatus() {
244 return signin_status_
.ToValue(client_
->GetProductVersion()).Pass();
247 void AboutSigninInternals::OnAccessTokenRequested(
248 const std::string
& account_id
,
249 const std::string
& consumer_id
,
250 const OAuth2TokenService::ScopeSet
& scopes
) {
251 // TODO(vadimt): Remove ScopedTracker below once crbug.com/422460 is fixed.
252 tracked_objects::ScopedTracker
tracking_profile(
253 FROM_HERE_WITH_EXPLICIT_FUNCTION(
254 "422460 AboutSigninInternals::OnAccessTokenRequested"));
256 TokenInfo
* token
= signin_status_
.FindToken(account_id
, consumer_id
, scopes
);
258 *token
= TokenInfo(consumer_id
, scopes
);
260 token
= new TokenInfo(consumer_id
, scopes
);
261 signin_status_
.token_info_map
[account_id
].push_back(token
);
267 void AboutSigninInternals::OnFetchAccessTokenComplete(
268 const std::string
& account_id
,
269 const std::string
& consumer_id
,
270 const OAuth2TokenService::ScopeSet
& scopes
,
271 GoogleServiceAuthError error
,
272 base::Time expiration_time
) {
273 TokenInfo
* token
= signin_status_
.FindToken(account_id
, consumer_id
, scopes
);
275 DVLOG(1) << "Can't find token: " << account_id
<< ", " << consumer_id
;
279 token
->receive_time
= base::Time::Now();
280 token
->error
= error
;
281 token
->expiration_time
= expiration_time
;
286 void AboutSigninInternals::OnTokenRemoved(
287 const std::string
& account_id
,
288 const OAuth2TokenService::ScopeSet
& scopes
) {
289 for (size_t i
= 0; i
< signin_status_
.token_info_map
[account_id
].size();
291 TokenInfo
* token
= signin_status_
.token_info_map
[account_id
][i
];
292 if (token
->scopes
== scopes
)
298 void AboutSigninInternals::OnRefreshTokenReceived(std::string status
) {
299 NotifySigninValueChanged(REFRESH_TOKEN_RECEIVED
, status
);
302 void AboutSigninInternals::OnAuthenticationResultReceived(std::string status
) {
303 NotifySigninValueChanged(AUTHENTICATION_RESULT_RECEIVED
, status
);
306 void AboutSigninInternals::OnCookieChanged(const net::CanonicalCookie
& cookie
,
308 DCHECK_EQ("LSID", cookie
.Name());
309 DCHECK_EQ(GaiaUrls::GetInstance()->gaia_url().host(), cookie
.Domain());
310 if (cookie
.IsSecure() && cookie
.IsHttpOnly()) {
311 GetCookieAccountsAsync();
315 void AboutSigninInternals::GetCookieAccountsAsync() {
316 // Don't bother calling /ListAccounts if no one will observe the response.
317 if (!gaia_fetcher_
&& signin_observers_
.might_have_observers()) {
318 // There is no list account request in flight.
319 gaia_fetcher_
.reset(new GaiaAuthFetcher(
320 this, GaiaConstants::kChromeSource
, client_
->GetURLRequestContext()));
321 gaia_fetcher_
->StartListAccounts();
325 void AboutSigninInternals::OnListAccountsSuccess(const std::string
& data
) {
326 gaia_fetcher_
.reset();
328 // Get account information from response data.
329 std::vector
<std::pair
<std::string
, bool> > gaia_accounts
;
330 bool valid_json
= gaia::ParseListAccountsData(data
, &gaia_accounts
);
332 VLOG(1) << "AboutSigninInternals::OnListAccountsSuccess: parsing error";
334 OnListAccountsComplete(gaia_accounts
);
338 void AboutSigninInternals::OnListAccountsFailure(
339 const GoogleServiceAuthError
& error
) {
340 gaia_fetcher_
.reset();
341 VLOG(1) << "AboutSigninInternals::OnListAccountsFailure:" << error
.ToString();
344 void AboutSigninInternals::OnListAccountsComplete(
345 std::vector
<std::pair
<std::string
, bool> >& gaia_accounts
) {
346 base::DictionaryValue signin_status
;
347 base::ListValue
* cookie_info
= new base::ListValue();
348 signin_status
.Set("cookie_info", cookie_info
);
350 for (size_t i
= 0; i
< gaia_accounts
.size(); ++i
) {
351 AddCookieEntry(cookie_info
,
352 gaia_accounts
[i
].first
,
353 gaia_accounts
[i
].second
? "Valid" : "Invalid");
356 if (gaia_accounts
.size() == 0)
357 AddCookieEntry(cookie_info
, "No Accounts Present.", "");
359 // Update the observers that the cookie's accounts are updated.
360 FOR_EACH_OBSERVER(AboutSigninInternals::Observer
,
362 OnCookieAccountsFetched(&signin_status
));
365 AboutSigninInternals::TokenInfo::TokenInfo(
366 const std::string
& consumer_id
,
367 const OAuth2TokenService::ScopeSet
& scopes
)
368 : consumer_id(consumer_id
),
370 request_time(base::Time::Now()),
371 error(GoogleServiceAuthError::AuthErrorNone()),
374 AboutSigninInternals::TokenInfo::~TokenInfo() {}
376 bool AboutSigninInternals::TokenInfo::LessThan(const TokenInfo
* a
,
377 const TokenInfo
* b
) {
378 return a
->consumer_id
< b
->consumer_id
||
379 (a
->consumer_id
== b
->consumer_id
&& a
->scopes
< b
->scopes
);
382 void AboutSigninInternals::TokenInfo::Invalidate() { removed_
= true; }
384 base::DictionaryValue
* AboutSigninInternals::TokenInfo::ToValue() const {
385 scoped_ptr
<base::DictionaryValue
> token_info(new base::DictionaryValue());
386 token_info
->SetString("service", consumer_id
);
388 std::string scopes_str
;
389 for (OAuth2TokenService::ScopeSet::const_iterator it
= scopes
.begin();
392 scopes_str
+= *it
+ "<br/>";
394 token_info
->SetString("scopes", scopes_str
);
395 token_info
->SetString("request_time", GetTimeStr(request_time
).c_str());
398 token_info
->SetString("status", "Token was revoked.");
399 } else if (!receive_time
.is_null()) {
400 if (error
== GoogleServiceAuthError::AuthErrorNone()) {
401 bool token_expired
= expiration_time
< base::Time::Now();
402 std::string status_str
= "";
404 status_str
= "<p style=\"color: #ffffff; background-color: #ff0000\">";
405 base::StringAppendF(&status_str
,
406 "Received token at %s. Expire at %s",
407 GetTimeStr(receive_time
).c_str(),
408 GetTimeStr(expiration_time
).c_str());
410 base::StringAppendF(&status_str
, "</p>");
411 token_info
->SetString("status", status_str
);
413 token_info
->SetString(
415 base::StringPrintf("Failure: %s", error
.ToString().c_str()));
418 token_info
->SetString("status", "Waiting for response");
421 return token_info
.release();
424 AboutSigninInternals::SigninStatus::SigninStatus()
425 : untimed_signin_fields(UNTIMED_FIELDS_COUNT
),
426 timed_signin_fields(TIMED_FIELDS_COUNT
) {}
428 AboutSigninInternals::SigninStatus::~SigninStatus() {
429 for (TokenInfoMap::iterator it
= token_info_map
.begin();
430 it
!= token_info_map
.end();
432 STLDeleteElements(&it
->second
);
436 AboutSigninInternals::TokenInfo
* AboutSigninInternals::SigninStatus::FindToken(
437 const std::string
& account_id
,
438 const std::string
& consumer_id
,
439 const OAuth2TokenService::ScopeSet
& scopes
) {
440 for (size_t i
= 0; i
< token_info_map
[account_id
].size(); ++i
) {
441 TokenInfo
* tmp
= token_info_map
[account_id
][i
];
442 if (tmp
->consumer_id
== consumer_id
&& tmp
->scopes
== scopes
)
448 scoped_ptr
<base::DictionaryValue
> AboutSigninInternals::SigninStatus::ToValue(
449 std::string product_version
) {
450 scoped_ptr
<base::DictionaryValue
> signin_status(new base::DictionaryValue());
451 base::ListValue
* signin_info
= new base::ListValue();
452 signin_status
->Set("signin_info", signin_info
);
454 // A summary of signin related info first.
455 base::ListValue
* basic_info
= AddSection(signin_info
, "Basic Information");
456 const std::string signin_status_string
=
457 untimed_signin_fields
[USERNAME
- UNTIMED_FIELDS_BEGIN
].empty()
460 AddSectionEntry(basic_info
, "Chrome Version", product_version
);
461 AddSectionEntry(basic_info
, "Signin Status", signin_status_string
);
462 AddSectionEntry(basic_info
, "Web Based Signin Enabled?",
463 switches::IsEnableWebBasedSignin() == true ? "True" : "False");
464 AddSectionEntry(basic_info
, "Webview Based Signin Enabled?",
465 switches::IsEnableWebviewBasedSignin() == true ? "True" : "False");
466 AddSectionEntry(basic_info
, "New Avatar Menu Enabled?",
467 switches::IsNewAvatarMenu() == true ? "True" : "False");
468 AddSectionEntry(basic_info
, "New Profile Management Enabled?",
469 switches::IsNewProfileManagement() == true ? "True" : "False");
470 AddSectionEntry(basic_info
, "Account Consistency Enabled?",
471 switches::IsEnableAccountConsistency() == true ? "True" : "False");
473 // Only add username. SID and LSID have moved to tokens section.
474 const std::string field
=
475 SigninStatusFieldToLabel(static_cast<UntimedSigninStatusField
>(USERNAME
));
476 AddSectionEntry(basic_info
,
478 untimed_signin_fields
[USERNAME
- UNTIMED_FIELDS_BEGIN
]);
480 #if !defined(OS_CHROMEOS)
481 // Time and status information of the possible sign in types.
482 base::ListValue
* detailed_info
=
483 AddSection(signin_info
, "Last Signin Details");
484 for (int i
= TIMED_FIELDS_BEGIN
; i
< TIMED_FIELDS_END
; ++i
) {
485 const std::string status_field_label
=
486 SigninStatusFieldToLabel(static_cast<TimedSigninStatusField
>(i
));
488 AddSectionEntry(detailed_info
,
490 timed_signin_fields
[i
- TIMED_FIELDS_BEGIN
].first
,
491 timed_signin_fields
[i
- TIMED_FIELDS_BEGIN
].second
);
493 #endif // !defined(OS_CHROMEOS)
495 // Token information for all services.
496 base::ListValue
* token_info
= new base::ListValue();
497 signin_status
->Set("token_info", token_info
);
498 for (TokenInfoMap::iterator it
= token_info_map
.begin();
499 it
!= token_info_map
.end();
501 base::ListValue
* token_details
= AddSection(token_info
, it
->first
);
503 std::sort(it
->second
.begin(), it
->second
.end(), TokenInfo::LessThan
);
504 const std::vector
<TokenInfo
*>& tokens
= it
->second
;
505 for (size_t i
= 0; i
< tokens
.size(); ++i
) {
506 base::DictionaryValue
* token_info
= tokens
[i
]->ToValue();
507 token_details
->Append(token_info
);
511 return signin_status
.Pass();