gaia_oauth_client.cc revision 2a99a7e74a7f215066514fe81d2bfa6639d9eddd
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/gaia_oauth_client.h" 6 7#include "base/json/json_reader.h" 8#include "base/logging.h" 9#include "base/memory/scoped_ptr.h" 10#include "base/values.h" 11#include "google_apis/gaia/gaia_urls.h" 12#include "googleurl/src/gurl.h" 13#include "net/base/escape.h" 14#include "net/http/http_status_code.h" 15#include "net/url_request/url_fetcher.h" 16#include "net/url_request/url_fetcher_delegate.h" 17#include "net/url_request/url_request_context_getter.h" 18 19namespace { 20const char kAccessTokenValue[] = "access_token"; 21const char kRefreshTokenValue[] = "refresh_token"; 22const char kExpiresInValue[] = "expires_in"; 23} 24 25namespace gaia { 26 27class GaiaOAuthClient::Core 28 : public base::RefCountedThreadSafe<GaiaOAuthClient::Core>, 29 public net::URLFetcherDelegate { 30 public: 31 Core(const std::string& gaia_url, 32 net::URLRequestContextGetter* request_context_getter) 33 : gaia_url_(gaia_url), 34 num_retries_(0), 35 request_context_getter_(request_context_getter), 36 delegate_(NULL), 37 request_type_(NO_PENDING_REQUEST) { 38 } 39 40 void GetTokensFromAuthCode(const OAuthClientInfo& oauth_client_info, 41 const std::string& auth_code, 42 int max_retries, 43 GaiaOAuthClient::Delegate* delegate); 44 void RefreshToken(const OAuthClientInfo& oauth_client_info, 45 const std::string& refresh_token, 46 int max_retries, 47 GaiaOAuthClient::Delegate* delegate); 48 void GetUserInfo(const std::string& oauth_access_token, 49 int max_retries, 50 Delegate* delegate); 51 52 // net::URLFetcherDelegate implementation. 53 virtual void OnURLFetchComplete(const net::URLFetcher* source) OVERRIDE; 54 55 private: 56 friend class base::RefCountedThreadSafe<Core>; 57 58 enum RequestType { 59 NO_PENDING_REQUEST, 60 TOKENS_FROM_AUTH_CODE, 61 REFRESH_TOKEN, 62 USER_INFO, 63 }; 64 65 virtual ~Core() {} 66 67 void MakeGaiaRequest(const std::string& post_body, 68 int max_retries, 69 GaiaOAuthClient::Delegate* delegate); 70 void HandleResponse(const net::URLFetcher* source, 71 bool* should_retry_request); 72 73 GURL gaia_url_; 74 int num_retries_; 75 scoped_refptr<net::URLRequestContextGetter> request_context_getter_; 76 GaiaOAuthClient::Delegate* delegate_; 77 scoped_ptr<net::URLFetcher> request_; 78 RequestType request_type_; 79}; 80 81void GaiaOAuthClient::Core::GetTokensFromAuthCode( 82 const OAuthClientInfo& oauth_client_info, 83 const std::string& auth_code, 84 int max_retries, 85 GaiaOAuthClient::Delegate* delegate) { 86 DCHECK_EQ(request_type_, NO_PENDING_REQUEST); 87 request_type_ = TOKENS_FROM_AUTH_CODE; 88 std::string post_body = 89 "code=" + net::EscapeUrlEncodedData(auth_code, true) + 90 "&client_id=" + net::EscapeUrlEncodedData(oauth_client_info.client_id, 91 true) + 92 "&client_secret=" + 93 net::EscapeUrlEncodedData(oauth_client_info.client_secret, true) + 94 "&redirect_uri=" + 95 net::EscapeUrlEncodedData(oauth_client_info.redirect_uri, true) + 96 "&grant_type=authorization_code"; 97 MakeGaiaRequest(post_body, max_retries, delegate); 98} 99 100void GaiaOAuthClient::Core::RefreshToken( 101 const OAuthClientInfo& oauth_client_info, 102 const std::string& refresh_token, 103 int max_retries, 104 GaiaOAuthClient::Delegate* delegate) { 105 DCHECK_EQ(request_type_, NO_PENDING_REQUEST); 106 request_type_ = REFRESH_TOKEN; 107 std::string post_body = 108 "refresh_token=" + net::EscapeUrlEncodedData(refresh_token, true) + 109 "&client_id=" + net::EscapeUrlEncodedData(oauth_client_info.client_id, 110 true) + 111 "&client_secret=" + 112 net::EscapeUrlEncodedData(oauth_client_info.client_secret, true) + 113 "&grant_type=refresh_token"; 114 MakeGaiaRequest(post_body, max_retries, delegate); 115} 116 117void GaiaOAuthClient::Core::GetUserInfo(const std::string& oauth_access_token, 118 int max_retries, 119 Delegate* delegate) { 120 DCHECK_EQ(request_type_, NO_PENDING_REQUEST); 121 DCHECK(!request_.get()); 122 request_type_ = USER_INFO; 123 delegate_ = delegate; 124 num_retries_ = 0; 125 request_.reset(net::URLFetcher::Create( 126 0, GURL(GaiaUrls::GetInstance()->oauth_user_info_url()), 127 net::URLFetcher::GET, this)); 128 request_->SetRequestContext(request_context_getter_); 129 request_->AddExtraRequestHeader( 130 "Authorization: OAuth " + oauth_access_token); 131 request_->SetMaxRetriesOn5xx(max_retries); 132 // Fetchers are sometimes cancelled because a network change was detected, 133 // especially at startup and after sign-in on ChromeOS. Retrying once should 134 // be enough in those cases; let the fetcher retry up to 3 times just in case. 135 // http://crbug.com/163710 136 request_->SetAutomaticallyRetryOnNetworkChanges(3); 137 request_->Start(); 138} 139 140void GaiaOAuthClient::Core::MakeGaiaRequest( 141 const std::string& post_body, 142 int max_retries, 143 GaiaOAuthClient::Delegate* delegate) { 144 DCHECK(!request_.get()) << "Tried to fetch two things at once!"; 145 delegate_ = delegate; 146 num_retries_ = 0; 147 request_.reset(net::URLFetcher::Create( 148 0, gaia_url_, net::URLFetcher::POST, this)); 149 request_->SetRequestContext(request_context_getter_); 150 request_->SetUploadData("application/x-www-form-urlencoded", post_body); 151 request_->SetMaxRetriesOn5xx(max_retries); 152 // See comment on SetAutomaticallyRetryOnNetworkChanges() above. 153 request_->SetAutomaticallyRetryOnNetworkChanges(3); 154 request_->Start(); 155} 156 157// URLFetcher::Delegate implementation. 158void GaiaOAuthClient::Core::OnURLFetchComplete( 159 const net::URLFetcher* source) { 160 bool should_retry = false; 161 HandleResponse(source, &should_retry); 162 if (should_retry) { 163 // Explicitly call ReceivedContentWasMalformed() to ensure the current 164 // request gets counted as a failure for calculation of the back-off 165 // period. If it was already a failure by status code, this call will 166 // be ignored. 167 request_->ReceivedContentWasMalformed(); 168 num_retries_++; 169 // We must set our request_context_getter_ again because 170 // URLFetcher::Core::RetryOrCompleteUrlFetch resets it to NULL... 171 request_->SetRequestContext(request_context_getter_); 172 request_->Start(); 173 } 174} 175 176void GaiaOAuthClient::Core::HandleResponse( 177 const net::URLFetcher* source, 178 bool* should_retry_request) { 179 // Keep the URLFetcher object in case we need to reuse it. 180 scoped_ptr<net::URLFetcher> old_request = request_.Pass(); 181 DCHECK_EQ(source, old_request.get()); 182 183 // RC_BAD_REQUEST means the arguments are invalid. No point retrying. We are 184 // done here. 185 if (source->GetResponseCode() == net::HTTP_BAD_REQUEST) { 186 delegate_->OnOAuthError(); 187 return; 188 } 189 190 scoped_ptr<DictionaryValue> response_dict; 191 if (source->GetResponseCode() == net::HTTP_OK) { 192 std::string data; 193 source->GetResponseAsString(&data); 194 scoped_ptr<Value> message_value(base::JSONReader::Read(data)); 195 if (message_value.get() && 196 message_value->IsType(Value::TYPE_DICTIONARY)) { 197 response_dict.reset( 198 static_cast<DictionaryValue*>(message_value.release())); 199 } 200 } 201 202 if (!response_dict.get()) { 203 // If we don't have an access token yet and the the error was not 204 // RC_BAD_REQUEST, we may need to retry. 205 if ((source->GetMaxRetriesOn5xx() != -1) && 206 (num_retries_ > source->GetMaxRetriesOn5xx())) { 207 // Retry limit reached. Give up. 208 delegate_->OnNetworkError(source->GetResponseCode()); 209 } else { 210 request_ = old_request.Pass(); 211 *should_retry_request = true; 212 } 213 return; 214 } 215 216 RequestType type = request_type_; 217 request_type_ = NO_PENDING_REQUEST; 218 219 switch (type) { 220 case USER_INFO: { 221 std::string email; 222 response_dict->GetString("email", &email); 223 delegate_->OnGetUserInfoResponse(email); 224 break; 225 } 226 227 case TOKENS_FROM_AUTH_CODE: 228 case REFRESH_TOKEN: { 229 std::string access_token; 230 std::string refresh_token; 231 int expires_in_seconds = 0; 232 response_dict->GetString(kAccessTokenValue, &access_token); 233 response_dict->GetString(kRefreshTokenValue, &refresh_token); 234 response_dict->GetInteger(kExpiresInValue, &expires_in_seconds); 235 236 if (access_token.empty()) { 237 delegate_->OnOAuthError(); 238 return; 239 } 240 241 if (type == REFRESH_TOKEN) { 242 delegate_->OnRefreshTokenResponse(access_token, expires_in_seconds); 243 } else { 244 delegate_->OnGetTokensResponse(refresh_token, 245 access_token, 246 expires_in_seconds); 247 } 248 break; 249 } 250 251 default: 252 NOTREACHED(); 253 } 254} 255 256GaiaOAuthClient::GaiaOAuthClient(const std::string& gaia_url, 257 net::URLRequestContextGetter* context_getter) { 258 core_ = new Core(gaia_url, context_getter); 259} 260 261GaiaOAuthClient::~GaiaOAuthClient() { 262} 263 264void GaiaOAuthClient::GetTokensFromAuthCode( 265 const OAuthClientInfo& oauth_client_info, 266 const std::string& auth_code, 267 int max_retries, 268 Delegate* delegate) { 269 return core_->GetTokensFromAuthCode(oauth_client_info, 270 auth_code, 271 max_retries, 272 delegate); 273} 274 275void GaiaOAuthClient::RefreshToken(const OAuthClientInfo& oauth_client_info, 276 const std::string& refresh_token, 277 int max_retries, 278 Delegate* delegate) { 279 return core_->RefreshToken(oauth_client_info, 280 refresh_token, 281 max_retries, 282 delegate); 283} 284 285void GaiaOAuthClient::GetUserInfo(const std::string& access_token, 286 int max_retries, 287 Delegate* delegate) { 288 return core_->GetUserInfo(access_token, max_retries, delegate); 289} 290 291} // namespace gaia 292