#include <memory>
#include <string>
#include <utility>
#include "base/memory/raw_ptr.h"
#include "base/notimplemented.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_future.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/browsing_data/chrome_browsing_data_model_delegate.h"
#include "chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.h"
#include "chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_factory.h"
#include "chrome/browser/device_api/device_attribute_api.h"
#include "chrome/browser/device_api/device_service_impl.h"
#include "chrome/browser/web_applications/isolated_web_apps/test/iwa_test_server_configurator.h"
#include "chrome/browser/web_applications/isolated_web_apps/test/policy_generator.h"
#include "chrome/browser/web_applications/policy/web_app_policy_constants.h"
#include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
#include "chrome/browser/web_applications/test/web_app_test.h"
#include "chrome/browser/web_applications/test/web_app_test_observers.h"
#include "chrome/browser/web_applications/web_app_helpers.h"
#include "chrome/browser/web_applications/web_app_provider.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/chrome_render_view_host_test_harness.h"
#include "chrome/test/base/testing_profile.h"
#include "components/account_id/account_id.h"
#include "components/nacl/common/buildflags.h"
#include "components/permissions/features.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "components/profile_metrics/browser_profile_type.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/common/content_features.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/navigation_simulator.h"
#include "content/public/test/web_contents_tester.h"
#include "net/base/features.h"
#include "services/data_decoder/public/cpp/test_support/in_process_data_decoder.h"
#include "url/gurl.h"
#if BUILDFLAG(IS_CHROMEOS)
#include "chrome/browser/ui/web_applications/test/isolated_web_app_test_utils.h"
#include "chrome/browser/web_applications/isolated_web_apps/policy/isolated_web_app_policy_constants.h"
#include "chrome/common/url_constants.h"
#endif
#if BUILDFLAG(ENABLE_NACL)
#include "chrome/browser/nacl_host/nacl_browser_delegate_impl.h"
#include "components/nacl/browser/nacl_browser.h"
#endif
#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "base/test/scoped_command_line.h"
#include "chrome/browser/ash/app_mode/web_app/web_kiosk_app_manager.h"
#include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
#include "chrome/common/chrome_switches.h"
#endif
namespace {
#if BUILDFLAG(ENABLE_NACL)
class ScopedNaClBrowserDelegate {
public:
explicit ScopedNaClBrowserDelegate(ProfileManager* profile_manager) {
nacl::NaClBrowser::SetDelegate(
std::make_unique<NaClBrowserDelegateImpl>(profile_manager));
}
~ScopedNaClBrowserDelegate() { nacl::NaClBrowser::ClearAndDeleteDelegate(); }
};
#endif
constexpr char kDefaultAppInstallUrl[] = …;
constexpr char kTrustedUrl[] = …;
constexpr char kUntrustedUrl[] = …;
constexpr char kKioskAppInstallUrl[] = …;
constexpr char kUserEmail[] = …;
constexpr char kNotAffiliatedErrorMessage[] = …;
#if BUILDFLAG(IS_CHROMEOS)
constexpr char kTrustedIwaManifestFile[] = "manifest_app1.json";
constexpr char kTrustedIwaManifestUrl[] =
"https://example.com/manifest_app1.json";
constexpr char kTrustedIwaSignedWebBundle[] = "web_bundle_app.swbn";
constexpr char kBaseUrlForUrlLoader[] = "https://example.com/";
constexpr char kUpdateManifestValueApp[] = R"(
{"versions":
[{"version": "1.0.0","src": "https://example.com/web_bundle_app.swbn"}]})";
constexpr char kUntrustedIwaAppOrigin[] =
"isolated-app://abc2sheak3vpmm7vmjqnjwuzx3xwot3vdayrlgnvbkq2mp5lg4daaaic";
#endif
#if BUILDFLAG(IS_CHROMEOS_ASH)
constexpr char kKioskAppUrl[] = "https://kiosk.com/sample";
constexpr char kInvalidKioskAppUrl[] = "https://invalid-kiosk.com/sample";
constexpr char kNotAllowedOriginErrorMessage[] =
"The current origin cannot use this web API because it is not allowed by "
"the DeviceAttributesAllowedForOrigins policy.";
#endif
}
namespace {
Result;
constexpr char kAnnotatedAssetId[] = …;
constexpr char kAnnotatedLocation[] = …;
constexpr char kDirectoryApiId[] = …;
constexpr char kHostname[] = …;
constexpr char kSerialNumber[] = …;
class FakeDeviceAttributeApi : public DeviceAttributeApi { … };
}
class DeviceAPIServiceTest { … };
class DeviceAPIServiceWebAppTest : public DeviceAPIServiceTest,
public WebAppTest { … };
TEST_F(DeviceAPIServiceWebAppTest, ConnectsForTrustedApps) { … }
TEST_F(DeviceAPIServiceWebAppTest, DoesNotConnectForIncognitoProfile) { … }
TEST_F(DeviceAPIServiceWebAppTest, DoesNotConnectForUntrustedApps) { … }
TEST_F(DeviceAPIServiceWebAppTest, DisconnectWhenTrustRevoked) { … }
TEST_F(DeviceAPIServiceWebAppTest, MultiOriginDisconnectWhenTrustRevoked) { … }
TEST_F(DeviceAPIServiceWebAppTest, ReportErrorForDefaultUser) { … }
#if BUILDFLAG(IS_CHROMEOS)
class DeviceAPIServiceIwaTest : public DeviceAPIServiceWebAppTest {
public:
void SetUp() override {
DeviceAPIServiceWebAppTest::SetUp();
#if BUILDFLAG(ENABLE_NACL)
nacl_browser_delegate_ = std::make_unique<ScopedNaClBrowserDelegate>(
profile_manager().profile_manager());
#endif
}
void InstallTrustedApps() override {
web_app::TestSignedWebBundle swbn_app =
web_app::TestSignedWebBundleBuilder::BuildDefault();
url_info = web_app::IsolatedWebAppUrlInfo::CreateFromSignedWebBundleId(
swbn_app.id);
web_app::IwaTestServerConfigurator configurator;
configurator.AddUpdateManifest(kTrustedIwaManifestFile,
kUpdateManifestValueApp);
configurator.AddSignedWebBundle(kTrustedIwaSignedWebBundle,
std::move(swbn_app));
configurator.ConfigureURLLoader(GURL(kBaseUrlForUrlLoader),
profile_url_loader_factory(),
fake_web_contents_manager());
web_app::WebAppTestInstallObserver install_observer(profile());
install_observer.BeginListening({app_id()});
web_app::PolicyGenerator policy_generator;
policy_generator.AddForceInstalledIwa(get_url_info().web_bundle_id(),
GURL(kTrustedIwaManifestUrl));
profile()->GetPrefs()->Set(prefs::kIsolatedWebAppInstallForceList,
policy_generator.Generate());
EXPECT_EQ(install_observer.Wait(), app_id());
task_environment()->RunUntilIdle();
}
web_app::FakeWebContentsManager& fake_web_contents_manager() {
return static_cast<web_app::FakeWebContentsManager&>(
provider()->web_contents_manager());
}
void RemoveTrustedIWAs() {
web_app::WebAppTestUninstallObserver uninstall_observer(profile());
uninstall_observer.BeginListening({app_id()});
web_app::PolicyGenerator policy_generator;
profile()->GetPrefs()->Set(prefs::kIsolatedWebAppInstallForceList,
policy_generator.Generate());
EXPECT_EQ(uninstall_observer.Wait(), app_id());
}
void TearDown() override {
ChromeBrowsingDataRemoverDelegateFactory::GetForProfile(profile())
->Shutdown();
DeviceAPIServiceWebAppTest::TearDown();
}
const web_app::IsolatedWebAppUrlInfo& get_url_info() const {
return *url_info;
}
const webapps::AppId& app_id() const { return get_url_info().app_id(); }
private:
#if BUILDFLAG(ENABLE_NACL)
std::unique_ptr<ScopedNaClBrowserDelegate> nacl_browser_delegate_;
#endif
base::test::ScopedFeatureList scoped_feature_list_{
features::kIsolatedWebApps};
std::optional<web_app::IsolatedWebAppUrlInfo> url_info;
};
TEST_F(DeviceAPIServiceIwaTest, ConnectsForTrustedApps) {
DeviceAPIServiceTest::TryCreatingService(
get_url_info().origin().GetURL(),
std::make_unique<DeviceAttributeApiImpl>(), web_contents());
remote()->FlushForTesting();
ASSERT_TRUE(remote()->is_connected());
}
TEST_F(DeviceAPIServiceIwaTest, DoesNotConnectForUntrustedApps) {
TryCreatingService(GURL(kUntrustedIwaAppOrigin),
std::make_unique<DeviceAttributeApiImpl>());
remote()->FlushForTesting();
ASSERT_FALSE(remote()->is_connected());
}
TEST_F(DeviceAPIServiceIwaTest, DisconnectWhenTrustRevoked) {
TryCreatingService(get_url_info().origin().GetURL(),
std::make_unique<DeviceAttributeApiImpl>());
remote()->FlushForTesting();
RemoveTrustedIWAs();
remote()->FlushForTesting();
ASSERT_FALSE(remote()->is_connected());
}
TEST_F(DeviceAPIServiceIwaTest, ReportErrorForDefaultUser) {
TryCreatingService(get_url_info().origin().GetURL(),
std::make_unique<DeviceAttributeApiImpl>());
VerifyErrorMessageResultForAllDeviceAttributesAPIs(
kNotAffiliatedErrorMessage);
ASSERT_TRUE(remote()->is_connected());
}
#endif
#if BUILDFLAG(IS_CHROMEOS_ASH)
class DeviceAPIServiceParamTest
: public DeviceAPIServiceWebAppTest,
public testing::WithParamInterface<std::pair<std::string, bool>> {
public:
void SetAllowedOriginFromParam() {
profile()->GetPrefs()->SetList(
prefs::kDeviceAttributesAllowedForOrigins,
base::Value::List().Append(GetParamOrigin()));
}
void SetAllowedOrigin(const std::string& origin) {
profile()->GetPrefs()->SetList(prefs::kDeviceAttributesAllowedForOrigins,
base::Value::List().Append(origin));
}
void EnableFeatureAndAllowlistOrigin(const base::Feature& param,
const std::string& origin) {
base::FieldTrialParams feature_params;
feature_params[permissions::feature_params::
kWebKioskBrowserPermissionsAllowlist.name] = origin;
feature_list_.InitAndEnableFeatureWithParameters(param, feature_params);
}
void EnableFeature(const base::Feature& param) {
feature_list_.InitAndEnableFeature(param);
}
void DisableFeature(const base::Feature& feature) {
feature_list_.InitAndDisableFeature(feature);
}
void SetKioskBrowserPermissionsAllowedForOrigins(const std::string& origin) {
profile()->GetPrefs()->SetList(
prefs::kKioskBrowserPermissionsAllowedForOrigins,
base::Value::List().Append(std::move(origin)));
}
void VerifyCanAccessForAllDeviceAttributesAPIs() {
base::test::TestFuture<blink::mojom::DeviceAttributeResultPtr> future;
remote()->get()->GetDirectoryId(future.GetCallback());
EXPECT_EQ(future.Take()->get_attribute(), kDirectoryApiId);
remote()->get()->GetHostname(future.GetCallback());
EXPECT_EQ(future.Take()->get_attribute(), kHostname);
remote()->get()->GetSerialNumber(future.GetCallback());
EXPECT_EQ(future.Take()->get_attribute(), kSerialNumber);
remote()->get()->GetAnnotatedAssetId(future.GetCallback());
EXPECT_EQ(future.Take()->get_attribute(), kAnnotatedAssetId);
remote()->get()->GetAnnotatedLocation(future.GetCallback());
EXPECT_EQ(future.Take()->get_attribute(), kAnnotatedLocation);
}
const std::string& GetParamOrigin() { return GetParam().first; }
bool ExpectApiAvailable() { return GetParam().second; }
private:
base::test::ScopedFeatureList feature_list_;
};
class DeviceAPIServiceRegularUserTest : public DeviceAPIServiceParamTest {
public:
void LoginRegularUser(bool is_affiliated) {
fake_user_manager_ = static_cast<ash::FakeChromeUserManager*>(
user_manager::UserManager::Get());
const user_manager::User* user =
fake_user_manager()->AddUserWithAffiliation(account_id(),
is_affiliated);
fake_user_manager()->UserLoggedIn(user->GetAccountId(),
user->username_hash(), false, false);
}
ash::FakeChromeUserManager* fake_user_manager() const {
return fake_user_manager_;
}
void RemoveAllowedOrigin() {
profile()->GetPrefs()->SetList(prefs::kDeviceAttributesAllowedForOrigins,
base::Value::List());
}
void TearDown() override {
provider()->Shutdown();
DeviceAPIServiceParamTest::TearDown();
}
private:
raw_ptr<ash::FakeChromeUserManager, DanglingUntriaged> fake_user_manager_;
};
TEST_F(DeviceAPIServiceRegularUserTest, ReportErrorForUnaffiliatedUser) {
LoginRegularUser(false);
TryCreatingService(GURL(kTrustedUrl),
std::make_unique<FakeDeviceAttributeApi>());
VerifyErrorMessageResultForAllDeviceAttributesAPIs(
kNotAffiliatedErrorMessage);
ASSERT_TRUE(remote()->is_connected());
}
TEST_F(DeviceAPIServiceRegularUserTest, ReportErrorForDisallowedOrigin) {
LoginRegularUser(true);
TryCreatingService(GURL(kTrustedUrl),
std::make_unique<FakeDeviceAttributeApi>());
RemoveAllowedOrigin();
VerifyErrorMessageResultForAllDeviceAttributesAPIs(
kNotAllowedOriginErrorMessage);
ASSERT_TRUE(remote()->is_connected());
}
TEST_P(DeviceAPIServiceRegularUserTest, TestPolicyOriginPatterns) {
SetAllowedOriginFromParam();
LoginRegularUser(true);
TryCreatingService(GURL(kTrustedUrl),
std::make_unique<FakeDeviceAttributeApi>());
if (ExpectApiAvailable()) {
VerifyCanAccessForAllDeviceAttributesAPIs();
} else {
VerifyErrorMessageResultForAllDeviceAttributesAPIs(
kNotAllowedOriginErrorMessage);
}
ASSERT_TRUE(remote()->is_connected());
}
INSTANTIATE_TEST_SUITE_P(
All,
DeviceAPIServiceRegularUserTest,
testing::ValuesIn(
{std::pair<std::string, bool>("*", false),
std::pair<std::string, bool>(".example.com", false),
std::pair<std::string, bool>("example.", false),
std::pair<std::string, bool>("file://example*", false),
std::pair<std::string, bool>("invalid-example.com", false),
std::pair<std::string, bool>(kTrustedUrl, true),
std::pair<std::string, bool>("https://example.com", true),
std::pair<std::string, bool>("https://example.com/sample", true),
std::pair<std::string, bool>("example.com", true),
std::pair<std::string, bool>("*://example.com:*/", true),
std::pair<std::string, bool>("[*.]example.com", true)}));
class DeviceAPIServiceWithKioskUserTest : public DeviceAPIServiceParamTest {
public:
DeviceAPIServiceWithKioskUserTest() {
fake_user_manager_.Reset(std::make_unique<ash::FakeChromeUserManager>());
}
void SetUp() override {
DeviceAPIServiceParamTest::SetUp();
command_line_.GetProcessCommandLine()->AppendSwitch(
switches::kForceAppMode);
app_manager_ = std::make_unique<ash::WebKioskAppManager>();
}
void TearDown() override {
app_manager_.reset();
DeviceAPIServiceParamTest::TearDown();
}
void LoginKioskUser() {
app_manager()->AddAppForTesting(account_id(), GURL(kKioskAppInstallUrl));
fake_user_manager()->AddWebKioskAppUser(account_id());
fake_user_manager()->LoginUser(account_id());
}
ash::FakeChromeUserManager* fake_user_manager() const {
return fake_user_manager_.Get();
}
ash::WebKioskAppManager* app_manager() const { return app_manager_.get(); }
private:
user_manager::TypedScopedUserManager<ash::FakeChromeUserManager>
fake_user_manager_;
std::unique_ptr<ash::WebKioskAppManager> app_manager_;
base::test::ScopedCommandLine command_line_;
};
TEST_F(DeviceAPIServiceWithKioskUserTest, ConnectsForKioskOrigin) {
LoginKioskUser();
TryCreatingService(GURL(kKioskAppUrl),
std::make_unique<DeviceAttributeApiImpl>());
remote()->FlushForTesting();
ASSERT_TRUE(remote()->is_connected());
}
TEST_F(DeviceAPIServiceWithKioskUserTest, DoesNotConnectForInvalidOrigin) {
LoginKioskUser();
TryCreatingService(GURL(kInvalidKioskAppUrl),
std::make_unique<DeviceAttributeApiImpl>());
remote()->FlushForTesting();
ASSERT_FALSE(remote()->is_connected());
}
TEST_F(DeviceAPIServiceWithKioskUserTest,
DoesNotConnectForNonKioskTrustedOrigin) {
LoginKioskUser();
TryCreatingService(GURL(kTrustedUrl),
std::make_unique<DeviceAttributeApiImpl>());
remote()->FlushForTesting();
ASSERT_FALSE(remote()->is_connected());
}
class DeviceAPIServiceWithChromeAppKioskUserTest
: public DeviceAPIServiceTest,
public ChromeRenderViewHostTestHarness {
public:
DeviceAPIServiceWithChromeAppKioskUserTest()
: account_id_(AccountId::FromUserEmail(kUserEmail)) {
fake_user_manager_.Reset(std::make_unique<ash::FakeChromeUserManager>());
}
void LoginChromeAppKioskUser() {
fake_user_manager()->AddKioskAppUser(account_id());
fake_user_manager()->LoginUser(account_id());
}
const AccountId& account_id() const { return account_id_; }
ash::FakeChromeUserManager* fake_user_manager() const {
return fake_user_manager_.Get();
}
void TryCreatingService(
const GURL& url,
std::unique_ptr<DeviceAttributeApi> device_attribute_api) {
DeviceAPIServiceTest::TryCreatingService(
url, std::move(device_attribute_api), web_contents());
}
private:
AccountId account_id_;
std::unique_ptr<user_manager::ScopedUserManager> scoped_user_manager_;
user_manager::TypedScopedUserManager<ash::FakeChromeUserManager>
fake_user_manager_;
};
TEST_F(DeviceAPIServiceWithChromeAppKioskUserTest,
DoesNotConnectForChromeAppKioskSession) {
LoginChromeAppKioskUser();
TryCreatingService(GURL(kKioskAppUrl),
std::make_unique<DeviceAttributeApiImpl>());
remote()->FlushForTesting();
ASSERT_FALSE(remote()->is_connected());
}
class DeviceAPIServiceWithKioskUserTestForOrigins
: public DeviceAPIServiceWithKioskUserTest {};
TEST_F(DeviceAPIServiceWithKioskUserTestForOrigins,
TestTrustedKioskOriginsWhenEnabledByFeature) {
EnableFeatureAndAllowlistOrigin(
permissions::features::kAllowMultipleOriginsForWebKioskPermissions,
kTrustedUrl);
SetAllowedOrigin(kTrustedUrl);
LoginKioskUser();
TryCreatingService(GURL(kTrustedUrl),
std::make_unique<FakeDeviceAttributeApi>());
remote()->FlushForTesting();
ASSERT_TRUE(remote()->is_connected());
VerifyCanAccessForAllDeviceAttributesAPIs();
}
TEST_F(DeviceAPIServiceWithKioskUserTestForOrigins,
TestUntrustedKioskOriginsWhenEnabledByFeature) {
EnableFeatureAndAllowlistOrigin(
permissions::features::kAllowMultipleOriginsForWebKioskPermissions,
kTrustedUrl);
SetAllowedOrigin(kUntrustedUrl);
LoginKioskUser();
TryCreatingService(GURL(kUntrustedUrl),
std::make_unique<FakeDeviceAttributeApi>());
remote()->FlushForTesting();
ASSERT_FALSE(remote()->is_connected());
}
TEST_F(DeviceAPIServiceWithKioskUserTestForOrigins,
TestTrustedKioskOriginWhenMultipleOriginPrefIsSet) {
EnableFeature(
permissions::features::kAllowMultipleOriginsForWebKioskPermissions);
SetKioskBrowserPermissionsAllowedForOrigins(kTrustedUrl);
SetAllowedOrigin(kTrustedUrl);
LoginKioskUser();
TryCreatingService(GURL(kTrustedUrl),
std::make_unique<FakeDeviceAttributeApi>());
remote()->FlushForTesting();
ASSERT_TRUE(remote()->is_connected());
VerifyCanAccessForAllDeviceAttributesAPIs();
}
TEST_F(DeviceAPIServiceWithKioskUserTestForOrigins,
TestKioskInstallOriginWhenMultipleOriginPrefIsNotSet) {
EnableFeature(
permissions::features::kAllowMultipleOriginsForWebKioskPermissions);
SetAllowedOrigin(kKioskAppInstallUrl);
LoginKioskUser();
TryCreatingService(GURL(kKioskAppInstallUrl),
std::make_unique<FakeDeviceAttributeApi>());
remote()->FlushForTesting();
ASSERT_TRUE(remote()->is_connected());
VerifyCanAccessForAllDeviceAttributesAPIs();
}
TEST_F(DeviceAPIServiceWithKioskUserTestForOrigins,
TestMultipleOriginPolicyWhenFeatureIsDisabled) {
DisableFeature(
permissions::features::kAllowMultipleOriginsForWebKioskPermissions);
SetKioskBrowserPermissionsAllowedForOrigins(kTrustedUrl);
SetAllowedOrigin(kTrustedUrl);
LoginKioskUser();
TryCreatingService(GURL(kTrustedUrl),
std::make_unique<FakeDeviceAttributeApi>());
remote()->FlushForTesting();
ASSERT_FALSE(remote()->is_connected());
}
TEST_P(DeviceAPIServiceWithKioskUserTestForOrigins, TestPolicyOriginPatterns) {
SetAllowedOriginFromParam();
LoginKioskUser();
TryCreatingService(GURL(kKioskAppUrl),
std::make_unique<FakeDeviceAttributeApi>());
remote()->FlushForTesting();
ASSERT_TRUE(remote()->is_connected());
if (ExpectApiAvailable()) {
VerifyCanAccessForAllDeviceAttributesAPIs();
} else {
VerifyErrorMessageResultForAllDeviceAttributesAPIs(
kNotAllowedOriginErrorMessage);
}
}
INSTANTIATE_TEST_SUITE_P(
All,
DeviceAPIServiceWithKioskUserTestForOrigins,
testing::ValuesIn({std::pair<std::string, bool>("*", false),
std::pair<std::string, bool>("*.kiosk.com", false),
std::pair<std::string, bool>("*kiosk.com", false),
std::pair<std::string, bool>("kiosk.", false),
std::pair<std::string, bool>(kInvalidKioskAppUrl, false),
std::pair<std::string, bool>(kKioskAppUrl, true),
std::pair<std::string, bool>("https://kiosk.com", true),
std::pair<std::string, bool>("https://kiosk.com/sample",
true),
std::pair<std::string, bool>("kiosk.com", true),
std::pair<std::string, bool>("*://kiosk.com:*/", true),
std::pair<std::string, bool>("[*.]kiosk.com", true)}));
#endif