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