mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-10-26 16:46:12 +01:00
feat: fine-grained privileges integration for fediverse users and world pseudo-category
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"category.name": "World",
|
||||||
"no-topics": "This forum doesn't know of any other topics yet.",
|
"no-topics": "This forum doesn't know of any other topics yet.",
|
||||||
|
|
||||||
"topic-event-announce-ago": "%1 shared <a href=\"%2\">this post</a> %3",
|
"topic-event-announce-ago": "%1 shared <a href=\"%2\">this post</a> %3",
|
||||||
|
|||||||
@@ -228,7 +228,7 @@ define('admin/manage/privileges', [
|
|||||||
applyPrivileges(bannedUsersPrivs, getBannedUsersInputSelector);
|
applyPrivileges(bannedUsersPrivs, getBannedUsersInputSelector);
|
||||||
|
|
||||||
// For rest that inherits from registered-users
|
// For rest that inherits from registered-users
|
||||||
const getRegisteredUsersInputSelector = (privs, i) => `.privilege-table tr[data-group-name]:not([data-group-name="registered-users"],[data-group-name="banned-users"],[data-group-name="guests"],[data-group-name="spiders"]) td[data-privilege="${privs[i]}"] input, .privilege-table tr[data-uid]:not([data-banned]) td[data-privilege="${privs[i]}"] input`;
|
const getRegisteredUsersInputSelector = (privs, i) => `.privilege-table tr[data-group-name]:not([data-group-name="registered-users"],[data-group-name="banned-users"],[data-group-name="guests"],[data-group-name="spiders"],[data-group-name="fediverse"]) td[data-privilege="${privs[i]}"] input, .privilege-table tr[data-uid]:not([data-banned]) td[data-privilege="${privs[i]}"] input`;
|
||||||
const registeredUsersPrivs = getPrivilegesFromRow('registered-users');
|
const registeredUsersPrivs = getPrivilegesFromRow('registered-users');
|
||||||
applyPrivileges(registeredUsersPrivs, getRegisteredUsersInputSelector);
|
applyPrivileges(registeredUsersPrivs, getRegisteredUsersInputSelector);
|
||||||
};
|
};
|
||||||
@@ -240,7 +240,7 @@ define('admin/manage/privileges', [
|
|||||||
inputSelectorFn = () => `.privilege-table tr[data-banned] td[data-privilege]:nth-child(${columnNo}) input`;
|
inputSelectorFn = () => `.privilege-table tr[data-banned] td[data-privilege]:nth-child(${columnNo}) input`;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
inputSelectorFn = () => `.privilege-table tr[data-group-name]:not([data-group-name="registered-users"],[data-group-name="banned-users"],[data-group-name="guests"],[data-group-name="spiders"]) td[data-privilege]:nth-child(${columnNo}) input, .privilege-table tr[data-uid]:not([data-banned]) td[data-privilege]:nth-child(${columnNo}) input`;
|
inputSelectorFn = () => `.privilege-table tr[data-group-name]:not([data-group-name="registered-users"],[data-group-name="banned-users"],[data-group-name="guests"],[data-group-name="spiders"],[data-group-name="fediverse"]) td[data-privilege]:nth-child(${columnNo}) input, .privilege-table tr[data-uid]:not([data-banned]) td[data-privilege]:nth-child(${columnNo}) input`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sourceChecked = getPrivilegeFromColumn(sourceGroupName, columnNo);
|
const sourceChecked = getPrivilegeFromColumn(sourceGroupName, columnNo);
|
||||||
|
|||||||
@@ -189,16 +189,18 @@ module.exports = function (utils, Benchpress, relative_path) {
|
|||||||
return states.map(function (priv) {
|
return states.map(function (priv) {
|
||||||
const guestDisabled = ['groups:moderate', 'groups:posts:upvote', 'groups:posts:downvote', 'groups:local:login', 'groups:group:create'];
|
const guestDisabled = ['groups:moderate', 'groups:posts:upvote', 'groups:posts:downvote', 'groups:local:login', 'groups:group:create'];
|
||||||
const spidersEnabled = ['groups:find', 'groups:read', 'groups:topics:read', 'groups:view:users', 'groups:view:tags', 'groups:view:groups'];
|
const spidersEnabled = ['groups:find', 'groups:read', 'groups:topics:read', 'groups:view:users', 'groups:view:tags', 'groups:view:groups'];
|
||||||
|
const fediverseEnabled = ['groups:view:users', 'groups:find', 'groups:read', 'groups:topics:read', 'groups:topics:create', 'groups:topics:reply', 'groups:topics:tag', 'groups:posts:edit', 'groups:posts:history', 'groups:posts:delete', 'groups:posts:upvote', 'groups:posts:downvote', 'groups:topics:delete'];
|
||||||
const globalModDisabled = ['groups:moderate'];
|
const globalModDisabled = ['groups:moderate'];
|
||||||
const disabled =
|
const disabled =
|
||||||
(member === 'guests' && (guestDisabled.includes(priv.name) || priv.name.startsWith('groups:admin:'))) ||
|
(member === 'guests' && (guestDisabled.includes(priv.name) || priv.name.startsWith('groups:admin:'))) ||
|
||||||
(member === 'spiders' && !spidersEnabled.includes(priv.name)) ||
|
(member === 'spiders' && !spidersEnabled.includes(priv.name)) ||
|
||||||
|
(member === 'fediverse' && !fediverseEnabled.includes(priv.name)) ||
|
||||||
(member === 'Global Moderators' && globalModDisabled.includes(priv.name));
|
(member === 'Global Moderators' && globalModDisabled.includes(priv.name));
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<td data-privilege="${priv.name}" data-value="${priv.state}" data-type="${priv.type}">
|
<td data-privilege="${priv.name}" data-value="${priv.state}" data-type="${priv.type}">
|
||||||
<div class="form-check text-center">
|
<div class="form-check text-center">
|
||||||
<input class="form-check-input float-none" autocomplete="off" type="checkbox"${(priv.state ? ' checked' : '')}${(disabled ? ' disabled="disabled"' : '')} />
|
<input class="form-check-input float-none${(disabled ? ' d-none"' : '')}" autocomplete="off" type="checkbox"${(priv.state ? ' checked' : '')}${(disabled ? ' disabled="disabled" aria-diabled="true"' : '')} />
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ const winston = require('winston');
|
|||||||
const nconf = require('nconf');
|
const nconf = require('nconf');
|
||||||
|
|
||||||
const db = require('../database');
|
const db = require('../database');
|
||||||
|
const privileges = require('../privileges');
|
||||||
const user = require('../user');
|
const user = require('../user');
|
||||||
const posts = require('../posts');
|
const posts = require('../posts');
|
||||||
const topics = require('../topics');
|
const topics = require('../topics');
|
||||||
@@ -17,20 +18,14 @@ const inbox = module.exports;
|
|||||||
|
|
||||||
inbox.create = async (req) => {
|
inbox.create = async (req) => {
|
||||||
const { object } = req.body;
|
const { object } = req.body;
|
||||||
const postData = await activitypub.mocks.post(object);
|
|
||||||
|
|
||||||
// Temporary, reject non-public notes.
|
// Temporary, reject non-public notes.
|
||||||
if (![...postData._activitypub.to, ...postData._activitypub.cc].includes(activitypub._constants.publicAddress)) {
|
if (![...object.to, ...object.cc].includes(activitypub._constants.publicAddress)) {
|
||||||
throw new Error('[[error:activitypub.not-implemented]]');
|
throw new Error('[[error:activitypub.not-implemented]]');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (postData) {
|
const tid = await activitypub.notes.assertTopic(0, object.id);
|
||||||
await activitypub.notes.assert(0, [postData]);
|
winston.info(`[activitypub/inbox] Parsing note ${object.id} into topic ${tid}`);
|
||||||
const tid = await activitypub.notes.assertTopic(0, postData.pid);
|
|
||||||
winston.info(`[activitypub/inbox] Parsing note ${postData.pid} into topic ${tid}`);
|
|
||||||
} else {
|
|
||||||
winston.warn('[activitypub/inbox] Received object was not a note');
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
inbox.update = async (req) => {
|
inbox.update = async (req) => {
|
||||||
@@ -43,6 +38,18 @@ inbox.update = async (req) => {
|
|||||||
throw new Error('[[error:activitypub.origin-mismatch]]');
|
throw new Error('[[error:activitypub.origin-mismatch]]');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const [exists, allowed] = await Promise.all([
|
||||||
|
posts.exists(object.id),
|
||||||
|
privileges.posts.can('posts:edit', object.id, activitypub._constants.uid),
|
||||||
|
]);
|
||||||
|
if (!exists || !allowed) {
|
||||||
|
winston.info(`[activitypub/inbox.update] ${object.id} not allowed to be edited.`);
|
||||||
|
return activitypub.send('uid', 0, actor, {
|
||||||
|
type: 'Reject',
|
||||||
|
object,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
switch (object.type) {
|
switch (object.type) {
|
||||||
case 'Note': {
|
case 'Note': {
|
||||||
const postData = await activitypub.mocks.post(object);
|
const postData = await activitypub.mocks.post(object);
|
||||||
@@ -70,6 +77,15 @@ inbox.like = async (req) => {
|
|||||||
throw new Error('[[error:activitypub.invalid-id]]');
|
throw new Error('[[error:activitypub.invalid-id]]');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const allowed = await privileges.posts.can('posts:upvote', id, activitypub._constants.uid);
|
||||||
|
if (!allowed) {
|
||||||
|
winston.info(`[activitypub/inbox.like] ${id} not allowed to be upvoted.`);
|
||||||
|
return activitypub.send('uid', 0, actor, {
|
||||||
|
type: 'Reject',
|
||||||
|
object,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
winston.info(`[activitypub/inbox/like] id ${id} via ${actor}`);
|
winston.info(`[activitypub/inbox/like] id ${id} via ${actor}`);
|
||||||
|
|
||||||
await posts.upvote(id, actor);
|
await posts.upvote(id, actor);
|
||||||
@@ -172,17 +188,29 @@ inbox.follow = async (req) => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
} else if (type === 'category') {
|
} else if (type === 'category') {
|
||||||
const exists = await categories.exists(id);
|
const [exists, allowed] = await Promise.all([
|
||||||
|
categories.exists(id),
|
||||||
|
privileges.categories.can('read', id, 'activitypub._constants.uid'),
|
||||||
|
]);
|
||||||
if (!exists) {
|
if (!exists) {
|
||||||
throw new Error('[[error:invalid-cid]]');
|
throw new Error('[[error:invalid-cid]]');
|
||||||
}
|
}
|
||||||
|
if (!allowed) {
|
||||||
|
return activitypub.send('uid', 0, req.body.actor, {
|
||||||
|
type: 'Reject',
|
||||||
|
object: {
|
||||||
|
type: 'Follow',
|
||||||
|
actor: req.body.actor,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const watchState = await categories.getWatchState([id], req.body.actor);
|
const watchState = await categories.getWatchState([id], req.body.actor);
|
||||||
if (watchState[0] !== categories.watchStates.tracking) {
|
if (watchState[0] !== categories.watchStates.tracking) {
|
||||||
await user.setCategoryWatchState(req.body.actor, id, categories.watchStates.tracking);
|
await user.setCategoryWatchState(req.body.actor, id, categories.watchStates.tracking);
|
||||||
}
|
}
|
||||||
|
|
||||||
await activitypub.send('cid', id, req.body.actor, {
|
activitypub.send('cid', id, req.body.actor, {
|
||||||
type: 'Accept',
|
type: 'Accept',
|
||||||
object: {
|
object: {
|
||||||
type: 'Follow',
|
type: 'Follow',
|
||||||
@@ -275,6 +303,16 @@ inbox.undo = async (req) => {
|
|||||||
throw new Error('[[error:invalid-pid]]');
|
throw new Error('[[error:invalid-pid]]');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const allowed = await privileges.posts.can('posts:upvote', id, activitypub._constants.uid);
|
||||||
|
if (!allowed) {
|
||||||
|
winston.info(`[activitypub/inbox.like] ${id} not allowed to be upvoted.`);
|
||||||
|
activitypub.send('uid', 0, actor, {
|
||||||
|
type: 'Reject',
|
||||||
|
object,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
await posts.unvote(id, actor);
|
await posts.unvote(id, actor);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ const requestCache = ttl({ ttl: 1000 * 60 * 5 }); // 5 minutes
|
|||||||
const ActivityPub = module.exports;
|
const ActivityPub = module.exports;
|
||||||
|
|
||||||
ActivityPub._constants = Object.freeze({
|
ActivityPub._constants = Object.freeze({
|
||||||
|
uid: -2,
|
||||||
publicAddress: 'https://www.w3.org/ns/activitystreams#Public',
|
publicAddress: 'https://www.w3.org/ns/activitystreams#Public',
|
||||||
});
|
});
|
||||||
ActivityPub._cache = requestCache;
|
ActivityPub._cache = requestCache;
|
||||||
@@ -163,7 +164,6 @@ ActivityPub.verify = async (req) => {
|
|||||||
return memo;
|
return memo;
|
||||||
}, []).join('\n');
|
}, []).join('\n');
|
||||||
|
|
||||||
|
|
||||||
// Verify the signature string via public key
|
// Verify the signature string via public key
|
||||||
try {
|
try {
|
||||||
// Retrieve public key from remote instance
|
// Retrieve public key from remote instance
|
||||||
@@ -188,6 +188,7 @@ ActivityPub.get = async (type, id, uri) => {
|
|||||||
const keyData = await ActivityPub.getPrivateKey(type, id);
|
const keyData = await ActivityPub.getPrivateKey(type, id);
|
||||||
const headers = id >= 0 ? await ActivityPub.sign(keyData, uri) : {};
|
const headers = id >= 0 ? await ActivityPub.sign(keyData, uri) : {};
|
||||||
winston.verbose(`[activitypub/get] ${uri}`);
|
winston.verbose(`[activitypub/get] ${uri}`);
|
||||||
|
try {
|
||||||
const { response, body } = await request.get(uri, {
|
const { response, body } = await request.get(uri, {
|
||||||
headers: {
|
headers: {
|
||||||
...headers,
|
...headers,
|
||||||
@@ -206,6 +207,10 @@ ActivityPub.get = async (type, id, uri) => {
|
|||||||
|
|
||||||
requestCache.set(cacheKey, body);
|
requestCache.set(cacheKey, body);
|
||||||
return body;
|
return body;
|
||||||
|
} catch (e) {
|
||||||
|
// Handle things like non-json body, etc.
|
||||||
|
throw new Error(`[[error:activitypub.get-failed]]`);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ActivityPub.send = async (type, id, targets, payload) => {
|
ActivityPub.send = async (type, id, targets, payload) => {
|
||||||
@@ -218,7 +223,7 @@ ActivityPub.send = async (type, id, targets, payload) => {
|
|||||||
let actor;
|
let actor;
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'uid': {
|
case 'uid': {
|
||||||
actor = `${nconf.get('url')}/uid/${id}`;
|
actor = `${nconf.get('url')}${id > 0 ? `/uid/${id}` : '/actor'}`;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ const winston = require('winston');
|
|||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
|
|
||||||
const db = require('../database');
|
const db = require('../database');
|
||||||
|
const privileges = require('../privileges');
|
||||||
const user = require('../user');
|
const user = require('../user');
|
||||||
const topics = require('../topics');
|
const topics = require('../topics');
|
||||||
const posts = require('../posts');
|
const posts = require('../posts');
|
||||||
@@ -205,8 +206,17 @@ Notes.assertTopic = async (uid, id) => {
|
|||||||
return tid;
|
return tid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const cid = tid ? await topics.getTopicField(tid, 'cid') : -1;
|
||||||
|
|
||||||
|
// Privilege check for local categories
|
||||||
|
const privilege = `topics:${tid ? 'reply' : 'create'}`;
|
||||||
|
const allowed = await privileges.categories.can(privilege, cid, activitypub._constants.uid);
|
||||||
|
console.log(privilege, cid, allowed);
|
||||||
|
if (!allowed) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
tid = tid || utils.generateUUID();
|
tid = tid || utils.generateUUID();
|
||||||
const cid = await topics.getTopicField(tid, 'cid');
|
|
||||||
|
|
||||||
let title = name || utils.decodeHTMLEntities(utils.stripHTMLTags(content));
|
let title = name || utils.decodeHTMLEntities(utils.stripHTMLTags(content));
|
||||||
if (title.length > 64) {
|
if (title.length > 64) {
|
||||||
@@ -229,7 +239,7 @@ Notes.assertTopic = async (uid, id) => {
|
|||||||
db.setObject(`topic:${tid}`, {
|
db.setObject(`topic:${tid}`, {
|
||||||
tid,
|
tid,
|
||||||
uid: authorId,
|
uid: authorId,
|
||||||
cid: cid || -1,
|
cid: cid,
|
||||||
mainPid,
|
mainPid,
|
||||||
title,
|
title,
|
||||||
slug: `${tid}/${slugify(title)}`,
|
slug: `${tid}/${slugify(title)}`,
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ module.exports = function (Categories) {
|
|||||||
['categories:name', 0, `${data.name.slice(0, 200).toLowerCase()}:${category.cid}`],
|
['categories:name', 0, `${data.name.slice(0, 200).toLowerCase()}:${category.cid}`],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
await privileges.categories.give(result.defaultPrivileges, category.cid, 'registered-users');
|
await privileges.categories.give(result.defaultPrivileges, category.cid, ['registered-users', 'fediverse']);
|
||||||
await privileges.categories.give(result.modPrivileges, category.cid, ['administrators', 'Global Moderators']);
|
await privileges.categories.give(result.modPrivileges, category.cid, ['administrators', 'Global Moderators']);
|
||||||
await privileges.categories.give(result.guestPrivileges, category.cid, ['guests', 'spiders']);
|
await privileges.categories.give(result.guestPrivileges, category.cid, ['guests', 'spiders']);
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
const nconf = require('nconf');
|
const nconf = require('nconf');
|
||||||
|
|
||||||
const meta = require('../../meta');
|
const meta = require('../../meta');
|
||||||
|
const privileges = require('../../privileges');
|
||||||
const posts = require('../../posts');
|
const posts = require('../../posts');
|
||||||
const topics = require('../../topics');
|
const topics = require('../../topics');
|
||||||
const categories = require('../../categories');
|
const categories = require('../../categories');
|
||||||
@@ -50,9 +51,10 @@ Actors.userBySlug = async function (req, res) {
|
|||||||
Actors.note = async function (req, res, next) {
|
Actors.note = async function (req, res, next) {
|
||||||
// technically a note isn't an actor, but it is here purely for organizational purposes.
|
// technically a note isn't an actor, but it is here purely for organizational purposes.
|
||||||
// but also, wouldn't it be wild if you could follow a note? lol.
|
// but also, wouldn't it be wild if you could follow a note? lol.
|
||||||
|
const allowed = await privileges.posts.can('topics:read', req.params.pid, activitypub._constants.uid);
|
||||||
const post = (await posts.getPostSummaryByPids([req.params.pid], req.uid, { stripTags: false })).pop();
|
const post = (await posts.getPostSummaryByPids([req.params.pid], req.uid, { stripTags: false })).pop();
|
||||||
if (!post) {
|
if (!allowed || !post) {
|
||||||
return next('route');
|
return res.sendStatus(404);
|
||||||
}
|
}
|
||||||
|
|
||||||
const payload = await activitypub.mocks.note(post);
|
const payload = await activitypub.mocks.note(post);
|
||||||
@@ -61,10 +63,11 @@ Actors.note = async function (req, res, next) {
|
|||||||
|
|
||||||
Actors.topic = async function (req, res, next) {
|
Actors.topic = async function (req, res, next) {
|
||||||
// When queried, a topic more or less returns the main pid's note representation
|
// When queried, a topic more or less returns the main pid's note representation
|
||||||
|
const allowed = await privileges.topics.can('topics:read', req.params.tid, activitypub._constants.uid);
|
||||||
const { mainPid, slug } = await topics.getTopicFields(req.params.tid, ['mainPid', 'slug']);
|
const { mainPid, slug } = await topics.getTopicFields(req.params.tid, ['mainPid', 'slug']);
|
||||||
const post = (await posts.getPostSummaryByPids([mainPid], req.uid, { stripTags: false })).pop();
|
const post = (await posts.getPostSummaryByPids([mainPid], req.uid, { stripTags: false })).pop();
|
||||||
if (!post) {
|
if (!allowed || !post) {
|
||||||
return next('route');
|
return res.sendStatus(404);
|
||||||
}
|
}
|
||||||
|
|
||||||
const payload = await activitypub.mocks.note(post);
|
const payload = await activitypub.mocks.note(post);
|
||||||
@@ -77,8 +80,11 @@ Actors.topic = async function (req, res, next) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Actors.category = async function (req, res, next) {
|
Actors.category = async function (req, res, next) {
|
||||||
const exists = await categories.exists(req.params.cid);
|
const [exists, allowed] = await Promise.all([
|
||||||
if (!exists) {
|
categories.exists(req.params.cid),
|
||||||
|
privileges.categories.can('find', req.params.cid, activitypub._constants.uid),
|
||||||
|
]);
|
||||||
|
if (!exists || !allowed) {
|
||||||
return next('route');
|
return next('route');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const nconf = require('nconf');
|
const nconf = require('nconf');
|
||||||
|
const winston = require('winston');
|
||||||
|
|
||||||
const user = require('../../user');
|
const user = require('../../user');
|
||||||
const activitypub = require('../../activitypub');
|
const activitypub = require('../../activitypub');
|
||||||
@@ -115,6 +116,7 @@ Controller.postInbox = async (req, res) => {
|
|||||||
// Note: underlying methods are internal use only, hence no exposure via src/api
|
// Note: underlying methods are internal use only, hence no exposure via src/api
|
||||||
const method = String(req.body.type).toLowerCase();
|
const method = String(req.body.type).toLowerCase();
|
||||||
if (!activitypub.inbox.hasOwnProperty(method)) {
|
if (!activitypub.inbox.hasOwnProperty(method)) {
|
||||||
|
winston.warn(`[activitypub/inbox] Received Activity of type ${method} but unable to handle. Ignoring.`);
|
||||||
return res.sendStatus(501);
|
return res.sendStatus(501);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
const categories = require('../../categories');
|
const categories = require('../../categories');
|
||||||
const privileges = require('../../privileges');
|
const privileges = require('../../privileges');
|
||||||
|
const utils = require('../../utils');
|
||||||
|
|
||||||
const privilegesController = module.exports;
|
const privilegesController = module.exports;
|
||||||
|
|
||||||
@@ -10,10 +11,10 @@ privilegesController.get = async function (req, res) {
|
|||||||
const isAdminPriv = req.params.cid === 'admin';
|
const isAdminPriv = req.params.cid === 'admin';
|
||||||
|
|
||||||
let privilegesData;
|
let privilegesData;
|
||||||
if (cid > 0) {
|
if (cid === 0) {
|
||||||
privilegesData = await privileges.categories.list(cid);
|
|
||||||
} else if (cid === 0) {
|
|
||||||
privilegesData = await (isAdminPriv ? privileges.admin.list(req.uid) : privileges.global.list());
|
privilegesData = await (isAdminPriv ? privileges.admin.list(req.uid) : privileges.global.list());
|
||||||
|
} else if (utils.isNumber(cid)) {
|
||||||
|
privilegesData = await privileges.categories.list(cid);
|
||||||
}
|
}
|
||||||
|
|
||||||
const categoriesData = [{
|
const categoriesData = [{
|
||||||
@@ -24,6 +25,12 @@ privilegesController.get = async function (req, res) {
|
|||||||
cid: 'admin',
|
cid: 'admin',
|
||||||
name: '[[admin/manage/privileges:admin]]',
|
name: '[[admin/manage/privileges:admin]]',
|
||||||
icon: 'fa-lock',
|
icon: 'fa-lock',
|
||||||
|
}, {
|
||||||
|
cid: -1,
|
||||||
|
name: '[[activitypub:category.name]]',
|
||||||
|
icon: 'fa-globe',
|
||||||
|
bgColor: '#0000ff',
|
||||||
|
color: '#ffffff',
|
||||||
}];
|
}];
|
||||||
|
|
||||||
let selectedCategory;
|
let selectedCategory;
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ require('./cache')(Groups);
|
|||||||
|
|
||||||
Groups.BANNED_USERS = 'banned-users';
|
Groups.BANNED_USERS = 'banned-users';
|
||||||
|
|
||||||
Groups.ephemeralGroups = ['guests', 'spiders'];
|
Groups.ephemeralGroups = ['guests', 'spiders', 'fediverse'];
|
||||||
|
|
||||||
Groups.systemGroups = [
|
Groups.systemGroups = [
|
||||||
'registered-users',
|
'registered-users',
|
||||||
@@ -55,7 +55,7 @@ Groups.removeEphemeralGroups = function (groups) {
|
|||||||
return groups;
|
return groups;
|
||||||
};
|
};
|
||||||
|
|
||||||
const isPrivilegeGroupRegex = /^cid:(?:\d+|admin):privileges:[\w\-:]+$/;
|
const isPrivilegeGroupRegex = /^cid:(?:-?\d+|admin):privileges:[\w\-:]+$/;
|
||||||
Groups.isPrivilegeGroup = function (groupName) {
|
Groups.isPrivilegeGroup = function (groupName) {
|
||||||
return isPrivilegeGroupRegex.test(groupName);
|
return isPrivilegeGroupRegex.test(groupName);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -436,6 +436,37 @@ async function giveGlobalPrivileges() {
|
|||||||
]), 'Global Moderators');
|
]), 'Global Moderators');
|
||||||
await privileges.global.give(['groups:view:users', 'groups:view:tags', 'groups:view:groups'], 'guests');
|
await privileges.global.give(['groups:view:users', 'groups:view:tags', 'groups:view:groups'], 'guests');
|
||||||
await privileges.global.give(['groups:view:users', 'groups:view:tags', 'groups:view:groups'], 'spiders');
|
await privileges.global.give(['groups:view:users', 'groups:view:tags', 'groups:view:groups'], 'spiders');
|
||||||
|
await privileges.global.give(['groups:view:users'], 'fediverse');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function giveWorldPrivileges() {
|
||||||
|
// should match privilege assignment logic in src/categories/create.js EXCEPT commented one liner below
|
||||||
|
const privileges = require('./privileges');
|
||||||
|
const defaultPrivileges = [
|
||||||
|
'groups:find',
|
||||||
|
'groups:read',
|
||||||
|
'groups:topics:read',
|
||||||
|
'groups:topics:create',
|
||||||
|
'groups:topics:reply',
|
||||||
|
'groups:topics:tag',
|
||||||
|
'groups:posts:edit',
|
||||||
|
'groups:posts:history',
|
||||||
|
'groups:posts:delete',
|
||||||
|
'groups:posts:upvote',
|
||||||
|
'groups:posts:downvote',
|
||||||
|
'groups:topics:delete',
|
||||||
|
];
|
||||||
|
const modPrivileges = defaultPrivileges.concat([
|
||||||
|
'groups:topics:schedule',
|
||||||
|
'groups:posts:view_deleted',
|
||||||
|
'groups:purge',
|
||||||
|
]);
|
||||||
|
const guestPrivileges = ['groups:find', 'groups:read', 'groups:topics:read'];
|
||||||
|
|
||||||
|
await privileges.categories.give(defaultPrivileges, -1, ['registered-users']);
|
||||||
|
await privileges.categories.give(defaultPrivileges.slice(3), -1, ['fediverse']); // different priv set for fediverse
|
||||||
|
await privileges.categories.give(modPrivileges, -1, ['administrators', 'Global Moderators']);
|
||||||
|
await privileges.categories.give(guestPrivileges, -1, ['guests', 'spiders']);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createCategories() {
|
async function createCategories() {
|
||||||
@@ -588,6 +619,7 @@ install.setup = async function () {
|
|||||||
const adminInfo = await createAdministrator();
|
const adminInfo = await createAdministrator();
|
||||||
await createGlobalModeratorsGroup();
|
await createGlobalModeratorsGroup();
|
||||||
await giveGlobalPrivileges();
|
await giveGlobalPrivileges();
|
||||||
|
await giveWorldPrivileges();
|
||||||
await createMenuItems();
|
await createMenuItems();
|
||||||
await createWelcomePost();
|
await createWelcomePost();
|
||||||
await enableDefaultPlugins();
|
await enableDefaultPlugins();
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ middleware.validate = async function (req, res, next) {
|
|||||||
const { actor, object } = req.body;
|
const { actor, object } = req.body;
|
||||||
|
|
||||||
// Origin checking
|
// Origin checking
|
||||||
if (typeof object !== 'string') {
|
if (typeof object !== 'string' && object.hasOwnProperty('id')) {
|
||||||
const actorHostname = new URL(actor).hostname;
|
const actorHostname = new URL(actor).hostname;
|
||||||
const objectHostname = new URL(object.id).hostname;
|
const objectHostname = new URL(object.id).hostname;
|
||||||
if (actorHostname !== objectHostname) {
|
if (actorHostname !== objectHostname) {
|
||||||
|
|||||||
@@ -154,11 +154,6 @@ privsCategories.can = async function (privilege, cid, uid) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// temporary
|
|
||||||
if (cid === -1) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const [disabled, isAdmin, isAllowed] = await Promise.all([
|
const [disabled, isAdmin, isAllowed] = await Promise.all([
|
||||||
categories.getCategoryField(cid, 'disabled'),
|
categories.getCategoryField(cid, 'disabled'),
|
||||||
user.isAdministrator(uid),
|
user.isAdministrator(uid),
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ const helpers = module.exports;
|
|||||||
const uidToSystemGroup = {
|
const uidToSystemGroup = {
|
||||||
0: 'guests',
|
0: 'guests',
|
||||||
'-1': 'spiders',
|
'-1': 'spiders',
|
||||||
|
'-2': 'fediverse',
|
||||||
};
|
};
|
||||||
|
|
||||||
helpers.isUsersAllowedTo = async function (privilege, uids, cid) {
|
helpers.isUsersAllowedTo = async function (privilege, uids, cid) {
|
||||||
|
|||||||
38
src/upgrades/4.0.0/assign_world_privileges.js
Normal file
38
src/upgrades/4.0.0/assign_world_privileges.js
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
// const db = require('../../database');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
name: 'Assigning default privileges to "World" pseudo-category',
|
||||||
|
timestamp: Date.UTC(2024, 1, 22),
|
||||||
|
method: async () => {
|
||||||
|
const privileges = require('../../privileges');
|
||||||
|
|
||||||
|
// should match privilege assignment logic in src/categories/create.js EXCEPT commented one liner below
|
||||||
|
const defaultPrivileges = [
|
||||||
|
'groups:find',
|
||||||
|
'groups:read',
|
||||||
|
'groups:topics:read',
|
||||||
|
'groups:topics:create',
|
||||||
|
'groups:topics:reply',
|
||||||
|
'groups:topics:tag',
|
||||||
|
'groups:posts:edit',
|
||||||
|
'groups:posts:history',
|
||||||
|
'groups:posts:delete',
|
||||||
|
'groups:posts:upvote',
|
||||||
|
'groups:posts:downvote',
|
||||||
|
'groups:topics:delete',
|
||||||
|
];
|
||||||
|
const modPrivileges = defaultPrivileges.concat([
|
||||||
|
'groups:topics:schedule',
|
||||||
|
'groups:posts:view_deleted',
|
||||||
|
'groups:purge',
|
||||||
|
]);
|
||||||
|
const guestPrivileges = ['groups:find', 'groups:read', 'groups:topics:read'];
|
||||||
|
|
||||||
|
await privileges.categories.give(defaultPrivileges, -1, ['registered-users']);
|
||||||
|
await privileges.categories.give(defaultPrivileges.slice(3), -1, ['fediverse']); // different priv set for fediverse
|
||||||
|
await privileges.categories.give(modPrivileges, -1, ['administrators', 'Global Moderators']);
|
||||||
|
await privileges.categories.give(guestPrivileges, -1, ['guests', 'spiders']);
|
||||||
|
},
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user