Implement a no sync rule (#7216)

* implement a no sync rule

* fix linting disable

* fix unused imports

* exclude more testing

* clean up fs usage

* clean up more fs usage

* remove duplicate of code

* fix compile errors
This commit is contained in:
Anthony Dresser
2019-09-17 13:32:42 -07:00
committed by GitHub
parent 4d62983680
commit 28d453fced
31 changed files with 305 additions and 201 deletions

View File

@@ -6,7 +6,7 @@
import * as adal from 'adal-node';
import * as azdata from 'azdata';
import * as crypto from 'crypto';
import * as fs from 'fs';
import { promises as fs } from 'fs';
export default class TokenCache implements adal.TokenCache {
private static CipherAlgorithm = 'aes-256-cbc';
@@ -58,23 +58,19 @@ export default class TokenCache implements adal.TokenCache {
});
}
public clear(): Thenable<void> {
let self = this;
public async clear(): Promise<void> {
// 1) Delete encrypted serialization file
// If we got an 'ENOENT' response, the file doesn't exist, which is fine
// 3) Delete the encryption key
return new Promise<void>((resolve, reject) => {
fs.unlink(self._cacheSerializationPath, err => {
if (err && err.code !== 'ENOENT') {
reject(err);
} else {
resolve();
}
});
})
.then(() => { return self._credentialProvider.deleteCredential(self._credentialServiceKey); })
.then(() => { });
try {
await fs.unlink(this._cacheSerializationPath);
} catch (err) {
if (err.code !== 'ENOENT') {
throw err;
}
}
await this._credentialProvider.deleteCredential(this._credentialServiceKey);
}
public find(query: any, callback: (error: Error, results: any[]) => void): void {
@@ -230,7 +226,7 @@ export default class TokenCache implements adal.TokenCache {
});
}
private readCache(): Thenable<adal.TokenResponse[]> {
private async readCache(): Promise<adal.TokenResponse[]> {
let self = this;
// NOTE: File system operations are performed synchronously to avoid annoying nested callbacks
@@ -239,13 +235,13 @@ export default class TokenCache implements adal.TokenCache {
// 3) Decrypt the file contents
// 4) Deserialize and return
return this.getOrCreateEncryptionParams()
.then(encryptionParams => {
.then(async encryptionParams => {
try {
return self.decryptCache('utf8', encryptionParams);
} catch (e) {
try {
// try to parse using 'binary' encoding and rewrite cache as UTF8
let response = self.decryptCache('binary', encryptionParams);
let response = await self.decryptCache('binary', encryptionParams);
self.writeCache(response);
return response;
} catch (e) {
@@ -260,17 +256,17 @@ export default class TokenCache implements adal.TokenCache {
});
}
private decryptCache(encoding: crypto.Utf8AsciiBinaryEncoding, encryptionParams: EncryptionParams): adal.TokenResponse[] {
let cacheCipher = fs.readFileSync(this._cacheSerializationPath, TokenCache.FsOptions);
private async decryptCache(encoding: crypto.Utf8AsciiBinaryEncoding, encryptionParams: EncryptionParams): Promise<adal.TokenResponse[]> {
let cacheCipher = await fs.readFile(this._cacheSerializationPath, TokenCache.FsOptions);
let decipher = crypto.createDecipheriv(TokenCache.CipherAlgorithm, encryptionParams.key, encryptionParams.initializationVector);
let cacheJson = decipher.update(cacheCipher, 'hex', encoding);
let cacheJson = decipher.update(cacheCipher.toString(), 'hex', encoding);
cacheJson += decipher.final(encoding);
// Deserialize the JSON into the array of tokens
let cacheObj = <adal.TokenResponse[]>JSON.parse(cacheJson);
for (let objIndex in cacheObj) {
for (const obj of cacheObj) {
// Rehydrate Date objects since they will always serialize as a string
cacheObj[objIndex].expiresOn = new Date(<string>cacheObj[objIndex].expiresOn);
obj.expiresOn = new Date(<string>obj.expiresOn);
}
return cacheObj;
@@ -297,7 +293,7 @@ export default class TokenCache implements adal.TokenCache {
// 4) Encrypt the JSON
// 3) Write to the file
return this.getOrCreateEncryptionParams()
.then(encryptionParams => {
.then(async encryptionParams => {
try {
let cacheJson = JSON.stringify(cache);
@@ -305,7 +301,7 @@ export default class TokenCache implements adal.TokenCache {
let cacheCipher = cipher.update(cacheJson, 'utf8', 'hex');
cacheCipher += cipher.final('hex');
fs.writeFileSync(self._cacheSerializationPath, cacheCipher, TokenCache.FsOptions);
await fs.writeFile(self._cacheSerializationPath, cacheCipher, TokenCache.FsOptions);
} catch (e) {
throw e;
}

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import * as fs from 'fs';
import { promises as fs } from 'fs';
import * as path from 'path';
import * as os from 'os';
import * as constants from './constants';
@@ -54,12 +54,12 @@ function pushDisposable(disposable: vscode.Disposable): void {
// this method is called when your extension is activated
// your extension is activated the very first time the command is executed
export function activate(context: vscode.ExtensionContext) {
export async function activate(context: vscode.ExtensionContext) {
extensionContext = context;
const apiWrapper = new ApiWrapper();
let appContext = new AppContext(extensionContext, apiWrapper);
let storagePath = findOrMakeStoragePath();
let storagePath = await findOrMakeStoragePath();
if (!storagePath) {
return undefined;
}
@@ -82,12 +82,21 @@ export function activate(context: vscode.ExtensionContext) {
};
}
async function exists(path: string): Promise<boolean> {
try {
await fs.access(path);
return true;
} catch (e) {
return false;
}
}
// Create the folder for storing the token caches
function findOrMakeStoragePath() {
async function findOrMakeStoragePath() {
let storagePath = path.join(getDefaultLogLocation(), constants.extensionName);
try {
if (!fs.existsSync(storagePath)) {
fs.mkdirSync(storagePath);
if (!(await exists(storagePath))) {
await fs.mkdir(storagePath);
console.log('Initialized Azure account extension storage.');
}
}
@@ -124,4 +133,3 @@ function registerCommands(appContext: AppContext, azureResourceTree: AzureResour
registerAzureResourceDatabaseCommands(appContext);
}