Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/services/authentication/browser/authenticationAccessService.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { Emitter, Event } from '../../../../base/common/event.js';
7
import { Disposable } from '../../../../base/common/lifecycle.js';
8
import { ExtensionIdentifier } from '../../../../platform/extensions/common/extensions.js';
9
import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';
10
import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
11
import { IProductService } from '../../../../platform/product/common/productService.js';
12
import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';
13
import { AllowedExtension } from '../common/authentication.js';
14
15
export const IAuthenticationAccessService = createDecorator<IAuthenticationAccessService>('IAuthenticationAccessService');
16
export interface IAuthenticationAccessService {
17
readonly _serviceBrand: undefined;
18
19
readonly onDidChangeExtensionSessionAccess: Event<{ providerId: string; accountName: string }>;
20
21
/**
22
* Check extension access to an account
23
* @param providerId The id of the authentication provider
24
* @param accountName The account name that access is checked for
25
* @param extensionId The id of the extension requesting access
26
* @returns Returns true or false if the user has opted to permanently grant or disallow access, and undefined
27
* if they haven't made a choice yet
28
*/
29
isAccessAllowed(providerId: string, accountName: string, extensionId: string): boolean | undefined;
30
readAllowedExtensions(providerId: string, accountName: string): AllowedExtension[];
31
updateAllowedExtensions(providerId: string, accountName: string, extensions: AllowedExtension[]): void;
32
removeAllowedExtensions(providerId: string, accountName: string): void;
33
}
34
35
// TODO@TylerLeonhardt: Move this class to MainThreadAuthentication
36
// TODO@TylerLeonhardt: Should this class only keep track of allowed things and throw away disallowed ones?
37
export class AuthenticationAccessService extends Disposable implements IAuthenticationAccessService {
38
_serviceBrand: undefined;
39
40
private _onDidChangeExtensionSessionAccess: Emitter<{ providerId: string; accountName: string }> = this._register(new Emitter<{ providerId: string; accountName: string }>());
41
readonly onDidChangeExtensionSessionAccess: Event<{ providerId: string; accountName: string }> = this._onDidChangeExtensionSessionAccess.event;
42
43
constructor(
44
@IStorageService private readonly _storageService: IStorageService,
45
@IProductService private readonly _productService: IProductService
46
) {
47
super();
48
}
49
50
isAccessAllowed(providerId: string, accountName: string, extensionId: string): boolean | undefined {
51
const trustedExtensionAuthAccess = this._productService.trustedExtensionAuthAccess;
52
const extensionKey = ExtensionIdentifier.toKey(extensionId);
53
if (Array.isArray(trustedExtensionAuthAccess)) {
54
if (trustedExtensionAuthAccess.includes(extensionKey)) {
55
return true;
56
}
57
} else if (trustedExtensionAuthAccess?.[providerId]?.includes(extensionKey)) {
58
return true;
59
}
60
61
const allowList = this.readAllowedExtensions(providerId, accountName);
62
const extensionData = allowList.find(extension => extension.id === extensionKey);
63
if (!extensionData) {
64
return undefined;
65
}
66
// This property didn't exist on this data previously, inclusion in the list at all indicates allowance
67
return extensionData.allowed !== undefined
68
? extensionData.allowed
69
: true;
70
}
71
72
readAllowedExtensions(providerId: string, accountName: string): AllowedExtension[] {
73
let trustedExtensions: AllowedExtension[] = [];
74
try {
75
const trustedExtensionSrc = this._storageService.get(`${providerId}-${accountName}`, StorageScope.APPLICATION);
76
if (trustedExtensionSrc) {
77
trustedExtensions = JSON.parse(trustedExtensionSrc);
78
}
79
} catch (err) { }
80
81
// Add trusted extensions from product.json if they're not already in the list
82
const trustedExtensionAuthAccess = this._productService.trustedExtensionAuthAccess;
83
const trustedExtensionIds =
84
// Case 1: trustedExtensionAuthAccess is an array
85
Array.isArray(trustedExtensionAuthAccess)
86
? trustedExtensionAuthAccess
87
// Case 2: trustedExtensionAuthAccess is an object
88
: typeof trustedExtensionAuthAccess === 'object'
89
? trustedExtensionAuthAccess[providerId] ?? []
90
: [];
91
92
for (const extensionId of trustedExtensionIds) {
93
const extensionKey = ExtensionIdentifier.toKey(extensionId);
94
const existingExtension = trustedExtensions.find(extension => extension.id === extensionKey);
95
if (!existingExtension) {
96
// Add new trusted extension (name will be set by caller if they have extension info)
97
trustedExtensions.push({
98
id: extensionKey,
99
name: extensionId, // Use original casing for display name
100
allowed: true,
101
trusted: true
102
});
103
} else {
104
// Update existing extension to be trusted
105
existingExtension.allowed = true;
106
existingExtension.trusted = true;
107
}
108
}
109
110
return trustedExtensions;
111
}
112
113
updateAllowedExtensions(providerId: string, accountName: string, extensions: AllowedExtension[]): void {
114
const allowList = this.readAllowedExtensions(providerId, accountName);
115
for (const extension of extensions) {
116
const extensionKey = ExtensionIdentifier.toKey(extension.id);
117
const index = allowList.findIndex(e => e.id === extensionKey);
118
if (index === -1) {
119
allowList.push({
120
...extension,
121
id: extensionKey
122
});
123
} else {
124
allowList[index].allowed = extension.allowed;
125
// Update name if provided and not already set to a proper name
126
if (extension.name && extension.name !== extensionKey && allowList[index].name !== extension.name) {
127
allowList[index].name = extension.name;
128
}
129
}
130
}
131
132
// Filter out trusted extensions before storing - they should only come from product.json, not user storage
133
const userManagedExtensions = allowList.filter(extension => !extension.trusted);
134
this._storageService.store(`${providerId}-${accountName}`, JSON.stringify(userManagedExtensions), StorageScope.APPLICATION, StorageTarget.USER);
135
this._onDidChangeExtensionSessionAccess.fire({ providerId, accountName });
136
}
137
138
removeAllowedExtensions(providerId: string, accountName: string): void {
139
this._storageService.remove(`${providerId}-${accountName}`, StorageScope.APPLICATION);
140
this._onDidChangeExtensionSessionAccess.fire({ providerId, accountName });
141
}
142
}
143
144
registerSingleton(IAuthenticationAccessService, AuthenticationAccessService, InstantiationType.Delayed);
145
146