1 // Copyright 2014 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_impl.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, 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 116 scoped_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 132 OAuth2AccessTokenFetcherImpl::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 141 OAuth2AccessTokenFetcherImpl::~OAuth2AccessTokenFetcherImpl() {} 142 143 void OAuth2AccessTokenFetcherImpl::CancelRequest() { fetcher_.reset(); } 144 145 void 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 155 void 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 167 void 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 // HTTP_FORBIDDEN (403) is treated as temporary error, because it may be 187 // '403 Rate Limit Exeeded.' 188 OnGetTokenFailure( 189 GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE)); 190 return; 191 case net::HTTP_BAD_REQUEST: { 192 // HTTP_BAD_REQUEST (400) usually contains error as per 193 // http://tools.ietf.org/html/rfc6749#section-5.2. 194 std::string gaia_error; 195 if (!ParseGetAccessTokenFailureResponse(source, &gaia_error)) { 196 OnGetTokenFailure( 197 GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_ERROR)); 198 return; 199 } 200 201 OAuth2ErrorCodesForHistogram access_error( 202 OAuth2ErrorToHistogramValue(gaia_error)); 203 UMA_HISTOGRAM_ENUMERATION("Gaia.BadRequestTypeForOAuth2AccessToken", 204 access_error, 205 OAUTH2_ACCESS_ERROR_COUNT); 206 207 OnGetTokenFailure( 208 access_error == OAUTH2_ACCESS_ERROR_INVALID_GRANT 209 ? GoogleServiceAuthError( 210 GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS) 211 : GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_ERROR)); 212 return; 213 } 214 default: { 215 if (source->GetResponseCode() >= net::HTTP_INTERNAL_SERVER_ERROR) { 216 // 5xx is always treated as transient. 217 OnGetTokenFailure(GoogleServiceAuthError( 218 GoogleServiceAuthError::SERVICE_UNAVAILABLE)); 219 } else { 220 // The other errors are treated as permanent error. 221 DLOG(ERROR) << "Unexpected persistent error: http_status=" 222 << source->GetResponseCode(); 223 OnGetTokenFailure(GoogleServiceAuthError( 224 GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS)); 225 } 226 return; 227 } 228 } 229 230 // The request was successfully fetched and it returned OK. 231 // Parse out the access token and the expiration time. 232 std::string access_token; 233 int expires_in; 234 if (!ParseGetAccessTokenSuccessResponse(source, &access_token, &expires_in)) { 235 DLOG(WARNING) << "Response doesn't match expected format"; 236 OnGetTokenFailure( 237 GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE)); 238 return; 239 } 240 // The token will expire in |expires_in| seconds. Take a 10% error margin to 241 // prevent reusing a token too close to its expiration date. 242 OnGetTokenSuccess( 243 access_token, 244 base::Time::Now() + base::TimeDelta::FromSeconds(9 * expires_in / 10)); 245 } 246 247 void OAuth2AccessTokenFetcherImpl::OnGetTokenSuccess( 248 const std::string& access_token, 249 const base::Time& expiration_time) { 250 FireOnGetTokenSuccess(access_token, expiration_time); 251 } 252 253 void OAuth2AccessTokenFetcherImpl::OnGetTokenFailure( 254 const GoogleServiceAuthError& error) { 255 state_ = ERROR_STATE; 256 FireOnGetTokenFailure(error); 257 } 258 259 void OAuth2AccessTokenFetcherImpl::OnURLFetchComplete( 260 const net::URLFetcher* source) { 261 CHECK(source); 262 CHECK(state_ == GET_ACCESS_TOKEN_STARTED); 263 EndGetAccessToken(source); 264 } 265 266 // static 267 GURL OAuth2AccessTokenFetcherImpl::MakeGetAccessTokenUrl() { 268 return GaiaUrls::GetInstance()->oauth2_token_url(); 269 } 270 271 // static 272 std::string OAuth2AccessTokenFetcherImpl::MakeGetAccessTokenBody( 273 const std::string& client_id, 274 const std::string& client_secret, 275 const std::string& refresh_token, 276 const std::vector<std::string>& scopes) { 277 std::string enc_client_id = net::EscapeUrlEncodedData(client_id, true); 278 std::string enc_client_secret = 279 net::EscapeUrlEncodedData(client_secret, true); 280 std::string enc_refresh_token = 281 net::EscapeUrlEncodedData(refresh_token, true); 282 if (scopes.empty()) { 283 return base::StringPrintf(kGetAccessTokenBodyFormat, 284 enc_client_id.c_str(), 285 enc_client_secret.c_str(), 286 enc_refresh_token.c_str()); 287 } else { 288 std::string scopes_string = JoinString(scopes, ' '); 289 return base::StringPrintf( 290 kGetAccessTokenBodyWithScopeFormat, 291 enc_client_id.c_str(), 292 enc_client_secret.c_str(), 293 enc_refresh_token.c_str(), 294 net::EscapeUrlEncodedData(scopes_string, true).c_str()); 295 } 296 } 297 298 // static 299 bool OAuth2AccessTokenFetcherImpl::ParseGetAccessTokenSuccessResponse( 300 const net::URLFetcher* source, 301 std::string* access_token, 302 int* expires_in) { 303 CHECK(access_token); 304 scoped_ptr<base::DictionaryValue> value = ParseGetAccessTokenResponse(source); 305 if (value.get() == NULL) 306 return false; 307 308 return value->GetString(kAccessTokenKey, access_token) && 309 value->GetInteger(kExpiresInKey, expires_in); 310 } 311 312 // static 313 bool OAuth2AccessTokenFetcherImpl::ParseGetAccessTokenFailureResponse( 314 const net::URLFetcher* source, 315 std::string* error) { 316 CHECK(error); 317 scoped_ptr<base::DictionaryValue> value = ParseGetAccessTokenResponse(source); 318 if (value.get() == NULL) 319 return false; 320 return value->GetString(kErrorKey, error); 321 } 322