diff --git a/src/activitypub/actors.js b/src/activitypub/actors.js index 6516523849..1962294a24 100644 --- a/src/activitypub/actors.js +++ b/src/activitypub/actors.js @@ -401,12 +401,10 @@ Actors.assertGroup = async (ids, options = {}) => { ]); // Privilege mask - const masksAdd = categoryObjs.reduce((sets, category) => { - if (category?._activitypub?.postingRestrictedToMods) { - sets.push(`cid:${category.cid}:privilegeMask`); - } - return sets; - }, []); + const [masksAdd, masksRemove] = categoryObjs.reduce(([add, remove], category) => { + (category?._activitypub?.postingRestrictedToMods ? add : remove).push(`cid:${category.cid}:privilegeMask`); + return [add, remove]; + }, [[], []]); await Promise.all([ db.setObjectBulk(bulkSet), @@ -415,6 +413,7 @@ Actors.assertGroup = async (ids, options = {}) => { db.setObject('handle:cid', queries.handleAdd), _migratePersonToGroup(categoryObjs), db.setsAdd(masksAdd, 'topics:create'), + db.setsRemove(masksRemove, 'topics:create'), ]); return categoryObjs; diff --git a/test/activitypub/privileges.js b/test/activitypub/privileges.js index 89fdb83245..695c2a2226 100644 --- a/test/activitypub/privileges.js +++ b/test/activitypub/privileges.js @@ -250,7 +250,7 @@ describe('Privilege masking', () => { }); }); - describe('postingRestrictedToMods', () => { + describe('postingRestrictedToMods (true on assert)', () => { let cid; let uid; @@ -297,4 +297,147 @@ describe('Privilege masking', () => { assert(!set['topics:create']); }); }); + + describe('postingRestrictedToMods (true on assert and re-assertion)', () => { + let cid; + let uid; + + before(async () => { + // Set up a mock group with `postingRestrictedToMods` bit set + ({ id: cid } = helpers.mocks.group({ + postingRestrictedToMods: true, + })); + + // Unprivileged user for testing + uid = await user.create({ username: utils.generateUUID().slice(0, 10) }); + + // Assert group, then re-assert again + await activitypub.actors.assertGroup([cid]); + await activitypub.actors.assertGroup([cid], { update: true }); + }); + + it('should fail the privileges .can() check if requested', async () => { + const can = await privileges.categories.can('topics:create', cid, uid); + assert(!can); + }); + + it('should return false in the privilege set when requested', async () => { + const set = await privileges.categories.get(cid, uid); + + assert(set); + assert(!set['topics:create']); + }); + }); + + describe('postingRestrictedToMods (true on assert, false on update)', () => { + let cid; + let uid; + + before(async () => { + // Set up a mock group with `postingRestrictedToMods` bit set + ({ id: cid } = helpers.mocks.group({ + postingRestrictedToMods: true, + })); + + // Unprivileged user for testing + uid = await user.create({ username: utils.generateUUID().slice(0, 10) }); + + await activitypub.actors.assertGroup([cid]); + + // Group updated "remotely" + helpers.mocks.group({ + id: cid, + postingRestrictedToMods: false, + }); + }); + + it('should remove the privilege mask if the bit is not present on group actor update', async () => { + let can = await privileges.categories.can('topics:create', cid, uid); + assert(!can, 'Initial state should be denied due to mask.'); + + // Group re-assertion + await activitypub.actors.assertGroup([cid], { update: true }); + + // Ensure mask is gone from db + const exists = await db.exists(`cid:${cid}:privilegeMask`); + assert(!exists); + + can = await privileges.categories.can('topics:create', cid, uid); + assert(can, 'Privilege should be restored after mask removal.'); + }); + }); + + describe('postingRestrictedToMods (true on assert, property missing on update)', () => { + let cid; + let uid; + + before(async () => { + // Set up a mock group with `postingRestrictedToMods` bit set + ({ id: cid } = helpers.mocks.group({ + postingRestrictedToMods: true, + })); + + // Unprivileged user for testing + uid = await user.create({ username: utils.generateUUID().slice(0, 10) }); + + await activitypub.actors.assertGroup([cid]); + + // Group updated "remotely" + helpers.mocks.group({ + id: cid, + }); + }); + + it('should remove the privilege mask if the bit is not present on group actor update', async () => { + let can = await privileges.categories.can('topics:create', cid, uid); + assert(!can, 'Initial state should be denied due to mask.'); + + // Group re-assertion + await activitypub.actors.assertGroup([cid], { update: true }); + + // Ensure mask is gone from db + const exists = await db.exists(`cid:${cid}:privilegeMask`); + assert(!exists); + + can = await privileges.categories.can('topics:create', cid, uid); + assert(can, 'Privilege should be restored after mask removal.'); + }); + }); + + describe('postingRestrictedToMods (false on assert, true on update)', () => { + let cid; + let uid; + + before(async () => { + // Set up a mock group with `postingRestrictedToMods` bit set to false + ({ id: cid } = helpers.mocks.group({ + postingRestrictedToMods: false, + })); + + // Unprivileged user for testing + uid = await user.create({ username: utils.generateUUID().slice(0, 10) }); + + await activitypub.actors.assertGroup([cid]); + + + // Update group "remotely", re-assert + helpers.mocks.group({ + id: cid, + postingRestrictedToMods: true, + }); + await activitypub.actors.assertGroup([cid], { update: true }); + }); + + it('should fail the privileges .can() check if requested', async () => { + const can = await privileges.categories.can('topics:create', cid, uid); + assert(!can); + }); + + it('should return false in the privilege set when requested', async () => { + const set = await privileges.categories.get(cid, uid); + + assert(set); + assert(!set['topics:create']); + }); + }); }); \ No newline at end of file