Home | History | Annotate | Download | only in supervised_user
      1 // Copyright 2014 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 "chrome/browser/supervised_user/supervised_user_refresh_token_fetcher.h"
      6 
      7 #include "base/callback.h"
      8 #include "base/json/json_reader.h"
      9 #include "base/logging.h"
     10 #include "base/strings/stringprintf.h"
     11 #include "base/values.h"
     12 #include "google_apis/gaia/gaia_constants.h"
     13 #include "google_apis/gaia/gaia_oauth_client.h"
     14 #include "google_apis/gaia/gaia_urls.h"
     15 #include "google_apis/gaia/google_service_auth_error.h"
     16 #include "google_apis/gaia/oauth2_api_call_flow.h"
     17 #include "google_apis/gaia/oauth2_token_service.h"
     18 #include "net/base/escape.h"
     19 #include "net/base/load_flags.h"
     20 #include "net/base/net_errors.h"
     21 #include "net/http/http_status_code.h"
     22 #include "net/url_request/url_fetcher.h"
     23 #include "net/url_request/url_request_status.h"
     24 
     25 using GaiaConstants::kChromeSyncSupervisedOAuth2Scope;
     26 using base::Time;
     27 using gaia::GaiaOAuthClient;
     28 using net::URLFetcher;
     29 using net::URLFetcherDelegate;
     30 using net::URLRequestContextGetter;
     31 
     32 namespace {
     33 
     34 const int kNumRetries = 1;
     35 
     36 static const char kIssueTokenBodyFormat[] =
     37     "client_id=%s"
     38     "&scope=%s"
     39     "&response_type=code"
     40     "&profile_id=%s"
     41     "&device_name=%s";
     42 
     43 // kIssueTokenBodyFormatDeviceIdAddendum is appended to kIssueTokenBodyFormat
     44 // if device_id is provided.
     45 static const char kIssueTokenBodyFormatDeviceIdAddendum[] = "&device_id=%s";
     46 
     47 static const char kAuthorizationHeaderFormat[] =
     48     "Authorization: Bearer %s";
     49 
     50 static const char kCodeKey[] = "code";
     51 
     52 class SupervisedUserRefreshTokenFetcherImpl
     53     : public SupervisedUserRefreshTokenFetcher,
     54       public OAuth2TokenService::Consumer,
     55       public URLFetcherDelegate,
     56       public GaiaOAuthClient::Delegate {
     57  public:
     58   SupervisedUserRefreshTokenFetcherImpl(
     59       OAuth2TokenService* oauth2_token_service,
     60       const std::string& account_id,
     61       const std::string& device_id,
     62       URLRequestContextGetter* context);
     63   virtual ~SupervisedUserRefreshTokenFetcherImpl();
     64 
     65   // SupervisedUserRefreshTokenFetcher implementation:
     66   virtual void Start(const std::string& supervised_user_id,
     67                      const std::string& device_name,
     68                      const TokenCallback& callback) OVERRIDE;
     69 
     70  protected:
     71   // OAuth2TokenService::Consumer implementation:
     72   virtual void OnGetTokenSuccess(const OAuth2TokenService::Request* request,
     73                                  const std::string& access_token,
     74                                  const Time& expiration_time) OVERRIDE;
     75   virtual void OnGetTokenFailure(const OAuth2TokenService::Request* request,
     76                                  const GoogleServiceAuthError& error) OVERRIDE;
     77 
     78   // net::URLFetcherDelegate implementation.
     79   virtual void OnURLFetchComplete(const URLFetcher* source) OVERRIDE;
     80 
     81   // GaiaOAuthClient::Delegate implementation:
     82   virtual void OnGetTokensResponse(const std::string& refresh_token,
     83                                    const std::string& access_token,
     84                                    int expires_in_seconds) OVERRIDE;
     85   virtual void OnRefreshTokenResponse(const std::string& access_token,
     86                                       int expires_in_seconds) OVERRIDE;
     87   virtual void OnOAuthError() OVERRIDE;
     88   virtual void OnNetworkError(int response_code) OVERRIDE;
     89 
     90  private:
     91   // Requests an access token, which is the first thing we need. This is where
     92   // we restart when the returned access token has expired.
     93   void StartFetching();
     94 
     95   void DispatchNetworkError(int error_code);
     96   void DispatchGoogleServiceAuthError(const GoogleServiceAuthError& error,
     97                                       const std::string& token);
     98   OAuth2TokenService* oauth2_token_service_;
     99   std::string account_id_;
    100   std::string device_id_;
    101   URLRequestContextGetter* context_;
    102 
    103   std::string device_name_;
    104   std::string supervised_user_id_;
    105   TokenCallback callback_;
    106 
    107   scoped_ptr<OAuth2TokenService::Request> access_token_request_;
    108   std::string access_token_;
    109   bool access_token_expired_;
    110   scoped_ptr<URLFetcher> url_fetcher_;
    111   scoped_ptr<GaiaOAuthClient> gaia_oauth_client_;
    112 };
    113 
    114 SupervisedUserRefreshTokenFetcherImpl::SupervisedUserRefreshTokenFetcherImpl(
    115     OAuth2TokenService* oauth2_token_service,
    116     const std::string& account_id,
    117     const std::string& device_id,
    118     URLRequestContextGetter* context)
    119     : OAuth2TokenService::Consumer("supervised_user"),
    120       oauth2_token_service_(oauth2_token_service),
    121       account_id_(account_id),
    122       device_id_(device_id),
    123       context_(context),
    124       access_token_expired_(false) {}
    125 
    126 SupervisedUserRefreshTokenFetcherImpl::
    127 ~SupervisedUserRefreshTokenFetcherImpl() {}
    128 
    129 void SupervisedUserRefreshTokenFetcherImpl::Start(
    130     const std::string& supervised_user_id,
    131     const std::string& device_name,
    132     const TokenCallback& callback) {
    133   DCHECK(callback_.is_null());
    134   supervised_user_id_ = supervised_user_id;
    135   device_name_ = device_name;
    136   callback_ = callback;
    137   StartFetching();
    138 }
    139 
    140 void SupervisedUserRefreshTokenFetcherImpl::StartFetching() {
    141   OAuth2TokenService::ScopeSet scopes;
    142   scopes.insert(GaiaConstants::kOAuth1LoginScope);
    143   access_token_request_ = oauth2_token_service_->StartRequest(
    144       account_id_, scopes, this);
    145 }
    146 
    147 void SupervisedUserRefreshTokenFetcherImpl::OnGetTokenSuccess(
    148     const OAuth2TokenService::Request* request,
    149     const std::string& access_token,
    150     const Time& expiration_time) {
    151   DCHECK_EQ(access_token_request_.get(), request);
    152   access_token_ = access_token;
    153 
    154   GURL url(GaiaUrls::GetInstance()->oauth2_issue_token_url());
    155   // GaiaOAuthClient uses id 0, so we use 1 to distinguish the requests in
    156   // unit tests.
    157   const int id = 1;
    158 
    159   url_fetcher_.reset(URLFetcher::Create(id, url, URLFetcher::POST, this));
    160 
    161   url_fetcher_->SetRequestContext(context_);
    162   url_fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES |
    163                              net::LOAD_DO_NOT_SAVE_COOKIES);
    164   url_fetcher_->SetAutomaticallyRetryOnNetworkChanges(kNumRetries);
    165   url_fetcher_->AddExtraRequestHeader(
    166       base::StringPrintf(kAuthorizationHeaderFormat, access_token.c_str()));
    167 
    168   std::string body = base::StringPrintf(
    169       kIssueTokenBodyFormat,
    170       net::EscapeUrlEncodedData(
    171           GaiaUrls::GetInstance()->oauth2_chrome_client_id(), true).c_str(),
    172       net::EscapeUrlEncodedData(kChromeSyncSupervisedOAuth2Scope, true).c_str(),
    173       net::EscapeUrlEncodedData(supervised_user_id_, true).c_str(),
    174       net::EscapeUrlEncodedData(device_name_, true).c_str());
    175   if (!device_id_.empty()) {
    176     body.append(base::StringPrintf(
    177         kIssueTokenBodyFormatDeviceIdAddendum,
    178         net::EscapeUrlEncodedData(device_id_, true).c_str()));
    179   }
    180   url_fetcher_->SetUploadData("application/x-www-form-urlencoded", body);
    181 
    182   url_fetcher_->Start();
    183 }
    184 
    185 void SupervisedUserRefreshTokenFetcherImpl::OnGetTokenFailure(
    186     const OAuth2TokenService::Request* request,
    187     const GoogleServiceAuthError& error) {
    188   DCHECK_EQ(access_token_request_.get(), request);
    189   callback_.Run(error, std::string());
    190   callback_.Reset();
    191 }
    192 
    193 void SupervisedUserRefreshTokenFetcherImpl::OnURLFetchComplete(
    194     const URLFetcher* source) {
    195   const net::URLRequestStatus& status = source->GetStatus();
    196   if (!status.is_success()) {
    197     DispatchNetworkError(status.error());
    198     return;
    199   }
    200 
    201   int response_code = source->GetResponseCode();
    202   if (response_code == net::HTTP_UNAUTHORIZED && !access_token_expired_) {
    203     access_token_expired_ = true;
    204     oauth2_token_service_->InvalidateToken(account_id_,
    205                                            OAuth2TokenService::ScopeSet(),
    206                                            access_token_);
    207     StartFetching();
    208     return;
    209   }
    210 
    211   if (response_code != net::HTTP_OK) {
    212     // TODO(bauerb): We should return the HTTP response code somehow.
    213     DLOG(WARNING) << "HTTP error " << response_code;
    214     DispatchGoogleServiceAuthError(
    215         GoogleServiceAuthError(GoogleServiceAuthError::CONNECTION_FAILED),
    216         std::string());
    217     return;
    218   }
    219 
    220   std::string response_body;
    221   source->GetResponseAsString(&response_body);
    222   scoped_ptr<base::Value> value(base::JSONReader::Read(response_body));
    223   base::DictionaryValue* dict = NULL;
    224   if (!value.get() || !value->GetAsDictionary(&dict)) {
    225     DispatchNetworkError(net::ERR_INVALID_RESPONSE);
    226     return;
    227   }
    228   std::string auth_code;
    229   if (!dict->GetString(kCodeKey, &auth_code)) {
    230     DispatchNetworkError(net::ERR_INVALID_RESPONSE);
    231     return;
    232   }
    233 
    234   gaia::OAuthClientInfo client_info;
    235   GaiaUrls* urls = GaiaUrls::GetInstance();
    236   client_info.client_id = urls->oauth2_chrome_client_id();
    237   client_info.client_secret = urls->oauth2_chrome_client_secret();
    238   gaia_oauth_client_.reset(new gaia::GaiaOAuthClient(context_));
    239   gaia_oauth_client_->GetTokensFromAuthCode(client_info, auth_code, kNumRetries,
    240                                             this);
    241 }
    242 
    243 void SupervisedUserRefreshTokenFetcherImpl::OnGetTokensResponse(
    244     const std::string& refresh_token,
    245     const std::string& access_token,
    246     int expires_in_seconds) {
    247   // TODO(bauerb): It would be nice if we could pass the access token as well,
    248   // so we don't need to fetch another one immediately.
    249   DispatchGoogleServiceAuthError(GoogleServiceAuthError::AuthErrorNone(),
    250                                  refresh_token);
    251 }
    252 
    253 void SupervisedUserRefreshTokenFetcherImpl::OnRefreshTokenResponse(
    254     const std::string& access_token,
    255     int expires_in_seconds) {
    256   NOTREACHED();
    257 }
    258 
    259 void SupervisedUserRefreshTokenFetcherImpl::OnOAuthError() {
    260   DispatchGoogleServiceAuthError(
    261       GoogleServiceAuthError(GoogleServiceAuthError::CONNECTION_FAILED),
    262       std::string());
    263 }
    264 
    265 void SupervisedUserRefreshTokenFetcherImpl::OnNetworkError(int response_code) {
    266   // TODO(bauerb): We should return the HTTP response code somehow.
    267   DLOG(WARNING) << "HTTP error " << response_code;
    268   DispatchGoogleServiceAuthError(
    269       GoogleServiceAuthError(GoogleServiceAuthError::CONNECTION_FAILED),
    270       std::string());
    271 }
    272 
    273 void SupervisedUserRefreshTokenFetcherImpl::DispatchNetworkError(
    274     int error_code) {
    275   DispatchGoogleServiceAuthError(
    276       GoogleServiceAuthError::FromConnectionError(error_code), std::string());
    277 }
    278 
    279 void SupervisedUserRefreshTokenFetcherImpl::DispatchGoogleServiceAuthError(
    280     const GoogleServiceAuthError& error,
    281     const std::string& token) {
    282   callback_.Run(error, token);
    283   callback_.Reset();
    284 }
    285 
    286 }  // namespace
    287 
    288 // static
    289 scoped_ptr<SupervisedUserRefreshTokenFetcher>
    290 SupervisedUserRefreshTokenFetcher::Create(
    291     OAuth2TokenService* oauth2_token_service,
    292     const std::string& account_id,
    293     const std::string& device_id,
    294     URLRequestContextGetter* context) {
    295   scoped_ptr<SupervisedUserRefreshTokenFetcher> fetcher(
    296       new SupervisedUserRefreshTokenFetcherImpl(oauth2_token_service,
    297                                                 account_id,
    298                                                 device_id,
    299                                                 context));
    300   return fetcher.Pass();
    301 }
    302 
    303 SupervisedUserRefreshTokenFetcher::~SupervisedUserRefreshTokenFetcher() {}
    304