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