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