merge_session_helper.cc revision 6e8cce623b6e4fe0c9e4af605d675dd9d0338c38
1// Copyright 2014 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 "google_apis/gaia/merge_session_helper.h"
6
7#include <vector>
8
9#include "base/json/json_reader.h"
10#include "base/stl_util.h"
11#include "base/strings/string_util.h"
12#include "base/strings/stringprintf.h"
13#include "base/time/time.h"
14#include "base/values.h"
15#include "google_apis/gaia/gaia_auth_fetcher.h"
16#include "google_apis/gaia/gaia_constants.h"
17#include "google_apis/gaia/gaia_urls.h"
18#include "google_apis/gaia/oauth2_token_service.h"
19#include "net/base/load_flags.h"
20#include "net/http/http_status_code.h"
21#include "net/url_request/url_fetcher.h"
22#include "net/url_request/url_fetcher_delegate.h"
23
24MergeSessionHelper::ExternalCcResultFetcher::ExternalCcResultFetcher(
25    MergeSessionHelper* helper) : helper_(helper) {
26  DCHECK(helper_);
27}
28
29MergeSessionHelper::ExternalCcResultFetcher::~ExternalCcResultFetcher() {
30  CleanupTransientState();
31}
32
33std::string MergeSessionHelper::ExternalCcResultFetcher::GetExternalCcResult() {
34  std::vector<std::string> results;
35  for (ResultMap::const_iterator it = results_.begin(); it != results_.end();
36       ++it) {
37    results.push_back(it->first + ":" + it->second);
38  }
39  return JoinString(results, ",");
40}
41
42void MergeSessionHelper::ExternalCcResultFetcher::Start() {
43  CleanupTransientState();
44  results_.clear();
45  gaia_auth_fetcher_.reset(
46      new GaiaAuthFetcher(this, GaiaConstants::kChromeSource,
47                          helper_->request_context()));
48  gaia_auth_fetcher_->StartGetCheckConnectionInfo();
49}
50
51bool MergeSessionHelper::ExternalCcResultFetcher::IsRunning() {
52  return gaia_auth_fetcher_ || fetchers_.size() > 0u;
53}
54
55void MergeSessionHelper::ExternalCcResultFetcher::TimeoutForTests() {
56  Timeout();
57}
58
59void
60MergeSessionHelper::ExternalCcResultFetcher::OnGetCheckConnectionInfoSuccess(
61    const std::string& data) {
62  base::Value* value = base::JSONReader::Read(data);
63  const base::ListValue* list;
64  if (!value || !value->GetAsList(&list))
65    return;
66
67  // Start a fetcher for each connection URL that needs to be checked.
68  for (size_t i = 0; i < list->GetSize(); ++i) {
69    const base::DictionaryValue* dict;
70    if (list->GetDictionary(i, &dict)) {
71      std::string token;
72      std::string url;
73      if (dict->GetString("carryBackToken", &token) &&
74          dict->GetString("url", &url)) {
75        results_[token] = "null";
76        net::URLFetcher* fetcher = CreateFetcher(GURL(url));
77        fetchers_[fetcher->GetOriginalURL()] = std::make_pair(token, fetcher);
78        fetcher->Start();
79      }
80    }
81  }
82
83  // Some fetches may timeout.  Start a timer to decide when the result fetcher
84  // has waited long enough.
85  // TODO(rogerta): I have no idea how long to wait before timing out.
86  // Gaia folks say this should take no more than 2 second even in mobile.
87  // This will need to be tweaked.
88  timer_.Start(FROM_HERE, base::TimeDelta::FromSeconds(5),
89               this, &MergeSessionHelper::ExternalCcResultFetcher::Timeout);
90}
91
92net::URLFetcher* MergeSessionHelper::ExternalCcResultFetcher::CreateFetcher(
93    const GURL& url) {
94  net::URLFetcher* fetcher = net::URLFetcher::Create(
95      0,
96      url,
97      net::URLFetcher::GET,
98      this);
99  fetcher->SetRequestContext(helper_->request_context());
100  fetcher->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES |
101                        net::LOAD_DO_NOT_SAVE_COOKIES);
102
103  // Fetchers are sometimes cancelled because a network change was detected,
104  // especially at startup and after sign-in on ChromeOS.
105  fetcher->SetAutomaticallyRetryOnNetworkChanges(1);
106  return fetcher;
107}
108
109void MergeSessionHelper::ExternalCcResultFetcher::OnURLFetchComplete(
110    const net::URLFetcher* source) {
111  const GURL& url = source->GetOriginalURL();
112  const net::URLRequestStatus& status = source->GetStatus();
113  int response_code = source->GetResponseCode();
114  if (status.is_success() && response_code == net::HTTP_OK &&
115      fetchers_.count(url) > 0) {
116    std::string data;
117    source->GetResponseAsString(&data);
118    // Only up to the first 16 characters of the response are important to GAIA.
119    // Truncate if needed to keep amount data sent back to GAIA down.
120    if (data.size() > 16)
121      data.resize(16);
122    results_[fetchers_[url].first] = data;
123
124    // Clean up tracking of this fetcher.  The rest will be cleaned up after
125    // the timer expires in CleanupTransientState().
126    DCHECK_EQ(source, fetchers_[url].second);
127    fetchers_.erase(url);
128    delete source;
129
130    // If all expected responses have been received, cancel the timer and
131    // report the result.
132    if (fetchers_.empty()) {
133      timer_.Stop();
134      CleanupTransientState();
135    }
136  }
137}
138
139void MergeSessionHelper::ExternalCcResultFetcher::Timeout() {
140  CleanupTransientState();
141}
142
143void MergeSessionHelper::ExternalCcResultFetcher::CleanupTransientState() {
144  gaia_auth_fetcher_.reset();
145
146  for (URLToTokenAndFetcher::const_iterator it = fetchers_.begin();
147       it != fetchers_.end(); ++it) {
148    delete it->second.second;
149  }
150  fetchers_.clear();
151}
152
153MergeSessionHelper::MergeSessionHelper(
154    OAuth2TokenService* token_service,
155    net::URLRequestContextGetter* request_context,
156    Observer* observer)
157    : token_service_(token_service),
158      request_context_(request_context),
159      result_fetcher_(this) {
160  if (observer)
161    AddObserver(observer);
162}
163
164MergeSessionHelper::~MergeSessionHelper() {
165  DCHECK(accounts_.empty());
166}
167
168void MergeSessionHelper::LogIn(const std::string& account_id) {
169  DCHECK(!account_id.empty());
170  VLOG(1) << "MergeSessionHelper::LogIn: " << account_id;
171  accounts_.push_back(account_id);
172  if (accounts_.size() == 1)
173    StartFetching();
174}
175
176void MergeSessionHelper::AddObserver(Observer* observer) {
177  observer_list_.AddObserver(observer);
178}
179
180void MergeSessionHelper::RemoveObserver(Observer* observer) {
181  observer_list_.RemoveObserver(observer);
182}
183
184void MergeSessionHelper::CancelAll() {
185  VLOG(1) << "MergeSessionHelper::CancelAll";
186  gaia_auth_fetcher_.reset();
187  uber_token_fetcher_.reset();
188  accounts_.clear();
189}
190
191void MergeSessionHelper::LogOut(
192    const std::string& account_id,
193    const std::vector<std::string>& accounts) {
194  DCHECK(!account_id.empty());
195  VLOG(1) << "MergeSessionHelper::LogOut: " << account_id
196          << " accounts=" << accounts.size();
197  LogOutInternal(account_id, accounts);
198}
199
200void MergeSessionHelper::LogOutInternal(
201    const std::string& account_id,
202    const std::vector<std::string>& accounts) {
203  bool pending = !accounts_.empty();
204
205  if (pending) {
206    for (std::deque<std::string>::const_iterator it = accounts_.begin() + 1;
207        it != accounts_.end(); it++) {
208      if (!it->empty() &&
209          (std::find(accounts.begin(), accounts.end(), *it) == accounts.end() ||
210           *it == account_id)) {
211        // We have a pending log in request for an account followed by
212        // a signout.
213        GoogleServiceAuthError error(GoogleServiceAuthError::REQUEST_CANCELED);
214        SignalComplete(*it, error);
215      }
216    }
217
218    // Remove every thing in the work list besides the one that is running.
219    accounts_.resize(1);
220  }
221
222  // Signal a logout to be the next thing to do unless the pending
223  // action is already a logout.
224  if (!pending || !accounts_.front().empty())
225    accounts_.push_back("");
226
227  for (std::vector<std::string>::const_iterator it = accounts.begin();
228      it != accounts.end(); it++) {
229    if (*it != account_id) {
230      DCHECK(!it->empty());
231      accounts_.push_back(*it);
232    }
233  }
234
235  if (!pending)
236    StartLogOutUrlFetch();
237}
238
239void MergeSessionHelper::LogOutAllAccounts() {
240  VLOG(1) << "MergeSessionHelper::LogOutAllAccounts";
241  LogOutInternal("", std::vector<std::string>());
242}
243
244void MergeSessionHelper::SignalComplete(
245    const std::string& account_id,
246    const GoogleServiceAuthError& error) {
247  // Its possible for the observer to delete |this| object.  Don't access
248  // access any members after this calling the observer.  This method should
249  // be the last call in any other method.
250  FOR_EACH_OBSERVER(Observer, observer_list_,
251                    MergeSessionCompleted(account_id, error));
252}
253
254void MergeSessionHelper::StartFetchingExternalCcResult() {
255  result_fetcher_.Start();
256}
257
258bool MergeSessionHelper::StillFetchingExternalCcResult() {
259  return result_fetcher_.IsRunning();
260}
261
262void MergeSessionHelper::StartLogOutUrlFetch() {
263  DCHECK(accounts_.front().empty());
264  VLOG(1) << "MergeSessionHelper::StartLogOutUrlFetch";
265  GURL logout_url(GaiaUrls::GetInstance()->service_logout_url());
266  net::URLFetcher* fetcher =
267      net::URLFetcher::Create(logout_url, net::URLFetcher::GET, this);
268  fetcher->SetRequestContext(request_context_);
269  fetcher->Start();
270}
271
272void MergeSessionHelper::OnUbertokenSuccess(const std::string& uber_token) {
273  VLOG(1) << "MergeSessionHelper::OnUbertokenSuccess"
274          << " account=" << accounts_.front();
275  gaia_auth_fetcher_.reset(new GaiaAuthFetcher(this,
276                                               GaiaConstants::kChromeSource,
277                                               request_context_));
278
279  // It's possible that not all external checks have completed.
280  // GetExternalCcResult() returns results for those that have.
281  gaia_auth_fetcher_->StartMergeSession(uber_token,
282                                        result_fetcher_.GetExternalCcResult());
283}
284
285void MergeSessionHelper::OnUbertokenFailure(
286    const GoogleServiceAuthError& error) {
287  VLOG(1) << "Failed to retrieve ubertoken"
288          << " account=" << accounts_.front()
289          << " error=" << error.ToString();
290  const std::string account_id = accounts_.front();
291  HandleNextAccount();
292  SignalComplete(account_id, error);
293}
294
295void MergeSessionHelper::OnMergeSessionSuccess(const std::string& data) {
296  VLOG(1) << "MergeSession successful account=" << accounts_.front();
297  const std::string account_id = accounts_.front();
298  HandleNextAccount();
299  SignalComplete(account_id, GoogleServiceAuthError::AuthErrorNone());
300}
301
302void MergeSessionHelper::OnMergeSessionFailure(
303    const GoogleServiceAuthError& error) {
304  VLOG(1) << "Failed MergeSession"
305          << " account=" << accounts_.front()
306          << " error=" << error.ToString();
307  const std::string account_id = accounts_.front();
308  HandleNextAccount();
309  SignalComplete(account_id, error);
310}
311
312void MergeSessionHelper::StartFetching() {
313  VLOG(1) << "MergeSessionHelper::StartFetching account_id="
314          << accounts_.front();
315  uber_token_fetcher_.reset(new UbertokenFetcher(token_service_,
316                                                 this,
317                                                 request_context_));
318  uber_token_fetcher_->StartFetchingToken(accounts_.front());
319}
320
321void MergeSessionHelper::OnURLFetchComplete(const net::URLFetcher* source) {
322  DCHECK(accounts_.front().empty());
323  VLOG(1) << "MergeSessionHelper::OnURLFetchComplete";
324  HandleNextAccount();
325}
326
327void MergeSessionHelper::HandleNextAccount() {
328  VLOG(1) << "MergeSessionHelper::HandleNextAccount";
329  accounts_.pop_front();
330  gaia_auth_fetcher_.reset();
331  if (accounts_.empty()) {
332    VLOG(1) << "MergeSessionHelper::HandleNextAccount: no more";
333    uber_token_fetcher_.reset();
334  } else {
335    if (accounts_.front().empty()) {
336      StartLogOutUrlFetch();
337    } else {
338      StartFetching();
339    }
340  }
341}
342