#include "chrome/common/extensions/permissions/chrome_permission_message_provider.h"
#include <algorithm>
#include <string_view>
#include <tuple>
#include <vector>
#include "base/compiler_specific.h"
#include "base/memory/raw_ptr.h"
#include "base/metrics/field_trial.h"
#include "base/stl_util.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "extensions/common/extensions_client.h"
#include "extensions/common/permissions/permission_message_util.h"
#include "extensions/common/permissions/permission_set.h"
#include "extensions/common/url_pattern.h"
#include "extensions/common/url_pattern_set.h"
#include "url/gurl.h"
using extensions::mojom::APIPermissionID;
namespace extensions {
namespace {
class ComparablePermission {
public:
explicit ComparablePermission(const PermissionMessage& msg) : msg_(&msg) {}
friend auto operator<=>(const ComparablePermission& a,
const ComparablePermission& b) {
return std::tie(a.msg_->message(), a.msg_->submessages()) <=>
std::tie(b.msg_->message(), b.msg_->submessages());
}
[[maybe_unused]] friend bool operator==(const ComparablePermission& a,
const ComparablePermission& b) {
return std::tie(a.msg_->message(), a.msg_->submessages()) ==
std::tie(b.msg_->message(), b.msg_->submessages());
}
private:
raw_ptr<const PermissionMessage> msg_;
};
using ComparablePermissions = std::vector<ComparablePermission>;
}
typedef std::set<PermissionMessage> PermissionMsgSet;
ChromePermissionMessageProvider::ChromePermissionMessageProvider() = default;
ChromePermissionMessageProvider::~ChromePermissionMessageProvider() = default;
PermissionMessages ChromePermissionMessageProvider::GetPermissionMessages(
const PermissionIDSet& permissions) const {
const std::vector<ChromePermissionMessageRule> rules =
ChromePermissionMessageRule::GetAllRules();
return GetPermissionMessagesHelper(permissions, rules);
}
bool ChromePermissionMessageProvider::IsPrivilegeIncrease(
const PermissionSet& granted_permissions,
const PermissionSet& requested_permissions,
Manifest::Type extension_type) const {
if (IsHostPrivilegeIncrease(granted_permissions, requested_permissions,
extension_type))
return true;
if (IsAPIOrManifestPrivilegeIncrease(granted_permissions,
requested_permissions))
return true;
return false;
}
PermissionIDSet ChromePermissionMessageProvider::GetAllPermissionIDs(
const PermissionSet& permissions,
Manifest::Type extension_type) const {
PermissionIDSet permission_ids;
AddAPIPermissions(permissions, &permission_ids);
AddManifestPermissions(permissions, &permission_ids);
AddHostPermissions(permissions, &permission_ids, extension_type);
return permission_ids;
}
PermissionIDSet ChromePermissionMessageProvider::GetManagementUIPermissionIDs(
const PermissionSet& permissions,
Manifest::Type extension_type) const {
PermissionIDSet permission_ids;
AddAPIPermissionsForManagementUIWarning(permissions, &permission_ids);
AddManifestPermissionsForManagementUIWarning(permissions, &permission_ids);
AddHostPermissions(permissions, &permission_ids, extension_type);
return permission_ids;
}
void ChromePermissionMessageProvider::AddAPIPermissionsForManagementUIWarning(
const PermissionSet& permissions,
PermissionIDSet* permission_ids) const {
for (const APIPermission* permission : permissions.apis()) {
if (permission->info()->requires_management_ui_warning())
permission_ids->InsertAll(permission->GetPermissions());
}
}
void ChromePermissionMessageProvider::
AddManifestPermissionsForManagementUIWarning(
const PermissionSet& permissions,
PermissionIDSet* permission_ids) const {
for (const ManifestPermission* p : permissions.manifest_permissions()) {
if (p->RequiresManagementUIWarning())
permission_ids->InsertAll(p->GetPermissions());
}
}
void ChromePermissionMessageProvider::AddAPIPermissions(
const PermissionSet& permissions,
PermissionIDSet* permission_ids) const {
for (const APIPermission* permission : permissions.apis())
permission_ids->InsertAll(permission->GetPermissions());
}
void ChromePermissionMessageProvider::AddManifestPermissions(
const PermissionSet& permissions,
PermissionIDSet* permission_ids) const {
for (const ManifestPermission* p : permissions.manifest_permissions())
permission_ids->InsertAll(p->GetPermissions());
}
void ChromePermissionMessageProvider::AddHostPermissions(
const PermissionSet& permissions,
PermissionIDSet* permission_ids,
Manifest::Type extension_type) const {
if (extension_type == Manifest::TYPE_PLATFORM_APP)
return;
if (permissions.ShouldWarnAllHosts()) {
permission_ids->insert(APIPermissionID::kHostsAll);
} else {
URLPatternSet regular_hosts;
ExtensionsClient::Get()->FilterHostPermissions(
permissions.effective_hosts(), ®ular_hosts, permission_ids);
std::set<std::string> hosts =
permission_message_util::GetDistinctHosts(regular_hosts, true, true);
for (const auto& host : hosts) {
permission_ids->insert(APIPermissionID::kHostReadWrite,
base::UTF8ToUTF16(host));
}
}
}
bool ChromePermissionMessageProvider::IsAPIOrManifestPrivilegeIncrease(
const PermissionSet& granted_permissions,
const PermissionSet& requested_permissions) const {
PermissionIDSet granted_ids;
AddAPIPermissions(granted_permissions, &granted_ids);
AddManifestPermissions(granted_permissions, &granted_ids);
if (granted_permissions.ShouldWarnAllHosts())
granted_ids.insert(APIPermissionID::kHostsAll);
PermissionIDSet potential_total_ids = granted_ids;
AddAPIPermissions(requested_permissions, &potential_total_ids);
AddManifestPermissions(requested_permissions, &potential_total_ids);
if (requested_permissions.ShouldWarnAllHosts())
potential_total_ids.insert(APIPermissionID::kHostsAll);
granted_ids.erase(APIPermissionID::kNewTabPageOverride);
potential_total_ids.erase(APIPermissionID::kNewTabPageOverride);
if (granted_ids.Includes(potential_total_ids))
return false;
PermissionMessages granted_messages = GetPermissionMessages(granted_ids);
PermissionMessages total_messages =
GetPermissionMessages(potential_total_ids);
ComparablePermissions granted_strings(granted_messages.begin(),
granted_messages.end());
ComparablePermissions total_strings(total_messages.begin(),
total_messages.end());
std::sort(granted_strings.begin(), granted_strings.end());
std::sort(total_strings.begin(), total_strings.end());
return !std::ranges::includes(granted_strings, total_strings);
}
bool ChromePermissionMessageProvider::IsHostPrivilegeIncrease(
const PermissionSet& granted_permissions,
const PermissionSet& requested_permissions,
Manifest::Type extension_type) const {
if (extension_type == Manifest::TYPE_PLATFORM_APP)
return false;
if (granted_permissions.HasEffectiveAccessToAllHosts())
return false;
if (requested_permissions.HasEffectiveAccessToAllHosts())
return true;
const URLPatternSet& granted_list = granted_permissions.effective_hosts();
const URLPatternSet& requested_list = requested_permissions.effective_hosts();
std::set<std::string> requested_hosts_set(
permission_message_util::GetDistinctHosts(requested_list, false, false));
std::set<std::string> granted_hosts_set(
permission_message_util::GetDistinctHosts(granted_list, false, false));
std::set<std::string> requested_hosts_only =
base::STLSetDifference<std::set<std::string>>(requested_hosts_set,
granted_hosts_set);
for (const auto& requested : requested_hosts_only) {
bool host_matched = false;
const std::string_view unmatched(requested);
for (const auto& granted : granted_hosts_set) {
if (granted.size() > 2 && granted[0] == '*' && granted[1] == '.') {
const std::string_view stripped_granted =
std::string_view(granted).substr(1);
if (base::EndsWith(unmatched, stripped_granted) ||
unmatched == stripped_granted.substr(1)) {
host_matched = true;
break;
}
}
}
if (!host_matched) {
return true;
}
}
return false;
}
PermissionMessages ChromePermissionMessageProvider::GetPermissionMessagesHelper(
const PermissionIDSet& permissions,
const std::vector<ChromePermissionMessageRule>& rules) const {
PermissionIDSet remaining_permissions = permissions;
PermissionMessages messages;
for (const auto& rule : rules) {
if (remaining_permissions.ContainsAllIDs(rule.required_permissions())) {
PermissionIDSet used_permissions =
remaining_permissions.GetAllPermissionsWithIDs(
rule.all_permissions());
messages.push_back(rule.GetPermissionMessage(used_permissions));
remaining_permissions =
PermissionIDSet::Difference(remaining_permissions, used_permissions);
}
}
return messages;
}
}