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
16 namespace {
17 const char kAccessTokenValue[] = "access_token";
18 const char kRefreshTokenValue[] = "refresh_token";
19 const char kExpiresInValue[] = "expires_in";
20 }
21
22 namespace gaia {
23
24 class GaiaOAuthClient::Core
25 : public base::RefCountedThreadSafe<GaiaOAuthClient::Core>,
26 public URLFetcher::Delegate {
27 public:
Core(const std::string & gaia_url,net::URLRequestContextGetter * request_context_getter)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
~Core()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
GetTokensFromAuthCode(const OAuthClientInfo & oauth_client_info,const std::string & auth_code,int max_retries,GaiaOAuthClient::Delegate * delegate)73 void 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
RefreshToken(const OAuthClientInfo & oauth_client_info,const std::string & refresh_token,int max_retries,GaiaOAuthClient::Delegate * delegate)87 void 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
MakeGaiaRequest(std::string post_body,int max_retries,GaiaOAuthClient::Delegate * delegate)101 void 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.
OnURLFetchComplete(const URLFetcher * source,const GURL & url,const net::URLRequestStatus & status,int response_code,const ResponseCookies & cookies,const std::string & data)116 void 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
HandleResponse(const URLFetcher * source,const GURL & url,const net::URLRequestStatus & status,int response_code,const std::string & data,bool * should_retry_request)139 void 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
GaiaOAuthClient(const std::string & gaia_url,net::URLRequestContextGetter * context_getter)188 GaiaOAuthClient::GaiaOAuthClient(const std::string& gaia_url,
189 net::URLRequestContextGetter* context_getter) {
190 core_ = new Core(gaia_url, context_getter);
191 }
192
~GaiaOAuthClient()193 GaiaOAuthClient::~GaiaOAuthClient() {
194 }
195
196
GetTokensFromAuthCode(const OAuthClientInfo & oauth_client_info,const std::string & auth_code,int max_retries,Delegate * delegate)197 void 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
RefreshToken(const OAuthClientInfo & oauth_client_info,const std::string & refresh_token,int max_retries,Delegate * delegate)208 void 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