• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
ExternalCcResultFetcher(MergeSessionHelper * helper)24 MergeSessionHelper::ExternalCcResultFetcher::ExternalCcResultFetcher(
25     MergeSessionHelper* helper) : helper_(helper) {
26   DCHECK(helper_);
27 }
28 
~ExternalCcResultFetcher()29 MergeSessionHelper::ExternalCcResultFetcher::~ExternalCcResultFetcher() {
30   CleanupTransientState();
31 }
32 
GetExternalCcResult()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 
Start()42 void 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 
IsRunning()51 bool MergeSessionHelper::ExternalCcResultFetcher::IsRunning() {
52   return gaia_auth_fetcher_ || fetchers_.size() > 0u;
53 }
54 
TimeoutForTests()55 void MergeSessionHelper::ExternalCcResultFetcher::TimeoutForTests() {
56   Timeout();
57 }
58 
59 void
OnGetCheckConnectionInfoSuccess(const std::string & data)60 MergeSessionHelper::ExternalCcResultFetcher::OnGetCheckConnectionInfoSuccess(
61     const std::string& data) {
62   scoped_ptr<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 
CreateFetcher(const GURL & url)92 net::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 
OnURLFetchComplete(const net::URLFetcher * source)109 void 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 
Timeout()139 void MergeSessionHelper::ExternalCcResultFetcher::Timeout() {
140   CleanupTransientState();
141 }
142 
CleanupTransientState()143 void 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 
MergeSessionHelper(OAuth2TokenService * token_service,net::URLRequestContextGetter * request_context,Observer * observer)153 MergeSessionHelper::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 
~MergeSessionHelper()164 MergeSessionHelper::~MergeSessionHelper() {
165   DCHECK(accounts_.empty());
166 }
167 
LogIn(const std::string & account_id)168 void 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 
AddObserver(Observer * observer)176 void MergeSessionHelper::AddObserver(Observer* observer) {
177   observer_list_.AddObserver(observer);
178 }
179 
RemoveObserver(Observer * observer)180 void MergeSessionHelper::RemoveObserver(Observer* observer) {
181   observer_list_.RemoveObserver(observer);
182 }
183 
CancelAll()184 void MergeSessionHelper::CancelAll() {
185   VLOG(1) << "MergeSessionHelper::CancelAll";
186   gaia_auth_fetcher_.reset();
187   uber_token_fetcher_.reset();
188   accounts_.clear();
189 }
190 
LogOut(const std::string & account_id,const std::vector<std::string> & accounts)191 void 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 
LogOutInternal(const std::string & account_id,const std::vector<std::string> & accounts)200 void 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 
LogOutAllAccounts()239 void MergeSessionHelper::LogOutAllAccounts() {
240   VLOG(1) << "MergeSessionHelper::LogOutAllAccounts";
241   LogOutInternal("", std::vector<std::string>());
242 }
243 
SignalComplete(const std::string & account_id,const GoogleServiceAuthError & error)244 void 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 
StartFetchingExternalCcResult()254 void MergeSessionHelper::StartFetchingExternalCcResult() {
255   result_fetcher_.Start();
256 }
257 
StillFetchingExternalCcResult()258 bool MergeSessionHelper::StillFetchingExternalCcResult() {
259   return result_fetcher_.IsRunning();
260 }
261 
StartLogOutUrlFetch()262 void 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 
OnUbertokenSuccess(const std::string & uber_token)272 void 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 
OnUbertokenFailure(const GoogleServiceAuthError & error)285 void 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 
OnMergeSessionSuccess(const std::string & data)295 void 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 
OnMergeSessionFailure(const GoogleServiceAuthError & error)302 void 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 
StartFetching()312 void 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 
OnURLFetchComplete(const net::URLFetcher * source)321 void MergeSessionHelper::OnURLFetchComplete(const net::URLFetcher* source) {
322   DCHECK(accounts_.front().empty());
323   VLOG(1) << "MergeSessionHelper::OnURLFetchComplete";
324   HandleNextAccount();
325 }
326 
HandleNextAccount()327 void 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