Align NTFS ensure traverse handling
This commit is contained in:
@@ -53,6 +53,8 @@ namespace C4IT_IAM_SET
|
||||
public ICollection<string> readerUserSids;
|
||||
public ICollection<string> writerUserSids;
|
||||
public Func<string, bool> CanManagePermissionsForPath;
|
||||
public Func<string, bool> CanManageTraversePermissionsForPath;
|
||||
public string traverseBoundaryPath;
|
||||
public bool forceStrictAdGroupNames;
|
||||
public bool WhatIf;
|
||||
|
||||
@@ -147,6 +149,10 @@ namespace C4IT_IAM_SET
|
||||
DefaultLogger.LogEntry(LogLevels.Info, $"Establishing connection to {baseFolder}, User: {username}, Password: {Helper.MaskAllButLastAndFirst(new NetworkCredential("", password).Password)}");
|
||||
using (Connection = new cNetworkConnection(baseFolder, username, new NetworkCredential("", password).Password))
|
||||
{
|
||||
var traverseBoundaryResult = ValidateTraverseBoundaryForCurrentFolder();
|
||||
if (traverseBoundaryResult.resultErrorId != 0)
|
||||
return traverseBoundaryResult;
|
||||
|
||||
var folderCheckResult = checkFolder();
|
||||
if (folderCheckResult.resultErrorId == 0)
|
||||
{
|
||||
@@ -299,6 +305,39 @@ namespace C4IT_IAM_SET
|
||||
};
|
||||
}
|
||||
|
||||
private ResultToken ValidateTraverseBoundaryForCurrentFolder()
|
||||
{
|
||||
var resultToken = new ResultToken(System.Reflection.MethodBase.GetCurrentMethod().ToString());
|
||||
resultToken.resultErrorId = 0;
|
||||
|
||||
var boundaryPath = GetNormalizedTraverseBoundaryPath();
|
||||
if (string.IsNullOrWhiteSpace(boundaryPath))
|
||||
return resultToken;
|
||||
|
||||
var targetParent = new DirectoryInfo(newFolderPath).Parent;
|
||||
if (targetParent == null)
|
||||
{
|
||||
resultToken.resultErrorId = 30009;
|
||||
resultToken.resultMessage = $"Traverse boundary '{traverseBoundaryPath}' cannot be validated because '{newFolderPath}' has no parent directory.";
|
||||
return resultToken;
|
||||
}
|
||||
|
||||
if (!Directory.Exists(boundaryPath))
|
||||
{
|
||||
resultToken.resultErrorId = 30009;
|
||||
resultToken.resultMessage = $"Traverse boundary '{traverseBoundaryPath}' does not exist or is not reachable.";
|
||||
return resultToken;
|
||||
}
|
||||
|
||||
if (!IsSameOrAncestorPath(boundaryPath, targetParent.FullName))
|
||||
{
|
||||
resultToken.resultErrorId = 30009;
|
||||
resultToken.resultMessage = $"Traverse boundary '{traverseBoundaryPath}' is not a parent path of '{newFolderPath}'.";
|
||||
}
|
||||
|
||||
return resultToken;
|
||||
}
|
||||
|
||||
public ResultToken ensureDataAreaPermissions(bool ensureTraverseGroups = false)
|
||||
{
|
||||
LogMethodBegin(MethodBase.GetCurrentMethod());
|
||||
@@ -327,6 +366,10 @@ namespace C4IT_IAM_SET
|
||||
|
||||
InitializeFolderContext();
|
||||
|
||||
var traverseBoundaryResult = ValidateTraverseBoundaryForCurrentFolder();
|
||||
if (traverseBoundaryResult.resultErrorId != 0)
|
||||
return traverseBoundaryResult;
|
||||
|
||||
ensureADGroups(resultToken);
|
||||
resultToken = ensureFolderPermissions(resultToken);
|
||||
|
||||
@@ -424,6 +467,10 @@ namespace C4IT_IAM_SET
|
||||
|
||||
var lvl = DataArea.GetRelativePath(parent.FullName, baseFolder).Count(n => n == Path.DirectorySeparatorChar);
|
||||
DefaultLogger.LogEntry(LogLevels.Debug, $"Ebene (lvl): {lvl}");
|
||||
var currentTraverseLevel = lvl;
|
||||
var defaultTraverseLoopIndex = lvl;
|
||||
var hasTraverseBoundary = !string.IsNullOrWhiteSpace(GetNormalizedTraverseBoundaryPath());
|
||||
var processedNearestTraverseParent = false;
|
||||
|
||||
// Überprüfen der Templates
|
||||
if (templates == null)
|
||||
@@ -472,9 +519,9 @@ namespace C4IT_IAM_SET
|
||||
return resultToken;
|
||||
}
|
||||
|
||||
for (int i = lvl; i >= createTraverseGroupLvl; i--)
|
||||
while (parent != null && (hasTraverseBoundary || defaultTraverseLoopIndex >= createTraverseGroupLvl))
|
||||
{
|
||||
DefaultLogger.LogEntry(LogLevels.Debug, $"Verarbeite Ebene {i}.");
|
||||
DefaultLogger.LogEntry(LogLevels.Debug, $"Verarbeite Ebene {currentTraverseLevel}.");
|
||||
|
||||
if (parent == null)
|
||||
{
|
||||
@@ -482,14 +529,22 @@ namespace C4IT_IAM_SET
|
||||
break;
|
||||
}
|
||||
|
||||
if (CanManagePermissionsForPath != null && !CanManagePermissionsForPath(parent.FullName))
|
||||
var canManageTraversePath = CanManageTraversePermissionsForPath ?? CanManagePermissionsForPath;
|
||||
if (canManageTraversePath != null && !canManageTraversePath(parent.FullName))
|
||||
{
|
||||
DefaultLogger.LogEntry(LogLevels.Debug, $"Überspringe Traverse-Verarbeitung für nicht verwaltbaren NTFS-Pfad: {parent.FullName}");
|
||||
if (IsTraverseBoundaryPath(parent.FullName))
|
||||
break;
|
||||
|
||||
parent = parent.Parent;
|
||||
if (parent != null)
|
||||
{
|
||||
lvl = DataArea.GetRelativePath(parent.FullName, baseFolder).Count(n => n == Path.DirectorySeparatorChar);
|
||||
DefaultLogger.LogEntry(LogLevels.Debug, $"Neue Ebene (lvl) nach Überspringen: {lvl}");
|
||||
currentTraverseLevel = hasTraverseBoundary
|
||||
? currentTraverseLevel + 1
|
||||
: DataArea.GetRelativePath(parent.FullName, baseFolder).Count(n => n == Path.DirectorySeparatorChar);
|
||||
if (!hasTraverseBoundary)
|
||||
defaultTraverseLoopIndex--;
|
||||
DefaultLogger.LogEntry(LogLevels.Debug, $"Neue Ebene (lvl) nach Überspringen: {currentTraverseLevel}");
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -530,13 +585,14 @@ namespace C4IT_IAM_SET
|
||||
var folderName = sanitizedSegments.Length > 0
|
||||
? sanitizedSegments[sanitizedSegments.Length - 1]
|
||||
: Helper.SanitizePathSegment(Path.GetFileName(parent.FullName.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)));
|
||||
var traverseTags = GetTraverseReplacementTags(parent.FullName);
|
||||
var boundedTraverseContext = Helper.GetBoundedAdGroupTemplateContext(
|
||||
traverseGroupTemplate.NamingTemplate,
|
||||
true,
|
||||
relativePath,
|
||||
sanitizedSegments,
|
||||
folderName,
|
||||
null,
|
||||
traverseTags,
|
||||
Helper.MaxAdGroupNameLength,
|
||||
$"Traverse fuer '{parent.FullName}'");
|
||||
var boundedTraverseDescriptionContext = Helper.GetBoundedAdGroupTemplateContext(
|
||||
@@ -545,7 +601,7 @@ namespace C4IT_IAM_SET
|
||||
relativePath,
|
||||
sanitizedSegments,
|
||||
folderName,
|
||||
null,
|
||||
traverseTags,
|
||||
Helper.MaxAdGroupDescriptionLength,
|
||||
$"Traverse fuer '{parent.FullName}'",
|
||||
"AD-Gruppenbeschreibung");
|
||||
@@ -555,13 +611,13 @@ namespace C4IT_IAM_SET
|
||||
var adjustedTraverseDescriptionSegments = boundedTraverseDescriptionContext.SanitizedSegments ?? Array.Empty<string>();
|
||||
var adjustedTraverseDescriptionRelativePath = adjustedTraverseDescriptionSegments.Length > 0 ? string.Join("_", adjustedTraverseDescriptionSegments) : string.Empty;
|
||||
var adjustedTraverseDescriptionFolderName = boundedTraverseDescriptionContext.FolderName;
|
||||
var traverseNameTemplate = Helper.ApplyTemplatePlaceholders(traverseGroupTemplate.NamingTemplate, true, adjustedTraverseRelativePath, adjustedTraverseSegments, adjustedTraverseFolderName);
|
||||
var traverseDescriptionTemplate = Helper.ApplyTemplatePlaceholders(traverseGroupTemplate.DescriptionTemplate, true, adjustedTraverseDescriptionRelativePath, adjustedTraverseDescriptionSegments, adjustedTraverseDescriptionFolderName);
|
||||
var traverseNameTemplate = Helper.ApplyTemplatePlaceholders(traverseGroupTemplate.NamingTemplate, true, adjustedTraverseRelativePath, adjustedTraverseSegments, adjustedTraverseFolderName).ReplaceTags(traverseTags);
|
||||
var traverseDescriptionTemplate = Helper.ApplyTemplatePlaceholders(traverseGroupTemplate.DescriptionTemplate, true, adjustedTraverseDescriptionRelativePath, adjustedTraverseDescriptionSegments, adjustedTraverseDescriptionFolderName).ReplaceTags(traverseTags);
|
||||
|
||||
string traverseRegex = null;
|
||||
try
|
||||
{
|
||||
traverseRegex = Helper.ApplyTemplatePlaceholders(traverseGroupTemplate.WildcardTemplate, true, adjustedTraverseRelativePath, adjustedTraverseSegments, adjustedTraverseFolderName);
|
||||
traverseRegex = Helper.ApplyTemplatePlaceholders(traverseGroupTemplate.WildcardTemplate, true, adjustedTraverseRelativePath, adjustedTraverseSegments, adjustedTraverseFolderName).ReplaceTags(traverseTags);
|
||||
DefaultLogger.LogEntry(LogLevels.Debug, $"traverseRegex: {traverseRegex}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -570,8 +626,12 @@ namespace C4IT_IAM_SET
|
||||
continue;
|
||||
}
|
||||
|
||||
var hasTraverseWildcard = !string.IsNullOrWhiteSpace(traverseRegex);
|
||||
foreach (FileSystemAccessRule acl in ACLs)
|
||||
{
|
||||
if (!hasTraverseWildcard)
|
||||
break;
|
||||
|
||||
var searchString = acl.IdentityReference.Value;
|
||||
var aclSplit = searchString.Split('\\');
|
||||
if (aclSplit.Length == 2)
|
||||
@@ -600,7 +660,18 @@ namespace C4IT_IAM_SET
|
||||
break;
|
||||
}
|
||||
|
||||
if (parentTraverseGroup == null && !string.IsNullOrEmpty(traverseNameTemplate))
|
||||
if (parentTraverseGroup == null && hasTraverseWildcard && !forceStrictAdGroupNames)
|
||||
{
|
||||
parentTraverseGroup = FindTraverseGroupByWildcard(domainContext, traverseRegex);
|
||||
if (parentTraverseGroup != null)
|
||||
{
|
||||
resultToken.reusedGroups.Add(parentTraverseGroup.Name);
|
||||
resultToken.ensuredTraverseGroups.Add(parentTraverseGroup.Name);
|
||||
DefaultLogger.LogEntry(LogLevels.Debug, $"Vorhandene Traverse-Gruppe per Wildcard wiederverwendet: {parentTraverseGroup.Name}");
|
||||
}
|
||||
}
|
||||
|
||||
if (parentTraverseGroup == null && !string.IsNullOrWhiteSpace(traverseNameTemplate))
|
||||
{
|
||||
for (var loop = 0; loop < 20; loop++)
|
||||
{
|
||||
@@ -616,7 +687,7 @@ namespace C4IT_IAM_SET
|
||||
}
|
||||
}
|
||||
|
||||
if (parentTraverseGroup == null && !traverseGroupTemplate.NamingTemplate.Equals(string.Empty))
|
||||
if (parentTraverseGroup == null && !string.IsNullOrWhiteSpace(traverseNameTemplate))
|
||||
{
|
||||
DefaultLogger.LogEntry(LogLevels.Debug, "Erstelle neue TraverseGroup.");
|
||||
if (newSecurityGroups == null)
|
||||
@@ -750,7 +821,7 @@ namespace C4IT_IAM_SET
|
||||
|
||||
if (parentTraverseGroup != null)
|
||||
{
|
||||
if (i == lvl)
|
||||
if (!processedNearestTraverseParent)
|
||||
{
|
||||
DefaultLogger.LogEntry(LogLevels.Debug, "Verarbeite SecurityGroups bei oberster Ebene.");
|
||||
foreach (var currentSecGroup in newSecurityGroups.IAM_SecurityGroups)
|
||||
@@ -773,6 +844,7 @@ namespace C4IT_IAM_SET
|
||||
continue;
|
||||
}
|
||||
traverseGroup = parentTraverseGroup;
|
||||
processedNearestTraverseParent = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -821,12 +893,19 @@ namespace C4IT_IAM_SET
|
||||
if (parentTraverseGroup != null && !resultToken.ensuredTraverseGroups.Contains(parentTraverseGroup.Name))
|
||||
resultToken.ensuredTraverseGroups.Add(parentTraverseGroup.Name);
|
||||
|
||||
if (IsTraverseBoundaryPath(parent.FullName))
|
||||
break;
|
||||
|
||||
// Aktualisiere parent und lvl für die nächste Iteration
|
||||
parent = parent.Parent;
|
||||
if (parent != null)
|
||||
{
|
||||
lvl = DataArea.GetRelativePath(parent.FullName, baseFolder).Count(n => n == Path.DirectorySeparatorChar);
|
||||
DefaultLogger.LogEntry(LogLevels.Debug, $"Neue Ebene (lvl) nach Aktualisierung: {lvl}");
|
||||
currentTraverseLevel = hasTraverseBoundary
|
||||
? currentTraverseLevel + 1
|
||||
: DataArea.GetRelativePath(parent.FullName, baseFolder).Count(n => n == Path.DirectorySeparatorChar);
|
||||
if (!hasTraverseBoundary)
|
||||
defaultTraverseLoopIndex--;
|
||||
DefaultLogger.LogEntry(LogLevels.Debug, $"Neue Ebene (lvl) nach Aktualisierung: {currentTraverseLevel}");
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -847,6 +926,177 @@ namespace C4IT_IAM_SET
|
||||
}
|
||||
}
|
||||
|
||||
private string GetNormalizedTraverseBoundaryPath()
|
||||
{
|
||||
return NormalizeDirectoryPath(traverseBoundaryPath);
|
||||
}
|
||||
|
||||
private bool IsTraverseBoundaryPath(string path)
|
||||
{
|
||||
var boundaryPath = GetNormalizedTraverseBoundaryPath();
|
||||
return !string.IsNullOrWhiteSpace(boundaryPath)
|
||||
&& PathsEqual(boundaryPath, path);
|
||||
}
|
||||
|
||||
private Dictionary<string, string> GetTraverseReplacementTags(string currentPath)
|
||||
{
|
||||
var visibleSegments = GetVisibleTraversePathSegments(currentPath);
|
||||
return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ "TRAVERSE_NAME", Helper.SanitizePathSegment(GetLastPathSegment(currentPath)) },
|
||||
{ "TRAVERSE_VISIBLEPATH", string.Join("_", visibleSegments.Select(Helper.SanitizePathSegment)) }
|
||||
};
|
||||
}
|
||||
|
||||
private IEnumerable<string> GetVisibleTraversePathSegments(string currentPath)
|
||||
{
|
||||
var normalizedCurrentPath = NormalizeDirectoryPath(currentPath);
|
||||
var boundaryPath = GetNormalizedTraverseBoundaryPath();
|
||||
if (string.IsNullOrWhiteSpace(boundaryPath))
|
||||
boundaryPath = NormalizeDirectoryPath(baseFolder);
|
||||
|
||||
var visibleRoot = GetParentPath(boundaryPath);
|
||||
if (string.IsNullOrWhiteSpace(visibleRoot))
|
||||
visibleRoot = boundaryPath;
|
||||
|
||||
var currentSegments = SplitPathSegments(normalizedCurrentPath);
|
||||
var rootSegments = SplitPathSegments(visibleRoot);
|
||||
if (currentSegments.Length <= rootSegments.Length)
|
||||
return currentSegments;
|
||||
|
||||
var isRootPrefix = rootSegments
|
||||
.Select((segment, index) => new { segment, index })
|
||||
.All(i => string.Equals(i.segment, currentSegments[i.index], StringComparison.OrdinalIgnoreCase));
|
||||
return isRootPrefix
|
||||
? currentSegments.Skip(rootSegments.Length)
|
||||
: currentSegments;
|
||||
}
|
||||
|
||||
private GroupPrincipal FindTraverseGroupByWildcard(PrincipalContext domainContext, string wildcardPattern)
|
||||
{
|
||||
if (domainContext == null || string.IsNullOrWhiteSpace(wildcardPattern))
|
||||
return null;
|
||||
|
||||
Regex wildcardRegex;
|
||||
try
|
||||
{
|
||||
wildcardRegex = new Regex(wildcardPattern, RegexOptions.IgnoreCase);
|
||||
}
|
||||
catch (Exception E)
|
||||
{
|
||||
cLogManager.DefaultLogger.LogException(E);
|
||||
return null;
|
||||
}
|
||||
|
||||
var basePath = "LDAP://" + domainName;
|
||||
if (!string.IsNullOrWhiteSpace(groupOUPath))
|
||||
basePath += "/" + groupOUPath;
|
||||
|
||||
DirectoryEntry entry = new DirectoryEntry
|
||||
{
|
||||
Path = basePath,
|
||||
Username = username,
|
||||
Password = new NetworkCredential("", password).Password,
|
||||
AuthenticationType = AuthenticationTypes.Secure | AuthenticationTypes.Sealing
|
||||
};
|
||||
|
||||
DirectorySearcher search = new DirectorySearcher(entry)
|
||||
{
|
||||
Filter = "(objectClass=group)"
|
||||
};
|
||||
search.PageSize = 100000;
|
||||
search.PropertiesToLoad.Add("sAMAccountName");
|
||||
search.PropertiesToLoad.Add("objectSid");
|
||||
|
||||
string matchedSid = null;
|
||||
string matchedName = null;
|
||||
var matchCount = 0;
|
||||
|
||||
foreach (SearchResult result in search.FindAll())
|
||||
{
|
||||
if (!result.Properties.Contains("sAMAccountName") || result.Properties["sAMAccountName"].Count == 0)
|
||||
continue;
|
||||
|
||||
var samAccountName = result.Properties["sAMAccountName"][0]?.ToString();
|
||||
if (string.IsNullOrWhiteSpace(samAccountName) || !wildcardRegex.IsMatch(samAccountName))
|
||||
continue;
|
||||
|
||||
matchCount++;
|
||||
if (matchCount > 1)
|
||||
{
|
||||
DefaultLogger.LogEntry(LogLevels.Warning, $"Multiple AD groups matched traverse wildcard '{wildcardPattern}' in '{basePath}'. Regex-based reuse is skipped.");
|
||||
search.Dispose();
|
||||
entry.Dispose();
|
||||
return null;
|
||||
}
|
||||
|
||||
matchedName = samAccountName;
|
||||
matchedSid = result.Properties.Contains("objectSid") && result.Properties["objectSid"].Count > 0
|
||||
? new SecurityIdentifier((byte[])result.Properties["objectSid"][0], 0).Value
|
||||
: null;
|
||||
}
|
||||
|
||||
search.Dispose();
|
||||
entry.Dispose();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(matchedSid))
|
||||
return null;
|
||||
|
||||
DefaultLogger.LogEntry(LogLevels.Debug, $"Reusing existing traverse AD group '{matchedName}' via wildcard '{wildcardPattern}'.");
|
||||
return GroupPrincipal.FindByIdentity(domainContext, IdentityType.Sid, matchedSid);
|
||||
}
|
||||
|
||||
private static string NormalizeDirectoryPath(string path)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
return string.Empty;
|
||||
|
||||
var normalized = path.Trim().Replace('/', '\\').TrimEnd('\\');
|
||||
if (normalized.StartsWith(@"\\", StringComparison.Ordinal))
|
||||
return @"\\" + string.Join("\\", SplitPathSegments(normalized));
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
private static bool PathsEqual(string left, string right)
|
||||
{
|
||||
return string.Equals(NormalizeDirectoryPath(left), NormalizeDirectoryPath(right), StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static bool IsSameOrAncestorPath(string ancestorPath, string path)
|
||||
{
|
||||
var ancestor = NormalizeDirectoryPath(ancestorPath);
|
||||
var current = NormalizeDirectoryPath(path);
|
||||
return string.Equals(ancestor, current, StringComparison.OrdinalIgnoreCase)
|
||||
|| current.StartsWith(ancestor + "\\", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static string GetParentPath(string path)
|
||||
{
|
||||
var segments = SplitPathSegments(path);
|
||||
if (segments.Length <= 1)
|
||||
return string.Empty;
|
||||
|
||||
if (NormalizeDirectoryPath(path).StartsWith(@"\\", StringComparison.Ordinal))
|
||||
return @"\\" + string.Join("\\", segments.Take(segments.Length - 1));
|
||||
|
||||
return string.Join("\\", segments.Take(segments.Length - 1));
|
||||
}
|
||||
|
||||
private static string GetLastPathSegment(string path)
|
||||
{
|
||||
var segments = SplitPathSegments(path);
|
||||
return segments.Length == 0 ? string.Empty : segments[segments.Length - 1];
|
||||
}
|
||||
|
||||
private static string[] SplitPathSegments(string path)
|
||||
{
|
||||
return (path ?? string.Empty)
|
||||
.Trim()
|
||||
.Replace('/', '\\')
|
||||
.Split(new[] { '\\' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
}
|
||||
|
||||
private bool TryEnsureGlobalGroupMembershipWithRetry(PrincipalContext domainContext, GroupPrincipal parentTraverseGroup, IAM_SecurityGroup currentSecGroup)
|
||||
{
|
||||
if (domainContext == null || parentTraverseGroup == null || currentSecGroup == null || string.IsNullOrWhiteSpace(currentSecGroup.UID))
|
||||
|
||||
Reference in New Issue
Block a user