Home Reference Source

core/live/user-to-user/stream-acl.js

import isArray from 'lodash/isArray';
import every from 'lodash/every';
import { KinveyError } from '../../errors';
import { isNonemptyString } from '../../utils';

const invalidValueMsg = 'Invalid ACL object value';


/**
 * @private
 */
export class StreamACL {
  constructor(obj) {
    this.publishers = [];
    this.subscribers = [];
    this.publisherGroups = [];
    this.subscriberGroups = [];

    if (obj) {
      if ((obj instanceof StreamACL)) {
        obj = obj.toPlainObject();
      }
      this._parsePlainObject(obj);
    }
  }

  /**
   * Checks if specified object has valid values for any
   * of the valid fiends for an ACL object
   * @param {object} obj Object to validate
   * @returns {boolean}
   */
  static isValidACLObject(obj) {
    try {
      const acl = new StreamACL(obj);
      return acl.isNotEmpty();
    } catch (err) {
      return false;
    }
  }

  /**
   * @param  {(User|User[]|string|string[])} publishers
   * @returns {this}
   */
  addPublishers(publishers) {
    this._addToAcl(this.publishers, publishers);
    return this;
  }

  /**
   * @param  {(User|User[]|string|string[])} subscribers
   * @returns {this}
   */
  addSubscribers(subscribers) {
    this._addToAcl(this.subscribers, subscribers);
    return this;
  }

  /**
   * @param  {(string|string[]|{_id: string})} groups
   * @returns {this}
   */
  addPublisherGroups(groups) {
    this._addToAcl(this.publisherGroups, groups);
    return this;
  }

  /**
   * @param  {(string|string[]|{_id: string})} groups
   * @returns {this}
   */
  addSubscriberGroups(groups) {
    this._addToAcl(this.subscriberGroups, groups);
    return this;
  }

  /**
   * @param  {(User|User[]|string|string[])} publishers
   * @returns {this}
   */
  removePublishers(publishers) {
    this._removeFromAcl(this.publishers, publishers);
    return this;
  }

  /**
   * @param  {(User|User[]|string|string[])} subscribers
   * @returns {this}
   */
  removeSubscribers(subscribers) {
    this._removeFromAcl(this.subscribers, subscribers);
    return this;
  }

  /**
   * @param  {(string|string[]|{_id: string})} groups
   * @returns {this}
   */
  removePublisherGroups(groups) {
    this._removeFromAcl(this.publisherGroups, groups);
    return this;
  }

  /**
   * @param  {(string|string[]|{_id: string})} groups
   * @returns {this}
   */
  removeSubscriberGroups(groups) {
    this._removeFromAcl(this.subscriberGroups, groups);
    return this;
  }

  /**
   * Indicates whether the current object has any user IDs,
   * in any of its fields
   * @returns {Boolean}
   */
  isNotEmpty() {
    return this.publishers.length
      || this.subscribers.length
      || this.publisherGroups.length
      || this.subscriberGroups.length;
  }

  /**
   * Converts the StreamACL object to a serializable object
   * @returns {PlainACLObject}
   */
  toPlainObject() {
    const res = {};

    if (this.subscribers.length) {
      res.subscribe = this.subscribers.slice(0);
    }
    if (this.publishers.length) {
      res.publish = this.publishers.slice(0);
    }

    if (this.subscriberGroups.length) {
      res.groups = res.groups || {};
      res.groups.subscribe = this.addSubscriberGroups.slice(0);
    }

    if (this.publisherGroups.length) {
      res.groups = res.groups || {};
      res.groups.publish = this.publisherGroups.slice(0);
    }

    return res;
  }

  /**
   * Populates current instance with data from a {@link {PlainACLObject}} object
   * @private
   * @param {PlainACLObject} plainACLObject
   */
  _parsePlainObject(plainACLObject) {
    if (plainACLObject.subscribe) {
      this.subscribers = this._ensureValidIdArray(plainACLObject.subscribe);
    }

    if (plainACLObject.publish) {
      this.publishers = this._ensureValidIdArray(plainACLObject.publish);
    }

    if (plainACLObject.groups) {
      if (plainACLObject.groups.publish) {
        this.publisherGroups = this._ensureValidIdArray(plainACLObject.groups.publish);
      }

      if (plainACLObject.groups.subscribe) {
        this.subscriberGroups = this._ensureValidIdArray(plainACLObject.groups.subscribe);
      }
    }
  }

  /**
   * Ensures that the specified array contains only nonempty strings
   * so that it can be assigned to a {@link StreamACL} property
   * @private
   * @param {string[]} arr An array to validate
   * @returns {string[]} The same array, if it is valid
   */
  _ensureValidIdArray(arr) {
    const isValid = this._isValidIdArray(arr);
    if (!isValid) {
      throw new KinveyError(invalidValueMsg);
    }
    return arr;
  }

  /**
   * @private
   * @param {string[]} arr
   * @returns {boolean}
   */
  _isValidIdArray(arr) {
    return isArray(arr) && every(arr, (o) => {
      const id = this._getAsId(o);
      return isNonemptyString(id);
    });
  }

  /**
   * @private
   * @param {string[]} arr The appropriate array to add users to -
   *    subscribers (or groups of them) or publishers (or groups of them)
   * @param {(User|User[]|string|string[])} users
   */
  _addToAcl(arr, users) {
    const usersArr = isArray(users) ? users : [users];
    const isValid = this._isValidIdArray(usersArr);
    if (!isValid) {
      throw new KinveyError(invalidValueMsg);
    }

    usersArr.forEach((u) => {
      const id = this._getAsId(u);
      arr.push(id);
    });
  }

  /**
   * @private
   * @param {string[]} arr The appropriate array to add users to -
   *    subscribers (or groups of them) or publishers (or groups of them)
   * @param {(User|User[]|string|string[])} users
   */
  _removeFromAcl(arr, users) {
    const usersArr = isArray(users) ? users : [users];

    usersArr.forEach((u) => {
      const id = this._getAsId(u);
      const index = arr.indexOf(id);
      if (index >= 0) {
        arr.splice(index, 1);
      }
    });
  }

  /**
   * @private
   * @param {Object} object Object which contains an "_id" property or is itself an id
   * @returns {string} The value of the "_id" property of the object, or the object itself, if it doesn't have such a property
   */
  _getAsId(o) {
    return (o && o._id) ? o._id : o;
  }
}