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