diff options
Diffstat (limited to 'chromium/chrome/browser/signin/chrome_signin_client_unittest.cc')
-rw-r--r-- | chromium/chrome/browser/signin/chrome_signin_client_unittest.cc | 438 |
1 files changed, 438 insertions, 0 deletions
diff --git a/chromium/chrome/browser/signin/chrome_signin_client_unittest.cc b/chromium/chrome/browser/signin/chrome_signin_client_unittest.cc new file mode 100644 index 00000000000..ed2ac0db988 --- /dev/null +++ b/chromium/chrome/browser/signin/chrome_signin_client_unittest.cc @@ -0,0 +1,438 @@ +// Copyright 2015 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. + +#include "chrome/browser/signin/chrome_signin_client.h" +#include <memory> +#include <utility> + +#include "base/bind.h" +#include "base/cxx17_backports.h" +#include "base/feature_list.h" +#include "base/memory/raw_ptr.h" +#include "base/notreached.h" +#include "base/run_loop.h" +#include "build/build_config.h" +#include "build/chromeos_buildflags.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/enterprise/util/managed_browser_utils.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/profiles/profile_attributes_entry.h" +#include "chrome/browser/profiles/profile_attributes_storage.h" +#include "chrome/browser/profiles/profile_manager.h" +#include "chrome/browser/signin/chrome_signin_client_factory.h" +#include "chrome/browser/signin/identity_manager_factory.h" +#include "chrome/browser/signin/signin_features.h" +#include "chrome/browser/signin/signin_util.h" +#include "chrome/common/pref_names.h" +#include "chrome/test/base/testing_browser_process.h" +#include "chrome/test/base/testing_profile.h" +#include "chrome/test/base/testing_profile_manager.h" +#include "components/prefs/pref_service.h" +#include "components/signin/public/base/consent_level.h" +#include "components/signin/public/base/signin_pref_names.h" +#include "components/signin/public/identity_manager/identity_manager.h" +#include "components/signin/public/identity_manager/identity_test_environment.h" +#include "content/public/browser/network_service_instance.h" +#include "content/public/test/browser_task_environment.h" +#include "services/network/test/test_network_connection_tracker.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +#if !defined(OS_ANDROID) +#include "chrome/test/base/browser_with_test_window_test.h" +#endif + +// ChromeOS has its own network delay logic. +#if !BUILDFLAG(IS_CHROMEOS_ASH) + +namespace { + +class CallbackTester { + public: + CallbackTester() : called_(0) {} + + void Increment(); + void IncrementAndUnblock(base::RunLoop* run_loop); + bool WasCalledExactlyOnce(); + + private: + int called_; +}; + +void CallbackTester::Increment() { + called_++; +} + +void CallbackTester::IncrementAndUnblock(base::RunLoop* run_loop) { + Increment(); + run_loop->QuitWhenIdle(); +} + +bool CallbackTester::WasCalledExactlyOnce() { + return called_ == 1; +} + +} // namespace + +class ChromeSigninClientTest : public testing::Test { + public: + ChromeSigninClientTest() { + // Create a signed-in profile. + TestingProfile::Builder builder; + profile_ = builder.Build(); + + signin_client_ = ChromeSigninClientFactory::GetForProfile(profile()); + } + + protected: + void SetUpNetworkConnection(bool respond_synchronously, + network::mojom::ConnectionType connection_type) { + auto* tracker = network::TestNetworkConnectionTracker::GetInstance(); + tracker->SetRespondSynchronously(respond_synchronously); + tracker->SetConnectionType(connection_type); + } + + void SetConnectionType(network::mojom::ConnectionType connection_type) { + network::TestNetworkConnectionTracker::GetInstance()->SetConnectionType( + connection_type); + } + + Profile* profile() { return profile_.get(); } + SigninClient* signin_client() { return signin_client_; } + + private: + content::BrowserTaskEnvironment task_environment_; + std::unique_ptr<Profile> profile_; + raw_ptr<SigninClient> signin_client_; +}; + +TEST_F(ChromeSigninClientTest, DelayNetworkCallRunsImmediatelyWithNetwork) { + SetUpNetworkConnection(true, network::mojom::ConnectionType::CONNECTION_3G); + CallbackTester tester; + signin_client()->DelayNetworkCall( + base::BindOnce(&CallbackTester::Increment, base::Unretained(&tester))); + ASSERT_TRUE(tester.WasCalledExactlyOnce()); +} + +TEST_F(ChromeSigninClientTest, DelayNetworkCallRunsAfterGetConnectionType) { + SetUpNetworkConnection(false, network::mojom::ConnectionType::CONNECTION_3G); + + base::RunLoop run_loop; + CallbackTester tester; + signin_client()->DelayNetworkCall( + base::BindOnce(&CallbackTester::IncrementAndUnblock, + base::Unretained(&tester), &run_loop)); + ASSERT_FALSE(tester.WasCalledExactlyOnce()); + run_loop.Run(); // Wait for IncrementAndUnblock(). + ASSERT_TRUE(tester.WasCalledExactlyOnce()); +} + +TEST_F(ChromeSigninClientTest, DelayNetworkCallRunsAfterNetworkChange) { + SetUpNetworkConnection(true, network::mojom::ConnectionType::CONNECTION_NONE); + + base::RunLoop run_loop; + CallbackTester tester; + signin_client()->DelayNetworkCall( + base::BindOnce(&CallbackTester::IncrementAndUnblock, + base::Unretained(&tester), &run_loop)); + + ASSERT_FALSE(tester.WasCalledExactlyOnce()); + SetConnectionType(network::mojom::ConnectionType::CONNECTION_3G); + run_loop.Run(); // Wait for IncrementAndUnblock(). + ASSERT_TRUE(tester.WasCalledExactlyOnce()); +} + +#if !defined(OS_ANDROID) + +class MockChromeSigninClient : public ChromeSigninClient { + public: + explicit MockChromeSigninClient(Profile* profile) + : ChromeSigninClient(profile) {} + + MOCK_METHOD1(ShowUserManager, void(const base::FilePath&)); + MOCK_METHOD1(LockForceSigninProfile, void(const base::FilePath&)); + + MOCK_METHOD3(SignOutCallback, + void(signin_metrics::ProfileSignout, + signin_metrics::SignoutDelete, + SigninClient::SignoutDecision signout_decision)); +}; + +class ChromeSigninClientSignoutTest : public BrowserWithTestWindowTest { + public: + ChromeSigninClientSignoutTest() : forced_signin_setter_(true) {} + void SetUp() override { + BrowserWithTestWindowTest::SetUp(); + CreateClient(browser()->profile()); + } + + void TearDown() override { + BrowserWithTestWindowTest::TearDown(); + TestingBrowserProcess::GetGlobal()->SetLocalState(nullptr); + } + + void CreateClient(Profile* profile) { + client_ = std::make_unique<MockChromeSigninClient>(profile); + } + + void PreSignOut(signin_metrics::ProfileSignout source_metric, + signin_metrics::SignoutDelete delete_metric) { + client_->PreSignOut(base::BindOnce(&MockChromeSigninClient::SignOutCallback, + base::Unretained(client_.get()), + source_metric, delete_metric), + source_metric); + } + + signin_util::ScopedForceSigninSetterForTesting forced_signin_setter_; + std::unique_ptr<MockChromeSigninClient> client_; +}; + +TEST_F(ChromeSigninClientSignoutTest, SignOut) { + signin_metrics::ProfileSignout source_metric = + signin_metrics::ProfileSignout::USER_CLICKED_SIGNOUT_SETTINGS; + signin_metrics::SignoutDelete delete_metric = + signin_metrics::SignoutDelete::kIgnoreMetric; + + EXPECT_CALL(*client_, ShowUserManager(browser()->profile()->GetPath())) + .Times(1); + EXPECT_CALL(*client_, LockForceSigninProfile(browser()->profile()->GetPath())) + .Times(1); + EXPECT_CALL( + *client_, + SignOutCallback(source_metric, delete_metric, + SigninClient::SignoutDecision::ALLOW_SIGNOUT)) + .Times(1); + + PreSignOut(source_metric, delete_metric); +} + +TEST_F(ChromeSigninClientSignoutTest, SignOutWithoutForceSignin) { + signin_util::ScopedForceSigninSetterForTesting signin_setter(false); + CreateClient(browser()->profile()); + + signin_metrics::ProfileSignout source_metric = + signin_metrics::ProfileSignout::USER_CLICKED_SIGNOUT_SETTINGS; + signin_metrics::SignoutDelete delete_metric = + signin_metrics::SignoutDelete::kIgnoreMetric; + + EXPECT_CALL(*client_, ShowUserManager(browser()->profile()->GetPath())) + .Times(0); + EXPECT_CALL(*client_, LockForceSigninProfile(browser()->profile()->GetPath())) + .Times(0); + EXPECT_CALL( + *client_, + SignOutCallback(source_metric, delete_metric, + SigninClient::SignoutDecision::ALLOW_SIGNOUT)) + .Times(1); + PreSignOut(source_metric, delete_metric); +} + +class ChromeSigninClientSignoutSourceTest + : public ::testing::WithParamInterface<signin_metrics::ProfileSignout>, + public ChromeSigninClientSignoutTest { + protected: + signin::IdentityTestEnvironment* identity_test_env() { + return &identity_test_env_; + } + + private: + signin::IdentityTestEnvironment identity_test_env_; +}; + +// Returns true if signout can be disallowed by policy for the given source. +bool IsSignoutDisallowedByPolicy( + Profile* profile, + signin_metrics::ProfileSignout signout_source) { + auto* identity_manager = + IdentityManagerFactory::GetForProfileIfExists(profile); + if (identity_manager && + !identity_manager->HasPrimaryAccount(signin::ConsentLevel::kSync)) { + return false; + } + + switch (signout_source) { + // NOTE: SIGNOUT_TEST == SIGNOUT_PREF_CHANGED. + case signin_metrics::ProfileSignout::SIGNOUT_PREF_CHANGED: + case signin_metrics::ProfileSignout::GOOGLE_SERVICE_NAME_PATTERN_CHANGED: + case signin_metrics::ProfileSignout::SIGNIN_PREF_CHANGED_DURING_SIGNIN: + case signin_metrics::ProfileSignout::USER_CLICKED_SIGNOUT_SETTINGS: + case signin_metrics::ProfileSignout::SERVER_FORCED_DISABLE: + case signin_metrics::ProfileSignout::TRANSFER_CREDENTIALS: + case signin_metrics::ProfileSignout:: + AUTHENTICATION_FAILED_WITH_FORCE_SIGNIN: + case signin_metrics::ProfileSignout::SIGNIN_NOT_ALLOWED_ON_PROFILE_INIT: + case signin_metrics::ProfileSignout::USER_TUNED_OFF_SYNC_FROM_DICE_UI: + return true; + case signin_metrics::ProfileSignout::ACCOUNT_REMOVED_FROM_DEVICE: + case signin_metrics::ProfileSignout:: + IOS_ACCOUNT_REMOVED_FROM_DEVICE_AFTER_RESTORE: + // TODO(msarda): Add more of the above cases to this "false" branch. + // For now only ACCOUNT_REMOVED_FROM_DEVICE is here to preserve the status + // quo. Additional internal sources of sign-out will be moved here in a + // follow up CL. + return false; + case signin_metrics::ProfileSignout::ABORT_SIGNIN: + // Allow signout because data has not been synced yet. + return false; + case signin_metrics::ProfileSignout::FORCE_SIGNOUT_ALWAYS_ALLOWED_FOR_TEST: + // Allow signout for tests that want to force it. + return false; + case signin_metrics::ProfileSignout::ACCOUNT_ID_MIGRATION: + // Allowed to force finish the account id migration. + return false; + case signin_metrics::ProfileSignout::USER_DELETED_ACCOUNT_COOKIES: + case signin_metrics::ProfileSignout::MOBILE_IDENTITY_CONSISTENCY_ROLLBACK: + // There's no special-casing for these in ChromeSigninClient, as they only + // happen when there's no sync account and policies aren't enforced. + // PrimaryAccountManager won't actually invoke PreSignOut in this case, + // thus it is fine for ChromeSigninClient to not have any special-casing. + return true; + case signin_metrics::ProfileSignout::NUM_PROFILE_SIGNOUT_METRICS: + NOTREACHED(); + return false; + } +} + +TEST_P(ChromeSigninClientSignoutSourceTest, UserSignoutAllowed) { + signin_metrics::ProfileSignout signout_source = GetParam(); + + TestingProfile::Builder builder; + builder.SetGuestSession(); + std::unique_ptr<TestingProfile> profile = builder.Build(); + + CreateClient(profile.get()); + ASSERT_TRUE(signin_util::IsUserSignoutAllowedForProfile(profile.get())); + + // Verify IdentityManager gets callback indicating sign-out is always allowed. + signin_metrics::SignoutDelete delete_metric = + signin_metrics::SignoutDelete::kIgnoreMetric; + EXPECT_CALL( + *client_, + SignOutCallback(signout_source, delete_metric, + SigninClient::SignoutDecision::ALLOW_SIGNOUT)) + .Times(1); + + PreSignOut(signout_source, delete_metric); +} + +#if defined(OS_WIN) || defined(OS_LINUX) || defined(OS_CHROMEOS) || \ + defined(OS_MAC) +TEST_P(ChromeSigninClientSignoutSourceTest, UserSignoutDisallowed) { + signin_metrics::ProfileSignout signout_source = GetParam(); + + TestingProfile::Builder builder; + builder.SetGuestSession(); + std::unique_ptr<TestingProfile> profile = builder.Build(); + + CreateClient(profile.get()); + + ASSERT_TRUE(signin_util::IsUserSignoutAllowedForProfile(profile.get())); + signin_util::SetUserSignoutAllowedForProfile(profile.get(), false); + ASSERT_FALSE(signin_util::IsUserSignoutAllowedForProfile(profile.get())); + + // Verify IdentityManager gets callback indicating sign-out is disallowed iff + // the source of the sign-out is a user-action. + SigninClient::SignoutDecision signout_decision = + IsSignoutDisallowedByPolicy(profile.get(), signout_source) + ? SigninClient::SignoutDecision::DISALLOW_SIGNOUT + : SigninClient::SignoutDecision::ALLOW_SIGNOUT; + signin_metrics::SignoutDelete delete_metric = + signin_metrics::SignoutDelete::kIgnoreMetric; + EXPECT_CALL(*client_, + SignOutCallback(signout_source, delete_metric, signout_decision)) + .Times(1); + + PreSignOut(signout_source, delete_metric); +} + +TEST_P(ChromeSigninClientSignoutSourceTest, UserSignoutDisallowedWithSync) { + signin_metrics::ProfileSignout signout_source = GetParam(); + + TestingProfile::Builder builder; + builder.SetGuestSession(); + std::unique_ptr<TestingProfile> profile = builder.Build(); + + CreateClient(profile.get()); + + ASSERT_TRUE(signin_util::IsUserSignoutAllowedForProfile(profile.get())); + signin_util::SetUserSignoutAllowedForProfile(profile.get(), false); + ASSERT_FALSE(signin_util::IsUserSignoutAllowedForProfile(profile.get())); + + // Verify IdentityManager gets callback indicating sign-out is disallowed iff + // the source of the sign-out is a user-action. + SigninClient::SignoutDecision signout_decision = + IsSignoutDisallowedByPolicy(profile.get(), signout_source) + ? SigninClient::SignoutDecision::DISALLOW_SIGNOUT + : SigninClient::SignoutDecision::ALLOW_SIGNOUT; + signin_metrics::SignoutDelete delete_metric = + signin_metrics::SignoutDelete::kIgnoreMetric; + identity_test_env()->MakePrimaryAccountAvailable("bob@example.com", + signin::ConsentLevel::kSync); + EXPECT_CALL(*client_, + SignOutCallback(signout_source, delete_metric, signout_decision)) + .Times(1); + + PreSignOut(signout_source, delete_metric); +} + +TEST_P(ChromeSigninClientSignoutSourceTest, + UserSignoutDisallowedAccountManagementAccepted) { + base::test::ScopedFeatureList features(kAccountPoliciesLoadedWithoutSync); + signin_metrics::ProfileSignout signout_source = GetParam(); + + TestingProfile::Builder builder; + builder.SetGuestSession(); + std::unique_ptr<TestingProfile> profile = builder.Build(); + + CreateClient(profile.get()); + + ASSERT_TRUE(signin_util::IsUserSignoutAllowedForProfile(profile.get())); + signin_util::SetUserSignoutAllowedForProfile(profile.get(), false); + ASSERT_FALSE(signin_util::IsUserSignoutAllowedForProfile(profile.get())); + + // Verify IdentityManager gets callback indicating sign-out is disallowed iff + // the source of the sign-out is a user-action. + SigninClient::SignoutDecision signout_decision = + IsSignoutDisallowedByPolicy(profile.get(), signout_source) + ? SigninClient::SignoutDecision::DISALLOW_SIGNOUT + : SigninClient::SignoutDecision::ALLOW_SIGNOUT; + signin_metrics::SignoutDelete delete_metric = + signin_metrics::SignoutDelete::kIgnoreMetric; + EXPECT_CALL(*client_, + SignOutCallback(signout_source, delete_metric, signout_decision)) + .Times(1); + + PreSignOut(signout_source, delete_metric); +} +#endif + +const signin_metrics::ProfileSignout kSignoutSources[] = { + signin_metrics::ProfileSignout::SIGNOUT_PREF_CHANGED, + signin_metrics::ProfileSignout::GOOGLE_SERVICE_NAME_PATTERN_CHANGED, + signin_metrics::ProfileSignout::SIGNIN_PREF_CHANGED_DURING_SIGNIN, + signin_metrics::ProfileSignout::USER_CLICKED_SIGNOUT_SETTINGS, + signin_metrics::ProfileSignout::ABORT_SIGNIN, + signin_metrics::ProfileSignout::SERVER_FORCED_DISABLE, + signin_metrics::ProfileSignout::TRANSFER_CREDENTIALS, + signin_metrics::ProfileSignout::AUTHENTICATION_FAILED_WITH_FORCE_SIGNIN, + signin_metrics::ProfileSignout::USER_TUNED_OFF_SYNC_FROM_DICE_UI, + signin_metrics::ProfileSignout::ACCOUNT_REMOVED_FROM_DEVICE, + signin_metrics::ProfileSignout::SIGNIN_NOT_ALLOWED_ON_PROFILE_INIT, + signin_metrics::ProfileSignout::FORCE_SIGNOUT_ALWAYS_ALLOWED_FOR_TEST, + signin_metrics::ProfileSignout::USER_DELETED_ACCOUNT_COOKIES, + signin_metrics::ProfileSignout::MOBILE_IDENTITY_CONSISTENCY_ROLLBACK, + signin_metrics::ProfileSignout::ACCOUNT_ID_MIGRATION, + signin_metrics::ProfileSignout:: + IOS_ACCOUNT_REMOVED_FROM_DEVICE_AFTER_RESTORE, +}; +static_assert(base::size(kSignoutSources) == + signin_metrics::ProfileSignout::NUM_PROFILE_SIGNOUT_METRICS, + "kSignoutSources should enumerate all ProfileSignout values"); + +INSTANTIATE_TEST_SUITE_P(AllSignoutSources, + ChromeSigninClientSignoutSourceTest, + testing::ValuesIn(kSignoutSources)); + +#endif // !defined(OS_ANDROID) +#endif // !BUILDFLAG(IS_CHROMEOS_ASH) |