Skip to content

Commit

Permalink
Projector: Disable Projector WebUIs when app is not available
Browse files Browse the repository at this point in the history
All four of the Projector WebUIs (chrome://projector/app/,
chrome-untrusted://projector, chrome://projector-annotator, and
chrome-untrusted://projector-annotator) should only be enabled when
the SWA is also enabled. Otherwise users can navigate to
chrome://projector/app/ to access the full app and circumvent feature
flags.

Bug: b/229019604
Change-Id: I9207261558a74cf2a1c2698fd9f1e7028efd3e5d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3581552
Reviewed-by: Trent Apted <tapted@chromium.org>
Commit-Queue: Toby Huang <tobyhuang@chromium.org>
Cr-Commit-Position: refs/heads/main@{#992390}
  • Loading branch information
Toby Huang authored and Chromium LUCI CQ committed Apr 14, 2022
1 parent 1c868f1 commit f60d131
Show file tree
Hide file tree
Showing 8 changed files with 271 additions and 59 deletions.
Expand Up @@ -33,7 +33,7 @@ bool UntrustedProjectorAnnotatorUIConfig::IsWebUIEnabled(
content::BrowserContext* browser_context) {
Profile* profile = Profile::FromBrowserContext(browser_context);
return ash::features::IsProjectorAnnotatorEnabled() &&
IsProjectorAllowedForProfile(profile);
IsProjectorAppEnabled(profile);
}

std::unique_ptr<content::WebUIController>
Expand Down
Expand Up @@ -40,8 +40,7 @@ UntrustedProjectorUIConfig::~UntrustedProjectorUIConfig() = default;
bool UntrustedProjectorUIConfig::IsWebUIEnabled(
content::BrowserContext* browser_context) {
Profile* profile = Profile::FromBrowserContext(browser_context);
return ash::features::IsProjectorEnabled() &&
IsProjectorAllowedForProfile(profile);
return IsProjectorAppEnabled(profile);
}

std::unique_ptr<content::WebUIController>
Expand Down
Expand Up @@ -5,14 +5,11 @@
#include "chrome/browser/ash/web_applications/projector_system_web_app_info.h"

#include "ash/constants/ash_features.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/webui/grit/ash_projector_app_trusted_resources.h"
#include "ash/webui/projector_app/public/cpp/projector_app_constants.h"
#include "chrome/browser/ash/web_applications/system_web_app_install_utils.h"
#include "chrome/browser/policy/profile_policy_connector.h"
#include "chrome/browser/ui/ash/projector/projector_utils.h"
#include "chrome/grit/generated_resources.h"
#include "components/prefs/pref_service.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/chromeos/styles/cros_styles.h"
Expand Down Expand Up @@ -90,25 +87,5 @@ gfx::Size ProjectorSystemWebAppDelegate::GetMinimumWindowSize() const {
}

bool ProjectorSystemWebAppDelegate::IsAppEnabled() const {
if (!IsProjectorAllowedForProfile(profile_))
return false;

// Projector for regular consumer users is controlled by a feature flag.
if (!profile_->GetProfilePolicyConnector()->IsManaged())
return ash::features::IsProjectorAllUserEnabled();

// Projector dogfood for supervised users is controlled by an enterprise
// policy. When the feature is out of dogfood phase the policy will be
// deprecated and the feature will be enabled by default.
if (profile_->IsChild()) {
return profile_->GetPrefs()->GetBoolean(
ash::prefs::kProjectorDogfoodForFamilyLinkEnabled);
}

// Projector for enterprise users is controlled by a combination fo a feature
// flag and an enterprise policy.
return ash::features::IsProjectorEnabled() &&
(ash::features::IsProjectorManagedUserIgnorePolicyEnabled() ||
profile_->GetPrefs()->GetBoolean(
ash::prefs::kProjectorAllowByPolicy));
return IsProjectorAppEnabled(profile_);
}
Expand Up @@ -52,8 +52,10 @@ class ProjectorNavigationThrottleTest : public InProcessBrowserTest {
public:
ProjectorNavigationThrottleTest()
: scoped_feature_list_(features::kProjector) {}
~ProjectorNavigationThrottleTest() override = default;

void SetUpOnMainThread() override {
InProcessBrowserTest::SetUpOnMainThread();
web_app::WebAppProvider::GetForTest(profile())
->system_web_app_manager()
.InstallSystemAppsForTesting();
Expand All @@ -68,8 +70,6 @@ class ProjectorNavigationThrottleTest : public InProcessBrowserTest {
task_runner_->GetMockTickClock());
}

~ProjectorNavigationThrottleTest() override = default;

protected:
Profile* profile() { return browser()->profile(); }
scoped_refptr<base::TestMockTimeTaskRunner> task_runner_;
Expand Down Expand Up @@ -208,10 +208,9 @@ IN_PROC_BROWSER_TEST_F(ProjectorNavigationThrottleTest,
// Verifies that navigating to chrome-untrusted://projector does not redirect.
IN_PROC_BROWSER_TEST_F(ProjectorNavigationThrottleTest,
UntrustedNavigationNoRedirect) {
std::string url = kChromeUIUntrustedProjectorAppUrl;
GURL gurl(url);
GURL untrusted_url(kChromeUIUntrustedProjectorAppUrl);

ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), gurl));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), untrusted_url));
web_app::FlushSystemWebAppLaunchesForTesting(profile());

Browser* app_browser =
Expand All @@ -222,11 +221,11 @@ IN_PROC_BROWSER_TEST_F(ProjectorNavigationThrottleTest,
content::WebContents* tab =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(tab);
EXPECT_EQ(tab->GetController().GetLastCommittedEntry()->GetPageType(),
EXPECT_EQ(tab->GetController().GetVisibleEntry()->GetPageType(),
content::PAGE_TYPE_NORMAL);

// URL remains unchanged.
EXPECT_EQ(tab->GetVisibleURL().spec(), url);
EXPECT_EQ(tab->GetVisibleURL(), untrusted_url);
// Verify the document language. We must use the deprecated
// ExecuteScriptAndExtract*() instead of EvalJs() due to CSP.
std::string lang;
Expand All @@ -236,14 +235,14 @@ IN_PROC_BROWSER_TEST_F(ProjectorNavigationThrottleTest,
EXPECT_EQ(lang, "en-US");
}

// Verifies that navigating to chrome://projector/app/ does not redirect.
// Verifies that navigating to chrome://projector/app/ does not redirect but
// launches the SWA.
IN_PROC_BROWSER_TEST_F(ProjectorNavigationThrottleTest,
TrustedNavigationNoRedirect) {
std::string url = kChromeUITrustedProjectorAppUrl;
GURL gurl(url);
GURL trusted_url(kChromeUITrustedProjectorAppUrl);

ui_test_utils::NavigateToURLWithDisposition(
browser(), gurl, WindowOpenDisposition::NEW_WINDOW,
browser(), trusted_url, WindowOpenDisposition::NEW_WINDOW,
ui_test_utils::BrowserTestWaitFlags::BROWSER_TEST_WAIT_FOR_BROWSER);
web_app::FlushSystemWebAppLaunchesForTesting(profile());

Expand All @@ -258,7 +257,120 @@ IN_PROC_BROWSER_TEST_F(ProjectorNavigationThrottleTest,
content::PAGE_TYPE_NORMAL);

// URL remains unchanged.
EXPECT_EQ(tab->GetVisibleURL().spec(), url);
EXPECT_EQ(tab->GetVisibleURL(), trusted_url);
}

class ProjectorNavigationThrottleDisabledTest : public InProcessBrowserTest {
public:
ProjectorNavigationThrottleDisabledTest() {
scoped_feature_list_.InitAndDisableFeature(features::kProjector);
}
~ProjectorNavigationThrottleDisabledTest() override = default;

void SetUpOnMainThread() override {
InProcessBrowserTest::SetUpOnMainThread();
web_app::WebAppProvider::GetForTest(profile())
->system_web_app_manager()
.InstallSystemAppsForTesting();
}

protected:
Profile* profile() { return browser()->profile(); }

private:
base::test::ScopedFeatureList scoped_feature_list_;
};

// Verifies that navigating to https://projector.apps.chrome does not launch the
// SWA when the app is disabled.
IN_PROC_BROWSER_TEST_F(ProjectorNavigationThrottleDisabledTest,
PwaNavigationLandingPage) {
GURL pwa_url(kChromeUIUntrustedProjectorPwaUrl);

// Simulate the user typing the url into the omnibox.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), pwa_url));
web_app::FlushSystemWebAppLaunchesForTesting(profile());

Browser* app_browser =
FindSystemWebAppBrowser(profile(), web_app::SystemAppType::PROJECTOR);
// Projector SWA is not open because it is disabled.
EXPECT_FALSE(app_browser);

content::WebContents* tab =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(tab);
// Normally, navigating to https://projector.apps.chrome would reach the
// landing page, but we don't have internet connection in this browser test.
EXPECT_EQ(tab->GetController().GetVisibleEntry()->GetPageType(),
content::PAGE_TYPE_ERROR);
EXPECT_EQ(tab->GetVisibleURL(), pwa_url);
}

// Verifies that chrome-untrusted://projector is not accessible when the app is
// disabled.
IN_PROC_BROWSER_TEST_F(ProjectorNavigationThrottleDisabledTest,
UntrustedNavigationInvalidUrl) {
GURL untrusted_url(kChromeUIUntrustedProjectorAppUrl);

ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), untrusted_url));
content::WebContents* tab =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(tab);
EXPECT_EQ(tab->GetController().GetVisibleEntry()->GetPageType(),
content::PAGE_TYPE_ERROR);
EXPECT_EQ(tab->GetVisibleURL(), untrusted_url);
}

// Verifies that chrome://projector/app/ is not accessible when the app is
// disabled.
IN_PROC_BROWSER_TEST_F(ProjectorNavigationThrottleDisabledTest,
TrustedNavigationInvalidUrl) {
GURL trusted_url(kChromeUITrustedProjectorAppUrl);

ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), trusted_url));
web_app::FlushSystemWebAppLaunchesForTesting(profile());

Browser* app_browser =
FindSystemWebAppBrowser(profile(), web_app::SystemAppType::PROJECTOR);
// Projector SWA is not open because it is disabled.
EXPECT_FALSE(app_browser);

content::WebContents* tab =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(tab);
EXPECT_EQ(tab->GetController().GetVisibleEntry()->GetPageType(),
content::PAGE_TYPE_ERROR);
EXPECT_EQ(tab->GetVisibleURL(), trusted_url);
}

// Verifies that chrome-untrusted://projector-annotator is not accessible when
// the app is disabled.
IN_PROC_BROWSER_TEST_F(ProjectorNavigationThrottleDisabledTest,
UntrustedAnnotatorNavigationInvalidUrl) {
GURL untrusted_annotator_url(kChromeUIUntrustedAnnotatorUrl);

ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), untrusted_annotator_url));
content::WebContents* tab =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(tab);
EXPECT_EQ(tab->GetController().GetVisibleEntry()->GetPageType(),
content::PAGE_TYPE_ERROR);
EXPECT_EQ(tab->GetVisibleURL(), untrusted_annotator_url);
}

// Verifies that chrome://projector-annotator is not accessible when the app is
// disabled.
IN_PROC_BROWSER_TEST_F(ProjectorNavigationThrottleDisabledTest,
TrustedAnnotatorNavigationInvalidUrl) {
GURL trusted_annotator_url(kChromeUITrustedAnnotatorUrl);

ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), trusted_annotator_url));
content::WebContents* tab =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(tab);
EXPECT_EQ(tab->GetController().GetVisibleEntry()->GetPageType(),
content::PAGE_TYPE_ERROR);
EXPECT_EQ(tab->GetVisibleURL(), trusted_annotator_url);
}

} // namespace ash
27 changes: 27 additions & 0 deletions chrome/browser/ui/ash/projector/projector_utils.cc
Expand Up @@ -4,10 +4,14 @@

#include "chrome/browser/ui/ash/projector/projector_utils.h"

#include "ash/constants/ash_features.h"
#include "ash/constants/ash_pref_names.h"
#include "chrome/browser/ash/drive/drive_integration_service.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/policy/profile_policy_connector.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "components/prefs/pref_service.h"

namespace {

Expand All @@ -31,6 +35,29 @@ bool IsProjectorAllowedForProfile(const Profile* profile) {
return user->HasGaiaAccount();
}

bool IsProjectorAppEnabled(const Profile* profile) {
if (!IsProjectorAllowedForProfile(profile))
return false;

// Projector for regular consumer users is controlled by a feature flag.
if (!profile->GetProfilePolicyConnector()->IsManaged())
return ash::features::IsProjectorAllUserEnabled();

// Projector dogfood for supervised users is controlled by an enterprise
// policy. When the feature is out of dogfood phase the policy will be
// deprecated and the feature will be enabled by default.
if (profile->IsChild()) {
return profile->GetPrefs()->GetBoolean(
ash::prefs::kProjectorDogfoodForFamilyLinkEnabled);
}

// Projector for enterprise users is controlled by a combination of a feature
// flag and an enterprise policy.
return ash::features::IsProjectorEnabled() &&
(ash::features::IsProjectorManagedUserIgnorePolicyEnabled() ||
profile->GetPrefs()->GetBoolean(ash::prefs::kProjectorAllowByPolicy));
}

drive::DriveIntegrationService* GetDriveIntegrationServiceForActiveProfile() {
return drive::DriveIntegrationServiceFactory::FindForProfile(
ProfileManager::GetActiveUserProfile());
Expand Down
3 changes: 3 additions & 0 deletions chrome/browser/ui/ash/projector/projector_utils.h
Expand Up @@ -14,6 +14,9 @@ class DriveIntegrationService;
// Returns whether Projector is allowed for given `profile`.
bool IsProjectorAllowedForProfile(const Profile* profile);

// Returns whether the Projector app is enabled.
bool IsProjectorAppEnabled(const Profile* profile);

drive::DriveIntegrationService* GetDriveIntegrationServiceForActiveProfile();

#endif // CHROME_BROWSER_UI_ASH_PROJECTOR_PROJECTOR_UTILS_H_

0 comments on commit f60d131

Please sign in to comment.