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/oauth2_access_token_fetcher.h"
      6 
      7 #include <algorithm>
      8 #include <string>
      9 #include <vector>
     10 
     11 #include "base/json/json_reader.h"
     12 #include "base/strings/string_util.h"
     13 #include "base/strings/stringprintf.h"
     14 #include "base/time/time.h"
     15 #include "base/values.h"
     16 #include "google_apis/gaia/gaia_urls.h"
     17 #include "google_apis/gaia/google_service_auth_error.h"
     18 #include "net/base/escape.h"
     19 #include "net/base/load_flags.h"
     20 #include "net/http/http_status_code.h"
     21 #include "net/url_request/url_fetcher.h"
     22 #include "net/url_request/url_request_context_getter.h"
     23 #include "net/url_request/url_request_status.h"
     24 
     25 using net::ResponseCookies;
     26 using net::URLFetcher;
     27 using net::URLFetcherDelegate;
     28 using net::URLRequestContextGetter;
     29 using net::URLRequestStatus;
     30 
     31 namespace {
     32 static const char kGetAccessTokenBodyFormat[] =
     33     "client_id=%s&"
     34     "client_secret=%s&"
     35     "grant_type=refresh_token&"
     36     "refresh_token=%s";
     37 
     38 static const char kGetAccessTokenBodyWithScopeFormat[] =
     39     "client_id=%s&"
     40     "client_secret=%s&"
     41     "grant_type=refresh_token&"
     42     "refresh_token=%s&"
     43     "scope=%s";
     44 
     45 static const char kAccessTokenKey[] = "access_token";
     46 static const char kExpiresInKey[] = "expires_in";
     47 
     48 static GoogleServiceAuthError CreateAuthError(URLRequestStatus status) {
     49   CHECK(!status.is_success());
     50   if (status.status() == URLRequestStatus::CANCELED) {
     51     return GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED);
     52   } else {
     53     DLOG(WARNING) << "Could not reach Google Accounts servers: errno "
     54                   << status.error();
     55     return GoogleServiceAuthError::FromConnectionError(status.error());
     56   }
     57 }
     58 
     59 static URLFetcher* CreateFetcher(URLRequestContextGetter* getter,
     60                                  const GURL& url,
     61                                  const std::string& body,
     62                                  URLFetcherDelegate* delegate) {
     63   bool empty_body = body.empty();
     64   URLFetcher* result = net::URLFetcher::Create(
     65       0, url,
     66       empty_body ? URLFetcher::GET : URLFetcher::POST,
     67       delegate);
     68 
     69   result->SetRequestContext(getter);
     70   result->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES |
     71                        net::LOAD_DO_NOT_SAVE_COOKIES);
     72   // Fetchers are sometimes cancelled because a network change was detected,
     73   // especially at startup and after sign-in on ChromeOS. Retrying once should
     74   // be enough in those cases; let the fetcher retry up to 3 times just in case.
     75   // http://crbug.com/163710
     76   result->SetAutomaticallyRetryOnNetworkChanges(3);
     77 
     78   if (!empty_body)
     79     result->SetUploadData("application/x-www-form-urlencoded", body);
     80 
     81   return result;
     82 }
     83 }  // namespace
     84 
     85 OAuth2AccessTokenFetcher::OAuth2AccessTokenFetcher(
     86     OAuth2AccessTokenConsumer* consumer,
     87     URLRequestContextGetter* getter)
     88     : consumer_(consumer),
     89       getter_(getter),
     90       state_(INITIAL) { }
     91 
     92 OAuth2AccessTokenFetcher::~OAuth2AccessTokenFetcher() { }
     93 
     94 void OAuth2AccessTokenFetcher::CancelRequest() {
     95   fetcher_.reset();
     96 }
     97 
     98 void OAuth2AccessTokenFetcher::Start(const std::string& client_id,
     99                                      const std::string& client_secret,
    100                                      const std::string& refresh_token,
    101                                      const std::vector<std::string>& scopes) {
    102   client_id_ = client_id;
    103   client_secret_ = client_secret;
    104   refresh_token_ = refresh_token;
    105   scopes_ = scopes;
    106   StartGetAccessToken();
    107 }
    108 
    109 void OAuth2AccessTokenFetcher::StartGetAccessToken() {
    110   CHECK_EQ(INITIAL, state_);
    111   state_ = GET_ACCESS_TOKEN_STARTED;
    112   fetcher_.reset(CreateFetcher(
    113       getter_,
    114       MakeGetAccessTokenUrl(),
    115       MakeGetAccessTokenBody(
    116           client_id_, client_secret_, refresh_token_, scopes_),
    117       this));
    118   fetcher_->Start();  // OnURLFetchComplete will be called.
    119 }
    120 
    121 void OAuth2AccessTokenFetcher::EndGetAccessToken(
    122     const net::URLFetcher* source) {
    123   CHECK_EQ(GET_ACCESS_TOKEN_STARTED, state_);
    124   state_ = GET_ACCESS_TOKEN_DONE;
    125 
    126   URLRequestStatus status = source->GetStatus();
    127   if (!status.is_success()) {
    128     OnGetTokenFailure(CreateAuthError(status));
    129     return;
    130   }
    131 
    132   // HTTP_FORBIDDEN (403) is treated as temporary error, because it may be
    133   // '403 Rate Limit Exeeded.'
    134   if (source->GetResponseCode() == net::HTTP_FORBIDDEN) {
    135     OnGetTokenFailure(GoogleServiceAuthError(
    136         GoogleServiceAuthError::SERVICE_UNAVAILABLE));
    137     return;
    138   }
    139 
    140   // The other errors are treated as permanent error.
    141   if (source->GetResponseCode() != net::HTTP_OK) {
    142     OnGetTokenFailure(GoogleServiceAuthError(
    143         GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS));
    144     return;
    145   }
    146 
    147   // The request was successfully fetched and it returned OK.
    148   // Parse out the access token and the expiration time.
    149   std::string access_token;
    150   int expires_in;
    151   if (!ParseGetAccessTokenResponse(source, &access_token, &expires_in)) {
    152     DLOG(WARNING) << "Response doesn't match expected format";
    153     OnGetTokenFailure(
    154         GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE));
    155     return;
    156   }
    157   // The token will expire in |expires_in| seconds. Take a 10% error margin to
    158   // prevent reusing a token too close to its expiration date.
    159   OnGetTokenSuccess(
    160       access_token,
    161       base::Time::Now() + base::TimeDelta::FromSeconds(9 * expires_in / 10));
    162 }
    163 
    164 void OAuth2AccessTokenFetcher::OnGetTokenSuccess(
    165     const std::string& access_token,
    166     const base::Time& expiration_time) {
    167   consumer_->OnGetTokenSuccess(access_token, expiration_time);
    168 }
    169 
    170 void OAuth2AccessTokenFetcher::OnGetTokenFailure(
    171     const GoogleServiceAuthError& error) {
    172   state_ = ERROR_STATE;
    173   consumer_->OnGetTokenFailure(error);
    174 }
    175 
    176 void OAuth2AccessTokenFetcher::OnURLFetchComplete(
    177     const net::URLFetcher* source) {
    178   CHECK(source);
    179   CHECK(state_ == GET_ACCESS_TOKEN_STARTED);
    180   EndGetAccessToken(source);
    181 }
    182 
    183 // static
    184 GURL OAuth2AccessTokenFetcher::MakeGetAccessTokenUrl() {
    185   return GURL(GaiaUrls::GetInstance()->oauth2_token_url());
    186 }
    187 
    188 // static
    189 std::string OAuth2AccessTokenFetcher::MakeGetAccessTokenBody(
    190     const std::string& client_id,
    191     const std::string& client_secret,
    192     const std::string& refresh_token,
    193     const std::vector<std::string>& scopes) {
    194   std::string enc_client_id = net::EscapeUrlEncodedData(client_id, true);
    195   std::string enc_client_secret =
    196       net::EscapeUrlEncodedData(client_secret, true);
    197   std::string enc_refresh_token =
    198       net::EscapeUrlEncodedData(refresh_token, true);
    199   if (scopes.empty()) {
    200     return base::StringPrintf(
    201         kGetAccessTokenBodyFormat,
    202         enc_client_id.c_str(),
    203         enc_client_secret.c_str(),
    204         enc_refresh_token.c_str());
    205   } else {
    206     std::string scopes_string = JoinString(scopes, ' ');
    207     return base::StringPrintf(
    208         kGetAccessTokenBodyWithScopeFormat,
    209         enc_client_id.c_str(),
    210         enc_client_secret.c_str(),
    211         enc_refresh_token.c_str(),
    212         net::EscapeUrlEncodedData(scopes_string, true).c_str());
    213   }
    214 }
    215 
    216 // static
    217 bool OAuth2AccessTokenFetcher::ParseGetAccessTokenResponse(
    218     const net::URLFetcher* source,
    219     std::string* access_token,
    220     int* expires_in) {
    221   CHECK(source);
    222   CHECK(access_token);
    223   std::string data;
    224   source->GetResponseAsString(&data);
    225   scoped_ptr<base::Value> value(base::JSONReader::Read(data));
    226   if (!value.get() || value->GetType() != base::Value::TYPE_DICTIONARY)
    227     return false;
    228 
    229   base::DictionaryValue* dict =
    230       static_cast<base::DictionaryValue*>(value.get());
    231   return dict->GetString(kAccessTokenKey, access_token) &&
    232       dict->GetInteger(kExpiresInKey, expires_in);
    233 }
    234