Home | History | Annotate | Download | only in browser
      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 
     27 using base::Time;
     28 using namespace signin_internals_util;
     29 
     30 namespace {
     31 
     32 std::string GetTimeStr(base::Time time) {
     33   return base::UTF16ToUTF8(base::TimeFormatShortDateAndTime(time));
     34 }
     35 
     36 base::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 
     47 void 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 
     58 void 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 
     67 std::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)
     80 std::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 
    105 AboutSigninInternals::AboutSigninInternals(
    106     ProfileOAuth2TokenService* token_service,
    107     SigninManagerBase* signin_manager)
    108     : token_service_(token_service),
    109       signin_manager_(signin_manager),
    110       client_(NULL) {}
    111 
    112 AboutSigninInternals::~AboutSigninInternals() {}
    113 
    114 void AboutSigninInternals::AddSigninObserver(
    115     AboutSigninInternals::Observer* observer) {
    116   signin_observers_.AddObserver(observer);
    117 }
    118 
    119 void AboutSigninInternals::RemoveSigninObserver(
    120     AboutSigninInternals::Observer* observer) {
    121   signin_observers_.RemoveObserver(observer);
    122 }
    123 
    124 void 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 
    140 void 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 
    163 void 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 
    203 void 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 
    216 void AboutSigninInternals::Shutdown() {
    217   signin_manager_->RemoveSigninDiagnosticsObserver(this);
    218   token_service_->RemoveDiagnosticsObserver(this);
    219   cookie_changed_subscription_.reset();
    220 }
    221 
    222 void 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 
    230 scoped_ptr<base::DictionaryValue> AboutSigninInternals::GetSigninStatus() {
    231   return signin_status_.ToValue(client_->GetProductVersion()).Pass();
    232 }
    233 
    234 void 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 
    249 void 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 
    268 void 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 
    280 void AboutSigninInternals::OnRefreshTokenReceived(std::string status) {
    281   NotifySigninValueChanged(REFRESH_TOKEN_RECEIVED, status);
    282 }
    283 
    284 void AboutSigninInternals::OnAuthenticationResultReceived(std::string status) {
    285   NotifySigninValueChanged(AUTHENTICATION_RESULT_RECEIVED, status);
    286 }
    287 
    288 void 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 
    298 void 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 
    308 void 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 
    321 void AboutSigninInternals::OnListAccountsFailure(
    322     const GoogleServiceAuthError& error) {
    323   gaia_fetcher_.reset();
    324   VLOG(1) << "AboutSigninInternals::OnListAccountsFailure:" << error.ToString();
    325 }
    326 
    327 void 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 
    348 AboutSigninInternals::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 
    357 AboutSigninInternals::TokenInfo::~TokenInfo() {}
    358 
    359 bool AboutSigninInternals::TokenInfo::LessThan(const TokenInfo* a,
    360                                                const TokenInfo* b) {
    361   return a->consumer_id < b->consumer_id || a->scopes < b->scopes;
    362 }
    363 
    364 void AboutSigninInternals::TokenInfo::Invalidate() { removed_ = true; }
    365 
    366 base::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 
    406 AboutSigninInternals::SigninStatus::SigninStatus()
    407     : untimed_signin_fields(UNTIMED_FIELDS_COUNT),
    408       timed_signin_fields(TIMED_FIELDS_COUNT) {}
    409 
    410 AboutSigninInternals::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 
    418 AboutSigninInternals::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 
    430 scoped_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