mirror of
https://github.com/strapi/strapi.git
synced 2026-05-03 16:22:30 +00:00
590 lines
22 KiB
TypeScript
590 lines
22 KiB
TypeScript
import { createTestBuilder } from 'api-tests/builder';
|
|
import { createStrapiInstance } from 'api-tests/strapi';
|
|
import { createAuthRequest } from 'api-tests/request';
|
|
import { createUtils } from 'api-tests/utils';
|
|
import type { Core } from '@strapi/types';
|
|
|
|
/**
|
|
* Integration tests for admin token permission sync.
|
|
*
|
|
* These tests verify that when role permissions or user-role bindings change,
|
|
* admin token permissions are correctly reconciled (clamped, never expanded).
|
|
*
|
|
* Sync triggers:
|
|
* - T1: role.assignPermissions → syncPermissionsForRole (fires when permissionsToAdd.length > 0 || permissionsToDelete.length > 0)
|
|
* - T2: admin::user afterUpdate lifecycle → syncPermissionsForUser (fires when roles field changes)
|
|
* - T3: admin::role beforeDelete/afterDelete lifecycles → syncPermissionsForUser for each affected user
|
|
*/
|
|
describe('Admin Token Role Sync (api)', () => {
|
|
const builder = createTestBuilder();
|
|
let strapi: Core.Strapi;
|
|
|
|
const CT_UID = 'api::sync-article.sync-article';
|
|
const SYNC_ACTION = 'plugin::content-manager.explorer.read';
|
|
const SYNC_ACTION_2 = 'plugin::content-manager.explorer.create';
|
|
const SYNC_SUBJECT = CT_UID;
|
|
|
|
const ADMIN_TOKEN_PERMS = [
|
|
{ action: 'admin::admin-tokens.create', subject: null, conditions: [], properties: {} },
|
|
{ action: 'admin::admin-tokens.read', subject: null, conditions: [], properties: {} },
|
|
{ action: 'admin::admin-tokens.update', subject: null, conditions: [], properties: {} },
|
|
{ action: 'admin::admin-tokens.delete', subject: null, conditions: [], properties: {} },
|
|
{ action: 'admin::admin-tokens.regenerate', subject: null, conditions: [], properties: {} },
|
|
];
|
|
|
|
// Shared fixtures created in beforeAll
|
|
let roleA: { id: number };
|
|
let roleB: { id: number };
|
|
let syncUserId: number;
|
|
let syncUserBothId: number;
|
|
let rqSync: Awaited<ReturnType<typeof createAuthRequest>>;
|
|
let rqSyncBoth: Awaited<ReturnType<typeof createAuthRequest>>;
|
|
|
|
// Per-test tokens (reset in beforeEach)
|
|
let tokenA: { id: number };
|
|
let tokenBoth: { id: number };
|
|
|
|
const deleteAllAdminTokens = async () => {
|
|
await strapi.db.query('admin::api-token').deleteMany({ where: { kind: 'admin' } });
|
|
};
|
|
|
|
/**
|
|
* Read permission rows directly from DB for a given token, bypassing the API.
|
|
* Returns only the fields relevant for assertions.
|
|
*/
|
|
const getAdminPermissionsFromDB = async (
|
|
tokenId: number
|
|
): Promise<Array<{ action: string; conditions: string[] }>> => {
|
|
const perms = await strapi.db.query('admin::permission').findMany({
|
|
where: { apiToken: { id: tokenId } },
|
|
});
|
|
return perms.map((p: { action: string; conditions: string[] }) => ({
|
|
action: p.action,
|
|
conditions: p.conditions ?? [],
|
|
}));
|
|
};
|
|
|
|
beforeAll(async () => {
|
|
await builder
|
|
.addContentType({
|
|
singularName: 'sync-article',
|
|
pluralName: 'sync-articles',
|
|
displayName: 'SyncArticle',
|
|
draftAndPublish: false,
|
|
attributes: { title: { type: 'string' } },
|
|
})
|
|
.build();
|
|
|
|
// @ts-expect-error - the JS test helper supports `register`, but its TS type is incomplete
|
|
strapi = await createStrapiInstance({
|
|
register({ strapi: instance }) {
|
|
instance.config.set('features.future.adminTokens', true);
|
|
},
|
|
});
|
|
strapi.config.set('admin.secrets.encryptionKey', 'test-encryption-key');
|
|
|
|
const utils = createUtils(strapi);
|
|
|
|
// roleA: grants SYNC_ACTION + SYNC_ACTION_2 + admin-tokens CRUD
|
|
roleA = await utils.createRole({
|
|
name: 'sync-test-role-a',
|
|
description: 'Role A for sync tests',
|
|
});
|
|
await utils.assignPermissionsToRole(roleA.id, [
|
|
{ action: SYNC_ACTION, subject: SYNC_SUBJECT, conditions: [], properties: {} },
|
|
{ action: SYNC_ACTION_2, subject: SYNC_SUBJECT, conditions: [], properties: {} },
|
|
...ADMIN_TOKEN_PERMS,
|
|
]);
|
|
|
|
// roleB: grants SYNC_ACTION only (intentionally overlaps roleA)
|
|
roleB = await utils.createRole({
|
|
name: 'sync-test-role-b',
|
|
description: 'Role B for sync tests',
|
|
});
|
|
await utils.assignPermissionsToRole(roleB.id, [
|
|
{ action: SYNC_ACTION, subject: SYNC_SUBJECT, conditions: [], properties: {} },
|
|
]);
|
|
|
|
// syncUser: single-role user (roleA only)
|
|
const syncUser = await utils.createUser({
|
|
email: 'sync-user@test.com',
|
|
firstname: 'Sync',
|
|
lastname: 'User',
|
|
isActive: true,
|
|
roles: [roleA.id],
|
|
});
|
|
syncUserId = syncUser.id;
|
|
rqSync = await createAuthRequest({ strapi, userInfo: { email: 'sync-user@test.com' } as any });
|
|
|
|
// syncUserBoth: multi-role user (roleA + roleB)
|
|
const syncUserBoth = await utils.createUser({
|
|
email: 'sync-user-both@test.com',
|
|
firstname: 'SyncBoth',
|
|
lastname: 'User',
|
|
isActive: true,
|
|
roles: [roleA.id, roleB.id],
|
|
});
|
|
syncUserBothId = syncUserBoth.id;
|
|
rqSyncBoth = await createAuthRequest({
|
|
strapi,
|
|
userInfo: { email: 'sync-user-both@test.com' } as any,
|
|
});
|
|
|
|
await deleteAllAdminTokens();
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await deleteAllAdminTokens();
|
|
if (syncUserId !== undefined)
|
|
await strapi.db.query('admin::user').delete({ where: { id: syncUserId } });
|
|
if (syncUserBothId !== undefined)
|
|
await strapi.db.query('admin::user').delete({ where: { id: syncUserBothId } });
|
|
if (roleA !== undefined)
|
|
await strapi.db.query('admin::role').delete({ where: { id: roleA.id } });
|
|
if (roleB !== undefined)
|
|
await strapi.db.query('admin::role').delete({ where: { id: roleB.id } });
|
|
await strapi.destroy();
|
|
await builder.cleanup();
|
|
});
|
|
|
|
beforeEach(async () => {
|
|
await deleteAllAdminTokens();
|
|
|
|
const utils = createUtils(strapi);
|
|
|
|
// Restore roleA to its full set of permissions before each test
|
|
await utils.assignPermissionsToRole(roleA.id, [
|
|
{ action: SYNC_ACTION, subject: SYNC_SUBJECT, conditions: [], properties: {} },
|
|
{ action: SYNC_ACTION_2, subject: SYNC_SUBJECT, conditions: [], properties: {} },
|
|
...ADMIN_TOKEN_PERMS,
|
|
]);
|
|
|
|
// Restore roleB to its full set of permissions before each test
|
|
await utils.assignPermissionsToRole(roleB.id, [
|
|
{ action: SYNC_ACTION, subject: SYNC_SUBJECT, conditions: [], properties: {} },
|
|
]);
|
|
|
|
// Restore syncUser to roleA only
|
|
await strapi.db.query('admin::user').update({
|
|
where: { id: syncUserId },
|
|
data: { roles: [roleA.id] },
|
|
});
|
|
|
|
// Restore syncUserBoth to roleA + roleB
|
|
await strapi.db.query('admin::user').update({
|
|
where: { id: syncUserBothId },
|
|
data: { roles: [roleA.id, roleB.id] },
|
|
});
|
|
|
|
// Create baseline tokens with SYNC_ACTION
|
|
const resA = await rqSync({
|
|
url: '/admin/admin-tokens',
|
|
method: 'POST',
|
|
body: {
|
|
name: 'sync-token-a',
|
|
adminPermissions: [
|
|
{ action: SYNC_ACTION, subject: SYNC_SUBJECT, conditions: [], properties: {} },
|
|
],
|
|
},
|
|
});
|
|
expect(resA.status).toBe(201);
|
|
tokenA = resA.body.data;
|
|
|
|
const resBoth = await rqSyncBoth({
|
|
url: '/admin/admin-tokens',
|
|
method: 'POST',
|
|
body: {
|
|
name: 'sync-token-both',
|
|
adminPermissions: [
|
|
{ action: SYNC_ACTION, subject: SYNC_SUBJECT, conditions: [], properties: {} },
|
|
],
|
|
},
|
|
});
|
|
expect(resBoth.status).toBe(201);
|
|
tokenBoth = resBoth.body.data;
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// T1 — Role permissions edited (assignPermissions → syncForRole)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
describe('T1 — Role permissions edited', () => {
|
|
test('T1-1 Remove permission → token permission deleted', async () => {
|
|
const utils = createUtils(strapi);
|
|
|
|
// Strip SYNC_ACTION from roleA (keep only admin-tokens CRUD so the user can still operate)
|
|
await utils.assignPermissionsToRole(roleA.id, [...ADMIN_TOKEN_PERMS]);
|
|
|
|
const perms = await getAdminPermissionsFromDB(tokenA.id);
|
|
const actions = perms.map((p) => p.action);
|
|
expect(actions).not.toContain(SYNC_ACTION);
|
|
});
|
|
|
|
test('T1-2 Add permission → existing token unaffected (no expansion)', async () => {
|
|
const utils = createUtils(strapi);
|
|
|
|
// tokenA was created with SYNC_ACTION only; now add SYNC_ACTION_2 to roleA
|
|
// Sync never expands — token should still have only SYNC_ACTION
|
|
await utils.assignPermissionsToRole(roleA.id, [
|
|
{ action: SYNC_ACTION, subject: SYNC_SUBJECT, conditions: [], properties: {} },
|
|
{ action: SYNC_ACTION_2, subject: SYNC_SUBJECT, conditions: [], properties: {} },
|
|
...ADMIN_TOKEN_PERMS,
|
|
]);
|
|
|
|
const perms = await getAdminPermissionsFromDB(tokenA.id);
|
|
const actions = perms.map((p) => p.action);
|
|
expect(actions).toContain(SYNC_ACTION);
|
|
expect(actions).not.toContain(SYNC_ACTION_2);
|
|
});
|
|
|
|
test('T1-3 Multi-role: permission shared by two roles; remove from one → token keeps it', async () => {
|
|
const utils = createUtils(strapi);
|
|
|
|
// Remove SYNC_ACTION from roleA; roleB still grants it
|
|
// syncUserBoth still has roleB → tokenBoth should keep SYNC_ACTION
|
|
await utils.assignPermissionsToRole(roleA.id, [
|
|
{ action: SYNC_ACTION_2, subject: SYNC_SUBJECT, conditions: [], properties: {} },
|
|
...ADMIN_TOKEN_PERMS,
|
|
]);
|
|
|
|
const perms = await getAdminPermissionsFromDB(tokenBoth.id);
|
|
const actions = perms.map((p) => p.action);
|
|
expect(actions).toContain(SYNC_ACTION);
|
|
});
|
|
|
|
test('T1-4 Conditions sync: role gains condition → token condition updated', async () => {
|
|
const utils = createUtils(strapi);
|
|
|
|
// Re-assign roleA with SYNC_ACTION + condition
|
|
// This deletes + re-creates permissions, triggering sync for the previous token (empty conditions)
|
|
await utils.assignPermissionsToRole(roleA.id, [
|
|
{
|
|
action: SYNC_ACTION,
|
|
subject: SYNC_SUBJECT,
|
|
conditions: ['admin::is-creator'],
|
|
properties: {},
|
|
},
|
|
...ADMIN_TOKEN_PERMS,
|
|
]);
|
|
|
|
const permsWithCondition = await getAdminPermissionsFromDB(tokenA.id);
|
|
const syncPerm = permsWithCondition.find((p) => p.action === SYNC_ACTION);
|
|
expect(syncPerm).toBeDefined();
|
|
expect(syncPerm?.conditions).toStrictEqual(['admin::is-creator']);
|
|
|
|
// Revert: re-assign with empty conditions
|
|
await utils.assignPermissionsToRole(roleA.id, [
|
|
{ action: SYNC_ACTION, subject: SYNC_SUBJECT, conditions: [], properties: {} },
|
|
...ADMIN_TOKEN_PERMS,
|
|
]);
|
|
|
|
const permsReverted = await getAdminPermissionsFromDB(tokenA.id);
|
|
const syncPermReverted = permsReverted.find((p) => p.action === SYNC_ACTION);
|
|
expect(syncPermReverted).toBeDefined();
|
|
expect(syncPermReverted?.conditions).toStrictEqual([]);
|
|
});
|
|
|
|
test('T1-5 Pure addition to second role triggers condition re-sync', async () => {
|
|
const utils = createUtils(strapi);
|
|
|
|
// roleX: SYNC_ACTION with condition + admin-token CRUD actions
|
|
// roleY: initially empty
|
|
const roleX = await utils.createRole({
|
|
name: 't1-5-role-x',
|
|
description: 'Role X for T1-5',
|
|
});
|
|
const roleY = await utils.createRole({
|
|
name: 't1-5-role-y',
|
|
description: 'Role Y for T1-5',
|
|
});
|
|
|
|
try {
|
|
await utils.assignPermissionsToRole(roleX.id, [
|
|
{
|
|
action: SYNC_ACTION,
|
|
subject: SYNC_SUBJECT,
|
|
conditions: ['admin::is-creator'],
|
|
properties: {},
|
|
},
|
|
...ADMIN_TOKEN_PERMS,
|
|
]);
|
|
|
|
const tempUser = await utils.createUser({
|
|
email: 't1-5-user@test.com',
|
|
firstname: 'T15',
|
|
lastname: 'User',
|
|
isActive: true,
|
|
roles: [roleX.id, roleY.id],
|
|
});
|
|
|
|
try {
|
|
const rqTemp = await createAuthRequest({
|
|
strapi,
|
|
userInfo: { email: 't1-5-user@test.com' } as any,
|
|
});
|
|
|
|
const resToken = await rqTemp({
|
|
url: '/admin/admin-tokens',
|
|
method: 'POST',
|
|
body: {
|
|
name: 't1-5-token',
|
|
adminPermissions: [
|
|
{
|
|
action: SYNC_ACTION,
|
|
subject: SYNC_SUBJECT,
|
|
conditions: ['admin::is-creator'],
|
|
properties: {},
|
|
},
|
|
],
|
|
},
|
|
});
|
|
expect(resToken.status).toBe(201);
|
|
const tempToken: { id: number } = resToken.body.data;
|
|
|
|
// Baseline: token should have condition admin::is-creator
|
|
const baselinePerms = await getAdminPermissionsFromDB(tempToken.id);
|
|
const baselinePerm = baselinePerms.find((p) => p.action === SYNC_ACTION);
|
|
expect(baselinePerm).toBeDefined();
|
|
expect(baselinePerm?.conditions).toStrictEqual(['admin::is-creator']);
|
|
|
|
// Pure addition to roleY — no deletions in roleY
|
|
await utils.assignPermissionsToRole(roleY.id, [
|
|
{ action: SYNC_ACTION, subject: SYNC_SUBJECT, conditions: [], properties: {} },
|
|
]);
|
|
|
|
// user has SYNC_ACTION from roleX (conditioned) AND roleY (unconditional)
|
|
// → anyUserPermIsUnconditional = true → enforcedConditions = [] → token updated
|
|
const afterPerms = await getAdminPermissionsFromDB(tempToken.id);
|
|
const afterPerm = afterPerms.find((p) => p.action === SYNC_ACTION);
|
|
expect(afterPerm).toBeDefined();
|
|
expect(afterPerm?.conditions).toStrictEqual([]);
|
|
} finally {
|
|
await strapi.db.query('admin::user').delete({ where: { id: tempUser.id } });
|
|
}
|
|
} finally {
|
|
await strapi.db.query('admin::api-token').deleteMany({ where: { kind: 'admin' } });
|
|
await strapi.db.query('admin::role').delete({ where: { id: roleX.id } });
|
|
await strapi.db.query('admin::role').delete({ where: { id: roleY.id } });
|
|
}
|
|
});
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// T2 — User-role binding changed (admin::user afterUpdate lifecycle)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
describe('T2 — User-role binding changed', () => {
|
|
test('T2-1 Remove role from user → token permissions from that role deleted', async () => {
|
|
// Strip all roles from syncUser
|
|
await strapi.db.query('admin::user').update({
|
|
where: { id: syncUserId },
|
|
data: { roles: [] },
|
|
});
|
|
|
|
const perms = await getAdminPermissionsFromDB(tokenA.id);
|
|
const actions = perms.map((p) => p.action);
|
|
expect(actions).not.toContain(SYNC_ACTION);
|
|
});
|
|
|
|
test('T2-2 Add role to user → existing token unaffected (no expansion)', async () => {
|
|
// Add roleB to syncUser (who currently only has roleA)
|
|
// Token was created with SYNC_ACTION only; sync is not expansive
|
|
await strapi.db.query('admin::user').update({
|
|
where: { id: syncUserId },
|
|
data: { roles: [roleA.id, roleB.id] },
|
|
});
|
|
|
|
const perms = await getAdminPermissionsFromDB(tokenA.id);
|
|
const actions = perms.map((p) => p.action);
|
|
expect(actions).toContain(SYNC_ACTION);
|
|
expect(actions).not.toContain(SYNC_ACTION_2);
|
|
});
|
|
|
|
test('T2-3 Multi-role: remove one role that also grants SYNC_ACTION via the other', async () => {
|
|
// syncUserBoth starts with roleA + roleB; remove roleA
|
|
// roleB still grants SYNC_ACTION → tokenBoth should keep it
|
|
await strapi.db.query('admin::user').update({
|
|
where: { id: syncUserBothId },
|
|
data: { roles: [roleB.id] },
|
|
});
|
|
|
|
const perms = await getAdminPermissionsFromDB(tokenBoth.id);
|
|
const actions = perms.map((p) => p.action);
|
|
expect(actions).toContain(SYNC_ACTION);
|
|
});
|
|
|
|
test('T2-4 Assign role with conditioned permission → token condition applied when only conditioned role grants it', async () => {
|
|
const utils = createUtils(strapi);
|
|
|
|
const roleC = await utils.createRole({
|
|
name: 'sync-test-role-c',
|
|
description: 'Role C with conditioned SYNC_ACTION',
|
|
});
|
|
|
|
try {
|
|
await utils.assignPermissionsToRole(roleC.id, [
|
|
{
|
|
action: SYNC_ACTION,
|
|
subject: SYNC_SUBJECT,
|
|
conditions: ['admin::is-creator'],
|
|
properties: {},
|
|
},
|
|
]);
|
|
|
|
// Add roleC to syncUser first so token keeps SYNC_ACTION when we remove it from roleA
|
|
await strapi.db.query('admin::user').update({
|
|
where: { id: syncUserId },
|
|
data: { roles: [roleA.id, roleC.id] },
|
|
});
|
|
|
|
// Strip SYNC_ACTION from roleA → sync runs; only roleC grants it (with condition) → token condition updated
|
|
await utils.assignPermissionsToRole(roleA.id, [...ADMIN_TOKEN_PERMS]);
|
|
|
|
const perms = await getAdminPermissionsFromDB(tokenA.id);
|
|
const syncPerm = perms.find((p) => p.action === SYNC_ACTION);
|
|
expect(syncPerm).toBeDefined();
|
|
expect(syncPerm?.conditions).toStrictEqual(['admin::is-creator']);
|
|
} finally {
|
|
await strapi.db.query('admin::role').delete({ where: { id: roleC.id } });
|
|
}
|
|
});
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// T3 — Role deleted (admin::role beforeDelete/afterDelete lifecycles)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
describe('T3 — Role deleted', () => {
|
|
test('T3-1 Delete role → only permissions sourced from that role are removed; others survive', async () => {
|
|
const utils = createUtils(strapi);
|
|
|
|
// Create two throw-away roles for a fresh tempUser
|
|
const tempRoleKept = await utils.createRole({
|
|
name: 'sync-temp-role-kept',
|
|
description: 'Temp role that will be kept',
|
|
});
|
|
const tempRoleDeleted = await utils.createRole({
|
|
name: 'sync-temp-role-deleted',
|
|
description: 'Temp role that will be deleted',
|
|
});
|
|
|
|
try {
|
|
// tempRoleKept: grants SYNC_ACTION + admin-tokens CRUD
|
|
await utils.assignPermissionsToRole(tempRoleKept.id, [
|
|
{ action: SYNC_ACTION, subject: SYNC_SUBJECT, conditions: [], properties: {} },
|
|
...ADMIN_TOKEN_PERMS,
|
|
]);
|
|
|
|
// tempRoleDeleted: grants SYNC_ACTION_2 only
|
|
await utils.assignPermissionsToRole(tempRoleDeleted.id, [
|
|
{ action: SYNC_ACTION_2, subject: SYNC_SUBJECT, conditions: [], properties: {} },
|
|
]);
|
|
|
|
const tempUser = await utils.createUser({
|
|
email: 'sync-temp-user@test.com',
|
|
firstname: 'Temp',
|
|
lastname: 'SyncUser',
|
|
isActive: true,
|
|
roles: [tempRoleKept.id, tempRoleDeleted.id],
|
|
});
|
|
|
|
try {
|
|
const tempRq = await createAuthRequest({
|
|
strapi,
|
|
userInfo: { email: 'sync-temp-user@test.com' } as any,
|
|
});
|
|
|
|
// Create a token with both SYNC_ACTION and SYNC_ACTION_2
|
|
const createRes = await tempRq({
|
|
url: '/admin/admin-tokens',
|
|
method: 'POST',
|
|
body: {
|
|
name: 'sync-temp-token',
|
|
adminPermissions: [
|
|
{ action: SYNC_ACTION, subject: SYNC_SUBJECT, conditions: [], properties: {} },
|
|
{ action: SYNC_ACTION_2, subject: SYNC_SUBJECT, conditions: [], properties: {} },
|
|
],
|
|
},
|
|
});
|
|
expect(createRes.status).toBe(201);
|
|
const tempToken = createRes.body.data;
|
|
|
|
// The role service's deleteByIds requires 0 users (checkRolesIdForDeletion).
|
|
// We bypass it by calling strapi.db.query directly so the beforeDelete/afterDelete
|
|
// lifecycles fire while tempUser still holds tempRoleDeleted, exercising the sync path.
|
|
// Role permissions are cleaned up manually first (mirrors what deleteByIds does internally).
|
|
await strapi.db
|
|
.query('admin::permission')
|
|
.deleteMany({ where: { role: { id: tempRoleDeleted.id } } });
|
|
await strapi.db.query('admin::role').delete({ where: { id: tempRoleDeleted.id } });
|
|
|
|
const perms = await getAdminPermissionsFromDB(tempToken.id);
|
|
const actions = perms.map((p) => p.action);
|
|
|
|
// SYNC_ACTION still covered by tempRoleKept → must survive
|
|
expect(actions).toContain(SYNC_ACTION);
|
|
// SYNC_ACTION_2 was exclusively from tempRoleDeleted → must be gone
|
|
expect(actions).not.toContain(SYNC_ACTION_2);
|
|
} finally {
|
|
await utils.deleteUserById(tempUser.id);
|
|
}
|
|
} finally {
|
|
// Clean up roles that may still exist
|
|
const kept = await strapi.db
|
|
.query('admin::role')
|
|
.findOne({ where: { id: tempRoleKept.id } });
|
|
if (kept !== null && kept !== undefined) {
|
|
await strapi.db.query('admin::role').delete({ where: { id: tempRoleKept.id } });
|
|
}
|
|
}
|
|
});
|
|
|
|
test("T3-2 Super-admin's tokens are never touched by sync", async () => {
|
|
const utils = createUtils(strapi);
|
|
|
|
const superAdminRole = await utils.getSuperAdminRole();
|
|
|
|
// Create a super-admin user with a token that has no permissions
|
|
const saUser = await utils.createUser({
|
|
email: 'sync-sa-user@test.com',
|
|
firstname: 'SA',
|
|
lastname: 'SyncUser',
|
|
isActive: true,
|
|
roles: [superAdminRole.id],
|
|
});
|
|
|
|
try {
|
|
const saRq = await createAuthRequest({
|
|
strapi,
|
|
userInfo: { email: 'sync-sa-user@test.com' } as any,
|
|
});
|
|
|
|
const createRes = await saRq({
|
|
url: '/admin/admin-tokens',
|
|
method: 'POST',
|
|
body: {
|
|
name: 'sync-sa-token',
|
|
adminPermissions: [],
|
|
},
|
|
});
|
|
expect(createRes.status).toBe(201);
|
|
const saToken = createRes.body.data;
|
|
|
|
// Trigger syncPermissionsForUser directly for the SA user.
|
|
// The guard inside syncPermissionsForUser skips super-admins entirely.
|
|
await (
|
|
strapi.service('admin::api-token-admin') as {
|
|
syncPermissionsForUser(id: number): Promise<void>;
|
|
}
|
|
).syncPermissionsForUser(saUser.id);
|
|
|
|
// SA token permissions must be unchanged (sync skips super-admins)
|
|
const perms = await getAdminPermissionsFromDB(saToken.id);
|
|
expect(perms).toStrictEqual([]);
|
|
} finally {
|
|
await utils.deleteUserById(saUser.id);
|
|
}
|
|
});
|
|
});
|
|
});
|