bcgov/common-object-management-service

View on GitHub
app/src/services/objectPermission.js

Summary

Maintainability
F
5 days
Test Coverage
D
66%
const { v4: uuidv4, NIL: SYSTEM_USER } = require('uuid');

const { Permissions } = require('../components/constants');
const { BucketPermission, ObjectPermission } = require('../db/models');

/**
 * The Object Permission DB Service
 */
const service = {
  /**
   * @function addPermissions
   * Grants object permissions to users
   * @param {string} objId The objectId uuid
   * @param {object[]} data Incoming array of `userId` and `permCode` tuples to add for this `objId`
   * @param {string} [currentUserId=SYSTEM_USER] The optional userId uuid actor; defaults to system user if unspecified
   * @param {object} [etrx=undefined] An optional Objection Transaction object
   * @returns {Promise<object>} The result of running the insert operation
   * @throws The error encountered upon db transaction failure
   */
  addPermissions: async (objId, data, currentUserId = SYSTEM_USER, etrx = undefined) => {
    if (!objId) {
      throw new Error('Invalid objId supplied');
    }
    if (!data || !Array.isArray(data) || !data.length) {
      throw new Error('Invalid data supplied');
    }

    let trx;
    try {
      trx = etrx ? etrx : await ObjectPermission.startTransaction();

      // Get existing permissions for the current object
      const currentPerms = await service.searchPermissions({ objId });
      const obj = data
        // Ensure all codes are upper cased
        .map(p => ({ ...p, code: p.permCode.toUpperCase().trim() }))
        // Filter out any invalid code values
        .filter(p => Object.values(Permissions).some(perm => perm === p.permCode))
        // Filter entry tuples that already exist
        .filter(p => !currentPerms.some(cp => cp.userId === p.userId && cp.permCode === p.permCode))
        // Create DB objects to insert
        .map(p => ({
          id: uuidv4(),
          userId: p.userId,
          objectId: objId,
          permCode: p.permCode,
          createdBy: currentUserId,
        }));

      // Insert missing entries
      let response = [];
      if (obj.length) {
        response = await ObjectPermission.query(trx).insertAndFetch(obj);
      }

      if (!etrx) await trx.commit();
      return Promise.resolve(response);
    } catch (err) {
      if (!etrx && trx) await trx.rollback();
      throw err;
    }
  },

  /**
   * @function listInheritedObjectIds
   * Get objects that are in bucket(s) with given permission(s) for given user(s)
   * with given permission(s) for given user(s).
   * @param {string[]} [params.userIds] Optional array of user id(s)
   * @param {string[]} [params.bucketIds] Optional array of bucket id(s)
   * @param {string[]} [params.permCodes] Optional array of PermCode(s)
   * @returns {Promise<object>} The result of running the find operation
  */
  listInheritedObjectIds: async (userIds = [], bucketIds = [], permCodes = []) => {
    return BucketPermission.query()
      .distinct('object.id AS objectId')
      .rightJoin('object', 'bucket_permission.bucketId', '=', 'object.bucketId')
      .modify((query) => {
        if (userIds.length) query.modify('filterUserId', userIds);
      })
      .modify((query) => {
        if (bucketIds.length) query.whereIn('bucket_permission.bucketId', bucketIds);
        if (permCodes.length) query.whereIn('bucket_permission.permCode', permCodes);
      })
      .then(response => response.map(entry => entry.objectId));
  },

  /**
   * @function removePermissions
   * Deletes object permissions for a user
   * @param {string} objId The objectId uuid
   * @param {string[]} [userIds=undefined] Optional incoming array of user userId uuids to change
   * @param {string[]} [permissions=undefined] An optional array of permission codes to remove; defaults to undefined
   * @param {object} [etrx=undefined] An optional Objection Transaction object
   * @returns {Promise<object>} The result of running the delete operation
   * @throws The error encountered upon db transaction failure
   */
  removePermissions: async (objId, userIds = undefined, permissions = undefined, etrx = undefined) => {
    if (!objId) {
      throw new Error('Invalid objId supplied');
    }

    let trx;
    try {
      trx = etrx ? etrx : await ObjectPermission.startTransaction();

      let perms = undefined;
      if (permissions && Array.isArray(permissions) && permissions.length) {
        const cleanPerms = permissions
          // Ensure all codes are upper cased
          .map(p => p.toUpperCase().trim())
          // Filter out any invalid code values
          .filter(p => Object.values(Permissions).some(perm => perm === p));
        // Set as undefined if empty array
        perms = (cleanPerms.length) ? cleanPerms : undefined;
      }

      const response = await ObjectPermission.query(trx)
        .delete()
        .modify('filterUserId', userIds)
        .modify('filterObjectId', objId)
        .modify('filterPermissionCode', perms)
        // Returns array of deleted rows instead of count
        // https://vincit.github.io/objection.js/recipes/returning-tricks.html
        .returning('*');

      if (!etrx) await trx.commit();
      return response;
    } catch (err) {
      if (!etrx && trx) await trx.rollback();
      throw err;
    }
  },

  /**
   * @function searchPermissions
   * Search and filter for specific object permissions
   * @param {string|string[]} [params.bucketId] Optional string or array of uuids representing the bucket
   * @param {string|string[]} [params.userId] Optional string or array of uuids representing the user
   * @param {string|string[]} [params.objId] Optional string or array of uuids representing the object
   * @param {string|string[]} [params.permCode] Optional string or array of permission codes
   * @returns {Promise<object>} The result of running the find operation
   */
  searchPermissions: (params) => {
    return ObjectPermission.query()
      .modify('filterBucketId', params.bucketId)
      .modify('filterUserId', params.userId)
      .modify('filterObjectId', params.objId)
      .modify('filterPermissionCode', params.permCode);
  }
};

module.exports = service;