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