Skip to content

Commit 04e9bb4

Browse files
committed
feat: Prevent invite links from being generated on abac rooms (#37325)
1 parent 87dd779 commit 04e9bb4

File tree

3 files changed

+193
-2
lines changed

3 files changed

+193
-2
lines changed

apps/meteor/app/invites/server/functions/findOrCreateInvite.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,13 @@ export const findOrCreateInvite = async (userId: string, invite: Pick<IInvite, '
6363
});
6464
}
6565

66+
if (settings.get('ABAC_Enabled') && room?.abacAttributes?.length) {
67+
throw new Meteor.Error('error-invalid-room', 'Room is ABAC managed', {
68+
method: 'findOrCreateInvite',
69+
field: 'rid',
70+
});
71+
}
72+
6673
if (!(await roomCoordinator.getRoomDirectives(room.t).allowMemberAction(room, RoomMemberActions.INVITE, userId))) {
6774
throw new Meteor.Error('error-room-type-not-allowed', 'Cannot create invite links for this room type', {
6875
method: 'findOrCreateInvite',

apps/meteor/app/invites/server/functions/validateInviteToken.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { Invites, Rooms } from '@rocket.chat/models';
22
import { Meteor } from 'meteor/meteor';
33

4+
import { settings } from '../../../settings/server';
5+
46
export const validateInviteToken = async (token: string) => {
57
if (!token || typeof token !== 'string') {
68
throw new Meteor.Error('error-invalid-token', 'The invite token is invalid.', {
@@ -25,6 +27,13 @@ export const validateInviteToken = async (token: string) => {
2527
});
2628
}
2729

30+
if (settings.get('ABAC_Enabled') && room?.abacAttributes?.length) {
31+
throw new Meteor.Error('error-invalid-room', 'Room is ABAC managed', {
32+
method: 'validateInviteToken',
33+
field: 'rid',
34+
});
35+
}
36+
2837
if (inviteData.expires && new Date(inviteData.expires).getTime() <= Date.now()) {
2938
throw new Meteor.Error('error-invite-expired', 'The invite token has expired.', {
3039
method: 'validateInviteToken',

apps/meteor/tests/end-to-end/api/abac.ts

Lines changed: 177 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,29 @@
11
import type { Credentials } from '@rocket.chat/api-client';
2-
import type { IRoom, IUser } from '@rocket.chat/core-typings';
2+
import type { IAbacAttributeDefinition, IRoom, IUser } from '@rocket.chat/core-typings';
33
import { expect } from 'chai';
44
import { before, after, describe, it } from 'mocha';
5+
import { MongoClient } from 'mongodb';
56

67
import { getCredentials, request, credentials } from '../../data/api-data';
78
import { updatePermission, updateSetting } from '../../data/permissions.helper';
89
import { createRoom, deleteRoom } from '../../data/rooms.helper';
910
import { deleteTeam } from '../../data/teams.helper';
1011
import { password } from '../../data/user';
1112
import { createUser, deleteUser, login } from '../../data/users.helper';
12-
import { IS_EE } from '../../e2e/config/constants';
13+
import { IS_EE, URL_MONGODB } from '../../e2e/config/constants';
14+
15+
// NOTE: This manipulates the DB directly to add ABAC attributes to a user
16+
// The idea is to avoid having to go through LDAP to add info to the user
17+
let connection: MongoClient;
18+
const addAbacAttributesToUserDirectly = async (userId: string, abacAttributes: IAbacAttributeDefinition[]) => {
19+
await connection.db().collection('users').updateOne(
20+
{
21+
// @ts-expect-error - collection types for _id
22+
_id: userId,
23+
},
24+
{ $set: { abacAttributes } },
25+
);
26+
};
1327

1428
(IS_EE ? describe : describe.skip)('[ABAC] (Enterprise Only)', function () {
1529
this.retries(0);
@@ -28,6 +42,8 @@ import { IS_EE } from '../../e2e/config/constants';
2842
before((done) => getCredentials(done));
2943

3044
before(async () => {
45+
connection = await MongoClient.connect(URL_MONGODB);
46+
3147
await updatePermission('abac-management', ['admin']);
3248
await updateSetting('ABAC_Enabled', true);
3349

@@ -40,6 +56,9 @@ import { IS_EE } from '../../e2e/config/constants';
4056
after(async () => {
4157
await deleteRoom({ type: 'p', roomId: testRoom._id });
4258
await deleteUser(unauthorizedUser);
59+
await updateSetting('ABAC_Enabled', false);
60+
61+
await connection.close();
4362
});
4463

4564
const v1 = '/api/v1';
@@ -1235,5 +1254,161 @@ import { IS_EE } from '../../e2e/config/constants';
12351254
});
12361255
});
12371256
});
1257+
1258+
describe('Invite links & ABAC management', () => {
1259+
const inviteAttrKey = `invite_attr_${Date.now()}`;
1260+
const validateAttrKey = `invite_val_attr_${Date.now()}`;
1261+
let managedRoomId: string;
1262+
let plainRoomId: string;
1263+
let plainRoomInviteToken: string;
1264+
const createdInviteIds: string[] = [];
1265+
1266+
before(async () => {
1267+
await updatePermission('create-invite-links', ['admin']);
1268+
await updateSetting('ABAC_Enabled', true);
1269+
});
1270+
1271+
it('should create an invite link for a private room without ABAC attributes when ABAC is enabled', async () => {
1272+
const plainRoom = (await createRoom({ type: 'p', name: `invite-plain-${Date.now()}` })).body.group;
1273+
plainRoomId = plainRoom._id;
1274+
1275+
await request
1276+
.post(`${v1}/findOrCreateInvite`)
1277+
.set(credentials)
1278+
.send({ rid: plainRoomId, days: 1, maxUses: 0 })
1279+
.expect(200)
1280+
.expect((res) => {
1281+
expect(res.body).to.have.property('success', true);
1282+
expect(res.body).to.have.property('rid', plainRoomId);
1283+
expect(res.body).to.have.property('days', 1);
1284+
expect(res.body).to.have.property('maxUses', 0);
1285+
plainRoomInviteToken = res.body._id;
1286+
createdInviteIds.push(plainRoomInviteToken);
1287+
});
1288+
});
1289+
1290+
it('validateInviteToken should return valid=true for token from non-ABAC managed room', async () => {
1291+
await request
1292+
.post(`${v1}/validateInviteToken`)
1293+
.send({ token: plainRoomInviteToken })
1294+
.expect(200)
1295+
.expect((res) => {
1296+
expect(res.body).to.have.property('success', true);
1297+
expect(res.body).to.have.property('valid', true);
1298+
});
1299+
});
1300+
1301+
it('validateInviteToken should return valid=false for random invalid token', async () => {
1302+
await request
1303+
.post(`${v1}/validateInviteToken`)
1304+
.send({ token: 'invalid123' })
1305+
.expect(200)
1306+
.expect((res) => {
1307+
expect(res.body).to.have.property('success', true);
1308+
expect(res.body).to.have.property('valid', false);
1309+
});
1310+
});
1311+
1312+
it('validateInviteToken should return valid=false after room becomes ABAC managed', async () => {
1313+
await request
1314+
.post(`${v1}/abac/attributes`)
1315+
.set(credentials)
1316+
.send({ key: validateAttrKey, values: ['one'] })
1317+
.expect(200)
1318+
.expect((res) => {
1319+
expect(res.body).to.have.property('success', true);
1320+
});
1321+
1322+
await addAbacAttributesToUserDirectly(credentials['X-User-Id'], [{ key: validateAttrKey, values: ['one'] }]);
1323+
1324+
await request
1325+
.post(`${v1}/abac/rooms/${plainRoomId}/attributes/${validateAttrKey}`)
1326+
.set(credentials)
1327+
.send({ values: ['one'] })
1328+
.expect(200)
1329+
.expect((res) => {
1330+
expect(res.body).to.have.property('success', true);
1331+
});
1332+
1333+
await request
1334+
.post(`${v1}/validateInviteToken`)
1335+
.send({ token: plainRoomInviteToken })
1336+
.expect(200)
1337+
.expect((res) => {
1338+
expect(res.body).to.have.property('success', true);
1339+
expect(res.body).to.have.property('valid', false);
1340+
});
1341+
});
1342+
1343+
it('validateInviteToken should return valid=true again after disabling ABAC', async () => {
1344+
await updateSetting('ABAC_Enabled', false);
1345+
1346+
await request
1347+
.post(`${v1}/validateInviteToken`)
1348+
.send({ token: plainRoomInviteToken })
1349+
.expect(200)
1350+
.expect((res) => {
1351+
expect(res.body).to.have.property('success', true);
1352+
expect(res.body).to.have.property('valid', true);
1353+
});
1354+
1355+
await updateSetting('ABAC_Enabled', true);
1356+
});
1357+
1358+
it('should fail creating an invite link for an ABAC managed room while ABAC is enabled', async () => {
1359+
const managedRoom = (await createRoom({ type: 'p', name: `invite-managed-${Date.now()}` })).body.group;
1360+
managedRoomId = managedRoom._id;
1361+
1362+
await request
1363+
.post(`${v1}/abac/attributes`)
1364+
.set(credentials)
1365+
.send({ key: inviteAttrKey, values: ['one'] })
1366+
.expect(200)
1367+
.expect((res) => {
1368+
expect(res.body).to.have.property('success', true);
1369+
});
1370+
1371+
await addAbacAttributesToUserDirectly(credentials['X-User-Id'], [{ key: inviteAttrKey, values: ['one'] }]);
1372+
1373+
await request
1374+
.post(`${v1}/abac/rooms/${managedRoomId}/attributes/${inviteAttrKey}`)
1375+
.set(credentials)
1376+
.send({ values: ['one'] })
1377+
.expect(200)
1378+
.expect((res) => {
1379+
expect(res.body).to.have.property('success', true);
1380+
});
1381+
1382+
await request
1383+
.post(`${v1}/findOrCreateInvite`)
1384+
.set(credentials)
1385+
.send({ rid: managedRoomId, days: 1, maxUses: 0 })
1386+
.expect(400)
1387+
.expect((res) => {
1388+
expect(res.body).to.have.property('success', false);
1389+
expect(res.body).to.have.property('errorType', 'error-invalid-room');
1390+
expect(res.body).to.have.property('error').that.includes('Room is ABAC managed');
1391+
});
1392+
});
1393+
1394+
it('should allow creating an invite link for previously ABAC managed room after disabling ABAC', async () => {
1395+
await updateSetting('ABAC_Enabled', false);
1396+
1397+
await request
1398+
.post(`${v1}/findOrCreateInvite`)
1399+
.set(credentials)
1400+
.send({ rid: managedRoomId, days: 1, maxUses: 0 })
1401+
.expect(200)
1402+
.expect((res) => {
1403+
expect(res.body).to.have.property('success', true);
1404+
expect(res.body).to.have.property('rid', managedRoomId);
1405+
createdInviteIds.push(res.body._id);
1406+
});
1407+
});
1408+
1409+
after(async () => {
1410+
await Promise.all(createdInviteIds.map((id) => request.delete(`${v1}/removeInvite/${id}`).set(credentials)));
1411+
});
1412+
});
12381413
});
12391414
});

0 commit comments

Comments
 (0)