Skip to content

Commit a7cf03d

Browse files
authored
Add elevation message to ports UI (#113990)
This change allows remote extension to handle elevation if they want. Fixes microsoft/vscode-remote-release#3922
1 parent efd298c commit a7cf03d

File tree

12 files changed

+149
-73
lines changed

12 files changed

+149
-73
lines changed

src/vs/platform/remote/common/tunnel.ts

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import { Emitter, Event } from 'vs/base/common/event';
77
import { IDisposable } from 'vs/base/common/lifecycle';
8+
import { isWindows } from 'vs/base/common/platform';
89
import { URI } from 'vs/base/common/uri';
910
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
1011
import { ILogService } from 'vs/platform/log/common/log';
@@ -30,6 +31,10 @@ export interface TunnelCreationOptions {
3031
elevationRequired?: boolean;
3132
}
3233

34+
export interface TunnelProviderFeatures {
35+
elevation: boolean;
36+
}
37+
3338
export interface ITunnelProvider {
3439
forwardPort(tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions): Promise<RemoteTunnel | undefined> | undefined;
3540
}
@@ -40,10 +45,11 @@ export interface ITunnelService {
4045
readonly tunnels: Promise<readonly RemoteTunnel[]>;
4146
readonly onTunnelOpened: Event<RemoteTunnel>;
4247
readonly onTunnelClosed: Event<{ host: string, port: number }>;
48+
readonly canElevate: boolean;
4349

44-
openTunnel(addressProvider: IAddressProvider | undefined, remoteHost: string | undefined, remotePort: number, localPort?: number): Promise<RemoteTunnel | undefined> | undefined;
50+
openTunnel(addressProvider: IAddressProvider | undefined, remoteHost: string | undefined, remotePort: number, localPort?: number, elevateIfNeeded?: boolean): Promise<RemoteTunnel | undefined> | undefined;
4551
closeTunnel(remoteHost: string, remotePort: number): Promise<void>;
46-
setTunnelProvider(provider: ITunnelProvider | undefined): IDisposable;
52+
setTunnelProvider(provider: ITunnelProvider | undefined, features: TunnelProviderFeatures): IDisposable;
4753
}
4854

4955
export function extractLocalHostUriMetaDataForPortMapping(uri: URI): { address: string, port: number } | undefined {
@@ -74,6 +80,10 @@ function getOtherLocalhost(host: string): string | undefined {
7480
return (host === 'localhost') ? '127.0.0.1' : ((host === '127.0.0.1') ? 'localhost' : undefined);
7581
}
7682

83+
export function isPortPrivileged(port: number): boolean {
84+
return !isWindows && (port < 1024);
85+
}
86+
7787
export abstract class AbstractTunnelService implements ITunnelService {
7888
declare readonly _serviceBrand: undefined;
7989

@@ -83,25 +93,33 @@ export abstract class AbstractTunnelService implements ITunnelService {
8393
public onTunnelClosed: Event<{ host: string, port: number }> = this._onTunnelClosed.event;
8494
protected readonly _tunnels = new Map</*host*/ string, Map</* port */ number, { refcount: number, readonly value: Promise<RemoteTunnel | undefined> }>>();
8595
protected _tunnelProvider: ITunnelProvider | undefined;
96+
protected _canElevate: boolean = false;
8697

8798
public constructor(
8899
@ILogService protected readonly logService: ILogService
89100
) { }
90101

91-
setTunnelProvider(provider: ITunnelProvider | undefined): IDisposable {
102+
setTunnelProvider(provider: ITunnelProvider | undefined, features: TunnelProviderFeatures): IDisposable {
103+
this._tunnelProvider = provider;
92104
if (!provider) {
105+
// clear features
106+
this._canElevate = false;
93107
return {
94108
dispose: () => { }
95109
};
96110
}
97-
this._tunnelProvider = provider;
111+
this._canElevate = features.elevation;
98112
return {
99113
dispose: () => {
100114
this._tunnelProvider = undefined;
101115
}
102116
};
103117
}
104118

119+
public get canElevate(): boolean {
120+
return this._canElevate;
121+
}
122+
105123
public get tunnels(): Promise<readonly RemoteTunnel[]> {
106124
return new Promise(async (resolve) => {
107125
const tunnels: RemoteTunnel[] = [];
@@ -129,7 +147,7 @@ export abstract class AbstractTunnelService implements ITunnelService {
129147
this._tunnels.clear();
130148
}
131149

132-
openTunnel(addressProvider: IAddressProvider | undefined, remoteHost: string | undefined, remotePort: number, localPort: number): Promise<RemoteTunnel | undefined> | undefined {
150+
openTunnel(addressProvider: IAddressProvider | undefined, remoteHost: string | undefined, remotePort: number, localPort?: number, elevateIfNeeded: boolean = false): Promise<RemoteTunnel | undefined> | undefined {
133151
if (!addressProvider) {
134152
return undefined;
135153
}
@@ -138,7 +156,7 @@ export abstract class AbstractTunnelService implements ITunnelService {
138156
remoteHost = 'localhost';
139157
}
140158

141-
const resolvedTunnel = this.retainOrCreateTunnel(addressProvider, remoteHost, remotePort, localPort);
159+
const resolvedTunnel = this.retainOrCreateTunnel(addressProvider, remoteHost, remotePort, localPort, elevateIfNeeded);
142160
if (!resolvedTunnel) {
143161
return resolvedTunnel;
144162
}
@@ -238,15 +256,11 @@ export abstract class AbstractTunnelService implements ITunnelService {
238256
return portMap ? portMap.get(remotePort) : undefined;
239257
}
240258

241-
protected abstract retainOrCreateTunnel(addressProvider: IAddressProvider, remoteHost: string, remotePort: number, localPort?: number): Promise<RemoteTunnel | undefined> | undefined;
242-
243-
protected isPortPrivileged(port: number): boolean {
244-
return port < 1024;
245-
}
259+
protected abstract retainOrCreateTunnel(addressProvider: IAddressProvider, remoteHost: string, remotePort: number, localPort: number | undefined, elevateIfNeeded: boolean): Promise<RemoteTunnel | undefined> | undefined;
246260
}
247261

248262
export class TunnelService extends AbstractTunnelService {
249-
protected retainOrCreateTunnel(_addressProvider: IAddressProvider, remoteHost: string, remotePort: number, localPort?: number | undefined): Promise<RemoteTunnel | undefined> | undefined {
263+
protected retainOrCreateTunnel(_addressProvider: IAddressProvider, remoteHost: string, remotePort: number, localPort: number | undefined, elevateIfNeeded: boolean): Promise<RemoteTunnel | undefined> | undefined {
250264
const existing = this.getTunnelFromMap(remoteHost, remotePort);
251265
if (existing) {
252266
++existing.refcount;
@@ -256,7 +270,7 @@ export class TunnelService extends AbstractTunnelService {
256270
if (this._tunnelProvider) {
257271
const preferredLocalPort = localPort === undefined ? remotePort : localPort;
258272
const tunnelOptions = { remoteAddress: { host: remoteHost, port: remotePort }, localAddressPort: localPort };
259-
const creationInfo = { elevationRequired: this.isPortPrivileged(preferredLocalPort) };
273+
const creationInfo = { elevationRequired: elevateIfNeeded ? isPortPrivileged(preferredLocalPort) : false };
260274
const tunnel = this._tunnelProvider.forwardPort(tunnelOptions, creationInfo);
261275
if (tunnel) {
262276
this.addTunnelToMap(remoteHost, remotePort, tunnel);

src/vs/platform/remote/node/tunnelService.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { NodeSocket } from 'vs/base/parts/ipc/node/ipc.net';
1111
import { ILogService } from 'vs/platform/log/common/log';
1212
import { IProductService } from 'vs/platform/product/common/productService';
1313
import { connectRemoteAgentTunnel, IConnectionOptions, IAddressProvider, ISocketFactory } from 'vs/platform/remote/common/remoteAgentConnection';
14-
import { AbstractTunnelService, RemoteTunnel } from 'vs/platform/remote/common/tunnel';
14+
import { AbstractTunnelService, isPortPrivileged, RemoteTunnel } from 'vs/platform/remote/common/tunnel';
1515
import { nodeSocketFactory } from 'vs/platform/remote/node/nodeSocketFactory';
1616
import { ISignService } from 'vs/platform/sign/common/sign';
1717

@@ -139,7 +139,7 @@ export class BaseTunnelService extends AbstractTunnelService {
139139
super(logService);
140140
}
141141

142-
protected retainOrCreateTunnel(addressProvider: IAddressProvider, remoteHost: string, remotePort: number, localPort?: number): Promise<RemoteTunnel | undefined> | undefined {
142+
protected retainOrCreateTunnel(addressProvider: IAddressProvider, remoteHost: string, remotePort: number, localPort: number | undefined, elevateIfNeeded: boolean): Promise<RemoteTunnel | undefined> | undefined {
143143
const existing = this.getTunnelFromMap(remoteHost, remotePort);
144144
if (existing) {
145145
++existing.refcount;
@@ -148,7 +148,7 @@ export class BaseTunnelService extends AbstractTunnelService {
148148

149149
if (this._tunnelProvider) {
150150
const preferredLocalPort = localPort === undefined ? remotePort : localPort;
151-
const creationInfo = { elevationRequired: this.isPortPrivileged(preferredLocalPort) };
151+
const creationInfo = { elevationRequired: elevateIfNeeded ? isPortPrivileged(preferredLocalPort) : false };
152152
const tunnelOptions = { remoteAddress: { host: remoteHost, port: remotePort }, localAddressPort: localPort };
153153
const tunnel = this._tunnelProvider.forwardPort(tunnelOptions, creationInfo);
154154
if (tunnel) {

src/vs/vscode.proposed.d.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,10 +234,18 @@ declare module 'vscode' {
234234
*/
235235
tunnelFactory?: (tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions) => Thenable<Tunnel> | undefined;
236236

237-
/**
237+
/**p
238238
* Provides filtering for candidate ports.
239239
*/
240240
showCandidatePort?: (host: string, port: number, detail: string) => Thenable<boolean>;
241+
242+
/**
243+
* Lets the resolver declare which tunnel factory features it supports.
244+
* UNDER DISCUSSION! MAY CHANGE SOON.
245+
*/
246+
tunnelFeatures?: {
247+
elevation: boolean;
248+
};
241249
}
242250

243251
export namespace workspace {

src/vs/workbench/api/browser/mainThreadTunnelService.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { MainThreadTunnelServiceShape, IExtHostContext, MainContext, ExtHostCont
77
import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService';
88
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
99
import { CandidatePort, IRemoteExplorerService, makeAddress } from 'vs/workbench/services/remote/common/remoteExplorerService';
10-
import { ITunnelProvider, ITunnelService, TunnelCreationOptions, TunnelOptions } from 'vs/platform/remote/common/tunnel';
10+
import { ITunnelProvider, ITunnelService, TunnelCreationOptions, TunnelProviderFeatures, TunnelOptions } from 'vs/platform/remote/common/tunnel';
1111
import { Disposable } from 'vs/base/common/lifecycle';
1212
import type { TunnelDescription } from 'vs/platform/remote/common/remoteAuthorityResolver';
1313

@@ -52,7 +52,7 @@ export class MainThreadTunnelService extends Disposable implements MainThreadTun
5252
this.remoteExplorerService.onFoundNewCandidates(candidates);
5353
}
5454

55-
async $setTunnelProvider(): Promise<void> {
55+
async $setTunnelProvider(features: TunnelProviderFeatures): Promise<void> {
5656
const tunnelProvider: ITunnelProvider = {
5757
forwardPort: (tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions) => {
5858
const forward = this._proxy.$forwardPort(tunnelOptions, tunnelCreationOptions);
@@ -75,7 +75,7 @@ export class MainThreadTunnelService extends Disposable implements MainThreadTun
7575
return undefined;
7676
}
7777
};
78-
this.tunnelService.setTunnelProvider(tunnelProvider);
78+
this.tunnelService.setTunnelProvider(tunnelProvider, features);
7979
}
8080

8181
dispose(): void {

src/vs/workbench/api/common/extHost.protocol.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ import * as search from 'vs/workbench/services/search/common/search';
4848
import { EditorGroupColumn, SaveReason } from 'vs/workbench/common/editor';
4949
import { ExtensionActivationReason } from 'vs/workbench/api/common/extHostExtensionActivator';
5050
import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService';
51-
import { TunnelCreationOptions, TunnelOptions } from 'vs/platform/remote/common/tunnel';
51+
import { TunnelCreationOptions, TunnelProviderFeatures, TunnelOptions } from 'vs/platform/remote/common/tunnel';
5252
import { Timeline, TimelineChangeEvent, TimelineOptions, TimelineProviderDescriptor, InternalTimelineOptions } from 'vs/workbench/contrib/timeline/common/timeline';
5353
import { revive } from 'vs/base/common/marshalling';
5454
import { IProcessedOutput, INotebookDisplayOrder, NotebookCellMetadata, NotebookDocumentMetadata, ICellEditOperation, NotebookCellsChangedEventDto, NotebookDataDto, IMainCellDto, INotebookDocumentFilter, INotebookKernelInfoDto2, TransientMetadata, INotebookCellStatusBarEntry, ICellRange, INotebookDecorationRenderOptions, INotebookExclusiveDocumentFilter } from 'vs/workbench/contrib/notebook/common/notebookCommon';
@@ -972,7 +972,7 @@ export interface MainThreadTunnelServiceShape extends IDisposable {
972972
$openTunnel(tunnelOptions: TunnelOptions, source: string | undefined): Promise<TunnelDto | undefined>;
973973
$closeTunnel(remote: { host: string, port: number }): Promise<void>;
974974
$getTunnels(): Promise<TunnelDescription[]>;
975-
$setTunnelProvider(): Promise<void>;
975+
$setTunnelProvider(features: TunnelProviderFeatures): Promise<void>;
976976
$onFoundNewCandidates(candidates: { host: string, port: number, detail: string }[]): Promise<void>;
977977
}
978978

src/vs/workbench/api/node/extHostTunnelService.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,9 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
195195
}
196196
if (provider.tunnelFactory) {
197197
this._forwardPortProvider = provider.tunnelFactory;
198-
await this._proxy.$setTunnelProvider();
198+
await this._proxy.$setTunnelProvider(provider.tunnelFeatures ?? {
199+
elevation: false
200+
});
199201
}
200202
} else {
201203
this._forwardPortProvider = undefined;

0 commit comments

Comments
 (0)