0

[automation] Remove unused permission matches from manifest

As part of the cleanup now that the automation API is fully private
(go/chrome-automation-partial-deprecation), remove a manifest
permission that is not used.

The permission will be converted to a single boolean in a follow-up
change.

AX-Relnotes: n/a.
Bug: b/265481580
Test: All automation tests
Change-Id: I04573735cf76172990fd6b6d9281d5620b5eb977
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5226180
Reviewed-by: Devlin Cronin <rdevlin.cronin@chromium.org>
Commit-Queue: Devlin Cronin <rdevlin.cronin@chromium.org>
Reviewed-by: David Tseng <dtseng@chromium.org>
Auto-Submit: Anastasia Helfinstein <anastasi@google.com>
Cr-Commit-Position: refs/heads/main@{#1255829}
This commit is contained in:
Anastasia Helfinstein
2024-02-02 23:46:43 +00:00
committed by Chromium LUCI CQ
parent 80e18907a2
commit f9a2aedd5e
10 changed files with 10 additions and 256 deletions

@ -50,9 +50,6 @@ bool ChromeAutomationInternalApiDelegate::CanRequestAutomation(
return true;
const GURL& url = contents->GetURL();
// TODO(aboxhall): check for webstore URL
if (automation_info->matches.MatchesURL(url))
return true;
int tab_id = ExtensionTabUtil::GetTabId(contents);
std::string unused_error;

@ -64,94 +64,6 @@ TEST_F(AutomationManifestTest, AsBooleanTrue) {
ASSERT_TRUE(info);
EXPECT_FALSE(info->desktop);
EXPECT_TRUE(info->matches.is_empty());
}
TEST_F(AutomationManifestTest, Matches) {
scoped_refptr<Extension> extension = LoadAndExpectWarning(
"automation_matches.json",
ErrorUtils::FormatErrorMessage(
automation_errors::kErrorInvalidMatch, "www.badpattern.com",
URLPattern::GetParseResultString(
URLPattern::ParseResult::kMissingSchemeSeparator)));
ASSERT_TRUE(extension.get());
EXPECT_TRUE(VerifyOnePermissionMessage(
extension->permissions_data(),
"Read your data on www.google.com and www.twitter.com"));
const AutomationInfo* info = AutomationInfo::Get(extension.get());
ASSERT_TRUE(info);
EXPECT_FALSE(info->desktop);
EXPECT_FALSE(info->matches.is_empty());
EXPECT_TRUE(info->matches.MatchesURL(GURL("http://www.google.com/")));
EXPECT_TRUE(info->matches.MatchesURL(GURL("http://www.google.com")));
EXPECT_TRUE(info->matches.MatchesURL(GURL("http://www.twitter.com/")));
EXPECT_TRUE(info->matches.MatchesURL(GURL("http://www.twitter.com")));
EXPECT_FALSE(info->matches.MatchesURL(GURL("http://www.bing.com/")));
EXPECT_FALSE(info->matches.MatchesURL(GURL("http://www.bing.com")));
}
TEST_F(AutomationManifestTest, MatchesAndPermissions) {
scoped_refptr<Extension> extension =
LoadAndExpectSuccess("automation_matches_and_permissions.json");
ASSERT_TRUE(extension.get());
EXPECT_TRUE(
VerifyTwoPermissionMessages(extension->permissions_data(),
"Read and change your data on www.google.com",
"Read your data on www.twitter.com", false));
const AutomationInfo* info = AutomationInfo::Get(extension.get());
ASSERT_TRUE(info);
EXPECT_FALSE(info->desktop);
EXPECT_FALSE(info->matches.is_empty());
EXPECT_TRUE(info->matches.MatchesURL(GURL("http://www.twitter.com/")));
EXPECT_TRUE(info->matches.MatchesURL(GURL("http://www.twitter.com")));
}
TEST_F(AutomationManifestTest, EmptyMatches) {
scoped_refptr<Extension> extension =
LoadAndExpectWarning("automation_empty_matches.json",
automation_errors::kErrorNoMatchesProvided);
ASSERT_TRUE(extension.get());
EXPECT_TRUE(VerifyNoPermissionMessages(extension->permissions_data()));
const AutomationInfo* info = AutomationInfo::Get(extension.get());
ASSERT_TRUE(info);
EXPECT_FALSE(info->desktop);
EXPECT_TRUE(info->matches.is_empty());
}
TEST_F(AutomationManifestTest, NoValidMatches) {
std::string error;
scoped_refptr<Extension> extension =
LoadExtension(ManifestData("automation_no_valid_matches.json"), &error);
ASSERT_TRUE(extension.get());
EXPECT_EQ("", error);
EXPECT_EQ(2u, extension->install_warnings().size());
EXPECT_EQ(ErrorUtils::FormatErrorMessage(
automation_errors::kErrorInvalidMatch, "www.badpattern.com",
URLPattern::GetParseResultString(
URLPattern::ParseResult::kMissingSchemeSeparator)),
extension->install_warnings()[0].message);
EXPECT_EQ(automation_errors::kErrorNoMatchesProvided,
extension->install_warnings()[1].message);
EXPECT_TRUE(VerifyNoPermissionMessages(extension->permissions_data()));
const AutomationInfo* info = AutomationInfo::Get(extension.get());
ASSERT_TRUE(info);
EXPECT_FALSE(info->desktop);
EXPECT_TRUE(info->matches.is_empty());
}
TEST_F(AutomationManifestTest, DesktopFalse) {
@ -167,7 +79,6 @@ TEST_F(AutomationManifestTest, DesktopFalse) {
ASSERT_TRUE(info);
EXPECT_FALSE(info->desktop);
EXPECT_TRUE(info->matches.is_empty());
}
TEST_F(AutomationManifestTest, DesktopTrue) {
@ -183,24 +94,6 @@ TEST_F(AutomationManifestTest, DesktopTrue) {
ASSERT_TRUE(info);
EXPECT_TRUE(info->desktop);
EXPECT_TRUE(info->matches.is_empty());
}
TEST_F(AutomationManifestTest, Desktop_MatchesSpecified) {
scoped_refptr<Extension> extension = LoadAndExpectWarning(
"automation_desktop_matches_specified.json",
automation_errors::kErrorDesktopTrueMatchesSpecified);
ASSERT_TRUE(extension.get());
EXPECT_TRUE(VerifyOnePermissionMessage(
extension->permissions_data(),
l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_FULL_ACCESS)));
const AutomationInfo* info = AutomationInfo::Get(extension.get());
ASSERT_TRUE(info);
EXPECT_TRUE(info->desktop);
EXPECT_TRUE(info->matches.is_empty());
}
} // namespace extensions

@ -1,12 +0,0 @@
{
"name": "unit_tests --gtest_filter=AutomationManifestTest.Desktop_MatchesSpecified",
"version": "1",
"manifest_version": 2,
"key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8xv6iO+j4kzj1HiBL93+XVJH/CRyAQMUHS/Z0l8nCAzaAFkW/JsNwxJqQhrZspnxLqbQxNncXs6g6bsXAwKHiEs+LSs+bIv0Gc/2ycZdhXJ8GhEsSMakog5dpQd1681c2gLK/8CrAoewE/0GIKhaFcp7a2iZlGh4Am6fgMKy0iQIDAQAB",
"automation": {
"desktop": true,
"matches": [
"http://www.google.com"
]
}
}

@ -1,9 +0,0 @@
{
"name": "unit_tests --gtest_filter=AutomationManifestTest.EmptyMatches",
"version": "1",
"manifest_version": 2,
"key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8xv6iO+j4kzj1HiBL93+XVJH/CRyAQMUHS/Z0l8nCAzaAFkW/JsNwxJqQhrZspnxLqbQxNncXs6g6bsXAwKHiEs+LSs+bIv0Gc/2ycZdhXJ8GhEsSMakog5dpQd1681c2gLK/8CrAoewE/0GIKhaFcp7a2iZlGh4Am6fgMKy0iQIDAQAB",
"automation": {
"matches": []
}
}

@ -1,11 +0,0 @@
{
"name": "unit_tests --gtest_filter=AutomationManifestTest.Matches",
"version": "1",
"manifest_version": 2,
"key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8xv6iO+j4kzj1HiBL93+XVJH/CRyAQMUHS/Z0l8nCAzaAFkW/JsNwxJqQhrZspnxLqbQxNncXs6g6bsXAwKHiEs+LSs+bIv0Gc/2ycZdhXJ8GhEsSMakog5dpQd1681c2gLK/8CrAoewE/0GIKhaFcp7a2iZlGh4Am6fgMKy0iQIDAQAB",
"automation": {
"matches": [ "http://www.google.com/",
"http://www.twitter.com/",
"www.badpattern.com" ]
}
}

@ -1,12 +0,0 @@
{
"name": "unit_tests --gtest_filter=AutomationManifestTest.Matches",
"version": "1",
"manifest_version": 2,
"key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8xv6iO+j4kzj1HiBL93+XVJH/CRyAQMUHS/Z0l8nCAzaAFkW/JsNwxJqQhrZspnxLqbQxNncXs6g6bsXAwKHiEs+LSs+bIv0Gc/2ycZdhXJ8GhEsSMakog5dpQd1681c2gLK/8CrAoewE/0GIKhaFcp7a2iZlGh4Am6fgMKy0iQIDAQAB",
"permissions": [
"http://www.google.com/"
],
"automation": {
"matches": [ "http://www.twitter.com/" ]
}
}

@ -1,9 +0,0 @@
{
"name": "unit_tests --gtest_filter=AutomationManifestTest.NoValidMatches",
"version": "1",
"manifest_version": 2,
"key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8xv6iO+j4kzj1HiBL93+XVJH/CRyAQMUHS/Z0l8nCAzaAFkW/JsNwxJqQhrZspnxLqbQxNncXs6g6bsXAwKHiEs+LSs+bIv0Gc/2ycZdhXJ8GhEsSMakog5dpQd1681c2gLK/8CrAoewE/0GIKhaFcp7a2iZlGh4Am6fgMKy0iQIDAQAB",
"automation": {
"matches": [ "www.badpattern.com" ]
}
}

@ -22,12 +22,6 @@
"description": "Whether to request permission to the whole ChromeOS desktop. If granted, this gives the extension access to every aspect of the desktop, and every site and app. If this permission is requested, all other permissions are implicitly included and do not need to be requested separately.",
"optional": true,
"type": "boolean"
},
"matches": {
"description": "A list of URL patterns for which this extension may request an automation tree. If not specified, automation permission will be granted for the sites for which the extension has a <a href='https://developer.chrome.com/extensions/declare_permissions#host-permissions'>host permission</a> or <a href='https://developer.chrome.com/extensions/declare_permissions#activeTab'>activeTab permission</a>).",
"optional": true,
"type": "array",
"items": { "type": "string" }
}
}
}

@ -17,7 +17,6 @@
#include "extensions/common/permissions/manifest_permission.h"
#include "extensions/common/permissions/permission_message_util.h"
#include "extensions/common/permissions/permissions_data.h"
#include "extensions/common/url_pattern.h"
#include "ipc/ipc_message.h"
#include "ipc/ipc_message_utils.h"
@ -25,14 +24,6 @@ using extensions::mojom::APIPermissionID;
namespace extensions {
namespace automation_errors {
const char kErrorDesktopTrueMatchesSpecified[] =
"Cannot specify matches for Automation if desktop=true is specified; "
"matches will be ignored.";
const char kErrorInvalidMatch[] = "Invalid match pattern '*': *";
const char kErrorNoMatchesProvided[] = "No valid match patterns provided.";
} // namespace automation_errors
namespace errors = manifest_errors;
namespace keys = extensions::manifest_keys;
using api::extensions_manifest_types::Automation;
@ -82,18 +73,6 @@ PermissionIDSet AutomationManifestPermission::GetPermissions() const {
PermissionIDSet permissions;
if (automation_info_->desktop) {
permissions.insert(APIPermissionID::kFullAccess);
} else if (automation_info_->matches.MatchesAllURLs()) {
permissions.insert(APIPermissionID::kHostsAll);
} else {
// Check if we get any additional permissions from FilterHostPermissions.
URLPatternSet regular_hosts;
ExtensionsClient::Get()->FilterHostPermissions(
automation_info_->matches, &regular_hosts, &permissions);
std::set<std::string> hosts =
permission_message_util::GetDistinctHosts(regular_hosts, true, true);
for (const auto& host : hosts)
permissions.insert(APIPermissionID::kHostReadOnly,
base::UTF8ToUTF16(host));
}
return permissions;
}
@ -116,10 +95,8 @@ std::unique_ptr<ManifestPermission> AutomationManifestPermission::Diff(
static_cast<const AutomationManifestPermission*>(rhs);
bool desktop = automation_info_->desktop && !other->automation_info_->desktop;
URLPatternSet matches = URLPatternSet::CreateDifference(
automation_info_->matches, other->automation_info_->matches);
return std::make_unique<AutomationManifestPermission>(
base::WrapUnique(new const AutomationInfo(desktop, matches)));
base::WrapUnique(new const AutomationInfo(desktop)));
}
std::unique_ptr<ManifestPermission> AutomationManifestPermission::Union(
@ -128,10 +105,8 @@ std::unique_ptr<ManifestPermission> AutomationManifestPermission::Union(
static_cast<const AutomationManifestPermission*>(rhs);
bool desktop = automation_info_->desktop || other->automation_info_->desktop;
URLPatternSet matches = URLPatternSet::CreateUnion(
automation_info_->matches, other->automation_info_->matches);
return std::make_unique<AutomationManifestPermission>(
base::WrapUnique(new const AutomationInfo(desktop, matches)));
base::WrapUnique(new const AutomationInfo(desktop)));
}
std::unique_ptr<ManifestPermission> AutomationManifestPermission::Intersect(
@ -140,15 +115,12 @@ std::unique_ptr<ManifestPermission> AutomationManifestPermission::Intersect(
static_cast<const AutomationManifestPermission*>(rhs);
bool desktop = automation_info_->desktop && other->automation_info_->desktop;
URLPatternSet matches = URLPatternSet::CreateIntersection(
automation_info_->matches, other->automation_info_->matches,
URLPatternSet::IntersectionBehavior::kStringComparison);
return std::make_unique<AutomationManifestPermission>(
base::WrapUnique(new const AutomationInfo(desktop, matches)));
base::WrapUnique(new const AutomationInfo(desktop)));
}
bool AutomationManifestPermission::RequiresManagementUIWarning() const {
return automation_info_->desktop || !automation_info_->matches.is_empty();
return automation_info_->desktop;
}
AutomationHandler::AutomationHandler() = default;
@ -188,8 +160,8 @@ ManifestPermission* AutomationHandler::CreateInitialRequiredPermission(
const Extension* extension) {
const AutomationInfo* info = AutomationInfo::Get(extension);
if (info) {
return new AutomationManifestPermission(base::WrapUnique(
new const AutomationInfo(info->desktop, info->matches)));
return new AutomationManifestPermission(
base::WrapUnique(new const AutomationInfo(info->desktop)));
}
return nullptr;
}
@ -223,39 +195,7 @@ std::unique_ptr<AutomationInfo> AutomationInfo::FromValue(
desktop = true;
}
URLPatternSet matches;
bool specified_matches = false;
if (automation_object.matches) {
if (desktop) {
install_warnings->emplace_back(
automation_errors::kErrorDesktopTrueMatchesSpecified);
} else {
specified_matches = true;
for (const auto& match : *automation_object.matches) {
// TODO(aboxhall): Refactor common logic from content_scripts_handler,
// manifest_url_handler and user_script.cc into a single location and
// re-use here.
URLPattern pattern(URLPattern::SCHEME_ALL &
~URLPattern::SCHEME_CHROMEUI);
URLPattern::ParseResult parse_result = pattern.Parse(match);
if (parse_result != URLPattern::ParseResult::kSuccess) {
install_warnings->emplace_back(ErrorUtils::FormatErrorMessage(
automation_errors::kErrorInvalidMatch, match,
URLPattern::GetParseResultString(parse_result)));
continue;
}
matches.AddPattern(pattern);
}
}
}
if (specified_matches && matches.is_empty()) {
install_warnings->emplace_back(automation_errors::kErrorNoMatchesProvided);
}
return base::WrapUnique(new AutomationInfo(desktop, matches));
return base::WrapUnique(new AutomationInfo(desktop));
}
// static
@ -268,23 +208,20 @@ std::unique_ptr<base::Value> AutomationInfo::ToValue(
std::unique_ptr<Automation> AutomationInfo::AsManifestType(
const AutomationInfo& info) {
std::unique_ptr<Automation> automation(new Automation);
if (!info.desktop && info.matches.size() == 0) {
if (!info.desktop) {
automation->as_boolean = true;
return automation;
}
automation->as_object.emplace();
automation->as_object->desktop = info.desktop;
if (info.matches.size() > 0)
automation->as_object->matches = info.matches.ToStringVector();
return automation;
}
AutomationInfo::AutomationInfo() : desktop(false) {}
AutomationInfo::AutomationInfo(bool desktop, const URLPatternSet& matches)
: desktop(desktop), matches(matches.Clone()) {}
AutomationInfo::AutomationInfo(bool desktop) : desktop(desktop) {}
AutomationInfo::~AutomationInfo() = default;

@ -10,7 +10,6 @@
#include "extensions/common/extension.h"
#include "extensions/common/manifest_handler.h"
#include "extensions/common/url_pattern_set.h"
#include "extensions/common/user_script.h"
namespace extensions {
@ -19,17 +18,8 @@ namespace api::extensions_manifest_types {
struct Automation;
}
class URLPatternSet;
class AutomationManifestPermission;
namespace automation_errors {
extern const char kErrorInvalidMatchPattern[];
extern const char kErrorDesktopTrueMatchesSpecified[];
extern const char kErrorURLMalformed[];
extern const char kErrorInvalidMatch[];
extern const char kErrorNoMatchesProvided[];
} // namespace automation_errors
// The parsed form of the automation manifest entry.
struct AutomationInfo : public Extension::ManifestData {
public:
@ -49,13 +39,9 @@ struct AutomationInfo : public Extension::ManifestData {
// true if the extension has requested 'desktop' permission.
const bool desktop;
// Returns the list of hosts that this extension can request an automation
// tree from.
const URLPatternSet matches;
private:
AutomationInfo();
AutomationInfo(bool desktop, const URLPatternSet& matches);
explicit AutomationInfo(bool desktop);
static std::unique_ptr<api::extensions_manifest_types::Automation>
AsManifestType(const AutomationInfo& info);