123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554 |
- // Copyright (c) 2012 The Chromium Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style license that can be
- // found in the LICENSE file.
- //
- // A complete set of unit tests for GaiaOAuthClient.
- #include <string>
- #include <utility>
- #include <vector>
- #include "base/bind.h"
- #include "base/json/json_reader.h"
- #include "base/memory/raw_ptr.h"
- #include "base/strings/string_number_conversions.h"
- #include "base/test/task_environment.h"
- #include "base/time/tick_clock.h"
- #include "base/values.h"
- #include "google_apis/gaia/gaia_oauth_client.h"
- #include "net/base/net_errors.h"
- #include "net/http/http_request_headers.h"
- #include "net/http/http_status_code.h"
- #include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
- #include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
- #include "services/network/test/test_url_loader_factory.h"
- #include "services/network/test/test_utils.h"
- #include "testing/gmock/include/gmock/gmock.h"
- #include "testing/gtest/include/gtest/gtest.h"
- #include "url/gurl.h"
- using ::testing::_;
- using ::testing::Eq;
- using ::testing::HasSubstr;
- using ::testing::Pointee;
- using ::testing::SaveArg;
- namespace {
- // Simulates some number of failures, followed by an optional success.
- // Does not distinguish between different URLs.
- class ResponseInjector {
- public:
- explicit ResponseInjector(network::TestURLLoaderFactory* url_loader_factory)
- : url_loader_factory_(url_loader_factory),
- response_code_(net::HTTP_OK),
- complete_immediately_(true),
- current_failure_count_(0),
- max_failure_count_(0) {
- url_loader_factory->SetInterceptor(
- base::BindRepeating(&ResponseInjector::AdjustResponseBasedOnSettings,
- base::Unretained(this)));
- }
- ResponseInjector(const ResponseInjector&) = delete;
- ResponseInjector& operator=(const ResponseInjector&) = delete;
- ~ResponseInjector() {
- url_loader_factory_->SetInterceptor(
- base::BindRepeating([](const network::ResourceRequest& request) {
- ADD_FAILURE() << "Unexpected fetch of:" << request.url;
- }));
- }
- void AdjustResponseBasedOnSettings(const network::ResourceRequest& request) {
- url_loader_factory_->ClearResponses();
- DCHECK(pending_url_.is_empty());
- pending_url_ = request.url;
- if (complete_immediately_) {
- Finish();
- }
- }
- void Finish() {
- net::HttpStatusCode response_code = response_code_;
- if (response_code_ != net::HTTP_OK && (max_failure_count_ != -1) &&
- (current_failure_count_ == max_failure_count_))
- response_code = net::HTTP_OK;
- if (response_code != net::HTTP_OK)
- ++current_failure_count_;
- url_loader_factory_->AddResponse(pending_url_.spec(), results_,
- response_code);
- pending_url_ = GURL();
- }
- std::string GetUploadData() {
- const std::vector<network::TestURLLoaderFactory::PendingRequest>& pending =
- *url_loader_factory_->pending_requests();
- if (pending.size() == 1) {
- return network::GetUploadData(pending[0].request);
- } else {
- ADD_FAILURE() << "Unexpected state in GetUploadData";
- return "";
- }
- }
- const net::HttpRequestHeaders GetRequestHeaders() {
- const std::vector<network::TestURLLoaderFactory::PendingRequest>& pending =
- *url_loader_factory_->pending_requests();
- if (pending.size() == 1) {
- return pending[0].request.headers;
- } else {
- ADD_FAILURE() << "Unexpected state in GetRequestHeaders";
- return {};
- }
- }
- void set_response_code(int response_code) {
- response_code_ = static_cast<net::HttpStatusCode>(response_code);
- }
- void set_max_failure_count(int count) {
- max_failure_count_ = count;
- }
- void set_results(const std::string& results) {
- results_ = results;
- }
- void set_complete_immediately(bool complete_immediately) {
- complete_immediately_ = complete_immediately;
- }
- private:
- raw_ptr<network::TestURLLoaderFactory> url_loader_factory_;
- GURL pending_url_;
- net::HttpStatusCode response_code_;
- bool complete_immediately_;
- int current_failure_count_;
- int max_failure_count_;
- std::string results_;
- };
- const std::string kTestAccessToken = "1/fFAGRNJru1FTz70BzhT3Zg";
- const std::string kTestAccessTokenHandle = "1/kjhH87dfgkj87Hhj5KJkjZ";
- const std::string kTestRefreshToken =
- "1/6BMfW9j53gdGImsixUH6kU5RsR4zwI9lUVX-tqf8JXQ";
- const std::string kTestUserEmail = "a_user@gmail.com";
- const std::string kTestUserId = "8675309";
- const int kTestExpiresIn = 3920;
- const std::string kDummyGetTokensResult =
- "{\"access_token\":\"" + kTestAccessToken + "\","
- "\"expires_in\":" + base::NumberToString(kTestExpiresIn) + ","
- "\"refresh_token\":\"" + kTestRefreshToken + "\"}";
- const std::string kDummyRefreshTokenResult =
- "{\"access_token\":\"" + kTestAccessToken + "\","
- "\"expires_in\":" + base::NumberToString(kTestExpiresIn) + "}";
- const std::string kDummyUserInfoResult =
- "{\"email\":\"" + kTestUserEmail + "\"}";
- const std::string kDummyUserIdResult =
- "{\"id\":\"" + kTestUserId + "\"}";
- const std::string kDummyFullUserInfoResult =
- "{"
- "\"family_name\": \"Bar\", "
- "\"name\": \"Foo Bar\", "
- "\"picture\": \"https://lh4.googleusercontent.com/hash/photo.jpg\", "
- "\"locale\": \"en\", "
- "\"gender\": \"male\", "
- "\"link\": \"https://plus.google.com/+FooBar\", "
- "\"given_name\": \"Foo\", "
- "\"id\": \"12345678901234567890\""
- "}";
- const std::string kDummyTokenInfoResult =
- "{\"issued_to\": \"1234567890.apps.googleusercontent.com\","
- "\"audience\": \"1234567890.apps.googleusercontent.com\","
- "\"scope\": \"https://googleapis.com/oauth2/v2/tokeninfo\","
- "\"expires_in\":" + base::NumberToString(kTestExpiresIn) + "}";
- const std::string kDummyTokenHandleInfoResult =
- "{\"audience\": \"1234567890.apps.googleusercontent.com\","
- "\"expires_in\":" + base::NumberToString(kTestExpiresIn) + "}";
- const std::string kDummyAccountCapabilitiesResult =
- "{\"accountCapabilities\": ["
- "{\"name\": \"accountcapabilities/111\", \"booleanValue\": false},"
- "{\"name\": \"accountcapabilities/222\", \"booleanValue\": true}"
- "]}";
- } // namespace
- namespace gaia {
- class MockGaiaOAuthClientDelegate : public gaia::GaiaOAuthClient::Delegate {
- public:
- MockGaiaOAuthClientDelegate() = default;
- MockGaiaOAuthClientDelegate(const MockGaiaOAuthClientDelegate&) = delete;
- MockGaiaOAuthClientDelegate& operator=(const MockGaiaOAuthClientDelegate&) =
- delete;
- MOCK_METHOD3(OnGetTokensResponse,
- void(const std::string& refresh_token,
- const std::string& access_token,
- int expires_in_seconds));
- MOCK_METHOD2(OnRefreshTokenResponse,
- void(const std::string& access_token, int expires_in_seconds));
- MOCK_METHOD1(OnGetUserEmailResponse, void(const std::string& user_email));
- MOCK_METHOD1(OnGetUserIdResponse, void(const std::string& user_id));
- MOCK_METHOD1(OnGetUserInfoResponse,
- void(std::unique_ptr<base::DictionaryValue> user_info));
- MOCK_METHOD1(OnGetTokenInfoResponse,
- void(std::unique_ptr<base::DictionaryValue> token_info));
- MOCK_METHOD1(OnGetAccountCapabilitiesResponse,
- void(std::unique_ptr<base::Value> account_capabilities));
- MOCK_METHOD0(OnOAuthError, void());
- MOCK_METHOD1(OnNetworkError, void(int response_code));
- };
- class GaiaOAuthClientTest : public testing::Test {
- protected:
- GaiaOAuthClientTest()
- : task_environment_(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
- void SetUp() override {
- client_info_.client_id = "test_client_id";
- client_info_.client_secret = "test_client_secret";
- client_info_.redirect_uri = "test_redirect_uri";
- }
- scoped_refptr<network::SharedURLLoaderFactory> GetSharedURLLoaderFactory() {
- return base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
- &url_loader_factory_);
- }
- void FlushNetwork() {
- // An event loop spin is required for things to be delivered from
- // TestURLLoaderFactory to its clients via mojo pipes. In addition,
- // some retries may have back off, so may need to advance (mock) time
- // for them to finish, too.
- task_environment_.FastForwardUntilNoTasksRemain();
- }
- protected:
- void TestAccountCapabilitiesUploadData(
- const std::vector<std::string>& capabilities_names,
- const std::string& expected_body) {
- ResponseInjector injector(&url_loader_factory_);
- injector.set_complete_immediately(false);
- MockGaiaOAuthClientDelegate delegate;
- GaiaOAuthClient auth(GetSharedURLLoaderFactory());
- auth.GetAccountCapabilities("some_token", capabilities_names, 1, &delegate);
- EXPECT_EQ(injector.GetUploadData(), expected_body);
- }
- base::test::TaskEnvironment task_environment_;
- network::TestURLLoaderFactory url_loader_factory_;
- OAuthClientInfo client_info_;
- };
- TEST_F(GaiaOAuthClientTest, NetworkFailure) {
- int response_code = net::HTTP_INTERNAL_SERVER_ERROR;
- MockGaiaOAuthClientDelegate delegate;
- EXPECT_CALL(delegate, OnNetworkError(response_code))
- .Times(1);
- ResponseInjector injector(&url_loader_factory_);
- injector.set_response_code(response_code);
- injector.set_max_failure_count(4);
- GaiaOAuthClient auth(GetSharedURLLoaderFactory());
- auth.GetTokensFromAuthCode(client_info_, "auth_code", 2, &delegate);
- FlushNetwork();
- }
- TEST_F(GaiaOAuthClientTest, NetworkFailureRecover) {
- int response_code = net::HTTP_INTERNAL_SERVER_ERROR;
- MockGaiaOAuthClientDelegate delegate;
- EXPECT_CALL(delegate, OnGetTokensResponse(kTestRefreshToken, kTestAccessToken,
- kTestExpiresIn)).Times(1);
- ResponseInjector injector(&url_loader_factory_);
- injector.set_response_code(response_code);
- injector.set_max_failure_count(4);
- injector.set_results(kDummyGetTokensResult);
- GaiaOAuthClient auth(GetSharedURLLoaderFactory());
- auth.GetTokensFromAuthCode(client_info_, "auth_code", -1, &delegate);
- FlushNetwork();
- }
- TEST_F(GaiaOAuthClientTest, NetworkFailureRecoverBackoff) {
- // Make sure long backoffs are expontential.
- int response_code = net::HTTP_INTERNAL_SERVER_ERROR;
- MockGaiaOAuthClientDelegate delegate;
- EXPECT_CALL(delegate, OnGetTokensResponse(kTestRefreshToken, kTestAccessToken,
- kTestExpiresIn))
- .Times(1);
- ResponseInjector injector(&url_loader_factory_);
- injector.set_response_code(response_code);
- injector.set_max_failure_count(21);
- injector.set_results(kDummyGetTokensResult);
- base::TimeTicks start = task_environment_.GetMockTickClock()->NowTicks();
- GaiaOAuthClient auth(GetSharedURLLoaderFactory());
- auth.GetTokensFromAuthCode(client_info_, "auth_code", -1, &delegate);
- FlushNetwork();
- // Default params are:
- // 40% jitter, 700ms initial, 1.4 exponent, ignore first 2 failures.
- // So after 19 retries, delay is at least:
- // 0.6 * 700ms * 1.4^(19-2) ~ 128s
- // After 20:
- // 0.6 * 700ms * 1.4^(20-2) ~ 179s
- //
- // ... so the whole thing should take at least 307s
- EXPECT_GE(task_environment_.GetMockTickClock()->NowTicks() - start,
- base::Seconds(307));
- }
- TEST_F(GaiaOAuthClientTest, OAuthFailure) {
- int response_code = net::HTTP_BAD_REQUEST;
- MockGaiaOAuthClientDelegate delegate;
- EXPECT_CALL(delegate, OnOAuthError()).Times(1);
- ResponseInjector injector(&url_loader_factory_);
- injector.set_response_code(response_code);
- injector.set_max_failure_count(-1);
- injector.set_results(kDummyGetTokensResult);
- GaiaOAuthClient auth(GetSharedURLLoaderFactory());
- auth.GetTokensFromAuthCode(client_info_, "auth_code", -1, &delegate);
- FlushNetwork();
- }
- TEST_F(GaiaOAuthClientTest, GetTokensSuccess) {
- MockGaiaOAuthClientDelegate delegate;
- EXPECT_CALL(delegate, OnGetTokensResponse(kTestRefreshToken, kTestAccessToken,
- kTestExpiresIn)).Times(1);
- ResponseInjector injector(&url_loader_factory_);
- injector.set_results(kDummyGetTokensResult);
- GaiaOAuthClient auth(GetSharedURLLoaderFactory());
- auth.GetTokensFromAuthCode(client_info_, "auth_code", -1, &delegate);
- FlushNetwork();
- }
- TEST_F(GaiaOAuthClientTest, GetTokensAfterNetworkFailure) {
- int response_code = net::HTTP_INTERNAL_SERVER_ERROR;
- MockGaiaOAuthClientDelegate failure_delegate;
- EXPECT_CALL(failure_delegate, OnNetworkError(response_code)).Times(1);
- MockGaiaOAuthClientDelegate success_delegate;
- EXPECT_CALL(success_delegate, OnGetTokensResponse(kTestRefreshToken,
- kTestAccessToken, kTestExpiresIn)).Times(1);
- ResponseInjector injector(&url_loader_factory_);
- injector.set_response_code(response_code);
- injector.set_max_failure_count(4);
- injector.set_results(kDummyGetTokensResult);
- GaiaOAuthClient auth(GetSharedURLLoaderFactory());
- auth.GetTokensFromAuthCode(client_info_, "auth_code", 2, &failure_delegate);
- FlushNetwork();
- auth.GetTokensFromAuthCode(client_info_, "auth_code", -1, &success_delegate);
- FlushNetwork();
- }
- TEST_F(GaiaOAuthClientTest, RefreshTokenSuccess) {
- MockGaiaOAuthClientDelegate delegate;
- EXPECT_CALL(delegate, OnRefreshTokenResponse(kTestAccessToken,
- kTestExpiresIn)).Times(1);
- ResponseInjector injector(&url_loader_factory_);
- injector.set_results(kDummyRefreshTokenResult);
- injector.set_complete_immediately(false);
- GaiaOAuthClient auth(GetSharedURLLoaderFactory());
- auth.RefreshToken(client_info_, "refresh_token", std::vector<std::string>(),
- -1, &delegate);
- EXPECT_THAT(injector.GetUploadData(), Not(HasSubstr("scope")));
- injector.Finish();
- FlushNetwork();
- }
- TEST_F(GaiaOAuthClientTest, RefreshTokenDownscopingSuccess) {
- MockGaiaOAuthClientDelegate delegate;
- EXPECT_CALL(delegate, OnRefreshTokenResponse(kTestAccessToken,
- kTestExpiresIn)).Times(1);
- ResponseInjector injector(&url_loader_factory_);
- injector.set_results(kDummyRefreshTokenResult);
- injector.set_complete_immediately(false);
- GaiaOAuthClient auth(GetSharedURLLoaderFactory());
- auth.RefreshToken(client_info_, "refresh_token",
- std::vector<std::string>(1, "scope4test"), -1, &delegate);
- EXPECT_THAT(injector.GetUploadData(), HasSubstr("&scope=scope4test"));
- injector.Finish();
- FlushNetwork();
- }
- TEST_F(GaiaOAuthClientTest, GetUserEmail) {
- MockGaiaOAuthClientDelegate delegate;
- EXPECT_CALL(delegate, OnGetUserEmailResponse(kTestUserEmail)).Times(1);
- ResponseInjector injector(&url_loader_factory_);
- injector.set_results(kDummyUserInfoResult);
- GaiaOAuthClient auth(GetSharedURLLoaderFactory());
- auth.GetUserEmail("access_token", 1, &delegate);
- FlushNetwork();
- }
- TEST_F(GaiaOAuthClientTest, GetUserId) {
- MockGaiaOAuthClientDelegate delegate;
- EXPECT_CALL(delegate, OnGetUserIdResponse(kTestUserId)).Times(1);
- ResponseInjector injector(&url_loader_factory_);
- injector.set_results(kDummyUserIdResult);
- GaiaOAuthClient auth(GetSharedURLLoaderFactory());
- auth.GetUserId("access_token", 1, &delegate);
- FlushNetwork();
- }
- TEST_F(GaiaOAuthClientTest, GetUserInfo) {
- std::unique_ptr<base::DictionaryValue> captured_result;
- MockGaiaOAuthClientDelegate delegate;
- EXPECT_CALL(delegate, OnGetUserInfoResponse(_))
- .WillOnce([&](std::unique_ptr<base::DictionaryValue> result) {
- captured_result = std::move(result);
- });
- ResponseInjector injector(&url_loader_factory_);
- injector.set_results(kDummyFullUserInfoResult);
- GaiaOAuthClient auth(GetSharedURLLoaderFactory());
- auth.GetUserInfo("access_token", 1, &delegate);
- FlushNetwork();
- absl::optional<base::Value> expected_value =
- base::JSONReader::Read(kDummyFullUserInfoResult);
- DCHECK(expected_value);
- ASSERT_TRUE(expected_value->is_dict());
- EXPECT_EQ(*expected_value, *captured_result);
- }
- TEST_F(GaiaOAuthClientTest, GetTokenInfo) {
- std::unique_ptr<base::DictionaryValue> captured_result;
- MockGaiaOAuthClientDelegate delegate;
- EXPECT_CALL(delegate, OnGetTokenInfoResponse(_))
- .WillOnce([&](std::unique_ptr<base::DictionaryValue> result) {
- captured_result = std::move(result);
- });
- ResponseInjector injector(&url_loader_factory_);
- injector.set_results(kDummyTokenInfoResult);
- GaiaOAuthClient auth(GetSharedURLLoaderFactory());
- auth.GetTokenInfo("some_token", 1, &delegate);
- FlushNetwork();
- std::string issued_to;
- ASSERT_TRUE(captured_result->GetString("issued_to", &issued_to));
- ASSERT_EQ("1234567890.apps.googleusercontent.com", issued_to);
- }
- TEST_F(GaiaOAuthClientTest, GetTokenHandleInfo) {
- std::unique_ptr<base::DictionaryValue> captured_result;
- MockGaiaOAuthClientDelegate delegate;
- EXPECT_CALL(delegate, OnGetTokenInfoResponse(_))
- .WillOnce([&](std::unique_ptr<base::DictionaryValue> result) {
- captured_result = std::move(result);
- });
- ResponseInjector injector(&url_loader_factory_);
- injector.set_results(kDummyTokenHandleInfoResult);
- GaiaOAuthClient auth(GetSharedURLLoaderFactory());
- auth.GetTokenHandleInfo("some_handle", 1, &delegate);
- FlushNetwork();
- std::string audience;
- ASSERT_TRUE(captured_result->GetString("audience", &audience));
- ASSERT_EQ("1234567890.apps.googleusercontent.com", audience);
- }
- TEST_F(GaiaOAuthClientTest, GetAccountCapabilities) {
- std::unique_ptr<base::Value> captured_result;
- MockGaiaOAuthClientDelegate delegate;
- EXPECT_CALL(delegate, OnGetAccountCapabilitiesResponse(_))
- .WillOnce([&](std::unique_ptr<base::Value> result) {
- captured_result = std::move(result);
- });
- ResponseInjector injector(&url_loader_factory_);
- injector.set_results(kDummyAccountCapabilitiesResult);
- injector.set_complete_immediately(false);
- GaiaOAuthClient auth(GetSharedURLLoaderFactory());
- auth.GetAccountCapabilities("some_token",
- {"capability1", "capability2", "capability3"}, 1,
- &delegate);
- std::string actual_authorization_header;
- EXPECT_TRUE(injector.GetRequestHeaders().GetHeader(
- "Authorization", &actual_authorization_header));
- EXPECT_EQ(actual_authorization_header, "Bearer some_token");
- std::string actual_method_override_header;
- EXPECT_TRUE(injector.GetRequestHeaders().GetHeader(
- "X-HTTP-Method-Override", &actual_method_override_header));
- EXPECT_EQ(actual_method_override_header, "GET");
- EXPECT_EQ(injector.GetUploadData(),
- "names=capability1&names=capability2&names=capability3");
- injector.Finish();
- FlushNetwork();
- auto capabilities =
- captured_result->FindListKey("accountCapabilities")->GetListDeprecated();
- ASSERT_EQ(capabilities.size(), 2U);
- EXPECT_EQ(*capabilities[0].FindStringKey("name"), "accountcapabilities/111");
- EXPECT_FALSE(*capabilities[0].FindBoolKey("booleanValue"));
- EXPECT_EQ(*capabilities[1].FindStringKey("name"), "accountcapabilities/222");
- EXPECT_TRUE(*capabilities[1].FindBoolKey("booleanValue"));
- }
- TEST_F(GaiaOAuthClientTest,
- GetAccountCapabilities_UploadData_OneCapabilityName) {
- TestAccountCapabilitiesUploadData({"capability"},
- /*expected_body=*/"names=capability");
- }
- TEST_F(GaiaOAuthClientTest,
- GetAccountCapabilities_UploadData_MultipleCapabilityNames) {
- TestAccountCapabilitiesUploadData(
- {"capability1", "capability2", "capability3"},
- /*expected_body=*/
- "names=capability1&names=capability2&names=capability3");
- }
- } // namespace gaia
|