diff --git a/LiamNtfs/C4IT_IAM_SET/DataArea_FileSystem.cs b/LiamNtfs/C4IT_IAM_SET/DataArea_FileSystem.cs index d149044..5c90cb9 100644 --- a/LiamNtfs/C4IT_IAM_SET/DataArea_FileSystem.cs +++ b/LiamNtfs/C4IT_IAM_SET/DataArea_FileSystem.cs @@ -586,6 +586,7 @@ namespace C4IT_IAM_SET ? sanitizedSegments[sanitizedSegments.Length - 1] : Helper.SanitizePathSegment(Path.GetFileName(parent.FullName.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar))); var traverseTags = GetTraverseReplacementTags(parent.FullName); + var rootContext = Helper.GetRootPathTemplateContext(baseFolder); var boundedTraverseContext = Helper.GetBoundedAdGroupTemplateContext( traverseGroupTemplate.NamingTemplate, true, @@ -594,7 +595,9 @@ namespace C4IT_IAM_SET folderName, traverseTags, Helper.MaxAdGroupNameLength, - $"Traverse fuer '{parent.FullName}'"); + $"Traverse fuer '{parent.FullName}'", + "AD-Gruppenname", + rootContext); var boundedTraverseDescriptionContext = Helper.GetBoundedAdGroupTemplateContext( traverseGroupTemplate.DescriptionTemplate, true, @@ -604,20 +607,21 @@ namespace C4IT_IAM_SET traverseTags, Helper.MaxAdGroupDescriptionLength, $"Traverse fuer '{parent.FullName}'", - "AD-Gruppenbeschreibung"); + "AD-Gruppenbeschreibung", + rootContext); var adjustedTraverseSegments = boundedTraverseContext.SanitizedSegments ?? Array.Empty(); var adjustedTraverseRelativePath = adjustedTraverseSegments.Length > 0 ? string.Join("_", adjustedTraverseSegments) : string.Empty; var adjustedTraverseFolderName = boundedTraverseContext.FolderName; var adjustedTraverseDescriptionSegments = boundedTraverseDescriptionContext.SanitizedSegments ?? Array.Empty(); var adjustedTraverseDescriptionRelativePath = adjustedTraverseDescriptionSegments.Length > 0 ? string.Join("_", adjustedTraverseDescriptionSegments) : string.Empty; var adjustedTraverseDescriptionFolderName = boundedTraverseDescriptionContext.FolderName; - var traverseNameTemplate = Helper.ApplyTemplatePlaceholders(traverseGroupTemplate.NamingTemplate, true, adjustedTraverseRelativePath, adjustedTraverseSegments, adjustedTraverseFolderName).ReplaceTags(traverseTags); - var traverseDescriptionTemplate = Helper.ApplyTemplatePlaceholders(traverseGroupTemplate.DescriptionTemplate, true, adjustedTraverseDescriptionRelativePath, adjustedTraverseDescriptionSegments, adjustedTraverseDescriptionFolderName).ReplaceTags(traverseTags); + var traverseNameTemplate = Helper.ApplyTemplatePlaceholders(traverseGroupTemplate.NamingTemplate, true, adjustedTraverseRelativePath, adjustedTraverseSegments, adjustedTraverseFolderName, rootContext).ReplaceTags(traverseTags); + var traverseDescriptionTemplate = Helper.ApplyTemplatePlaceholders(traverseGroupTemplate.DescriptionTemplate, true, adjustedTraverseDescriptionRelativePath, adjustedTraverseDescriptionSegments, adjustedTraverseDescriptionFolderName, rootContext).ReplaceTags(traverseTags); string traverseRegex = null; try { - traverseRegex = Helper.ApplyTemplatePlaceholders(traverseGroupTemplate.WildcardTemplate, true, adjustedTraverseRelativePath, adjustedTraverseSegments, adjustedTraverseFolderName).ReplaceTags(traverseTags); + traverseRegex = Helper.ApplyTemplatePlaceholders(traverseGroupTemplate.WildcardTemplate, true, adjustedTraverseRelativePath, adjustedTraverseSegments, adjustedTraverseFolderName, rootContext).ReplaceTags(traverseTags); 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 8cc44f9..33f8c6b 100644 --- a/LiamNtfs/C4IT_IAM_SET/Helper.cs +++ b/LiamNtfs/C4IT_IAM_SET/Helper.cs @@ -28,6 +28,14 @@ namespace C4IT_IAM_Engine public string Strategy { get; set; } = string.Empty; } + public sealed class RootPathTemplateContext + { + public string Server { get; set; } = string.Empty; + public string[] Segments { get; set; } = Array.Empty(); + public string Name { get; set; } = string.Empty; + public string Path { get; set; } = string.Empty; + } + public static string ReplaceLoopTag(this string str, int loop) { return Regex.Replace(str, @"(?{{(?[^}]*)(?LOOP)(?[^{]*)}})", loop <= 0 ? "" : "${prefix}" + loop + "${postfix}"); @@ -40,11 +48,17 @@ namespace C4IT_IAM_Engine current.Replace("{{" + value.Key + "}}", value.Value)); } public static string ApplyTemplatePlaceholders(string templateValue, bool allowRelativePath, string defaultRelativePath, string[] sanitizedSegments, string folderName) + { + return ApplyTemplatePlaceholders(templateValue, allowRelativePath, defaultRelativePath, sanitizedSegments, folderName, null); + } + + public static string ApplyTemplatePlaceholders(string templateValue, bool allowRelativePath, string defaultRelativePath, string[] sanitizedSegments, string folderName, RootPathTemplateContext rootContext) { if (templateValue == null) return string.Empty; var result = Regex.Replace(templateValue, @"{{\s*NAME\s*}}", folderName ?? string.Empty, RegexOptions.IgnoreCase); + result = ApplyRootPathPlaceholders(result, rootContext); if (allowRelativePath) { @@ -67,6 +81,27 @@ namespace C4IT_IAM_Engine return result; } + + public static RootPathTemplateContext GetRootPathTemplateContext(string rootPath) + { + var segments = SplitPathSegments(rootPath); + if (segments.Length == 0) + return new RootPathTemplateContext(); + + var isUncPath = (rootPath ?? string.Empty).Trim().Replace('/', '\\').StartsWith(@"\\", StringComparison.Ordinal); + var server = isUncPath ? SanitizePathSegment(segments[0]) : string.Empty; + var pathSegments = isUncPath ? segments.Skip(1).ToArray() : segments; + var sanitizedPathSegments = pathSegments.Select(SanitizePathSegment).ToArray(); + + return new RootPathTemplateContext + { + Server = server, + Segments = sanitizedPathSegments, + Name = sanitizedPathSegments.Length == 0 ? string.Empty : sanitizedPathSegments[sanitizedPathSegments.Length - 1], + Path = sanitizedPathSegments.Length == 0 ? string.Empty : string.Join("_", sanitizedPathSegments) + }; + } + public static BoundedTemplateContext GetBoundedAdGroupTemplateContext( string templateValue, bool allowRelativePath, @@ -76,13 +111,14 @@ namespace C4IT_IAM_Engine IDictionary replacementTags, int maxLength, string logContext, - string valueLabel = "AD-Gruppenname") + string valueLabel = "AD-Gruppenname", + RootPathTemplateContext rootContext = null) { var effectiveSegments = (sanitizedSegments ?? Array.Empty()).Where(i => i != null).ToArray(); var effectiveFolderName = folderName ?? string.Empty; var currentRelativePath = GetCurrentRelativePath(effectiveSegments, defaultRelativePath); - var originalValue = MaterializeTemplateValue(templateValue, allowRelativePath, currentRelativePath, effectiveSegments, effectiveFolderName, replacementTags); - var measuredValue = MaterializeTemplateValueForLength(templateValue, allowRelativePath, currentRelativePath, effectiveSegments, effectiveFolderName, replacementTags); + var originalValue = MaterializeTemplateValue(templateValue, allowRelativePath, currentRelativePath, effectiveSegments, effectiveFolderName, replacementTags, rootContext); + var measuredValue = MaterializeTemplateValueForLength(templateValue, allowRelativePath, currentRelativePath, effectiveSegments, effectiveFolderName, replacementTags, rootContext); 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; @@ -109,8 +145,8 @@ namespace C4IT_IAM_Engine break; currentRelativePath = GetCurrentRelativePath(effectiveSegments, defaultRelativePath); - originalValue = MaterializeTemplateValue(templateValue, allowRelativePath, currentRelativePath, effectiveSegments, effectiveFolderName, replacementTags); - measuredValue = MaterializeTemplateValueForLength(templateValue, allowRelativePath, currentRelativePath, effectiveSegments, effectiveFolderName, replacementTags); + originalValue = MaterializeTemplateValue(templateValue, allowRelativePath, currentRelativePath, effectiveSegments, effectiveFolderName, replacementTags, rootContext); + measuredValue = MaterializeTemplateValueForLength(templateValue, allowRelativePath, currentRelativePath, effectiveSegments, effectiveFolderName, replacementTags, rootContext); } var initialValue = MaterializeTemplateValue( @@ -119,7 +155,8 @@ namespace C4IT_IAM_Engine GetCurrentRelativePath(sanitizedSegments, defaultRelativePath), sanitizedSegments, folderName, - replacementTags); + replacementTags, + rootContext); var result = new BoundedTemplateContext { SanitizedSegments = effectiveSegments, @@ -181,9 +218,10 @@ namespace C4IT_IAM_Engine string defaultRelativePath, string[] sanitizedSegments, string folderName, - IDictionary replacementTags) + IDictionary replacementTags, + RootPathTemplateContext rootContext) { - return ApplyTemplatePlaceholders(templateValue, allowRelativePath, defaultRelativePath, sanitizedSegments, folderName) + return ApplyTemplatePlaceholders(templateValue, allowRelativePath, defaultRelativePath, sanitizedSegments, folderName, rootContext) .ReplaceTags(replacementTags) .ToUpper(); } @@ -194,10 +232,43 @@ namespace C4IT_IAM_Engine string defaultRelativePath, string[] sanitizedSegments, string folderName, - IDictionary replacementTags) + IDictionary replacementTags, + RootPathTemplateContext rootContext) { return NormalizeLoopPlaceholderLength( - MaterializeTemplateValue(templateValue, allowRelativePath, defaultRelativePath, sanitizedSegments, folderName, replacementTags)); + MaterializeTemplateValue(templateValue, allowRelativePath, defaultRelativePath, sanitizedSegments, folderName, replacementTags, rootContext)); + } + + private static string ApplyRootPathPlaceholders(string templateValue, RootPathTemplateContext rootContext) + { + if (templateValue == null) + return string.Empty; + + var context = rootContext ?? new RootPathTemplateContext(); + var result = Regex.Replace(templateValue, @"{{\s*ROOT_SERVER\s*}}", context.Server ?? string.Empty, RegexOptions.IgnoreCase); + result = Regex.Replace(result, @"{{\s*ROOT_NAME\s*}}", context.Name ?? string.Empty, RegexOptions.IgnoreCase); + result = Regex.Replace(result, @"{{\s*ROOT_PATH(?:\s*\(\s*(\d+)\s*\))?\s*}}", match => + { + var segments = context.Segments ?? Array.Empty(); + if (!match.Groups[1].Success) + return context.Path ?? string.Empty; + + if (!int.TryParse(match.Groups[1].Value, out var segmentCount) || segmentCount <= 0) + return string.Empty; + + var take = Math.Min(segmentCount, segments.Length); + return take == 0 ? string.Empty : string.Join("_", segments.Skip(segments.Length - take)); + }, RegexOptions.IgnoreCase); + result = Regex.Replace(result, @"{{\s*ROOT_SEGMENT\s*\(\s*(\d+)\s*\)\s*}}", match => + { + var segments = context.Segments ?? Array.Empty(); + if (!int.TryParse(match.Groups[1].Value, out var segmentIndex) || segmentIndex < 0 || segmentIndex >= segments.Length) + return string.Empty; + + return segments[segmentIndex] ?? string.Empty; + }, RegexOptions.IgnoreCase); + + return result; } private static string NormalizeLoopPlaceholderLength(string templateValue) @@ -217,6 +288,14 @@ namespace C4IT_IAM_Engine return NormalizeLoopPlaceholderLength(templateValue).Length; } + private static string[] SplitPathSegments(string path) + { + return (path ?? string.Empty) + .Trim() + .Replace('/', '\\') + .Split(new[] { '\\' }, StringSplitOptions.RemoveEmptyEntries); + } + private static string GetCurrentRelativePath(string[] sanitizedSegments, string fallbackRelativePath) { if (sanitizedSegments != null && sanitizedSegments.Length > 0) diff --git a/LiamNtfs/C4IT_IAM_SET/SecurityGroup.cs b/LiamNtfs/C4IT_IAM_SET/SecurityGroup.cs index 4029f79..8d2fa36 100644 --- a/LiamNtfs/C4IT_IAM_SET/SecurityGroup.cs +++ b/LiamNtfs/C4IT_IAM_SET/SecurityGroup.cs @@ -150,6 +150,7 @@ namespace C4IT_IAM_Engine var folderName = sanitizedSegments.Length > 0 ? sanitizedSegments[sanitizedSegments.Length - 1] : Helper.SanitizePathSegment(Path.GetFileName(newFolderPath.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar))); + var rootContext = Helper.GetRootPathTemplateContext(baseFolder); foreach (var template in resolvedTemplates) { @@ -206,7 +207,9 @@ namespace C4IT_IAM_Engine folderName, replacementTags, Helper.MaxAdGroupNameLength, - $"{template.Type}/{template.Scope} fuer '{newFolderPath}'"); + $"{template.Type}/{template.Scope} fuer '{newFolderPath}'", + "AD-Gruppenname", + rootContext); var boundedDescriptionContext = Helper.GetBoundedAdGroupTemplateContext( template.DescriptionTemplate, @@ -217,7 +220,8 @@ namespace C4IT_IAM_Engine replacementTags, Helper.MaxAdGroupDescriptionLength, $"{template.Type}/{template.Scope} fuer '{newFolderPath}'", - "AD-Gruppenbeschreibung"); + "AD-Gruppenbeschreibung", + rootContext); var adjustedNameSegments = boundedNameContext.SanitizedSegments ?? Array.Empty(); var adjustedNameRelativePath = adjustedNameSegments.Length > 0 ? string.Join("_", adjustedNameSegments) : string.Empty; @@ -226,16 +230,16 @@ namespace C4IT_IAM_Engine var adjustedDescriptionRelativePath = adjustedDescriptionSegments.Length > 0 ? string.Join("_", adjustedDescriptionSegments) : string.Empty; var adjustedDescriptionFolderName = boundedDescriptionContext.FolderName; - template.NamingTemplate = Helper.ApplyTemplatePlaceholders(template.NamingTemplate, template.Type != SecurityGroupType.Traverse, adjustedNameRelativePath, adjustedNameSegments, adjustedNameFolderName) + template.NamingTemplate = Helper.ApplyTemplatePlaceholders(template.NamingTemplate, template.Type != SecurityGroupType.Traverse, adjustedNameRelativePath, adjustedNameSegments, adjustedNameFolderName, rootContext) .ReplaceTags(customTags).ReplaceTags(tags) .ToUpper(); - template.DescriptionTemplate = Helper.ApplyTemplatePlaceholders(template.DescriptionTemplate, template.Type != SecurityGroupType.Traverse, adjustedDescriptionRelativePath, adjustedDescriptionSegments, adjustedDescriptionFolderName) + template.DescriptionTemplate = Helper.ApplyTemplatePlaceholders(template.DescriptionTemplate, template.Type != SecurityGroupType.Traverse, adjustedDescriptionRelativePath, adjustedDescriptionSegments, adjustedDescriptionFolderName, rootContext) .ReplaceTags(customTags).ReplaceTags(tags) .ToUpper(); - template.WildcardTemplate = Helper.ApplyTemplatePlaceholders(template.WildcardTemplate, template.Type != SecurityGroupType.Traverse, adjustedNameRelativePath, adjustedNameSegments, adjustedNameFolderName) + template.WildcardTemplate = Helper.ApplyTemplatePlaceholders(template.WildcardTemplate, template.Type != SecurityGroupType.Traverse, adjustedNameRelativePath, adjustedNameSegments, adjustedNameFolderName, rootContext) .ReplaceTags(customTags).ReplaceTags(tags) .ToUpper(); diff --git a/Sonstiges/LIAM_NTFS_AdditionalConfiguration_BlackWhitelist_Konzept.md b/Sonstiges/LIAM_NTFS_AdditionalConfiguration_BlackWhitelist_Konzept.md index 0c67d68..89d4e89 100644 --- a/Sonstiges/LIAM_NTFS_AdditionalConfiguration_BlackWhitelist_Konzept.md +++ b/Sonstiges/LIAM_NTFS_AdditionalConfiguration_BlackWhitelist_Konzept.md @@ -182,6 +182,55 @@ ACL_G_FILE_SHARES_SHARE2_T Wenn das Traverse-`NamingTemplate` leer ist, ist das kein Fehler. Es wird dann keine neue Traverse-Gruppe angelegt. Bestehende Gruppen werden aber weiterhin ueber ACLs und, sofern gepflegt, ueber `Wildcard` gesucht und konfiguriert. Sind `NamingTemplate` und `Wildcard` leer, ist die Traverse-Verarbeitung fuer diesen Parent ein No-op. +### 10. Root-Path-Platzhalter + +Naming Conventions koennen zusaetzlich Bestandteile des konfigurierten `RootPath` verwenden. Die Platzhalter funktionieren in `NamingTemplate`, `DescriptionTemplate` und `Wildcard`. + +Fuer: + +```text +RootPath=\\SRVWSM001.imagoverum.com\file_shares\share2 +Zielpfad=\\SRVWSM001.imagoverum.com\file_shares\share2\test33 +``` + +stehen folgende Root-Platzhalter zur Verfuegung: + +- `{{ROOT_SERVER}}`: Serveranteil, z.B. `SRVWSM001.imagoverum.com` +- `{{ROOT_NAME}}`: letzter Root-Segmentname, z.B. `share2` +- `{{ROOT_PATH}}`: alle Root-Segmente nach dem Server, z.B. `file_shares_share2` +- `{{ROOT_PATH(1)}}`: die letzten `n` Root-Segmente, z.B. `share2` +- `{{ROOT_PATH(2)}}`: z.B. `file_shares_share2` +- `{{ROOT_SEGMENT(0)}}`: erstes Root-Segment nach dem Server, z.B. `file_shares` +- `{{ROOT_SEGMENT(1)}}`: zweites Root-Segment nach dem Server, z.B. `share2` + +Root-Segmente werden wie Ordnersegmente sanitisiert. Leerzeichen und Bindestriche werden zu `_`. Nicht vorhandene `ROOT_SEGMENT(n)`-Werte werden zu einem leeren String. Wenn `ROOT_PATH(n)` mehr Segmente anfordert als vorhanden sind, werden alle vorhandenen Root-Segmente verwendet. + +Beispiel: + +```text +{{ADGroupPrefix}}_{{ROOT_NAME}}.{{NAME}}{{GROUPTYPEPOSTFIX}} +``` + +ergibt fuer die Owner-Gruppe des Zielordners: + +```text +ACL_SHARE2.TEST33_O +``` + +Alternativ mit Namespace-/Root-Anteil: + +```text +{{ADGroupPrefix}}_{{ROOT_PATH(2)}}.{{NAME}}{{GROUPTYPEPOSTFIX}} +``` + +ergibt: + +```text +ACL_FILE_SHARES_SHARE2.TEST33_O +``` + +Die bestehenden Platzhalter `{{NAME}}`, `{{RELATIVEPATH}}`, `{{TRAVERSE_NAME}}` und `{{TRAVERSE_VISIBLEPATH}}` bleiben unveraendert. + ## Matching-Regeln Empfohlene Semantik: