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 |