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
5 changes: 5 additions & 0 deletions .changeset/wise-regions-sip.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@rocket.chat/meteor": patch
---

Fixes an issue on `canAccessRoom` where `abacAttributes` were not fetched in some endpoint calls
59 changes: 54 additions & 5 deletions .github/actions/update-version-durability/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .github/actions/update-version-durability/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"dependencies": {
"@actions/core": "^1.10.1",
"@octokit/rest": "^21.0.0",
"axios": "^1.7.2",
"axios": "^1.16.0",
"beauty-html": "^1.3.1",
"colors": "^1.4.0",
"diff": "^5.1.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ export const roomAccessAttributes = {
t: 1,
teamId: 1,
prid: 1,
abacAttributes: 1,
};
98 changes: 95 additions & 3 deletions apps/meteor/tests/end-to-end/api/abac.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2093,9 +2093,10 @@ const addAbacAttributesToUserDirectly = async (userId: string, abacAttributes: I
.get(`${v1}/groups.history`)
.set(cacheUserCreds)
.query({ roomId: cacheRoom._id })
.expect(403)
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('errorType', 'error-room-not-found');
});
});

Expand Down Expand Up @@ -2142,6 +2143,87 @@ const addAbacAttributesToUserDirectly = async (userId: string, abacAttributes: I
});
});

describe('Room access via projection-path endpoints (groups.messages)', () => {
// Unlike groups.history (which re-loads the full room), groups.messages gates access
// solely through the shared `roomAccessAttributes` projection. That projection must carry
// `abacAttributes`, otherwise canAccessRoom falls back to the subscription check and a
// non-compliant but still-subscribed member can read messages.
let projRoom: IRoom;
const projAttrKey = `proj_access_attr_${Date.now()}`;
let projUser: IUser;
let projUserCreds: Credentials;

before(async function () {
this.timeout(10000);

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

projRoom = (await createRoom({ type: 'p', name: `abac-proj-room-${Date.now()}` })).body.group;

projUser = await createUser();
projUserCreds = await login(projUser.username, password);
await addAbacAttributesToUserDirectly(projUser._id, [{ key: projAttrKey, values: ['on'] }]);
await addAbacAttributesToUserDirectly(credentials['X-User-Id'], [{ key: projAttrKey, values: ['on'] }]);

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

await request
.post(`${v1}/groups.invite`)
.set(credentials)
.send({ roomId: projRoom._id, usernames: [projUser.username] })
.expect(200);

await request
.post(`${v1}/chat.sendMessage`)
.set(credentials)
.send({ message: { rid: projRoom._id, msg: 'Seed message for projection access test' } })
.expect(200);

// Evaluate every access check against the PDP, no cached decisions
await updateSetting('Abac_Cache_Decision_Time_Seconds', 0);
});

after(async () => {
await deleteRoom({ type: 'p', roomId: projRoom._id });
await deleteUser(projUser);
await updateSetting('Abac_Cache_Decision_Time_Seconds', 300);
});

it('PROJECTION: compliant member can read messages', async () => {
await request
.get(`${v1}/groups.messages`)
.set(projUserCreds)
.query({ roomId: projRoom._id })
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('messages').that.is.an('array');
});
});

it('PROJECTION: member that lost its attributes is denied while still subscribed', async () => {
await addAbacAttributesToUserDirectly(projUser._id, []);

await request
.get(`${v1}/groups.messages`)
.set(projUserCreds)
.query({ roomId: projRoom._id })
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('errorType', 'error-room-not-found');
});
});
});

describe('list abac rooms', () => {
const listAttrKey1 = `list_attr_1_${Date.now()}`;
const listAttrKey2 = `list_attr_2_${Date.now()}`;
Expand Down Expand Up @@ -3178,13 +3260,18 @@ const addAbacAttributesToUserDirectly = async (userId: string, abacAttributes: I
.get('/api/v1/groups.history')
.set(userCreds)
.query({ roomId: room._id })
.expect(403)
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('errorType', 'error-room-not-found');
});
});

it('user is removed from room after access DENY', async () => {
await mockServerReset();
await seedDefaultMocks();
await seedGetDecisionBulk([{ resourceDecisions: [{ decision: 'DECISION_PERMIT', ephemeralResourceId: room._id }] }]);

const res = await request.get('/api/v1/groups.members').set(credentials).query({ roomId: room._id }).expect(200);

const usernames = res.body.members.map((m: IUser) => m.username);
Expand Down Expand Up @@ -3257,6 +3344,10 @@ const addAbacAttributesToUserDirectly = async (userId: string, abacAttributes: I
});

it('denied user is not a member of the room', async () => {
await mockServerReset();
await seedDefaultMocks();
await seedGetDecisionBulk([{ resourceDecisions: [{ decision: 'DECISION_PERMIT', ephemeralResourceId: room._id }] }]);

const res = await request.get('/api/v1/groups.members').set(credentials).query({ roomId: room._id }).expect(200);

const usernames = res.body.members.map((m: IUser) => m.username);
Expand Down Expand Up @@ -3316,9 +3407,10 @@ const addAbacAttributesToUserDirectly = async (userId: string, abacAttributes: I
.get('/api/v1/groups.history')
.set(userCredentials)
.query({ roomId: room._id })
.expect(403)
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('errorType', 'error-room-not-found');
});
});

Expand Down
14 changes: 8 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,11 @@
"external-editor/tmp": "0.2.6",
"@sematext/gc-stats@npm:^1.5.9": "patch:@sematext/gc-stats@npm%3A1.5.9#~/.yarn/patches/@sematext-gc-stats-npm-1.5.9-01e77be4d0.patch",
"adm-zip": "0.5.9",
"axios@npm:^1.6.1": "1.15.2",
"axios@npm:^1.7.4": "1.15.2",
"axios@npm:^1.7.8": "1.15.2",
"axios@npm:^1.11.0": "1.15.2",
"axios@npm:^1.13.2": "1.15.2",
"axios@npm:^1.6.1": "1.16.0",
"axios@npm:^1.7.4": "1.16.0",
"axios@npm:^1.7.8": "1.16.0",
"axios@npm:^1.11.0": "1.16.0",
"axios@npm:^1.13.2": "1.16.0",
"sass/immutable": "5.1.5",
"jws@npm:^3.2.2": "3.2.3",
"jws@npm:^4.0.0": "4.0.1",
Expand Down Expand Up @@ -70,7 +70,7 @@
"minimist": "1.2.6",
"mongodb": "6.10.0",
"protobufjs": "7.5.8",
"webdav/axios": "0.31.1",
"webdav/axios": "0.32.0",
"flatted@npm:^3.1.0": "3.4.2",
"flatted@npm:^3.2.9": "3.4.2",
"flatted@npm:^3.3.1": "3.4.2",
Expand All @@ -87,6 +87,8 @@
"picomatch@npm:^4.0.3": "^4.0.4",
"path-to-regexp@npm:^8.1.0": "^8.4.0",
"serialize-javascript": "^7.0.5",
"launch-editor/shell-quote": "1.8.4",
"npm-run-all/shell-quote": "1.8.4",
"underscore": "1.13.8",
"undici@npm:^6.19.5": "^6.24.0",
"webpack": "~5.104.0",
Expand Down
26 changes: 13 additions & 13 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -16997,25 +16997,25 @@ __metadata:
languageName: node
linkType: hard

"axios@npm:0.31.1":
version: 0.31.1
resolution: "axios@npm:0.31.1"
"axios@npm:0.32.0":
version: 0.32.0
resolution: "axios@npm:0.32.0"
dependencies:
follow-redirects: "npm:^1.15.4"
form-data: "npm:^4.0.4"
proxy-from-env: "npm:^1.1.0"
checksum: 10/097dffdc0ab1229fd2f2e792d038e81272568e8280217933f4ad68234761ffebe2b51468f6190f5d98eb3df9511800083f74f78dda4c6229b24b4f9d98d300e5
checksum: 10/1e35bb0778507851d32587176f72e9dbd9b091e4e7d59b8b60ce77c78fe1663f2b2e949478db1f71998ce68b3eb94df3031fcce110ce4e1c637474c50119e0a1
languageName: node
linkType: hard

"axios@npm:1.15.2":
version: 1.15.2
resolution: "axios@npm:1.15.2"
"axios@npm:1.16.0":
version: 1.16.0
resolution: "axios@npm:1.16.0"
dependencies:
follow-redirects: "npm:^1.15.11"
follow-redirects: "npm:^1.16.0"
form-data: "npm:^4.0.5"
proxy-from-env: "npm:^2.1.0"
checksum: 10/eebbd8cb777316d4252cd994a06ec9fb956ef519214a62dab6c5443ae8b753b5116e9a770502316789e6cdef1101e6aae53b6936d6a3791b2d66d75f4d7d2462
checksum: 10/cf8b521ff732c21550b38c8739aef556ea5e14b268468bb89e4307416ab262b462e582474e810963a3d61c2392ab47ad35c11d05eff027de7c97113bc7411623
languageName: node
linkType: hard

Expand Down Expand Up @@ -34048,10 +34048,10 @@ __metadata:
languageName: node
linkType: hard

"shell-quote@npm:^1.4.3, shell-quote@npm:^1.6.1, shell-quote@npm:^1.8.1":
version: 1.8.1
resolution: "shell-quote@npm:1.8.1"
checksum: 10/af19ab5a1ec30cb4b2f91fd6df49a7442d5c4825a2e269b3712eded10eedd7f9efeaab96d57829880733fc55bcdd8e9b1d8589b4befb06667c731d08145e274d
"shell-quote@npm:1.8.4":
version: 1.8.4
resolution: "shell-quote@npm:1.8.4"
checksum: 10/a3e3796385f2cd5cf0b78207a4439f0c7395c0833fc75b2473084b5d298c109c5c0fa687fcd1c04e4b4484866e5bb8eaae7efae443b80fff71ea7e29baf11f0c
languageName: node
linkType: hard

Expand Down
Loading