gaia_oauth_client.cc revision 2a99a7e74a7f215066514fe81d2bfa6639d9eddd
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/values.h"
11#include "google_apis/gaia/gaia_urls.h"
12#include "googleurl/src/gurl.h"
13#include "net/base/escape.h"
14#include "net/http/http_status_code.h"
15#include "net/url_request/url_fetcher.h"
16#include "net/url_request/url_fetcher_delegate.h"
17#include "net/url_request/url_request_context_getter.h"
18
19namespace {
20const char kAccessTokenValue[] = "access_token";
21const char kRefreshTokenValue[] = "refresh_token";
22const char kExpiresInValue[] = "expires_in";
23}
24
25namespace gaia {
26
27class GaiaOAuthClient::Core
28    : public base::RefCountedThreadSafe<GaiaOAuthClient::Core>,
29      public net::URLFetcherDelegate {
30 public:
31  Core(const std::string& gaia_url,
32       net::URLRequestContextGetter* request_context_getter)
33           : gaia_url_(gaia_url),
34             num_retries_(0),
35             request_context_getter_(request_context_getter),
36             delegate_(NULL),
37             request_type_(NO_PENDING_REQUEST) {
38  }
39
40  void GetTokensFromAuthCode(const OAuthClientInfo& oauth_client_info,
41                             const std::string& auth_code,
42                             int max_retries,
43                             GaiaOAuthClient::Delegate* delegate);
44  void RefreshToken(const OAuthClientInfo& oauth_client_info,
45                    const std::string& refresh_token,
46                    int max_retries,
47                    GaiaOAuthClient::Delegate* delegate);
48  void GetUserInfo(const std::string& oauth_access_token,
49                    int max_retries,
50                    Delegate* delegate);
51
52  // net::URLFetcherDelegate implementation.
53  virtual void OnURLFetchComplete(const net::URLFetcher* source) OVERRIDE;
54
55 private:
56  friend class base::RefCountedThreadSafe<Core>;
57
58  enum RequestType {
59    NO_PENDING_REQUEST,
60    TOKENS_FROM_AUTH_CODE,
61    REFRESH_TOKEN,
62    USER_INFO,
63  };
64
65  virtual ~Core() {}
66
67  void MakeGaiaRequest(const std::string& post_body,
68                       int max_retries,
69                       GaiaOAuthClient::Delegate* delegate);
70  void HandleResponse(const net::URLFetcher* source,
71                      bool* should_retry_request);
72
73  GURL gaia_url_;
74  int num_retries_;
75  scoped_refptr<net::URLRequestContextGetter> request_context_getter_;
76  GaiaOAuthClient::Delegate* delegate_;
77  scoped_ptr<net::URLFetcher> request_;
78  RequestType request_type_;
79};
80
81void GaiaOAuthClient::Core::GetTokensFromAuthCode(
82    const OAuthClientInfo& oauth_client_info,
83    const std::string& auth_code,
84    int max_retries,
85    GaiaOAuthClient::Delegate* delegate) {
86  DCHECK_EQ(request_type_, NO_PENDING_REQUEST);
87  request_type_ = TOKENS_FROM_AUTH_CODE;
88  std::string post_body =
89      "code=" + net::EscapeUrlEncodedData(auth_code, true) +
90      "&client_id=" + net::EscapeUrlEncodedData(oauth_client_info.client_id,
91                                                true) +
92      "&client_secret=" +
93      net::EscapeUrlEncodedData(oauth_client_info.client_secret, true) +
94      "&redirect_uri=" +
95      net::EscapeUrlEncodedData(oauth_client_info.redirect_uri, true) +
96      "&grant_type=authorization_code";
97  MakeGaiaRequest(post_body, max_retries, delegate);
98}
99
100void GaiaOAuthClient::Core::RefreshToken(
101    const OAuthClientInfo& oauth_client_info,
102    const std::string& refresh_token,
103    int max_retries,
104    GaiaOAuthClient::Delegate* delegate) {
105  DCHECK_EQ(request_type_, NO_PENDING_REQUEST);
106  request_type_ = REFRESH_TOKEN;
107  std::string post_body =
108      "refresh_token=" + net::EscapeUrlEncodedData(refresh_token, true) +
109      "&client_id=" + net::EscapeUrlEncodedData(oauth_client_info.client_id,
110                                                true) +
111      "&client_secret=" +
112      net::EscapeUrlEncodedData(oauth_client_info.client_secret, true) +
113      "&grant_type=refresh_token";
114  MakeGaiaRequest(post_body, max_retries, delegate);
115}
116
117void GaiaOAuthClient::Core::GetUserInfo(const std::string& oauth_access_token,
118                                        int max_retries,
119                                        Delegate* delegate) {
120  DCHECK_EQ(request_type_, NO_PENDING_REQUEST);
121  DCHECK(!request_.get());
122  request_type_ = USER_INFO;
123  delegate_ = delegate;
124  num_retries_ = 0;
125  request_.reset(net::URLFetcher::Create(
126      0, GURL(GaiaUrls::GetInstance()->oauth_user_info_url()),
127      net::URLFetcher::GET, this));
128  request_->SetRequestContext(request_context_getter_);
129  request_->AddExtraRequestHeader(
130      "Authorization: OAuth " + oauth_access_token);
131  request_->SetMaxRetriesOn5xx(max_retries);
132  // Fetchers are sometimes cancelled because a network change was detected,
133  // especially at startup and after sign-in on ChromeOS. Retrying once should
134  // be enough in those cases; let the fetcher retry up to 3 times just in case.
135  // http://crbug.com/163710
136  request_->SetAutomaticallyRetryOnNetworkChanges(3);
137  request_->Start();
138}
139
140void GaiaOAuthClient::Core::MakeGaiaRequest(
141    const std::string& post_body,
142    int max_retries,
143    GaiaOAuthClient::Delegate* delegate) {
144  DCHECK(!request_.get()) << "Tried to fetch two things at once!";
145  delegate_ = delegate;
146  num_retries_ = 0;
147  request_.reset(net::URLFetcher::Create(
148      0, gaia_url_, net::URLFetcher::POST, this));
149  request_->SetRequestContext(request_context_getter_);
150  request_->SetUploadData("application/x-www-form-urlencoded", post_body);
151  request_->SetMaxRetriesOn5xx(max_retries);
152  // See comment on SetAutomaticallyRetryOnNetworkChanges() above.
153  request_->SetAutomaticallyRetryOnNetworkChanges(3);
154  request_->Start();
155}
156
157// URLFetcher::Delegate implementation.
158void GaiaOAuthClient::Core::OnURLFetchComplete(
159    const net::URLFetcher* source) {
160  bool should_retry = false;
161  HandleResponse(source, &should_retry);
162  if (should_retry) {
163    // Explicitly call ReceivedContentWasMalformed() to ensure the current
164    // request gets counted as a failure for calculation of the back-off
165    // period.  If it was already a failure by status code, this call will
166    // be ignored.
167    request_->ReceivedContentWasMalformed();
168    num_retries_++;
169    // We must set our request_context_getter_ again because
170    // URLFetcher::Core::RetryOrCompleteUrlFetch resets it to NULL...
171    request_->SetRequestContext(request_context_getter_);
172    request_->Start();
173  }
174}
175
176void GaiaOAuthClient::Core::HandleResponse(
177    const net::URLFetcher* source,
178    bool* should_retry_request) {
179  // Keep the URLFetcher object in case we need to reuse it.
180  scoped_ptr<net::URLFetcher> old_request = request_.Pass();
181  DCHECK_EQ(source, old_request.get());
182
183  // RC_BAD_REQUEST means the arguments are invalid. No point retrying. We are
184  // done here.
185  if (source->GetResponseCode() == net::HTTP_BAD_REQUEST) {
186    delegate_->OnOAuthError();
187    return;
188  }
189
190  scoped_ptr<DictionaryValue> response_dict;
191  if (source->GetResponseCode() == net::HTTP_OK) {
192    std::string data;
193    source->GetResponseAsString(&data);
194    scoped_ptr<Value> message_value(base::JSONReader::Read(data));
195    if (message_value.get() &&
196        message_value->IsType(Value::TYPE_DICTIONARY)) {
197      response_dict.reset(
198          static_cast<DictionaryValue*>(message_value.release()));
199    }
200  }
201
202  if (!response_dict.get()) {
203    // If we don't have an access token yet and the the error was not
204    // RC_BAD_REQUEST, we may need to retry.
205    if ((source->GetMaxRetriesOn5xx() != -1) &&
206        (num_retries_ > source->GetMaxRetriesOn5xx())) {
207      // Retry limit reached. Give up.
208      delegate_->OnNetworkError(source->GetResponseCode());
209    } else {
210      request_ = old_request.Pass();
211      *should_retry_request = true;
212    }
213    return;
214  }
215
216  RequestType type = request_type_;
217  request_type_ = NO_PENDING_REQUEST;
218
219  switch (type) {
220    case USER_INFO: {
221      std::string email;
222      response_dict->GetString("email", &email);
223      delegate_->OnGetUserInfoResponse(email);
224      break;
225    }
226
227    case TOKENS_FROM_AUTH_CODE:
228    case REFRESH_TOKEN: {
229      std::string access_token;
230      std::string refresh_token;
231      int expires_in_seconds = 0;
232      response_dict->GetString(kAccessTokenValue, &access_token);
233      response_dict->GetString(kRefreshTokenValue, &refresh_token);
234      response_dict->GetInteger(kExpiresInValue, &expires_in_seconds);
235
236      if (access_token.empty()) {
237        delegate_->OnOAuthError();
238        return;
239      }
240
241      if (type == REFRESH_TOKEN) {
242        delegate_->OnRefreshTokenResponse(access_token, expires_in_seconds);
243      } else {
244        delegate_->OnGetTokensResponse(refresh_token,
245                                       access_token,
246                                       expires_in_seconds);
247      }
248      break;
249    }
250
251    default:
252      NOTREACHED();
253  }
254}
255
256GaiaOAuthClient::GaiaOAuthClient(const std::string& gaia_url,
257                                 net::URLRequestContextGetter* context_getter) {
258  core_ = new Core(gaia_url, context_getter);
259}
260
261GaiaOAuthClient::~GaiaOAuthClient() {
262}
263
264void GaiaOAuthClient::GetTokensFromAuthCode(
265    const OAuthClientInfo& oauth_client_info,
266    const std::string& auth_code,
267    int max_retries,
268    Delegate* delegate) {
269  return core_->GetTokensFromAuthCode(oauth_client_info,
270                                      auth_code,
271                                      max_retries,
272                                      delegate);
273}
274
275void GaiaOAuthClient::RefreshToken(const OAuthClientInfo& oauth_client_info,
276                                   const std::string& refresh_token,
277                                   int max_retries,
278                                   Delegate* delegate) {
279  return core_->RefreshToken(oauth_client_info,
280                             refresh_token,
281                             max_retries,
282                             delegate);
283}
284
285void GaiaOAuthClient::GetUserInfo(const std::string& access_token,
286                                  int max_retries,
287                                  Delegate* delegate) {
288  return core_->GetUserInfo(access_token, max_retries, delegate);
289}
290
291}  // namespace gaia
292