239 lines
12 KiB
TypeScript
239 lines
12 KiB
TypeScript
import { createHash } from 'node:crypto';
|
|
import type { GpoDriveMapPreferenceValue, GpoPolicyObservation, GpoPreferenceContext } from '../../src/domain/gpo-policy';
|
|
import { gpoPolicyObservationValidationErrors } from '../../src/domain/gpo-policy';
|
|
import type { GpoPolicyScope } from '../../src/domain/gpo-ingestion';
|
|
import { xmlElementStartLines } from './xml-source-lines';
|
|
|
|
export const GPP_DRIVE_MAPS_REDUCER = { id: 'gpo-preferences-drive-maps-reducer', version: '1.0.0' } as const;
|
|
|
|
const drivesClsid = '8fddcc1a-0c3c-43cd-a6b4-71a6df20da8c';
|
|
const driveClsid = '935d1b74-9cb8-4e3c-9914-7dd559b7a417';
|
|
const guidPattern = /^\{?([a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12})\}?$/i;
|
|
|
|
type Operation = Exclude<GpoPreferenceContext['operation'], 'configure'>;
|
|
type Targeting = GpoPreferenceContext['targeting'];
|
|
|
|
export interface GppDriveMapInstruction {
|
|
version: 1;
|
|
cse: 'drive-maps';
|
|
uid: string;
|
|
sequence: number;
|
|
line: number;
|
|
operation: Operation;
|
|
disabled: boolean;
|
|
removePolicy: boolean;
|
|
applyOnce: boolean;
|
|
targeting: Targeting;
|
|
value: GpoDriveMapPreferenceValue;
|
|
}
|
|
|
|
export interface DriveMapsPreferenceDocument {
|
|
version: 1;
|
|
parser: { id: string; version: string };
|
|
sourcePath: string;
|
|
sourceSha256: string;
|
|
scope: GpoPolicyScope;
|
|
preferenceType: 'drive-maps';
|
|
status: 'complete' | 'partial' | 'invalid';
|
|
secretIndicatorStatus: 'not-detected' | 'present';
|
|
instructions: GppDriveMapInstruction[];
|
|
diagnosticCodes: string[];
|
|
}
|
|
|
|
export interface DriveMapsParseContext {
|
|
root: Element;
|
|
text: string;
|
|
scope: GpoPolicyScope;
|
|
parser: { id: string; version: string };
|
|
sourcePath: string;
|
|
sourceSha256: string;
|
|
secretIndicatorStatus: 'not-detected' | 'present';
|
|
diagnostics: Set<string>;
|
|
summarizeTargeting: (item: Element, diagnostics: Set<string>) => Targeting | undefined;
|
|
}
|
|
|
|
const visibilityValues = { NOCHANGE: 'no-change', HIDE: 'hide', SHOW: 'show' } as const;
|
|
const allowedPropertyAttributes = new Set([
|
|
'action', 'thisdrive', 'alldrives', 'username', 'cpassword', 'path', 'label', 'persistent', 'useletter', 'letter'
|
|
]);
|
|
|
|
function attribute(element: Element, name: string): string | undefined {
|
|
const normalized = name.toLocaleLowerCase('en-US');
|
|
return [...element.attributes].find(candidate => candidate.localName.toLocaleLowerCase('en-US') === normalized)?.value;
|
|
}
|
|
|
|
function canonicalGuid(value: string | undefined): string | undefined {
|
|
return value ? guidPattern.exec(value.trim())?.[1].toLocaleLowerCase('en-US') : undefined;
|
|
}
|
|
|
|
function booleanAttribute(element: Element, name: string, diagnostics: Set<string>): boolean | undefined {
|
|
const value = attribute(element, name);
|
|
if (value === undefined || value === '0') return false;
|
|
if (value === '1') return true;
|
|
diagnostics.add('gpo-preference-boolean-invalid');
|
|
return undefined;
|
|
}
|
|
|
|
function operation(value: string | undefined): Operation | undefined {
|
|
const code = (value ?? 'U').toLocaleUpperCase('en-US');
|
|
return ({ C: 'create', D: 'delete', R: 'replace', U: 'update' } as const)[code as 'C' | 'D' | 'R' | 'U'];
|
|
}
|
|
|
|
function visibilityAttribute(
|
|
element: Element,
|
|
name: string,
|
|
diagnostics: Set<string>
|
|
): GpoDriveMapPreferenceValue['thisDrive'] | undefined {
|
|
const key = (attribute(element, name) ?? 'NOCHANGE').toLocaleUpperCase('en-US') as keyof typeof visibilityValues;
|
|
if (Object.hasOwn(visibilityValues, key)) return visibilityValues[key];
|
|
diagnostics.add('gpo-preference-drive-visibility-invalid');
|
|
return undefined;
|
|
}
|
|
|
|
function isUncPath(value: string): boolean {
|
|
if (!value.startsWith('\\\\') || value.includes('/') || /[\u0000-\u001f]/.test(value)) return false;
|
|
const segments = value.slice(2).split('\\');
|
|
return segments.length >= 2 && Boolean(segments[0].trim()) && Boolean(segments[1].trim());
|
|
}
|
|
|
|
function validUserName(value: string): boolean {
|
|
if (!value) return true;
|
|
const separator = value.indexOf('\\');
|
|
return separator > 0 && separator === value.lastIndexOf('\\') && separator < value.length - 1 && !value.includes('/');
|
|
}
|
|
|
|
function selectionMode(operationValue: Operation, useLetter: boolean): GpoDriveMapPreferenceValue['selectionMode'] {
|
|
if (useLetter) return 'exact';
|
|
if (operationValue === 'delete') return 'delete-range';
|
|
if (operationValue === 'create') return 'first-available';
|
|
return 'first-mapped-in-range';
|
|
}
|
|
|
|
function directChildren(element: Element, name: string): Element[] {
|
|
const normalized = name.toLocaleLowerCase('en-US');
|
|
return [...element.children].filter(child => child.localName.toLocaleLowerCase('en-US') === normalized);
|
|
}
|
|
|
|
function itemLines(text: string): number[] {
|
|
return xmlElementStartLines(text, /<(?:[A-Za-z_][\w.-]*:)?Drive(?=[\s/>])/g);
|
|
}
|
|
|
|
function parseDriveValue(
|
|
properties: Element,
|
|
operationValue: Operation,
|
|
diagnostics: Set<string>
|
|
): GpoDriveMapPreferenceValue | undefined {
|
|
const letter = attribute(properties, 'letter')?.trim().toLocaleUpperCase('en-US');
|
|
if (!letter || !/^[A-Z]$/.test(letter)) diagnostics.add('gpo-preference-drive-letter-invalid');
|
|
const useLetter = booleanAttribute(properties, 'useLetter', diagnostics);
|
|
const persistent = booleanAttribute(properties, 'persistent', diagnostics);
|
|
const thisDrive = visibilityAttribute(properties, 'thisDrive', diagnostics);
|
|
const allDrives = visibilityAttribute(properties, 'allDrives', diagnostics);
|
|
const path = attribute(properties, 'path');
|
|
const pathRequired = operationValue === 'create' || operationValue === 'replace';
|
|
if ((pathRequired && !path) || (path && !isUncPath(path))) diagnostics.add('gpo-preference-drive-path-invalid');
|
|
const userName = attribute(properties, 'userName');
|
|
if (userName !== undefined && !validUserName(userName)) diagnostics.add('gpo-preference-drive-user-invalid');
|
|
if (!letter || useLetter === undefined || persistent === undefined || !thisDrive || !allDrives) return undefined;
|
|
return {
|
|
kind: 'drive-map-preference', path, label: attribute(properties, 'label'), persistent,
|
|
userName, passwordAttributePresent: [...properties.attributes]
|
|
.some(candidate => candidate.localName.toLocaleLowerCase('en-US') === 'cpassword'),
|
|
letter, useLetter, selectionMode: selectionMode(operationValue, useLetter), thisDrive, allDrives
|
|
};
|
|
}
|
|
|
|
export function parseDriveMapsPreference(context: DriveMapsParseContext): DriveMapsPreferenceDocument {
|
|
const { root, diagnostics } = context;
|
|
const rootDisabled = booleanAttribute(root, 'disabled', diagnostics) ?? false;
|
|
if (canonicalGuid(attribute(root, 'clsid')) !== drivesClsid) diagnostics.add('gpo-preference-drives-root-invalid');
|
|
const items = directChildren(root, 'Drive');
|
|
const unsupportedScope = context.scope !== 'user';
|
|
const unsupportedRootChild = [...root.children].some(child => child.localName.toLocaleLowerCase('en-US') !== 'drive');
|
|
let partial = unsupportedScope || unsupportedRootChild;
|
|
if (unsupportedScope) diagnostics.add('gpo-preference-drives-scope-unsupported');
|
|
if (unsupportedRootChild) diagnostics.add('gpo-preference-drive-item-unsupported');
|
|
const lines = itemLines(context.text);
|
|
if (lines.length !== items.length) diagnostics.add('gpo-preference-line-provenance-invalid');
|
|
let invalid = diagnostics.has('gpo-preference-attribute-limit')
|
|
|| diagnostics.has('gpo-preference-drives-root-invalid')
|
|
|| diagnostics.has('gpo-preference-line-provenance-invalid')
|
|
|| diagnostics.has('gpo-preference-boolean-invalid');
|
|
const instructions: GppDriveMapInstruction[] = [];
|
|
items.forEach((item, sequence) => {
|
|
const before = diagnostics.size;
|
|
const uid = canonicalGuid(attribute(item, 'uid'));
|
|
if (!uid || canonicalGuid(attribute(item, 'clsid')) !== driveClsid) diagnostics.add('gpo-preference-drive-item-invalid');
|
|
const properties = directChildren(item, 'Properties');
|
|
const allowedItemChildren = [...item.children].every(child => ['properties', 'filters'].includes(child.localName.toLocaleLowerCase('en-US')));
|
|
if (properties.length !== 1 || !allowedItemChildren) diagnostics.add('gpo-preference-drive-item-invalid');
|
|
const props = properties[0];
|
|
const unsupportedShape = props && (props.children.length > 0 || [...props.attributes]
|
|
.some(candidate => !allowedPropertyAttributes.has(candidate.localName.toLocaleLowerCase('en-US'))));
|
|
if (unsupportedShape) {
|
|
diagnostics.add('gpo-preference-drive-item-unsupported');
|
|
partial = true;
|
|
return;
|
|
}
|
|
const operationValue = props ? operation(attribute(props, 'action')) : undefined;
|
|
if (!operationValue) diagnostics.add('gpo-preference-action-invalid');
|
|
const removePolicy = booleanAttribute(item, 'removePolicy', diagnostics) ?? false;
|
|
const applyOnce = booleanAttribute(item, 'applyOnce', diagnostics) ?? false;
|
|
const itemDisabled = booleanAttribute(item, 'disabled', diagnostics) ?? false;
|
|
const itemTargeting = context.summarizeTargeting(item, diagnostics);
|
|
if (operationValue === 'delete' && removePolicy) diagnostics.add('gpo-preference-remove-policy-invalid');
|
|
const value = props && operationValue ? parseDriveValue(props, operationValue, diagnostics) : undefined;
|
|
if (diagnostics.size !== before || !uid || !operationValue || !itemTargeting || !value) { invalid = true; return; }
|
|
instructions.push({
|
|
version: 1, cse: 'drive-maps', uid, sequence, line: lines[sequence] ?? 1, operation: operationValue,
|
|
disabled: rootDisabled || itemDisabled, removePolicy, applyOnce, targeting: itemTargeting, value
|
|
});
|
|
});
|
|
if (diagnostics.has('gpo-preference-boolean-invalid')
|
|
|| diagnostics.has('gpo-preference-drive-visibility-invalid')
|
|
|| diagnostics.has('gpo-preference-drive-letter-invalid')
|
|
|| diagnostics.has('gpo-preference-drive-path-invalid')
|
|
|| diagnostics.has('gpo-preference-drive-user-invalid')) invalid = true;
|
|
return {
|
|
version: 1, parser: context.parser, sourcePath: context.sourcePath, sourceSha256: context.sourceSha256,
|
|
scope: context.scope, preferenceType: 'drive-maps', status: invalid ? 'invalid' : partial ? 'partial' : 'complete',
|
|
secretIndicatorStatus: context.secretIndicatorStatus, instructions: invalid || unsupportedScope ? [] : instructions,
|
|
diagnosticCodes: [...diagnostics].sort()
|
|
};
|
|
}
|
|
|
|
export function reduceDriveMapsPreferences(
|
|
document: DriveMapsPreferenceDocument,
|
|
context: { backupInstanceId: string; sourceArtifactId: string; artifactId: string }
|
|
): GpoPolicyObservation[] {
|
|
if (document.status === 'invalid' || document.scope !== 'user') return [];
|
|
return document.instructions.map(instruction => {
|
|
const id = `gpo-policy-${createHash('sha256').update([
|
|
context.backupInstanceId, document.sourceSha256, instruction.uid, String(instruction.sequence), GPP_DRIVE_MAPS_REDUCER.version
|
|
].join('\0')).digest('hex').slice(0, 32)}`;
|
|
const diagnostics = new Set<string>();
|
|
if (instruction.targeting.status === 'present') diagnostics.add('gpo-preference-targeting-unevaluated');
|
|
if (instruction.value.passwordAttributePresent) diagnostics.add('gpo-secret-indicator-present');
|
|
const observation: GpoPolicyObservation = {
|
|
version: 1, id, backupInstanceId: context.backupInstanceId, sourceArtifactId: context.sourceArtifactId,
|
|
artifactId: context.artifactId, sourcePath: document.sourcePath, sourceSha256: document.sourceSha256,
|
|
scope: 'user', deliveryMechanism: 'group-policy-preference', parser: document.parser,
|
|
reducer: GPP_DRIVE_MAPS_REDUCER,
|
|
identity: {
|
|
status: 'unresolved', namespace: 'group-policy-preference', section: 'drive map',
|
|
key: `${instruction.value.letter}:${instruction.value.selectionMode}`
|
|
},
|
|
state: 'instruction', values: [instruction.value],
|
|
preference: {
|
|
cse: 'drive-maps', itemUid: instruction.uid, operation: instruction.operation, sequence: instruction.sequence,
|
|
disabled: instruction.disabled, removePolicy: instruction.removePolicy, applyOnce: instruction.applyOnce,
|
|
targeting: instruction.targeting
|
|
},
|
|
sourceLines: [instruction.line], effectiveState: 'unknown', diagnosticCodes: [...diagnostics].sort()
|
|
};
|
|
const errors = gpoPolicyObservationValidationErrors(observation);
|
|
if (errors.length) throw new Error(`Intern ungültige Drive-Maps-Beobachtung: ${errors.join(' ')}`);
|
|
return observation;
|
|
});
|
|
}
|