Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "google_apis/gaia/oauth2_access_token_fetcher.h" | 5 #include "google_apis/gaia/oauth2_access_token_fetcher.h" |
| 6 | 6 |
| 7 #include <algorithm> | 7 #include <algorithm> |
| 8 #include <string> | 8 #include <string> |
| 9 #include <vector> | 9 #include <vector> |
| 10 | 10 |
| 11 #include "base/json/json_reader.h" | 11 #include "base/json/json_reader.h" |
| 12 #include "base/metrics/histogram.h" | |
| 13 #include "base/metrics/sparse_histogram.h" | |
| 12 #include "base/strings/string_util.h" | 14 #include "base/strings/string_util.h" |
| 13 #include "base/strings/stringprintf.h" | 15 #include "base/strings/stringprintf.h" |
| 14 #include "base/time/time.h" | 16 #include "base/time/time.h" |
| 15 #include "base/values.h" | 17 #include "base/values.h" |
| 16 #include "google_apis/gaia/gaia_urls.h" | 18 #include "google_apis/gaia/gaia_urls.h" |
| 17 #include "google_apis/gaia/google_service_auth_error.h" | 19 #include "google_apis/gaia/google_service_auth_error.h" |
| 18 #include "net/base/escape.h" | 20 #include "net/base/escape.h" |
| 19 #include "net/base/load_flags.h" | 21 #include "net/base/load_flags.h" |
| 20 #include "net/http/http_status_code.h" | 22 #include "net/http/http_status_code.h" |
| 21 #include "net/url_request/url_fetcher.h" | 23 #include "net/url_request/url_fetcher.h" |
| (...skipping 15 matching lines...) Expand all Loading... | |
| 37 | 39 |
| 38 static const char kGetAccessTokenBodyWithScopeFormat[] = | 40 static const char kGetAccessTokenBodyWithScopeFormat[] = |
| 39 "client_id=%s&" | 41 "client_id=%s&" |
| 40 "client_secret=%s&" | 42 "client_secret=%s&" |
| 41 "grant_type=refresh_token&" | 43 "grant_type=refresh_token&" |
| 42 "refresh_token=%s&" | 44 "refresh_token=%s&" |
| 43 "scope=%s"; | 45 "scope=%s"; |
| 44 | 46 |
| 45 static const char kAccessTokenKey[] = "access_token"; | 47 static const char kAccessTokenKey[] = "access_token"; |
| 46 static const char kExpiresInKey[] = "expires_in"; | 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 } | |
| 47 | 81 |
| 48 static GoogleServiceAuthError CreateAuthError(URLRequestStatus status) { | 82 static GoogleServiceAuthError CreateAuthError(URLRequestStatus status) { |
| 49 CHECK(!status.is_success()); | 83 CHECK(!status.is_success()); |
| 50 if (status.status() == URLRequestStatus::CANCELED) { | 84 if (status.status() == URLRequestStatus::CANCELED) { |
| 51 return GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED); | 85 return GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED); |
| 52 } else { | 86 } else { |
| 53 DLOG(WARNING) << "Could not reach Google Accounts servers: errno " | 87 DLOG(WARNING) << "Could not reach Google Accounts servers: errno " |
| 54 << status.error(); | 88 << status.error(); |
| 55 return GoogleServiceAuthError::FromConnectionError(status.error()); | 89 return GoogleServiceAuthError::FromConnectionError(status.error()); |
| 56 } | 90 } |
| (...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 117 this)); | 151 this)); |
| 118 fetcher_->Start(); // OnURLFetchComplete will be called. | 152 fetcher_->Start(); // OnURLFetchComplete will be called. |
| 119 } | 153 } |
| 120 | 154 |
| 121 void OAuth2AccessTokenFetcher::EndGetAccessToken( | 155 void OAuth2AccessTokenFetcher::EndGetAccessToken( |
| 122 const net::URLFetcher* source) { | 156 const net::URLFetcher* source) { |
| 123 CHECK_EQ(GET_ACCESS_TOKEN_STARTED, state_); | 157 CHECK_EQ(GET_ACCESS_TOKEN_STARTED, state_); |
| 124 state_ = GET_ACCESS_TOKEN_DONE; | 158 state_ = GET_ACCESS_TOKEN_DONE; |
| 125 | 159 |
| 126 URLRequestStatus status = source->GetStatus(); | 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); | |
| 127 if (!status.is_success()) { | 165 if (!status.is_success()) { |
| 128 OnGetTokenFailure(CreateAuthError(status)); | 166 OnGetTokenFailure(CreateAuthError(status)); |
| 129 return; | 167 return; |
| 130 } | 168 } |
| 131 | 169 |
| 132 // HTTP_FORBIDDEN (403) is treated as temporary error, because it may be | 170 // HTTP_FORBIDDEN (403) is treated as temporary error, because it may be |
| 133 // '403 Rate Limit Exeeded.' | 171 // '403 Rate Limit Exeeded.' |
| 134 if (source->GetResponseCode() == net::HTTP_FORBIDDEN) { | 172 if (source->GetResponseCode() == net::HTTP_FORBIDDEN) { |
| 135 OnGetTokenFailure(GoogleServiceAuthError( | 173 OnGetTokenFailure(GoogleServiceAuthError( |
| 136 GoogleServiceAuthError::SERVICE_UNAVAILABLE)); | 174 GoogleServiceAuthError::SERVICE_UNAVAILABLE)); |
| 137 return; | 175 return; |
| 138 } | 176 } |
| 139 | 177 |
| 178 if (source->GetResponseCode() == net::HTTP_BAD_REQUEST) { | |
| 179 // HTTP_BAD_REQUEST (400) usually contains error as per | |
| 180 // http://tools.ietf.org/html/rfc6749#section-5.2. | |
| 181 std::string gaia_error; | |
| 182 OAuth2ErrorCodesForHistogram access_error(OAUTH2_ACCESS_ERROR_UNKNOWN); | |
| 183 if (ParseGetAccessTokenFailureResponse(source, &gaia_error)) | |
| 184 access_error = OAuth2ErrorToHistogramValue(gaia_error); | |
| 185 UMA_HISTOGRAM_ENUMERATION("Gaia.BadRequestTypeForOAuth2AccessToken", | |
| 186 access_error, OAUTH2_ACCESS_ERROR_COUNT); | |
| 187 OnGetTokenFailure(GoogleServiceAuthError( | |
| 188 GoogleServiceAuthError::SERVICE_ERROR)); | |
|
pavely
2013/11/04 23:25:28
You still need to return INVALID_GAIA_CREDENTIALS
| |
| 189 return; | |
| 190 } | |
| 191 | |
| 140 // The other errors are treated as permanent error. | 192 // The other errors are treated as permanent error. |
| 141 if (source->GetResponseCode() != net::HTTP_OK) { | 193 if (source->GetResponseCode() != net::HTTP_OK) { |
| 142 OnGetTokenFailure(GoogleServiceAuthError( | 194 OnGetTokenFailure(GoogleServiceAuthError( |
| 143 GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS)); | 195 GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS)); |
| 144 return; | 196 return; |
| 145 } | 197 } |
| 146 | 198 |
| 147 // The request was successfully fetched and it returned OK. | 199 // The request was successfully fetched and it returned OK. |
| 148 // Parse out the access token and the expiration time. | 200 // Parse out the access token and the expiration time. |
| 149 std::string access_token; | 201 std::string access_token; |
| 150 int expires_in; | 202 int expires_in; |
| 151 if (!ParseGetAccessTokenResponse(source, &access_token, &expires_in)) { | 203 if (!ParseGetAccessTokenSuccessResponse( |
| 204 source, &access_token, &expires_in)) { | |
| 152 DLOG(WARNING) << "Response doesn't match expected format"; | 205 DLOG(WARNING) << "Response doesn't match expected format"; |
| 153 OnGetTokenFailure( | 206 OnGetTokenFailure( |
| 154 GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE)); | 207 GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE)); |
| 155 return; | 208 return; |
| 156 } | 209 } |
| 157 // The token will expire in |expires_in| seconds. Take a 10% error margin to | 210 // The token will expire in |expires_in| seconds. Take a 10% error margin to |
| 158 // prevent reusing a token too close to its expiration date. | 211 // prevent reusing a token too close to its expiration date. |
| 159 OnGetTokenSuccess( | 212 OnGetTokenSuccess( |
| 160 access_token, | 213 access_token, |
| 161 base::Time::Now() + base::TimeDelta::FromSeconds(9 * expires_in / 10)); | 214 base::Time::Now() + base::TimeDelta::FromSeconds(9 * expires_in / 10)); |
| (...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 206 std::string scopes_string = JoinString(scopes, ' '); | 259 std::string scopes_string = JoinString(scopes, ' '); |
| 207 return base::StringPrintf( | 260 return base::StringPrintf( |
| 208 kGetAccessTokenBodyWithScopeFormat, | 261 kGetAccessTokenBodyWithScopeFormat, |
| 209 enc_client_id.c_str(), | 262 enc_client_id.c_str(), |
| 210 enc_client_secret.c_str(), | 263 enc_client_secret.c_str(), |
| 211 enc_refresh_token.c_str(), | 264 enc_refresh_token.c_str(), |
| 212 net::EscapeUrlEncodedData(scopes_string, true).c_str()); | 265 net::EscapeUrlEncodedData(scopes_string, true).c_str()); |
| 213 } | 266 } |
| 214 } | 267 } |
| 215 | 268 |
| 216 // static | 269 scoped_ptr<base::DictionaryValue> ParseGetAccessTokenResponse( |
| 217 bool OAuth2AccessTokenFetcher::ParseGetAccessTokenResponse( | 270 const net::URLFetcher* source) { |
| 218 const net::URLFetcher* source, | |
| 219 std::string* access_token, | |
| 220 int* expires_in) { | |
| 221 CHECK(source); | 271 CHECK(source); |
| 222 CHECK(access_token); | 272 |
| 223 std::string data; | 273 std::string data; |
| 224 source->GetResponseAsString(&data); | 274 source->GetResponseAsString(&data); |
| 225 scoped_ptr<base::Value> value(base::JSONReader::Read(data)); | 275 scoped_ptr<base::Value> value(base::JSONReader::Read(data)); |
| 226 if (!value.get() || value->GetType() != base::Value::TYPE_DICTIONARY) | 276 if (!value.get() || value->GetType() != base::Value::TYPE_DICTIONARY) |
| 277 value.reset(); | |
| 278 | |
| 279 return scoped_ptr<base::DictionaryValue>( | |
| 280 static_cast<base::DictionaryValue*>(value.release())); | |
| 281 } | |
| 282 | |
| 283 // static | |
| 284 bool OAuth2AccessTokenFetcher::ParseGetAccessTokenSuccessResponse( | |
| 285 const net::URLFetcher* source, | |
| 286 std::string* access_token, | |
| 287 int* expires_in) { | |
| 288 CHECK(access_token); | |
| 289 scoped_ptr<base::DictionaryValue> value = ParseGetAccessTokenResponse( | |
| 290 source); | |
| 291 if (value.get() == NULL) | |
| 227 return false; | 292 return false; |
| 228 | 293 |
| 229 base::DictionaryValue* dict = | 294 return value->GetString(kAccessTokenKey, access_token) && |
| 230 static_cast<base::DictionaryValue*>(value.get()); | 295 value->GetInteger(kExpiresInKey, expires_in); |
| 231 return dict->GetString(kAccessTokenKey, access_token) && | |
| 232 dict->GetInteger(kExpiresInKey, expires_in); | |
| 233 } | 296 } |
| 297 | |
| 298 // static | |
| 299 bool OAuth2AccessTokenFetcher::ParseGetAccessTokenFailureResponse( | |
| 300 const net::URLFetcher* source, | |
| 301 std::string* error) { | |
| 302 CHECK(error); | |
| 303 scoped_ptr<base::DictionaryValue> value = ParseGetAccessTokenResponse( | |
| 304 source); | |
| 305 if (value.get() == NULL) | |
| 306 return false; | |
| 307 return value->GetString(kErrorKey, error); | |
| 308 } | |
| OLD | NEW |