diff --git a/src/commands/newConnection/CoreEmulatorConfiguration.ts b/src/commands/newConnection/CoreEmulatorConfiguration.ts new file mode 100644 index 00000000..ac0bce6a --- /dev/null +++ b/src/commands/newConnection/CoreEmulatorConfiguration.ts @@ -0,0 +1,11 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export interface CoreEmulatorConfiguration { + /** + * Indicates if the connection is to an emulator. + */ + isEmulator: boolean; +} diff --git a/src/commands/newConnection/MongoEmulatorConfiguration.ts b/src/commands/newConnection/MongoEmulatorConfiguration.ts new file mode 100644 index 00000000..cb6cb645 --- /dev/null +++ b/src/commands/newConnection/MongoEmulatorConfiguration.ts @@ -0,0 +1,16 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export interface MongoEmulatorConfiguration { + /** + * Indicates if the connection is to an emulator. + */ + isEmulator: boolean; + + /** + * Indicates if the emulator security should be disabled. + */ + disableEmulatorSecurity: boolean; +} diff --git a/src/commands/newConnection/MongoExecuteStep.ts b/src/commands/newConnection/MongoExecuteStep.ts index 63647e14..a08235e9 100644 --- a/src/commands/newConnection/MongoExecuteStep.ts +++ b/src/commands/newConnection/MongoExecuteStep.ts @@ -33,7 +33,7 @@ export class MongoExecuteStep extends AzureWizardExecuteStep { public priority: number = 100; @@ -55,9 +56,9 @@ export class ExecuteStep extends AzureWizardExecuteStep { @@ -53,7 +53,7 @@ export class MongoShellScriptRunner extends vscode.Disposable { const args: string[] = execArgs.slice() || []; // Snapshot since we modify it args.push(connectionString); - if (isEmulator && disableEmulatorSecurity) { + if (emulatorConfiguration.isEmulator && emulatorConfiguration.disableEmulatorSecurity) { // Without these the connection will fail due to the self-signed DocDB certificate if (args.indexOf('--tlsAllowInvalidCertificates') < 0) { args.push('--tlsAllowInvalidCertificates'); @@ -123,7 +123,7 @@ export class MongoShellScriptRunner extends vscode.Disposable { public static async createShell( context: IActionContext, - connectionInfo: { connectionString: string; isEmulator: boolean; disableEmulatorSecurity: boolean }, + connectionInfo: { connectionString: string; emulatorConfiguration: MongoEmulatorConfiguration }, ): Promise { const config = vscode.workspace.getConfiguration(); let shellPath: string | undefined = config.get(ext.settingsKeys.mongoShellPath); @@ -146,8 +146,7 @@ export class MongoShellScriptRunner extends vscode.Disposable { shellPath, shellArgs, connectionInfo.connectionString, - connectionInfo.isEmulator, - connectionInfo.disableEmulatorSecurity, + connectionInfo.emulatorConfiguration, ext.outputChannel, timeout, ); diff --git a/src/mongo/services/IConnectionParams.ts b/src/mongo/services/IConnectionParams.ts index 71877070..7cb60ccd 100644 --- a/src/mongo/services/IConnectionParams.ts +++ b/src/mongo/services/IConnectionParams.ts @@ -3,10 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { MongoEmulatorConfiguration } from '../commands/newConnection/MongoEmulatorConfiguration'; + export interface IConnectionParams { connectionString: string; databaseName: string; extensionUserAgent: string; - isEmulator: boolean; - disableEmulatorSecurity: boolean; + emulatorConfiguration: MongoEmulatorConfiguration; } diff --git a/src/mongo/services/languageService.ts b/src/mongo/services/languageService.ts index ff328461..ecbd2b5d 100644 --- a/src/mongo/services/languageService.ts +++ b/src/mongo/services/languageService.ts @@ -55,11 +55,12 @@ export class LanguageService { }); connection.onRequest('connect', (connectionParams: IConnectionParams) => { + const emulatorConfiguration = connectionParams.emulatorConfiguration; void connectToMongoClient( connectionParams.connectionString, connectionParams.extensionUserAgent, - connectionParams.isEmulator, - connectionParams.disableEmulatorSecurity, + emulatorConfiguration.isEmulator, + emulatorConfiguration.disableEmulatorSecurity, ).then((account) => { this.db = account.db(connectionParams.databaseName); void this.schemaService.registerSchemas(this.db).then((schemas) => { diff --git a/src/mongoClusters/CredentialCache.ts b/src/mongoClusters/CredentialCache.ts index 35ef5986..7bc1ac25 100644 --- a/src/mongoClusters/CredentialCache.ts +++ b/src/mongoClusters/CredentialCache.ts @@ -4,14 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import { addAuthenticationDataToConnectionString } from './utils/connectionStringHelpers'; +import { MongoEmulatorConfiguration } from '../commands/newConnection/MongoEmulatorConfiguration'; export interface MongoClustersCredentials { mongoClusterId: string; connectionStringWithPassword?: string; connectionString: string; connectionUser: string; - isEmulator?: boolean; - disableEmulatorSecurity?: boolean; + emulatorConfiguration?: MongoEmulatorConfiguration; } export class CredentialCache { @@ -26,12 +26,8 @@ export class CredentialCache { return CredentialCache._store.has(mongoClusterId) as boolean; } - public static isEmulator(mongoClusterId: string): boolean { - return CredentialCache._store.get(mongoClusterId)?.isEmulator as boolean; - } - - public static disableEmulatorSecurity(mongoClusterId: string): boolean { - return CredentialCache._store.get(mongoClusterId)?.disableEmulatorSecurity as boolean; + public static getEmulatorConfiguration(mongoClusterId: string): MongoEmulatorConfiguration | undefined { + return CredentialCache._store.get(mongoClusterId)?.emulatorConfiguration; } public static getCredentials(mongoClusterId: string): MongoClustersCredentials | undefined { @@ -42,10 +38,6 @@ export class CredentialCache { CredentialCache._store.delete(mongoClusterId); } - /** - * - * @param connectionString connection string with credentials - */ /** * Sets the credentials for a given connection string and stores them in the credential cache. * @@ -53,16 +45,14 @@ export class CredentialCache { * @param connectionString - The connection string to which the credentials will be added. * @param username - The username to be used for authentication. * @param password - The password to be used for authentication. - * @param isEmulator - Indicates whether the account is an emulator. It is optional as it's only relevant for workspace items - * @param disableEmulatorSecurity - Indicates whether the emulator security is disabled. It is optional as it's only relevant for workspace items + * @param emulatorConfiguration - The emulator configuration object. */ public static setCredentials( mongoClusterId: string, connectionString: string, username: string, password: string, - isEmulator?: boolean, - disableEmulatorSecurity?: boolean, + emulatorConfiguration?: MongoEmulatorConfiguration, ): void { const connectionStringWithPassword = addAuthenticationDataToConnectionString( connectionString, @@ -75,8 +65,7 @@ export class CredentialCache { connectionStringWithPassword: connectionStringWithPassword, connectionString: connectionString, connectionUser: username, - isEmulator: isEmulator, - disableEmulatorSecurity: disableEmulatorSecurity, + emulatorConfiguration: emulatorConfiguration, }; CredentialCache._store.set(mongoClusterId, credentials); diff --git a/src/mongoClusters/MongoClustersClient.ts b/src/mongoClusters/MongoClustersClient.ts index 84a3a6e2..d273182c 100644 --- a/src/mongoClusters/MongoClustersClient.ts +++ b/src/mongoClusters/MongoClustersClient.ts @@ -29,6 +29,7 @@ import { CredentialCache } from './CredentialCache'; import { areMongoDBAzure, getHostsFromConnectionString } from './utils/connectionStringHelpers'; import { getMongoClusterMetadata, type MongoClusterMetadata } from './utils/getMongoClusterMetadata'; import { toFilterQueryObj } from './utils/toFilterQuery'; +import { MongoEmulatorConfiguration } from '../commands/newConnection/MongoEmulatorConfiguration'; export interface DatabaseItemModel { name: string; @@ -64,8 +65,7 @@ export class MongoClustersClient { static _clients: Map = new Map(); private _mongoClient: MongoClient; - private isEmulator: boolean; - private disableEmulatorSecurity: boolean; + private emulatorConfiguration: MongoEmulatorConfiguration; /** * Use getClient instead of a constructor. Connections/Client are being cached and reused. @@ -86,8 +86,7 @@ export class MongoClustersClient { const userAgentString = areMongoDBAzure(hosts) ? appendExtensionUserAgent() : undefined; const cStringPassword = CredentialCache.getConnectionStringWithPassword(this.credentialId); - this.isEmulator = CredentialCache.isEmulator(this.credentialId); - this.disableEmulatorSecurity = CredentialCache.disableEmulatorSecurity(this.credentialId); + this.emulatorConfiguration = CredentialCache.getEmulatorConfiguration(this.credentialId); // Prepare the options object and prepare the appName // appname appears to be the correct equivalent to user-agent for mongo @@ -96,10 +95,10 @@ export class MongoClustersClient { appName: userAgentString, }; - if (this.isEmulator) { + if (this.emulatorConfiguration?.isEmulator) { mongoClientOptions.serverSelectionTimeoutMS = 4000; - if (this.disableEmulatorSecurity) { + if (this.emulatorConfiguration?.disableEmulatorSecurity) { // Prevents self signed certificate error for emulator https://github.com/microsoft/vscode-cosmosdb/issues/1241#issuecomment-614446198 mongoClientOptions.tlsAllowInvalidCertificates = true; } @@ -109,10 +108,10 @@ export class MongoClustersClient { this._mongoClient = await MongoClient.connect(cStringPassword as string, mongoClientOptions); } catch (error) { const message = parseError(error).message; - if (this.isEmulator && message.includes('ECONNREFUSED')) { + if (this.emulatorConfiguration?.isEmulator && message.includes('ECONNREFUSED')) { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access error.message = `Unable to connect to local Mongo DB emulator. Make sure it is started correctly. See ${Links.LocalConnectionDebuggingTips} for tips.`; - } else if (this.isEmulator && message.includes('self-signed certificate')) { + } else if (this.emulatorConfiguration?.isEmulator && message.includes('self-signed certificate')) { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access error.message = `The local Mongo DB emulator is using a self-signed certificate. To connect to the emulator, you must import the emulator's TLS/SSL certificate. See ${Links.LocalConnectionDebuggingTips} for tips.`; } diff --git a/src/mongoClusters/tree/MongoClusterModel.ts b/src/mongoClusters/tree/MongoClusterModel.ts index f5f72665..7687962c 100644 --- a/src/mongoClusters/tree/MongoClusterModel.ts +++ b/src/mongoClusters/tree/MongoClusterModel.ts @@ -5,6 +5,12 @@ import { type MongoCluster, type Resource } from '@azure/arm-cosmosdb'; import { type Experience } from '../../AzureDBExperiences'; +import { type MongoEmulatorConfiguration } from '../../commands/newConnection/MongoEmulatorConfiguration'; + +export interface EmulatorConfiguration { + isEmulator: boolean; + disableEmulatorConfiguration: boolean; +} // Selecting only the properties used in the extension, but keeping an easy option to extend the model later and offer full coverage of MongoCluster // '|' means that you can only access properties that are common to both types. @@ -50,15 +56,5 @@ interface ResourceModelInUse extends Resource { * We use it to filter the list of accounts when displaying them. * Also, sometimes we need to know if the account is an emulator to show/hide some UI elements. */ - isEmulator?: boolean; - - /** - * Indicates whether the emulator security should be disabled. - * - * This property is set when an account is being added to the workspace. - * - * We use it to disable the tls/ssl when connecting to the emulator, - * as it can be complicated for some users to set up the emulator correctly. - */ - disableEmulatorSecurity?: boolean; + emulatorConfiguration?: MongoEmulatorConfiguration; } diff --git a/src/mongoClusters/tree/MongoClusterResourceItem.ts b/src/mongoClusters/tree/MongoClusterResourceItem.ts index 79f94a67..5ec40d18 100644 --- a/src/mongoClusters/tree/MongoClusterResourceItem.ts +++ b/src/mongoClusters/tree/MongoClusterResourceItem.ts @@ -115,7 +115,7 @@ export class MongoClusterResourceItem extends MongoClusterItemBase { nonNullValue(clusterConnectionString), nonNullProp(wizardContext, 'selectedUserName'), nonNullProp(wizardContext, 'password'), - // here, isEmulator is not set, as it's a resource item from Azure resources, not a workspace item, therefore, no emulator support needed + // here, emulatorConfiguration is not set, as it's a resource item from Azure resources, not a workspace item, therefore, no emulator support needed ); ext.outputChannel.append( diff --git a/src/mongoClusters/tree/workspace/LocalEmulators/LocalMongoEmulatorsItem.ts b/src/mongoClusters/tree/workspace/LocalEmulators/LocalMongoEmulatorsItem.ts index 18e22166..7265ea4b 100644 --- a/src/mongoClusters/tree/workspace/LocalEmulators/LocalMongoEmulatorsItem.ts +++ b/src/mongoClusters/tree/workspace/LocalEmulators/LocalMongoEmulatorsItem.ts @@ -14,6 +14,7 @@ import { SharedWorkspaceStorage } from '../../../../tree/workspace-api/SharedWor import { type MongoClusterModel } from '../../MongoClusterModel'; import { MongoClusterWorkspaceItem } from '../MongoClusterWorkspaceItem'; import { NewMongoEmulatorConnectionItem } from './NewMongoEmulatorConnectionItem'; +import { type MongoEmulatorConfiguration } from '../../../newConnection/MongoEmulatorConfiguration'; export class LocalMongoEmulatorsItem implements CosmosDBTreeElement, TreeElementWithContextValue { public readonly id: string; @@ -29,13 +30,17 @@ export class LocalMongoEmulatorsItem implements CosmosDBTreeElement, TreeElement ...allItems .filter((item) => item.properties?.isEmulator) // only show emulators .map((item) => { + const emulatorConfiguration: MongoEmulatorConfiguration = { + isEmulator: true, + disableEmulatorSecurity: !!item.properties?.disableEmulatorSecurity, + }; + const model: MongoClusterModel = { id: item.id, name: item.name, dbExperience: MongoClustersExperience, connectionString: item?.secrets?.[0], - isEmulator: true, - disableEmulatorSecurity: !!item.properties?.disableEmulatorSecurity, + emulatorConfiguration: emulatorConfiguration, }; return new MongoClusterWorkspaceItem(model); diff --git a/src/mongoClusters/tree/workspace/MongoClusterWorkspaceItem.ts b/src/mongoClusters/tree/workspace/MongoClusterWorkspaceItem.ts index 153e3ea2..f1dfe8a3 100644 --- a/src/mongoClusters/tree/workspace/MongoClusterWorkspaceItem.ts +++ b/src/mongoClusters/tree/workspace/MongoClusterWorkspaceItem.ts @@ -22,6 +22,7 @@ import { ProvidePasswordStep } from '../../wizards/authenticate/ProvidePasswordS import { ProvideUserNameStep } from '../../wizards/authenticate/ProvideUsernameStep'; import { MongoClusterItemBase } from '../MongoClusterItemBase'; import { type MongoClusterModel } from '../MongoClusterModel'; +import { type MongoEmulatorConfiguration } from '../../newConnection/MongoEmulatorConfiguration'; import ConnectionString from 'mongodb-connection-string-url'; @@ -89,8 +90,7 @@ export class MongoClusterWorkspaceItem extends MongoClusterItemBase { connectionString.toString(), username, password, - this.mongoCluster.isEmulator, // only workspace items can potentially be connecting to an emulator - this.mongoCluster.disableEmulatorSecurity, // only workspace items can potentially be connecting to an emulator + this.mongoCluster.emulatorConfiguration, // only workspace items can potentially be connecting to an emulator ); // Attempt to create the client with the provided credentials @@ -165,9 +165,9 @@ export class MongoClusterWorkspaceItem extends MongoClusterItemBase { let description: string | undefined = undefined; let tooltipMessage: string | undefined = undefined; - if (this.mongoCluster.isEmulator) { + if (this.mongoCluster.emulatorConfiguration?.isEmulator) { // For emulator clusters, show TLS/SSL status if security is disabled - if (this.mongoCluster.disableEmulatorSecurity) { + if (this.mongoCluster.emulatorConfiguration?.disableEmulatorConfiguration) { description = '⚠ TLS/SSL Disabled'; tooltipMessage = '⚠️ **Security:** TLS/SSL Disabled'; } else { @@ -185,7 +185,7 @@ export class MongoClusterWorkspaceItem extends MongoClusterItemBase { contextValue: this.contextValue, label: this.mongoCluster.name, description: description, - iconPath: this.mongoCluster.isEmulator + iconPath: this.mongoCluster.emulatorConfiguration?.isEmulator ? new vscode.ThemeIcon('plug') : new vscode.ThemeIcon('server-environment'), collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, diff --git a/src/tree/mongo/MongoAccountModel.ts b/src/tree/mongo/MongoAccountModel.ts index b921868b..267003b1 100644 --- a/src/tree/mongo/MongoAccountModel.ts +++ b/src/tree/mongo/MongoAccountModel.ts @@ -4,9 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import { type CosmosAccountModel } from '../CosmosAccountModel'; +import { type MongoEmulatorConfiguration } from '../../commands/newConnection/MongoEmulatorConfiguration'; export type MongoAccountModel = CosmosAccountModel & { connectionString?: string; - isEmulator?: boolean; - disableEmulatorSecurity?: boolean; + emulatorConfiguration?: MongoEmulatorConfiguration; }; diff --git a/src/tree/mongo/MongoAccountResourceItem.ts b/src/tree/mongo/MongoAccountResourceItem.ts index 99263fad..16a484c6 100644 --- a/src/tree/mongo/MongoAccountResourceItem.ts +++ b/src/tree/mongo/MongoAccountResourceItem.ts @@ -17,6 +17,7 @@ import { createCosmosDBManagementClient } from '../../utils/azureClients'; import { CosmosDBAccountResourceItemBase } from '../CosmosDBAccountResourceItemBase'; import { type CosmosDBTreeElement } from '../CosmosDBTreeElement'; import { type MongoAccountModel } from './MongoAccountModel'; +import { type MongoEmulatorConfiguration } from '../../commands/newConnection/MongoEmulatorConfiguration'; /** * This implementation relies on information from the MongoAccountModel, i.e. @@ -33,7 +34,7 @@ export class MongoAccountResourceItem extends CosmosDBAccountResourceItemBase { account: MongoAccountModel, experience: Experience, readonly databaseAccount?: DatabaseAccountGetResults, // TODO: exploring during v1->v2 migration - readonly isEmulator?: boolean, // TODO: exploring during v1->v2 migration + readonly emulatorConfiguration?: MongoEmulatorConfiguration, // TODO: exploring during v1->v2 migration ) { super(account, experience); } @@ -106,7 +107,7 @@ export class MongoAccountResourceItem extends CosmosDBAccountResourceItemBase { //TODO: simplify the api for CrednetialCache to accept full connection strings with credentials const username: string | undefined = cString.username; const password: string | undefined = cString.password; - CredentialCache.setCredentials(this.id, cString.toString(), username, password); + CredentialCache.setCredentials(this.id, cString.toString(), username, password, this.account.emulatorConfiguration); mongoClient = await MongoClustersClient.getClient(this.id).catch(async (error) => { console.error(error);