Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/services/authentication/browser/dynamicAuthenticationProviderStorageService.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 { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';
7
import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';
8
import { IDynamicAuthenticationProviderStorageService, DynamicAuthenticationProviderInfo, DynamicAuthenticationProviderTokensChangeEvent } from '../common/dynamicAuthenticationProviderStorage.js';
9
import { ISecretStorageService } from '../../../../platform/secrets/common/secrets.js';
10
import { IAuthorizationTokenResponse, isAuthorizationTokenResponse } from '../../../../base/common/oauth.js';
11
import { ILogService } from '../../../../platform/log/common/log.js';
12
import { Emitter, Event } from '../../../../base/common/event.js';
13
import { Disposable } from '../../../../base/common/lifecycle.js';
14
import { Queue } from '../../../../base/common/async.js';
15
16
export class DynamicAuthenticationProviderStorageService extends Disposable implements IDynamicAuthenticationProviderStorageService {
17
declare readonly _serviceBrand: undefined;
18
19
private static readonly PROVIDERS_STORAGE_KEY = 'dynamicAuthProviders';
20
21
private readonly _onDidChangeTokens = this._register(new Emitter<DynamicAuthenticationProviderTokensChangeEvent>());
22
readonly onDidChangeTokens: Event<DynamicAuthenticationProviderTokensChangeEvent> = this._onDidChangeTokens.event;
23
24
constructor(
25
@IStorageService private readonly storageService: IStorageService,
26
@ISecretStorageService private readonly secretStorageService: ISecretStorageService,
27
@ILogService private readonly logService: ILogService
28
) {
29
super();
30
31
// Listen for secret storage changes and emit events for dynamic auth provider token changes
32
const queue = new Queue<void>();
33
this._register(this.secretStorageService.onDidChangeSecret(async (key: string) => {
34
let payload: { isDynamicAuthProvider: boolean; authProviderId: string; clientId: string } | undefined;
35
try {
36
payload = JSON.parse(key);
37
} catch (error) {
38
// Ignore errors... must not be a dynamic auth provider
39
}
40
if (payload?.isDynamicAuthProvider) {
41
void queue.queue(async () => {
42
const tokens = await this.getSessionsForDynamicAuthProvider(payload.authProviderId, payload.clientId);
43
this._onDidChangeTokens.fire({
44
authProviderId: payload.authProviderId,
45
clientId: payload.clientId,
46
tokens
47
});
48
});
49
}
50
}));
51
}
52
53
async getClientRegistration(providerId: string): Promise<{ clientId?: string; clientSecret?: string } | undefined> {
54
// First try new combined SecretStorage format
55
const key = `dynamicAuthProvider:clientRegistration:${providerId}`;
56
const credentialsValue = await this.secretStorageService.get(key);
57
if (credentialsValue) {
58
try {
59
const credentials = JSON.parse(credentialsValue);
60
if (credentials && (credentials.clientId || credentials.clientSecret)) {
61
return credentials;
62
}
63
} catch {
64
await this.secretStorageService.delete(key);
65
}
66
}
67
68
// Just grab the client id from the provider
69
const providers = this._getStoredProviders();
70
const provider = providers.find(p => p.providerId === providerId);
71
return provider?.clientId ? { clientId: provider.clientId } : undefined;
72
}
73
74
getClientId(providerId: string): string | undefined {
75
// For backward compatibility, try old storage format first
76
const providers = this._getStoredProviders();
77
const provider = providers.find(p => p.providerId === providerId);
78
return provider?.clientId;
79
}
80
81
async storeClientRegistration(providerId: string, authorizationServer: string, clientId: string, clientSecret?: string, label?: string): Promise<void> {
82
// Store provider information for backward compatibility and UI display
83
this._trackProvider(providerId, authorizationServer, clientId, label);
84
85
// Store both client ID and secret together in SecretStorage
86
const key = `dynamicAuthProvider:clientRegistration:${providerId}`;
87
const credentials = { clientId, clientSecret };
88
await this.secretStorageService.set(key, JSON.stringify(credentials));
89
}
90
91
private _trackProvider(providerId: string, authorizationServer: string, clientId: string, label?: string): void {
92
const providers = this._getStoredProviders();
93
94
// Check if provider already exists
95
const existingProviderIndex = providers.findIndex(p => p.providerId === providerId);
96
if (existingProviderIndex === -1) {
97
// Add new provider with provided or default info
98
const newProvider: DynamicAuthenticationProviderInfo = {
99
providerId,
100
label: label || providerId, // Use provided label or providerId as default
101
authorizationServer,
102
clientId
103
};
104
providers.push(newProvider);
105
this._storeProviders(providers);
106
} else {
107
const existingProvider = providers[existingProviderIndex];
108
// Create new provider object with updated info
109
const updatedProvider: DynamicAuthenticationProviderInfo = {
110
providerId,
111
label: label || existingProvider.label,
112
authorizationServer,
113
clientId
114
};
115
providers[existingProviderIndex] = updatedProvider;
116
this._storeProviders(providers);
117
}
118
}
119
120
private _getStoredProviders(): DynamicAuthenticationProviderInfo[] {
121
const stored = this.storageService.get(DynamicAuthenticationProviderStorageService.PROVIDERS_STORAGE_KEY, StorageScope.APPLICATION, '[]');
122
try {
123
const providerInfos = JSON.parse(stored);
124
// MIGRATION: remove after an iteration or 2
125
for (const providerInfo of providerInfos) {
126
if (!providerInfo.authorizationServer) {
127
providerInfo.authorizationServer = providerInfo.issuer;
128
}
129
}
130
return providerInfos;
131
} catch {
132
return [];
133
}
134
}
135
136
private _storeProviders(providers: DynamicAuthenticationProviderInfo[]): void {
137
this.storageService.store(
138
DynamicAuthenticationProviderStorageService.PROVIDERS_STORAGE_KEY,
139
JSON.stringify(providers),
140
StorageScope.APPLICATION,
141
StorageTarget.MACHINE
142
);
143
}
144
145
getInteractedProviders(): ReadonlyArray<DynamicAuthenticationProviderInfo> {
146
return this._getStoredProviders();
147
}
148
149
async removeDynamicProvider(providerId: string): Promise<void> {
150
// Get provider info before removal for secret cleanup
151
const providers = this._getStoredProviders();
152
const providerInfo = providers.find(p => p.providerId === providerId);
153
154
// Remove from stored providers
155
const filteredProviders = providers.filter(p => p.providerId !== providerId);
156
this._storeProviders(filteredProviders);
157
158
// Remove sessions from secret storage if we have the provider info
159
if (providerInfo) {
160
const secretKey = JSON.stringify({ isDynamicAuthProvider: true, authProviderId: providerId, clientId: providerInfo.clientId });
161
await this.secretStorageService.delete(secretKey);
162
}
163
164
// Remove client credentials from new SecretStorage format
165
const credentialsKey = `dynamicAuthProvider:clientRegistration:${providerId}`;
166
await this.secretStorageService.delete(credentialsKey);
167
}
168
169
async getSessionsForDynamicAuthProvider(authProviderId: string, clientId: string): Promise<(IAuthorizationTokenResponse & { created_at: number })[] | undefined> {
170
const key = JSON.stringify({ isDynamicAuthProvider: true, authProviderId, clientId });
171
const value = await this.secretStorageService.get(key);
172
if (value) {
173
const parsed = JSON.parse(value);
174
if (!Array.isArray(parsed) || !parsed.every((t) => typeof t.created_at === 'number' && isAuthorizationTokenResponse(t))) {
175
this.logService.error(`Invalid session data for ${authProviderId} (${clientId}) in secret storage:`, parsed);
176
await this.secretStorageService.delete(key);
177
return undefined;
178
}
179
return parsed;
180
}
181
return undefined;
182
}
183
184
async setSessionsForDynamicAuthProvider(authProviderId: string, clientId: string, sessions: (IAuthorizationTokenResponse & { created_at: number })[]): Promise<void> {
185
const key = JSON.stringify({ isDynamicAuthProvider: true, authProviderId, clientId });
186
const value = JSON.stringify(sessions);
187
await this.secretStorageService.set(key, value);
188
this.logService.trace(`Set session data for ${authProviderId} (${clientId}) in secret storage:`, sessions);
189
}
190
}
191
192
registerSingleton(IDynamicAuthenticationProviderStorageService, DynamicAuthenticationProviderStorageService, InstantiationType.Delayed);
193
194