1// Copyright (c) 2011 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/common/net/gaia/gaia_oauth_client.h"
6
7#include "base/json/json_reader.h"
8#include "base/memory/scoped_ptr.h"
9#include "base/values.h"
10#include "chrome/common/net/http_return.h"
11#include "chrome/common/net/url_fetcher.h"
12#include "googleurl/src/gurl.h"
13#include "net/base/escape.h"
14#include "net/url_request/url_request_context_getter.h"
15
16namespace {
17const char kAccessTokenValue[] = "access_token";
18const char kRefreshTokenValue[] = "refresh_token";
19const char kExpiresInValue[] = "expires_in";
20}
21
22namespace gaia {
23
24class GaiaOAuthClient::Core
25    : public base::RefCountedThreadSafe<GaiaOAuthClient::Core>,
26      public URLFetcher::Delegate {
27 public:
28  Core(const std::string& gaia_url,
29       net::URLRequestContextGetter* request_context_getter)
30           : gaia_url_(gaia_url),
31             num_retries_(0),
32             request_context_getter_(request_context_getter),
33             delegate_(NULL) {}
34
35  virtual ~Core() { }
36
37  void GetTokensFromAuthCode(const OAuthClientInfo& oauth_client_info,
38                             const std::string& auth_code,
39                             int max_retries,
40                             GaiaOAuthClient::Delegate* delegate);
41  void RefreshToken(const OAuthClientInfo& oauth_client_info,
42                    const std::string& refresh_token,
43                    int max_retries,
44                    GaiaOAuthClient::Delegate* delegate);
45
46  // URLFetcher::Delegate implementation.
47  virtual void OnURLFetchComplete(
48    const URLFetcher* source,
49    const GURL& url,
50    const net::URLRequestStatus& status,
51    int response_code,
52    const ResponseCookies& cookies,
53    const std::string& data);
54
55 private:
56  void MakeGaiaRequest(std::string post_body,
57                       int max_retries,
58                       GaiaOAuthClient::Delegate* delegate);
59  void HandleResponse(const URLFetcher* source,
60                      const GURL& url,
61                      const net::URLRequestStatus& status,
62                      int response_code,
63                      const std::string& data,
64                      bool* should_retry_request);
65
66  GURL gaia_url_;
67  int num_retries_;
68  scoped_refptr<net::URLRequestContextGetter> request_context_getter_;
69  GaiaOAuthClient::Delegate* delegate_;
70  scoped_ptr<URLFetcher> request_;
71};
72
73void GaiaOAuthClient::Core::GetTokensFromAuthCode(
74    const OAuthClientInfo& oauth_client_info,
75    const std::string& auth_code,
76    int max_retries,
77    GaiaOAuthClient::Delegate* delegate) {
78  std::string post_body =
79      "code=" + EscapeUrlEncodedData(auth_code) +
80      "&client_id=" + EscapeUrlEncodedData(oauth_client_info.client_id) +
81      "&client_secret=" +
82      EscapeUrlEncodedData(oauth_client_info.client_secret) +
83      "&redirect_uri=oob&grant_type=authorization_code";
84  MakeGaiaRequest(post_body, max_retries, delegate);
85}
86
87void GaiaOAuthClient::Core::RefreshToken(
88    const OAuthClientInfo& oauth_client_info,
89    const std::string& refresh_token,
90    int max_retries,
91    GaiaOAuthClient::Delegate* delegate) {
92  std::string post_body =
93      "refresh_token=" + EscapeUrlEncodedData(refresh_token) +
94      "&client_id=" + EscapeUrlEncodedData(oauth_client_info.client_id) +
95      "&client_secret=" +
96      EscapeUrlEncodedData(oauth_client_info.client_secret) +
97      "&grant_type=refresh_token";
98  MakeGaiaRequest(post_body, max_retries, delegate);
99}
100
101void GaiaOAuthClient::Core::MakeGaiaRequest(
102    std::string post_body,
103    int max_retries,
104    GaiaOAuthClient::Delegate* delegate) {
105  DCHECK(!request_.get()) << "Tried to fetch two things at once!";
106  delegate_ = delegate;
107  num_retries_ = 0;
108  request_.reset(URLFetcher::Create(0, gaia_url_, URLFetcher::POST, this));
109  request_->set_request_context(request_context_getter_);
110  request_->set_upload_data("application/x-www-form-urlencoded", post_body);
111  request_->set_max_retries(max_retries);
112  request_->Start();
113}
114
115// URLFetcher::Delegate implementation.
116void GaiaOAuthClient::Core::OnURLFetchComplete(
117    const URLFetcher* source,
118    const GURL& url,
119    const net::URLRequestStatus& status,
120    int response_code,
121    const ResponseCookies& cookies,
122    const std::string& data) {
123  bool should_retry = false;
124  HandleResponse(source, url, status, response_code, data, &should_retry);
125  if (should_retry) {
126    // If the response code is greater than or equal to 500, then the back-off
127    // period has been increased at the network level; otherwise, explicitly
128    // call ReceivedContentWasMalformed() to count the current request as a
129    // failure and increase the back-off period.
130    if (response_code < 500)
131      request_->ReceivedContentWasMalformed();
132    num_retries_++;
133    request_->Start();
134  } else {
135    request_.reset();
136  }
137}
138
139void GaiaOAuthClient::Core::HandleResponse(
140    const URLFetcher* source,
141    const GURL& url,
142    const net::URLRequestStatus& status,
143    int response_code,
144    const std::string& data,
145    bool* should_retry_request) {
146  *should_retry_request = false;
147  // RC_BAD_REQUEST means the arguments are invalid. No point retrying. We are
148  // done here.
149  if (response_code == RC_BAD_REQUEST) {
150    delegate_->OnOAuthError();
151    return;
152  }
153  std::string access_token;
154  std::string refresh_token;
155  int expires_in_seconds = 0;
156  if (response_code == RC_REQUEST_OK) {
157    scoped_ptr<Value> message_value(
158        base::JSONReader::Read(data, false));
159    if (message_value.get() &&
160        message_value->IsType(Value::TYPE_DICTIONARY)) {
161      scoped_ptr<DictionaryValue> response_dict(
162          static_cast<DictionaryValue*>(message_value.release()));
163      response_dict->GetString(kAccessTokenValue, &access_token);
164      response_dict->GetString(kRefreshTokenValue, &refresh_token);
165      response_dict->GetInteger(kExpiresInValue, &expires_in_seconds);
166    }
167  }
168  if (access_token.empty()) {
169    // If we don't have an access token yet and the the error was not
170    // RC_BAD_REQUEST, we may need to retry.
171    if ((-1 != source->max_retries()) &&
172        (num_retries_ > source->max_retries())) {
173      // Retry limit reached. Give up.
174      delegate_->OnNetworkError(response_code);
175    } else {
176      *should_retry_request = true;
177    }
178  } else if (refresh_token.empty()) {
179    // If we only have an access token, then this was a refresh request.
180    delegate_->OnRefreshTokenResponse(access_token, expires_in_seconds);
181  } else {
182    delegate_->OnGetTokensResponse(refresh_token,
183                                   access_token,
184                                   expires_in_seconds);
185  }
186}
187
188GaiaOAuthClient::GaiaOAuthClient(const std::string& gaia_url,
189                                 net::URLRequestContextGetter* context_getter) {
190  core_ = new Core(gaia_url, context_getter);
191}
192
193GaiaOAuthClient::~GaiaOAuthClient() {
194}
195
196
197void GaiaOAuthClient::GetTokensFromAuthCode(
198    const OAuthClientInfo& oauth_client_info,
199    const std::string& auth_code,
200    int max_retries,
201    Delegate* delegate) {
202  return core_->GetTokensFromAuthCode(oauth_client_info,
203                                      auth_code,
204                                      max_retries,
205                                      delegate);
206}
207
208void GaiaOAuthClient::RefreshToken(const OAuthClientInfo& oauth_client_info,
209                                   const std::string& refresh_token,
210                                   int max_retries,
211                                   Delegate* delegate) {
212  return core_->RefreshToken(oauth_client_info,
213                             refresh_token,
214                             max_retries,
215                             delegate);
216}
217
218}  // namespace gaia
219