blob: d05f57cd0f27e70f7a5b8f7a3f30a54e76087ff0 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/signin/core/browser/about_signin_internals.h"
#include "base/command_line.h"
#include "base/debug/trace_event.h"
#include "base/hash.h"
#include "base/i18n/time_formatting.h"
#include "base/logging.h"
#include "base/prefs/pref_service.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "components/signin/core/browser/profile_oauth2_token_service.h"
#include "components/signin/core/browser/signin_client.h"
#include "components/signin/core/browser/signin_internals_util.h"
#include "components/signin/core/browser/signin_manager.h"
#include "components/signin/core/common/profile_management_switches.h"
#include "components/signin/core/common/signin_switches.h"
#include "google_apis/gaia/gaia_constants.h"
using base::Time;
using namespace signin_internals_util;
namespace {
std::string GetTimeStr(base::Time time) {
return base::UTF16ToUTF8(base::TimeFormatShortDateAndTime(time));
}
base::ListValue* AddSection(base::ListValue* parent_list,
const std::string& title) {
scoped_ptr<base::DictionaryValue> section(new base::DictionaryValue());
base::ListValue* section_contents = new base::ListValue();
section->SetString("title", title);
section->Set("data", section_contents);
parent_list->Append(section.release());
return section_contents;
}
void AddSectionEntry(base::ListValue* section_list,
const std::string& field_name,
const std::string& field_status,
const std::string& field_time = "") {
scoped_ptr<base::DictionaryValue> entry(new base::DictionaryValue());
entry->SetString("label", field_name);
entry->SetString("status", field_status);
entry->SetString("time", field_time);
section_list->Append(entry.release());
}
std::string SigninStatusFieldToLabel(UntimedSigninStatusField field) {
switch (field) {
case USERNAME:
return "User Id";
case UNTIMED_FIELDS_END:
NOTREACHED();
return std::string();
}
NOTREACHED();
return std::string();
}
std::string SigninStatusFieldToLabel(TimedSigninStatusField field) {
switch (field) {
case SIGNIN_TYPE:
return "Type";
case AUTHENTICATION_RESULT_RECEIVED:
return "Last Authentication Result Received";
case REFRESH_TOKEN_RECEIVED:
return "Last RefreshToken Received";
case GET_USER_INFO_STATUS:
return "Last OnGetUserInfo Received";
case UBER_TOKEN_STATUS:
return "Last OnUberToken Received";
case MERGE_SESSION_STATUS:
return "Last OnMergeSession Received";
case TIMED_FIELDS_END:
NOTREACHED();
return "Error";
}
NOTREACHED();
return "Error";
}
} // anonymous namespace
AboutSigninInternals::AboutSigninInternals(
ProfileOAuth2TokenService* token_service,
SigninManagerBase* signin_manager)
: token_service_(token_service),
signin_manager_(signin_manager),
client_(NULL) {}
AboutSigninInternals::~AboutSigninInternals() {}
void AboutSigninInternals::AddSigninObserver(
AboutSigninInternals::Observer* observer) {
signin_observers_.AddObserver(observer);
}
void AboutSigninInternals::RemoveSigninObserver(
AboutSigninInternals::Observer* observer) {
signin_observers_.RemoveObserver(observer);
}
void AboutSigninInternals::NotifySigninValueChanged(
const UntimedSigninStatusField& field,
const std::string& value) {
unsigned int field_index = field - UNTIMED_FIELDS_BEGIN;
DCHECK(field_index >= 0 &&
field_index < signin_status_.untimed_signin_fields.size());
signin_status_.untimed_signin_fields[field_index] = value;
// Also persist these values in the prefs.
const std::string pref_path = SigninStatusFieldToString(field);
client_->GetPrefs()->SetString(pref_path.c_str(), value);
NotifyObservers();
}
void AboutSigninInternals::NotifySigninValueChanged(
const TimedSigninStatusField& field,
const std::string& value) {
unsigned int field_index = field - TIMED_FIELDS_BEGIN;
DCHECK(field_index >= 0 &&
field_index < signin_status_.timed_signin_fields.size());
Time now = Time::NowFromSystemTime();
std::string time_as_str =
base::UTF16ToUTF8(base::TimeFormatFriendlyDate(now));
TimedSigninStatusValue timed_value(value, time_as_str);
signin_status_.timed_signin_fields[field_index] = timed_value;
// Also persist these values in the prefs.
const std::string value_pref = SigninStatusFieldToString(field) + ".value";
const std::string time_pref = SigninStatusFieldToString(field) + ".time";
client_->GetPrefs()->SetString(value_pref.c_str(), value);
client_->GetPrefs()->SetString(time_pref.c_str(), time_as_str);
NotifyObservers();
}
void AboutSigninInternals::RefreshSigninPrefs() {
// Return if no client exists. Can occur in unit tests.
if (!client_)
return;
PrefService* pref_service = client_->GetPrefs();
for (int i = UNTIMED_FIELDS_BEGIN; i < UNTIMED_FIELDS_END; ++i) {
const std::string pref_path =
SigninStatusFieldToString(static_cast<UntimedSigninStatusField>(i));
signin_status_.untimed_signin_fields[i - UNTIMED_FIELDS_BEGIN] =
pref_service->GetString(pref_path.c_str());
}
for (int i = TIMED_FIELDS_BEGIN; i < TIMED_FIELDS_END; ++i) {
const std::string value_pref =
SigninStatusFieldToString(static_cast<TimedSigninStatusField>(i)) +
".value";
const std::string time_pref =
SigninStatusFieldToString(static_cast<TimedSigninStatusField>(i)) +
".time";
TimedSigninStatusValue value(pref_service->GetString(value_pref.c_str()),
pref_service->GetString(time_pref.c_str()));
signin_status_.timed_signin_fields[i - TIMED_FIELDS_BEGIN] = value;
}
// TODO(rogerta): Get status and timestamps for oauth2 tokens.
NotifyObservers();
}
void AboutSigninInternals::Initialize(SigninClient* client) {
DCHECK(!client_);
client_ = client;
RefreshSigninPrefs();
signin_manager_->AddSigninDiagnosticsObserver(this);
token_service_->AddDiagnosticsObserver(this);
}
void AboutSigninInternals::Shutdown() {
signin_manager_->RemoveSigninDiagnosticsObserver(this);
token_service_->RemoveDiagnosticsObserver(this);
}
void AboutSigninInternals::NotifyObservers() {
FOR_EACH_OBSERVER(AboutSigninInternals::Observer,
signin_observers_,
OnSigninStateChanged(
signin_status_.ToValue(client_->GetProductVersion())));
}
scoped_ptr<base::DictionaryValue> AboutSigninInternals::GetSigninStatus() {
return signin_status_.ToValue(client_->GetProductVersion()).Pass();
}
void AboutSigninInternals::OnAccessTokenRequested(
const std::string& account_id,
const std::string& consumer_id,
const OAuth2TokenService::ScopeSet& scopes) {
TokenInfo* token = signin_status_.FindToken(account_id, consumer_id, scopes);
if (token) {
*token = TokenInfo(consumer_id, scopes);
} else {
token = new TokenInfo(consumer_id, scopes);
signin_status_.token_info_map[account_id].push_back(token);
}
NotifyObservers();
}
void AboutSigninInternals::OnFetchAccessTokenComplete(
const std::string& account_id,
const std::string& consumer_id,
const OAuth2TokenService::ScopeSet& scopes,
GoogleServiceAuthError error,
base::Time expiration_time) {
TokenInfo* token = signin_status_.FindToken(account_id, consumer_id, scopes);
if (!token) {
DVLOG(1) << "Can't find token: " << account_id << ", " << consumer_id;
return;
}
token->receive_time = base::Time::Now();
token->error = error;
token->expiration_time = expiration_time;
NotifyObservers();
}
void AboutSigninInternals::OnTokenRemoved(
const std::string& account_id,
const OAuth2TokenService::ScopeSet& scopes) {
for (size_t i = 0; i < signin_status_.token_info_map[account_id].size();
++i) {
TokenInfo* token = signin_status_.token_info_map[account_id][i];
if (token->scopes == scopes)
token->Invalidate();
}
NotifyObservers();
}
void AboutSigninInternals::OnRefreshTokenReceived(std::string status) {
NotifySigninValueChanged(REFRESH_TOKEN_RECEIVED, status);
}
void AboutSigninInternals::OnAuthenticationResultReceived(std::string status) {
NotifySigninValueChanged(AUTHENTICATION_RESULT_RECEIVED, status);
}
AboutSigninInternals::TokenInfo::TokenInfo(
const std::string& consumer_id,
const OAuth2TokenService::ScopeSet& scopes)
: consumer_id(consumer_id),
scopes(scopes),
request_time(base::Time::Now()),
error(GoogleServiceAuthError::AuthErrorNone()),
removed_(false) {}
AboutSigninInternals::TokenInfo::~TokenInfo() {}
bool AboutSigninInternals::TokenInfo::LessThan(const TokenInfo* a,
const TokenInfo* b) {
return a->consumer_id < b->consumer_id || a->scopes < b->scopes;
}
void AboutSigninInternals::TokenInfo::Invalidate() { removed_ = true; }
base::DictionaryValue* AboutSigninInternals::TokenInfo::ToValue() const {
scoped_ptr<base::DictionaryValue> token_info(new base::DictionaryValue());
token_info->SetString("service", consumer_id);
std::string scopes_str;
for (OAuth2TokenService::ScopeSet::const_iterator it = scopes.begin();
it != scopes.end();
++it) {
scopes_str += *it + "<br/>";
}
token_info->SetString("scopes", scopes_str);
token_info->SetString("request_time", GetTimeStr(request_time).c_str());
if (removed_) {
token_info->SetString("status", "Token was revoked.");
} else if (!receive_time.is_null()) {
if (error == GoogleServiceAuthError::AuthErrorNone()) {
bool token_expired = expiration_time < base::Time::Now();
std::string status_str = "";
if (token_expired)
status_str = "<p style=\"color: #ffffff; background-color: #ff0000\">";
base::StringAppendF(&status_str,
"Received token at %s. Expire at %s",
GetTimeStr(receive_time).c_str(),
GetTimeStr(expiration_time).c_str());
if (token_expired)
base::StringAppendF(&status_str, "</p>");
token_info->SetString("status", status_str);
} else {
token_info->SetString(
"status",
base::StringPrintf("Failure: %s", error.ToString().c_str()));
}
} else {
token_info->SetString("status", "Waiting for response");
}
return token_info.release();
}
AboutSigninInternals::SigninStatus::SigninStatus()
: untimed_signin_fields(UNTIMED_FIELDS_COUNT),
timed_signin_fields(TIMED_FIELDS_COUNT) {}
AboutSigninInternals::SigninStatus::~SigninStatus() {
for (TokenInfoMap::iterator it = token_info_map.begin();
it != token_info_map.end();
++it) {
STLDeleteElements(&it->second);
}
}
AboutSigninInternals::TokenInfo* AboutSigninInternals::SigninStatus::FindToken(
const std::string& account_id,
const std::string& consumer_id,
const OAuth2TokenService::ScopeSet& scopes) {
for (size_t i = 0; i < token_info_map[account_id].size(); ++i) {
TokenInfo* tmp = token_info_map[account_id][i];
if (tmp->consumer_id == consumer_id && tmp->scopes == scopes)
return tmp;
}
return NULL;
}
scoped_ptr<base::DictionaryValue> AboutSigninInternals::SigninStatus::ToValue(
std::string product_version) {
scoped_ptr<base::DictionaryValue> signin_status(new base::DictionaryValue());
base::ListValue* signin_info = new base::ListValue();
signin_status->Set("signin_info", signin_info);
// A summary of signin related info first.
base::ListValue* basic_info = AddSection(signin_info, "Basic Information");
const std::string signin_status_string =
untimed_signin_fields[USERNAME - UNTIMED_FIELDS_BEGIN].empty()
? "Not Signed In"
: "Signed In";
AddSectionEntry(basic_info, "Chrome Version", product_version);
AddSectionEntry(basic_info, "Signin Status", signin_status_string);
AddSectionEntry(basic_info, "Web Based Signin Enabled?",
switches::IsEnableWebBasedSignin() == true ? "True" : "False");
AddSectionEntry(basic_info, "New Profile Management Enabled?",
switches::IsNewProfileManagement() == true ? "True" : "False");
AddSectionEntry(basic_info, "New Avatar Menu Enabled?",
switches::IsNewAvatarMenu() == true ? "True" : "False");
bool new_avatar_menu_flag =
CommandLine::ForCurrentProcess()->HasSwitch(switches::kNewAvatarMenu);
AddSectionEntry(basic_info, "New Avatar Menu Flag Set?",
new_avatar_menu_flag ? "True" : "False");
AddSectionEntry(basic_info, "Account Consistency Enabled?",
switches::IsEnableAccountConsistency() == true ? "True" : "False");
// Only add username. SID and LSID have moved to tokens section.
const std::string field =
SigninStatusFieldToLabel(static_cast<UntimedSigninStatusField>(USERNAME));
AddSectionEntry(basic_info,
field,
untimed_signin_fields[USERNAME - UNTIMED_FIELDS_BEGIN]);
// Time and status information of the possible sign in types.
base::ListValue* detailed_info =
AddSection(signin_info, "Last Signin Details");
for (int i = TIMED_FIELDS_BEGIN; i < TIMED_FIELDS_END; ++i) {
const std::string status_field_label =
SigninStatusFieldToLabel(static_cast<TimedSigninStatusField>(i));
AddSectionEntry(detailed_info,
status_field_label,
timed_signin_fields[i - TIMED_FIELDS_BEGIN].first,
timed_signin_fields[i - TIMED_FIELDS_BEGIN].second);
}
// Token information for all services.
base::ListValue* token_info = new base::ListValue();
signin_status->Set("token_info", token_info);
for (TokenInfoMap::iterator it = token_info_map.begin();
it != token_info_map.end();
++it) {
base::ListValue* token_details = AddSection(token_info, it->first);
std::sort(it->second.begin(), it->second.end(), TokenInfo::LessThan);
const std::vector<TokenInfo*>& tokens = it->second;
for (size_t i = 0; i < tokens.size(); ++i) {
base::DictionaryValue* token_info = tokens[i]->ToValue();
token_details->Append(token_info);
}
}
return signin_status.Pass();
}