1 // Copyright (c) 2011 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 "chrome/common/net/gaia/gaia_oauth_client.h" 6 7 #include "base/json/json_reader.h" 8 #include "base/memory/scoped_ptr.h" 9 #include "base/values.h" 10 #include "chrome/common/net/http_return.h" 11 #include "chrome/common/net/url_fetcher.h" 12 #include "googleurl/src/gurl.h" 13 #include "net/base/escape.h" 14 #include "net/url_request/url_request_context_getter.h" 15 16 namespace { 17 const char kAccessTokenValue[] = "access_token"; 18 const char kRefreshTokenValue[] = "refresh_token"; 19 const char kExpiresInValue[] = "expires_in"; 20 } 21 22 namespace gaia { 23 24 class GaiaOAuthClient::Core 25 : public base::RefCountedThreadSafe<GaiaOAuthClient::Core>, 26 public URLFetcher::Delegate { 27 public: 28 Core(const std::string& gaia_url, 29 net::URLRequestContextGetter* request_context_getter) 30 : gaia_url_(gaia_url), 31 num_retries_(0), 32 request_context_getter_(request_context_getter), 33 delegate_(NULL) {} 34 35 virtual ~Core() { } 36 37 void GetTokensFromAuthCode(const OAuthClientInfo& oauth_client_info, 38 const std::string& auth_code, 39 int max_retries, 40 GaiaOAuthClient::Delegate* delegate); 41 void RefreshToken(const OAuthClientInfo& oauth_client_info, 42 const std::string& refresh_token, 43 int max_retries, 44 GaiaOAuthClient::Delegate* delegate); 45 46 // URLFetcher::Delegate implementation. 47 virtual void OnURLFetchComplete( 48 const URLFetcher* source, 49 const GURL& url, 50 const net::URLRequestStatus& status, 51 int response_code, 52 const ResponseCookies& cookies, 53 const std::string& data); 54 55 private: 56 void MakeGaiaRequest(std::string post_body, 57 int max_retries, 58 GaiaOAuthClient::Delegate* delegate); 59 void HandleResponse(const URLFetcher* source, 60 const GURL& url, 61 const net::URLRequestStatus& status, 62 int response_code, 63 const std::string& data, 64 bool* should_retry_request); 65 66 GURL gaia_url_; 67 int num_retries_; 68 scoped_refptr<net::URLRequestContextGetter> request_context_getter_; 69 GaiaOAuthClient::Delegate* delegate_; 70 scoped_ptr<URLFetcher> request_; 71 }; 72 73 void GaiaOAuthClient::Core::GetTokensFromAuthCode( 74 const OAuthClientInfo& oauth_client_info, 75 const std::string& auth_code, 76 int max_retries, 77 GaiaOAuthClient::Delegate* delegate) { 78 std::string post_body = 79 "code=" + EscapeUrlEncodedData(auth_code) + 80 "&client_id=" + EscapeUrlEncodedData(oauth_client_info.client_id) + 81 "&client_secret=" + 82 EscapeUrlEncodedData(oauth_client_info.client_secret) + 83 "&redirect_uri=oob&grant_type=authorization_code"; 84 MakeGaiaRequest(post_body, max_retries, delegate); 85 } 86 87 void GaiaOAuthClient::Core::RefreshToken( 88 const OAuthClientInfo& oauth_client_info, 89 const std::string& refresh_token, 90 int max_retries, 91 GaiaOAuthClient::Delegate* delegate) { 92 std::string post_body = 93 "refresh_token=" + EscapeUrlEncodedData(refresh_token) + 94 "&client_id=" + EscapeUrlEncodedData(oauth_client_info.client_id) + 95 "&client_secret=" + 96 EscapeUrlEncodedData(oauth_client_info.client_secret) + 97 "&grant_type=refresh_token"; 98 MakeGaiaRequest(post_body, max_retries, delegate); 99 } 100 101 void GaiaOAuthClient::Core::MakeGaiaRequest( 102 std::string post_body, 103 int max_retries, 104 GaiaOAuthClient::Delegate* delegate) { 105 DCHECK(!request_.get()) << "Tried to fetch two things at once!"; 106 delegate_ = delegate; 107 num_retries_ = 0; 108 request_.reset(URLFetcher::Create(0, gaia_url_, URLFetcher::POST, this)); 109 request_->set_request_context(request_context_getter_); 110 request_->set_upload_data("application/x-www-form-urlencoded", post_body); 111 request_->set_max_retries(max_retries); 112 request_->Start(); 113 } 114 115 // URLFetcher::Delegate implementation. 116 void GaiaOAuthClient::Core::OnURLFetchComplete( 117 const URLFetcher* source, 118 const GURL& url, 119 const net::URLRequestStatus& status, 120 int response_code, 121 const ResponseCookies& cookies, 122 const std::string& data) { 123 bool should_retry = false; 124 HandleResponse(source, url, status, response_code, data, &should_retry); 125 if (should_retry) { 126 // If the response code is greater than or equal to 500, then the back-off 127 // period has been increased at the network level; otherwise, explicitly 128 // call ReceivedContentWasMalformed() to count the current request as a 129 // failure and increase the back-off period. 130 if (response_code < 500) 131 request_->ReceivedContentWasMalformed(); 132 num_retries_++; 133 request_->Start(); 134 } else { 135 request_.reset(); 136 } 137 } 138 139 void GaiaOAuthClient::Core::HandleResponse( 140 const URLFetcher* source, 141 const GURL& url, 142 const net::URLRequestStatus& status, 143 int response_code, 144 const std::string& data, 145 bool* should_retry_request) { 146 *should_retry_request = false; 147 // RC_BAD_REQUEST means the arguments are invalid. No point retrying. We are 148 // done here. 149 if (response_code == RC_BAD_REQUEST) { 150 delegate_->OnOAuthError(); 151 return; 152 } 153 std::string access_token; 154 std::string refresh_token; 155 int expires_in_seconds = 0; 156 if (response_code == RC_REQUEST_OK) { 157 scoped_ptr<Value> message_value( 158 base::JSONReader::Read(data, false)); 159 if (message_value.get() && 160 message_value->IsType(Value::TYPE_DICTIONARY)) { 161 scoped_ptr<DictionaryValue> response_dict( 162 static_cast<DictionaryValue*>(message_value.release())); 163 response_dict->GetString(kAccessTokenValue, &access_token); 164 response_dict->GetString(kRefreshTokenValue, &refresh_token); 165 response_dict->GetInteger(kExpiresInValue, &expires_in_seconds); 166 } 167 } 168 if (access_token.empty()) { 169 // If we don't have an access token yet and the the error was not 170 // RC_BAD_REQUEST, we may need to retry. 171 if ((-1 != source->max_retries()) && 172 (num_retries_ > source->max_retries())) { 173 // Retry limit reached. Give up. 174 delegate_->OnNetworkError(response_code); 175 } else { 176 *should_retry_request = true; 177 } 178 } else if (refresh_token.empty()) { 179 // If we only have an access token, then this was a refresh request. 180 delegate_->OnRefreshTokenResponse(access_token, expires_in_seconds); 181 } else { 182 delegate_->OnGetTokensResponse(refresh_token, 183 access_token, 184 expires_in_seconds); 185 } 186 } 187 188 GaiaOAuthClient::GaiaOAuthClient(const std::string& gaia_url, 189 net::URLRequestContextGetter* context_getter) { 190 core_ = new Core(gaia_url, context_getter); 191 } 192 193 GaiaOAuthClient::~GaiaOAuthClient() { 194 } 195 196 197 void GaiaOAuthClient::GetTokensFromAuthCode( 198 const OAuthClientInfo& oauth_client_info, 199 const std::string& auth_code, 200 int max_retries, 201 Delegate* delegate) { 202 return core_->GetTokensFromAuthCode(oauth_client_info, 203 auth_code, 204 max_retries, 205 delegate); 206 } 207 208 void GaiaOAuthClient::RefreshToken(const OAuthClientInfo& oauth_client_info, 209 const std::string& refresh_token, 210 int max_retries, 211 Delegate* delegate) { 212 return core_->RefreshToken(oauth_client_info, 213 refresh_token, 214 max_retries, 215 delegate); 216 } 217 218 } // namespace gaia 219