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