mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-21 00:36:13 +08:00
90 lines
2.6 KiB
JavaScript
90 lines
2.6 KiB
JavaScript
const assert = require('node:assert/strict');
|
|
const { test } = require('node:test');
|
|
const {
|
|
InvalidSecurityContractError,
|
|
normalizeSecurityPolicyDecision,
|
|
normalizeSecurityPrincipal,
|
|
} = require('@qinglong/runtime-core/security');
|
|
|
|
const NOW = 10_000;
|
|
|
|
test('normalizes an active principal without retaining mutable input', () => {
|
|
const input = {
|
|
subject: { type: 'user', id: 'usr_primary' },
|
|
authenticationId: 'session:abc123',
|
|
authenticatedAtMs: 9_000,
|
|
expiresAtMs: 11_000,
|
|
assurance: 'multi_factor',
|
|
};
|
|
const principal = normalizeSecurityPrincipal(input, NOW);
|
|
input.subject.id = 'usr_changed';
|
|
assert.deepEqual(principal, {
|
|
subject: { type: 'user', id: 'usr_primary' },
|
|
authenticationId: 'session:abc123',
|
|
authenticatedAtMs: 9_000,
|
|
expiresAtMs: 11_000,
|
|
assurance: 'multi_factor',
|
|
});
|
|
assert.equal(Object.isFrozen(principal), true);
|
|
assert.equal(Object.isFrozen(principal.subject), true);
|
|
});
|
|
|
|
test('rejects expired, future, malformed and widened principals', () => {
|
|
const principal = {
|
|
subject: { type: 'worker', id: 'worker-1' },
|
|
authenticationId: 'mtls:worker-1',
|
|
authenticatedAtMs: 9_000,
|
|
expiresAtMs: 11_000,
|
|
assurance: 'service',
|
|
};
|
|
for (const candidate of [
|
|
{ ...principal, expiresAtMs: NOW },
|
|
{ ...principal, authenticatedAtMs: NOW + 1 },
|
|
{ ...principal, subject: { type: 'root', id: 'root' } },
|
|
{ ...principal, debug: true },
|
|
]) {
|
|
assert.throws(
|
|
() => normalizeSecurityPrincipal(candidate, NOW),
|
|
InvalidSecurityContractError,
|
|
);
|
|
}
|
|
});
|
|
|
|
test('normalizes bounded policy decisions and version fences', () => {
|
|
const input = {
|
|
effect: 'allow',
|
|
reasons: ['role_grant'],
|
|
fence: { projectVersion: 3, bindingVersion: 7 },
|
|
};
|
|
const decision = normalizeSecurityPolicyDecision(input);
|
|
input.reasons[0] = 'changed';
|
|
assert.deepEqual(decision, {
|
|
effect: 'allow',
|
|
reasons: ['role_grant'],
|
|
fence: { projectVersion: 3, bindingVersion: 7 },
|
|
});
|
|
assert.equal(Object.isFrozen(decision.reasons), true);
|
|
});
|
|
|
|
test('rejects empty or unbounded reasons and invalid fences', () => {
|
|
for (const candidate of [
|
|
{ effect: 'deny', reasons: [], fence: null },
|
|
{ effect: 'unknown', reasons: ['unknown_effect'], fence: null },
|
|
{
|
|
effect: 'allow',
|
|
reasons: ['role-grant'],
|
|
fence: { projectVersion: 1, bindingVersion: null },
|
|
},
|
|
{
|
|
effect: 'allow',
|
|
reasons: ['role_grant'],
|
|
fence: { projectVersion: 0, bindingVersion: null },
|
|
},
|
|
]) {
|
|
assert.throws(
|
|
() => normalizeSecurityPolicyDecision(candidate),
|
|
InvalidSecurityContractError,
|
|
);
|
|
}
|
|
});
|