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