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
GetTimeStr(base::Time time)28 std::string GetTimeStr(base::Time time) {
29 return base::UTF16ToUTF8(base::TimeFormatShortDateAndTime(time));
30 }
31
AddSection(base::ListValue * parent_list,const std::string & title)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
AddSectionEntry(base::ListValue * section_list,const std::string & field_name,const std::string & field_status,const std::string & field_time="")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
SigninStatusFieldToLabel(UntimedSigninStatusField field)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
SigninStatusFieldToLabel(TimedSigninStatusField field)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
AboutSigninInternals(ProfileOAuth2TokenService * token_service,SigninManagerBase * signin_manager)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
~AboutSigninInternals()97 AboutSigninInternals::~AboutSigninInternals() {}
98
AddSigninObserver(AboutSigninInternals::Observer * observer)99 void AboutSigninInternals::AddSigninObserver(
100 AboutSigninInternals::Observer* observer) {
101 signin_observers_.AddObserver(observer);
102 }
103
RemoveSigninObserver(AboutSigninInternals::Observer * observer)104 void AboutSigninInternals::RemoveSigninObserver(
105 AboutSigninInternals::Observer* observer) {
106 signin_observers_.RemoveObserver(observer);
107 }
108
NotifySigninValueChanged(const UntimedSigninStatusField & field,const std::string & value)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
NotifySigninValueChanged(const TimedSigninStatusField & field,const std::string & value)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
RefreshSigninPrefs()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
Initialize(SigninClient * client)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
Shutdown()189 void AboutSigninInternals::Shutdown() {
190 signin_manager_->RemoveSigninDiagnosticsObserver(this);
191 token_service_->RemoveDiagnosticsObserver(this);
192 }
193
NotifyObservers()194 void AboutSigninInternals::NotifyObservers() {
195 FOR_EACH_OBSERVER(AboutSigninInternals::Observer,
196 signin_observers_,
197 OnSigninStateChanged(
198 signin_status_.ToValue(client_->GetProductVersion())));
199 }
200
GetSigninStatus()201 scoped_ptr<base::DictionaryValue> AboutSigninInternals::GetSigninStatus() {
202 return signin_status_.ToValue(client_->GetProductVersion()).Pass();
203 }
204
OnAccessTokenRequested(const std::string & account_id,const std::string & consumer_id,const OAuth2TokenService::ScopeSet & scopes)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
OnFetchAccessTokenComplete(const std::string & account_id,const std::string & consumer_id,const OAuth2TokenService::ScopeSet & scopes,GoogleServiceAuthError error,base::Time expiration_time)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
OnTokenRemoved(const std::string & account_id,const OAuth2TokenService::ScopeSet & scopes)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
OnRefreshTokenReceived(std::string status)251 void AboutSigninInternals::OnRefreshTokenReceived(std::string status) {
252 NotifySigninValueChanged(REFRESH_TOKEN_RECEIVED, status);
253 }
254
OnAuthenticationResultReceived(std::string status)255 void AboutSigninInternals::OnAuthenticationResultReceived(std::string status) {
256 NotifySigninValueChanged(AUTHENTICATION_RESULT_RECEIVED, status);
257 }
258
TokenInfo(const std::string & consumer_id,const OAuth2TokenService::ScopeSet & scopes)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
~TokenInfo()268 AboutSigninInternals::TokenInfo::~TokenInfo() {}
269
LessThan(const TokenInfo * a,const TokenInfo * b)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
Invalidate()275 void AboutSigninInternals::TokenInfo::Invalidate() { removed_ = true; }
276
ToValue() const277 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
SigninStatus()317 AboutSigninInternals::SigninStatus::SigninStatus()
318 : untimed_signin_fields(UNTIMED_FIELDS_COUNT),
319 timed_signin_fields(TIMED_FIELDS_COUNT) {}
320
~SigninStatus()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
FindToken(const std::string & account_id,const std::string & consumer_id,const OAuth2TokenService::ScopeSet & scopes)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
ToValue(std::string product_version)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