Delete both cache files on decryption failure (#23291)

This commit is contained in:
Cheena Malhotra
2023-06-01 15:49:29 -07:00
committed by GitHub
parent 8d36400c2f
commit d3c996dc5c
3 changed files with 47 additions and 17 deletions

View File

@@ -870,10 +870,15 @@ export abstract class AzureAuth implements vscode.Disposable {
protected toBase64UrlEncoding(base64string: string): string { protected toBase64UrlEncoding(base64string: string): string {
return base64string.replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_'); // Need to use base64url encoding return base64string.replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_'); // Need to use base64url encoding
} }
public async deleteAllCacheMsal(): Promise<void> { public async deleteAllCacheMsal(): Promise<void> {
this.clientApplication.clearCache(); this.clientApplication.clearCache();
await this.msalCacheProvider.clearLocalCache();
// unlink both cache files
await this.msalCacheProvider.unlinkMsalCache();
await this.msalCacheProvider.unlinkLocalCache();
} }
public async deleteAllCacheAdal(): Promise<void> { public async deleteAllCacheAdal(): Promise<void> {
const results = await this.tokenCache.findCredentials(''); const results = await this.tokenCache.findCredentials('');

View File

@@ -152,7 +152,9 @@ export class AzureAccountProvider implements azdata.AccountProvider, vscode.Disp
try { try {
// Fetch cached token from local cache if token is available and valid. // Fetch cached token from local cache if token is available and valid.
let accessToken = await this.msalCacheProvider.getTokenFromLocalCache(account.key.accountId, tenantId, resource); let accessToken = await this.msalCacheProvider.getTokenFromLocalCache(account.key.accountId, tenantId, resource);
if (this.isValidToken(accessToken)) { if (this.isValidToken(accessToken) &&
// Ensure MSAL Cache contains user account
(await this.clientApplication.getAllAccounts()).find((accountInfo) => accountInfo.homeAccountId === account.key.accountId)) {
return accessToken; return accessToken;
} // else fallback to fetching a new token. } // else fallback to fetching a new token.
} catch (e) { } catch (e) {

View File

@@ -65,7 +65,7 @@ export class MsalCachePluginProvider {
public getCachePlugin(): ICachePlugin { public getCachePlugin(): ICachePlugin {
const beforeCacheAccess = async (cacheContext: TokenCacheContext): Promise<void> => { const beforeCacheAccess = async (cacheContext: TokenCacheContext): Promise<void> => {
try { try {
const decryptedData = await this.readCache(this._msalCacheConfiguration); const decryptedData = await this.readCache(this._msalCacheConfiguration, this._localCacheConfiguration);
cacheContext.tokenCache.deserialize(decryptedData); cacheContext.tokenCache.deserialize(decryptedData);
} catch (e) { } catch (e) {
// Handle deserialization error in cache file in case file gets corrupted. // Handle deserialization error in cache file in case file gets corrupted.
@@ -101,7 +101,7 @@ export class MsalCachePluginProvider {
* @returns Access Token. * @returns Access Token.
*/ */
public async getTokenFromLocalCache(accountId: string, tenantId: string, resource: azdata.AzureResource): Promise<Token | undefined> { public async getTokenFromLocalCache(accountId: string, tenantId: string, resource: azdata.AzureResource): Promise<Token | undefined> {
let cache = JSON.parse(await this.readCache(this._localCacheConfiguration)) as LocalAccountCache; let cache = JSON.parse(await this.readCache(this._localCacheConfiguration, this._msalCacheConfiguration)) as LocalAccountCache;
let token = cache?.tokens?.find(token => ( let token = cache?.tokens?.find(token => (
token.key === accountId && token.key === accountId &&
token.tenantId === tenantId && token.tenantId === tenantId &&
@@ -118,7 +118,7 @@ export class MsalCachePluginProvider {
let updateCount = 0; let updateCount = 0;
let indexToUpdate = -1; let indexToUpdate = -1;
let cache: LocalAccountCache; let cache: LocalAccountCache;
cache = JSON.parse(await this.readCache(this._localCacheConfiguration)) as LocalAccountCache; cache = JSON.parse(await this.readCache(this._localCacheConfiguration, this._msalCacheConfiguration)) as LocalAccountCache;
if (cache?.tokens) { if (cache?.tokens) {
cache.tokens.forEach((t, i) => { cache.tokens.forEach((t, i) => {
if (t.key === token.key && t.tenantId === token.tenantId && t.resource === token.resource if (t.key === token.key && t.tenantId === token.tenantId && t.resource === token.resource
@@ -157,7 +157,7 @@ export class MsalCachePluginProvider {
* @param accountId Account ID * @param accountId Account ID
*/ */
public async clearAccountFromLocalCache(accountId: string): Promise<void> { public async clearAccountFromLocalCache(accountId: string): Promise<void> {
let cache = JSON.parse(await this.readCache(this._localCacheConfiguration)) as LocalAccountCache; let cache = JSON.parse(await this.readCache(this._localCacheConfiguration, this._msalCacheConfiguration)) as LocalAccountCache;
let tokenIndices: number[] = []; let tokenIndices: number[] = [];
if (cache?.tokens) { if (cache?.tokens) {
cache.tokens.forEach((t, i) => { cache.tokens.forEach((t, i) => {
@@ -173,10 +173,17 @@ export class MsalCachePluginProvider {
} }
/** /**
* Clears local access token cache. * Deletes Msal access token cache file
*/ */
public async clearLocalCache(): Promise<void> { public async unlinkMsalCache(): Promise<void> {
await this.writeCache(JSON.stringify({ tokens: [] }), this._localCacheConfiguration); await fsPromises.unlink(this._msalCacheConfiguration.cacheFilePath);
}
/**
* Deletes local access token cache file.
*/
public async unlinkLocalCache(): Promise<void> {
await fsPromises.unlink(this._localCacheConfiguration.cacheFilePath);
} }
//#region Private helper methods //#region Private helper methods
@@ -194,26 +201,42 @@ export class MsalCachePluginProvider {
} }
} }
private async readCache(config: CacheConfiguration): Promise<string> { /**
config.lockTaken = await this.waitAndLock(config.lockFilePath, config.lockTaken); * Reads from an encrypted cache file based on currentConfig provided.
* @param currentConfig Currently used cache configuration.
* @param alternateConfig Alternate cache configuration for resetting needs.
* @returns Decrypted data.
*/
private async readCache(currentConfig: CacheConfiguration, alternateConfig: CacheConfiguration): Promise<string> {
currentConfig.lockTaken = await this.waitAndLock(currentConfig.lockFilePath, currentConfig.lockTaken);
try { try {
const cache = await fsPromises.readFile(config.cacheFilePath, { encoding: 'utf8' }); const cache = await fsPromises.readFile(currentConfig.cacheFilePath, { encoding: 'utf8' });
const decryptedData = await this._fileEncryptionHelper.fileOpener(cache!, true); const decryptedData = await this._fileEncryptionHelper.fileOpener(cache!, true);
return decryptedData; return decryptedData;
} catch (e) { } catch (e) {
if (e.code === 'ENOENT') { if (e.code === 'ENOENT') {
// File doesn't exist, log and continue // File doesn't exist, log and continue
Logger.verbose(`MsalCachePlugin: Cache file for '${config.name}' cache not found on disk: ${e.code}`); Logger.verbose(`MsalCachePlugin: Cache file for '${currentConfig.name}' cache not found on disk: ${e.code}`);
} }
else { else {
Logger.error(`MsalCachePlugin: Failed to read from cache file: ${e}`); Logger.error(`MsalCachePlugin: Failed to read from cache file: ${e}`);
Logger.verbose(`MsalCachePlugin: Error occurred when trying to read cache file, file will be deleted: ${e.message}`); Logger.verbose(`MsalCachePlugin: Error occurred when trying to read cache file ${currentConfig.name}, file will be deleted: ${e.message}`);
await fsPromises.unlink(config.cacheFilePath); await fsPromises.unlink(currentConfig.cacheFilePath);
// Ensure both configurations are not same.
if (currentConfig.name !== alternateConfig.name) {
// Delete alternate cache file as well.
alternateConfig.lockTaken = await this.waitAndLock(alternateConfig.lockFilePath, alternateConfig.lockTaken);
await fsPromises.unlink(alternateConfig.cacheFilePath);
lockFile.unlockSync(alternateConfig.lockFilePath);
alternateConfig.lockTaken = false;
Logger.verbose(`MsalCachePlugin: Cache file for ${alternateConfig.name} cache also deleted.`);
}
} }
return '{}'; // Return empty json string if cache not read. return '{}'; // Return empty json string if cache not read.
} finally { } finally {
lockFile.unlockSync(config.lockFilePath); lockFile.unlockSync(currentConfig.lockFilePath);
config.lockTaken = false; currentConfig.lockTaken = false;
} }
} }