about_signin_internals.cc revision 6e8cce623b6e4fe0c9e4af605d675dd9d0338c38
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_->GetAuthenticatedUsername().empty()) { 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 FOR_EACH_OBSERVER(AboutSigninInternals::Observer, 224 signin_observers_, 225 OnSigninStateChanged( 226 signin_status_.ToValue(client_->GetProductVersion()))); 227} 228 229scoped_ptr<base::DictionaryValue> AboutSigninInternals::GetSigninStatus() { 230 return signin_status_.ToValue(client_->GetProductVersion()).Pass(); 231} 232 233void AboutSigninInternals::OnAccessTokenRequested( 234 const std::string& account_id, 235 const std::string& consumer_id, 236 const OAuth2TokenService::ScopeSet& scopes) { 237 TokenInfo* token = signin_status_.FindToken(account_id, consumer_id, scopes); 238 if (token) { 239 *token = TokenInfo(consumer_id, scopes); 240 } else { 241 token = new TokenInfo(consumer_id, scopes); 242 signin_status_.token_info_map[account_id].push_back(token); 243 } 244 245 NotifyObservers(); 246} 247 248void AboutSigninInternals::OnFetchAccessTokenComplete( 249 const std::string& account_id, 250 const std::string& consumer_id, 251 const OAuth2TokenService::ScopeSet& scopes, 252 GoogleServiceAuthError error, 253 base::Time expiration_time) { 254 TokenInfo* token = signin_status_.FindToken(account_id, consumer_id, scopes); 255 if (!token) { 256 DVLOG(1) << "Can't find token: " << account_id << ", " << consumer_id; 257 return; 258 } 259 260 token->receive_time = base::Time::Now(); 261 token->error = error; 262 token->expiration_time = expiration_time; 263 264 NotifyObservers(); 265} 266 267void AboutSigninInternals::OnTokenRemoved( 268 const std::string& account_id, 269 const OAuth2TokenService::ScopeSet& scopes) { 270 for (size_t i = 0; i < signin_status_.token_info_map[account_id].size(); 271 ++i) { 272 TokenInfo* token = signin_status_.token_info_map[account_id][i]; 273 if (token->scopes == scopes) 274 token->Invalidate(); 275 } 276 NotifyObservers(); 277} 278 279void AboutSigninInternals::OnRefreshTokenReceived(std::string status) { 280 NotifySigninValueChanged(REFRESH_TOKEN_RECEIVED, status); 281} 282 283void AboutSigninInternals::OnAuthenticationResultReceived(std::string status) { 284 NotifySigninValueChanged(AUTHENTICATION_RESULT_RECEIVED, status); 285} 286 287void AboutSigninInternals::OnCookieChanged( 288 const net::CanonicalCookie* cookie) { 289 if (cookie->Name() == "LSID" && 290 cookie->Domain() == GaiaUrls::GetInstance()->gaia_url().host() && 291 cookie->IsSecure() && 292 cookie->IsHttpOnly()) { 293 GetCookieAccountsAsync(); 294 } 295} 296 297void AboutSigninInternals::GetCookieAccountsAsync() { 298 if (!gaia_fetcher_) { 299 // There is no list account request in flight. 300 gaia_fetcher_.reset(new GaiaAuthFetcher( 301 this, GaiaConstants::kChromeSource, client_->GetURLRequestContext())); 302 gaia_fetcher_->StartListAccounts(); 303 } 304} 305 306void AboutSigninInternals::OnListAccountsSuccess(const std::string& data) { 307 gaia_fetcher_.reset(); 308 309 // Get account information from response data. 310 std::vector<std::pair<std::string, bool> > gaia_accounts; 311 bool valid_json = gaia::ParseListAccountsData(data, &gaia_accounts); 312 if (!valid_json) { 313 VLOG(1) << "AboutSigninInternals::OnListAccountsSuccess: parsing error"; 314 } else { 315 OnListAccountsComplete(gaia_accounts); 316 } 317} 318 319void AboutSigninInternals::OnListAccountsFailure( 320 const GoogleServiceAuthError& error) { 321 gaia_fetcher_.reset(); 322 VLOG(1) << "AboutSigninInternals::OnListAccountsFailure:" << error.ToString(); 323} 324 325void AboutSigninInternals::OnListAccountsComplete( 326 std::vector<std::pair<std::string, bool> >& gaia_accounts) { 327 scoped_ptr<base::DictionaryValue> signin_status(new base::DictionaryValue()); 328 base::ListValue* cookie_info = new base::ListValue(); 329 signin_status->Set("cookie_info", cookie_info); 330 331 for (size_t i = 0; i < gaia_accounts.size(); ++i) { 332 AddCookieEntry(cookie_info, 333 gaia_accounts[i].first, 334 gaia_accounts[i].second ? "Valid" : "Invalid"); 335 } 336 337 if (gaia_accounts.size() == 0) 338 AddCookieEntry(cookie_info, "No Accounts Present.", ""); 339 340 // Update the observers that the cookie's accounts are updated. 341 FOR_EACH_OBSERVER(AboutSigninInternals::Observer, 342 signin_observers_, 343 OnCookieAccountsFetched(signin_status.Pass())); 344} 345 346AboutSigninInternals::TokenInfo::TokenInfo( 347 const std::string& consumer_id, 348 const OAuth2TokenService::ScopeSet& scopes) 349 : consumer_id(consumer_id), 350 scopes(scopes), 351 request_time(base::Time::Now()), 352 error(GoogleServiceAuthError::AuthErrorNone()), 353 removed_(false) {} 354 355AboutSigninInternals::TokenInfo::~TokenInfo() {} 356 357bool AboutSigninInternals::TokenInfo::LessThan(const TokenInfo* a, 358 const TokenInfo* b) { 359 return a->consumer_id < b->consumer_id || a->scopes < b->scopes; 360} 361 362void AboutSigninInternals::TokenInfo::Invalidate() { removed_ = true; } 363 364base::DictionaryValue* AboutSigninInternals::TokenInfo::ToValue() const { 365 scoped_ptr<base::DictionaryValue> token_info(new base::DictionaryValue()); 366 token_info->SetString("service", consumer_id); 367 368 std::string scopes_str; 369 for (OAuth2TokenService::ScopeSet::const_iterator it = scopes.begin(); 370 it != scopes.end(); 371 ++it) { 372 scopes_str += *it + "<br/>"; 373 } 374 token_info->SetString("scopes", scopes_str); 375 token_info->SetString("request_time", GetTimeStr(request_time).c_str()); 376 377 if (removed_) { 378 token_info->SetString("status", "Token was revoked."); 379 } else if (!receive_time.is_null()) { 380 if (error == GoogleServiceAuthError::AuthErrorNone()) { 381 bool token_expired = expiration_time < base::Time::Now(); 382 std::string status_str = ""; 383 if (token_expired) 384 status_str = "<p style=\"color: #ffffff; background-color: #ff0000\">"; 385 base::StringAppendF(&status_str, 386 "Received token at %s. Expire at %s", 387 GetTimeStr(receive_time).c_str(), 388 GetTimeStr(expiration_time).c_str()); 389 if (token_expired) 390 base::StringAppendF(&status_str, "</p>"); 391 token_info->SetString("status", status_str); 392 } else { 393 token_info->SetString( 394 "status", 395 base::StringPrintf("Failure: %s", error.ToString().c_str())); 396 } 397 } else { 398 token_info->SetString("status", "Waiting for response"); 399 } 400 401 return token_info.release(); 402} 403 404AboutSigninInternals::SigninStatus::SigninStatus() 405 : untimed_signin_fields(UNTIMED_FIELDS_COUNT), 406 timed_signin_fields(TIMED_FIELDS_COUNT) {} 407 408AboutSigninInternals::SigninStatus::~SigninStatus() { 409 for (TokenInfoMap::iterator it = token_info_map.begin(); 410 it != token_info_map.end(); 411 ++it) { 412 STLDeleteElements(&it->second); 413 } 414} 415 416AboutSigninInternals::TokenInfo* AboutSigninInternals::SigninStatus::FindToken( 417 const std::string& account_id, 418 const std::string& consumer_id, 419 const OAuth2TokenService::ScopeSet& scopes) { 420 for (size_t i = 0; i < token_info_map[account_id].size(); ++i) { 421 TokenInfo* tmp = token_info_map[account_id][i]; 422 if (tmp->consumer_id == consumer_id && tmp->scopes == scopes) 423 return tmp; 424 } 425 return NULL; 426} 427 428scoped_ptr<base::DictionaryValue> AboutSigninInternals::SigninStatus::ToValue( 429 std::string product_version) { 430 scoped_ptr<base::DictionaryValue> signin_status(new base::DictionaryValue()); 431 base::ListValue* signin_info = new base::ListValue(); 432 signin_status->Set("signin_info", signin_info); 433 434 // A summary of signin related info first. 435 base::ListValue* basic_info = AddSection(signin_info, "Basic Information"); 436 const std::string signin_status_string = 437 untimed_signin_fields[USERNAME - UNTIMED_FIELDS_BEGIN].empty() 438 ? "Not Signed In" 439 : "Signed In"; 440 AddSectionEntry(basic_info, "Chrome Version", product_version); 441 AddSectionEntry(basic_info, "Signin Status", signin_status_string); 442 AddSectionEntry(basic_info, "Web Based Signin Enabled?", 443 switches::IsEnableWebBasedSignin() == true ? "True" : "False"); 444 AddSectionEntry(basic_info, "New Avatar Menu Enabled?", 445 switches::IsNewAvatarMenu() == true ? "True" : "False"); 446 AddSectionEntry(basic_info, "New Profile Management Enabled?", 447 switches::IsNewProfileManagement() == true ? "True" : "False"); 448 AddSectionEntry(basic_info, "Account Consistency Enabled?", 449 switches::IsEnableAccountConsistency() == true ? "True" : "False"); 450 451 // Only add username. SID and LSID have moved to tokens section. 452 const std::string field = 453 SigninStatusFieldToLabel(static_cast<UntimedSigninStatusField>(USERNAME)); 454 AddSectionEntry(basic_info, 455 field, 456 untimed_signin_fields[USERNAME - UNTIMED_FIELDS_BEGIN]); 457 458#if !defined(OS_CHROMEOS) 459 // Time and status information of the possible sign in types. 460 base::ListValue* detailed_info = 461 AddSection(signin_info, "Last Signin Details"); 462 for (int i = TIMED_FIELDS_BEGIN; i < TIMED_FIELDS_END; ++i) { 463 const std::string status_field_label = 464 SigninStatusFieldToLabel(static_cast<TimedSigninStatusField>(i)); 465 466 AddSectionEntry(detailed_info, 467 status_field_label, 468 timed_signin_fields[i - TIMED_FIELDS_BEGIN].first, 469 timed_signin_fields[i - TIMED_FIELDS_BEGIN].second); 470 } 471#endif // !defined(OS_CHROMEOS) 472 473 // Token information for all services. 474 base::ListValue* token_info = new base::ListValue(); 475 signin_status->Set("token_info", token_info); 476 for (TokenInfoMap::iterator it = token_info_map.begin(); 477 it != token_info_map.end(); 478 ++it) { 479 base::ListValue* token_details = AddSection(token_info, it->first); 480 481 std::sort(it->second.begin(), it->second.end(), TokenInfo::LessThan); 482 const std::vector<TokenInfo*>& tokens = it->second; 483 for (size_t i = 0; i < tokens.size(); ++i) { 484 base::DictionaryValue* token_info = tokens[i]->ToValue(); 485 token_details->Append(token_info); 486 } 487 } 488 489 return signin_status.Pass(); 490} 491