Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions apps/meteor/ee/server/lib/audit/methods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,12 @@ Meteor.methods<ServerMethods>({
if (type === 'u') {
const usersId = await getUsersIdFromUserName(usernames);
query['u._id'] = { $in: usersId };

const abacRooms = await Rooms.findAllPrivateRoomsWithAbacAttributes({ projection: { _id: 1 } })
.map((doc) => doc._id)
.toArray();

query.rid = { $nin: abacRooms };
} else {
const roomInfo = await getRoomInfoByAuditParams({ type, roomId: rid, users: usernames, visitor, agent, userId: user._id });
if (!roomInfo) {
Expand Down
161 changes: 161 additions & 0 deletions apps/meteor/tests/end-to-end/api/abac.ts
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,167 @@ const addAbacAttributesToUserDirectly = async (userId: string, abacAttributes: I
});
});

describe('Audit messages by user and ABAC-managed rooms', () => {
let auditUser: IUser;
let auditUserCreds: Credentials;
let auditRoom: IRoom;
const auditAttrKey = `audit_attr_${Date.now()}`;
let startDate: Date;
let endDate: Date;

before(async () => {
startDate = new Date();
endDate = new Date(startDate.getTime() + 1000 * 60);

auditUser = await createUser();
auditUserCreds = await login(auditUser.username, password);

await request
.post(`${v1}/abac/attributes`)
.set(credentials)
.send({ key: auditAttrKey, values: ['v1'] })
.expect(200);

await addAbacAttributesToUserDirectly(auditUser._id, [{ key: auditAttrKey, values: ['v1'] }]);
await addAbacAttributesToUserDirectly(credentials['X-User-Id'], [{ key: auditAttrKey, values: ['v1'] }]);

const roomRes = await createRoom({ type: 'p', name: `abac-audit-user-room-${Date.now()}`, members: [auditUser.username!] });
auditRoom = roomRes.body.group as IRoom;

await request
.post(`${v1}/abac/rooms/${auditRoom._id}/attributes/${auditAttrKey}`)
.set(credentials)
.send({ values: ['v1'] })
.expect(200);
});

after(async () => {
await deleteRoom({ type: 'p', roomId: auditRoom._id });
await deleteUser(auditUser);
});

it("should return no messages when auditing a user that's part of an ABAC-managed room", async () => {
await request
.post(`${v1}/chat.sendMessage`)
.set(auditUserCreds)
.send({ message: { rid: auditRoom._id, msg: 'audit message in abac room' } })
.expect(200);

await request
.post(methodCall('auditGetMessages'))
.set(credentials)
.send({
message: JSON.stringify({
method: 'auditGetMessages',
params: [
{
type: 'u',
msg: 'audit message in abac room',
startDate: { $date: startDate },
endDate: { $date: endDate },
rid: '',
users: [auditUser.username],
visitor: '',
agent: '',
},
],
id: 'abac-audit-1',
msg: 'method',
}),
})
.expect(200)
.expect((res) => {
const parsed = JSON.parse(res.body.message);
expect(parsed).to.have.property('result');
expect(parsed.result).to.be.an('array').that.is.empty;
});
});

it('should return no messages when auditing a user that WAS part of an ABAC-managed room', async () => {
await request
.post(`${v1}/chat.sendMessage`)
.set(auditUserCreds)
.send({ message: { rid: auditRoom._id, msg: 'audit message before removal' } })
.expect(200);

await request.post(`${v1}/groups.kick`).set(credentials).send({ roomId: auditRoom._id, username: auditUser.username }).expect(200);

await request
.post(methodCall('auditGetMessages'))
.set(credentials)
.send({
message: JSON.stringify({
method: 'auditGetMessages',
params: [
{
type: 'u',
msg: 'audit message before removal',
startDate: { $date: startDate },
endDate: { $date: endDate },
rid: '',
users: [auditUser.username],
visitor: '',
agent: '',
},
],
id: 'abac-audit-2',
msg: 'method',
}),
})
.expect(200)
.expect((res) => {
const parsed = JSON.parse(res.body.message);
expect(parsed).to.have.property('result');
expect(parsed.result).to.be.an('array').that.is.empty;
});
});

it("should return messages when auditing a user that is part of a room that's no longer ABAC-managed", async () => {
await request
.post(`${v1}/groups.invite`)
.set(credentials)
.send({ roomId: auditRoom._id, usernames: [auditUser.username] })
.expect(200);

await request.delete(`${v1}/abac/rooms/${auditRoom._id}/attributes/${auditAttrKey}`).set(credentials).expect(200);

await request
.post(`${v1}/chat.sendMessage`)
.set(auditUserCreds)
.send({ message: { rid: auditRoom._id, msg: 'audit message after room no longer abac' } })
.expect(200);

await request
.post(methodCall('auditGetMessages'))
.set(credentials)
.send({
message: JSON.stringify({
method: 'auditGetMessages',
params: [
{
type: 'u',
msg: 'audit message after room no longer abac',
startDate: { $date: startDate },
endDate: { $date: endDate },
rid: '',
users: [auditUser.username],
visitor: '',
agent: '',
},
],
id: 'abac-audit-3',
msg: 'method',
}),
})
.expect(200)
.expect((res) => {
const parsed = JSON.parse(res.body.message);
expect(parsed).to.have.property('result');
expect(parsed.result).to.be.an('array').with.lengthOf.greaterThan(0);
});
});
});

it('PUT room attribute should replace values and keep inUse=true', async () => {
await request
.put(`${v1}/abac/rooms/${testRoom._id}/attributes/${updatedKey}`)
Expand Down
2 changes: 1 addition & 1 deletion apps/meteor/tests/end-to-end/api/audit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ import { IS_EE } from '../../e2e/config/constants';

expect(message.result).to.be.an('array').with.lengthOf.greaterThan(1);
const entry = message.result.find((audition: any) => {
return audition.fields.rids.includes(testChannel._id);
return audition.fields?.rids?.includes(testChannel._id);
});
expect(entry).to.have.property('u').that.is.an('object').deep.equal({
_id: 'rocketchat.internal.admin.test',
Expand Down
1 change: 1 addition & 0 deletions packages/model-typings/src/models/IRoomsModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,7 @@ export interface IRoomsModel extends IBaseModel<IRoom> {
findByType(type: IRoom['t'], options?: FindOptions<IRoom>): FindCursor<IRoom>;
findByTypeInIds(type: IRoom['t'], ids: string[], options?: FindOptions<IRoom>): FindCursor<IRoom>;
findPrivateRoomsByIdsWithAbacAttributes(ids: string[], options?: FindOptions<IRoom>): FindCursor<IRoom>;
findAllPrivateRoomsWithAbacAttributes(options?: FindOptions<IRoom>): FindCursor<IRoom>;
findBySubscriptionUserId(userId: string, options?: FindOptions<IRoom>): Promise<FindCursor<IRoom>>;
findBySubscriptionUserIdUpdatedAfter(userId: string, updatedAfter: Date, options?: FindOptions<IRoom>): Promise<FindCursor<IRoom>>;
findByNameAndTypeNotDefault(
Expand Down
9 changes: 9 additions & 0 deletions packages/models/src/models/Rooms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1311,6 +1311,15 @@ export class RoomsRaw extends BaseRaw<IRoom> implements IRoomsModel {
return this.find(query, options);
}

findAllPrivateRoomsWithAbacAttributes(options: FindOptions<IRoom> = {}): FindCursor<IRoom> {
const query: Filter<IRoom> = {
t: 'p',
abacAttributes: { $exists: true, $ne: [] },
};

return this.find(query, options);
}

async findBySubscriptionUserId(userId: IUser['_id'], options: FindOptions<IRoom> = {}): Promise<FindCursor<IRoom>> {
const data = (await Subscriptions.findByUserId(userId, { projection: { rid: 1 } }).toArray()).map((item) => item.rid);

Expand Down
Loading