Home | History | Annotate | Download | only in gaia
      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, GaiaConstants::kChromeSource,
     47                           helper_->request_context()));
     48   gaia_auth_fetcher_->StartGetCheckConnectionInfo();
     49 }
     50 
     51 bool MergeSessionHelper::ExternalCcResultFetcher::IsRunning() {
     52   return gaia_auth_fetcher_ || fetchers_.size() > 0u;
     53 }
     54 
     55 void MergeSessionHelper::ExternalCcResultFetcher::TimeoutForTests() {
     56   Timeout();
     57 }
     58 
     59 void
     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 
     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 
    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 
    139 void MergeSessionHelper::ExternalCcResultFetcher::Timeout() {
    140   CleanupTransientState();
    141 }
    142 
    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 
    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 
    164 MergeSessionHelper::~MergeSessionHelper() {
    165   DCHECK(accounts_.empty());
    166 }
    167 
    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 
    176 void MergeSessionHelper::AddObserver(Observer* observer) {
    177   observer_list_.AddObserver(observer);
    178 }
    179 
    180 void MergeSessionHelper::RemoveObserver(Observer* observer) {
    181   observer_list_.RemoveObserver(observer);
    182 }
    183 
    184 void MergeSessionHelper::CancelAll() {
    185   VLOG(1) << "MergeSessionHelper::CancelAll";
    186   gaia_auth_fetcher_.reset();
    187   uber_token_fetcher_.reset();
    188   accounts_.clear();
    189 }
    190 
    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 
    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 
    239 void MergeSessionHelper::LogOutAllAccounts() {
    240   VLOG(1) << "MergeSessionHelper::LogOutAllAccounts";
    241   LogOutInternal("", std::vector<std::string>());
    242 }
    243 
    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 
    254 void MergeSessionHelper::StartFetchingExternalCcResult() {
    255   result_fetcher_.Start();
    256 }
    257 
    258 bool MergeSessionHelper::StillFetchingExternalCcResult() {
    259   return result_fetcher_.IsRunning();
    260 }
    261 
    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 
    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 
    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 
    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 
    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 
    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 
    321 void MergeSessionHelper::OnURLFetchComplete(const net::URLFetcher* source) {
    322   DCHECK(accounts_.front().empty());
    323   VLOG(1) << "MergeSessionHelper::OnURLFetchComplete";
    324   HandleNextAccount();
    325 }
    326 
    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