Clear token cache + remove config filter (#17808)

This commit is contained in:
Cheena Malhotra 2023-09-18 10:15:47 -07:00 коммит произвёл GitHub
Родитель dc0ff297f4
Коммит a7b505aba8
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
12 изменённых файлов: 86 добавлений и 53 удалений

Просмотреть файл

@ -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();
}
}