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