Commit 06661f17 authored by Stefan Teodorescu's avatar Stefan Teodorescu Committed by Commit Bot

Show extension information on management page

Add a new section in the chrome://management page to show force
installed extensions with powerful capabilities (like ability to read
and write all data on the page).

Bug: 879146
Change-Id: Ie45368caec53952a4c18efb07f3d80c21d5540b2
Reviewed-on: https://chromium-review.googlesource.com/1235674Reviewed-by: default avatarSergey Volk <servolk@chromium.org>
Reviewed-by: default avatarDevlin <rdevlin.cronin@chromium.org>
Reviewed-by: default avatarDemetrios Papadopoulos <dpapad@chromium.org>
Reviewed-by: default avatarMarton Hunyady <hunyadym@chromium.org>
Reviewed-by: default avatarMaksim Ivanov <emaxx@chromium.org>
Commit-Queue: Stefan Teodorescu <fane@google.com>
Cr-Commit-Position: refs/heads/master@{#594764}
parent 48c549d0
......@@ -27,3 +27,36 @@ body section {
#main-section {
padding-inline-start: 0;
}
#extensions-table {
border: 0;
}
#extensions-table li {
padding: 2px;
}
#extensions-table ul {
list-style: none;
padding: 0;
}
#extensions-table th {
border: 0;
color: grey;
padding: 7px;
text-align: start;
}
#extensions-table td {
border: 0;
padding: 0 5px 0;
}
#reporting-info-list li {
padding: 2px;
}
.section-title {
font-weight: bold;
}
......@@ -6,8 +6,10 @@
<title>$i18n{title}</title>
<link rel="stylesheet" href="chrome://resources/css/chrome_shared.css">
<link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<link rel="stylesheet" href="management.css">
<script src="chrome://resources/js/assert.js"></script>
<script src="chrome://resources/js/cr.js"></script>
<script src="chrome://resources/js/load_time_data.js"></script>
<script src="chrome://resources/js/promise_resolver.js"></script>
......@@ -23,16 +25,29 @@
</header>
<section id="main-section">
<!-- This is where page content gets dynamically added. -->
<section id="management-status">
<section id="management-status" hidden>
<p></p>
</section>
<section id="policies">
<div id="device-configuration" hidden>
<section id="policies" hidden>
<h2 class="section-title">$i18n{deviceReporting}</h2>
<div id="device-configuration">
$i18n{deviceConfiguration}
</div>
<ul id="reporting-info-list"></ul>
</section>
<section id="extensions" hidden>
<h2 class="section-title">$i18n{extensionReporting}</h2>
<div id="extensions-installed">
$i18n{extensionsInstalled}
</div>
<table id="extensions-table">
<tr>
<th>$i18n{extensionName}</th>
<th>$i18n{extensionPermissions}</th>
</tr>
</table>
</section>
</div>
</body>
......
......@@ -2,6 +2,14 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @typedef {{
* name: string,
* permissions: !Array<string>
* }}
*/
let Extension;
cr.define('management', function() {
/**
* A singleton object that handles communication between browser and WebUI.
......@@ -23,6 +31,7 @@ cr.define('management', function() {
// former case.
this.browserProxy_.getDeviceManagementStatus()
.then(function(managedString) {
$('management-status').hidden = false;
document.body.querySelector('#management-status > p').textContent =
managedString;
})
......@@ -39,7 +48,7 @@ cr.define('management', function() {
if (reportingSources.length == 0)
return;
$('device-configuration').hidden = false;
$('policies').hidden = false;
for (const id of reportingSources) {
const element = document.createElement('li');
......@@ -47,6 +56,39 @@ cr.define('management', function() {
$('reporting-info-list').appendChild(element);
}
});
// Show names and permissions of |extensions| in a table.
this.browserProxy_.getExtensions().then(function(extensions) {
if (extensions.length == 0)
return;
const table = $('extensions-table');
for (const /** Extension */ extension of extensions) {
assert(
extension.hasOwnProperty('permissions'),
'Each extension must have the permissions field');
assert(
extension.hasOwnProperty('name'),
'Each extension must have the name field');
const permissionsList = document.createElement('ul');
for (const perm of extension.permissions) {
const permissionElement = document.createElement('li');
permissionElement.textContent = perm;
permissionsList.appendChild(permissionElement);
}
const row = table.insertRow();
const nameCell = row.insertCell();
// insertCell(-1) inserts at the last position.
const permissionsCell = row.insertCell(-1);
nameCell.textContent = extension.name;
permissionsCell.appendChild(permissionsList);
}
$('extensions').hidden = false;
});
}
}
......@@ -62,6 +104,12 @@ cr.define('management', function() {
* @return {!Promise<!Array<string>>} Types of device reporting.
*/
getReportingInfo() {}
/**
* Each extension has a name and a list of permission messages.
* @return {!Promise<!Array<!Extension>>} List of extensions.
*/
getExtensions() {}
}
/**
......@@ -77,6 +125,11 @@ cr.define('management', function() {
getReportingInfo() {
return cr.sendWithPromise('getReportingInfo');
}
/** @override */
getExtensions() {
return cr.sendWithPromise('getExtensions');
}
}
// Make Page a singleton.
......
......@@ -19,8 +19,17 @@ content::WebUIDataSource* CreateManagementUIHtmlSource() {
content::WebUIDataSource* source =
content::WebUIDataSource::Create(chrome::kChromeUIManagementHost);
source->AddLocalizedString("title", IDS_MANAGEMENT_TITLE);
source->AddLocalizedString("deviceReporting",
IDS_MANAGEMENT_DEVICE_REPORTING);
source->AddLocalizedString("deviceConfiguration",
IDS_MANAGEMENT_DEVICE_CONFIGURATION);
source->AddLocalizedString("extensionReporting",
IDS_MANAGEMENT_EXTENSION_REPORTING);
source->AddLocalizedString("extensionsInstalled",
IDS_MANAGEMENT_EXTENSIONS_INSTALLED);
source->AddLocalizedString("extensionName", IDS_MANAGEMENT_EXTENSIONS_NAME);
source->AddLocalizedString("extensionPermissions",
IDS_MANAGEMENT_EXTENSIONS_PERMISSIONS);
source->AddLocalizedString(kManagementLogUploadEnabled,
IDS_MANAGEMENT_LOG_UPLOAD_ENABLED);
source->AddLocalizedString(kManagementReportActivityTimes,
......
......@@ -4,7 +4,11 @@
#include "chrome/browser/ui/webui/management_ui_handler.h"
#include <algorithm>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/bind_helpers.h"
......@@ -13,8 +17,10 @@
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/profiles/profile.h"
#include "components/strings/grit/components_strings.h"
#include "content/public/browser/web_contents.h"
#include "extensions/buildflags/buildflags.h"
#include "ui/base/l10n/l10n_util.h"
#if defined(OS_CHROMEOS)
......@@ -26,6 +32,16 @@
#include "chrome/browser/chromeos/policy/system_log_uploader.h"
#endif // defined(OS_CHROMEOS)
#if BUILDFLAG(ENABLE_EXTENSIONS)
#include "chrome/common/extensions/permissions/chrome_permission_message_provider.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_set.h"
#include "extensions/common/manifest.h"
#include "extensions/common/permissions/permission_message_provider.h"
#include "extensions/common/permissions/permissions_data.h"
#endif // BUILDFLAG(ENABLE_EXTENSIONS)
const char kManagementLogUploadEnabled[] = "managementLogUploadEnabled";
const char kManagementReportActivityTimes[] = "managementReportActivityTimes";
const char kManagementReportHardwareStatus[] = "managementReportHardwareStatus";
......@@ -94,6 +110,53 @@ void AddChromeOSReportingInfo(base::Value* report_sources) {
}
#endif // defined(OS_CHROMEOS)
#if BUILDFLAG(ENABLE_EXTENSIONS)
std::vector<base::Value> GetPermissionsForExtension(
scoped_refptr<const extensions::Extension> extension) {
std::vector<base::Value> permission_messages;
// Only consider force installed extensions
if (!extensions::Manifest::IsPolicyLocation(extension->location()))
return permission_messages;
extensions::PermissionIDSet permissions =
extensions::PermissionMessageProvider::Get()->GetAllPermissionIDs(
extension->permissions_data()->active_permissions(),
extension->GetType());
const extensions::PermissionMessages messages =
extensions::PermissionMessageProvider::Get()
->GetPowerfulPermissionMessages(permissions);
for (const auto& message : messages)
permission_messages.push_back(base::Value(message.message()));
return permission_messages;
}
base::Value GetPowerfulExtensions(const extensions::ExtensionSet& extensions) {
base::Value powerful_extensions(base::Value::Type::LIST);
for (const auto& extension : extensions) {
std::vector<base::Value> permission_messages =
GetPermissionsForExtension(extension);
// Only show extension on page if there is at least one permission
// message to show.
if (!permission_messages.empty()) {
base::Value extension_to_add(base::Value::Type::DICTIONARY);
extension_to_add.SetKey("name", base::Value(extension->name()));
extension_to_add.SetKey("permissions",
base::Value(std::move(permission_messages)));
powerful_extensions.GetList().push_back(std::move(extension_to_add));
}
}
return powerful_extensions;
}
#endif // BUILDFLAG(ENABLE_EXTENSIONS)
} // namespace
ManagementUIHandler::ManagementUIHandler() {}
......@@ -109,6 +172,10 @@ void ManagementUIHandler::RegisterMessages() {
"getReportingInfo",
base::BindRepeating(&ManagementUIHandler::HandleGetReportingInfo,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"getExtensions",
base::BindRepeating(&ManagementUIHandler::HandleGetExtensions,
base::Unretained(this)));
}
void ManagementUIHandler::HandleGetDeviceManagementStatus(
......@@ -141,3 +208,20 @@ void ManagementUIHandler::HandleGetReportingInfo(const base::ListValue* args) {
ResolveJavascriptCallback(args->GetList()[0] /* callback_id */,
report_sources);
}
void ManagementUIHandler::HandleGetExtensions(const base::ListValue* args) {
#if BUILDFLAG(ENABLE_EXTENSIONS)
// List of all enabled extensions
const extensions::ExtensionSet& extensions =
extensions::ExtensionRegistry::Get(Profile::FromWebUI(web_ui()))
->enabled_extensions();
base::Value powerful_extensions = GetPowerfulExtensions(extensions);
ResolveJavascriptCallback(args->GetList()[0] /* callback_id */,
powerful_extensions);
#else
ResolveJavascriptCallback(args->GetList()[0] /* callback_id */,
base::Value(base::Value::Type::LIST));
#endif // BUILDFLAG(ENABLE_EXTENSIONS)
}
......@@ -37,6 +37,8 @@ class ManagementUIHandler : public content::WebUIMessageHandler {
void HandleGetReportingInfo(const base::ListValue* args);
void HandleGetExtensions(const base::ListValue* args);
DISALLOW_COPY_AND_ASSIGN(ManagementUIHandler);
};
......
......@@ -10,7 +10,6 @@
#include "base/stl_util.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/common/extensions/permissions/chrome_permission_message_rules.h"
#include "extensions/common/extensions_client.h"
#include "extensions/common/permissions/permission_message_util.h"
#include "extensions/common/permissions/permission_set.h"
......@@ -57,30 +56,10 @@ ChromePermissionMessageProvider::~ChromePermissionMessageProvider() {
PermissionMessages ChromePermissionMessageProvider::GetPermissionMessages(
const PermissionIDSet& permissions) const {
std::vector<ChromePermissionMessageRule> rules =
const std::vector<ChromePermissionMessageRule> rules =
ChromePermissionMessageRule::GetAllRules();
// Apply each of the rules, in order, to generate the messages for the given
// permissions. Once a permission is used in a rule, remove it from the set
// of available permissions so it cannot be applied to subsequent rules.
PermissionIDSet remaining_permissions = permissions;
PermissionMessages messages;
for (const auto& rule : rules) {
// Only apply the rule if we have all the required permission IDs.
if (remaining_permissions.ContainsAllIDs(rule.required_permissions())) {
// We can apply the rule. Add all the required permissions, and as many
// optional permissions as we can, to the new message.
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;
return GetPermissionMessagesHelper(permissions, rules);
}
bool ChromePermissionMessageProvider::IsPrivilegeIncrease(
......@@ -108,6 +87,22 @@ PermissionIDSet ChromePermissionMessageProvider::GetAllPermissionIDs(
return permission_ids;
}
PermissionMessages
ChromePermissionMessageProvider::GetPowerfulPermissionMessages(
const PermissionIDSet& permissions) const {
std::vector<ChromePermissionMessageRule> all_rules =
ChromePermissionMessageRule::GetAllRules();
// TODO(crbug.com/888981): Find a better way to get wanted rules. Maybe add a
// bool to each one telling if we should consider it here or not.
constexpr size_t rules_considered = 15;
const std::vector<extensions::ChromePermissionMessageRule> rules(
all_rules.begin(),
all_rules.begin() + std::min(rules_considered, all_rules.size()));
return GetPermissionMessagesHelper(permissions, rules);
}
void ChromePermissionMessageProvider::AddAPIPermissions(
const PermissionSet& permissions,
PermissionIDSet* permission_ids) const {
......@@ -267,4 +262,30 @@ bool ChromePermissionMessageProvider::IsHostPrivilegeIncrease(
return false;
}
PermissionMessages ChromePermissionMessageProvider::GetPermissionMessagesHelper(
const PermissionIDSet& permissions,
const std::vector<ChromePermissionMessageRule>& rules) const {
// Apply each of the rules, in order, to generate the messages for the given
// permissions. Once a permission is used in a rule, remove it from the set
// of available permissions so it cannot be applied to subsequent rules.
PermissionIDSet remaining_permissions = permissions;
PermissionMessages messages;
for (const auto& rule : rules) {
// Only apply the rule if we have all the required permission IDs.
if (remaining_permissions.ContainsAllIDs(rule.required_permissions())) {
// We can apply the rule. Add all the required permissions, and as many
// optional permissions as we can, to the new message.
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;
}
} // namespace extensions
......@@ -6,9 +6,11 @@
#define CHROME_COMMON_EXTENSIONS_PERMISSIONS_CHROME_PERMISSION_MESSAGE_PROVIDER_H_
#include <set>
#include <vector>
#include "base/macros.h"
#include "base/strings/string16.h"
#include "chrome/common/extensions/permissions/chrome_permission_message_rules.h"
#include "extensions/common/permissions/permission_message_provider.h"
namespace extensions {
......@@ -27,6 +29,8 @@ class ChromePermissionMessageProvider : public PermissionMessageProvider {
// PermissionMessageProvider implementation.
PermissionMessages GetPermissionMessages(
const PermissionIDSet& permissions) const override;
PermissionMessages GetPowerfulPermissionMessages(
const PermissionIDSet& permissions) const override;
bool IsPrivilegeIncrease(const PermissionSet& granted_permissions,
const PermissionSet& requested_permissions,
Manifest::Type extension_type) const override;
......@@ -60,6 +64,10 @@ class ChromePermissionMessageProvider : public PermissionMessageProvider {
const PermissionSet& requested_permissions,
Manifest::Type extension_type) const;
PermissionMessages GetPermissionMessagesHelper(
const PermissionIDSet& permissions,
const std::vector<ChromePermissionMessageRule>& rules) const;
DISALLOW_COPY_AND_ASSIGN(ChromePermissionMessageProvider);
};
......
......@@ -43,6 +43,15 @@ class ChromePermissionMessageProviderUnittest : public testing::Test {
type));
}
PermissionMessages GetPowerfulMessages(const APIPermissionSet& permissions,
Manifest::Type type) {
return message_provider_->GetPowerfulPermissionMessages(
message_provider_->GetAllPermissionIDs(
PermissionSet(permissions, ManifestPermissionSet(), URLPatternSet(),
URLPatternSet()),
type));
}
bool IsPrivilegeIncrease(const APIPermissionSet& granted_permissions,
const APIPermissionSet& requested_permissions) {
return message_provider_->IsPrivilegeIncrease(
......@@ -161,4 +170,36 @@ TEST_F(ChromePermissionMessageProviderUnittest,
messages.front().message());
}
// Checks whether powerful permissions are returned correctly.
TEST_F(ChromePermissionMessageProviderUnittest, PowerfulPermissions) {
{
APIPermissionSet permissions;
permissions.insert(APIPermission::kTab);
PermissionMessages messages =
GetPowerfulMessages(permissions, Manifest::TYPE_EXTENSION);
ASSERT_EQ(1U, messages.size());
EXPECT_EQ(
l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_HISTORY_READ),
messages.front().message());
}
{
APIPermissionSet permissions;
permissions.insert(APIPermission::kBookmark);
PermissionMessages messages =
GetPowerfulMessages(permissions, Manifest::TYPE_EXTENSION);
ASSERT_EQ(0U, messages.size());
}
{
APIPermissionSet permissions;
permissions.insert(APIPermission::kTab);
permissions.insert(APIPermission::kBookmark);
PermissionMessages messages =
GetPowerfulMessages(permissions, Manifest::TYPE_EXTENSION);
ASSERT_EQ(1U, messages.size());
EXPECT_EQ(
l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_HISTORY_READ),
messages.front().message());
}
}
} // namespace extensions
......@@ -43,6 +43,11 @@ class ShellPermissionMessageProvider : public PermissionMessageProvider {
return PermissionMessages();
}
PermissionMessages GetPowerfulPermissionMessages(
const PermissionIDSet& permissions) const override {
return PermissionMessages();
}
bool IsPrivilegeIncrease(const PermissionSet& granted_permissions,
const PermissionSet& requested_permissions,
Manifest::Type extension_type) const override {
......
<?xml version="1.0" encoding="utf-8"?>
<grit-part>
<message name="IDS_MANAGEMENT_TITLE" desc="Title of chrome://management page, which shows the administrator's capabilities in a managed environment" translateable="false">
Management
Management overview
</message>
<message name="IDS_MANAGEMENT_DEVICE_NOT_MANAGED" desc="Message indicating that the device is not managed" translateable="false">
Your device is not managed by an administrator
</message>
<message name="IDS_MANAGEMENT_DEVICE_MANAGED_BY" desc="Message indicating that the device is enterprise enrolled to be managed by an administrator, from a specific domain" translateable="false">
Your device is managed by <ph name="ENROLLMENT_DOMAIN">$1<ex>example.com</ex></ph>
Your device is managed by <ph name="ENROLLMENT_DOMAIN">$1<ex>example.com</ex></ph>. This means your administrators may remotely configure your device and account.
</message>
<message name="IDS_MANAGEMENT_DEVICE_MANAGED" desc="Message indicating that the device is enterprise enrolled to be managed by an administrator" translateable="false">
Your device is managed by an administrator
</message>
<message name="IDS_MANAGEMENT_DEVICE_REPORTING" desc="Title of the types of device reporting section of the page" translateable="false">
Device reporting
</message>
<message name="IDS_MANAGEMENT_DEVICE_CONFIGURATION" desc="Message telling users that their administrator has set some specific policies on their device" translateable="false">
Your device has been configured to:
</message>
......@@ -31,4 +35,17 @@
<message name="IDS_MANAGEMENT_REPORT_DEVICE_USERS" desc="Message stating that administrators see all users that have used the device recently." translateable="false">
report list of device users that have recently logged in
</message>
<message name="IDS_MANAGEMENT_EXTENSION_REPORTING" desc="Title of the force installed extensions section of the page" translateable="false">
Extension reporting
</message>
<message name="IDS_MANAGEMENT_EXTENSIONS_INSTALLED" desc="Message describing that the administrator has installed some powerful extensions on the managed user's browser" translateable="false">
Your administrators have installed extensions with powerful capabilities
</message>
<message name="IDS_MANAGEMENT_EXTENSIONS_NAME" desc="Title of a column of the extension table showing the name of the extension" translateable="false">
Name
</message>
<message name="IDS_MANAGEMENT_EXTENSIONS_PERMISSIONS" desc="Title of a column of the extension table showing the permissions of the extension" translateable="false">
Permissions
</message>
</grit-part>
......@@ -33,6 +33,12 @@ class PermissionMessageProvider {
virtual PermissionMessages GetPermissionMessages(
const PermissionIDSet& permissions) const = 0;
// Same as the above function, but instead of returning the full list of
// permission messages, returns just a list of permissions considered
// powerful.
virtual PermissionMessages GetPowerfulPermissionMessages(
const PermissionIDSet& permissions) const = 0;
// Returns true if |requested_permissions| has a greater privilege level than
// |granted_permissions|.
// Whether certain permissions are considered varies by extension type.
......
......@@ -36,6 +36,11 @@ class ShellPermissionMessageProvider : public PermissionMessageProvider {
return PermissionMessages();
}
PermissionMessages GetPowerfulPermissionMessages(
const PermissionIDSet& permissions) const override {
return PermissionMessages();
}
bool IsPrivilegeIncrease(const PermissionSet& granted_permissions,
const PermissionSet& requested_permissions,
Manifest::Type extension_type) const override {
......
......@@ -17,6 +17,11 @@ PermissionMessages TestPermissionMessageProvider::GetPermissionMessages(
return PermissionMessages();
}
PermissionMessages TestPermissionMessageProvider::GetPowerfulPermissionMessages(
const PermissionIDSet& permissions) const {
return PermissionMessages();
}
bool TestPermissionMessageProvider::IsPrivilegeIncrease(
const PermissionSet& granted_permissions,
const PermissionSet& requested_permissions,
......
......@@ -18,6 +18,8 @@ class TestPermissionMessageProvider : public PermissionMessageProvider {
private:
PermissionMessages GetPermissionMessages(
const PermissionIDSet& permissions) const override;
PermissionMessages GetPowerfulPermissionMessages(
const PermissionIDSet& permissions) const override;
bool IsPrivilegeIncrease(const PermissionSet& granted_permissions,
const PermissionSet& requested_permissions,
Manifest::Type extension_type) const override;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment