39948-vm/backend/tests/integration/access-policy.test.js
2026-06-28 21:29:29 +02:00

268 lines
7.0 KiB
JavaScript

const assert = require('node:assert/strict');
const test = require('node:test');
const db = require('../../src/db/models');
const AccessPolicy = require('../../src/services/access-policy');
const AccessPolicyAuditService = require('../../src/services/access-policy-audit');
const suffix = `${Date.now()}-${process.pid}`;
test.after(async () => {
await db.sequelize.close();
});
async function authenticateWithTimeout(timeoutMs = 1500) {
let timeoutId;
const timeout = new Promise((_, reject) => {
timeoutId = setTimeout(
() => reject(new Error(`Database unavailable after ${timeoutMs}ms`)),
timeoutMs,
);
});
try {
await Promise.race([db.sequelize.authenticate(), timeout]);
} finally {
clearTimeout(timeoutId);
}
}
async function withTransaction(t, callback) {
try {
await authenticateWithTimeout();
} catch (error) {
t.skip(`Database unavailable: ${error.message}`);
return;
}
const transaction = await db.sequelize.transaction();
try {
await callback(transaction);
} finally {
await transaction.rollback();
}
}
async function createRole(name, transaction) {
return db.roles.create({ name }, { transaction });
}
async function createPermission(name, transaction) {
return db.permissions.create({ name }, { transaction });
}
async function createUser({ email, role }, transaction) {
const user = await db.users.create(
{
email,
password: 'not-used-in-test',
emailVerified: true,
},
{ transaction },
);
await user.setApp_role(role, { transaction });
return user;
}
async function createProject({ slug, visibility }, transaction) {
return db.projects.create(
{
name: `Test ${slug}`,
slug,
production_presentation_visibility: visibility,
},
{ transaction },
);
}
test('guest can view public production presentation only', async (t) => {
await withTransaction(t, async (transaction) => {
const publicProject = await createProject(
{
slug: `test-public-runtime-access-${suffix}`,
visibility: 'public',
},
transaction,
);
const privateProject = await createProject(
{
slug: `test-private-runtime-access-${suffix}`,
visibility: 'private',
},
transaction,
);
assert.equal(
await AccessPolicy.canViewProductionPresentation(
null,
publicProject.slug,
{ transaction },
),
true,
);
assert.equal(
await AccessPolicy.canViewProductionPresentation(
null,
privateProject.slug,
{ transaction },
),
false,
);
});
});
test('public user can view granted private production presentation', async (t) => {
await withTransaction(t, async (transaction) => {
const publicRole = await createRole('Public', transaction);
const publicUser = await createUser(
{
email: `public-granted-${suffix}@example.test`,
role: publicRole,
},
transaction,
);
const privateProject = await createProject(
{
slug: `test-private-granted-runtime-access-${suffix}`,
visibility: 'private',
},
transaction,
);
await db.production_presentation_access.create(
{
userId: publicUser.id,
projectId: privateProject.id,
},
{ transaction },
);
const authUser = await db.users.findOne({
where: { id: publicUser.id },
include: [
{ association: 'app_role', include: [{ association: 'permissions' }] },
{ association: 'custom_permissions' },
],
transaction,
});
assert.equal(
await AccessPolicy.canViewProductionPresentation(
authUser.get({ plain: true }),
privateProject.slug,
{ transaction },
),
true,
);
});
});
test('internal user with permission can use admin api and view private presentation', async (t) => {
await withTransaction(t, async (transaction) => {
const role = await createRole('Content Reviewer', transaction);
const permission = await createPermission(
`TEST_READ_PROJECTS_${suffix}`,
transaction,
);
await role.setPermissions([permission], { transaction });
const internalUser = await createUser(
{
email: `internal-access-${suffix}@example.test`,
role,
},
transaction,
);
const privateProject = await createProject(
{
slug: `test-private-internal-runtime-access-${suffix}`,
visibility: 'private',
},
transaction,
);
const authUser = await db.users.findOne({
where: { id: internalUser.id },
include: [
{ association: 'app_role', include: [{ association: 'permissions' }] },
{ association: 'custom_permissions' },
],
transaction,
});
const plainUser = authUser.get({ plain: true });
assert.equal(AccessPolicy.canUseAdminApi(plainUser), true);
assert.equal(
await AccessPolicy.canViewProductionPresentation(
plainUser,
privateProject.slug,
{ transaction },
),
true,
);
});
});
test('audit finds and cleanup removes stale Public grants', async (t) => {
await withTransaction(t, async (transaction) => {
const publicRole = await createRole('Public', transaction);
const internalRole = await createRole('Tour Designer', transaction);
const permission = await createPermission(
`TEST_READ_USERS_${suffix}`,
transaction,
);
await publicRole.setPermissions([permission], { transaction });
const publicUser = await createUser(
{
email: `public-stale-${suffix}@example.test`,
role: publicRole,
},
transaction,
);
await publicUser.setCustom_permissions([permission], { transaction });
const internalUser = await createUser(
{
email: `internal-stale-grant-${suffix}@example.test`,
role: internalRole,
},
transaction,
);
const privateProject = await createProject(
{
slug: `test-private-stale-grant-${suffix}`,
visibility: 'private',
},
transaction,
);
await db.production_presentation_access.create(
{
userId: internalUser.id,
projectId: privateProject.id,
},
{ transaction },
);
const report = await AccessPolicyAuditService.findViolations({
transaction,
});
assert.ok(report.publicRolePermissions.length >= 1);
assert.ok(report.publicUsersWithCustomPermissions.length >= 1);
assert.ok(report.productionPresentationAccessForNonPublicUsers.length >= 1);
const cleanup = await AccessPolicyAuditService.cleanupViolations({
transaction,
});
assert.ok(cleanup.removedPublicRolePermissions >= 1);
assert.ok(cleanup.clearedPublicUserCustomPermissions >= 1);
assert.ok(cleanup.removedNonPublicProductionPresentationGrants >= 1);
const after = await AccessPolicyAuditService.findViolations({
transaction,
});
assert.equal(AccessPolicyAuditService.hasViolations(after), false);
});
});