diff --git a/LiamNtfs/C4IT.LIAM.Ntfs.cs b/LiamNtfs/C4IT.LIAM.Ntfs.cs index 0aa268d..1b8180a 100644 --- a/LiamNtfs/C4IT.LIAM.Ntfs.cs +++ b/LiamNtfs/C4IT.LIAM.Ntfs.cs @@ -53,6 +53,8 @@ namespace C4IT.LIAM } public static Guid nftsModuleId = new Guid("77e213a1-6517-ea11-4881-000c2980fd94"); + private const string AdditionalConfigurationExcludePathsKey = "NtfsExcludePaths"; + private const string AdditionalConfigurationIncludePathsKey = "NtfsIncludePaths"; public readonly cNtfsBase ntfsBase = new cNtfsBase(); public readonly cActiveDirectoryBase activeDirectoryBase = new cActiveDirectoryBase(); private readonly Dictionary> publishedShareCache = new Dictionary>(StringComparer.OrdinalIgnoreCase); @@ -208,6 +210,9 @@ namespace C4IT.LIAM if (!await LogonAsync()) return null; var classification = ClassifyPath(UID); + if (!PathsEqual(classification?.NormalizedPath, this.RootPath) && !ShouldIncludeDataArea(classification)) + return null; + return await BuildDataAreaAsync(classification); } catch (Exception E) @@ -434,13 +439,13 @@ namespace C4IT.LIAM string matchingConfigurationKey; string matchingRule; - if (IsBlacklistedFolderPath(classification, out matchingConfigurationKey, out matchingRule)) + if (IsPathBlacklisted(classification, out matchingConfigurationKey, out matchingRule)) { LogEntry($"Skip NTFS path '{classification.NormalizedPath}' due to AdditionalConfiguration rule '{matchingConfigurationKey}={matchingRule}'", LogLevels.Debug); return false; } - if (!IsWhitelistedFolderPath(classification, true, out matchingConfigurationKey, out matchingRule)) + if (!IsPathWhitelisted(classification, true, out matchingConfigurationKey, out matchingRule)) { LogEntry($"Skip NTFS path '{classification.NormalizedPath}' because no AdditionalConfiguration whitelist matched", LogLevels.Debug); return false; @@ -456,25 +461,19 @@ namespace C4IT.LIAM string matchingConfigurationKey; string matchingRule; - if (IsBlacklistedFolderPath(classification, out matchingConfigurationKey, out matchingRule)) + if (IsPathBlacklisted(classification, out matchingConfigurationKey, out matchingRule)) { LogEntry($"Skip NTFS subtree '{classification.NormalizedPath}' due to AdditionalConfiguration rule '{matchingConfigurationKey}={matchingRule}'", LogLevels.Debug); return false; } - if (HasAdditionalConfigurationValues("NtfsIncludeFolderNames")) + if (!HasAdditionalConfigurationValues(AdditionalConfigurationIncludePathsKey)) return true; - if (!HasAdditionalConfigurationValues("NtfsIncludeRelativePaths")) + if (IsPathWhitelisted(classification, true, out matchingConfigurationKey, out matchingRule)) return true; - if (classification.Kind != eNtfsPathKind.Folder) - return true; - - if (IsWhitelistedFolderPath(classification, true, out matchingConfigurationKey, out matchingRule)) - return true; - - LogEntry($"Skip NTFS subtree '{classification.NormalizedPath}' because it is outside AdditionalConfiguration whitelist 'NtfsIncludeRelativePaths'", LogLevels.Debug); + LogEntry($"Skip NTFS subtree '{classification.NormalizedPath}' because it is outside AdditionalConfiguration whitelist '{AdditionalConfigurationIncludePathsKey}'", LogLevels.Debug); return false; } @@ -486,85 +485,57 @@ namespace C4IT.LIAM return Regex.Match(displayName ?? string.Empty, this.DataAreaRegEx).Success; } - private bool IsBlacklistedFolderPath(cNtfsPathClassification classification, out string matchingConfigurationKey, out string matchingRule) + private bool IsPathBlacklisted(cNtfsPathClassification classification, out string matchingConfigurationKey, out string matchingRule) { - matchingConfigurationKey = null; - matchingRule = null; - - if (classification == null || classification.Kind != eNtfsPathKind.Folder) - return false; - - foreach (var excludedFolderName in GetAdditionalConfigurationValues("NtfsExcludeFolderNames")) - { - if (!MatchesAdditionalConfigurationPattern(classification.DisplayName, excludedFolderName)) - continue; - - matchingConfigurationKey = "NtfsExcludeFolderNames"; - matchingRule = excludedFolderName; - return true; - } - - var relativePath = GetRelativePathFromRoot(classification.NormalizedPath); - foreach (var excludedRelativePath in GetAdditionalConfigurationValues("NtfsExcludeRelativePaths")) - { - if (!MatchesAdditionalConfigurationPattern(relativePath, excludedRelativePath)) - continue; - - matchingConfigurationKey = "NtfsExcludeRelativePaths"; - matchingRule = excludedRelativePath; - return true; - } - - return false; + return TryMatchPathPolicy(classification, AdditionalConfigurationExcludePathsKey, false, out matchingConfigurationKey, out matchingRule); } - private bool IsWhitelistedFolderPath(cNtfsPathClassification classification, bool allowRelativePathAncestorMatches, out string matchingConfigurationKey, out string matchingRule) + private bool IsPathWhitelisted(cNtfsPathClassification classification, bool allowPathAncestorMatches, out string matchingConfigurationKey, out string matchingRule) { matchingConfigurationKey = null; matchingRule = null; - if (classification == null || classification.Kind != eNtfsPathKind.Folder) + if (!HasAdditionalConfigurationValues(AdditionalConfigurationIncludePathsKey)) return true; - var hasIncludeFolderNames = HasAdditionalConfigurationValues("NtfsIncludeFolderNames"); - var hasIncludeRelativePaths = HasAdditionalConfigurationValues("NtfsIncludeRelativePaths"); - if (!hasIncludeFolderNames && !hasIncludeRelativePaths) - return true; + return TryMatchPathPolicy(classification, AdditionalConfigurationIncludePathsKey, allowPathAncestorMatches, out matchingConfigurationKey, out matchingRule); + } - foreach (var includedFolderName in GetAdditionalConfigurationValues("NtfsIncludeFolderNames")) + private bool TryMatchPathPolicy(cNtfsPathClassification classification, string key, bool allowPathAncestorMatches, out string matchingConfigurationKey, out string matchingRule) + { + matchingConfigurationKey = null; + matchingRule = null; + + if (classification == null || string.IsNullOrWhiteSpace(key)) + return false; + + var patterns = GetAdditionalConfigurationValues(key).ToList(); + if (patterns.Count == 0) + return false; + + foreach (var pattern in patterns) { - if (!MatchesAdditionalConfigurationPattern(classification.DisplayName, includedFolderName)) + if (!MatchesPathPolicy(classification, pattern)) continue; - matchingConfigurationKey = "NtfsIncludeFolderNames"; - matchingRule = includedFolderName; + matchingConfigurationKey = key; + matchingRule = pattern; return true; } - var relativePath = GetRelativePathFromRoot(classification.NormalizedPath); - foreach (var includedRelativePath in GetAdditionalConfigurationValues("NtfsIncludeRelativePaths")) + if (!allowPathAncestorMatches) + return false; + + foreach (var pattern in patterns) { - if (!MatchesAdditionalConfigurationPattern(relativePath, includedRelativePath)) + if (!CanPathLeadToPattern(classification, pattern)) continue; - matchingConfigurationKey = "NtfsIncludeRelativePaths"; - matchingRule = includedRelativePath; + matchingConfigurationKey = key; + matchingRule = pattern; return true; } - if (allowRelativePathAncestorMatches) - { - foreach (var includedRelativePath in GetAdditionalConfigurationValues("NtfsIncludeRelativePaths")) - { - if (!IsRelativePathAncestorOfPattern(relativePath, includedRelativePath)) - continue; - - matchingConfigurationKey = "NtfsIncludeRelativePaths"; - matchingRule = includedRelativePath; - return true; - } - } - return false; } @@ -611,12 +582,45 @@ namespace C4IT.LIAM .Replace('/', '\\'); } + private bool MatchesPathPolicy(cNtfsPathClassification classification, string pattern) + { + if (classification == null || string.IsNullOrWhiteSpace(pattern)) + return false; + + foreach (var candidate in GetPathPolicyCandidates(classification)) + { + if (MatchesAdditionalConfigurationPattern(candidate, pattern)) + return true; + } + + return false; + } + + private IEnumerable GetPathPolicyCandidates(cNtfsPathClassification classification) + { + if (classification == null) + return Enumerable.Empty(); + + var candidates = new List(); + var relativePath = GetRelativePathFromRoot(classification.NormalizedPath); + if (!string.IsNullOrWhiteSpace(relativePath)) + candidates.Add(relativePath); + + if (!string.IsNullOrWhiteSpace(classification.NormalizedPath)) + candidates.Add(classification.NormalizedPath); + + return candidates + .Where(i => !string.IsNullOrWhiteSpace(i)) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList(); + } + private bool MatchesAdditionalConfigurationPattern(string value, string pattern) { if (string.IsNullOrWhiteSpace(value) || string.IsNullOrWhiteSpace(pattern)) return false; - var normalizedValue = value.Trim().Replace('/', '\\'); + var normalizedValue = value.Trim().Replace('/', '\\').Trim('\\'); var normalizedPattern = pattern.Trim().Replace('/', '\\').Trim('\\'); if (string.IsNullOrWhiteSpace(normalizedPattern)) return false; @@ -625,27 +629,51 @@ namespace C4IT.LIAM return Regex.IsMatch(normalizedValue, regexPattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); } - private bool IsRelativePathAncestorOfPattern(string relativePath, string pattern) + private bool CanPathLeadToPattern(cNtfsPathClassification classification, string pattern) { - var normalizedRelativePath = (relativePath ?? string.Empty).Trim().Replace('/', '\\').Trim('\\'); - if (string.IsNullOrWhiteSpace(normalizedRelativePath) || string.IsNullOrWhiteSpace(pattern)) + if (classification == null || string.IsNullOrWhiteSpace(pattern)) return false; - var prefixSegments = new List(); - foreach (var patternSegment in pattern.Trim().Replace('/', '\\').Trim('\\').Split(new[] { '\\' }, StringSplitOptions.RemoveEmptyEntries)) + foreach (var candidate in GetPathPolicyCandidates(classification)) { - if (patternSegment.Contains("*")) - break; - - prefixSegments.Add(patternSegment); + if (IsPathAncestorOfPattern(candidate, pattern)) + return true; } - if (prefixSegments.Count == 0) + return false; + } + + private bool IsPathAncestorOfPattern(string path, string pattern) + { + var normalizedPath = (path ?? string.Empty).Trim().Replace('/', '\\').Trim('\\'); + var normalizedPattern = (pattern ?? string.Empty).Trim().Replace('/', '\\').Trim('\\'); + if (string.IsNullOrWhiteSpace(normalizedPath) || string.IsNullOrWhiteSpace(normalizedPattern)) return false; - var normalizedPatternPrefix = string.Join("\\", prefixSegments); - return normalizedPatternPrefix.Equals(normalizedRelativePath, StringComparison.OrdinalIgnoreCase) - || normalizedPatternPrefix.StartsWith(normalizedRelativePath + "\\", StringComparison.OrdinalIgnoreCase); + var pathSegments = normalizedPath.Split(new[] { '\\' }, StringSplitOptions.RemoveEmptyEntries); + var patternSegments = normalizedPattern.Split(new[] { '\\' }, StringSplitOptions.RemoveEmptyEntries); + if (pathSegments.Length > patternSegments.Length) + return false; + + for (var segmentIndex = 0; segmentIndex < pathSegments.Length; segmentIndex++) + { + if (segmentIndex >= patternSegments.Length) + return false; + + if (!MatchesPatternSegment(pathSegments[segmentIndex], patternSegments[segmentIndex])) + return false; + } + + return true; + } + + private bool MatchesPatternSegment(string valueSegment, string patternSegment) + { + if (string.IsNullOrWhiteSpace(valueSegment) || string.IsNullOrWhiteSpace(patternSegment)) + return false; + + var regexPattern = "^" + Regex.Escape(patternSegment).Replace("\\*", ".*") + "$"; + return Regex.IsMatch(valueSegment, regexPattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); } private List GetDfsObjectPrefixes(string path) @@ -964,10 +992,10 @@ namespace C4IT.LIAM string matchingConfigurationKey; string matchingRule; - if (IsBlacklistedFolderPath(classification, out matchingConfigurationKey, out matchingRule)) + if (IsPathBlacklisted(classification, out matchingConfigurationKey, out matchingRule)) return false; - return IsWhitelistedFolderPath(classification, false, out matchingConfigurationKey, out matchingRule); + return IsPathWhitelisted(classification, false, out matchingConfigurationKey, out matchingRule); } private IEnumerable BuildSecurityGroupTemplates() diff --git a/Sonstiges/LIAM_NTFS_AdditionalConfiguration_BlackWhitelist_Konzept.md b/Sonstiges/LIAM_NTFS_AdditionalConfiguration_BlackWhitelist_Konzept.md index e65c68e..19bfa5b 100644 --- a/Sonstiges/LIAM_NTFS_AdditionalConfiguration_BlackWhitelist_Konzept.md +++ b/Sonstiges/LIAM_NTFS_AdditionalConfiguration_BlackWhitelist_Konzept.md @@ -2,9 +2,9 @@ ## Ziel -Dieses Dokument beschreibt einen kleinen technischen Entwurf, um dem NTFS-Provider ueber `AdditionalConfiguration` eine Blacklist und spaeter optional auch eine Whitelist fuer Ordner mitzugeben. +Dieses Dokument beschreibt einen kleinen technischen Entwurf, um dem NTFS-Provider ueber `AdditionalConfiguration` eine Blacklist und spaeter optional auch eine Whitelist fuer NTFS-Pfade mitzugeben. -Im ersten Schritt soll damit die Enumeration von Unterordnern fuer `getDataAreasAsync()` gezielt eingeschraenkt werden, ohne bestehende Konfigurationen zu brechen. +Die Policy soll klassifizierungsunabhaengig arbeiten und damit fuer Shares, DFS-Links, DFS-Namespaces und Folder dieselbe Matching-Logik verwenden. ## Ausgangslage @@ -22,36 +22,25 @@ Der bestehende Filter `ShouldIncludeDataArea()` wirkt nur auf den `DisplayName` Die neue Logik soll eine explizite Pfad-Policy fuer den NTFS-Provider einfuehren. -Diese Policy entscheidet fuer jeden gefundenen Unterordner: +Diese Policy entscheidet fuer jeden gefundenen Pfad: - darf als DataArea materialisiert werden - darf weiter traversiert werden -Fuer Schritt 1 reicht es, wenn ein ausgeschlossener Ordner weder als DataArea geliefert noch weiter traversiert wird. +Ein ausgeschlossener Pfad soll weder als DataArea geliefert noch weiter traversiert werden. ## Vorgeschlagene Konfigurationsschluessel -### Minimal fuer Schritt 1 +### Aktueller Stand -- `NtfsExcludeFolderNames` -- `NtfsExcludeRelativePaths` +- `NtfsExcludePaths` +- `NtfsIncludePaths` Beispiel: ```text -NtfsExcludeFolderNames=Temp;Archiv;System Volume Information -NtfsExcludeRelativePaths=Abteilung\Alt;IT\_disabled -``` - -### Erweiterbar fuer Schritt 2 - -- `NtfsIncludeFolderNames` -- `NtfsIncludeRelativePaths` - -Beispiel: - -```text -NtfsIncludeRelativePaths=Fachbereiche\*;Shares\Produktion\* +NtfsExcludePaths=Archiv;*\Temp;Abteilung\Alt;\\server\share\legacy\* +NtfsIncludePaths=Fachbereiche\*;Shares\Produktion\*;\\server\dfs\namespace\link\* ``` ## Matching-Regeln @@ -61,9 +50,10 @@ Empfohlene Semantik: - Trennzeichen fuer Mehrfachwerte: `;` - Auswertung case-insensitive - Leerzeichen an Eintraegen vor dem Match trimmen -- Pfade relativ zu `RootPath` vergleichen +- Matching gegen relative Pfade unter `RootPath` und gegen normalisierte absolute UNC-Pfade - Interne Normalisierung auf konsistente UNC-/Directory-Notation - Zunaechst nur einfache Wildcards `*` unterstuetzen, keine freien Regex-Ausdruecke +- Include-Regeln duerfen fuer die Traversierung auch uebergeordnete Pfade freischalten, wenn diese zu einem spaeter passenden Zielpfad fuehren Empfohlene Prioritaet: @@ -81,8 +71,8 @@ Im NTFS-Provider sollte eine kleine Policy-Schicht entstehen, zum Beispiel: - `GetAdditionalConfigurationValues(string key)` - `ShouldTraversePath(string fullPath)` -- `MatchesFolderNamePolicy(string folderName)` -- `MatchesRelativePathPolicy(string fullPath)` +- `MatchesPathPolicy(...)` +- `TryMatchPathPolicy(...)` Die Methode `IsAdditionalConfigurationEnabled()` bleibt fuer boolesche Flags bestehen und wird durch Listen-/String-Helfer ergaenzt. @@ -98,11 +88,9 @@ Vorteil: - kein Umbau der allgemeinen NTFS-Basis erforderlich - fachliche Wirkung genau dort, wo DataAreas erzeugt werden -### 3. Spaetere Wiederverwendung fuer Permission-Management +### 3. Wiederverwendung fuer Permission-Management -Aktuell erlaubt `IsPermissionManagedFolderPath()` jeden als `Folder` klassifizierten Pfad. - -Wenn ausgeschlossene Ordner spaeter auch nicht mehr fuer Berechtigungs-Provisionierung oder Traverse-Gruppen bearbeitet werden sollen, sollte dieselbe Policy dort wiederverwendet werden. +`IsPermissionManagedFolderPath()` bleibt fachlich auf Folder beschraenkt, verwendet fuer Black-/Whitelist aber dieselbe generische Path-Policy. Damit wird vermieden, dass ein Ordner zwar nicht mehr als DataArea sichtbar ist, aber weiterhin im Permission-Flow auftaucht. @@ -112,8 +100,7 @@ Nicht Teil des ersten Schritts: - Umbau von `cNtfsBase` auf generische Filter-Callbacks - freie Regex-Konfiguration in `AdditionalConfiguration` -- unterschiedliche Regeln fuer Shares, DFS-Links und Folder -- Policy-Auswertung fuer `LoadDataArea()` ausserhalb des normalen Traversal-Falls +- unterschiedliche Regeln je Klassifikationstyp Diese Themen koennen spaeter folgen, sind aber fuer den initialen Nutzen nicht noetig. @@ -128,7 +115,7 @@ Fuer ausgeschlossene Pfade sollte auf `Debug` geloggt werden: Beispiel: ```text -Skip NTFS path '\\server\share\IT\_disabled' due to AdditionalConfiguration rule 'NtfsExcludeRelativePaths' +Skip NTFS path '\\server\share\IT\_disabled' due to AdditionalConfiguration rule 'NtfsExcludePaths=IT\_disabled' ``` Das ist wichtig, damit fehlende DataAreas spaeter im Betrieb nachvollziehbar bleiben. @@ -136,13 +123,11 @@ Das ist wichtig, damit fehlende DataAreas spaeter im Betrieb nachvollziehbar ble ## Empfohlener Umsetzungsplan 1. Listenparser fuer `AdditionalConfiguration` im NTFS-Provider einfuehren. -2. Pfadnormalisierung und relatives Matching gegen `RootPath` kapseln. +2. Pfadnormalisierung und Matching fuer relative sowie absolute Pfade kapseln. 3. `GetChildDataAreasAsync()` um `ShouldTraversePath()` erweitern. 4. Debug-Logging fuer Skip-Faelle einfuehren. -5. Optional in einem zweiten Schritt dieselbe Policy in `IsPermissionManagedFolderPath()` wiederverwenden. +5. Dieselbe Policy in `IsPermissionManagedFolderPath()` und `LoadDataArea()` wiederverwenden. ## Kurzfazit -Fuer den ersten Schritt ist eine blacklist-basierte Pfad-Policy im NTFS-Provider der pragmatischste Weg. - -Sie ist klein genug fuer eine risikoarme Implementierung, passt in die vorhandene `AdditionalConfiguration`-Architektur und laesst sich spaeter ohne Bruch zu einer echten Include-/Exclude-Policy ausbauen. +Die generische Path-Policy im NTFS-Provider ist klein genug fuer eine risikoarme Implementierung, passt in die vorhandene `AdditionalConfiguration`-Architektur und arbeitet ohne Sonderregeln pro Klassifikationstyp.