Disable firewall check. It takes signifficant time, need to be on FILE thread.
[chromium-blink-merge.git] / google_apis / gaia / fake_gaia.cc
blob4cfe6040e53e0ab68bc4dac93116348e3fa8caf1
1 // Copyright (c) 2013 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.
5 #include "google_apis/gaia/fake_gaia.h"
7 #include <vector>
9 #include "base/base_paths.h"
10 #include "base/bind.h"
11 #include "base/bind_helpers.h"
12 #include "base/file_util.h"
13 #include "base/files/file_path.h"
14 #include "base/json/json_writer.h"
15 #include "base/logging.h"
16 #include "base/memory/linked_ptr.h"
17 #include "base/path_service.h"
18 #include "base/strings/string_number_conversions.h"
19 #include "base/strings/string_split.h"
20 #include "base/strings/string_util.h"
21 #include "base/strings/stringprintf.h"
22 #include "base/values.h"
23 #include "google_apis/gaia/gaia_constants.h"
24 #include "google_apis/gaia/gaia_urls.h"
25 #include "net/base/url_util.h"
26 #include "net/cookies/parsed_cookie.h"
27 #include "net/http/http_status_code.h"
28 #include "net/test/embedded_test_server/http_request.h"
29 #include "net/test/embedded_test_server/http_response.h"
30 #include "url/url_parse.h"
32 #define REGISTER_RESPONSE_HANDLER(url, method) \
33 request_handlers_.insert(std::make_pair( \
34 url.path(), base::Bind(&FakeGaia::method, base::Unretained(this))))
36 #define REGISTER_PATH_RESPONSE_HANDLER(path, method) \
37 request_handlers_.insert(std::make_pair( \
38 path, base::Bind(&FakeGaia::method, base::Unretained(this))))
40 using namespace net::test_server;
42 namespace {
44 const base::FilePath::CharType kServiceLogin[] =
45 FILE_PATH_LITERAL("google_apis/test/service_login.html");
47 // OAuth2 Authentication header value prefix.
48 const char kAuthHeaderBearer[] = "Bearer ";
49 const char kAuthHeaderOAuth[] = "OAuth ";
51 const char kListAccountsResponseFormat[] =
52 "[\"gaia.l.a.r\",[[\"gaia.l.a\",1,\"\",\"%s\",\"\",1,1,0]]]";
54 typedef std::map<std::string, std::string> CookieMap;
56 // Parses cookie name-value map our of |request|.
57 CookieMap GetRequestCookies(const HttpRequest& request) {
58 CookieMap result;
59 std::map<std::string, std::string>::const_iterator iter =
60 request.headers.find("Cookie");
61 if (iter != request.headers.end()) {
62 std::vector<std::string> cookie_nv_pairs;
63 base::SplitString(iter->second, ' ', &cookie_nv_pairs);
64 for(std::vector<std::string>::const_iterator cookie_line =
65 cookie_nv_pairs.begin();
66 cookie_line != cookie_nv_pairs.end();
67 ++cookie_line) {
68 std::vector<std::string> name_value;
69 base::SplitString(*cookie_line, '=', &name_value);
70 if (name_value.size() != 2)
71 continue;
73 std::string value = name_value[1];
74 if (value.size() && value[value.size() - 1] == ';')
75 value = value.substr(0, value.size() -1);
77 result.insert(std::make_pair(name_value[0], value));
80 return result;
83 // Extracts the |access_token| from authorization header of |request|.
84 bool GetAccessToken(const HttpRequest& request,
85 const char* auth_token_prefix,
86 std::string* access_token) {
87 std::map<std::string, std::string>::const_iterator auth_header_entry =
88 request.headers.find("Authorization");
89 if (auth_header_entry != request.headers.end()) {
90 if (StartsWithASCII(auth_header_entry->second, auth_token_prefix, true)) {
91 *access_token = auth_header_entry->second.substr(
92 strlen(auth_token_prefix));
93 return true;
97 return false;
100 void SetCookies(BasicHttpResponse* http_response,
101 const std::string& sid_cookie,
102 const std::string& lsid_cookie) {
103 http_response->AddCustomHeader(
104 "Set-Cookie",
105 base::StringPrintf("SID=%s; Path=/; HttpOnly;", sid_cookie.c_str()));
106 http_response->AddCustomHeader(
107 "Set-Cookie",
108 base::StringPrintf("LSID=%s; Path=/; HttpOnly;", lsid_cookie.c_str()));
111 } // namespace
113 FakeGaia::AccessTokenInfo::AccessTokenInfo()
114 : expires_in(3600) {}
116 FakeGaia::AccessTokenInfo::~AccessTokenInfo() {}
118 FakeGaia::MergeSessionParams::MergeSessionParams() {
121 FakeGaia::MergeSessionParams::~MergeSessionParams() {
124 FakeGaia::FakeGaia() {
125 base::FilePath source_root_dir;
126 PathService::Get(base::DIR_SOURCE_ROOT, &source_root_dir);
127 CHECK(base::ReadFileToString(
128 source_root_dir.Append(base::FilePath(kServiceLogin)),
129 &service_login_response_));
132 FakeGaia::~FakeGaia() {}
134 void FakeGaia::SetMergeSessionParams(
135 const MergeSessionParams& params) {
136 merge_session_params_ = params;
139 void FakeGaia::Initialize() {
140 GaiaUrls* gaia_urls = GaiaUrls::GetInstance();
141 // Handles /MergeSession GAIA call.
142 REGISTER_RESPONSE_HANDLER(
143 gaia_urls->merge_session_url(), HandleMergeSession);
145 // Handles /o/oauth2/programmatic_auth GAIA call.
146 REGISTER_RESPONSE_HANDLER(
147 gaia_urls->client_login_to_oauth2_url(), HandleProgramaticAuth);
149 // Handles /ServiceLogin GAIA call.
150 REGISTER_RESPONSE_HANDLER(
151 gaia_urls->service_login_url(), HandleServiceLogin);
153 // Handles /OAuthLogin GAIA call.
154 REGISTER_RESPONSE_HANDLER(
155 gaia_urls->oauth1_login_url(), HandleOAuthLogin);
157 // Handles /ServiceLoginAuth GAIA call.
158 REGISTER_RESPONSE_HANDLER(
159 gaia_urls->service_login_auth_url(), HandleServiceLoginAuth);
161 // Handles /SSO GAIA call (not GAIA, made up for SAML tests).
162 REGISTER_PATH_RESPONSE_HANDLER("/SSO", HandleSSO);
164 // Handles /o/oauth2/token GAIA call.
165 REGISTER_RESPONSE_HANDLER(
166 gaia_urls->oauth2_token_url(), HandleAuthToken);
168 // Handles /oauth2/v2/tokeninfo GAIA call.
169 REGISTER_RESPONSE_HANDLER(
170 gaia_urls->oauth2_token_info_url(), HandleTokenInfo);
172 // Handles /oauth2/v2/IssueToken GAIA call.
173 REGISTER_RESPONSE_HANDLER(
174 gaia_urls->oauth2_issue_token_url(), HandleIssueToken);
176 // Handles /ListAccounts GAIA call.
177 REGISTER_RESPONSE_HANDLER(
178 gaia_urls->list_accounts_url(), HandleListAccounts);
181 scoped_ptr<HttpResponse> FakeGaia::HandleRequest(const HttpRequest& request) {
182 // The scheme and host of the URL is actually not important but required to
183 // get a valid GURL in order to parse |request.relative_url|.
184 GURL request_url = GURL("http://localhost").Resolve(request.relative_url);
185 std::string request_path = request_url.path();
186 scoped_ptr<BasicHttpResponse> http_response(new BasicHttpResponse());
187 RequestHandlerMap::iterator iter = request_handlers_.find(request_path);
188 if (iter != request_handlers_.end()) {
189 LOG(WARNING) << "Serving request " << request_path;
190 iter->second.Run(request, http_response.get());
191 } else {
192 LOG(ERROR) << "Unhandled request " << request_path;
193 return scoped_ptr<HttpResponse>(); // Request not understood.
196 return http_response.PassAs<HttpResponse>();
199 void FakeGaia::IssueOAuthToken(const std::string& auth_token,
200 const AccessTokenInfo& token_info) {
201 access_token_info_map_.insert(std::make_pair(auth_token, token_info));
204 void FakeGaia::RegisterSamlUser(const std::string& account_id,
205 const GURL& saml_idp) {
206 saml_account_idp_map_[account_id] = saml_idp;
209 // static
210 bool FakeGaia::GetQueryParameter(const std::string& query,
211 const std::string& key,
212 std::string* value) {
213 // Name and scheme actually don't matter, but are required to get a valid URL
214 // for parsing.
215 GURL query_url("http://localhost?" + query);
216 return net::GetValueForKeyInQuery(query_url, key, value);
219 void FakeGaia::HandleMergeSession(const HttpRequest& request,
220 BasicHttpResponse* http_response) {
221 http_response->set_code(net::HTTP_UNAUTHORIZED);
222 if (merge_session_params_.session_sid_cookie.empty() ||
223 merge_session_params_.session_lsid_cookie.empty()) {
224 http_response->set_code(net::HTTP_BAD_REQUEST);
225 return;
228 std::string uber_token;
229 if (!GetQueryParameter(request.content, "uberauth", &uber_token) ||
230 uber_token != merge_session_params_.gaia_uber_token) {
231 LOG(ERROR) << "Missing or invalid 'uberauth' param in /MergeSession call";
232 return;
235 std::string continue_url;
236 if (!GetQueryParameter(request.content, "continue", &continue_url)) {
237 LOG(ERROR) << "Missing or invalid 'continue' param in /MergeSession call";
238 return;
241 std::string source;
242 if (!GetQueryParameter(request.content, "source", &source)) {
243 LOG(ERROR) << "Missing or invalid 'source' param in /MergeSession call";
244 return;
247 SetCookies(http_response,
248 merge_session_params_.session_sid_cookie,
249 merge_session_params_.session_lsid_cookie);
250 // TODO(zelidrag): Not used now.
251 http_response->set_content("OK");
252 http_response->set_code(net::HTTP_OK);
255 void FakeGaia::HandleProgramaticAuth(
256 const HttpRequest& request,
257 BasicHttpResponse* http_response) {
258 http_response->set_code(net::HTTP_UNAUTHORIZED);
259 if (merge_session_params_.auth_code.empty()) {
260 http_response->set_code(net::HTTP_BAD_REQUEST);
261 return;
264 GaiaUrls* gaia_urls = GaiaUrls::GetInstance();
265 std::string scope;
266 if (!GetQueryParameter(request.content, "scope", &scope) ||
267 GaiaConstants::kOAuth1LoginScope != scope) {
268 return;
271 CookieMap cookies = GetRequestCookies(request);
272 CookieMap::const_iterator sid_iter = cookies.find("SID");
273 if (sid_iter == cookies.end() ||
274 sid_iter->second != merge_session_params_.auth_sid_cookie) {
275 LOG(ERROR) << "/o/oauth2/programmatic_auth missing SID cookie";
276 return;
278 CookieMap::const_iterator lsid_iter = cookies.find("LSID");
279 if (lsid_iter == cookies.end() ||
280 lsid_iter->second != merge_session_params_.auth_lsid_cookie) {
281 LOG(ERROR) << "/o/oauth2/programmatic_auth missing LSID cookie";
282 return;
285 std::string client_id;
286 if (!GetQueryParameter(request.content, "client_id", &client_id) ||
287 gaia_urls->oauth2_chrome_client_id() != client_id) {
288 return;
291 http_response->AddCustomHeader(
292 "Set-Cookie",
293 base::StringPrintf(
294 "oauth_code=%s; Path=/o/GetOAuth2Token; Secure; HttpOnly;",
295 merge_session_params_.auth_code.c_str()));
296 http_response->set_code(net::HTTP_OK);
297 http_response->set_content_type("text/html");
300 void FakeGaia::FormatJSONResponse(const base::DictionaryValue& response_dict,
301 BasicHttpResponse* http_response) {
302 std::string response_json;
303 base::JSONWriter::Write(&response_dict, &response_json);
304 http_response->set_content(response_json);
305 http_response->set_code(net::HTTP_OK);
308 const FakeGaia::AccessTokenInfo* FakeGaia::FindAccessTokenInfo(
309 const std::string& auth_token,
310 const std::string& client_id,
311 const std::string& scope_string) const {
312 if (auth_token.empty() || client_id.empty())
313 return NULL;
315 std::vector<std::string> scope_list;
316 base::SplitString(scope_string, ' ', &scope_list);
317 ScopeSet scopes(scope_list.begin(), scope_list.end());
319 for (AccessTokenInfoMap::const_iterator entry(
320 access_token_info_map_.lower_bound(auth_token));
321 entry != access_token_info_map_.upper_bound(auth_token);
322 ++entry) {
323 if (entry->second.audience == client_id &&
324 (scope_string.empty() || entry->second.scopes == scopes)) {
325 return &(entry->second);
329 return NULL;
332 void FakeGaia::HandleServiceLogin(const HttpRequest& request,
333 BasicHttpResponse* http_response) {
334 http_response->set_code(net::HTTP_OK);
335 http_response->set_content(service_login_response_);
336 http_response->set_content_type("text/html");
339 void FakeGaia::HandleOAuthLogin(const HttpRequest& request,
340 BasicHttpResponse* http_response) {
341 http_response->set_code(net::HTTP_UNAUTHORIZED);
342 if (merge_session_params_.gaia_uber_token.empty()) {
343 http_response->set_code(net::HTTP_FORBIDDEN);
344 return;
347 std::string access_token;
348 if (!GetAccessToken(request, kAuthHeaderOAuth, &access_token)) {
349 LOG(ERROR) << "/OAuthLogin missing access token in the header";
350 return;
353 GURL request_url = GURL("http://localhost").Resolve(request.relative_url);
354 std::string request_query = request_url.query();
356 std::string source;
357 if (!GetQueryParameter(request_query, "source", &source)) {
358 LOG(ERROR) << "Missing 'source' param in /OAuthLogin call";
359 return;
362 std::string issue_uberauth;
363 if (GetQueryParameter(request_query, "issueuberauth", &issue_uberauth) &&
364 issue_uberauth == "1") {
365 http_response->set_content(merge_session_params_.gaia_uber_token);
366 http_response->set_code(net::HTTP_OK);
367 // Issue GAIA uber token.
368 } else {
369 LOG(FATAL) << "/OAuthLogin for SID/LSID is not supported";
373 void FakeGaia::HandleServiceLoginAuth(const HttpRequest& request,
374 BasicHttpResponse* http_response) {
375 std::string continue_url =
376 GaiaUrls::GetInstance()->service_login_url().spec();
377 GetQueryParameter(request.content, "continue", &continue_url);
379 std::string redirect_url = continue_url;
381 std::string email;
382 if (GetQueryParameter(request.content, "Email", &email) &&
383 saml_account_idp_map_.find(email) != saml_account_idp_map_.end()) {
384 GURL url(saml_account_idp_map_[email]);
385 url = net::AppendQueryParameter(url, "SAMLRequest", "fake_request");
386 url = net::AppendQueryParameter(url, "RelayState", continue_url);
387 redirect_url = url.spec();
388 } else if (!merge_session_params_.auth_sid_cookie.empty() &&
389 !merge_session_params_.auth_lsid_cookie.empty()) {
390 SetCookies(http_response,
391 merge_session_params_.auth_sid_cookie,
392 merge_session_params_.auth_lsid_cookie);
395 http_response->set_code(net::HTTP_TEMPORARY_REDIRECT);
396 http_response->AddCustomHeader("Location", redirect_url);
399 void FakeGaia::HandleSSO(const HttpRequest& request,
400 BasicHttpResponse* http_response) {
401 if (!merge_session_params_.auth_sid_cookie.empty() &&
402 !merge_session_params_.auth_lsid_cookie.empty()) {
403 SetCookies(http_response,
404 merge_session_params_.auth_sid_cookie,
405 merge_session_params_.auth_lsid_cookie);
407 std::string relay_state;
408 GetQueryParameter(request.content, "RelayState", &relay_state);
409 std::string redirect_url = relay_state;
410 http_response->set_code(net::HTTP_TEMPORARY_REDIRECT);
411 http_response->AddCustomHeader("Location", redirect_url);
414 void FakeGaia::HandleAuthToken(const HttpRequest& request,
415 BasicHttpResponse* http_response) {
416 std::string grant_type;
417 std::string refresh_token;
418 std::string client_id;
419 std::string scope;
420 std::string auth_code;
421 const AccessTokenInfo* token_info = NULL;
422 GetQueryParameter(request.content, "scope", &scope);
424 if (!GetQueryParameter(request.content, "grant_type", &grant_type)) {
425 http_response->set_code(net::HTTP_BAD_REQUEST);
426 LOG(ERROR) << "No 'grant_type' param in /o/oauth2/token";
427 return;
430 if (grant_type == "authorization_code") {
431 if (!GetQueryParameter(request.content, "code", &auth_code) ||
432 auth_code != merge_session_params_.auth_code) {
433 http_response->set_code(net::HTTP_BAD_REQUEST);
434 LOG(ERROR) << "No 'code' param in /o/oauth2/token";
435 return;
438 if (GaiaConstants::kOAuth1LoginScope != scope) {
439 http_response->set_code(net::HTTP_BAD_REQUEST);
440 LOG(ERROR) << "Invalid scope for /o/oauth2/token - " << scope;
441 return;
444 base::DictionaryValue response_dict;
445 response_dict.SetString("refresh_token",
446 merge_session_params_.refresh_token);
447 response_dict.SetString("access_token",
448 merge_session_params_.access_token);
449 response_dict.SetInteger("expires_in", 3600);
450 FormatJSONResponse(response_dict, http_response);
451 } else if (GetQueryParameter(request.content,
452 "refresh_token",
453 &refresh_token) &&
454 GetQueryParameter(request.content,
455 "client_id",
456 &client_id) &&
457 (token_info = FindAccessTokenInfo(refresh_token,
458 client_id,
459 scope))) {
460 base::DictionaryValue response_dict;
461 response_dict.SetString("access_token", token_info->token);
462 response_dict.SetInteger("expires_in", 3600);
463 FormatJSONResponse(response_dict, http_response);
464 } else {
465 LOG(ERROR) << "Bad request for /o/oauth2/token - "
466 << "refresh_token = " << refresh_token
467 << ", scope = " << scope
468 << ", client_id = " << client_id;
469 http_response->set_code(net::HTTP_BAD_REQUEST);
473 void FakeGaia::HandleTokenInfo(const HttpRequest& request,
474 BasicHttpResponse* http_response) {
475 const AccessTokenInfo* token_info = NULL;
476 std::string access_token;
477 if (GetQueryParameter(request.content, "access_token", &access_token)) {
478 for (AccessTokenInfoMap::const_iterator entry(
479 access_token_info_map_.begin());
480 entry != access_token_info_map_.end();
481 ++entry) {
482 if (entry->second.token == access_token) {
483 token_info = &(entry->second);
484 break;
489 if (token_info) {
490 base::DictionaryValue response_dict;
491 response_dict.SetString("issued_to", token_info->issued_to);
492 response_dict.SetString("audience", token_info->audience);
493 response_dict.SetString("user_id", token_info->user_id);
494 std::vector<std::string> scope_vector(token_info->scopes.begin(),
495 token_info->scopes.end());
496 response_dict.SetString("scope", JoinString(scope_vector, " "));
497 response_dict.SetInteger("expires_in", token_info->expires_in);
498 response_dict.SetString("email", token_info->email);
499 FormatJSONResponse(response_dict, http_response);
500 } else {
501 http_response->set_code(net::HTTP_BAD_REQUEST);
505 void FakeGaia::HandleIssueToken(const HttpRequest& request,
506 BasicHttpResponse* http_response) {
507 std::string access_token;
508 std::string scope;
509 std::string client_id;
510 const AccessTokenInfo* token_info = NULL;
511 if (GetAccessToken(request, kAuthHeaderBearer, &access_token) &&
512 GetQueryParameter(request.content, "scope", &scope) &&
513 GetQueryParameter(request.content, "client_id", &client_id) &&
514 (token_info = FindAccessTokenInfo(access_token, client_id, scope))) {
515 base::DictionaryValue response_dict;
516 response_dict.SetString("issueAdvice", "auto");
517 response_dict.SetString("expiresIn",
518 base::IntToString(token_info->expires_in));
519 response_dict.SetString("token", token_info->token);
520 FormatJSONResponse(response_dict, http_response);
521 } else {
522 http_response->set_code(net::HTTP_BAD_REQUEST);
526 void FakeGaia::HandleListAccounts(const HttpRequest& request,
527 BasicHttpResponse* http_response) {
528 http_response->set_content(base::StringPrintf(
529 kListAccountsResponseFormat, merge_session_params_.email.c_str()));
530 http_response->set_code(net::HTTP_OK);