chromium/chrome/browser/extensions/external_provider_impl_chromeos_unittest.cc

// Copyright 2013 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/extensions/external_provider_impl.h"

#include <memory>

#include "ash/constants/ash_pref_names.h"
#include "base/command_line.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/test/scoped_path_override.h"
#include "chrome/browser/ash/customization/customization_document.h"
#include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_service_test_base.h"
#include "chrome/browser/prefs/pref_service_syncable_util.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/signin/identity_test_environment_profile_adaptor.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.h"
#include "chromeos/ash/components/system/fake_statistics_provider.h"
#include "chromeos/ash/components/system/statistics_provider.h"
#include "components/sync/base/command_line_switches.h"
#include "components/sync/base/data_type.h"
#include "components/sync/base/pref_names.h"
#include "components/sync/model/sync_change_processor.h"
#include "components/sync/test/fake_sync_change_processor.h"
#include "components/sync_preferences/pref_service_syncable.h"
#include "components/user_manager/scoped_user_manager.h"
#include "content/public/test/test_utils.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/test_extension_registry_observer.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace extensions {

namespace {

const char kExternalAppId[] = "kekdneafjmhmndejhmbcadfiiofngffo";
const char kStandaloneAppId[] = "ldnnhddmnhbkjipkidpdiheffobcpfmf";
const char kStandaloneChildAppId[] = "hcglmfcclpfgljeaiahehebeoaiicbko";

const char kTestUserAccount[] = "user@test";

class ExternalProviderImplChromeOSTest : public ExtensionServiceTestBase {
 public:
  ExternalProviderImplChromeOSTest()
      : fake_user_manager_(new ash::FakeChromeUserManager()),
        scoped_user_manager_(base::WrapUnique(fake_user_manager_.get())) {}

  ExternalProviderImplChromeOSTest(const ExternalProviderImplChromeOSTest&) =
      delete;
  ExternalProviderImplChromeOSTest& operator=(
      const ExternalProviderImplChromeOSTest&) = delete;

  ~ExternalProviderImplChromeOSTest() override {}

  void InitServiceWithExternalProviders(bool standalone) {
    InitServiceWithExternalProvidersAndUserType(standalone,
                                                false /* is_child */);
  }

  void InitServiceWithExternalProvidersAndUserType(bool standalone,
                                                   bool is_child) {
    InitializeEmptyExtensionService();

    if (is_child) {
      profile_->SetIsSupervisedProfile();
    }

    service_->Init();

    if (standalone) {
      external_externsions_overrides_ =
          std::make_unique<base::ScopedPathOverride>(
              chrome::DIR_STANDALONE_EXTERNAL_EXTENSIONS,
              data_dir().Append("external_standalone"));
    } else {
      external_externsions_overrides_ =
          std::make_unique<base::ScopedPathOverride>(
              chrome::DIR_EXTERNAL_EXTENSIONS, data_dir().Append("external"));
    }

    // This switch is set when creating a TestingProfile, but needs to be
    // removed for some ExternalProviders to be created.
    base::CommandLine::ForCurrentProcess()->RemoveSwitch(
        switches::kDisableDefaultApps);

    ProviderCollection providers;
    ExternalProviderImpl::CreateExternalProviders(service_, profile_.get(),
                                                  &providers);

    for (std::unique_ptr<ExternalProviderInterface>& provider : providers) {
      service_->AddProviderForTesting(std::move(provider));
    }
  }

  // ExtensionServiceTestBase overrides:
  void SetUp() override { ExtensionServiceTestBase::SetUp(); }

  void TearDown() override {
    // If some extensions are being installed (on a background thread) and we
    // stop before the intsallation is complete, some installation related
    // objects might be leaked (as the background thread won't block on exit and
    // finish cleanly).
    // So ensure we let pending extension installations finish.
    WaitForPendingStandaloneExtensionsInstalled();
    ExtensionServiceTestBase::TearDown();
  }

  // Waits until all possible standalone extensions are installed.
  void WaitForPendingStandaloneExtensionsInstalled() {
    service_->CheckForExternalUpdates();
    base::RunLoop().RunUntilIdle();
    PendingExtensionManager* const pending_extension_manager =
        service_->pending_extension_manager();
    while (pending_extension_manager->IsIdPending(kStandaloneAppId) ||
           pending_extension_manager->IsIdPending(kStandaloneChildAppId)) {
      base::RunLoop().RunUntilIdle();
    }
  }

  void ValidateExternalProviderCountInAppMode(size_t expected_count) {
    base::CommandLine* command = base::CommandLine::ForCurrentProcess();
    command->AppendSwitchASCII(switches::kForceAppMode, std::string());
    command->AppendSwitchASCII(switches::kAppId, std::string("app_id"));

    InitializeEmptyExtensionService();

    ProviderCollection providers;
    ExternalProviderImpl::CreateExternalProviders(service_, profile_.get(),
                                                  &providers);

    EXPECT_EQ(providers.size(), expected_count);
  }

  ash::FakeChromeUserManager* fake_user_manager() const {
    return fake_user_manager_;
  }

 private:
  std::unique_ptr<base::ScopedPathOverride> external_externsions_overrides_;
  ash::system::ScopedFakeStatisticsProvider fake_statistics_provider_;
  raw_ptr<ash::FakeChromeUserManager, DanglingUntriaged> fake_user_manager_;
  user_manager::ScopedUserManager scoped_user_manager_;
};

}  // namespace

// Normal mode, external app should be installed.
TEST_F(ExternalProviderImplChromeOSTest, Normal) {
  InitServiceWithExternalProviders(false);

  TestExtensionRegistryObserver observer(registry(), kExternalAppId);

  service_->CheckForExternalUpdates();

  scoped_refptr<const Extension> loaded_extension =
      observer.WaitForExtensionLoaded();

  EXPECT_EQ(loaded_extension->id(), kExternalAppId);
}

// App mode, no external app should be installed.
TEST_F(ExternalProviderImplChromeOSTest, AppMode) {
  base::CommandLine* command = base::CommandLine::ForCurrentProcess();
  command->AppendSwitchASCII(switches::kForceAppMode, std::string());
  command->AppendSwitchASCII(switches::kAppId, std::string("app_id"));

  InitServiceWithExternalProviders(false);

  service_->CheckForExternalUpdates();
  base::RunLoop().RunUntilIdle();

  EXPECT_FALSE(registry()->GetInstalledExtension(kExternalAppId));
}

// Normal mode, standalone app should be installed, because sync is enabled but
// not running.
// flaky: crbug.com/854206
TEST_F(ExternalProviderImplChromeOSTest, DISABLED_Standalone) {
  InitServiceWithExternalProviders(true);

  WaitForPendingStandaloneExtensionsInstalled();

  EXPECT_TRUE(registry()->GetInstalledExtension(kStandaloneAppId));
  // Also include apps available for child.
  EXPECT_TRUE(registry()->GetInstalledExtension(kStandaloneChildAppId));
}

// Should include only subset of default apps
// flaky: crbug.com/854206
TEST_F(ExternalProviderImplChromeOSTest, DISABLED_StandaloneChild) {
  InitServiceWithExternalProvidersAndUserType(true /* standalone */,
                                              true /* is_child */);

  WaitForPendingStandaloneExtensionsInstalled();

  // kStandaloneAppId is not available for child.
  EXPECT_FALSE(registry()->GetInstalledExtension(kStandaloneAppId));
  EXPECT_TRUE(registry()->GetInstalledExtension(kStandaloneChildAppId));
}

// Normal mode, standalone app should be installed, because sync is disabled.
TEST_F(ExternalProviderImplChromeOSTest, SyncDisabled) {
  base::CommandLine::ForCurrentProcess()->AppendSwitch(syncer::kDisableSync);

  InitServiceWithExternalProviders(true);

  TestExtensionRegistryObserver observer(registry(), kStandaloneAppId);

  service_->CheckForExternalUpdates();

  scoped_refptr<const Extension> loaded_extension =
      observer.WaitForExtensionLoaded();
  EXPECT_EQ(loaded_extension->id(), kStandaloneAppId);
  EXPECT_TRUE(registry()->GetInstalledExtension(kStandaloneAppId));
}

// User signed in, sync service started, install app when sync is disabled by
// policy.
TEST_F(ExternalProviderImplChromeOSTest, PolicyDisabled) {
  InitServiceWithExternalProviders(true);

  // Log user in, start sync.
  TestingBrowserProcess::GetGlobal()->SetProfileManager(
      std::make_unique<ProfileManagerWithoutInit>(temp_dir().GetPath()));

  auto identity_test_env_profile_adaptor =
      std::make_unique<IdentityTestEnvironmentProfileAdaptor>(profile());
  identity_test_env_profile_adaptor->identity_test_env()
      ->MakePrimaryAccountAvailable("[email protected]",
                                    signin::ConsentLevel::kSync);

  // Sync is dsabled by policy.
  profile_->GetPrefs()->SetBoolean(syncer::prefs::internal::kSyncManaged, true);

  TestExtensionRegistryObserver observer(registry(), kStandaloneAppId);

  // App sync will wait for priority sync to complete.
  service_->CheckForExternalUpdates();

  scoped_refptr<const Extension> loaded_extension =
      observer.WaitForExtensionLoaded();
  EXPECT_EQ(loaded_extension->id(), kStandaloneAppId);
  EXPECT_TRUE(registry()->GetInstalledExtension(kStandaloneAppId));

  TestingBrowserProcess::GetGlobal()->SetProfileManager(nullptr);
}

// User signed in, sync service started, install app when priority sync is
// completed.
TEST_F(ExternalProviderImplChromeOSTest, PriorityCompleted) {
  InitServiceWithExternalProviders(true);

  // User is logged in.
  auto identity_test_env_profile_adaptor =
      std::make_unique<IdentityTestEnvironmentProfileAdaptor>(profile());
  identity_test_env_profile_adaptor->identity_test_env()->SetPrimaryAccount(
      "[email protected]", signin::ConsentLevel::kSync);

  // OOBE screen completed with OS sync enabled.
  PrefService* prefs = profile()->GetPrefs();
  prefs->SetBoolean(ash::prefs::kSyncOobeCompleted, true);

  TestExtensionRegistryObserver observer(registry(), kStandaloneAppId);

  // Priority sync completed.
  PrefServiceSyncableFromProfile(profile())
      ->GetSyncableService(syncer::OS_PRIORITY_PREFERENCES)
      ->MergeDataAndStartSyncing(
          syncer::OS_PRIORITY_PREFERENCES, syncer::SyncDataList(),
          std::make_unique<syncer::FakeSyncChangeProcessor>());

  // App sync will wait for priority sync to complete.
  service_->CheckForExternalUpdates();

  scoped_refptr<const Extension> loaded_extension =
      observer.WaitForExtensionLoaded();
  EXPECT_EQ(loaded_extension->id(), kStandaloneAppId);
  EXPECT_TRUE(registry()->GetInstalledExtension(kStandaloneAppId));
}

// Validate the external providers enabled in the Chrome App Kiosk session. The
// expected number should be 3.
// - |policy_provider|.
// - |kiosk_app_provider|.
// - |secondary_kiosk_app_provider|.
TEST_F(ExternalProviderImplChromeOSTest, ChromeAppKiosk) {
  const AccountId kiosk_account_id(AccountId::FromUserEmail(kTestUserAccount));
  fake_user_manager()->AddKioskAppUser(kiosk_account_id);
  fake_user_manager()->LoginUser(kiosk_account_id);

  ValidateExternalProviderCountInAppMode(3u);
}

// Validate the external providers enabled in the Web App Kiosk session. The
// expected number should be only 1.
// - |policy_provider|.
TEST_F(ExternalProviderImplChromeOSTest, WebAppKiosk) {
  const AccountId kiosk_account_id(AccountId::FromUserEmail(kTestUserAccount));
  fake_user_manager()->AddWebKioskAppUser(kiosk_account_id);
  fake_user_manager()->LoginUser(kiosk_account_id);

  ValidateExternalProviderCountInAppMode(1u);
}

}  // namespace extensions