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