Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / google_apis / gaia / fake_gaia.cc
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.
4
5 #include "google_apis/gaia/fake_gaia.h"
6
7 #include <vector>
8
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"
31
32 #define REGISTER_RESPONSE_HANDLER(url, method) \
33   request_handlers_.insert(std::make_pair( \
34         url.path(), base::Bind(&FakeGaia::method, base::Unretained(this))))
35
36 #define REGISTER_PATH_RESPONSE_HANDLER(path, method) \
37   request_handlers_.insert(std::make_pair( \
38         path, base::Bind(&FakeGaia::method, base::Unretained(this))))
39
40 using namespace net::test_server;
41
42 namespace {
43
44 const base::FilePath::CharType kServiceLogin[] =
45     FILE_PATH_LITERAL("google_apis/test/service_login.html");
46
47 // OAuth2 Authentication header value prefix.
48 const char kAuthHeaderBearer[] = "Bearer ";
49 const char kAuthHeaderOAuth[] = "OAuth ";
50
51 const char kListAccountsResponseFormat[] =
52     "[\"gaia.l.a.r\",[[\"gaia.l.a\",1,\"\",\"%s\",\"\",1,1,0]]]";
53
54 typedef std::map<std::string, std::string> CookieMap;
55
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;
72
73       std::string value = name_value[1];
74       if (value.size() && value[value.size() - 1] == ';')
75         value = value.substr(0, value.size() -1);
76
77       result.insert(std::make_pair(name_value[0], value));
78     }
79   }
80   return result;
81 }
82
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;
94     }
95   }
96
97   return false;
98 }
99
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()));
109 }
110
111 }  // namespace
112
113 FakeGaia::AccessTokenInfo::AccessTokenInfo()
114   : expires_in(3600) {}
115
116 FakeGaia::AccessTokenInfo::~AccessTokenInfo() {}
117
118 FakeGaia::MergeSessionParams::MergeSessionParams() {
119 }
120
121 FakeGaia::MergeSessionParams::~MergeSessionParams() {
122 }
123
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_));
130 }
131
132 FakeGaia::~FakeGaia() {}
133
134 void FakeGaia::SetMergeSessionParams(
135     const MergeSessionParams& params) {
136   merge_session_params_ = params;
137 }
138
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);
144
145   // Handles /o/oauth2/programmatic_auth GAIA call.
146   REGISTER_RESPONSE_HANDLER(
147       gaia_urls->client_login_to_oauth2_url(), HandleProgramaticAuth);
148
149   // Handles /ServiceLogin GAIA call.
150   REGISTER_RESPONSE_HANDLER(
151       gaia_urls->service_login_url(), HandleServiceLogin);
152
153   // Handles /OAuthLogin GAIA call.
154   REGISTER_RESPONSE_HANDLER(
155       gaia_urls->oauth1_login_url(), HandleOAuthLogin);
156
157   // Handles /ServiceLoginAuth GAIA call.
158   REGISTER_RESPONSE_HANDLER(
159       gaia_urls->service_login_auth_url(), HandleServiceLoginAuth);
160
161   // Handles /SSO GAIA call (not GAIA, made up for SAML tests).
162   REGISTER_PATH_RESPONSE_HANDLER("/SSO", HandleSSO);
163
164   // Handles /o/oauth2/token GAIA call.
165   REGISTER_RESPONSE_HANDLER(
166       gaia_urls->oauth2_token_url(), HandleAuthToken);
167
168   // Handles /oauth2/v2/tokeninfo GAIA call.
169   REGISTER_RESPONSE_HANDLER(
170       gaia_urls->oauth2_token_info_url(), HandleTokenInfo);
171
172   // Handles /oauth2/v2/IssueToken GAIA call.
173   REGISTER_RESPONSE_HANDLER(
174       gaia_urls->oauth2_issue_token_url(), HandleIssueToken);
175
176   // Handles /ListAccounts GAIA call.
177   REGISTER_RESPONSE_HANDLER(
178       gaia_urls->list_accounts_url(), HandleListAccounts);
179 }
180
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.
194   }
195
196   return http_response.PassAs<HttpResponse>();
197 }
198
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));
202 }
203
204 void FakeGaia::RegisterSamlUser(const std::string& account_id,
205                                 const GURL& saml_idp) {
206   saml_account_idp_map_[account_id] = saml_idp;
207 }
208
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);
217 }
218
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;
226   }
227
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;
233   }
234
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;
239   }
240
241   std::string source;
242   if (!GetQueryParameter(request.content, "source", &source)) {
243     LOG(ERROR) << "Missing or invalid 'source' param in /MergeSession call";
244     return;
245   }
246
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);
253 }
254
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;
262   }
263
264   GaiaUrls* gaia_urls = GaiaUrls::GetInstance();
265   std::string scope;
266   if (!GetQueryParameter(request.content, "scope", &scope) ||
267       GaiaConstants::kOAuth1LoginScope != scope) {
268     return;
269   }
270
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;
277   }
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;
283   }
284
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;
289   }
290
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");
298 }
299
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);
306 }
307
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;
314
315   std::vector<std::string> scope_list;
316   base::SplitString(scope_string, ' ', &scope_list);
317   ScopeSet scopes(scope_list.begin(), scope_list.end());
318
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);
326     }
327   }
328
329   return NULL;
330 }
331
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");
337 }
338
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;
345   }
346
347   std::string access_token;
348   if (!GetAccessToken(request, kAuthHeaderOAuth, &access_token)) {
349     LOG(ERROR) << "/OAuthLogin missing access token in the header";
350     return;
351   }
352
353   GURL request_url = GURL("http://localhost").Resolve(request.relative_url);
354   std::string request_query = request_url.query();
355
356   std::string source;
357   if (!GetQueryParameter(request_query, "source", &source)) {
358     LOG(ERROR) << "Missing 'source' param in /OAuthLogin call";
359     return;
360   }
361
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";
370   }
371 }
372
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);
378
379   std::string redirect_url = continue_url;
380
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     http_response->AddCustomHeader("Google-Accounts-SAML", "Start");
389   } else if (!merge_session_params_.auth_sid_cookie.empty() &&
390              !merge_session_params_.auth_lsid_cookie.empty()) {
391     SetCookies(http_response,
392                merge_session_params_.auth_sid_cookie,
393                merge_session_params_.auth_lsid_cookie);
394   }
395
396   http_response->set_code(net::HTTP_TEMPORARY_REDIRECT);
397   http_response->AddCustomHeader("Location", redirect_url);
398 }
399
400 void FakeGaia::HandleSSO(const HttpRequest& request,
401                          BasicHttpResponse* http_response) {
402   if (!merge_session_params_.auth_sid_cookie.empty() &&
403       !merge_session_params_.auth_lsid_cookie.empty()) {
404     SetCookies(http_response,
405                merge_session_params_.auth_sid_cookie,
406                merge_session_params_.auth_lsid_cookie);
407   }
408   std::string relay_state;
409   GetQueryParameter(request.content, "RelayState", &relay_state);
410   std::string redirect_url = relay_state;
411   http_response->set_code(net::HTTP_TEMPORARY_REDIRECT);
412   http_response->AddCustomHeader("Location", redirect_url);
413   http_response->AddCustomHeader("Google-Accounts-SAML", "End");
414 }
415
416 void FakeGaia::HandleAuthToken(const HttpRequest& request,
417                                BasicHttpResponse* http_response) {
418   std::string scope;
419   GetQueryParameter(request.content, "scope", &scope);
420
421   std::string grant_type;
422   if (!GetQueryParameter(request.content, "grant_type", &grant_type)) {
423     http_response->set_code(net::HTTP_BAD_REQUEST);
424     LOG(ERROR) << "No 'grant_type' param in /o/oauth2/token";
425     return;
426   }
427
428   if (grant_type == "authorization_code") {
429     std::string auth_code;
430     if (!GetQueryParameter(request.content, "code", &auth_code) ||
431         auth_code != merge_session_params_.auth_code) {
432       http_response->set_code(net::HTTP_BAD_REQUEST);
433       LOG(ERROR) << "No 'code' param in /o/oauth2/token";
434       return;
435     }
436
437     if (GaiaConstants::kOAuth1LoginScope != scope) {
438       http_response->set_code(net::HTTP_BAD_REQUEST);
439       LOG(ERROR) << "Invalid scope for /o/oauth2/token - " << scope;
440       return;
441     }
442
443     base::DictionaryValue response_dict;
444     response_dict.SetString("refresh_token",
445                             merge_session_params_.refresh_token);
446     response_dict.SetString("access_token",
447                             merge_session_params_.access_token);
448     response_dict.SetInteger("expires_in", 3600);
449     FormatJSONResponse(response_dict, http_response);
450     return;
451   }
452
453   std::string refresh_token;
454   std::string client_id;
455   if (GetQueryParameter(request.content, "refresh_token", &refresh_token) &&
456       GetQueryParameter(request.content, "client_id", &client_id)) {
457     const AccessTokenInfo* token_info =
458         FindAccessTokenInfo(refresh_token, client_id, scope);
459     if (token_info) {
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       return;
465     }
466   }
467
468   LOG(ERROR) << "Bad request for /o/oauth2/token - "
469               << "refresh_token = " << refresh_token
470               << ", scope = " << scope
471               << ", client_id = " << client_id;
472   http_response->set_code(net::HTTP_BAD_REQUEST);
473 }
474
475 void FakeGaia::HandleTokenInfo(const HttpRequest& request,
476                                BasicHttpResponse* http_response) {
477   const AccessTokenInfo* token_info = NULL;
478   std::string access_token;
479   if (GetQueryParameter(request.content, "access_token", &access_token)) {
480     for (AccessTokenInfoMap::const_iterator entry(
481              access_token_info_map_.begin());
482          entry != access_token_info_map_.end();
483          ++entry) {
484       if (entry->second.token == access_token) {
485         token_info = &(entry->second);
486         break;
487       }
488     }
489   }
490
491   if (token_info) {
492     base::DictionaryValue response_dict;
493     response_dict.SetString("issued_to", token_info->issued_to);
494     response_dict.SetString("audience", token_info->audience);
495     response_dict.SetString("user_id", token_info->user_id);
496     std::vector<std::string> scope_vector(token_info->scopes.begin(),
497                                           token_info->scopes.end());
498     response_dict.SetString("scope", JoinString(scope_vector, " "));
499     response_dict.SetInteger("expires_in", token_info->expires_in);
500     response_dict.SetString("email", token_info->email);
501     FormatJSONResponse(response_dict, http_response);
502   } else {
503     http_response->set_code(net::HTTP_BAD_REQUEST);
504   }
505 }
506
507 void FakeGaia::HandleIssueToken(const HttpRequest& request,
508                                 BasicHttpResponse* http_response) {
509   std::string access_token;
510   std::string scope;
511   std::string client_id;
512   if (GetAccessToken(request, kAuthHeaderBearer, &access_token) &&
513       GetQueryParameter(request.content, "scope", &scope) &&
514       GetQueryParameter(request.content, "client_id", &client_id)) {
515     const AccessTokenInfo* token_info =
516         FindAccessTokenInfo(access_token, client_id, scope);
517     if (token_info) {
518       base::DictionaryValue response_dict;
519       response_dict.SetString("issueAdvice", "auto");
520       response_dict.SetString("expiresIn",
521                               base::IntToString(token_info->expires_in));
522       response_dict.SetString("token", token_info->token);
523       FormatJSONResponse(response_dict, http_response);
524       return;
525     }
526   }
527   http_response->set_code(net::HTTP_BAD_REQUEST);
528 }
529
530 void FakeGaia::HandleListAccounts(const HttpRequest& request,
531                                  BasicHttpResponse* http_response) {
532   http_response->set_content(base::StringPrintf(
533       kListAccountsResponseFormat, merge_session_params_.email.c_str()));
534   http_response->set_code(net::HTTP_OK);
535 }