Skip to content
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
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
1 change: 1 addition & 0 deletions package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@
"LanguageService.lsFailedToStart": "We encountered an issue starting the language server. Reverting to Jedi language engine. Check the Python output panel for details.",
"LanguageService.lsFailedToDownload": "We encountered an issue downloading the language server. Reverting to Jedi language engine. Check the Python output panel for details.",
"LanguageService.lsFailedToExtract": "We encountered an issue extracting the language server. Reverting to Jedi language engine. Check the Python output panel for details.",
"LanguageService.untrustedWorkspaceMessage": "Only Pylance is supported in untrusted workspaces, setting language server to None.",
"LanguageService.downloadFailedOutputMessage": "Language server download failed",
"LanguageService.extractionFailedOutputMessage": "Language server extraction failed",
"LanguageService.extractionCompletedOutputMessage": "Language server download complete",
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
20 changes: 15 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 @@ -235,6 +237,14 @@ export class LanguageServerExtensionActivationService
}
}

if (
!this.workspaceService.isTrusted &&
serverType !== LanguageServerType.Node &&
serverType !== LanguageServerType.None
) {
this.output.appendLine(LanguageService.untrustedWorkspaceMessage());
serverType = LanguageServerType.None;
}
this.sendTelemetryForChosenLanguageServer(serverType).ignoreErrors();
await this.logStartup(serverType);
let server = this.serviceContainer.get<ILanguageServerActivator>(ILanguageServerActivator, serverType);
Expand Down Expand Up @@ -305,7 +315,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
4 changes: 4 additions & 0 deletions src/client/common/utils/localize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,10 @@ export namespace LanguageService {
'LanguageService.startingNone',
'Editor support is inactive since language server is set to None.',
);
export const untrustedWorkspaceMessage = localize(
'LanguageService.untrustedWorkspaceMessage',
'Only Pylance is supported in untrusted workspaces, setting language server to None.',
);

export const reloadAfterLanguageServerChange = localize(
'LanguageService.reloadAfterLanguageServerChange',
Expand Down
32 changes: 23 additions & 9 deletions src/client/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import { ProgressLocation, ProgressOptions, window } from 'vscode';

import { buildApi, IExtensionApi } from './api';
import { IApplicationShell, IWorkspaceService } from './common/application/types';
import { IAsyncDisposableRegistry, IExperimentService, IExtensionContext } from './common/types';
import { IAsyncDisposableRegistry, IDisposableRegistry, IExperimentService, IExtensionContext } from './common/types';
import { createDeferred } from './common/utils/async';
import { Common } from './common/utils/localize';
import { activateComponents } from './extensionActivation';
Expand All @@ -42,6 +42,7 @@ import { sendErrorTelemetry, sendStartupTelemetry } from './startupTelemetry';
import { IStartupDurations } from './types';
import { runAfterActivation } from './common/utils/runAfterActivation';
import { IInterpreterService } from './interpreter/contracts';
import { WorkspaceService } from './common/application/workspace';

durations.codeLoadingTime = stopWatch.elapsedTime;

Expand All @@ -59,6 +60,13 @@ export async function activate(context: IExtensionContext): Promise<IExtensionAp
let ready: Promise<void>;
let serviceContainer: IServiceContainer;
try {
const workspaceService = new WorkspaceService();
context.subscriptions.push(
workspaceService.onDidGrantWorkspaceTrust(async () => {
await deactivate();
await activate(context);
}),
);
[api, ready, serviceContainer] = await activateUnsafe(context, stopWatch, durations);
} catch (ex) {
// We want to completely handle the error
Expand All @@ -79,9 +87,13 @@ export function deactivate(): Thenable<void> {
// Make sure to shutdown anybody who needs it.
if (activatedServiceContainer) {
const registry = activatedServiceContainer.get<IAsyncDisposableRegistry>(IAsyncDisposableRegistry);
if (registry) {
return registry.dispose();
}
const disposables = activatedServiceContainer.get<IDisposableRegistry>(IDisposableRegistry);
const promises = Promise.all(disposables.map((d) => d.dispose()));
return promises.then(() => {
if (registry) {
return registry.dispose();
}
});
}

return Promise.resolve();
Expand Down Expand Up @@ -133,11 +145,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