Assessor_2.0/server/gpo/gpp-drive-maps.ts

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, context.artifactId, 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;
});
}