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