blob: cf982e4ba581286a5fb8d3aa2a3f8ab92d61e549 [file] [log] [blame]
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/signin/force_signin_verifier.h"
#include "base/functional/bind.h"
#include "base/run_loop.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/task_environment.h"
#include "chrome/browser/profiles/profile.h"
#include "components/signin/public/identity_manager/identity_test_environment.h"
#include "content/public/browser/network_service_instance.h"
#include "services/network/test/test_network_connection_tracker.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
class ForceSigninVerifierWithAccessToInternalsForTesting
: public ForceSigninVerifier {
public:
explicit ForceSigninVerifierWithAccessToInternalsForTesting(
signin::IdentityManager* identity_manager)
: ForceSigninVerifier(
nullptr,
identity_manager,
base::BindOnce(&ForceSigninVerifierWithAccessToInternalsForTesting::
OnTokenFetchComplete,
base::Unretained(this))) {}
bool IsDelayTaskPosted() { return GetOneShotTimerForTesting()->IsRunning(); }
int FailureCount() { return GetBackoffEntryForTesting()->failure_count(); }
signin::PrimaryAccountAccessTokenFetcher* access_token_fetcher() {
return GetAccessTokenFetcherForTesting();
}
// Three states possible:
// - token_is_valid_.has_value() == false, meaning the token is not set yet.
// - token_is_valid_.value() == true, meanig the token is set and valid.
// - token_is_valid_.value() == false, meanig the token is set and invalid.
std::optional<bool> GetTokenIsValid() { return token_is_valid_; }
void OnTokenFetchComplete(bool token_is_valid) {
token_is_valid_ = token_is_valid;
}
bool IsRequestWaitingForRefreshToken() const {
return GetRequestIsWaitingForRefreshTokensForTesting();
}
public:
std::optional<bool> token_is_valid_;
};
// A NetworkConnectionObserver that invokes a base::RepeatingClosure when
// NetworkConnectionObserver::OnConnectionChanged() is invoked.
class NetworkConnectionObserverHelper
: public network::NetworkConnectionTracker::NetworkConnectionObserver {
public:
explicit NetworkConnectionObserverHelper(base::RepeatingClosure closure)
: closure_(std::move(closure)) {
DCHECK(!closure_.is_null());
content::GetNetworkConnectionTracker()->AddNetworkConnectionObserver(this);
}
NetworkConnectionObserverHelper(const NetworkConnectionObserverHelper&) =
delete;
NetworkConnectionObserverHelper& operator=(
const NetworkConnectionObserverHelper&) = delete;
~NetworkConnectionObserverHelper() override {
content::GetNetworkConnectionTracker()->RemoveNetworkConnectionObserver(
this);
}
void OnConnectionChanged(network::mojom::ConnectionType type) override {
closure_.Run();
}
private:
base::RepeatingClosure closure_;
};
// Used to select which type of network type NetworkConnectionTracker should
// be configured to.
enum class NetworkConnectionType {
Undecided,
ConnectionNone,
ConnectionWifi,
Connection4G,
};
// Used to select which type of response NetworkConnectionTracker should give.
enum class NetworkResponseType {
Undecided,
Synchronous,
Asynchronous,
};
// Forces the network connection type to change to |connection_type| and wait
// till the notification has been propagated to the observers. Also change the
// response type to be synchronous/asynchronous based on |response_type|.
void ConfigureNetworkConnectionTracker(NetworkConnectionType connection_type,
NetworkResponseType response_type) {
network::TestNetworkConnectionTracker* tracker =
network::TestNetworkConnectionTracker::GetInstance();
switch (response_type) {
case NetworkResponseType::Undecided:
// nothing to do
break;
case NetworkResponseType::Synchronous:
tracker->SetRespondSynchronously(true);
break;
case NetworkResponseType::Asynchronous:
tracker->SetRespondSynchronously(false);
break;
}
if (connection_type != NetworkConnectionType::Undecided) {
network::mojom::ConnectionType mojom_connection_type =
network::mojom::ConnectionType::CONNECTION_UNKNOWN;
switch (connection_type) {
case NetworkConnectionType::Undecided:
NOTREACHED();
case NetworkConnectionType::ConnectionNone:
mojom_connection_type = network::mojom::ConnectionType::CONNECTION_NONE;
break;
case NetworkConnectionType::ConnectionWifi:
mojom_connection_type = network::mojom::ConnectionType::CONNECTION_WIFI;
break;
case NetworkConnectionType::Connection4G:
mojom_connection_type = network::mojom::ConnectionType::CONNECTION_4G;
break;
}
DCHECK_NE(mojom_connection_type,
network::mojom::ConnectionType::CONNECTION_UNKNOWN);
base::RunLoop wait_for_network_type_change;
NetworkConnectionObserverHelper scoped_observer(
wait_for_network_type_change.QuitWhenIdleClosure());
tracker->SetConnectionType(mojom_connection_type);
wait_for_network_type_change.Run();
}
}
// Forces the current sequence's task runner to spin. This is used because the
// ForceSigninVerifier ends up posting task to the sequence's task runner when
// MetworkConnectionTracker is returning results asynchronously.
void SpinCurrentSequenceTaskRunner() {
base::RunLoop run_loop;
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, run_loop.QuitClosure());
run_loop.Run();
}
} // namespace
TEST(ForceSigninVerifierTest, OnGetTokenSuccess) {
base::test::TaskEnvironment scoped_task_env;
signin::IdentityTestEnvironment identity_test_env;
const AccountInfo account_info =
identity_test_env.MakePrimaryAccountAvailable(
"email@test.com", signin::ConsentLevel::kSync);
ForceSigninVerifierWithAccessToInternalsForTesting verifier(
identity_test_env.identity_manager());
ASSERT_NE(nullptr, verifier.access_token_fetcher());
ASSERT_FALSE(verifier.IsDelayTaskPosted());
ASSERT_FALSE(verifier.GetTokenIsValid().has_value());
identity_test_env.WaitForAccessTokenRequestIfNecessaryAndRespondWithToken(
account_info.account_id, /*token=*/"", base::Time());
ASSERT_EQ(nullptr, verifier.access_token_fetcher());
std::optional<bool> token = verifier.GetTokenIsValid().has_value();
ASSERT_TRUE(token.has_value());
ASSERT_TRUE(token.value());
ASSERT_FALSE(verifier.IsDelayTaskPosted());
ASSERT_EQ(0, verifier.FailureCount());
}
TEST(ForceSigninVerifierTest, OnGetTokenWaitForRefreshTokenThenSuccess) {
base::test::TaskEnvironment scoped_task_env;
signin::IdentityTestEnvironment identity_test_env;
const AccountInfo account_info =
identity_test_env.MakePrimaryAccountAvailable(
"email@test.com", signin::ConsentLevel::kSync);
// Simulate a reset to make the refresh tokens unavailable at first.
identity_test_env.ResetToAccountsNotYetLoadedFromDiskState();
ForceSigninVerifierWithAccessToInternalsForTesting verifier(
identity_test_env.identity_manager());
EXPECT_TRUE(verifier.IsRequestWaitingForRefreshToken());
// Simlate a relaod to make the refresh tokens available.
identity_test_env.ReloadAccountsFromDisk();
identity_test_env.WaitForRefreshTokensLoaded();
EXPECT_FALSE(verifier.IsRequestWaitingForRefreshToken());
EXPECT_FALSE(verifier.GetTokenIsValid().has_value());
EXPECT_NE(nullptr, verifier.access_token_fetcher());
identity_test_env.WaitForAccessTokenRequestIfNecessaryAndRespondWithToken(
account_info.account_id, /*token=*/"", base::Time());
ASSERT_EQ(nullptr, verifier.access_token_fetcher());
std::optional<bool> token = verifier.GetTokenIsValid().has_value();
ASSERT_TRUE(token.has_value());
ASSERT_TRUE(token.value());
ASSERT_FALSE(verifier.IsDelayTaskPosted());
ASSERT_EQ(0, verifier.FailureCount());
}
TEST(ForceSigninVerifierTest, OnGetTokenPersistentFailure) {
base::test::TaskEnvironment scoped_task_env;
signin::IdentityTestEnvironment identity_test_env;
const AccountInfo account_info =
identity_test_env.MakePrimaryAccountAvailable(
"email@test.com", signin::ConsentLevel::kSync);
ForceSigninVerifierWithAccessToInternalsForTesting verifier(
identity_test_env.identity_manager());
ASSERT_NE(nullptr, verifier.access_token_fetcher());
ASSERT_FALSE(verifier.IsDelayTaskPosted());
ASSERT_FALSE(verifier.GetTokenIsValid().has_value());
identity_test_env.WaitForAccessTokenRequestIfNecessaryAndRespondWithError(
GoogleServiceAuthError(
GoogleServiceAuthError::State::INVALID_GAIA_CREDENTIALS));
ASSERT_EQ(nullptr, verifier.access_token_fetcher());
std::optional<bool> token = verifier.GetTokenIsValid();
ASSERT_TRUE(token.has_value());
ASSERT_FALSE(token.value());
ASSERT_FALSE(verifier.IsDelayTaskPosted());
ASSERT_EQ(0, verifier.FailureCount());
}
TEST(ForceSigninVerifierTest, OnGetTokenTransientFailure) {
base::test::TaskEnvironment scoped_task_env;
signin::IdentityTestEnvironment identity_test_env;
const AccountInfo account_info =
identity_test_env.MakePrimaryAccountAvailable(
"email@test.com", signin::ConsentLevel::kSync);
ForceSigninVerifierWithAccessToInternalsForTesting verifier(
identity_test_env.identity_manager());
ASSERT_NE(nullptr, verifier.access_token_fetcher());
ASSERT_FALSE(verifier.IsDelayTaskPosted());
ASSERT_FALSE(verifier.GetTokenIsValid().has_value());
identity_test_env.WaitForAccessTokenRequestIfNecessaryAndRespondWithError(
GoogleServiceAuthError(GoogleServiceAuthError::State::CONNECTION_FAILED));
ASSERT_EQ(nullptr, verifier.access_token_fetcher());
ASSERT_FALSE(verifier.GetTokenIsValid().has_value());
ASSERT_TRUE(verifier.IsDelayTaskPosted());
ASSERT_EQ(1, verifier.FailureCount());
}
TEST(ForceSigninVerifierTest, OnLostConnection) {
base::test::TaskEnvironment scoped_task_env;
signin::IdentityTestEnvironment identity_test_env;
const AccountInfo account_info =
identity_test_env.MakePrimaryAccountAvailable(
"email@test.com", signin::ConsentLevel::kSync);
ForceSigninVerifierWithAccessToInternalsForTesting verifier(
identity_test_env.identity_manager());
identity_test_env.WaitForAccessTokenRequestIfNecessaryAndRespondWithError(
GoogleServiceAuthError(GoogleServiceAuthError::State::CONNECTION_FAILED));
ASSERT_EQ(1, verifier.FailureCount());
ASSERT_EQ(nullptr, verifier.access_token_fetcher());
ASSERT_TRUE(verifier.IsDelayTaskPosted());
ConfigureNetworkConnectionTracker(NetworkConnectionType::ConnectionNone,
NetworkResponseType::Undecided);
ASSERT_EQ(0, verifier.FailureCount());
ASSERT_EQ(nullptr, verifier.access_token_fetcher());
ASSERT_FALSE(verifier.IsDelayTaskPosted());
}
TEST(ForceSigninVerifierTest, OnReconnected) {
base::test::TaskEnvironment scoped_task_env;
signin::IdentityTestEnvironment identity_test_env;
const AccountInfo account_info =
identity_test_env.MakePrimaryAccountAvailable(
"email@test.com", signin::ConsentLevel::kSync);
ForceSigninVerifierWithAccessToInternalsForTesting verifier(
identity_test_env.identity_manager());
identity_test_env.WaitForAccessTokenRequestIfNecessaryAndRespondWithError(
GoogleServiceAuthError(GoogleServiceAuthError::State::CONNECTION_FAILED));
ASSERT_EQ(1, verifier.FailureCount());
ASSERT_EQ(nullptr, verifier.access_token_fetcher());
ASSERT_TRUE(verifier.IsDelayTaskPosted());
ConfigureNetworkConnectionTracker(NetworkConnectionType::ConnectionWifi,
NetworkResponseType::Undecided);
ASSERT_EQ(0, verifier.FailureCount());
ASSERT_NE(nullptr, verifier.access_token_fetcher());
ASSERT_FALSE(verifier.IsDelayTaskPosted());
}
TEST(ForceSigninVerifierTest, GetNetworkStatusAsync) {
base::test::TaskEnvironment scoped_task_env;
signin::IdentityTestEnvironment identity_test_env;
const AccountInfo account_info =
identity_test_env.MakePrimaryAccountAvailable(
"email@test.com", signin::ConsentLevel::kSync);
ConfigureNetworkConnectionTracker(NetworkConnectionType::Undecided,
NetworkResponseType::Asynchronous);
ForceSigninVerifierWithAccessToInternalsForTesting verifier(
identity_test_env.identity_manager());
// There is no network type at first.
ASSERT_EQ(nullptr, verifier.access_token_fetcher());
// Waiting for the network type returns.
SpinCurrentSequenceTaskRunner();
// Get the type and send the request.
ASSERT_NE(nullptr, verifier.access_token_fetcher());
}
TEST(ForceSigninVerifierTest, LaunchVerifierWithoutNetwork) {
base::test::TaskEnvironment scoped_task_env;
signin::IdentityTestEnvironment identity_test_env;
const AccountInfo account_info =
identity_test_env.MakePrimaryAccountAvailable(
"email@test.com", signin::ConsentLevel::kSync);
ConfigureNetworkConnectionTracker(NetworkConnectionType::ConnectionNone,
NetworkResponseType::Asynchronous);
ForceSigninVerifierWithAccessToInternalsForTesting verifier(
identity_test_env.identity_manager());
// There is no network type.
ASSERT_EQ(nullptr, verifier.access_token_fetcher());
// Waiting for the network type returns.
SpinCurrentSequenceTaskRunner();
// Get the type, there is no network connection, don't send the request.
ASSERT_EQ(nullptr, verifier.access_token_fetcher());
// Network is resumed.
ConfigureNetworkConnectionTracker(NetworkConnectionType::ConnectionWifi,
NetworkResponseType::Undecided);
// Send the request.
ASSERT_NE(nullptr, verifier.access_token_fetcher());
}
TEST(ForceSigninVerifierTest, ChangeNetworkFromWIFITo4GWithOnGoingRequest) {
base::test::TaskEnvironment scoped_task_env;
signin::IdentityTestEnvironment identity_test_env;
const AccountInfo account_info =
identity_test_env.MakePrimaryAccountAvailable(
"email@test.com", signin::ConsentLevel::kSync);
ConfigureNetworkConnectionTracker(NetworkConnectionType::ConnectionWifi,
NetworkResponseType::Asynchronous);
ForceSigninVerifierWithAccessToInternalsForTesting verifier(
identity_test_env.identity_manager());
EXPECT_EQ(nullptr, verifier.access_token_fetcher());
// Waiting for the network type returns.
SpinCurrentSequenceTaskRunner();
// The network type if wifi, send the request.
auto* first_request = verifier.access_token_fetcher();
EXPECT_NE(nullptr, first_request);
// Network is changed to 4G.
ConfigureNetworkConnectionTracker(NetworkConnectionType::Connection4G,
NetworkResponseType::Undecided);
// There is still one on-going request.
EXPECT_EQ(first_request, verifier.access_token_fetcher());
identity_test_env.WaitForAccessTokenRequestIfNecessaryAndRespondWithToken(
account_info.account_id, /*token=*/"", base::Time());
}
TEST(ForceSigninVerifierTest, ChangeNetworkFromWIFITo4GWithFinishedRequest) {
base::test::TaskEnvironment scoped_task_env;
signin::IdentityTestEnvironment identity_test_env;
const AccountInfo account_info =
identity_test_env.MakePrimaryAccountAvailable(
"email@test.com", signin::ConsentLevel::kSync);
ConfigureNetworkConnectionTracker(NetworkConnectionType::ConnectionWifi,
NetworkResponseType::Asynchronous);
ForceSigninVerifierWithAccessToInternalsForTesting verifier(
identity_test_env.identity_manager());
EXPECT_EQ(nullptr, verifier.access_token_fetcher());
// Waiting for the network type returns.
SpinCurrentSequenceTaskRunner();
// The network type if wifi, send the request.
EXPECT_NE(nullptr, verifier.access_token_fetcher());
// Finishes the request.
identity_test_env.WaitForAccessTokenRequestIfNecessaryAndRespondWithToken(
account_info.account_id, /*token=*/"", base::Time());
EXPECT_EQ(nullptr, verifier.access_token_fetcher());
// Network is changed to 4G.
ConfigureNetworkConnectionTracker(NetworkConnectionType::Connection4G,
NetworkResponseType::Undecided);
// No more request because it's verfied already.
EXPECT_EQ(nullptr, verifier.access_token_fetcher());
}
// Regression test for https://crbug.com/1259864
TEST(ForceSigninVerifierTest, DeleteWithPendingRequestShouldNotCrash) {
base::test::TaskEnvironment scoped_task_env;
signin::IdentityTestEnvironment identity_test_env;
const AccountInfo account_info =
identity_test_env.MakePrimaryAccountAvailable(
"email@test.com", signin::ConsentLevel::kSync);
ConfigureNetworkConnectionTracker(NetworkConnectionType::Undecided,
NetworkResponseType::Asynchronous);
{
ForceSigninVerifierWithAccessToInternalsForTesting verifier(
identity_test_env.identity_manager());
// There is no network type at first.
ASSERT_EQ(nullptr, verifier.access_token_fetcher());
// Delete the verifier while the request is pending.
}
// Waiting for the network type returns, this should not crash.
SpinCurrentSequenceTaskRunner();
}