diff --git a/package-lock.json b/package-lock.json index 323eddd..17803f8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "auth0-source-control-extension-tools", - "version": "4.6.0", + "version": "4.7.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 6f611ee..84c267f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "auth0-source-control-extension-tools", - "version": "4.6.0", + "version": "4.7.0", "description": "Supporting tools for the Source Control extensions", "main": "lib/index.js", "scripts": { diff --git a/src/auth0/handlers/index.js b/src/auth0/handlers/index.js index 16d0b8a..9436175 100644 --- a/src/auth0/handlers/index.js +++ b/src/auth0/handlers/index.js @@ -20,7 +20,6 @@ import * as roles from './roles'; import * as branding from './branding'; import * as prompts from './prompts'; import * as migrations from './migrations'; -import * as organizations from './organizations'; export { rules, @@ -44,6 +43,5 @@ export { roles, branding, prompts, - migrations, - organizations + migrations }; diff --git a/src/auth0/handlers/organizations.js b/src/auth0/handlers/organizations.js deleted file mode 100644 index 03ba427..0000000 --- a/src/auth0/handlers/organizations.js +++ /dev/null @@ -1,191 +0,0 @@ -import _ from 'lodash'; -import DefaultHandler, { order } from './default'; -import { calcChanges } from '../../utils'; -import log from '../../logger'; - -export const schema = { - type: 'array', - items: { - type: 'object', - properties: { - name: { type: 'string' }, - display_name: { type: 'string' }, - branding: { type: 'object' }, - metadata: { type: 'object' }, - connections: { - type: 'array', - items: { - type: 'object', - properties: { - connection_id: { type: 'string' }, - assign_membership_on_login: { type: 'boolean' } - } - } - } - }, - required: [ 'name' ] - } -}; - -export default class OrganizationsHandler extends DefaultHandler { - constructor(config) { - super({ - ...config, - type: 'organizations', - id: 'id', - identifiers: [ 'name' ] - }); - } - - async deleteOrganization(org) { - await this.client.organizations.delete({ id: org.id }); - } - - async deleteOrganizations(data) { - if (this.config('AUTH0_ALLOW_DELETE') === 'true' || this.config('AUTH0_ALLOW_DELETE') === true) { - await this.client.pool.addEachTask({ - data: data || [], - generator: item => this.deleteOrganization(item).then(() => { - this.didDelete(item); - this.deleted += 1; - }).catch((err) => { - throw new Error(`Problem deleting ${this.type} ${this.objString(item)}\n${err}`); - }) - }).promise(); - } else { - log.warn(`Detected the following organizations should be deleted. Doing so may be destructive.\nYou can enable deletes by setting 'AUTH0_ALLOW_DELETE' to true in the config - \n${data.map(i => this.objString(i)).join('\n')}`); - } - } - - async createOrganization(org) { - const organization = { ...org }; - delete organization.connections; - - const created = await this.client.organizations.create(organization); - - if (typeof org.connections !== 'undefined' && org.connections.length > 0) { - await Promise.all(org.connections.map(conn => this.client.organizations.addEnabledConnection({ id: created.id }, conn))); - } - - return created; - } - - async createOrganizations(creates) { - await this.client.pool.addEachTask({ - data: creates || [], - generator: item => this.createOrganization(item).then((data) => { - this.didCreate(data); - this.created += 1; - }).catch((err) => { - throw new Error(`Problem creating ${this.type} ${this.objString(item)}\n${err}`); - }) - }).promise(); - } - - async updateOrganization(org, organizations) { - const { connections: existingConnections } = await organizations.find(orgToUpdate => orgToUpdate.name === org.name); - - const params = { id: org.id }; - const { connections } = org; - - delete org.connections; - delete org.name; - delete org.id; - - await this.client.organizations.update(params, org); - - const connectionsToRemove = existingConnections.filter(c => !connections.find(x => x.connection_id === c.connection_id)); - const connectionsToAdd = connections.filter(c => !existingConnections.find(x => x.connection_id === c.connection_id)); - const connectionsToUpdate = connections.filter(c => existingConnections.find(x => x.connection_id === c.connection_id && x.assign_membership_on_login !== c.assign_membership_on_login)); - - // Handle updates first - await Promise.all(connectionsToUpdate.map(conn => this.client.organizations - .updateEnabledConnection(Object.assign({ connection_id: conn.connection_id }, params), { assign_membership_on_login: conn.assign_membership_on_login }) - .catch(() => { - throw new Error(`Problem updating Enabled Connection ${conn.connection_id} for organizations ${params.id}`); - }))); - - await Promise.all(connectionsToAdd.map(conn => this.client.organizations - .addEnabledConnection(params, _.omit(conn, 'connection')) - .catch(() => { - throw new Error(`Problem adding Enabled Connection ${conn.connection_id} for organizations ${params.id}`); - }))); - - await Promise.all(connectionsToRemove.map(conn => this.client.organizations - .removeEnabledConnection(Object.assign({ connection_id: conn.connection_id }, params)) - .catch(() => { - throw new Error(`Problem removing Enabled Connection ${conn.connection_id} for organizations ${params.id}`); - }))); - - return params; - } - - async updateOrganizations(updates, orgs) { - await this.client.pool.addEachTask({ - data: updates || [], - generator: item => this.updateOrganization(item, orgs).then((data) => { - this.didUpdate(data); - this.updated += 1; - }).catch((err) => { - throw new Error(`Problem updating ${this.type} ${this.objString(item)}\n${err}`); - }) - }).promise(); - } - - async getType() { - if (this.existing) { - return this.existing; - } - - if (!this.client.organizations || typeof this.client.organizations.getAll !== 'function') { - return []; - } - - try { - const organizations = await this.client.organizations.getAll({ pagination: true }); - for (let index = 0; index < organizations.length; index++) { - const connections = await this.client.organizations.connections.get({ id: organizations[index].id }); - organizations[index].connections = connections; - } - this.existing = organizations; - return this.existing; - } catch (err) { - if (err.statusCode === 404 || err.statusCode === 501) { - return []; - } - throw err; - } - } - - // Run after connections - @order('70') - async processChanges(assets) { - const { organizations } = assets; - // Do nothing if not set - if (!organizations) return; - // Gets organizations from destination tenant - const existing = await this.getType(); - const changes = calcChanges(organizations, existing, [ 'id', 'name' ]); - - log.debug(`Start processChanges for organizations [delete:${changes.del.length}] [update:${changes.update.length}], [create:${changes.create.length}]`); - - const myChanges = [ { del: changes.del }, { create: changes.create }, { update: changes.update } ]; - - await Promise.all(myChanges.map(async (change) => { - switch (true) { - case change.del && change.del.length > 0: - await this.deleteOrganizations(change.del); - break; - case change.create && change.create.length > 0: - await this.createOrganizations(changes.create); - break; - case change.update && change.update.length > 0: - await this.updateOrganizations(change.update, existing); - break; - default: - break; - } - })); - } -} diff --git a/tests/auth0/handlers/organizations.tests.js b/tests/auth0/handlers/organizations.tests.js deleted file mode 100644 index b751d8e..0000000 --- a/tests/auth0/handlers/organizations.tests.js +++ /dev/null @@ -1,365 +0,0 @@ -const { expect } = require('chai'); -const organizations = require('../../../src/auth0/handlers/organizations'); - -const pool = { - addEachTask: (data) => { - if (data.data && data.data.length) { - data.generator(data.data[0]); - } - return { promise: () => null }; - } -}; - -const sampleOrg = { - id: '123', - name: 'acme', - display_name: 'Acme Inc' -}; - -const sampleConnection = { - connection_id: 'con_123', assign_membership_on_login: true -}; -const sampleConnection2 = { - connection_id: 'con_456', assign_membership_on_login: false -}; - - -describe('#organizations handler', () => { - const config = function(key) { - return config.data && config.data[key]; - }; - - config.data = { - AUTH0_ALLOW_DELETE: true - }; - - describe('#organizations validate', () => { - it('should not allow same id', async () => { - const handler = new organizations.default({ client: {}, config }); - const stageFn = Object.getPrototypeOf(handler).validate; - const data = [ - { - id: '123', - name: 'Acme' - }, - { - id: '123', - name: 'Contoso' - } - ]; - - try { - await stageFn.apply(handler, [ { organizations: data } ]); - } catch (err) { - expect(err).to.be.an('object'); - expect(err.message).to.include('Only one rule must be defined for the same order number in a stage.'); - } - }); - - it('should not allow same names', async () => { - const handler = new organizations.default({ client: {}, config }); - const stageFn = Object.getPrototypeOf(handler).validate; - const data = [ - { - name: 'Acme' - }, - { - name: 'Acme' - } - ]; - - try { - await stageFn.apply(handler, [ { organizations: data } ]); - } catch (err) { - expect(err).to.be.an('object'); - expect(err.message).to.include('Names must be unique'); - } - }); - - it('should pass validation', async () => { - const handler = new organizations.default({ client: {}, config }); - const stageFn = Object.getPrototypeOf(handler).validate; - const data = [ - { - name: 'Acme' - } - ]; - - await stageFn.apply(handler, [ { organizations: data } ]); - }); - }); - - describe('#organizations process', () => { - it('should return empty if no organization asset', async () => { - const auth0 = { - organizations: { - }, - pool - }; - - const handler = new organizations.default({ client: auth0, config }); - const stageFn = Object.getPrototypeOf(handler).processChanges; - const response = await stageFn.apply(handler, [ { } ]); - expect(response).to.equal(undefined); - }); - - it('should create organization', async () => { - const auth0 = { - organizations: { - create: (data) => { - expect(data).to.be.an('object'); - expect(data.name).to.equal('acme'); - expect(data.display_name).to.equal('Acme'); - expect(data.connections).to.equal(undefined); - data.id = 'fake'; - return Promise.resolve(data); - }, - update: () => Promise.resolve([]), - delete: () => Promise.resolve([]), - getAll: () => Promise.resolve([]), - addEnabledConnection: (org, connection) => { - expect(org.id).to.equal('fake'); - expect(connection).to.be.an('object'); - expect(connection.connection_id).to.equal('123'); - expect(connection.assign_membership_on_login).to.equal(true); - return Promise.resolve(connection); - } - }, - pool - }; - - const handler = new organizations.default({ client: auth0, config }); - const stageFn = Object.getPrototypeOf(handler).processChanges; - await stageFn.apply(handler, [ - { - organizations: [ - { - name: 'acme', - display_name: 'Acme', - connections: [ - { - connection_id: '123', - assign_membership_on_login: true - } - ] - } - ] - } - ]); - }); - - it('should get organizations', async () => { - const auth0 = { - organizations: { - getAll: () => Promise.resolve([ sampleOrg ]), - connections: { - get: () => [ - sampleConnection - ] - } - }, - pool - }; - - const handler = new organizations.default({ client: auth0, config }); - const data = await handler.getType(); - expect(data).to.deep.equal([ Object.assign({}, sampleOrg, { connections: [ sampleConnection ] }) ]); - }); - - it('should get all organizations', async () => { - const organizationsPage1 = Array.from({ length: 50 }, (v, i) => ({ name: 'acme' + i, display_name: 'Acme ' + i })); - const organizationsPage2 = Array.from({ length: 40 }, (v, i) => ({ name: 'acme' + (i + 50), display_name: 'Acme ' + (i + 50) })); - - const auth0 = { - organizations: { - getAll: () => Promise.resolve([ ...organizationsPage2, ...organizationsPage1 ]), - connections: { - get: () => Promise.resolve({}) - } - }, - pool - }; - - const handler = new organizations.default({ client: auth0, config }); - const data = await handler.getType(); - expect(data).to.have.length(90); - }); - - it('should return an empty array for old versions of the sdk', async () => { - const auth0 = { - pool - }; - - const handler = new organizations.default({ client: auth0, config }); - const data = await handler.getType(); - expect(data).to.deep.equal([]); - }); - - it('should return an empty array for 501 status code', async () => { - const auth0 = { - organizations: { - getAll: () => { - const error = new Error('Feature is not yet implemented'); - error.statusCode = 501; - throw error; - } - }, - pool - }; - - const handler = new organizations.default({ client: auth0, config }); - const data = await handler.getType(); - expect(data).to.deep.equal([]); - }); - - it('should return an empty array for 404 status code', async () => { - const auth0 = { - organizations: { - getAll: () => { - const error = new Error('Not found'); - error.statusCode = 404; - throw error; - } - }, - pool - }; - - const handler = new organizations.default({ client: auth0, config }); - const data = await handler.getType(); - expect(data).to.deep.equal([]); - }); - - it('should throw an error for all other failed requests', async () => { - const auth0 = { - organizations: { - getAll: () => { - const error = new Error('Bad request'); - error.statusCode = 500; - throw error; - } - }, - pool - }; - - const handler = new organizations.default({ client: auth0, config }); - try { - await handler.getType(); - } catch (error) { - expect(error).to.be.an.instanceOf(Error); - } - }); - - it('should call getAll once', async () => { - let shouldThrow = false; - const auth0 = { - organizations: { - getAll: () => { - if (!shouldThrow) { - return [ sampleOrg ]; - } - - throw new Error('Unexpected'); - }, - connections: { - get: () => Promise.resolve([]) - } - }, - pool - }; - - const handler = new organizations.default({ client: auth0, config }); - let data = await handler.getType(); - expect(data).to.deep.equal([ sampleOrg ]); - - shouldThrow = true; - data = await handler.getType(); - expect(data).to.deep.equal([ sampleOrg ]); - }); - - it('should update organizations', async () => { - const auth0 = { - organizations: { - create: () => Promise.resolve([]), - update: (params, data) => { - expect(params).to.be.an('object'); - expect(params.id).to.equal('123'); - expect(data.display_name).to.equal('Acme 2'); - return Promise.resolve(data); - }, - delete: () => Promise.resolve([]), - getAll: () => Promise.resolve([ sampleOrg ]), - connections: { - get: () => [ - sampleConnection, - sampleConnection2 - ] - }, - addEnabledConnection: (params, data) => { - expect(params).to.be.an('object'); - expect(params.id).to.equal('123'); - expect(data).to.be.an('object'); - expect(data.connection_id).to.equal('con_789'); - expect(data.assign_membership_on_login).to.equal(false); - return Promise.resolve(data); - }, - removeEnabledConnection: (params) => { - expect(params).to.be.an('object'); - expect(params.id).to.equal('123'); - expect(params.connection_id).to.equal(sampleConnection2.connection_id); - return Promise.resolve(); - }, - updateEnabledConnection: (params, data) => { - expect(params).to.be.an('object'); - expect(params.id).to.equal('123'); - expect(params.connection_id).to.equal(sampleConnection.connection_id); - expect(data).to.be.an('object'); - expect(data.assign_membership_on_login).to.equal(false); - return Promise.resolve(data); - } - }, - pool - }; - - const handler = new organizations.default({ client: auth0, config }); - const stageFn = Object.getPrototypeOf(handler).processChanges; - - await stageFn.apply(handler, [ - { - organizations: [ - { - id: '123', - name: 'acme', - display_name: 'Acme 2', - connections: [ - { connection_id: 'con_123', assign_membership_on_login: false }, - { connection_id: 'con_789', assign_membership_on_login: false } - ] - } - ] - } - ]); - }); - - it('should delete organizations', async () => { - const auth0 = { - organizations: { - create: () => Promise.resolve([]), - update: () => Promise.resolve([]), - delete: (data) => { - expect(data).to.be.an('object'); - expect(data.id).to.equal(sampleOrg.id); - return Promise.resolve(data); - }, - getAll: () => Promise.resolve([ sampleOrg ]), - connections: { - get: () => [] - } - }, - pool - }; - const handler = new organizations.default({ client: auth0, config }); - const stageFn = Object.getPrototypeOf(handler).processChanges; - await stageFn.apply(handler, [ { organizations: [ {} ] } ]); - }); - }); -});