From 609aa2e00a86b9a315f41856794f32cb6d1073d7 Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Wed, 10 Nov 2021 22:51:33 +0100 Subject: [PATCH] [platform] postponing plugin privacy dialog till the next start when installing plugins in headless mode (IDEA-259912) GitOrigin-RevId: e80c71a52e4b1685f163bd860e8ac6aad0960bbf --- .../resources/messages/CoreBundle.properties | 11 ++- .../ide/plugins/PluginManagerCore.java | 86 ++++++++++++++++--- .../resources/messages/IdeBundle.properties | 7 -- .../ide/plugins/PluginManagerMain.java | 59 +++++++++---- .../com/intellij/idea/ApplicationLoader.kt | 7 +- 5 files changed, 130 insertions(+), 40 deletions(-) diff --git a/platform/core-api/resources/messages/CoreBundle.properties b/platform/core-api/resources/messages/CoreBundle.properties index 9f65b3374317..af048e811186 100644 --- a/platform/core-api/resources/messages/CoreBundle.properties +++ b/platform/core-api/resources/messages/CoreBundle.properties @@ -73,4 +73,13 @@ failed.to.make.the.following.files.writable.error.message=Failed to make the fol failed.to.make.file.writable.error.message=Failed to make {0} writable. vfs.corruption.notification.title=File system caches corrupted -vfs.corruption.notification.text=Restart is required to rebuild corrupted caches \ No newline at end of file +vfs.corruption.notification.text=Restart is required to rebuild corrupted caches + +third.party.plugins.privacy.note.title=Third-Party Plugins Privacy Note +third.party.plugins.privacy.note.text=The following plugins aren''t coming from JetBrains:

\ + {0}

\ + Using third-party plugins may involve a plugin vendor processing your personal data.
\ + Please check the plugin vendor\u2019s documentation for details concerning personal data processing.

\ + JetBrains is not responsible for any processing of your personal data by any third-party plugin vendors. +third.party.plugins.privacy.note.accept=Accept +third.party.plugins.privacy.note.disable=Disable Plugins diff --git a/platform/core-impl/src/com/intellij/ide/plugins/PluginManagerCore.java b/platform/core-impl/src/com/intellij/ide/plugins/PluginManagerCore.java index 3bc919b0448b..2693c1abd014 100644 --- a/platform/core-impl/src/com/intellij/ide/plugins/PluginManagerCore.java +++ b/platform/core-impl/src/com/intellij/ide/plugins/PluginManagerCore.java @@ -4,10 +4,9 @@ package com.intellij.ide.plugins; import com.intellij.ReviseWhenPortedToJDK; import com.intellij.core.CoreBundle; import com.intellij.diagnostic.*; +import com.intellij.icons.AllIcons; import com.intellij.ide.plugins.cl.PluginAwareClassLoader; -import com.intellij.ide.plugins.cl.PluginClassLoader; import com.intellij.openapi.application.PathManager; -import com.intellij.openapi.application.ex.ApplicationInfoEx; import com.intellij.openapi.application.impl.ApplicationInfoImpl; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.extensions.PluginDescriptor; @@ -17,6 +16,7 @@ import com.intellij.openapi.util.BuildNumber; import com.intellij.openapi.util.NlsContexts; import com.intellij.openapi.util.NlsSafe; import com.intellij.openapi.util.io.FileUtilRt; +import com.intellij.openapi.util.io.NioFiles; import com.intellij.openapi.util.text.HtmlChunk; import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.PlatformUtils; @@ -24,6 +24,7 @@ import com.intellij.util.containers.ContainerUtil; import com.intellij.util.lang.UrlClassLoader; import org.jetbrains.annotations.*; +import javax.swing.*; import java.awt.*; import java.io.*; import java.lang.invoke.MethodHandles; @@ -39,6 +40,7 @@ import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; +import java.util.stream.StreamSupport; /** * See Plugin Model documentation. @@ -48,8 +50,8 @@ import java.util.stream.Stream; public final class PluginManagerCore { public static final @NonNls String META_INF = "META-INF/"; - public static final PluginId CORE_ID = PluginId.getId("com.intellij"); public static final String CORE_PLUGIN_ID = "com.intellij"; + public static final PluginId CORE_ID = PluginId.getId(CORE_PLUGIN_ID); public static final PluginId JAVA_PLUGIN_ID = PluginId.getId("com.intellij.java"); static final PluginId JAVA_MODULE_ID = PluginId.getId("com.intellij.modules.java"); @@ -74,6 +76,9 @@ public final class PluginManagerCore { private static final boolean IGNORE_DISABLED_PLUGINS = Boolean.getBoolean("idea.ignore.disabled.plugins"); private static final MethodType HAS_LOADED_CLASS_METHOD_TYPE = MethodType.methodType(boolean.class, String.class); + private static final String THIRD_PARTY_PLUGINS_FILE = "alien_plugins.txt"; + private static volatile @Nullable Boolean ourThirdPartyPluginsNoteAccepted = null; + private static Reference>> brokenPluginVersions; private static volatile @Nullable PluginSet pluginSet; private static Map pluginLoadingErrors; @@ -338,13 +343,7 @@ public final class PluginManagerCore { } public static boolean isDevelopedByJetBrains(@NotNull PluginDescriptor plugin) { - String vendor = plugin.getVendor(); - return isDevelopedByJetBrains(vendor) || - (vendor == null && // a core plugin - !(plugin.getPluginClassLoader() instanceof PluginClassLoader) && - plugin instanceof IdeaPluginDescriptorImpl && - !((IdeaPluginDescriptorImpl)plugin).isUseIdeaClassLoader && - ApplicationInfoEx.getInstanceEx().isVendorJetBrains()); + return CORE_ID.equals(plugin.getPluginId()) || SPECIAL_IDEA_PLUGIN_ID.equals(plugin.getPluginId()) || isDevelopedByJetBrains(plugin.getVendor()); } public static boolean isDevelopedByJetBrains(@Nullable String vendorString) { @@ -799,6 +798,23 @@ public final class PluginManagerCore { Collections.singletonList(CORE_ID + " (platform prefix: " + System.getProperty(PlatformUtils.PLATFORM_PREFIX_KEY) + ")")); } + Collection aliens = get3rdPartyPlugins(idMap); + if (!aliens.isEmpty()) { + if (GraphicsEnvironment.isHeadless()) { + getLogger().info("3rd-party plugin privacy note not accepted yet; disabling plugins for this headless session"); + aliens.forEach(descriptor -> descriptor.setEnabled(false)); + } + else if (!ask3rdPartyPluginsPrivacyConsent(aliens)) { + getLogger().info("3rd-party plugin privacy note declined; disabling plugins"); + aliens.forEach(descriptor -> descriptor.setEnabled(false)); + PluginEnabler.HEADLESS.disableById(aliens.stream().map(descriptor -> descriptor.getPluginId()).collect(Collectors.toSet())); + ourThirdPartyPluginsNoteAccepted = Boolean.FALSE; + } + else { + ourThirdPartyPluginsNoteAccepted = Boolean.TRUE; + } + } + PluginSetBuilder pluginSetBuilder = new PluginSetBuilder(loadingResult.getEnabledPlugins()); disableIncompatiblePlugins(pluginSetBuilder, idMap, pluginErrorsById); pluginSetBuilder.checkPluginCycles(globalErrors); @@ -837,6 +853,56 @@ public final class PluginManagerCore { return new PluginManagerState(pluginSet, disabledRequired, disabledAfterInit); } + @ApiStatus.Internal + static @Nullable Boolean isThirdPartyPluginsNoteAccepted() { + Boolean result = ourThirdPartyPluginsNoteAccepted; + ourThirdPartyPluginsNoteAccepted = null; + return result; + } + + @ApiStatus.Internal + static synchronized void write3rdPartyPlugins(@NotNull Collection aliens) { + Path file = Paths.get(PathManager.getConfigPath(), THIRD_PARTY_PLUGINS_FILE); + try { + NioFiles.createDirectories(file.getParent()); + try (BufferedWriter writer = Files.newBufferedWriter(file, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.APPEND)) { + //noinspection SSBasedInspection + writePluginsList(aliens.stream().map(PluginDescriptor::getPluginId).collect(Collectors.toList()), writer); + } + } + catch (IOException e) { + getLogger().error(file.toString(), e); + } + } + + private static Collection get3rdPartyPlugins(Map descriptors) { + Path file = Paths.get(PathManager.getConfigPath(), THIRD_PARTY_PLUGINS_FILE); + if (Files.exists(file)) { + try { + List ids = Files.readAllLines(file); + Files.delete(file); + return ids.stream().map(id -> descriptors.get(PluginId.getId(id))).filter(descriptor -> descriptor != null).collect(Collectors.toList()); + } + catch (IOException e) { + getLogger().error(file.toString(), e); + } + } + + return Collections.emptyList(); + } + + private static boolean ask3rdPartyPluginsPrivacyConsent(Iterable descriptors) { + String title = CoreBundle.message("third.party.plugins.privacy.note.title"); + String pluginList = StreamSupport.stream(descriptors.spliterator(), false) + .map(descriptor -> "   " + descriptor.getName() + " (" + descriptor.getVendor() + ')') + .collect(Collectors.joining("
")); + String text = CoreBundle.message("third.party.plugins.privacy.note.text", pluginList); + String[] buttons = {CoreBundle.message("third.party.plugins.privacy.note.accept"), CoreBundle.message("third.party.plugins.privacy.note.disable")}; + int choice = JOptionPane.showOptionDialog(null, text, title, JOptionPane.OK_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE, + AllIcons.General.WarningDialog, buttons, buttons[0]); + return choice == 0; + } + @SuppressWarnings("DuplicatedCode") private static @Nullable Map> checkAndPut(@NotNull IdeaPluginDescriptorImpl descriptor, @NotNull PluginId id, diff --git a/platform/platform-api/resources/messages/IdeBundle.properties b/platform/platform-api/resources/messages/IdeBundle.properties index 285b5abe0fe2..ffdce3cef8fb 100644 --- a/platform/platform-api/resources/messages/IdeBundle.properties +++ b/platform/platform-api/resources/messages/IdeBundle.properties @@ -1002,13 +1002,6 @@ run.anything.context.separator.modules=Modules run.anything.context.tooltip=Choose context the current command will be executed in run.anything.accessible.name=Run anything -third.party.plugins.privacy.note.title=Third-Party Plugins Privacy Note -third.party.plugins.privacy.note.message=Using third-party plugins may involve a plugin vendor processing your personal data.
\ - Please check the plugin vendor\u2019s documentation for details concerning personal data processing.

\ - JetBrains is not responsible for any processing of your personal data by any third-party plugin vendors. -third.party.plugins.privacy.note.yes=Accept -third.party.plugins.privacy.note.no=Cancel - plugin.signature.not.signed=The ''{0}'' plugin has not been digitally signed, and its authenticity cannot be verified. Installing or updating unsigned plugins may expose your system to risk.

Plugin details: Id: {1} Version: {2} {3} jetbrains.certificate.not.found=JetBrains certificate is not found. jetbrains.certificate.vendor=Vendor: {0} diff --git a/platform/platform-impl/src/com/intellij/ide/plugins/PluginManagerMain.java b/platform/platform-impl/src/com/intellij/ide/plugins/PluginManagerMain.java index b041b744f967..bd0f8fd83c17 100644 --- a/platform/platform-impl/src/com/intellij/ide/plugins/PluginManagerMain.java +++ b/platform/platform-impl/src/com/intellij/ide/plugins/PluginManagerMain.java @@ -1,10 +1,13 @@ // Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.ide.plugins; +import com.intellij.CommonBundle; +import com.intellij.core.CoreBundle; import com.intellij.ide.BrowserUtil; import com.intellij.ide.IdeBundle; import com.intellij.ide.plugins.marketplace.statistics.PluginManagerUsageCollector; import com.intellij.ide.plugins.marketplace.statistics.enums.DialogAcceptanceResultEnum; +import com.intellij.idea.Main; import com.intellij.notification.Notification; import com.intellij.notification.NotificationAction; import com.intellij.notification.NotificationType; @@ -44,6 +47,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.function.Consumer; +import java.util.stream.Collectors; public final class PluginManagerMain { private PluginManagerMain() { } @@ -298,31 +302,50 @@ public final class PluginManagerMain { .notify(project); } - public static boolean checkThirdPartyPluginsAllowed(@NotNull Iterable descriptors) { + public static boolean checkThirdPartyPluginsAllowed(@NotNull Collection descriptors) { + @SuppressWarnings("SSBasedInspection") Collection aliens = descriptors.stream() + .filter(descriptor -> !(descriptor.isBundled() || PluginManagerCore.isDevelopedByJetBrains(descriptor))) + .collect(Collectors.toList()); + if (aliens.isEmpty()) return true; + UpdateSettings updateSettings = UpdateSettings.getInstance(); if (updateSettings.isThirdPartyPluginsAllowed()) { PluginManagerUsageCollector.thirdPartyAcceptanceCheck(DialogAcceptanceResultEnum.AUTO_ACCEPTED); return true; } - for (IdeaPluginDescriptor descriptor : descriptors) { - if (!PluginManagerCore.isDevelopedByJetBrains(descriptor)) { - String title = IdeBundle.message("third.party.plugins.privacy.note.title"); - String message = IdeBundle.message("third.party.plugins.privacy.note.message"); - String yesText = IdeBundle.message("third.party.plugins.privacy.note.yes"); - String noText = IdeBundle.message("third.party.plugins.privacy.note.no"); - if (Messages.showYesNoDialog(message, title, yesText, noText, Messages.getWarningIcon()) == Messages.YES) { - updateSettings.setThirdPartyPluginsAllowed(true); - PluginManagerUsageCollector.thirdPartyAcceptanceCheck(DialogAcceptanceResultEnum.ACCEPTED); - return true; - } - else { - PluginManagerUsageCollector.thirdPartyAcceptanceCheck(DialogAcceptanceResultEnum.DECLINED); - return false; - } - } + if (Main.isHeadless()) { + // postponing the dialog till the next start + PluginManagerCore.write3rdPartyPlugins(aliens); + return true; } - return true; + String title = CoreBundle.message("third.party.plugins.privacy.note.title"); + String pluginList = aliens.stream() + .map(descriptor -> "   " + descriptor.getName() + " (" + descriptor.getVendor() + ')') + .collect(Collectors.joining("
")); + String message = CoreBundle.message("third.party.plugins.privacy.note.text", pluginList); + String yesText = CoreBundle.message("third.party.plugins.privacy.note.accept"), noText = CommonBundle.getCancelButtonText(); + if (Messages.showYesNoDialog(message, title, yesText, noText, Messages.getWarningIcon()) == Messages.YES) { + updateSettings.setThirdPartyPluginsAllowed(true); + PluginManagerUsageCollector.thirdPartyAcceptanceCheck(DialogAcceptanceResultEnum.ACCEPTED); + return true; + } + else { + PluginManagerUsageCollector.thirdPartyAcceptanceCheck(DialogAcceptanceResultEnum.DECLINED); + return false; + } + } + + @ApiStatus.Internal + public static void checkThirdPartyPluginsAllowed() { + Boolean noteAccepted = PluginManagerCore.isThirdPartyPluginsNoteAccepted(); + if (noteAccepted == Boolean.TRUE) { + UpdateSettings.getInstance().setThirdPartyPluginsAllowed(true); + PluginManagerUsageCollector.thirdPartyAcceptanceCheck(DialogAcceptanceResultEnum.ACCEPTED); + } + else if (noteAccepted == Boolean.FALSE) { + PluginManagerUsageCollector.thirdPartyAcceptanceCheck(DialogAcceptanceResultEnum.DECLINED); + } } } diff --git a/platform/platform-impl/src/com/intellij/idea/ApplicationLoader.kt b/platform/platform-impl/src/com/intellij/idea/ApplicationLoader.kt index c7d72672dbb7..0cda071856f5 100644 --- a/platform/platform-impl/src/com/intellij/idea/ApplicationLoader.kt +++ b/platform/platform-impl/src/com/intellij/idea/ApplicationLoader.kt @@ -8,10 +8,7 @@ import com.intellij.diagnostic.* import com.intellij.diagnostic.StartUpMeasurer.Activities import com.intellij.icons.AllIcons import com.intellij.ide.* -import com.intellij.ide.plugins.IdeaPluginDescriptorImpl -import com.intellij.ide.plugins.PluginManagerCore -import com.intellij.ide.plugins.PluginSet -import com.intellij.ide.plugins.StartupAbortedException +import com.intellij.ide.plugins.* import com.intellij.ide.ui.laf.darcula.DarculaLaf import com.intellij.openapi.application.* import com.intellij.openapi.application.ex.ApplicationEx @@ -186,6 +183,8 @@ private fun startApp(app: ApplicationImpl, addActivateAndWindowsCliListeners() initAppActivity.end() + PluginManagerMain.checkThirdPartyPluginsAllowed() + if (starter.requiredModality == ApplicationStarter.NOT_IN_EDT) { starter.main(args) // no need to use pool once plugins are loaded