about_signin_internals.cc revision 1320f92c476a1ad9d19dba2a48c72b75566198e9
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_auth_fetcher.h" 22#include "google_apis/gaia/gaia_auth_util.h" 23#include "google_apis/gaia/gaia_constants.h" 24#include "google_apis/gaia/gaia_urls.h" 25#include "net/cookies/canonical_cookie.h" 26 27using base::Time; 28using namespace signin_internals_util; 29 30namespace { 31 32std::string GetTimeStr(base::Time time) { 33 return base::UTF16ToUTF8(base::TimeFormatShortDateAndTime(time)); 34} 35 36base::ListValue* AddSection(base::ListValue* parent_list, 37 const std::string& title) { 38 scoped_ptr<base::DictionaryValue> section(new base::DictionaryValue()); 39 base::ListValue* section_contents = new base::ListValue(); 40 41 section->SetString("title", title); 42 section->Set("data", section_contents); 43 parent_list->Append(section.release()); 44 return section_contents; 45} 46 47void AddSectionEntry(base::ListValue* section_list, 48 const std::string& field_name, 49 const std::string& field_status, 50 const std::string& field_time = "") { 51 scoped_ptr<base::DictionaryValue> entry(new base::DictionaryValue()); 52 entry->SetString("label", field_name); 53 entry->SetString("status", field_status); 54 entry->SetString("time", field_time); 55 section_list->Append(entry.release()); 56} 57 58void AddCookieEntry(base::ListValue* accounts_list, 59 const std::string& field_email, 60 const std::string& field_valid) { 61 scoped_ptr<base::DictionaryValue> entry(new base::DictionaryValue()); 62 entry->SetString("email", field_email); 63 entry->SetString("valid", field_valid); 64 accounts_list->Append(entry.release()); 65} 66 67std::string SigninStatusFieldToLabel(UntimedSigninStatusField field) { 68 switch (field) { 69 case USERNAME: 70 return "User Id"; 71 case UNTIMED_FIELDS_END: 72 NOTREACHED(); 73 return std::string(); 74 } 75 NOTREACHED(); 76 return std::string(); 77} 78 79#if !defined (OS_CHROMEOS) 80std::string SigninStatusFieldToLabel(TimedSigninStatusField field) { 81 switch (field) { 82 case SIGNIN_TYPE: 83 return "Type"; 84 case AUTHENTICATION_RESULT_RECEIVED: 85 return "Last Authentication Result Received"; 86 case REFRESH_TOKEN_RECEIVED: 87 return "Last RefreshToken Received"; 88 case GET_USER_INFO_STATUS: 89 return "Last OnGetUserInfo Received"; 90 case UBER_TOKEN_STATUS: 91 return "Last OnUberToken Received"; 92 case MERGE_SESSION_STATUS: 93 return "Last OnMergeSession Received"; 94 case TIMED_FIELDS_END: 95 NOTREACHED(); 96 return "Error"; 97 } 98 NOTREACHED(); 99 return "Error"; 100} 101#endif // !defined (OS_CHROMEOS) 102 103} // anonymous namespace 104 105AboutSigninInternals::AboutSigninInternals( 106 ProfileOAuth2TokenService* token_service, 107 SigninManagerBase* signin_manager) 108 : token_service_(token_service), 109 signin_manager_(signin_manager), 110 client_(NULL) {} 111 112AboutSigninInternals::~AboutSigninInternals() {} 113 114void AboutSigninInternals::AddSigninObserver( 115 AboutSigninInternals::Observer* observer) { 116 signin_observers_.AddObserver(observer); 117} 118 119void AboutSigninInternals::RemoveSigninObserver( 120 AboutSigninInternals::Observer* observer) { 121 signin_observers_.RemoveObserver(observer); 122} 123 124void AboutSigninInternals::NotifySigninValueChanged( 125 const UntimedSigninStatusField& field, 126 const std::string& value) { 127 unsigned int field_index = field - UNTIMED_FIELDS_BEGIN; 128 DCHECK(field_index >= 0 && 129 field_index < signin_status_.untimed_signin_fields.size()); 130 131 signin_status_.untimed_signin_fields[field_index] = value; 132 133 // Also persist these values in the prefs. 134 const std::string pref_path = SigninStatusFieldToString(field); 135 client_->GetPrefs()->SetString(pref_path.c_str(), value); 136 137 NotifyObservers(); 138} 139 140void AboutSigninInternals::NotifySigninValueChanged( 141 const TimedSigninStatusField& field, 142 const std::string& value) { 143 unsigned int field_index = field - TIMED_FIELDS_BEGIN; 144 DCHECK(field_index >= 0 && 145 field_index < signin_status_.timed_signin_fields.size()); 146 147 Time now = Time::NowFromSystemTime(); 148 std::string time_as_str = 149 base::UTF16ToUTF8(base::TimeFormatShortDateAndTime(now)); 150 TimedSigninStatusValue timed_value(value, time_as_str); 151 152 signin_status_.timed_signin_fields[field_index] = timed_value; 153 154 // Also persist these values in the prefs. 155 const std::string value_pref = SigninStatusFieldToString(field) + ".value"; 156 const std::string time_pref = SigninStatusFieldToString(field) + ".time"; 157 client_->GetPrefs()->SetString(value_pref.c_str(), value); 158 client_->GetPrefs()->SetString(time_pref.c_str(), time_as_str); 159 160 NotifyObservers(); 161} 162 163void AboutSigninInternals::RefreshSigninPrefs() { 164 // Since the AboutSigninInternals has a dependency on the SigninManager 165 // (as seen in the AboutSigninInternalsFactory) the SigninManager can have 166 // the AuthenticatedUsername set before AboutSigninInternals can observe it. 167 // For that scenario, read the AuthenticatedUsername if it exists. 168 if (signin_manager_->IsAuthenticated()) { 169 signin_status_.untimed_signin_fields[USERNAME] = 170 signin_manager_->GetAuthenticatedUsername(); 171 } 172 173 // Return if no client exists. Can occur in unit tests. 174 if (!client_) 175 return; 176 177 PrefService* pref_service = client_->GetPrefs(); 178 for (int i = UNTIMED_FIELDS_BEGIN; i < UNTIMED_FIELDS_END; ++i) { 179 const std::string pref_path = 180 SigninStatusFieldToString(static_cast<UntimedSigninStatusField>(i)); 181 182 signin_status_.untimed_signin_fields[i - UNTIMED_FIELDS_BEGIN] = 183 pref_service->GetString(pref_path.c_str()); 184 } 185 for (int i = TIMED_FIELDS_BEGIN; i < TIMED_FIELDS_END; ++i) { 186 const std::string value_pref = 187 SigninStatusFieldToString(static_cast<TimedSigninStatusField>(i)) + 188 ".value"; 189 const std::string time_pref = 190 SigninStatusFieldToString(static_cast<TimedSigninStatusField>(i)) + 191 ".time"; 192 193 TimedSigninStatusValue value(pref_service->GetString(value_pref.c_str()), 194 pref_service->GetString(time_pref.c_str())); 195 signin_status_.timed_signin_fields[i - TIMED_FIELDS_BEGIN] = value; 196 } 197 198 // TODO(rogerta): Get status and timestamps for oauth2 tokens. 199 200 NotifyObservers(); 201} 202 203void AboutSigninInternals::Initialize(SigninClient* client) { 204 DCHECK(!client_); 205 client_ = client; 206 207 RefreshSigninPrefs(); 208 209 signin_manager_->AddSigninDiagnosticsObserver(this); 210 token_service_->AddDiagnosticsObserver(this); 211 cookie_changed_subscription_ = client_->AddCookieChangedCallback( 212 base::Bind(&AboutSigninInternals::OnCookieChanged, 213 base::Unretained(this))); 214} 215 216void AboutSigninInternals::Shutdown() { 217 signin_manager_->RemoveSigninDiagnosticsObserver(this); 218 token_service_->RemoveDiagnosticsObserver(this); 219 cookie_changed_subscription_.reset(); 220} 221 222void AboutSigninInternals::NotifyObservers() { 223 scoped_ptr<base::DictionaryValue> signin_status_value = 224 signin_status_.ToValue(client_->GetProductVersion()); 225 FOR_EACH_OBSERVER(AboutSigninInternals::Observer, 226 signin_observers_, 227 OnSigninStateChanged(signin_status_value.get())); 228} 229 230scoped_ptr<base::DictionaryValue> AboutSigninInternals::GetSigninStatus() { 231 return signin_status_.ToValue(client_->GetProductVersion()).Pass(); 232} 233 234void AboutSigninInternals::OnAccessTokenRequested( 235 const std::string& account_id, 236 const std::string& consumer_id, 237 const OAuth2TokenService::ScopeSet& scopes) { 238 TokenInfo* token = signin_status_.FindToken(account_id, consumer_id, scopes); 239 if (token) { 240 *token = TokenInfo(consumer_id, scopes); 241 } else { 242 token = new TokenInfo(consumer_id, scopes); 243 signin_status_.token_info_map[account_id].push_back(token); 244 } 245 246 NotifyObservers(); 247} 248 249void AboutSigninInternals::OnFetchAccessTokenComplete( 250 const std::string& account_id, 251 const std::string& consumer_id, 252 const OAuth2TokenService::ScopeSet& scopes, 253 GoogleServiceAuthError error, 254 base::Time expiration_time) { 255 TokenInfo* token = signin_status_.FindToken(account_id, consumer_id, scopes); 256 if (!token) { 257 DVLOG(1) << "Can't find token: " << account_id << ", " << consumer_id; 258 return; 259 } 260 261 token->receive_time = base::Time::Now(); 262 token->error = error; 263 token->expiration_time = expiration_time; 264 265 NotifyObservers(); 266} 267 268void AboutSigninInternals::OnTokenRemoved( 269 const std::string& account_id, 270 const OAuth2TokenService::ScopeSet& scopes) { 271 for (size_t i = 0; i < signin_status_.token_info_map[account_id].size(); 272 ++i) { 273 TokenInfo* token = signin_status_.token_info_map[account_id][i]; 274 if (token->scopes == scopes) 275 token->Invalidate(); 276 } 277 NotifyObservers(); 278} 279 280void AboutSigninInternals::OnRefreshTokenReceived(std::string status) { 281 NotifySigninValueChanged(REFRESH_TOKEN_RECEIVED, status); 282} 283 284void AboutSigninInternals::OnAuthenticationResultReceived(std::string status) { 285 NotifySigninValueChanged(AUTHENTICATION_RESULT_RECEIVED, status); 286} 287 288void AboutSigninInternals::OnCookieChanged( 289 const net::CanonicalCookie* cookie) { 290 if (cookie->Name() == "LSID" && 291 cookie->Domain() == GaiaUrls::GetInstance()->gaia_url().host() && 292 cookie->IsSecure() && 293 cookie->IsHttpOnly()) { 294 GetCookieAccountsAsync(); 295 } 296} 297 298void AboutSigninInternals::GetCookieAccountsAsync() { 299 // Don't bother calling /ListAccounts if no one will observe the response. 300 if (!gaia_fetcher_ && signin_observers_.might_have_observers()) { 301 // There is no list account request in flight. 302 gaia_fetcher_.reset(new GaiaAuthFetcher( 303 this, GaiaConstants::kChromeSource, client_->GetURLRequestContext())); 304 gaia_fetcher_->StartListAccounts(); 305 } 306} 307 308void AboutSigninInternals::OnListAccountsSuccess(const std::string& data) { 309 gaia_fetcher_.reset(); 310 311 // Get account information from response data. 312 std::vector<std::pair<std::string, bool> > gaia_accounts; 313 bool valid_json = gaia::ParseListAccountsData(data, &gaia_accounts); 314 if (!valid_json) { 315 VLOG(1) << "AboutSigninInternals::OnListAccountsSuccess: parsing error"; 316 } else { 317 OnListAccountsComplete(gaia_accounts); 318 } 319} 320 321void AboutSigninInternals::OnListAccountsFailure( 322 const GoogleServiceAuthError& error) { 323 gaia_fetcher_.reset(); 324 VLOG(1) << "AboutSigninInternals::OnListAccountsFailure:" << error.ToString(); 325} 326 327void AboutSigninInternals::OnListAccountsComplete( 328 std::vector<std::pair<std::string, bool> >& gaia_accounts) { 329 base::DictionaryValue signin_status; 330 base::ListValue* cookie_info = new base::ListValue(); 331 signin_status.Set("cookie_info", cookie_info); 332 333 for (size_t i = 0; i < gaia_accounts.size(); ++i) { 334 AddCookieEntry(cookie_info, 335 gaia_accounts[i].first, 336 gaia_accounts[i].second ? "Valid" : "Invalid"); 337 } 338 339 if (gaia_accounts.size() == 0) 340 AddCookieEntry(cookie_info, "No Accounts Present.", ""); 341 342 // Update the observers that the cookie's accounts are updated. 343 FOR_EACH_OBSERVER(AboutSigninInternals::Observer, 344 signin_observers_, 345 OnCookieAccountsFetched(&signin_status)); 346} 347 348AboutSigninInternals::TokenInfo::TokenInfo( 349 const std::string& consumer_id, 350 const OAuth2TokenService::ScopeSet& scopes) 351 : consumer_id(consumer_id), 352 scopes(scopes), 353 request_time(base::Time::Now()), 354 error(GoogleServiceAuthError::AuthErrorNone()), 355 removed_(false) {} 356 357AboutSigninInternals::TokenInfo::~TokenInfo() {} 358 359bool AboutSigninInternals::TokenInfo::LessThan(const TokenInfo* a, 360 const TokenInfo* b) { 361 return a->consumer_id < b->consumer_id || a->scopes < b->scopes; 362} 363 364void AboutSigninInternals::TokenInfo::Invalidate() { removed_ = true; } 365 366base::DictionaryValue* AboutSigninInternals::TokenInfo::ToValue() const { 367 scoped_ptr<base::DictionaryValue> token_info(new base::DictionaryValue()); 368 token_info->SetString("service", consumer_id); 369 370 std::string scopes_str; 371 for (OAuth2TokenService::ScopeSet::const_iterator it = scopes.begin(); 372 it != scopes.end(); 373 ++it) { 374 scopes_str += *it + "<br/>"; 375 } 376 token_info->SetString("scopes", scopes_str); 377 token_info->SetString("request_time", GetTimeStr(request_time).c_str()); 378 379 if (removed_) { 380 token_info->SetString("status", "Token was revoked."); 381 } else if (!receive_time.is_null()) { 382 if (error == GoogleServiceAuthError::AuthErrorNone()) { 383 bool token_expired = expiration_time < base::Time::Now(); 384 std::string status_str = ""; 385 if (token_expired) 386 status_str = "<p style=\"color: #ffffff; background-color: #ff0000\">"; 387 base::StringAppendF(&status_str, 388 "Received token at %s. Expire at %s", 389 GetTimeStr(receive_time).c_str(), 390 GetTimeStr(expiration_time).c_str()); 391 if (token_expired) 392 base::StringAppendF(&status_str, "</p>"); 393 token_info->SetString("status", status_str); 394 } else { 395 token_info->SetString( 396 "status", 397 base::StringPrintf("Failure: %s", error.ToString().c_str())); 398 } 399 } else { 400 token_info->SetString("status", "Waiting for response"); 401 } 402 403 return token_info.release(); 404} 405 406AboutSigninInternals::SigninStatus::SigninStatus() 407 : untimed_signin_fields(UNTIMED_FIELDS_COUNT), 408 timed_signin_fields(TIMED_FIELDS_COUNT) {} 409 410AboutSigninInternals::SigninStatus::~SigninStatus() { 411 for (TokenInfoMap::iterator it = token_info_map.begin(); 412 it != token_info_map.end(); 413 ++it) { 414 STLDeleteElements(&it->second); 415 } 416} 417 418AboutSigninInternals::TokenInfo* AboutSigninInternals::SigninStatus::FindToken( 419 const std::string& account_id, 420 const std::string& consumer_id, 421 const OAuth2TokenService::ScopeSet& scopes) { 422 for (size_t i = 0; i < token_info_map[account_id].size(); ++i) { 423 TokenInfo* tmp = token_info_map[account_id][i]; 424 if (tmp->consumer_id == consumer_id && tmp->scopes == scopes) 425 return tmp; 426 } 427 return NULL; 428} 429 430scoped_ptr<base::DictionaryValue> AboutSigninInternals::SigninStatus::ToValue( 431 std::string product_version) { 432 scoped_ptr<base::DictionaryValue> signin_status(new base::DictionaryValue()); 433 base::ListValue* signin_info = new base::ListValue(); 434 signin_status->Set("signin_info", signin_info); 435 436 // A summary of signin related info first. 437 base::ListValue* basic_info = AddSection(signin_info, "Basic Information"); 438 const std::string signin_status_string = 439 untimed_signin_fields[USERNAME - UNTIMED_FIELDS_BEGIN].empty() 440 ? "Not Signed In" 441 : "Signed In"; 442 AddSectionEntry(basic_info, "Chrome Version", product_version); 443 AddSectionEntry(basic_info, "Signin Status", signin_status_string); 444 AddSectionEntry(basic_info, "Web Based Signin Enabled?", 445 switches::IsEnableWebBasedSignin() == true ? "True" : "False"); 446 AddSectionEntry(basic_info, "New Avatar Menu Enabled?", 447 switches::IsNewAvatarMenu() == true ? "True" : "False"); 448 AddSectionEntry(basic_info, "New Profile Management Enabled?", 449 switches::IsNewProfileManagement() == true ? "True" : "False"); 450 AddSectionEntry(basic_info, "Account Consistency Enabled?", 451 switches::IsEnableAccountConsistency() == true ? "True" : "False"); 452 453 // Only add username. SID and LSID have moved to tokens section. 454 const std::string field = 455 SigninStatusFieldToLabel(static_cast<UntimedSigninStatusField>(USERNAME)); 456 AddSectionEntry(basic_info, 457 field, 458 untimed_signin_fields[USERNAME - UNTIMED_FIELDS_BEGIN]); 459 460#if !defined(OS_CHROMEOS) 461 // Time and status information of the possible sign in types. 462 base::ListValue* detailed_info = 463 AddSection(signin_info, "Last Signin Details"); 464 for (int i = TIMED_FIELDS_BEGIN; i < TIMED_FIELDS_END; ++i) { 465 const std::string status_field_label = 466 SigninStatusFieldToLabel(static_cast<TimedSigninStatusField>(i)); 467 468 AddSectionEntry(detailed_info, 469 status_field_label, 470 timed_signin_fields[i - TIMED_FIELDS_BEGIN].first, 471 timed_signin_fields[i - TIMED_FIELDS_BEGIN].second); 472 } 473#endif // !defined(OS_CHROMEOS) 474 475 // Token information for all services. 476 base::ListValue* token_info = new base::ListValue(); 477 signin_status->Set("token_info", token_info); 478 for (TokenInfoMap::iterator it = token_info_map.begin(); 479 it != token_info_map.end(); 480 ++it) { 481 base::ListValue* token_details = AddSection(token_info, it->first); 482 483 std::sort(it->second.begin(), it->second.end(), TokenInfo::LessThan); 484 const std::vector<TokenInfo*>& tokens = it->second; 485 for (size_t i = 0; i < tokens.size(); ++i) { 486 base::DictionaryValue* token_info = tokens[i]->ToValue(); 487 token_details->Append(token_info); 488 } 489 } 490 491 return signin_status.Pass(); 492} 493