auth0-extensions/auth0-source-control-extension-tools

View on GitHub
src/auth0/handlers/roles.js

Summary

Maintainability
F
3 days
Test Coverage
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' },
      id: { type: 'string' },
      description: { type: 'string' },
      permissions: {
        type: 'array',
        items: {
          type: 'object',
          properties: {
            permission_name: { type: 'string' },
            resource_server_identifier: { type: 'string' }
          }
        }
      }
    },
    required: [ 'name' ]
  }
};

export default class RoleHandler extends DefaultHandler {
  constructor(config) {
    super({
      ...config,
      type: 'roles',
      id: 'id',
      identifiers: [ 'name' ]
    });
  }

  async createRole(data) {
    const role = { ...data };
    delete role.permissions;

    const created = await this.client.roles.create(role);

    if (typeof data.permissions !== 'undefined' && data.permissions.length > 0) {
      await this.client.roles.permissions.create({ id: created.id }, { permissions: data.permissions });
    }

    return created;
  }

  async createRoles(creates) {
    await this.client.pool.addEachTask({
      data: creates || [],
      generator: (item) => this.createRole(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 deleteRole(data) {
    await this.client.roles.delete({ id: data.id });
  }

  async deleteRoles(dels) {
    if (this.config('AUTH0_ALLOW_DELETE') === 'true' || this.config('AUTH0_ALLOW_DELETE') === true) {
      await this.client.pool.addEachTask({
        data: dels || [],
        generator: (item) => this.deleteRole(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 roles should be deleted. Doing so may be destructive.\nYou can enable deletes by setting 'AUTH0_ALLOW_DELETE' to true in the config
      \n${dels.map((i) => this.objString(i)).join('\n')}`);
    }
  }

  async updateRole(data, roles) {
    const existingRole = await roles.find((roleDataForUpdate) => roleDataForUpdate.name === data.name);

    const params = { id: data.id };
    const newPermissions = data.permissions;

    delete data.permissions;
    delete data.id;

    await this.client.roles.update(params, data);

    if (typeof existingRole.permissions !== 'undefined' && existingRole.permissions.length > 0) {
      await this.client.roles.permissions.delete(params, { permissions: existingRole.permissions });
    }

    if (typeof newPermissions !== 'undefined' && newPermissions.length > 0) {
      await this.client.roles.permissions.create(params, { permissions: newPermissions });
    }

    return params;
  }

  async updateRoles(updates, roles) {
    await this.client.pool.addEachTask({
      data: updates || [],
      generator: (item) => this.updateRole(item, roles).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;
    }

    // in case client version does not support roles
    if (!this.client.roles || typeof this.client.roles.getAll !== 'function') {
      return [];
    }

    try {
      const roles = await this.client.roles.getAll({ paginate: true });
      for (let index = 0; index < roles.length; index++) {
        const permissions = await this.client.roles.permissions.getAll({ paginate: true, id: roles[index].id });
        const strippedPerms = await Promise.all(permissions.map(async (permission) => {
          delete permission.resource_server_name;
          delete permission.description;
          return permission;
        }));
        roles[index].permissions = strippedPerms;
      }
      this.existing = roles;
      return this.existing;
    } catch (err) {
      if (err.statusCode === 404 || err.statusCode === 501) {
        return [];
      }
      throw err;
    }
  }

  @order('60')
  async processChanges(assets) {
    const { roles } = assets;
    // Do nothing if not set
    if (!roles) return;
    // Gets roles from destination tenant
    const existing = await this.getType();
    const changes = calcChanges(roles, existing, [ 'id', 'name' ]);
    log.debug(`Start processChanges for roles [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.deleteRoles(change.del);
          break;
        case change.create && change.create.length > 0:
          await this.createRoles(changes.create);
          break;
        case change.update && change.update.length > 0:
          await this.updateRoles(change.update, existing);
          break;
        default:
          break;
      }
    }));
  }
}