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_constants.h" 22 23 using base::Time; 24 using namespace signin_internals_util; 25 26 namespace { 27 28 std::string GetTimeStr(base::Time time) { 29 return base::UTF16ToUTF8(base::TimeFormatShortDateAndTime(time)); 30 } 31 32 base::ListValue* AddSection(base::ListValue* parent_list, 33 const std::string& title) { 34 scoped_ptr<base::DictionaryValue> section(new base::DictionaryValue()); 35 base::ListValue* section_contents = new base::ListValue(); 36 37 section->SetString("title", title); 38 section->Set("data", section_contents); 39 parent_list->Append(section.release()); 40 return section_contents; 41 } 42 43 void AddSectionEntry(base::ListValue* section_list, 44 const std::string& field_name, 45 const std::string& field_status, 46 const std::string& field_time = "") { 47 scoped_ptr<base::DictionaryValue> entry(new base::DictionaryValue()); 48 entry->SetString("label", field_name); 49 entry->SetString("status", field_status); 50 entry->SetString("time", field_time); 51 section_list->Append(entry.release()); 52 } 53 54 std::string SigninStatusFieldToLabel(UntimedSigninStatusField field) { 55 switch (field) { 56 case USERNAME: 57 return "User Id"; 58 case UNTIMED_FIELDS_END: 59 NOTREACHED(); 60 return std::string(); 61 } 62 NOTREACHED(); 63 return std::string(); 64 } 65 66 std::string SigninStatusFieldToLabel(TimedSigninStatusField field) { 67 switch (field) { 68 case SIGNIN_TYPE: 69 return "Type"; 70 case AUTHENTICATION_RESULT_RECEIVED: 71 return "Last Authentication Result Received"; 72 case REFRESH_TOKEN_RECEIVED: 73 return "Last RefreshToken Received"; 74 case GET_USER_INFO_STATUS: 75 return "Last OnGetUserInfo Received"; 76 case UBER_TOKEN_STATUS: 77 return "Last OnUberToken Received"; 78 case MERGE_SESSION_STATUS: 79 return "Last OnMergeSession Received"; 80 case TIMED_FIELDS_END: 81 NOTREACHED(); 82 return "Error"; 83 } 84 NOTREACHED(); 85 return "Error"; 86 } 87 88 } // anonymous namespace 89 90 AboutSigninInternals::AboutSigninInternals( 91 ProfileOAuth2TokenService* token_service, 92 SigninManagerBase* signin_manager) 93 : token_service_(token_service), 94 signin_manager_(signin_manager), 95 client_(NULL) {} 96 97 AboutSigninInternals::~AboutSigninInternals() {} 98 99 void AboutSigninInternals::AddSigninObserver( 100 AboutSigninInternals::Observer* observer) { 101 signin_observers_.AddObserver(observer); 102 } 103 104 void AboutSigninInternals::RemoveSigninObserver( 105 AboutSigninInternals::Observer* observer) { 106 signin_observers_.RemoveObserver(observer); 107 } 108 109 void AboutSigninInternals::NotifySigninValueChanged( 110 const UntimedSigninStatusField& field, 111 const std::string& value) { 112 unsigned int field_index = field - UNTIMED_FIELDS_BEGIN; 113 DCHECK(field_index >= 0 && 114 field_index < signin_status_.untimed_signin_fields.size()); 115 116 signin_status_.untimed_signin_fields[field_index] = value; 117 118 // Also persist these values in the prefs. 119 const std::string pref_path = SigninStatusFieldToString(field); 120 client_->GetPrefs()->SetString(pref_path.c_str(), value); 121 122 NotifyObservers(); 123 } 124 125 void AboutSigninInternals::NotifySigninValueChanged( 126 const TimedSigninStatusField& field, 127 const std::string& value) { 128 unsigned int field_index = field - TIMED_FIELDS_BEGIN; 129 DCHECK(field_index >= 0 && 130 field_index < signin_status_.timed_signin_fields.size()); 131 132 Time now = Time::NowFromSystemTime(); 133 std::string time_as_str = 134 base::UTF16ToUTF8(base::TimeFormatFriendlyDate(now)); 135 TimedSigninStatusValue timed_value(value, time_as_str); 136 137 signin_status_.timed_signin_fields[field_index] = timed_value; 138 139 // Also persist these values in the prefs. 140 const std::string value_pref = SigninStatusFieldToString(field) + ".value"; 141 const std::string time_pref = SigninStatusFieldToString(field) + ".time"; 142 client_->GetPrefs()->SetString(value_pref.c_str(), value); 143 client_->GetPrefs()->SetString(time_pref.c_str(), time_as_str); 144 145 NotifyObservers(); 146 } 147 148 void AboutSigninInternals::RefreshSigninPrefs() { 149 // Return if no client exists. Can occur in unit tests. 150 if (!client_) 151 return; 152 153 PrefService* pref_service = client_->GetPrefs(); 154 for (int i = UNTIMED_FIELDS_BEGIN; i < UNTIMED_FIELDS_END; ++i) { 155 const std::string pref_path = 156 SigninStatusFieldToString(static_cast<UntimedSigninStatusField>(i)); 157 158 signin_status_.untimed_signin_fields[i - UNTIMED_FIELDS_BEGIN] = 159 pref_service->GetString(pref_path.c_str()); 160 } 161 for (int i = TIMED_FIELDS_BEGIN; i < TIMED_FIELDS_END; ++i) { 162 const std::string value_pref = 163 SigninStatusFieldToString(static_cast<TimedSigninStatusField>(i)) + 164 ".value"; 165 const std::string time_pref = 166 SigninStatusFieldToString(static_cast<TimedSigninStatusField>(i)) + 167 ".time"; 168 169 TimedSigninStatusValue value(pref_service->GetString(value_pref.c_str()), 170 pref_service->GetString(time_pref.c_str())); 171 signin_status_.timed_signin_fields[i - TIMED_FIELDS_BEGIN] = value; 172 } 173 174 // TODO(rogerta): Get status and timestamps for oauth2 tokens. 175 176 NotifyObservers(); 177 } 178 179 void AboutSigninInternals::Initialize(SigninClient* client) { 180 DCHECK(!client_); 181 client_ = client; 182 183 RefreshSigninPrefs(); 184 185 signin_manager_->AddSigninDiagnosticsObserver(this); 186 token_service_->AddDiagnosticsObserver(this); 187 } 188 189 void AboutSigninInternals::Shutdown() { 190 signin_manager_->RemoveSigninDiagnosticsObserver(this); 191 token_service_->RemoveDiagnosticsObserver(this); 192 } 193 194 void AboutSigninInternals::NotifyObservers() { 195 FOR_EACH_OBSERVER(AboutSigninInternals::Observer, 196 signin_observers_, 197 OnSigninStateChanged( 198 signin_status_.ToValue(client_->GetProductVersion()))); 199 } 200 201 scoped_ptr<base::DictionaryValue> AboutSigninInternals::GetSigninStatus() { 202 return signin_status_.ToValue(client_->GetProductVersion()).Pass(); 203 } 204 205 void AboutSigninInternals::OnAccessTokenRequested( 206 const std::string& account_id, 207 const std::string& consumer_id, 208 const OAuth2TokenService::ScopeSet& scopes) { 209 TokenInfo* token = signin_status_.FindToken(account_id, consumer_id, scopes); 210 if (token) { 211 *token = TokenInfo(consumer_id, scopes); 212 } else { 213 token = new TokenInfo(consumer_id, scopes); 214 signin_status_.token_info_map[account_id].push_back(token); 215 } 216 217 NotifyObservers(); 218 } 219 220 void AboutSigninInternals::OnFetchAccessTokenComplete( 221 const std::string& account_id, 222 const std::string& consumer_id, 223 const OAuth2TokenService::ScopeSet& scopes, 224 GoogleServiceAuthError error, 225 base::Time expiration_time) { 226 TokenInfo* token = signin_status_.FindToken(account_id, consumer_id, scopes); 227 if (!token) { 228 DVLOG(1) << "Can't find token: " << account_id << ", " << consumer_id; 229 return; 230 } 231 232 token->receive_time = base::Time::Now(); 233 token->error = error; 234 token->expiration_time = expiration_time; 235 236 NotifyObservers(); 237 } 238 239 void AboutSigninInternals::OnTokenRemoved( 240 const std::string& account_id, 241 const OAuth2TokenService::ScopeSet& scopes) { 242 for (size_t i = 0; i < signin_status_.token_info_map[account_id].size(); 243 ++i) { 244 TokenInfo* token = signin_status_.token_info_map[account_id][i]; 245 if (token->scopes == scopes) 246 token->Invalidate(); 247 } 248 NotifyObservers(); 249 } 250 251 void AboutSigninInternals::OnRefreshTokenReceived(std::string status) { 252 NotifySigninValueChanged(REFRESH_TOKEN_RECEIVED, status); 253 } 254 255 void AboutSigninInternals::OnAuthenticationResultReceived(std::string status) { 256 NotifySigninValueChanged(AUTHENTICATION_RESULT_RECEIVED, status); 257 } 258 259 AboutSigninInternals::TokenInfo::TokenInfo( 260 const std::string& consumer_id, 261 const OAuth2TokenService::ScopeSet& scopes) 262 : consumer_id(consumer_id), 263 scopes(scopes), 264 request_time(base::Time::Now()), 265 error(GoogleServiceAuthError::AuthErrorNone()), 266 removed_(false) {} 267 268 AboutSigninInternals::TokenInfo::~TokenInfo() {} 269 270 bool AboutSigninInternals::TokenInfo::LessThan(const TokenInfo* a, 271 const TokenInfo* b) { 272 return a->consumer_id < b->consumer_id || a->scopes < b->scopes; 273 } 274 275 void AboutSigninInternals::TokenInfo::Invalidate() { removed_ = true; } 276 277 base::DictionaryValue* AboutSigninInternals::TokenInfo::ToValue() const { 278 scoped_ptr<base::DictionaryValue> token_info(new base::DictionaryValue()); 279 token_info->SetString("service", consumer_id); 280 281 std::string scopes_str; 282 for (OAuth2TokenService::ScopeSet::const_iterator it = scopes.begin(); 283 it != scopes.end(); 284 ++it) { 285 scopes_str += *it + "<br/>"; 286 } 287 token_info->SetString("scopes", scopes_str); 288 token_info->SetString("request_time", GetTimeStr(request_time).c_str()); 289 290 if (removed_) { 291 token_info->SetString("status", "Token was revoked."); 292 } else if (!receive_time.is_null()) { 293 if (error == GoogleServiceAuthError::AuthErrorNone()) { 294 bool token_expired = expiration_time < base::Time::Now(); 295 std::string status_str = ""; 296 if (token_expired) 297 status_str = "<p style=\"color: #ffffff; background-color: #ff0000\">"; 298 base::StringAppendF(&status_str, 299 "Received token at %s. Expire at %s", 300 GetTimeStr(receive_time).c_str(), 301 GetTimeStr(expiration_time).c_str()); 302 if (token_expired) 303 base::StringAppendF(&status_str, "</p>"); 304 token_info->SetString("status", status_str); 305 } else { 306 token_info->SetString( 307 "status", 308 base::StringPrintf("Failure: %s", error.ToString().c_str())); 309 } 310 } else { 311 token_info->SetString("status", "Waiting for response"); 312 } 313 314 return token_info.release(); 315 } 316 317 AboutSigninInternals::SigninStatus::SigninStatus() 318 : untimed_signin_fields(UNTIMED_FIELDS_COUNT), 319 timed_signin_fields(TIMED_FIELDS_COUNT) {} 320 321 AboutSigninInternals::SigninStatus::~SigninStatus() { 322 for (TokenInfoMap::iterator it = token_info_map.begin(); 323 it != token_info_map.end(); 324 ++it) { 325 STLDeleteElements(&it->second); 326 } 327 } 328 329 AboutSigninInternals::TokenInfo* AboutSigninInternals::SigninStatus::FindToken( 330 const std::string& account_id, 331 const std::string& consumer_id, 332 const OAuth2TokenService::ScopeSet& scopes) { 333 for (size_t i = 0; i < token_info_map[account_id].size(); ++i) { 334 TokenInfo* tmp = token_info_map[account_id][i]; 335 if (tmp->consumer_id == consumer_id && tmp->scopes == scopes) 336 return tmp; 337 } 338 return NULL; 339 } 340 341 scoped_ptr<base::DictionaryValue> AboutSigninInternals::SigninStatus::ToValue( 342 std::string product_version) { 343 scoped_ptr<base::DictionaryValue> signin_status(new base::DictionaryValue()); 344 base::ListValue* signin_info = new base::ListValue(); 345 signin_status->Set("signin_info", signin_info); 346 347 // A summary of signin related info first. 348 base::ListValue* basic_info = AddSection(signin_info, "Basic Information"); 349 const std::string signin_status_string = 350 untimed_signin_fields[USERNAME - UNTIMED_FIELDS_BEGIN].empty() 351 ? "Not Signed In" 352 : "Signed In"; 353 AddSectionEntry(basic_info, "Chrome Version", product_version); 354 AddSectionEntry(basic_info, "Signin Status", signin_status_string); 355 AddSectionEntry(basic_info, "Web Based Signin Enabled?", 356 switches::IsEnableWebBasedSignin() == true ? "True" : "False"); 357 AddSectionEntry(basic_info, "New Profile Management Enabled?", 358 switches::IsNewProfileManagement() == true ? "True" : "False"); 359 AddSectionEntry(basic_info, "New Avatar Menu Enabled?", 360 switches::IsNewAvatarMenu() == true ? "True" : "False"); 361 bool new_avatar_menu_flag = 362 CommandLine::ForCurrentProcess()->HasSwitch(switches::kNewAvatarMenu); 363 AddSectionEntry(basic_info, "New Avatar Menu Flag Set?", 364 new_avatar_menu_flag ? "True" : "False"); 365 AddSectionEntry(basic_info, "Account Consistency Enabled?", 366 switches::IsEnableAccountConsistency() == true ? "True" : "False"); 367 368 // Only add username. SID and LSID have moved to tokens section. 369 const std::string field = 370 SigninStatusFieldToLabel(static_cast<UntimedSigninStatusField>(USERNAME)); 371 AddSectionEntry(basic_info, 372 field, 373 untimed_signin_fields[USERNAME - UNTIMED_FIELDS_BEGIN]); 374 375 // Time and status information of the possible sign in types. 376 base::ListValue* detailed_info = 377 AddSection(signin_info, "Last Signin Details"); 378 for (int i = TIMED_FIELDS_BEGIN; i < TIMED_FIELDS_END; ++i) { 379 const std::string status_field_label = 380 SigninStatusFieldToLabel(static_cast<TimedSigninStatusField>(i)); 381 382 AddSectionEntry(detailed_info, 383 status_field_label, 384 timed_signin_fields[i - TIMED_FIELDS_BEGIN].first, 385 timed_signin_fields[i - TIMED_FIELDS_BEGIN].second); 386 } 387 388 // Token information for all services. 389 base::ListValue* token_info = new base::ListValue(); 390 signin_status->Set("token_info", token_info); 391 for (TokenInfoMap::iterator it = token_info_map.begin(); 392 it != token_info_map.end(); 393 ++it) { 394 base::ListValue* token_details = AddSection(token_info, it->first); 395 396 std::sort(it->second.begin(), it->second.end(), TokenInfo::LessThan); 397 const std::vector<TokenInfo*>& tokens = it->second; 398 for (size_t i = 0; i < tokens.size(); ++i) { 399 base::DictionaryValue* token_info = tokens[i]->ToValue(); 400 token_details->Append(token_info); 401 } 402 } 403 404 return signin_status.Pass(); 405 } 406