From 8549686920da176eec64cddda42ba925b58eff20 Mon Sep 17 00:00:00 2001 From: "zelidrag@chromium.org" Date: Tue, 17 Dec 2013 22:26:55 +0000 Subject: Added CrOS-specific OAuth2 browser test. Expanded FakeGaia class to cover all other auth related calls. BUG=none TEST=OAuth2Test.* R=rogerta@chromium.org, xiyuan@chromium.org Review URL: https://codereview.chromium.org/99863007 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@241389 0039d316-1c4b-4281-b951-d872f2087c98 --- google_apis/gaia/fake_gaia.cc | 439 ++++++++++++++++++++++++++++++++---------- google_apis/gaia/fake_gaia.h | 64 +++++- 2 files changed, 396 insertions(+), 107 deletions(-) (limited to 'google_apis') diff --git a/google_apis/gaia/fake_gaia.cc b/google_apis/gaia/fake_gaia.cc index 7474aca8..9eddd85 100644 --- a/google_apis/gaia/fake_gaia.cc +++ b/google_apis/gaia/fake_gaia.cc @@ -7,6 +7,8 @@ #include #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,25 @@ #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)))) + +#define REGISTER_PATH_RESPONSE_HANDLER(path, method) \ + request_handlers_.insert(std::make_pair( \ + 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 +62,310 @@ FakeGaia::FakeGaia() { FakeGaia::~FakeGaia() {} -scoped_ptr 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); + + // Handles /SSO GAIA call (not GAIA, made up for SAML tests). + REGISTER_PATH_RESPONSE_HANDLER("/SSO", HandleSSO); +} + +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 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); +} + +void FakeGaia::HandleSSO(const HttpRequest& request, + 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); - 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 { + 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 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::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 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 { + 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 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 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 { - // Request not understood. - return scoped_ptr(); + LOG(ERROR) << "Unhandled request " << request_path; + return scoped_ptr(); // Request not understood. } return http_response.PassAs(); @@ -167,16 +381,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 +389,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 +412,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::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; +} diff --git a/google_apis/gaia/fake_gaia.h b/google_apis/gaia/fake_gaia.h index 7e211ae..6052c2b 100644 --- a/google_apis/gaia/fake_gaia.h +++ b/google_apis/gaia/fake_gaia.h @@ -10,6 +10,7 @@ #include #include "base/basictypes.h" +#include "base/callback.h" #include "base/memory/scoped_ptr.h" #include "url/gurl.h" @@ -49,6 +50,18 @@ class FakeGaia { FakeGaia(); ~FakeGaia(); + // Sets the initial value of tokens and cookies. + void 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); + + // Initializes HTTP request handlers. Should be called after switches + // for tweaking GaiaUrls are in place. + void Initialize(); + // Handles a request and returns a response if the request was recognized as a // GAIA request. Note that this respects the switches::kGaiaUrl and friends so // that this can used with EmbeddedTestServer::RegisterRequestHandler(). @@ -81,16 +94,61 @@ class FakeGaia { void FormatJSONResponse(const base::DictionaryValue& response_dict, net::test_server::BasicHttpResponse* http_response); + typedef base::Callback + HttpRequestHandlerCallback; + typedef std::map RequestHandlerMap; + + // HTTP request handlers. + void HandleProgramaticAuth( + const net::test_server::HttpRequest& request, + net::test_server::BasicHttpResponse* http_response); + void HandleServiceLogin(const net::test_server::HttpRequest& request, + net::test_server::BasicHttpResponse* http_response); + void HandleOAuthLogin(const net::test_server::HttpRequest& request, + net::test_server::BasicHttpResponse* http_response); + void HandleSSO(const net::test_server::HttpRequest& request, + net::test_server::BasicHttpResponse* http_response); + void HandleMergeSession(const net::test_server::HttpRequest& request, + net::test_server::BasicHttpResponse* http_response); + void HandleServiceLoginAuth( + const net::test_server::HttpRequest& request, + net::test_server::BasicHttpResponse* http_response); + void HandleAuthToken(const net::test_server::HttpRequest& request, + net::test_server::BasicHttpResponse* http_response); + void HandleTokenInfo(const net::test_server::HttpRequest& request, + net::test_server::BasicHttpResponse* http_response); + void HandleIssueToken(const net::test_server::HttpRequest& request, + net::test_server::BasicHttpResponse* http_response); + // Returns the access token associated with |auth_token| that matches the // given |client_id| and |scope_string|. If |scope_string| is empty, the first // token satisfying the other criteria is returned. Returns NULL if no token // matches. - const AccessTokenInfo* GetAccessTokenInfo(const std::string& auth_token, - const std::string& client_id, - const std::string& scope_string) + const AccessTokenInfo* FindAccessTokenInfo(const std::string& auth_token, + const std::string& client_id, + const std::string& scope_string) const; + // Extracts the |access_token| from authorization header of |request|. + static bool GetAccessToken(const net::test_server::HttpRequest& request, + const char* auth_token_prefix, + std::string* access_token); + + // auth_code cookie value response for /o/oauth2/programmatic_auth call. + std::string fake_auth_code_; + + // refresh_token field value response for the initial /o/oauth2/token call + // with ...&grant_type=authorization_code. + std::string fake_refresh_token_; + std::string fake_access_token_; + std::string fake_gaia_uber_token_; + std::string fake_session_sid_cookie_; + std::string fake_session_lsid_cookie_; + AccessTokenInfoMap access_token_info_map_; + RequestHandlerMap request_handlers_; std::string service_login_response_; SamlAccountIdpMap saml_account_idp_map_; -- cgit v1.1