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
105 changes: 65 additions & 40 deletions apps/meteor/tests/end-to-end/api/abac.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
import { updatePermission, updateSetting } from '../../data/permissions.helper';
import { createRoom, deleteRoom } from '../../data/rooms.helper';
import { deleteTeam } from '../../data/teams.helper';
import { adminEmail, password } from '../../data/user';
import { adminEmail, adminUsername, password } from '../../data/user';
import { createUser, deleteUser, login } from '../../data/users.helper';
import { IS_EE, URL_MONGODB } from '../../e2e/config/constants';

Expand Down Expand Up @@ -47,7 +47,6 @@ const addAbacAttributesToUserDirectly = async (userId: string, abacAttributes: I
const updatedKey = `${initialKey}_renamed`;
const anotherKey = `${initialKey}_another`;
let attributeId: string;
let paginationBase: string;
let page1AttributeIds: string[] = [];

before((done) => getCredentials(done));
Expand Down Expand Up @@ -328,7 +327,7 @@ const addAbacAttributesToUserDirectly = async (userId: string, abacAttributes: I
});

it('GET should paginate attributes (page 1)', async () => {
paginationBase = `pg_${Date.now()}`;
const paginationBase = `pg_${Date.now()}`;
await Promise.all(
['a', 'b', 'c'].map((suffix) =>
request
Expand Down Expand Up @@ -455,39 +454,6 @@ const addAbacAttributesToUserDirectly = async (userId: string, abacAttributes: I
expect(res.body).to.have.property('key', updatedKey);
});
});

it('PUT should update key only (key-updated)', async () => {
Comment thread
KevLehman marked this conversation as resolved.
const testKey = `${initialKey}_update_test`;
await request
.post(`${v1}/abac/attributes`)
.set(credentials)
.send({ key: testKey, values: ['val1', 'val2'] })
.expect(200);

const listRes = await request.get(`${v1}/abac/attributes`).query({ key: testKey }).set(credentials).expect(200);

const attr = listRes.body.attributes.find((a: any) => a.key === testKey);
expect(attr).to.exist;
const attrId = attr._id;

const newKey = `${initialKey}_update_test_renamed`;
await request
.put(`${v1}/abac/attributes/${attrId}`)
.set(credentials)
.send({ key: newKey })
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
});

await request
.get(`${v1}/abac/attributes/${attrId}`)
.set(credentials)
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('key', newKey);
});
});
});

describe('Room Attribute Operations', () => {
Expand Down Expand Up @@ -1350,13 +1316,11 @@ const addAbacAttributesToUserDirectly = async (userId: string, abacAttributes: I
});

describe('Room attribute limits (max 10 attribute keys)', () => {
const tempAttrKeys: string[] = [];
it('Reset room attributes before limit test and populate with 10 keys', async () => {
await request.delete(`${v1}/abac/rooms/${testRoom._id}/attributes`).set(credentials).expect(200);

const timestamp = Date.now();
const keys = Array.from({ length: 10 }, (_, i) => `limitk_${timestamp}_${i}`);
tempAttrKeys.push(...keys);
await Promise.all(
keys.map((k) =>
request
Expand Down Expand Up @@ -1477,6 +1441,16 @@ const addAbacAttributesToUserDirectly = async (userId: string, abacAttributes: I
});
});

it('DELETE /abac/rooms/:rid/attributes/:key succeeds while disabled (endpoint has no enabled-check)', async () => {
await request
.delete(`${v1}/abac/rooms/${testRoom._id}/attributes/never_added_${Date.now()}`)
.set(credentials)
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
});
});
Comment thread
KevLehman marked this conversation as resolved.

after(async () => {
await updateSetting('ABAC_Enabled', true);
});
Expand Down Expand Up @@ -3497,6 +3471,19 @@ const addAbacAttributesToUserDirectly = async (userId: string, abacAttributes: I
message: 'ABAC_PDP_Health_IdP_Failed',
});
});

it('returns 200 ABAC_PDP_Health_OK when platform, IdP, and authorization all succeed', async () => {
await seedDefaultMocks();
await seedGetEntitlements({});
Comment thread
KevLehman marked this conversation as resolved.

const res = await request.get('/api/v1/abac/pdp/health').set(credentials).expect(200);

expect(res.body).to.deep.include({
success: true,
available: true,
message: 'ABAC_PDP_Health_OK',
});
});
});

describe('users.info abacAttributes visibility', () => {
Expand Down Expand Up @@ -3526,12 +3513,11 @@ const addAbacAttributesToUserDirectly = async (userId: string, abacAttributes: I
const v1 = '/api/v1';
const NS = 'example.com';
const fqn = (key: string, value: string) => `https://${NS}/attr/${key}/value/${value}`;
const adminPassword = password;
let storeConnection: MongoClient;

const makeAdmin = async (slug: string) => {
const u = await createUser({ roles: ['admin'], username: `vstore-${slug}-${Date.now()}-${Math.random().toString(36).slice(2, 7)}` });
const creds = await login(u.username, adminPassword);
const creds = await login(u.username, password);
return { user: u, creds };
};

Expand Down Expand Up @@ -4173,6 +4159,45 @@ const addAbacAttributesToUserDirectly = async (userId: string, abacAttributes: I
});
});

describe('GET /abac/audit query filters', () => {
it('scopes events to the actor passed in the query', async () => {
const mine = await request
.get(`${v1}/abac/audit`)
.set(credentials)
.query({ count: 100, actor: { username: adminUsername } })
.expect(200);
expect(mine.body.events).to.be.an('array').that.is.not.empty;
(mine.body.events as Array<{ actor?: { username?: string } }>).forEach((e) => {
expect(e.actor?.username).to.equal(adminUsername);
});

const other = await request
.get(`${v1}/abac/audit`)
.set(credentials)
.query({ count: 100, actor: { _id: 'no-such-actor-xyz' } })
.expect(200);
expect(other.body.events).to.be.an('array').that.is.empty;
expect(other.body.total).to.equal(0);
});

it('returns no events when start is in the future', async () => {
const future = new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString();
const res = await request.get(`${v1}/abac/audit`).set(credentials).query({ count: 100, start: future }).expect(200);
expect(res.body.events).to.be.an('array').that.is.empty;
expect(res.body.total).to.equal(0);
});

it('returns no events when end precedes all events', async () => {
const res = await request
.get(`${v1}/abac/audit`)
.set(credentials)
.query({ count: 100, end: new Date(1).toISOString() })
.expect(200);
expect(res.body.events).to.be.an('array').that.is.empty;
expect(res.body.total).to.equal(0);
Comment thread
KevLehman marked this conversation as resolved.
});
});

describe('local-mode regression (ABAC_Attribute_Store=local)', () => {
const localKey = `vstore_local_${Date.now()}`;
let localRoom: IRoom;
Expand Down
46 changes: 46 additions & 0 deletions ee/packages/abac/src/can-access-object.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -263,4 +263,50 @@ describe('AbacService.canAccessObject (unit)', () => {
expect(query.$and[0].abacAttributes.$elemMatch.values.$all).toEqual(['eng']);
});
});

describe('PDP unavailable (fail-closed)', () => {
const usePdp = (over: Record<string, jest.Mock> = {}) => {
const pdp = {
isAvailable: jest.fn().mockResolvedValue(true),
canAccessObject: jest.fn(),
...over,
} as any;
(service as any).pdp = pdp;
return pdp;
};

beforeEach(() => {
mockSubscriptionsFindOneByRoomIdAndUserId.mockResolvedValue({ _id: 'SUB' });
});

it('returns false (does not grant) when the PDP decision call throws', async () => {
const pdp = usePdp({ canAccessObject: jest.fn().mockRejectedValue(new Error('virtru down')) });

const result = await service.canAccessObject(baseRoom as any, baseUser as any, AbacAccessOperation.READ, AbacObjectType.ROOM);

expect(result).toBe(false);
expect(pdp.canAccessObject).toHaveBeenCalled();
expect(mockRoomRemoveUserFromRoom).not.toHaveBeenCalled();
expect(mockSubscriptionsSetAbacLastTimeCheckedByUserIdAndRoomId).not.toHaveBeenCalled();
});

it('returns false without calling the PDP when it reports unavailable', async () => {
const pdp = usePdp({ isAvailable: jest.fn().mockResolvedValue(false) });

const result = await service.canAccessObject(baseRoom as any, baseUser as any, AbacAccessOperation.READ, AbacObjectType.ROOM);

expect(result).toBe(false);
expect(pdp.canAccessObject).not.toHaveBeenCalled();
expect(mockSubscriptionsSetAbacLastTimeCheckedByUserIdAndRoomId).not.toHaveBeenCalled();
});

it('returns false without any DB lookup when no PDP is configured', async () => {
(service as any).pdp = undefined;

const result = await service.canAccessObject(baseRoom as any, baseUser as any, AbacAccessOperation.READ, AbacObjectType.ROOM);

expect(result).toBe(false);
expect(mockSubscriptionsFindOneByRoomIdAndUserId).not.toHaveBeenCalled();
});
});
});
Loading
Loading