oauth2_access_token_fetcher_impl.cc revision a1401311d1ab56c4ed0a474bd38c108f75cb0cd9
14e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)// Copyright 2014 The Chromium Authors. All rights reserved. 24e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be 34e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)// found in the LICENSE file. 44e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) 50f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)#include "google_apis/gaia/oauth2_access_token_fetcher_impl.h" 60f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) 70f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)#include <algorithm> 8f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include <string> 94e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)#include <vector> 104e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) 114e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)#include "base/json/json_reader.h" 124e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)#include "base/metrics/histogram.h" 130f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)#include "base/metrics/sparse_histogram.h" 144e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)#include "base/strings/string_util.h" 154e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)#include "base/strings/stringprintf.h" 164e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)#include "base/time/time.h" 170f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)#include "base/values.h" 184e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)#include "google_apis/gaia/gaia_urls.h" 190f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)#include "google_apis/gaia/google_service_auth_error.h" 204e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)#include "net/base/escape.h" 214e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)#include "net/base/load_flags.h" 224e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)#include "net/http/http_status_code.h" 23f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include "net/url_request/url_fetcher.h" 244e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)#include "net/url_request/url_request_context_getter.h" 254e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)#include "net/url_request/url_request_status.h" 264e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) 274e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)using net::ResponseCookies; 284e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)using net::URLFetcher; 29f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)using net::URLFetcherDelegate; 30f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)using net::URLRequestContextGetter; 31f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)using net::URLRequestStatus; 324e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) 334e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)namespace { 344e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)static const char kGetAccessTokenBodyFormat[] = 354e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) "client_id=%s&" 364e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) "client_secret=%s&" 374e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) "grant_type=refresh_token&" 384e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) "refresh_token=%s"; 394e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) 400f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)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 47static const char kAccessTokenKey[] = "access_token"; 48static const char kExpiresInKey[] = "expires_in"; 49static const char kErrorKey[] = "error"; 50 51// Enumerated constants for logging server responses on 400 errors, matching 52// RFC 6749. 53enum 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 64OAuth2ErrorCodesForHistogram 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 82static 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 93static 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, empty_body ? URLFetcher::GET : URLFetcher::POST, delegate); 100 101 result->SetRequestContext(getter); 102 result->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES | 103 net::LOAD_DO_NOT_SAVE_COOKIES); 104 // Fetchers are sometimes cancelled because a network change was detected, 105 // especially at startup and after sign-in on ChromeOS. Retrying once should 106 // be enough in those cases; let the fetcher retry up to 3 times just in case. 107 // http://crbug.com/163710 108 result->SetAutomaticallyRetryOnNetworkChanges(3); 109 110 if (!empty_body) 111 result->SetUploadData("application/x-www-form-urlencoded", body); 112 113 return result; 114} 115 116scoped_ptr<base::DictionaryValue> ParseGetAccessTokenResponse( 117 const net::URLFetcher* source) { 118 CHECK(source); 119 120 std::string data; 121 source->GetResponseAsString(&data); 122 scoped_ptr<base::Value> value(base::JSONReader::Read(data)); 123 if (!value.get() || value->GetType() != base::Value::TYPE_DICTIONARY) 124 value.reset(); 125 126 return scoped_ptr<base::DictionaryValue>( 127 static_cast<base::DictionaryValue*>(value.release())); 128} 129 130} // namespace 131 132OAuth2AccessTokenFetcherImpl::OAuth2AccessTokenFetcherImpl( 133 OAuth2AccessTokenConsumer* consumer, 134 net::URLRequestContextGetter* getter, 135 const std::string& refresh_token) 136 : OAuth2AccessTokenFetcher(consumer), 137 getter_(getter), 138 refresh_token_(refresh_token), 139 state_(INITIAL) {} 140 141OAuth2AccessTokenFetcherImpl::~OAuth2AccessTokenFetcherImpl() {} 142 143void OAuth2AccessTokenFetcherImpl::CancelRequest() { fetcher_.reset(); } 144 145void OAuth2AccessTokenFetcherImpl::Start( 146 const std::string& client_id, 147 const std::string& client_secret, 148 const std::vector<std::string>& scopes) { 149 client_id_ = client_id; 150 client_secret_ = client_secret; 151 scopes_ = scopes; 152 StartGetAccessToken(); 153} 154 155void OAuth2AccessTokenFetcherImpl::StartGetAccessToken() { 156 CHECK_EQ(INITIAL, state_); 157 state_ = GET_ACCESS_TOKEN_STARTED; 158 fetcher_.reset( 159 CreateFetcher(getter_, 160 MakeGetAccessTokenUrl(), 161 MakeGetAccessTokenBody( 162 client_id_, client_secret_, refresh_token_, scopes_), 163 this)); 164 fetcher_->Start(); // OnURLFetchComplete will be called. 165} 166 167void OAuth2AccessTokenFetcherImpl::EndGetAccessToken( 168 const net::URLFetcher* source) { 169 CHECK_EQ(GET_ACCESS_TOKEN_STARTED, state_); 170 state_ = GET_ACCESS_TOKEN_DONE; 171 172 URLRequestStatus status = source->GetStatus(); 173 int histogram_value = 174 status.is_success() ? source->GetResponseCode() : status.error(); 175 UMA_HISTOGRAM_SPARSE_SLOWLY("Gaia.ResponseCodesForOAuth2AccessToken", 176 histogram_value); 177 if (!status.is_success()) { 178 OnGetTokenFailure(CreateAuthError(status)); 179 return; 180 } 181 182 switch (source->GetResponseCode()) { 183 case net::HTTP_OK: 184 break; 185 case net::HTTP_FORBIDDEN: 186 case net::HTTP_INTERNAL_SERVER_ERROR: 187 // HTTP_FORBIDDEN (403) is treated as temporary error, because it may be 188 // '403 Rate Limit Exeeded.' 500 is always treated as transient. 189 OnGetTokenFailure( 190 GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE)); 191 return; 192 case net::HTTP_BAD_REQUEST: { 193 // HTTP_BAD_REQUEST (400) usually contains error as per 194 // http://tools.ietf.org/html/rfc6749#section-5.2. 195 std::string gaia_error; 196 if (!ParseGetAccessTokenFailureResponse(source, &gaia_error)) { 197 OnGetTokenFailure( 198 GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_ERROR)); 199 return; 200 } 201 202 OAuth2ErrorCodesForHistogram access_error( 203 OAuth2ErrorToHistogramValue(gaia_error)); 204 UMA_HISTOGRAM_ENUMERATION("Gaia.BadRequestTypeForOAuth2AccessToken", 205 access_error, 206 OAUTH2_ACCESS_ERROR_COUNT); 207 208 OnGetTokenFailure( 209 access_error == OAUTH2_ACCESS_ERROR_INVALID_GRANT 210 ? GoogleServiceAuthError( 211 GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS) 212 : GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_ERROR)); 213 return; 214 } 215 default: 216 // The other errors are treated as permanent error. 217 OnGetTokenFailure(GoogleServiceAuthError( 218 GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS)); 219 return; 220 } 221 222 // The request was successfully fetched and it returned OK. 223 // Parse out the access token and the expiration time. 224 std::string access_token; 225 int expires_in; 226 if (!ParseGetAccessTokenSuccessResponse(source, &access_token, &expires_in)) { 227 DLOG(WARNING) << "Response doesn't match expected format"; 228 OnGetTokenFailure( 229 GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE)); 230 return; 231 } 232 // The token will expire in |expires_in| seconds. Take a 10% error margin to 233 // prevent reusing a token too close to its expiration date. 234 OnGetTokenSuccess( 235 access_token, 236 base::Time::Now() + base::TimeDelta::FromSeconds(9 * expires_in / 10)); 237} 238 239void OAuth2AccessTokenFetcherImpl::OnGetTokenSuccess( 240 const std::string& access_token, 241 const base::Time& expiration_time) { 242 FireOnGetTokenSuccess(access_token, expiration_time); 243} 244 245void OAuth2AccessTokenFetcherImpl::OnGetTokenFailure( 246 const GoogleServiceAuthError& error) { 247 state_ = ERROR_STATE; 248 FireOnGetTokenFailure(error); 249} 250 251void OAuth2AccessTokenFetcherImpl::OnURLFetchComplete( 252 const net::URLFetcher* source) { 253 CHECK(source); 254 CHECK(state_ == GET_ACCESS_TOKEN_STARTED); 255 EndGetAccessToken(source); 256} 257 258// static 259GURL OAuth2AccessTokenFetcherImpl::MakeGetAccessTokenUrl() { 260 return GaiaUrls::GetInstance()->oauth2_token_url(); 261} 262 263// static 264std::string OAuth2AccessTokenFetcherImpl::MakeGetAccessTokenBody( 265 const std::string& client_id, 266 const std::string& client_secret, 267 const std::string& refresh_token, 268 const std::vector<std::string>& scopes) { 269 std::string enc_client_id = net::EscapeUrlEncodedData(client_id, true); 270 std::string enc_client_secret = 271 net::EscapeUrlEncodedData(client_secret, true); 272 std::string enc_refresh_token = 273 net::EscapeUrlEncodedData(refresh_token, true); 274 if (scopes.empty()) { 275 return base::StringPrintf(kGetAccessTokenBodyFormat, 276 enc_client_id.c_str(), 277 enc_client_secret.c_str(), 278 enc_refresh_token.c_str()); 279 } else { 280 std::string scopes_string = JoinString(scopes, ' '); 281 return base::StringPrintf( 282 kGetAccessTokenBodyWithScopeFormat, 283 enc_client_id.c_str(), 284 enc_client_secret.c_str(), 285 enc_refresh_token.c_str(), 286 net::EscapeUrlEncodedData(scopes_string, true).c_str()); 287 } 288} 289 290// static 291bool OAuth2AccessTokenFetcherImpl::ParseGetAccessTokenSuccessResponse( 292 const net::URLFetcher* source, 293 std::string* access_token, 294 int* expires_in) { 295 CHECK(access_token); 296 scoped_ptr<base::DictionaryValue> value = ParseGetAccessTokenResponse(source); 297 if (value.get() == NULL) 298 return false; 299 300 return value->GetString(kAccessTokenKey, access_token) && 301 value->GetInteger(kExpiresInKey, expires_in); 302} 303 304// static 305bool OAuth2AccessTokenFetcherImpl::ParseGetAccessTokenFailureResponse( 306 const net::URLFetcher* source, 307 std::string* error) { 308 CHECK(error); 309 scoped_ptr<base::DictionaryValue> value = ParseGetAccessTokenResponse(source); 310 if (value.get() == NULL) 311 return false; 312 return value->GetString(kErrorKey, error); 313} 314