From a6a77c674b4c6111d0a213f537eebdd2f4099808 Mon Sep 17 00:00:00 2001 From: Tim Noonan Date: Mon, 12 Aug 2019 17:29:00 -0600 Subject: [PATCH] fix: deleting and org auth file never worked --- src/org.ts | 148 +++++++++++++++++++++++++++++-------------- test/unit/orgTest.ts | 59 +++++++++++++++-- 2 files changed, 153 insertions(+), 54 deletions(-) diff --git a/src/org.ts b/src/org.ts index 278936142f..e48f8fbc82 100644 --- a/src/org.ts +++ b/src/org.ts @@ -26,6 +26,7 @@ import { QueryResult } from 'jsforce'; import { join as pathJoin } from 'path'; import { AuthFields, AuthInfo } from './authInfo'; import { Aliases } from './config/aliases'; +import { AuthInfoConfig } from './config/authInfoConfig'; import { Config } from './config/config'; import { ConfigAggregator, ConfigInfo } from './config/configAggregator'; import { ConfigContents } from './config/configStore'; @@ -127,53 +128,14 @@ export class Org extends AsyncCreatable { if (this.getConnection().isUsingAccessToken()) { return Promise.resolve(); } - - const auths: AuthInfo[] = await this.readUserAuthFiles(); - const aliases: Aliases = await Aliases.create(Aliases.getDefaultOptions()); - this.logger.info(`Cleaning up usernames in org: ${this.getOrgId()}`); - - for (const auth of auths) { - const username = auth.getFields().username; - - const aliasKeys = (username && aliases.getKeysByValue(username)) || []; - aliases.unsetAll(aliasKeys); - - let orgForUser; - if (username === this.getUsername()) { - orgForUser = this; - } else { - const _info = await AuthInfo.create({ username }); - const connection: Connection = await Connection.create({ authInfo: _info }); - orgForUser = await Org.create({ connection }); - } - - const orgType = this.isDevHubOrg() ? Config.DEFAULT_DEV_HUB_USERNAME : Config.DEFAULT_USERNAME; - - const configInfo: ConfigInfo = await orgForUser.configAggregator.getInfo(orgType); - - if ( - (configInfo.value === username || aliasKeys.includes(configInfo.value as string)) && - (configInfo.isGlobal() || configInfo.isLocal()) - ) { - await Config.update(configInfo.isGlobal(), orgType, undefined); - } - - const orgUsers: OrgUsersConfig = await this.retrieveOrgUsersConfig(); - await this.manageDelete(async () => await orgUsers.unlink(), orgUsers.getPath(), throwWhenRemoveFails); - } - - await aliases.write(); - - // Delete the sandbox org config file if it exists. - // This is an optional file so don't throw an error when the file doesn't exist. - const sandboxOrgConfig = await this.retrieveSandboxOrgConfig(); - if (await sandboxOrgConfig.exists()) { - await this.manageDelete( - async () => await sandboxOrgConfig.unlink(), - sandboxOrgConfig.getPath(), - throwWhenRemoveFails - ); - } + await this.removeSandboxConfig(throwWhenRemoveFails); + await this.removeUsers(throwWhenRemoveFails); + await this.removeUsersConfig(); + // An attempt to remove this org's auth file occurs in this.removeUsersConfig. That's because this org's usersname is also + // included in the OrgUser config file. + // + // So, just in case no users are added to this org we will try the remove again. + await this.removeAuth(); } /** @@ -417,7 +379,7 @@ export class Org extends AsyncCreatable { * @param {value} The value to save */ public async setSandboxOrgConfigField(field: SandboxOrgConfig.Fields, value: string): Promise { - const sandboxOrgConfig = await this.retrieveSandboxOrgConfig(); + const sandboxOrgConfig: SandboxOrgConfig = await this.retrieveSandboxOrgConfig(); sandboxOrgConfig.set(field, value); await sandboxOrgConfig.write(); return this; @@ -487,6 +449,25 @@ export class Org extends AsyncCreatable { return this.connection; } + /** + * Returns a promise to delete an auth info file from the local file system and any related cache information for + * this Org.. You don't want to call this method directly. Instead consider calling Org.remove() + */ + public async removeAuth(): Promise { + const username = ensure(this.getUsername()); + this.logger.debug(`Removing auth for user: ${username}`); + const config = await AuthInfoConfig.create({ + ...AuthInfoConfig.getOptions(username), + throwOnNotFound: false + }); + + this.logger.debug(`Clearing auth cache for user: ${username}`); + AuthInfo.clearCache(username); + if (await config.exists()) { + await config.unlink(); + } + } + /** * Initialize async components. */ @@ -523,6 +504,17 @@ export class Org extends AsyncCreatable { throw new SfdxError('Not Supported'); } + /** + * Deletes the users config file + */ + private async removeUsersConfig() { + const config = await this.retrieveOrgUsersConfig(); + if (await config.exists()) { + this.logger.debug(`Removing org users config at: ${config.getPath()}`); + await config.unlink(); + } + } + /** * @ignore */ @@ -540,6 +532,66 @@ export class Org extends AsyncCreatable { } }); } + + /** + * Remove the org users auth file. + * @param throwWhenRemoveFails true if manageDelete should throw or not if the deleted fails. + */ + private async removeUsers(throwWhenRemoveFails: boolean) { + this.logger.debug(`Removing users associate with org: ${this.getOrgId()}`); + const config = await this.retrieveOrgUsersConfig(); + this.logger.debug(`using path for org users: ${config.getPath()}`); + if (await config.exists()) { + const _auths: AuthInfo[] = await this.readUserAuthFiles(); + const aliases: Aliases = await Aliases.create(Aliases.getDefaultOptions()); + this.logger.info(`Cleaning up usernames in org: ${this.getOrgId()}`); + + for (const auth of _auths) { + const username = auth.getFields().username; + + const aliasKeys = (username && aliases.getKeysByValue(username)) || []; + aliases.unsetAll(aliasKeys); + + let orgForUser; + if (username === this.getUsername()) { + orgForUser = this; + } else { + const _info = await AuthInfo.create({ username }); + const connection: Connection = await Connection.create({ authInfo: _info }); + orgForUser = await Org.create({ connection }); + } + + const orgType = this.isDevHubOrg() ? Config.DEFAULT_DEV_HUB_USERNAME : Config.DEFAULT_USERNAME; + + const configInfo: ConfigInfo = await orgForUser.configAggregator.getInfo(orgType); + + if ( + (configInfo.value === username || aliasKeys.includes(configInfo.value as string)) && + (configInfo.isGlobal() || configInfo.isLocal()) + ) { + await Config.update(configInfo.isGlobal(), orgType, undefined); + } + await orgForUser.removeAuth(); + } + + await aliases.write(); + } + } + + /** + * Remove an associate sandbox config. + * @param throwWhenRemoveFails true if manageDelete should throw or not if the deleted fails. + */ + private async removeSandboxConfig(throwWhenRemoveFails: boolean) { + const sandboxOrgConfig = await this.retrieveSandboxOrgConfig(); + if (await sandboxOrgConfig.exists()) { + await this.manageDelete( + async () => await sandboxOrgConfig.unlink(), + sandboxOrgConfig.getPath(), + throwWhenRemoveFails + ); + } + } } export namespace Org { diff --git a/test/unit/orgTest.ts b/test/unit/orgTest.ts index 4401e59233..13875f366a 100644 --- a/test/unit/orgTest.ts +++ b/test/unit/orgTest.ts @@ -276,6 +276,14 @@ describe('Org Tests', () => { }); it('should remove config setting', async () => { + stubMethod($$.SANDBOX, ConfigFile.prototype, 'exists').callsFake(async function() { + return this.path && this.path.endsWith(`${testData.orgId}.json`); + }); + + stubMethod($$.SANDBOX, fs, 'unlink').callsFake(() => { + return Promise.resolve({}); + }); + const configAggregator: ConfigAggregator = await ConfigAggregator.create(); const org: Org = await Org.create({ connection: await Connection.create({ @@ -301,6 +309,13 @@ describe('Org Tests', () => { }); it('should remove the alias', async () => { + stubMethod($$.SANDBOX, ConfigFile.prototype, 'exists').callsFake(async function() { + return this.path && this.path.endsWith(`${testData.orgId}.json`); + }); + + stubMethod($$.SANDBOX, fs, 'unlink').callsFake(() => { + return Promise.resolve({}); + }); const org: Org = await Org.create({ connection: await Connection.create({ authInfo: await AuthInfo.create({ username: testData.username }) @@ -336,10 +351,6 @@ describe('Org Tests', () => { await org.remove(); - expect(deletedPaths).includes( - pathJoin(await $$.globalPathRetriever($$.id), Global.STATE_FOLDER, `${testData.orgId}.json`) - ); - expect(deletedPaths).not.includes( pathJoin(await $$.globalPathRetriever($$.id), Global.STATE_FOLDER, `${testData.orgId}.sandbox.json`) ); @@ -430,6 +441,8 @@ describe('Org Tests', () => { await config.set(Config.DEFAULT_USERNAME, ensureString(org0Username)); await config.write(); + expect(await config.exists()).to.be.true; + const configAggregator = await orgs[0].getConfigAggregator().reload(); const info = configAggregator.getInfo(Config.DEFAULT_USERNAME); expect(info).has.property('value', org0Username); @@ -446,6 +459,12 @@ describe('Org Tests', () => { alias = await Aliases.fetch('foo'); expect(alias).eq(undefined); + + const configOrg0 = await AuthInfoConfig.create({ + ...AuthInfoConfig.getOptions(orgs[0].getUsername()), + throwOnNotFound: false + }); + expect(await configOrg0.exists()).to.be.false; }); it('should not try to delete auth files when deleting an org via access token', async () => { @@ -710,12 +729,40 @@ describe('Org Tests', () => { describe('sandbox org config', () => { it('set field', async () => { + // Stub exists so only the auth file and sandbox config file exist. No users config file. + stubMethod($$.SANDBOX, ConfigFile.prototype, 'exists').callsFake(async function() { + if (this.path && this.path.endsWith(`${testData.orgId}.json`)) { + return Promise.resolve(false); + } + return Promise.resolve(true); + }); + + // Stub to track the deleted paths. + const deletedPaths: string[] = []; + stubMethod($$.SANDBOX, ConfigFile.prototype, 'unlink').callsFake(function(this: ConfigFile) { + deletedPaths.push(this.getPath()); + return Promise.resolve({}); + }); + + // Create an org and add a sandbox config const org: Org = await Org.create({ aliasOrUsername: testData.username }); expect(await org.getSandboxOrgConfigField(SandboxOrgConfig.Fields.PROD_ORG_USERNAME)).to.be.undefined; - await org.setSandboxOrgConfigField(SandboxOrgConfig.Fields.PROD_ORG_USERNAME, 'user@sandbox.org'); - expect(await org.getSandboxOrgConfigField(SandboxOrgConfig.Fields.PROD_ORG_USERNAME)).to.eq('user@sandbox.org'); + + // Remove the org + await org.remove(); + + // Expect there are only two files. + expect(deletedPaths).to.have.length(2); + // Expect the sandbox config is deleted. + expect(deletedPaths).includes( + pathJoin(await $$.globalPathRetriever($$.id), Global.STATE_FOLDER, `${testData.orgId}.sandbox.json`) + ); + // Expect the auth file is deleted. + expect(deletedPaths).includes( + pathJoin(await $$.globalPathRetriever($$.id), Global.STATE_FOLDER, `${org.getUsername()}.json`) + ); }); }); });