mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-10-26 16:46:12 +01:00
fix: additional tests for remote privileges, enforcing privileges for remote edits and deletes
This commit is contained in:
@@ -95,6 +95,12 @@ inbox.update = async (req) => {
|
|||||||
try {
|
try {
|
||||||
switch (true) {
|
switch (true) {
|
||||||
case isNote: {
|
case isNote: {
|
||||||
|
const cid = await posts.getCidByPid(object.id);
|
||||||
|
const allowed = await privileges.categories.can('posts:edit', cid, activitypub._constants.uid);
|
||||||
|
if (!allowed) {
|
||||||
|
throw new Error('[[error:no-privileges]]');
|
||||||
|
}
|
||||||
|
|
||||||
const postData = await activitypub.mocks.post(object);
|
const postData = await activitypub.mocks.post(object);
|
||||||
postData.tags = await activitypub.notes._normalizeTags(postData._activitypub.tag, postData.cid);
|
postData.tags = await activitypub.notes._normalizeTags(postData._activitypub.tag, postData.cid);
|
||||||
await posts.edit(postData);
|
await posts.edit(postData);
|
||||||
@@ -200,7 +206,7 @@ inbox.delete = async (req) => {
|
|||||||
|
|
||||||
const objectHostname = new URL(pid).hostname;
|
const objectHostname = new URL(pid).hostname;
|
||||||
if (actorHostname !== objectHostname) {
|
if (actorHostname !== objectHostname) {
|
||||||
throw new Error('[[error:activitypub.origin-mismatch]]');
|
return reject('Delete', object, actor);
|
||||||
}
|
}
|
||||||
|
|
||||||
const [isNote/* , isActor */] = await Promise.all([
|
const [isNote/* , isActor */] = await Promise.all([
|
||||||
@@ -210,6 +216,12 @@ inbox.delete = async (req) => {
|
|||||||
|
|
||||||
switch (true) {
|
switch (true) {
|
||||||
case isNote: {
|
case isNote: {
|
||||||
|
const cid = await posts.getCidByPid(pid);
|
||||||
|
const allowed = await privileges.categories.can('posts:edit', cid, activitypub._constants.uid);
|
||||||
|
if (!allowed) {
|
||||||
|
return reject('Delete', object, actor);
|
||||||
|
}
|
||||||
|
|
||||||
const uid = await posts.getPostField(pid, 'uid');
|
const uid = await posts.getPostField(pid, 'uid');
|
||||||
await activitypub.feps.announce(pid, req.body);
|
await activitypub.feps.announce(pid, req.body);
|
||||||
await api.posts[method]({ uid }, { pid });
|
await api.posts[method]({ uid }, { pid });
|
||||||
|
|||||||
@@ -202,3 +202,26 @@ Helpers.mocks.update = (override = {}) => {
|
|||||||
return { activity };
|
return { activity };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Helpers.mocks.delete = (override = {}) => {
|
||||||
|
let actor = override.actor;
|
||||||
|
let object = override.object;
|
||||||
|
if (!actor) {
|
||||||
|
({ id: actor } = Helpers.mocks.person());
|
||||||
|
}
|
||||||
|
if (!object) {
|
||||||
|
({ id: object } = Helpers.mocks.note());
|
||||||
|
}
|
||||||
|
|
||||||
|
const activity = {
|
||||||
|
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||||
|
id: `${Helpers.mocks._baseUrl}/delete/${encodeURIComponent(object.id || object)}`,
|
||||||
|
type: 'Delete',
|
||||||
|
to: [activitypub._constants.publicAddress],
|
||||||
|
cc: [`${actor}/followers`],
|
||||||
|
actor,
|
||||||
|
object,
|
||||||
|
};
|
||||||
|
|
||||||
|
return { activity };
|
||||||
|
};
|
||||||
|
|
||||||
|
|||||||
204
test/activitypub/privileges.js
Normal file
204
test/activitypub/privileges.js
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const assert = require('assert');
|
||||||
|
const nconf = require('nconf');
|
||||||
|
|
||||||
|
const db = require('../mocks/databasemock');
|
||||||
|
const user = require('../../src/user');
|
||||||
|
const topics = require('../../src/topics');
|
||||||
|
const posts = require('../../src/posts');
|
||||||
|
const categories = require('../../src/categories');
|
||||||
|
const privileges = require('../../src/privileges');
|
||||||
|
const meta = require('../../src/meta');
|
||||||
|
const install = require('../../src/install');
|
||||||
|
const utils = require('../../src/utils');
|
||||||
|
const activitypub = require('../../src/activitypub');
|
||||||
|
|
||||||
|
const helpers = require('./helpers');
|
||||||
|
|
||||||
|
describe('Privilege logic for remote users/content (ActivityPub)', () => {
|
||||||
|
before(async () => {
|
||||||
|
meta.config.activitypubEnabled = 1;
|
||||||
|
// await install.giveWorldPrivileges();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('"fediverse" pseudo-user', () => {
|
||||||
|
describe('no privileges given', () => {
|
||||||
|
let uid;
|
||||||
|
let cid;
|
||||||
|
let topicData;
|
||||||
|
let postData;
|
||||||
|
let mainPid;
|
||||||
|
let handle;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
uid = await user.create({ username: utils.generateUUID() });
|
||||||
|
({ cid } = await categories.create({ name: utils.generateUUID() }));
|
||||||
|
({ topicData, postData } = await topics.post({
|
||||||
|
cid,
|
||||||
|
uid,
|
||||||
|
title: utils.generateUUID(),
|
||||||
|
content: utils.generateUUID(),
|
||||||
|
}));
|
||||||
|
handle = await categories.getCategoryField(cid, 'handle');
|
||||||
|
const privsToRemove = await privileges.categories.getGroupPrivilegeList();
|
||||||
|
await privileges.categories.rescind(privsToRemove, cid, ['fediverse']);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('incoming requests', () => {
|
||||||
|
it('should not respond to a webfinger request to a category\'s handle', async () => {
|
||||||
|
const response = await activitypub.helpers.query(`${handle}@${nconf.get('url_parsed').hostname}`);
|
||||||
|
assert.strictEqual(response, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not respond to a request for the category actor', async () => {
|
||||||
|
await assert.rejects(
|
||||||
|
activitypub.get('uid', uid, `${nconf.get('url')}/category/${cid}`),
|
||||||
|
{ message: '[[error:activitypub.get-failed]]' }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not respond to a request for a topic collection', async () => {
|
||||||
|
await assert.rejects(
|
||||||
|
activitypub.get('uid', uid, `${nconf.get('url')}/topic/${topicData.tid}`),
|
||||||
|
{ message: '[[error:activitypub.get-failed]]' }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not respond to a request for a post', async () => {
|
||||||
|
await assert.rejects(
|
||||||
|
activitypub.get('uid', uid, `${nconf.get('url')}/post/${topicData.mainPid}`),
|
||||||
|
{ message: '[[error:activitypub.get-failed]]' }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('incoming activities', () => {
|
||||||
|
describe('Create(Note)', () => {
|
||||||
|
let note;
|
||||||
|
let activity;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
({ note } = helpers.mocks.note({
|
||||||
|
cc: [`${nconf.get('url')}/category/${cid}`],
|
||||||
|
}));
|
||||||
|
({ activity } = helpers.mocks.create(note));
|
||||||
|
await activitypub.inbox.create({ body: activity });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not assert the note', async () => {
|
||||||
|
const exists = await posts.exists(note.id);
|
||||||
|
assert.strictEqual(exists, false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Update(Note)', () => {
|
||||||
|
let note;
|
||||||
|
let activity;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
({ note } = helpers.mocks.note({
|
||||||
|
cc: [`${nconf.get('url')}/category/${cid}`],
|
||||||
|
}));
|
||||||
|
({ activity } = helpers.mocks.create(note));
|
||||||
|
await privileges.categories.give(['groups:topics:create'], cid, ['fediverse']);
|
||||||
|
await activitypub.inbox.create({ body: activity });
|
||||||
|
});
|
||||||
|
|
||||||
|
after(async () => {
|
||||||
|
await privileges.categories.rescind(['groups:topics:create'], cid, ['fediverse']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should assert the note', async () => {
|
||||||
|
const exists = await posts.exists(note.id);
|
||||||
|
assert.strictEqual(exists, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not allow edits to the note', async () => {
|
||||||
|
const oldContent = note.content;
|
||||||
|
note.content = 'new content';
|
||||||
|
({ activity } = helpers.mocks.update({
|
||||||
|
object: note,
|
||||||
|
}));
|
||||||
|
|
||||||
|
await activitypub.inbox.update({ body: activity });
|
||||||
|
|
||||||
|
const postData = await posts.getPostData(note.id);
|
||||||
|
assert.strictEqual(postData.content, oldContent);
|
||||||
|
assert.strictEqual(postData.edited, 0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Delete(Note)', () => {
|
||||||
|
let note;
|
||||||
|
let activity;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
({ note } = helpers.mocks.note({
|
||||||
|
cc: [`${nconf.get('url')}/category/${cid}`],
|
||||||
|
}));
|
||||||
|
({ activity } = helpers.mocks.create(note));
|
||||||
|
await privileges.categories.give(['groups:topics:create'], cid, ['fediverse']);
|
||||||
|
await activitypub.inbox.create({ body: activity });
|
||||||
|
});
|
||||||
|
|
||||||
|
after(async () => {
|
||||||
|
await privileges.categories.rescind(['groups:topics:create'], cid, ['fediverse']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should assert the note', async () => {
|
||||||
|
const exists = await posts.exists(note.id);
|
||||||
|
assert.strictEqual(exists, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should ignore remote deletion of said note', async () => {
|
||||||
|
({ activity } = helpers.mocks.delete({ object: note }));
|
||||||
|
await activitypub.inbox.delete({ body: activity });
|
||||||
|
|
||||||
|
const exists = await posts.exists(note.id);
|
||||||
|
assert.strictEqual(exists, true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('outgoing requests', () => {
|
||||||
|
it('should not federate out a new post', async () => {
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not federate out a post edit', async () => {
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not federate out a post deletion', async () => {
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not federate out a post announce', async () => {
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('regular privilege set', () => {
|
||||||
|
let cid;
|
||||||
|
let handle;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
({ cid } = await categories.create({ name: utils.generateUUID() }));
|
||||||
|
handle = await categories.getCategoryField(cid, 'handle');
|
||||||
|
const privsToRemove = await privileges.categories.getGroupPrivilegeList();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('groups:find', () => {
|
||||||
|
it('should return webfinger response to a category\'s handle', async () => {
|
||||||
|
const response = await activitypub.helpers.query(`${handle}@${nconf.get('url_parsed').hostname}`);
|
||||||
|
|
||||||
|
assert(response);
|
||||||
|
assert.strictEqual(response.subject, `acct:${handle}@${nconf.get('url_parsed').hostname}`);
|
||||||
|
assert.strictEqual(response.hostname, nconf.get('url_parsed').hostname);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user