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