From 332ece532c4524143c63bc7bc0cf3ffa273ba3a6 Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Wed, 28 Jun 2023 18:17:13 +0300 Subject: [PATCH] PY-59132 Ask for user confirmation when installing packages with a quickfix Namely, if a package doesn't belong to the list of "well-known" packages, which we maintain for the Packaging Tool Window, such as Django, upon installing it with a quickfix (either "Install package" or "Install and import package") we ask for an explicit confirmation, mentioning that a package can execute potentially malicious code while being installed. This extra step should allow a user to double-check a package name, making sure that it's not a spoofing variant. This confirmation dialog can be disabled IDE-wide. GitOrigin-RevId: 012909e2695eabb1df96e0d45ed20befd0cf2e31 --- .../messages/PyBundle.properties | 5 +++ .../PyPackageRequirementsInspection.java | 31 +++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/python/pluginResources/messages/PyBundle.properties b/python/pluginResources/messages/PyBundle.properties index 844d68b992e9..52d1782c3558 100644 --- a/python/pluginResources/messages/PyBundle.properties +++ b/python/pluginResources/messages/PyBundle.properties @@ -944,6 +944,11 @@ python.packaging.progress.text.installing.specific.package=Installing package '' python.packaging.progress.title.installing.packages=Installing packages python.packaging.progress.title.uninstalling.packages=Uninstalling packages python.packaging.failed.to.install.packages.title=Failed to Install Packages +python.packaging.dialog.title.install.package.confirmation=Confirm Package Installation +python.packaging.dialog.message.install.package.confirmation=\ + You are installing the package {0}. \ + Make sure that the package name is correct.\n\n\ + Do you want to continue? python.unresolved.reference.inspection.install.package=Install package {0} diff --git a/python/src/com/jetbrains/python/inspections/PyPackageRequirementsInspection.java b/python/src/com/jetbrains/python/inspections/PyPackageRequirementsInspection.java index 539db16921a9..94ce12b6d595 100644 --- a/python/src/com/jetbrains/python/inspections/PyPackageRequirementsInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyPackageRequirementsInspection.java @@ -11,6 +11,8 @@ import com.intellij.codeInspection.util.InspectionMessage; import com.intellij.codeInspection.util.IntentionFamilyName; import com.intellij.core.CoreBundle; import com.intellij.execution.ExecutionException; +import com.intellij.icons.AllIcons; +import com.intellij.ide.util.PropertiesComponent; import com.intellij.idea.ActionsBundle; import com.intellij.notification.*; import com.intellij.openapi.application.ApplicationManager; @@ -19,6 +21,8 @@ import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleUtilCore; import com.intellij.openapi.project.Project; import com.intellij.openapi.projectRoots.Sdk; +import com.intellij.openapi.ui.DoNotAskOption; +import com.intellij.openapi.ui.MessageDialogBuilder; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.util.JDOMExternalizableStringList; import com.intellij.openapi.util.NlsSafe; @@ -495,6 +499,8 @@ public class PyPackageRequirementsInspection extends PyInspection { } public static class InstallPackageQuickFix implements LocalQuickFix { + public static final String CONFIRM_PACKAGE_INSTALLATION_PROPERTY = "python.confirm.package.installation"; + protected final @NotNull String myPackageName; public InstallPackageQuickFix(@NotNull String packageName) { @@ -508,6 +514,22 @@ public class PyPackageRequirementsInspection extends PyInspection { @Override public final void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { + boolean isWellKnownPackage = ApplicationManager.getApplication() + .getService(PyPIPackageRanking.class) + .getPackageRank().containsKey(myPackageName); + boolean confirmationEnabled = PropertiesComponent.getInstance().getBoolean(CONFIRM_PACKAGE_INSTALLATION_PROPERTY, true); + if (!isWellKnownPackage && confirmationEnabled) { + boolean confirmed = MessageDialogBuilder + .yesNo(PyBundle.message("python.packaging.dialog.title.install.package.confirmation"), + PyBundle.message("python.packaging.dialog.message.install.package.confirmation", myPackageName)) + .icon(AllIcons.General.WarningDialog) + .doNotAsk(new ConfirmPackageInstallationDoNotAskOption()) + .ask(project); + if (!confirmed) { + return; + } + } + PsiElement element = descriptor.getPsiElement(); if (element == null) return; Module module = ModuleUtilCore.findModuleForPsiElement(element); @@ -546,6 +568,15 @@ public class PyPackageRequirementsInspection extends PyInspection { public @NotNull IntentionPreviewInfo generatePreview(@NotNull Project project, @NotNull ProblemDescriptor previewDescriptor) { return IntentionPreviewInfo.EMPTY; } + + private static class ConfirmPackageInstallationDoNotAskOption extends DoNotAskOption.Adapter { + @Override + public void rememberChoice(boolean isSelected, int exitCode) { + if (isSelected && exitCode == Messages.OK) { + PropertiesComponent.getInstance().setValue(CONFIRM_PACKAGE_INSTALLATION_PROPERTY, false, true); + } + } + } } public static class InstallAndImportPackageQuickFix extends InstallPackageQuickFix {