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.
5 #include "chrome/browser/supervised_user/legacy/supervised_user_refresh_token_fetcher.h"
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"
25 using GaiaConstants::kChromeSyncSupervisedOAuth2Scope
;
27 using gaia::GaiaOAuthClient
;
28 using net::URLFetcher
;
29 using net::URLFetcherDelegate
;
30 using net::URLRequestContextGetter
;
34 const int kNumRetries
= 1;
36 static const char kIssueTokenBodyFormat
[] =
43 // kIssueTokenBodyFormatDeviceIdAddendum is appended to kIssueTokenBodyFormat
44 // if device_id is provided.
45 // TODO(pavely): lib_ver is passed to differentiate IssueToken requests from
46 // different code locations. Remove once device_id mismatch is understood.
48 static const char kIssueTokenBodyFormatDeviceIdAddendum
[] =
49 "&device_id=%s&lib_ver=supervised_user";
51 static const char kAuthorizationHeaderFormat
[] =
52 "Authorization: Bearer %s";
54 static const char kCodeKey
[] = "code";
56 class SupervisedUserRefreshTokenFetcherImpl
57 : public SupervisedUserRefreshTokenFetcher
,
58 public OAuth2TokenService::Consumer
,
59 public URLFetcherDelegate
,
60 public GaiaOAuthClient::Delegate
{
62 SupervisedUserRefreshTokenFetcherImpl(
63 OAuth2TokenService
* oauth2_token_service
,
64 const std::string
& account_id
,
65 const std::string
& device_id
,
66 URLRequestContextGetter
* context
);
67 ~SupervisedUserRefreshTokenFetcherImpl() override
;
69 // SupervisedUserRefreshTokenFetcher implementation:
70 void Start(const std::string
& supervised_user_id
,
71 const std::string
& device_name
,
72 const TokenCallback
& callback
) override
;
75 // OAuth2TokenService::Consumer implementation:
76 void OnGetTokenSuccess(const OAuth2TokenService::Request
* request
,
77 const std::string
& access_token
,
78 const Time
& expiration_time
) override
;
79 void OnGetTokenFailure(const OAuth2TokenService::Request
* request
,
80 const GoogleServiceAuthError
& error
) override
;
82 // net::URLFetcherDelegate implementation.
83 void OnURLFetchComplete(const URLFetcher
* source
) override
;
85 // GaiaOAuthClient::Delegate implementation:
86 void OnGetTokensResponse(const std::string
& refresh_token
,
87 const std::string
& access_token
,
88 int expires_in_seconds
) override
;
89 void OnRefreshTokenResponse(const std::string
& access_token
,
90 int expires_in_seconds
) override
;
91 void OnOAuthError() override
;
92 void OnNetworkError(int response_code
) override
;
95 // Requests an access token, which is the first thing we need. This is where
96 // we restart when the returned access token has expired.
99 void DispatchNetworkError(int error_code
);
100 void DispatchGoogleServiceAuthError(const GoogleServiceAuthError
& error
,
101 const std::string
& token
);
102 OAuth2TokenService
* oauth2_token_service_
;
103 std::string account_id_
;
104 std::string device_id_
;
105 URLRequestContextGetter
* context_
;
107 std::string device_name_
;
108 std::string supervised_user_id_
;
109 TokenCallback callback_
;
111 scoped_ptr
<OAuth2TokenService::Request
> access_token_request_
;
112 std::string access_token_
;
113 bool access_token_expired_
;
114 scoped_ptr
<URLFetcher
> url_fetcher_
;
115 scoped_ptr
<GaiaOAuthClient
> gaia_oauth_client_
;
118 SupervisedUserRefreshTokenFetcherImpl::SupervisedUserRefreshTokenFetcherImpl(
119 OAuth2TokenService
* oauth2_token_service
,
120 const std::string
& account_id
,
121 const std::string
& device_id
,
122 URLRequestContextGetter
* context
)
123 : OAuth2TokenService::Consumer("supervised_user"),
124 oauth2_token_service_(oauth2_token_service
),
125 account_id_(account_id
),
126 device_id_(device_id
),
128 access_token_expired_(false) {}
130 SupervisedUserRefreshTokenFetcherImpl::
131 ~SupervisedUserRefreshTokenFetcherImpl() {}
133 void SupervisedUserRefreshTokenFetcherImpl::Start(
134 const std::string
& supervised_user_id
,
135 const std::string
& device_name
,
136 const TokenCallback
& callback
) {
137 DCHECK(callback_
.is_null());
138 supervised_user_id_
= supervised_user_id
;
139 device_name_
= device_name
;
140 callback_
= callback
;
144 void SupervisedUserRefreshTokenFetcherImpl::StartFetching() {
145 OAuth2TokenService::ScopeSet scopes
;
146 scopes
.insert(GaiaConstants::kOAuth1LoginScope
);
147 access_token_request_
= oauth2_token_service_
->StartRequest(
148 account_id_
, scopes
, this);
151 void SupervisedUserRefreshTokenFetcherImpl::OnGetTokenSuccess(
152 const OAuth2TokenService::Request
* request
,
153 const std::string
& access_token
,
154 const Time
& expiration_time
) {
155 DCHECK_EQ(access_token_request_
.get(), request
);
156 access_token_
= access_token
;
158 GURL
url(GaiaUrls::GetInstance()->oauth2_issue_token_url());
159 // GaiaOAuthClient uses id 0, so we use 1 to distinguish the requests in
163 url_fetcher_
= URLFetcher::Create(id
, url
, URLFetcher::POST
, this);
165 url_fetcher_
->SetRequestContext(context_
);
166 url_fetcher_
->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES
|
167 net::LOAD_DO_NOT_SAVE_COOKIES
);
168 url_fetcher_
->SetAutomaticallyRetryOnNetworkChanges(kNumRetries
);
169 url_fetcher_
->AddExtraRequestHeader(
170 base::StringPrintf(kAuthorizationHeaderFormat
, access_token
.c_str()));
172 std::string body
= base::StringPrintf(
173 kIssueTokenBodyFormat
,
174 net::EscapeUrlEncodedData(
175 GaiaUrls::GetInstance()->oauth2_chrome_client_id(), true).c_str(),
176 net::EscapeUrlEncodedData(kChromeSyncSupervisedOAuth2Scope
, true).c_str(),
177 net::EscapeUrlEncodedData(supervised_user_id_
, true).c_str(),
178 net::EscapeUrlEncodedData(device_name_
, true).c_str());
179 if (!device_id_
.empty()) {
180 body
.append(base::StringPrintf(
181 kIssueTokenBodyFormatDeviceIdAddendum
,
182 net::EscapeUrlEncodedData(device_id_
, true).c_str()));
184 url_fetcher_
->SetUploadData("application/x-www-form-urlencoded", body
);
186 url_fetcher_
->Start();
189 void SupervisedUserRefreshTokenFetcherImpl::OnGetTokenFailure(
190 const OAuth2TokenService::Request
* request
,
191 const GoogleServiceAuthError
& error
) {
192 DCHECK_EQ(access_token_request_
.get(), request
);
193 callback_
.Run(error
, std::string());
197 void SupervisedUserRefreshTokenFetcherImpl::OnURLFetchComplete(
198 const URLFetcher
* source
) {
199 const net::URLRequestStatus
& status
= source
->GetStatus();
200 if (!status
.is_success()) {
201 DispatchNetworkError(status
.error());
205 int response_code
= source
->GetResponseCode();
206 if (response_code
== net::HTTP_UNAUTHORIZED
&& !access_token_expired_
) {
207 access_token_expired_
= true;
208 oauth2_token_service_
->InvalidateAccessToken(
209 account_id_
, OAuth2TokenService::ScopeSet(), access_token_
);
214 if (response_code
!= net::HTTP_OK
) {
215 // TODO(bauerb): We should return the HTTP response code somehow.
216 DLOG(WARNING
) << "HTTP error " << response_code
;
217 DispatchGoogleServiceAuthError(
218 GoogleServiceAuthError(GoogleServiceAuthError::CONNECTION_FAILED
),
223 std::string response_body
;
224 source
->GetResponseAsString(&response_body
);
225 scoped_ptr
<base::Value
> value
= base::JSONReader::Read(response_body
);
226 base::DictionaryValue
* dict
= NULL
;
227 if (!value
.get() || !value
->GetAsDictionary(&dict
)) {
228 DispatchNetworkError(net::ERR_INVALID_RESPONSE
);
231 std::string auth_code
;
232 if (!dict
->GetString(kCodeKey
, &auth_code
)) {
233 DispatchNetworkError(net::ERR_INVALID_RESPONSE
);
237 gaia::OAuthClientInfo client_info
;
238 GaiaUrls
* urls
= GaiaUrls::GetInstance();
239 client_info
.client_id
= urls
->oauth2_chrome_client_id();
240 client_info
.client_secret
= urls
->oauth2_chrome_client_secret();
241 gaia_oauth_client_
.reset(new gaia::GaiaOAuthClient(context_
));
242 gaia_oauth_client_
->GetTokensFromAuthCode(client_info
, auth_code
, kNumRetries
,
246 void SupervisedUserRefreshTokenFetcherImpl::OnGetTokensResponse(
247 const std::string
& refresh_token
,
248 const std::string
& access_token
,
249 int expires_in_seconds
) {
250 // TODO(bauerb): It would be nice if we could pass the access token as well,
251 // so we don't need to fetch another one immediately.
252 DispatchGoogleServiceAuthError(GoogleServiceAuthError::AuthErrorNone(),
256 void SupervisedUserRefreshTokenFetcherImpl::OnRefreshTokenResponse(
257 const std::string
& access_token
,
258 int expires_in_seconds
) {
262 void SupervisedUserRefreshTokenFetcherImpl::OnOAuthError() {
263 DispatchGoogleServiceAuthError(
264 GoogleServiceAuthError(GoogleServiceAuthError::CONNECTION_FAILED
),
268 void SupervisedUserRefreshTokenFetcherImpl::OnNetworkError(int response_code
) {
269 // TODO(bauerb): We should return the HTTP response code somehow.
270 DLOG(WARNING
) << "HTTP error " << response_code
;
271 DispatchGoogleServiceAuthError(
272 GoogleServiceAuthError(GoogleServiceAuthError::CONNECTION_FAILED
),
276 void SupervisedUserRefreshTokenFetcherImpl::DispatchNetworkError(
278 DispatchGoogleServiceAuthError(
279 GoogleServiceAuthError::FromConnectionError(error_code
), std::string());
282 void SupervisedUserRefreshTokenFetcherImpl::DispatchGoogleServiceAuthError(
283 const GoogleServiceAuthError
& error
,
284 const std::string
& token
) {
285 callback_
.Run(error
, token
);
292 scoped_ptr
<SupervisedUserRefreshTokenFetcher
>
293 SupervisedUserRefreshTokenFetcher::Create(
294 OAuth2TokenService
* oauth2_token_service
,
295 const std::string
& account_id
,
296 const std::string
& device_id
,
297 URLRequestContextGetter
* context
) {
298 scoped_ptr
<SupervisedUserRefreshTokenFetcher
> fetcher(
299 new SupervisedUserRefreshTokenFetcherImpl(oauth2_token_service
,
303 return fetcher
.Pass();
306 SupervisedUserRefreshTokenFetcher::~SupervisedUserRefreshTokenFetcher() {}