Compare commits
27 Commits
f1fb5dbc1b
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
18a96d3e23 | ||
|
|
c1e26ba615 | ||
|
|
c4efc41c4b | ||
|
|
daec5adfb5 | ||
|
|
dd0ebd3d6d | ||
|
|
0b1dbd7925 | ||
|
|
e7fb041ff5 | ||
|
|
5dfccca237 | ||
|
|
f9133b3549 | ||
|
|
10c487572d | ||
|
|
e9bed32334 | ||
|
|
af5f0784b8 | ||
|
|
077bac99ae | ||
|
|
2f1ab27ce9 | ||
|
|
b0fd1d0c75 | ||
|
|
b9cd74c5a9 | ||
|
|
e8b138f9c3 | ||
|
|
3ca1a41816 | ||
|
|
e23397c2ac | ||
|
|
fb9494063b | ||
|
|
fd818f9eb2 | ||
|
|
2217a393be | ||
|
|
6abe0a690b | ||
|
|
fe059de50d | ||
|
|
60518a801f | ||
|
|
b0cfed750c | ||
|
|
275abe8ebd |
@@ -48,6 +48,13 @@ namespace C4IT.LIAM
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class cLiamAclPermissionDefaults
|
||||||
|
{
|
||||||
|
public const int Read = 0x200A9;
|
||||||
|
public const int Write = 0x301BF;
|
||||||
|
public const int Owner = 0x301BF;
|
||||||
|
}
|
||||||
|
|
||||||
public class cLiamProviderData : ICloneable
|
public class cLiamProviderData : ICloneable
|
||||||
{
|
{
|
||||||
|
|
||||||
@@ -80,6 +87,9 @@ namespace C4IT.LIAM
|
|||||||
public string ReadGroupGlobal { get; set; } = "";
|
public string ReadGroupGlobal { get; set; } = "";
|
||||||
public string ReadGroupLocal { get; set; } = "";
|
public string ReadGroupLocal { get; set; } = "";
|
||||||
public string TraverseGroup { get; set; } = "";
|
public string TraverseGroup { get; set; } = "";
|
||||||
|
public int ReadACLPermission { get; set; } = cLiamAclPermissionDefaults.Read;
|
||||||
|
public int WriteACLPermission { get; set; } = cLiamAclPermissionDefaults.Write;
|
||||||
|
public int OwnerACLPermission { get; set; } = cLiamAclPermissionDefaults.Owner;
|
||||||
|
|
||||||
public List<cLiamNamingConvention> NamingConventions { get; set; } = new List<cLiamNamingConvention>();
|
public List<cLiamNamingConvention> NamingConventions { get; set; } = new List<cLiamNamingConvention>();
|
||||||
public Dictionary<string, string> CustomTags { get; set; } = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
public Dictionary<string, string> CustomTags { get; set; } = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
@@ -98,6 +108,9 @@ namespace C4IT.LIAM
|
|||||||
To.GroupFilter = From.GroupFilter;
|
To.GroupFilter = From.GroupFilter;
|
||||||
To.GroupRegEx = From.GroupRegEx;
|
To.GroupRegEx = From.GroupRegEx;
|
||||||
To.GroupPath = From.GroupPath;
|
To.GroupPath = From.GroupPath;
|
||||||
|
To.ReadACLPermission = From.ReadACLPermission;
|
||||||
|
To.WriteACLPermission = From.WriteACLPermission;
|
||||||
|
To.OwnerACLPermission = From.OwnerACLPermission;
|
||||||
To.CustomTags = From.CustomTags;
|
To.CustomTags = From.CustomTags;
|
||||||
To.AdditionalConfiguration = From.AdditionalConfiguration;
|
To.AdditionalConfiguration = From.AdditionalConfiguration;
|
||||||
To.CustomTags = From.CustomTags;
|
To.CustomTags = From.CustomTags;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -17,15 +17,15 @@ using C4IT.Logging;
|
|||||||
|
|
||||||
using static C4IT.Logging.cLogManager;
|
using static C4IT.Logging.cLogManager;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Threading;
|
||||||
using C4IT_IAM_GET;
|
using C4IT_IAM_GET;
|
||||||
|
using C4IT.LIAM;
|
||||||
|
|
||||||
|
|
||||||
namespace C4IT_IAM_SET
|
namespace C4IT_IAM_SET
|
||||||
{
|
{
|
||||||
public class DataArea_FileSystem
|
public class DataArea_FileSystem
|
||||||
{
|
{
|
||||||
public const string constApplicationDataPath = "%ProgramData%\\Consulting4IT GmbH\\LIAM";
|
|
||||||
|
|
||||||
public string domainName;
|
public string domainName;
|
||||||
public string effectiveDomainController;
|
public string effectiveDomainController;
|
||||||
public string username;
|
public string username;
|
||||||
@@ -57,13 +57,16 @@ namespace C4IT_IAM_SET
|
|||||||
public Func<string, bool> CanManageTraversePermissionsForPath;
|
public Func<string, bool> CanManageTraversePermissionsForPath;
|
||||||
public string traverseBoundaryPath;
|
public string traverseBoundaryPath;
|
||||||
public bool forceStrictAdGroupNames;
|
public bool forceStrictAdGroupNames;
|
||||||
|
public SecurityGroupReuseMode adGroupReuseMode = SecurityGroupReuseMode.Safe;
|
||||||
|
public bool adGroupMarkerBackfill;
|
||||||
public string groupNameSanitizeReplacement = Helper.DefaultGroupNameSanitizeReplacement;
|
public string groupNameSanitizeReplacement = Helper.DefaultGroupNameSanitizeReplacement;
|
||||||
public bool preserveAdGroupNameCase;
|
public bool preserveAdGroupNameCase;
|
||||||
public bool WhatIf;
|
public bool WhatIf;
|
||||||
|
public Func<string, Helper.RootPathTemplateContext> GetTemplateContextForPath;
|
||||||
|
|
||||||
public int ReadACLPermission = 0x200A9;
|
public int ReadACLPermission = cLiamAclPermissionDefaults.Read;
|
||||||
public int WriteACLPermission = 0x301BF;
|
public int WriteACLPermission = cLiamAclPermissionDefaults.Write;
|
||||||
public int OwnerACLPermission = 0x1F01FF;
|
public int OwnerACLPermission = cLiamAclPermissionDefaults.Owner;
|
||||||
|
|
||||||
|
|
||||||
public string ConfigID;
|
public string ConfigID;
|
||||||
@@ -74,20 +77,32 @@ namespace C4IT_IAM_SET
|
|||||||
public List<IAM_SecurityGroupTemplate> templates;
|
public List<IAM_SecurityGroupTemplate> templates;
|
||||||
|
|
||||||
public int createTraverseGroupLvl = 0;
|
public int createTraverseGroupLvl = 0;
|
||||||
|
private static int engineFileLoggerInitialized;
|
||||||
|
|
||||||
public DataArea_FileSystem()
|
public DataArea_FileSystem()
|
||||||
{
|
{
|
||||||
var logDirectory = Environment.ExpandEnvironmentVariables(constApplicationDataPath);
|
EnsureEngineFileLoggerInitialized();
|
||||||
Helper.CreatePathWithWriteAccess(logDirectory);
|
|
||||||
var LogPath = Path.Combine(logDirectory, "Logs");
|
|
||||||
cLogManagerFile.CreateInstance(Path.Combine(LogPath, "LIAM.log"));
|
|
||||||
|
|
||||||
DefaultLogger.LogEntry(LogLevels.Info, "=================================================");
|
if (DefaultLogger != null)
|
||||||
DefaultLogger.LogEntry(LogLevels.Info, $"LIAM engine v{Assembly.GetExecutingAssembly().GetName().Version} started");
|
{
|
||||||
|
DefaultLogger.LogEntry(LogLevels.Info, "=================================================");
|
||||||
|
DefaultLogger.LogEntry(LogLevels.Info, $"LIAM engine v{Assembly.GetExecutingAssembly().GetName().Version} started");
|
||||||
|
}
|
||||||
|
|
||||||
templates = new List<IAM_SecurityGroupTemplate>();
|
templates = new List<IAM_SecurityGroupTemplate>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void EnsureEngineFileLoggerInitialized()
|
||||||
|
{
|
||||||
|
if (DefaultLogger != null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (Interlocked.Exchange(ref engineFileLoggerInitialized, 1) != 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
cLogManagerFile.CreateInstance(LocalMachine: true, A: Assembly.GetExecutingAssembly());
|
||||||
|
}
|
||||||
|
|
||||||
private string GetAdServer()
|
private string GetAdServer()
|
||||||
{
|
{
|
||||||
return string.IsNullOrWhiteSpace(effectiveDomainController) ? domainName : effectiveDomainController;
|
return string.IsNullOrWhiteSpace(effectiveDomainController) ? domainName : effectiveDomainController;
|
||||||
@@ -311,10 +326,18 @@ namespace C4IT_IAM_SET
|
|||||||
effectiveDomainController = effectiveDomainController,
|
effectiveDomainController = effectiveDomainController,
|
||||||
password = password,
|
password = password,
|
||||||
ForceStrictAdGroupNames = forceStrictAdGroupNames,
|
ForceStrictAdGroupNames = forceStrictAdGroupNames,
|
||||||
PreserveAdGroupNameCase = preserveAdGroupNameCase
|
PreserveAdGroupNameCase = preserveAdGroupNameCase,
|
||||||
|
ReuseMode = adGroupReuseMode,
|
||||||
|
MarkerBackfill = adGroupMarkerBackfill
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Helper.RootPathTemplateContext GetEffectiveTemplateContext(string path)
|
||||||
|
{
|
||||||
|
return GetTemplateContextForPath?.Invoke(path)
|
||||||
|
?? Helper.GetRootPathTemplateContext(baseFolder, groupNameSanitizeReplacement);
|
||||||
|
}
|
||||||
|
|
||||||
private ResultToken ValidateTraverseBoundaryForCurrentFolder()
|
private ResultToken ValidateTraverseBoundaryForCurrentFolder()
|
||||||
{
|
{
|
||||||
var resultToken = new ResultToken(System.Reflection.MethodBase.GetCurrentMethod().ToString());
|
var resultToken = new ResultToken(System.Reflection.MethodBase.GetCurrentMethod().ToString());
|
||||||
@@ -324,11 +347,11 @@ namespace C4IT_IAM_SET
|
|||||||
if (string.IsNullOrWhiteSpace(boundaryPath))
|
if (string.IsNullOrWhiteSpace(boundaryPath))
|
||||||
return resultToken;
|
return resultToken;
|
||||||
|
|
||||||
var targetParent = new DirectoryInfo(newFolderPath).Parent;
|
var targetPath = NormalizeDirectoryPath(newFolderPath);
|
||||||
if (targetParent == null)
|
if (string.IsNullOrWhiteSpace(targetPath))
|
||||||
{
|
{
|
||||||
resultToken.resultErrorId = 30009;
|
resultToken.resultErrorId = 30009;
|
||||||
resultToken.resultMessage = $"Traverse boundary '{traverseBoundaryPath}' cannot be validated because '{newFolderPath}' has no parent directory.";
|
resultToken.resultMessage = $"Traverse boundary '{traverseBoundaryPath}' cannot be validated because the target path is empty.";
|
||||||
return resultToken;
|
return resultToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -339,16 +362,144 @@ namespace C4IT_IAM_SET
|
|||||||
return resultToken;
|
return resultToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!IsSameOrAncestorPath(boundaryPath, targetParent.FullName))
|
if (!IsSameOrAncestorPath(boundaryPath, targetPath))
|
||||||
{
|
{
|
||||||
resultToken.resultErrorId = 30009;
|
resultToken.resultErrorId = 30009;
|
||||||
resultToken.resultMessage = $"Traverse boundary '{traverseBoundaryPath}' is not a parent path of '{newFolderPath}'.";
|
resultToken.resultMessage = $"Traverse boundary '{traverseBoundaryPath}' is not the target path or a parent path of '{newFolderPath}'.";
|
||||||
}
|
}
|
||||||
|
|
||||||
return resultToken;
|
return resultToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ResultToken ensureDataAreaPermissions(bool ensureTraverseGroups = false)
|
public ResultToken ensureDataAreaPermissions(bool ensureTraverseGroups = false)
|
||||||
|
{
|
||||||
|
LogMethodBegin(MethodBase.GetCurrentMethod());
|
||||||
|
var stopwatch = Stopwatch.StartNew();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
DefaultLogger.LogEntry(
|
||||||
|
LogLevels.Debug,
|
||||||
|
$"NTFS engine ensure permissions start. Stage=Start Path='{newFolderPath}', BaseFolder='{baseFolder}', EnsureTraverseGroups={ensureTraverseGroups}, WhatIf={WhatIf}");
|
||||||
|
|
||||||
|
var resultToken = checkRequiredVariablesForEnsure();
|
||||||
|
if (resultToken.resultErrorId != 0)
|
||||||
|
return resultToken;
|
||||||
|
|
||||||
|
DefaultLogger.LogEntry(
|
||||||
|
LogLevels.Debug,
|
||||||
|
$"NTFS engine ensure permissions stage. Stage=NetworkConnection Path='{newFolderPath}', BaseFolder='{baseFolder}'");
|
||||||
|
|
||||||
|
if (Connection != null)
|
||||||
|
Connection.Dispose();
|
||||||
|
|
||||||
|
using (Connection = new cNetworkConnection(baseFolder, username, new NetworkCredential("", password).Password))
|
||||||
|
{
|
||||||
|
DefaultLogger.LogEntry(
|
||||||
|
LogLevels.Debug,
|
||||||
|
$"NTFS engine ensure permissions stage finished. Stage=NetworkConnection Path='{newFolderPath}', Elapsed='{stopwatch.Elapsed}'");
|
||||||
|
|
||||||
|
DefaultLogger.LogEntry(
|
||||||
|
LogLevels.Debug,
|
||||||
|
$"NTFS engine ensure permissions stage. Stage=DirectoryExists Path='{newFolderPath}'");
|
||||||
|
if (!Directory.Exists(newFolderPath))
|
||||||
|
{
|
||||||
|
resultToken.resultErrorId = 30203;
|
||||||
|
resultToken.resultMessage = "Verzeichnis existiert nicht";
|
||||||
|
return resultToken;
|
||||||
|
}
|
||||||
|
DefaultLogger.LogEntry(
|
||||||
|
LogLevels.Debug,
|
||||||
|
$"NTFS engine ensure permissions stage finished. Stage=DirectoryExists Path='{newFolderPath}', Elapsed='{stopwatch.Elapsed}'");
|
||||||
|
|
||||||
|
DefaultLogger.LogEntry(
|
||||||
|
LogLevels.Debug,
|
||||||
|
$"NTFS engine ensure permissions stage. Stage=InitializeFolderContext Path='{newFolderPath}'");
|
||||||
|
var parentDirectory = Directory.GetParent(newFolderPath);
|
||||||
|
if (string.IsNullOrWhiteSpace(newFolderParent))
|
||||||
|
newFolderParent = parentDirectory?.FullName;
|
||||||
|
|
||||||
|
InitializeFolderContext();
|
||||||
|
DefaultLogger.LogEntry(
|
||||||
|
LogLevels.Debug,
|
||||||
|
$"NTFS engine ensure permissions stage finished. Stage=InitializeFolderContext Path='{newFolderPath}', ParentPath='{newFolderParent}', Elapsed='{stopwatch.Elapsed}'");
|
||||||
|
|
||||||
|
DefaultLogger.LogEntry(
|
||||||
|
LogLevels.Debug,
|
||||||
|
$"NTFS engine ensure permissions stage. Stage=ValidateTraverseBoundary Path='{newFolderPath}'");
|
||||||
|
var traverseBoundaryResult = ValidateTraverseBoundaryForCurrentFolder();
|
||||||
|
if (traverseBoundaryResult.resultErrorId != 0)
|
||||||
|
return traverseBoundaryResult;
|
||||||
|
DefaultLogger.LogEntry(
|
||||||
|
LogLevels.Debug,
|
||||||
|
$"NTFS engine ensure permissions stage finished. Stage=ValidateTraverseBoundary Path='{newFolderPath}', Elapsed='{stopwatch.Elapsed}'");
|
||||||
|
|
||||||
|
DefaultLogger.LogEntry(
|
||||||
|
LogLevels.Debug,
|
||||||
|
$"NTFS engine ensure permissions stage. Stage=EnsureADGroups Path='{newFolderPath}'");
|
||||||
|
ensureADGroups(resultToken);
|
||||||
|
DefaultLogger.LogEntry(
|
||||||
|
LogLevels.Debug,
|
||||||
|
$"NTFS engine ensure permissions stage finished. Stage=EnsureADGroups Path='{newFolderPath}', CreatedGroups={resultToken.createdGroups.Count}, ReusedGroups={resultToken.reusedGroups.Count}, Warnings={resultToken.warnings.Count}, Elapsed='{stopwatch.Elapsed}'");
|
||||||
|
|
||||||
|
DefaultLogger.LogEntry(
|
||||||
|
LogLevels.Debug,
|
||||||
|
$"NTFS engine ensure permissions stage. Stage=EnsureFolderPermissions Path='{newFolderPath}'");
|
||||||
|
resultToken = ensureFolderPermissions(resultToken);
|
||||||
|
DefaultLogger.LogEntry(
|
||||||
|
LogLevels.Debug,
|
||||||
|
$"NTFS engine ensure permissions stage finished. Stage=EnsureFolderPermissions Path='{newFolderPath}', ResultErrorId={resultToken.resultErrorId}, AddedAcls={resultToken.addedAclEntries.Count}, SkippedAcls={resultToken.skippedAclEntries.Count}, Elapsed='{stopwatch.Elapsed}'");
|
||||||
|
|
||||||
|
if (resultToken.resultErrorId != 0)
|
||||||
|
return resultToken;
|
||||||
|
|
||||||
|
if (ensureTraverseGroups)
|
||||||
|
{
|
||||||
|
DefaultLogger.LogEntry(
|
||||||
|
LogLevels.Debug,
|
||||||
|
$"NTFS engine ensure permissions stage. Stage=SetTraversePermissions Path='{newFolderPath}'");
|
||||||
|
var traverseResult = SetTraversePermissions();
|
||||||
|
if (traverseResult != null)
|
||||||
|
{
|
||||||
|
resultToken.createdGroups.AddRange(traverseResult.createdGroups);
|
||||||
|
resultToken.reusedGroups.AddRange(traverseResult.reusedGroups);
|
||||||
|
resultToken.addedAclEntries.AddRange(traverseResult.addedAclEntries);
|
||||||
|
resultToken.skippedAclEntries.AddRange(traverseResult.skippedAclEntries);
|
||||||
|
resultToken.ensuredTraverseGroups.AddRange(traverseResult.ensuredTraverseGroups);
|
||||||
|
resultToken.warnings.AddRange(traverseResult.warnings);
|
||||||
|
if (traverseResult.resultErrorId != 0)
|
||||||
|
{
|
||||||
|
resultToken.resultErrorId = traverseResult.resultErrorId;
|
||||||
|
resultToken.resultMessage = traverseResult.resultMessage;
|
||||||
|
return resultToken;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DefaultLogger.LogEntry(
|
||||||
|
LogLevels.Debug,
|
||||||
|
$"NTFS engine ensure permissions stage finished. Stage=SetTraversePermissions Path='{newFolderPath}', TraverseGroups={resultToken.ensuredTraverseGroups.Count}, AddedAcls={resultToken.addedAclEntries.Count}, Elapsed='{stopwatch.Elapsed}'");
|
||||||
|
}
|
||||||
|
|
||||||
|
resultToken.resultMessage = WhatIf
|
||||||
|
? "Gruppen- und ACL-Vorschau erfolgreich erstellt"
|
||||||
|
: "Gruppen und ACLs erfolgreich sichergestellt";
|
||||||
|
DefaultLogger.LogEntry(
|
||||||
|
LogLevels.Debug,
|
||||||
|
$"NTFS engine ensure permissions finished. Stage=Finished Path='{newFolderPath}', ResultErrorId={resultToken.resultErrorId}, ResultMessage='{resultToken.resultMessage}', Elapsed='{stopwatch.Elapsed}'");
|
||||||
|
return resultToken;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception E)
|
||||||
|
{
|
||||||
|
cLogManager.DefaultLogger.LogException(E);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
LogMethodEnd(MethodBase.GetCurrentMethod());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ResultToken ensureTraversePermissionsOnly()
|
||||||
{
|
{
|
||||||
LogMethodBegin(MethodBase.GetCurrentMethod());
|
LogMethodBegin(MethodBase.GetCurrentMethod());
|
||||||
|
|
||||||
@@ -380,42 +531,15 @@ namespace C4IT_IAM_SET
|
|||||||
if (traverseBoundaryResult.resultErrorId != 0)
|
if (traverseBoundaryResult.resultErrorId != 0)
|
||||||
return traverseBoundaryResult;
|
return traverseBoundaryResult;
|
||||||
|
|
||||||
ensureADGroups(resultToken);
|
EnsureExistingPermissionGroupsForTraverse(resultToken);
|
||||||
resultToken = ensureFolderPermissions(resultToken);
|
resultToken = MergeResultTokens(resultToken, SetTraversePermissions(true));
|
||||||
|
if (resultToken.resultErrorId == 0)
|
||||||
if (resultToken.resultErrorId != 0)
|
|
||||||
return resultToken;
|
|
||||||
|
|
||||||
if (ensureTraverseGroups)
|
|
||||||
{
|
{
|
||||||
if (WhatIf)
|
resultToken.resultMessage = WhatIf
|
||||||
{
|
? "Traverse-Gruppen- und ACL-Vorschau erfolgreich erstellt"
|
||||||
resultToken.warnings.Add("Traverse group preview is not supported in WhatIf mode for automatic DataArea ensure.");
|
: "Traverse-Gruppen und ACLs erfolgreich sichergestellt";
|
||||||
resultToken.resultMessage = "Gruppen- und ACL-Vorschau erfolgreich erstellt";
|
|
||||||
return resultToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
var traverseResult = SetTraversePermissions();
|
|
||||||
if (traverseResult != null)
|
|
||||||
{
|
|
||||||
resultToken.createdGroups.AddRange(traverseResult.createdGroups);
|
|
||||||
resultToken.reusedGroups.AddRange(traverseResult.reusedGroups);
|
|
||||||
resultToken.addedAclEntries.AddRange(traverseResult.addedAclEntries);
|
|
||||||
resultToken.skippedAclEntries.AddRange(traverseResult.skippedAclEntries);
|
|
||||||
resultToken.ensuredTraverseGroups.AddRange(traverseResult.ensuredTraverseGroups);
|
|
||||||
resultToken.warnings.AddRange(traverseResult.warnings);
|
|
||||||
if (traverseResult.resultErrorId != 0)
|
|
||||||
{
|
|
||||||
resultToken.resultErrorId = traverseResult.resultErrorId;
|
|
||||||
resultToken.resultMessage = traverseResult.resultMessage;
|
|
||||||
return resultToken;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
resultToken.resultMessage = WhatIf
|
|
||||||
? "Gruppen- und ACL-Vorschau erfolgreich erstellt"
|
|
||||||
: "Gruppen und ACLs erfolgreich sichergestellt";
|
|
||||||
return resultToken;
|
return resultToken;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -430,7 +554,51 @@ namespace C4IT_IAM_SET
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ResultToken SetTraversePermissions()
|
private void EnsureExistingPermissionGroupsForTraverse(ResultToken resultToken)
|
||||||
|
{
|
||||||
|
newSecurityGroups.IAM_SecurityGroups.Clear();
|
||||||
|
newSecurityGroups.GenerateNewSecurityGroups(baseFolder,
|
||||||
|
newDataArea.IAM_Folders[0].technicalName,
|
||||||
|
groupPrefix,
|
||||||
|
groupOUPath,
|
||||||
|
groupPermissionStrategy,
|
||||||
|
groupTraverseTag,
|
||||||
|
groupReadTag,
|
||||||
|
groupWriteTag,
|
||||||
|
groupOwnerTag,
|
||||||
|
groupDLTag,
|
||||||
|
groupGTag,
|
||||||
|
groupCustomTags,
|
||||||
|
templates,
|
||||||
|
ReadACLPermission,
|
||||||
|
WriteACLPermission,
|
||||||
|
OwnerACLPermission,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
groupNameSanitizeReplacement,
|
||||||
|
preserveAdGroupNameCase,
|
||||||
|
GetEffectiveTemplateContext(newDataArea.IAM_Folders[0].technicalName));
|
||||||
|
|
||||||
|
var existingGroups = new List<IAM_SecurityGroup>();
|
||||||
|
foreach (var securityGroup in newSecurityGroups.IAM_SecurityGroups)
|
||||||
|
{
|
||||||
|
var existingGroup = newSecurityGroups.PreviewADGroup(groupOUPath, securityGroup, newDataArea.IAM_Folders[0].technicalName);
|
||||||
|
if (existingGroup == null || string.IsNullOrWhiteSpace(securityGroup.UID))
|
||||||
|
{
|
||||||
|
if (securityGroup.Scope == GroupScope.Global)
|
||||||
|
resultToken.warnings.Add($"Traverse-only: LIAM-Berechtigungsgruppe '{securityGroup.Name}' fehlt und wird nicht angelegt.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
resultToken.reusedGroups.Add(securityGroup.Name);
|
||||||
|
existingGroups.Add(securityGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
newSecurityGroups.IAM_SecurityGroups.Clear();
|
||||||
|
newSecurityGroups.IAM_SecurityGroups.AddRange(existingGroups);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ResultToken SetTraversePermissions(bool includeCurrentFolder = false)
|
||||||
{
|
{
|
||||||
LogMethodBegin(MethodBase.GetCurrentMethod());
|
LogMethodBegin(MethodBase.GetCurrentMethod());
|
||||||
|
|
||||||
@@ -467,20 +635,21 @@ namespace C4IT_IAM_SET
|
|||||||
DirectoryInfo newDir = new DirectoryInfo(newDataArea.IAM_Folders[0].technicalName);
|
DirectoryInfo newDir = new DirectoryInfo(newDataArea.IAM_Folders[0].technicalName);
|
||||||
DefaultLogger.LogEntry(LogLevels.Debug, $"Neues Verzeichnis: {newDir.FullName}");
|
DefaultLogger.LogEntry(LogLevels.Debug, $"Neues Verzeichnis: {newDir.FullName}");
|
||||||
|
|
||||||
DirectoryInfo parent = newDir.Parent;
|
DirectoryInfo parent = includeCurrentFolder ? newDir : newDir.Parent;
|
||||||
if (parent == null)
|
if (parent == null)
|
||||||
{
|
{
|
||||||
DefaultLogger.LogEntry(LogLevels.Error, "Parent-Verzeichnis ist null.");
|
DefaultLogger.LogEntry(LogLevels.Error, "Traverse-Startverzeichnis ist null.");
|
||||||
return resultToken;
|
return resultToken;
|
||||||
}
|
}
|
||||||
DefaultLogger.LogEntry(LogLevels.Debug, $"Parent-Verzeichnis: {parent.FullName}");
|
DefaultLogger.LogEntry(LogLevels.Debug, $"Traverse-Startverzeichnis: {parent.FullName}");
|
||||||
|
|
||||||
var lvl = DataArea.GetRelativePath(parent.FullName, baseFolder).Count(n => n == Path.DirectorySeparatorChar);
|
var lvl = DataArea.GetRelativePath(parent.FullName, baseFolder).Count(n => n == Path.DirectorySeparatorChar);
|
||||||
DefaultLogger.LogEntry(LogLevels.Debug, $"Ebene (lvl): {lvl}");
|
DefaultLogger.LogEntry(LogLevels.Debug, $"Ebene (lvl): {lvl}");
|
||||||
var currentTraverseLevel = lvl;
|
var currentTraverseLevel = lvl;
|
||||||
var defaultTraverseLoopIndex = lvl;
|
var defaultTraverseLoopIndex = lvl;
|
||||||
var hasTraverseBoundary = !string.IsNullOrWhiteSpace(GetNormalizedTraverseBoundaryPath());
|
var hasTraverseBoundary = !string.IsNullOrWhiteSpace(GetNormalizedTraverseBoundaryPath());
|
||||||
var processedNearestTraverseParent = false;
|
var sourcePath = NormalizeDirectoryPath(newDataArea.IAM_Folders[0].technicalName);
|
||||||
|
var sourcePermissionGroupsAdded = false;
|
||||||
|
|
||||||
// Überprüfen der Templates
|
// Überprüfen der Templates
|
||||||
if (templates == null)
|
if (templates == null)
|
||||||
@@ -519,6 +688,10 @@ namespace C4IT_IAM_SET
|
|||||||
}
|
}
|
||||||
|
|
||||||
GroupPrincipal traverseGroup = null;
|
GroupPrincipal traverseGroup = null;
|
||||||
|
string traverseGroupName = null;
|
||||||
|
var visitedTraverseStates = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
var traverseIterationCount = 0;
|
||||||
|
const int maxTraverseIterations = 256;
|
||||||
|
|
||||||
// Überprüfen, ob createTraverseGroupLvl initialisiert ist
|
// Überprüfen, ob createTraverseGroupLvl initialisiert ist
|
||||||
DefaultLogger.LogEntry(LogLevels.Debug, $"createTraverseGroupLvl: {createTraverseGroupLvl}");
|
DefaultLogger.LogEntry(LogLevels.Debug, $"createTraverseGroupLvl: {createTraverseGroupLvl}");
|
||||||
@@ -539,6 +712,24 @@ namespace C4IT_IAM_SET
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
traverseIterationCount++;
|
||||||
|
if (traverseIterationCount > maxTraverseIterations)
|
||||||
|
{
|
||||||
|
resultToken.resultErrorId = 30200;
|
||||||
|
resultToken.resultMessage = $"Traverse-Verarbeitung fuer '{newDir.FullName}' wurde nach {maxTraverseIterations} Iterationen abgebrochen.";
|
||||||
|
DefaultLogger.LogEntry(LogLevels.Error, resultToken.resultMessage);
|
||||||
|
return resultToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
var traverseState = $"{NormalizeDirectoryPath(parent.FullName)}|{currentTraverseLevel}|{defaultTraverseLoopIndex}";
|
||||||
|
if (!visitedTraverseStates.Add(traverseState))
|
||||||
|
{
|
||||||
|
resultToken.resultErrorId = 30200;
|
||||||
|
resultToken.resultMessage = $"Traverse-Verarbeitung fuer '{newDir.FullName}' wurde abgebrochen, weil der Pfad '{parent.FullName}' auf Ebene {currentTraverseLevel} erneut verarbeitet werden sollte.";
|
||||||
|
DefaultLogger.LogEntry(LogLevels.Error, resultToken.resultMessage);
|
||||||
|
return resultToken;
|
||||||
|
}
|
||||||
|
|
||||||
var canManageTraversePath = CanManageTraversePermissionsForPath ?? CanManagePermissionsForPath;
|
var canManageTraversePath = CanManageTraversePermissionsForPath ?? CanManagePermissionsForPath;
|
||||||
if (canManageTraversePath != null && !canManageTraversePath(parent.FullName))
|
if (canManageTraversePath != null && !canManageTraversePath(parent.FullName))
|
||||||
{
|
{
|
||||||
@@ -563,6 +754,29 @@ namespace C4IT_IAM_SET
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (PathsEqual(parent.FullName, sourcePath) && string.IsNullOrWhiteSpace(traverseGroupName))
|
||||||
|
{
|
||||||
|
DefaultLogger.LogEntry(LogLevels.Debug, $"Skip own Traverse-Gruppe fuer '{parent.FullName}' because no child permission or traverse group needs it.");
|
||||||
|
if (IsTraverseBoundaryPath(parent.FullName))
|
||||||
|
break;
|
||||||
|
|
||||||
|
parent = parent.Parent;
|
||||||
|
if (parent != null)
|
||||||
|
{
|
||||||
|
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 Lazy-Skip: {currentTraverseLevel}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DefaultLogger.LogEntry(LogLevels.Debug, "Parent nach Lazy-Skip ist null.");
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
DefaultLogger.LogEntry(LogLevels.Debug, $"Hole ACL für Ordner: {parent.FullName}");
|
DefaultLogger.LogEntry(LogLevels.Debug, $"Hole ACL für Ordner: {parent.FullName}");
|
||||||
AuthorizationRuleCollection ACLs = null;
|
AuthorizationRuleCollection ACLs = null;
|
||||||
try
|
try
|
||||||
@@ -583,6 +797,7 @@ namespace C4IT_IAM_SET
|
|||||||
}
|
}
|
||||||
|
|
||||||
GroupPrincipal parentTraverseGroup = null;
|
GroupPrincipal parentTraverseGroup = null;
|
||||||
|
string parentTraverseGroupName = null;
|
||||||
var parentTraverseAclExists = false;
|
var parentTraverseAclExists = false;
|
||||||
string relativePathRaw = DataArea.GetRelativePath(parent.FullName, baseFolder).Trim(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
|
string relativePathRaw = DataArea.GetRelativePath(parent.FullName, baseFolder).Trim(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
|
||||||
relativePathRaw = relativePathRaw.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
|
relativePathRaw = relativePathRaw.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
|
||||||
@@ -595,8 +810,8 @@ namespace C4IT_IAM_SET
|
|||||||
var folderName = sanitizedSegments.Length > 0
|
var folderName = sanitizedSegments.Length > 0
|
||||||
? sanitizedSegments[sanitizedSegments.Length - 1]
|
? sanitizedSegments[sanitizedSegments.Length - 1]
|
||||||
: Helper.SanitizePathSegment(Path.GetFileName(parent.FullName.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)), groupNameSanitizeReplacement);
|
: Helper.SanitizePathSegment(Path.GetFileName(parent.FullName.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)), groupNameSanitizeReplacement);
|
||||||
var traverseTags = GetTraverseReplacementTags(parent.FullName);
|
var traverseTags = GetTraverseReplacementTags(parent.FullName, traverseGroupTemplate.Scope);
|
||||||
var rootContext = Helper.GetRootPathTemplateContext(baseFolder, groupNameSanitizeReplacement);
|
var rootContext = GetEffectiveTemplateContext(parent.FullName);
|
||||||
var boundedTraverseContext = Helper.GetBoundedAdGroupTemplateContext(
|
var boundedTraverseContext = Helper.GetBoundedAdGroupTemplateContext(
|
||||||
traverseGroupTemplate.NamingTemplate,
|
traverseGroupTemplate.NamingTemplate,
|
||||||
true,
|
true,
|
||||||
@@ -684,16 +899,8 @@ namespace C4IT_IAM_SET
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parentTraverseGroup == null && hasTraverseWildcard && !forceStrictAdGroupNames)
|
if (parentTraverseGroup != null)
|
||||||
{
|
parentTraverseGroupName = parentTraverseGroup.Name;
|
||||||
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))
|
if (parentTraverseGroup == null && !string.IsNullOrWhiteSpace(traverseNameTemplate))
|
||||||
{
|
{
|
||||||
@@ -704,6 +911,7 @@ namespace C4IT_IAM_SET
|
|||||||
if (parentTraverseGroup == null)
|
if (parentTraverseGroup == null)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
parentTraverseGroupName = parentTraverseGroup.Name;
|
||||||
resultToken.reusedGroups.Add(candidateName);
|
resultToken.reusedGroups.Add(candidateName);
|
||||||
resultToken.ensuredTraverseGroups.Add(candidateName);
|
resultToken.ensuredTraverseGroups.Add(candidateName);
|
||||||
DefaultLogger.LogEntry(LogLevels.Debug, $"Vorhandene Traverse-Gruppe wiederverwendet: {candidateName}");
|
DefaultLogger.LogEntry(LogLevels.Debug, $"Vorhandene Traverse-Gruppe wiederverwendet: {candidateName}");
|
||||||
@@ -711,6 +919,13 @@ namespace C4IT_IAM_SET
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (parentTraverseGroup == null && hasTraverseWildcard && !forceStrictAdGroupNames)
|
||||||
|
{
|
||||||
|
DefaultLogger.LogEntry(
|
||||||
|
LogLevels.Debug,
|
||||||
|
$"Traverse wildcard '{traverseRegex}' is only used for ACL-linked groups on '{parent.FullName}'. Global wildcard reuse is skipped to avoid reusing a traverse group from another path.");
|
||||||
|
}
|
||||||
|
|
||||||
if (parentTraverseGroup == null && !string.IsNullOrWhiteSpace(traverseNameTemplate))
|
if (parentTraverseGroup == null && !string.IsNullOrWhiteSpace(traverseNameTemplate))
|
||||||
{
|
{
|
||||||
DefaultLogger.LogEntry(LogLevels.Debug, "Erstelle neue TraverseGroup.");
|
DefaultLogger.LogEntry(LogLevels.Debug, "Erstelle neue TraverseGroup.");
|
||||||
@@ -731,7 +946,9 @@ namespace C4IT_IAM_SET
|
|||||||
Name = traverseNameTemplate.ReplaceLoopTag(loop),
|
Name = traverseNameTemplate.ReplaceLoopTag(loop),
|
||||||
description = traverseDescriptionTemplate.ReplaceLoopTag(loop),
|
description = traverseDescriptionTemplate.ReplaceLoopTag(loop),
|
||||||
technicalName = "CN=" + traverseNameTemplate.ReplaceLoopTag(loop) + "," + groupOUPath,
|
technicalName = "CN=" + traverseNameTemplate.ReplaceLoopTag(loop) + "," + groupOUPath,
|
||||||
Scope = traverseGroupTemplate.Scope
|
securityGroupType = SecurityGroupType.Traverse,
|
||||||
|
Scope = traverseGroupTemplate.Scope,
|
||||||
|
MarkerPath = parent.FullName
|
||||||
};
|
};
|
||||||
DefaultLogger.LogEntry(LogLevels.Debug, $"Erstellte TraverseGroup: {newTraverseGroup.Name} (Loop: {loop})");
|
DefaultLogger.LogEntry(LogLevels.Debug, $"Erstellte TraverseGroup: {newTraverseGroup.Name} (Loop: {loop})");
|
||||||
loop++;
|
loop++;
|
||||||
@@ -751,60 +968,58 @@ namespace C4IT_IAM_SET
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parent.Parent != null)
|
DefaultLogger.LogEntry(LogLevels.Debug, "Erstelle AD-Gruppe.");
|
||||||
|
if (WhatIf)
|
||||||
{
|
{
|
||||||
DefaultLogger.LogEntry(LogLevels.Debug, "Parent.Parent ist nicht null. Erstelle AD-Gruppe.");
|
parentTraverseGroupName = newTraverseGroup.Name;
|
||||||
if (WhatIf)
|
resultToken.createdGroups.Add(newTraverseGroup.Name);
|
||||||
{
|
resultToken.ensuredTraverseGroups.Add(newTraverseGroup.Name);
|
||||||
resultToken.createdGroups.Add(newTraverseGroup.Name);
|
resultToken.warnings.Add($"Traverse-Gruppe würde angelegt werden: {newTraverseGroup.Name}");
|
||||||
resultToken.ensuredTraverseGroups.Add(newTraverseGroup.Name);
|
resultToken.addedAclEntries.Add(newTraverseGroup.Name);
|
||||||
resultToken.warnings.Add($"Traverse-Gruppe würde angelegt werden: {newTraverseGroup.Name}");
|
parentTraverseAclExists = true;
|
||||||
resultToken.addedAclEntries.Add(newTraverseGroup.Name);
|
|
||||||
parentTraverseAclExists = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
newSecurityGroups.CreateADGroup(groupOUPath, newTraverseGroup, null);
|
|
||||||
DefaultLogger.LogEntry(LogLevels.Debug, $"AD-Gruppe erstellt: {newTraverseGroup.Name}");
|
|
||||||
resultToken.createdGroups.Add(newTraverseGroup.Name);
|
|
||||||
resultToken.ensuredTraverseGroups.Add(newTraverseGroup.Name);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
DefaultLogger.LogEntry(LogLevels.Error, $"Fehler beim Erstellen der AD-Gruppe: {ex.Message}");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
parentTraverseGroup = GroupPrincipal.FindByIdentity(domainContext, newTraverseGroup.Name);
|
|
||||||
if (parentTraverseGroup == null)
|
|
||||||
{
|
|
||||||
DefaultLogger.LogEntry(LogLevels.Error, $"parentTraverseGroup konnte nach Erstellung der Gruppe nicht gefunden werden: {newTraverseGroup.Name}");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var accesscontrol = parent.GetAccessControl();
|
|
||||||
accesscontrol.AddAccessRule(new FileSystemAccessRule(parentTraverseGroup.Sid,
|
|
||||||
FileSystemRights.Read, InheritanceFlags.None, PropagationFlags.None,
|
|
||||||
AccessControlType.Allow));
|
|
||||||
DefaultLogger.LogEntry(LogLevels.Debug, $"Setze Traverse-ACL auf: {parent.FullName} für {parentTraverseGroup.DistinguishedName}");
|
|
||||||
parent.SetAccessControl(accesscontrol);
|
|
||||||
resultToken.addedAclEntries.Add(parentTraverseGroup.Name);
|
|
||||||
parentTraverseAclExists = true;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
DefaultLogger.LogEntry(LogLevels.Error, $"Fehler beim Setzen der ACL: {ex.Message}");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
DefaultLogger.LogEntry(LogLevels.Debug, "Parent.Parent ist null. Traverse-ACL kann nicht gesetzt werden.");
|
try
|
||||||
|
{
|
||||||
|
newSecurityGroups.CreateADGroup(groupOUPath, newTraverseGroup, null);
|
||||||
|
DefaultLogger.LogEntry(LogLevels.Debug, $"AD-Gruppe erstellt: {newTraverseGroup.Name}");
|
||||||
|
resultToken.createdGroups.Add(newTraverseGroup.Name);
|
||||||
|
resultToken.ensuredTraverseGroups.Add(newTraverseGroup.Name);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
DefaultLogger.LogEntry(LogLevels.Error, $"Fehler beim Erstellen der AD-Gruppe: {ex.Message}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
parentTraverseGroup = GroupPrincipal.FindByIdentity(domainContext, newTraverseGroup.Name);
|
||||||
|
if (parentTraverseGroup == null)
|
||||||
|
{
|
||||||
|
DefaultLogger.LogEntry(LogLevels.Error, $"parentTraverseGroup konnte nach Erstellung der Gruppe nicht gefunden werden: {newTraverseGroup.Name}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
parentTraverseGroupName = parentTraverseGroup.Name;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var accesscontrol = parent.GetAccessControl();
|
||||||
|
accesscontrol.AddAccessRule(new FileSystemAccessRule(parentTraverseGroup.Sid,
|
||||||
|
FileSystemRights.Read, InheritanceFlags.None, PropagationFlags.None,
|
||||||
|
AccessControlType.Allow));
|
||||||
|
DefaultLogger.LogEntry(LogLevels.Debug, $"Setze Traverse-ACL auf: {parent.FullName} für {parentTraverseGroup.DistinguishedName}");
|
||||||
|
parent.SetAccessControl(accesscontrol);
|
||||||
|
resultToken.addedAclEntries.Add(parentTraverseGroup.Name);
|
||||||
|
parentTraverseAclExists = true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
DefaultLogger.LogEntry(LogLevels.Error, $"Fehler beim Setzen der ACL: {ex.Message}");
|
||||||
|
resultToken.resultErrorId = 30200;
|
||||||
|
resultToken.resultMessage = $"Fehler beim Setzen der Traverse-ACL auf '{parent.FullName}' fuer '{parentTraverseGroupName ?? newTraverseGroup.Name}': {ex.Message}";
|
||||||
|
return resultToken;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -839,66 +1054,84 @@ namespace C4IT_IAM_SET
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
DefaultLogger.LogEntry(LogLevels.Error, $"Fehler beim Sicherstellen der Traverse-ACL: {ex.Message}");
|
DefaultLogger.LogEntry(LogLevels.Error, $"Fehler beim Sicherstellen der Traverse-ACL: {ex.Message}");
|
||||||
continue;
|
resultToken.resultErrorId = 30200;
|
||||||
|
resultToken.resultMessage = $"Fehler beim Sicherstellen der Traverse-ACL auf '{parent.FullName}' fuer '{parentTraverseGroup.Name}': {ex.Message}";
|
||||||
|
return resultToken;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parentTraverseGroup != null)
|
if (parentTraverseGroup != null || !string.IsNullOrWhiteSpace(parentTraverseGroupName))
|
||||||
{
|
{
|
||||||
if (!processedNearestTraverseParent)
|
var displayTraverseGroupName = parentTraverseGroup?.Name ?? parentTraverseGroupName;
|
||||||
|
var isSourceTraversePath = PathsEqual(parent.FullName, sourcePath);
|
||||||
|
if (!sourcePermissionGroupsAdded)
|
||||||
{
|
{
|
||||||
DefaultLogger.LogEntry(LogLevels.Debug, "Verarbeite SecurityGroups bei oberster Ebene.");
|
if (isSourceTraversePath)
|
||||||
foreach (var currentSecGroup in newSecurityGroups.IAM_SecurityGroups)
|
|
||||||
{
|
{
|
||||||
if (currentSecGroup == null)
|
DefaultLogger.LogEntry(LogLevels.Debug, $"Skip direct permission group membership for own Traverse-Gruppe '{displayTraverseGroupName}' on '{parent.FullName}'.");
|
||||||
{
|
|
||||||
DefaultLogger.LogEntry(LogLevels.Error, "currentSecGroup ist null.");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (currentSecGroup.Scope != GroupScope.Global)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (WhatIf)
|
|
||||||
{
|
|
||||||
resultToken.warnings.Add($"Traverse-Gruppe '{parentTraverseGroup.Name}' würde Mitglied '{currentSecGroup.Name}' erhalten.");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!TryEnsureGlobalGroupMembershipWithRetry(domainContext, parentTraverseGroup, currentSecGroup))
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
traverseGroup = parentTraverseGroup;
|
else
|
||||||
processedNearestTraverseParent = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (traverseGroup != null && parentTraverseGroup != null)
|
|
||||||
{
|
{
|
||||||
try
|
DefaultLogger.LogEntry(LogLevels.Debug, "Verarbeite SecurityGroups in erster uebergeordneter Traverse-Gruppe.");
|
||||||
|
foreach (var currentSecGroup in newSecurityGroups.IAM_SecurityGroups)
|
||||||
{
|
{
|
||||||
if (!parentTraverseGroup.Members.Contains(traverseGroup))
|
if (currentSecGroup == null)
|
||||||
{
|
{
|
||||||
if (WhatIf)
|
DefaultLogger.LogEntry(LogLevels.Error, "currentSecGroup ist null.");
|
||||||
{
|
continue;
|
||||||
resultToken.warnings.Add($"Traverse-Gruppe '{parentTraverseGroup.Name}' würde verschachtelte Gruppe '{traverseGroup.Name}' erhalten.");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (!TryEnsureNestedTraverseGroupMembershipWithRetry(parentTraverseGroup, traverseGroup))
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (currentSecGroup.Scope != GroupScope.Global)
|
||||||
|
continue;
|
||||||
|
if (IsSameGroupName(currentSecGroup.Name, displayTraverseGroupName))
|
||||||
|
{
|
||||||
|
DefaultLogger.LogEntry(LogLevels.Debug, $"Skip self membership for Traverse-Gruppe '{displayTraverseGroupName}'.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (WhatIf)
|
||||||
|
{
|
||||||
|
resultToken.warnings.Add($"Traverse-Gruppe '{displayTraverseGroupName}' würde Mitglied '{currentSecGroup.Name}' erhalten.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!TryEnsureGlobalGroupMembershipWithRetry(domainContext, parentTraverseGroup, currentSecGroup))
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
|
||||||
{
|
sourcePermissionGroupsAdded = true;
|
||||||
DefaultLogger.LogEntry(LogLevels.Error, $"Fehler beim Hinzufügen der Traverse-Gruppe: {ex.Message}");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(traverseGroupName))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (IsSameGroupName(displayTraverseGroupName, traverseGroupName))
|
||||||
|
{
|
||||||
|
DefaultLogger.LogEntry(LogLevels.Debug, $"Skip nested self membership for Traverse-Gruppe '{displayTraverseGroupName}'.");
|
||||||
|
}
|
||||||
|
else if (WhatIf)
|
||||||
|
{
|
||||||
|
resultToken.warnings.Add($"Traverse-Gruppe '{displayTraverseGroupName}' würde verschachtelte Gruppe '{traverseGroupName}' erhalten.");
|
||||||
|
}
|
||||||
|
else if (traverseGroup != null && parentTraverseGroup != null && !parentTraverseGroup.Members.Contains(traverseGroup))
|
||||||
|
{
|
||||||
|
if (!TryEnsureNestedTraverseGroupMembershipWithRetry(parentTraverseGroup, traverseGroup))
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
DefaultLogger.LogEntry(LogLevels.Error, $"Fehler beim Hinzufügen der Traverse-Gruppe: {ex.Message}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
traverseGroup = parentTraverseGroup;
|
||||||
|
traverseGroupName = displayTraverseGroupName;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!WhatIf)
|
if (!WhatIf && parentTraverseGroup != null)
|
||||||
{
|
{
|
||||||
parentTraverseGroup.Save();
|
parentTraverseGroup.Save();
|
||||||
DefaultLogger.LogEntry(LogLevels.Debug, $"parentTraverseGroup gespeichert: {parentTraverseGroup.Name}");
|
DefaultLogger.LogEntry(LogLevels.Debug, $"parentTraverseGroup gespeichert: {parentTraverseGroup.Name}");
|
||||||
@@ -914,8 +1147,8 @@ namespace C4IT_IAM_SET
|
|||||||
DefaultLogger.LogEntry(LogLevels.Debug, "parentTraverseGroup ist null.");
|
DefaultLogger.LogEntry(LogLevels.Debug, "parentTraverseGroup ist null.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parentTraverseGroup != null && !resultToken.ensuredTraverseGroups.Contains(parentTraverseGroup.Name))
|
if (!string.IsNullOrWhiteSpace(parentTraverseGroupName) && !resultToken.ensuredTraverseGroups.Contains(parentTraverseGroupName))
|
||||||
resultToken.ensuredTraverseGroups.Add(parentTraverseGroup.Name);
|
resultToken.ensuredTraverseGroups.Add(parentTraverseGroupName);
|
||||||
|
|
||||||
if (IsTraverseBoundaryPath(parent.FullName))
|
if (IsTraverseBoundaryPath(parent.FullName))
|
||||||
break;
|
break;
|
||||||
@@ -962,14 +1195,45 @@ namespace C4IT_IAM_SET
|
|||||||
&& PathsEqual(boundaryPath, path);
|
&& PathsEqual(boundaryPath, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Dictionary<string, string> GetTraverseReplacementTags(string currentPath)
|
private Dictionary<string, string> GetTraverseReplacementTags(string currentPath, GroupScope traverseScope)
|
||||||
{
|
{
|
||||||
var visibleSegments = GetVisibleTraversePathSegments(currentPath);
|
var visibleSegments = GetVisibleTraversePathSegments(currentPath);
|
||||||
return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
var tags = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
if (groupCustomTags != null)
|
||||||
{
|
{
|
||||||
{ "TRAVERSE_NAME", Helper.SanitizePathSegment(GetLastPathSegment(currentPath), groupNameSanitizeReplacement) },
|
foreach (var customTag in groupCustomTags)
|
||||||
{ "TRAVERSE_VISIBLEPATH", Helper.JoinSanitizedPathSegments(visibleSegments.Select(i => Helper.SanitizePathSegment(i, groupNameSanitizeReplacement)), groupNameSanitizeReplacement) }
|
tags[customTag.Key] = customTag.Value;
|
||||||
};
|
}
|
||||||
|
|
||||||
|
tags["PREFIX"] = groupPrefix;
|
||||||
|
tags["GROUPTYPEPOSTFIX"] = groupTraverseTag;
|
||||||
|
tags["SCOPETAG"] = traverseScope == GroupScope.Local ? groupDLTag : groupGTag;
|
||||||
|
tags["TRAVERSE_NAME"] = Helper.SanitizePathSegment(GetLastPathSegment(currentPath), groupNameSanitizeReplacement);
|
||||||
|
tags["TRAVERSE_VISIBLEPATH"] = Helper.JoinSanitizedPathSegments(visibleSegments.Select(i => Helper.SanitizePathSegment(i, groupNameSanitizeReplacement)), groupNameSanitizeReplacement);
|
||||||
|
return tags;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsSameGroupName(string left, string right)
|
||||||
|
{
|
||||||
|
return !string.IsNullOrWhiteSpace(left)
|
||||||
|
&& !string.IsNullOrWhiteSpace(right)
|
||||||
|
&& string.Equals(left, right, StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsSamePrincipal(GroupPrincipal left, GroupPrincipal right)
|
||||||
|
{
|
||||||
|
if (left == null || right == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var leftSid = left.Sid?.Value;
|
||||||
|
var rightSid = right.Sid?.Value;
|
||||||
|
if (!string.IsNullOrWhiteSpace(leftSid) && !string.IsNullOrWhiteSpace(rightSid))
|
||||||
|
return string.Equals(leftSid, rightSid, StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(left.DistinguishedName) && !string.IsNullOrWhiteSpace(right.DistinguishedName))
|
||||||
|
return string.Equals(left.DistinguishedName, right.DistinguishedName, StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
return IsSameGroupName(left.SamAccountName ?? left.Name, right.SamAccountName ?? right.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerable<string> GetVisibleTraversePathSegments(string currentPath)
|
private IEnumerable<string> GetVisibleTraversePathSegments(string currentPath)
|
||||||
@@ -1152,6 +1416,12 @@ namespace C4IT_IAM_SET
|
|||||||
if (parentTraverseGroup == null || traverseGroup == null)
|
if (parentTraverseGroup == null || traverseGroup == null)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
if (IsSamePrincipal(parentTraverseGroup, traverseGroup))
|
||||||
|
{
|
||||||
|
DefaultLogger.LogEntry(LogLevels.Debug, $"Skip nested self membership for Traverse-Gruppe '{parentTraverseGroup.Name}'.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return RetryTraverseMembershipAction(
|
return RetryTraverseMembershipAction(
|
||||||
traverseGroup.Name,
|
traverseGroup.Name,
|
||||||
true,
|
true,
|
||||||
@@ -1343,7 +1613,8 @@ namespace C4IT_IAM_SET
|
|||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
groupNameSanitizeReplacement,
|
groupNameSanitizeReplacement,
|
||||||
preserveAdGroupNameCase);
|
preserveAdGroupNameCase,
|
||||||
|
GetEffectiveTemplateContext(newDataArea.IAM_Folders[0].technicalName));
|
||||||
|
|
||||||
List<UserPrincipal> owners = getUserPrincipalBySid(ownerUserSids);
|
List<UserPrincipal> owners = getUserPrincipalBySid(ownerUserSids);
|
||||||
List<UserPrincipal> writers = getUserPrincipalBySid(writerUserSids);
|
List<UserPrincipal> writers = getUserPrincipalBySid(writerUserSids);
|
||||||
@@ -1509,7 +1780,8 @@ namespace C4IT_IAM_SET
|
|||||||
existingADGroupCount,
|
existingADGroupCount,
|
||||||
0,
|
0,
|
||||||
groupNameSanitizeReplacement,
|
groupNameSanitizeReplacement,
|
||||||
preserveAdGroupNameCase);
|
preserveAdGroupNameCase,
|
||||||
|
GetEffectiveTemplateContext(newDataArea.IAM_Folders[0].technicalName));
|
||||||
/*
|
/*
|
||||||
if (existingADGroupCount > 0 && !templates.All(t => t.Type == SecurityGroupType.Traverse || Regex.IsMatch(t.NamingTemplate, @"(?<loopTag>{{(?<prefix>[^}]*)(?<loop>LOOP)(?<postfix>[^{]*)}})")))
|
if (existingADGroupCount > 0 && !templates.All(t => t.Type == SecurityGroupType.Traverse || Regex.IsMatch(t.NamingTemplate, @"(?<loopTag>{{(?<prefix>[^}]*)(?<loop>LOOP)(?<postfix>[^{]*)}})")))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ using System.Security.AccessControl;
|
|||||||
using System.Security.Principal;
|
using System.Security.Principal;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
using System.Xml.Serialization;
|
using System.Xml.Serialization;
|
||||||
@@ -32,8 +33,6 @@ namespace C4IT_IAM_GET
|
|||||||
}
|
}
|
||||||
public class DataArea_FileSystem
|
public class DataArea_FileSystem
|
||||||
{
|
{
|
||||||
public const string constApplicationDataPath = "%ProgramData%\\Consulting4IT GmbH\\LIAM";
|
|
||||||
|
|
||||||
public string domainName;
|
public string domainName;
|
||||||
public string DomainIPAddress;
|
public string DomainIPAddress;
|
||||||
public string username;
|
public string username;
|
||||||
@@ -74,6 +73,7 @@ namespace C4IT_IAM_GET
|
|||||||
|
|
||||||
public string SecGroupXML;
|
public string SecGroupXML;
|
||||||
public string DataAreasXML;
|
public string DataAreasXML;
|
||||||
|
private static int engineFileLoggerInitialized;
|
||||||
|
|
||||||
public SecureString Password { get => password; set => password = value; }
|
public SecureString Password { get => password; set => password = value; }
|
||||||
public string StartDir { get => startDir; set => startDir = value; }
|
public string StartDir { get => startDir; set => startDir = value; }
|
||||||
@@ -130,15 +130,26 @@ namespace C4IT_IAM_GET
|
|||||||
|
|
||||||
public DataArea_FileSystem()
|
public DataArea_FileSystem()
|
||||||
{
|
{
|
||||||
var logDirectory = Environment.ExpandEnvironmentVariables(constApplicationDataPath);
|
EnsureEngineFileLoggerInitialized();
|
||||||
Helper.CreatePathWithWriteAccess(logDirectory);
|
if (DefaultLogger != null)
|
||||||
var LogPath = Path.Combine(logDirectory, "Logs");
|
{
|
||||||
cLogManagerFile.CreateInstance(Path.Combine(LogPath, "LIAM.log"));
|
DefaultLogger.LogEntry(LogLevels.Info, "=================================================");
|
||||||
DefaultLogger.LogEntry(LogLevels.Info, "=================================================");
|
DefaultLogger.LogEntry(LogLevels.Info, $"LIAM engine v{Assembly.GetExecutingAssembly().GetName().Version} started");
|
||||||
DefaultLogger.LogEntry(LogLevels.Info, $"LIAM engine v{Assembly.GetExecutingAssembly().GetName().Version} started");
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void EnsureEngineFileLoggerInitialized()
|
||||||
|
{
|
||||||
|
if (DefaultLogger != null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (Interlocked.Exchange(ref engineFileLoggerInitialized, 1) != 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
cLogManagerFile.CreateInstance(LocalMachine: true, A: Assembly.GetExecutingAssembly());
|
||||||
|
}
|
||||||
|
|
||||||
private void FillMatchingGroups()
|
private void FillMatchingGroups()
|
||||||
{
|
{
|
||||||
LogMethodBegin(MethodBase.GetCurrentMethod());
|
LogMethodBegin(MethodBase.GetCurrentMethod());
|
||||||
|
|||||||
@@ -33,6 +33,9 @@ namespace C4IT_IAM_Engine
|
|||||||
public sealed class RootPathTemplateContext
|
public sealed class RootPathTemplateContext
|
||||||
{
|
{
|
||||||
public string Server { get; set; } = string.Empty;
|
public string Server { get; set; } = string.Empty;
|
||||||
|
public string ServerName { get; set; } = string.Empty;
|
||||||
|
public string DfsNamespaceName { get; set; } = string.Empty;
|
||||||
|
public string ShareName { get; set; } = string.Empty;
|
||||||
public string[] Segments { get; set; } = Array.Empty<string>();
|
public string[] Segments { get; set; } = Array.Empty<string>();
|
||||||
public string Name { get; set; } = string.Empty;
|
public string Name { get; set; } = string.Empty;
|
||||||
public string Path { get; set; } = string.Empty;
|
public string Path { get; set; } = string.Empty;
|
||||||
@@ -109,6 +112,8 @@ namespace C4IT_IAM_Engine
|
|||||||
return new RootPathTemplateContext
|
return new RootPathTemplateContext
|
||||||
{
|
{
|
||||||
Server = server,
|
Server = server,
|
||||||
|
ServerName = server,
|
||||||
|
ShareName = sanitizedPathSegments.Length == 0 ? string.Empty : sanitizedPathSegments[sanitizedPathSegments.Length - 1],
|
||||||
Segments = sanitizedPathSegments,
|
Segments = sanitizedPathSegments,
|
||||||
Name = sanitizedPathSegments.Length == 0 ? string.Empty : sanitizedPathSegments[sanitizedPathSegments.Length - 1],
|
Name = sanitizedPathSegments.Length == 0 ? string.Empty : sanitizedPathSegments[sanitizedPathSegments.Length - 1],
|
||||||
Path = sanitizedPathSegments.Length == 0 ? string.Empty : JoinSanitizedPathSegments(sanitizedPathSegments, groupNameSanitizeReplacement),
|
Path = sanitizedPathSegments.Length == 0 ? string.Empty : JoinSanitizedPathSegments(sanitizedPathSegments, groupNameSanitizeReplacement),
|
||||||
@@ -308,6 +313,9 @@ namespace C4IT_IAM_Engine
|
|||||||
|
|
||||||
var context = rootContext ?? new RootPathTemplateContext();
|
var context = rootContext ?? new RootPathTemplateContext();
|
||||||
var result = Regex.Replace(templateValue, @"{{\s*ROOT_SERVER\s*}}", context.Server ?? string.Empty, RegexOptions.IgnoreCase);
|
var result = Regex.Replace(templateValue, @"{{\s*ROOT_SERVER\s*}}", context.Server ?? string.Empty, RegexOptions.IgnoreCase);
|
||||||
|
result = Regex.Replace(result, @"{{\s*SERVER_NAME\s*}}", context.ServerName ?? context.Server ?? string.Empty, RegexOptions.IgnoreCase);
|
||||||
|
result = Regex.Replace(result, @"{{\s*DFS_NAMESPACE_NAME\s*}}", context.DfsNamespaceName ?? string.Empty, RegexOptions.IgnoreCase);
|
||||||
|
result = Regex.Replace(result, @"{{\s*SHARE_NAME\s*}}", context.ShareName ?? string.Empty, RegexOptions.IgnoreCase);
|
||||||
result = Regex.Replace(result, @"{{\s*ROOT_NAME\s*}}", context.Name ?? string.Empty, RegexOptions.IgnoreCase);
|
result = Regex.Replace(result, @"{{\s*ROOT_NAME\s*}}", context.Name ?? string.Empty, RegexOptions.IgnoreCase);
|
||||||
result = Regex.Replace(result, @"{{\s*ROOT_PATH(?:\s*\(\s*(\d+)\s*\))?\s*}}", match =>
|
result = Regex.Replace(result, @"{{\s*ROOT_PATH(?:\s*\(\s*(\d+)\s*\))?\s*}}", match =>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -26,9 +26,13 @@ namespace C4IT_IAM_Engine
|
|||||||
public SecureString password;
|
public SecureString password;
|
||||||
public bool ForceStrictAdGroupNames;
|
public bool ForceStrictAdGroupNames;
|
||||||
public bool PreserveAdGroupNameCase;
|
public bool PreserveAdGroupNameCase;
|
||||||
|
public SecurityGroupReuseMode ReuseMode = SecurityGroupReuseMode.Safe;
|
||||||
|
public bool MarkerBackfill;
|
||||||
|
|
||||||
public List<IAM_SecurityGroup> IAM_SecurityGroups;
|
public List<IAM_SecurityGroup> IAM_SecurityGroups;
|
||||||
public string rootUID;
|
public string rootUID;
|
||||||
|
private const string MarkerAttributeName = "info";
|
||||||
|
private const string MarkerPrefix = "LIAM;Provider=Ntfs;";
|
||||||
public SecurityGroups()
|
public SecurityGroups()
|
||||||
{
|
{
|
||||||
IAM_SecurityGroups = new List<IAM_SecurityGroup>();
|
IAM_SecurityGroups = new List<IAM_SecurityGroup>();
|
||||||
@@ -138,7 +142,8 @@ namespace C4IT_IAM_Engine
|
|||||||
int loop = 0,
|
int loop = 0,
|
||||||
int existingADGroupCount = 0,
|
int existingADGroupCount = 0,
|
||||||
string groupNameSanitizeReplacement = Helper.DefaultGroupNameSanitizeReplacement,
|
string groupNameSanitizeReplacement = Helper.DefaultGroupNameSanitizeReplacement,
|
||||||
bool preserveAdGroupNameCase = false)
|
bool preserveAdGroupNameCase = false,
|
||||||
|
Helper.RootPathTemplateContext templateContext = null)
|
||||||
{
|
{
|
||||||
LogMethodBegin(MethodBase.GetCurrentMethod());
|
LogMethodBegin(MethodBase.GetCurrentMethod());
|
||||||
try
|
try
|
||||||
@@ -159,7 +164,7 @@ namespace C4IT_IAM_Engine
|
|||||||
var folderName = sanitizedSegments.Length > 0
|
var folderName = sanitizedSegments.Length > 0
|
||||||
? sanitizedSegments[sanitizedSegments.Length - 1]
|
? sanitizedSegments[sanitizedSegments.Length - 1]
|
||||||
: Helper.SanitizePathSegment(Path.GetFileName(newFolderPath.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)), groupNameSanitizeReplacement);
|
: Helper.SanitizePathSegment(Path.GetFileName(newFolderPath.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)), groupNameSanitizeReplacement);
|
||||||
var rootContext = Helper.GetRootPathTemplateContext(baseFolder, groupNameSanitizeReplacement);
|
var rootContext = templateContext ?? Helper.GetRootPathTemplateContext(baseFolder, groupNameSanitizeReplacement);
|
||||||
|
|
||||||
foreach (var template in resolvedTemplates)
|
foreach (var template in resolvedTemplates)
|
||||||
{
|
{
|
||||||
@@ -269,9 +274,11 @@ namespace C4IT_IAM_Engine
|
|||||||
WildcardPattern = ownerGlobal.WildcardTemplate,
|
WildcardPattern = ownerGlobal.WildcardTemplate,
|
||||||
|
|
||||||
technicalName = "CN=" + ownerGlobal.NamingTemplate + "," + ouPath,
|
technicalName = "CN=" + ownerGlobal.NamingTemplate + "," + ouPath,
|
||||||
|
securityGroupType = SecurityGroupType.Owner,
|
||||||
targetTyp = (int)IAM_TargetType.FileSystem,
|
targetTyp = (int)IAM_TargetType.FileSystem,
|
||||||
rights = (FileSystemRights)ownerACLPermission,
|
rights = (FileSystemRights)ownerACLPermission,
|
||||||
Scope = GroupScope.Global
|
Scope = GroupScope.Global,
|
||||||
|
MarkerPath = newFolderPath
|
||||||
};
|
};
|
||||||
IAM_SecurityGroups.Add(osecGroup);
|
IAM_SecurityGroups.Add(osecGroup);
|
||||||
|
|
||||||
@@ -283,9 +290,11 @@ namespace C4IT_IAM_Engine
|
|||||||
WildcardPattern = writeGlobal.WildcardTemplate,
|
WildcardPattern = writeGlobal.WildcardTemplate,
|
||||||
|
|
||||||
technicalName = "CN=" + writeGlobal.NamingTemplate + "," + ouPath,
|
technicalName = "CN=" + writeGlobal.NamingTemplate + "," + ouPath,
|
||||||
|
securityGroupType = SecurityGroupType.Write,
|
||||||
targetTyp = (int)IAM_TargetType.FileSystem,
|
targetTyp = (int)IAM_TargetType.FileSystem,
|
||||||
rights = (FileSystemRights)writeACLPermission,
|
rights = (FileSystemRights)writeACLPermission,
|
||||||
Scope = GroupScope.Global
|
Scope = GroupScope.Global,
|
||||||
|
MarkerPath = newFolderPath
|
||||||
};
|
};
|
||||||
IAM_SecurityGroups.Add(wsecGroup);
|
IAM_SecurityGroups.Add(wsecGroup);
|
||||||
|
|
||||||
@@ -297,9 +306,11 @@ namespace C4IT_IAM_Engine
|
|||||||
WildcardPattern = readGlobal.WildcardTemplate,
|
WildcardPattern = readGlobal.WildcardTemplate,
|
||||||
|
|
||||||
technicalName = "CN=" + readGlobal.NamingTemplate + "," + ouPath,
|
technicalName = "CN=" + readGlobal.NamingTemplate + "," + ouPath,
|
||||||
|
securityGroupType = SecurityGroupType.Read,
|
||||||
targetTyp = (int)IAM_TargetType.FileSystem,
|
targetTyp = (int)IAM_TargetType.FileSystem,
|
||||||
rights = (FileSystemRights)readACLPermission,
|
rights = (FileSystemRights)readACLPermission,
|
||||||
Scope = GroupScope.Global
|
Scope = GroupScope.Global,
|
||||||
|
MarkerPath = newFolderPath
|
||||||
};
|
};
|
||||||
IAM_SecurityGroups.Add(rsecGroup);
|
IAM_SecurityGroups.Add(rsecGroup);
|
||||||
|
|
||||||
@@ -315,9 +326,11 @@ namespace C4IT_IAM_Engine
|
|||||||
WildcardPattern = ownerDL.WildcardTemplate,
|
WildcardPattern = ownerDL.WildcardTemplate,
|
||||||
|
|
||||||
technicalName = "CN=" + ownerDL.NamingTemplate + "," + ouPath,
|
technicalName = "CN=" + ownerDL.NamingTemplate + "," + ouPath,
|
||||||
|
securityGroupType = SecurityGroupType.Owner,
|
||||||
targetTyp = (int)IAM_TargetType.FileSystem,
|
targetTyp = (int)IAM_TargetType.FileSystem,
|
||||||
rights = (FileSystemRights)ownerACLPermission,
|
rights = (FileSystemRights)ownerACLPermission,
|
||||||
Scope = GroupScope.Local
|
Scope = GroupScope.Local,
|
||||||
|
MarkerPath = newFolderPath
|
||||||
};
|
};
|
||||||
osecDLGroup.memberGroups.Add(osecGroup);
|
osecDLGroup.memberGroups.Add(osecGroup);
|
||||||
IAM_SecurityGroups.Add(osecDLGroup);
|
IAM_SecurityGroups.Add(osecDLGroup);
|
||||||
@@ -330,9 +343,11 @@ namespace C4IT_IAM_Engine
|
|||||||
WildcardPattern = writeDL.WildcardTemplate,
|
WildcardPattern = writeDL.WildcardTemplate,
|
||||||
|
|
||||||
technicalName = "CN=" + writeDL.NamingTemplate + "," + ouPath,
|
technicalName = "CN=" + writeDL.NamingTemplate + "," + ouPath,
|
||||||
|
securityGroupType = SecurityGroupType.Write,
|
||||||
targetTyp = (int)IAM_TargetType.FileSystem,
|
targetTyp = (int)IAM_TargetType.FileSystem,
|
||||||
rights = (FileSystemRights)writeACLPermission,
|
rights = (FileSystemRights)writeACLPermission,
|
||||||
Scope = GroupScope.Local
|
Scope = GroupScope.Local,
|
||||||
|
MarkerPath = newFolderPath
|
||||||
};
|
};
|
||||||
wsecDLGroup.memberGroups.Add(wsecGroup);
|
wsecDLGroup.memberGroups.Add(wsecGroup);
|
||||||
IAM_SecurityGroups.Add(wsecDLGroup);
|
IAM_SecurityGroups.Add(wsecDLGroup);
|
||||||
@@ -345,9 +360,11 @@ namespace C4IT_IAM_Engine
|
|||||||
WildcardPattern = readDL.WildcardTemplate,
|
WildcardPattern = readDL.WildcardTemplate,
|
||||||
|
|
||||||
technicalName = "CN=" + readDL.NamingTemplate + "," + ouPath,
|
technicalName = "CN=" + readDL.NamingTemplate + "," + ouPath,
|
||||||
|
securityGroupType = SecurityGroupType.Read,
|
||||||
targetTyp = (int)IAM_TargetType.FileSystem,
|
targetTyp = (int)IAM_TargetType.FileSystem,
|
||||||
rights = (FileSystemRights)readACLPermission,
|
rights = (FileSystemRights)readACLPermission,
|
||||||
Scope = GroupScope.Local
|
Scope = GroupScope.Local,
|
||||||
|
MarkerPath = newFolderPath
|
||||||
};
|
};
|
||||||
rsecDLGroup.memberGroups.Add(rsecGroup);
|
rsecDLGroup.memberGroups.Add(rsecGroup);
|
||||||
IAM_SecurityGroups.Add(rsecDLGroup);
|
IAM_SecurityGroups.Add(rsecDLGroup);
|
||||||
@@ -661,6 +678,168 @@ namespace C4IT_IAM_Engine
|
|||||||
group.CommitChanges();
|
group.CommitChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string NormalizeMarkerPath(string path)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(path))
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
return path.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar).TrimEnd(Path.DirectorySeparatorChar);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetMarkerScope(GroupScope scope)
|
||||||
|
{
|
||||||
|
return scope == GroupScope.Local ? "DomainLocal" : "Global";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string BuildMarker(IAM_SecurityGroup secGroup)
|
||||||
|
{
|
||||||
|
return $"{MarkerPrefix}Path={NormalizeMarkerPath(secGroup.MarkerPath)};Role={secGroup.securityGroupType};Scope={GetMarkerScope(secGroup.Scope)}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Dictionary<string, string> ParseMarker(string marker)
|
||||||
|
{
|
||||||
|
var result = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
if (string.IsNullOrWhiteSpace(marker) || !marker.StartsWith(MarkerPrefix, StringComparison.OrdinalIgnoreCase))
|
||||||
|
return result;
|
||||||
|
|
||||||
|
foreach (var part in marker.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries))
|
||||||
|
{
|
||||||
|
var separatorIndex = part.IndexOf('=');
|
||||||
|
if (separatorIndex <= 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
result[part.Substring(0, separatorIndex).Trim()] = part.Substring(separatorIndex + 1).Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<string> GetLiamMarkers(string info)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(info))
|
||||||
|
return Enumerable.Empty<string>();
|
||||||
|
|
||||||
|
return info
|
||||||
|
.Split(new[] { "\r\n", "\n" }, StringSplitOptions.RemoveEmptyEntries)
|
||||||
|
.Select(i => i.Trim())
|
||||||
|
.Where(i => i.StartsWith(MarkerPrefix, StringComparison.OrdinalIgnoreCase));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsMatchingMarker(string marker, IAM_SecurityGroup secGroup)
|
||||||
|
{
|
||||||
|
var values = ParseMarker(marker);
|
||||||
|
if (values.Count == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return values.TryGetValue("Provider", out var provider)
|
||||||
|
&& string.Equals(provider, "Ntfs", StringComparison.OrdinalIgnoreCase)
|
||||||
|
&& values.TryGetValue("Path", out var markerPath)
|
||||||
|
&& string.Equals(NormalizeMarkerPath(markerPath), NormalizeMarkerPath(secGroup.MarkerPath), StringComparison.OrdinalIgnoreCase)
|
||||||
|
&& values.TryGetValue("Role", out var role)
|
||||||
|
&& string.Equals(role, secGroup.securityGroupType.ToString(), StringComparison.OrdinalIgnoreCase)
|
||||||
|
&& values.TryGetValue("Scope", out var scope)
|
||||||
|
&& string.Equals(scope, GetMarkerScope(secGroup.Scope), StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryReadInfoAttribute(DirectoryEntry group, out string info)
|
||||||
|
{
|
||||||
|
info = string.Empty;
|
||||||
|
if (group == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
group.RefreshCache(new[] { MarkerAttributeName });
|
||||||
|
if (group.Properties.Contains(MarkerAttributeName) && group.Properties[MarkerAttributeName].Count > 0)
|
||||||
|
info = group.Properties[MarkerAttributeName].Value?.ToString() ?? string.Empty;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception E)
|
||||||
|
{
|
||||||
|
DefaultLogger.LogEntry(LogLevels.Warning, $"AD group marker attribute '{MarkerAttributeName}' cannot be read for '{group.Path}'. Marker-based reuse is not available. {E.Message}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool HasMatchingMarker(DirectoryEntry group, IAM_SecurityGroup secGroup)
|
||||||
|
{
|
||||||
|
if (!TryReadInfoAttribute(group, out var info))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return GetLiamMarkers(info).Any(marker => IsMatchingMarker(marker, secGroup));
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryWriteMarker(DirectoryEntry group, IAM_SecurityGroup secGroup, bool onlyIfNoLiamMarker)
|
||||||
|
{
|
||||||
|
if (group == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!TryReadInfoAttribute(group, out var info))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var existingMarkers = GetLiamMarkers(info).ToList();
|
||||||
|
if (existingMarkers.Any(marker => IsMatchingMarker(marker, secGroup)))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (onlyIfNoLiamMarker && existingMarkers.Count > 0)
|
||||||
|
{
|
||||||
|
DefaultLogger.LogEntry(LogLevels.Warning, $"AD group '{secGroup.Name}' already has a LIAM marker, but it does not match path '{secGroup.MarkerPath}' and role '{secGroup.securityGroupType}'. Marker backfill is skipped.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var marker = BuildMarker(secGroup);
|
||||||
|
var newInfo = string.IsNullOrWhiteSpace(info)
|
||||||
|
? marker
|
||||||
|
: info.TrimEnd('\r', '\n') + Environment.NewLine + marker;
|
||||||
|
group.Properties[MarkerAttributeName].Value = newInfo;
|
||||||
|
group.CommitChanges();
|
||||||
|
DefaultLogger.LogEntry(LogLevels.Debug, $"AD group marker written to '{secGroup.Name}': {marker}");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception E)
|
||||||
|
{
|
||||||
|
DefaultLogger.LogEntry(LogLevels.Warning, $"AD group marker attribute '{MarkerAttributeName}' cannot be written for '{secGroup.Name}'. {E.Message}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private DirectoryEntry ValidateExistingGroupReuse(DirectoryEntry existingGroup, IAM_SecurityGroup secGroup, string folderPath, bool linkedFromFolderAcl)
|
||||||
|
{
|
||||||
|
if (existingGroup == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (ReuseMode == SecurityGroupReuseMode.Name)
|
||||||
|
{
|
||||||
|
if (!linkedFromFolderAcl && !HasMatchingMarker(existingGroup, secGroup))
|
||||||
|
DefaultLogger.LogEntry(LogLevels.Warning, $"AD group '{secGroup.Name}' is reused by name because NtfsAdGroupReuseMode=Name is active. This bypasses ACL/marker validation for '{secGroup.MarkerPath}'.");
|
||||||
|
|
||||||
|
ApplyExistingGroup(secGroup, existingGroup);
|
||||||
|
return existingGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (linkedFromFolderAcl)
|
||||||
|
{
|
||||||
|
if (MarkerBackfill)
|
||||||
|
TryWriteMarker(existingGroup, secGroup, true);
|
||||||
|
|
||||||
|
ApplyExistingGroup(secGroup, existingGroup);
|
||||||
|
return existingGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (HasMatchingMarker(existingGroup, secGroup))
|
||||||
|
{
|
||||||
|
ApplyExistingGroup(secGroup, existingGroup);
|
||||||
|
return existingGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
var message = $"Existing AD group '{secGroup.Name}' was found by name for '{folderPath ?? secGroup.MarkerPath}', but it is not linked on the folder ACL and has no matching LIAM marker. Reuse is blocked in NtfsAdGroupReuseMode=Safe. Set NtfsAdGroupReuseMode=Name to allow legacy name-based reuse.";
|
||||||
|
DefaultLogger.LogEntry(LogLevels.Warning, message);
|
||||||
|
throw new InvalidOperationException(message);
|
||||||
|
}
|
||||||
|
|
||||||
public DirectoryEntry EnsureADGroup(string ouPath, IAM_SecurityGroup secGroup, List<UserPrincipal> users, string folderPath = null)
|
public DirectoryEntry EnsureADGroup(string ouPath, IAM_SecurityGroup secGroup, List<UserPrincipal> users, string folderPath = null)
|
||||||
{
|
{
|
||||||
LogMethodBegin(MethodBase.GetCurrentMethod());
|
LogMethodBegin(MethodBase.GetCurrentMethod());
|
||||||
@@ -668,8 +847,12 @@ namespace C4IT_IAM_Engine
|
|||||||
{
|
{
|
||||||
secGroup.CreatedNewEntry = false;
|
secGroup.CreatedNewEntry = false;
|
||||||
DirectoryEntry existingGroup = null;
|
DirectoryEntry existingGroup = null;
|
||||||
|
var linkedFromFolderAcl = false;
|
||||||
if (!ForceStrictAdGroupNames)
|
if (!ForceStrictAdGroupNames)
|
||||||
|
{
|
||||||
existingGroup = FindGroupEntryFromFolderAcl(folderPath, secGroup.WildcardPattern);
|
existingGroup = FindGroupEntryFromFolderAcl(folderPath, secGroup.WildcardPattern);
|
||||||
|
linkedFromFolderAcl = existingGroup != null;
|
||||||
|
}
|
||||||
|
|
||||||
if (existingGroup == null)
|
if (existingGroup == null)
|
||||||
existingGroup = FindGroupEntry(secGroup.Name);
|
existingGroup = FindGroupEntry(secGroup.Name);
|
||||||
@@ -680,8 +863,8 @@ namespace C4IT_IAM_Engine
|
|||||||
if (existingGroup == null)
|
if (existingGroup == null)
|
||||||
return CreateADGroup(ouPath, secGroup, users);
|
return CreateADGroup(ouPath, secGroup, users);
|
||||||
|
|
||||||
|
existingGroup = ValidateExistingGroupReuse(existingGroup, secGroup, folderPath, linkedFromFolderAcl);
|
||||||
AddMissingMembers(existingGroup, secGroup, users);
|
AddMissingMembers(existingGroup, secGroup, users);
|
||||||
ApplyExistingGroup(secGroup, existingGroup);
|
|
||||||
return existingGroup;
|
return existingGroup;
|
||||||
}
|
}
|
||||||
catch (Exception E)
|
catch (Exception E)
|
||||||
@@ -702,8 +885,12 @@ namespace C4IT_IAM_Engine
|
|||||||
{
|
{
|
||||||
secGroup.CreatedNewEntry = false;
|
secGroup.CreatedNewEntry = false;
|
||||||
DirectoryEntry existingGroup = null;
|
DirectoryEntry existingGroup = null;
|
||||||
|
var linkedFromFolderAcl = false;
|
||||||
if (!ForceStrictAdGroupNames)
|
if (!ForceStrictAdGroupNames)
|
||||||
|
{
|
||||||
existingGroup = FindGroupEntryFromFolderAcl(folderPath, secGroup.WildcardPattern);
|
existingGroup = FindGroupEntryFromFolderAcl(folderPath, secGroup.WildcardPattern);
|
||||||
|
linkedFromFolderAcl = existingGroup != null;
|
||||||
|
}
|
||||||
|
|
||||||
if (existingGroup == null)
|
if (existingGroup == null)
|
||||||
existingGroup = FindGroupEntry(secGroup.Name);
|
existingGroup = FindGroupEntry(secGroup.Name);
|
||||||
@@ -714,8 +901,7 @@ namespace C4IT_IAM_Engine
|
|||||||
if (existingGroup == null)
|
if (existingGroup == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
ApplyExistingGroup(secGroup, existingGroup);
|
return ValidateExistingGroupReuse(existingGroup, secGroup, folderPath, linkedFromFolderAcl);
|
||||||
return existingGroup;
|
|
||||||
}
|
}
|
||||||
catch (Exception E)
|
catch (Exception E)
|
||||||
{
|
{
|
||||||
@@ -738,55 +924,45 @@ namespace C4IT_IAM_Engine
|
|||||||
secGroup.Name = groupName;
|
secGroup.Name = groupName;
|
||||||
secGroup.technicalName = "CN=" + groupName + "," + ouPath;
|
secGroup.technicalName = "CN=" + groupName + "," + ouPath;
|
||||||
|
|
||||||
if (!GroupAllreadyExisting(groupName))
|
if (GroupAllreadyExisting(groupName))
|
||||||
|
throw new InvalidOperationException($"AD group '{groupName}' already exists. Create mode does not reuse existing groups.");
|
||||||
|
|
||||||
|
DirectoryEntry entry = new DirectoryEntry("LDAP://" + GetLdapServer() + "/" + ouPath, username, new NetworkCredential("", password).Password, AuthenticationTypes.Secure | AuthenticationTypes.Sealing);
|
||||||
|
DefaultLogger.LogEntry(LogLevels.Debug, $"Creating ad entry with CN / sAmAccountName: {groupName}");
|
||||||
|
DirectoryEntry group = entry.Children.Add("CN=" + groupName, "group");
|
||||||
|
group.Properties["sAmAccountName"].Value = groupName;
|
||||||
|
if (users != null && secGroup.Scope == GroupScope.Global)
|
||||||
{
|
{
|
||||||
|
foreach (var user in users)
|
||||||
DirectoryEntry entry = new DirectoryEntry("LDAP://" + GetLdapServer() + "/" + ouPath, username, new NetworkCredential("", password).Password, AuthenticationTypes.Secure | AuthenticationTypes.Sealing);
|
|
||||||
DefaultLogger.LogEntry(LogLevels.Debug, $"Creating ad entry with CN / sAmAccountName: {groupName}");
|
|
||||||
DirectoryEntry group = entry.Children.Add("CN=" + groupName, "group");
|
|
||||||
group.Properties["sAmAccountName"].Value = groupName;
|
|
||||||
if (users != null && secGroup.Scope == GroupScope.Global)
|
|
||||||
{
|
{
|
||||||
foreach (var user in users)
|
DefaultLogger.LogEntry(LogLevels.Debug, $"Adding member: {user.DistinguishedName}");
|
||||||
{
|
group.Properties["member"].Add(user.DistinguishedName);
|
||||||
DefaultLogger.LogEntry(LogLevels.Debug, $"Adding member: {user.DistinguishedName}");
|
|
||||||
group.Properties["member"].Add(user.DistinguishedName);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if(!String.IsNullOrEmpty(secGroup.description))
|
|
||||||
{
|
|
||||||
DefaultLogger.LogEntry(LogLevels.Debug, $"Setting description: {secGroup.description}");
|
|
||||||
group.Properties["description"].Value = secGroup.description;
|
|
||||||
}
|
|
||||||
var groupType = secGroup.Scope == GroupScope.Global ? GroupScopeValues.Global : GroupScopeValues.Local;
|
|
||||||
DefaultLogger.LogEntry(LogLevels.Debug, $"Setting groupType to: {groupType}");
|
|
||||||
group.Properties["groupType"].Value = groupType;
|
|
||||||
if (secGroup.Scope == GroupScope.Local)
|
|
||||||
foreach (var iGroup in secGroup.memberGroups)
|
|
||||||
{
|
|
||||||
DefaultLogger.LogEntry(LogLevels.Debug, $"Adding member: {iGroup.technicalName}");
|
|
||||||
group.Properties["member"].Add(iGroup.technicalName);
|
|
||||||
}
|
|
||||||
|
|
||||||
group.CommitChanges();
|
|
||||||
DirectoryEntry ent = new DirectoryEntry("LDAP://" + GetLdapServer() + "/" + "CN=" + groupName + "," + ouPath, username, new NetworkCredential("", password).Password, AuthenticationTypes.Secure | AuthenticationTypes.Sealing);
|
|
||||||
|
|
||||||
var objectid = SecurityGroups.getSID(ent);
|
|
||||||
DefaultLogger.LogEntry(LogLevels.Debug, $"Security group created in ad: {secGroup.technicalName}");
|
|
||||||
secGroup.UID = objectid;
|
|
||||||
secGroup.CreatedNewEntry = true;
|
|
||||||
return ent;
|
|
||||||
}
|
}
|
||||||
else
|
if(!String.IsNullOrEmpty(secGroup.description))
|
||||||
{
|
{
|
||||||
DirectoryEntry e = FindGroupEntry(secGroup.Name);
|
DefaultLogger.LogEntry(LogLevels.Debug, $"Setting description: {secGroup.description}");
|
||||||
if (e == null)
|
group.Properties["description"].Value = secGroup.description;
|
||||||
return null;
|
|
||||||
AddMissingMembers(e, secGroup, users);
|
|
||||||
ApplyExistingGroup(secGroup, e);
|
|
||||||
return e;
|
|
||||||
}
|
}
|
||||||
return null;
|
var groupType = secGroup.Scope == GroupScope.Global ? GroupScopeValues.Global : GroupScopeValues.Local;
|
||||||
|
DefaultLogger.LogEntry(LogLevels.Debug, $"Setting groupType to: {groupType}");
|
||||||
|
group.Properties["groupType"].Value = groupType;
|
||||||
|
if (secGroup.Scope == GroupScope.Local)
|
||||||
|
foreach (var iGroup in secGroup.memberGroups)
|
||||||
|
{
|
||||||
|
DefaultLogger.LogEntry(LogLevels.Debug, $"Adding member: {iGroup.technicalName}");
|
||||||
|
group.Properties["member"].Add(iGroup.technicalName);
|
||||||
|
}
|
||||||
|
|
||||||
|
group.CommitChanges();
|
||||||
|
DirectoryEntry ent = new DirectoryEntry("LDAP://" + GetLdapServer() + "/" + "CN=" + groupName + "," + ouPath, username, new NetworkCredential("", password).Password, AuthenticationTypes.Secure | AuthenticationTypes.Sealing);
|
||||||
|
|
||||||
|
var objectid = SecurityGroups.getSID(ent);
|
||||||
|
DefaultLogger.LogEntry(LogLevels.Debug, $"Security group created in ad: {secGroup.technicalName}");
|
||||||
|
secGroup.UID = objectid;
|
||||||
|
secGroup.CreatedNewEntry = true;
|
||||||
|
TryWriteMarker(ent, secGroup, false);
|
||||||
|
return ent;
|
||||||
}
|
}
|
||||||
catch (Exception E)
|
catch (Exception E)
|
||||||
{
|
{
|
||||||
@@ -865,11 +1041,17 @@ namespace C4IT_IAM_Engine
|
|||||||
public int targetTyp;
|
public int targetTyp;
|
||||||
public GroupScope Scope;
|
public GroupScope Scope;
|
||||||
public FileSystemRights rights;
|
public FileSystemRights rights;
|
||||||
|
public string MarkerPath;
|
||||||
public IAM_SecurityGroup()
|
public IAM_SecurityGroup()
|
||||||
{
|
{
|
||||||
memberGroups = new List<IAM_SecurityGroup>();
|
memberGroups = new List<IAM_SecurityGroup>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public enum SecurityGroupReuseMode
|
||||||
|
{
|
||||||
|
Safe,
|
||||||
|
Name
|
||||||
|
}
|
||||||
public enum SecurityGroupType
|
public enum SecurityGroupType
|
||||||
{
|
{
|
||||||
[XmlEnum(Name = "0")]
|
[XmlEnum(Name = "0")]
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ namespace LiamNtfs
|
|||||||
public PrincipalContext adContext = null;
|
public PrincipalContext adContext = null;
|
||||||
public Exception LastException { get; private set; } = null;
|
public Exception LastException { get; private set; } = null;
|
||||||
public string LastErrorMessage { get; private set; } = null;
|
public string LastErrorMessage { get; private set; } = null;
|
||||||
|
public cNtfsEnumerationDiagnostics LastEnumerationDiagnostics { get; private set; } = new cNtfsEnumerationDiagnostics();
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void ResetError()
|
public void ResetError()
|
||||||
@@ -169,6 +170,7 @@ namespace LiamNtfs
|
|||||||
public async Task<cNtfsCollectionBase> RequestFoldersListAsync(string rootPath, int depth)
|
public async Task<cNtfsCollectionBase> RequestFoldersListAsync(string rootPath, int depth)
|
||||||
{
|
{
|
||||||
ResetError();
|
ResetError();
|
||||||
|
LastEnumerationDiagnostics = new cNtfsEnumerationDiagnostics(rootPath, depth);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await Task.Delay(0);
|
await Task.Delay(0);
|
||||||
@@ -217,7 +219,9 @@ namespace LiamNtfs
|
|||||||
}
|
}
|
||||||
catch (Exception E)
|
catch (Exception E)
|
||||||
{
|
{
|
||||||
cLogManager.LogEntry($"Could not enumerate directories under '{rootPath.FullName}': {E.Message}", LogLevels.Warning);
|
LastEnumerationDiagnostics.EnumerationFailures++;
|
||||||
|
LastEnumerationDiagnostics.AddFailure(rootPath.FullName, E);
|
||||||
|
cLogManager.LogEntry($"NTFS enumeration failed under '{rootPath.FullName}'. ReturnedPartialResult=true. Error='{E.Message}'", LogLevels.Warning);
|
||||||
cLogManager.LogException(E, LogLevels.Debug);
|
cLogManager.LogException(E, LogLevels.Debug);
|
||||||
return folders;
|
return folders;
|
||||||
}
|
}
|
||||||
@@ -239,11 +243,14 @@ namespace LiamNtfs
|
|||||||
}
|
}
|
||||||
catch (Exception E)
|
catch (Exception E)
|
||||||
{
|
{
|
||||||
cLogManager.LogEntry($"Could not read directory metadata for '{directory.FullName}': {E.Message}", LogLevels.Warning);
|
LastEnumerationDiagnostics.MetadataFailures++;
|
||||||
|
LastEnumerationDiagnostics.AddFailure(directory.FullName, E);
|
||||||
|
cLogManager.LogEntry($"NTFS directory metadata read failed for '{directory.FullName}'. DirectorySkipped=true. Error='{E.Message}'", LogLevels.Warning);
|
||||||
cLogManager.LogException(E, LogLevels.Debug);
|
cLogManager.LogException(E, LogLevels.Debug);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LastEnumerationDiagnostics.FoldersEnumerated++;
|
||||||
folders.Add(folder);
|
folders.Add(folder);
|
||||||
if (depth <= 0)
|
if (depth <= 0)
|
||||||
continue;
|
continue;
|
||||||
@@ -256,7 +263,9 @@ namespace LiamNtfs
|
|||||||
}
|
}
|
||||||
catch (Exception E)
|
catch (Exception E)
|
||||||
{
|
{
|
||||||
cLogManager.LogEntry($"Could not scan subtree '{directory.FullName}': {E.Message}", LogLevels.Warning);
|
LastEnumerationDiagnostics.SubtreeFailures++;
|
||||||
|
LastEnumerationDiagnostics.AddFailure(directory.FullName, E);
|
||||||
|
cLogManager.LogEntry($"NTFS subtree scan failed for '{directory.FullName}'. ReturnedPartialResult=true. Error='{E.Message}'", LogLevels.Warning);
|
||||||
cLogManager.LogException(E, LogLevels.Debug);
|
cLogManager.LogException(E, LogLevels.Debug);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -330,6 +339,39 @@ namespace LiamNtfs
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class cNtfsEnumerationDiagnostics
|
||||||
|
{
|
||||||
|
private const int MaxSamples = 10;
|
||||||
|
|
||||||
|
public cNtfsEnumerationDiagnostics()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public cNtfsEnumerationDiagnostics(string rootPath, int depth)
|
||||||
|
{
|
||||||
|
RootPath = rootPath ?? string.Empty;
|
||||||
|
Depth = depth;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string RootPath { get; private set; } = string.Empty;
|
||||||
|
public int Depth { get; private set; }
|
||||||
|
public int FoldersEnumerated { get; set; }
|
||||||
|
public int EnumerationFailures { get; set; }
|
||||||
|
public int MetadataFailures { get; set; }
|
||||||
|
public int SubtreeFailures { get; set; }
|
||||||
|
public List<string> FailureSamples { get; } = new List<string>();
|
||||||
|
|
||||||
|
public int TotalFailures => EnumerationFailures + MetadataFailures + SubtreeFailures;
|
||||||
|
|
||||||
|
public void AddFailure(string path, Exception exception)
|
||||||
|
{
|
||||||
|
if (FailureSamples.Count >= MaxSamples)
|
||||||
|
return;
|
||||||
|
|
||||||
|
FailureSamples.Add($"{path}: {exception?.GetType().Name}: {exception?.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public class cNtfsLogonInfo
|
public class cNtfsLogonInfo
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -68,7 +68,10 @@ namespace C4IT.LIAM.Activities
|
|||||||
|
|
||||||
var dataMain = dataProvider.GetDataList(constFragmentNameConfigProviderMain,
|
var dataMain = dataProvider.GetDataList(constFragmentNameConfigProviderMain,
|
||||||
"ID, [Expression-ObjectId] as EOID, GCCDomain, GCCTarget, GCCMaxDepth, " +
|
"ID, [Expression-ObjectId] as EOID, GCCDomain, GCCTarget, GCCMaxDepth, " +
|
||||||
"GCCgroupLDAPFilter, GCCgroupOUPath, GCCPermissionGroupStrategy, GCCtargetType",
|
"GCCgroupLDAPFilter, GCCgroupOUPath, GCCPermissionGroupStrategy, GCCtargetType, " +
|
||||||
|
"OwnerACLPermission.C4IT_EnumValueInt as OwnerACLPermission, " +
|
||||||
|
"WriteACLPermission.C4IT_EnumValueInt as WriteACLPermission, " +
|
||||||
|
"ReadACLPermission.C4IT_EnumValueInt as ReadACLPermission",
|
||||||
$"[Expression-ObjectId] = '{configEOID}'");
|
$"[Expression-ObjectId] = '{configEOID}'");
|
||||||
|
|
||||||
Guid ConfigClassId = (Guid)dataMain.First()["ID"];
|
Guid ConfigClassId = (Guid)dataMain.First()["ID"];
|
||||||
|
|||||||
@@ -2,7 +2,9 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Activities;
|
using System.Activities;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Security.Cryptography;
|
||||||
using static C4IT.Logging.cLogManager;
|
using static C4IT.Logging.cLogManager;
|
||||||
using Matrix42.Contracts.Platform.Data;
|
using Matrix42.Contracts.Platform.Data;
|
||||||
using Matrix42.ServiceRepository.Contracts.Components;
|
using Matrix42.ServiceRepository.Contracts.Components;
|
||||||
@@ -120,6 +122,9 @@ namespace LiamWorkflowActivities
|
|||||||
var CM = MethodBase.GetCurrentMethod();
|
var CM = MethodBase.GetCurrentMethod();
|
||||||
LogMethodBegin(CM);
|
LogMethodBegin(CM);
|
||||||
cLogManager.DefaultLogger.LogAssemblyInfo(Ass);
|
cLogManager.DefaultLogger.LogAssemblyInfo(Ass);
|
||||||
|
LogAssemblyIdentity("WorkflowActivities", Ass);
|
||||||
|
LogAssemblyIdentity("LiamNtfs", typeof(cLiamProviderNtfs).Assembly);
|
||||||
|
LogAssemblyIdentity("LiamBaseClasses", typeof(cLiamProviderBase).Assembly);
|
||||||
IsInitialized = true;
|
IsInitialized = true;
|
||||||
LogMethodEnd(CM);
|
LogMethodEnd(CM);
|
||||||
}
|
}
|
||||||
@@ -127,6 +132,39 @@ namespace LiamWorkflowActivities
|
|||||||
catch { };
|
catch { };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void LogAssemblyIdentity(string label, Assembly assembly)
|
||||||
|
{
|
||||||
|
if (assembly == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var fileVersion = string.IsNullOrWhiteSpace(assembly.Location)
|
||||||
|
? null
|
||||||
|
: FileVersionInfo.GetVersionInfo(assembly.Location);
|
||||||
|
var informationalVersion = assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion ?? string.Empty;
|
||||||
|
LogEntry(
|
||||||
|
$"LIAM assembly identity: Label='{label}', Name='{assembly.GetName().Name}', AssemblyVersion='{assembly.GetName().Version}', InformationalVersion='{informationalVersion}', FileVersion='{fileVersion?.FileVersion}', ProductVersion='{fileVersion?.ProductVersion}', Sha256='{GetAssemblySha256(assembly)}', Location='{assembly.Location}'",
|
||||||
|
LogLevels.Info);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetAssemblySha256(Assembly assembly)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (assembly == null || string.IsNullOrWhiteSpace(assembly.Location) || !File.Exists(assembly.Location))
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
using (var stream = File.OpenRead(assembly.Location))
|
||||||
|
using (var sha256 = SHA256.Create())
|
||||||
|
{
|
||||||
|
return BitConverter.ToString(sha256.ComputeHash(stream)).Replace("-", string.Empty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public bool LoadLicensingInformation(bool force = false)
|
public bool LoadLicensingInformation(bool force = false)
|
||||||
{
|
{
|
||||||
if (cC4ITLicenseM42ESM.Instance == null || force)
|
if (cC4ITLicenseM42ESM.Instance == null || force)
|
||||||
@@ -262,7 +300,10 @@ namespace LiamWorkflowActivities
|
|||||||
GroupFilter = cLIAMHelper.getStringFromObject(dataMain["GCCgroupLDAPFilter"]),
|
GroupFilter = cLIAMHelper.getStringFromObject(dataMain["GCCgroupLDAPFilter"]),
|
||||||
GroupPath = cLIAMHelper.getStringFromObject(dataMain["GCCgroupOUPath"]),
|
GroupPath = cLIAMHelper.getStringFromObject(dataMain["GCCgroupOUPath"]),
|
||||||
GroupStrategy = (eLiamGroupStrategies)cLIAMHelper.getIntFromObject(dataMain["GCCPermissionGroupStrategy"]),
|
GroupStrategy = (eLiamGroupStrategies)cLIAMHelper.getIntFromObject(dataMain["GCCPermissionGroupStrategy"]),
|
||||||
ProviderType = (eLiamProviderTypes)cLIAMHelper.getIntFromObject(dataMain["GCCtargetType"])
|
ProviderType = (eLiamProviderTypes)cLIAMHelper.getIntFromObject(dataMain["GCCtargetType"]),
|
||||||
|
OwnerACLPermission = GetAclPermissionFromDataRow(dataMain, "OwnerACLPermission", cLiamAclPermissionDefaults.Owner),
|
||||||
|
WriteACLPermission = GetAclPermissionFromDataRow(dataMain, "WriteACLPermission", cLiamAclPermissionDefaults.Write),
|
||||||
|
ReadACLPermission = GetAclPermissionFromDataRow(dataMain, "ReadACLPermission", cLiamAclPermissionDefaults.Read)
|
||||||
};
|
};
|
||||||
if (dataAdditional != null)
|
if (dataAdditional != null)
|
||||||
{
|
{
|
||||||
@@ -383,6 +424,34 @@ namespace LiamWorkflowActivities
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int GetAclPermissionFromDataRow(DataRow row, string columnName, int defaultValue)
|
||||||
|
{
|
||||||
|
if (row == null || string.IsNullOrWhiteSpace(columnName) || !row.Table.Columns.Contains(columnName))
|
||||||
|
return defaultValue;
|
||||||
|
|
||||||
|
var rawValue = cLIAMHelper.getStringFromObject(row[columnName]);
|
||||||
|
if (string.IsNullOrWhiteSpace(rawValue))
|
||||||
|
return defaultValue;
|
||||||
|
|
||||||
|
var value = rawValue.Trim();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (value.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
|
||||||
|
return Convert.ToInt32(value.Substring(2), 16);
|
||||||
|
|
||||||
|
if (int.TryParse(value, out var parsedValue))
|
||||||
|
return parsedValue;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogEntry($"Could not parse ACL permission config value '{columnName}'='{rawValue}': {ex.Message}. Falling back to default '{defaultValue}'.", LogLevels.Warning);
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
LogEntry($"Could not parse ACL permission config value '{columnName}'='{rawValue}'. Falling back to default '{defaultValue}'.", LogLevels.Warning);
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
public ProviderCacheEntry getDataProvider(Guid ProviderConfigClassID)
|
public ProviderCacheEntry getDataProvider(Guid ProviderConfigClassID)
|
||||||
{
|
{
|
||||||
var CM = MethodBase.GetCurrentMethod();
|
var CM = MethodBase.GetCurrentMethod();
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using C4IT_IAM_Engine;
|
|||||||
using LiamAD;
|
using LiamAD;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -77,6 +78,9 @@ namespace LiamWorkflowActivities
|
|||||||
|
|
||||||
public static class LiamWorkflowRuntime
|
public static class LiamWorkflowRuntime
|
||||||
{
|
{
|
||||||
|
private const string AutomaticNtfsEnsureTimeoutSecondsKey = "NtfsAutomaticEnsureTimeoutSeconds";
|
||||||
|
private const int DefaultAutomaticNtfsEnsureTimeoutSeconds = 300;
|
||||||
|
|
||||||
public static async Task<GetDataAreasOperationResult> GetDataAreasFromProviderAsync(cLiamProviderBase provider, string configurationId = null, bool? simulateConfiguredNtfsPermissionEnsure = null)
|
public static async Task<GetDataAreasOperationResult> GetDataAreasFromProviderAsync(cLiamProviderBase provider, string configurationId = null, bool? simulateConfiguredNtfsPermissionEnsure = null)
|
||||||
{
|
{
|
||||||
var result = new GetDataAreasOperationResult();
|
var result = new GetDataAreasOperationResult();
|
||||||
@@ -421,62 +425,219 @@ namespace LiamWorkflowActivities
|
|||||||
|
|
||||||
var allowFolderEnsure = IsAdditionalConfigurationEnabled(provider, "EnsureNtfsPermissionGroups");
|
var allowFolderEnsure = IsAdditionalConfigurationEnabled(provider, "EnsureNtfsPermissionGroups");
|
||||||
var allowSharePathEnsure = IsAdditionalConfigurationEnabled(provider, "EnsureNtfsPermissionGroupsForShares");
|
var allowSharePathEnsure = IsAdditionalConfigurationEnabled(provider, "EnsureNtfsPermissionGroupsForShares");
|
||||||
if (!allowFolderEnsure && !allowSharePathEnsure)
|
var allowTraverseEnsure = IsAdditionalConfigurationEnabled(provider, "EnsureNtfsTraverseGroups");
|
||||||
|
var ensureTimeoutSeconds = GetAdditionalConfigurationInt32(
|
||||||
|
provider,
|
||||||
|
AutomaticNtfsEnsureTimeoutSecondsKey,
|
||||||
|
DefaultAutomaticNtfsEnsureTimeoutSeconds,
|
||||||
|
0);
|
||||||
|
var ensureTimeout = ensureTimeoutSeconds > 0
|
||||||
|
? TimeSpan.FromSeconds(ensureTimeoutSeconds)
|
||||||
|
: TimeSpan.Zero;
|
||||||
|
if (!allowFolderEnsure && !allowSharePathEnsure && !allowTraverseEnsure)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
foreach (var ntfsArea in dataAreas
|
LogEntry(
|
||||||
.Where(dataArea =>
|
$"Automatic NTFS ensure configured. PermissionGroups={allowFolderEnsure}, PermissionGroupsForShares={allowSharePathEnsure}, TraverseGroups={allowTraverseEnsure}, DataAreas={dataAreas.Count}, WhatIf={simulateOnly}, TimeoutSeconds={ensureTimeoutSeconds}",
|
||||||
allowFolderEnsure && dataArea is cLiamNtfsFolder
|
LogLevels.Debug);
|
||||||
|| allowSharePathEnsure && dataArea is cLiamNtfsShare)
|
|
||||||
.Cast<cLiamNtfsPermissionDataAreaBase>())
|
if (allowFolderEnsure || allowSharePathEnsure)
|
||||||
{
|
{
|
||||||
var folderPath = ntfsArea.TechnicalName;
|
foreach (var ntfsArea in dataAreas
|
||||||
if (string.IsNullOrWhiteSpace(folderPath))
|
.Where(dataArea =>
|
||||||
continue;
|
allowFolderEnsure && dataArea is cLiamNtfsFolder
|
||||||
|
|| allowSharePathEnsure && dataArea is cLiamNtfsShare)
|
||||||
if (!Directory.Exists(folderPath))
|
.Cast<cLiamNtfsPermissionDataAreaBase>())
|
||||||
{
|
{
|
||||||
LogEntry($"Skipping automatic NTFS permission group ensure for '{folderPath}' because the directory does not exist.", LogLevels.Warning);
|
var folderPath = ntfsArea.TechnicalName;
|
||||||
continue;
|
if (string.IsNullOrWhiteSpace(folderPath))
|
||||||
}
|
continue;
|
||||||
|
|
||||||
var ensureResult = await ntfsProvider.EnsureMissingPermissionGroupsAsync(
|
var ensureTraverseInPermissionPhase = !allowTraverseEnsure && (ntfsArea is cLiamNtfsFolder || ntfsArea is cLiamNtfsShare);
|
||||||
folderPath,
|
LogEntry(
|
||||||
null,
|
$"Automatic NTFS permission group ensure starting for '{folderPath}'. AllowSharePathEnsure={allowSharePathEnsure}, EnsureTraverseInPermissionPhase={ensureTraverseInPermissionPhase}, WhatIf={simulateOnly}",
|
||||||
null,
|
LogLevels.Debug);
|
||||||
null,
|
|
||||||
null,
|
ResultToken ensureResult;
|
||||||
allowSharePathEnsure,
|
try
|
||||||
ntfsArea is cLiamNtfsFolder || ntfsArea is cLiamNtfsShare,
|
{
|
||||||
simulateOnly);
|
ensureResult = await RunAutomaticNtfsEnsureOperationAsync(
|
||||||
if (ensureResult == null)
|
folderPath,
|
||||||
|
"permission group ensure",
|
||||||
|
() => ntfsProvider.EnsureMissingPermissionGroupsAsync(
|
||||||
|
folderPath,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
allowSharePathEnsure,
|
||||||
|
ensureTraverseInPermissionPhase,
|
||||||
|
simulateOnly),
|
||||||
|
ensureTimeout);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogException(ex);
|
||||||
|
LogEntry($"Automatic NTFS permission group ensure failed for '{folderPath}' with exception: {ex.Message}", LogLevels.Error);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ensureResult == null)
|
||||||
|
{
|
||||||
|
var providerMessage = ntfsProvider.GetLastErrorMessage() ?? "Provider returned no result.";
|
||||||
|
LogEntry(
|
||||||
|
$"Automatic NTFS permission group ensure skipped for '{folderPath}' because the provider returned no result. ProviderMessage='{providerMessage}'",
|
||||||
|
LogLevels.Warning);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ensureResult.resultErrorId != 0)
|
||||||
|
{
|
||||||
|
if (IsAutomaticNtfsEnsureTimeout(ensureResult))
|
||||||
|
{
|
||||||
|
SetAutomaticEnsureTimeoutError(result, folderPath, ensureResult, "permission group ensure");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
LogEntry(
|
||||||
|
$"Automatic NTFS permission group ensure skipped for '{folderPath}' after resultErrorId={ensureResult.resultErrorId}. ResultMessage='{ensureResult.resultMessage ?? string.Empty}'",
|
||||||
|
LogLevels.Warning);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (simulateOnly)
|
||||||
|
{
|
||||||
|
LogAutomaticNtfsEnsurePreviewDebug(folderPath, ensureResult, "permission group ensure");
|
||||||
|
result.AutomaticEnsurePreview.Add(MapAutomaticEnsurePreview(folderPath, ensureResult));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
LogAutomaticNtfsEnsureDebug(folderPath, ensureResult, "permission group ensure");
|
||||||
|
LogEntry($"Automatic NTFS permission group resolve starting for '{folderPath}'.", LogLevels.Debug);
|
||||||
|
await ntfsArea.ResolvePermissionGroupsAsync(folderPath);
|
||||||
|
LogEntry($"Automatic NTFS permission group resolve finished for '{folderPath}'.", LogLevels.Debug);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allowTraverseEnsure)
|
||||||
|
{
|
||||||
|
foreach (var ntfsArea in dataAreas
|
||||||
|
.Where(dataArea => dataArea is cLiamNtfsFolder || dataArea is cLiamNtfsShare)
|
||||||
|
.Cast<cLiamNtfsPermissionDataAreaBase>())
|
||||||
{
|
{
|
||||||
result.ErrorCode = "WF_GET_DATAAREAS_ENSURE_NTFS_GROUPS_FAILED";
|
var folderPath = ntfsArea.TechnicalName;
|
||||||
result.ErrorMessage = $"Automatic NTFS permission group ensure failed for '{folderPath}' because the provider returned no result.";
|
if (string.IsNullOrWhiteSpace(folderPath))
|
||||||
return false;
|
continue;
|
||||||
}
|
|
||||||
|
|
||||||
if (ensureResult.resultErrorId != 0)
|
LogEntry(
|
||||||
{
|
$"Automatic NTFS traverse group ensure starting for '{folderPath}'. WhatIf={simulateOnly}",
|
||||||
result.ErrorCode = "WF_GET_DATAAREAS_ENSURE_NTFS_GROUPS_FAILED";
|
LogLevels.Debug);
|
||||||
result.ErrorMessage = $"Automatic NTFS permission group ensure failed for '{folderPath}': {ensureResult.resultMessage}";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (simulateOnly)
|
ResultToken ensureResult;
|
||||||
{
|
try
|
||||||
LogAutomaticNtfsEnsurePreviewDebug(folderPath, ensureResult);
|
{
|
||||||
result.AutomaticEnsurePreview.Add(MapAutomaticEnsurePreview(folderPath, ensureResult));
|
ensureResult = await RunAutomaticNtfsEnsureOperationAsync(
|
||||||
continue;
|
folderPath,
|
||||||
}
|
"traverse group ensure",
|
||||||
|
() => ntfsProvider.EnsureTraverseGroupsAsync(
|
||||||
|
folderPath,
|
||||||
|
null,
|
||||||
|
simulateOnly),
|
||||||
|
ensureTimeout);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogException(ex);
|
||||||
|
LogEntry($"Automatic NTFS traverse group ensure failed for '{folderPath}' with exception: {ex.Message}", LogLevels.Error);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
LogAutomaticNtfsEnsureDebug(folderPath, ensureResult);
|
if (ensureResult == null)
|
||||||
await ntfsArea.ResolvePermissionGroupsAsync(folderPath);
|
{
|
||||||
|
var providerMessage = ntfsProvider.GetLastErrorMessage() ?? "Provider returned no result.";
|
||||||
|
LogEntry(
|
||||||
|
$"Automatic NTFS traverse group ensure skipped for '{folderPath}' because the provider returned no result. ProviderMessage='{providerMessage}'",
|
||||||
|
LogLevels.Warning);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ensureResult.resultErrorId != 0)
|
||||||
|
{
|
||||||
|
if (IsAutomaticNtfsEnsureTimeout(ensureResult))
|
||||||
|
{
|
||||||
|
SetAutomaticEnsureTimeoutError(result, folderPath, ensureResult, "traverse group ensure");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
LogEntry(
|
||||||
|
$"Automatic NTFS traverse group ensure skipped for '{folderPath}' after resultErrorId={ensureResult.resultErrorId}. ResultMessage='{ensureResult.resultMessage ?? string.Empty}'",
|
||||||
|
LogLevels.Warning);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (simulateOnly)
|
||||||
|
{
|
||||||
|
LogAutomaticNtfsEnsurePreviewDebug(folderPath, ensureResult, "traverse group ensure");
|
||||||
|
result.AutomaticEnsurePreview.Add(MapAutomaticEnsurePreview(folderPath, ensureResult));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
LogAutomaticNtfsEnsureDebug(folderPath, ensureResult, "traverse group ensure");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static async Task<ResultToken> RunAutomaticNtfsEnsureOperationAsync(
|
||||||
|
string folderPath,
|
||||||
|
string operationLabel,
|
||||||
|
Func<Task<ResultToken>> operation,
|
||||||
|
TimeSpan timeout)
|
||||||
|
{
|
||||||
|
var operationTask = Task.Run(operation);
|
||||||
|
#pragma warning disable 4014
|
||||||
|
// Fire-and-forget observer for exceptions that happen after a timeout.
|
||||||
|
operationTask.ContinueWith(
|
||||||
|
task => LogException(task.Exception),
|
||||||
|
TaskContinuationOptions.OnlyOnFaulted);
|
||||||
|
#pragma warning restore 4014
|
||||||
|
|
||||||
|
if (timeout <= TimeSpan.Zero)
|
||||||
|
return await operationTask;
|
||||||
|
|
||||||
|
var completedTask = await Task.WhenAny(operationTask, Task.Delay(timeout));
|
||||||
|
if (completedTask == operationTask)
|
||||||
|
return await operationTask;
|
||||||
|
|
||||||
|
var message =
|
||||||
|
$"Automatic NTFS {operationLabel} timed out for '{folderPath}' after {timeout.TotalSeconds:F0}s. " +
|
||||||
|
"The workflow will stop automatic NTFS ensure processing for this run. " +
|
||||||
|
"The underlying NTFS/AD operation may still finish in the worker process.";
|
||||||
|
LogEntry(message, LogLevels.Error);
|
||||||
|
return new ResultToken("AutomaticNtfsEnsureTimeout")
|
||||||
|
{
|
||||||
|
resultErrorId = 30010,
|
||||||
|
resultMessage = message
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsAutomaticNtfsEnsureTimeout(ResultToken ensureResult)
|
||||||
|
{
|
||||||
|
return ensureResult?.resultErrorId == 30010;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SetAutomaticEnsureTimeoutError(
|
||||||
|
GetDataAreasOperationResult result,
|
||||||
|
string folderPath,
|
||||||
|
ResultToken ensureResult,
|
||||||
|
string operationLabel)
|
||||||
|
{
|
||||||
|
result.ErrorCode = "WF_GET_DATAAREAS_ENSURE_NTFS_GROUPS_TIMEOUT";
|
||||||
|
result.ErrorMessage = ensureResult?.resultMessage
|
||||||
|
?? $"Automatic NTFS {operationLabel} timed out for '{folderPath}'.";
|
||||||
|
}
|
||||||
|
|
||||||
private static NtfsAutomaticEnsurePreviewEntry MapAutomaticEnsurePreview(string folderPath, ResultToken ensureResult)
|
private static NtfsAutomaticEnsurePreviewEntry MapAutomaticEnsurePreview(string folderPath, ResultToken ensureResult)
|
||||||
{
|
{
|
||||||
return new NtfsAutomaticEnsurePreviewEntry
|
return new NtfsAutomaticEnsurePreviewEntry
|
||||||
@@ -493,13 +654,13 @@ namespace LiamWorkflowActivities
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void LogAutomaticNtfsEnsurePreviewDebug(string folderPath, ResultToken ensureResult)
|
private static void LogAutomaticNtfsEnsurePreviewDebug(string folderPath, ResultToken ensureResult, string operationLabel)
|
||||||
{
|
{
|
||||||
if (ensureResult == null)
|
if (ensureResult == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
LogEntry(
|
LogEntry(
|
||||||
$"Automatic NTFS permission group ensure preview finished for '{folderPath}'. " +
|
$"Automatic NTFS {operationLabel} preview finished for '{folderPath}'. " +
|
||||||
$"WouldCreateGroups={ensureResult.createdGroups.Count}, " +
|
$"WouldCreateGroups={ensureResult.createdGroups.Count}, " +
|
||||||
$"WouldReuseGroups={ensureResult.reusedGroups.Count}, " +
|
$"WouldReuseGroups={ensureResult.reusedGroups.Count}, " +
|
||||||
$"WouldAddAcls={ensureResult.addedAclEntries.Count}, " +
|
$"WouldAddAcls={ensureResult.addedAclEntries.Count}, " +
|
||||||
@@ -510,13 +671,13 @@ namespace LiamWorkflowActivities
|
|||||||
LogLevels.Debug);
|
LogLevels.Debug);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void LogAutomaticNtfsEnsureDebug(string folderPath, ResultToken ensureResult)
|
private static void LogAutomaticNtfsEnsureDebug(string folderPath, ResultToken ensureResult, string operationLabel)
|
||||||
{
|
{
|
||||||
if (ensureResult == null)
|
if (ensureResult == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
LogEntry(
|
LogEntry(
|
||||||
$"Automatic NTFS permission group ensure finished for '{folderPath}'. " +
|
$"Automatic NTFS {operationLabel} finished for '{folderPath}'. " +
|
||||||
$"CreatedGroups={ensureResult.createdGroups.Count}, " +
|
$"CreatedGroups={ensureResult.createdGroups.Count}, " +
|
||||||
$"ReusedGroups={ensureResult.reusedGroups.Count}, " +
|
$"ReusedGroups={ensureResult.reusedGroups.Count}, " +
|
||||||
$"AddedAcls={ensureResult.addedAclEntries.Count}, " +
|
$"AddedAcls={ensureResult.addedAclEntries.Count}, " +
|
||||||
@@ -529,42 +690,42 @@ namespace LiamWorkflowActivities
|
|||||||
if (ensureResult.createdGroups.Count > 0)
|
if (ensureResult.createdGroups.Count > 0)
|
||||||
{
|
{
|
||||||
LogEntry(
|
LogEntry(
|
||||||
$"Automatic NTFS permission group ensure detected missing AD groups for '{folderPath}' and created them: {string.Join(", ", ensureResult.createdGroups)}",
|
$"Automatic NTFS {operationLabel} detected missing AD groups for '{folderPath}' and created them: {string.Join(", ", ensureResult.createdGroups)}",
|
||||||
LogLevels.Debug);
|
LogLevels.Debug);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ensureResult.reusedGroups.Count > 0)
|
if (ensureResult.reusedGroups.Count > 0)
|
||||||
{
|
{
|
||||||
LogEntry(
|
LogEntry(
|
||||||
$"Automatic NTFS permission group ensure reused existing AD groups for '{folderPath}': {string.Join(", ", ensureResult.reusedGroups)}",
|
$"Automatic NTFS {operationLabel} reused existing AD groups for '{folderPath}': {string.Join(", ", ensureResult.reusedGroups)}",
|
||||||
LogLevels.Debug);
|
LogLevels.Debug);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ensureResult.addedAclEntries.Count > 0)
|
if (ensureResult.addedAclEntries.Count > 0)
|
||||||
{
|
{
|
||||||
LogEntry(
|
LogEntry(
|
||||||
$"Automatic NTFS permission group ensure added missing ACL entries for '{folderPath}': {string.Join(", ", ensureResult.addedAclEntries)}",
|
$"Automatic NTFS {operationLabel} added missing ACL entries for '{folderPath}': {string.Join(", ", ensureResult.addedAclEntries)}",
|
||||||
LogLevels.Debug);
|
LogLevels.Debug);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ensureResult.skippedAclEntries.Count > 0)
|
if (ensureResult.skippedAclEntries.Count > 0)
|
||||||
{
|
{
|
||||||
LogEntry(
|
LogEntry(
|
||||||
$"Automatic NTFS permission group ensure kept existing ACL entries for '{folderPath}': {string.Join(", ", ensureResult.skippedAclEntries)}",
|
$"Automatic NTFS {operationLabel} kept existing ACL entries for '{folderPath}': {string.Join(", ", ensureResult.skippedAclEntries)}",
|
||||||
LogLevels.Debug);
|
LogLevels.Debug);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ensureResult.ensuredTraverseGroups.Count > 0)
|
if (ensureResult.ensuredTraverseGroups.Count > 0)
|
||||||
{
|
{
|
||||||
LogEntry(
|
LogEntry(
|
||||||
$"Automatic NTFS permission group ensure touched traverse groups for '{folderPath}': {string.Join(", ", ensureResult.ensuredTraverseGroups)}",
|
$"Automatic NTFS {operationLabel} touched traverse groups for '{folderPath}': {string.Join(", ", ensureResult.ensuredTraverseGroups)}",
|
||||||
LogLevels.Debug);
|
LogLevels.Debug);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ensureResult.warnings.Count > 0)
|
if (ensureResult.warnings.Count > 0)
|
||||||
{
|
{
|
||||||
LogEntry(
|
LogEntry(
|
||||||
$"Automatic NTFS permission group ensure produced warnings for '{folderPath}': {string.Join(" | ", ensureResult.warnings)}",
|
$"Automatic NTFS {operationLabel} produced warnings for '{folderPath}': {string.Join(" | ", ensureResult.warnings)}",
|
||||||
LogLevels.Debug);
|
LogLevels.Debug);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -582,6 +743,33 @@ namespace LiamWorkflowActivities
|
|||||||
|| rawValue.Equals("yes", StringComparison.OrdinalIgnoreCase);
|
|| rawValue.Equals("yes", StringComparison.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int GetAdditionalConfigurationInt32(cLiamProviderBase provider, string key, int defaultValue, int minValue)
|
||||||
|
{
|
||||||
|
if (provider?.AdditionalConfiguration == null || string.IsNullOrWhiteSpace(key))
|
||||||
|
return defaultValue;
|
||||||
|
|
||||||
|
if (!provider.AdditionalConfiguration.TryGetValue(key, out var rawValue) || string.IsNullOrWhiteSpace(rawValue))
|
||||||
|
return defaultValue;
|
||||||
|
|
||||||
|
if (!int.TryParse(rawValue.Trim(), NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsedValue))
|
||||||
|
{
|
||||||
|
LogEntry(
|
||||||
|
$"AdditionalConfiguration '{key}' has invalid integer value '{rawValue}'. Defaulting to {defaultValue}.",
|
||||||
|
LogLevels.Warning);
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parsedValue < minValue)
|
||||||
|
{
|
||||||
|
LogEntry(
|
||||||
|
$"AdditionalConfiguration '{key}' value {parsedValue} is below minimum {minValue}. Defaulting to {defaultValue}.",
|
||||||
|
LogLevels.Warning);
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsedValue;
|
||||||
|
}
|
||||||
|
|
||||||
private static bool IsWorkflowWhatIfEnabled(cLiamProviderBase provider)
|
private static bool IsWorkflowWhatIfEnabled(cLiamProviderBase provider)
|
||||||
{
|
{
|
||||||
return IsAdditionalConfigurationEnabled(provider, "WhatIf");
|
return IsAdditionalConfigurationEnabled(provider, "WhatIf");
|
||||||
|
|||||||
@@ -283,6 +283,7 @@
|
|||||||
<RowDefinition Height="Auto"/>
|
<RowDefinition Height="Auto"/>
|
||||||
<RowDefinition Height="Auto"/>
|
<RowDefinition Height="Auto"/>
|
||||||
<RowDefinition Height="Auto"/>
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
<TextBlock Text="Folder Path" Grid.Row="0" Grid.Column="0" Margin="0,0,8,8" VerticalAlignment="Center"/>
|
<TextBlock Text="Folder Path" Grid.Row="0" Grid.Column="0" Margin="0,0,8,8" VerticalAlignment="Center"/>
|
||||||
@@ -299,7 +300,8 @@
|
|||||||
|
|
||||||
<CheckBox x:Name="NtfsEnsureTraverseCheckBox" Grid.Row="4" Grid.Column="1" Margin="0,0,0,8" Content="Ensure traverse groups and ACLs on parent path"/>
|
<CheckBox x:Name="NtfsEnsureTraverseCheckBox" Grid.Row="4" Grid.Column="1" Margin="0,0,0,8" Content="Ensure traverse groups and ACLs on parent path"/>
|
||||||
|
|
||||||
<Button x:Name="ExecuteNtfsEnsureButton" Grid.Row="5" Grid.Column="1" Width="220" HorizontalAlignment="Right" Content="Ensure Missing Groups / ACLs" Click="ExecuteNtfsEnsureButton_Click"/>
|
<Button x:Name="ExecuteNtfsEnsureButton" Grid.Row="5" Grid.Column="1" Width="220" HorizontalAlignment="Right" Margin="0,0,0,8" Content="Ensure Missing Groups / ACLs" Click="ExecuteNtfsEnsureButton_Click"/>
|
||||||
|
<Button x:Name="ExecuteNtfsTraverseOnlyButton" Grid.Row="6" Grid.Column="1" Width="220" HorizontalAlignment="Right" Content="Ensure Traverse Only" Click="ExecuteNtfsTraverseOnlyButton_Click"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
</TabControl>
|
</TabControl>
|
||||||
|
|||||||
@@ -584,7 +584,7 @@ namespace LiamWorkflowDiagnostics
|
|||||||
|
|
||||||
if (runWhatIf && result.AutomaticEnsurePreview != null && result.AutomaticEnsurePreview.Count > 0)
|
if (runWhatIf && result.AutomaticEnsurePreview != null && result.AutomaticEnsurePreview.Count > 0)
|
||||||
{
|
{
|
||||||
AppendLog($"EnsureNtfsPermissionGroups wurde nur simuliert fuer {result.AutomaticEnsurePreview.Count} Ordner. Details stehen im Result-JSON.", LogLevels.Warning);
|
AppendLog($"Automatisches NTFS-Ensure wurde nur simuliert fuer {result.AutomaticEnsurePreview.Count} DataAreas. Details stehen im Result-JSON.", LogLevels.Warning);
|
||||||
}
|
}
|
||||||
|
|
||||||
AppendLog($"DataAreas erhalten: {result.DataAreas.Count}");
|
AppendLog($"DataAreas erhalten: {result.DataAreas.Count}");
|
||||||
@@ -617,35 +617,21 @@ namespace LiamWorkflowDiagnostics
|
|||||||
|
|
||||||
await ExecuteProviderActionAsync("NTFS Folder Create", async () =>
|
await ExecuteProviderActionAsync("NTFS Folder Create", async () =>
|
||||||
{
|
{
|
||||||
var result = await Task.Run(() => LiamWorkflowRuntime.CreateDataAreaAsync(
|
var result = await RunWithWorkflowWhatIfAsync(provider, () => Task.Run(() => LiamWorkflowRuntime.CreateDataAreaAsync(
|
||||||
provider,
|
provider,
|
||||||
folderPath,
|
folderPath,
|
||||||
parentPath,
|
parentPath,
|
||||||
null,
|
null,
|
||||||
ownerSids,
|
ownerSids,
|
||||||
readerSids,
|
readerSids,
|
||||||
writerSids));
|
writerSids)));
|
||||||
|
|
||||||
return new
|
return new
|
||||||
{
|
{
|
||||||
result.Success,
|
result.Success,
|
||||||
ResultToken = MapResultToken(result.ResultToken)
|
ResultToken = MapResultToken(result.ResultToken)
|
||||||
};
|
};
|
||||||
}, () =>
|
}, actionHandlesWhatIf: true);
|
||||||
{
|
|
||||||
return CreateWhatIfResult(
|
|
||||||
"NTFS Folder Create",
|
|
||||||
"Wuerde einen Ordner anlegen und fehlende Gruppen sowie ACLs sicherstellen. Es wurden keine Aenderungen ausgefuehrt.",
|
|
||||||
new
|
|
||||||
{
|
|
||||||
ProviderRootPath = provider.RootPath,
|
|
||||||
NewFolderPath = folderPath,
|
|
||||||
ParentFolderPath = parentPath,
|
|
||||||
OwnerSids = ownerSids,
|
|
||||||
ReaderSids = readerSids,
|
|
||||||
WriterSids = writerSids
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -667,35 +653,21 @@ namespace LiamWorkflowDiagnostics
|
|||||||
|
|
||||||
await ExecuteProviderActionAsync("NTFS Ensure Groups / ACLs", async () =>
|
await ExecuteProviderActionAsync("NTFS Ensure Groups / ACLs", async () =>
|
||||||
{
|
{
|
||||||
var result = await Task.Run(() => LiamWorkflowRuntime.EnsureNtfsPermissionGroupsAsync(
|
var result = await RunWithWorkflowWhatIfAsync(provider, () => Task.Run(() => LiamWorkflowRuntime.EnsureNtfsPermissionGroupsAsync(
|
||||||
provider,
|
provider,
|
||||||
folderPath,
|
folderPath,
|
||||||
null,
|
null,
|
||||||
ownerSids,
|
ownerSids,
|
||||||
readerSids,
|
readerSids,
|
||||||
writerSids,
|
writerSids,
|
||||||
ensureTraverse));
|
ensureTraverse)));
|
||||||
|
|
||||||
return new
|
return new
|
||||||
{
|
{
|
||||||
result.Success,
|
result.Success,
|
||||||
ResultToken = MapResultToken(result.ResultToken)
|
ResultToken = MapResultToken(result.ResultToken)
|
||||||
};
|
};
|
||||||
}, () =>
|
}, actionHandlesWhatIf: true);
|
||||||
{
|
|
||||||
return CreateWhatIfResult(
|
|
||||||
"NTFS Ensure Groups / ACLs",
|
|
||||||
"Wuerde fehlende NTFS-Berechtigungsgruppen und ACLs additiv sicherstellen. Es wurden keine Aenderungen ausgefuehrt.",
|
|
||||||
new
|
|
||||||
{
|
|
||||||
ProviderRootPath = provider.RootPath,
|
|
||||||
FolderPath = folderPath,
|
|
||||||
OwnerSids = ownerSids,
|
|
||||||
ReaderSids = readerSids,
|
|
||||||
WriterSids = writerSids,
|
|
||||||
EnsureTraverseGroups = ensureTraverse
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -704,6 +676,34 @@ namespace LiamWorkflowDiagnostics
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async void ExecuteNtfsTraverseOnlyButton_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var provider = EnsureInitializedProvider<cLiamProviderNtfs>("NTFS");
|
||||||
|
var folderPath = GetRequiredText(NtfsEnsureFolderPathTextBox.Text, "Folder Path");
|
||||||
|
|
||||||
|
await ExecuteProviderActionAsync("NTFS Ensure Traverse Only", async () =>
|
||||||
|
{
|
||||||
|
var token = await Task.Run(() => provider.EnsureTraverseGroupsAsync(
|
||||||
|
folderPath,
|
||||||
|
null,
|
||||||
|
IsWhatIfEnabled));
|
||||||
|
|
||||||
|
return new
|
||||||
|
{
|
||||||
|
Success = token != null && token.resultErrorId == 0,
|
||||||
|
ResultToken = MapResultToken(token)
|
||||||
|
};
|
||||||
|
}, actionHandlesWhatIf: true);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
AppendLog($"NTFS Ensure Traverse Only fehlgeschlagen: {ex.Message}", LogLevels.Error);
|
||||||
|
MessageBox.Show(this, ex.ToString(), "NTFS Ensure Traverse Only", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async void ExecuteAdCreateButton_Click(object sender, RoutedEventArgs e)
|
private async void ExecuteAdCreateButton_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -950,7 +950,7 @@ namespace LiamWorkflowDiagnostics
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ExecuteProviderActionAsync(string actionName, Func<Task<object>> action, Func<object> whatIfAction = null)
|
private async Task ExecuteProviderActionAsync(string actionName, Func<Task<object>> action, Func<object> whatIfAction = null, bool actionHandlesWhatIf = false)
|
||||||
{
|
{
|
||||||
if (action == null)
|
if (action == null)
|
||||||
throw new ArgumentNullException(nameof(action));
|
throw new ArgumentNullException(nameof(action));
|
||||||
@@ -959,11 +959,11 @@ namespace LiamWorkflowDiagnostics
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
SaveSettings();
|
SaveSettings();
|
||||||
var runInWhatIfMode = IsWhatIfEnabled && whatIfAction != null;
|
var runInWhatIfMode = IsWhatIfEnabled && (whatIfAction != null || actionHandlesWhatIf);
|
||||||
AppendLog(runInWhatIfMode
|
AppendLog(runInWhatIfMode
|
||||||
? $"{actionName} im WhatIf-Modus gestartet. Schreibende Aenderungen werden nur simuliert."
|
? $"{actionName} im WhatIf-Modus gestartet. Schreibende Aenderungen werden nur simuliert."
|
||||||
: $"{actionName} gestartet.");
|
: $"{actionName} gestartet.");
|
||||||
var result = runInWhatIfMode
|
var result = runInWhatIfMode && whatIfAction != null
|
||||||
? await Task.Run(whatIfAction)
|
? await Task.Run(whatIfAction)
|
||||||
: await action();
|
: await action();
|
||||||
ResultTextBox.Text = JsonConvert.SerializeObject(result, Formatting.Indented);
|
ResultTextBox.Text = JsonConvert.SerializeObject(result, Formatting.Indented);
|
||||||
@@ -985,6 +985,39 @@ namespace LiamWorkflowDiagnostics
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<TResult> RunWithWorkflowWhatIfAsync<TResult>(cLiamProviderBase provider, Func<Task<TResult>> action)
|
||||||
|
{
|
||||||
|
if (provider == null)
|
||||||
|
throw new ArgumentNullException(nameof(provider));
|
||||||
|
if (action == null)
|
||||||
|
throw new ArgumentNullException(nameof(action));
|
||||||
|
|
||||||
|
if (!IsWhatIfEnabled)
|
||||||
|
return await action();
|
||||||
|
|
||||||
|
var additionalConfiguration = provider.AdditionalConfiguration;
|
||||||
|
if (additionalConfiguration == null)
|
||||||
|
{
|
||||||
|
additionalConfiguration = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
provider.AdditionalConfiguration = additionalConfiguration;
|
||||||
|
}
|
||||||
|
|
||||||
|
var hadWhatIf = additionalConfiguration.TryGetValue("WhatIf", out var previousWhatIf);
|
||||||
|
additionalConfiguration["WhatIf"] = "1";
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return await action();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (hadWhatIf)
|
||||||
|
additionalConfiguration["WhatIf"] = previousWhatIf;
|
||||||
|
else
|
||||||
|
additionalConfiguration.Remove("WhatIf");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private bool TryGetSuccessFlag(object instance, out bool success)
|
private bool TryGetSuccessFlag(object instance, out bool success)
|
||||||
{
|
{
|
||||||
success = false;
|
success = false;
|
||||||
|
|||||||
77
README.md
77
README.md
@@ -22,6 +22,9 @@ Die wichtigsten Eingaben fuer die Ersetzung sind:
|
|||||||
| `{{RELATIVEPATH(0)}}` | Letztes relatives Pfadsegment. | `test33` |
|
| `{{RELATIVEPATH(0)}}` | Letztes relatives Pfadsegment. | `test33` |
|
||||||
| `{{RELATIVEPATH(1)}}` | Die letzten zwei relativen Pfadsegmente. Bei weniger Segmenten werden alle vorhandenen verwendet. | z. B. `team1_test33` |
|
| `{{RELATIVEPATH(1)}}` | Die letzten zwei relativen Pfadsegmente. Bei weniger Segmenten werden alle vorhandenen verwendet. | z. B. `team1_test33` |
|
||||||
| `{{ROOT_SERVER}}` | Server-/Namespace-Teil des UNC-RootPath. | `server` |
|
| `{{ROOT_SERVER}}` | Server-/Namespace-Teil des UNC-RootPath. | `server` |
|
||||||
|
| `{{SERVER_NAME}}` | Server-/Namespace-Teil des aktuell verarbeiteten UNC-Pfads. | `server` |
|
||||||
|
| `{{DFS_NAMESPACE_NAME}}` | Name des DFS-Namespaces, wenn der aktuelle Pfad in einem DFS-Namespace liegt. | `File_Shares` |
|
||||||
|
| `{{SHARE_NAME}}` | Klassifizierter Share-Name: klassischer Share oder DFS-Link, in dem die aktuelle DataArea liegt. | `share2` |
|
||||||
| `{{ROOT_NAME}}` | Letztes Segment des RootPath. | `share2` |
|
| `{{ROOT_NAME}}` | Letztes Segment des RootPath. | `share2` |
|
||||||
| `{{ROOT_PATH}}` | Alle RootPath-Segmente ohne Server, mit Segmenttrenner verbunden. | `file_shares_share2` |
|
| `{{ROOT_PATH}}` | Alle RootPath-Segmente ohne Server, mit Segmenttrenner verbunden. | `file_shares_share2` |
|
||||||
| `{{ROOT_PATH(1)}}` | Letztes RootPath-Segment. | `share2` |
|
| `{{ROOT_PATH(1)}}` | Letztes RootPath-Segment. | `share2` |
|
||||||
@@ -31,6 +34,8 @@ Die wichtigsten Eingaben fuer die Ersetzung sind:
|
|||||||
|
|
||||||
`{{RELATIVEPATH(n)}}` zaehlt von hinten: `0` ist das letzte Segment, `1` sind die letzten zwei Segmente, `2` die letzten drei Segmente usw. `{{ROOT_PATH(n)}}` verwendet dagegen die letzten `n` Segmente des RootPath.
|
`{{RELATIVEPATH(n)}}` zaehlt von hinten: `0` ist das letzte Segment, `1` sind die letzten zwei Segmente, `2` die letzten drei Segmente usw. `{{ROOT_PATH(n)}}` verwendet dagegen die letzten `n` Segmente des RootPath.
|
||||||
|
|
||||||
|
`{{ROOT_*}}` bezieht sich immer auf den konfigurierten `RootPath`. `{{SERVER_NAME}}`, `{{DFS_NAMESPACE_NAME}}` und `{{SHARE_NAME}}` werden dagegen anhand der NTFS-Pfadklassifizierung des aktuell verarbeiteten Pfads ermittelt. Bei einem klassischen Share ist `{{SHARE_NAME}}` der Share. Bei DFS ist `{{DFS_NAMESPACE_NAME}}` der Namespace und `{{SHARE_NAME}}` der DFS-Link.
|
||||||
|
|
||||||
Wenn die aktuelle DataArea dem `RootPath` selbst entspricht, ist `{{RELATIVEPATH}}` leer. `{{NAME}}` ist dann der Name des RootPath, also z. B. `share2` oder `LEI.01.test`.
|
Wenn die aktuelle DataArea dem `RootPath` selbst entspricht, ist `{{RELATIVEPATH}}` leer. `{{NAME}}` ist dann der Name des RootPath, also z. B. `share2` oder `LEI.01.test`.
|
||||||
|
|
||||||
### Rollen- und Scope-Platzhalter
|
### Rollen- und Scope-Platzhalter
|
||||||
@@ -205,13 +210,18 @@ Im Diagnose-JSON erscheinen diese Werte unter `AdditionalConfiguration`. Paramet
|
|||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `EnsureNtfsPermissionGroups` | `true`, `1`, `yes` | Stellt beim Auslesen von NTFS-Ordnern automatisch fehlende AD-Berechtigungsgruppen und NTFS-ACLs sicher. |
|
| `EnsureNtfsPermissionGroups` | `true`, `1`, `yes` | Stellt beim Auslesen von NTFS-Ordnern automatisch fehlende AD-Berechtigungsgruppen und NTFS-ACLs sicher. |
|
||||||
| `EnsureNtfsPermissionGroupsForShares` | `true`, `1`, `yes` | Erweitert das automatische Ensure auf Share-DataAreas. Ohne diesen Parameter wird das automatische Ensure nur fuer Ordner ausgefuehrt. |
|
| `EnsureNtfsPermissionGroupsForShares` | `true`, `1`, `yes` | Erweitert das automatische Ensure auf Share-DataAreas. Ohne diesen Parameter wird das automatische Ensure nur fuer Ordner ausgefuehrt. |
|
||||||
|
| `EnsureNtfsTraverseGroups` | `true`, `1`, `yes` | Aktiviert die automatische Traverse-Gruppenverarbeitung als eigene Phase. Traverse-Gruppen und Traverse-ACLs werden sichergestellt; vorhandene LIAM-Owner/Write/Read-Globalgruppen werden in die naechste Traverse-Gruppe aufgenommen. |
|
||||||
| `AllowManualNtfsPermissionEnsureForShares` | `true`, `1`, `yes` | Erlaubt die manuelle Ensure-Aktivitaet auch fuer Share-DataAreas. |
|
| `AllowManualNtfsPermissionEnsureForShares` | `true`, `1`, `yes` | Erlaubt die manuelle Ensure-Aktivitaet auch fuer Share-DataAreas. |
|
||||||
| `NtfsIncludePaths` | Pfadliste, getrennt mit `;`, | oder Zeilenumbruechen | Beschraenkt die NTFS-Verarbeitung auf passende Pfade. Unterstuetzt relative Pfade unterhalb des RootPath, absolute UNC-Pfade und einfache Wildcards mit `*`. Wenn der Parameter leer ist, sind alle Pfade eingeschlossen. |
|
| `NtfsIncludePaths` | Pfadliste, getrennt mit `;`, | oder Zeilenumbruechen | Beschraenkt die NTFS-Verarbeitung auf passende Pfade. Unterstuetzt relative Pfade unterhalb des RootPath, absolute UNC-Pfade und einfache Wildcards mit `*`. Wenn der Parameter leer ist, sind alle Pfade eingeschlossen. |
|
||||||
| `NtfsExcludePaths` | Pfadliste, getrennt mit `;`, | oder Zeilenumbruechen | Schliesst passende Pfade von der NTFS-Verarbeitung aus. Excludes gewinnen gegen Includes. Unterstuetzt relative Pfade, absolute UNC-Pfade und einfache Wildcards mit `*`. |
|
| `NtfsExcludePaths` | Pfadliste, getrennt mit `;`, | oder Zeilenumbruechen | Schliesst passende Pfade von der NTFS-Verarbeitung aus. Excludes gewinnen gegen Includes. Unterstuetzt relative Pfade, absolute UNC-Pfade und einfache Wildcards mit `*`. |
|
||||||
| `NtfsTraverseBoundaryPath` | Relativer oder absoluter Pfad | Setzt eine Traverse-Grenze fuer die Traverse-Gruppenverarbeitung. Damit koennen Traverse-Gruppen ueber den eigentlichen Einsprung hinaus bis zu einer definierten Ebene sichergestellt werden. |
|
| `NtfsTraverseBoundaryPath` | Relativer oder absoluter Pfad | Setzt eine Traverse-Grenze fuer die Traverse-Gruppenverarbeitung. Damit koennen Traverse-Gruppen ueber den eigentlichen Einsprung hinaus bis zu einer definierten Ebene sichergestellt werden. Der Boundary-Pfad selbst darf ebenfalls als Traverse-Ziel verarbeitet werden. Wenn der Parameter leer ist, verwendet LIAM den `RootPath` als Traverse-Grenze. |
|
||||||
|
| `NtfsPermissionGroupsMinLevel` / `NtfsPermissionGroupsMaxLevel` | Ganzzahl, z. B. `0`, `1`, `2` | Optionaler Levelbereich fuer Owner/Write/Read-Ensure. Ebene `0` ist der `RootPath`; Unterordner sind `1`, `2`, `3`, ... Wenn beide Parameter leer sind, bleibt das bisherige Verhalten unveraendert. |
|
||||||
|
| `NtfsTraverseGroupsMinLevel` / `NtfsTraverseGroupsMaxLevel` | Ganzzahl, z. B. `-1`, `0`, `2` | Optionaler Levelbereich fuer Traverse-Ensure. Ebene `0` ist der `RootPath`; Parent-Pfade oberhalb des `RootPath` sind `-1`, `-2`, ... Wenn `NtfsTraverseGroupsMaxLevel` nicht gesetzt ist, wird fuer Traverse implizit `MaxDepth - 1` verwendet. |
|
||||||
| `NtfsGroupNameSanitizeReplacement` | Zeichenfolge, z. B. `_`, `.`, leer, `none`, `remove`, `<empty>` | Legt fest, womit ungueltige Zeichen in dynamischen gruppennamenrelevanten Pfadbestandteilen ersetzt werden. Standard ist `_`. Mit leerem Wert oder `none`/`remove`/`<empty>` werden ungueltige Zeichen entfernt und Pfadsegmente ohne Trennzeichen verbunden. |
|
| `NtfsGroupNameSanitizeReplacement` | Zeichenfolge, z. B. `_`, `.`, leer, `none`, `remove`, `<empty>` | Legt fest, womit ungueltige Zeichen in dynamischen gruppennamenrelevanten Pfadbestandteilen ersetzt werden. Standard ist `_`. Mit leerem Wert oder `none`/`remove`/`<empty>` werden ungueltige Zeichen entfernt und Pfadsegmente ohne Trennzeichen verbunden. |
|
||||||
| `PreserveNtfsAdGroupNameCase` | `true`, `1`, `yes` | Unterbindet das automatische Uppercase fuer generierte NTFS-AD-Gruppennamen. Ohne diesen Parameter werden generierte Gruppennamen wie bisher in Grossbuchstaben erzeugt. |
|
| `PreserveNtfsAdGroupNameCase` | `true`, `1`, `yes` | Unterbindet das automatische Uppercase fuer generierte NTFS-AD-Gruppennamen. Ohne diesen Parameter werden generierte Gruppennamen wie bisher in Grossbuchstaben erzeugt. |
|
||||||
| `ForceStrictAdGroupNames` | `true`, `1`, `yes` | Erzwingt strikte AD-Gruppennamen. Wildcard-/ACL-basierte Wiederverwendung abweichender bestehender Gruppen wird damit eingeschraenkt; es werden nur exakt passende konfigurierte oder generierte Namen verwendet. |
|
| `ForceStrictAdGroupNames` | `true`, `1`, `yes` | Erzwingt strikte AD-Gruppennamen. Wildcard-/ACL-basierte Wiederverwendung abweichender bestehender Gruppen wird damit eingeschraenkt; es werden nur exakt passende konfigurierte oder generierte Namen verwendet. |
|
||||||
|
| `NtfsAdGroupReuseMode` | `Safe`, `Name` | Steuert die Wiederverwendung vorhandener AD-Gruppen im automatischen Ensure. Standard ist `Safe`: vorhandene Gruppen werden nur wiederverwendet, wenn sie bereits passend auf der Ordner-ACL liegen oder einen passenden LIAM-Marker im AD-Attribut `info` besitzen. `Name` erlaubt die Legacy-Wiederverwendung nur anhand des Namens und wird als unsicherer Modus geloggt. |
|
||||||
|
| `NtfsAdGroupMarkerBackfill` | `true`, `1`, `yes` | Ergaenzt bei sicher wiederverwendeten, ACL-verknuepften Gruppen einen LIAM-Marker im AD-Attribut `info`, sofern das Attribut les- und schreibbar ist. |
|
||||||
| `NtfsAdDomainControllers` | Kommagetrennte DC-Liste, z. B. `dc01.contoso.local,dc02.contoso.local` | Pinnt NTFS-AD-Operationen auf einen Domain Controller. Der erste erreichbare DC wird verwendet. Wenn kein Eintrag erreichbar ist oder der Parameter fehlt, wird der PDC Emulator verwendet; danach faellt der Code auf die normale Domain-Locator-Logik zurueck. Der ausgewaehlte DC wird im Debug-Log protokolliert. |
|
| `NtfsAdDomainControllers` | Kommagetrennte DC-Liste, z. B. `dc01.contoso.local,dc02.contoso.local` | Pinnt NTFS-AD-Operationen auf einen Domain Controller. Der erste erreichbare DC wird verwendet. Wenn kein Eintrag erreichbar ist oder der Parameter fehlt, wird der PDC Emulator verwendet; danach faellt der Code auf die normale Domain-Locator-Logik zurueck. Der ausgewaehlte DC wird im Debug-Log protokolliert. |
|
||||||
|
|
||||||
Beispiele:
|
Beispiele:
|
||||||
@@ -219,14 +229,79 @@ Beispiele:
|
|||||||
```text
|
```text
|
||||||
EnsureNtfsPermissionGroups=1
|
EnsureNtfsPermissionGroups=1
|
||||||
EnsureNtfsPermissionGroupsForShares=1
|
EnsureNtfsPermissionGroupsForShares=1
|
||||||
|
EnsureNtfsTraverseGroups=1
|
||||||
NtfsIncludePaths=Finance\*;HR\Reports
|
NtfsIncludePaths=Finance\*;HR\Reports
|
||||||
NtfsExcludePaths=*\_archive\*
|
NtfsExcludePaths=*\_archive\*
|
||||||
NtfsTraverseBoundaryPath=\\fileserver\file_shares
|
NtfsTraverseBoundaryPath=\\fileserver\file_shares
|
||||||
|
NtfsPermissionGroupsMinLevel=1
|
||||||
|
NtfsPermissionGroupsMaxLevel=2
|
||||||
|
NtfsTraverseGroupsMinLevel=-1
|
||||||
|
NtfsTraverseGroupsMaxLevel=2
|
||||||
NtfsGroupNameSanitizeReplacement=.
|
NtfsGroupNameSanitizeReplacement=.
|
||||||
PreserveNtfsAdGroupNameCase=1
|
PreserveNtfsAdGroupNameCase=1
|
||||||
NtfsAdDomainControllers=dc01.contoso.local,dc02.contoso.local
|
NtfsAdDomainControllers=dc01.contoso.local,dc02.contoso.local
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Automatisches NTFS-Ensure
|
||||||
|
|
||||||
|
Beim Auslesen der NTFS-DataAreas kann LIAM Gruppen und ACLs automatisch sicherstellen. Die automatische Verarbeitung ist bewusst fehlertolerant: Wenn ein einzelner DataArea-Pfad wegen Whitelist, Blacklist, fehlendem Verzeichnis oder Providerfehler nicht verarbeitet werden kann, wird dieser Pfad im Log als Warning uebersprungen und die naechste DataArea weiter verarbeitet.
|
||||||
|
|
||||||
|
Die Schalter koennen kombiniert werden:
|
||||||
|
|
||||||
|
| Konfiguration | Verhalten |
|
||||||
|
| --- | --- |
|
||||||
|
| `EnsureNtfsPermissionGroups=1` | Erstellt oder reused Owner/Write/Read-Gruppen und setzt die passenden NTFS-ACLs fuer Ordner. Das bestehende Verhalten mit eingebetteter Traverse-Verarbeitung bleibt aktiv, solange `EnsureNtfsTraverseGroups` nicht gesetzt ist. |
|
||||||
|
| `EnsureNtfsPermissionGroups=1` und `EnsureNtfsPermissionGroupsForShares=1` | Wie oben, aber auch fuer Share-DataAreas. |
|
||||||
|
| `EnsureNtfsTraverseGroups=1` | Fuehrt eine separate Traverse-only-Phase fuer Ordner und Share-DataAreas aus. Es werden Traverse-Gruppen und Traverse-ACLs sichergestellt, aber keine Owner/Write/Read-Gruppen erzeugt. |
|
||||||
|
| `EnsureNtfsPermissionGroups=1` und `EnsureNtfsTraverseGroups=1` | Fuehrt zuerst Owner/Write/Read-Ensure aus und danach die separate Traverse-only-Phase. Dadurch koennen neu angelegte Owner/Write/Read-Globalgruppen direkt in die Traverse-Gruppen aufgenommen werden. Die eingebettete Traverse-Verarbeitung im Permission-Ensure wird in diesem Modus deaktiviert, damit Traverse nicht doppelt laeuft. |
|
||||||
|
|
||||||
|
Traverse-only sucht die erwarteten LIAM-Owner/Write/Read-Gruppen anhand der Naming-Conventions und bestehenden ACLs. Gefunden werden nur LIAM-Gruppen, die zur konfigurierten Wildcard passen; beliebige Legacy-ACL-Gruppen werden nicht automatisch als Traverse-Mitglieder uebernommen. Fehlende Owner/Write/Read-Gruppen werden in Traverse-only nicht angelegt, sondern als Warning protokolliert.
|
||||||
|
|
||||||
|
Fuer AGP werden die Global-Gruppen auf der ACL und als Traverse-Mitglieder verwendet. Fuer AGDLP liegen weiterhin die DomainLocal-Gruppen auf der NTFS-ACL; die Traverse-Mitgliedschaft verwendet die zugehoerigen Global-Gruppen.
|
||||||
|
|
||||||
|
Traverse-Gruppen werden lazy erzeugt: LIAM legt eine Traverse-Gruppe fuer einen Pfad nur dann an, wenn darunter mindestens eine LIAM-Owner/Write/Read-Gruppe oder eine darunterliegende Traverse-Gruppe aufgenommen werden soll. Bestehende Traverse-Gruppen werden weiterverwendet, leere neue Traverse-Gruppen ohne aktuelle Mitglieder werden aber nicht vorsorglich erzeugt.
|
||||||
|
|
||||||
|
#### Traverse-Grenze
|
||||||
|
|
||||||
|
`NtfsTraverseBoundaryPath` begrenzt, bis zu welcher Ebene Traverse-Gruppen und Traverse-ACLs sichergestellt werden. Wenn der Parameter nicht gesetzt ist, verwendet LIAM den `RootPath` als Traverse-Grenze. Damit darf die Traverse-Verarbeitung standardmaessig auch Parent-Pfade oberhalb des Include-Filters erreichen, solange der Pfad existiert und nicht durch `NtfsExcludePaths` ausgeschlossen ist.
|
||||||
|
|
||||||
|
Beispiel:
|
||||||
|
|
||||||
|
```text
|
||||||
|
RootPath=\\intra.brkr.corp\File_Shares\BRE.01.LEW
|
||||||
|
NtfsIncludePaths=\\intra.brkr.corp\File_Shares\BRE.01.LEW\Projekte\*
|
||||||
|
EnsureNtfsTraverseGroups=1
|
||||||
|
```
|
||||||
|
|
||||||
|
In diesem Beispiel ist keine explizite Boundary noetig, weil `RootPath` automatisch als Traverse-Grenze verwendet wird. Dadurch koennen Traverse-Gruppen fuer `...\BRE.01.LEW`, `...\Projekte` und die Ordner darunter sichergestellt werden, sobald darunter Berechtigungsgruppen oder weitere Traverse-Gruppen aufzunehmen sind. Ein abweichender `NtfsTraverseBoundaryPath` ist nur noetig, wenn die Traverse-Verarbeitung bewusst oberhalb oder unterhalb des `RootPath` enden soll.
|
||||||
|
|
||||||
|
#### Levelbereiche fuer Ensure und Ordneranlage
|
||||||
|
|
||||||
|
Die optionalen Levelbereiche schraenken ein, auf welchen Ebenen Gruppen und ACLs automatisch sichergestellt werden. Wenn keine Permission-Level-Parameter gesetzt sind, bleibt Owner/Write/Read unveraendert. Fuer Traverse wird ohne explizites `NtfsTraverseGroupsMaxLevel` automatisch `MaxDepth - 1` als maximale Verarbeitungsebene verwendet.
|
||||||
|
|
||||||
|
Ebene `0` ist immer der konfigurierte `RootPath`. Unterordner unterhalb des `RootPath` haben positive Level (`1`, `2`, `3`, ...). Parent-Pfade oberhalb des `RootPath` haben negative Level (`-1`, `-2`, ...). Negative Level sind vor allem fuer Traverse relevant, wenn `NtfsTraverseBoundaryPath` oberhalb des `RootPath` liegt.
|
||||||
|
|
||||||
|
Beispiel:
|
||||||
|
|
||||||
|
```text
|
||||||
|
RootPath=\\server\share\Bereich
|
||||||
|
NtfsTraverseBoundaryPath=\\server\share
|
||||||
|
NtfsPermissionGroupsMinLevel=1
|
||||||
|
NtfsPermissionGroupsMaxLevel=2
|
||||||
|
NtfsTraverseGroupsMinLevel=-1
|
||||||
|
NtfsTraverseGroupsMaxLevel=2
|
||||||
|
```
|
||||||
|
|
||||||
|
Damit werden Owner/Write/Read-Gruppen nur fuer `\\server\share\Bereich\...` auf Ebene `1` und `2` erzeugt. Traverse-Gruppen duerfen zusaetzlich bis zum Share auf Ebene `-1` erzeugt werden. `NtfsTraverseBoundaryPath` bleibt die harte Grenze fuer Traverse; die Min-/Max-Level legen nur fest, welche Ebenen innerhalb dieses Korridors verarbeitet werden.
|
||||||
|
|
||||||
|
Die Permission-Level gelten auch fuer die explizite NTFS-Ordneranlage ueber `CreateDataArea`. Wenn der Zielordner ausserhalb des Permission-Levelbereichs liegt oder die Permission-Levelkonfiguration ungueltig ist, wird die Ordneranlage vor AD-/ACL-Aenderungen abgebrochen. Traverse-Level blockieren die Ordneranlage nicht; sie schraenken nur die Traverse-Gruppen und Traverse-ACLs ein.
|
||||||
|
|
||||||
|
Wenn nur ein Min- oder Max-Wert gesetzt ist, ist die andere Seite offen. Ausnahme: Fuer Traverse ist ein fehlender Max-Wert durch `MaxDepth - 1` begrenzt. Ungueltige Werte oder ein Min-Wert groesser als der Max-Wert fuehren dazu, dass die betroffene Ensure-Phase mit einer Warning uebersprungen wird; bei `CreateDataArea` fuehrt eine ungueltige Permission-Levelkonfiguration zum Abbruch vor der Anlage.
|
||||||
|
|
||||||
|
#### WhatIf / Preview
|
||||||
|
|
||||||
|
Wenn `WhatIf=1` gesetzt ist, fuehrt LIAM die automatische NTFS-Verarbeitung als Preview aus. Fuer Permission-Ensure und Traverse-Ensure werden geplante Gruppen, wiederverwendete Gruppen, geplante ACLs, bestehende ACLs, Traverse-Gruppen und Warnings im Ergebnis und im Debug-Log ausgegeben. Es werden keine AD-Gruppen, Gruppenmitgliedschaften oder ACLs dauerhaft geaendert.
|
||||||
|
|
||||||
### Active Directory
|
### Active Directory
|
||||||
|
|
||||||
| Parameter | Werte | Wirkung |
|
| Parameter | Werte | Wirkung |
|
||||||
|
|||||||
@@ -7,5 +7,5 @@ using System.Runtime.InteropServices;
|
|||||||
[assembly: AssemblyCopyright("Copyright © 2026, Consulting4IT GmbH, Germany")]
|
[assembly: AssemblyCopyright("Copyright © 2026, Consulting4IT GmbH, Germany")]
|
||||||
[assembly: AssemblyTrademark("")]
|
[assembly: AssemblyTrademark("")]
|
||||||
|
|
||||||
[assembly: AssemblyInformationalVersion("3.3.3")]
|
[assembly: AssemblyInformationalVersion("3.3.5")]
|
||||||
[assembly: AssemblyVersion("3.3.3")]
|
[assembly: AssemblyVersion("3.3.5")]
|
||||||
|
|||||||
695
Sonstiges/New-LiamNtfsDemoShareStructure.ps1
Normal file
695
Sonstiges/New-LiamNtfsDemoShareStructure.ps1
Normal file
@@ -0,0 +1,695 @@
|
|||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Creates a local LIAM NTFS demo fixture with folders and ACL groups from a production log.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
This script recreates the BRE.01.LEW/Projekte folder and ACL-group fixture from
|
||||||
|
the diagnostic log in the local imagoverum.com demo environment:
|
||||||
|
|
||||||
|
- local folder: C:\file_shares\share2
|
||||||
|
- optional SMB share: \\<local-server>\file_shares\share2
|
||||||
|
- group strategy: Ntfs_AGDLP
|
||||||
|
- group tags: FS for global groups, UG for domain-local groups
|
||||||
|
- access tags: _O, _W, _R, _T
|
||||||
|
|
||||||
|
By default the script creates the exact folders visible in the log and creates
|
||||||
|
the logged ACL groups in the configured OU, then grants them Read permissions
|
||||||
|
on the matching local folders. Expected LIAM groups can be created separately
|
||||||
|
with -CreateExpectedLiamGroups.
|
||||||
|
|
||||||
|
Run with -WhatIf first. The ActiveDirectory module is only required when
|
||||||
|
AD group creation and ACL assignment are enabled.
|
||||||
|
#>
|
||||||
|
|
||||||
|
[CmdletBinding(SupportsShouldProcess = $true)]
|
||||||
|
param(
|
||||||
|
[string]$ShareRootPath = 'C:\file_shares',
|
||||||
|
|
||||||
|
[string]$RootFolderName = 'share2',
|
||||||
|
|
||||||
|
[string]$SmbShareName = 'file_shares',
|
||||||
|
|
||||||
|
[string]$GroupOuDN = 'OU=AGP,OU=LIAM,OU=Global,DC=imagoverum,DC=com',
|
||||||
|
|
||||||
|
[string[]]$AdditionalFolderRelativePaths = @(),
|
||||||
|
|
||||||
|
[string]$GroupNameSanitizeReplacement = '_',
|
||||||
|
|
||||||
|
[switch]$PreserveAdGroupNameCase,
|
||||||
|
|
||||||
|
[switch]$CreateExpectedLiamGroups,
|
||||||
|
|
||||||
|
[switch]$SkipLogAclFixture,
|
||||||
|
|
||||||
|
[switch]$SkipAdGroups,
|
||||||
|
|
||||||
|
[switch]$SkipNtfsAcl,
|
||||||
|
|
||||||
|
[switch]$SkipSmbShare
|
||||||
|
)
|
||||||
|
|
||||||
|
Set-StrictMode -Version Latest
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
|
||||||
|
if ([System.Environment]::OSVersion.Platform -ne [System.PlatformID]::Win32NT) {
|
||||||
|
throw 'Dieses Script kann nur unter Windows ausgefuehrt werden.'
|
||||||
|
}
|
||||||
|
|
||||||
|
$rootPath = Join-Path -Path $ShareRootPath -ChildPath $RootFolderName
|
||||||
|
$readRights = [System.Security.AccessControl.FileSystemRights]0x200A9
|
||||||
|
$writeRights = [System.Security.AccessControl.FileSystemRights]0x301BF
|
||||||
|
$ownerRights = [System.Security.AccessControl.FileSystemRights]0x1F01FF
|
||||||
|
|
||||||
|
$logFolderRelativePaths = @(
|
||||||
|
'Arbeitsgruppen',
|
||||||
|
'Quartalsplanungen',
|
||||||
|
'Projekte',
|
||||||
|
'Projekte\__Templ',
|
||||||
|
'Projekte\_BAMS_AD Projekte',
|
||||||
|
'Projekte\__Admin Only',
|
||||||
|
'Projekte\_LSMS_AD Projekte',
|
||||||
|
'Projekte\_SW Projekte',
|
||||||
|
'Projekte\_AD Projekte',
|
||||||
|
'Projekte\_Templ_BMID',
|
||||||
|
'Projekte\_HW Projekte',
|
||||||
|
'Projekte\_IVD_Projekte',
|
||||||
|
'ProjekteAbgeschlossen',
|
||||||
|
'RESTORE',
|
||||||
|
'Design Control Harmonization',
|
||||||
|
'PMO'
|
||||||
|
)
|
||||||
|
|
||||||
|
$logAclFixtures = @(
|
||||||
|
@{
|
||||||
|
RelativePath = ''
|
||||||
|
Groups = @(
|
||||||
|
'UG_BRE.BDAL.DE.Users',
|
||||||
|
'DALDE-gArchimedesLEW_ALL_RO',
|
||||||
|
'DALDE-gArchimedesLEW_Admins',
|
||||||
|
'DALDE-gArchimedesLEW_Projekte_Browse',
|
||||||
|
'UG_BRE.01',
|
||||||
|
'FS_BRE.01.Archimedes_LEW-RW',
|
||||||
|
'AG_ETT.01.File Server Administrators_Restricted'
|
||||||
|
)
|
||||||
|
},
|
||||||
|
@{
|
||||||
|
RelativePath = 'Projekte'
|
||||||
|
Groups = @(
|
||||||
|
'UG_BRE.BDAL.DE.Users',
|
||||||
|
'DALDE-gArchimedesLEW_AccessGroup_PAW',
|
||||||
|
'DALDE-gArchimedesLEW_AccessGroup_SVC',
|
||||||
|
'DALDE-gArchimedesLEW_AccessGroup_TER',
|
||||||
|
'DALDE-gArchimedesLEW_ALL_RO',
|
||||||
|
'DALDE-gArchimedesLEW_Admins',
|
||||||
|
'DALDE-gArchimedesLEW_AccessGroup_PRM',
|
||||||
|
'DALDE-gArchimedesLEW_AccessGroup_QS',
|
||||||
|
'DALDE-gArchimedesLEW_AccessGroup_WB',
|
||||||
|
'DALDE-gArchimedesLEW_RO_Leipzig',
|
||||||
|
'DALDE-gArchimedesLEW_AccessGroup_BOM_RO',
|
||||||
|
'DALDE-gArchimedesLEW_AccessGroup_EEL',
|
||||||
|
'DALDE-gArchimedesLEW_AccessGroup_TPS',
|
||||||
|
'DALDE-gArchimedesLEW_RW',
|
||||||
|
'DALDE-gArchimedesLEW_AccessGroup_KST',
|
||||||
|
'DALDE-gArchimedesLEW_AccessGroup_ESW_SW_Only',
|
||||||
|
'DALDE-_Autocad',
|
||||||
|
'DALDE-gArchimedesLEW_AccessGroup_APP',
|
||||||
|
'DALDE-gArchimedesLEW_Projekte_RO',
|
||||||
|
'DALDE-gArchimedesLEW_AccessGroup_ESW_SW_HW',
|
||||||
|
'DALDE-gArchimedesLEW_AccessGroup_EPT',
|
||||||
|
'DALDE-gArchimedesLEW_Projekte_Browse',
|
||||||
|
'UG_BRE.01',
|
||||||
|
'AG_ETT.01.File Server Administrators_Restricted'
|
||||||
|
)
|
||||||
|
},
|
||||||
|
@{
|
||||||
|
RelativePath = 'Projekte\__Templ'
|
||||||
|
Groups = @(
|
||||||
|
'UG_BRE.BDAL.DE.Users',
|
||||||
|
'DALDE-gArchimedesLEW_Projekte_Browse',
|
||||||
|
'UG_BRE.01',
|
||||||
|
'FG_BRE.01.ArchimedesLEW_AccessGroup_APP',
|
||||||
|
'FG_BRE.01.ArchimedesLEW_AccessGroup_GL',
|
||||||
|
'FG_BRE.01.ArchimedesLEW_AccessGroup_EEl',
|
||||||
|
'FG_BRE.01.ArchimedesLEW_AccessGroup_EpT',
|
||||||
|
'FG_BRE.01.ArchimedesLEW_AccessGroup_ESw',
|
||||||
|
'FG_BRE.01.ArchimedesLEW_AccessGroup_G&L',
|
||||||
|
'FG_BRE.01.ArchimedesLEW_AccessGroup_KSt',
|
||||||
|
'FG_BRE.01.ArchimedesLEW_AccessGroup_PRM',
|
||||||
|
'FG_BRE.01.ArchimedesLEW_AccessGroup_QS',
|
||||||
|
'FG_BRE.01.ArchimedesLEW_AccessGroup_Svc',
|
||||||
|
'FG_BRE.01.ArchimedesLEW_AccessGroup_TeR',
|
||||||
|
'FG_BRE.01.ArchimedesLEW_AccessGroup_TPS',
|
||||||
|
'FG_BRE.01.ArchimedesLEW_AccessGroup_RA_LSMS',
|
||||||
|
'FG_BRE.01.ArchimedesLEW_AccessGroup_Proc',
|
||||||
|
'FG_BRE.01.ArchimedesLEW_AccessGroup_BD',
|
||||||
|
'FG_BRE.01.ArchimedesLEW_AccessGroup_Mark',
|
||||||
|
'FG_BRE.01.ArchimedesLEW_AccessGroup_Fin',
|
||||||
|
'AG_ETT.01.File Server Administrators_Restricted'
|
||||||
|
)
|
||||||
|
},
|
||||||
|
@{
|
||||||
|
RelativePath = 'Projekte\_BAMS_AD Projekte'
|
||||||
|
Groups = @(
|
||||||
|
'AG_ETT.01.File Server Administrators_Restricted'
|
||||||
|
)
|
||||||
|
},
|
||||||
|
@{
|
||||||
|
RelativePath = 'Projekte\__Admin Only'
|
||||||
|
Groups = @(
|
||||||
|
'DALDE-gArchimedesLEW_Admins',
|
||||||
|
'AG_ETT.01.File Server Administrators_Restricted'
|
||||||
|
)
|
||||||
|
},
|
||||||
|
@{
|
||||||
|
RelativePath = 'Projekte\_LSMS_AD Projekte'
|
||||||
|
Groups = @(
|
||||||
|
'AG_ETT.01.File Server Administrators_Restricted'
|
||||||
|
)
|
||||||
|
},
|
||||||
|
@{
|
||||||
|
RelativePath = 'Projekte\_SW Projekte'
|
||||||
|
Groups = @(
|
||||||
|
'DALDE-gArchimedesLEW_AccessGroup_PAW',
|
||||||
|
'DALDE-gArchimedesLEW_AccessGroup_SVC',
|
||||||
|
'DALDE-gArchimedesLEW_SWProjekte_RW',
|
||||||
|
'DALDE-gArchimedesLEW_AccessGroup_TER',
|
||||||
|
'DALDE-gArchimedesLEW_ManagerSW_RW',
|
||||||
|
'DALDE-gArchimedesLEW_Admins',
|
||||||
|
'DALDE-gArchimedesLEW_AccessGroup_PRM',
|
||||||
|
'DALDE-gArchimedesLEW_AccessGroup_QS',
|
||||||
|
'DALDE-gArchimedesLEW_AccessGroup_WB',
|
||||||
|
'DALDE-gArchimedesLEW_SWProjekte_RO',
|
||||||
|
'DALDE-gArchimedesLEW_AccessGroup_BOM_RO',
|
||||||
|
'DALDE-gArchimedesLEW_AccessGroup_EEL',
|
||||||
|
'DALDE-gArchimedesLEW_AccessGroup_TPS',
|
||||||
|
'DALDE-gArchimedesLEW_AccessGroup_KST',
|
||||||
|
'DALDE-gArchimedesLEW_AccessGroup_ESW_SW_Only',
|
||||||
|
'DALDE-gArchimedesLEW_AccessGroup_APP',
|
||||||
|
'DALDE-gArchimedesLEW_AccessGroup_ESW_SW_HW',
|
||||||
|
'DALDE-gArchimedesLEW_AccessGroup_EPT',
|
||||||
|
'DALDE-gArchimedesLEW_Projekte_Browse',
|
||||||
|
'AG_ETT.01.File Server Administrators_Restricted'
|
||||||
|
)
|
||||||
|
},
|
||||||
|
@{
|
||||||
|
RelativePath = 'Projekte\_AD Projekte'
|
||||||
|
Groups = @(
|
||||||
|
'UG_BRE.BDAL.DE.Users',
|
||||||
|
'DALDE-gArchimedesLEW_AccessGroup_PAW',
|
||||||
|
'DALDE-gArchimedesLEW_AccessGroup_SVC',
|
||||||
|
'DALDE-gArchimedesLEW_AccessGroup_TER',
|
||||||
|
'DALDE-gArchimedesLEW_ALL_RO',
|
||||||
|
'DALDE-gArchimedesLEW_AccessGroup_PRM',
|
||||||
|
'DALDE-gArchimedesLEW_AccessGroup_QS',
|
||||||
|
'DALDE-gArchimedesLEW_AccessGroup_WB',
|
||||||
|
'DALDE-gArchimedesLEW_RO_Leipzig',
|
||||||
|
'DALDE-gArchimedesLEW_AccessGroup_BOM_RO',
|
||||||
|
'DALDE-gArchimedesLEW_AccessGroup_EEL',
|
||||||
|
'DALDE-gArchimedesLEW_AccessGroup_TPS',
|
||||||
|
'DALDE-gArchimedesLEW_RW',
|
||||||
|
'DALDE-gArchimedesLEW_AccessGroup_KST',
|
||||||
|
'DALDE-gArchimedesLEW_RO',
|
||||||
|
'DALDE-gArchimedesLEW_AccessGroup_ESW_SW_Only',
|
||||||
|
'DALDE-_Autocad',
|
||||||
|
'DALDE-gArchimedesLEW_AccessGroup_APP',
|
||||||
|
'DALDE-gArchimedesLEW_Projekte_RO',
|
||||||
|
'DALDE-gArchimedesLEW_AccessGroup_ESW_SW_HW',
|
||||||
|
'DALDE-gArchimedesLEW_AccessGroup_EPT',
|
||||||
|
'DALDE-gArchimedesLEW_Projekte_Browse',
|
||||||
|
'UG_BRE.01',
|
||||||
|
'FS_BRE.01.Archimedes_LEW_Projekte_AD Projekte RW',
|
||||||
|
'FS_BRE.01.Archimedes_LEW_Projekte_AD Projekte RO',
|
||||||
|
'AG_ETT.01.File Server Administrators_Restricted'
|
||||||
|
)
|
||||||
|
},
|
||||||
|
@{
|
||||||
|
RelativePath = 'Projekte\_Templ_BMID'
|
||||||
|
Groups = @(
|
||||||
|
'AG_ETT.01.File Server Administrators_Restricted'
|
||||||
|
)
|
||||||
|
},
|
||||||
|
@{
|
||||||
|
RelativePath = 'Projekte\_HW Projekte'
|
||||||
|
Groups = @(
|
||||||
|
'DALDE-gArchimedesLEW_AccessGroup_PAW',
|
||||||
|
'DALDE-gArchimedesLEW_AccessGroup_SVC',
|
||||||
|
'DALDE-gArchimedesLEW_AccessGroup_FSt',
|
||||||
|
'DALDE-gArchimedesLEW_ManagerHW_RW',
|
||||||
|
'DALDE-gArchimedesLEW_AccessGroup_GL',
|
||||||
|
'DALDE-gArchimedesLEW_AccessGroup_TER',
|
||||||
|
'DALDE-gArchimedesLEW_AccessGroup_EBIO',
|
||||||
|
'DALDE-gArchimedesLEW_AccessGroup_PRM',
|
||||||
|
'DALDE-gArchimedesLEW_HWProjekt_RO',
|
||||||
|
'DALDE-gArchimedesLEW_AccessGroup_QS',
|
||||||
|
'DALDE-gArchimedesLEW_AccessGroup_WB',
|
||||||
|
'DALDE-gArchimedesLEW_AccessGroup_BOM_RO',
|
||||||
|
'DALDE-gArchimedesLEW_HWProjekt_RFAP1_RO',
|
||||||
|
'DALDE-gArchimedesLEW_AccessGroup_EEL',
|
||||||
|
'DALDE-gArchimedesLEW_AccessGroup_TPS',
|
||||||
|
'DALDE-gArchimedesLEW_AccessGroup_KST',
|
||||||
|
'DALDE-gArchimedesLEW_AccessGroup_APP',
|
||||||
|
'DALDE-gArchimedesLEW_AccessGroup_ESW_SW_HW',
|
||||||
|
'DALDE-gArchimedesLEW_AccessGroup_EPT',
|
||||||
|
'DALDE-gArchimedesLEW_Projekte_Browse',
|
||||||
|
'UG_BRE.01.ArchimedesLEW_Projekte_HW Projekte RW',
|
||||||
|
'AG_ETT.01.File Server Administrators_Restricted'
|
||||||
|
)
|
||||||
|
},
|
||||||
|
@{
|
||||||
|
RelativePath = 'Projekte\_IVD_Projekte'
|
||||||
|
Groups = @(
|
||||||
|
'DALDE-gArchimedesLEW_IVD_Projekte_RW',
|
||||||
|
'FS_BRE.01.ArchimedesLEW_Projekte_IVD Projekte RW',
|
||||||
|
'FS_BRE.01.ArchimedesLEW_Projekte_IVD Projekte RO',
|
||||||
|
'AG_ETT.01.File Server Administrators_Restricted'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
function ConvertTo-LiamSafeNameSegment {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$Value,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$Replacement,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[bool]$PreserveCase
|
||||||
|
)
|
||||||
|
|
||||||
|
$safeReplacement = if ($null -eq $Replacement) { '_' } else { $Replacement.Trim() }
|
||||||
|
if ($safeReplacement -in @('<empty>', 'empty', 'none', 'remove')) {
|
||||||
|
$safeReplacement = ''
|
||||||
|
}
|
||||||
|
if ([string]::IsNullOrWhiteSpace($safeReplacement) -and $Replacement -notin @('<empty>', 'empty', 'none', 'remove')) {
|
||||||
|
$safeReplacement = '_'
|
||||||
|
}
|
||||||
|
|
||||||
|
$safeValue = [regex]::Replace($Value, '[\x00-\x1F\x7F/\\\[\]:;\|=,\+\*\?<>]', $safeReplacement)
|
||||||
|
if ($PreserveCase) {
|
||||||
|
return $safeValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return $safeValue.ToUpperInvariant()
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-LiamFolderToken {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$FolderPath
|
||||||
|
)
|
||||||
|
|
||||||
|
$itemName = Split-Path -Path $FolderPath -Leaf
|
||||||
|
if ([string]::IsNullOrWhiteSpace($itemName)) {
|
||||||
|
$itemName = $RootFolderName
|
||||||
|
}
|
||||||
|
|
||||||
|
ConvertTo-LiamSafeNameSegment `
|
||||||
|
-Value $itemName `
|
||||||
|
-Replacement $GroupNameSanitizeReplacement `
|
||||||
|
-PreserveCase $PreserveAdGroupNameCase.IsPresent
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-LiamGroupSet {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$FolderPath
|
||||||
|
)
|
||||||
|
|
||||||
|
$name = Get-LiamFolderToken -FolderPath $FolderPath
|
||||||
|
|
||||||
|
[pscustomobject]@{
|
||||||
|
FolderPath = $FolderPath
|
||||||
|
Name = $name
|
||||||
|
GlobalOwner = "FS_${name}_O"
|
||||||
|
GlobalWrite = "FS_${name}_W"
|
||||||
|
GlobalRead = "FS_${name}_R"
|
||||||
|
GlobalTraverse = "FS_${name}_T"
|
||||||
|
LocalOwner = "UG_${name}_O"
|
||||||
|
LocalWrite = "UG_${name}_W"
|
||||||
|
LocalRead = "UG_${name}_R"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Ensure-Folder {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$Path
|
||||||
|
)
|
||||||
|
|
||||||
|
if (Test-Path -LiteralPath $Path) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($PSCmdlet.ShouldProcess($Path, 'Create directory')) {
|
||||||
|
New-Item -ItemType Directory -Path $Path -Force | Out-Null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Ensure-SmbShare {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$Name,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$Path
|
||||||
|
)
|
||||||
|
|
||||||
|
$existingShare = Get-SmbShare -Name $Name -ErrorAction SilentlyContinue
|
||||||
|
if ($existingShare) {
|
||||||
|
if ($existingShare.Path -ne $Path) {
|
||||||
|
Write-Warning "SMB share '$Name' already points to '$($existingShare.Path)', not '$Path'."
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($PSCmdlet.ShouldProcess($Name, "Create SMB share for '$Path'")) {
|
||||||
|
New-SmbShare -Name $Name -Path $Path -ChangeAccess 'Authenticated Users' | Out-Null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function ConvertTo-LdapFilterEscapedValue {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$Value
|
||||||
|
)
|
||||||
|
|
||||||
|
$escapedValue = $Value.Replace('\', '\5c')
|
||||||
|
$escapedValue = $escapedValue.Replace('*', '\2a')
|
||||||
|
$escapedValue = $escapedValue.Replace('(', '\28')
|
||||||
|
$escapedValue = $escapedValue.Replace(')', '\29')
|
||||||
|
$escapedValue = $escapedValue.Replace([string][char]0, '\00')
|
||||||
|
|
||||||
|
return $escapedValue
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-AdGroupBySamAccountName {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$SamAccountName,
|
||||||
|
|
||||||
|
[string[]]$Properties = @()
|
||||||
|
)
|
||||||
|
|
||||||
|
$escapedSamAccountName = ConvertTo-LdapFilterEscapedValue -Value $SamAccountName
|
||||||
|
$parameters = @{
|
||||||
|
LDAPFilter = "(sAMAccountName=$escapedSamAccountName)"
|
||||||
|
ErrorAction = 'Stop'
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($Properties.Count -gt 0) {
|
||||||
|
$parameters['Properties'] = $Properties
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return Get-ADGroup @parameters | Select-Object -First 1
|
||||||
|
}
|
||||||
|
catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException] {
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Ensure-AdGroup {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$Name,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[ValidateSet('Global', 'DomainLocal')]
|
||||||
|
[string]$Scope,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$Description
|
||||||
|
)
|
||||||
|
|
||||||
|
$existingGroup = Get-AdGroupBySamAccountName -SamAccountName $Name
|
||||||
|
if ($existingGroup) {
|
||||||
|
return $existingGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($PSCmdlet.ShouldProcess($Name, "Create AD group in '$GroupOuDN'")) {
|
||||||
|
New-ADGroup `
|
||||||
|
-Name $Name `
|
||||||
|
-SamAccountName $Name `
|
||||||
|
-GroupCategory Security `
|
||||||
|
-GroupScope $Scope `
|
||||||
|
-Path $GroupOuDN `
|
||||||
|
-Description $Description | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
return Get-AdGroupBySamAccountName -SamAccountName $Name
|
||||||
|
}
|
||||||
|
|
||||||
|
function Ensure-AdGroupMembership {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$ParentGroup,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$MemberGroup
|
||||||
|
)
|
||||||
|
|
||||||
|
$parent = Get-AdGroupBySamAccountName -SamAccountName $ParentGroup -Properties @('member')
|
||||||
|
$member = Get-AdGroupBySamAccountName -SamAccountName $MemberGroup
|
||||||
|
if (-not $parent -or -not $member) {
|
||||||
|
if ($WhatIfPreference) {
|
||||||
|
if ($PSCmdlet.ShouldProcess($ParentGroup, "Add member group '$MemberGroup'")) {
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
throw "Cannot add '$MemberGroup' to '$ParentGroup' because at least one group does not exist."
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($parent.member -contains $member.DistinguishedName) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($PSCmdlet.ShouldProcess($ParentGroup, "Add member group '$MemberGroup'")) {
|
||||||
|
Add-ADGroupMember -Identity $parent -Members $member
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Ensure-LiamGroups {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[pscustomobject]$GroupSet
|
||||||
|
)
|
||||||
|
|
||||||
|
$groups = @(
|
||||||
|
@{ Name = $GroupSet.GlobalOwner; Scope = 'Global'; Description = "$($GroupSet.Name) - _O" }
|
||||||
|
@{ Name = $GroupSet.GlobalWrite; Scope = 'Global'; Description = "$($GroupSet.Name) - _W" }
|
||||||
|
@{ Name = $GroupSet.GlobalRead; Scope = 'Global'; Description = "$($GroupSet.Name) - _R" }
|
||||||
|
@{ Name = $GroupSet.LocalOwner; Scope = 'DomainLocal'; Description = "$($GroupSet.Name) - _O" }
|
||||||
|
@{ Name = $GroupSet.LocalWrite; Scope = 'DomainLocal'; Description = "$($GroupSet.Name) - _W" }
|
||||||
|
@{ Name = $GroupSet.LocalRead; Scope = 'DomainLocal'; Description = "$($GroupSet.Name) - _R" }
|
||||||
|
@{ Name = $GroupSet.GlobalTraverse; Scope = 'Global'; Description = "$($GroupSet.Name) - _T" }
|
||||||
|
)
|
||||||
|
|
||||||
|
foreach ($group in $groups) {
|
||||||
|
Ensure-AdGroup -Name $group.Name -Scope $group.Scope -Description $group.Description | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
Ensure-AdGroupMembership -ParentGroup $GroupSet.LocalOwner -MemberGroup $GroupSet.GlobalOwner
|
||||||
|
Ensure-AdGroupMembership -ParentGroup $GroupSet.LocalWrite -MemberGroup $GroupSet.GlobalWrite
|
||||||
|
Ensure-AdGroupMembership -ParentGroup $GroupSet.LocalRead -MemberGroup $GroupSet.GlobalRead
|
||||||
|
}
|
||||||
|
|
||||||
|
function Ensure-LogAclFixtureGroups {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[object[]]$Fixtures
|
||||||
|
)
|
||||||
|
|
||||||
|
$groupNames = $Fixtures |
|
||||||
|
ForEach-Object { $_['Groups'] } |
|
||||||
|
Where-Object { -not [string]::IsNullOrWhiteSpace($_) } |
|
||||||
|
Sort-Object -Unique
|
||||||
|
|
||||||
|
foreach ($groupName in $groupNames) {
|
||||||
|
$groupScope = if ($groupName -like 'UG_*') { 'DomainLocal' } else { 'Global' }
|
||||||
|
Ensure-AdGroup `
|
||||||
|
-Name $groupName `
|
||||||
|
-Scope $groupScope `
|
||||||
|
-Description 'Source ACL group from BRE.01.LEW diagnostic log' | Out-Null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Add-FolderAccessRule {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$Path,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$Account,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[System.Security.AccessControl.FileSystemRights]$Rights
|
||||||
|
)
|
||||||
|
|
||||||
|
if (-not (Test-Path -LiteralPath $Path)) {
|
||||||
|
if ($WhatIfPreference) {
|
||||||
|
if ($PSCmdlet.ShouldProcess($Path, "Add NTFS ACL '$Rights' for '$Account'")) {
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
throw "Path not found: $Path"
|
||||||
|
}
|
||||||
|
|
||||||
|
$acl = Get-Acl -LiteralPath $Path
|
||||||
|
$identity = [System.Security.Principal.NTAccount]::new($Account)
|
||||||
|
try {
|
||||||
|
$sid = $identity.Translate([System.Security.Principal.SecurityIdentifier])
|
||||||
|
}
|
||||||
|
catch [System.Security.Principal.IdentityNotMappedException] {
|
||||||
|
if ($WhatIfPreference) {
|
||||||
|
if ($PSCmdlet.ShouldProcess($Path, "Add NTFS ACL '$Rights' for '$Account'")) {
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
throw
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($rule in $acl.Access) {
|
||||||
|
if ($rule.AccessControlType -ne [System.Security.AccessControl.AccessControlType]::Allow) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if ($rule.IdentityReference.Translate([System.Security.Principal.SecurityIdentifier]).Value -ne $sid.Value) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (($rule.FileSystemRights -band $Rights) -eq $Rights) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$accessRule = [System.Security.AccessControl.FileSystemAccessRule]::new(
|
||||||
|
$sid,
|
||||||
|
$Rights,
|
||||||
|
[System.Security.AccessControl.InheritanceFlags]'ContainerInherit,ObjectInherit',
|
||||||
|
[System.Security.AccessControl.PropagationFlags]::None,
|
||||||
|
[System.Security.AccessControl.AccessControlType]::Allow
|
||||||
|
)
|
||||||
|
|
||||||
|
$acl.AddAccessRule($accessRule)
|
||||||
|
|
||||||
|
if ($PSCmdlet.ShouldProcess($Path, "Add NTFS ACL '$Rights' for '$Account'")) {
|
||||||
|
Set-Acl -LiteralPath $Path -AclObject $acl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Ensure-LiamNtfsAcl {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[pscustomobject]$GroupSet,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$DomainNetBiosName
|
||||||
|
)
|
||||||
|
|
||||||
|
Add-FolderAccessRule -Path $GroupSet.FolderPath -Account "$DomainNetBiosName\$($GroupSet.LocalOwner)" -Rights $ownerRights
|
||||||
|
Add-FolderAccessRule -Path $GroupSet.FolderPath -Account "$DomainNetBiosName\$($GroupSet.LocalWrite)" -Rights $writeRights
|
||||||
|
Add-FolderAccessRule -Path $GroupSet.FolderPath -Account "$DomainNetBiosName\$($GroupSet.LocalRead)" -Rights $readRights
|
||||||
|
}
|
||||||
|
|
||||||
|
function Ensure-LogAclFixtureAcls {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[object[]]$Fixtures,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$DomainNetBiosName
|
||||||
|
)
|
||||||
|
|
||||||
|
foreach ($fixture in $Fixtures) {
|
||||||
|
$relativePath = $fixture['RelativePath']
|
||||||
|
$folderPath = if ([string]::IsNullOrWhiteSpace($relativePath)) {
|
||||||
|
$rootPath
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Join-Path -Path $rootPath -ChildPath $relativePath
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($groupName in ($fixture['Groups'] | Sort-Object -Unique)) {
|
||||||
|
Add-FolderAccessRule `
|
||||||
|
-Path $folderPath `
|
||||||
|
-Account "$DomainNetBiosName\$groupName" `
|
||||||
|
-Rights $readRights
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$relativeFolders = @($logFolderRelativePaths + $AdditionalFolderRelativePaths) |
|
||||||
|
Where-Object { -not [string]::IsNullOrWhiteSpace($_) } |
|
||||||
|
ForEach-Object { $_.Trim().TrimStart('\', '/') } |
|
||||||
|
Sort-Object -Unique
|
||||||
|
|
||||||
|
Ensure-Folder -Path $ShareRootPath
|
||||||
|
Ensure-Folder -Path $rootPath
|
||||||
|
|
||||||
|
foreach ($relativeFolder in $relativeFolders) {
|
||||||
|
Ensure-Folder -Path (Join-Path -Path $rootPath -ChildPath $relativeFolder)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not $SkipSmbShare) {
|
||||||
|
Ensure-SmbShare -Name $SmbShareName -Path $ShareRootPath
|
||||||
|
}
|
||||||
|
|
||||||
|
$foldersToManage = @($rootPath)
|
||||||
|
foreach ($relativeFolder in $relativeFolders) {
|
||||||
|
$foldersToManage += (Join-Path -Path $rootPath -ChildPath $relativeFolder)
|
||||||
|
}
|
||||||
|
|
||||||
|
$domainNetBiosName = $null
|
||||||
|
if (-not $SkipAdGroups -or -not $SkipNtfsAcl) {
|
||||||
|
Import-Module ActiveDirectory -ErrorAction Stop
|
||||||
|
$domainNetBiosName = (Get-ADDomain).NetBIOSName
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not $SkipAdGroups -and -not $SkipLogAclFixture) {
|
||||||
|
Ensure-LogAclFixtureGroups -Fixtures $logAclFixtures
|
||||||
|
}
|
||||||
|
|
||||||
|
$summary = foreach ($folder in $foldersToManage) {
|
||||||
|
$groupSet = Get-LiamGroupSet -FolderPath $folder
|
||||||
|
|
||||||
|
if (-not $SkipAdGroups -and $CreateExpectedLiamGroups) {
|
||||||
|
Ensure-LiamGroups -GroupSet $groupSet
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not $SkipNtfsAcl -and $CreateExpectedLiamGroups) {
|
||||||
|
Ensure-LiamNtfsAcl -GroupSet $groupSet -DomainNetBiosName $domainNetBiosName
|
||||||
|
}
|
||||||
|
|
||||||
|
$relativePath = if ($folder -eq $rootPath) {
|
||||||
|
''
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$folder.Substring($rootPath.Length).TrimStart('\')
|
||||||
|
}
|
||||||
|
$fixture = $logAclFixtures | Where-Object { $_['RelativePath'] -eq $relativePath } | Select-Object -First 1
|
||||||
|
|
||||||
|
[pscustomobject]@{
|
||||||
|
Folder = $folder
|
||||||
|
LogAclGroupCount = if ($fixture) { $fixture['Groups'].Count } else { 0 }
|
||||||
|
LocalOwnerAcl = $groupSet.LocalOwner
|
||||||
|
LocalWriteAcl = $groupSet.LocalWrite
|
||||||
|
LocalReadAcl = $groupSet.LocalRead
|
||||||
|
GlobalOwner = $groupSet.GlobalOwner
|
||||||
|
GlobalWrite = $groupSet.GlobalWrite
|
||||||
|
GlobalRead = $groupSet.GlobalRead
|
||||||
|
GlobalTraverse = $groupSet.GlobalTraverse
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not $SkipNtfsAcl -and -not $SkipLogAclFixture) {
|
||||||
|
Ensure-LogAclFixtureAcls -Fixtures $logAclFixtures -DomainNetBiosName $domainNetBiosName
|
||||||
|
}
|
||||||
|
|
||||||
|
$summary
|
||||||
|
|
||||||
|
Write-Host ''
|
||||||
|
Write-Host 'LIAM provider RootPath for this machine:'
|
||||||
|
Write-Host ("\\{0}\{1}\{2}" -f $env:COMPUTERNAME, $SmbShareName, $RootFolderName)
|
||||||
Reference in New Issue
Block a user