Skip to content

Commit

Permalink
files - store file paths on disk for save as elevated user flow
Browse files Browse the repository at this point in the history
  • Loading branch information
bpasero committed Oct 8, 2024
1 parent d78a74b commit 28000df
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 28 deletions.
18 changes: 15 additions & 3 deletions src/vs/code/node/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,14 +125,26 @@ export async function main(argv: string[]): Promise<any> {

// Write File
else if (args['file-write']) {
const source = args._[0];
const target = args._[1];
const argsFile = args._[0];
if (!argsFile || !isAbsolute(argsFile) || !existsSync(argsFile) || !statSync(argsFile).isFile()) {
throw new Error('Using --file-write with invalid arguments.');
}

let source: string | undefined;
let target: string | undefined;
try {
const argsContents = JSON.parse(readFileSync(argsFile, 'utf8'));
source = argsContents.source;
target = argsContents.target;
} catch (error) {
throw new Error('Using --file-write with invalid arguments.');
}

// Windows: set the paths as allowed UNC paths given
// they are explicitly provided by the user as arguments
if (isWindows) {
for (const path of [source, target]) {
if (isUNC(path)) {
if (typeof path === 'string' && isUNC(path)) {
addUNCHostToAllowlist(URI.file(path).authority);
}
}
Expand Down
56 changes: 33 additions & 23 deletions src/vs/platform/native/electron-main/nativeHostMainService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import { CancellationError } from '../../../base/common/errors.js';
import { IConfigurationService } from '../../configuration/common/configuration.js';
import { IProxyAuthService } from './auth.js';
import { AuthInfo, Credentials, IRequestService } from '../../request/common/request.js';
import { randomPath } from '../../../base/common/extpath.js';

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

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

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

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

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

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

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

if (error) {
reject(error);
} else {
resolve(undefined);
}
sudoPrompt.exec(sudoCommand.join(' '), promptOptions, (error?, stdout?, stderr?) => {
if (stdout) {
this.logService.trace(`[sudo-prompt] received stdout: ${stdout}`);
}

if (stderr) {
this.logService.error(`[sudo-prompt] received stderr: ${stderr}`);
}

if (error) {
reject(error);
} else {
resolve(undefined);
}
});
});
});
} finally {
await fs.promises.unlink(argsFile);
}
}

async isRunningUnderARM64Translation(): Promise<boolean> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,29 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { localize } from '../../../../nls.js';
import { VSBuffer, VSBufferReadable, VSBufferReadableStream } from '../../../../base/common/buffer.js';
import { randomPath } from '../../../../base/common/extpath.js';
import { Schemas } from '../../../../base/common/network.js';
import { URI } from '../../../../base/common/uri.js';
import { IFileService, IFileStatWithMetadata, IWriteFileOptions } from '../../../../platform/files/common/files.js';
import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';
import { INativeHostService } from '../../../../platform/native/common/native.js';
import { IWorkspaceTrustRequestService } from '../../../../platform/workspace/common/workspaceTrust.js';
import { INativeWorkbenchEnvironmentService } from '../../environment/electron-sandbox/environmentService.js';
import { IElevatedFileService } from '../common/elevatedFileService.js';

import { isWindows } from '../../../../base/common/platform.js';
import { ILabelService } from '../../../../platform/label/common/label.js';
export class NativeElevatedFileService implements IElevatedFileService {

readonly _serviceBrand: undefined;

constructor(
@INativeHostService private readonly nativeHostService: INativeHostService,
@IFileService private readonly fileService: IFileService,
@INativeWorkbenchEnvironmentService private readonly environmentService: INativeWorkbenchEnvironmentService
@INativeWorkbenchEnvironmentService private readonly environmentService: INativeWorkbenchEnvironmentService,
@IWorkspaceTrustRequestService private readonly workspaceTrustRequestService: IWorkspaceTrustRequestService,
@ILabelService private readonly labelService: ILabelService
) { }

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

async writeFileElevated(resource: URI, value: VSBuffer | VSBufferReadable | VSBufferReadableStream, options?: IWriteFileOptions): Promise<IFileStatWithMetadata> {
const trusted = await this.workspaceTrustRequestService.requestWorkspaceTrust({
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)),
});
if (!trusted) {
throw new Error(localize('fileNotTrusted', "Workspace is not trusted."));
}

const source = URI.file(randomPath(this.environmentService.userDataPath, 'code-elevated'));
try {
// write into a tmp file first
Expand Down

0 comments on commit 28000df

Please sign in to comment.