mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 18:46:40 -05:00
# Conflicts: # extensions/mssql/config.json
This commit is contained in:
@@ -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",
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 decipherIv = crypto.createDecipheriv(this._algorithm, this._keyBuffer!, this._ivBuffer!);
|
||||||
|
if (this._authLibrary === AuthLibrary.ADAL) {
|
||||||
const split = content.split('%');
|
const split = content.split('%');
|
||||||
if (split.length !== 2) {
|
if (split.length !== 2) {
|
||||||
throw new Error('File didn\'t contain the auth tag.');
|
throw new Error('File didn\'t contain the auth tag.');
|
||||||
}
|
}
|
||||||
decipherIv.setAuthTag(Buffer.from(split[1], 'hex'));
|
(decipherIv as crypto.DecipherGCM).setAuthTag(Buffer.from(split[1], this._binaryEncoding));
|
||||||
return `${decipherIv.update(split[0], 'hex', 'utf8')}${decipherIv.final('utf8')}`;
|
plaintext = split[0];
|
||||||
};
|
}
|
||||||
|
return `${decipherIv.update(plaintext, this._binaryEncoding, '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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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}`);
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|
||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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.`);
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user