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/strings/string_util.h" 13 #include "base/strings/stringprintf.h" 14 #include "base/time/time.h" 15 #include "base/values.h" 16 #include "google_apis/gaia/gaia_urls.h" 17 #include "google_apis/gaia/google_service_auth_error.h" 18 #include "net/base/escape.h" 19 #include "net/base/load_flags.h" 20 #include "net/http/http_status_code.h" 21 #include "net/url_request/url_fetcher.h" 22 #include "net/url_request/url_request_context_getter.h" 23 #include "net/url_request/url_request_status.h" 24 25 using net::ResponseCookies; 26 using net::URLFetcher; 27 using net::URLFetcherDelegate; 28 using net::URLRequestContextGetter; 29 using net::URLRequestStatus; 30 31 namespace { 32 static const char kGetAccessTokenBodyFormat[] = 33 "client_id=%s&" 34 "client_secret=%s&" 35 "grant_type=refresh_token&" 36 "refresh_token=%s"; 37 38 static const char kGetAccessTokenBodyWithScopeFormat[] = 39 "client_id=%s&" 40 "client_secret=%s&" 41 "grant_type=refresh_token&" 42 "refresh_token=%s&" 43 "scope=%s"; 44 45 static const char kAccessTokenKey[] = "access_token"; 46 static const char kExpiresInKey[] = "expires_in"; 47 48 static GoogleServiceAuthError CreateAuthError(URLRequestStatus status) { 49 CHECK(!status.is_success()); 50 if (status.status() == URLRequestStatus::CANCELED) { 51 return GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED); 52 } else { 53 DLOG(WARNING) << "Could not reach Google Accounts servers: errno " 54 << status.error(); 55 return GoogleServiceAuthError::FromConnectionError(status.error()); 56 } 57 } 58 59 static URLFetcher* CreateFetcher(URLRequestContextGetter* getter, 60 const GURL& url, 61 const std::string& body, 62 URLFetcherDelegate* delegate) { 63 bool empty_body = body.empty(); 64 URLFetcher* result = net::URLFetcher::Create( 65 0, url, 66 empty_body ? URLFetcher::GET : URLFetcher::POST, 67 delegate); 68 69 result->SetRequestContext(getter); 70 result->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES | 71 net::LOAD_DO_NOT_SAVE_COOKIES); 72 // Fetchers are sometimes cancelled because a network change was detected, 73 // especially at startup and after sign-in on ChromeOS. Retrying once should 74 // be enough in those cases; let the fetcher retry up to 3 times just in case. 75 // http://crbug.com/163710 76 result->SetAutomaticallyRetryOnNetworkChanges(3); 77 78 if (!empty_body) 79 result->SetUploadData("application/x-www-form-urlencoded", body); 80 81 return result; 82 } 83 } // namespace 84 85 OAuth2AccessTokenFetcher::OAuth2AccessTokenFetcher( 86 OAuth2AccessTokenConsumer* consumer, 87 URLRequestContextGetter* getter) 88 : consumer_(consumer), 89 getter_(getter), 90 state_(INITIAL) { } 91 92 OAuth2AccessTokenFetcher::~OAuth2AccessTokenFetcher() { } 93 94 void OAuth2AccessTokenFetcher::CancelRequest() { 95 fetcher_.reset(); 96 } 97 98 void OAuth2AccessTokenFetcher::Start(const std::string& client_id, 99 const std::string& client_secret, 100 const std::string& refresh_token, 101 const std::vector<std::string>& scopes) { 102 client_id_ = client_id; 103 client_secret_ = client_secret; 104 refresh_token_ = refresh_token; 105 scopes_ = scopes; 106 StartGetAccessToken(); 107 } 108 109 void OAuth2AccessTokenFetcher::StartGetAccessToken() { 110 CHECK_EQ(INITIAL, state_); 111 state_ = GET_ACCESS_TOKEN_STARTED; 112 fetcher_.reset(CreateFetcher( 113 getter_, 114 MakeGetAccessTokenUrl(), 115 MakeGetAccessTokenBody( 116 client_id_, client_secret_, refresh_token_, scopes_), 117 this)); 118 fetcher_->Start(); // OnURLFetchComplete will be called. 119 } 120 121 void OAuth2AccessTokenFetcher::EndGetAccessToken( 122 const net::URLFetcher* source) { 123 CHECK_EQ(GET_ACCESS_TOKEN_STARTED, state_); 124 state_ = GET_ACCESS_TOKEN_DONE; 125 126 URLRequestStatus status = source->GetStatus(); 127 if (!status.is_success()) { 128 OnGetTokenFailure(CreateAuthError(status)); 129 return; 130 } 131 132 // HTTP_FORBIDDEN (403) is treated as temporary error, because it may be 133 // '403 Rate Limit Exeeded.' 134 if (source->GetResponseCode() == net::HTTP_FORBIDDEN) { 135 OnGetTokenFailure(GoogleServiceAuthError( 136 GoogleServiceAuthError::SERVICE_UNAVAILABLE)); 137 return; 138 } 139 140 // The other errors are treated as permanent error. 141 if (source->GetResponseCode() != net::HTTP_OK) { 142 OnGetTokenFailure(GoogleServiceAuthError( 143 GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS)); 144 return; 145 } 146 147 // The request was successfully fetched and it returned OK. 148 // Parse out the access token and the expiration time. 149 std::string access_token; 150 int expires_in; 151 if (!ParseGetAccessTokenResponse(source, &access_token, &expires_in)) { 152 DLOG(WARNING) << "Response doesn't match expected format"; 153 OnGetTokenFailure( 154 GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE)); 155 return; 156 } 157 // 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. 159 OnGetTokenSuccess( 160 access_token, 161 base::Time::Now() + base::TimeDelta::FromSeconds(9 * expires_in / 10)); 162 } 163 164 void OAuth2AccessTokenFetcher::OnGetTokenSuccess( 165 const std::string& access_token, 166 const base::Time& expiration_time) { 167 consumer_->OnGetTokenSuccess(access_token, expiration_time); 168 } 169 170 void OAuth2AccessTokenFetcher::OnGetTokenFailure( 171 const GoogleServiceAuthError& error) { 172 state_ = ERROR_STATE; 173 consumer_->OnGetTokenFailure(error); 174 } 175 176 void OAuth2AccessTokenFetcher::OnURLFetchComplete( 177 const net::URLFetcher* source) { 178 CHECK(source); 179 CHECK(state_ == GET_ACCESS_TOKEN_STARTED); 180 EndGetAccessToken(source); 181 } 182 183 // static 184 GURL OAuth2AccessTokenFetcher::MakeGetAccessTokenUrl() { 185 return GURL(GaiaUrls::GetInstance()->oauth2_token_url()); 186 } 187 188 // static 189 std::string OAuth2AccessTokenFetcher::MakeGetAccessTokenBody( 190 const std::string& client_id, 191 const std::string& client_secret, 192 const std::string& refresh_token, 193 const std::vector<std::string>& scopes) { 194 std::string enc_client_id = net::EscapeUrlEncodedData(client_id, true); 195 std::string enc_client_secret = 196 net::EscapeUrlEncodedData(client_secret, true); 197 std::string enc_refresh_token = 198 net::EscapeUrlEncodedData(refresh_token, true); 199 if (scopes.empty()) { 200 return base::StringPrintf( 201 kGetAccessTokenBodyFormat, 202 enc_client_id.c_str(), 203 enc_client_secret.c_str(), 204 enc_refresh_token.c_str()); 205 } else { 206 std::string scopes_string = JoinString(scopes, ' '); 207 return base::StringPrintf( 208 kGetAccessTokenBodyWithScopeFormat, 209 enc_client_id.c_str(), 210 enc_client_secret.c_str(), 211 enc_refresh_token.c_str(), 212 net::EscapeUrlEncodedData(scopes_string, true).c_str()); 213 } 214 } 215 216 // static 217 bool OAuth2AccessTokenFetcher::ParseGetAccessTokenResponse( 218 const net::URLFetcher* source, 219 std::string* access_token, 220 int* expires_in) { 221 CHECK(source); 222 CHECK(access_token); 223 std::string data; 224 source->GetResponseAsString(&data); 225 scoped_ptr<base::Value> value(base::JSONReader::Read(data)); 226 if (!value.get() || value->GetType() != base::Value::TYPE_DICTIONARY) 227 return false; 228 229 base::DictionaryValue* dict = 230 static_cast<base::DictionaryValue*>(value.get()); 231 return dict->GetString(kAccessTokenKey, access_token) && 232 dict->GetInteger(kExpiresInKey, expires_in); 233 } 234