mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-20 16:07:11 +08:00
256 lines
7.5 KiB
JavaScript
256 lines
7.5 KiB
JavaScript
const assert = require('node:assert/strict');
|
|
const { test } = require('node:test');
|
|
|
|
const {
|
|
LocalIdentityCredentialAdministrationAuthorizationError,
|
|
createLocalIdentityCredentialAdministrationService,
|
|
} = require('@qinglong/local-admin/identity-credential-administration');
|
|
|
|
const MUTATION_ID = '82000000-0000-4000-8000-000000000001';
|
|
const SUBJECT = Object.freeze({ type: 'agent', id: 'agent-planner' });
|
|
const PRINCIPAL = Object.freeze({
|
|
subject: { type: 'user', id: 'owner-user' },
|
|
authenticationId: 'local_identity_admin:test',
|
|
authenticatedAtMs: 0,
|
|
expiresAtMs: 120_000,
|
|
assurance: 'local_console',
|
|
});
|
|
|
|
function projectPolicy(role = 'owner') {
|
|
return {
|
|
async resolve() {
|
|
return {
|
|
project: {
|
|
id: 'default',
|
|
name: 'Default',
|
|
slug: 'default',
|
|
status: 'active',
|
|
version: 1,
|
|
createdAtMs: 0,
|
|
updatedAtMs: 0,
|
|
},
|
|
binding: {
|
|
projectId: 'default',
|
|
subject: PRINCIPAL.subject,
|
|
version: 1,
|
|
state: 'active',
|
|
role,
|
|
mutationId: 'owner-binding',
|
|
changedBy: PRINCIPAL.subject,
|
|
createdAtMs: 0,
|
|
},
|
|
};
|
|
},
|
|
async append() {
|
|
throw new Error('not used');
|
|
},
|
|
};
|
|
}
|
|
|
|
test('replays a committed credential after time advances without rereading Identity state', async () => {
|
|
let nowMs = 1_000;
|
|
let identityReads = 0;
|
|
let stored;
|
|
const repository = {
|
|
async resolveAuthorityProjectId() {
|
|
return 'default';
|
|
},
|
|
async resolveIdentity() {
|
|
identityReads += 1;
|
|
return {
|
|
subject: SUBJECT,
|
|
status: 'active',
|
|
version: 1,
|
|
createdAtMs: 0,
|
|
updatedAtMs: 0,
|
|
};
|
|
},
|
|
async resolveIdentityMutation() {
|
|
return null;
|
|
},
|
|
async appendAuthorizedIdentity() {
|
|
throw new Error('not used');
|
|
},
|
|
async inspectAuthorizedIdentity() {
|
|
throw new Error('not used');
|
|
},
|
|
async resolveCredentialMutation() {
|
|
return stored ?? null;
|
|
},
|
|
async appendAuthorizedCredential(command) {
|
|
if (!stored) {
|
|
stored = Object.freeze({
|
|
projectId: command.authorization.projectId,
|
|
credential: Object.freeze({ ...command.credential }),
|
|
mutation: Object.freeze({ ...command.mutation }),
|
|
delivery: command.delivery,
|
|
audit: command.audit,
|
|
});
|
|
return Object.freeze({ status: 'inserted', ...stored });
|
|
}
|
|
assert.deepEqual(command.credential, stored.credential);
|
|
assert.deepEqual(command.mutation, stored.mutation);
|
|
assert.deepEqual(command.delivery, stored.delivery);
|
|
return Object.freeze({ status: 'existing', ...stored });
|
|
},
|
|
async inspectAuthorizedCredential() {
|
|
throw new Error('not used');
|
|
},
|
|
async resolveDeliveryAcknowledgement() {
|
|
return null;
|
|
},
|
|
async appendAuthorizedDeliveryAcknowledgement() {
|
|
throw new Error('not used');
|
|
},
|
|
async record() {},
|
|
};
|
|
const service = createLocalIdentityCredentialAdministrationService(
|
|
projectPolicy(),
|
|
repository,
|
|
{ now: () => nowMs },
|
|
);
|
|
const request = {
|
|
projectId: 'default',
|
|
operation: 'issue',
|
|
credentialId: 'agent-planner-primary',
|
|
target: SUBJECT,
|
|
expectedCurrentVersion: 0,
|
|
pepperKeyId: 'owner-v1',
|
|
secretDigest: 'a'.repeat(64),
|
|
deliveryDigest: 'b'.repeat(64),
|
|
notBeforeAtMs: 1_000,
|
|
expiresAtMs: 61_000,
|
|
mutationId: MUTATION_ID,
|
|
requestId: 'identity-credential-replay',
|
|
principal: PRINCIPAL,
|
|
};
|
|
|
|
assert.equal((await service.changeCredential(request)).status, 'inserted');
|
|
nowMs = 30_000;
|
|
assert.equal((await service.changeCredential(request)).status, 'existing');
|
|
assert.equal(identityReads, 1);
|
|
assert.equal(stored.mutation.createdAtMs, 1_000);
|
|
assert.equal(stored.credential.notBeforeAtMs, 1_000);
|
|
});
|
|
|
|
test('inspects current versions only after Owner authorization', async () => {
|
|
const audits = [];
|
|
const identity = Object.freeze({
|
|
subject: SUBJECT,
|
|
status: 'active',
|
|
version: 7,
|
|
createdAtMs: 100,
|
|
updatedAtMs: 700,
|
|
});
|
|
const credential = Object.freeze({
|
|
credentialId: 'agent-planner-primary',
|
|
version: 5,
|
|
pepperKeyId: 'owner-v1',
|
|
state: 'active',
|
|
subject: SUBJECT,
|
|
subjectStatus: 'active',
|
|
secretDigest: 'a'.repeat(64),
|
|
createdAtMs: 500,
|
|
notBeforeAtMs: 500,
|
|
expiresAtMs: 60_500,
|
|
});
|
|
const repository = {
|
|
async resolveAuthorityProjectId() {
|
|
return 'default';
|
|
},
|
|
async resolveIdentity() {
|
|
throw new Error('not used');
|
|
},
|
|
async resolveIdentityMutation() {
|
|
throw new Error('not used');
|
|
},
|
|
async appendAuthorizedIdentity() {
|
|
throw new Error('not used');
|
|
},
|
|
async inspectAuthorizedIdentity(command) {
|
|
assert.equal(command.audit.operationId, 'identity.inspect');
|
|
assert.equal(command.authorization.actor.id, 'owner-user');
|
|
return { identity, audit: command.audit };
|
|
},
|
|
async resolveCredentialMutation() {
|
|
throw new Error('not used');
|
|
},
|
|
async appendAuthorizedCredential() {
|
|
throw new Error('not used');
|
|
},
|
|
async inspectAuthorizedCredential(command) {
|
|
assert.equal(command.audit.operationId, 'credential.inspect');
|
|
assert.equal(command.credentialId, credential.credentialId);
|
|
return { credential, audit: command.audit };
|
|
},
|
|
async resolveDeliveryAcknowledgement() {
|
|
throw new Error('not used');
|
|
},
|
|
async appendAuthorizedDeliveryAcknowledgement() {
|
|
throw new Error('not used');
|
|
},
|
|
async record(audit) {
|
|
audits.push(audit);
|
|
},
|
|
};
|
|
const owner = createLocalIdentityCredentialAdministrationService(
|
|
projectPolicy(),
|
|
repository,
|
|
{ now: () => 1_000 },
|
|
);
|
|
const identityResult = await owner.inspectIdentity({
|
|
projectId: 'default',
|
|
target: SUBJECT,
|
|
auditEventId: '82000000-0000-4000-8000-000000000002',
|
|
requestId: 'identity-inspect',
|
|
principal: PRINCIPAL,
|
|
});
|
|
const credentialResult = await owner.inspectCredential({
|
|
projectId: 'default',
|
|
credentialId: credential.credentialId,
|
|
auditEventId: '82000000-0000-4000-8000-000000000003',
|
|
requestId: 'credential-inspect',
|
|
principal: PRINCIPAL,
|
|
});
|
|
assert.equal(identityResult.identity.version, 7);
|
|
assert.equal(credentialResult.credential.version, 5);
|
|
|
|
const nonOwner = createLocalIdentityCredentialAdministrationService(
|
|
projectPolicy('admin'),
|
|
repository,
|
|
{ now: () => 1_000 },
|
|
);
|
|
await assert.rejects(
|
|
nonOwner.inspectIdentity({
|
|
projectId: 'default',
|
|
target: SUBJECT,
|
|
auditEventId: '82000000-0000-4000-8000-000000000004',
|
|
requestId: 'identity-inspect-denied',
|
|
principal: PRINCIPAL,
|
|
}),
|
|
LocalIdentityCredentialAdministrationAuthorizationError,
|
|
);
|
|
assert.equal(audits.length, 1);
|
|
assert.equal(audits[0].operationId, 'identity.inspect');
|
|
assert.equal(audits[0].outcome, 'denied');
|
|
|
|
const foreignProjectOwner =
|
|
createLocalIdentityCredentialAdministrationService(
|
|
projectPolicy(),
|
|
repository,
|
|
{ now: () => 1_000 },
|
|
);
|
|
await assert.rejects(
|
|
foreignProjectOwner.inspectCredential({
|
|
projectId: 'secondary',
|
|
credentialId: credential.credentialId,
|
|
auditEventId: '82000000-0000-4000-8000-000000000005',
|
|
requestId: 'credential-inspect-foreign-project',
|
|
principal: PRINCIPAL,
|
|
}),
|
|
LocalIdentityCredentialAdministrationAuthorizationError,
|
|
);
|
|
assert.equal(audits.length, 2);
|
|
assert.deepEqual(audits[1].reasons, ['instance_authority_project_required']);
|
|
});
|