- add sources.
[platform/framework/web/crosswalk.git] / src / google_apis / gaia / oauth2_access_token_fetcher.cc
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/oauth2_access_token_fetcher.h"
6
7 #include <algorithm>
8 #include <string>
9 #include <vector>
10
11 #include "base/json/json_reader.h"
12 #include "base/strings/string_util.h"
13 #include "base/strings/stringprintf.h"
14 #include "base/time/time.h"
15 #include "base/values.h"
16 #include "google_apis/gaia/gaia_urls.h"
17 #include "google_apis/gaia/google_service_auth_error.h"
18 #include "net/base/escape.h"
19 #include "net/base/load_flags.h"
20 #include "net/http/http_status_code.h"
21 #include "net/url_request/url_fetcher.h"
22 #include "net/url_request/url_request_context_getter.h"
23 #include "net/url_request/url_request_status.h"
24
25 using net::ResponseCookies;
26 using net::URLFetcher;
27 using net::URLFetcherDelegate;
28 using net::URLRequestContextGetter;
29 using net::URLRequestStatus;
30
31 namespace {
32 static const char kGetAccessTokenBodyFormat[] =
33     "client_id=%s&"
34     "client_secret=%s&"
35     "grant_type=refresh_token&"
36     "refresh_token=%s";
37
38 static const char kGetAccessTokenBodyWithScopeFormat[] =
39     "client_id=%s&"
40     "client_secret=%s&"
41     "grant_type=refresh_token&"
42     "refresh_token=%s&"
43     "scope=%s";
44
45 static const char kAccessTokenKey[] = "access_token";
46 static const char kExpiresInKey[] = "expires_in";
47
48 static GoogleServiceAuthError CreateAuthError(URLRequestStatus status) {
49   CHECK(!status.is_success());
50   if (status.status() == URLRequestStatus::CANCELED) {
51     return GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED);
52   } else {
53     DLOG(WARNING) << "Could not reach Google Accounts servers: errno "
54                   << status.error();
55     return GoogleServiceAuthError::FromConnectionError(status.error());
56   }
57 }
58
59 static URLFetcher* CreateFetcher(URLRequestContextGetter* getter,
60                                  const GURL& url,
61                                  const std::string& body,
62                                  URLFetcherDelegate* delegate) {
63   bool empty_body = body.empty();
64   URLFetcher* result = net::URLFetcher::Create(
65       0, url,
66       empty_body ? URLFetcher::GET : URLFetcher::POST,
67       delegate);
68
69   result->SetRequestContext(getter);
70   result->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES |
71                        net::LOAD_DO_NOT_SAVE_COOKIES);
72   // Fetchers are sometimes cancelled because a network change was detected,
73   // especially at startup and after sign-in on ChromeOS. Retrying once should
74   // be enough in those cases; let the fetcher retry up to 3 times just in case.
75   // http://crbug.com/163710
76   result->SetAutomaticallyRetryOnNetworkChanges(3);
77
78   if (!empty_body)
79     result->SetUploadData("application/x-www-form-urlencoded", body);
80
81   return result;
82 }
83 }  // namespace
84
85 OAuth2AccessTokenFetcher::OAuth2AccessTokenFetcher(
86     OAuth2AccessTokenConsumer* consumer,
87     URLRequestContextGetter* getter)
88     : consumer_(consumer),
89       getter_(getter),
90       state_(INITIAL) { }
91
92 OAuth2AccessTokenFetcher::~OAuth2AccessTokenFetcher() { }
93
94 void OAuth2AccessTokenFetcher::CancelRequest() {
95   fetcher_.reset();
96 }
97
98 void OAuth2AccessTokenFetcher::Start(const std::string& client_id,
99                                      const std::string& client_secret,
100                                      const std::string& refresh_token,
101                                      const std::vector<std::string>& scopes) {
102   client_id_ = client_id;
103   client_secret_ = client_secret;
104   refresh_token_ = refresh_token;
105   scopes_ = scopes;
106   StartGetAccessToken();
107 }
108
109 void OAuth2AccessTokenFetcher::StartGetAccessToken() {
110   CHECK_EQ(INITIAL, state_);
111   state_ = GET_ACCESS_TOKEN_STARTED;
112   fetcher_.reset(CreateFetcher(
113       getter_,
114       MakeGetAccessTokenUrl(),
115       MakeGetAccessTokenBody(
116           client_id_, client_secret_, refresh_token_, scopes_),
117       this));
118   fetcher_->Start();  // OnURLFetchComplete will be called.
119 }
120
121 void OAuth2AccessTokenFetcher::EndGetAccessToken(
122     const net::URLFetcher* source) {
123   CHECK_EQ(GET_ACCESS_TOKEN_STARTED, state_);
124   state_ = GET_ACCESS_TOKEN_DONE;
125
126   URLRequestStatus status = source->GetStatus();
127   if (!status.is_success()) {
128     OnGetTokenFailure(CreateAuthError(status));
129     return;
130   }
131
132   // HTTP_FORBIDDEN (403) is treated as temporary error, because it may be
133   // '403 Rate Limit Exeeded.'
134   if (source->GetResponseCode() == net::HTTP_FORBIDDEN) {
135     OnGetTokenFailure(GoogleServiceAuthError(
136         GoogleServiceAuthError::SERVICE_UNAVAILABLE));
137     return;
138   }
139
140   // The other errors are treated as permanent error.
141   if (source->GetResponseCode() != net::HTTP_OK) {
142     OnGetTokenFailure(GoogleServiceAuthError(
143         GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS));
144     return;
145   }
146
147   // The request was successfully fetched and it returned OK.
148   // Parse out the access token and the expiration time.
149   std::string access_token;
150   int expires_in;
151   if (!ParseGetAccessTokenResponse(source, &access_token, &expires_in)) {
152     DLOG(WARNING) << "Response doesn't match expected format";
153     OnGetTokenFailure(
154         GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE));
155     return;
156   }
157   // The token will expire in |expires_in| seconds. Take a 10% error margin to
158   // prevent reusing a token too close to its expiration date.
159   OnGetTokenSuccess(
160       access_token,
161       base::Time::Now() + base::TimeDelta::FromSeconds(9 * expires_in / 10));
162 }
163
164 void OAuth2AccessTokenFetcher::OnGetTokenSuccess(
165     const std::string& access_token,
166     const base::Time& expiration_time) {
167   consumer_->OnGetTokenSuccess(access_token, expiration_time);
168 }
169
170 void OAuth2AccessTokenFetcher::OnGetTokenFailure(
171     const GoogleServiceAuthError& error) {
172   state_ = ERROR_STATE;
173   consumer_->OnGetTokenFailure(error);
174 }
175
176 void OAuth2AccessTokenFetcher::OnURLFetchComplete(
177     const net::URLFetcher* source) {
178   CHECK(source);
179   CHECK(state_ == GET_ACCESS_TOKEN_STARTED);
180   EndGetAccessToken(source);
181 }
182
183 // static
184 GURL OAuth2AccessTokenFetcher::MakeGetAccessTokenUrl() {
185   return GaiaUrls::GetInstance()->oauth2_token_url();
186 }
187
188 // static
189 std::string OAuth2AccessTokenFetcher::MakeGetAccessTokenBody(
190     const std::string& client_id,
191     const std::string& client_secret,
192     const std::string& refresh_token,
193     const std::vector<std::string>& scopes) {
194   std::string enc_client_id = net::EscapeUrlEncodedData(client_id, true);
195   std::string enc_client_secret =
196       net::EscapeUrlEncodedData(client_secret, true);
197   std::string enc_refresh_token =
198       net::EscapeUrlEncodedData(refresh_token, true);
199   if (scopes.empty()) {
200     return base::StringPrintf(
201         kGetAccessTokenBodyFormat,
202         enc_client_id.c_str(),
203         enc_client_secret.c_str(),
204         enc_refresh_token.c_str());
205   } else {
206     std::string scopes_string = JoinString(scopes, ' ');
207     return base::StringPrintf(
208         kGetAccessTokenBodyWithScopeFormat,
209         enc_client_id.c_str(),
210         enc_client_secret.c_str(),
211         enc_refresh_token.c_str(),
212         net::EscapeUrlEncodedData(scopes_string, true).c_str());
213   }
214 }
215
216 // static
217 bool OAuth2AccessTokenFetcher::ParseGetAccessTokenResponse(
218     const net::URLFetcher* source,
219     std::string* access_token,
220     int* expires_in) {
221   CHECK(source);
222   CHECK(access_token);
223   std::string data;
224   source->GetResponseAsString(&data);
225   scoped_ptr<base::Value> value(base::JSONReader::Read(data));
226   if (!value.get() || value->GetType() != base::Value::TYPE_DICTIONARY)
227     return false;
228
229   base::DictionaryValue* dict =
230       static_cast<base::DictionaryValue*>(value.get());
231   return dict->GetString(kAccessTokenKey, access_token) &&
232       dict->GetInteger(kExpiresInKey, expires_in);
233 }