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