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