diff --git a/.changeset/spicy-phones-breathe.md b/.changeset/spicy-phones-breathe.md new file mode 100644 index 0000000000000..71bb6affc744b --- /dev/null +++ b/.changeset/spicy-phones-breathe.md @@ -0,0 +1,6 @@ +--- +"@rocket.chat/meteor": patch +"@rocket.chat/abac": patch +--- + +Fixes an issue where some actions made by the abac service were not broadcasting to clients, which affected reactivity diff --git a/apps/meteor/client/cachedStores/RoomsCachedStore.ts b/apps/meteor/client/cachedStores/RoomsCachedStore.ts index 85e78c9041b4f..f02ec85cced80 100644 --- a/apps/meteor/client/cachedStores/RoomsCachedStore.ts +++ b/apps/meteor/client/cachedStores/RoomsCachedStore.ts @@ -1,5 +1,5 @@ import type { IOmnichannelRoom, IRoom, IRoomWithRetentionPolicy } from '@rocket.chat/core-typings'; -import { DEFAULT_SLA_CONFIG, isABACManagedRoom, isRoomNativeFederated, LivechatPriorityWeight } from '@rocket.chat/core-typings'; +import { DEFAULT_SLA_CONFIG, isRoomNativeFederated, LivechatPriorityWeight } from '@rocket.chat/core-typings'; import type { SubscriptionWithRoom } from '@rocket.chat/ui-contexts'; import { PrivateCachedStore } from '../lib/cachedStores/CachedStore'; @@ -53,9 +53,7 @@ class RoomsCachedStore extends PrivateCachedStore { source: (room as IOmnichannelRoom | undefined)?.source, queuedAt: (room as IOmnichannelRoom | undefined)?.queuedAt, federated: room.federated, - ...(isABACManagedRoom(room) && { - abacAttributes: room.abacAttributes, - }), + abacAttributes: room.abacAttributes, ...(isRoomNativeFederated(room) && { federation: room.federation, }), diff --git a/ee/packages/abac/src/helper.ts b/ee/packages/abac/src/helper.ts index 5522b09b99662..e3309be4d1abf 100644 --- a/ee/packages/abac/src/helper.ts +++ b/ee/packages/abac/src/helper.ts @@ -225,14 +225,8 @@ export function buildRoomNonCompliantConditionsFromSubject(subjectAttributes: IA return conditions; } -export async function getAbacRoom( - rid: string, -): Promise> { - const room = await Rooms.findOneByIdAndType< - Pick - >(rid, 'p', { - projection: { abacAttributes: 1, t: 1, teamMain: 1, teamDefault: 1, default: 1, name: 1 }, - }); +export async function getAbacRoom(rid: string): Promise { + const room = await Rooms.findOneByIdAndType(rid, 'p'); if (!room) { throw new AbacRoomNotFoundError(); } diff --git a/ee/packages/abac/src/index.ts b/ee/packages/abac/src/index.ts index 9c3bf028ca673..675376c23a9b5 100644 --- a/ee/packages/abac/src/index.ts +++ b/ee/packages/abac/src/index.ts @@ -1,4 +1,4 @@ -import { Room, ServiceClass, Settings } from '@rocket.chat/core-services'; +import { api, Room, ServiceClass, Settings } from '@rocket.chat/core-services'; import type { AbacActor, IAbacService } from '@rocket.chat/core-services'; import { AbacAccessOperation, AbacObjectType } from '@rocket.chat/core-typings'; import type { @@ -428,6 +428,10 @@ export class AbacService extends ServiceClass implements IAbacService { return Rooms.isAbacAttributeInUse(key, attribute.values || []); } + private broadcastRoomUpdate(room: IRoom): void { + void api.broadcast('watch.rooms', { clientAction: 'updated', room }); + } + async setRoomAbacAttributes(rid: string, attributes: Record, actor: AbacActor): Promise { await this.ensurePdpAvailable(); const room = await getAbacRoom(rid); @@ -435,6 +439,7 @@ export class AbacService extends ServiceClass implements IAbacService { if (!Object.keys(attributes).length && room.abacAttributes?.length) { await Rooms.unsetAbacAttributesById(rid); void Audit.objectAttributesRemoved({ _id: room._id, name: room.name }, room.abacAttributes, actor); + this.broadcastRoomUpdate({ ...room, abacAttributes: undefined }); return; } @@ -445,6 +450,10 @@ export class AbacService extends ServiceClass implements IAbacService { const updated = await Rooms.setAbacAttributesById(rid, normalized); void Audit.objectAttributeChanged({ _id: room._id, name: room.name }, room.abacAttributes || [], normalized, 'updated', actor); + if (updated) { + this.broadcastRoomUpdate(updated); + } + const previous: IAbacAttributeDefinition[] = room.abacAttributes || []; if (diffAttributeSets(previous, normalized).added) { await this.onRoomAttributesChanged(room, updated?.abacAttributes ?? normalized); @@ -476,6 +485,8 @@ export class AbacService extends ServiceClass implements IAbacService { ); const next = [...previous, { key, values }]; + this.broadcastRoomUpdate({ ...room, abacAttributes: next }); + await this.onRoomAttributesChanged(room, next); return; } @@ -486,7 +497,7 @@ export class AbacService extends ServiceClass implements IAbacService { return; } - await Rooms.updateAbacAttributeValuesArrayFilteredById(rid, key, values); + const updated = await Rooms.updateAbacAttributeValuesArrayFilteredById(rid, key, values); void Audit.objectAttributeChanged( { _id: room._id, name: room.name }, room.abacAttributes || [], @@ -495,6 +506,10 @@ export class AbacService extends ServiceClass implements IAbacService { actor, ); + if (updated) { + this.broadcastRoomUpdate(updated); + } + if (diffAttributeSets([previous[existingIndex]], [{ key, values }]).added) { const next = previous.map((a, i) => (i === existingIndex ? { key, values } : a)); await this.onRoomAttributesChanged(room, next); @@ -516,17 +531,16 @@ export class AbacService extends ServiceClass implements IAbacService { await Rooms.unsetAbacAttributesById(rid); void Audit.objectAttributesRemoved({ _id: room._id }, previous, actor); + this.broadcastRoomUpdate({ ...room, abacAttributes: undefined }); + return; } await Rooms.removeAbacAttributeByRoomIdAndKey(rid, key); - void Audit.objectAttributeRemoved( - { _id: room._id, name: room.name }, - previous, - previous.filter((a) => a.key !== key), - 'key-removed', - actor, - ); + const next = previous.filter((a) => a.key !== key); + void Audit.objectAttributeRemoved({ _id: room._id, name: room.name }, previous, next, 'key-removed', actor); + + this.broadcastRoomUpdate({ ...room, abacAttributes: next }); } async addRoomAbacAttributeByKey(rid: string, key: string, values: string[], actor: AbacActor): Promise { @@ -549,6 +563,8 @@ export class AbacService extends ServiceClass implements IAbacService { void Audit.objectAttributeChanged({ _id: room._id, name: room.name }, previous, next, 'key-added', actor); + this.broadcastRoomUpdate({ ...room, abacAttributes: next }); + await this.onRoomAttributesChanged(room, next); } @@ -570,6 +586,11 @@ export class AbacService extends ServiceClass implements IAbacService { 'key-updated', actor, ); + + if (updated) { + this.broadcastRoomUpdate(updated); + } + if (diffAttributeSets([exists], [{ key, values }]).added) { await this.onRoomAttributesChanged(room, updated?.abacAttributes || []); } @@ -582,13 +603,10 @@ export class AbacService extends ServiceClass implements IAbacService { } const updated = await Rooms.insertAbacAttributeIfNotExistsById(rid, key, values); - void Audit.objectAttributeChanged( - { _id: room._id, name: room.name }, - room.abacAttributes || [], - updated?.abacAttributes || [], - 'key-added', - actor, - ); + const nextAttributes = updated?.abacAttributes || [...(room.abacAttributes || []), { key, values }]; + void Audit.objectAttributeChanged({ _id: room._id, name: room.name }, room.abacAttributes || [], nextAttributes, 'key-added', actor); + + this.broadcastRoomUpdate({ ...room, abacAttributes: nextAttributes }); await this.onRoomAttributesChanged(room, updated?.abacAttributes || []); } diff --git a/ee/packages/abac/src/pdp/VirtruPDP.ts b/ee/packages/abac/src/pdp/VirtruPDP.ts index 6cbe7939cf2b1..457d30d822979 100644 --- a/ee/packages/abac/src/pdp/VirtruPDP.ts +++ b/ee/packages/abac/src/pdp/VirtruPDP.ts @@ -491,7 +491,7 @@ export class VirtruPDP implements IPolicyDecisionPoint { msg: 'User has no entity key for Virtru PDP evaluation, treating as non-compliant for all ABAC rooms', userId: user._id, }); - return abacRooms as IRoom[]; + return abacRooms; } const decisionRequests = abacRooms.map((room) => ({ diff --git a/ee/packages/abac/src/service.spec.ts b/ee/packages/abac/src/service.spec.ts index 0402b7523a0c7..d07035ea50dd8 100644 --- a/ee/packages/abac/src/service.spec.ts +++ b/ee/packages/abac/src/service.spec.ts @@ -69,6 +69,9 @@ jest.mock('@rocket.chat/core-services', () => { Room: { removeUserFromRoom: jest.fn(), }, + api: { + broadcast: jest.fn(), + }, }; }); diff --git a/ee/packages/abac/src/user-auto-removal.spec.ts b/ee/packages/abac/src/user-auto-removal.spec.ts index 2929e783fd8da..e401da82e5de6 100644 --- a/ee/packages/abac/src/user-auto-removal.spec.ts +++ b/ee/packages/abac/src/user-auto-removal.spec.ts @@ -17,6 +17,9 @@ jest.mock('@rocket.chat/core-services', () => ({ await Subscriptions.removeByRoomIdAndUserId(roomId, user._id); }, }, + api: { + broadcast: jest.fn(), + }, MeteorError: class extends Error {}, isMeteorError: () => false, }));