Chromium Code Reviews| Index: google_apis/gaia/fake_gaia.cc |
| diff --git a/google_apis/gaia/fake_gaia.cc b/google_apis/gaia/fake_gaia.cc |
| index 7474aca80a9b0c0f184869f1295b13b1a6142fb0..98c48eeaeb10c0d17a7f6292a4ff235b8e040fe4 100644 |
| --- a/google_apis/gaia/fake_gaia.cc |
| +++ b/google_apis/gaia/fake_gaia.cc |
| @@ -7,6 +7,8 @@ |
| #include <vector> |
| #include "base/base_paths.h" |
| +#include "base/bind.h" |
| +#include "base/bind_helpers.h" |
| #include "base/file_util.h" |
| #include "base/files/file_path.h" |
| #include "base/json/json_writer.h" |
| @@ -15,6 +17,7 @@ |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_split.h" |
| #include "base/strings/string_util.h" |
| +#include "base/strings/stringprintf.h" |
| #include "base/values.h" |
| #include "google_apis/gaia/gaia_urls.h" |
| #include "net/base/url_util.h" |
| @@ -23,11 +26,21 @@ |
| #include "net/test/embedded_test_server/http_response.h" |
| #include "url/url_parse.h" |
| +#define REGISTER_RESPONSE_HANDLER(url, method) \ |
| + request_handlers_.insert(std::make_pair( \ |
| + url.path(), base::Bind(&FakeGaia::method, base::Unretained(this)))) |
| + |
| using namespace net::test_server; |
| namespace { |
| + |
| const base::FilePath::CharType kServiceLogin[] = |
| FILE_PATH_LITERAL("google_apis/test/service_login.html"); |
| + |
| +// OAuth2 Authentication header value prefix. |
| +const char kAuthHeaderBearer[] = "Bearer "; |
| +const char kAuthHeaderOAuth[] = "OAuth "; |
| + |
| } |
| FakeGaia::AccessTokenInfo::AccessTokenInfo() |
| @@ -45,113 +58,307 @@ FakeGaia::FakeGaia() { |
| FakeGaia::~FakeGaia() {} |
| -scoped_ptr<HttpResponse> FakeGaia::HandleRequest(const HttpRequest& request) { |
| +void FakeGaia::SetAuthTokens(const std::string& auth_code, |
| + const std::string& refresh_token, |
| + const std::string& access_token, |
| + const std::string& gaia_uber_token, |
| + const std::string& session_sid_cookie, |
| + const std::string& session_lsid_cookie) { |
| + fake_auth_code_ = auth_code; |
| + fake_refresh_token_ = refresh_token; |
| + fake_access_token_ = access_token; |
| + fake_gaia_uber_token_ = gaia_uber_token; |
| + fake_session_sid_cookie_ = session_sid_cookie; |
| + fake_session_lsid_cookie_ = session_lsid_cookie; |
| +} |
| + |
| +void FakeGaia::Initialize() { |
| GaiaUrls* gaia_urls = GaiaUrls::GetInstance(); |
| + // Handles /ServiceLogin GAIA call. |
| + REGISTER_RESPONSE_HANDLER( |
| + gaia_urls->service_login_url(), HandleServiceLogin); |
| + |
| + // Handles /ServiceLoginAuth GAIA call. |
| + REGISTER_RESPONSE_HANDLER( |
| + gaia_urls->service_login_auth_url(), HandleServiceLoginAuth); |
| + |
| + // Handles /o/oauth2/programmatic_auth GAIA call. |
| + REGISTER_RESPONSE_HANDLER( |
| + gaia_urls->client_login_to_oauth2_url(), HandleProgramaticAuth); |
| + |
| + // Handles /o/oauth2/token GAIA call. |
| + REGISTER_RESPONSE_HANDLER( |
| + gaia_urls->oauth2_token_url(), HandleAuthToken); |
| + |
| + // Handles /OAuthLogin GAIA call. |
| + REGISTER_RESPONSE_HANDLER( |
| + gaia_urls->oauth1_login_url(), HandleOAuthLogin); |
| + |
| + // Handles /MergeSession GAIA call. |
| + REGISTER_RESPONSE_HANDLER( |
| + gaia_urls->merge_session_url(), HandleMergeSession); |
| + |
| + // Handles /oauth2/v2/IssueToken GAIA call. |
| + REGISTER_RESPONSE_HANDLER( |
| + gaia_urls->oauth2_issue_token_url(), HandleIssueToken); |
| + |
| + // Handles /oauth2/v2/tokeninfo GAIA call. |
| + REGISTER_RESPONSE_HANDLER( |
| + gaia_urls->oauth2_token_info_url(), HandleTokenInfo); |
| +} |
| + |
| +void FakeGaia::HandleProgramaticAuth( |
| + const HttpRequest& request, |
| + BasicHttpResponse* http_response) { |
| + GaiaUrls* gaia_urls = GaiaUrls::GetInstance(); |
| + std::string scope; |
| + if (!GetQueryParameter(request.content, "scope", &scope) || |
| + gaia_urls->oauth1_login_scope() != scope) { |
| + http_response->set_code(net::HTTP_BAD_REQUEST); |
| + return; |
| + } |
| + |
| + std::string client_id; |
| + if (!GetQueryParameter(request.content, "client_id", &client_id) || |
| + gaia_urls->oauth2_chrome_client_id() != client_id) { |
| + http_response->set_code(net::HTTP_BAD_REQUEST); |
| + return; |
| + } |
| + |
| + http_response->AddCustomHeader( |
| + "Set-Cookie", |
| + base::StringPrintf( |
| + "oauth_code=%s; Path=/o/GetOAuth2Token; Secure; HttpOnly;", |
| + fake_auth_code_.c_str())); |
| + http_response->set_code(net::HTTP_OK); |
| + http_response->set_content_type("text/html"); |
| +} |
| + |
| +void FakeGaia::HandleServiceLogin(const HttpRequest& request, |
| + BasicHttpResponse* http_response) { |
| + http_response->set_code(net::HTTP_OK); |
| + http_response->set_content(service_login_response_); |
| + http_response->set_content_type("text/html"); |
| +} |
| + |
| +void FakeGaia::HandleOAuthLogin(const HttpRequest& request, |
| + BasicHttpResponse* http_response) { |
| + http_response->set_code(net::HTTP_BAD_REQUEST); |
| + std::string access_token; |
| + if (!GetAccessToken(request, kAuthHeaderOAuth, &access_token)) { |
| + LOG(ERROR) << "/OAuthLogin missing access token in the header"; |
| + return; |
| + } |
| - // The scheme and host of the URL is actually not important but required to |
| - // get a valid GURL in order to parse |request.relative_url|. |
| GURL request_url = GURL("http://localhost").Resolve(request.relative_url); |
| - std::string request_path = request_url.path(); |
| + std::string request_query = request_url.query(); |
| - scoped_ptr<BasicHttpResponse> http_response(new BasicHttpResponse()); |
| - if (request_path == gaia_urls->service_login_url().path()) { |
| + std::string source; |
| + if (!GetQueryParameter(request_query, "source", &source)) { |
| + LOG(ERROR) << "Missing 'source' param in /OAuthLogin call"; |
| + return; |
| + } |
| + |
| + std::string issue_uberauth; |
| + if (GetQueryParameter(request_query, "issueuberauth", &issue_uberauth) && |
| + issue_uberauth == "1") { |
| + http_response->set_content(fake_gaia_uber_token_); |
| http_response->set_code(net::HTTP_OK); |
| - http_response->set_content(service_login_response_); |
| - http_response->set_content_type("text/html"); |
| - } else if (request_path == gaia_urls->service_login_auth_url().path()) { |
| - std::string continue_url = gaia_urls->service_login_url().spec(); |
| - GetQueryParameter(request.content, "continue", &continue_url); |
| - std::string redirect_url = continue_url; |
| - |
| - std::string email; |
| - if (GetQueryParameter(request.content, "Email", &email) && |
| - saml_account_idp_map_.find(email) != saml_account_idp_map_.end()) { |
| - GURL url(saml_account_idp_map_[email]); |
| - url = net::AppendQueryParameter(url, "SAMLRequest", "fake_request"); |
| - url = net::AppendQueryParameter(url, "RelayState", continue_url); |
| - redirect_url = url.spec(); |
| - } |
| + // Issue GAIA uber token. |
| + } else { |
| + LOG(FATAL) << "/OAuthLogin for SID/LSID is not supported"; |
| + } |
| +} |
| + |
| +void FakeGaia::HandleMergeSession(const HttpRequest& request, |
| + BasicHttpResponse* http_response) { |
| + http_response->set_code(net::HTTP_BAD_REQUEST); |
| + |
| + std::string uber_token; |
| + if (!GetQueryParameter(request.content, "uberauth", &uber_token) || |
| + uber_token != fake_gaia_uber_token_) { |
| + LOG(ERROR) << "Missing or invalid 'uberauth' param in /MergeSession call"; |
| + return; |
| + } |
| + |
| + std::string continue_url; |
| + if (!GetQueryParameter(request.content, "continue", &continue_url)) { |
| + LOG(ERROR) << "Missing or invalid 'continue' param in /MergeSession call"; |
| + return; |
| + } |
| + |
| + std::string source; |
| + if (!GetQueryParameter(request.content, "source", &source)) { |
| + LOG(ERROR) << "Missing or invalid 'source' param in /MergeSession call"; |
| + return; |
| + } |
| + |
| + http_response->AddCustomHeader( |
| + "Set-Cookie", |
| + base::StringPrintf( |
| + "SID=%s; LSID=%s; Path=/; Secure; HttpOnly;", |
| + fake_session_sid_cookie_.c_str(), |
| + fake_session_lsid_cookie_.c_str())); |
| + // TODO(zelidrag): Not used now. |
| + http_response->set_content("OK"); |
| + http_response->set_code(net::HTTP_OK); |
| +} |
| + |
| + |
| +void FakeGaia::HandleServiceLoginAuth(const HttpRequest& request, |
| + BasicHttpResponse* http_response) { |
| + std::string continue_url = |
| + GaiaUrls::GetInstance()->service_login_url().spec(); |
| + GetQueryParameter(request.content, "continue", &continue_url); |
| + |
| + std::string redirect_url = continue_url; |
| + |
| + std::string email; |
| + if (GetQueryParameter(request.content, "Email", &email) && |
| + saml_account_idp_map_.find(email) != saml_account_idp_map_.end()) { |
| + GURL url(saml_account_idp_map_[email]); |
| + url = net::AppendQueryParameter(url, "SAMLRequest", "fake_request"); |
| + url = net::AppendQueryParameter(url, "RelayState", continue_url); |
| + redirect_url = url.spec(); |
| + } |
| + |
| + http_response->set_code(net::HTTP_TEMPORARY_REDIRECT); |
| + http_response->AddCustomHeader("Location", redirect_url); |
| +} |
| - http_response->set_code(net::HTTP_TEMPORARY_REDIRECT); |
| - http_response->AddCustomHeader("Location", redirect_url); |
| - } else if (request_path == gaia_urls->oauth2_token_url().path()) { |
| - std::string refresh_token; |
| - std::string client_id; |
| - std::string scope; |
| - const AccessTokenInfo* token_info = NULL; |
| - GetQueryParameter(request.content, "scope", &scope); |
| - if (GetQueryParameter(request.content, "refresh_token", &refresh_token) && |
| - GetQueryParameter(request.content, "client_id", &client_id) && |
| - (token_info = GetAccessTokenInfo(refresh_token, client_id, scope))) { |
| - base::DictionaryValue response_dict; |
| - response_dict.SetString("access_token", token_info->token); |
| - response_dict.SetInteger("expires_in", 3600); |
| - FormatJSONResponse(response_dict, http_response.get()); |
| - } else { |
| +void FakeGaia::HandleSSO(const HttpRequest& request, |
|
xiyuan
2013/12/17 16:52:37
Do we need to REGISTER_RESPONSE_HANDLER HandleSSO
zel
2013/12/17 17:10:30
Done.
|
| + BasicHttpResponse* http_response) { |
| + std::string relay_state; |
| + GetQueryParameter(request.content, "RelayState", &relay_state); |
| + std::string redirect_url = relay_state; |
| + http_response->set_code(net::HTTP_TEMPORARY_REDIRECT); |
| + http_response->AddCustomHeader("Location", redirect_url); |
| +} |
| + |
| +void FakeGaia::HandleAuthToken(const HttpRequest& request, |
| + BasicHttpResponse* http_response) { |
| + std::string grant_type; |
| + std::string refresh_token; |
| + std::string client_id; |
| + std::string scope; |
| + std::string auth_code; |
| + const AccessTokenInfo* token_info = NULL; |
| + GetQueryParameter(request.content, "scope", &scope); |
| + |
| + if (!GetQueryParameter(request.content, "grant_type", &grant_type)) { |
| + http_response->set_code(net::HTTP_BAD_REQUEST); |
| + LOG(ERROR) << "No 'grant_type' param in /o/oauth2/token"; |
| + return; |
| + } |
| + |
| + if (grant_type == "authorization_code") { |
| + if (!GetQueryParameter(request.content, "code", &auth_code) || |
| + auth_code != fake_auth_code_) { |
| http_response->set_code(net::HTTP_BAD_REQUEST); |
| - } |
| - } else if (request_path == gaia_urls->oauth2_token_info_url().path()) { |
| - const AccessTokenInfo* token_info = NULL; |
| - std::string access_token; |
| - if (GetQueryParameter(request.content, "access_token", &access_token)) { |
| - for (AccessTokenInfoMap::const_iterator entry( |
| - access_token_info_map_.begin()); |
| - entry != access_token_info_map_.end(); |
| - ++entry) { |
| - if (entry->second.token == access_token) { |
| - token_info = &(entry->second); |
| - break; |
| - } |
| - } |
| + LOG(ERROR) << "No 'code' param in /o/oauth2/token"; |
| + return; |
| } |
| - if (token_info) { |
| - base::DictionaryValue response_dict; |
| - response_dict.SetString("issued_to", token_info->issued_to); |
| - response_dict.SetString("audience", token_info->audience); |
| - response_dict.SetString("user_id", token_info->user_id); |
| - std::vector<std::string> scope_vector(token_info->scopes.begin(), |
| - token_info->scopes.end()); |
| - response_dict.SetString("scope", JoinString(scope_vector, " ")); |
| - response_dict.SetInteger("expires_in", token_info->expires_in); |
| - response_dict.SetString("email", token_info->email); |
| - FormatJSONResponse(response_dict, http_response.get()); |
| - } else { |
| + if (GaiaUrls::GetInstance()->oauth1_login_scope() != scope) { |
| http_response->set_code(net::HTTP_BAD_REQUEST); |
| - } |
| - } else if (request_path == gaia_urls->oauth2_issue_token_url().path()) { |
| - std::string access_token; |
| - std::map<std::string, std::string>::const_iterator auth_header_entry = |
| - request.headers.find("Authorization"); |
| - if (auth_header_entry != request.headers.end()) { |
| - if (StartsWithASCII(auth_header_entry->second, "Bearer ", true)) |
| - access_token = auth_header_entry->second.substr(7); |
| + LOG(ERROR) << "Invalid scope for /o/oauth2/token - " << scope; |
| + return; |
| } |
| - std::string scope; |
| - std::string client_id; |
| - const AccessTokenInfo* token_info = NULL; |
| - if (GetQueryParameter(request.content, "scope", &scope) && |
| - GetQueryParameter(request.content, "client_id", &client_id) && |
| - (token_info = GetAccessTokenInfo(access_token, client_id, scope))) { |
| - base::DictionaryValue response_dict; |
| - response_dict.SetString("issueAdvice", "auto"); |
| - response_dict.SetString("expiresIn", |
| - base::IntToString(token_info->expires_in)); |
| - response_dict.SetString("token", token_info->token); |
| - FormatJSONResponse(response_dict, http_response.get()); |
| - } else { |
| - http_response->set_code(net::HTTP_BAD_REQUEST); |
| + base::DictionaryValue response_dict; |
| + response_dict.SetString("refresh_token", fake_refresh_token_); |
| + response_dict.SetString("access_token", fake_access_token_); |
| + response_dict.SetInteger("expires_in", 3600); |
| + FormatJSONResponse(response_dict, http_response); |
| + } else if (GetQueryParameter(request.content, |
| + "refresh_token", |
| + &refresh_token) && |
| + GetQueryParameter(request.content, |
| + "client_id", |
| + &client_id) && |
| + (token_info = FindAccessTokenInfo(refresh_token, |
| + client_id, |
| + scope))) { |
| + base::DictionaryValue response_dict; |
| + response_dict.SetString("access_token", token_info->token); |
| + response_dict.SetInteger("expires_in", 3600); |
| + FormatJSONResponse(response_dict, http_response); |
| + } else { |
| + LOG(ERROR) << "Bad request for /o/oauth2/token - " |
| + << "refresh_token = " << refresh_token |
| + << ", scope = " << scope |
| + << ", client_id = " << client_id; |
| + http_response->set_code(net::HTTP_BAD_REQUEST); |
| + } |
| +} |
| + |
| +void FakeGaia::HandleTokenInfo(const HttpRequest& request, |
| + BasicHttpResponse* http_response) { |
| + const AccessTokenInfo* token_info = NULL; |
| + std::string access_token; |
| + if (GetQueryParameter(request.content, "access_token", &access_token)) { |
| + for (AccessTokenInfoMap::const_iterator entry( |
| + access_token_info_map_.begin()); |
| + entry != access_token_info_map_.end(); |
| + ++entry) { |
| + if (entry->second.token == access_token) { |
| + token_info = &(entry->second); |
| + break; |
| + } |
| } |
| - } else if (request_path == "/SSO") { |
| - std::string relay_state; |
| - GetQueryParameter(request.content, "RelayState", &relay_state); |
| - std::string redirect_url = relay_state; |
| - http_response->set_code(net::HTTP_TEMPORARY_REDIRECT); |
| - http_response->AddCustomHeader("Location", redirect_url); |
| + } |
| + |
| + if (token_info) { |
| + base::DictionaryValue response_dict; |
| + response_dict.SetString("issued_to", token_info->issued_to); |
| + response_dict.SetString("audience", token_info->audience); |
| + response_dict.SetString("user_id", token_info->user_id); |
| + std::vector<std::string> scope_vector(token_info->scopes.begin(), |
| + token_info->scopes.end()); |
| + response_dict.SetString("scope", JoinString(scope_vector, " ")); |
| + response_dict.SetInteger("expires_in", token_info->expires_in); |
| + response_dict.SetString("email", token_info->email); |
| + FormatJSONResponse(response_dict, http_response); |
| } else { |
| - // Request not understood. |
| - return scoped_ptr<HttpResponse>(); |
| + http_response->set_code(net::HTTP_BAD_REQUEST); |
| + } |
| +} |
| + |
| +void FakeGaia::HandleIssueToken(const HttpRequest& request, |
| + BasicHttpResponse* http_response) { |
| + std::string access_token; |
| + std::string scope; |
| + std::string client_id; |
| + const AccessTokenInfo* token_info = NULL; |
| + if (GetAccessToken(request, kAuthHeaderBearer, &access_token) && |
| + GetQueryParameter(request.content, "scope", &scope) && |
| + GetQueryParameter(request.content, "client_id", &client_id) && |
| + (token_info = FindAccessTokenInfo(access_token, client_id, scope))) { |
| + base::DictionaryValue response_dict; |
| + response_dict.SetString("issueAdvice", "auto"); |
| + response_dict.SetString("expiresIn", |
| + base::IntToString(token_info->expires_in)); |
| + response_dict.SetString("token", token_info->token); |
| + FormatJSONResponse(response_dict, http_response); |
| + } else { |
| + http_response->set_code(net::HTTP_BAD_REQUEST); |
| + } |
| +} |
| + |
| + |
| +scoped_ptr<HttpResponse> FakeGaia::HandleRequest(const HttpRequest& request) { |
| + // The scheme and host of the URL is actually not important but required to |
| + // get a valid GURL in order to parse |request.relative_url|. |
| + GURL request_url = GURL("http://localhost").Resolve(request.relative_url); |
| + std::string request_path = request_url.path(); |
| + scoped_ptr<BasicHttpResponse> http_response(new BasicHttpResponse()); |
| + RequestHandlerMap::iterator iter = request_handlers_.find(request_path); |
| + if (iter != request_handlers_.end()) { |
| + LOG(WARNING) << "Serving request " << request_path; |
| + iter->second.Run(request, http_response.get()); |
| + } else { |
| + LOG(ERROR) << "Unhandled request " << request_path; |
| + return scoped_ptr<HttpResponse>(); // Request not understood. |
| } |
| return http_response.PassAs<HttpResponse>(); |
| @@ -167,16 +374,6 @@ void FakeGaia::RegisterSamlUser(const std::string& account_id, |
| saml_account_idp_map_[account_id] = saml_idp; |
| } |
| -// static |
| -bool FakeGaia::GetQueryParameter(const std::string& query, |
| - const std::string& key, |
| - std::string* value) { |
| - // Name and scheme actually don't matter, but are required to get a valid URL |
| - // for parsing. |
| - GURL query_url("http://localhost?" + query); |
| - return net::GetValueForKeyInQuery(query_url, key, value); |
| -} |
| - |
| void FakeGaia::FormatJSONResponse(const base::DictionaryValue& response_dict, |
| BasicHttpResponse* http_response) { |
| std::string response_json; |
| @@ -185,7 +382,7 @@ void FakeGaia::FormatJSONResponse(const base::DictionaryValue& response_dict, |
| http_response->set_code(net::HTTP_OK); |
| } |
| -const FakeGaia::AccessTokenInfo* FakeGaia::GetAccessTokenInfo( |
| +const FakeGaia::AccessTokenInfo* FakeGaia::FindAccessTokenInfo( |
| const std::string& auth_token, |
| const std::string& client_id, |
| const std::string& scope_string) const { |
| @@ -208,3 +405,30 @@ const FakeGaia::AccessTokenInfo* FakeGaia::GetAccessTokenInfo( |
| return NULL; |
| } |
| + |
| +// static |
| +bool FakeGaia::GetQueryParameter(const std::string& query, |
| + const std::string& key, |
| + std::string* value) { |
| + // Name and scheme actually don't matter, but are required to get a valid URL |
| + // for parsing. |
| + GURL query_url("http://localhost?" + query); |
| + return net::GetValueForKeyInQuery(query_url, key, value); |
| +} |
| + |
| +// static |
| +bool FakeGaia::GetAccessToken(const HttpRequest& request, |
| + const char* auth_token_prefix, |
| + std::string* access_token) { |
| + std::map<std::string, std::string>::const_iterator auth_header_entry = |
| + request.headers.find("Authorization"); |
| + if (auth_header_entry != request.headers.end()) { |
| + if (StartsWithASCII(auth_header_entry->second, auth_token_prefix, true)) { |
| + *access_token = auth_header_entry->second.substr( |
| + strlen(auth_token_prefix)); |
| + return true; |
| + } |
| + } |
| + |
| + return false; |
| +} |