trustedDomains.ts 8.7 KB
Newer Older
P
Pine Wu 已提交
1 2 3 4 5
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

P
Pine Wu 已提交
6
import { URI } from 'vs/base/common/uri';
P
Pine Wu 已提交
7 8
import { localize } from 'vs/nls';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
9
import { IProductService } from 'vs/platform/product/common/productService';
P
Pine Wu 已提交
10 11
import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
P
Pine Wu 已提交
12
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
P
Pine Wu 已提交
13
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
14
import { IAuthenticationService } from 'vs/workbench/services/authentication/browser/authenticationService';
15 16 17
import { IFileService } from 'vs/platform/files/common/files';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
18 19
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
P
Pine Wu 已提交
20

P
Pine Wu 已提交
21
const TRUSTED_DOMAINS_URI = URI.parse('trustedDomains:/Trusted Domains');
P
Pine Wu 已提交
22

P
Pine Wu 已提交
23 24 25
export const TRUSTED_DOMAINS_STORAGE_KEY = 'http.linkProtectionTrustedDomains';
export const TRUSTED_DOMAINS_CONTENT_STORAGE_KEY = 'http.linkProtectionTrustedDomainsContent';

P
Pine Wu 已提交
26 27
export const manageTrustedDomainSettingsCommand = {
	id: 'workbench.action.manageTrustedDomain',
P
Pine Wu 已提交
28
	description: {
P
Pine Wu 已提交
29
		description: localize('trustedDomain.manageTrustedDomain', 'Manage Trusted Domains'),
P
Pine Wu 已提交
30 31 32
		args: []
	},
	handler: async (accessor: ServicesAccessor) => {
P
Pine Wu 已提交
33
		const editorService = accessor.get(IEditorService);
34
		editorService.openEditor({ resource: TRUSTED_DOMAINS_URI, mode: 'jsonc', options: { pinned: true } });
P
Pine Wu 已提交
35
		return;
P
Pine Wu 已提交
36 37 38
	}
};

39 40
type ConfigureTrustedDomainsQuickPickItem = IQuickPickItem & ({ id: 'manage'; } | { id: 'trust'; toTrust: string });

P
Pine Wu 已提交
41 42 43 44
type ConfigureTrustedDomainsChoiceClassification = {
	choice: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
};

P
Pine Wu 已提交
45 46 47
export async function configureOpenerTrustedDomainsHandler(
	trustedDomains: string[],
	domainToConfigure: string,
48
	resource: URI,
P
Pine Wu 已提交
49
	quickInputService: IQuickInputService,
P
Pine Wu 已提交
50
	storageService: IStorageService,
P
Pine Wu 已提交
51
	editorService: IEditorService,
52 53 54
	telemetryService: ITelemetryService,
	notificationService: INotificationService,
	clipboardService: IClipboardService,
P
Pine Wu 已提交
55
) {
P
Pine Wu 已提交
56
	const parsedDomainToConfigure = URI.parse(domainToConfigure);
P
Pine Wu 已提交
57
	const toplevelDomainSegements = parsedDomainToConfigure.authority.split('.');
P
Pine Wu 已提交
58
	const domainEnd = toplevelDomainSegements.slice(toplevelDomainSegements.length - 2).join('.');
P
Pine Wu 已提交
59
	const topLevelDomain = '*.' + domainEnd;
60
	const options: ConfigureTrustedDomainsQuickPickItem[] = [];
P
Pine Wu 已提交
61

62
	options.push({
P
Pine Wu 已提交
63
		type: 'item',
P
Pine Wu 已提交
64
		label: localize('trustedDomain.trustDomain', 'Trust {0}', domainToConfigure),
65 66
		id: 'trust',
		toTrust: domainToConfigure,
P
Pine Wu 已提交
67
		picked: true
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
	});

	const isIP =
		toplevelDomainSegements.length === 4 &&
		toplevelDomainSegements.every(segment =>
			Number.isInteger(+segment) || Number.isInteger(+segment.split(':')[0]));

	if (isIP) {
		if (parsedDomainToConfigure.authority.includes(':')) {
			const base = parsedDomainToConfigure.authority.split(':')[0];
			options.push({
				type: 'item',
				label: localize('trustedDomain.trustAllPorts', 'Trust {0} on all ports', base),
				toTrust: base + ':*',
				id: 'trust'
			});
		}
	} else {
		options.push({
			type: 'item',
			label: localize('trustedDomain.trustSubDomain', 'Trust {0} and all its subdomains', domainEnd),
			toTrust: topLevelDomain,
			id: 'trust'
		});
	}

	options.push({
P
Pine Wu 已提交
95
		type: 'item',
P
Pine Wu 已提交
96
		label: localize('trustedDomain.trustAllDomains', 'Trust all domains (disables link protection)'),
97 98 99 100
		toTrust: '*',
		id: 'trust'
	});
	options.push({
P
Pine Wu 已提交
101
		type: 'item',
P
Pine Wu 已提交
102 103
		label: localize('trustedDomain.manageTrustedDomains', 'Manage Trusted Domains'),
		id: 'manage'
104
	});
P
Pine Wu 已提交
105

P
Pine Wu 已提交
106
	const pickedResult = await quickInputService.pick<ConfigureTrustedDomainsQuickPickItem>(
107
		options, { activeItem: options[0] }
P
polish  
Pine Wu 已提交
108
	);
P
Pine Wu 已提交
109

P
Pine Wu 已提交
110 111 112 113 114 115 116 117
	if (pickedResult && pickedResult.id) {
		telemetryService.publicLog2<{ choice: string }, ConfigureTrustedDomainsChoiceClassification>(
			'trustedDomains.configureTrustedDomainsQuickPickChoice',
			{ choice: pickedResult.id }
		);

		switch (pickedResult.id) {
			case 'manage':
118
				await editorService.openEditor({
P
Pine Wu 已提交
119
					resource: TRUSTED_DOMAINS_URI,
120 121
					mode: 'jsonc',
					options: { pinned: true }
P
Pine Wu 已提交
122
				});
123 124
				notificationService.prompt(Severity.Info, localize('configuringURL', "Configuring trust for: {0}", resource.toString()),
					[{ label: 'Copy', run: () => clipboardService.writeText(resource.toString()) }]);
P
Pine Wu 已提交
125
				return trustedDomains;
126 127
			case 'trust':
				const itemToTrust = pickedResult.toTrust;
P
Pine Wu 已提交
128
				if (trustedDomains.indexOf(itemToTrust) === -1) {
P
Pine Wu 已提交
129
					storageService.remove(TRUSTED_DOMAINS_CONTENT_STORAGE_KEY, StorageScope.GLOBAL);
P
Pine Wu 已提交
130
					storageService.store(
P
Pine Wu 已提交
131
						TRUSTED_DOMAINS_STORAGE_KEY,
P
Pine Wu 已提交
132 133 134
						JSON.stringify([...trustedDomains, itemToTrust]),
						StorageScope.GLOBAL
					);
P
Pine Wu 已提交
135

136
					return [...trustedDomains, itemToTrust];
P
Pine Wu 已提交
137
				}
P
Pine Wu 已提交
138 139 140 141 142
		}
	}

	return [];
}
P
Pine Wu 已提交
143

J
Jackson Kearl 已提交
144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
// Exported for testing.
export function extractGitHubRemotesFromGitConfig(gitConfig: string): string[] {
	const domains = new Set<string>();
	let match: RegExpExecArray | null;

	const RemoteMatcher = /^\s*url\s*=\s*(?:git@|https:\/\/)github\.com(?::|\/)(\S*)\s*$/mg;
	while (match = RemoteMatcher.exec(gitConfig)) {
		const repo = match[1].replace(/\.git$/, '');
		if (repo) {
			domains.add(`https://github.com/${repo}/`);
		}
	}
	return [...domains];
}

159 160
async function getRemotes(fileService: IFileService, textFileService: ITextFileService, contextService: IWorkspaceContextService): Promise<string[]> {
	const workspaceUris = contextService.getWorkspace().folders.map(folder => folder.uri);
161 162 163 164 165 166 167 168 169 170 171 172
	const domains = await Promise.race([
		new Promise<string[][]>(resolve => setTimeout(() => resolve([]), 2000)),
		Promise.all<string[]>(workspaceUris.map(async workspaceUri => {
			const path = workspaceUri.path;
			const uri = workspaceUri.with({ path: `${path !== '/' ? path : ''}/.git/config` });
			const exists = await fileService.exists(uri);
			if (!exists) {
				return [];
			}
			const gitConfig = (await (textFileService.read(uri, { acceptTextOnly: true }).catch(() => ({ value: '' })))).value;
			return extractGitHubRemotesFromGitConfig(gitConfig);
		}))]);
173 174 175 176 177

	const set = domains.reduce((set, list) => list.reduce((set, item) => set.add(item), set), new Set<string>());
	return [...set];
}

178 179 180 181
export interface IStaticTrustedDomains {
	readonly defaultTrustedDomains: string[];
	readonly trustedDomains: string[];
}
182

183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199
export interface ITrustedDomains extends IStaticTrustedDomains {
	readonly userDomains: string[];
	readonly workspaceDomains: string[];
}

export async function readTrustedDomains(accessor: ServicesAccessor): Promise<ITrustedDomains> {
	const { defaultTrustedDomains, trustedDomains } = readStaticTrustedDomains(accessor);
	const [workspaceDomains, userDomains] = await Promise.all([readWorkspaceTrustedDomains(accessor), readAuthenticationTrustedDomains(accessor)]);
	return {
		workspaceDomains,
		userDomains,
		defaultTrustedDomains,
		trustedDomains,
	};
}

export async function readWorkspaceTrustedDomains(accessor: ServicesAccessor): Promise<string[]> {
200 201 202
	const fileService = accessor.get(IFileService);
	const textFileService = accessor.get(ITextFileService);
	const workspaceContextService = accessor.get(IWorkspaceContextService);
203 204 205 206 207 208 209 210 211 212 213 214 215
	return getRemotes(fileService, textFileService, workspaceContextService);
}

export async function readAuthenticationTrustedDomains(accessor: ServicesAccessor): Promise<string[]> {
	const authenticationService = accessor.get(IAuthenticationService);
	return authenticationService.isAuthenticationProviderRegistered('github') && ((await authenticationService.getSessions('github')) ?? []).length > 0
		? [`https://github.com`]
		: [];
}

export function readStaticTrustedDomains(accessor: ServicesAccessor): IStaticTrustedDomains {
	const storageService = accessor.get(IStorageService);
	const productService = accessor.get(IProductService);
216

P
Pine Wu 已提交
217
	const defaultTrustedDomains: string[] = productService.linkProtectionTrustedDomains
P
Pine Wu 已提交
218 219 220
		? [...productService.linkProtectionTrustedDomains]
		: [];

P
Pine Wu 已提交
221
	let trustedDomains: string[] = [];
P
Pine Wu 已提交
222
	try {
P
Pine Wu 已提交
223
		const trustedDomainsSrc = storageService.get(TRUSTED_DOMAINS_STORAGE_KEY, StorageScope.GLOBAL);
P
Pine Wu 已提交
224 225 226 227 228
		if (trustedDomainsSrc) {
			trustedDomains = JSON.parse(trustedDomainsSrc);
		}
	} catch (err) { }

P
Pine Wu 已提交
229 230
	return {
		defaultTrustedDomains,
231
		trustedDomains,
P
Pine Wu 已提交
232
	};
P
Pine Wu 已提交
233
}