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/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 static const char kIssueTokenBodyFormatDeviceIdAddendum
[] = "&device_id=%s";
47 static const char kAuthorizationHeaderFormat
[] =
48 "Authorization: Bearer %s";
50 static const char kCodeKey
[] = "code";
52 class SupervisedUserRefreshTokenFetcherImpl
53 : public SupervisedUserRefreshTokenFetcher
,
54 public OAuth2TokenService::Consumer
,
55 public URLFetcherDelegate
,
56 public GaiaOAuthClient::Delegate
{
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();
65 // SupervisedUserRefreshTokenFetcher implementation:
66 virtual void Start(const std::string
& supervised_user_id
,
67 const std::string
& device_name
,
68 const TokenCallback
& callback
) OVERRIDE
;
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
;
78 // net::URLFetcherDelegate implementation.
79 virtual void OnURLFetchComplete(const URLFetcher
* source
) OVERRIDE
;
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
;
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.
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_
;
103 std::string device_name_
;
104 std::string supervised_user_id_
;
105 TokenCallback callback_
;
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_
;
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
),
124 access_token_expired_(false) {}
126 SupervisedUserRefreshTokenFetcherImpl::
127 ~SupervisedUserRefreshTokenFetcherImpl() {}
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
;
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);
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
;
154 GURL
url(GaiaUrls::GetInstance()->oauth2_issue_token_url());
155 // GaiaOAuthClient uses id 0, so we use 1 to distinguish the requests in
159 url_fetcher_
.reset(URLFetcher::Create(id
, url
, URLFetcher::POST
, this));
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()));
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()));
180 url_fetcher_
->SetUploadData("application/x-www-form-urlencoded", body
);
182 url_fetcher_
->Start();
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());
193 void SupervisedUserRefreshTokenFetcherImpl::OnURLFetchComplete(
194 const URLFetcher
* source
) {
195 const net::URLRequestStatus
& status
= source
->GetStatus();
196 if (!status
.is_success()) {
197 DispatchNetworkError(status
.error());
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(),
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
),
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
);
228 std::string auth_code
;
229 if (!dict
->GetString(kCodeKey
, &auth_code
)) {
230 DispatchNetworkError(net::ERR_INVALID_RESPONSE
);
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
,
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(),
253 void SupervisedUserRefreshTokenFetcherImpl::OnRefreshTokenResponse(
254 const std::string
& access_token
,
255 int expires_in_seconds
) {
259 void SupervisedUserRefreshTokenFetcherImpl::OnOAuthError() {
260 DispatchGoogleServiceAuthError(
261 GoogleServiceAuthError(GoogleServiceAuthError::CONNECTION_FAILED
),
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
),
273 void SupervisedUserRefreshTokenFetcherImpl::DispatchNetworkError(
275 DispatchGoogleServiceAuthError(
276 GoogleServiceAuthError::FromConnectionError(error_code
), std::string());
279 void SupervisedUserRefreshTokenFetcherImpl::DispatchGoogleServiceAuthError(
280 const GoogleServiceAuthError
& error
,
281 const std::string
& token
) {
282 callback_
.Run(error
, token
);
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
,
300 return fetcher
.Pass();
303 SupervisedUserRefreshTokenFetcher::~SupervisedUserRefreshTokenFetcher() {}