Home | History | Annotate | Download | only in gaia
      1 // Copyright (c) 2012 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/oauth2_api_call_flow.h"
      6 
      7 #include <string>
      8 #include <vector>
      9 
     10 #include "base/basictypes.h"
     11 #include "base/strings/stringprintf.h"
     12 #include "google_apis/gaia/gaia_urls.h"
     13 #include "net/base/escape.h"
     14 #include "net/base/load_flags.h"
     15 #include "net/http/http_status_code.h"
     16 #include "net/url_request/url_fetcher.h"
     17 #include "net/url_request/url_request_context_getter.h"
     18 #include "net/url_request/url_request_status.h"
     19 
     20 using net::ResponseCookies;
     21 using net::URLFetcher;
     22 using net::URLFetcherDelegate;
     23 using net::URLRequestContextGetter;
     24 using net::URLRequestStatus;
     25 
     26 namespace {
     27 static const char kAuthorizationHeaderFormat[] =
     28     "Authorization: Bearer %s";
     29 
     30 static std::string MakeAuthorizationHeader(const std::string& auth_token) {
     31   return base::StringPrintf(kAuthorizationHeaderFormat, auth_token.c_str());
     32 }
     33 }  // namespace
     34 
     35 OAuth2ApiCallFlow::OAuth2ApiCallFlow(
     36     net::URLRequestContextGetter* context,
     37     const std::string& refresh_token,
     38     const std::string& access_token,
     39     const std::vector<std::string>& scopes)
     40     : context_(context),
     41       refresh_token_(refresh_token),
     42       access_token_(access_token),
     43       scopes_(scopes),
     44       state_(INITIAL),
     45       tried_mint_access_token_(false) {
     46 }
     47 
     48 OAuth2ApiCallFlow::~OAuth2ApiCallFlow() {}
     49 
     50 void OAuth2ApiCallFlow::Start() {
     51   BeginApiCall();
     52 }
     53 
     54 void OAuth2ApiCallFlow::BeginApiCall() {
     55   CHECK(state_ == INITIAL || state_ == MINT_ACCESS_TOKEN_DONE);
     56 
     57   // If the access token is empty then directly try to mint one.
     58   if (access_token_.empty()) {
     59     BeginMintAccessToken();
     60   } else {
     61     state_ = API_CALL_STARTED;
     62     url_fetcher_.reset(CreateURLFetcher());
     63     url_fetcher_->Start();  // OnURLFetchComplete will be called.
     64   }
     65 }
     66 
     67 void OAuth2ApiCallFlow::EndApiCall(const net::URLFetcher* source) {
     68   CHECK_EQ(API_CALL_STARTED, state_);
     69   state_ = API_CALL_DONE;
     70 
     71   URLRequestStatus status = source->GetStatus();
     72   if (!status.is_success()) {
     73     state_ = ERROR_STATE;
     74     ProcessApiCallFailure(source);
     75     return;
     76   }
     77 
     78   // If the response code is 401 Unauthorized then access token may have
     79   // expired. So try generating a new access token.
     80   if (source->GetResponseCode() == net::HTTP_UNAUTHORIZED) {
     81     // If we already tried minting a new access token, don't do it again.
     82     if (tried_mint_access_token_) {
     83       state_ = ERROR_STATE;
     84       ProcessApiCallFailure(source);
     85     } else {
     86       BeginMintAccessToken();
     87     }
     88 
     89     return;
     90   }
     91 
     92   if (source->GetResponseCode() != net::HTTP_OK) {
     93     state_ = ERROR_STATE;
     94     ProcessApiCallFailure(source);
     95     return;
     96   }
     97 
     98   ProcessApiCallSuccess(source);
     99 }
    100 
    101 void OAuth2ApiCallFlow::BeginMintAccessToken() {
    102   CHECK(state_ == INITIAL || state_ == API_CALL_DONE);
    103   CHECK(!tried_mint_access_token_);
    104   state_ = MINT_ACCESS_TOKEN_STARTED;
    105   tried_mint_access_token_ = true;
    106 
    107   oauth2_access_token_fetcher_.reset(CreateAccessTokenFetcher());
    108   oauth2_access_token_fetcher_->Start(
    109       GaiaUrls::GetInstance()->oauth2_chrome_client_id(),
    110       GaiaUrls::GetInstance()->oauth2_chrome_client_secret(),
    111       refresh_token_,
    112       scopes_);
    113 }
    114 
    115 void OAuth2ApiCallFlow::EndMintAccessToken(
    116     const GoogleServiceAuthError* error) {
    117   CHECK_EQ(MINT_ACCESS_TOKEN_STARTED, state_);
    118 
    119   if (!error) {
    120     state_ = MINT_ACCESS_TOKEN_DONE;
    121     BeginApiCall();
    122   } else {
    123     state_ = ERROR_STATE;
    124     ProcessMintAccessTokenFailure(*error);
    125   }
    126 }
    127 
    128 OAuth2AccessTokenFetcher* OAuth2ApiCallFlow::CreateAccessTokenFetcher() {
    129   return new OAuth2AccessTokenFetcher(this, context_);
    130 }
    131 
    132 void OAuth2ApiCallFlow::OnURLFetchComplete(const net::URLFetcher* source) {
    133   CHECK(source);
    134   CHECK_EQ(API_CALL_STARTED, state_);
    135   EndApiCall(source);
    136 }
    137 
    138 void OAuth2ApiCallFlow::OnGetTokenSuccess(const std::string& access_token,
    139                                           const base::Time& expiration_time) {
    140   access_token_ = access_token;
    141   EndMintAccessToken(NULL);
    142 }
    143 
    144 void OAuth2ApiCallFlow::OnGetTokenFailure(
    145     const GoogleServiceAuthError& error) {
    146   EndMintAccessToken(&error);
    147 }
    148 
    149 URLFetcher* OAuth2ApiCallFlow::CreateURLFetcher() {
    150   std::string body = CreateApiCallBody();
    151   bool empty_body = body.empty();
    152   URLFetcher* result = net::URLFetcher::Create(
    153       0,
    154       CreateApiCallUrl(),
    155       empty_body ? URLFetcher::GET : URLFetcher::POST,
    156       this);
    157 
    158   result->SetRequestContext(context_);
    159   result->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES |
    160                        net::LOAD_DO_NOT_SAVE_COOKIES);
    161   result->AddExtraRequestHeader(MakeAuthorizationHeader(access_token_));
    162   // Fetchers are sometimes cancelled because a network change was detected,
    163   // especially at startup and after sign-in on ChromeOS. Retrying once should
    164   // be enough in those cases; let the fetcher retry up to 3 times just in case.
    165   // http://crbug.com/163710
    166   result->SetAutomaticallyRetryOnNetworkChanges(3);
    167 
    168   if (!empty_body)
    169     result->SetUploadData("application/x-www-form-urlencoded", body);
    170 
    171   return result;
    172 }
    173