Skip to content

Commit 28000df

Browse files
committed
files - store file paths on disk for save as elevated user flow
1 parent d78a74b commit 28000df

File tree

3 files changed

+62
-28
lines changed

3 files changed

+62
-28
lines changed

src/vs/code/node/cli.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -125,14 +125,26 @@ export async function main(argv: string[]): Promise<any> {
125125

126126
// Write File
127127
else if (args['file-write']) {
128-
const source = args._[0];
129-
const target = args._[1];
128+
const argsFile = args._[0];
129+
if (!argsFile || !isAbsolute(argsFile) || !existsSync(argsFile) || !statSync(argsFile).isFile()) {
130+
throw new Error('Using --file-write with invalid arguments.');
131+
}
132+
133+
let source: string | undefined;
134+
let target: string | undefined;
135+
try {
136+
const argsContents = JSON.parse(readFileSync(argsFile, 'utf8'));
137+
source = argsContents.source;
138+
target = argsContents.target;
139+
} catch (error) {
140+
throw new Error('Using --file-write with invalid arguments.');
141+
}
130142

131143
// Windows: set the paths as allowed UNC paths given
132144
// they are explicitly provided by the user as arguments
133145
if (isWindows) {
134146
for (const path of [source, target]) {
135-
if (isUNC(path)) {
147+
if (typeof path === 'string' && isUNC(path)) {
136148
addUNCHostToAllowlist(URI.file(path).authority);
137149
}
138150
}

src/vs/platform/native/electron-main/nativeHostMainService.ts

Lines changed: 33 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import { CancellationError } from '../../../base/common/errors.js';
4747
import { IConfigurationService } from '../../configuration/common/configuration.js';
4848
import { IProxyAuthService } from './auth.js';
4949
import { AuthInfo, Credentials, IRequestService } from '../../request/common/request.js';
50+
import { randomPath } from '../../../base/common/extpath.js';
5051

5152
export interface INativeHostMainService extends AddFirstParameterToFunctions<ICommonNativeHostService, Promise<unknown> /* only methods, not events */, number | undefined /* window ID */> { }
5253

@@ -568,35 +569,44 @@ export class NativeHostMainService extends Disposable implements INativeHostMain
568569
async writeElevated(windowId: number | undefined, source: URI, target: URI, options?: { unlock?: boolean }): Promise<void> {
569570
const sudoPrompt = await import('@vscode/sudo-prompt');
570571

571-
return new Promise<void>((resolve, reject) => {
572-
const sudoCommand: string[] = [`"${this.cliPath}"`];
573-
if (options?.unlock) {
574-
sudoCommand.push('--file-chmod');
575-
}
572+
const argsFile = randomPath(this.environmentMainService.userDataPath, 'code-elevated');
573+
await Promises.writeFile(argsFile, JSON.stringify({ source: source.fsPath, target: target.fsPath }));
576574

577-
sudoCommand.push('--file-write', `"${source.fsPath}"`, `"${target.fsPath}"`);
575+
try {
576+
await new Promise<void>((resolve, reject) => {
577+
const sudoCommand: string[] = [`"${this.cliPath}"`];
578+
if (options?.unlock) {
579+
sudoCommand.push('--file-chmod');
580+
}
578581

579-
const promptOptions = {
580-
name: this.productService.nameLong.replace('-', ''),
581-
icns: (isMacintosh && this.environmentMainService.isBuilt) ? join(dirname(this.environmentMainService.appRoot), `${this.productService.nameShort}.icns`) : undefined
582-
};
582+
sudoCommand.push('--file-write', `"${argsFile}"`);
583583

584-
sudoPrompt.exec(sudoCommand.join(' '), promptOptions, (error?, stdout?, stderr?) => {
585-
if (stdout) {
586-
this.logService.trace(`[sudo-prompt] received stdout: ${stdout}`);
587-
}
584+
const promptOptions = {
585+
name: this.productService.nameLong.replace('-', ''),
586+
icns: (isMacintosh && this.environmentMainService.isBuilt) ? join(dirname(this.environmentMainService.appRoot), `${this.productService.nameShort}.icns`) : undefined
587+
};
588588

589-
if (stderr) {
590-
this.logService.trace(`[sudo-prompt] received stderr: ${stderr}`);
591-
}
589+
this.logService.trace(`[sudo-prompt] running command: ${sudoCommand.join(' ')}`);
592590

593-
if (error) {
594-
reject(error);
595-
} else {
596-
resolve(undefined);
597-
}
591+
sudoPrompt.exec(sudoCommand.join(' '), promptOptions, (error?, stdout?, stderr?) => {
592+
if (stdout) {
593+
this.logService.trace(`[sudo-prompt] received stdout: ${stdout}`);
594+
}
595+
596+
if (stderr) {
597+
this.logService.error(`[sudo-prompt] received stderr: ${stderr}`);
598+
}
599+
600+
if (error) {
601+
reject(error);
602+
} else {
603+
resolve(undefined);
604+
}
605+
});
598606
});
599-
});
607+
} finally {
608+
await fs.promises.unlink(argsFile);
609+
}
600610
}
601611

602612
async isRunningUnderARM64Translation(): Promise<boolean> {

src/vs/workbench/services/files/electron-sandbox/elevatedFileService.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,29 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6+
import { localize } from '../../../../nls.js';
67
import { VSBuffer, VSBufferReadable, VSBufferReadableStream } from '../../../../base/common/buffer.js';
78
import { randomPath } from '../../../../base/common/extpath.js';
89
import { Schemas } from '../../../../base/common/network.js';
910
import { URI } from '../../../../base/common/uri.js';
1011
import { IFileService, IFileStatWithMetadata, IWriteFileOptions } from '../../../../platform/files/common/files.js';
1112
import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';
1213
import { INativeHostService } from '../../../../platform/native/common/native.js';
14+
import { IWorkspaceTrustRequestService } from '../../../../platform/workspace/common/workspaceTrust.js';
1315
import { INativeWorkbenchEnvironmentService } from '../../environment/electron-sandbox/environmentService.js';
1416
import { IElevatedFileService } from '../common/elevatedFileService.js';
15-
17+
import { isWindows } from '../../../../base/common/platform.js';
18+
import { ILabelService } from '../../../../platform/label/common/label.js';
1619
export class NativeElevatedFileService implements IElevatedFileService {
1720

1821
readonly _serviceBrand: undefined;
1922

2023
constructor(
2124
@INativeHostService private readonly nativeHostService: INativeHostService,
2225
@IFileService private readonly fileService: IFileService,
23-
@INativeWorkbenchEnvironmentService private readonly environmentService: INativeWorkbenchEnvironmentService
26+
@INativeWorkbenchEnvironmentService private readonly environmentService: INativeWorkbenchEnvironmentService,
27+
@IWorkspaceTrustRequestService private readonly workspaceTrustRequestService: IWorkspaceTrustRequestService,
28+
@ILabelService private readonly labelService: ILabelService
2429
) { }
2530

2631
isSupported(resource: URI): boolean {
@@ -32,6 +37,13 @@ export class NativeElevatedFileService implements IElevatedFileService {
3237
}
3338

3439
async writeFileElevated(resource: URI, value: VSBuffer | VSBufferReadable | VSBufferReadableStream, options?: IWriteFileOptions): Promise<IFileStatWithMetadata> {
40+
const trusted = await this.workspaceTrustRequestService.requestWorkspaceTrust({
41+
message: isWindows ? localize('fileNotTrustedMessageWindows', "You are about to save '{0}' as admin.", this.labelService.getUriLabel(resource)) : localize('fileNotTrustedMessagePosix', "You are about to save '{0}' as super user.", this.labelService.getUriLabel(resource)),
42+
});
43+
if (!trusted) {
44+
throw new Error(localize('fileNotTrusted', "Workspace is not trusted."));
45+
}
46+
3547
const source = URI.file(randomPath(this.environmentService.userDataPath, 'code-elevated'));
3648
try {
3749
// write into a tmp file first

0 commit comments

Comments
 (0)