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   // RC_BAD_REQUEST means the arguments are invalid. No point retrying. We are
    245   // done here.
    246   if (source->GetResponseCode() == net::HTTP_BAD_REQUEST) {
    247     delegate_->OnOAuthError();
    248     return;
    249   }
    250 
    251   scoped_ptr<base::DictionaryValue> response_dict;
    252   if (source->GetResponseCode() == net::HTTP_OK) {
    253     std::string data;
    254     source->GetResponseAsString(&data);
    255     scoped_ptr<base::Value> message_value(base::JSONReader::Read(data));
    256     if (message_value.get() &&
    257         message_value->IsType(base::Value::TYPE_DICTIONARY)) {
    258       response_dict.reset(
    259           static_cast<base::DictionaryValue*>(message_value.release()));
    260     }
    261   }
    262 
    263   if (!response_dict.get()) {
    264     // If we don't have an access token yet and the the error was not
    265     // RC_BAD_REQUEST, we may need to retry.
    266     if ((source->GetMaxRetriesOn5xx() != -1) &&
    267         (num_retries_ >= source->GetMaxRetriesOn5xx())) {
    268       // Retry limit reached. Give up.
    269       delegate_->OnNetworkError(source->GetResponseCode());
    270     } else {
    271       request_ = old_request.Pass();
    272       *should_retry_request = true;
    273     }
    274     return;
    275   }
    276 
    277   RequestType type = request_type_;
    278   request_type_ = NO_PENDING_REQUEST;
    279 
    280   switch (type) {
    281     case USER_EMAIL: {
    282       std::string email;
    283       response_dict->GetString("email", &email);
    284       delegate_->OnGetUserEmailResponse(email);
    285       break;
    286     }
    287 
    288     case USER_ID: {
    289       std::string id;
    290       response_dict->GetString("id", &id);
    291       delegate_->OnGetUserIdResponse(id);
    292       break;
    293     }
    294 
    295     case TOKEN_INFO: {
    296       delegate_->OnGetTokenInfoResponse(response_dict.Pass());
    297       break;
    298     }
    299 
    300     case TOKENS_FROM_AUTH_CODE:
    301     case REFRESH_TOKEN: {
    302       std::string access_token;
    303       std::string refresh_token;
    304       int expires_in_seconds = 0;
    305       response_dict->GetString(kAccessTokenValue, &access_token);
    306       response_dict->GetString(kRefreshTokenValue, &refresh_token);
    307       response_dict->GetInteger(kExpiresInValue, &expires_in_seconds);
    308 
    309       if (access_token.empty()) {
    310         delegate_->OnOAuthError();
    311         return;
    312       }
    313 
    314       if (type == REFRESH_TOKEN) {
    315         delegate_->OnRefreshTokenResponse(access_token, expires_in_seconds);
    316       } else {
    317         delegate_->OnGetTokensResponse(refresh_token,
    318                                        access_token,
    319                                        expires_in_seconds);
    320       }
    321       break;
    322     }
    323 
    324     default:
    325       NOTREACHED();
    326   }
    327 }
    328 
    329 GaiaOAuthClient::GaiaOAuthClient(net::URLRequestContextGetter* context_getter) {
    330   core_ = new Core(context_getter);
    331 }
    332 
    333 GaiaOAuthClient::~GaiaOAuthClient() {
    334 }
    335 
    336 void GaiaOAuthClient::GetTokensFromAuthCode(
    337     const OAuthClientInfo& oauth_client_info,
    338     const std::string& auth_code,
    339     int max_retries,
    340     Delegate* delegate) {
    341   return core_->GetTokensFromAuthCode(oauth_client_info,
    342                                       auth_code,
    343                                       max_retries,
    344                                       delegate);
    345 }
    346 
    347 void GaiaOAuthClient::RefreshToken(
    348     const OAuthClientInfo& oauth_client_info,
    349     const std::string& refresh_token,
    350     const std::vector<std::string>& scopes,
    351     int max_retries,
    352     Delegate* delegate) {
    353   return core_->RefreshToken(oauth_client_info,
    354                              refresh_token,
    355                              scopes,
    356                              max_retries,
    357                              delegate);
    358 }
    359 
    360 void GaiaOAuthClient::GetUserEmail(const std::string& access_token,
    361                                   int max_retries,
    362                                   Delegate* delegate) {
    363   return core_->GetUserEmail(access_token, max_retries, delegate);
    364 }
    365 
    366 void GaiaOAuthClient::GetUserId(const std::string& access_token,
    367                                 int max_retries,
    368                                 Delegate* delegate) {
    369   return core_->GetUserId(access_token, max_retries, delegate);
    370 }
    371 
    372 void GaiaOAuthClient::GetTokenInfo(const std::string& access_token,
    373                                    int max_retries,
    374                                    Delegate* delegate) {
    375   return core_->GetTokenInfo(access_token, max_retries, delegate);
    376 }
    377 
    378 }  // namespace gaia
    379