1// Copyright (c) 2012 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 "google_apis/gaia/gaia_oauth_client.h"
6
7#include "base/json/json_reader.h"
8#include "base/logging.h"
9#include "base/memory/scoped_ptr.h"
10#include "base/strings/string_util.h"
11#include "base/values.h"
12#include "google_apis/gaia/gaia_urls.h"
13#include "net/base/escape.h"
14#include "net/base/load_flags.h"
15#include "net/http/http_status_code.h"
16#include "net/url_request/url_fetcher.h"
17#include "net/url_request/url_fetcher_delegate.h"
18#include "net/url_request/url_request_context_getter.h"
19#include "url/gurl.h"
20
21namespace {
22const char kAccessTokenValue[] = "access_token";
23const char kRefreshTokenValue[] = "refresh_token";
24const char kExpiresInValue[] = "expires_in";
25}
26
27namespace gaia {
28
29// Use a non-zero number, so unit tests can differentiate the URLFetcher used by
30// this class from other fetchers (most other code just hardcodes the ID to 0).
31const int GaiaOAuthClient::kUrlFetcherId = 17109006;
32
33class GaiaOAuthClient::Core
34    : public base::RefCountedThreadSafe<GaiaOAuthClient::Core>,
35      public net::URLFetcherDelegate {
36 public:
37  Core(net::URLRequestContextGetter* request_context_getter)
38      : num_retries_(0),
39        request_context_getter_(request_context_getter),
40        delegate_(NULL),
41        request_type_(NO_PENDING_REQUEST) {
42  }
43
44  void GetTokensFromAuthCode(const OAuthClientInfo& oauth_client_info,
45                             const std::string& auth_code,
46                             int max_retries,
47                             GaiaOAuthClient::Delegate* delegate);
48  void RefreshToken(const OAuthClientInfo& oauth_client_info,
49                    const std::string& refresh_token,
50                    const std::vector<std::string>& scopes,
51                    int max_retries,
52                    GaiaOAuthClient::Delegate* delegate);
53  void GetUserEmail(const std::string& oauth_access_token,
54                    int max_retries,
55                    Delegate* delegate);
56  void GetUserId(const std::string& oauth_access_token,
57                 int max_retries,
58                 Delegate* delegate);
59  void GetUserInfo(const std::string& oauth_access_token,
60                   int max_retries,
61                   Delegate* delegate);
62  void GetTokenInfo(const std::string& oauth_access_token,
63                    int max_retries,
64                    Delegate* delegate);
65
66  // net::URLFetcherDelegate implementation.
67  virtual void OnURLFetchComplete(const net::URLFetcher* source) OVERRIDE;
68
69 private:
70  friend class base::RefCountedThreadSafe<Core>;
71
72  enum RequestType {
73    NO_PENDING_REQUEST,
74    TOKENS_FROM_AUTH_CODE,
75    REFRESH_TOKEN,
76    TOKEN_INFO,
77    USER_EMAIL,
78    USER_ID,
79    USER_INFO,
80  };
81
82  virtual ~Core() {}
83
84  void GetUserInfoImpl(RequestType type,
85                       const std::string& oauth_access_token,
86                       int max_retries,
87                       Delegate* delegate);
88  void MakeGaiaRequest(const GURL& url,
89                       const std::string& post_body,
90                       int max_retries,
91                       GaiaOAuthClient::Delegate* delegate);
92  void HandleResponse(const net::URLFetcher* source,
93                      bool* should_retry_request);
94
95  int num_retries_;
96  scoped_refptr<net::URLRequestContextGetter> request_context_getter_;
97  GaiaOAuthClient::Delegate* delegate_;
98  scoped_ptr<net::URLFetcher> request_;
99  RequestType request_type_;
100};
101
102void GaiaOAuthClient::Core::GetTokensFromAuthCode(
103    const OAuthClientInfo& oauth_client_info,
104    const std::string& auth_code,
105    int max_retries,
106    GaiaOAuthClient::Delegate* delegate) {
107  DCHECK_EQ(request_type_, NO_PENDING_REQUEST);
108  request_type_ = TOKENS_FROM_AUTH_CODE;
109  std::string post_body =
110      "code=" + net::EscapeUrlEncodedData(auth_code, true) +
111      "&client_id=" + net::EscapeUrlEncodedData(oauth_client_info.client_id,
112                                                true) +
113      "&client_secret=" +
114      net::EscapeUrlEncodedData(oauth_client_info.client_secret, true) +
115      "&redirect_uri=" +
116      net::EscapeUrlEncodedData(oauth_client_info.redirect_uri, true) +
117      "&grant_type=authorization_code";
118  MakeGaiaRequest(GURL(GaiaUrls::GetInstance()->oauth2_token_url()),
119                  post_body, max_retries, delegate);
120}
121
122void GaiaOAuthClient::Core::RefreshToken(
123    const OAuthClientInfo& oauth_client_info,
124    const std::string& refresh_token,
125    const std::vector<std::string>& scopes,
126    int max_retries,
127    GaiaOAuthClient::Delegate* delegate) {
128  DCHECK_EQ(request_type_, NO_PENDING_REQUEST);
129  request_type_ = REFRESH_TOKEN;
130  std::string post_body =
131      "refresh_token=" + net::EscapeUrlEncodedData(refresh_token, true) +
132      "&client_id=" + net::EscapeUrlEncodedData(oauth_client_info.client_id,
133                                                true) +
134      "&client_secret=" +
135      net::EscapeUrlEncodedData(oauth_client_info.client_secret, true) +
136      "&grant_type=refresh_token";
137
138  if (!scopes.empty()) {
139    std::string scopes_string = JoinString(scopes, ' ');
140    post_body += "&scope=" + net::EscapeUrlEncodedData(scopes_string, true);
141  }
142
143  MakeGaiaRequest(GURL(GaiaUrls::GetInstance()->oauth2_token_url()),
144                  post_body, max_retries, delegate);
145}
146
147void GaiaOAuthClient::Core::GetUserEmail(const std::string& oauth_access_token,
148                                         int max_retries,
149                                         Delegate* delegate) {
150  GetUserInfoImpl(USER_EMAIL, oauth_access_token, max_retries, delegate);
151}
152
153void GaiaOAuthClient::Core::GetUserId(const std::string& oauth_access_token,
154                                      int max_retries,
155                                      Delegate* delegate) {
156  GetUserInfoImpl(USER_ID, oauth_access_token, max_retries, delegate);
157}
158
159void GaiaOAuthClient::Core::GetUserInfo(const std::string& oauth_access_token,
160                                      int max_retries,
161                                      Delegate* delegate) {
162  GetUserInfoImpl(USER_INFO, oauth_access_token, max_retries, delegate);
163}
164
165void GaiaOAuthClient::Core::GetUserInfoImpl(
166    RequestType type,
167    const std::string& oauth_access_token,
168    int max_retries,
169    Delegate* delegate) {
170  DCHECK_EQ(request_type_, NO_PENDING_REQUEST);
171  DCHECK(!request_.get());
172  request_type_ = type;
173  delegate_ = delegate;
174  num_retries_ = 0;
175  request_.reset(net::URLFetcher::Create(
176      kUrlFetcherId, GURL(GaiaUrls::GetInstance()->oauth_user_info_url()),
177      net::URLFetcher::GET, this));
178  request_->SetRequestContext(request_context_getter_.get());
179  request_->AddExtraRequestHeader("Authorization: OAuth " + oauth_access_token);
180  request_->SetMaxRetriesOn5xx(max_retries);
181  request_->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES |
182                         net::LOAD_DO_NOT_SAVE_COOKIES);
183
184  // Fetchers are sometimes cancelled because a network change was detected,
185  // especially at startup and after sign-in on ChromeOS. Retrying once should
186  // be enough in those cases; let the fetcher retry up to 3 times just in case.
187  // http://crbug.com/163710
188  request_->SetAutomaticallyRetryOnNetworkChanges(3);
189  request_->Start();
190}
191
192void GaiaOAuthClient::Core::GetTokenInfo(const std::string& oauth_access_token,
193                                         int max_retries,
194                                         Delegate* delegate) {
195  DCHECK_EQ(request_type_, NO_PENDING_REQUEST);
196  DCHECK(!request_.get());
197  request_type_ = TOKEN_INFO;
198  std::string post_body =
199      "access_token=" + net::EscapeUrlEncodedData(oauth_access_token, true);
200  MakeGaiaRequest(GURL(GaiaUrls::GetInstance()->oauth2_token_info_url()),
201                  post_body,
202                  max_retries,
203                  delegate);
204}
205
206void GaiaOAuthClient::Core::MakeGaiaRequest(
207    const GURL& url,
208    const std::string& post_body,
209    int max_retries,
210    GaiaOAuthClient::Delegate* delegate) {
211  DCHECK(!request_.get()) << "Tried to fetch two things at once!";
212  delegate_ = delegate;
213  num_retries_ = 0;
214  request_.reset(net::URLFetcher::Create(
215      kUrlFetcherId, url, net::URLFetcher::POST, this));
216  request_->SetRequestContext(request_context_getter_.get());
217  request_->SetUploadData("application/x-www-form-urlencoded", post_body);
218  request_->SetMaxRetriesOn5xx(max_retries);
219  request_->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES |
220                         net::LOAD_DO_NOT_SAVE_COOKIES);
221  // See comment on SetAutomaticallyRetryOnNetworkChanges() above.
222  request_->SetAutomaticallyRetryOnNetworkChanges(3);
223  request_->Start();
224}
225
226// URLFetcher::Delegate implementation.
227void GaiaOAuthClient::Core::OnURLFetchComplete(
228    const net::URLFetcher* source) {
229  bool should_retry = false;
230  HandleResponse(source, &should_retry);
231  if (should_retry) {
232    // Explicitly call ReceivedContentWasMalformed() to ensure the current
233    // request gets counted as a failure for calculation of the back-off
234    // period.  If it was already a failure by status code, this call will
235    // be ignored.
236    request_->ReceivedContentWasMalformed();
237    num_retries_++;
238    // We must set our request_context_getter_ again because
239    // URLFetcher::Core::RetryOrCompleteUrlFetch resets it to NULL...
240    request_->SetRequestContext(request_context_getter_.get());
241    request_->Start();
242  }
243}
244
245void GaiaOAuthClient::Core::HandleResponse(
246    const net::URLFetcher* source,
247    bool* should_retry_request) {
248  // Move ownership of the request fetcher into a local scoped_ptr which
249  // will be nuked when we're done handling the request, unless we need
250  // to retry, in which case ownership will be returned to request_.
251  scoped_ptr<net::URLFetcher> old_request = request_.Pass();
252  DCHECK_EQ(source, old_request.get());
253
254  // HTTP_BAD_REQUEST means the arguments are invalid.  HTTP_UNAUTHORIZED means
255  // the access or refresh token is invalid. No point retrying. We are
256  // done here.
257  int response_code = source->GetResponseCode();
258  if (response_code == net::HTTP_BAD_REQUEST ||
259      response_code == net::HTTP_UNAUTHORIZED) {
260    delegate_->OnOAuthError();
261    return;
262  }
263
264  scoped_ptr<base::DictionaryValue> response_dict;
265  if (source->GetResponseCode() == net::HTTP_OK) {
266    std::string data;
267    source->GetResponseAsString(&data);
268    scoped_ptr<base::Value> message_value(base::JSONReader::Read(data));
269    if (message_value.get() &&
270        message_value->IsType(base::Value::TYPE_DICTIONARY)) {
271      response_dict.reset(
272          static_cast<base::DictionaryValue*>(message_value.release()));
273    }
274  }
275
276  if (!response_dict.get()) {
277    // If we don't have an access token yet and the the error was not
278    // RC_BAD_REQUEST, we may need to retry.
279    if ((source->GetMaxRetriesOn5xx() != -1) &&
280        (num_retries_ >= source->GetMaxRetriesOn5xx())) {
281      // Retry limit reached. Give up.
282      delegate_->OnNetworkError(source->GetResponseCode());
283    } else {
284      request_ = old_request.Pass();
285      *should_retry_request = true;
286    }
287    return;
288  }
289
290  RequestType type = request_type_;
291  request_type_ = NO_PENDING_REQUEST;
292
293  switch (type) {
294    case USER_EMAIL: {
295      std::string email;
296      response_dict->GetString("email", &email);
297      delegate_->OnGetUserEmailResponse(email);
298      break;
299    }
300
301    case USER_ID: {
302      std::string id;
303      response_dict->GetString("id", &id);
304      delegate_->OnGetUserIdResponse(id);
305      break;
306    }
307
308    case USER_INFO: {
309      delegate_->OnGetUserInfoResponse(response_dict.Pass());
310      break;
311    }
312
313    case TOKEN_INFO: {
314      delegate_->OnGetTokenInfoResponse(response_dict.Pass());
315      break;
316    }
317
318    case TOKENS_FROM_AUTH_CODE:
319    case REFRESH_TOKEN: {
320      std::string access_token;
321      std::string refresh_token;
322      int expires_in_seconds = 0;
323      response_dict->GetString(kAccessTokenValue, &access_token);
324      response_dict->GetString(kRefreshTokenValue, &refresh_token);
325      response_dict->GetInteger(kExpiresInValue, &expires_in_seconds);
326
327      if (access_token.empty()) {
328        delegate_->OnOAuthError();
329        return;
330      }
331
332      if (type == REFRESH_TOKEN) {
333        delegate_->OnRefreshTokenResponse(access_token, expires_in_seconds);
334      } else {
335        delegate_->OnGetTokensResponse(refresh_token,
336                                       access_token,
337                                       expires_in_seconds);
338      }
339      break;
340    }
341
342    default:
343      NOTREACHED();
344  }
345}
346
347GaiaOAuthClient::GaiaOAuthClient(net::URLRequestContextGetter* context_getter) {
348  core_ = new Core(context_getter);
349}
350
351GaiaOAuthClient::~GaiaOAuthClient() {
352}
353
354void GaiaOAuthClient::GetTokensFromAuthCode(
355    const OAuthClientInfo& oauth_client_info,
356    const std::string& auth_code,
357    int max_retries,
358    Delegate* delegate) {
359  return core_->GetTokensFromAuthCode(oauth_client_info,
360                                      auth_code,
361                                      max_retries,
362                                      delegate);
363}
364
365void GaiaOAuthClient::RefreshToken(
366    const OAuthClientInfo& oauth_client_info,
367    const std::string& refresh_token,
368    const std::vector<std::string>& scopes,
369    int max_retries,
370    Delegate* delegate) {
371  return core_->RefreshToken(oauth_client_info,
372                             refresh_token,
373                             scopes,
374                             max_retries,
375                             delegate);
376}
377
378void GaiaOAuthClient::GetUserEmail(const std::string& access_token,
379                                  int max_retries,
380                                  Delegate* delegate) {
381  return core_->GetUserEmail(access_token, max_retries, delegate);
382}
383
384void GaiaOAuthClient::GetUserId(const std::string& access_token,
385                                int max_retries,
386                                Delegate* delegate) {
387  return core_->GetUserId(access_token, max_retries, delegate);
388}
389
390void GaiaOAuthClient::GetUserInfo(const std::string& access_token,
391                                  int max_retries,
392                                  Delegate* delegate) {
393  return core_->GetUserInfo(access_token, max_retries, delegate);
394}
395
396void GaiaOAuthClient::GetTokenInfo(const std::string& access_token,
397                                   int max_retries,
398                                   Delegate* delegate) {
399  return core_->GetTokenInfo(access_token, max_retries, delegate);
400}
401
402}  // namespace gaia
403