Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/services/authentication/browser/authenticationQueryService.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 } from '../../../../base/common/event.js';
7
import { Disposable } from '../../../../base/common/lifecycle.js';
8
import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';
9
import { ILogService } from '../../../../platform/log/common/log.js';
10
import { AuthenticationSessionAccount, IAuthenticationService, IAuthenticationExtensionsService, INTERNAL_AUTH_PROVIDER_PREFIX } from '../common/authentication.js';
11
import {
12
IAuthenticationQueryService,
13
IProviderQuery,
14
IAccountQuery,
15
IAccountExtensionQuery,
16
IAccountMcpServerQuery,
17
IAccountExtensionsQuery,
18
IAccountMcpServersQuery,
19
IAccountEntitiesQuery,
20
IProviderExtensionQuery,
21
IProviderMcpServerQuery,
22
IExtensionQuery,
23
IMcpServerQuery,
24
IActiveEntities,
25
IAuthenticationUsageStats,
26
IBaseQuery
27
} from '../common/authenticationQuery.js';
28
import { IAuthenticationUsageService } from './authenticationUsageService.js';
29
import { IAuthenticationMcpUsageService } from './authenticationMcpUsageService.js';
30
import { IAuthenticationAccessService } from './authenticationAccessService.js';
31
import { IAuthenticationMcpAccessService } from './authenticationMcpAccessService.js';
32
import { IAuthenticationMcpService } from './authenticationMcpService.js';
33
import { ExtensionIdentifier } from '../../../../platform/extensions/common/extensions.js';
34
35
/**
36
* Base implementation for query interfaces
37
*/
38
abstract class BaseQuery implements IBaseQuery {
39
constructor(
40
public readonly providerId: string,
41
protected readonly queryService: AuthenticationQueryService
42
) { }
43
}
44
45
/**
46
* Implementation of account-extension query operations
47
*/
48
class AccountExtensionQuery extends BaseQuery implements IAccountExtensionQuery {
49
constructor(
50
providerId: string,
51
public readonly accountName: string,
52
public readonly extensionId: string,
53
queryService: AuthenticationQueryService
54
) {
55
super(providerId, queryService);
56
}
57
58
isAccessAllowed(): boolean | undefined {
59
return this.queryService.authenticationAccessService.isAccessAllowed(this.providerId, this.accountName, this.extensionId);
60
}
61
62
setAccessAllowed(allowed: boolean, extensionName?: string): void {
63
this.queryService.authenticationAccessService.updateAllowedExtensions(
64
this.providerId,
65
this.accountName,
66
[{ id: this.extensionId, name: extensionName || this.extensionId, allowed }]
67
);
68
}
69
70
addUsage(scopes: readonly string[], extensionName: string): void {
71
this.queryService.authenticationUsageService.addAccountUsage(
72
this.providerId,
73
this.accountName,
74
scopes,
75
this.extensionId,
76
extensionName
77
);
78
}
79
80
getUsage(): {
81
readonly extensionId: string;
82
readonly extensionName: string;
83
readonly scopes: readonly string[];
84
readonly lastUsed: number;
85
}[] {
86
const allUsages = this.queryService.authenticationUsageService.readAccountUsages(this.providerId, this.accountName);
87
return allUsages
88
.filter(usage => usage.extensionId === ExtensionIdentifier.toKey(this.extensionId))
89
.map(usage => ({
90
extensionId: usage.extensionId,
91
extensionName: usage.extensionName,
92
scopes: usage.scopes || [],
93
lastUsed: usage.lastUsed
94
}));
95
}
96
97
removeUsage(): void {
98
// Get current usages, filter out this extension, and store the rest
99
const allUsages = this.queryService.authenticationUsageService.readAccountUsages(this.providerId, this.accountName);
100
const filteredUsages = allUsages.filter(usage => usage.extensionId !== this.extensionId);
101
102
// Clear all usages and re-add the filtered ones
103
this.queryService.authenticationUsageService.removeAccountUsage(this.providerId, this.accountName);
104
for (const usage of filteredUsages) {
105
this.queryService.authenticationUsageService.addAccountUsage(
106
this.providerId,
107
this.accountName,
108
usage.scopes || [],
109
usage.extensionId,
110
usage.extensionName
111
);
112
}
113
}
114
115
setAsPreferred(): void {
116
this.queryService.authenticationExtensionsService.updateAccountPreference(
117
this.extensionId,
118
this.providerId,
119
{ label: this.accountName, id: this.accountName }
120
);
121
}
122
123
isPreferred(): boolean {
124
const preferredAccount = this.queryService.authenticationExtensionsService.getAccountPreference(this.extensionId, this.providerId);
125
return preferredAccount === this.accountName;
126
}
127
128
isTrusted(): boolean {
129
const allowedExtensions = this.queryService.authenticationAccessService.readAllowedExtensions(this.providerId, this.accountName);
130
const extension = allowedExtensions.find(ext => ext.id === this.extensionId);
131
return extension?.trusted === true;
132
}
133
}
134
135
/**
136
* Implementation of account-MCP server query operations
137
*/
138
class AccountMcpServerQuery extends BaseQuery implements IAccountMcpServerQuery {
139
constructor(
140
providerId: string,
141
public readonly accountName: string,
142
public readonly mcpServerId: string,
143
queryService: AuthenticationQueryService
144
) {
145
super(providerId, queryService);
146
}
147
148
isAccessAllowed(): boolean | undefined {
149
return this.queryService.authenticationMcpAccessService.isAccessAllowed(this.providerId, this.accountName, this.mcpServerId);
150
}
151
152
setAccessAllowed(allowed: boolean, mcpServerName?: string): void {
153
this.queryService.authenticationMcpAccessService.updateAllowedMcpServers(
154
this.providerId,
155
this.accountName,
156
[{ id: this.mcpServerId, name: mcpServerName || this.mcpServerId, allowed }]
157
);
158
}
159
160
addUsage(scopes: readonly string[], mcpServerName: string): void {
161
this.queryService.authenticationMcpUsageService.addAccountUsage(
162
this.providerId,
163
this.accountName,
164
scopes,
165
this.mcpServerId,
166
mcpServerName
167
);
168
}
169
170
getUsage(): {
171
readonly mcpServerId: string;
172
readonly mcpServerName: string;
173
readonly scopes: readonly string[];
174
readonly lastUsed: number;
175
}[] {
176
const allUsages = this.queryService.authenticationMcpUsageService.readAccountUsages(this.providerId, this.accountName);
177
return allUsages
178
.filter(usage => usage.mcpServerId === this.mcpServerId)
179
.map(usage => ({
180
mcpServerId: usage.mcpServerId,
181
mcpServerName: usage.mcpServerName,
182
scopes: usage.scopes || [],
183
lastUsed: usage.lastUsed
184
}));
185
}
186
187
removeUsage(): void {
188
// Get current usages, filter out this MCP server, and store the rest
189
const allUsages = this.queryService.authenticationMcpUsageService.readAccountUsages(this.providerId, this.accountName);
190
const filteredUsages = allUsages.filter(usage => usage.mcpServerId !== this.mcpServerId);
191
192
// Clear all usages and re-add the filtered ones
193
this.queryService.authenticationMcpUsageService.removeAccountUsage(this.providerId, this.accountName);
194
for (const usage of filteredUsages) {
195
this.queryService.authenticationMcpUsageService.addAccountUsage(
196
this.providerId,
197
this.accountName,
198
usage.scopes || [],
199
usage.mcpServerId,
200
usage.mcpServerName
201
);
202
}
203
}
204
205
setAsPreferred(): void {
206
this.queryService.authenticationMcpService.updateAccountPreference(
207
this.mcpServerId,
208
this.providerId,
209
{ label: this.accountName, id: this.accountName }
210
);
211
}
212
213
isPreferred(): boolean {
214
const preferredAccount = this.queryService.authenticationMcpService.getAccountPreference(this.mcpServerId, this.providerId);
215
return preferredAccount === this.accountName;
216
}
217
218
isTrusted(): boolean {
219
const allowedMcpServers = this.queryService.authenticationMcpAccessService.readAllowedMcpServers(this.providerId, this.accountName);
220
const mcpServer = allowedMcpServers.find(server => server.id === this.mcpServerId);
221
return mcpServer?.trusted === true;
222
}
223
}
224
225
/**
226
* Implementation of account-extensions query operations
227
*/
228
class AccountExtensionsQuery extends BaseQuery implements IAccountExtensionsQuery {
229
constructor(
230
providerId: string,
231
public readonly accountName: string,
232
queryService: AuthenticationQueryService
233
) {
234
super(providerId, queryService);
235
}
236
237
getAllowedExtensions(): { id: string; name: string; allowed?: boolean; lastUsed?: number; trusted?: boolean }[] {
238
const allowedExtensions = this.queryService.authenticationAccessService.readAllowedExtensions(this.providerId, this.accountName);
239
const usages = this.queryService.authenticationUsageService.readAccountUsages(this.providerId, this.accountName);
240
241
return allowedExtensions
242
.filter(ext => ext.allowed !== false)
243
.map(ext => {
244
// Find the most recent usage for this extension
245
const extensionUsages = usages.filter(usage => usage.extensionId === ext.id);
246
const lastUsed = extensionUsages.length > 0 ? Math.max(...extensionUsages.map(u => u.lastUsed)) : undefined;
247
248
// Check if trusted through the extension query
249
const extensionQuery = new AccountExtensionQuery(this.providerId, this.accountName, ext.id, this.queryService);
250
const trusted = extensionQuery.isTrusted();
251
252
return {
253
id: ext.id,
254
name: ext.name,
255
allowed: ext.allowed,
256
lastUsed,
257
trusted
258
};
259
});
260
}
261
262
allowAccess(extensionIds: string[]): void {
263
const extensionsToAllow = extensionIds.map(id => ({ id, name: id, allowed: true }));
264
this.queryService.authenticationAccessService.updateAllowedExtensions(this.providerId, this.accountName, extensionsToAllow);
265
}
266
267
removeAccess(extensionIds: string[]): void {
268
const extensionsToRemove = extensionIds.map(id => ({ id, name: id, allowed: false }));
269
this.queryService.authenticationAccessService.updateAllowedExtensions(this.providerId, this.accountName, extensionsToRemove);
270
}
271
272
forEach(callback: (extensionQuery: IAccountExtensionQuery) => void): void {
273
const usages = this.queryService.authenticationUsageService.readAccountUsages(this.providerId, this.accountName);
274
const allowedExtensions = this.queryService.authenticationAccessService.readAllowedExtensions(this.providerId, this.accountName);
275
276
// Combine extensions from both usage and access data
277
const extensionIds = new Set<string>();
278
usages.forEach(usage => extensionIds.add(usage.extensionId));
279
allowedExtensions.forEach(ext => extensionIds.add(ext.id));
280
281
for (const extensionId of extensionIds) {
282
const extensionQuery = new AccountExtensionQuery(this.providerId, this.accountName, extensionId, this.queryService);
283
callback(extensionQuery);
284
}
285
}
286
}
287
288
/**
289
* Implementation of account-MCP servers query operations
290
*/
291
class AccountMcpServersQuery extends BaseQuery implements IAccountMcpServersQuery {
292
constructor(
293
providerId: string,
294
public readonly accountName: string,
295
queryService: AuthenticationQueryService
296
) {
297
super(providerId, queryService);
298
}
299
300
getAllowedMcpServers(): { id: string; name: string; allowed?: boolean; lastUsed?: number; trusted?: boolean }[] {
301
return this.queryService.authenticationMcpAccessService.readAllowedMcpServers(this.providerId, this.accountName)
302
.filter(server => server.allowed !== false);
303
}
304
305
allowAccess(mcpServerIds: string[]): void {
306
const mcpServersToAllow = mcpServerIds.map(id => ({ id, name: id, allowed: true }));
307
this.queryService.authenticationMcpAccessService.updateAllowedMcpServers(this.providerId, this.accountName, mcpServersToAllow);
308
}
309
310
removeAccess(mcpServerIds: string[]): void {
311
const mcpServersToRemove = mcpServerIds.map(id => ({ id, name: id, allowed: false }));
312
this.queryService.authenticationMcpAccessService.updateAllowedMcpServers(this.providerId, this.accountName, mcpServersToRemove);
313
}
314
315
forEach(callback: (mcpServerQuery: IAccountMcpServerQuery) => void): void {
316
const usages = this.queryService.authenticationMcpUsageService.readAccountUsages(this.providerId, this.accountName);
317
const allowedMcpServers = this.queryService.authenticationMcpAccessService.readAllowedMcpServers(this.providerId, this.accountName);
318
319
// Combine MCP servers from both usage and access data
320
const mcpServerIds = new Set<string>();
321
usages.forEach(usage => mcpServerIds.add(usage.mcpServerId));
322
allowedMcpServers.forEach(server => mcpServerIds.add(server.id));
323
324
for (const mcpServerId of mcpServerIds) {
325
const mcpServerQuery = new AccountMcpServerQuery(this.providerId, this.accountName, mcpServerId, this.queryService);
326
callback(mcpServerQuery);
327
}
328
}
329
}
330
331
/**
332
* Implementation of account-entities query operations for type-agnostic operations
333
*/
334
class AccountEntitiesQuery extends BaseQuery implements IAccountEntitiesQuery {
335
constructor(
336
providerId: string,
337
public readonly accountName: string,
338
queryService: AuthenticationQueryService
339
) {
340
super(providerId, queryService);
341
}
342
343
hasAnyUsage(): boolean {
344
// Check extension usage
345
const extensionUsages = this.queryService.authenticationUsageService.readAccountUsages(this.providerId, this.accountName);
346
if (extensionUsages.length > 0) {
347
return true;
348
}
349
350
// Check MCP server usage
351
const mcpUsages = this.queryService.authenticationMcpUsageService.readAccountUsages(this.providerId, this.accountName);
352
if (mcpUsages.length > 0) {
353
return true;
354
}
355
356
// Check extension access
357
const allowedExtensions = this.queryService.authenticationAccessService.readAllowedExtensions(this.providerId, this.accountName);
358
if (allowedExtensions.some(ext => ext.allowed !== false)) {
359
return true;
360
}
361
362
// Check MCP server access
363
const allowedMcpServers = this.queryService.authenticationMcpAccessService.readAllowedMcpServers(this.providerId, this.accountName);
364
if (allowedMcpServers.some(server => server.allowed !== false)) {
365
return true;
366
}
367
368
return false;
369
}
370
371
getEntityCount(): { extensions: number; mcpServers: number; total: number } {
372
// Use the same logic as getAllEntities to count all entities with usage or access
373
const extensionUsages = this.queryService.authenticationUsageService.readAccountUsages(this.providerId, this.accountName);
374
const allowedExtensions = this.queryService.authenticationAccessService.readAllowedExtensions(this.providerId, this.accountName).filter(ext => ext.allowed);
375
const extensionIds = new Set<string>();
376
extensionUsages.forEach(usage => extensionIds.add(usage.extensionId));
377
allowedExtensions.forEach(ext => extensionIds.add(ext.id));
378
379
const mcpUsages = this.queryService.authenticationMcpUsageService.readAccountUsages(this.providerId, this.accountName);
380
const allowedMcpServers = this.queryService.authenticationMcpAccessService.readAllowedMcpServers(this.providerId, this.accountName).filter(server => server.allowed);
381
const mcpServerIds = new Set<string>();
382
mcpUsages.forEach(usage => mcpServerIds.add(usage.mcpServerId));
383
allowedMcpServers.forEach(server => mcpServerIds.add(server.id));
384
385
const extensionCount = extensionIds.size;
386
const mcpServerCount = mcpServerIds.size;
387
388
return {
389
extensions: extensionCount,
390
mcpServers: mcpServerCount,
391
total: extensionCount + mcpServerCount
392
};
393
}
394
395
removeAllAccess(): void {
396
// Remove all extension access
397
const extensionsQuery = new AccountExtensionsQuery(this.providerId, this.accountName, this.queryService);
398
const extensions = extensionsQuery.getAllowedExtensions();
399
const extensionIds = extensions.map(ext => ext.id);
400
if (extensionIds.length > 0) {
401
extensionsQuery.removeAccess(extensionIds);
402
}
403
404
// Remove all MCP server access
405
const mcpServersQuery = new AccountMcpServersQuery(this.providerId, this.accountName, this.queryService);
406
const mcpServers = mcpServersQuery.getAllowedMcpServers();
407
const mcpServerIds = mcpServers.map(server => server.id);
408
if (mcpServerIds.length > 0) {
409
mcpServersQuery.removeAccess(mcpServerIds);
410
}
411
}
412
413
forEach(callback: (entityId: string, entityType: 'extension' | 'mcpServer') => void): void {
414
// Iterate over extensions
415
const extensionsQuery = new AccountExtensionsQuery(this.providerId, this.accountName, this.queryService);
416
extensionsQuery.forEach(extensionQuery => {
417
callback(extensionQuery.extensionId, 'extension');
418
});
419
420
// Iterate over MCP servers
421
const mcpServersQuery = new AccountMcpServersQuery(this.providerId, this.accountName, this.queryService);
422
mcpServersQuery.forEach(mcpServerQuery => {
423
callback(mcpServerQuery.mcpServerId, 'mcpServer');
424
});
425
}
426
}
427
428
/**
429
* Implementation of account query operations
430
*/
431
class AccountQuery extends BaseQuery implements IAccountQuery {
432
constructor(
433
providerId: string,
434
public readonly accountName: string,
435
queryService: AuthenticationQueryService
436
) {
437
super(providerId, queryService);
438
}
439
440
extension(extensionId: string): IAccountExtensionQuery {
441
return new AccountExtensionQuery(this.providerId, this.accountName, extensionId, this.queryService);
442
}
443
444
mcpServer(mcpServerId: string): IAccountMcpServerQuery {
445
return new AccountMcpServerQuery(this.providerId, this.accountName, mcpServerId, this.queryService);
446
}
447
448
extensions(): IAccountExtensionsQuery {
449
return new AccountExtensionsQuery(this.providerId, this.accountName, this.queryService);
450
}
451
452
mcpServers(): IAccountMcpServersQuery {
453
return new AccountMcpServersQuery(this.providerId, this.accountName, this.queryService);
454
}
455
456
entities(): IAccountEntitiesQuery {
457
return new AccountEntitiesQuery(this.providerId, this.accountName, this.queryService);
458
}
459
460
remove(): void {
461
// Remove all extension access and usage data
462
this.queryService.authenticationAccessService.removeAllowedExtensions(this.providerId, this.accountName);
463
this.queryService.authenticationUsageService.removeAccountUsage(this.providerId, this.accountName);
464
465
// Remove all MCP server access and usage data
466
this.queryService.authenticationMcpAccessService.removeAllowedMcpServers(this.providerId, this.accountName);
467
this.queryService.authenticationMcpUsageService.removeAccountUsage(this.providerId, this.accountName);
468
}
469
}
470
471
/**
472
* Implementation of provider-extension query operations
473
*/
474
class ProviderExtensionQuery extends BaseQuery implements IProviderExtensionQuery {
475
constructor(
476
providerId: string,
477
public readonly extensionId: string,
478
queryService: AuthenticationQueryService
479
) {
480
super(providerId, queryService);
481
}
482
483
getPreferredAccount(): string | undefined {
484
return this.queryService.authenticationExtensionsService.getAccountPreference(this.extensionId, this.providerId);
485
}
486
487
setPreferredAccount(account: AuthenticationSessionAccount): void {
488
this.queryService.authenticationExtensionsService.updateAccountPreference(this.extensionId, this.providerId, account);
489
}
490
491
removeAccountPreference(): void {
492
this.queryService.authenticationExtensionsService.removeAccountPreference(this.extensionId, this.providerId);
493
}
494
}
495
496
/**
497
* Implementation of provider-MCP server query operations
498
*/
499
class ProviderMcpServerQuery extends BaseQuery implements IProviderMcpServerQuery {
500
constructor(
501
providerId: string,
502
public readonly mcpServerId: string,
503
queryService: AuthenticationQueryService
504
) {
505
super(providerId, queryService);
506
}
507
508
async getLastUsedAccount(): Promise<string | undefined> {
509
try {
510
const accounts = await this.queryService.authenticationService.getAccounts(this.providerId);
511
let lastUsedAccount: string | undefined;
512
let lastUsedTime = 0;
513
514
for (const account of accounts) {
515
const usages = this.queryService.authenticationMcpUsageService.readAccountUsages(this.providerId, account.label);
516
const mcpServerUsages = usages.filter(usage => usage.mcpServerId === this.mcpServerId);
517
518
for (const usage of mcpServerUsages) {
519
if (usage.lastUsed > lastUsedTime) {
520
lastUsedTime = usage.lastUsed;
521
lastUsedAccount = account.label;
522
}
523
}
524
}
525
526
return lastUsedAccount;
527
} catch {
528
return undefined;
529
}
530
}
531
532
getPreferredAccount(): string | undefined {
533
return this.queryService.authenticationMcpService.getAccountPreference(this.mcpServerId, this.providerId);
534
}
535
536
setPreferredAccount(account: AuthenticationSessionAccount): void {
537
this.queryService.authenticationMcpService.updateAccountPreference(this.mcpServerId, this.providerId, account);
538
}
539
540
removeAccountPreference(): void {
541
this.queryService.authenticationMcpService.removeAccountPreference(this.mcpServerId, this.providerId);
542
}
543
544
async getUsedAccounts(): Promise<string[]> {
545
try {
546
const accounts = await this.queryService.authenticationService.getAccounts(this.providerId);
547
const usedAccounts: string[] = [];
548
549
for (const account of accounts) {
550
const usages = this.queryService.authenticationMcpUsageService.readAccountUsages(this.providerId, account.label);
551
if (usages.some(usage => usage.mcpServerId === this.mcpServerId)) {
552
usedAccounts.push(account.label);
553
}
554
}
555
556
return usedAccounts;
557
} catch {
558
return [];
559
}
560
}
561
}
562
563
/**
564
* Implementation of provider query operations
565
*/
566
class ProviderQuery extends BaseQuery implements IProviderQuery {
567
constructor(
568
providerId: string,
569
queryService: AuthenticationQueryService
570
) {
571
super(providerId, queryService);
572
}
573
574
account(accountName: string): IAccountQuery {
575
return new AccountQuery(this.providerId, accountName, this.queryService);
576
}
577
578
extension(extensionId: string): IProviderExtensionQuery {
579
return new ProviderExtensionQuery(this.providerId, extensionId, this.queryService);
580
}
581
582
mcpServer(mcpServerId: string): IProviderMcpServerQuery {
583
return new ProviderMcpServerQuery(this.providerId, mcpServerId, this.queryService);
584
}
585
586
async getActiveEntities(): Promise<IActiveEntities> {
587
const extensions: string[] = [];
588
const mcpServers: string[] = [];
589
590
try {
591
const accounts = await this.queryService.authenticationService.getAccounts(this.providerId);
592
593
for (const account of accounts) {
594
// Get extension usages
595
const extensionUsages = this.queryService.authenticationUsageService.readAccountUsages(this.providerId, account.label);
596
for (const usage of extensionUsages) {
597
if (!extensions.includes(usage.extensionId)) {
598
extensions.push(usage.extensionId);
599
}
600
}
601
602
// Get MCP server usages
603
const mcpUsages = this.queryService.authenticationMcpUsageService.readAccountUsages(this.providerId, account.label);
604
for (const usage of mcpUsages) {
605
if (!mcpServers.includes(usage.mcpServerId)) {
606
mcpServers.push(usage.mcpServerId);
607
}
608
}
609
}
610
} catch {
611
// Return empty arrays if there's an error
612
}
613
614
return { extensions, mcpServers };
615
}
616
617
async getAccountNames(): Promise<string[]> {
618
try {
619
const accounts = await this.queryService.authenticationService.getAccounts(this.providerId);
620
return accounts.map(account => account.label);
621
} catch {
622
return [];
623
}
624
}
625
626
async getUsageStats(): Promise<IAuthenticationUsageStats> {
627
const recentActivity: { accountName: string; lastUsed: number; usageCount: number }[] = [];
628
let totalSessions = 0;
629
let totalAccounts = 0;
630
631
try {
632
const accounts = await this.queryService.authenticationService.getAccounts(this.providerId);
633
totalAccounts = accounts.length;
634
635
for (const account of accounts) {
636
const extensionUsages = this.queryService.authenticationUsageService.readAccountUsages(this.providerId, account.label);
637
const mcpUsages = this.queryService.authenticationMcpUsageService.readAccountUsages(this.providerId, account.label);
638
639
const allUsages = [...extensionUsages, ...mcpUsages];
640
const usageCount = allUsages.length;
641
const lastUsed = Math.max(...allUsages.map(u => u.lastUsed), 0);
642
643
if (usageCount > 0) {
644
recentActivity.push({ accountName: account.label, lastUsed, usageCount });
645
}
646
}
647
648
// Sort by most recent activity
649
recentActivity.sort((a, b) => b.lastUsed - a.lastUsed);
650
651
// Count total sessions (approximate)
652
totalSessions = recentActivity.reduce((sum, activity) => sum + activity.usageCount, 0);
653
} catch {
654
// Return default stats if there's an error
655
}
656
657
return { totalSessions, totalAccounts, recentActivity };
658
}
659
660
async forEachAccount(callback: (accountQuery: IAccountQuery) => void): Promise<void> {
661
try {
662
const accounts = await this.queryService.authenticationService.getAccounts(this.providerId);
663
for (const account of accounts) {
664
const accountQuery = new AccountQuery(this.providerId, account.label, this.queryService);
665
callback(accountQuery);
666
}
667
} catch {
668
// Silently handle errors in enumeration
669
}
670
}
671
}
672
673
/**
674
* Implementation of extension query operations (cross-provider)
675
*/
676
class ExtensionQuery implements IExtensionQuery {
677
constructor(
678
public readonly extensionId: string,
679
private readonly queryService: AuthenticationQueryService
680
) { }
681
682
async getProvidersWithAccess(includeInternal?: boolean): Promise<string[]> {
683
const providersWithAccess: string[] = [];
684
const providerIds = this.queryService.authenticationService.getProviderIds();
685
686
for (const providerId of providerIds) {
687
// Skip internal providers unless explicitly requested
688
if (!includeInternal && providerId.startsWith(INTERNAL_AUTH_PROVIDER_PREFIX)) {
689
continue;
690
}
691
692
try {
693
const accounts = await this.queryService.authenticationService.getAccounts(providerId);
694
const hasAccess = accounts.some(account => {
695
const accessAllowed = this.queryService.authenticationAccessService.isAccessAllowed(providerId, account.label, this.extensionId);
696
return accessAllowed === true;
697
});
698
699
if (hasAccess) {
700
providersWithAccess.push(providerId);
701
}
702
} catch {
703
// Skip providers that error
704
}
705
}
706
707
return providersWithAccess;
708
}
709
710
getAllAccountPreferences(includeInternal?: boolean): Map<string, string> {
711
const preferences = new Map<string, string>();
712
const providerIds = this.queryService.authenticationService.getProviderIds();
713
714
for (const providerId of providerIds) {
715
// Skip internal providers unless explicitly requested
716
if (!includeInternal && providerId.startsWith(INTERNAL_AUTH_PROVIDER_PREFIX)) {
717
continue;
718
}
719
720
const preferredAccount = this.queryService.authenticationExtensionsService.getAccountPreference(this.extensionId, providerId);
721
if (preferredAccount) {
722
preferences.set(providerId, preferredAccount);
723
}
724
}
725
726
return preferences;
727
}
728
729
provider(providerId: string): IProviderExtensionQuery {
730
return new ProviderExtensionQuery(providerId, this.extensionId, this.queryService);
731
}
732
}
733
734
/**
735
* Implementation of MCP server query operations (cross-provider)
736
*/
737
class McpServerQuery implements IMcpServerQuery {
738
constructor(
739
public readonly mcpServerId: string,
740
private readonly queryService: AuthenticationQueryService
741
) { }
742
743
async getProvidersWithAccess(includeInternal?: boolean): Promise<string[]> {
744
const providersWithAccess: string[] = [];
745
const providerIds = this.queryService.authenticationService.getProviderIds();
746
747
for (const providerId of providerIds) {
748
// Skip internal providers unless explicitly requested
749
if (!includeInternal && providerId.startsWith(INTERNAL_AUTH_PROVIDER_PREFIX)) {
750
continue;
751
}
752
753
try {
754
const accounts = await this.queryService.authenticationService.getAccounts(providerId);
755
const hasAccess = accounts.some(account => {
756
const accessAllowed = this.queryService.authenticationMcpAccessService.isAccessAllowed(providerId, account.label, this.mcpServerId);
757
return accessAllowed === true;
758
});
759
760
if (hasAccess) {
761
providersWithAccess.push(providerId);
762
}
763
} catch {
764
// Skip providers that error
765
}
766
}
767
768
return providersWithAccess;
769
}
770
771
getAllAccountPreferences(includeInternal?: boolean): Map<string, string> {
772
const preferences = new Map<string, string>();
773
const providerIds = this.queryService.authenticationService.getProviderIds();
774
775
for (const providerId of providerIds) {
776
// Skip internal providers unless explicitly requested
777
if (!includeInternal && providerId.startsWith(INTERNAL_AUTH_PROVIDER_PREFIX)) {
778
continue;
779
}
780
781
const preferredAccount = this.queryService.authenticationMcpService.getAccountPreference(this.mcpServerId, providerId);
782
if (preferredAccount) {
783
preferences.set(providerId, preferredAccount);
784
}
785
}
786
787
return preferences;
788
}
789
790
provider(providerId: string): IProviderMcpServerQuery {
791
return new ProviderMcpServerQuery(providerId, this.mcpServerId, this.queryService);
792
}
793
}
794
795
/**
796
* Main implementation of the authentication query service
797
*/
798
export class AuthenticationQueryService extends Disposable implements IAuthenticationQueryService {
799
declare readonly _serviceBrand: undefined;
800
801
private readonly _onDidChangePreferences = this._register(new Emitter<{
802
readonly providerId: string;
803
readonly entityType: 'extension' | 'mcpServer';
804
readonly entityIds: string[];
805
}>());
806
readonly onDidChangePreferences = this._onDidChangePreferences.event;
807
808
private readonly _onDidChangeAccess = this._register(new Emitter<{
809
readonly providerId: string;
810
readonly accountName: string;
811
}>());
812
readonly onDidChangeAccess = this._onDidChangeAccess.event;
813
814
constructor(
815
@IAuthenticationService public readonly authenticationService: IAuthenticationService,
816
@IAuthenticationUsageService public readonly authenticationUsageService: IAuthenticationUsageService,
817
@IAuthenticationMcpUsageService public readonly authenticationMcpUsageService: IAuthenticationMcpUsageService,
818
@IAuthenticationAccessService public readonly authenticationAccessService: IAuthenticationAccessService,
819
@IAuthenticationMcpAccessService public readonly authenticationMcpAccessService: IAuthenticationMcpAccessService,
820
@IAuthenticationExtensionsService public readonly authenticationExtensionsService: IAuthenticationExtensionsService,
821
@IAuthenticationMcpService public readonly authenticationMcpService: IAuthenticationMcpService,
822
@ILogService public readonly logService: ILogService
823
) {
824
super();
825
826
// Forward events from underlying services
827
this._register(this.authenticationExtensionsService.onDidChangeAccountPreference(e => {
828
this._onDidChangePreferences.fire({
829
providerId: e.providerId,
830
entityType: 'extension',
831
entityIds: e.extensionIds
832
});
833
}));
834
835
this._register(this.authenticationMcpService.onDidChangeAccountPreference(e => {
836
this._onDidChangePreferences.fire({
837
providerId: e.providerId,
838
entityType: 'mcpServer',
839
entityIds: e.mcpServerIds
840
});
841
}));
842
843
this._register(this.authenticationAccessService.onDidChangeExtensionSessionAccess(e => {
844
this._onDidChangeAccess.fire({
845
providerId: e.providerId,
846
accountName: e.accountName
847
});
848
}));
849
850
this._register(this.authenticationMcpAccessService.onDidChangeMcpSessionAccess(e => {
851
this._onDidChangeAccess.fire({
852
providerId: e.providerId,
853
accountName: e.accountName
854
});
855
}));
856
}
857
858
provider(providerId: string): IProviderQuery {
859
return new ProviderQuery(providerId, this);
860
}
861
862
extension(extensionId: string): IExtensionQuery {
863
return new ExtensionQuery(extensionId, this);
864
}
865
866
mcpServer(mcpServerId: string): IMcpServerQuery {
867
return new McpServerQuery(mcpServerId, this);
868
}
869
870
getProviderIds(includeInternal?: boolean): string[] {
871
return this.authenticationService.getProviderIds().filter(providerId => {
872
// Filter out internal providers unless explicitly included
873
return includeInternal || !providerId.startsWith(INTERNAL_AUTH_PROVIDER_PREFIX);
874
});
875
}
876
877
async clearAllData(confirmation: 'CLEAR_ALL_AUTH_DATA', includeInternal: boolean = true): Promise<void> {
878
if (confirmation !== 'CLEAR_ALL_AUTH_DATA') {
879
throw new Error('Must provide confirmation string to clear all authentication data');
880
}
881
882
const providerIds = this.getProviderIds(includeInternal);
883
884
for (const providerId of providerIds) {
885
try {
886
const accounts = await this.authenticationService.getAccounts(providerId);
887
888
for (const account of accounts) {
889
// Clear extension data
890
this.authenticationAccessService.removeAllowedExtensions(providerId, account.label);
891
this.authenticationUsageService.removeAccountUsage(providerId, account.label);
892
893
// Clear MCP server data
894
this.authenticationMcpAccessService.removeAllowedMcpServers(providerId, account.label);
895
this.authenticationMcpUsageService.removeAccountUsage(providerId, account.label);
896
}
897
} catch (error) {
898
this.logService.error(`Error clearing data for provider ${providerId}:`, error);
899
}
900
}
901
902
this.logService.info('All authentication data cleared');
903
}
904
}
905
906
registerSingleton(IAuthenticationQueryService, AuthenticationQueryService, InstantiationType.Delayed);
907
908