From ca15d635d472d25934ed1fb62d5791b1450cc17b Mon Sep 17 00:00:00 2001 From: Meik Date: Wed, 18 Mar 2026 16:32:40 +0100 Subject: [PATCH] Bound NTFS AD group name lengths --- LiamNtfs/C4IT_IAM_SET/DataArea_FileSystem.cs | 18 +- LiamNtfs/C4IT_IAM_SET/Helper.cs | 256 +++++++++++++++---- LiamNtfs/C4IT_IAM_SET/SecurityGroup.cs | 29 ++- 3 files changed, 249 insertions(+), 54 deletions(-) diff --git a/LiamNtfs/C4IT_IAM_SET/DataArea_FileSystem.cs b/LiamNtfs/C4IT_IAM_SET/DataArea_FileSystem.cs index 9ac3272..1667795 100644 --- a/LiamNtfs/C4IT_IAM_SET/DataArea_FileSystem.cs +++ b/LiamNtfs/C4IT_IAM_SET/DataArea_FileSystem.cs @@ -530,13 +530,25 @@ namespace C4IT_IAM_SET var folderName = sanitizedSegments.Length > 0 ? sanitizedSegments[sanitizedSegments.Length - 1] : Helper.SanitizePathSegment(Path.GetFileName(parent.FullName.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar))); - var traverseNameTemplate = Helper.ApplyTemplatePlaceholders(traverseGroupTemplate.NamingTemplate, true, relativePath, sanitizedSegments, folderName); - var traverseDescriptionTemplate = Helper.ApplyTemplatePlaceholders(traverseGroupTemplate.DescriptionTemplate, true, relativePath, sanitizedSegments, folderName); + var boundedTraverseContext = Helper.GetBoundedAdGroupTemplateContext( + traverseGroupTemplate.NamingTemplate, + true, + relativePath, + sanitizedSegments, + folderName, + null, + Helper.MaxAdGroupNameLength, + $"Traverse fuer '{parent.FullName}'"); + var adjustedTraverseSegments = boundedTraverseContext.SanitizedSegments ?? Array.Empty(); + var adjustedTraverseRelativePath = adjustedTraverseSegments.Length > 0 ? string.Join("_", adjustedTraverseSegments) : string.Empty; + var adjustedTraverseFolderName = boundedTraverseContext.FolderName; + var traverseNameTemplate = Helper.ApplyTemplatePlaceholders(traverseGroupTemplate.NamingTemplate, true, adjustedTraverseRelativePath, adjustedTraverseSegments, adjustedTraverseFolderName); + var traverseDescriptionTemplate = Helper.ApplyTemplatePlaceholders(traverseGroupTemplate.DescriptionTemplate, true, adjustedTraverseRelativePath, adjustedTraverseSegments, adjustedTraverseFolderName); string traverseRegex = null; try { - traverseRegex = Helper.ApplyTemplatePlaceholders(traverseGroupTemplate.WildcardTemplate, true, relativePath, sanitizedSegments, folderName); + traverseRegex = Helper.ApplyTemplatePlaceholders(traverseGroupTemplate.WildcardTemplate, true, adjustedTraverseRelativePath, adjustedTraverseSegments, adjustedTraverseFolderName); DefaultLogger.LogEntry(LogLevels.Debug, $"traverseRegex: {traverseRegex}"); } catch (Exception ex) diff --git a/LiamNtfs/C4IT_IAM_SET/Helper.cs b/LiamNtfs/C4IT_IAM_SET/Helper.cs index ce87261..2742a40 100644 --- a/LiamNtfs/C4IT_IAM_SET/Helper.cs +++ b/LiamNtfs/C4IT_IAM_SET/Helper.cs @@ -1,4 +1,5 @@ -using System; +using C4IT.Logging; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -10,57 +11,141 @@ namespace C4IT_IAM_Engine { public static class Helper { + public const int MaxAdGroupNameLength = 64; + public const int MaxAdGroupLoopDigits = 3; + + public sealed class BoundedTemplateContext + { + public string[] SanitizedSegments { get; set; } = Array.Empty(); + public string FolderName { get; set; } = string.Empty; + public bool WasShortened { get; set; } + public string OriginalValue { get; set; } = string.Empty; + public string FinalValue { get; set; } = string.Empty; + public string Strategy { get; set; } = string.Empty; + } + public static string ReplaceLoopTag(this string str, int loop) { return Regex.Replace(str, @"(?{{(?[^}]*)(?LOOP)(?[^{]*)}})", loop <= 0 ? "" : "${prefix}" + loop + "${postfix}"); } - public static string ReplaceTags(this string str, IDictionary dict) - { - if (str.Equals(string.Empty) || str == null || dict == null || dict.Count == 0) - return str; - return dict.Aggregate(str, (current, value) => - current.Replace("{{" + value.Key + "}}", value.Value)); - } - public static string ApplyTemplatePlaceholders(string templateValue, bool allowRelativePath, string defaultRelativePath, string[] sanitizedSegments, string folderName) - { - if (templateValue == null) - return string.Empty; - - var result = Regex.Replace(templateValue, @"{{\s*NAME\s*}}", folderName ?? string.Empty, RegexOptions.IgnoreCase); - - if (allowRelativePath) - { - result = Regex.Replace(result, @"{{\s*RELATIVEPATH(?:\s*\(\s*(\d+)\s*\))?\s*}}", match => - { - if (sanitizedSegments == null || sanitizedSegments.Length == 0) - return string.Empty; - - if (!match.Groups[1].Success) - return defaultRelativePath; - - if (!int.TryParse(match.Groups[1].Value, out var segmentIndex) || segmentIndex < 0) - return defaultRelativePath; - - var segmentCount = Math.Min(sanitizedSegments.Length, segmentIndex + 1); - var skip = sanitizedSegments.Length - segmentCount; - return string.Join("_", sanitizedSegments.Skip(skip)); - }, RegexOptions.IgnoreCase); - } - - return result; - } - public static string SanitizePathSegment(string segment) - { - if (string.IsNullOrEmpty(segment)) - return string.Empty; - - return Regex.Replace(segment, @"[\s\-]", "_"); - } - public static void CreatePathWithWriteAccess(string FilePath) - { - try - { - var PF = Environment.ExpandEnvironmentVariables(FilePath); + public static string ReplaceTags(this string str, IDictionary dict) + { + if (str.Equals(string.Empty) || str == null || dict == null || dict.Count == 0) + return str; + return dict.Aggregate(str, (current, value) => + current.Replace("{{" + value.Key + "}}", value.Value)); + } + public static string ApplyTemplatePlaceholders(string templateValue, bool allowRelativePath, string defaultRelativePath, string[] sanitizedSegments, string folderName) + { + if (templateValue == null) + return string.Empty; + + var result = Regex.Replace(templateValue, @"{{\s*NAME\s*}}", folderName ?? string.Empty, RegexOptions.IgnoreCase); + + if (allowRelativePath) + { + result = Regex.Replace(result, @"{{\s*RELATIVEPATH(?:\s*\(\s*(\d+)\s*\))?\s*}}", match => + { + if (sanitizedSegments == null || sanitizedSegments.Length == 0) + return string.Empty; + + if (!match.Groups[1].Success) + return defaultRelativePath; + + if (!int.TryParse(match.Groups[1].Value, out var segmentIndex) || segmentIndex < 0) + return defaultRelativePath; + + var segmentCount = Math.Min(sanitizedSegments.Length, segmentIndex + 1); + var skip = sanitizedSegments.Length - segmentCount; + return string.Join("_", sanitizedSegments.Skip(skip)); + }, RegexOptions.IgnoreCase); + } + + return result; + } + public static BoundedTemplateContext GetBoundedAdGroupTemplateContext( + string templateValue, + bool allowRelativePath, + string defaultRelativePath, + string[] sanitizedSegments, + string folderName, + IDictionary replacementTags, + int maxLength, + string logContext) + { + var effectiveSegments = (sanitizedSegments ?? Array.Empty()).Where(i => i != null).ToArray(); + var effectiveFolderName = folderName ?? string.Empty; + var availableLength = Math.Max(1, maxLength - GetLoopReservationLength(templateValue)); + var originalValue = MaterializeTemplateValue(templateValue, allowRelativePath, defaultRelativePath, effectiveSegments, effectiveFolderName, replacementTags); + var usesRelativePath = allowRelativePath && Regex.IsMatch(templateValue ?? string.Empty, @"{{\s*RELATIVEPATH", RegexOptions.IgnoreCase); + var usesName = Regex.IsMatch(templateValue ?? string.Empty, @"{{\s*NAME\s*}}", RegexOptions.IgnoreCase); + var strategy = string.Empty; + + while (originalValue.Length > availableLength) + { + var changed = false; + + if (usesRelativePath && TryShortenRelativePath(ref effectiveSegments)) + { + effectiveFolderName = effectiveSegments.Length > 0 ? effectiveSegments[effectiveSegments.Length - 1] : string.Empty; + if (string.IsNullOrWhiteSpace(strategy)) + strategy = "truncate-relativepath"; + changed = true; + } + else if (usesName && !usesRelativePath && TryShortenName(ref effectiveFolderName)) + { + if (string.IsNullOrWhiteSpace(strategy)) + strategy = "truncate-name"; + changed = true; + } + + if (!changed) + break; + + originalValue = MaterializeTemplateValue(templateValue, allowRelativePath, defaultRelativePath, effectiveSegments, effectiveFolderName, replacementTags); + } + + var result = new BoundedTemplateContext + { + SanitizedSegments = effectiveSegments, + FolderName = effectiveSegments.Length > 0 ? effectiveSegments[effectiveSegments.Length - 1] : effectiveFolderName, + OriginalValue = MaterializeTemplateValue(templateValue, allowRelativePath, defaultRelativePath, sanitizedSegments, folderName, replacementTags), + FinalValue = originalValue, + WasShortened = !string.Equals( + MaterializeTemplateValue(templateValue, allowRelativePath, defaultRelativePath, sanitizedSegments, folderName, replacementTags), + originalValue, + StringComparison.Ordinal), + Strategy = strategy + }; + + if (result.WasShortened) + { + cLogManager.DefaultLogger.LogEntry( + LogLevels.Warning, + $"AD-Gruppenname gekuerzt ({logContext}): '{result.OriginalValue}' ({result.OriginalValue.Length}) -> '{result.FinalValue}' ({result.FinalValue.Length}), Strategie: {result.Strategy}, Limit: {availableLength}."); + } + + if (result.FinalValue.Length > availableLength) + { + cLogManager.DefaultLogger.LogEntry( + LogLevels.Warning, + $"AD-Gruppenname ueberschreitet weiterhin das sichere Limit ({logContext}): '{result.FinalValue}' ({result.FinalValue.Length}), Limit: {availableLength}."); + } + + return result; + } + public static string SanitizePathSegment(string segment) + { + if (string.IsNullOrEmpty(segment)) + return string.Empty; + + return Regex.Replace(segment, @"[\s\-]", "_"); + } + public static void CreatePathWithWriteAccess(string FilePath) + { + try + { + var PF = Environment.ExpandEnvironmentVariables(FilePath); Directory.CreateDirectory(PF); } catch { } @@ -77,5 +162,80 @@ namespace C4IT_IAM_Engine else return new string(maskingChar, input.Length); } + + private static string MaterializeTemplateValue( + string templateValue, + bool allowRelativePath, + string defaultRelativePath, + string[] sanitizedSegments, + string folderName, + IDictionary replacementTags) + { + return ApplyTemplatePlaceholders(templateValue, allowRelativePath, defaultRelativePath, sanitizedSegments, folderName) + .ReplaceTags(replacementTags) + .ToUpper(); + } + + private static int GetLoopReservationLength(string templateValue) + { + if (string.IsNullOrWhiteSpace(templateValue)) + return 0; + + var reservation = 0; + foreach (Match match in Regex.Matches(templateValue, @"{{(?[^}]*)(?LOOP)(?[^{]*)}}", RegexOptions.IgnoreCase)) + { + reservation += match.Groups["prefix"].Value.Length + MaxAdGroupLoopDigits + match.Groups["postfix"].Value.Length; + } + + return reservation; + } + + private static bool TryShortenRelativePath(ref string[] segments) + { + if (segments == null || segments.Length == 0) + return false; + + var lastIndex = segments.Length - 1; + var candidateIndex = -1; + var candidateLength = 1; + + for (var i = 0; i < lastIndex; i++) + { + if (string.IsNullOrWhiteSpace(segments[i]) || segments[i].Length <= candidateLength) + continue; + + candidateIndex = i; + candidateLength = segments[i].Length; + } + + if (candidateIndex >= 0) + { + segments[candidateIndex] = segments[candidateIndex].Substring(0, segments[candidateIndex].Length - 1); + return true; + } + + if (segments.Length > 1) + { + segments = segments.Skip(1).ToArray(); + return true; + } + + if (segments[lastIndex].Length > 1) + { + segments[lastIndex] = segments[lastIndex].Substring(0, segments[lastIndex].Length - 1); + return true; + } + + return false; + } + + private static bool TryShortenName(ref string folderName) + { + if (string.IsNullOrWhiteSpace(folderName) || folderName.Length <= 1) + return false; + + folderName = folderName.Substring(0, folderName.Length - 1); + return true; + } } } diff --git a/LiamNtfs/C4IT_IAM_SET/SecurityGroup.cs b/LiamNtfs/C4IT_IAM_SET/SecurityGroup.cs index 14ca3b3..de11b0e 100644 --- a/LiamNtfs/C4IT_IAM_SET/SecurityGroup.cs +++ b/LiamNtfs/C4IT_IAM_SET/SecurityGroup.cs @@ -189,16 +189,39 @@ namespace C4IT_IAM_Engine tags.Add("GROUPTYPEPOSTFIX", GroupTypeTag); tags.Add("SCOPETAG", GroupScopeTag); - template.NamingTemplate = Helper.ApplyTemplatePlaceholders(template.NamingTemplate, template.Type != SecurityGroupType.Traverse, relativePath, sanitizedSegments, folderName) + var replacementTags = new Dictionary(StringComparer.OrdinalIgnoreCase); + if (customTags != null) + { + foreach (var customTag in customTags) + replacementTags[customTag.Key] = customTag.Value; + } + foreach (var tag in tags) + replacementTags[tag.Key] = tag.Value; + + var boundedNameContext = Helper.GetBoundedAdGroupTemplateContext( + template.NamingTemplate, + template.Type != SecurityGroupType.Traverse, + relativePath, + sanitizedSegments, + folderName, + replacementTags, + Helper.MaxAdGroupNameLength, + $"{template.Type}/{template.Scope} fuer '{newFolderPath}'"); + + var adjustedSegments = boundedNameContext.SanitizedSegments ?? Array.Empty(); + var adjustedRelativePath = adjustedSegments.Length > 0 ? string.Join("_", adjustedSegments) : string.Empty; + var adjustedFolderName = boundedNameContext.FolderName; + + template.NamingTemplate = Helper.ApplyTemplatePlaceholders(template.NamingTemplate, template.Type != SecurityGroupType.Traverse, adjustedRelativePath, adjustedSegments, adjustedFolderName) .ReplaceTags(customTags).ReplaceTags(tags) .ToUpper(); - template.DescriptionTemplate = Helper.ApplyTemplatePlaceholders(template.DescriptionTemplate, template.Type != SecurityGroupType.Traverse, relativePath, sanitizedSegments, folderName) + template.DescriptionTemplate = Helper.ApplyTemplatePlaceholders(template.DescriptionTemplate, template.Type != SecurityGroupType.Traverse, adjustedRelativePath, adjustedSegments, adjustedFolderName) .ReplaceTags(customTags).ReplaceTags(tags) .ToUpper(); - template.WildcardTemplate = Helper.ApplyTemplatePlaceholders(template.WildcardTemplate, template.Type != SecurityGroupType.Traverse, relativePath, sanitizedSegments, folderName) + template.WildcardTemplate = Helper.ApplyTemplatePlaceholders(template.WildcardTemplate, template.Type != SecurityGroupType.Traverse, adjustedRelativePath, adjustedSegments, adjustedFolderName) .ReplaceTags(customTags).ReplaceTags(tags) .ToUpper();