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
TokenService()27 TokenService::TokenService()
28 : token_loading_query_(0) {
29 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
30 }
31
~TokenService()32 TokenService::~TokenService() {
33 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
34 ResetCredentialsInMemory();
35 }
36
Initialize(const char * const source,Profile * profile)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
ResetCredentialsInMemory()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
UpdateCredentials(const GaiaAuthConsumer::ClientLoginResult & credentials)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
LoadTokensFromDB()102 void TokenService::LoadTokensFromDB() {
103 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
104 token_loading_query_ = web_data_service_->GetAllTokens(this);
105 }
106
SaveAuthTokenToDB(const std::string & service,const std::string & auth_token)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
EraseTokensFromDB()113 void TokenService::EraseTokensFromDB() {
114 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
115 web_data_service_->RemoveAllTokens();
116 }
117
AreCredentialsValid() const118 bool TokenService::AreCredentialsValid() const {
119 return !credentials_.lsid.empty() && !credentials_.sid.empty();
120 }
121
HasLsid() const122 bool TokenService::HasLsid() const {
123 return !credentials_.lsid.empty();
124 }
125
GetLsid() const126 const std::string& TokenService::GetLsid() const {
127 return credentials_.lsid;
128 }
129
StartFetchingTokens()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.
HasTokenForService(const char * const service) const142 bool TokenService::HasTokenForService(const char* const service) const {
143 return token_map_.count(service) > 0;
144 }
145
GetTokenForService(const char * const service) const146 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.
FireTokenAvailableNotification(const std::string & service,const std::string & auth_token)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
FireTokenRequestFailedNotification(const std::string & service,const GoogleServiceAuthError & error)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
IssueAuthTokenForTest(const std::string & service,const std::string & auth_token)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
OnIssueAuthTokenSuccess(const std::string & service,const std::string & auth_token)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
OnIssueAuthTokenFailure(const std::string & service,const GoogleServiceAuthError & error)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
OnWebDataServiceRequestDone(WebDataService::Handle h,const WDTypedResult * result)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.
LoadTokensIntoMemory(const std::map<std::string,std::string> & db_tokens,std::map<std::string,std::string> * in_memory_tokens)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
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)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