From 18a96d3e234b0bc3b0f770370373c42207720bc7 Mon Sep 17 00:00:00 2001 From: Meik Date: Thu, 16 Jul 2026 20:16:03 +0200 Subject: [PATCH] Add classified NTFS path template placeholders --- LiamNtfs/C4IT.LIAM.Ntfs.cs | 52 ++++++++++++++++++-- LiamNtfs/C4IT_IAM_SET/DataArea_FileSystem.cs | 18 +++++-- LiamNtfs/C4IT_IAM_SET/Helper.cs | 8 +++ LiamNtfs/C4IT_IAM_SET/SecurityGroup.cs | 5 +- README.md | 5 ++ 5 files changed, 79 insertions(+), 9 deletions(-) diff --git a/LiamNtfs/C4IT.LIAM.Ntfs.cs b/LiamNtfs/C4IT.LIAM.Ntfs.cs index cd21a4b..c174c55 100644 --- a/LiamNtfs/C4IT.LIAM.Ntfs.cs +++ b/LiamNtfs/C4IT.LIAM.Ntfs.cs @@ -1342,6 +1342,9 @@ namespace C4IT.LIAM $"NTFS filesystem engine creation stage. Stage=Start Path='{folderPath}', ParentPath='{parentFolderPath}'", LogLevels.Debug); + var groupNameSanitizeReplacement = GetAdditionalConfigurationValueOrDefault( + AdditionalConfigurationGroupNameSanitizeReplacementKey, + Helper.DefaultGroupNameSanitizeReplacement); var requiresDomainLocalTag = this.GroupStrategy == eLiamGroupStrategies.Ntfs_AGDLP || (NamingConventions ?? Enumerable.Empty()) .Any(i => i.Scope == eLiamAccessRoleScopes.DomainLocal); @@ -1398,9 +1401,8 @@ namespace C4IT.LIAM engine.forceStrictAdGroupNames = IsAdditionalConfigurationEnabled("ForceStrictAdGroupNames"); engine.adGroupReuseMode = GetAdGroupReuseMode(); engine.adGroupMarkerBackfill = IsAdditionalConfigurationEnabled(AdditionalConfigurationAdGroupMarkerBackfillKey); - engine.groupNameSanitizeReplacement = GetAdditionalConfigurationValueOrDefault( - AdditionalConfigurationGroupNameSanitizeReplacementKey, - Helper.DefaultGroupNameSanitizeReplacement); + engine.groupNameSanitizeReplacement = groupNameSanitizeReplacement; + engine.GetTemplateContextForPath = path => GetTemplateContextForPath(path, groupNameSanitizeReplacement); engine.preserveAdGroupNameCase = IsAdditionalConfigurationEnabled(AdditionalConfigurationPreserveAdGroupNameCaseKey); engine.OwnerACLPermission = this.OwnerACLPermission; engine.WriteACLPermission = this.WriteACLPermission; @@ -1427,6 +1429,50 @@ namespace C4IT.LIAM return engine; } + private Helper.RootPathTemplateContext GetTemplateContextForPath(string path, string groupNameSanitizeReplacement) + { + var context = Helper.GetRootPathTemplateContext(this.RootPath, groupNameSanitizeReplacement); + var normalizedPath = NormalizeUncPath(path); + var segments = GetUncSegments(normalizedPath); + if (segments.Length > 0) + context.ServerName = Helper.SanitizePathSegment(segments[0], groupNameSanitizeReplacement); + + var classification = ClassifyPath(normalizedPath); + var dfsPrefixes = GetDfsObjectPrefixes(normalizedPath); + if (dfsPrefixes.Count > 0) + { + context.DfsNamespaceName = GetLastSanitizedPathSegment(dfsPrefixes[0], groupNameSanitizeReplacement); + + if (classification != null && classification.Kind != eNtfsPathKind.DfsNamespaceRoot) + context.ShareName = GetLastSanitizedPathSegment(classification.BoundaryPath, groupNameSanitizeReplacement); + else + context.ShareName = string.Empty; + + return context; + } + + if (classification != null + && !string.IsNullOrWhiteSpace(classification.BoundaryPath) + && (classification.Kind == eNtfsPathKind.ClassicShare || classification.Kind == eNtfsPathKind.Folder)) + { + context.ShareName = GetLastSanitizedPathSegment(classification.BoundaryPath, groupNameSanitizeReplacement); + } + else if (segments.Length > 1) + { + context.ShareName = Helper.SanitizePathSegment(segments[1], groupNameSanitizeReplacement); + } + + return context; + } + + private string GetLastSanitizedPathSegment(string path, string groupNameSanitizeReplacement) + { + var segments = GetUncSegments(path); + return segments.Length == 0 + ? string.Empty + : Helper.SanitizePathSegment(segments[segments.Length - 1], groupNameSanitizeReplacement); + } + private bool IsAdditionalConfigurationEnabled(string key) { if (AdditionalConfiguration == null || string.IsNullOrWhiteSpace(key)) diff --git a/LiamNtfs/C4IT_IAM_SET/DataArea_FileSystem.cs b/LiamNtfs/C4IT_IAM_SET/DataArea_FileSystem.cs index 4cd0d6b..7cef512 100644 --- a/LiamNtfs/C4IT_IAM_SET/DataArea_FileSystem.cs +++ b/LiamNtfs/C4IT_IAM_SET/DataArea_FileSystem.cs @@ -62,6 +62,7 @@ namespace C4IT_IAM_SET public string groupNameSanitizeReplacement = Helper.DefaultGroupNameSanitizeReplacement; public bool preserveAdGroupNameCase; public bool WhatIf; + public Func GetTemplateContextForPath; public int ReadACLPermission = cLiamAclPermissionDefaults.Read; public int WriteACLPermission = cLiamAclPermissionDefaults.Write; @@ -331,6 +332,12 @@ namespace C4IT_IAM_SET }; } + private Helper.RootPathTemplateContext GetEffectiveTemplateContext(string path) + { + return GetTemplateContextForPath?.Invoke(path) + ?? Helper.GetRootPathTemplateContext(baseFolder, groupNameSanitizeReplacement); + } + private ResultToken ValidateTraverseBoundaryForCurrentFolder() { var resultToken = new ResultToken(System.Reflection.MethodBase.GetCurrentMethod().ToString()); @@ -569,7 +576,8 @@ namespace C4IT_IAM_SET 0, 0, groupNameSanitizeReplacement, - preserveAdGroupNameCase); + preserveAdGroupNameCase, + GetEffectiveTemplateContext(newDataArea.IAM_Folders[0].technicalName)); var existingGroups = new List(); foreach (var securityGroup in newSecurityGroups.IAM_SecurityGroups) @@ -803,7 +811,7 @@ namespace C4IT_IAM_SET ? sanitizedSegments[sanitizedSegments.Length - 1] : Helper.SanitizePathSegment(Path.GetFileName(parent.FullName.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)), groupNameSanitizeReplacement); var traverseTags = GetTraverseReplacementTags(parent.FullName, traverseGroupTemplate.Scope); - var rootContext = Helper.GetRootPathTemplateContext(baseFolder, groupNameSanitizeReplacement); + var rootContext = GetEffectiveTemplateContext(parent.FullName); var boundedTraverseContext = Helper.GetBoundedAdGroupTemplateContext( traverseGroupTemplate.NamingTemplate, true, @@ -1605,7 +1613,8 @@ namespace C4IT_IAM_SET 0, 0, groupNameSanitizeReplacement, - preserveAdGroupNameCase); + preserveAdGroupNameCase, + GetEffectiveTemplateContext(newDataArea.IAM_Folders[0].technicalName)); List owners = getUserPrincipalBySid(ownerUserSids); List writers = getUserPrincipalBySid(writerUserSids); @@ -1771,7 +1780,8 @@ namespace C4IT_IAM_SET existingADGroupCount, 0, groupNameSanitizeReplacement, - preserveAdGroupNameCase); + preserveAdGroupNameCase, + GetEffectiveTemplateContext(newDataArea.IAM_Folders[0].technicalName)); /* if (existingADGroupCount > 0 && !templates.All(t => t.Type == SecurityGroupType.Traverse || Regex.IsMatch(t.NamingTemplate, @"(?{{(?[^}]*)(?LOOP)(?[^{]*)}})"))) { diff --git a/LiamNtfs/C4IT_IAM_SET/Helper.cs b/LiamNtfs/C4IT_IAM_SET/Helper.cs index 6e79336..eec4d56 100644 --- a/LiamNtfs/C4IT_IAM_SET/Helper.cs +++ b/LiamNtfs/C4IT_IAM_SET/Helper.cs @@ -33,6 +33,9 @@ namespace C4IT_IAM_Engine public sealed class RootPathTemplateContext { public string Server { get; set; } = string.Empty; + public string ServerName { get; set; } = string.Empty; + public string DfsNamespaceName { get; set; } = string.Empty; + public string ShareName { 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; @@ -109,6 +112,8 @@ namespace C4IT_IAM_Engine return new RootPathTemplateContext { Server = server, + ServerName = server, + ShareName = sanitizedPathSegments.Length == 0 ? string.Empty : sanitizedPathSegments[sanitizedPathSegments.Length - 1], Segments = sanitizedPathSegments, Name = sanitizedPathSegments.Length == 0 ? string.Empty : sanitizedPathSegments[sanitizedPathSegments.Length - 1], Path = sanitizedPathSegments.Length == 0 ? string.Empty : JoinSanitizedPathSegments(sanitizedPathSegments, groupNameSanitizeReplacement), @@ -308,6 +313,9 @@ namespace C4IT_IAM_Engine 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*SERVER_NAME\s*}}", context.ServerName ?? context.Server ?? string.Empty, RegexOptions.IgnoreCase); + result = Regex.Replace(result, @"{{\s*DFS_NAMESPACE_NAME\s*}}", context.DfsNamespaceName ?? string.Empty, RegexOptions.IgnoreCase); + result = Regex.Replace(result, @"{{\s*SHARE_NAME\s*}}", context.ShareName ?? 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 => { diff --git a/LiamNtfs/C4IT_IAM_SET/SecurityGroup.cs b/LiamNtfs/C4IT_IAM_SET/SecurityGroup.cs index ea9d224..6f8b3f6 100644 --- a/LiamNtfs/C4IT_IAM_SET/SecurityGroup.cs +++ b/LiamNtfs/C4IT_IAM_SET/SecurityGroup.cs @@ -142,7 +142,8 @@ namespace C4IT_IAM_Engine int loop = 0, int existingADGroupCount = 0, string groupNameSanitizeReplacement = Helper.DefaultGroupNameSanitizeReplacement, - bool preserveAdGroupNameCase = false) + bool preserveAdGroupNameCase = false, + Helper.RootPathTemplateContext templateContext = null) { LogMethodBegin(MethodBase.GetCurrentMethod()); try @@ -163,7 +164,7 @@ namespace C4IT_IAM_Engine var folderName = sanitizedSegments.Length > 0 ? sanitizedSegments[sanitizedSegments.Length - 1] : Helper.SanitizePathSegment(Path.GetFileName(newFolderPath.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)), groupNameSanitizeReplacement); - var rootContext = Helper.GetRootPathTemplateContext(baseFolder, groupNameSanitizeReplacement); + var rootContext = templateContext ?? Helper.GetRootPathTemplateContext(baseFolder, groupNameSanitizeReplacement); foreach (var template in resolvedTemplates) { diff --git a/README.md b/README.md index 2c3ff13..42538f2 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,9 @@ Die wichtigsten Eingaben fuer die Ersetzung sind: | `{{RELATIVEPATH(0)}}` | Letztes relatives Pfadsegment. | `test33` | | `{{RELATIVEPATH(1)}}` | Die letzten zwei relativen Pfadsegmente. Bei weniger Segmenten werden alle vorhandenen verwendet. | z. B. `team1_test33` | | `{{ROOT_SERVER}}` | Server-/Namespace-Teil des UNC-RootPath. | `server` | +| `{{SERVER_NAME}}` | Server-/Namespace-Teil des aktuell verarbeiteten UNC-Pfads. | `server` | +| `{{DFS_NAMESPACE_NAME}}` | Name des DFS-Namespaces, wenn der aktuelle Pfad in einem DFS-Namespace liegt. | `File_Shares` | +| `{{SHARE_NAME}}` | Klassifizierter Share-Name: klassischer Share oder DFS-Link, in dem die aktuelle DataArea liegt. | `share2` | | `{{ROOT_NAME}}` | Letztes Segment des RootPath. | `share2` | | `{{ROOT_PATH}}` | Alle RootPath-Segmente ohne Server, mit Segmenttrenner verbunden. | `file_shares_share2` | | `{{ROOT_PATH(1)}}` | Letztes RootPath-Segment. | `share2` | @@ -31,6 +34,8 @@ Die wichtigsten Eingaben fuer die Ersetzung sind: `{{RELATIVEPATH(n)}}` zaehlt von hinten: `0` ist das letzte Segment, `1` sind die letzten zwei Segmente, `2` die letzten drei Segmente usw. `{{ROOT_PATH(n)}}` verwendet dagegen die letzten `n` Segmente des RootPath. +`{{ROOT_*}}` bezieht sich immer auf den konfigurierten `RootPath`. `{{SERVER_NAME}}`, `{{DFS_NAMESPACE_NAME}}` und `{{SHARE_NAME}}` werden dagegen anhand der NTFS-Pfadklassifizierung des aktuell verarbeiteten Pfads ermittelt. Bei einem klassischen Share ist `{{SHARE_NAME}}` der Share. Bei DFS ist `{{DFS_NAMESPACE_NAME}}` der Namespace und `{{SHARE_NAME}}` der DFS-Link. + Wenn die aktuelle DataArea dem `RootPath` selbst entspricht, ist `{{RELATIVEPATH}}` leer. `{{NAME}}` ist dann der Name des RootPath, also z. B. `share2` oder `LEI.01.test`. ### Rollen- und Scope-Platzhalter