Skip to content

Configurable Command Plugin Permissions #1297

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions docs/settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,45 @@ The Visual Studio Code Swift extension comes with a number of settings you can u

This document outlines useful configuration options not covered by the settings descriptions in the extension settings page.

## Command Plugins

Swift packages can define [command plugins](https://github.com/swiftlang/swift-package-manager/blob/main/Documentation/Plugins.md) that can perform arbitrary tasks. For example, the [swift-format](https://github.com/swiftlang/swift-format) package exposes a `format-source-code` command which will use swift-format to format source code in a folder. These plugin commands can be invoked from VS Code using `> Swift: Run Command Plugin`.

A plugin may require permissions to perform tasks like writing to the file system or using the network. If a plugin command requires one of these permissions, you will be prompted in the integrated terminal to accept them. If you trust the command and wish to apply permissions on every command execution, you can configure a setting in your `settings.json`.

```json
{
"swift.pluginPermissions": {
"PluginName:command": {
"allowWritingToPackageDirectory": true,
"allowWritingToDirectory": "/some/path/",
"allowNetworkConnections": "all",
"disableSandbox": true
}
}
}
```

A key of `PluginName:command` will set permissions for a specific command. A key of `PluginName` will set permissions for all commands in the plugin.

Alternatively, you can define a task in your tasks.json and define permissions directly on the task. This will create a new entry in the list shown by `> Swift: Run Command Plugin`.

```json
{
"type": "swift-plugin",
"command": "command_plugin",
"args": ["--foo"],
"cwd": "command-plugin",
"problemMatcher": ["$swiftc"],
"label": "swift: command-plugin from tasks.json",

"allowWritingToPackageDirectory": true,
"allowWritingToDirectory": "/some/path/",
"allowNetworkConnections": "all",
"disableSandbox": true
}
```

## SourceKit-LSP

[SourceKit-LSP](https://github.com/apple/sourcekit-lsp) is the language server used by the the Swift extension to provide symbol completion, jump to definition etc. It is developed by Apple to provide Swift and C language support for any editor that supports the Language Server Protocol.
Expand Down
40 changes: 40 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,46 @@
"type": "boolean",
"default": true,
"markdownDescription": "Controls whether or not the extension will contribute environment variables defined in `Swift: Environment Variables` to the integrated terminal. If this is set to `true` and a custom `Swift: Path` is also set then the swift path is appended to the terminal's `PATH`."
},
"swift.pluginPermissions": {
"type": "object",
"default": {},
"markdownDescription": "Configures a list of permissions to be used when running a command plugins.\n\nPermissions objects are defined in the form:\n\n`{ \"PluginName:command\": { \"allowWritingToPackageDirectory\": true } }`.\n\nA key of `PluginName:command` will set permissions for a specific command. A key of `PluginName` will set permissions for all commands in the plugin.",
"scope": "machine-overridable",
"patternProperties": {
"^([a-zA-Z0-9_-]+(:[a-zA-Z0-9_-]+)?)$": {
"type": "object",
"properties": {
"disableSandbox": {
"type": "boolean",
"description": "Disable using the sandbox when executing plugins"
},
"allowWritingToPackageDirectory": {
"type": "boolean",
"description": "Allow the plugin to write to the package directory"
},
"allowWritingToDirectory": {
"oneOf": [
{
"type": "string",
"description": "Allow the plugin to write to an additional directory"
},
{
"type": "array",
"items": {
"type": "string"
},
"description": "Allow the plugin to write to additional directories"
}
]
},
"allowNetworkConnections": {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this not be declared as an "enum" with the values SwiftPM accepts?
Even if SwiftPM makes a change, it's trivial for this plugin to update the values and do a release (theoretically at least, not sure what release policies this repo follows).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Almost, but the local and all cases have associated values. local, local:1234 and local:1234,4321 are all valid values, which complicates a straight mapping here in the settings definition.

"type": "string",
"description": "Allow the plugin to make network connections"
}
}
}
}
}
}
},
Expand Down
24 changes: 24 additions & 0 deletions src/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,23 @@ export interface FolderConfiguration {
readonly autoGenerateLaunchConfigurations: boolean;
/** disable automatic running of swift package resolve */
readonly disableAutoResolve: boolean;
/** look up saved permissions for the supplied plugin */
pluginPermissions(pluginId: string): PluginPermissionConfiguration;
}

export interface PluginPermissionConfiguration {
/** Disable using the sandbox when executing plugins */
disableSandbox?: boolean;
/** Allow the plugin to write to the package directory */
allowWritingToPackageDirectory?: boolean;
/** Allow the plugin to write to an additional directory or directories */
allowWritingToDirectory?: string | string[];
/**
* Allow the plugin to make network connections
* For a list of valid options see:
* https://github.com/swiftlang/swift-package-manager/blob/0401a2ae55077cfd1f4c0acd43ae0a1a56ab21ef/Sources/Commands/PackageCommands/PluginCommand.swift#L62
*/
allowNetworkConnections?: string;
}

/**
Expand Down Expand Up @@ -145,6 +162,13 @@ const configuration = {
.getConfiguration("swift", workspaceFolder)
.get<boolean>("searchSubfoldersForPackages", false);
},
pluginPermissions(pluginId: string): PluginPermissionConfiguration {
return (
vscode.workspace.getConfiguration("swift", workspaceFolder).get<{
[key: string]: PluginPermissionConfiguration;
}>("pluginPermissions", {})[pluginId] ?? {}
);
},
};
},

Expand Down
87 changes: 73 additions & 14 deletions src/tasks/SwiftPluginTaskProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ import * as vscode from "vscode";
import * as path from "path";
import { WorkspaceContext } from "../WorkspaceContext";
import { PackagePlugin } from "../SwiftPackage";
import configuration from "../configuration";
import { swiftRuntimeEnv } from "../utilities/utilities";
import { SwiftExecution } from "../tasks/SwiftExecution";
import { resolveTaskCwd } from "../utilities/tasks";
import configuration, { PluginPermissionConfiguration } from "../configuration";
import { SwiftTask } from "./SwiftTaskProvider";

// Interface class for defining task configuration
Expand Down Expand Up @@ -76,14 +76,9 @@ export class SwiftPluginTaskProvider implements vscode.TaskProvider {
// We need to create a new Task object here.
// Reusing the task parameter doesn't seem to work.
const swift = this.workspaceContext.toolchain.getToolchainExecutable("swift");
const sandboxArg = task.definition.disableSandbox ? ["--disable-sandbox"] : [];
const writingToPackageArg = task.definition.allowWritingToPackageDirectory
? ["--allow-writing-to-package-directory"]
: [];
let swiftArgs = [
"package",
...sandboxArg,
...writingToPackageArg,
...this.pluginArguments(task.definition as PluginPermissionConfiguration),
task.definition.command,
...task.definition.args,
];
Expand Down Expand Up @@ -121,15 +116,9 @@ export class SwiftPluginTaskProvider implements vscode.TaskProvider {
const relativeCwd = path.relative(config.scope.uri.fsPath, config.cwd.fsPath);
const taskDefinitionCwd = relativeCwd !== "" ? relativeCwd : undefined;
const definition = this.getTaskDefinition(plugin, taskDefinitionCwd);
// Add arguments based on definition
const sandboxArg = definition.disableSandbox ? ["--disable-sandbox"] : [];
const writingToPackageArg = definition.allowWritingToPackageDirectory
? ["--allow-writing-to-package-directory"]
: [];
let swiftArgs = [
"package",
...sandboxArg,
...writingToPackageArg,
...this.pluginArgumentsFromConfiguration(config.scope, definition, plugin),
plugin.command,
...definition.args,
];
Expand Down Expand Up @@ -196,9 +185,79 @@ export class SwiftPluginTaskProvider implements vscode.TaskProvider {
definition.allowWritingToPackageDirectory = true;
break;

case "swift-format, format-source-code":
definition.allowWritingToPackageDirectory = true;
break;

default:
break;
}
return definition;
}

/**
* Generates a list of permission related plugin arguments from two potential sources,
* the hardcoded list of permissions defined on a per-plugin basis in getTaskDefinition
* and the user-configured permissions in the workspace settings. User-configured permissions
* are keyed by either plugin command name (package), or in the form `name:command`.
* User-configured permissions take precedence over the hardcoded permissions, and the more
* specific form of `name:command` takes precedence over the more general form of `name`.
* @param folderContext The folder context to search for the `swift.pluginPermissions` key.
* @param taskDefinition The task definition to search for the `disableSandbox` and `allowWritingToPackageDirectory` keys.
* @param plugin The plugin to generate arguments for.
* @returns A list of permission related arguments to pass when invoking the plugin.
*/
private pluginArgumentsFromConfiguration(
folderContext: vscode.WorkspaceFolder,
taskDefinition: vscode.TaskDefinition,
plugin: PackagePlugin
): string[] {
const config = configuration.folder(folderContext);
const packageConfig = config.pluginPermissions(plugin.package);
const commandConfig = config.pluginPermissions(`${plugin.package}:${plugin.command}`);

const taskDefinitionConfiguration: PluginPermissionConfiguration = {};
if (taskDefinition.disableSandbox) {
taskDefinitionConfiguration.disableSandbox = true;
}
if (taskDefinition.allowWritingToPackageDirectory) {
taskDefinitionConfiguration.allowWritingToPackageDirectory = true;
}
if (taskDefinition.allowWritingToDirectory) {
taskDefinitionConfiguration.allowWritingToDirectory =
taskDefinition.allowWritingToDirectory;
}
if (taskDefinition.allowNetworkConnections) {
taskDefinitionConfiguration.allowNetworkConnections =
taskDefinition.allowNetworkConnections;
}

return this.pluginArguments({
...packageConfig,
...commandConfig,
...taskDefinitionConfiguration,
});
}

private pluginArguments(config: PluginPermissionConfiguration): string[] {
const args = [];
if (config.disableSandbox) {
args.push("--disable-sandbox");
}
if (config.allowWritingToPackageDirectory) {
args.push("--allow-writing-to-package-directory");
}
if (config.allowWritingToDirectory) {
if (Array.isArray(config.allowWritingToDirectory)) {
args.push("--allow-writing-to-directory", ...config.allowWritingToDirectory);
} else {
args.push("--allow-writing-to-directory");
}
}
if (config.allowNetworkConnections) {
args.push("--allow-network-connections");
args.push(config.allowNetworkConnections);
}
return args;
}
}
Loading
Loading