MSAL cache encryption + log improvements (#22335) (#22354)

# Conflicts:
#	extensions/mssql/config.json
This commit is contained in:
Cheena Malhotra
2023-03-16 18:48:08 -07:00
committed by GitHub
parent d162d0a00f
commit d9ba75a1a5
18 changed files with 174 additions and 70 deletions

View File

@@ -353,7 +353,6 @@
"@azure/msal-node": "^1.9.0", "@azure/msal-node": "^1.9.0",
"@azure/storage-blob": "^12.6.0", "@azure/storage-blob": "^12.6.0",
"axios": "^0.27.2", "axios": "^0.27.2",
"crypto": "^1.0.1",
"lockfile": "1.0.4", "lockfile": "1.0.4",
"msal": "^1.4.16", "msal": "^1.4.16",
"node-fetch": "^2.6.7", "node-fetch": "^2.6.7",

View File

@@ -18,7 +18,7 @@ import {
import { Deferred } from '../interfaces'; import { Deferred } from '../interfaces';
import * as url from 'url'; import * as url from 'url';
import * as Constants from '../../constants'; import * as Constants from '../../constants';
import { SimpleTokenCache } from '../simpleTokenCache'; import { SimpleTokenCache } from '../utils/simpleTokenCache';
import { MemoryDatabase } from '../utils/memoryDatabase'; import { MemoryDatabase } from '../utils/memoryDatabase';
import axios, { AxiosRequestConfig, AxiosResponse } from 'axios'; import axios, { AxiosRequestConfig, AxiosResponse } from 'axios';
import { Logger } from '../../utils/Logger'; import { Logger } from '../../utils/Logger';

View File

@@ -8,7 +8,7 @@ import { AzureAccountProviderMetadata, AzureAuthType, Resource, Tenant } from 'a
import { Deferred } from '../interfaces'; import { Deferred } from '../interfaces';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import * as crypto from 'crypto'; import * as crypto from 'crypto';
import { SimpleTokenCache } from '../simpleTokenCache'; import { SimpleTokenCache } from '../utils/simpleTokenCache';
import { SimpleWebServer } from '../utils/simpleWebServer'; import { SimpleWebServer } from '../utils/simpleWebServer';
import { AzureAuthError } from './azureAuthError'; import { AzureAuthError } from './azureAuthError';
import { Logger } from '../../utils/Logger'; import { Logger } from '../../utils/Logger';

View File

@@ -21,7 +21,7 @@ import {
} from 'azurecore'; } from 'azurecore';
import { Deferred } from '../interfaces'; import { Deferred } from '../interfaces';
import { AuthenticationResult, DeviceCodeRequest, PublicClientApplication } from '@azure/msal-node'; import { AuthenticationResult, DeviceCodeRequest, PublicClientApplication } from '@azure/msal-node';
import { SimpleTokenCache } from '../simpleTokenCache'; import { SimpleTokenCache } from '../utils/simpleTokenCache';
import { Logger } from '../../utils/Logger'; import { Logger } from '../../utils/Logger';
const localize = nls.loadMessageBundle(); const localize = nls.loadMessageBundle();

View File

@@ -14,7 +14,7 @@ import {
} from 'azurecore'; } from 'azurecore';
import { Deferred } from './interfaces'; import { Deferred } from './interfaces';
import { AuthenticationResult, PublicClientApplication } from '@azure/msal-node'; import { AuthenticationResult, PublicClientApplication } from '@azure/msal-node';
import { SimpleTokenCache } from './simpleTokenCache'; import { SimpleTokenCache } from './utils/simpleTokenCache';
import { Logger } from '../utils/Logger'; import { Logger } from '../utils/Logger';
import { MultiTenantTokenResponse, Token, AzureAuth } from './auths/azureAuth'; import { MultiTenantTokenResponse, Token, AzureAuth } from './auths/azureAuth';
import { AzureAuthCodeGrant } from './auths/azureAuthCodeGrant'; import { AzureAuthCodeGrant } from './auths/azureAuthCodeGrant';
@@ -105,7 +105,7 @@ export class AzureAccountProvider implements azdata.AccountProvider, vscode.Disp
private async _initialize(storedAccounts: AzureAccount[]): Promise<AzureAccount[]> { private async _initialize(storedAccounts: AzureAccount[]): Promise<AzureAccount[]> {
const accounts: AzureAccount[] = []; const accounts: AzureAccount[] = [];
console.log(`Initializing stored accounts ${JSON.stringify(accounts)}`); Logger.verbose(`Initializing stored accounts ${JSON.stringify(accounts)}`);
const updatedAccounts = filterAccounts(storedAccounts, this.authLibrary); const updatedAccounts = filterAccounts(storedAccounts, this.authLibrary);
for (let account of updatedAccounts) { for (let account of updatedAccounts) {
const azureAuth = this.getAuthMethod(account); const azureAuth = this.getAuthMethod(account);

View File

@@ -3,11 +3,13 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import * as path from 'path';
import * as azdata from 'azdata'; import * as azdata from 'azdata';
import * as events from 'events'; import * as events from 'events';
import * as nls from 'vscode-nls'; import * as nls from 'vscode-nls';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import { SimpleTokenCache } from './simpleTokenCache'; import { promises as fsPromises } from 'fs';
import { SimpleTokenCache } from './utils/simpleTokenCache';
import providerSettings from './providerSettings'; import providerSettings from './providerSettings';
import { AzureAccountProvider as AzureAccountProvider } from './azureAccountProvider'; import { AzureAccountProvider as AzureAccountProvider } from './azureAccountProvider';
import { AzureAccountProviderMetadata } from 'azurecore'; import { AzureAccountProviderMetadata } from 'azurecore';
@@ -144,8 +146,8 @@ export class AzureAccountProviderService implements vscode.Disposable {
const isSaw: boolean = vscode.env.appName.toLowerCase().indexOf(Constants.Saw) > 0; const isSaw: boolean = vscode.env.appName.toLowerCase().indexOf(Constants.Saw) > 0;
const noSystemKeychain = vscode.workspace.getConfiguration(Constants.AzureSection).get<boolean>(Constants.NoSystemKeyChainSection); const noSystemKeychain = vscode.workspace.getConfiguration(Constants.AzureSection).get<boolean>(Constants.NoSystemKeyChainSection);
const tokenCacheKey = `azureTokenCache-${provider.metadata.id}`; const tokenCacheKey = `azureTokenCache-${provider.metadata.id}`;
// Hardcode the MSAL Cache Key so there is only one cache location const tokenCacheKeyMsal = Constants.MSALCacheName;
const tokenCacheKeyMsal = `azureTokenCacheMsal-azure_publicCloud`; await this.clearOldCacheIfExists();
try { try {
if (!this._credentialProvider) { if (!this._credentialProvider) {
throw new Error('Credential provider not registered'); throw new Error('Credential provider not registered');
@@ -156,7 +158,7 @@ export class AzureAccountProviderService implements vscode.Disposable {
await simpleTokenCache.init(); await simpleTokenCache.init();
// MSAL Cache Plugin // MSAL Cache Plugin
this._cachePluginProvider = new MsalCachePluginProvider(tokenCacheKeyMsal, this._userStoragePath); this._cachePluginProvider = new MsalCachePluginProvider(tokenCacheKeyMsal, this._userStoragePath, this._credentialProvider);
const msalConfiguration: Configuration = { const msalConfiguration: Configuration = {
auth: { auth: {
@@ -185,6 +187,22 @@ export class AzureAccountProviderService implements vscode.Disposable {
} }
} }
/**
* Clears old cache file that is no longer needed on system.
*/
private async clearOldCacheIfExists(): Promise<void> {
let filePath = path.join(this._userStoragePath, Constants.oldMsalCacheFileName);
try {
await fsPromises.access(filePath);
await fsPromises.unlink('file:' + filePath);
Logger.verbose(`Old cache file removed successfully.`);
} catch (e) {
if (e.code !== 'ENOENT') {
Logger.verbose(`Error occurred while removing old cache file: ${e}`);
} // else file doesn't exist.
}
}
private getLoggerCallback(): ILoggerCallback { private getLoggerCallback(): ILoggerCallback {
return (level: number, message: string, containsPii: boolean) => { return (level: number, message: string, containsPii: boolean) => {
if (!containsPii) { if (!containsPii) {

View File

@@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { promises as fs, constants as fsConstants } from 'fs'; import { promises as fs, constants as fsConstants } from 'fs';
import { Logger } from '../../utils/Logger';
export type ReadWriteHook = (contents: string) => Promise<string>; export type ReadWriteHook = (contents: string) => Promise<string>;
const noOpHook: ReadWriteHook = async (contents): Promise<string> => { const noOpHook: ReadWriteHook = async (contents): Promise<string> => {
@@ -97,7 +98,7 @@ export class FileDatabase {
fileContents = await fs.readFile(this.dbPath, { encoding: 'utf8' }); fileContents = await fs.readFile(this.dbPath, { encoding: 'utf8' });
fileContents = await this.readHook(fileContents); fileContents = await this.readHook(fileContents);
} catch (ex) { } catch (ex) {
console.log(`file db does not exist ${ex}`); Logger.error(`Error occurred when initializing File Database from file system cache, ADAL cache will be reset: ${ex}`);
await this.createFile(); await this.createFile();
this.db = {}; this.db = {};
this.isDirty = true; this.isDirty = true;
@@ -107,7 +108,7 @@ export class FileDatabase {
try { try {
this.db = JSON.parse(fileContents); this.db = JSON.parse(fileContents);
} catch (ex) { } catch (ex) {
console.log(`DB was corrupted, resetting it ${ex}`); Logger.error(`Error occurred when reading file database contents as JSON, ADAL cache will be reset: ${ex}`);
await this.createFile(); await this.createFile();
this.db = {}; this.db = {};
} }
@@ -139,7 +140,7 @@ export class FileDatabase {
this.isDirty = false; this.isDirty = false;
} catch (ex) { } catch (ex) {
console.log(`File saving is erroring! ${ex}`); Logger.error(`Error occurred while saving cache contents to file storage, this may cause issues with ADAL cache persistence: ${ex}`);
} finally { } finally {
this.isSaving = false; this.isSaving = false;
} }

View File

@@ -3,32 +3,50 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata'; import * as azdata from 'azdata';
import * as os from 'os';
import * as crypto from 'crypto'; import * as crypto from 'crypto';
import * as vscode from 'vscode';
import { AuthLibrary } from '../../constants';
import * as LocalizedConstants from '../../localizedConstants';
import { Logger } from '../../utils/Logger';
export class FileEncryptionHelper { export class FileEncryptionHelper {
constructor( constructor(
private _credentialService: azdata.CredentialProvider, private readonly _authLibrary: AuthLibrary,
private _fileName: string, private readonly _credentialService: azdata.CredentialProvider,
) { } protected readonly _fileName: string
) {
this._algorithm = this._authLibrary === AuthLibrary.MSAL ? 'aes-256-cbc' : 'aes-256-gcm';
this._bufferEncoding = this._authLibrary === AuthLibrary.MSAL ? 'utf16le' : 'hex';
this._binaryEncoding = this._authLibrary === AuthLibrary.MSAL ? 'base64' : 'hex';
}
private _algorithm: string;
private _bufferEncoding: BufferEncoding;
private _binaryEncoding: crypto.HexBase64BinaryEncoding;
private _ivBuffer: Buffer | undefined; private _ivBuffer: Buffer | undefined;
private _keyBuffer: Buffer | undefined; private _keyBuffer: Buffer | undefined;
async init(): Promise<void> { public async init(): Promise<void> {
const iv = await this._credentialService.readCredential(`${this._fileName}-iv`);
const key = await this._credentialService.readCredential(`${this._fileName}-key`); const ivCredId = `${this._fileName}-iv`;
if (!iv?.password || !key?.password) { const keyCredId = `${this._fileName}-key`;
const iv = await this.readEncryptionKey(ivCredId);
const key = await this.readEncryptionKey(keyCredId);
if (!iv || !key) {
this._ivBuffer = crypto.randomBytes(16); this._ivBuffer = crypto.randomBytes(16);
this._keyBuffer = crypto.randomBytes(32); this._keyBuffer = crypto.randomBytes(32);
try {
await this._credentialService.saveCredential(`${this._fileName}-iv`, this._ivBuffer.toString('hex')); if (!await this.saveEncryptionKey(ivCredId, this._ivBuffer.toString(this._bufferEncoding))
await this._credentialService.saveCredential(`${this._fileName}-key`, this._keyBuffer.toString('hex')); || !await this.saveEncryptionKey(keyCredId, this._keyBuffer.toString(this._bufferEncoding))) {
} catch (ex) { Logger.error(`Encryption keys could not be saved in credential store, this will cause access token persistence issues.`);
console.log(ex); await this.showCredSaveErrorOnWindows();
} }
} else { } else {
this._ivBuffer = Buffer.from(iv.password, 'hex'); this._ivBuffer = Buffer.from(iv, this._bufferEncoding);
this._keyBuffer = Buffer.from(key.password, 'hex'); this._keyBuffer = Buffer.from(key, this._bufferEncoding);
} }
} }
@@ -36,21 +54,68 @@ export class FileEncryptionHelper {
if (!this._keyBuffer || !this._ivBuffer) { if (!this._keyBuffer || !this._ivBuffer) {
await this.init(); await this.init();
} }
const cipherIv = crypto.createCipheriv('aes-256-gcm', this._keyBuffer!, this._ivBuffer!); const cipherIv = crypto.createCipheriv(this._algorithm, this._keyBuffer!, this._ivBuffer!);
return `${cipherIv.update(content, 'utf8', 'hex')}${cipherIv.final('hex')}%${cipherIv.getAuthTag().toString('hex')}`; let cipherText = `${cipherIv.update(content, 'utf8', this._binaryEncoding)}${cipherIv.final(this._binaryEncoding)}`;
}; if (this._authLibrary === AuthLibrary.ADAL) {
cipherText += `%${(cipherIv as crypto.CipherGCM).getAuthTag().toString(this._binaryEncoding)}`;
}
return cipherText;
}
fileOpener = async (content: string): Promise<string> => { fileOpener = async (content: string): Promise<string> => {
if (!this._keyBuffer || !this._ivBuffer) { if (!this._keyBuffer || !this._ivBuffer) {
await this.init(); await this.init();
} }
const decipherIv = crypto.createDecipheriv('aes-256-gcm', this._keyBuffer!, this._ivBuffer!); let plaintext = content;
const split = content.split('%'); const decipherIv = crypto.createDecipheriv(this._algorithm, this._keyBuffer!, this._ivBuffer!);
if (split.length !== 2) { if (this._authLibrary === AuthLibrary.ADAL) {
throw new Error('File didn\'t contain the auth tag.'); const split = content.split('%');
if (split.length !== 2) {
throw new Error('File didn\'t contain the auth tag.');
}
(decipherIv as crypto.DecipherGCM).setAuthTag(Buffer.from(split[1], this._binaryEncoding));
plaintext = split[0];
} }
decipherIv.setAuthTag(Buffer.from(split[1], 'hex')); return `${decipherIv.update(plaintext, this._binaryEncoding, 'utf8')}${decipherIv.final('utf8')}`;
return `${decipherIv.update(split[0], 'hex', 'utf8')}${decipherIv.final('utf8')}`; }
};
protected async readEncryptionKey(credentialId: string): Promise<string | undefined> {
return (await this._credentialService.readCredential(credentialId))?.password;
}
protected async saveEncryptionKey(credentialId: string, password: string): Promise<boolean> {
let status: boolean = false;
try {
await this._credentialService.saveCredential(credentialId, password)
.then((result) => {
status = result;
if (result) {
Logger.info(`FileEncryptionHelper: Successfully saved encryption key ${credentialId} for ${this._authLibrary} persistent cache encryption in system credential store.`);
}
}, (e => {
throw Error(`FileEncryptionHelper: Could not save encryption key: ${credentialId}: ${e}`);
}));
} catch (ex) {
if (os.platform() === 'win32') {
Logger.error(`FileEncryptionHelper: Please try cleaning saved credentials from Windows Credential Manager created by Azure Data Studio to allow creating new credentials.`);
}
Logger.error(ex);
throw ex;
}
return status;
}
protected async showCredSaveErrorOnWindows(): Promise<void> {
if (os.platform() === 'win32') {
await vscode.window.showWarningMessage(LocalizedConstants.azureCredStoreSaveFailedError,
LocalizedConstants.reloadChoice, LocalizedConstants.cancel)
.then(async (selection) => {
if (selection === LocalizedConstants.reloadChoice) {
await vscode.commands.executeCommand('workbench.action.reloadWindow');
}
}, error => {
Logger.error(error);
});
}
}
} }

View File

@@ -8,20 +8,23 @@ import { promises as fsPromises } from 'fs';
import * as lockFile from 'lockfile'; import * as lockFile from 'lockfile';
import * as path from 'path'; import * as path from 'path';
import { AccountsClearTokenCacheCommand } from '../../constants'; import * as azdata from 'azdata';
import { AccountsClearTokenCacheCommand, AuthLibrary } from '../../constants';
import { Logger } from '../../utils/Logger'; import { Logger } from '../../utils/Logger';
import { FileEncryptionHelper } from './fileEncryptionHelper';
export class MsalCachePluginProvider { export class MsalCachePluginProvider {
constructor( constructor(
private readonly _serviceName: string, private readonly _serviceName: string,
private readonly _msalFilePath: string, private readonly _msalFilePath: string,
private readonly _credentialService: azdata.CredentialProvider
) { ) {
this._msalFilePath = path.join(this._msalFilePath, this._serviceName); this._msalFilePath = path.join(this._msalFilePath, this._serviceName);
this._serviceName = this._serviceName.replace(/-/, '_'); this._fileEncryptionHelper = new FileEncryptionHelper(AuthLibrary.MSAL, this._credentialService, this._serviceName);
Logger.verbose(`MsalCachePluginProvider: Using cache path ${_msalFilePath} and serviceName ${_serviceName}`);
} }
private _lockTaken: boolean = false; private _lockTaken: boolean = false;
private _fileEncryptionHelper: FileEncryptionHelper;
private getLockfilePath(): string { private getLockfilePath(): string {
return this._msalFilePath + '.lockfile'; return this._msalFilePath + '.lockfile';
@@ -33,8 +36,9 @@ export class MsalCachePluginProvider {
await this.waitAndLock(lockFilePath); await this.waitAndLock(lockFilePath);
try { try {
const cache = await fsPromises.readFile(this._msalFilePath, { encoding: 'utf8' }); const cache = await fsPromises.readFile(this._msalFilePath, { encoding: 'utf8' });
const decryptedData = await this._fileEncryptionHelper.fileOpener(cache!);
try { try {
cacheContext.tokenCache.deserialize(cache); 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.
// Clearing cache here will ensure account is marked stale so re-authentication can be triggered. // Clearing cache here will ensure account is marked stale so re-authentication can be triggered.
@@ -49,7 +53,8 @@ export class MsalCachePluginProvider {
} }
else { else {
Logger.error(`MsalCachePlugin: Failed to read from cache file: ${e}`); Logger.error(`MsalCachePlugin: Failed to read from cache file: ${e}`);
throw e; 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' });
} }
} finally { } finally {
lockFile.unlockSync(lockFilePath); lockFile.unlockSync(lockFilePath);
@@ -62,7 +67,8 @@ export class MsalCachePluginProvider {
await this.waitAndLock(lockFilePath); await this.waitAndLock(lockFilePath);
try { try {
const data = cacheContext.tokenCache.serialize(); const data = cacheContext.tokenCache.serialize();
await fsPromises.writeFile(this._msalFilePath, data, { encoding: 'utf8' }); const encryptedData = await this._fileEncryptionHelper.fileSaver(data!);
await fsPromises.writeFile(this._msalFilePath, encryptedData, { encoding: 'utf8' });
Logger.verbose(`MsalCachePlugin: Token written to cache successfully.`); Logger.verbose(`MsalCachePlugin: Token written to cache successfully.`);
} catch (e) { } catch (e) {
Logger.error(`MsalCachePlugin: Failed to write to cache file. ${e}`); Logger.error(`MsalCachePlugin: Failed to write to cache file. ${e}`);

View File

@@ -4,9 +4,10 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import * as keytarType from 'keytar'; import * as keytarType from 'keytar';
import { join, parse } from 'path'; import { join, parse } from 'path';
import { FileDatabase } from './utils/fileDatabase'; import { FileDatabase } from './fileDatabase';
import * as azdata from 'azdata'; import * as azdata from 'azdata';
import { FileEncryptionHelper } from './utils/fileEncryptionHelper'; import { FileEncryptionHelper } from './fileEncryptionHelper';
import { AuthLibrary } from '../../constants';
function getSystemKeytar(): Keytar | undefined { function getSystemKeytar(): Keytar | undefined {
try { try {
@@ -25,7 +26,7 @@ const separator = '§';
async function getFileKeytar(filePath: string, credentialService: azdata.CredentialProvider): Promise<Keytar | undefined> { async function getFileKeytar(filePath: string, credentialService: azdata.CredentialProvider): Promise<Keytar | undefined> {
const fileName = parse(filePath).base; const fileName = parse(filePath).base;
const fileEncryptionHelper: FileEncryptionHelper = new FileEncryptionHelper(credentialService, fileName); const fileEncryptionHelper: FileEncryptionHelper = new FileEncryptionHelper(AuthLibrary.ADAL, credentialService, fileName);
const db = new FileDatabase(filePath, fileEncryptionHelper.fileOpener, fileEncryptionHelper.fileSaver); const db = new FileDatabase(filePath, fileEncryptionHelper.fileOpener, fileEncryptionHelper.fileSaver);
await db.initialize(); await db.initialize();

View File

@@ -5,6 +5,7 @@
import * as http from 'http'; import * as http from 'http';
import * as url from 'url'; import * as url from 'url';
import { AddressInfo } from 'net'; import { AddressInfo } from 'net';
import { Logger } from '../../utils/Logger';
export type WebHandler = (req: http.IncomingMessage, reqUrl: url.UrlWithParsedQuery, res: http.ServerResponse) => void; export type WebHandler = (req: http.IncomingMessage, reqUrl: url.UrlWithParsedQuery, res: http.ServerResponse) => void;
@@ -24,7 +25,7 @@ export class SimpleWebServer {
const time = new Date().getTime(); const time = new Date().getTime();
if (time - this.lastUsed > this.autoShutoffTimer) { if (time - this.lastUsed > this.autoShutoffTimer) {
console.log('Shutting off webserver...'); Logger.verbose('Shutting off webserver...');
this.shutdown().catch(console.error); this.shutdown().catch(console.error);
} }
}, 1000); }, 1000);

View File

@@ -12,6 +12,7 @@ import * as azureResourceUtils from './azureResource/utils';
import * as constants from './constants'; import * as constants from './constants';
import * as loc from './localizedConstants'; import * as loc from './localizedConstants';
import * as utils from './utils'; import * as utils from './utils';
import { Logger } from './utils/Logger';
const typesClause = [ const typesClause = [
azureResource.AzureResourceType.sqlDatabase, azureResource.AzureResourceType.sqlDatabase,
@@ -60,10 +61,10 @@ export class AzureDataGridProvider implements azdata.DataGridProvider {
}); });
items.push(...newItems); items.push(...newItems);
} catch (err) { } catch (err) {
console.log(err); Logger.error(err);
} }
} catch (err) { } catch (err) {
console.log(err); Logger.error(err);
} }
})); }));
})); }));

View File

@@ -10,6 +10,7 @@ import { IAzureResourceService } from '../interfaces';
import { AzureResourceErrorMessageUtil } from '../utils'; import { AzureResourceErrorMessageUtil } from '../utils';
import { ResourceGraphClient } from '@azure/arm-resourcegraph'; import { ResourceGraphClient } from '@azure/arm-resourcegraph';
import { AzureAccount, azureResource } from 'azurecore'; import { AzureAccount, azureResource } from 'azurecore';
import { Logger } from '../../utils/Logger';
export abstract class ResourceTreeDataProviderBase<T extends azureResource.AzureResource> implements azureResource.IAzureResourceTreeDataProvider { export abstract class ResourceTreeDataProviderBase<T extends azureResource.AzureResource> implements azureResource.IAzureResourceTreeDataProvider {
public browseConnectionMode: boolean = false; public browseConnectionMode: boolean = false;
@@ -32,7 +33,7 @@ export abstract class ResourceTreeDataProviderBase<T extends azureResource.Azure
treeItem: this.getTreeItemForResource(resource, element.account) treeItem: this.getTreeItemForResource(resource, element.account)
}).sort((a, b) => (<any>a.treeItem.label).localeCompare(b.treeItem.label)); }).sort((a, b) => (<any>a.treeItem.label).localeCompare(b.treeItem.label));
} catch (error) { } catch (error) {
console.log(AzureResourceErrorMessageUtil.getErrorMessage(error)); Logger.error(AzureResourceErrorMessageUtil.getErrorMessage(error));
throw error; throw error;
} }
} }
@@ -102,7 +103,7 @@ export async function queryGraphResources<T extends GraphData>(resourceClient: R
} }
} catch (err2) { } catch (err2) {
// Just log, we still want to throw the original error if something happens parsing the error // Just log, we still want to throw the original error if something happens parsing the error
console.log(`Unexpected error while parsing error from querying resources : ${err2}`); Logger.error(`Unexpected error while parsing error from querying resources : ${err2}`);
} }
throw err; throw err;
} }

View File

@@ -10,6 +10,7 @@ import * as WS from 'ws';
import { IAzureTerminalService } from '../interfaces'; import { IAzureTerminalService } from '../interfaces';
import { AzureAccount, Tenant } from 'azurecore'; import { AzureAccount, Tenant } from 'azurecore';
import { Logger } from '../../utils/Logger';
const localize = nls.loadMessageBundle(); const localize = nls.loadMessageBundle();
@@ -68,8 +69,8 @@ export class AzureTerminalService implements IAzureTerminalService {
let userSettingsResult: AxiosResponse<any>; let userSettingsResult: AxiosResponse<any>;
try { try {
userSettingsResult = await axios.get(userSettingsUri, settings); userSettingsResult = await axios.get(userSettingsUri, settings);
} catch (ex) { } catch (ex) {// Log as info as exception is handled
console.log(ex, ex.response); Logger.info(ex, ex.response);
await handleNeverUsed(); await handleNeverUsed();
return; return;
} }
@@ -85,8 +86,8 @@ export class AzureTerminalService implements IAzureTerminalService {
let provisionResult: AxiosResponse<any>; let provisionResult: AxiosResponse<any>;
try { try {
provisionResult = await axios.put(consoleRequestUri, {}, settings); provisionResult = await axios.put(consoleRequestUri, {}, settings);
} catch (ex) { } catch (ex) {// Log as info as exception is handled
console.log(ex, ex.response); Logger.info(ex, ex.response);
await handleNeverUsed(); await handleNeverUsed();
return; return;
} }
@@ -215,7 +216,7 @@ class AzureTerminal implements vscode.Pseudoterminal {
this.socket?.ping(); this.socket?.ping();
}, 5000); }, 5000);
} catch (ex) { } catch (ex) {
console.log(ex); Logger.error(ex);
} }
} }
@@ -234,7 +235,7 @@ class AzureTerminal implements vscode.Pseudoterminal {
} }
}); });
} catch (ex) { } catch (ex) {
console.log(`Error establishing terminal. ${ex}, ${ex.response}`); Logger.info(`Error establishing terminal. ${ex}, ${ex.response}`);
await handleNeverUsed(); await handleNeverUsed();
return undefined; return undefined;
} }
@@ -246,8 +247,8 @@ class AzureTerminal implements vscode.Pseudoterminal {
} }
if (!terminalUri) { if (!terminalUri) {
console.log(terminalResult); Logger.error(terminalResult);
throw new Error(terminalResult.data); throw Error(terminalResult.data);
} }
return terminalUri; return terminalUri;

View File

@@ -39,6 +39,8 @@ export const AzureTenantConfigSection = AzureSection + '.' + TenantSection + '.'
export const NoSystemKeyChainSection = 'noSystemKeychain'; export const NoSystemKeyChainSection = 'noSystemKeychain';
export const oldMsalCacheFileName = 'azureTokenCacheMsal-azure_publicCloud';
/** MSAL Account version */ /** MSAL Account version */
export const AccountVersion = '2.0'; export const AccountVersion = '2.0';
@@ -61,6 +63,8 @@ export const dataGridProviderId = 'azure-resources';
export const AzureTokenFolderName = 'Azure Accounts'; export const AzureTokenFolderName = 'Azure Accounts';
export const MSALCacheName = 'accessTokenCache';
export const DefaultAuthLibrary = 'ADAL'; export const DefaultAuthLibrary = 'ADAL';
export enum BuiltInCommands { export enum BuiltInCommands {

View File

@@ -100,7 +100,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<azurec
updatePiiLoggingLevel(); updatePiiLoggingLevel();
// Create the provider service and activate // Create the provider service and activate
initAzureAccountProvider(extensionContext, storagePath, authLibrary!).catch((err) => console.log(err)); initAzureAccountProvider(extensionContext, storagePath, authLibrary!).catch((err) => Logger.error(err));
registerAzureServices(appContext); registerAzureServices(appContext);
const azureResourceTree = new AzureResourceTreeProvider(appContext, authLibrary); const azureResourceTree = new AzureResourceTreeProvider(appContext, authLibrary);
@@ -119,7 +119,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<azurec
if (portalEndpoint && subscriptionId && resourceGroup && type && name) { if (portalEndpoint && subscriptionId && resourceGroup && type && name) {
await vscode.env.openExternal(vscode.Uri.parse(`${portalEndpoint}/#resource/subscriptions/${subscriptionId}/resourceGroups/${resourceGroup}/providers/${type}/${name}`)); await vscode.env.openExternal(vscode.Uri.parse(`${portalEndpoint}/#resource/subscriptions/${subscriptionId}/resourceGroups/${resourceGroup}/providers/${type}/${name}`));
} else { } else {
console.log(`Missing required values - subscriptionId : ${subscriptionId} resourceGroup : ${resourceGroup} type: ${type} name: ${name}`); Logger.error(`Missing required values - subscriptionId : ${subscriptionId} resourceGroup : ${resourceGroup} type: ${type} name: ${name}`);
void vscode.window.showErrorMessage(loc.unableToOpenAzureLink); void vscode.window.showErrorMessage(loc.unableToOpenAzureLink);
} }
}); });
@@ -248,7 +248,7 @@ async function findOrMakeStoragePath() {
await fs.mkdir(defaultLogLocation, { recursive: true }); await fs.mkdir(defaultLogLocation, { recursive: true });
} catch (e) { } catch (e) {
if (e.code !== 'EEXIST') { if (e.code !== 'EEXIST') {
console.log(`Creating the base directory failed... ${e}`); Logger.error(`Creating the base directory failed... ${e}`);
return undefined; return undefined;
} }
} }
@@ -257,13 +257,13 @@ async function findOrMakeStoragePath() {
await fs.mkdir(storagePath, { recursive: true }); await fs.mkdir(storagePath, { recursive: true });
} catch (e) { } catch (e) {
if (e.code !== 'EEXIST') { if (e.code !== 'EEXIST') {
console.error(`Initialization of Azure account extension storage failed: ${e}`); Logger.error(`Initialization of Azure account extension storage failed: ${e}`);
console.error('Azure accounts will not be available'); Logger.error('Azure accounts will not be available');
return undefined; return undefined;
} }
} }
console.log('Initialized Azure account extension storage.'); Logger.verbose('Initialized Azure account extension storage.');
return storagePath; return storagePath;
} }
@@ -273,7 +273,7 @@ async function initAzureAccountProvider(extensionContext: vscode.ExtensionContex
extensionContext.subscriptions.push(accountProviderService); extensionContext.subscriptions.push(accountProviderService);
await accountProviderService.activate(); await accountProviderService.activate();
} catch (err) { } catch (err) {
console.log('Unexpected error starting account provider: ' + err.message); Logger.error('Unexpected error starting account provider: ' + err.message);
} }
} }

View File

@@ -10,6 +10,7 @@ export const extensionName = localize('azurecore.extensionName', "Azure Accounts
export const requiresReload = localize('azurecore.requiresReload', "Modifying this setting requires reloading the window for all changes to take effect."); export const requiresReload = localize('azurecore.requiresReload', "Modifying this setting requires reloading the window for all changes to take effect.");
export const reload = localize('azurecore.reload', "Reload"); export const reload = localize('azurecore.reload', "Reload");
export const cancel = localize('azurecore.reload', "Cancel");
export const australiaCentral = localize('azurecore.australiacentral', "Australia Central"); export const australiaCentral = localize('azurecore.australiacentral', "Australia Central");
export const australiaCentral2 = localize('azurecore.australiacentral2', "Australia Central 2"); export const australiaCentral2 = localize('azurecore.australiacentral2', "Australia Central 2");
@@ -84,3 +85,6 @@ export const invalidTenant = localize('azurecore.invalidTenant', "Invalid tenant
export function unableToFetchTokenError(tenant: string): string { export function unableToFetchTokenError(tenant: string): string {
return localize('azurecore.unableToFetchToken', "Unable to get token for tenant {0}", tenant); return localize('azurecore.unableToFetchToken', "Unable to get token for tenant {0}", tenant);
} }
// Error Messages
export const azureCredStoreSaveFailedError = localize('azure.credStoreSaveFailedError', `Keys for token cache could not be saved in credential store, this may cause Azure access token persistence issues and connection instabilities. It's likely that SqlTools has reached credential storage limit on Windows, please clear at least 2 credentials that start with "Microsoft.SqlTools|" in Windows Credential Manager and reload.`);

View File

@@ -690,10 +690,12 @@ crypt@~0.0.1:
resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b"
integrity sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs= integrity sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=
crypto@^1.0.1: debug@3.1.0:
version "1.0.1" version "3.1.0"
resolved "https://registry.yarnpkg.com/crypto/-/crypto-1.0.1.tgz#2af1b7cad8175d24c8a1b0778255794a21803037" resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
integrity sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig== integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
dependencies:
ms "2.0.0"
debug@3.1.0: debug@3.1.0:
version "3.1.0" version "3.1.0"