NodeBB/NodeBB

View on GitHub
src/privileges/categories.js

Summary

Maintainability
A
30 mins
Test Coverage

'use strict';

const _ = require('lodash');

const categories = require('../categories');
const user = require('../user');
const groups = require('../groups');
const helpers = require('./helpers');
const plugins = require('../plugins');
const utils = require('../utils');

const privsCategories = module.exports;

/**
 * Looking to add a new category privilege via plugin/theme? Attach a hook to
 * `static:privileges.categories.init` and call .set() on the privilege map passed
 * in to your listener.
 */
const _privilegeMap = new Map([
    ['find', { label: '[[admin/manage/privileges:find-category]]', type: 'viewing' }],
    ['read', { label: '[[admin/manage/privileges:access-category]]', type: 'viewing' }],
    ['topics:read', { label: '[[admin/manage/privileges:access-topics]]', type: 'viewing' }],
    ['topics:create', { label: '[[admin/manage/privileges:create-topics]]', type: 'posting' }],
    ['topics:reply', { label: '[[admin/manage/privileges:reply-to-topics]]', type: 'posting' }],
    ['topics:schedule', { label: '[[admin/manage/privileges:schedule-topics]]', type: 'posting' }],
    ['topics:tag', { label: '[[admin/manage/privileges:tag-topics]]', type: 'posting' }],
    ['posts:edit', { label: '[[admin/manage/privileges:edit-posts]]', type: 'posting' }],
    ['posts:history', { label: '[[admin/manage/privileges:view-edit-history]]', type: 'posting' }],
    ['posts:delete', { label: '[[admin/manage/privileges:delete-posts]]', type: 'posting' }],
    ['posts:upvote', { label: '[[admin/manage/privileges:upvote-posts]]', type: 'posting' }],
    ['posts:downvote', { label: '[[admin/manage/privileges:downvote-posts]]', type: 'posting' }],
    ['topics:delete', { label: '[[admin/manage/privileges:delete-topics]]', type: 'posting' }],
    ['posts:view_deleted', { label: '[[admin/manage/privileges:view-deleted]]', type: 'moderation' }],
    ['purge', { label: '[[admin/manage/privileges:purge]]', type: 'moderation' }],
    ['moderate', { label: '[[admin/manage/privileges:moderate]]', type: 'moderation' }],
]);

privsCategories.init = async () => {
    privsCategories._coreSize = _privilegeMap.size;
    await plugins.hooks.fire('static:privileges.categories.init', {
        privileges: _privilegeMap,
    });
    for (const [, value] of _privilegeMap) {
        if (value && !value.type) {
            value.type = 'other';
        }
    }
};

privsCategories.getType = function (privilege) {
    const priv = _privilegeMap.get(privilege);
    return priv && priv.type ? priv.type : '';
};

privsCategories.getUserPrivilegeList = async () => await plugins.hooks.fire('filter:privileges.list', Array.from(_privilegeMap.keys()));
privsCategories.getGroupPrivilegeList = async () => await plugins.hooks.fire('filter:privileges.groups.list', Array.from(_privilegeMap.keys()).map(privilege => `groups:${privilege}`));

privsCategories.getPrivilegeList = async () => {
    const [user, group] = await Promise.all([
        privsCategories.getUserPrivilegeList(),
        privsCategories.getGroupPrivilegeList(),
    ]);
    return user.concat(group);
};

privsCategories.getPrivilegesByFilter = function (filter) {
    return Array.from(_privilegeMap.entries())
        .filter(priv => priv[1] && (!filter || priv[1].type === filter))
        .map(priv => priv[0]);
};

// Method used in admin/category controller to show all users/groups with privs in that given cid
privsCategories.list = async function (cid) {
    let labels = Array.from(_privilegeMap.values()).map(data => data.label);
    labels = await utils.promiseParallel({
        users: plugins.hooks.fire('filter:privileges.list_human', labels.slice()),
        groups: plugins.hooks.fire('filter:privileges.groups.list_human', labels.slice()),
    });

    const keys = await utils.promiseParallel({
        users: privsCategories.getUserPrivilegeList(),
        groups: privsCategories.getGroupPrivilegeList(),
    });

    const payload = await utils.promiseParallel({
        labels,
        labelData: Array.from(_privilegeMap.values()),
        users: helpers.getUserPrivileges(cid, keys.users),
        groups: helpers.getGroupPrivileges(cid, keys.groups),
    });
    payload.keys = keys;

    payload.columnCountUserOther = payload.labels.users.length - privsCategories._coreSize;
    payload.columnCountGroupOther = payload.labels.groups.length - privsCategories._coreSize;

    return payload;
};

privsCategories.get = async function (cid, uid) {
    const privs = [
        'topics:create', 'topics:read', 'topics:schedule',
        'topics:tag', 'read', 'posts:view_deleted',
    ];

    const [userPrivileges, isAdministrator, isModerator] = await Promise.all([
        helpers.isAllowedTo(privs, uid, cid),
        user.isAdministrator(uid),
        user.isModerator(uid, cid),
    ]);

    const combined = userPrivileges.map(allowed => allowed || isAdministrator);
    const privData = _.zipObject(privs, combined);
    const isAdminOrMod = isAdministrator || isModerator;

    return await plugins.hooks.fire('filter:privileges.categories.get', {
        ...privData,
        cid: cid,
        uid: uid,
        editable: isAdminOrMod,
        view_deleted: isAdminOrMod || privData['posts:view_deleted'],
        isAdminOrMod: isAdminOrMod,
    });
};

privsCategories.isAdminOrMod = async function (cid, uid) {
    if (parseInt(uid, 10) <= 0) {
        return false;
    }
    const [isAdmin, isMod] = await Promise.all([
        user.isAdministrator(uid),
        user.isModerator(uid, cid),
    ]);
    return isAdmin || isMod;
};

privsCategories.isUserAllowedTo = async function (privilege, cid, uid) {
    if ((Array.isArray(privilege) && !privilege.length) || (Array.isArray(cid) && !cid.length)) {
        return [];
    }
    if (!cid) {
        return false;
    }
    const results = await helpers.isAllowedTo(privilege, uid, Array.isArray(cid) ? cid : [cid]);

    if (Array.isArray(results) && results.length) {
        return Array.isArray(cid) ? results : results[0];
    }
    return false;
};

privsCategories.can = async function (privilege, cid, uid) {
    if (!cid) {
        return false;
    }
    const [disabled, isAdmin, isAllowed] = await Promise.all([
        categories.getCategoryField(cid, 'disabled'),
        user.isAdministrator(uid),
        privsCategories.isUserAllowedTo(privilege, cid, uid),
    ]);
    return !disabled && (isAllowed || isAdmin);
};

privsCategories.filterCids = async function (privilege, cids, uid) {
    if (!Array.isArray(cids) || !cids.length) {
        return [];
    }

    cids = _.uniq(cids);
    const [categoryData, allowedTo, isAdmin] = await Promise.all([
        categories.getCategoriesFields(cids, ['disabled']),
        helpers.isAllowedTo(privilege, uid, cids),
        user.isAdministrator(uid),
    ]);
    return cids.filter(
        (cid, index) => !!cid && !categoryData[index].disabled && (allowedTo[index] || isAdmin)
    );
};

privsCategories.getBase = async function (privilege, cids, uid) {
    return await utils.promiseParallel({
        categories: categories.getCategoriesFields(cids, ['disabled']),
        allowedTo: helpers.isAllowedTo(privilege, uid, cids),
        view_deleted: helpers.isAllowedTo('posts:view_deleted', uid, cids),
        view_scheduled: helpers.isAllowedTo('topics:schedule', uid, cids),
        isAdmin: user.isAdministrator(uid),
    });
};

privsCategories.filterUids = async function (privilege, cid, uids) {
    if (!uids.length) {
        return [];
    }

    uids = _.uniq(uids);

    const [allowedTo, isAdmins] = await Promise.all([
        helpers.isUsersAllowedTo(privilege, uids, cid),
        user.isAdministrator(uids),
    ]);
    return uids.filter((uid, index) => allowedTo[index] || isAdmins[index]);
};

privsCategories.give = async function (privileges, cid, members) {
    await helpers.giveOrRescind(groups.join, privileges, cid, members);
    plugins.hooks.fire('action:privileges.categories.give', {
        privileges: privileges,
        cids: Array.isArray(cid) ? cid : [cid],
        members: Array.isArray(members) ? members : [members],
    });
};

privsCategories.rescind = async function (privileges, cid, members) {
    await helpers.giveOrRescind(groups.leave, privileges, cid, members);
    plugins.hooks.fire('action:privileges.categories.rescind', {
        privileges: privileges,
        cids: Array.isArray(cid) ? cid : [cid],
        members: Array.isArray(members) ? members : [members],
    });
};

privsCategories.canMoveAllTopics = async function (currentCid, targetCid, uid) {
    const [isAdmin, isModerators] = await Promise.all([
        user.isAdministrator(uid),
        user.isModerator(uid, [currentCid, targetCid]),
    ]);
    return isAdmin || !isModerators.includes(false);
};

privsCategories.canPostTopic = async function (uid) {
    let cids = await categories.getAllCidsFromSet('categories:cid');
    cids = await privsCategories.filterCids('topics:create', cids, uid);
    return cids.length > 0;
};

privsCategories.userPrivileges = async function (cid, uid) {
    const userPrivilegeList = await privsCategories.getUserPrivilegeList();
    return await helpers.userOrGroupPrivileges(cid, uid, userPrivilegeList);
};

privsCategories.groupPrivileges = async function (cid, groupName) {
    const groupPrivilegeList = await privsCategories.getGroupPrivilegeList();
    return await helpers.userOrGroupPrivileges(cid, groupName, groupPrivilegeList);
};

privsCategories.getUidsWithPrivilege = async function (cids, privilege) {
    return await helpers.getUidsWithPrivilege(cids, privilege);
};