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.
20const char* TokenService::kServices[] = {
21  GaiaConstants::kGaiaService,
22  GaiaConstants::kSyncService,
23  GaiaConstants::kTalkService,
24  GaiaConstants::kDeviceManagementService
25};
26
27TokenService::TokenService()
28    : token_loading_query_(0) {
29  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
30}
31
32TokenService::~TokenService() {
33  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
34  ResetCredentialsInMemory();
35}
36
37void 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
73void 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
91void 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
102void TokenService::LoadTokensFromDB() {
103  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
104  token_loading_query_ = web_data_service_->GetAllTokens(this);
105}
106
107void 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
113void TokenService::EraseTokensFromDB() {
114  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
115  web_data_service_->RemoveAllTokens();
116}
117
118bool TokenService::AreCredentialsValid() const {
119  return !credentials_.lsid.empty() && !credentials_.sid.empty();
120}
121
122bool TokenService::HasLsid() const {
123  return !credentials_.lsid.empty();
124}
125
126const std::string& TokenService::GetLsid() const {
127  return credentials_.lsid;
128}
129
130void 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.
142bool TokenService::HasTokenForService(const char* const service) const {
143  return token_map_.count(service) > 0;
144}
145
146const 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.
161void 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
172void 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
183void 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
189void 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
198void 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
205void 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.
228void 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
254void 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