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 "base/base_paths.h" 8 #include "base/file_util.h" 9 #include "base/files/file_path.h" 10 #include "base/json/json_writer.h" 11 #include "base/logging.h" 12 #include "base/path_service.h" 13 #include "base/strings/string_number_conversions.h" 14 #include "base/strings/string_split.h" 15 #include "base/strings/string_util.h" 16 #include "base/values.h" 17 #include "google_apis/gaia/gaia_urls.h" 18 #include "net/base/url_util.h" 19 #include "net/http/http_status_code.h" 20 #include "net/test/embedded_test_server/http_request.h" 21 #include "net/test/embedded_test_server/http_response.h" 22 #include "url/url_parse.h" 23 24 using namespace net::test_server; 25 26 namespace { 27 const base::FilePath::CharType kServiceLogin[] = 28 FILE_PATH_LITERAL("google_apis/test/service_login.html"); 29 } 30 31 FakeGaia::AccessTokenInfo::AccessTokenInfo() 32 : expires_in(3600) {} 33 34 FakeGaia::AccessTokenInfo::~AccessTokenInfo() {} 35 36 FakeGaia::FakeGaia() { 37 base::FilePath source_root_dir; 38 PathService::Get(base::DIR_SOURCE_ROOT, &source_root_dir); 39 CHECK(base::ReadFileToString( 40 source_root_dir.Append(base::FilePath(kServiceLogin)), 41 &service_login_response_)); 42 } 43 44 FakeGaia::~FakeGaia() {} 45 46 scoped_ptr<HttpResponse> FakeGaia::HandleRequest(const HttpRequest& request) { 47 GaiaUrls* gaia_urls = GaiaUrls::GetInstance(); 48 49 // The scheme and host of the URL is actually not important but required to 50 // get a valid GURL in order to parse |request.relative_url|. 51 GURL request_url = GURL("http://localhost").Resolve(request.relative_url); 52 std::string request_path = request_url.path(); 53 54 scoped_ptr<BasicHttpResponse> http_response(new BasicHttpResponse()); 55 if (request_path == gaia_urls->service_login_url().path()) { 56 http_response->set_code(net::HTTP_OK); 57 http_response->set_content(service_login_response_); 58 http_response->set_content_type("text/html"); 59 } else if (request_path == gaia_urls->service_login_auth_url().path()) { 60 std::string continue_url = gaia_urls->service_login_url().spec(); 61 GetQueryParameter(request.content, "continue", &continue_url); 62 http_response->set_code(net::HTTP_OK); 63 const std::string redirect_js = 64 "document.location.href = '" + continue_url + "'"; 65 http_response->set_content( 66 "<HTML><HEAD><SCRIPT>\n" + redirect_js + "\n</SCRIPT></HEAD></HTML>"); 67 http_response->set_content_type("text/html"); 68 } else if (request_path == gaia_urls->oauth2_token_url().path()) { 69 std::string refresh_token; 70 std::string client_id; 71 std::string scope; 72 const AccessTokenInfo* token_info = NULL; 73 GetQueryParameter(request.content, "scope", &scope); 74 if (GetQueryParameter(request.content, "refresh_token", &refresh_token) && 75 GetQueryParameter(request.content, "client_id", &client_id) && 76 (token_info = GetAccessTokenInfo(refresh_token, client_id, scope))) { 77 base::DictionaryValue response_dict; 78 response_dict.SetString("access_token", token_info->token); 79 response_dict.SetInteger("expires_in", 3600); 80 FormatJSONResponse(response_dict, http_response.get()); 81 } else { 82 http_response->set_code(net::HTTP_BAD_REQUEST); 83 } 84 } else if (request_path == gaia_urls->oauth2_token_info_url().path()) { 85 const AccessTokenInfo* token_info = NULL; 86 std::string access_token; 87 if (GetQueryParameter(request.content, "access_token", &access_token)) { 88 for (AccessTokenInfoMap::const_iterator entry( 89 access_token_info_map_.begin()); 90 entry != access_token_info_map_.end(); 91 ++entry) { 92 if (entry->second.token == access_token) { 93 token_info = &(entry->second); 94 break; 95 } 96 } 97 } 98 99 if (token_info) { 100 base::DictionaryValue response_dict; 101 response_dict.SetString("issued_to", token_info->issued_to); 102 response_dict.SetString("audience", token_info->audience); 103 response_dict.SetString("user_id", token_info->user_id); 104 std::vector<std::string> scope_vector(token_info->scopes.begin(), 105 token_info->scopes.end()); 106 response_dict.SetString("scope", JoinString(scope_vector, " ")); 107 response_dict.SetInteger("expires_in", token_info->expires_in); 108 response_dict.SetString("email", token_info->email); 109 FormatJSONResponse(response_dict, http_response.get()); 110 } else { 111 http_response->set_code(net::HTTP_BAD_REQUEST); 112 } 113 } else if (request_path == gaia_urls->oauth2_issue_token_url().path()) { 114 std::string access_token; 115 std::map<std::string, std::string>::const_iterator auth_header_entry = 116 request.headers.find("Authorization"); 117 if (auth_header_entry != request.headers.end()) { 118 if (StartsWithASCII(auth_header_entry->second, "Bearer ", true)) 119 access_token = auth_header_entry->second.substr(7); 120 } 121 122 std::string scope; 123 std::string client_id; 124 const AccessTokenInfo* token_info = NULL; 125 if (GetQueryParameter(request.content, "scope", &scope) && 126 GetQueryParameter(request.content, "client_id", &client_id) && 127 (token_info = GetAccessTokenInfo(access_token, client_id, scope))) { 128 base::DictionaryValue response_dict; 129 response_dict.SetString("issueAdvice", "auto"); 130 response_dict.SetString("expiresIn", 131 base::IntToString(token_info->expires_in)); 132 response_dict.SetString("token", token_info->token); 133 FormatJSONResponse(response_dict, http_response.get()); 134 } else { 135 http_response->set_code(net::HTTP_BAD_REQUEST); 136 } 137 } else { 138 // Request not understood. 139 return scoped_ptr<HttpResponse>(); 140 } 141 142 return http_response.PassAs<HttpResponse>(); 143 } 144 145 void FakeGaia::IssueOAuthToken(const std::string& auth_token, 146 const AccessTokenInfo& token_info) { 147 access_token_info_map_.insert(std::make_pair(auth_token, token_info)); 148 } 149 150 void FakeGaia::FormatJSONResponse(const base::DictionaryValue& response_dict, 151 BasicHttpResponse* http_response) { 152 std::string response_json; 153 base::JSONWriter::Write(&response_dict, &response_json); 154 http_response->set_content(response_json); 155 http_response->set_code(net::HTTP_OK); 156 } 157 158 const FakeGaia::AccessTokenInfo* FakeGaia::GetAccessTokenInfo( 159 const std::string& auth_token, 160 const std::string& client_id, 161 const std::string& scope_string) const { 162 if (auth_token.empty() || client_id.empty()) 163 return NULL; 164 165 std::vector<std::string> scope_list; 166 base::SplitString(scope_string, ' ', &scope_list); 167 ScopeSet scopes(scope_list.begin(), scope_list.end()); 168 169 for (AccessTokenInfoMap::const_iterator entry( 170 access_token_info_map_.lower_bound(auth_token)); 171 entry != access_token_info_map_.upper_bound(auth_token); 172 ++entry) { 173 if (entry->second.audience == client_id && 174 (scope_string.empty() || entry->second.scopes == scopes)) { 175 return &(entry->second); 176 } 177 } 178 179 return NULL; 180 } 181 182 // static 183 bool FakeGaia::GetQueryParameter(const std::string& query, 184 const std::string& key, 185 std::string* value) { 186 // Name and scheme actually don't matter, but are required to get a valid URL 187 // for parsing. 188 GURL query_url("http://localhost?" + query); 189 return net::GetValueForKeyInQuery(query_url, key, value); 190 } 191