using System; using System.Collections.Generic; using System.Diagnostics; using System.DirectoryServices.AccountManagement; using System.IO; using System.Linq; using System.Net; using System.Reflection; using System.Security.AccessControl; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; using C4IT.Logging; using C4IT.Matrix42.ServerInfo; using C4IT_IAM; using C4IT_IAM_Engine; using C4IT_IAM_SET; using LiamNtfs; using static C4IT.Logging.cLogManager; using static LiamNtfs.cActiveDirectoryBase; namespace C4IT.LIAM { public static class LiamInitializer { static public cLiamProviderBase CreateInstance(cLiamConfiguration LiamConfiguration, cLiamProviderData ProviderData) { return new cLiamProviderNtfs(LiamConfiguration, ProviderData); } } public class cLiamProviderNtfs : cLiamProviderBase { private enum eNtfsPathKind { Unknown = 0, ServerRoot = 1, ClassicShare = 2, DfsNamespaceRoot = 3, DfsLink = 4, Folder = 5 } private sealed class cNtfsPathClassification { public string NormalizedPath { get; set; } = string.Empty; public eNtfsPathKind Kind { get; set; } = eNtfsPathKind.Unknown; public string BoundaryPath { get; set; } = string.Empty; public string ParentBoundaryPath { get; set; } = string.Empty; public string ParentPath { get; set; } = string.Empty; public string DisplayName { get; set; } = string.Empty; public int Level { get; set; } = -1; } private sealed class cNtfsLevelRange { public bool IsConfigured { get; set; } public bool IsValid { get; set; } = true; public int MinLevel { get; set; } = int.MinValue; public int MaxLevel { get; set; } = int.MaxValue; public string ErrorMessage { get; set; } = string.Empty; public bool Contains(int level) { return !IsConfigured || level >= MinLevel && level <= MaxLevel; } } private sealed class cNtfsDataAreaDiagnostics { private const int MaxSamples = 10; public int ScanRequests { get; set; } public int FoldersEnumerated { get; set; } public int EnumerationFailures { get; set; } public int MetadataFailures { get; set; } public int SubtreeFailures { get; set; } public int DataAreaCandidates { get; set; } public int IncludedDataAreas { get; set; } public int SkippedByDataAreaRegex { get; set; } public int SkippedByExcludeRule { get; set; } public int SkippedByMissingIncludeRule { get; set; } public int SkippedSubtreesByExcludeRule { get; set; } public int SkippedSubtreesByMissingIncludeRule { get; set; } public int AclReadFailures { get; set; } public int AclEntriesEvaluated { get; set; } public int UnresolvedAclSids { get; set; } public int AclGroupsWithoutNamingMatch { get; set; } public int DataAreasWithoutPermissionMapping { get; set; } public List EnumerationFailureSamples { get; } = new List(); public List FilterSamples { get; } = new List(); public List MappingIssueSamples { get; } = new List(); public void MergeEnumeration(cNtfsEnumerationDiagnostics diagnostics) { if (diagnostics == null) return; ScanRequests++; FoldersEnumerated += diagnostics.FoldersEnumerated; EnumerationFailures += diagnostics.EnumerationFailures; MetadataFailures += diagnostics.MetadataFailures; SubtreeFailures += diagnostics.SubtreeFailures; AddSamples(EnumerationFailureSamples, diagnostics.FailureSamples); } public void AddFilterSample(string sample) { AddSample(FilterSamples, sample); } public void AddMappingIssueSample(string sample) { AddSample(MappingIssueSamples, sample); } private static void AddSamples(List target, IEnumerable samples) { if (target == null || samples == null) return; foreach (var sample in samples) AddSample(target, sample); } private static void AddSample(List target, string sample) { if (target == null || string.IsNullOrWhiteSpace(sample) || target.Count >= MaxSamples) return; target.Add(sample); } } public static Guid nftsModuleId = new Guid("77e213a1-6517-ea11-4881-000c2980fd94"); private const string AdditionalConfigurationExcludePathsKey = "NtfsExcludePaths"; private const string AdditionalConfigurationIncludePathsKey = "NtfsIncludePaths"; private const string AdditionalConfigurationTraverseBoundaryPathKey = "NtfsTraverseBoundaryPath"; private const string AdditionalConfigurationPermissionGroupsMinLevelKey = "NtfsPermissionGroupsMinLevel"; private const string AdditionalConfigurationPermissionGroupsMaxLevelKey = "NtfsPermissionGroupsMaxLevel"; private const string AdditionalConfigurationTraverseGroupsMinLevelKey = "NtfsTraverseGroupsMinLevel"; private const string AdditionalConfigurationTraverseGroupsMaxLevelKey = "NtfsTraverseGroupsMaxLevel"; private const string AdditionalConfigurationGroupNameSanitizeReplacementKey = "NtfsGroupNameSanitizeReplacement"; private const string AdditionalConfigurationPreserveAdGroupNameCaseKey = "PreserveNtfsAdGroupNameCase"; private const string AdditionalConfigurationAdDomainControllersKey = "NtfsAdDomainControllers"; public readonly cNtfsBase ntfsBase = new cNtfsBase(); public readonly cActiveDirectoryBase activeDirectoryBase = new cActiveDirectoryBase(); private readonly Dictionary> publishedShareCache = new Dictionary>(StringComparer.OrdinalIgnoreCase); private readonly Dictionary dfsEntryPathCache = new Dictionary(StringComparer.OrdinalIgnoreCase); private cNtfsDataAreaDiagnostics currentDataAreaDiagnostics; //public readonly bool WithoutPrivateFolders = true; public cLiamProviderNtfs(cLiamConfiguration LiamConfiguration, cLiamProviderData ProviderData) : base(LiamConfiguration, ProviderData) { this.ReplaceNtfsCustomTags(); } public override async Task LogonAsync() { if (!cC4ITLicenseM42ESM.Instance.IsValid || !cC4ITLicenseM42ESM.Instance.Modules.ContainsKey(nftsModuleId)) { LogEntry($"Error: License not valid", LogLevels.Error); return false; } return await LogonAsync(true); } private void ReplaceNtfsCustomTags() { foreach (var namingConvention in NamingConventions) { String key = null; String value = null; key = "GROUPTYPEPOSTFIX"; if (namingConvention.AccessRole == eLiamAccessRoles.Owner) { value = CustomTags["Filesystem_GroupOwnerTag"]; } else if (namingConvention.AccessRole == eLiamAccessRoles.Write) { value = CustomTags["Filesystem_GroupWriteTag"]; } else if (namingConvention.AccessRole == eLiamAccessRoles.Read) { value = CustomTags["Filesystem_GroupReadTag"]; } else if (namingConvention.AccessRole == eLiamAccessRoles.Traverse) { value = CustomTags["Filesystem_GroupTraverseTag"]; } if (!String.IsNullOrEmpty(key) && !String.IsNullOrEmpty(value)) { namingConvention.DescriptionTemplate = namingConvention.DescriptionTemplate.Replace($"{{{{{key}}}}}", value); namingConvention.NamingTemplate = namingConvention.NamingTemplate.Replace($"{{{{{key}}}}}", value); namingConvention.Wildcard = namingConvention.Wildcard.Replace($"{{{{{key}}}}}", value); } value = null; key = "SCOPE"; if (namingConvention.Scope == eLiamAccessRoleScopes.DomainLocal) { value = CustomTags["Filesystem_GroupDomainLocalTag"]; } else if (namingConvention.Scope == eLiamAccessRoleScopes.Global) { value = CustomTags["Filesystem_GroupGlobalTag"]; } if (!String.IsNullOrEmpty(key) && !String.IsNullOrEmpty(value)) { namingConvention.DescriptionTemplate = namingConvention.DescriptionTemplate.Replace($"{{{{{key}}}}}", value); namingConvention.NamingTemplate = namingConvention.NamingTemplate.Replace($"{{{{{key}}}}}", value); namingConvention.Wildcard = namingConvention.Wildcard.Replace($"{{{{{key}}}}}", value); } } } public async Task LogonAsync(bool force = false) { var CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); try { var LI = new cNtfsLogonInfo() { Domain = Domain, DomainControllers = GetAdditionalConfigurationValue(AdditionalConfigurationAdDomainControllersKey), User = Credential?.Identification, UserSecret = Credential?.Secret, TargetNetworkName = RootPath, TargetGroupPath = this.GroupPath }; var RetVal = await ntfsBase.LogonAsync(LI) && await activeDirectoryBase.LogonAsync(LI); return RetVal; } catch (Exception E) { LogException(E); } finally { LogMethodEnd(CM); } return false; } public override async Task> getDataAreasAsync(int Depth = -1) { var CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); var stopwatch = Stopwatch.StartNew(); currentDataAreaDiagnostics = new cNtfsDataAreaDiagnostics(); try { if (!cC4ITLicenseM42ESM.Instance.IsValid || !cC4ITLicenseM42ESM.Instance.Modules.ContainsKey(nftsModuleId)) { LogEntry($"Error: License not valid", LogLevels.Error); return new List(); } if (!await LogonAsync()) { LogEntry($"NTFS getDataAreas failed. Stage=Logon RootPath='{RootPath}', Error='{GetLastErrorMessage()}'", LogLevels.Warning); return null; } if (string.IsNullOrEmpty(this.RootPath)) { LogEntry("NTFS getDataAreas failed. Stage=Configuration RootPath is empty.", LogLevels.Warning); return null; } LogNtfsAdContext(); var DataAreas = new List(); var rootClassification = ClassifyPath(this.RootPath); var rootDataArea = await BuildDataAreaAsync(rootClassification); if (rootDataArea == null) { LogEntry($"NTFS getDataAreas failed. Stage=RootClassification RootPath='{RootPath}', PathKind='{rootClassification?.Kind}', NormalizedPath='{rootClassification?.NormalizedPath}'", LogLevels.Warning); return null; } DataAreas.Add(rootDataArea); currentDataAreaDiagnostics.IncludedDataAreas++; if (Depth == 0) { LogDataAreaScanSummary(DataAreas, stopwatch.Elapsed); return DataAreas; } DataAreas.AddRange(await GetChildDataAreasAsync(rootClassification, Depth)); LogDataAreaScanSummary(DataAreas, stopwatch.Elapsed); return DataAreas; } catch (Exception E) { LogException(E); } finally { LogMethodEnd(CM); } return null; } public override async Task LoadDataArea(string UID) { var CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); try { await Task.Delay(0); if (!cC4ITLicenseM42ESM.Instance.IsValid || !cC4ITLicenseM42ESM.Instance.Modules.ContainsKey(nftsModuleId)) { LogEntry($"Error: License not valid", LogLevels.Error); return null; } 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) { LogException(E); return null; } finally { LogMethodEnd(CM); } } private async Task BuildDataAreaAsync(cNtfsPathClassification classification, cNtfsResultFolder folderResult = null) { if (classification == null) return null; switch (classification.Kind) { case eNtfsPathKind.ServerRoot: { return new cLiamNtfsServerRoot(this, classification.NormalizedPath, classification.Level); } case eNtfsPathKind.ClassicShare: case eNtfsPathKind.DfsLink: { var share = new cLiamNtfsShare(this, new cNtfsResultShare() { DisplayName = classification.DisplayName, Path = classification.NormalizedPath, Level = classification.Level }, classification.ParentBoundaryPath); await share.ResolvePermissionGroupsAsync(share.TechnicalName); return share; } case eNtfsPathKind.DfsNamespaceRoot: { var namespaceRoot = new cLiamNtfsDfsNamespaceRoot(this, new cNtfsResultShare() { DisplayName = classification.DisplayName, Path = classification.NormalizedPath, Level = classification.Level }); await namespaceRoot.ResolvePermissionGroupsAsync(namespaceRoot.TechnicalName); return namespaceRoot; } case eNtfsPathKind.Folder: { var folderData = folderResult ?? new cNtfsResultFolder() { DisplayName = classification.DisplayName, Path = classification.NormalizedPath, Level = classification.Level, CreatedDate = Directory.Exists(classification.NormalizedPath) ? new DirectoryInfo(classification.NormalizedPath).CreationTimeUtc.ToString("s") : DateTime.MinValue.ToString("s") }; folderData.Level = classification.Level; if (folderData.Parent == null && !string.IsNullOrWhiteSpace(classification.ParentPath)) folderData.Parent = new cNtfsResultFolder() { Path = classification.ParentPath }; var parentPath = !string.IsNullOrWhiteSpace(classification.ParentPath) ? classification.ParentPath : classification.ParentBoundaryPath; var folder = new cLiamNtfsFolder(this, null, null, folderData, parentPath); await folder.ResolvePermissionGroupsAsync(folder.TechnicalName); return folder; } default: return null; } } private cNtfsPathClassification ClassifyPath(string path) { var normalizedPath = NormalizeUncPath(path); var segments = GetUncSegments(normalizedPath); var classification = new cNtfsPathClassification() { NormalizedPath = normalizedPath, DisplayName = GetDisplayName(normalizedPath), Level = getDepth(normalizedPath) }; if (segments.Length == 1) { classification.Kind = eNtfsPathKind.ServerRoot; return classification; } if (segments.Length < 2) return classification; classification.ParentPath = segments.Length > 2 ? BuildUncPath(segments, segments.Length - 1) : string.Empty; var dfsPrefixes = GetDfsObjectPrefixes(normalizedPath); if (dfsPrefixes.Count > 0) { var namespaceRootPath = dfsPrefixes[0]; var deepestDfsPath = dfsPrefixes[dfsPrefixes.Count - 1]; if (PathsEqual(normalizedPath, namespaceRootPath)) { classification.Kind = eNtfsPathKind.DfsNamespaceRoot; classification.BoundaryPath = normalizedPath; return classification; } if (PathsEqual(normalizedPath, deepestDfsPath)) { classification.Kind = eNtfsPathKind.DfsLink; classification.BoundaryPath = deepestDfsPath; classification.ParentBoundaryPath = dfsPrefixes.Count > 1 ? dfsPrefixes[dfsPrefixes.Count - 2] : namespaceRootPath; return classification; } classification.Kind = eNtfsPathKind.Folder; classification.BoundaryPath = deepestDfsPath; classification.ParentBoundaryPath = classification.ParentPath; return classification; } var shareBoundaryPath = GetPublishedShareBoundaryPath(segments); if (!string.IsNullOrWhiteSpace(shareBoundaryPath)) { if (PathsEqual(normalizedPath, shareBoundaryPath)) { classification.Kind = eNtfsPathKind.ClassicShare; classification.BoundaryPath = shareBoundaryPath; return classification; } classification.Kind = eNtfsPathKind.Folder; classification.BoundaryPath = shareBoundaryPath; classification.ParentBoundaryPath = classification.ParentPath; return classification; } if (Directory.Exists(normalizedPath)) { classification.Kind = eNtfsPathKind.Folder; classification.ParentBoundaryPath = classification.ParentPath; } return classification; } private async Task> GetChildDataAreasAsync(cNtfsPathClassification parentClassification, int depth) { var children = new List(); if (parentClassification == null || depth == 0) return children; if (parentClassification.Kind == eNtfsPathKind.ServerRoot) { foreach (var childPath in GetServerRootChildPaths(parentClassification.NormalizedPath)) { var childClassification = ClassifyPath(childPath); if (!ShouldTraverseDataArea(childClassification)) continue; if (ShouldIncludeDataArea(childClassification)) { var childDataArea = await BuildDataAreaAsync(childClassification); if (childDataArea != null) children.Add(childDataArea); } if (depth > 1) children.AddRange(await GetChildDataAreasAsync(childClassification, depth - 1)); } return children; } var folderEntries = await ntfsBase.RequestFoldersListAsync(parentClassification.NormalizedPath, 1); currentDataAreaDiagnostics?.MergeEnumeration(ntfsBase.LastEnumerationDiagnostics); if (folderEntries == null) { LogEntry($"NTFS scan returned null while enumerating children of '{parentClassification.NormalizedPath}'. ChildrenSkipped=true.", LogLevels.Warning); return children; } foreach (var entry in folderEntries.Values.OfType()) { var childClassification = ClassifyPath(entry.Path); if (!ShouldTraverseDataArea(childClassification)) continue; if (ShouldIncludeDataArea(childClassification)) { var childDataArea = await BuildDataAreaAsync(childClassification, entry); if (childDataArea != null) children.Add(childDataArea); } if (depth > 1) children.AddRange(await GetChildDataAreasAsync(childClassification, depth - 1)); } return children; } private IEnumerable GetServerRootChildPaths(string serverRootPath) { var segments = GetUncSegments(serverRootPath); if (segments.Length != 1) return Enumerable.Empty(); var serverName = segments[0]; return GetPublishedShareNames(serverName) .OrderBy(i => i, StringComparer.OrdinalIgnoreCase) .Select(shareName => BuildUncPath(new[] { serverName, shareName }, 2)); } private bool ShouldIncludeDataArea(cNtfsPathClassification classification) { if (classification == null) return false; if (currentDataAreaDiagnostics != null) currentDataAreaDiagnostics.DataAreaCandidates++; if (!MatchesDataAreaRegEx(classification.DisplayName)) { if (currentDataAreaDiagnostics != null) { currentDataAreaDiagnostics.SkippedByDataAreaRegex++; currentDataAreaDiagnostics.AddFilterSample($"DataAreaRegEx rejected '{classification.NormalizedPath}' with DisplayName='{classification.DisplayName}' and DataAreaRegEx='{this.DataAreaRegEx}'"); } LogEntry($"Skip NTFS path '{classification.NormalizedPath}' because DisplayName='{classification.DisplayName}' does not match DataAreaRegEx='{this.DataAreaRegEx}'", LogLevels.Debug); return false; } string matchingConfigurationKey; string matchingRule; if (IsPathBlacklisted(classification, out matchingConfigurationKey, out matchingRule)) { if (currentDataAreaDiagnostics != null) { currentDataAreaDiagnostics.SkippedByExcludeRule++; currentDataAreaDiagnostics.AddFilterSample($"Exclude rejected '{classification.NormalizedPath}' via {matchingConfigurationKey}={matchingRule}"); } LogEntry($"Skip NTFS path '{classification.NormalizedPath}' due to AdditionalConfiguration rule '{matchingConfigurationKey}={matchingRule}'", LogLevels.Debug); return false; } if (!IsPathWhitelisted(classification, true, out matchingConfigurationKey, out matchingRule)) { if (currentDataAreaDiagnostics != null) { currentDataAreaDiagnostics.SkippedByMissingIncludeRule++; currentDataAreaDiagnostics.AddFilterSample($"Include missing for '{classification.NormalizedPath}' via {AdditionalConfigurationIncludePathsKey}"); } LogEntry($"Skip NTFS path '{classification.NormalizedPath}' because no AdditionalConfiguration whitelist matched", LogLevels.Debug); return false; } if (currentDataAreaDiagnostics != null) currentDataAreaDiagnostics.IncludedDataAreas++; return true; } private bool ShouldTraverseDataArea(cNtfsPathClassification classification) { if (classification == null) return false; string matchingConfigurationKey; string matchingRule; if (IsPathBlacklisted(classification, out matchingConfigurationKey, out matchingRule)) { if (currentDataAreaDiagnostics != null) { currentDataAreaDiagnostics.SkippedSubtreesByExcludeRule++; currentDataAreaDiagnostics.AddFilterSample($"Subtree exclude rejected '{classification.NormalizedPath}' via {matchingConfigurationKey}={matchingRule}"); } LogEntry($"Skip NTFS subtree '{classification.NormalizedPath}' due to AdditionalConfiguration rule '{matchingConfigurationKey}={matchingRule}'", LogLevels.Debug); return false; } if (!HasAdditionalConfigurationValues(AdditionalConfigurationIncludePathsKey)) return true; if (IsPathWhitelisted(classification, true, out matchingConfigurationKey, out matchingRule)) return true; if (currentDataAreaDiagnostics != null) { currentDataAreaDiagnostics.SkippedSubtreesByMissingIncludeRule++; currentDataAreaDiagnostics.AddFilterSample($"Subtree outside include whitelist '{classification.NormalizedPath}' via {AdditionalConfigurationIncludePathsKey}"); } LogEntry($"Skip NTFS subtree '{classification.NormalizedPath}' because it is outside AdditionalConfiguration whitelist '{AdditionalConfigurationIncludePathsKey}'", LogLevels.Debug); return false; } private bool MatchesDataAreaRegEx(string displayName) { if (string.IsNullOrEmpty(this.DataAreaRegEx)) return true; return Regex.Match(displayName ?? string.Empty, this.DataAreaRegEx).Success; } private bool IsPathBlacklisted(cNtfsPathClassification classification, out string matchingConfigurationKey, out string matchingRule) { return TryMatchPathPolicy(classification, AdditionalConfigurationExcludePathsKey, false, out matchingConfigurationKey, out matchingRule); } private bool IsPathWhitelisted(cNtfsPathClassification classification, bool allowPathAncestorMatches, out string matchingConfigurationKey, out string matchingRule) { matchingConfigurationKey = null; matchingRule = null; if (!HasAdditionalConfigurationValues(AdditionalConfigurationIncludePathsKey)) return true; return TryMatchPathPolicy(classification, AdditionalConfigurationIncludePathsKey, allowPathAncestorMatches, out matchingConfigurationKey, out matchingRule); } 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 (!MatchesPathPolicy(classification, pattern)) continue; matchingConfigurationKey = key; matchingRule = pattern; return true; } if (!allowPathAncestorMatches) return false; foreach (var pattern in patterns) { if (!CanPathLeadToPattern(classification, pattern)) continue; matchingConfigurationKey = key; matchingRule = pattern; return true; } return false; } private bool HasAdditionalConfigurationValues(string key) { return GetAdditionalConfigurationValues(key).Any(); } private IEnumerable GetAdditionalConfigurationValues(string key) { if (AdditionalConfiguration == null || string.IsNullOrWhiteSpace(key)) return Enumerable.Empty(); string rawValue; if (!AdditionalConfiguration.TryGetValue(key, out rawValue) || string.IsNullOrWhiteSpace(rawValue)) return Enumerable.Empty(); return rawValue .Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries) .Select(i => i.Trim()) .Where(i => !string.IsNullOrWhiteSpace(i)) .Distinct(StringComparer.OrdinalIgnoreCase) .ToList(); } private string GetRelativePathFromRoot(string path) { var normalizedRoot = NormalizeUncPath(this.RootPath); var normalizedPath = NormalizeUncPath(path); if (string.IsNullOrWhiteSpace(normalizedRoot) || string.IsNullOrWhiteSpace(normalizedPath)) return string.Empty; if (PathsEqual(normalizedRoot, normalizedPath)) return string.Empty; var rootWithSeparator = normalizedRoot + "\\"; if (!normalizedPath.StartsWith(rootWithSeparator, StringComparison.OrdinalIgnoreCase)) return normalizedPath; return normalizedPath.Substring(rootWithSeparator.Length) .Trim() .TrimStart('\\') .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('/', '\\').Trim('\\'); var normalizedPattern = pattern.Trim().Replace('/', '\\').Trim('\\'); if (string.IsNullOrWhiteSpace(normalizedPattern)) return false; var regexPattern = "^" + Regex.Escape(normalizedPattern).Replace("\\*", ".*") + "$"; return Regex.IsMatch(normalizedValue, regexPattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); } private bool CanPathLeadToPattern(cNtfsPathClassification classification, string pattern) { if (classification == null || string.IsNullOrWhiteSpace(pattern)) return false; foreach (var candidate in GetPathPolicyCandidates(classification)) { if (IsPathAncestorOfPattern(candidate, pattern)) return true; } 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 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 void LogNtfsAdContext() { var includePaths = string.Join(";", GetAdditionalConfigurationValues(AdditionalConfigurationIncludePathsKey)); var excludePaths = string.Join(";", GetAdditionalConfigurationValues(AdditionalConfigurationExcludePathsKey)); var dc = activeDirectoryBase?.EffectiveDomainController; LogEntry( $"NTFS getDataAreas context: Stage=Start RootPath='{RootPath}', MaxDepth={MaxDepth}, Domain='{Domain}', DomainController='{(string.IsNullOrWhiteSpace(dc) ? "(domain locator)" : dc)}', Credential='{Credential?.Identification}', GroupStrategy='{GroupStrategy}', DataAreaRegEx='{DataAreaRegEx}', IncludePaths='{includePaths}', ExcludePaths='{excludePaths}'", LogLevels.Info); } private void LogDataAreaScanSummary(ICollection dataAreas, TimeSpan duration) { var diagnostics = currentDataAreaDiagnostics; if (diagnostics == null) return; var returnedDataAreas = dataAreas?.Count ?? 0; var permissionDataAreas = dataAreas?.OfType().Count() ?? 0; var issueCount = diagnostics.EnumerationFailures + diagnostics.MetadataFailures + diagnostics.SubtreeFailures + diagnostics.AclReadFailures + diagnostics.UnresolvedAclSids + diagnostics.AclGroupsWithoutNamingMatch + diagnostics.DataAreasWithoutPermissionMapping; LogEntry( $"NTFS getDataAreas summary: Stage=Summary RootPath='{RootPath}', MaxDepth={MaxDepth}, ReturnedDataAreas={returnedDataAreas}, PermissionDataAreas={permissionDataAreas}, ScanRequests={diagnostics.ScanRequests}, FoldersEnumerated={diagnostics.FoldersEnumerated}, DataAreaCandidates={diagnostics.DataAreaCandidates}, IncludedCandidates={diagnostics.IncludedDataAreas}, SkippedByDataAreaRegEx={diagnostics.SkippedByDataAreaRegex}, SkippedByExcludeRule={diagnostics.SkippedByExcludeRule}, SkippedByMissingIncludeRule={diagnostics.SkippedByMissingIncludeRule}, SkippedSubtreesByExcludeRule={diagnostics.SkippedSubtreesByExcludeRule}, SkippedSubtreesByMissingIncludeRule={diagnostics.SkippedSubtreesByMissingIncludeRule}, EnumerationFailures={diagnostics.EnumerationFailures}, MetadataFailures={diagnostics.MetadataFailures}, SubtreeFailures={diagnostics.SubtreeFailures}, AclReadFailures={diagnostics.AclReadFailures}, AclEntriesEvaluated={diagnostics.AclEntriesEvaluated}, UnresolvedAclSids={diagnostics.UnresolvedAclSids}, AclGroupsWithoutNamingMatch={diagnostics.AclGroupsWithoutNamingMatch}, DataAreasWithoutPermissionMapping={diagnostics.DataAreasWithoutPermissionMapping}, Duration='{duration}'", issueCount > 0 ? LogLevels.Warning : LogLevels.Info); if (diagnostics.EnumerationFailureSamples.Count > 0) LogEntry($"NTFS diagnostic samples: Stage=Enumeration Samples='{string.Join(" | ", diagnostics.EnumerationFailureSamples)}'", LogLevels.Warning); if (diagnostics.FilterSamples.Count > 0) LogEntry($"NTFS diagnostic samples: Stage=Filter Samples='{string.Join(" | ", diagnostics.FilterSamples)}'", LogLevels.Info); if (diagnostics.MappingIssueSamples.Count > 0) LogEntry($"NTFS diagnostic samples: Stage=AclMapping Samples='{string.Join(" | ", diagnostics.MappingIssueSamples)}'", LogLevels.Warning); } internal void RecordAclReadFailure(string path) { if (currentDataAreaDiagnostics != null) { currentDataAreaDiagnostics.AclReadFailures++; currentDataAreaDiagnostics.AddMappingIssueSample($"ACL read failed for '{path}'"); } LogEntry($"NTFS data area ACL read failed. Stage=AclRead Path='{path}'", LogLevels.Warning); } internal void RecordAclEntryEvaluated() { if (currentDataAreaDiagnostics != null) currentDataAreaDiagnostics.AclEntriesEvaluated++; } internal void RecordUnresolvedAclSid(string path, string sid) { if (currentDataAreaDiagnostics != null) { currentDataAreaDiagnostics.UnresolvedAclSids++; currentDataAreaDiagnostics.AddMappingIssueSample($"Unresolved ACL SID '{sid}' on '{path}'"); } LogEntry($"NTFS ACL SID could not be resolved to an AD group. Stage=AclSidResolution Path='{path}', Sid='{sid}'", LogLevels.Warning); } internal void RecordAclGroupWithoutNamingMatch(string path, string samAccountName) { if (currentDataAreaDiagnostics != null) { currentDataAreaDiagnostics.AclGroupsWithoutNamingMatch++; currentDataAreaDiagnostics.AddMappingIssueSample($"ACL group '{samAccountName}' on '{path}' did not match naming conventions"); } } internal void RecordDataAreaWithoutPermissionMapping(string path, IEnumerable aclGroups, string ownerWildcard, string writeWildcard, string readWildcard) { var groups = string.Join(",", aclGroups ?? Enumerable.Empty()); if (currentDataAreaDiagnostics != null) { currentDataAreaDiagnostics.DataAreasWithoutPermissionMapping++; currentDataAreaDiagnostics.AddMappingIssueSample($"DataArea '{path}' has no Owner/Write/Read mapping. ACL groups: {groups}"); } LogEntry( $"NTFS data area returned without Owner/Write/Read group mapping. Stage=AclMapping Path='{path}', AclGroups='{groups}', OwnerWildcard='{ownerWildcard}', WriteWildcard='{writeWildcard}', ReadWildcard='{readWildcard}'", LogLevels.Warning); } private List GetDfsObjectPrefixes(string path) { var normalizedPath = NormalizeUncPath(path); var segments = GetUncSegments(normalizedPath); var prefixes = new List(); for (var segmentCount = 2; segmentCount <= segments.Length; segmentCount++) { var prefix = BuildUncPath(segments, segmentCount); string entryPath; if (!TryGetDfsEntryPath(prefix, out entryPath)) continue; prefixes.Add(!string.IsNullOrWhiteSpace(entryPath) ? NormalizeUncPath(entryPath) : prefix); } return prefixes .Distinct(StringComparer.OrdinalIgnoreCase) .OrderBy(i => GetUncSegments(i).Length) .ToList(); } private bool TryGetDfsEntryPath(string path, out string entryPath) { var normalizedPath = NormalizeUncPath(path); if (dfsEntryPathCache.TryGetValue(normalizedPath, out entryPath)) return !string.IsNullOrWhiteSpace(entryPath); string resolvedEntryPath; if (cNetworkConnection.TryGetDfsEntryPath(normalizedPath, out resolvedEntryPath)) { entryPath = NormalizeUncPath(string.IsNullOrWhiteSpace(resolvedEntryPath) ? normalizedPath : resolvedEntryPath); dfsEntryPathCache[normalizedPath] = entryPath; return true; } dfsEntryPathCache[normalizedPath] = string.Empty; entryPath = string.Empty; return false; } private string GetPublishedShareBoundaryPath(string[] segments) { if (segments == null || segments.Length < 2) return string.Empty; var serverName = segments[0]; var publishedShares = GetPublishedShareNames(serverName); if (!publishedShares.Contains(segments[1])) return string.Empty; return BuildUncPath(segments, 2); } private bool PathsEqual(string left, string right) { return string.Equals( NormalizeUncPath(left), NormalizeUncPath(right), StringComparison.OrdinalIgnoreCase); } private string NormalizeUncPath(string path) { if (string.IsNullOrWhiteSpace(path)) return string.Empty; var segments = path.Trim().Replace('/', '\\').Split(new[] { '\\' }, StringSplitOptions.RemoveEmptyEntries); if (segments.Length == 0) return string.Empty; return @"\\" + string.Join("\\", segments); } private string[] GetUncSegments(string path) { var normalized = NormalizeUncPath(path); return normalized.Split(new[] { '\\' }, StringSplitOptions.RemoveEmptyEntries); } private string BuildUncPath(string[] segments, int segmentCount) { if (segments == null || segmentCount <= 0 || segments.Length < segmentCount) return string.Empty; return @"\\" + string.Join("\\", segments.Take(segmentCount)); } private string GetDisplayName(string path) { var segments = GetUncSegments(path); if (segments.Length == 0) return string.Empty; return segments.Last(); } private HashSet GetPublishedShareNames(string serverName) { HashSet shares; if (publishedShareCache.TryGetValue(serverName, out shares)) return shares; shares = new HashSet(StringComparer.OrdinalIgnoreCase); try { using (var connection = new cNetworkConnection(this.RootPath, this.Credential?.Identification, this.Credential?.Secret)) { foreach (var share in connection.EnumNetShares(serverName)) { if (!IsVisibleDiskShare(share)) continue; shares.Add(share.shi1_netname); } } } catch (Exception ex) { LogException(ex); } publishedShareCache[serverName] = shares; return shares; } private bool IsVisibleDiskShare(C4IT_IAM.SHARE_INFO_1 share) { if (string.IsNullOrWhiteSpace(share.shi1_netname)) return false; if (share.shi1_netname.StartsWith("ERROR=", StringComparison.OrdinalIgnoreCase)) return false; if (share.shi1_netname.EndsWith("$", StringComparison.OrdinalIgnoreCase)) return false; var shareType = share.shi1_type & 0xFF; return shareType == (uint)C4IT_IAM.SHARE_TYPE.STYPE_DISKTREE; } public override async Task> getSecurityGroupsAsync(string groupFilter) { var CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); try { if (!cC4ITLicenseM42ESM.Instance.IsValid || !cC4ITLicenseM42ESM.Instance.Modules.ContainsKey(nftsModuleId)) { LogEntry($"Error: License not valid", LogLevels.Error); return new List(); } if (!await LogonAsync()) return null; if (string.IsNullOrEmpty(this.GroupPath)) return null; var SecurityGroups = new List(); var SGL = await activeDirectoryBase.RequestSecurityGroupsListAsync(groupFilter); if (SGL == null) return null; foreach (var Entry in SGL) { if (!string.IsNullOrEmpty(this.GroupRegEx) && !Regex.Match(Entry.Value.DisplayName, this.GroupRegEx).Success) continue; var SecurityGroup = new cLiamAdGroup(this, (cSecurityGroupResult)Entry.Value); SecurityGroups.Add(SecurityGroup); } return SecurityGroups; } catch (Exception E) { LogException(E); } finally { LogMethodEnd(CM); } return null; } public Task CreateDataAreaAsync( string newFolderPath, string newFolderParent, IDictionary customTags, IEnumerable ownerSids, IEnumerable readerSids, IEnumerable writerSids, bool whatIf = false) { var classification = ClassifyPath(newFolderPath); string levelSkipReason; if (!IsCreateDataAreaLevelManagedPath(classification, out levelSkipReason)) { return Task.FromResult(new ResultToken(System.Reflection.MethodBase.GetCurrentMethod().ToString()) { resultErrorId = 30008, resultMessage = levelSkipReason }); } var engine = CreateFilesystemEngine( newFolderPath, newFolderParent, customTags, ownerSids, readerSids, writerSids); engine.WhatIf = whatIf; var result = engine.createDataArea(); return Task.FromResult(result); } public Task EnsureMissingPermissionGroupsAsync( string folderPath, IDictionary customTags, IEnumerable ownerSids, IEnumerable readerSids, IEnumerable writerSids, bool allowSharePathEnsure = false, bool ensureTraverseGroups = false, bool whatIf = false) { var classification = ClassifyPath(folderPath); var allowShareKinds = allowSharePathEnsure; if (!IsSupportedPermissionManagedPathKind( classification, allowShareKinds ? new[] { eNtfsPathKind.Folder, eNtfsPathKind.ClassicShare, eNtfsPathKind.DfsLink } : new[] { eNtfsPathKind.Folder })) { return Task.FromResult(new ResultToken(System.Reflection.MethodBase.GetCurrentMethod().ToString()) { resultErrorId = 30008, resultMessage = allowShareKinds ? $"NTFS permission ensure is only supported for folder and share paths. DFS namespaces and server roots are skipped: {folderPath}" : $"NTFS permission ensure is only supported for folder paths unless share support is explicitly enabled. Shares, DFS namespaces and server roots are skipped: {folderPath}" }); } string matchingConfigurationKey; string matchingRule; if (IsPathBlacklisted(classification, out matchingConfigurationKey, out matchingRule)) { return Task.FromResult(new ResultToken(System.Reflection.MethodBase.GetCurrentMethod().ToString()) { resultErrorId = 30008, resultMessage = $"NTFS permission ensure skipped for '{folderPath}' due to AdditionalConfiguration rule '{matchingConfigurationKey}={matchingRule}'." }); } if (!IsPathWhitelisted(classification, false, out matchingConfigurationKey, out matchingRule)) { return Task.FromResult(new ResultToken(System.Reflection.MethodBase.GetCurrentMethod().ToString()) { resultErrorId = 30008, resultMessage = $"NTFS permission ensure skipped for '{folderPath}' because no AdditionalConfiguration whitelist matched." }); } string levelSkipReason; if (!IsPermissionLevelManagedPath(classification, out levelSkipReason)) { return Task.FromResult(new ResultToken(System.Reflection.MethodBase.GetCurrentMethod().ToString()) { resultErrorId = 30008, resultMessage = levelSkipReason }); } var parentPath = Directory.GetParent(folderPath)?.FullName; var engine = CreateFilesystemEngine( folderPath, parentPath, customTags, ownerSids, readerSids, writerSids); engine.WhatIf = whatIf; var allowTraverseGroups = ensureTraverseGroups && IsSupportedPermissionManagedPathKind( classification, eNtfsPathKind.Folder, eNtfsPathKind.ClassicShare, eNtfsPathKind.DfsLink); var resultToken = engine.ensureDataAreaPermissions(allowTraverseGroups); if (!allowTraverseGroups && ensureTraverseGroups) resultToken.warnings.Add($"Traverse groups are currently only ensured for folder and share paths. Traverse processing was skipped for '{folderPath}'."); return Task.FromResult(resultToken); } public Task EnsureTraverseGroupsAsync( string folderPath, IDictionary customTags = null, bool whatIf = false) { var classification = ClassifyPath(folderPath); if (!IsSupportedPermissionManagedPathKind( classification, eNtfsPathKind.Folder, eNtfsPathKind.ClassicShare, eNtfsPathKind.DfsLink)) { return Task.FromResult(new ResultToken(System.Reflection.MethodBase.GetCurrentMethod().ToString()) { resultErrorId = 30008, resultMessage = $"NTFS traverse ensure is only supported for folder and share paths. DFS namespaces and server roots are skipped: {folderPath}" }); } string matchingConfigurationKey; string matchingRule; if (IsPathBlacklisted(classification, out matchingConfigurationKey, out matchingRule)) { return Task.FromResult(new ResultToken(System.Reflection.MethodBase.GetCurrentMethod().ToString()) { resultErrorId = 30008, resultMessage = $"NTFS traverse ensure skipped for '{folderPath}' due to AdditionalConfiguration rule '{matchingConfigurationKey}={matchingRule}'." }); } var traverseLevelRange = GetTraverseLevelRange(); if (!traverseLevelRange.IsValid) { var message = $"NTFS traverse ensure skipped for '{folderPath}' because {traverseLevelRange.ErrorMessage}"; LogEntry(message, LogLevels.Warning); return Task.FromResult(new ResultToken(System.Reflection.MethodBase.GetCurrentMethod().ToString()) { resultErrorId = 30008, resultMessage = message }); } string levelSkipReason; if (!IsLevelManagedPath(classification, traverseLevelRange, "traverse group ensure", out levelSkipReason)) { return Task.FromResult(new ResultToken(System.Reflection.MethodBase.GetCurrentMethod().ToString()) { resultErrorId = 30008, resultMessage = levelSkipReason }); } if (!IsTraversePermissionCandidatePath(folderPath)) { return Task.FromResult(new ResultToken(System.Reflection.MethodBase.GetCurrentMethod().ToString()) { resultErrorId = 30008, resultMessage = $"NTFS traverse ensure skipped for '{folderPath}' because the path is not managed by the traverse configuration." }); } var parentPath = GetEngineParentPath(classification, folderPath); var engine = CreateFilesystemEngine( folderPath, parentPath, customTags, null, null, null); engine.WhatIf = whatIf; return Task.FromResult(engine.ensureTraversePermissionsOnly()); } private static string GetEngineParentPath(cNtfsPathClassification classification, string folderPath) { if (classification != null && !string.IsNullOrWhiteSpace(classification.ParentPath)) return classification.ParentPath; if (classification != null && !string.IsNullOrWhiteSpace(classification.ParentBoundaryPath)) return classification.ParentBoundaryPath; try { var parentPath = Directory.GetParent(folderPath)?.FullName; return string.IsNullOrWhiteSpace(parentPath) ? folderPath : parentPath; } catch { return folderPath; } } private DataArea_FileSystem CreateFilesystemEngine( string folderPath, string parentFolderPath, IDictionary customTags, IEnumerable ownerSids, IEnumerable readerSids, IEnumerable writerSids) { var requiresDomainLocalTag = this.GroupStrategy == eLiamGroupStrategies.Ntfs_AGDLP || (NamingConventions ?? Enumerable.Empty()) .Any(i => i.Scope == eLiamAccessRoleScopes.DomainLocal); var mergedCustomTags = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (var tag in CustomTags) mergedCustomTags[tag.Key] = tag.Value; if (customTags != null) { foreach (var tag in customTags) mergedCustomTags[tag.Key] = tag.Value; } var engine = new DataArea_FileSystem { ConfigID = "manual", domainName = this.Domain, effectiveDomainController = activeDirectoryBase.EffectiveDomainController, username = this.Credential.Identification, password = new NetworkCredential("", this.Credential.Secret).SecurePassword, baseFolder = this.RootPath, newFolderPath = folderPath, newFolderParent = parentFolderPath, groupPrefix = GetRequiredCustomTag("Filesystem_GroupPrefixTag"), groupOUPath = this.GroupPath, groupPermissionStrategy = (C4IT_IAM_GET.PermissionGroupStrategy)this.GroupStrategy, groupCustomTags = mergedCustomTags, ownerUserSids = ownerSids?.Where(i => !string.IsNullOrWhiteSpace(i)).Distinct(StringComparer.OrdinalIgnoreCase).ToList() ?? new List(), readerUserSids = readerSids?.Where(i => !string.IsNullOrWhiteSpace(i)).Distinct(StringComparer.OrdinalIgnoreCase).ToList() ?? new List(), writerUserSids = writerSids?.Where(i => !string.IsNullOrWhiteSpace(i)).Distinct(StringComparer.OrdinalIgnoreCase).ToList() ?? new List(), groupOwnerTag = GetRequiredCustomTag("Filesystem_GroupOwnerTag"), groupWriteTag = GetRequiredCustomTag("Filesystem_GroupWriteTag"), groupReadTag = GetRequiredCustomTag("Filesystem_GroupReadTag"), groupTraverseTag = GetRequiredCustomTag("Filesystem_GroupTraverseTag"), groupDLTag = requiresDomainLocalTag ? GetRequiredCustomTag("Filesystem_GroupDomainLocalTag") : string.Empty, groupGTag = GetRequiredCustomTag("Filesystem_GroupGlobalTag"), CanManagePermissionsForPath = IsPermissionManagedFolderPath, CanManageTraversePermissionsForPath = IsTraversePermissionManagedPath, forceStrictAdGroupNames = IsAdditionalConfigurationEnabled("ForceStrictAdGroupNames"), groupNameSanitizeReplacement = GetAdditionalConfigurationValueOrDefault( AdditionalConfigurationGroupNameSanitizeReplacementKey, Helper.DefaultGroupNameSanitizeReplacement), preserveAdGroupNameCase = IsAdditionalConfigurationEnabled(AdditionalConfigurationPreserveAdGroupNameCaseKey) }; engine.traverseBoundaryPath = GetAdditionalConfigurationValue(AdditionalConfigurationTraverseBoundaryPathKey); foreach (var template in BuildSecurityGroupTemplates()) engine.templates.Add(template); return engine; } private bool IsAdditionalConfigurationEnabled(string key) { if (AdditionalConfiguration == null || string.IsNullOrWhiteSpace(key)) return false; if (!AdditionalConfiguration.TryGetValue(key, out var rawValue) || string.IsNullOrWhiteSpace(rawValue)) return false; return rawValue.Equals("true", StringComparison.OrdinalIgnoreCase) || rawValue.Equals("1", StringComparison.OrdinalIgnoreCase) || rawValue.Equals("yes", StringComparison.OrdinalIgnoreCase); } private string GetAdditionalConfigurationValue(string key) { if (AdditionalConfiguration == null || string.IsNullOrWhiteSpace(key)) return string.Empty; if (!AdditionalConfiguration.TryGetValue(key, out var rawValue) || string.IsNullOrWhiteSpace(rawValue)) return string.Empty; return rawValue.Trim(); } private string GetAdditionalConfigurationValueOrDefault(string key, string defaultValue) { if (AdditionalConfiguration == null || string.IsNullOrWhiteSpace(key)) return defaultValue; if (!AdditionalConfiguration.TryGetValue(key, out var rawValue)) return defaultValue; return rawValue == null ? string.Empty : rawValue.Trim(); } public bool IsPermissionManagedFolderPath(string path) { return IsPermissionManagedPath(path, eNtfsPathKind.Folder); } public bool IsPermissionManagedSharePath(string path) { return IsPermissionManagedPath(path, eNtfsPathKind.ClassicShare, eNtfsPathKind.DfsLink); } private bool IsPermissionManagedPath(string path, params eNtfsPathKind[] supportedKinds) { if (!IsPermissionManagedPathCandidate(path, supportedKinds)) return false; var classification = ClassifyPath(path); string levelSkipReason; return IsPermissionLevelManagedPath(classification, out levelSkipReason); } private bool IsPermissionManagedPathCandidate(string path, params eNtfsPathKind[] supportedKinds) { var classification = ClassifyPath(path); if (!IsSupportedPermissionManagedPathKind(classification, supportedKinds)) return false; string matchingConfigurationKey; string matchingRule; if (IsPathBlacklisted(classification, out matchingConfigurationKey, out matchingRule)) return false; if (!IsPathWhitelisted(classification, false, out matchingConfigurationKey, out matchingRule)) return false; return true; } private bool IsTraversePermissionManagedPath(string path) { if (!IsTraversePermissionCandidatePath(path)) return false; string levelSkipReason; return IsTraverseLevelManagedPath(ClassifyPath(path), out levelSkipReason); } private bool IsTraversePermissionCandidatePath(string path) { if (string.IsNullOrWhiteSpace(GetAdditionalConfigurationValue(AdditionalConfigurationTraverseBoundaryPathKey))) return IsPermissionManagedPathCandidate(path, eNtfsPathKind.Folder); var classification = ClassifyPath(path); if (classification == null || classification.Kind == eNtfsPathKind.ServerRoot || classification.Kind == eNtfsPathKind.Unknown) return false; string matchingConfigurationKey; string matchingRule; if (IsPathBlacklisted(classification, out matchingConfigurationKey, out matchingRule)) return false; return Directory.Exists(path); } private bool IsPermissionLevelManagedPath(cNtfsPathClassification classification, out string skipReason) { return IsLevelManagedPath( classification, GetPermissionLevelRange(), "permission group ensure", out skipReason); } private bool IsCreateDataAreaLevelManagedPath(cNtfsPathClassification classification, out string skipReason) { return IsLevelManagedPath( classification, GetPermissionLevelRange(), "create data area", out skipReason); } private bool IsTraverseLevelManagedPath(cNtfsPathClassification classification, out string skipReason) { return IsLevelManagedPath( classification, GetTraverseLevelRange(), "traverse group ensure", out skipReason); } private bool IsLevelManagedPath(cNtfsPathClassification classification, cNtfsLevelRange range, string operationName, out string skipReason) { skipReason = string.Empty; if (range == null || !range.IsConfigured) return true; if (classification == null) { skipReason = $"NTFS {operationName} skipped because the path could not be classified."; return false; } if (!range.IsValid) { skipReason = $"NTFS {operationName} skipped for '{classification.NormalizedPath}' because {range.ErrorMessage}"; LogEntry(skipReason, LogLevels.Warning); return false; } if (range.Contains(classification.Level)) return true; skipReason = $"NTFS {operationName} skipped for '{classification.NormalizedPath}' because level {classification.Level} is outside configured range {range.MinLevel}..{range.MaxLevel}."; LogEntry(skipReason, LogLevels.Debug); return false; } private cNtfsLevelRange GetPermissionLevelRange() { return GetConfiguredLevelRange( AdditionalConfigurationPermissionGroupsMinLevelKey, AdditionalConfigurationPermissionGroupsMaxLevelKey, "permission group level range"); } private cNtfsLevelRange GetTraverseLevelRange() { var range = GetConfiguredLevelRange( AdditionalConfigurationTraverseGroupsMinLevelKey, AdditionalConfigurationTraverseGroupsMaxLevelKey, "traverse group level range"); if (range.IsValid && !HasAdditionalConfigurationValue(AdditionalConfigurationTraverseGroupsMaxLevelKey) && MaxDepth > 0) { var maxDepthLevel = MaxDepth - 1; if (range.IsConfigured) { range.MaxLevel = Math.Min(range.MaxLevel, maxDepthLevel); } else { range.IsConfigured = true; range.MaxLevel = maxDepthLevel; } if (range.MinLevel > range.MaxLevel) { range.IsValid = false; range.ErrorMessage = $"AdditionalConfiguration traverse group level range is invalid because min level {range.MinLevel} is greater than effective max level {range.MaxLevel}."; } } return range; } private cNtfsLevelRange GetConfiguredLevelRange(string minKey, string maxKey, string label) { var range = new cNtfsLevelRange(); var minValue = GetAdditionalConfigurationValue(minKey); var maxValue = GetAdditionalConfigurationValue(maxKey); var hasMin = !string.IsNullOrWhiteSpace(minValue); var hasMax = !string.IsNullOrWhiteSpace(maxValue); range.IsConfigured = hasMin || hasMax; if (!range.IsConfigured) return range; int parsedValue; if (hasMin) { if (!int.TryParse(minValue, out parsedValue)) { range.IsValid = false; range.ErrorMessage = $"AdditionalConfiguration '{minKey}' for {label} is not a valid integer: '{minValue}'."; return range; } range.MinLevel = parsedValue; } if (hasMax) { if (!int.TryParse(maxValue, out parsedValue)) { range.IsValid = false; range.ErrorMessage = $"AdditionalConfiguration '{maxKey}' for {label} is not a valid integer: '{maxValue}'."; return range; } range.MaxLevel = parsedValue; } if (range.MinLevel > range.MaxLevel) { range.IsValid = false; range.ErrorMessage = $"AdditionalConfiguration {label} is invalid because min level {range.MinLevel} is greater than max level {range.MaxLevel}."; } return range; } private bool HasAdditionalConfigurationValue(string key) { return !string.IsNullOrWhiteSpace(GetAdditionalConfigurationValue(key)); } private static bool IsSupportedPermissionManagedPathKind(cNtfsPathClassification classification, params eNtfsPathKind[] supportedKinds) { if (classification == null || supportedKinds == null || supportedKinds.Length == 0) return false; return supportedKinds.Contains(classification.Kind); } private IEnumerable BuildSecurityGroupTemplates() { var templates = new List(); var namingConventions = (NamingConventions ?? Enumerable.Empty()).ToList(); var hasStrategyMatchingTraverseConvention = namingConventions.Any(i => TryMapSecurityGroupType(i.AccessRole, out var securityGroupType) && securityGroupType == SecurityGroupType.Traverse && IsStrategyMatchingTraverseScope(i.Scope)); foreach (var namingConvention in namingConventions) { if (!TryMapSecurityGroupType(namingConvention.AccessRole, out var securityGroupType)) continue; if (securityGroupType == SecurityGroupType.Traverse && hasStrategyMatchingTraverseConvention && !IsStrategyMatchingTraverseScope(namingConvention.Scope)) { continue; } if (!TryMapGroupScope(namingConvention.Scope, securityGroupType, out var groupScope)) continue; templates.Add(new IAM_SecurityGroupTemplate( namingConvention.NamingTemplate, namingConvention.DescriptionTemplate, namingConvention.Wildcard, securityGroupType, groupScope)); } return templates; } private bool TryMapSecurityGroupType(eLiamAccessRoles accessRole, out SecurityGroupType securityGroupType) { securityGroupType = SecurityGroupType.Read; switch (accessRole) { case eLiamAccessRoles.Owner: securityGroupType = SecurityGroupType.Owner; return true; case eLiamAccessRoles.Write: securityGroupType = SecurityGroupType.Write; return true; case eLiamAccessRoles.Read: securityGroupType = SecurityGroupType.Read; return true; case eLiamAccessRoles.Traverse: securityGroupType = SecurityGroupType.Traverse; return true; default: return false; } } private bool TryMapGroupScope(eLiamAccessRoleScopes scope, SecurityGroupType type, out GroupScope groupScope) { groupScope = GroupScope.Global; if (type == SecurityGroupType.Traverse) { groupScope = GetStrategyTraverseGroupScope(); return true; } switch (scope) { case eLiamAccessRoleScopes.Global: groupScope = GroupScope.Global; return true; case eLiamAccessRoleScopes.DomainLocal: groupScope = GroupScope.Local; return true; case eLiamAccessRoleScopes.Unknown: return false; default: return false; } } private GroupScope GetStrategyTraverseGroupScope() { return this.GroupStrategy == eLiamGroupStrategies.Ntfs_AGDLP ? GroupScope.Local : GroupScope.Global; } private bool IsStrategyMatchingTraverseScope(eLiamAccessRoleScopes scope) { if (scope == eLiamAccessRoleScopes.Unknown) return true; var strategyScope = GetStrategyTraverseGroupScope(); return strategyScope == GroupScope.Local ? scope == eLiamAccessRoleScopes.DomainLocal : scope == eLiamAccessRoleScopes.Global; } private string GetRequiredCustomTag(string key) { if (CustomTags.TryGetValue(key, out var value) && !string.IsNullOrWhiteSpace(value)) return value; if (string.Equals(key, "Filesystem_GroupPrefixTag", StringComparison.OrdinalIgnoreCase) && CustomTags.TryGetValue("ADGroupPrefix", out value) && !string.IsNullOrWhiteSpace(value)) { return value; } throw new InvalidOperationException($"Missing NTFS custom tag '{key}'."); } public int getDepth(string path) { return getDepth(this.RootPath, path); } public static int getDepth(DirectoryInfo root, DirectoryInfo folder) { var rootDepth = root.FullName.TrimEnd(Path.DirectorySeparatorChar).Split(Path.DirectorySeparatorChar).Length; var folderDepth = folder.FullName.TrimEnd(Path.DirectorySeparatorChar).Split(Path.DirectorySeparatorChar).Length; return folderDepth - rootDepth; } public static int getDepth(string root, string folder) { if (string.IsNullOrWhiteSpace(root) || string.IsNullOrWhiteSpace(folder)) return -1; var rootSegments = root.Trim().Replace('/', '\\').Split(new[] { '\\' }, StringSplitOptions.RemoveEmptyEntries); var folderSegments = folder.Trim().Replace('/', '\\').Split(new[] { '\\' }, StringSplitOptions.RemoveEmptyEntries); return folderSegments.Length - rootSegments.Length; } public override string GetLastErrorMessage() { var messages = new List(); if (!string.IsNullOrEmpty(ntfsBase?.LastErrorMessage)) messages.Add(ntfsBase.LastErrorMessage); if (!string.IsNullOrEmpty(activeDirectoryBase?.LastErrorMessage)) messages.Add(activeDirectoryBase.LastErrorMessage); return messages.Count > 0 ? string.Join(" | ", messages) : null; } } public abstract class cLiamNtfsPermissionDataAreaBase : cLiamDataAreaBase { public new readonly cLiamProviderNtfs Provider = null; public string OwnerGroupIdentifier = "S-1-0-0"; public string WriteGroupIdentifier = "S-1-0-0"; public string ReadGroupIdentifier = "S-1-0-0"; public string TraverseGroupIdentifier = "S-1-0-0"; protected cLiamNtfsPermissionDataAreaBase(cLiamProviderNtfs Provider) : base(Provider) { this.Provider = Provider; this.SupportsOwners = true; this.SupportsPermissions = true; } public override async Task> GetOwnersAsync() { var CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); try { return await GetMembersAsync(true); } catch (Exception E) { LogException(E); return null; } finally { LogMethodEnd(CM); } } protected async Task> GetMembersAsync(bool owners) { var CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); try { var AD = this.Provider?.activeDirectoryBase; if (AD == null) { LogEntry($"Could not get ad class from Provider for data area '{this.TechnicalName}'", LogLevels.Warning); return null; } cADCollectionBase lstMembers; this.OwnerGroupIdentifier = this.OwnerRef ?? this.OwnerGroupIdentifier; if (owners && !String.IsNullOrEmpty(this.OwnerGroupIdentifier)) lstMembers = await AD.GetMembersAsync(this.OwnerGroupIdentifier); else lstMembers = null; if (lstMembers == null) { LogEntry($"Could not get owner list for data area '{this.TechnicalName}'", LogLevels.Warning); return null; } var RetVal = new List(lstMembers.Count); LogEntry($"Owners for data area found: {lstMembers.Count}", LogLevels.Debug); foreach (var MemberEntry in lstMembers.Values) { var User = new cLiamUserInfo() { DisplayName = MemberEntry.DisplayName, UserPrincipalName = (MemberEntry as cADUserResult).UserPrincipalName, SID = MemberEntry.ID }; RetVal.Add(User); } return RetVal; } catch (Exception E) { LogException(E); return null; } finally { LogMethodEnd(CM); } } public async Task ResolvePermissionGroupsAsync(string path) { var ACLs = Provider.activeDirectoryBase.GetAccessControlList(path); if (ACLs == null) { Provider.RecordAclReadFailure(path); return; } var ownerNamingConvention = Provider.NamingConventions.First(i => i.AccessRole == eLiamAccessRoles.Owner && (Provider.GroupStrategy == eLiamGroupStrategies.Ntfs_AGP && i.Scope == eLiamAccessRoleScopes.Global || Provider.GroupStrategy == eLiamGroupStrategies.Ntfs_AGDLP && i.Scope == eLiamAccessRoleScopes.DomainLocal)); var writeNamingConvention = Provider.NamingConventions.First(i => i.AccessRole == eLiamAccessRoles.Write && (Provider.GroupStrategy == eLiamGroupStrategies.Ntfs_AGP && i.Scope == eLiamAccessRoleScopes.Global || Provider.GroupStrategy == eLiamGroupStrategies.Ntfs_AGDLP && i.Scope == eLiamAccessRoleScopes.DomainLocal)); var readNamingConvention = Provider.NamingConventions.First(i => i.AccessRole == eLiamAccessRoles.Read && (Provider.GroupStrategy == eLiamGroupStrategies.Ntfs_AGP && i.Scope == eLiamAccessRoleScopes.Global || Provider.GroupStrategy == eLiamGroupStrategies.Ntfs_AGDLP && i.Scope == eLiamAccessRoleScopes.DomainLocal)); var traverseNamingConvention = Provider.NamingConventions.First(i => i.AccessRole == eLiamAccessRoles.Traverse); var resolvedAclGroups = new List(); var matchedOwner = false; var matchedWrite = false; var matchedRead = false; foreach (FileSystemAccessRule rule in ACLs) { var aclSid = rule.IdentityReference.Value; if (aclSid == "S-1-1-0") continue; Provider.RecordAclEntryEvaluated(); GroupPrincipal grp = GroupPrincipal.FindByIdentity(Provider.activeDirectoryBase.adContext, IdentityType.Sid, aclSid); if (grp == null) { Provider.RecordUnresolvedAclSid(path, aclSid); continue; } var samAccountName = grp.SamAccountName ?? string.Empty; if (string.IsNullOrWhiteSpace(samAccountName)) { DefaultLogger.LogEntry(LogLevels.Debug, $"ACL SID '{aclSid}' on '{path}' resolved to '{grp.Name}', but no sAMAccountName is available. Naming convention matching skipped."); continue; } resolvedAclGroups.Add(samAccountName); if (Regex.IsMatch(samAccountName, ownerNamingConvention.Wildcard, RegexOptions.IgnoreCase)) { matchedOwner = true; this.OwnerGroupIdentifier = aclSid; DefaultLogger.LogEntry(LogLevels.Debug, $"ACL SID '{aclSid}' on '{path}' resolved to '{samAccountName}' and matched Owner naming convention '{ownerNamingConvention.Wildcard}'."); if (Provider.GroupStrategy == eLiamGroupStrategies.Ntfs_AGDLP) { var ldapFilter = String.Format("memberOf={0}", grp.DistinguishedName); var res = await Provider.activeDirectoryBase.RequestSecurityGroupsListAsync(ldapFilter); var ownerNamingConventionGlobal = Provider.NamingConventions.First(i => i.AccessRole == eLiamAccessRoles.Owner && i.Scope == eLiamAccessRoleScopes.Global); var matchedGlobalGroup = false; foreach (var memberItem in res ?? new cADCollectionBase()) { var SecurityGroup = new cLiamAdGroup(this.Provider, (cSecurityGroupResult)memberItem.Value); if (Regex.IsMatch(SecurityGroup.TechnicalName, ownerNamingConventionGlobal.Wildcard, RegexOptions.IgnoreCase)) { this.OwnerGroupIdentifier = SecurityGroup.UID; matchedGlobalGroup = true; DefaultLogger.LogEntry(LogLevels.Debug, $"AGDLP Owner ACL group '{samAccountName}' resolved to global group '{SecurityGroup.TechnicalName}' with SID '{SecurityGroup.UID}'."); } } if (!matchedGlobalGroup) DefaultLogger.LogEntry(LogLevels.Debug, $"AGDLP Owner ACL group '{samAccountName}' matched, but no nested global group matched naming convention '{ownerNamingConventionGlobal.Wildcard}'. Keeping ACL SID '{aclSid}'."); } } else if (Regex.IsMatch(samAccountName, writeNamingConvention.Wildcard, RegexOptions.IgnoreCase)) { matchedWrite = true; this.WriteGroupIdentifier = aclSid; DefaultLogger.LogEntry(LogLevels.Debug, $"ACL SID '{aclSid}' on '{path}' resolved to '{samAccountName}' and matched Write naming convention '{writeNamingConvention.Wildcard}'."); if (Provider.GroupStrategy == eLiamGroupStrategies.Ntfs_AGDLP) { var ldapFilter = String.Format("memberOf={0}", grp.DistinguishedName); var res = await Provider.activeDirectoryBase.RequestSecurityGroupsListAsync(ldapFilter); var writeNamingConventionGlobal = Provider.NamingConventions.First(i => i.AccessRole == eLiamAccessRoles.Write && i.Scope == eLiamAccessRoleScopes.Global); var matchedGlobalGroup = false; foreach (var memberItem in res ?? new cADCollectionBase()) { var SecurityGroup = new cLiamAdGroup(this.Provider, (cSecurityGroupResult)memberItem.Value); if (Regex.IsMatch(SecurityGroup.TechnicalName, writeNamingConventionGlobal.Wildcard, RegexOptions.IgnoreCase)) { this.WriteGroupIdentifier = SecurityGroup.UID; matchedGlobalGroup = true; DefaultLogger.LogEntry(LogLevels.Debug, $"AGDLP Write ACL group '{samAccountName}' resolved to global group '{SecurityGroup.TechnicalName}' with SID '{SecurityGroup.UID}'."); } } if (!matchedGlobalGroup) DefaultLogger.LogEntry(LogLevels.Debug, $"AGDLP Write ACL group '{samAccountName}' matched, but no nested global group matched naming convention '{writeNamingConventionGlobal.Wildcard}'. Keeping ACL SID '{aclSid}'."); } } else if (Regex.IsMatch(samAccountName, readNamingConvention.Wildcard, RegexOptions.IgnoreCase)) { matchedRead = true; this.ReadGroupIdentifier = aclSid; DefaultLogger.LogEntry(LogLevels.Debug, $"ACL SID '{aclSid}' on '{path}' resolved to '{samAccountName}' and matched Read naming convention '{readNamingConvention.Wildcard}'."); if (Provider.GroupStrategy == eLiamGroupStrategies.Ntfs_AGDLP) { var ldapFilter = String.Format("memberOf={0}", grp.DistinguishedName); var res = await Provider.activeDirectoryBase.RequestSecurityGroupsListAsync(ldapFilter); var readNamingConventionGlobal = Provider.NamingConventions.First(i => i.AccessRole == eLiamAccessRoles.Read && i.Scope == eLiamAccessRoleScopes.Global); var matchedGlobalGroup = false; foreach (var memberItem in res ?? new cADCollectionBase()) { var SecurityGroup = new cLiamAdGroup(this.Provider, (cSecurityGroupResult)memberItem.Value); if (Regex.IsMatch(SecurityGroup.TechnicalName, readNamingConventionGlobal.Wildcard, RegexOptions.IgnoreCase)) { this.ReadGroupIdentifier = SecurityGroup.UID; matchedGlobalGroup = true; DefaultLogger.LogEntry(LogLevels.Debug, $"AGDLP Read ACL group '{samAccountName}' resolved to global group '{SecurityGroup.TechnicalName}' with SID '{SecurityGroup.UID}'."); } } if (!matchedGlobalGroup) DefaultLogger.LogEntry(LogLevels.Debug, $"AGDLP Read ACL group '{samAccountName}' matched, but no nested global group matched naming convention '{readNamingConventionGlobal.Wildcard}'. Keeping ACL SID '{aclSid}'."); } } else if (Regex.IsMatch(samAccountName, traverseNamingConvention.Wildcard, RegexOptions.IgnoreCase)) { this.TraverseGroupIdentifier = aclSid; DefaultLogger.LogEntry(LogLevels.Debug, $"ACL SID '{aclSid}' on '{path}' resolved to '{samAccountName}' and matched Traverse naming convention '{traverseNamingConvention.Wildcard}'."); } else { Provider.RecordAclGroupWithoutNamingMatch(path, samAccountName); DefaultLogger.LogEntry(LogLevels.Debug, $"ACL SID '{aclSid}' on '{path}' resolved to '{samAccountName}', but did not match Owner/Write/Read/Traverse naming conventions."); } } if (!matchedOwner && !matchedWrite && !matchedRead) Provider.RecordDataAreaWithoutPermissionMapping( path, resolvedAclGroups, ownerNamingConvention.Wildcard, writeNamingConvention.Wildcard, readNamingConvention.Wildcard); } } public class cLiamNtfsShare : cLiamNtfsPermissionDataAreaBase { private readonly cNtfsResultBase Share = null; public cLiamNtfsShare(cLiamProviderNtfs Provider, cNtfsResultBase Share, string parentPath = null) : base(Provider) { this.Share = Share; this.DisplayName = Share.Path.Split('\\').Last(); this.TechnicalName = Share.Path; this.UID = cLiamNtfsFolder.GetUniqueDataAreaID(Share.Path); this.Level = Share.Level; this.DataType = eLiamDataAreaTypes.NtfsShare; if (Directory.Exists(Share.Path)) this.CreatedDate = new DirectoryInfo(Share.Path).CreationTimeUtc.ToString("s"); if (!string.IsNullOrWhiteSpace(parentPath)) this.ParentUID = cLiamNtfsFolder.GetUniqueDataAreaID(parentPath); } internal async Task> getFolders() { var CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); try { await Task.Delay(0); var RetVal = new List(0); return RetVal; } catch (Exception E) { LogException(E); return null; } finally { LogMethodEnd(CM); } } public override async Task> getChildrenAsync(int Depth = -1) { var CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); try { await Task.Delay(0); var RetVal = new List(); return RetVal; } catch (Exception E) { LogException(E); return null; } finally { LogMethodEnd(CM); } } } public class cLiamNtfsDfsNamespaceRoot : cLiamNtfsPermissionDataAreaBase { private readonly cNtfsResultBase NamespaceRoot = null; public cLiamNtfsDfsNamespaceRoot(cLiamProviderNtfs Provider, cNtfsResultBase NamespaceRoot) : base(Provider) { this.NamespaceRoot = NamespaceRoot; this.DisplayName = NamespaceRoot.Path.Split('\\').Last(); this.TechnicalName = NamespaceRoot.Path; this.UID = cLiamNtfsFolder.GetUniqueDataAreaID(NamespaceRoot.Path); this.Level = NamespaceRoot.Level; this.DataType = eLiamDataAreaTypes.DfsNamespaceRoot; if (Directory.Exists(NamespaceRoot.Path)) this.CreatedDate = new DirectoryInfo(NamespaceRoot.Path).CreationTimeUtc.ToString("s"); } public override async Task> getChildrenAsync(int Depth = -1) { await Task.Delay(0); return new List(); } } public class cLiamNtfsServerRoot : cLiamDataAreaBase { public new readonly cLiamProviderNtfs Provider = null; public cLiamNtfsServerRoot(cLiamProviderNtfs Provider, string path, int level) : base(Provider) { this.Provider = Provider; this.DisplayName = path.Split('\\').Last(); this.TechnicalName = path; this.UID = cLiamNtfsFolder.GetUniqueDataAreaID(path); this.Level = level; this.DataType = eLiamDataAreaTypes.NtfsServerRoot; } public override async Task> getChildrenAsync(int Depth = -1) { await Task.Delay(0); return new List(); } } public class cLiamAdGroup : cLiamDataAreaBase { public new readonly cLiamProviderNtfs Provider = null; public readonly string dn = null; public readonly string scope = null; public override Task> getChildrenAsync(int Depth = -1) { throw new NotImplementedException(); } public cLiamAdGroup(cLiamProviderNtfs Provider, cSecurityGroupResult secGroup) : base(Provider) { this.UID = secGroup.ID; this.TechnicalName = secGroup.DisplayName; this.Provider = Provider; this.dn = secGroup.Path; this.scope = secGroup.Scope.ToString(); } } public class cLiamNtfsFolder : cLiamNtfsPermissionDataAreaBase { public readonly cLiamNtfsShare Share = null; public readonly cLiamNtfsFolder NtfsRootFolder = null; public cLiamNtfsFolder(cLiamProviderNtfs Provider, cLiamNtfsShare share, cLiamNtfsFolder ntfsRootFolder, cNtfsResultFolder NtfsFolder, string parentPathOverride = null) : base(Provider) { var ntfsParent = NtfsFolder.Parent; this.NtfsRootFolder = ntfsRootFolder; this.Share = share; this.TechnicalName = NtfsFolder.Path; this.UID =GetUniqueDataAreaID(NtfsFolder.Path); this.DisplayName = new DirectoryInfo(NtfsFolder.Path).Name; this.Level = NtfsFolder.Level; this.DataType = eLiamDataAreaTypes.NtfsFolder; this.CreatedDate = NtfsFolder.CreatedDate; if (!string.IsNullOrWhiteSpace(parentPathOverride)) { this.ParentUID = GetUniqueDataAreaID(parentPathOverride); } else if (ntfsParent != null) { this.ParentUID = GetUniqueDataAreaID(ntfsParent.Path); } else if (this.Level == 1) { this.ParentUID = GetUniqueDataAreaID(this.Provider.RootPath); } } public static string GetUniqueDataAreaID(string fullPath) { LogMethodBegin(MethodBase.GetCurrentMethod()); try { var md5 = new System.Security.Cryptography.MD5CryptoServiceProvider(); var utf8 = new System.Text.UTF8Encoding(); var hash = BitConverter.ToString(md5.ComputeHash(utf8.GetBytes(fullPath))); hash = hash.ToLower().Replace("-", ""); return hash; } catch (Exception E) { cLogManager.DefaultLogger.LogException(E); throw; } finally { LogMethodEnd(MethodBase.GetCurrentMethod()); } } public string GetUniqueDataAreaID() { return GetUniqueDataAreaID(this.TechnicalName); } public override async Task> getChildrenAsync(int Depth = 1) { //TODO implement getChildrenAsync var CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); try { await Task.Delay(0); var DataAreas = new List(); return DataAreas; } catch (Exception E) { LogException(E); return null; } finally { LogMethodEnd(CM); } } } }