Skip to content

Commit 04397b7

Browse files
authored
Configurable Command Plugin Permissions (#1297)
* Configurable Command Plugin Permissions Packages can define their own plugins either directly or through their dependencies. These plugins define commands, and the extension exposes a list of these when you use `> Swift: Run Command Plugin`. If a command requires special permissions to write to disk or use the network the user is prompted in the integrated terminal to type "yes". This can be bypassed by passing a permission flag to the command such as `--allow-writing-to-package-directory`. The extension does supply permission flags for a small list of well known package plugins, however if the user creates their own or uses one not on this list they must enter "yes" every time they run the command plugin. This patch introduces a new setting that can be specified globally or on a per workspace folder basis that allows users to configure which permission flags should be used when running the command. The setting is defined under `swift.pluginPermissions`, and is specified as an object in the following form: ```json { "PluginName:intent-name": { "allowWritingToPackageDirectory": true, "allowWritingToDirectory: "/some/path", "allowNetworkConnections: "all", "disableSandbox": true, } } ``` - The top level string key is the command id in the form `command_name:intent_name`. For instance, swift-format's format-source-code command would be specified as `swift-format:format-source-code` - Each permission in the permissions lookup is optional. - `allowWritingToDirectory` can also be specified as an array of paths. - The valid values for `allowNetworkConnections` can be found here: https://github.com/swiftlang/swift-package-manager/blob/0401a2ae55077cfd1f4c0acd43ae0a1a56ab21ef/Sources/Commands/PackageCommands/PluginCommand.swift#L62 Issue: #1277
1 parent d6c1275 commit 04397b7

File tree

6 files changed

+300
-87
lines changed

6 files changed

+300
-87
lines changed

Diff for: docs/settings.md

+39
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,45 @@ The Visual Studio Code Swift extension comes with a number of settings you can u
44

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

7+
## Command Plugins
8+
9+
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`.
10+
11+
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`.
12+
13+
```json
14+
{
15+
"swift.pluginPermissions": {
16+
"PluginName:command": {
17+
"allowWritingToPackageDirectory": true,
18+
"allowWritingToDirectory": "/some/path/",
19+
"allowNetworkConnections": "all",
20+
"disableSandbox": true
21+
}
22+
}
23+
}
24+
```
25+
26+
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.
27+
28+
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`.
29+
30+
```json
31+
{
32+
"type": "swift-plugin",
33+
"command": "command_plugin",
34+
"args": ["--foo"],
35+
"cwd": "command-plugin",
36+
"problemMatcher": ["$swiftc"],
37+
"label": "swift: command-plugin from tasks.json",
38+
39+
"allowWritingToPackageDirectory": true,
40+
"allowWritingToDirectory": "/some/path/",
41+
"allowNetworkConnections": "all",
42+
"disableSandbox": true
43+
}
44+
```
45+
746
## SourceKit-LSP
847

948
[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.

Diff for: package.json

+40
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,46 @@
394394
"type": "boolean",
395395
"default": true,
396396
"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`."
397+
},
398+
"swift.pluginPermissions": {
399+
"type": "object",
400+
"default": {},
401+
"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.",
402+
"scope": "machine-overridable",
403+
"patternProperties": {
404+
"^([a-zA-Z0-9_-]+(:[a-zA-Z0-9_-]+)?)$": {
405+
"type": "object",
406+
"properties": {
407+
"disableSandbox": {
408+
"type": "boolean",
409+
"description": "Disable using the sandbox when executing plugins"
410+
},
411+
"allowWritingToPackageDirectory": {
412+
"type": "boolean",
413+
"description": "Allow the plugin to write to the package directory"
414+
},
415+
"allowWritingToDirectory": {
416+
"oneOf": [
417+
{
418+
"type": "string",
419+
"description": "Allow the plugin to write to an additional directory"
420+
},
421+
{
422+
"type": "array",
423+
"items": {
424+
"type": "string"
425+
},
426+
"description": "Allow the plugin to write to additional directories"
427+
}
428+
]
429+
},
430+
"allowNetworkConnections": {
431+
"type": "string",
432+
"description": "Allow the plugin to make network connections"
433+
}
434+
}
435+
}
436+
}
397437
}
398438
}
399439
},

Diff for: src/configuration.ts

+24
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,23 @@ export interface FolderConfiguration {
6565
readonly autoGenerateLaunchConfigurations: boolean;
6666
/** disable automatic running of swift package resolve */
6767
readonly disableAutoResolve: boolean;
68+
/** look up saved permissions for the supplied plugin */
69+
pluginPermissions(pluginId: string): PluginPermissionConfiguration;
70+
}
71+
72+
export interface PluginPermissionConfiguration {
73+
/** Disable using the sandbox when executing plugins */
74+
disableSandbox?: boolean;
75+
/** Allow the plugin to write to the package directory */
76+
allowWritingToPackageDirectory?: boolean;
77+
/** Allow the plugin to write to an additional directory or directories */
78+
allowWritingToDirectory?: string | string[];
79+
/**
80+
* Allow the plugin to make network connections
81+
* For a list of valid options see:
82+
* https://github.com/swiftlang/swift-package-manager/blob/0401a2ae55077cfd1f4c0acd43ae0a1a56ab21ef/Sources/Commands/PackageCommands/PluginCommand.swift#L62
83+
*/
84+
allowNetworkConnections?: string;
6885
}
6986

7087
/**
@@ -145,6 +162,13 @@ const configuration = {
145162
.getConfiguration("swift", workspaceFolder)
146163
.get<boolean>("searchSubfoldersForPackages", false);
147164
},
165+
pluginPermissions(pluginId: string): PluginPermissionConfiguration {
166+
return (
167+
vscode.workspace.getConfiguration("swift", workspaceFolder).get<{
168+
[key: string]: PluginPermissionConfiguration;
169+
}>("pluginPermissions", {})[pluginId] ?? {}
170+
);
171+
},
148172
};
149173
},
150174

Diff for: src/tasks/SwiftPluginTaskProvider.ts

+73-14
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ import * as vscode from "vscode";
1616
import * as path from "path";
1717
import { WorkspaceContext } from "../WorkspaceContext";
1818
import { PackagePlugin } from "../SwiftPackage";
19-
import configuration from "../configuration";
2019
import { swiftRuntimeEnv } from "../utilities/utilities";
2120
import { SwiftExecution } from "../tasks/SwiftExecution";
2221
import { resolveTaskCwd } from "../utilities/tasks";
22+
import configuration, { PluginPermissionConfiguration } from "../configuration";
2323
import { SwiftTask } from "./SwiftTaskProvider";
2424

2525
// Interface class for defining task configuration
@@ -76,14 +76,9 @@ export class SwiftPluginTaskProvider implements vscode.TaskProvider {
7676
// We need to create a new Task object here.
7777
// Reusing the task parameter doesn't seem to work.
7878
const swift = this.workspaceContext.toolchain.getToolchainExecutable("swift");
79-
const sandboxArg = task.definition.disableSandbox ? ["--disable-sandbox"] : [];
80-
const writingToPackageArg = task.definition.allowWritingToPackageDirectory
81-
? ["--allow-writing-to-package-directory"]
82-
: [];
8379
let swiftArgs = [
8480
"package",
85-
...sandboxArg,
86-
...writingToPackageArg,
81+
...this.pluginArguments(task.definition as PluginPermissionConfiguration),
8782
task.definition.command,
8883
...task.definition.args,
8984
];
@@ -121,15 +116,9 @@ export class SwiftPluginTaskProvider implements vscode.TaskProvider {
121116
const relativeCwd = path.relative(config.scope.uri.fsPath, config.cwd.fsPath);
122117
const taskDefinitionCwd = relativeCwd !== "" ? relativeCwd : undefined;
123118
const definition = this.getTaskDefinition(plugin, taskDefinitionCwd);
124-
// Add arguments based on definition
125-
const sandboxArg = definition.disableSandbox ? ["--disable-sandbox"] : [];
126-
const writingToPackageArg = definition.allowWritingToPackageDirectory
127-
? ["--allow-writing-to-package-directory"]
128-
: [];
129119
let swiftArgs = [
130120
"package",
131-
...sandboxArg,
132-
...writingToPackageArg,
121+
...this.pluginArgumentsFromConfiguration(config.scope, definition, plugin),
133122
plugin.command,
134123
...definition.args,
135124
];
@@ -196,9 +185,79 @@ export class SwiftPluginTaskProvider implements vscode.TaskProvider {
196185
definition.allowWritingToPackageDirectory = true;
197186
break;
198187

188+
case "swift-format, format-source-code":
189+
definition.allowWritingToPackageDirectory = true;
190+
break;
191+
199192
default:
200193
break;
201194
}
202195
return definition;
203196
}
197+
198+
/**
199+
* Generates a list of permission related plugin arguments from two potential sources,
200+
* the hardcoded list of permissions defined on a per-plugin basis in getTaskDefinition
201+
* and the user-configured permissions in the workspace settings. User-configured permissions
202+
* are keyed by either plugin command name (package), or in the form `name:command`.
203+
* User-configured permissions take precedence over the hardcoded permissions, and the more
204+
* specific form of `name:command` takes precedence over the more general form of `name`.
205+
* @param folderContext The folder context to search for the `swift.pluginPermissions` key.
206+
* @param taskDefinition The task definition to search for the `disableSandbox` and `allowWritingToPackageDirectory` keys.
207+
* @param plugin The plugin to generate arguments for.
208+
* @returns A list of permission related arguments to pass when invoking the plugin.
209+
*/
210+
private pluginArgumentsFromConfiguration(
211+
folderContext: vscode.WorkspaceFolder,
212+
taskDefinition: vscode.TaskDefinition,
213+
plugin: PackagePlugin
214+
): string[] {
215+
const config = configuration.folder(folderContext);
216+
const packageConfig = config.pluginPermissions(plugin.package);
217+
const commandConfig = config.pluginPermissions(`${plugin.package}:${plugin.command}`);
218+
219+
const taskDefinitionConfiguration: PluginPermissionConfiguration = {};
220+
if (taskDefinition.disableSandbox) {
221+
taskDefinitionConfiguration.disableSandbox = true;
222+
}
223+
if (taskDefinition.allowWritingToPackageDirectory) {
224+
taskDefinitionConfiguration.allowWritingToPackageDirectory = true;
225+
}
226+
if (taskDefinition.allowWritingToDirectory) {
227+
taskDefinitionConfiguration.allowWritingToDirectory =
228+
taskDefinition.allowWritingToDirectory;
229+
}
230+
if (taskDefinition.allowNetworkConnections) {
231+
taskDefinitionConfiguration.allowNetworkConnections =
232+
taskDefinition.allowNetworkConnections;
233+
}
234+
235+
return this.pluginArguments({
236+
...packageConfig,
237+
...commandConfig,
238+
...taskDefinitionConfiguration,
239+
});
240+
}
241+
242+
private pluginArguments(config: PluginPermissionConfiguration): string[] {
243+
const args = [];
244+
if (config.disableSandbox) {
245+
args.push("--disable-sandbox");
246+
}
247+
if (config.allowWritingToPackageDirectory) {
248+
args.push("--allow-writing-to-package-directory");
249+
}
250+
if (config.allowWritingToDirectory) {
251+
if (Array.isArray(config.allowWritingToDirectory)) {
252+
args.push("--allow-writing-to-directory", ...config.allowWritingToDirectory);
253+
} else {
254+
args.push("--allow-writing-to-directory");
255+
}
256+
}
257+
if (config.allowNetworkConnections) {
258+
args.push("--allow-network-connections");
259+
args.push(config.allowNetworkConnections);
260+
}
261+
return args;
262+
}
204263
}

0 commit comments

Comments
 (0)