From 7d0a7a6721b9ce45b4075cb9a8d508beadb0cae5 Mon Sep 17 00:00:00 2001 From: Cheena Malhotra <13396919+cheenamalhotra@users.noreply.github.com> Date: Thu, 18 May 2023 13:59:02 -0700 Subject: [PATCH] Fix CMS password login issues (#21260) --- .../cmsResource/tree/cmsResourceTreeNode.ts | 6 -- extensions/cms/src/cmsUtils.ts | 55 +++++++++++++++---- 2 files changed, 43 insertions(+), 18 deletions(-) diff --git a/extensions/cms/src/cmsResource/tree/cmsResourceTreeNode.ts b/extensions/cms/src/cmsResource/tree/cmsResourceTreeNode.ts index 2d6dd66fa9..6067ff55df 100644 --- a/extensions/cms/src/cmsResource/tree/cmsResourceTreeNode.ts +++ b/extensions/cms/src/cmsResource/tree/cmsResourceTreeNode.ts @@ -36,12 +36,6 @@ export class CmsResourceTreeNode extends CmsResourceTreeNodeBase { public async getChildren(): Promise { try { let nodes: CmsResourceTreeNodeBase[] = []; - if (!this.ownerUri) { - // Set back password to get ownerUri - if (this.connection.options.authenticationType === azdata.connection.AuthenticationType.SqlLogin && this.connection.options.savePassword === true) { - this.connection.options.password = await this.appContext.cmsUtils.getPassword(this.connection.options.user); - } - } return this.appContext.cmsUtils.createCmsServer(this.connection, this.name, this.description).then(async (result) => { // update the owner uri and the connection this._ownerUri = result.ownerUri; diff --git a/extensions/cms/src/cmsUtils.ts b/extensions/cms/src/cmsUtils.ts index b3c8b401d9..69a325ced7 100644 --- a/extensions/cms/src/cmsUtils.ts +++ b/extensions/cms/src/cmsUtils.ts @@ -15,6 +15,13 @@ const cmsProvider: string = 'MSSQL-CMS'; const mssqlProvider: string = 'MSSQL'; const CredentialNamespace = 'cmsCredentials'; +const CRED_PREFIX = 'Microsoft.SqlTools'; +const CRED_SEPARATOR = '|'; +const CRED_KEYVALUE_SEPARATOR = ':'; +const CRED_ID_PREFIX = 'id:'; +const CRED_ITEMTYPE_PREFIX = 'itemtype:'; +const CRED_PROFILE_USER = 'Profile'; + interface CreateCmsResult { listRegisteredServersResult: mssql.ListRegisteredServersResult; connection: azdata.connection.Connection; @@ -31,17 +38,32 @@ export class CmsUtils { private _cmsService: mssql.ICmsService; private _registeredCmsServers: ICmsResourceNodeInfo[] = []; - // [SuppressMessage("Microsoft.Security", "CS002:SecretInNextLine", Justification="Used for unit testing, not actual valid credential")] - public async savePassword(username: string, password: string): Promise { + public async savePassword(credId: string, password: string): Promise { let provider = await this.credentialProvider(); - let result = await provider.saveCredential(username, password); + let result = await provider.saveCredential(credId, password); return result; } - public async getPassword(username: string): Promise { + public async getPassword(connection: azdata.connection.Connection): Promise { let provider = await this.credentialProvider(); - let credential = await provider.readCredential(username); - return credential ? credential.password : undefined; + let credId = this.formatCredentialId(connection); + let credential = await provider.readCredential(credId); + return credential?.password ? credential.password : undefined; + } + + /** + * Creates a formatted credential usable for uniquely identifying a SQL Connection. + * This string can be decoded but is not optimized for this. + * @param connection connection instance - required + * @returns formatted string with server, DB and username + */ + private formatCredentialId(connection: azdata.connection.Connection): string { + const cred: string[] = [CRED_PREFIX]; + cred.push(CRED_ITEMTYPE_PREFIX.concat(CRED_PROFILE_USER)); + cred.push('Server' + CRED_KEYVALUE_SEPARATOR + CRED_ID_PREFIX.concat(connection.options.server)); + cred.push('Database' + CRED_KEYVALUE_SEPARATOR + CRED_ID_PREFIX.concat(connection.options.database)); + cred.push('User' + CRED_KEYVALUE_SEPARATOR + CRED_ID_PREFIX.concat(connection.options.user)); + return cred.join(CRED_SEPARATOR); } public getSavedServers(): ICmsResourceNodeInfo[] { @@ -56,7 +78,9 @@ export class CmsUtils { let ownerUri = await azdata.connection.getUriForConnection(connection.connectionId); if (!ownerUri) { // Make a connection if it's not already connected - let result = await azdata.connection.connect(Utils.toConnectionProfile(connection), false, false); + let profile = Utils.toConnectionProfile(connection); + profile.password = await this.getPassword(connection); + let result = await azdata.connection.connect(profile, false, false); if (result) { ownerUri = await azdata.connection.getUriForConnection(result.connectionId); } @@ -85,6 +109,10 @@ export class CmsUtils { name: string, description: string): Promise { let provider = await this.getCmsService(); connection.providerName = connection.providerName === cmsProvider ? mssqlProvider : connection.providerName; + // Populate password if it's a SQL Login mode. + if (connection.options.authenticationType === azdata.connection.AuthenticationType.SqlLogin && connection.options.savePassword) { + connection.options.password = await this.getPassword(connection); + } let ownerUri = await azdata.connection.getUriForConnection(connection.connectionId); if (!ownerUri) { // Make a connection if it's not already connected @@ -121,7 +149,7 @@ export class CmsUtils { }); } if (connection.options.authenticationType === azdata.connection.AuthenticationType.SqlLogin && connection.options.savePassword) { - this._credentialProvider.deleteCredential(connection.options.user); + this._credentialProvider.deleteCredential(this.formatCredentialId(connection)); } } @@ -207,6 +235,11 @@ export class CmsUtils { // Getters public get registeredCmsServers(): ICmsResourceNodeInfo[] { + this._registeredCmsServers.forEach(async server => { + if (server.connection.options.authenticationType === azdata.connection.AuthenticationType.SqlLogin) { + server.connection.options.password = await this.getPassword(server.connection); + } + }); return this._registeredCmsServers; } @@ -239,10 +272,10 @@ export class CmsUtils { if (connection) { // remove group ID from connection if a user chose connection // from the recent connections list - connection.options['groupId'] = null; + connection.options['groupId'] = undefined; connection.providerName = mssqlProvider; if (connection.options.savePassword) { - await this.savePassword(connection.options.user, connection.options.password); + await this.savePassword(this.formatCredentialId(connection), connection.options.password); } return connection; } @@ -250,7 +283,6 @@ export class CmsUtils { } // Static Functions - public getConnectionProfile(connection: azdata.connection.Connection): azdata.IConnectionProfile { let connectionProfile: azdata.IConnectionProfile = { connectionName: connection.options.connectionName, @@ -269,5 +301,4 @@ export class CmsUtils { }; return connectionProfile; } - }