From b03e750ac2fb3fb3fe892cd6a77f930149cef539 Mon Sep 17 00:00:00 2001 From: Eryk Rakowski Date: Wed, 2 Sep 2020 02:59:56 +0200 Subject: [PATCH] feat(extensions): add support for some chrome.management APIs (#25098) * fix: initialize management policy * fix(extensions): crash when using chrome.management * test: add tests * docs: add a note about chrome.management * fix: lint errors * fix: lint errors * fix: remove favicon_service include * fix: add missing management permission * docs: more supported apis * fix: extensions.md line endings --- docs/api/extensions.md | 12 + filenames.gni | 2 + .../electron_management_api_delegate.cc | 235 ++++++++++++++++++ .../electron_management_api_delegate.h | 86 +++++++ .../extensions/electron_extension_system.cc | 5 +- .../extensions/electron_extension_system.h | 1 + .../electron_extensions_api_client.cc | 6 + .../electron_extensions_api_client.h | 1 + .../extensions/api/_permission_features.json | 64 ++++- .../electron_extensions_api_provider.cc | 1 + spec-main/extensions-spec.ts | 20 ++ 11 files changed, 430 insertions(+), 3 deletions(-) create mode 100644 shell/browser/extensions/api/management/electron_management_api_delegate.cc create mode 100644 shell/browser/extensions/api/management/electron_management_api_delegate.h diff --git a/docs/api/extensions.md b/docs/api/extensions.md index 4a93951024921..f483d6318b056 100644 --- a/docs/api/extensions.md +++ b/docs/api/extensions.md @@ -102,3 +102,15 @@ The following methods of `chrome.tabs` are supported: > **Note:** In Chrome, passing `-1` as a tab ID signifies the "currently active > tab". Since Electron has no such concept, passing `-1` as a tab ID is not > supported and will raise an error. + +### `chrome.management` + +The following methods of `chrome.management` are supported: + +- `chrome.management.getAll` +- `chrome.management.get` +- `chrome.management.getSelf` +- `chrome.management.getPermissionWarningsById` +- `chrome.management.getPermissionWarningsByManifest` +- `chrome.management.onEnabled` +- `chrome.management.onDisabled` diff --git a/filenames.gni b/filenames.gni index bd2284e045233..e5a158ed45b39 100644 --- a/filenames.gni +++ b/filenames.gni @@ -626,6 +626,8 @@ filenames = { "shell/browser/extensions/api/resources_private/resources_private_api.h", "shell/browser/extensions/api/runtime/electron_runtime_api_delegate.cc", "shell/browser/extensions/api/runtime/electron_runtime_api_delegate.h", + "shell/browser/extensions/api/management/electron_management_api_delegate.cc", + "shell/browser/extensions/api/management/electron_management_api_delegate.h", "shell/browser/extensions/api/tabs/tabs_api.cc", "shell/browser/extensions/api/tabs/tabs_api.h", "shell/browser/extensions/api/streams_private/streams_private_api.cc", diff --git a/shell/browser/extensions/api/management/electron_management_api_delegate.cc b/shell/browser/extensions/api/management/electron_management_api_delegate.cc new file mode 100644 index 0000000000000..19e0cd9c84c8d --- /dev/null +++ b/shell/browser/extensions/api/management/electron_management_api_delegate.cc @@ -0,0 +1,235 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// TODO(sentialx): emit relevant events in Electron's session? +#include "shell/browser/extensions/api/management/electron_management_api_delegate.h" + +#include +#include +#include + +#include "base/bind.h" +#include "base/macros.h" +#include "base/strings/strcat.h" +#include "base/strings/utf_string_conversions.h" +#include "base/task/post_task.h" +#include "chrome/common/extensions/extension_metrics.h" +#include "chrome/common/extensions/manifest_handlers/app_launch_info.h" +#include "chrome/common/web_application_info.h" +#include "chrome/common/webui_url_constants.h" +#include "content/public/browser/browser_context.h" +#include "content/public/browser/web_contents.h" +#include "extensions/browser/api/management/management_api.h" +#include "extensions/browser/api/management/management_api_constants.h" +#include "extensions/browser/disable_reason.h" +#include "extensions/browser/extension_prefs.h" +#include "extensions/browser/extension_registry.h" +#include "extensions/browser/extension_system.h" +#include "extensions/common/api/management.h" +#include "extensions/common/extension.h" +#include "services/data_decoder/public/cpp/data_decoder.h" +#include "third_party/blink/public/mojom/manifest/display_mode.mojom.h" + +namespace { +class ManagementSetEnabledFunctionInstallPromptDelegate + : public extensions::InstallPromptDelegate { + public: + ManagementSetEnabledFunctionInstallPromptDelegate( + content::WebContents* web_contents, + content::BrowserContext* browser_context, + const extensions::Extension* extension, + const base::Callback& callback) { + // TODO(sentialx): emit event + } + ~ManagementSetEnabledFunctionInstallPromptDelegate() override {} + + private: + base::WeakPtrFactory + weak_factory_{this}; + + DISALLOW_COPY_AND_ASSIGN(ManagementSetEnabledFunctionInstallPromptDelegate); +}; + +class ManagementUninstallFunctionUninstallDialogDelegate + : public extensions::UninstallDialogDelegate { + public: + ManagementUninstallFunctionUninstallDialogDelegate( + extensions::ManagementUninstallFunctionBase* function, + const extensions::Extension* target_extension, + bool show_programmatic_uninstall_ui) { + // TODO(sentialx): emit event + } + + ~ManagementUninstallFunctionUninstallDialogDelegate() override {} + + private: + DISALLOW_COPY_AND_ASSIGN(ManagementUninstallFunctionUninstallDialogDelegate); +}; + +} // namespace + +ElectronManagementAPIDelegate::ElectronManagementAPIDelegate() {} + +ElectronManagementAPIDelegate::~ElectronManagementAPIDelegate() {} + +void ElectronManagementAPIDelegate::LaunchAppFunctionDelegate( + const extensions::Extension* extension, + content::BrowserContext* context) const { + // TODO(sentialx): emit event + extensions::RecordAppLaunchType(extension_misc::APP_LAUNCH_EXTENSION_API, + extension->GetType()); +} + +GURL ElectronManagementAPIDelegate::GetFullLaunchURL( + const extensions::Extension* extension) const { + return extensions::AppLaunchInfo::GetFullLaunchURL(extension); +} + +extensions::LaunchType ElectronManagementAPIDelegate::GetLaunchType( + const extensions::ExtensionPrefs* prefs, + const extensions::Extension* extension) const { + // TODO(sentialx) + return extensions::LAUNCH_TYPE_DEFAULT; +} + +void ElectronManagementAPIDelegate:: + GetPermissionWarningsByManifestFunctionDelegate( + extensions::ManagementGetPermissionWarningsByManifestFunction* function, + const std::string& manifest_str) const { + data_decoder::DataDecoder::ParseJsonIsolated( + manifest_str, + base::BindOnce( + &extensions::ManagementGetPermissionWarningsByManifestFunction:: + OnParse, + function)); +} + +std::unique_ptr +ElectronManagementAPIDelegate::SetEnabledFunctionDelegate( + content::WebContents* web_contents, + content::BrowserContext* browser_context, + const extensions::Extension* extension, + const base::Callback& callback) const { + return std::unique_ptr( + new ManagementSetEnabledFunctionInstallPromptDelegate( + web_contents, browser_context, extension, callback)); +} + +std::unique_ptr +ElectronManagementAPIDelegate::UninstallFunctionDelegate( + extensions::ManagementUninstallFunctionBase* function, + const extensions::Extension* target_extension, + bool show_programmatic_uninstall_ui) const { + return std::unique_ptr( + new ManagementUninstallFunctionUninstallDialogDelegate( + function, target_extension, show_programmatic_uninstall_ui)); +} + +bool ElectronManagementAPIDelegate::CreateAppShortcutFunctionDelegate( + extensions::ManagementCreateAppShortcutFunction* function, + const extensions::Extension* extension, + std::string* error) const { + return false; // TODO(sentialx): route event and return true +} + +std::unique_ptr +ElectronManagementAPIDelegate::GenerateAppForLinkFunctionDelegate( + extensions::ManagementGenerateAppForLinkFunction* function, + content::BrowserContext* context, + const std::string& title, + const GURL& launch_url) const { + // TODO(sentialx) + return nullptr; +} + +bool ElectronManagementAPIDelegate::CanContextInstallWebApps( + content::BrowserContext* context) const { + // TODO(sentialx) + return false; +} + +void ElectronManagementAPIDelegate::InstallOrLaunchReplacementWebApp( + content::BrowserContext* context, + const GURL& web_app_url, + InstallOrLaunchWebAppCallback callback) const { + // TODO(sentialx) +} + +bool ElectronManagementAPIDelegate::CanContextInstallAndroidApps( + content::BrowserContext* context) const { + return false; +} + +void ElectronManagementAPIDelegate::CheckAndroidAppInstallStatus( + const std::string& package_name, + AndroidAppInstallStatusCallback callback) const { + std::move(callback).Run(false); +} + +void ElectronManagementAPIDelegate::InstallReplacementAndroidApp( + const std::string& package_name, + InstallAndroidAppCallback callback) const { + std::move(callback).Run(false); +} + +void ElectronManagementAPIDelegate::EnableExtension( + content::BrowserContext* context, + const std::string& extension_id) const { + // const extensions::Extension* extension = + // extensions::ExtensionRegistry::Get(context)->GetExtensionById( + // extension_id, extensions::ExtensionRegistry::EVERYTHING); + + // TODO(sentialx): we don't have ExtensionService + // If the extension was disabled for a permissions increase, the Management + // API will have displayed a re-enable prompt to the user, so we know it's + // safe to grant permissions here. + // extensions::ExtensionSystem::Get(context) + // ->extension_service() + // ->GrantPermissionsAndEnableExtension(extension); +} + +void ElectronManagementAPIDelegate::DisableExtension( + content::BrowserContext* context, + const extensions::Extension* source_extension, + const std::string& extension_id, + extensions::disable_reason::DisableReason disable_reason) const { + // TODO(sentialx): we don't have ExtensionService + // extensions::ExtensionSystem::Get(context) + // ->extension_service() + // ->DisableExtensionWithSource(source_extension, extension_id, + // disable_reason); +} + +bool ElectronManagementAPIDelegate::UninstallExtension( + content::BrowserContext* context, + const std::string& transient_extension_id, + extensions::UninstallReason reason, + base::string16* error) const { + // TODO(sentialx): we don't have ExtensionService + // return extensions::ExtensionSystem::Get(context) + // ->extension_service() + // ->UninstallExtension(transient_extension_id, reason, error); + return false; +} + +void ElectronManagementAPIDelegate::SetLaunchType( + content::BrowserContext* context, + const std::string& extension_id, + extensions::LaunchType launch_type) const { + // TODO(sentialx) + // extensions::SetLaunchType(context, extension_id, launch_type); +} + +GURL ElectronManagementAPIDelegate::GetIconURL( + const extensions::Extension* extension, + int icon_size, + ExtensionIconSet::MatchType match, + bool grayscale) const { + GURL icon_url(base::StringPrintf("%s%s/%d/%d%s", + chrome::kChromeUIExtensionIconURL, + extension->id().c_str(), icon_size, match, + grayscale ? "?grayscale=true" : "")); + CHECK(icon_url.is_valid()); + return icon_url; +} diff --git a/shell/browser/extensions/api/management/electron_management_api_delegate.h b/shell/browser/extensions/api/management/electron_management_api_delegate.h new file mode 100644 index 0000000000000..4c985debeed83 --- /dev/null +++ b/shell/browser/extensions/api/management/electron_management_api_delegate.h @@ -0,0 +1,86 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SHELL_BROWSER_EXTENSIONS_API_MANAGEMENT_ELECTRON_MANAGEMENT_API_DELEGATE_H_ +#define SHELL_BROWSER_EXTENSIONS_API_MANAGEMENT_ELECTRON_MANAGEMENT_API_DELEGATE_H_ + +#include +#include + +#include "base/task/cancelable_task_tracker.h" +#include "extensions/browser/api/management/management_api_delegate.h" + +class ElectronManagementAPIDelegate : public extensions::ManagementAPIDelegate { + public: + ElectronManagementAPIDelegate(); + ~ElectronManagementAPIDelegate() override; + + // ManagementAPIDelegate. + void LaunchAppFunctionDelegate( + const extensions::Extension* extension, + content::BrowserContext* context) const override; + GURL GetFullLaunchURL(const extensions::Extension* extension) const override; + extensions::LaunchType GetLaunchType( + const extensions::ExtensionPrefs* prefs, + const extensions::Extension* extension) const override; + void GetPermissionWarningsByManifestFunctionDelegate( + extensions::ManagementGetPermissionWarningsByManifestFunction* function, + const std::string& manifest_str) const override; + std::unique_ptr SetEnabledFunctionDelegate( + content::WebContents* web_contents, + content::BrowserContext* browser_context, + const extensions::Extension* extension, + const base::Callback& callback) const override; + std::unique_ptr + UninstallFunctionDelegate( + extensions::ManagementUninstallFunctionBase* function, + const extensions::Extension* target_extension, + bool show_programmatic_uninstall_ui) const override; + bool CreateAppShortcutFunctionDelegate( + extensions::ManagementCreateAppShortcutFunction* function, + const extensions::Extension* extension, + std::string* error) const override; + std::unique_ptr + GenerateAppForLinkFunctionDelegate( + extensions::ManagementGenerateAppForLinkFunction* function, + content::BrowserContext* context, + const std::string& title, + const GURL& launch_url) const override; + bool CanContextInstallWebApps( + content::BrowserContext* context) const override; + void InstallOrLaunchReplacementWebApp( + content::BrowserContext* context, + const GURL& web_app_url, + ManagementAPIDelegate::InstallOrLaunchWebAppCallback callback) + const override; + bool CanContextInstallAndroidApps( + content::BrowserContext* context) const override; + void CheckAndroidAppInstallStatus( + const std::string& package_name, + ManagementAPIDelegate::AndroidAppInstallStatusCallback callback) + const override; + void InstallReplacementAndroidApp( + const std::string& package_name, + ManagementAPIDelegate::InstallAndroidAppCallback callback) const override; + void EnableExtension(content::BrowserContext* context, + const std::string& extension_id) const override; + void DisableExtension( + content::BrowserContext* context, + const extensions::Extension* source_extension, + const std::string& extension_id, + extensions::disable_reason::DisableReason disable_reason) const override; + bool UninstallExtension(content::BrowserContext* context, + const std::string& transient_extension_id, + extensions::UninstallReason reason, + base::string16* error) const override; + void SetLaunchType(content::BrowserContext* context, + const std::string& extension_id, + extensions::LaunchType launch_type) const override; + GURL GetIconURL(const extensions::Extension* extension, + int icon_size, + ExtensionIconSet::MatchType match, + bool grayscale) const override; +}; + +#endif // SHELL_BROWSER_EXTENSIONS_API_MANAGEMENT_ELECTRON_MANAGEMENT_API_DELEGATE_H_ diff --git a/shell/browser/extensions/electron_extension_system.cc b/shell/browser/extensions/electron_extension_system.cc index 7e51adfd4118f..cd795cc8d7ce4 100755 --- a/shell/browser/extensions/electron_extension_system.cc +++ b/shell/browser/extensions/electron_extension_system.cc @@ -25,6 +25,7 @@ #include "extensions/browser/api/app_runtime/app_runtime_api.h" #include "extensions/browser/extension_registry.h" #include "extensions/browser/info_map.h" +#include "extensions/browser/management_policy.h" #include "extensions/browser/notification_types.h" #include "extensions/browser/null_app_sorting.h" #include "extensions/browser/quota_service.h" @@ -91,6 +92,8 @@ void ElectronExtensionSystem::InitForRegularProfile(bool extensions_enabled) { if (!browser_context_->IsOffTheRecord()) LoadComponentExtensions(); + + management_policy_.reset(new ManagementPolicy); } std::unique_ptr ParseManifest( @@ -130,7 +133,7 @@ RuntimeData* ElectronExtensionSystem::runtime_data() { } ManagementPolicy* ElectronExtensionSystem::management_policy() { - return nullptr; + return management_policy_.get(); } ServiceWorkerManager* ElectronExtensionSystem::service_worker_manager() { diff --git a/shell/browser/extensions/electron_extension_system.h b/shell/browser/extensions/electron_extension_system.h index ea5e07b84330b..e1f0d767be191 100755 --- a/shell/browser/extensions/electron_extension_system.h +++ b/shell/browser/extensions/electron_extension_system.h @@ -104,6 +104,7 @@ class ElectronExtensionSystem : public ExtensionSystem { std::unique_ptr quota_service_; std::unique_ptr shared_user_script_manager_; std::unique_ptr app_sorting_; + std::unique_ptr management_policy_; std::unique_ptr extension_loader_; diff --git a/shell/browser/extensions/electron_extensions_api_client.cc b/shell/browser/extensions/electron_extensions_api_client.cc index 5da08766e7e99..f79d8281059f4 100644 --- a/shell/browser/extensions/electron_extensions_api_client.cc +++ b/shell/browser/extensions/electron_extensions_api_client.cc @@ -9,6 +9,7 @@ #include "extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest_delegate.h" #include "printing/buildflags/buildflags.h" +#include "shell/browser/extensions/api/management/electron_management_api_delegate.h" #include "shell/browser/extensions/electron_extension_web_contents_observer.h" #include "shell/browser/extensions/electron_messaging_delegate.h" @@ -59,6 +60,11 @@ void ElectronExtensionsAPIClient::AttachWebContentsHelpers( #endif } +ManagementAPIDelegate* +ElectronExtensionsAPIClient::CreateManagementAPIDelegate() const { + return new ElectronManagementAPIDelegate; +} + std::unique_ptr ElectronExtensionsAPIClient::CreateMimeHandlerViewGuestDelegate( MimeHandlerViewGuest* guest) const { diff --git a/shell/browser/extensions/electron_extensions_api_client.h b/shell/browser/extensions/electron_extensions_api_client.h index 1747d7e982668..03d07d8453bb0 100644 --- a/shell/browser/extensions/electron_extensions_api_client.h +++ b/shell/browser/extensions/electron_extensions_api_client.h @@ -25,6 +25,7 @@ class ElectronExtensionsAPIClient : public ExtensionsAPIClient { std::unique_ptr CreateMimeHandlerViewGuestDelegate( MimeHandlerViewGuest* guest) const override; + ManagementAPIDelegate* CreateManagementAPIDelegate() const override; private: std::unique_ptr messaging_delegate_; diff --git a/shell/common/extensions/api/_permission_features.json b/shell/common/extensions/api/_permission_features.json index 6f0aacd586037..44d71cc852ef8 100644 --- a/shell/common/extensions/api/_permission_features.json +++ b/shell/common/extensions/api/_permission_features.json @@ -5,5 +5,65 @@ "extension" ], "location": "component" - } -} + }, + "management": [ + { + "channel": "stable", + "extension_types": [ + "extension", + "legacy_packaged_app" + ] + }, + { + "channel": "stable", + "extension_types": [ + "platform_app" + ], + "whitelist": [ + "AE27D69DBE571F4B1694F05C89B710C646792231", // Published ADT + // TODO(grv): clean up once Apps developer tool is published. + "5107DE9024C329EEA9C9A72D94C16723790C6422", // Apps Developer Tool. + "8C0B1873FFFB65E4D0F4D772879F7304CEF125C2", // Apps Editor old. + "FA0501B579070BB9CBD4FCAEC8CB0EDF22BA2F04", // Apps Editor published. + "EE17C698905F7F2E6DDC87C9C30F11E164C829F4", // Watchdog (Activity Log) + "90113DA9516526D24DAF156C629CC41C049E8882", // Watchdog Test Version + "4A4EA121622FCA3D78ED2AB534197F43D7189EE0", // Spark nightly build. + "9FDE6E7F06FCFA11D9A05041C7FF6D8AE662F5D1", // Spark release. + "50B4A905D522C06E27CA6D099E3E54BDA1F152C5", // Spark Beta channel. + "BA0C8BB92084C9741312D90D3EA882526853455F", // Spark dev channel. + "5F57A9AE8DFF5D6BB09DF8606270402612E871E5", // http://crbug.com/422624 + "46578A13607D38F1DC8E280C4F499FB0A2F9565C", // http://crbug.com/819404 + "898FB5A39687D210766B8998BA4530B99C9E6586", // http://crbug.com/819404 + "82F30B65397BC3E4ADE627BBD857AB8A58210648", // http://crbug.com/819404 + "C74B2AF138F9EDECD04D0965AB36CA66C8290466" // http://crbug.com/957772 + ] + }, + { + "channel": "stable", + "extension_types": [ + "hosted_app" + ], + "whitelist": [ + "B44D08FD98F1523ED5837D78D0A606EA9D6206E5" // Web Store + ] + }, + { + "channel": "stable", + "extension_types": [ + "platform_app" + ], + "session_types": [ + "kiosk" + ] + }, + { + "channel": "stable", + "dependencies": [ + "behavior:imprivata_login_screen_extension" + ], + "extension_types": [ + "login_screen_extension" + ] + } + ] +} \ No newline at end of file diff --git a/shell/common/extensions/electron_extensions_api_provider.cc b/shell/common/extensions/electron_extensions_api_provider.cc index e02aee3f2df07..8acf2c3875dab 100644 --- a/shell/common/extensions/electron_extensions_api_provider.cc +++ b/shell/common/extensions/electron_extensions_api_provider.cc @@ -38,6 +38,7 @@ constexpr APIPermissionInfo::InitInfo permissions_to_register[] = { APIPermissionInfo::kFlagInternal}, {APIPermission::kResourcesPrivate, "resourcesPrivate", APIPermissionInfo::kFlagCannotBeOptional}, + {APIPermission::kManagement, "management"}, }; base::span GetPermissionInfos() { return base::make_span(permissions_to_register); diff --git a/spec-main/extensions-spec.ts b/spec-main/extensions-spec.ts index 812cde9ba0ca0..113ddddde993e 100644 --- a/spec-main/extensions-spec.ts +++ b/spec-main/extensions-spec.ts @@ -37,6 +37,26 @@ describe('chrome extensions', () => { }); }); + it('does not crash when using chrome.management', async () => { + const customSession = session.fromPartition(`persist:${require('uuid').v4()}`); + const w = new BrowserWindow({ show: false, webPreferences: { session: customSession, sandbox: true } }); + w.loadURL('about:blank'); + + await emittedOnce(w.webContents, 'dom-ready'); + await customSession.loadExtension(path.join(fixtures, 'extensions', 'persistent-background-page')); + const args: any = await emittedOnce(app, 'web-contents-created'); + const wc: Electron.WebContents = args[1]; + await expect(wc.executeJavaScript(` + (() => { + return new Promise((resolve) => { + chrome.management.getSelf((info) => { + resolve(info); + }); + }) + })(); + `)).to.eventually.have.property('id'); + }); + it('can open WebSQLDatabase in a background page', async () => { const customSession = session.fromPartition(`persist:${require('uuid').v4()}`); const w = new BrowserWindow({ show: false, webPreferences: { session: customSession, sandbox: true } });