Home | History | Annotate | Download | only in signin
      1 // Copyright 2013 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 "chrome/browser/signin/token_service.h"
      6 
      7 #include "base/basictypes.h"
      8 #include "base/command_line.h"
      9 #include "base/prefs/pref_service.h"
     10 #include "base/strings/string_util.h"
     11 #include "chrome/browser/chrome_notification_types.h"
     12 #include "chrome/browser/profiles/profile.h"
     13 #include "chrome/browser/signin/signin_manager.h"
     14 #include "chrome/browser/signin/signin_manager_factory.h"
     15 #include "chrome/browser/webdata/token_web_data.h"
     16 #include "chrome/common/chrome_switches.h"
     17 #include "chrome/common/pref_names.h"
     18 #include "content/public/browser/browser_thread.h"
     19 #include "content/public/browser/notification_service.h"
     20 #include "content/public/browser/notification_source.h"
     21 #include "google_apis/gaia/gaia_auth_fetcher.h"
     22 #include "google_apis/gaia/gaia_constants.h"
     23 #include "net/url_request/url_request_context_getter.h"
     24 
     25 #if defined(OS_CHROMEOS)
     26 #include "base/metrics/histogram.h"
     27 #endif
     28 
     29 using content::BrowserThread;
     30 using namespace signin_internals_util;
     31 
     32 namespace {
     33 
     34 // List of services that are capable of ClientLogin-based authentication.
     35 const char* kServices[] = {
     36   GaiaConstants::kSyncService,
     37   GaiaConstants::kLSOService
     38 };
     39 
     40 #define FOR_DIAGNOSTICS_OBSERVERS(func)                               \
     41   do {                                                                \
     42     FOR_EACH_OBSERVER(SigninDiagnosticsObserver,                      \
     43                       signin_diagnostics_observers_,                  \
     44                       func);                                          \
     45   } while (0)                                                         \
     46 
     47 }  // namespace
     48 
     49 
     50 TokenService::TokenService()
     51     : profile_(NULL),
     52       token_loading_query_(0),
     53       tokens_loaded_(false) {
     54   // Allow constructor to be called outside the UI thread, so it can be mocked
     55   // out for unit tests.
     56 
     57   COMPILE_ASSERT(arraysize(kServices) == arraysize(fetchers_),
     58                  kServices_and_fetchers_dont_have_same_size);
     59 }
     60 
     61 TokenService::~TokenService() {
     62 }
     63 
     64 void TokenService::Shutdown() {
     65   if (!source_.empty()) {
     66     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
     67     ResetCredentialsInMemory();
     68   }
     69   token_web_data_ = NULL;
     70 }
     71 
     72 void TokenService::Initialize(const char* const source,
     73                               Profile* profile) {
     74   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
     75   if (!source_.empty()) {
     76     // Already initialized.
     77     return;
     78   }
     79   DCHECK(!profile_);
     80   profile_ = profile;
     81   getter_ = profile->GetRequestContext();
     82   // Since the user can create a bookmark in incognito, sync may be running.
     83   // Thus we have to go for explicit access.
     84   token_web_data_ = TokenWebData::FromBrowserContext(profile);
     85   source_ = std::string(source);
     86 
     87   CommandLine* cmd_line = CommandLine::ForCurrentProcess();
     88   // Allow the token service to be cleared from the command line. We rely on
     89   // SigninManager::Initialize() being called to clear out the
     90   // kGoogleServicesUsername pref before we call EraseTokensFromDB() as
     91   // otherwise the system would be in an invalid state.
     92   if (cmd_line->HasSwitch(switches::kClearTokenService))
     93     EraseTokensFromDB();
     94 
     95   // Allow a token to be injected from the command line.
     96   if (cmd_line->HasSwitch(switches::kSetToken)) {
     97     std::string value = cmd_line->GetSwitchValueASCII(switches::kSetToken);
     98     int separator = value.find(':');
     99     std::string service = value.substr(0, separator);
    100     std::string token = value.substr(separator + 1);
    101     token_map_[service] = token;
    102     SaveAuthTokenToDB(service, token);
    103   }
    104 }
    105 
    106 void TokenService::AddAuthTokenManually(const std::string& service,
    107                                         const std::string& auth_token) {
    108   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    109   VLOG(1) << "Got an authorization token for " << service;
    110   token_map_[service] = auth_token;
    111   FireTokenAvailableNotification(service, auth_token);
    112   SaveAuthTokenToDB(service, auth_token);
    113 
    114 // We don't ever want to fetch OAuth2 tokens from LSO service token in case
    115 // when ChromeOS is in forced OAuth2 use mode. OAuth2 token should only
    116 // arrive into token service exclusively through UpdateCredentialsWithOAuth2.
    117 #if !defined(OS_CHROMEOS)
    118   // If we got ClientLogin token for "lso" service, and we don't already have
    119   // OAuth2 tokens, start fetching OAuth2 login scoped token pair.
    120   if (service == GaiaConstants::kLSOService && !HasOAuthLoginToken()) {
    121     int index = GetServiceIndex(service);
    122     CHECK_GE(index, 0);
    123     // iOS fetches the service tokens outside of the TokenService.
    124     if (!fetchers_[index].get()) {
    125       fetchers_[index].reset(new GaiaAuthFetcher(this, source_, getter_.get()));
    126     }
    127     fetchers_[index]->StartLsoForOAuthLoginTokenExchange(auth_token);
    128   }
    129 #endif
    130 }
    131 
    132 
    133 void TokenService::ResetCredentialsInMemory() {
    134   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    135 
    136   // Terminate any running fetchers. Callbacks will not return.
    137   for (size_t i = 0; i < arraysize(kServices); ++i) {
    138     fetchers_[i].reset();
    139   }
    140 
    141   // Cancel pending loads. Callbacks will not return.
    142   if (token_loading_query_) {
    143     token_web_data_->CancelRequest(token_loading_query_);
    144     token_loading_query_ = 0;
    145   }
    146 
    147   tokens_loaded_ = false;
    148   token_map_.clear();
    149   credentials_ = GaiaAuthConsumer::ClientLoginResult();
    150 }
    151 
    152 void TokenService::UpdateCredentials(
    153     const GaiaAuthConsumer::ClientLoginResult& credentials) {
    154   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    155   credentials_ = credentials;
    156 
    157   SaveAuthTokenToDB(GaiaConstants::kGaiaLsid, credentials.lsid);
    158   SaveAuthTokenToDB(GaiaConstants::kGaiaSid, credentials.sid);
    159 
    160   // Cancel any currently running requests.
    161   for (size_t i = 0; i < arraysize(kServices); i++) {
    162     fetchers_[i].reset();
    163   }
    164 
    165   // Notify AboutSigninInternals that a new lsid and sid are available.
    166   FOR_DIAGNOSTICS_OBSERVERS(NotifySigninValueChanged(
    167       signin_internals_util::SID, credentials.sid));
    168   FOR_DIAGNOSTICS_OBSERVERS(NotifySigninValueChanged(LSID, credentials.lsid));
    169 }
    170 
    171 void TokenService::UpdateCredentialsWithOAuth2(
    172     const GaiaAuthConsumer::ClientOAuthResult& oauth2_tokens) {
    173   SaveOAuth2Credentials(oauth2_tokens);
    174 }
    175 
    176 void TokenService::LoadTokensFromDB() {
    177   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    178   if (token_web_data_.get())
    179     token_loading_query_ = token_web_data_->GetAllTokens(this);
    180 }
    181 
    182 void TokenService::SaveAuthTokenToDB(const std::string& service,
    183                                      const std::string& auth_token) {
    184   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    185   if (token_web_data_.get())
    186     token_web_data_->SetTokenForService(service, auth_token);
    187 }
    188 
    189 void TokenService::EraseTokensFromDB() {
    190   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    191   if (token_web_data_.get())
    192     token_web_data_->RemoveAllTokens();
    193 
    194   content::NotificationService::current()->Notify(
    195       chrome::NOTIFICATION_TOKENS_CLEARED,
    196       content::Source<TokenService>(this),
    197       content::NotificationService::NoDetails());
    198 
    199   // Clear in-memory token values stored by AboutSigninInternals
    200   // Note that although this is clearing in-memory values, it belongs here and
    201   // not in ResetCredentialsInMemory() (which is invoked both on sign out and
    202   // shutdown).
    203   for (size_t i = 0; i < kNumTokenPrefs; ++i) {
    204     FOR_DIAGNOSTICS_OBSERVERS(NotifyClearStoredToken(kTokenPrefsArray[i]));
    205   }
    206 
    207 }
    208 
    209 bool TokenService::TokensLoadedFromDB() const {
    210   return tokens_loaded_;
    211 }
    212 
    213 // static
    214 int TokenService::GetServiceIndex(const std::string& service) {
    215   for (size_t i = 0; i < arraysize(kServices); ++i) {
    216     if (kServices[i] == service)
    217       return i;
    218   }
    219   return -1;
    220 }
    221 
    222 bool TokenService::AreCredentialsValid() const {
    223   return !credentials_.lsid.empty() && !credentials_.sid.empty();
    224 }
    225 
    226 void TokenService::StartFetchingTokens() {
    227   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    228   DCHECK(AreCredentialsValid());
    229   for (size_t i = 0; i < arraysize(kServices); i++) {
    230     fetchers_[i].reset(new GaiaAuthFetcher(this, source_, getter_.get()));
    231     fetchers_[i]->StartIssueAuthToken(
    232         credentials_.sid, credentials_.lsid, kServices[i]);
    233   }
    234 }
    235 
    236 // Services dependent on a token will check if a token is available.
    237 // If it isn't, they'll go to sleep until they get a token event.
    238 bool TokenService::HasTokenForService(const char* service) const {
    239   return token_map_.count(service) > 0;
    240 }
    241 
    242 const std::string& TokenService::GetTokenForService(
    243     const char* const service) const {
    244 
    245   if (token_map_.count(service) > 0) {
    246     // Note map[key] is not const.
    247     return (*token_map_.find(service)).second;
    248   }
    249   return EmptyString();
    250 }
    251 
    252 bool TokenService::HasOAuthLoginToken() const {
    253   return HasTokenForService(GaiaConstants::kGaiaOAuth2LoginRefreshToken);
    254 }
    255 
    256 const std::string& TokenService::GetOAuth2LoginRefreshToken() const {
    257   return GetTokenForService(GaiaConstants::kGaiaOAuth2LoginRefreshToken);
    258 }
    259 
    260 // static
    261 void TokenService::GetServiceNames(std::vector<std::string>* names) {
    262   names->resize(arraysize(kServices));
    263   std::copy(kServices, kServices + arraysize(kServices), names->begin());
    264 }
    265 
    266 // Note that this can fire twice or more for any given service.
    267 // It can fire once from the DB read, and then once from the initial
    268 // fetcher. Future fetches can cause more notification firings.
    269 // The DB read will not however fire a notification if the fetcher
    270 // returned first. So it's always safe to use the latest notification.
    271 void TokenService::FireTokenAvailableNotification(
    272     const std::string& service,
    273     const std::string& auth_token) {
    274 
    275   TokenAvailableDetails details(service, auth_token);
    276   content::NotificationService::current()->Notify(
    277       chrome::NOTIFICATION_TOKEN_AVAILABLE,
    278       content::Source<TokenService>(this),
    279       content::Details<const TokenAvailableDetails>(&details));
    280 }
    281 
    282 void TokenService::FireTokenRequestFailedNotification(
    283     const std::string& service,
    284     const GoogleServiceAuthError& error) {
    285 
    286 #if defined(OS_CHROMEOS)
    287   std::string metric = "TokenService.TokenRequestFailed." + service;
    288   // We can't use the UMA_HISTOGRAM_ENUMERATION here since the macro creates
    289   // a static histogram in the function - locking us to one metric name. Since
    290   // the metric name can be "TokenService.TokenRequestFailed." + one of four
    291   // different values, we need to create the histogram ourselves and add the
    292   // sample.
    293   base::HistogramBase* histogram =
    294       base::LinearHistogram::FactoryGet(
    295           metric,
    296           1,
    297           GoogleServiceAuthError::NUM_STATES,
    298           GoogleServiceAuthError::NUM_STATES + 1,
    299           base::HistogramBase::kUmaTargetedHistogramFlag);
    300   histogram->Add(error.state());
    301 #endif
    302 
    303   FOR_DIAGNOSTICS_OBSERVERS(
    304       NotifyTokenReceivedFailure(service, error.ToString()));
    305 
    306   TokenRequestFailedDetails details(service, error);
    307   content::NotificationService::current()->Notify(
    308       chrome::NOTIFICATION_TOKEN_REQUEST_FAILED,
    309       content::Source<TokenService>(this),
    310       content::Details<const TokenRequestFailedDetails>(&details));
    311 }
    312 
    313 void TokenService::IssueAuthTokenForTest(const std::string& service,
    314                                          const std::string& auth_token) {
    315   token_map_[service] = auth_token;
    316   FireTokenAvailableNotification(service, auth_token);
    317 }
    318 
    319 void TokenService::OnIssueAuthTokenSuccess(const std::string& service,
    320                                            const std::string& auth_token) {
    321   FOR_DIAGNOSTICS_OBSERVERS(
    322       NotifyTokenReceivedSuccess(service, auth_token, true));
    323   AddAuthTokenManually(service, auth_token);
    324 }
    325 
    326 void TokenService::OnIssueAuthTokenFailure(const std::string& service,
    327     const GoogleServiceAuthError& error) {
    328   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    329   LOG(WARNING) << "Auth token issuing failed for service:" << service
    330                << ", error: " << error.ToString();
    331   FOR_DIAGNOSTICS_OBSERVERS(
    332       NotifyTokenReceivedFailure(service, error.ToString()));
    333   FireTokenRequestFailedNotification(service, error);
    334 }
    335 
    336 void TokenService::OnClientOAuthSuccess(const ClientOAuthResult& result) {
    337   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    338   VLOG(1) << "Got OAuth2 login token pair";
    339   SaveOAuth2Credentials(result);
    340 }
    341 
    342 void TokenService::SaveOAuth2Credentials(const ClientOAuthResult& result) {
    343   token_map_[GaiaConstants::kGaiaOAuth2LoginRefreshToken] =
    344       result.refresh_token;
    345   SaveAuthTokenToDB(GaiaConstants::kGaiaOAuth2LoginRefreshToken,
    346       result.refresh_token);
    347   // We don't save expiration information for now.
    348 
    349   FOR_DIAGNOSTICS_OBSERVERS(
    350       NotifyTokenReceivedSuccess(GaiaConstants::kGaiaOAuth2LoginRefreshToken,
    351                                  result.refresh_token, true));
    352 
    353   FireTokenAvailableNotification(GaiaConstants::kGaiaOAuth2LoginRefreshToken,
    354       result.refresh_token);
    355 }
    356 
    357 void TokenService::OnClientOAuthFailure(
    358    const GoogleServiceAuthError& error) {
    359   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    360   LOG(WARNING) << "OAuth2 login token pair fetch failed: " << error.ToString();
    361   FireTokenRequestFailedNotification(
    362       GaiaConstants::kGaiaOAuth2LoginRefreshToken, error);
    363 }
    364 
    365 void TokenService::OnWebDataServiceRequestDone(WebDataServiceBase::Handle h,
    366                                                const WDTypedResult* result) {
    367   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    368   DCHECK(token_loading_query_);
    369   token_loading_query_ = 0;
    370 
    371   // If the fetch failed, there will be no result. In that case, we just don't
    372   // load any tokens at all from the DB.
    373   if (result) {
    374     DCHECK(result->GetType() == TOKEN_RESULT);
    375     const WDResult<std::map<std::string, std::string> > * token_result =
    376         static_cast<const WDResult<std::map<std::string, std::string> > * > (
    377             result);
    378     LoadTokensIntoMemory(token_result->GetValue(), &token_map_);
    379   }
    380 
    381   tokens_loaded_ = true;
    382   content::NotificationService::current()->Notify(
    383       chrome::NOTIFICATION_TOKEN_LOADING_FINISHED,
    384       content::Source<TokenService>(this),
    385       content::NotificationService::NoDetails());
    386 }
    387 
    388 // Load tokens from the db_token map into the in memory token map.
    389 void TokenService::LoadTokensIntoMemory(
    390     const std::map<std::string, std::string>& db_tokens,
    391     std::map<std::string, std::string>* in_memory_tokens) {
    392   // Ensure that there are no active fetchers at the time we first load
    393   // tokens from the DB into memory.
    394   for (size_t i = 0; i < arraysize(kServices); ++i) {
    395     DCHECK(NULL == fetchers_[i].get());
    396   }
    397 
    398   for (size_t i = 0; i < arraysize(kServices); i++) {
    399     LoadSingleTokenIntoMemory(db_tokens, in_memory_tokens, kServices[i]);
    400   }
    401   LoadSingleTokenIntoMemory(db_tokens, in_memory_tokens,
    402       GaiaConstants::kGaiaOAuth2LoginRefreshToken);
    403 
    404   if (credentials_.lsid.empty() && credentials_.sid.empty()) {
    405     // Look for GAIA SID and LSID tokens.  If we have both, and the current
    406     // crendentials are empty, update the credentials.
    407     std::string lsid;
    408     std::string sid;
    409 
    410     if (db_tokens.count(GaiaConstants::kGaiaLsid) > 0)
    411       lsid = db_tokens.find(GaiaConstants::kGaiaLsid)->second;
    412 
    413     if (db_tokens.count(GaiaConstants::kGaiaSid) > 0)
    414       sid = db_tokens.find(GaiaConstants::kGaiaSid)->second;
    415 
    416     if (!lsid.empty() && !sid.empty()) {
    417       FOR_DIAGNOSTICS_OBSERVERS(NotifySigninValueChanged(
    418           signin_internals_util::SID, sid));
    419       FOR_DIAGNOSTICS_OBSERVERS(NotifySigninValueChanged(LSID, lsid));
    420 
    421       credentials_ = GaiaAuthConsumer::ClientLoginResult(sid,
    422                                                          lsid,
    423                                                          std::string(),
    424                                                          std::string());
    425     }
    426   }
    427 }
    428 
    429 void TokenService::LoadSingleTokenIntoMemory(
    430     const std::map<std::string, std::string>& db_tokens,
    431     std::map<std::string, std::string>* in_memory_tokens,
    432     const std::string& service) {
    433   // OnIssueAuthTokenSuccess should come from the same thread.
    434   // If a token is already present in the map, it could only have
    435   // come from a DB read or from IssueAuthToken. Since we should never
    436   // fetch from the DB twice in a browser session, it must be from
    437   // OnIssueAuthTokenSuccess, which is a live fetcher.
    438   //
    439   // Network fetched tokens take priority over DB tokens, so exclude tokens
    440   // which have already been loaded by the fetcher.
    441   if (!in_memory_tokens->count(service) && db_tokens.count(service)) {
    442     std::string db_token = db_tokens.find(service)->second;
    443     if (!db_token.empty()) {
    444       VLOG(1) << "Loading " << service << " token from DB: " << db_token;
    445       (*in_memory_tokens)[service] = db_token;
    446       FireTokenAvailableNotification(service, db_token);
    447       // Failures are only for network errors.
    448 
    449       // Update the token info for about:sigin-internals, but don't update the
    450       // time-stamps since we only care about the time it was downloaded.
    451       FOR_DIAGNOSTICS_OBSERVERS(
    452           NotifyTokenReceivedSuccess(service, db_token, false));
    453     }
    454   }
    455 }
    456 
    457 void TokenService::AddSigninDiagnosticsObserver(
    458     SigninDiagnosticsObserver* observer) {
    459   signin_diagnostics_observers_.AddObserver(observer);
    460 }
    461 
    462 void TokenService::RemoveSigninDiagnosticsObserver(
    463     SigninDiagnosticsObserver* observer) {
    464   signin_diagnostics_observers_.RemoveObserver(observer);
    465 }
    466