diff --git a/base/src/META-INF/blaze-base.xml b/base/src/META-INF/blaze-base.xml index 4864a1adc19..4581fc10d64 100644 --- a/base/src/META-INF/blaze-base.xml +++ b/base/src/META-INF/blaze-base.xml @@ -325,7 +325,7 @@ + serviceImplementation="com.google.idea.blaze.base.project.TrustAwareProjectCreator"/> + @@ -678,6 +679,7 @@ + diff --git a/base/src/com/google/idea/blaze/base/command/CommandLineBlazeCommandRunner.java b/base/src/com/google/idea/blaze/base/command/CommandLineBlazeCommandRunner.java index ab34188afab..47f77bde9a5 100644 --- a/base/src/com/google/idea/blaze/base/command/CommandLineBlazeCommandRunner.java +++ b/base/src/com/google/idea/blaze/base/command/CommandLineBlazeCommandRunner.java @@ -28,13 +28,17 @@ import com.google.idea.blaze.base.command.buildresult.BuildResultHelperBep; import com.google.idea.blaze.base.command.buildresult.ParsedBepOutput; import com.google.idea.blaze.base.console.BlazeConsoleLineProcessorProvider; +import com.google.idea.blaze.base.execution.BazelGuard; +import com.google.idea.blaze.base.execution.ExecutionDeniedException; import com.google.idea.blaze.base.logging.utils.querysync.BuildDepsStatsScope; import com.google.idea.blaze.base.logging.utils.querysync.SyncQueryStatsScope; import com.google.idea.blaze.base.model.primitives.WorkspaceRoot; import com.google.idea.blaze.base.run.testlogs.BlazeTestResults; import com.google.idea.blaze.base.scope.BlazeContext; import com.google.idea.blaze.base.scope.output.SummaryOutput; +import com.google.idea.blaze.base.scope.output.IssueOutput; import com.google.idea.blaze.base.scope.scopes.SharedStringPoolScope; +import com.google.idea.blaze.base.settings.Blaze; import com.google.idea.blaze.base.sync.aspects.BlazeBuildOutputs; import com.google.idea.blaze.base.sync.aspects.BuildResult; import com.google.idea.blaze.base.sync.aspects.BuildResult.Status; @@ -65,6 +69,11 @@ public BlazeBuildOutputs run( BuildResultHelper buildResultHelper, BlazeContext context, Map envVars) { + try { + performGuardCheck(project, context); + } catch (ExecutionDeniedException e) { + return BlazeBuildOutputs.noOutputs(BuildResult.FATAL_ERROR); + } BuildResult buildResult = issueBuild(blazeCommandBuilder, WorkspaceRoot.fromProject(project), envVars, context); @@ -104,10 +113,17 @@ public BlazeTestResults runTest( BuildResultHelper buildResultHelper, BlazeContext context, Map envVars) { + try { + performGuardCheck(project, context); + } catch (ExecutionDeniedException e) { + return BlazeTestResults.NO_RESULTS; + } + // For tests, we have to pass the environment variables as `--test_env`, otherwise they don't get forwarded for (Map.Entry env: envVars.entrySet()) { blazeCommandBuilder.addBlazeFlags(BlazeFlags.TEST_ENV, String.format("%s=%s", env.getKey(), env.getValue())); } + BuildResult buildResult = issueBuild(blazeCommandBuilder, WorkspaceRoot.fromProject(project), envVars, context); if (buildResult.status == Status.FATAL_ERROR) { @@ -130,6 +146,8 @@ public InputStream runQuery( BuildResultHelper buildResultHelper, BlazeContext context) throws BuildException { + performGuardCheckAsBuildException(project, context); + try (Closer closer = Closer.create()) { Path tempFile = Files.createTempFile( @@ -176,6 +194,8 @@ public InputStream runBlazeInfo( BuildResultHelper buildResultHelper, BlazeContext context) throws BuildException { + performGuardCheckAsBuildException(project, context); + try (Closer closer = Closer.create()) { Path tmpFile = Files.createTempFile( @@ -227,4 +247,27 @@ public Optional getMaxCommandLineLength() { // so choose a value somewhere south of that, which seems to work. return Optional.of(130000); } + + private void performGuardCheck(Project project, BlazeContext context) + throws ExecutionDeniedException { + try { + BazelGuard.checkExtensionsIsExecutionAllowed(project); + } catch (ExecutionDeniedException e) { + IssueOutput.error( + "Can't invoke " + + Blaze.buildSystemName(project) + + " because the project is not trusted") + .submit(context); + throw e; + } + } + + private void performGuardCheckAsBuildException(Project project, BlazeContext context) + throws BuildException { + try { + performGuardCheck(project, context); + } catch (ExecutionDeniedException e) { + throw new BuildException(e); + } + } } diff --git a/base/src/com/google/idea/blaze/base/execution/BazelGuard.java b/base/src/com/google/idea/blaze/base/execution/BazelGuard.java new file mode 100644 index 00000000000..644dc490270 --- /dev/null +++ b/base/src/com/google/idea/blaze/base/execution/BazelGuard.java @@ -0,0 +1,34 @@ +/* + * Copyright 2024 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.idea.blaze.base.execution; + +import com.intellij.openapi.extensions.ExtensionPointName; +import com.intellij.openapi.project.Project; + +/** Interface for checking if tools execution is allowed for the imported project. */ +public interface BazelGuard { + + ExtensionPointName EP_NAME = + ExtensionPointName.create("com.google.idea.blaze.BlazeGuard"); + + void checkIsExecutionAllowed(Project project) throws ExecutionDeniedException; + + static void checkExtensionsIsExecutionAllowed(Project project) throws ExecutionDeniedException { + for (var extension : EP_NAME.getExtensionList()) { + extension.checkIsExecutionAllowed(project); + } + } +} diff --git a/base/src/com/google/idea/blaze/base/execution/ExecutionDeniedException.java b/base/src/com/google/idea/blaze/base/execution/ExecutionDeniedException.java new file mode 100644 index 00000000000..2446941f9dc --- /dev/null +++ b/base/src/com/google/idea/blaze/base/execution/ExecutionDeniedException.java @@ -0,0 +1,23 @@ +/* + * Copyright 2024 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.idea.blaze.base.execution; + +/** Exception thrown when tool execution is denied. */ +public class ExecutionDeniedException extends Exception { + public ExecutionDeniedException(String message) { + super(message); + } +} diff --git a/base/src/com/google/idea/blaze/base/execution/TrustedProjectGuard.java b/base/src/com/google/idea/blaze/base/execution/TrustedProjectGuard.java new file mode 100644 index 00000000000..b9b0afa0d61 --- /dev/null +++ b/base/src/com/google/idea/blaze/base/execution/TrustedProjectGuard.java @@ -0,0 +1,29 @@ +/* + * Copyright 2024 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.idea.blaze.base.execution; + +import com.intellij.ide.impl.TrustedProjects; +import com.intellij.openapi.project.Project; + +/** A {@link BazelGuard} that only allows execution if the project is trusted. */ +public class TrustedProjectGuard implements BazelGuard { + @Override + public void checkIsExecutionAllowed(Project project) throws ExecutionDeniedException { + if (!TrustedProjects.isTrusted(project)) { + throw new ExecutionDeniedException("Execution is not allowed because project is not trusted"); + } + } +} diff --git a/base/src/com/google/idea/blaze/base/project/AutoImportProjectOpenProcessor.java b/base/src/com/google/idea/blaze/base/project/AutoImportProjectOpenProcessor.java index 0b4c693a30d..3edb2f17ced 100644 --- a/base/src/com/google/idea/blaze/base/project/AutoImportProjectOpenProcessor.java +++ b/base/src/com/google/idea/blaze/base/project/AutoImportProjectOpenProcessor.java @@ -36,6 +36,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.Objects; +import java.util.Optional; import javax.swing.Icon; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; @@ -113,7 +114,9 @@ Project doOpenProject( } Project newProject = createProject(virtualFile); - Objects.requireNonNull(newProject); + if (newProject == null) { + return null; + } newProject.putUserData(PROJECT_AUTO_IMPORTED, true); @@ -170,12 +173,13 @@ private Project createProject(@NotNull VirtualFile virtualFile) { LOG.error("Failed to commit project import builder", e); } - Project newProject = builder.createProject(name, projectFilePath); - if (newProject == null) { - LOG.error("Failed to Bazel create project"); + Optional returnedValue = ExtendableBazelProjectCreator.getInstance() + .createProject(builder, name, projectFilePath); + if (returnedValue.isEmpty()) { return null; } - + + Project newProject = returnedValue.get(); newProject.save(); if (!builder.validate(null, newProject)) { diff --git a/base/src/com/google/idea/blaze/base/project/DefaultBazelProjectCreator.java b/base/src/com/google/idea/blaze/base/project/DefaultBazelProjectCreator.java deleted file mode 100644 index ffa0110974f..00000000000 --- a/base/src/com/google/idea/blaze/base/project/DefaultBazelProjectCreator.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2024 The Bazel Authors. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.google.idea.blaze.base.project; - -import com.intellij.ide.util.projectWizard.ProjectBuilder; -import com.intellij.openapi.project.Project; - -/** Default implementation of {@link ExtendableBazelProjectCreator}. */ -public class DefaultBazelProjectCreator implements ExtendableBazelProjectCreator { - - /** - * Creates a project with additional configuration. - * - * @param builder the project builder - * @param name the name of the project - * @param path the path to the project - * @return the created project - */ - @Override - public Project createProject(ProjectBuilder builder, String name, String path) { - return builder.createProject(name, path); - } - - /** Returns true if the project can be created. */ - @Override - public boolean canCreateProject() { - return true; - } -} diff --git a/base/src/com/google/idea/blaze/base/project/ExtendableBazelProjectCreator.java b/base/src/com/google/idea/blaze/base/project/ExtendableBazelProjectCreator.java index b1a24eb64b3..928fc796a40 100644 --- a/base/src/com/google/idea/blaze/base/project/ExtendableBazelProjectCreator.java +++ b/base/src/com/google/idea/blaze/base/project/ExtendableBazelProjectCreator.java @@ -15,8 +15,12 @@ */ package com.google.idea.blaze.base.project; +import com.google.idea.blaze.base.settings.BuildSystemName; import com.intellij.ide.util.projectWizard.ProjectBuilder; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.project.Project; +import java.util.Optional; +import javax.annotation.Nullable; /** Interface for creating a project with additional configuration. */ public interface ExtendableBazelProjectCreator { @@ -26,10 +30,14 @@ public interface ExtendableBazelProjectCreator { * @param builder the project builder * @param name the name of the project * @param path the path to the project - * @return the created project + * @return the created project, can be null if the project cannot be created */ - public Project createProject(ProjectBuilder builder, String name, String path); + public Optional createProject(ProjectBuilder builder, String name, String path); /** Returns true if the project can be created. */ - public boolean canCreateProject(); + public boolean canCreateProject(@Nullable BuildSystemName buildSystemName); + + static ExtendableBazelProjectCreator getInstance() { + return ApplicationManager.getApplication().getService(ExtendableBazelProjectCreator.class); + } } diff --git a/base/src/com/google/idea/blaze/base/project/TrustAwareProjectCreator.java b/base/src/com/google/idea/blaze/base/project/TrustAwareProjectCreator.java new file mode 100644 index 00000000000..f6faf0d277c --- /dev/null +++ b/base/src/com/google/idea/blaze/base/project/TrustAwareProjectCreator.java @@ -0,0 +1,71 @@ +/* + * Copyright 2024 The Bazel Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.idea.blaze.base.project; + +import com.google.idea.blaze.base.settings.BuildSystemName; +import com.intellij.ide.IdeBundle; +import com.intellij.ide.impl.TrustedProjects; +import com.intellij.ide.util.projectWizard.ProjectBuilder; +import com.intellij.openapi.application.ApplicationInfo; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.MessageDialogBuilder; +import java.util.Optional; +import javax.annotation.Nullable; + +/** Creates a project if the user has trusted the project. */ +public class TrustAwareProjectCreator implements ExtendableBazelProjectCreator { + + /** + * Creates a project if the user has trusted the project. + * + * @param builder the project builder + * @param name the name of the project + * @param path the path to the project + * @return the created project, null if the user did not trust the project + */ + @Override + public Optional createProject(ProjectBuilder builder, String name, String path) { + if (!canCreateProject(null)) { + return Optional.empty(); + } + + return Optional.of(builder.createProject(name, path)); + } + + /** Returns true if the user has trusted the project. */ + @Override + public boolean canCreateProject(@Nullable BuildSystemName buildSystemName) { + var trustText = IdeBundle.message("untrusted.project.dialog.trust.button"); + var dontOpenText = IdeBundle.message("untrusted.project.open.dialog.cancel.button"); + + var choice = + new MessageDialogBuilder.Message( + IdeBundle.message("untrusted.project.general.dialog.title"), + IdeBundle.message( + "untrusted.project.open.dialog.text", + ApplicationInfo.getInstance().getFullApplicationName())) + .buttons( + IdeBundle.message("untrusted.project.dialog.trust.button"), + IdeBundle.message("untrusted.project.open.dialog.cancel.button")) + .defaultButton(trustText) + .focusedButton(dontOpenText) + .asWarning() + .help(TrustedProjects.TRUSTED_PROJECTS_HELP_TOPIC) + .show(null, null); + + return trustText.equals(choice); + } +} diff --git a/base/src/com/google/idea/blaze/base/sync/ProjectStateSyncTask.java b/base/src/com/google/idea/blaze/base/sync/ProjectStateSyncTask.java index 8ebe813eb40..b62a8514e6f 100644 --- a/base/src/com/google/idea/blaze/base/sync/ProjectStateSyncTask.java +++ b/base/src/com/google/idea/blaze/base/sync/ProjectStateSyncTask.java @@ -21,6 +21,7 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; import com.google.idea.blaze.base.async.FutureUtil; +import com.google.idea.blaze.base.async.FutureUtil.FutureResult; import com.google.idea.blaze.base.async.executor.BlazeExecutor; import com.google.idea.blaze.base.command.BlazeCommandName; import com.google.idea.blaze.base.command.BlazeFlags; @@ -28,6 +29,7 @@ import com.google.idea.blaze.base.command.info.BlazeInfo; import com.google.idea.blaze.base.command.info.BlazeInfoProvider; import com.google.idea.blaze.base.command.info.BlazeInfoRunner; +import com.google.idea.blaze.base.execution.ExecutionDeniedException; import com.google.idea.blaze.base.io.FileOperationProvider; import com.google.idea.blaze.base.model.BlazeVersionData; import com.google.idea.blaze.base.model.primitives.WorkspaceRoot; @@ -126,15 +128,24 @@ private SyncProjectState getProjectState(BlazeContext context, BlazeSyncParams p workingSetFuture = Futures.immediateFuture(null); } - BlazeInfo blazeInfo = + FutureResult blazeInfoResult = FutureUtil.waitForFuture(context, blazeInfoFuture) .timed(Blaze.buildSystemName(project) + "Info", EventType.BlazeInvocation) .withProgressMessage( String.format("Running %s info...", Blaze.buildSystemName(project))) .onError(String.format("Could not run %s info", Blaze.buildSystemName(project))) - .run() - .result(); + .run(); + + BlazeInfo blazeInfo = blazeInfoResult.result(); if (blazeInfo == null) { + Exception exception = blazeInfoResult.exception(); + if (exception != null) { + Throwable cause = exception.getCause(); + if (cause instanceof BuildException + && cause.getCause() instanceof ExecutionDeniedException) { + throw new SyncCanceledException(); + } + } throw new SyncFailedException(); } BlazeVersionData blazeVersionData = diff --git a/base/src/com/google/idea/blaze/base/vcs/git/GitBlazeVcsHandlerProvider.java b/base/src/com/google/idea/blaze/base/vcs/git/GitBlazeVcsHandlerProvider.java index 45e17b17ac2..7ccb6042930 100644 --- a/base/src/com/google/idea/blaze/base/vcs/git/GitBlazeVcsHandlerProvider.java +++ b/base/src/com/google/idea/blaze/base/vcs/git/GitBlazeVcsHandlerProvider.java @@ -20,6 +20,8 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; import com.google.idea.blaze.base.async.process.ExternalTask; +import com.google.idea.blaze.base.execution.BazelGuard; +import com.google.idea.blaze.base.execution.ExecutionDeniedException; import com.google.idea.blaze.base.io.FileOperationProvider; import com.google.idea.blaze.base.model.primitives.WorkspacePath; import com.google.idea.blaze.base.model.primitives.WorkspaceRoot; @@ -50,6 +52,12 @@ public String getVcsName() { @Override public boolean handlesProject(Project project, WorkspaceRoot workspaceRoot) { + try { + BazelGuard.checkExtensionsIsExecutionAllowed(project); + } catch (ExecutionDeniedException e) { + logger.warn("Git provider is not allowed because of", e); + return false; + } return Blaze.getBuildSystemName(project) == BuildSystemName.Bazel && isGitRepository(workspaceRoot) && tracksRemote(workspaceRoot); diff --git a/base/src/com/google/idea/blaze/base/wizard2/BlazeProjectCreator.java b/base/src/com/google/idea/blaze/base/wizard2/BlazeProjectCreator.java index e8136e18384..28549b0dc66 100644 --- a/base/src/com/google/idea/blaze/base/wizard2/BlazeProjectCreator.java +++ b/base/src/com/google/idea/blaze/base/wizard2/BlazeProjectCreator.java @@ -37,6 +37,7 @@ import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Optional; import javax.swing.SwingUtilities; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.VisibleForTesting; @@ -105,14 +106,13 @@ public BlazeProjectCreator.CreatedProjectDescriptor createProject( FileUtil.ensureExists(ideaDir); } - Project newProject = - ApplicationManager.getApplication() - .getService(ExtendableBazelProjectCreator.class) + Optional returnedValue = + ExtendableBazelProjectCreator.getInstance() .createProject(projectBuilder, projectName, projectFilePath); - if (newProject == null) { + if (returnedValue.isEmpty()) { return null; } - + Project newProject = returnedValue.get(); if (!ApplicationManager.getApplication().isUnitTestMode()) { newProject.save(); } diff --git a/base/src/com/google/idea/blaze/base/wizard2/ui/BlazeSelectWorkspaceControl.java b/base/src/com/google/idea/blaze/base/wizard2/ui/BlazeSelectWorkspaceControl.java index 008669b5916..c4fd285a654 100644 --- a/base/src/com/google/idea/blaze/base/wizard2/ui/BlazeSelectWorkspaceControl.java +++ b/base/src/com/google/idea/blaze/base/wizard2/ui/BlazeSelectWorkspaceControl.java @@ -26,7 +26,6 @@ import com.google.idea.blaze.base.wizard2.TopLevelSelectWorkspaceOption; import com.google.idea.blaze.base.wizard2.WorkspaceTypeList; import com.intellij.openapi.Disposable; -import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.options.CancelledConfigurationException; import com.intellij.openapi.options.ConfigurationException; import com.intellij.ui.IdeBorderFactory.PlainSmallWithoutIndent; @@ -155,9 +154,8 @@ public JComponent getUiComponent() { } public void validateAndUpdateBuilder() throws ConfigurationException { - if (!ApplicationManager.getApplication() - .getService(ExtendableBazelProjectCreator.class) - .canCreateProject()) { + if (!ExtendableBazelProjectCreator.getInstance() + .canCreateProject(getSelectedOption().getWorkspaceData().buildSystem())) { throw new CancelledConfigurationException(); }