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