core/acl.js

/*
 * Your installation or use of this SugarCRM file is subject to the applicable
 * terms available at
 * http://support.sugarcrm.com/Resources/Master_Subscription_Agreements/.
 * If you do not agree to all of the applicable terms or do not have the
 * authority to bind the entity as an authorized representative, then do not
 * install or use this SugarCRM file.
 *
 * Copyright (C) SugarCRM Inc. All rights reserved.
 */

const User = require('./user');

/**
 * Dictionary that maps actions to permissions.
 * FIXME: make this constant again
 * @private
 */
let Action2Permission = {
    view: 'read',
    readonly: 'read',
    edit: 'write',
    detail: 'read',
    list: 'read',
    disabled: 'read',
};

/**
 * ACL helper to convert ACLs data into booleans.
 *
 * @param {string} action Action name.
 * @param {Object} acls ACL hash.
 * @return {boolean} Flag indicating if the current user has access.
 * @private
 */
function hasAccess(action, acls) {
    let access;

    if (acls.access === 'no') {
        access = 'no';
    } else {
        access = acls[action];
    }

    return access !== 'no';
}

/**
 * ACL helper to convert ACLs data on fields into booleans.
 *
 * @param {string} action Action name.
 * @param {Object} acls ACL hash.
 * @param {string} field Name of the Module's field.
 * @return {boolean} Flag indicating if the current user has access.
 * @private
 */
function hasAccessToField(action, acls, field) {
    let access;

    action = Action2Permission[action] || action;
    if (acls.fields[field] && acls.fields[field][action]) {
        access = acls.fields[field][action];
    }

    return access !== 'no';
}

/**
 * The ACL module provides methods to check ACLs for modules and fields.
 *
 * @module Core/Acl
 */

/**
 * @alias module:Core/Acl
 */
let Acl = {
    /**
     * Checks ACLs to see if the current user can perform the given action on a
     * given module or record.
     *
     * @param {string} action Action name.
     * @param {string} module Module name.
     * @param {Object} [options] Options.
     * @param {string} [options.field] Name of the field to check access to.
     * @param {string} [options.acls] Record's ACLs that take precedence over
     *   the module's ACLs. These are normally supplied by the server as part
     *   of the data response in `_acl`.
     * @return {boolean} `true` if the current user has access to the given
     *   `action`; `false` otherwise.
     */
    hasAccess: function(action, module, options) {
        let field;
        let recordAcls;

        if (!_.isObject(options)) {
            // TODO: Throw deprecation warning and remove this code.
            field = arguments[3];
            recordAcls = arguments[4];
        } else {
            field = options.field;
            recordAcls = options.acls;
        }

        let acls = User.getAcls()[module];
        if (!acls && !recordAcls) {
            return true;
        }

        acls = acls || {};
        if (recordAcls) {
            let fieldAcls = _.extend({}, acls.fields, recordAcls.fields);
            acls = _.extend({}, acls, recordAcls);
            acls.fields = fieldAcls;
        }

        let access = hasAccess(action, acls);
        if (access && field && acls.fields) {
            access = hasAccessToField(action, acls, field);

            // if the field is in a group, see if we have access to the group
            let moduleMeta = SUGAR.App.metadata.getModule(module);
            let fieldMeta = (moduleMeta && moduleMeta.fields) ? moduleMeta.fields[field] : null;
            if (access && fieldMeta && fieldMeta.group) {
                access = hasAccessToField(action, acls, fieldMeta.group);
            }
        }

        return access;
    },

    /**
     * Checks ACLs to see if the current user can perform the given action on a
     * given model's field.
     *
     * @param {string} action Action name.
     * @param {Object} model Model instance.
     * @param {string} [field] Name of the model field.
     * @return {boolean} `true` if the current user has access to the given
     *   `action`; `false` otherwise.
     */
    hasAccessToModel: function(action, model, field) {
        let id;
        let module;
        let acls;

        if (model) {
            id = model.id;
            module = model.module;
            acls = model.get('_acl') || { fields: {} };
        }

        if (action === 'edit' && !id) {
            action = 'create';
        }

        return this.hasAccess(action, module, {field, acls});
    },

    /**
     * Checks ACLs to see if the current user can perform the given
     * action on any model.
     *
     * ```
     * const Acl = require('./acl');
     *
     * // Check whether user has `admin` access for any module.
     * Acl.hasAccessToAny('admin');
     *
     * // Check whether user has `developer` access for any module.
     * Acl.hasAccessToAny('developer');
     * ```
     *
     * @param {string} action Action name.
     * @return {boolean} `true` if the current user has access to the given
     *   `action`; `false` otherwise.
     */
    hasAccessToAny: function(action) {
        return _.some(User.getAcls(), function(obj, module) {
                return this.hasAccess(action, module);
        }, this);
    },

    _hasAccess: function(action, acls) {
        if (!SUGAR.App.config.sidecarCompatMode) {
            SUGAR.App.logger.error('Core.Acl#_hasAccess is a private method that you are not allowed to access. ' +
                'Please use only the public API.');
            return;
        }

        SUGAR.App.logger.warn('Core.Acl#_hasAccess is a private method that you should not access. ' +
            'You will NOT be allowed to access it in the next release. Please update your code to rely on the ' +
            'public API only.');

        return hasAccess(action, acls);
    },

    _hasAccessToField: function(action, acls, field) {
        if (!SUGAR.App.config.sidecarCompatMode) {
            SUGAR.App.logger.error('Core.Acl#_hasAccessToField is a private method that you are not allowed ' +
                'to access. Please use only the public API.');
            return;
        }

        SUGAR.App.logger.warn('Core.Acl#_hasAccessToField is a private method that you should not access. ' +
            'You will NOT be allowed to access it in the next release. Please update your code to rely on the public ' +
            'API only.');

        return hasAccessToField(action, acls, field);
    },

    /**
     * This method has no effect. Do not use it.
     *
     * @deprecated since 7.10
     */
    clearCache: function() {
        SUGAR.App.logger.warn('`Core.Acl#clearCache` is deprecated since 7.10 and will be removed in a future ' +
            'release. This method has no effect. Please do not use it anymore');
        this._accessToAny = {};
    }
};

Object.defineProperty(Acl, 'action2permission', {
    configurable: true,

    get: function() {
        SUGAR.App.logger.warn('`Core.Acl.action2permission` has been made private since 7.10 and you will not be able ' +
            'to access it in the next release. Please do not use it anymore');
        return Action2Permission;
    },

    set: function(val) {
        SUGAR.App.logger.warn('`Core.Acl.action2permission` has been made private since 7.10 and you will not be able ' +
            'to access it in the next release. Please do not use it anymore');
        Action2Permission = val;
    },
});

/**
 * Dummy _accessToAny variable.
 *
 * @deprecated
 * @private
 */
let dummyAccessToAny = {};

Object.defineProperty(Acl, '_accessToAny', {
    configurable: true,

    get: function() {
        SUGAR.App.logger.warn('`Core.Acl#_accessToAny` is deprecated since 7.10 and will be removed in a future ' +
            'release. Please do not use it anymore');
        return dummyAccessToAny;
    },

    set: function(val) {
        SUGAR.App.logger.warn('`Core.Acl#_accessToAny` is deprecated since 7.10 and will be removed in a future ' +
            'release. Please do not use it anymore');
        dummyAccessToAny = val;
    },
});

module.exports = Acl;