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     state_ = ERROR_STATE;
     95     ProcessApiCallFailure(source);
     96     return;
     97   }
     98 
     99   ProcessApiCallSuccess(source);
    100 }
    101 
    102 void OAuth2ApiCallFlow::BeginMintAccessToken() {
    103   CHECK(state_ == INITIAL || state_ == API_CALL_DONE);
    104   CHECK(!tried_mint_access_token_);
    105   state_ = MINT_ACCESS_TOKEN_STARTED;
    106   tried_mint_access_token_ = true;
    107 
    108   oauth2_access_token_fetcher_.reset(CreateAccessTokenFetcher());
    109   oauth2_access_token_fetcher_->Start(
    110       GaiaUrls::GetInstance()->oauth2_chrome_client_id(),
    111       GaiaUrls::GetInstance()->oauth2_chrome_client_secret(),
    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 OAuth2AccessTokenFetcherImpl(this, context_, refresh_token_);
    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