Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
1 change: 1 addition & 0 deletions news/1 Enhancements/18031.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Declare limited support for untrusted workspaces by only supporting Pylance.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
},
"capabilities": {
"untrustedWorkspaces": {
"supported": false
"supported": "limited",
"description": "Only Partial IntelliSense with Pylance is supported. Cannot execute Python with untrusted files."
},
"virtualWorkspaces": {
"supported": "limited",
Expand Down
19 changes: 14 additions & 5 deletions src/client/activation/activationManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@ export class ExtensionActivationManager implements IExtensionActivationManager {
@inject(IExperimentService) private readonly experiments: IExperimentService,
@inject(IInterpreterPathService) private readonly interpreterPathService: IInterpreterPathService,
) {
if (!this.workspaceService.isTrusted) {
this.activationServices = this.activationServices.filter(
(service) => service.supportedWorkspaceTypes.untrustedWorkspace,
);
this.singleActivationServices = this.singleActivationServices.filter(
(service) => service.supportedWorkspaceTypes.untrustedWorkspace,
);
}
if (this.workspaceService.isVirtualWorkspace) {
this.activationServices = this.activationServices.filter(
(service) => service.supportedWorkspaceTypes.virtualWorkspace,
Expand Down Expand Up @@ -80,13 +88,14 @@ export class ExtensionActivationManager implements IExtensionActivationManager {
}
this.activatedWorkspaces.add(key);

if (this.experiments.inExperimentSync(DeprecatePythonPath.experiment)) {
await this.interpreterPathService.copyOldInterpreterStorageValuesToNew(resource);
if (this.workspaceService.isTrusted) {
// Do not interact with interpreters in a untrusted workspace.
if (this.experiments.inExperimentSync(DeprecatePythonPath.experiment)) {
await this.interpreterPathService.copyOldInterpreterStorageValuesToNew(resource);
}
await this.autoSelection.autoSelectInterpreter(resource);
}

await sendActivationTelemetry(this.fileSystem, this.workspaceService, resource);

await this.autoSelection.autoSelectInterpreter(resource);
await Promise.all(this.activationServices.map((item) => item.activate(resource)));
await this.appDiagnostics.performPreStartupHealthCheck(resource);
}
Expand Down
12 changes: 7 additions & 5 deletions src/client/activation/activationService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export class LanguageServerExtensionActivationService

private readonly output: OutputChannel;

private readonly interpreterService: IInterpreterService;
private readonly interpreterService?: IInterpreterService;

private readonly languageServerChangeHandler: LanguageServerChangeHandler;

Expand All @@ -69,14 +69,16 @@ export class LanguageServerExtensionActivationService
) {
this.workspaceService = this.serviceContainer.get<IWorkspaceService>(IWorkspaceService);
this.configurationService = this.serviceContainer.get<IConfigurationService>(IConfigurationService);
this.interpreterService = this.serviceContainer.get<IInterpreterService>(IInterpreterService);
this.output = this.serviceContainer.get<OutputChannel>(IOutputChannel, STANDARD_OUTPUT_CHANNEL);

const disposables = serviceContainer.get<IDisposableRegistry>(IDisposableRegistry);
disposables.push(this);
disposables.push(this.workspaceService.onDidChangeConfiguration(this.onDidChangeConfiguration.bind(this)));
disposables.push(this.workspaceService.onDidChangeWorkspaceFolders(this.onWorkspaceFoldersChanged, this));
disposables.push(this.interpreterService.onDidChangeInterpreter(this.onDidChangeInterpreter.bind(this)));
if (this.workspaceService.isTrusted) {
this.interpreterService = this.serviceContainer.get<IInterpreterService>(IInterpreterService);
disposables.push(this.interpreterService.onDidChangeInterpreter(this.onDidChangeInterpreter.bind(this)));
}

this.languageServerChangeHandler = new LanguageServerChangeHandler(
this.getCurrentLanguageServerType(),
Expand All @@ -93,7 +95,7 @@ export class LanguageServerExtensionActivationService
const stopWatch = new StopWatch();
// Get a new server and dispose of the old one (might be the same one)
this.resource = resource;
const interpreter = await this.interpreterService.getActiveInterpreter(resource);
const interpreter = await this.interpreterService?.getActiveInterpreter(resource);
const key = await this.getKey(resource, interpreter);

// If we have an old server with a different key, then deactivate it as the
Expand Down Expand Up @@ -305,7 +307,7 @@ export class LanguageServerExtensionActivationService
resource,
workspacePathNameForGlobalWorkspaces,
);
interpreter = interpreter || (await this.interpreterService.getActiveInterpreter(resource));
interpreter = interpreter || (await this.interpreterService?.getActiveInterpreter(resource));
const interperterPortion = interpreter ? `${interpreter.path}-${interpreter.envName}` : '';
return `${resourcePortion}-${interperterPortion}`;
}
Expand Down
1 change: 1 addition & 0 deletions src/client/activation/node/analysisOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export class NodeLanguageServerAnalysisOptions extends LanguageServerAnalysisOpt
protected async getInitializationOptions() {
return {
experimentationSupport: true,
trustedWorkspaceSupport: true,
};
}
}
18 changes: 18 additions & 0 deletions src/client/activation/node/languageServerProxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { FileBasedCancellationStrategy } from '../common/cancellationUtils';
import { ProgressReporting } from '../progress';
import { ILanguageClientFactory, ILanguageServerFolderService, ILanguageServerProxy } from '../types';
import { traceDecoratorError, traceDecoratorVerbose, traceError } from '../../logging';
import { IWorkspaceService } from '../../common/application/types';

namespace InExperiment {
export const Method = 'python/inExperiment';
Expand Down Expand Up @@ -64,6 +65,7 @@ export class NodeLanguageServerProxy implements ILanguageServerProxy {
@inject(IExperimentService) private readonly experimentService: IExperimentService,
@inject(IInterpreterPathService) private readonly interpreterPathService: IInterpreterPathService,
@inject(IEnvironmentVariablesProvider) private readonly environmentService: IEnvironmentVariablesProvider,
@inject(IWorkspaceService) private readonly workspace: IWorkspaceService,
) {
this.startupCompleted = createDeferred<void>();
}
Expand Down Expand Up @@ -127,6 +129,14 @@ export class NodeLanguageServerProxy implements ILanguageServerProxy {
}
});

this.disposables.push(
this.workspace.onDidGrantWorkspaceTrust(() => {
this.languageClient!.onReady().then(() => {
this.languageClient!.sendNotification('python/workspaceTrusted', { isTrusted: true });
});
}),
);

this.disposables.push(this.languageClient.start());
await this.serverReady();

Expand Down Expand Up @@ -224,5 +234,13 @@ export class NodeLanguageServerProxy implements ILanguageServerProxy {
return { value };
},
);

this.disposables.push(
this.languageClient!.onRequest('python/isTrustedWorkspace', async () => {
return {
isTrusted: this.workspace.isTrusted,
};
}),
);
}
}
7 changes: 6 additions & 1 deletion src/client/application/diagnostics/applicationDiagnostics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import { inject, injectable, named } from 'inversify';
import { DiagnosticSeverity } from 'vscode';
import { IWorkspaceService } from '../../common/application/types';
import { isTestExecution, STANDARD_OUTPUT_CHANNEL } from '../../common/constants';
import { IOutputChannel, Resource } from '../../common/types';
import { IServiceContainer } from '../../ioc/types';
Expand All @@ -26,7 +27,11 @@ export class ApplicationDiagnostics implements IApplicationDiagnostics {
if (isTestExecution()) {
return;
}
const services = this.serviceContainer.getAll<IDiagnosticsService>(IDiagnosticsService);
let services = this.serviceContainer.getAll<IDiagnosticsService>(IDiagnosticsService);
const workspaceService = this.serviceContainer.get<IWorkspaceService>(IWorkspaceService);
if (!workspaceService.isTrusted) {
services = services.filter((item) => item.runInUntrustedWorkspace);
}
// Perform these validation checks in the foreground.
await this.runDiagnostics(
services.filter((item) => !item.runInBackground),
Expand Down
1 change: 1 addition & 0 deletions src/client/application/diagnostics/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export abstract class BaseDiagnosticsService implements IDiagnosticsService, IDi
@unmanaged() protected serviceContainer: IServiceContainer,
@unmanaged() disposableRegistry: IDisposableRegistry,
@unmanaged() public readonly runInBackground: boolean = false,
@unmanaged() public readonly runInUntrustedWorkspace: boolean = false,
) {
this.filterService = serviceContainer.get<IDiagnosticFilterService>(IDiagnosticFilterService);
disposableRegistry.push(this);
Expand Down
8 changes: 7 additions & 1 deletion src/client/application/diagnostics/checks/envPathVariable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,13 @@ export class EnvironmentPathVariableDiagnosticsService extends BaseDiagnosticsSe
@inject(IServiceContainer) serviceContainer: IServiceContainer,
@inject(IDisposableRegistry) disposableRegistry: IDisposableRegistry,
) {
super([DiagnosticCodes.InvalidEnvironmentPathVariableDiagnostic], serviceContainer, disposableRegistry, true);
super(
[DiagnosticCodes.InvalidEnvironmentPathVariableDiagnostic],
serviceContainer,
disposableRegistry,
true,
true,
);
this.platform = this.serviceContainer.get<IPlatformService>(IPlatformService);
this.messageService = serviceContainer.get<IDiagnosticHandlerService<MessageCommandPrompt>>(
IDiagnosticHandlerService,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export class PylanceDefaultDiagnosticService extends BaseDiagnosticsService {
protected readonly messageService: IDiagnosticHandlerService<MessageCommandPrompt>,
@inject(IDisposableRegistry) disposableRegistry: IDisposableRegistry,
) {
super([DiagnosticCodes.PylanceDefaultDiagnostic], serviceContainer, disposableRegistry, true);
super([DiagnosticCodes.PylanceDefaultDiagnostic], serviceContainer, disposableRegistry, true, true);

this.initialMementoValue = this.context.globalState.get(EXTENSION_VERSION_MEMENTO);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export class SwitchToDefaultLanguageServerDiagnosticService extends BaseDiagnost
protected readonly messageService: IDiagnosticHandlerService<MessageCommandPrompt>,
@inject(IDisposableRegistry) disposableRegistry: IDisposableRegistry,
) {
super([DiagnosticCodes.JediPython27NotSupportedDiagnostic], serviceContainer, disposableRegistry, true);
super([DiagnosticCodes.JediPython27NotSupportedDiagnostic], serviceContainer, disposableRegistry, true, true);
}

public diagnose(resource: Resource): Promise<IDiagnostic[]> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export class UpgradeCodeRunnerDiagnosticService extends BaseDiagnosticsService {
@inject(IDisposableRegistry) disposableRegistry: IDisposableRegistry,
@inject(IExtensions) private readonly extensions: IExtensions,
) {
super([DiagnosticCodes.UpgradeCodeRunnerDiagnostic], serviceContainer, disposableRegistry, true);
super([DiagnosticCodes.UpgradeCodeRunnerDiagnostic], serviceContainer, disposableRegistry, true, true);
this.workspaceService = this.serviceContainer.get<IWorkspaceService>(IWorkspaceService);
}

Expand Down
1 change: 1 addition & 0 deletions src/client/application/diagnostics/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export const IDiagnosticsService = Symbol('IDiagnosticsService');

export interface IDiagnosticsService {
readonly runInBackground: boolean;
readonly runInUntrustedWorkspace: boolean;
diagnose(resource: Resource): Promise<IDiagnostic[]>;
canHandle(diagnostic: IDiagnostic): Promise<boolean>;
handle(diagnostics: IDiagnostic[]): Promise<void>;
Expand Down
10 changes: 10 additions & 0 deletions src/client/common/application/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -675,6 +675,16 @@ export interface IWorkspaceService {
*/
readonly rootPath: string | undefined;

/**
* When true, the user has explicitly trusted the contents of the workspace.
*/
readonly isTrusted: boolean;

/**
* Event that fires when the current workspace has been trusted.
*/
readonly onDidGrantWorkspaceTrust: Event<void>;

/**
* List of workspace folders or `undefined` when no folder is open.
* *Note* that the first entry corresponds to the value of `rootPath`.
Expand Down
8 changes: 8 additions & 0 deletions src/client/common/application/workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,14 @@ export class WorkspaceService implements IWorkspaceService {
return !!isVirtualWorkspace;
}

public get isTrusted(): boolean {
return workspace.isTrusted;
}

public get onDidGrantWorkspaceTrust(): Event<void> {
return workspace.onDidGrantWorkspaceTrust;
}

private get searchExcludes() {
const searchExcludes = this.getConfiguration('search.exclude');
const enabledSearchExcludes = Object.keys(searchExcludes).filter((key) => searchExcludes.get(key) === true);
Expand Down
12 changes: 7 additions & 5 deletions src/client/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,11 +133,13 @@ async function activateUnsafe(
setTimeout(async () => {
if (activatedServiceContainer) {
const workspaceService = activatedServiceContainer.get<IWorkspaceService>(IWorkspaceService);
const interpreterManager = activatedServiceContainer.get<IInterpreterService>(IInterpreterService);
const workspaces = workspaceService.workspaceFolders ?? [];
await interpreterManager
.refresh(workspaces.length > 0 ? workspaces[0].uri : undefined)
.catch((ex) => traceError('Python Extension: interpreterManager.refresh', ex));
if (workspaceService.isTrusted) {
const interpreterManager = activatedServiceContainer.get<IInterpreterService>(IInterpreterService);
const workspaces = workspaceService.workspaceFolders ?? [];
await interpreterManager
.refresh(workspaces.length > 0 ? workspaces[0].uri : undefined)
.catch((ex) => traceError('Python Extension: interpreterManager.refresh', ex));
}
}

runAfterActivation();
Expand Down
Loading