113 lines
5.6 KiB
TypeScript
113 lines
5.6 KiB
TypeScript
import { createHash } from 'node:crypto';
|
|
import { describe, expect, it } from 'vitest';
|
|
import {
|
|
parseGroupPolicyPreferenceXml,
|
|
reduceGroupPolicyPreferences
|
|
} from '../../server/gpo/group-policy-preference-parser';
|
|
import type { VerifiedZipFile } from '../../server/gpo/zip-inventory';
|
|
|
|
const rootClsid = '{3125E937-EB16-4b4c-9934-544FC6D24D26}';
|
|
const userClsid = '{DF5F1855-51E5-4d24-8B1A-D9BDE98BA1D1}';
|
|
const groupClsid = '{6D4A79E4-529C-4481-ABD0-F5BD7EA93BA7}';
|
|
|
|
function verifiedFile(content: string): VerifiedZipFile {
|
|
const bytes = new TextEncoder().encode(content);
|
|
return {
|
|
entry: {
|
|
index: 1,
|
|
relativePath: 'synthetic/DomainSysvol/GPO/Machine/Preferences/Groups/Groups.xml',
|
|
kind: 'file',
|
|
compressionMethod: 8,
|
|
compressedBytes: bytes.byteLength,
|
|
uncompressedBytes: bytes.byteLength,
|
|
crc32: '00000000',
|
|
sha256: createHash('sha256').update(bytes).digest('hex')
|
|
},
|
|
bytes
|
|
};
|
|
}
|
|
|
|
function context() {
|
|
return { backupInstanceId: 'backup-synthetic', sourceArtifactId: 'source-synthetic', artifactId: 'entry-synthetic' };
|
|
}
|
|
|
|
describe('Local Users and Groups preference CSE', () => {
|
|
it('keeps user, group and ordered membership semantics without retaining cpassword', () => {
|
|
const secret = 'synthetic-secret-never-return';
|
|
const document = parseGroupPolicyPreferenceXml(verifiedFile([
|
|
`<?xml version="1.0"?><Groups clsid="${rootClsid}" disabled="0">`,
|
|
` <User clsid="${userClsid}" uid="{11111111-1111-4111-8111-111111111111}" applyOnce="1">`,
|
|
` <Properties action="U" userName="SyntheticAdmin" newName="SyntheticOperator" fullName="Synthetic Operator" cpassword="${secret}" changeLogon="1" noChange="1" neverExpires="1" acctDisabled="0"/>`,
|
|
' <Filters><FilterGroup bool="AND" not="0" name="Synthetic\\Scope"/></Filters>',
|
|
' </User>',
|
|
` <Group clsid="${groupClsid}" uid="{22222222-2222-4222-8222-222222222222}">`,
|
|
' <Properties action="U" groupName="Administrators" groupSid="S-1-5-32-544" deleteAllUsers="1" deleteAllGroups="0" removeAccounts="0">',
|
|
' <Members>',
|
|
' <Member action="REMOVE" name="Synthetic\\Old"/>',
|
|
' <Member action="ADD" name="ignored-because-sid-wins" sid="S-1-5-21-1-2-3-1001"/>',
|
|
' </Members>',
|
|
' </Properties>',
|
|
' </Group>',
|
|
'</Groups>'
|
|
].join('\r\n')), 'computer');
|
|
|
|
expect(document).toMatchObject({
|
|
preferenceType: 'local-users-groups', status: 'complete', secretIndicatorStatus: 'present'
|
|
});
|
|
expect(JSON.stringify(document)).not.toContain(secret);
|
|
const observations = reduceGroupPolicyPreferences(document, context());
|
|
expect(JSON.stringify(observations)).not.toContain(secret);
|
|
expect(observations).toEqual([
|
|
expect.objectContaining({
|
|
state: 'instruction',
|
|
identity: expect.objectContaining({ section: 'local user', key: 'syntheticadmin' }),
|
|
preference: expect.objectContaining({ cse: 'local-users-groups', operation: 'update', sequence: 0, applyOnce: true }),
|
|
values: [{
|
|
kind: 'local-user-preference', userName: 'SyntheticAdmin', newName: 'SyntheticOperator',
|
|
fullName: 'Synthetic Operator', passwordAttributePresent: true, changePasswordAtLogon: true,
|
|
accountDisabled: false, passwordNeverExpires: true, passwordChangeAllowed: false
|
|
}],
|
|
sourceLines: [2],
|
|
effectiveState: 'unknown',
|
|
diagnosticCodes: ['gpo-preference-targeting-unevaluated', 'gpo-secret-indicator-present']
|
|
}),
|
|
expect.objectContaining({
|
|
identity: expect.objectContaining({ section: 'local group', key: 'S-1-5-32-544' }),
|
|
preference: expect.objectContaining({ cse: 'local-users-groups', operation: 'update', sequence: 1 }),
|
|
values: [{
|
|
kind: 'local-group-preference', groupName: 'Administrators', groupSid: 'S-1-5-32-544',
|
|
suppressCurrentUserOperation: false, deleteAllUsers: true, deleteAllGroups: false,
|
|
members: [
|
|
{ sequence: 0, operation: 'remove', selector: { kind: 'name', value: 'Synthetic\\Old' } },
|
|
{ sequence: 1, operation: 'add', selector: { kind: 'sid', value: 'S-1-5-21-1-2-3-1001' } }
|
|
]
|
|
}],
|
|
sourceLines: [6],
|
|
effectiveState: 'unknown'
|
|
})
|
|
]);
|
|
});
|
|
|
|
it('fails closed for malformed supported member and boolean semantics', () => {
|
|
const document = parseGroupPolicyPreferenceXml(verifiedFile(
|
|
`<Groups clsid="${rootClsid}"><Group clsid="${groupClsid}" uid="{33333333-3333-4333-8333-333333333333}" disabled="maybe"><Properties groupName="Administrators"><Members><Member action="MOVE" name="Synthetic\\User"/></Members></Properties></Group></Groups>`
|
|
), 'computer');
|
|
expect(document).toMatchObject({ status: 'invalid', instructions: [] });
|
|
expect(document.diagnosticCodes).toEqual(expect.arrayContaining([
|
|
'gpo-preference-boolean-invalid', 'gpo-preference-local-group-member-invalid'
|
|
]));
|
|
expect(reduceGroupPolicyPreferences(document, context())).toEqual([]);
|
|
});
|
|
|
|
it('keeps unknown supported-CSE item shapes partial without salvaging those items', () => {
|
|
const document = parseGroupPolicyPreferenceXml(verifiedFile(
|
|
`<Groups clsid="${rootClsid}"><User clsid="${userClsid}" uid="{44444444-4444-4444-8444-444444444444}"><Properties userName="Synthetic"><Unknown/></Properties></User></Groups>`
|
|
), 'user');
|
|
expect(document).toMatchObject({
|
|
preferenceType: 'local-users-groups', status: 'partial', instructions: [],
|
|
diagnosticCodes: ['gpo-preference-local-user-item-unsupported']
|
|
});
|
|
expect(reduceGroupPolicyPreferences(document, context())).toEqual([]);
|
|
});
|
|
});
|