|
7 | 7 | * as an undeclared field; a non-system write naming a set the security |
8 | 8 | * catalog does not hold is refused, and a grant that carries no name is |
9 | 9 | * refused as `required`. One grant per user, set and organization: the |
10 | | - * declared unique index (`unique: 'organization'`, NULL-safe on the |
11 | | - * organization) refuses a second, platform-wide grants included, on every |
12 | | - * write path, so a revoke leaves no grant of that set in force. |
| 10 | + * declared unique index (`unique: 'organization'`) refuses a second on every |
| 11 | + * write path, so a revoke leaves no grant of that set in force. Its |
| 12 | + * organization key part is the plain column since ADR-0131 D1 (the tenant |
| 13 | + * column is NOT NULL), so on the pre-18 nullable column this harness models a |
| 14 | + * grant with no organization is exempt, like a NULL in any key part. |
13 | 15 | * |
14 | 16 | * Measured on a REAL `ObjectQL` engine over a real SQL driver with the REAL |
15 | 17 | * `SecurityPlugin` started on it the way a kernel composition does, because |
@@ -309,42 +311,39 @@ describe('one grant per user, set and organization — the declared unique \'org |
309 | 311 | expect(await grantsOfSet(h, 'u_d1', 'qa_alpha')).toEqual([]); |
310 | 312 | }); |
311 | 313 |
|
312 | | - it('NEGATIVE — a second platform-wide grant (organization NULL) is refused 409 by the index itself, for every writer; the revoke leaves none in force', async () => { |
| 314 | + it('a second grant with no organization is NOT refused by the index on a pre-18 nullable column: the plain organization key part exempts it (ADR-0131 D1)', async () => { |
313 | 315 | const h = await boot(); |
314 | 316 | await h.engine.insert('sys_user_permission_set', { id: 'ups_p1', user_id: 'u_p1', permission_set: 'qa_alpha', organization_id: null }, { context: ADMIN } as any); |
315 | | - for (const context of [ADMIN, SYS]) { |
316 | | - const err = await refusalOf(() => h.engine.insert( |
317 | | - 'sys_user_permission_set', { user_id: 'u_p1', permission_set: 'qa_alpha' }, { context } as any, |
318 | | - )); |
319 | | - expect(duplicateShape(err)).toEqual(DUPLICATE); |
320 | | - } |
321 | | - expect((await grantsOfSet(h, 'u_p1', 'qa_alpha')).map((g) => g.id)).toEqual(['ups_p1']); |
322 | | - |
323 | | - await h.engine.delete('sys_user_permission_set', { where: { id: 'ups_p1' }, context: ADMIN } as any); |
324 | | - expect(await grantsOfSet(h, 'u_p1', 'qa_alpha')).toEqual([]); |
| 317 | + await knexOf(h)('sys_user_permission_set').insert({ id: 'ups_p2', user_id: 'u_p1', permission_set: 'qa_alpha', organization_id: null }); |
| 318 | + expect((await grantsOfSet(h, 'u_p1', 'qa_alpha')).map((g) => g.id).sort()).toEqual(['ups_p1', 'ups_p2']); |
325 | 319 | }); |
326 | 320 |
|
327 | | - it('NEGATIVE — the index refuses a platform-wide duplicate on paths no hook sees: a two-row batch insert stores nothing, and raw SQL beneath the engine is refused', async () => { |
| 321 | + it('NEGATIVE — the index refuses an organization duplicate on paths no hook sees: a two-row batch insert stores nothing, and raw SQL beneath the engine is refused', async () => { |
328 | 322 | const h = await boot(); |
| 323 | + await h.engine.insert('sys_member', { id: 'mem_b1', user_id: 'u_b1', organization_id: ORG, role: 'member' }, { context: SYS } as any); |
329 | 324 | const err = await refusalOf(() => h.engine.insert( |
330 | 325 | 'sys_user_permission_set', |
331 | | - [{ user_id: 'u_b1', permission_set: 'qa_alpha' }, { user_id: 'u_b1', permission_set: 'qa_alpha' }], |
| 326 | + [ |
| 327 | + { user_id: 'u_b1', permission_set: 'qa_alpha', organization_id: ORG }, |
| 328 | + { user_id: 'u_b1', permission_set: 'qa_alpha', organization_id: ORG }, |
| 329 | + ], |
332 | 330 | { context: SYS } as any, |
333 | 331 | )); |
334 | 332 | expect(duplicateShape(err)).toEqual(DUPLICATE); |
335 | 333 | expect(await grantsOf(h, 'u_b1')).toEqual([]); |
336 | 334 |
|
337 | | - await knexOf(h)('sys_user_permission_set').insert({ id: 'raw_1', user_id: 'u_r1', permission_set: 'qa_alpha', organization_id: null }); |
| 335 | + await knexOf(h)('sys_user_permission_set').insert({ id: 'raw_1', user_id: 'u_r1', permission_set: 'qa_alpha', organization_id: ORG }); |
338 | 336 | await expect( |
339 | | - knexOf(h)('sys_user_permission_set').insert({ id: 'raw_2', user_id: 'u_r1', permission_set: 'qa_alpha', organization_id: null }), |
| 337 | + knexOf(h)('sys_user_permission_set').insert({ id: 'raw_2', user_id: 'u_r1', permission_set: 'qa_alpha', organization_id: ORG }), |
340 | 338 | ).rejects.toThrow(/UNIQUE constraint failed/); |
341 | 339 | expect((await grantsOf(h, 'u_r1')).map((g) => g.id)).toEqual(['raw_1']); |
342 | 340 | }); |
343 | 341 |
|
344 | | - it('NEGATIVE — an update that makes a platform-wide duplicate (re-naming the set) is refused 409 and the row is untouched', async () => { |
| 342 | + it('NEGATIVE — an update that makes an organization duplicate (re-naming the set) is refused 409 and the row is untouched', async () => { |
345 | 343 | const h = await boot(); |
346 | | - await h.engine.insert('sys_user_permission_set', { id: 'ups_u1', user_id: 'u_u1', permission_set: 'qa_alpha' }, { context: ADMIN } as any); |
347 | | - await h.engine.insert('sys_user_permission_set', { id: 'ups_u2', user_id: 'u_u1', permission_set: 'qa_beta' }, { context: ADMIN } as any); |
| 344 | + await h.engine.insert('sys_member', { id: 'mem_u1', user_id: 'u_u1', organization_id: ORG, role: 'member' }, { context: SYS } as any); |
| 345 | + await h.engine.insert('sys_user_permission_set', { id: 'ups_u1', user_id: 'u_u1', permission_set: 'qa_alpha', organization_id: ORG }, { context: ADMIN } as any); |
| 346 | + await h.engine.insert('sys_user_permission_set', { id: 'ups_u2', user_id: 'u_u1', permission_set: 'qa_beta', organization_id: ORG }, { context: ADMIN } as any); |
348 | 347 | const err = await refusalOf(() => h.engine.update( |
349 | 348 | 'sys_user_permission_set', { id: 'ups_u2', permission_set: 'qa_alpha' }, { context: ADMIN } as any, |
350 | 349 | )); |
|
0 commit comments