Clear token cache + remove config filter (#17808)
This commit is contained in:
Родитель
dc0ff297f4
Коммит
a7b505aba8
|
@ -204,7 +204,10 @@
|
|||
<source xml:lang="en">An error occurred while removing user account: {0}</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="noAzureAccountForRemoval">
|
||||
<source xml:land="en">No Azure Account can be found for removal.</source>
|
||||
<source xml:lang="en">No Azure Account can be found for removal.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="clearedAzureTokenCache">
|
||||
<source xml:lang="en">Azure token cache cleared successfully.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="cannotConnect">
|
||||
<source xml:lang="en">Cannot connect due to expired tokens. Please re-authenticate and try again.</source>
|
||||
|
|
|
@ -104,6 +104,9 @@
|
|||
<trans-unit id="mssql.removeAadAccount">
|
||||
<source xml:lang="en">Remove Azure Account</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="mssql.clearAzureAccountTokenCache">
|
||||
<source xml:lang="en">Clear Azure acccount token cache</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="mssql.rebuildIntelliSenseCache">
|
||||
<source xml:lang="en">Refresh IntelliSense Cache</source>
|
||||
</trans-unit>
|
||||
|
|
|
@ -561,6 +561,11 @@
|
|||
"command": "mssql.removeAadAccount",
|
||||
"title": "%mssql.removeAadAccount%",
|
||||
"category": "MS SQL"
|
||||
},
|
||||
{
|
||||
"command": "mssql.clearAzureAccountTokenCache",
|
||||
"title": "%mssql.clearAzureAccountTokenCache%",
|
||||
"category": "MS SQL"
|
||||
}
|
||||
],
|
||||
"keybindings": [
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
"mssql.copyObjectName":"Copy Object Name",
|
||||
"mssql.addAadAccount":"Add Azure Account",
|
||||
"mssql.removeAadAccount":"Remove Azure Account",
|
||||
"mssql.clearAzureAccountTokenCache":"Clear Azure acccount token cache",
|
||||
"mssql.rebuildIntelliSenseCache":"Refresh IntelliSense Cache",
|
||||
"mssql.logDebugInfo":"[Optional] Log debug output to the VS Code console (Help -> Toggle Developer Tools)",
|
||||
"mssql.maxRecentConnections":"The maximum number of recently used connections to store in the connection list.",
|
||||
|
|
|
@ -70,6 +70,8 @@ export abstract class AzureController {
|
|||
|
||||
public abstract removeAccount(account: IAccount): Promise<void>;
|
||||
|
||||
public abstract clearTokenCache(): void;
|
||||
|
||||
public abstract handleAuthMapping(): void;
|
||||
|
||||
public isSqlAuthProviderEnabled(): boolean {
|
||||
|
|
|
@ -26,6 +26,9 @@ export class FileEncryptionHelper {
|
|||
this._binaryEncoding = 'base64';
|
||||
}
|
||||
|
||||
private ivCredId = `${this._fileName}-iv`;
|
||||
private keyCredId = `${this._fileName}-key`;
|
||||
|
||||
private _algorithm: string;
|
||||
private _bufferEncoding: BufferEncoding;
|
||||
private _binaryEncoding: crypto.BinaryToTextEncoding;
|
||||
|
@ -33,19 +36,15 @@ export class FileEncryptionHelper {
|
|||
private _keyBuffer: Buffer | undefined;
|
||||
|
||||
public async init(): Promise<void> {
|
||||
|
||||
const ivCredId = `${this._fileName}-iv`;
|
||||
const keyCredId = `${this._fileName}-key`;
|
||||
|
||||
const iv = await this.readEncryptionKey(ivCredId);
|
||||
const key = await this.readEncryptionKey(keyCredId);
|
||||
const iv = await this.readEncryptionKey(this.ivCredId);
|
||||
const key = await this.readEncryptionKey(this.keyCredId);
|
||||
|
||||
if (!iv || !key) {
|
||||
this._ivBuffer = crypto.randomBytes(16);
|
||||
this._keyBuffer = crypto.randomBytes(32);
|
||||
|
||||
if (!await this.saveEncryptionKey(ivCredId, this._ivBuffer.toString(this._bufferEncoding))
|
||||
|| !await this.saveEncryptionKey(keyCredId, this._keyBuffer.toString(this._bufferEncoding))) {
|
||||
if (!await this.saveEncryptionKey(this.ivCredId, this._ivBuffer.toString(this._bufferEncoding))
|
||||
|| !await this.saveEncryptionKey(this.keyCredId, this._keyBuffer.toString(this._bufferEncoding))) {
|
||||
this._logger.error(`Encryption keys could not be saved in credential store, this will cause access token persistence issues.`);
|
||||
await this.showCredSaveErrorOnWindows();
|
||||
}
|
||||
|
@ -72,13 +71,25 @@ export class FileEncryptionHelper {
|
|||
return cipherText;
|
||||
}
|
||||
|
||||
fileOpener = async (content: string): Promise<string> => {
|
||||
if (!this._keyBuffer || !this._ivBuffer) {
|
||||
await this.init();
|
||||
fileOpener = async (content: string, resetOnError?: boolean): Promise<string> => {
|
||||
try {
|
||||
if (!this._keyBuffer || !this._ivBuffer) {
|
||||
await this.init();
|
||||
}
|
||||
let plaintext = content;
|
||||
const decipherIv = crypto.createDecipheriv(this._algorithm, this._keyBuffer!, this._ivBuffer!);
|
||||
return `${decipherIv.update(plaintext, this._binaryEncoding, 'utf8')}${decipherIv.final('utf8')}`;
|
||||
} catch (ex) {
|
||||
this._logger.error(`FileEncryptionHelper: Error occurred when decrypting data, IV/KEY will be reset: ${ex}`);
|
||||
if (resetOnError) {
|
||||
// Reset IV/Keys if crypto cannot encrypt/decrypt data.
|
||||
// This could be a possible case of corruption of expected iv/key combination
|
||||
await this.clearEncryptionKeys();
|
||||
await this.init();
|
||||
}
|
||||
// Throw error so cache file can be reset to empty.
|
||||
throw new Error(`Decryption failed with error: ${ex}`);
|
||||
}
|
||||
let encryptedText = content;
|
||||
const decipherIv = crypto.createDecipheriv(this._algorithm, this._keyBuffer!, this._ivBuffer!);
|
||||
return `${decipherIv.update(encryptedText, this._binaryEncoding, 'utf8')}${decipherIv.final('utf8')}`;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -118,6 +129,17 @@ export class FileEncryptionHelper {
|
|||
return status;
|
||||
}
|
||||
|
||||
public async clearEncryptionKeys(): Promise<void> {
|
||||
await this.deleteEncryptionKey(this.ivCredId);
|
||||
await this.deleteEncryptionKey(this.keyCredId);
|
||||
this._ivBuffer = undefined;
|
||||
this._keyBuffer = undefined;
|
||||
}
|
||||
|
||||
protected async deleteEncryptionKey(credentialId: string): Promise<boolean> {
|
||||
return (await this._credentialStore.deleteCredential(credentialId));
|
||||
}
|
||||
|
||||
private async showCredSaveErrorOnWindows(): Promise<void> {
|
||||
if (os.platform() === 'win32') {
|
||||
await this._vscodeWrapper.showWarningMessageAdvanced(LocalizedConstants.msgAzureCredStoreSaveFailedError, undefined,
|
||||
|
|
|
@ -310,23 +310,6 @@ export abstract class MsalAzureAuth {
|
|||
throw new Error('Tenant did not have display name or id');
|
||||
}
|
||||
|
||||
const getTenantConfigurationSet = (): Set<string> => {
|
||||
const configuration = vscode.workspace.getConfiguration(Constants.azureTenantConfigSection);
|
||||
let values: string[] = configuration.get('filter') ?? [];
|
||||
return new Set<string>(values);
|
||||
};
|
||||
|
||||
// The user wants to ignore this tenant.
|
||||
if (getTenantConfigurationSet().has(tenant.id)) {
|
||||
this.logger.info(`Tenant ${tenant.id} found in the ignore list, authentication will not be attempted.`);
|
||||
return false;
|
||||
}
|
||||
|
||||
const updateTenantConfigurationSet = async (set: Set<string>): Promise<void> => {
|
||||
const configuration = vscode.workspace.getConfiguration('azure.tenant.config');
|
||||
await configuration.update('filter', Array.from(set), vscode.ConfigurationTarget.Global);
|
||||
};
|
||||
|
||||
interface IConsentMessageItem extends vscode.MessageItem {
|
||||
booleanResult: boolean;
|
||||
action?: (tenantId: string) => Promise<void>;
|
||||
|
@ -343,20 +326,8 @@ export abstract class MsalAzureAuth {
|
|||
booleanResult: false
|
||||
};
|
||||
|
||||
const dontAskAgainItem: IConsentMessageItem = {
|
||||
title: LocalizedConstants.azureConsentDialogIgnore,
|
||||
booleanResult: false,
|
||||
action: async (tenantId: string) => {
|
||||
let set = getTenantConfigurationSet();
|
||||
set.add(tenantId);
|
||||
await updateTenantConfigurationSet(set);
|
||||
}
|
||||
|
||||
};
|
||||
const messageBody = (tenant.id === Constants.organizationTenant.id)
|
||||
? Utils.formatString(LocalizedConstants.azureConsentDialogBody, tenant.displayName, tenant.id, settings.id)
|
||||
: Utils.formatString(LocalizedConstants.azureConsentDialogBodyAccount, settings.id);
|
||||
const result = await vscode.window.showInformationMessage(messageBody, { modal: true }, openItem, closeItem, dontAskAgainItem);
|
||||
const messageBody = Utils.formatString(LocalizedConstants.azureConsentDialogBodyAccount, settings.id);
|
||||
const result = await vscode.window.showInformationMessage(messageBody, { modal: true }, openItem, closeItem);
|
||||
|
||||
if (result?.action) {
|
||||
await result.action(tenant.id);
|
||||
|
@ -460,9 +431,6 @@ export abstract class MsalAzureAuth {
|
|||
protected toBase64UrlEncoding(base64string: string): string {
|
||||
return base64string.replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_'); // Need to use base64url encoding
|
||||
}
|
||||
public async deleteAllCache(): Promise<void> {
|
||||
this.clientApplication.clearCache();
|
||||
}
|
||||
|
||||
public async clearCredentials(account: IAccount): Promise<void> {
|
||||
try {
|
||||
|
|
|
@ -65,6 +65,14 @@ export class MsalAzureController extends AzureController {
|
|||
azureAuth.loadTokenCache();
|
||||
}
|
||||
|
||||
public async clearTokenCache(): Promise<void> {
|
||||
this.clientApplication.clearCache();
|
||||
await this._cachePluginProvider.unlinkMsalCache();
|
||||
|
||||
// Delete Encryption Keys
|
||||
await this._cachePluginProvider.clearCacheEncryptionKeys();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears old cache file that is no longer needed on system.
|
||||
*/
|
||||
|
|
|
@ -34,6 +34,17 @@ export class MsalCachePluginProvider {
|
|||
return this._msalFilePath + '.lockfile';
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes Msal access token cache file
|
||||
*/
|
||||
public async unlinkMsalCache(): Promise<void> {
|
||||
await fsPromises.unlink(this._msalFilePath);
|
||||
}
|
||||
|
||||
public async clearCacheEncryptionKeys(): Promise<void> {
|
||||
await this._fileEncryptionHelper.clearEncryptionKeys();
|
||||
}
|
||||
|
||||
public getCachePlugin(): ICachePlugin {
|
||||
const lockFilePath = this.getLockfilePath();
|
||||
const beforeCacheAccess = async (cacheContext: TokenCacheContext): Promise<void> => {
|
||||
|
@ -47,7 +58,7 @@ export class MsalCachePluginProvider {
|
|||
// Handle deserialization error in cache file in case file gets corrupted.
|
||||
// Clearing cache here will ensure account is marked stale so re-authentication can be triggered.
|
||||
this._logger.verbose(`MsalCachePlugin: Error occurred when trying to read cache file, file contents will be cleared: ${e.message}`);
|
||||
await fsPromises.writeFile(this._msalFilePath, '', { encoding: 'utf8' });
|
||||
await fsPromises.unlink(this._msalFilePath);
|
||||
}
|
||||
this._logger.verbose(`MsalCachePlugin: Token read from cache successfully.`);
|
||||
} catch (e) {
|
||||
|
@ -56,7 +67,7 @@ export class MsalCachePluginProvider {
|
|||
this._logger.verbose(`MsalCachePlugin: Cache file not found on disk: ${e.code}`);
|
||||
} else {
|
||||
this._logger.error(`MsalCachePlugin: Failed to read from cache file, file contents will be cleared : ${e}`);
|
||||
await fsPromises.writeFile(this._msalFilePath, '', { encoding: 'utf8' });
|
||||
await fsPromises.unlink(this._msalFilePath);
|
||||
}
|
||||
} finally {
|
||||
lockFile.unlockSync(lockFilePath);
|
||||
|
|
|
@ -66,6 +66,7 @@ export const cmdAzureSignInWithDeviceCode = 'azure-account.loginWithDeviceCode';
|
|||
export const cmdAzureSignInToCloud = 'azure-account.loginToCloud';
|
||||
export const cmdAadRemoveAccount = 'mssql.removeAadAccount';
|
||||
export const cmdAadAddAccount = 'mssql.addAadAccount';
|
||||
export const cmdClearAzureTokenCache = 'mssql.clearAzureAccountTokenCache';
|
||||
export const piiLogging = 'piiLogging';
|
||||
export const mssqlPiiLogging = 'mssql.piiLogging';
|
||||
export const enableSqlAuthenticationProvider = 'mssql.enableSqlAuthenticationProvider';
|
||||
|
|
|
@ -1062,5 +1062,9 @@ export default class ConnectionManager {
|
|||
this.vscodeWrapper.showInformationMessage(LocalizedConstants.noAzureAccountForRemoval);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public onClearTokenCache(): void {
|
||||
this.azureController.clearTokenCache();
|
||||
this.vscodeWrapper.showInformationMessage(LocalizedConstants.clearedAzureTokenCache);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -159,7 +159,8 @@ export default class MainController implements vscode.Disposable {
|
|||
this._event.on(Constants.cmdAadRemoveAccount, () => this.removeAadAccount(this._prompter));
|
||||
this.registerCommand(Constants.cmdAadAddAccount);
|
||||
this._event.on(Constants.cmdAadAddAccount, () => this.addAadAccount());
|
||||
|
||||
this.registerCommandWithArgs(Constants.cmdClearAzureTokenCache);
|
||||
this._event.on(Constants.cmdClearAzureTokenCache, () => this.onClearAzureTokenCache());
|
||||
this.initializeObjectExplorer();
|
||||
|
||||
this.registerCommandWithArgs(Constants.cmdConnectObjectExplorerProfile);
|
||||
|
@ -1321,4 +1322,8 @@ export default class MainController implements vscode.Disposable {
|
|||
public addAadAccount(): void {
|
||||
this.connectionManager.addAccount();
|
||||
}
|
||||
|
||||
public onClearAzureTokenCache(): void {
|
||||
this.connectionManager.onClearTokenCache();
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче