Home | History | Annotate | Download | only in gaia
      1 // Copyright (c) 2011 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/net/gaia/token_service.h"
      6 
      7 #include "base/command_line.h"
      8 #include "base/string_util.h"
      9 #include "chrome/browser/profiles/profile.h"
     10 #include "chrome/common/chrome_switches.h"
     11 #include "chrome/common/net/gaia/gaia_auth_fetcher.h"
     12 #include "chrome/common/net/gaia/gaia_constants.h"
     13 #include "content/browser/browser_thread.h"
     14 #include "content/common/notification_service.h"
     15 #include "net/url_request/url_request_context_getter.h"
     16 
     17 // Unfortunately kNumServices must be defined in the .h.
     18 // TODO(chron): Sync doesn't use the TalkToken anymore so we can stop
     19 //              requesting it.
     20 const char* TokenService::kServices[] = {
     21   GaiaConstants::kGaiaService,
     22   GaiaConstants::kSyncService,
     23   GaiaConstants::kTalkService,
     24   GaiaConstants::kDeviceManagementService
     25 };
     26 
     27 TokenService::TokenService()
     28     : token_loading_query_(0) {
     29   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
     30 }
     31 
     32 TokenService::~TokenService() {
     33   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
     34   ResetCredentialsInMemory();
     35 }
     36 
     37 void TokenService::Initialize(const char* const source,
     38                               Profile* profile) {
     39 
     40   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
     41   if (!source_.empty()) {
     42     // Already initialized.
     43     return;
     44   }
     45   getter_ = profile->GetRequestContext();
     46   // Since the user can create a bookmark in incognito, sync may be running.
     47   // Thus we have to go for explicit access.
     48   web_data_service_ = profile->GetWebDataService(Profile::EXPLICIT_ACCESS);
     49   source_ = std::string(source);
     50 
     51 #ifndef NDEBUG
     52   CommandLine* cmd_line = CommandLine::ForCurrentProcess();
     53   // Allow the token service to be cleared from the command line.
     54   if (cmd_line->HasSwitch(switches::kClearTokenService))
     55     EraseTokensFromDB();
     56 
     57   // Allow a token to be injected from the command line.
     58   if (cmd_line->HasSwitch(switches::kSetToken)) {
     59     std::string value = cmd_line->GetSwitchValueASCII(switches::kSetToken);
     60     int separator = value.find(':');
     61     std::string service = value.substr(0, separator);
     62     std::string token = value.substr(separator + 1);
     63     token_map_[service] = token;
     64     SaveAuthTokenToDB(service, token);
     65   }
     66 #endif
     67 
     68   registrar_.Add(this,
     69                  NotificationType::TOKEN_UPDATED,
     70                  NotificationService::AllSources());
     71 }
     72 
     73 void TokenService::ResetCredentialsInMemory() {
     74   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
     75 
     76   // Terminate any running fetchers. Callbacks will not return.
     77   for (int i = 0; i < kNumServices; i++) {
     78     fetchers_[i].reset();
     79   }
     80 
     81   // Cancel pending loads. Callbacks will not return.
     82   if (token_loading_query_) {
     83     web_data_service_->CancelRequest(token_loading_query_);
     84     token_loading_query_ = 0;
     85   }
     86 
     87   token_map_.clear();
     88   credentials_ = GaiaAuthConsumer::ClientLoginResult();
     89 }
     90 
     91 void TokenService::UpdateCredentials(
     92     const GaiaAuthConsumer::ClientLoginResult& credentials) {
     93   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
     94   credentials_ = credentials;
     95 
     96   // Cancels any currently running requests.
     97   for (int i = 0; i < kNumServices; i++) {
     98     fetchers_[i].reset(new GaiaAuthFetcher(this, source_, getter_));
     99   }
    100 }
    101 
    102 void TokenService::LoadTokensFromDB() {
    103   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    104   token_loading_query_ = web_data_service_->GetAllTokens(this);
    105 }
    106 
    107 void TokenService::SaveAuthTokenToDB(const std::string& service,
    108                                      const std::string& auth_token) {
    109   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    110   web_data_service_->SetTokenForService(service, auth_token);
    111 }
    112 
    113 void TokenService::EraseTokensFromDB() {
    114   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    115   web_data_service_->RemoveAllTokens();
    116 }
    117 
    118 bool TokenService::AreCredentialsValid() const {
    119   return !credentials_.lsid.empty() && !credentials_.sid.empty();
    120 }
    121 
    122 bool TokenService::HasLsid() const {
    123   return !credentials_.lsid.empty();
    124 }
    125 
    126 const std::string& TokenService::GetLsid() const {
    127   return credentials_.lsid;
    128 }
    129 
    130 void TokenService::StartFetchingTokens() {
    131   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    132   DCHECK(AreCredentialsValid());
    133   for (int i = 0; i < kNumServices; i++) {
    134     fetchers_[i]->StartIssueAuthToken(credentials_.sid,
    135                                       credentials_.lsid,
    136                                       kServices[i]);
    137   }
    138 }
    139 
    140 // Services dependent on a token will check if a token is available.
    141 // If it isn't, they'll go to sleep until they get a token event.
    142 bool TokenService::HasTokenForService(const char* const service) const {
    143   return token_map_.count(service) > 0;
    144 }
    145 
    146 const std::string& TokenService::GetTokenForService(
    147     const char* const service) const {
    148 
    149   if (token_map_.count(service) > 0) {
    150     // Note map[key] is not const.
    151     return (*token_map_.find(service)).second;
    152   }
    153   return EmptyString();
    154 }
    155 
    156 // Note that this can fire twice or more for any given service.
    157 // It can fire once from the DB read, and then once from the initial
    158 // fetcher. Future fetches can cause more notification firings.
    159 // The DB read will not however fire a notification if the fetcher
    160 // returned first. So it's always safe to use the latest notification.
    161 void TokenService::FireTokenAvailableNotification(
    162     const std::string& service,
    163     const std::string& auth_token) {
    164 
    165   TokenAvailableDetails details(service, auth_token);
    166   NotificationService::current()->Notify(
    167       NotificationType::TOKEN_AVAILABLE,
    168       Source<TokenService>(this),
    169       Details<const TokenAvailableDetails>(&details));
    170 }
    171 
    172 void TokenService::FireTokenRequestFailedNotification(
    173     const std::string& service,
    174     const GoogleServiceAuthError& error) {
    175 
    176   TokenRequestFailedDetails details(service, error);
    177   NotificationService::current()->Notify(
    178       NotificationType::TOKEN_REQUEST_FAILED,
    179       Source<TokenService>(this),
    180       Details<const TokenRequestFailedDetails>(&details));
    181 }
    182 
    183 void TokenService::IssueAuthTokenForTest(const std::string& service,
    184                                          const std::string& auth_token) {
    185   token_map_[service] = auth_token;
    186   FireTokenAvailableNotification(service, auth_token);
    187 }
    188 
    189 void TokenService::OnIssueAuthTokenSuccess(const std::string& service,
    190                                            const std::string& auth_token) {
    191   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    192   VLOG(1) << "Got an authorization token for " << service;
    193   token_map_[service] = auth_token;
    194   FireTokenAvailableNotification(service, auth_token);
    195   SaveAuthTokenToDB(service, auth_token);
    196 }
    197 
    198 void TokenService::OnIssueAuthTokenFailure(const std::string& service,
    199     const GoogleServiceAuthError& error) {
    200   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    201   LOG(WARNING) << "Auth token issuing failed for service:" << service;
    202   FireTokenRequestFailedNotification(service, error);
    203 }
    204 
    205 void TokenService::OnWebDataServiceRequestDone(WebDataService::Handle h,
    206                                                const WDTypedResult* result) {
    207   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    208   DCHECK(token_loading_query_);
    209   token_loading_query_ = 0;
    210 
    211   // If the fetch failed, there will be no result. In that case, we just don't
    212   // load any tokens at all from the DB.
    213   if (result) {
    214     DCHECK(result->GetType() == TOKEN_RESULT);
    215     const WDResult<std::map<std::string, std::string> > * token_result =
    216         static_cast<const WDResult<std::map<std::string, std::string> > * > (
    217             result);
    218     LoadTokensIntoMemory(token_result->GetValue(), &token_map_);
    219   }
    220 
    221   NotificationService::current()->Notify(
    222       NotificationType::TOKEN_LOADING_FINISHED,
    223       Source<TokenService>(this),
    224       NotificationService::NoDetails());
    225 }
    226 
    227 // Load tokens from the db_token map into the in memory token map.
    228 void TokenService::LoadTokensIntoMemory(
    229     const std::map<std::string, std::string>& db_tokens,
    230     std::map<std::string, std::string>* in_memory_tokens) {
    231 
    232   for (int i = 0; i < kNumServices; i++) {
    233     // OnIssueAuthTokenSuccess should come from the same thread.
    234     // If a token is already present in the map, it could only have
    235     // come from a DB read or from IssueAuthToken. Since we should never
    236     // fetch from the DB twice in a browser session, it must be from
    237     // OnIssueAuthTokenSuccess, which is a live fetcher.
    238     //
    239     // Network fetched tokens take priority over DB tokens, so exclude tokens
    240     // which have already been loaded by the fetcher.
    241     if (!in_memory_tokens->count(kServices[i]) &&
    242         db_tokens.count(kServices[i])) {
    243       std::string db_token = db_tokens.find(kServices[i])->second;
    244       if (!db_token.empty()) {
    245         VLOG(1) << "Loading " << kServices[i] << "token from DB: " << db_token;
    246         (*in_memory_tokens)[kServices[i]] = db_token;
    247         FireTokenAvailableNotification(kServices[i], db_token);
    248         // Failures are only for network errors.
    249       }
    250     }
    251   }
    252 }
    253 
    254 void TokenService::Observe(NotificationType type,
    255                            const NotificationSource& source,
    256                            const NotificationDetails& details) {
    257   DCHECK(type == NotificationType::TOKEN_UPDATED);
    258   TokenAvailableDetails* tok_details =
    259       Details<TokenAvailableDetails>(details).ptr();
    260   OnIssueAuthTokenSuccess(tok_details->service(), tok_details->token());
    261 }
    262