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_access_token_fetcher.h" 6 7 #include <algorithm> 8 #include <string> 9 #include <vector> 10 11 #include "base/json/json_reader.h" 12 #include "base/metrics/histogram.h" 13 #include "base/metrics/sparse_histogram.h" 14 #include "base/strings/string_util.h" 15 #include "base/strings/stringprintf.h" 16 #include "base/time/time.h" 17 #include "base/values.h" 18 #include "google_apis/gaia/gaia_urls.h" 19 #include "google_apis/gaia/google_service_auth_error.h" 20 #include "net/base/escape.h" 21 #include "net/base/load_flags.h" 22 #include "net/http/http_status_code.h" 23 #include "net/url_request/url_fetcher.h" 24 #include "net/url_request/url_request_context_getter.h" 25 #include "net/url_request/url_request_status.h" 26 27 using net::ResponseCookies; 28 using net::URLFetcher; 29 using net::URLFetcherDelegate; 30 using net::URLRequestContextGetter; 31 using net::URLRequestStatus; 32 33 namespace { 34 static const char kGetAccessTokenBodyFormat[] = 35 "client_id=%s&" 36 "client_secret=%s&" 37 "grant_type=refresh_token&" 38 "refresh_token=%s"; 39 40 static const char kGetAccessTokenBodyWithScopeFormat[] = 41 "client_id=%s&" 42 "client_secret=%s&" 43 "grant_type=refresh_token&" 44 "refresh_token=%s&" 45 "scope=%s"; 46 47 static const char kAccessTokenKey[] = "access_token"; 48 static const char kExpiresInKey[] = "expires_in"; 49 static const char kErrorKey[] = "error"; 50 51 // Enumerated constants for logging server responses on 400 errors, matching 52 // RFC 6749. 53 enum OAuth2ErrorCodesForHistogram { 54 OAUTH2_ACCESS_ERROR_INVALID_REQUEST = 0, 55 OAUTH2_ACCESS_ERROR_INVALID_CLIENT, 56 OAUTH2_ACCESS_ERROR_INVALID_GRANT, 57 OAUTH2_ACCESS_ERROR_UNAUTHORIZED_CLIENT, 58 OAUTH2_ACCESS_ERROR_UNSUPPORTED_GRANT_TYPE, 59 OAUTH2_ACCESS_ERROR_INVALID_SCOPE, 60 OAUTH2_ACCESS_ERROR_UNKNOWN, 61 OAUTH2_ACCESS_ERROR_COUNT 62 }; 63 64 OAuth2ErrorCodesForHistogram OAuth2ErrorToHistogramValue( 65 const std::string& error) { 66 if (error == "invalid_request") 67 return OAUTH2_ACCESS_ERROR_INVALID_REQUEST; 68 else if (error == "invalid_client") 69 return OAUTH2_ACCESS_ERROR_INVALID_CLIENT; 70 else if (error == "invalid_grant") 71 return OAUTH2_ACCESS_ERROR_INVALID_GRANT; 72 else if (error == "unauthorized_client") 73 return OAUTH2_ACCESS_ERROR_UNAUTHORIZED_CLIENT; 74 else if (error == "unsupported_grant_type") 75 return OAUTH2_ACCESS_ERROR_UNSUPPORTED_GRANT_TYPE; 76 else if (error == "invalid_scope") 77 return OAUTH2_ACCESS_ERROR_INVALID_SCOPE; 78 79 return OAUTH2_ACCESS_ERROR_UNKNOWN; 80 } 81 82 static GoogleServiceAuthError CreateAuthError(URLRequestStatus status) { 83 CHECK(!status.is_success()); 84 if (status.status() == URLRequestStatus::CANCELED) { 85 return GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED); 86 } else { 87 DLOG(WARNING) << "Could not reach Google Accounts servers: errno " 88 << status.error(); 89 return GoogleServiceAuthError::FromConnectionError(status.error()); 90 } 91 } 92 93 static URLFetcher* CreateFetcher(URLRequestContextGetter* getter, 94 const GURL& url, 95 const std::string& body, 96 URLFetcherDelegate* delegate) { 97 bool empty_body = body.empty(); 98 URLFetcher* result = net::URLFetcher::Create( 99 0, url, 100 empty_body ? URLFetcher::GET : URLFetcher::POST, 101 delegate); 102 103 result->SetRequestContext(getter); 104 result->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES | 105 net::LOAD_DO_NOT_SAVE_COOKIES); 106 // Fetchers are sometimes cancelled because a network change was detected, 107 // especially at startup and after sign-in on ChromeOS. Retrying once should 108 // be enough in those cases; let the fetcher retry up to 3 times just in case. 109 // http://crbug.com/163710 110 result->SetAutomaticallyRetryOnNetworkChanges(3); 111 112 if (!empty_body) 113 result->SetUploadData("application/x-www-form-urlencoded", body); 114 115 return result; 116 } 117 } // namespace 118 119 OAuth2AccessTokenFetcher::OAuth2AccessTokenFetcher( 120 OAuth2AccessTokenConsumer* consumer, 121 URLRequestContextGetter* getter) 122 : consumer_(consumer), 123 getter_(getter), 124 state_(INITIAL) { } 125 126 OAuth2AccessTokenFetcher::~OAuth2AccessTokenFetcher() { } 127 128 void OAuth2AccessTokenFetcher::CancelRequest() { 129 fetcher_.reset(); 130 } 131 132 void OAuth2AccessTokenFetcher::Start(const std::string& client_id, 133 const std::string& client_secret, 134 const std::string& refresh_token, 135 const std::vector<std::string>& scopes) { 136 client_id_ = client_id; 137 client_secret_ = client_secret; 138 refresh_token_ = refresh_token; 139 scopes_ = scopes; 140 StartGetAccessToken(); 141 } 142 143 void OAuth2AccessTokenFetcher::StartGetAccessToken() { 144 CHECK_EQ(INITIAL, state_); 145 state_ = GET_ACCESS_TOKEN_STARTED; 146 fetcher_.reset(CreateFetcher( 147 getter_, 148 MakeGetAccessTokenUrl(), 149 MakeGetAccessTokenBody( 150 client_id_, client_secret_, refresh_token_, scopes_), 151 this)); 152 fetcher_->Start(); // OnURLFetchComplete will be called. 153 } 154 155 void OAuth2AccessTokenFetcher::EndGetAccessToken( 156 const net::URLFetcher* source) { 157 CHECK_EQ(GET_ACCESS_TOKEN_STARTED, state_); 158 state_ = GET_ACCESS_TOKEN_DONE; 159 160 URLRequestStatus status = source->GetStatus(); 161 int histogram_value = status.is_success() ? source->GetResponseCode() : 162 status.error(); 163 UMA_HISTOGRAM_SPARSE_SLOWLY("Gaia.ResponseCodesForOAuth2AccessToken", 164 histogram_value); 165 if (!status.is_success()) { 166 OnGetTokenFailure(CreateAuthError(status)); 167 return; 168 } 169 170 switch (source->GetResponseCode()) { 171 case net::HTTP_OK: 172 break; 173 case net::HTTP_FORBIDDEN: 174 case net::HTTP_INTERNAL_SERVER_ERROR: 175 // HTTP_FORBIDDEN (403) is treated as temporary error, because it may be 176 // '403 Rate Limit Exeeded.' 500 is always treated as transient. 177 OnGetTokenFailure(GoogleServiceAuthError( 178 GoogleServiceAuthError::SERVICE_UNAVAILABLE)); 179 return; 180 case net::HTTP_BAD_REQUEST: { 181 // HTTP_BAD_REQUEST (400) usually contains error as per 182 // http://tools.ietf.org/html/rfc6749#section-5.2. 183 std::string gaia_error; 184 if (!ParseGetAccessTokenFailureResponse(source, &gaia_error)) { 185 OnGetTokenFailure(GoogleServiceAuthError( 186 GoogleServiceAuthError::SERVICE_ERROR)); 187 return; 188 } 189 190 OAuth2ErrorCodesForHistogram access_error(OAuth2ErrorToHistogramValue( 191 gaia_error)); 192 UMA_HISTOGRAM_ENUMERATION("Gaia.BadRequestTypeForOAuth2AccessToken", 193 access_error, OAUTH2_ACCESS_ERROR_COUNT); 194 195 OnGetTokenFailure(access_error == OAUTH2_ACCESS_ERROR_INVALID_GRANT ? 196 GoogleServiceAuthError( 197 GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS) : 198 GoogleServiceAuthError( 199 GoogleServiceAuthError::SERVICE_ERROR)); 200 return; 201 } 202 default: 203 // The other errors are treated as permanent error. 204 OnGetTokenFailure(GoogleServiceAuthError( 205 GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS)); 206 return; 207 } 208 209 // The request was successfully fetched and it returned OK. 210 // Parse out the access token and the expiration time. 211 std::string access_token; 212 int expires_in; 213 if (!ParseGetAccessTokenSuccessResponse( 214 source, &access_token, &expires_in)) { 215 DLOG(WARNING) << "Response doesn't match expected format"; 216 OnGetTokenFailure( 217 GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE)); 218 return; 219 } 220 // The token will expire in |expires_in| seconds. Take a 10% error margin to 221 // prevent reusing a token too close to its expiration date. 222 OnGetTokenSuccess( 223 access_token, 224 base::Time::Now() + base::TimeDelta::FromSeconds(9 * expires_in / 10)); 225 } 226 227 void OAuth2AccessTokenFetcher::OnGetTokenSuccess( 228 const std::string& access_token, 229 const base::Time& expiration_time) { 230 consumer_->OnGetTokenSuccess(access_token, expiration_time); 231 } 232 233 void OAuth2AccessTokenFetcher::OnGetTokenFailure( 234 const GoogleServiceAuthError& error) { 235 state_ = ERROR_STATE; 236 consumer_->OnGetTokenFailure(error); 237 } 238 239 void OAuth2AccessTokenFetcher::OnURLFetchComplete( 240 const net::URLFetcher* source) { 241 CHECK(source); 242 CHECK(state_ == GET_ACCESS_TOKEN_STARTED); 243 EndGetAccessToken(source); 244 } 245 246 // static 247 GURL OAuth2AccessTokenFetcher::MakeGetAccessTokenUrl() { 248 return GaiaUrls::GetInstance()->oauth2_token_url(); 249 } 250 251 // static 252 std::string OAuth2AccessTokenFetcher::MakeGetAccessTokenBody( 253 const std::string& client_id, 254 const std::string& client_secret, 255 const std::string& refresh_token, 256 const std::vector<std::string>& scopes) { 257 std::string enc_client_id = net::EscapeUrlEncodedData(client_id, true); 258 std::string enc_client_secret = 259 net::EscapeUrlEncodedData(client_secret, true); 260 std::string enc_refresh_token = 261 net::EscapeUrlEncodedData(refresh_token, true); 262 if (scopes.empty()) { 263 return base::StringPrintf( 264 kGetAccessTokenBodyFormat, 265 enc_client_id.c_str(), 266 enc_client_secret.c_str(), 267 enc_refresh_token.c_str()); 268 } else { 269 std::string scopes_string = JoinString(scopes, ' '); 270 return base::StringPrintf( 271 kGetAccessTokenBodyWithScopeFormat, 272 enc_client_id.c_str(), 273 enc_client_secret.c_str(), 274 enc_refresh_token.c_str(), 275 net::EscapeUrlEncodedData(scopes_string, true).c_str()); 276 } 277 } 278 279 scoped_ptr<base::DictionaryValue> ParseGetAccessTokenResponse( 280 const net::URLFetcher* source) { 281 CHECK(source); 282 283 std::string data; 284 source->GetResponseAsString(&data); 285 scoped_ptr<base::Value> value(base::JSONReader::Read(data)); 286 if (!value.get() || value->GetType() != base::Value::TYPE_DICTIONARY) 287 value.reset(); 288 289 return scoped_ptr<base::DictionaryValue>( 290 static_cast<base::DictionaryValue*>(value.release())); 291 } 292 293 // static 294 bool OAuth2AccessTokenFetcher::ParseGetAccessTokenSuccessResponse( 295 const net::URLFetcher* source, 296 std::string* access_token, 297 int* expires_in) { 298 CHECK(access_token); 299 scoped_ptr<base::DictionaryValue> value = ParseGetAccessTokenResponse( 300 source); 301 if (value.get() == NULL) 302 return false; 303 304 return value->GetString(kAccessTokenKey, access_token) && 305 value->GetInteger(kExpiresInKey, expires_in); 306 } 307 308 // static 309 bool OAuth2AccessTokenFetcher::ParseGetAccessTokenFailureResponse( 310 const net::URLFetcher* source, 311 std::string* error) { 312 CHECK(error); 313 scoped_ptr<base::DictionaryValue> value = ParseGetAccessTokenResponse( 314 source); 315 if (value.get() == NULL) 316 return false; 317 return value->GetString(kErrorKey, error); 318 } 319