IDEA-262969 added merging of untrusted project dialogs into one for several linked projects

GitOrigin-RevId: 44cd546b7ef69a54314191a5d3a4db0cc6caa07f
This commit is contained in:
Sergei Vorobyov
2021-02-26 16:10:30 +00:00
committed by intellij-monorepo-bot
parent 72515c81bf
commit da1051c784
13 changed files with 119 additions and 84 deletions
@@ -83,7 +83,7 @@ public class AttachExternalProjectAction extends DumbAwareAction {
projectImportProvider
);
if (wizard != null && (wizard.getStepCount() <= 0 || wizard.showAndGet())) {
ExternalSystemUtil.confirmLoadingUntrustedProjectIfNeeded(project, externalSystemId);
ExternalSystemUtil.confirmFullLoadingUntrustedProjectIfNeeded(project, externalSystemId);
ImportModuleAction.createFromWizard(project, wizard);
}
}
@@ -15,6 +15,14 @@ unlinked.project.notification.help.text=\
If you are unsure or this is not a {0} project, press 'Skip'.<br>\
You''ll be able to import the {0} project later from context menu of {0} build script.
untrusted.project.notification.trust.button=Load {0} {1,choice,1#Project|1<Projects}
untrusted.project.notification.open.preview.action=Preview Mode
untrusted.project.notification.description=The opened untrusted project is not fully loaded.
untrusted.project.notification.title=Load Untrusted {0} {1,choice,1#Project|1<Projects}?
untrusted.project.notification.text=\
If you don\u2019t trust the source, cancel the loading process to only browse the project\u2019s sources.\
Loading, running or building a {0} {1,choice,1#project|1<projects} may execute potentially malicious code from its build scripts.
# Settings.
settings.build.tools.display.name=Build Tools
settings.build.tools.auto.reload.hit=Auto-Reload Projects
@@ -4,9 +4,12 @@ package com.intellij.openapi.externalSystem.service.project;
import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.openapi.extensions.RequiredElement;
import com.intellij.openapi.externalSystem.model.ProjectSystemId;
import com.intellij.openapi.util.Pair;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.xmlb.annotations.Attribute;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import java.util.Map;
/**
* Specify if {@link ExternalSystemProjectResolver} for this {@link ProjectSystemId}
@@ -27,11 +30,9 @@ public class ExternalResolverIsSafe {
@RequiredElement
public boolean executesTrustedCodeOnly;
public static boolean executesTrustedCodeOnly(@NotNull ProjectSystemId systemId) {
return EP_NAME.extensions()
.filter(extension -> extension.systemId.equals(systemId.getId()))
.findFirst()
.map(extension -> extension.executesTrustedCodeOnly)
.orElse(false);
public static boolean executesTrustedCodeOnly(ProjectSystemId... systemIds) {
Map<String, Boolean> executesTrustedCodeOnlyIndex =
ContainerUtil.map2Map(EP_NAME.getExtensionList(), it -> new Pair<>(it.systemId, it.executesTrustedCodeOnly));
return ContainerUtil.and(systemIds, it -> executesTrustedCodeOnlyIndex.getOrDefault(it.getId(), false));
}
}
@@ -73,10 +73,11 @@ public class RefreshAllExternalProjectsAction extends DumbAwareAction {
// We save all documents because there is a possible case that there is an external system config file changed inside the ide.
FileDocumentManager.getInstance().saveAllDocuments();
boolean isPreviewMode = ExternalSystemUtil.confirmFullLoadingUntrustedProjectIfNeeded(project, systemIds.toArray(new ProjectSystemId[0]));
for (ProjectSystemId externalSystemId : systemIds) {
ExternalSystemActionsCollector.trigger(project, externalSystemId, this, e);
ImportSpecBuilder importSpec = new ImportSpecBuilder(project, externalSystemId);
if (!ExternalSystemUtil.confirmLoadingUntrustedProjectIfNeeded(project, externalSystemId)) {
if (!isPreviewMode) {
importSpec.usePreviewMode();
}
ExternalSystemUtil.refreshProjects(importSpec.forceWhenUptodate(true));
@@ -82,7 +82,7 @@ public class RefreshExternalProjectAction extends ExternalSystemNodeAction<Abstr
: linkedProjectSettings.getExternalProjectPath();
ImportSpecBuilder importSpec = new ImportSpecBuilder(project, projectSystemId);
if (!ExternalSystemUtil.confirmLoadingUntrustedProjectIfNeeded(project, projectSystemId)) {
if (!ExternalSystemUtil.confirmFullLoadingUntrustedProjectIfNeeded(project, projectSystemId)) {
importSpec.usePreviewMode();
}
ExternalSystemUtil.refreshProject(externalProjectPath, importSpec);
@@ -3,6 +3,7 @@ package com.intellij.openapi.externalSystem.autoimport
import com.intellij.codeInsight.daemon.DaemonCodeAnalyzerSettings
import com.intellij.ide.file.BatchFileChangeListener
import com.intellij.ide.impl.getTrustedState
import com.intellij.openapi.Disposable
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.PersistentStateComponent
@@ -27,6 +28,7 @@ import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.registry.Registry
import com.intellij.util.LocalTimeCounter.currentTime
import com.intellij.util.ThreeState
import com.intellij.util.concurrency.AppExecutorUtil
import com.intellij.util.ui.update.MergingUpdateQueue
import com.intellij.util.ui.update.Update
@@ -126,6 +128,9 @@ class AutoImportProjectTracker(private val project: Project) : ExternalSystemPro
if (isDisabled.get() || Registry.`is`("external.system.auto.import.disabled")) return
if (!projectChangeOperation.isOperationCompleted()) return
if (smart && !projectRefreshOperation.isOperationCompleted()) return
val systemIds = projectDataMap.keys.map { it.systemId }.toSet().toTypedArray()
val isAllowedDialogs = !smart || project.getTrustedState() == ThreeState.UNSURE
val isPreviewMode = isAllowedDialogs && !ExternalSystemUtil.confirmFullLoadingUntrustedProjectIfNeeded(project, *systemIds)
var isSkippedProjectRefresh = true
for ((projectId, projectData) in projectDataMap) {
val isAllowAutoReload = !smart || projectData.isActivated
@@ -134,7 +139,6 @@ class AutoImportProjectTracker(private val project: Project) : ExternalSystemPro
LOG.debug("${projectId.readableName}: Project refresh")
val hasUndefinedModifications = !projectData.status.isUpToDate()
val settingsContext = projectData.settingsTracker.getSettingsContext()
val isPreviewMode = !smart && !ExternalSystemUtil.confirmLoadingUntrustedProjectIfNeeded(project, projectId.systemId)
val context = ProjectReloadContext(!smart, isPreviewMode, hasUndefinedModifications, settingsContext)
projectData.projectAware.reloadProject(context)
}
@@ -14,7 +14,7 @@ fun ExternalSystemUnlinkedProjectAware.getProjectId(externalProjectPath: String)
}
fun ExternalSystemUnlinkedProjectAware.linkAndLoadProjectWithLoadingConfirmation(project: Project, externalProjectPath: String) {
ExternalSystemUtil.confirmLoadingUntrustedProjectIfNeeded(project, systemId)
ExternalSystemUtil.confirmFullLoadingUntrustedProjectIfNeeded(project, systemId)
linkAndLoadProject(project, externalProjectPath)
}
@@ -8,6 +8,7 @@ import com.intellij.ide.impl.confirmOpeningUntrustedProject
import com.intellij.ide.impl.setTrusted
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.externalSystem.ExternalSystemManager
import com.intellij.openapi.externalSystem.autolink.UnlinkedProjectNotificationAware
import com.intellij.openapi.externalSystem.model.ExternalSystemDataKeys
import com.intellij.openapi.externalSystem.model.ProjectSystemId
@@ -18,12 +19,25 @@ import com.intellij.openapi.project.ex.ProjectManagerEx
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.vfs.LocalFileSystem
import com.intellij.openapi.vfs.VirtualFile
import org.apache.commons.lang.StringUtils
import org.jetbrains.annotations.ApiStatus
import java.nio.file.Path
@ApiStatus.Experimental
abstract class AbstractOpenProjectProvider : OpenProjectProvider {
protected open val systemId: ProjectSystemId? = null
protected open val systemId: ProjectSystemId by lazy {
/**
* Tries to resolve external system id
* Note: Implemented approach is super heuristics.
* Please, override [systemId] to avoid discrepancy with real id.
*/
val readableName = StringUtils.splitByCharacterTypeCamelCase(javaClass.simpleName).first()
val manager = ExternalSystemManager.EP_NAME.findFirstSafe {
StringUtils.equalsIgnoreCase(StringUtils.splitByCharacterTypeCamelCase(it.javaClass.simpleName).first(), readableName)
}
manager?.systemId ?: ProjectSystemId(readableName.toUpperCase())
}
protected abstract fun isProjectFile(file: VirtualFile): Boolean
@@ -42,7 +56,7 @@ abstract class AbstractOpenProjectProvider : OpenProjectProvider {
val nioPath = projectDirectory.toNioPath()
val isValidIdeaProject = isValidProjectPath(nioPath)
val untrustedProjectChoice = confirmOpeningUntrustedProject(projectFile, systemId?.readableName!!)
val untrustedProjectChoice = confirmOpeningUntrustedProject(projectFile, systemId.readableName)
if (untrustedProjectChoice == OpenUntrustedProjectChoice.CANCEL) return null
val options = OpenProjectTask(
@@ -54,7 +68,7 @@ abstract class AbstractOpenProjectProvider : OpenProjectProvider {
project.setTrusted(untrustedProjectChoice == OpenUntrustedProjectChoice.IMPORT)
if (isValidIdeaProject) {
systemId?.let { UnlinkedProjectNotificationAware.enableNotifications(project, it) }
UnlinkedProjectNotificationAware.enableNotifications(project, systemId)
}
else {
project.putUserData(ExternalSystemDataKeys.NEWLY_IMPORTED_PROJECT, true)
@@ -80,7 +94,7 @@ abstract class AbstractOpenProjectProvider : OpenProjectProvider {
val projectFile = localFileSystem.refreshAndFindFileByPath(projectFilePath)
if (projectFile == null) {
val shortPath = FileUtil.getLocationRelativeToUserHome(FileUtil.toSystemDependentName(projectFilePath), false)
throw IllegalArgumentException(ExternalSystemBundle.message("error.project.does.not.exist", systemId?.readableName, shortPath))
throw IllegalArgumentException(ExternalSystemBundle.message("error.project.does.not.exist", systemId.readableName, shortPath))
}
linkToExistingProject(projectFile, project)
}
@@ -1,12 +1,11 @@
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.openapi.externalSystem.service.project
import com.intellij.CommonBundle
import com.intellij.ide.IdeBundle
import com.intellij.ide.impl.TrustChangeNotifier
import com.intellij.ide.impl.isTrusted
import com.intellij.openapi.extensions.ExtensionPointName
import com.intellij.openapi.externalSystem.service.project.ExternalResolverIsSafe.executesTrustedCodeOnly
import com.intellij.openapi.externalSystem.util.ExternalSystemBundle
import com.intellij.openapi.externalSystem.util.ExternalSystemUtil.confirmLoadingUntrustedProjectIfNeeded
import com.intellij.openapi.fileEditor.FileEditor
import com.intellij.openapi.project.DumbAware
@@ -23,15 +22,19 @@ class UntrustedProjectNotificationProvider : EditorNotifications.Provider<Editor
if (project.isTrusted()) {
return null
}
val provider = EP_NAME.findFirstSafe {
!executesTrustedCodeOnly(it.systemId) &&
it.shouldShowEditorNotification(project)
} ?: return null
val providers = EP_NAME.extensions.filter { it.shouldShowEditorNotification(project) }
val systemIds = providers.map { it.systemId }.toTypedArray()
if (providers.isEmpty() || executesTrustedCodeOnly(*systemIds)) {
return null
}
val systemsPresentation = systemIds.joinToString { it.readableName }
return EditorNotificationPanel().apply {
text = IdeBundle.message("untrusted.project.notification.description")
createActionLabel(IdeBundle.message("untrusted.project.notification.trust.button", provider.systemId.readableName), {
if (confirmLoadingUntrustedProjectIfNeeded(project, provider.systemId, CommonBundle.getCancelButtonText())) {
provider.loadAllLinkedProjects(project)
text = ExternalSystemBundle.message("untrusted.project.notification.description")
createActionLabel(ExternalSystemBundle.message("untrusted.project.notification.trust.button", systemsPresentation, systemIds.size), {
if (confirmLoadingUntrustedProjectIfNeeded(project, *systemIds)) {
for (provider in providers) {
provider.loadAllLinkedProjects(project)
}
}
}, false)
}
@@ -54,7 +54,6 @@ import com.intellij.openapi.externalSystem.service.notification.ExternalSystemNo
import com.intellij.openapi.externalSystem.service.notification.NotificationData;
import com.intellij.openapi.externalSystem.service.notification.NotificationSource;
import com.intellij.openapi.externalSystem.service.project.ExternalProjectRefreshCallback;
import com.intellij.openapi.externalSystem.service.project.ExternalResolverIsSafe;
import com.intellij.openapi.externalSystem.service.project.ProjectDataManager;
import com.intellij.openapi.externalSystem.service.project.manage.ContentRootDataService;
import com.intellij.openapi.externalSystem.service.project.manage.ExternalProjectsManagerImpl;
@@ -77,8 +76,8 @@ import com.intellij.openapi.progress.util.AbstractProgressIndicatorExBase;
import com.intellij.openapi.project.DumbAwareAction;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.MessageDialogBuilder;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.NlsContexts.Button;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.UserDataHolderBase;
@@ -110,11 +109,10 @@ import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.function.Predicate;
import java.util.function.Supplier;
import static com.intellij.ide.impl.TrustedProjects.confirmImportingUntrustedProject;
import static com.intellij.ide.impl.TrustedProjects.getTrustedState;
import static com.intellij.ide.impl.TrustedProjects.*;
import static com.intellij.openapi.externalSystem.service.project.ExternalResolverIsSafe.executesTrustedCodeOnly;
import static com.intellij.openapi.externalSystem.settings.AbstractExternalSystemLocalSettings.SyncType.*;
import static com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil.doWriteAction;
import static org.jetbrains.annotations.Nls.Capitalization.Sentence;
@@ -684,58 +682,59 @@ public final class ExternalSystemUtil {
@NotNull Project project,
@NotNull ProjectSystemId systemId
) {
return confirmLoadingUntrustedProjectIfNeeded(project, systemId, CommonBundle.getCancelButtonText(), ThreeState.UNSURE::equals);
return confirmLoadingUntrustedProjectIfNeeded(project, () -> ThreeState.UNSURE.equals(getTrustedState(project)), systemId);
}
public static boolean confirmFullLoadingUntrustedProjectIfNeeded(
@NotNull Project project,
ProjectSystemId... systemIds
) {
return confirmFullLoadingUntrustedProjectIfNeeded(project, () -> true, systemIds);
}
public static boolean confirmLoadingUntrustedProjectIfNeeded(
@NotNull Project project,
@NotNull ProjectSystemId systemId
ProjectSystemId... systemIds
) {
return confirmLoadingUntrustedProjectIfNeeded(project, systemId, __ -> true);
return confirmLoadingUntrustedProjectIfNeeded(project, () -> true, systemIds);
}
public static boolean confirmFullLoadingUntrustedProjectIfNeeded(
@NotNull Project project,
@NotNull Supplier<Boolean> confirmation,
ProjectSystemId... systemIds
) {
String systemsPresentation = StringUtil.join(systemIds, it -> it.getReadableName(), ", ");
return isTrusted(project) || project.isDefault() || executesTrustedCodeOnly(systemIds) ||
confirmation.get() && confirmImportingUntrustedProject(project, () ->
MessageDialogBuilder.yesNo(
ExternalSystemBundle.message("untrusted.project.notification.title", systemsPresentation, systemIds.length),
ExternalSystemBundle.message("untrusted.project.notification.text", systemsPresentation, systemIds.length)
)
.yesText(ExternalSystemBundle.message("untrusted.project.notification.trust.button", systemsPresentation, systemIds.length))
.noText(ExternalSystemBundle.message("untrusted.project.notification.open.preview.action"))
.asWarning()
.ask(project)
);
}
public static boolean confirmLoadingUntrustedProjectIfNeeded(
@NotNull Project project,
@NotNull ProjectSystemId systemId,
@NotNull @Button String cancelButtonText
@NotNull Supplier<Boolean> confirmation,
ProjectSystemId... systemIds
) {
return confirmLoadingUntrustedProjectIfNeeded(project, systemId, cancelButtonText, __ -> true);
}
public static boolean confirmLoadingUntrustedProjectIfNeeded(
@NotNull Project project,
@NotNull ProjectSystemId systemId,
@NotNull Predicate<ThreeState> confirmation
) {
String cancelButtonText = ExternalSystemBundle.message("unlinked.project.notification.open.preview.action");
return confirmLoadingUntrustedProjectIfNeeded(project, systemId, cancelButtonText, confirmation);
}
public static boolean confirmLoadingUntrustedProjectIfNeeded(
@NotNull Project project,
@NotNull ProjectSystemId systemId,
@NotNull @Button String cancelButtonText,
@NotNull Predicate<ThreeState> confirmation
) {
if (project.isDefault()) {
return true;
}
if (ExternalResolverIsSafe.executesTrustedCodeOnly(systemId)) {
return true;
}
ThreeState state = getTrustedState(project);
if (state.equals(ThreeState.YES)) {
return true;
}
if (!confirmation.test(state)) {
return false;
}
String systemName = systemId.getReadableName();
return confirmImportingUntrustedProject(
project, systemName,
ExternalSystemBundle.message("unlinked.project.notification.load.action", systemName),
cancelButtonText
);
String systemsPresentation = StringUtil.join(systemIds, it -> it.getReadableName(), ", ");
return isTrusted(project) || project.isDefault() || executesTrustedCodeOnly(systemIds) ||
confirmation.get() && confirmImportingUntrustedProject(project, () ->
MessageDialogBuilder.yesNo(
ExternalSystemBundle.message("untrusted.project.notification.title", systemsPresentation, systemIds.length),
ExternalSystemBundle.message("untrusted.project.notification.text", systemsPresentation, systemIds.length)
)
.yesText(ExternalSystemBundle.message("untrusted.project.notification.trust.button", systemsPresentation, systemIds.length))
.noText(CommonBundle.getCancelButtonText())
.asWarning()
.ask(project)
);
}
public static boolean isNewProject(Project project) {
@@ -54,19 +54,24 @@ fun confirmImportingUntrustedProject(project: Project,
@Nls buildSystemName: String,
@Nls yesButtonText: String,
@Nls noButtonText: String): Boolean {
return confirmImportingUntrustedProject(project) {
MessageDialogBuilder.yesNo(title = IdeBundle.message("untrusted.project.import.warning.title", buildSystemName),
message = IdeBundle.message("untrusted.project.import.warning.text", buildSystemName))
.yesText(yesButtonText)
.noText(noButtonText)
.asWarning()
.ask(project)
}
}
fun confirmImportingUntrustedProject(project: Project, confirm: () -> Boolean): Boolean {
val trustedCheckResult = isProjectImplicitlyTrusted(project)
if (trustedCheckResult is Trusted) {
project.setTrusted(true)
return true
}
val answer = MessageDialogBuilder.yesNo(title = IdeBundle.message("untrusted.project.import.warning.title", buildSystemName),
message = IdeBundle.message("untrusted.project.import.warning.text", buildSystemName))
.yesText(yesButtonText)
.noText(noButtonText)
.asWarning()
.ask(project)
val answer = confirm()
project.setTrusted(answer)
return answer
}
@@ -167,11 +172,11 @@ class TrustedProjectSettings : SimplePersistentStateComponent<TrustedProjectSett
}
interface TrustChangeNotifier {
fun projectTrusted(project: Project);
fun projectTrusted(project: Project)
companion object {
@JvmField
@Topic.AppLevel
val TOPIC = Topic.create("Trusted project status", TrustChangeNotifier::class.java);
val TOPIC = Topic.create("Trusted project status", TrustChangeNotifier::class.java)
}
}
@@ -27,7 +27,7 @@ class ImportProjectFromScriptAction: ExternalSystemAction() {
val virtualFile = e.getData<VirtualFile>(CommonDataKeys.VIRTUAL_FILE) ?: return
val project = e.getData<Project>(CommonDataKeys.PROJECT) ?: return
val externalProjectPath = getDefaultPath(virtualFile)
ExternalSystemUtil.confirmLoadingUntrustedProjectIfNeeded(project, GradleConstants.SYSTEM_ID)
ExternalSystemUtil.confirmFullLoadingUntrustedProjectIfNeeded(project, GradleConstants.SYSTEM_ID)
linkAndRefreshGradleProject(externalProjectPath, project)
}
@@ -1046,7 +1046,7 @@ public class MavenUtil {
}
public static boolean isProjectTrustedEnoughToImport(Project project, boolean askConfirmation) {
return ExternalSystemUtil.confirmLoadingUntrustedProjectIfNeeded(project, SYSTEM_ID, __ -> askConfirmation);
return ExternalSystemUtil.confirmFullLoadingUntrustedProjectIfNeeded(project, () -> askConfirmation, SYSTEM_ID);
}
public static void restartMavenConnectors(Project project) {