using C4IT_IAM; using C4IT_IAM_Engine; using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.DirectoryServices; using System.DirectoryServices.AccountManagement; using System.IO; using System.Linq; using System.Net; using System.Security; using System.Security.AccessControl; using System.Security.Principal; using System.Text.RegularExpressions; using C4IT.Logging; using static C4IT.Logging.cLogManager; using System.Reflection; using C4IT_IAM_GET; namespace C4IT_IAM_SET { public class DataArea_FileSystem { public const string constApplicationDataPath = "%ProgramData%\\Consulting4IT GmbH\\LIAM"; public string domainName; public string username; public SecureString password; private cNetworkConnection Connection; public string groupPrefix; public string groupOUPath; public string baseFolder; public string newFolderPath; public string newFolder; public string newFolderParent; public PermissionGroupStrategy groupPermissionStrategy; public string groupDescriptionTemplate; public string groupNamingTemplate; public string groupDLTag; public string groupGTag; public string groupOwnerTag; public string groupReadTag; public string groupWriteTag; public string groupTraverseTag; public string groupWildcard; public IDictionary groupCustomTags; public ICollection ownerUserSids; public ICollection readerUserSids; public ICollection writerUserSids; public Func CanManagePermissionsForPath; public bool forceStrictAdGroupNames; public bool WhatIf; public int ReadACLPermission = 0x200A9; public int WriteACLPermission = 0x301BF; public int OwnerACLPermission = 0x1F01FF; public string ConfigID; Dictionary adGroupDic; public DataArea newDataArea; public SecurityGroups newSecurityGroups; public List templates; public int createTraverseGroupLvl = 0; public DataArea_FileSystem() { var logDirectory = Environment.ExpandEnvironmentVariables(constApplicationDataPath); Helper.CreatePathWithWriteAccess(logDirectory); var LogPath = Path.Combine(logDirectory, "Logs"); cLogManagerFile.CreateInstance(Path.Combine(LogPath, "LIAM.log")); DefaultLogger.LogEntry(LogLevels.Info, "================================================="); DefaultLogger.LogEntry(LogLevels.Info, $"LIAM engine v{Assembly.GetExecutingAssembly().GetName().Version} started"); templates = new List(); } private ResultToken checkRequiredVariables() { ResultToken resultToken = new ResultToken(System.Reflection.MethodBase.GetCurrentMethod().ToString()); resultToken.resultErrorId = 0; if (String.IsNullOrEmpty(ConfigID)) { resultToken.resultErrorId = 30001; resultToken.resultMessage = "Kein ConfigID gewählt "; return resultToken; } if (string.IsNullOrEmpty(username) | String.IsNullOrEmpty(new NetworkCredential("", password).Password) | String.IsNullOrEmpty(domainName)) { resultToken.resultErrorId = 30002; resultToken.resultMessage = "Fehlende Anmeldeinformationen"; return resultToken; } if (String.IsNullOrEmpty(groupPrefix)) { resultToken.resultErrorId = 30004; resultToken.resultMessage = "Kein Gruppen Präfix angegeben"; return resultToken; } if (String.IsNullOrEmpty(newFolderPath)) { resultToken.resultErrorId = 30005; resultToken.resultMessage = "Kein Pfad für neues Verzeichnis angegeben"; return resultToken; } if (String.IsNullOrEmpty(newFolderParent)) { resultToken.resultErrorId = 30006; resultToken.resultMessage = "Kein Pfad für neues Übergeordnetesverzeichnis angegeben"; return resultToken; } if (String.IsNullOrEmpty(baseFolder)) { resultToken.resultErrorId = 30007; resultToken.resultMessage = "Kein Basisverzeichnis angegeben"; return resultToken; } return resultToken; } public ResultToken createDataArea() { LogMethodBegin(MethodBase.GetCurrentMethod()); try { ResultToken resultToken = new ResultToken(System.Reflection.MethodBase.GetCurrentMethod().ToString()); resultToken.resultErrorId = 0; if (checkRequiredVariables().resultErrorId == 0) { InitializeFolderContext(); try { // ImpersonationHelper.Impersonate(domainName, username, new NetworkCredential("", password).Password, delegate // { if (Connection != null) Connection.Dispose(); DefaultLogger.LogEntry(LogLevels.Info, $"Establishing connection to {baseFolder}, User: {username}, Password: {Helper.MaskAllButLastAndFirst(new NetworkCredential("", password).Password)}"); using (Connection = new cNetworkConnection(baseFolder, username, new NetworkCredential("", password).Password)) { var folderCheckResult = checkFolder(); if (folderCheckResult.resultErrorId == 0) { try { createADGroups(resultToken); try { resultToken = MergeResultTokens(resultToken, createFolder()); if (resultToken.resultErrorId == 0) { try { resultToken = MergeResultTokens(resultToken, SetTraversePermissions()); } catch (Exception e) { resultToken.resultErrorId = 30200; resultToken.resultMessage = "Fehler beim setzen der Traverserechte \n" + e.Message; } } } catch (Exception e) { resultToken.resultErrorId = 30200; resultToken.resultMessage = "Fehler beim Erstellen der Verzeichnisse \n" + e.Message; } } catch (Exception e) { resultToken.resultErrorId = 30100; resultToken.resultMessage = "Fehler beim Erstellen der AD Gruppen \n" + e.Message; } } else { resultToken = folderCheckResult; } /* }, logonType, logonProvider); */ } } catch (Exception e) { resultToken.resultErrorId = 30000; resultToken.resultMessage = "Fehler beim Herstellen der Verbindung \n " + e.Message; } } else { return checkRequiredVariables(); } return resultToken; } catch (Exception E) { cLogManager.DefaultLogger.LogException(E); throw; } finally { LogMethodEnd(MethodBase.GetCurrentMethod()); } } private ResultToken MergeResultTokens(ResultToken target, ResultToken source) { if (target == null) return source; if (source == null) return target; if (source.resultErrorId != 0 || target.resultErrorId == 0) target.resultErrorId = source.resultErrorId; if (!string.IsNullOrWhiteSpace(source.resultMessage)) target.resultMessage = source.resultMessage; if (!string.IsNullOrWhiteSpace(source.resultFunction)) target.resultFunction = source.resultFunction; target.createdGroups.AddRange(source.createdGroups); target.reusedGroups.AddRange(source.reusedGroups); target.addedAclEntries.AddRange(source.addedAclEntries); target.skippedAclEntries.AddRange(source.skippedAclEntries); target.ensuredTraverseGroups.AddRange(source.ensuredTraverseGroups); target.warnings.AddRange(source.warnings); return target; } private ResultToken checkRequiredVariablesForEnsure() { ResultToken resultToken = new ResultToken(System.Reflection.MethodBase.GetCurrentMethod().ToString()); resultToken.resultErrorId = 0; if (String.IsNullOrEmpty(ConfigID)) { resultToken.resultErrorId = 30001; resultToken.resultMessage = "Kein ConfigID gewählt "; return resultToken; } if (string.IsNullOrEmpty(username) | String.IsNullOrEmpty(new NetworkCredential("", password).Password) | String.IsNullOrEmpty(domainName)) { resultToken.resultErrorId = 30002; resultToken.resultMessage = "Fehlende Anmeldeinformationen"; return resultToken; } if (String.IsNullOrEmpty(groupPrefix)) { resultToken.resultErrorId = 30004; resultToken.resultMessage = "Kein Gruppen Präfix angegeben"; return resultToken; } if (String.IsNullOrEmpty(newFolderPath)) { resultToken.resultErrorId = 30005; resultToken.resultMessage = "Kein Pfad für Verzeichnis angegeben"; return resultToken; } if (String.IsNullOrEmpty(baseFolder)) { resultToken.resultErrorId = 30007; resultToken.resultMessage = "Kein Basisverzeichnis angegeben"; return resultToken; } return resultToken; } private void InitializeFolderContext() { newDataArea = new DataArea(); var folder = new IAM_Folder { configurationID = ConfigID, technicalName = newFolderPath, targetType = (int)IAM_TargetType.FileSystem, Parent = newFolderParent, ParentUID = string.IsNullOrWhiteSpace(newFolderParent) ? string.Empty : DataArea.GetUniqueDataAreaID(newFolderParent), baseFolder = baseFolder }; newDataArea.IAM_Folders.Add(folder); newSecurityGroups = new SecurityGroups { username = username, domainName = domainName, password = password, ForceStrictAdGroupNames = forceStrictAdGroupNames }; } public ResultToken ensureDataAreaPermissions(bool ensureTraverseGroups = false) { LogMethodBegin(MethodBase.GetCurrentMethod()); try { var resultToken = checkRequiredVariablesForEnsure(); if (resultToken.resultErrorId != 0) return resultToken; if (Connection != null) Connection.Dispose(); using (Connection = new cNetworkConnection(baseFolder, username, new NetworkCredential("", password).Password)) { if (!Directory.Exists(newFolderPath)) { resultToken.resultErrorId = 30203; resultToken.resultMessage = "Verzeichnis existiert nicht"; return resultToken; } var parentDirectory = Directory.GetParent(newFolderPath); if (string.IsNullOrWhiteSpace(newFolderParent)) newFolderParent = parentDirectory?.FullName; InitializeFolderContext(); ensureADGroups(resultToken); resultToken = ensureFolderPermissions(resultToken); if (resultToken.resultErrorId != 0) return resultToken; if (ensureTraverseGroups) { if (WhatIf) { resultToken.warnings.Add("Traverse group preview is not supported in WhatIf mode for automatic DataArea ensure."); 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; } } catch (Exception E) { cLogManager.DefaultLogger.LogException(E); throw; } finally { LogMethodEnd(MethodBase.GetCurrentMethod()); } } private ResultToken SetTraversePermissions() { LogMethodBegin(MethodBase.GetCurrentMethod()); try { ResultToken resultToken = new ResultToken(System.Reflection.MethodBase.GetCurrentMethod().ToString()); resultToken.resultErrorId = 0; // Loggen der DomainContext-Parameter DefaultLogger.LogEntry(LogLevels.Debug, $"DomainName: {domainName}, Username: {username}"); if (string.IsNullOrEmpty(domainName) || string.IsNullOrEmpty(username) || password is null) { DefaultLogger.LogEntry(LogLevels.Error, "Eines der DomainContext-Parameter ist null oder leer."); return resultToken; } var domainContext = new PrincipalContext(ContextType.Domain, domainName, username, new NetworkCredential("", password).Password); DefaultLogger.LogEntry(LogLevels.Debug, "PrincipalContext erfolgreich erstellt."); // Überprüfen von newDataArea und IAM_Folders if (newDataArea == null) { DefaultLogger.LogEntry(LogLevels.Error, "newDataArea ist null."); return resultToken; } if (newDataArea.IAM_Folders == null || newDataArea.IAM_Folders.Count == 0) { DefaultLogger.LogEntry(LogLevels.Error, "IAM_Folders ist null oder leer."); return resultToken; } DirectoryInfo newDir = new DirectoryInfo(newDataArea.IAM_Folders[0].technicalName); DefaultLogger.LogEntry(LogLevels.Debug, $"Neues Verzeichnis: {newDir.FullName}"); DirectoryInfo parent = newDir.Parent; if (parent == null) { DefaultLogger.LogEntry(LogLevels.Error, "Parent-Verzeichnis ist null."); return resultToken; } DefaultLogger.LogEntry(LogLevels.Debug, $"Parent-Verzeichnis: {parent.FullName}"); var lvl = DataArea.GetRelativePath(parent.FullName, baseFolder).Count(n => n == Path.DirectorySeparatorChar); DefaultLogger.LogEntry(LogLevels.Debug, $"Ebene (lvl): {lvl}"); // Überprüfen der Templates if (templates == null) { DefaultLogger.LogEntry(LogLevels.Error, "templates ist null."); return resultToken; } var traverseGroupTemplate = templates.FirstOrDefault(t => t.Type.Equals(SecurityGroupType.Traverse)); if (traverseGroupTemplate == null) { DefaultLogger.LogEntry(LogLevels.Error, "traverseGroupTemplate ist null."); return resultToken; } DefaultLogger.LogEntry(LogLevels.Debug, $"traverseGroupTemplate gefunden"); // Überprüfen der traverseGroupTemplate-Eigenschaften if (traverseGroupTemplate.WildcardTemplate == null) { DefaultLogger.LogEntry(LogLevels.Error, "WildcardTemplate von traverseGroupTemplate ist null."); return resultToken; } DefaultLogger.LogEntry(LogLevels.Debug, $"traverseGroupTemplate.WildcardTemplate: {traverseGroupTemplate.WildcardTemplate}"); if (traverseGroupTemplate.NamingTemplate == null) { DefaultLogger.LogEntry(LogLevels.Error, "NamingTemplate von traverseGroupTemplate ist null."); return resultToken; } DefaultLogger.LogEntry(LogLevels.Debug, $"traverseGroupTemplate.NamingTemplate: {traverseGroupTemplate.NamingTemplate}"); if (string.IsNullOrEmpty(baseFolder)) { DefaultLogger.LogEntry(LogLevels.Error, "baseFolder ist null oder leer."); return resultToken; } GroupPrincipal traverseGroup = null; // Überprüfen, ob createTraverseGroupLvl initialisiert ist DefaultLogger.LogEntry(LogLevels.Debug, $"createTraverseGroupLvl: {createTraverseGroupLvl}"); if (createTraverseGroupLvl == -1) { DefaultLogger.LogEntry(LogLevels.Error, "createTraverseGroupLvl ist auf -1 gesetzt."); return resultToken; } for (int i = lvl; i >= createTraverseGroupLvl; i--) { DefaultLogger.LogEntry(LogLevels.Debug, $"Verarbeite Ebene {i}."); if (parent == null) { DefaultLogger.LogEntry(LogLevels.Error, "Parent ist null innerhalb der Schleife."); break; } if (CanManagePermissionsForPath != null && !CanManagePermissionsForPath(parent.FullName)) { DefaultLogger.LogEntry(LogLevels.Debug, $"Überspringe Traverse-Verarbeitung für nicht verwaltbaren NTFS-Pfad: {parent.FullName}"); parent = parent.Parent; if (parent != null) { lvl = DataArea.GetRelativePath(parent.FullName, baseFolder).Count(n => n == Path.DirectorySeparatorChar); DefaultLogger.LogEntry(LogLevels.Debug, $"Neue Ebene (lvl) nach Überspringen: {lvl}"); } else { DefaultLogger.LogEntry(LogLevels.Debug, "Parent nach Überspringen ist null."); } continue; } DefaultLogger.LogEntry(LogLevels.Debug, $"Hole ACL für Ordner: {parent.FullName}"); AuthorizationRuleCollection ACLs = null; try { ACLs = parent.GetAccessControl(AccessControlSections.Access).GetAccessRules(true, true, typeof(NTAccount)); DefaultLogger.LogEntry(LogLevels.Debug, "ACLs erfolgreich abgerufen."); } catch (Exception ex) { DefaultLogger.LogEntry(LogLevels.Error, $"Fehler beim Abrufen der ACLs: {ex.Message}"); continue; // Weiter zur nächsten Iteration } if (ACLs == null) { DefaultLogger.LogEntry(LogLevels.Error, "ACLs ist null."); continue; } GroupPrincipal parentTraverseGroup = null; var parentTraverseAclExists = false; string relativePathRaw = DataArea.GetRelativePath(parent.FullName, baseFolder).Trim(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); relativePathRaw = relativePathRaw.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); DefaultLogger.LogEntry(LogLevels.Debug, $"relativePath vor Normalisierung: {relativePathRaw}"); var relativePathSegments = relativePathRaw.Split(new[] { Path.DirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries); var sanitizedSegments = relativePathSegments.Select(Helper.SanitizePathSegment).ToArray(); var relativePath = sanitizedSegments.Length > 0 ? string.Join("_", sanitizedSegments) : string.Empty; DefaultLogger.LogEntry(LogLevels.Debug, $"relativePath nach Normalisierung: {relativePath}"); var folderName = sanitizedSegments.Length > 0 ? sanitizedSegments[sanitizedSegments.Length - 1] : Helper.SanitizePathSegment(Path.GetFileName(parent.FullName.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar))); var boundedTraverseContext = Helper.GetBoundedAdGroupTemplateContext( traverseGroupTemplate.NamingTemplate, true, relativePath, sanitizedSegments, folderName, null, Helper.MaxAdGroupNameLength, $"Traverse fuer '{parent.FullName}'"); var boundedTraverseDescriptionContext = Helper.GetBoundedAdGroupTemplateContext( traverseGroupTemplate.DescriptionTemplate, true, relativePath, sanitizedSegments, folderName, null, Helper.MaxAdGroupDescriptionLength, $"Traverse fuer '{parent.FullName}'", "AD-Gruppenbeschreibung"); var adjustedTraverseSegments = boundedTraverseContext.SanitizedSegments ?? Array.Empty(); var adjustedTraverseRelativePath = adjustedTraverseSegments.Length > 0 ? string.Join("_", adjustedTraverseSegments) : string.Empty; var adjustedTraverseFolderName = boundedTraverseContext.FolderName; var adjustedTraverseDescriptionSegments = boundedTraverseDescriptionContext.SanitizedSegments ?? Array.Empty(); var adjustedTraverseDescriptionRelativePath = adjustedTraverseDescriptionSegments.Length > 0 ? string.Join("_", adjustedTraverseDescriptionSegments) : string.Empty; var adjustedTraverseDescriptionFolderName = boundedTraverseDescriptionContext.FolderName; var traverseNameTemplate = Helper.ApplyTemplatePlaceholders(traverseGroupTemplate.NamingTemplate, true, adjustedTraverseRelativePath, adjustedTraverseSegments, adjustedTraverseFolderName); var traverseDescriptionTemplate = Helper.ApplyTemplatePlaceholders(traverseGroupTemplate.DescriptionTemplate, true, adjustedTraverseDescriptionRelativePath, adjustedTraverseDescriptionSegments, adjustedTraverseDescriptionFolderName); string traverseRegex = null; try { traverseRegex = Helper.ApplyTemplatePlaceholders(traverseGroupTemplate.WildcardTemplate, true, adjustedTraverseRelativePath, adjustedTraverseSegments, adjustedTraverseFolderName); DefaultLogger.LogEntry(LogLevels.Debug, $"traverseRegex: {traverseRegex}"); } catch (Exception ex) { DefaultLogger.LogEntry(LogLevels.Error, $"Fehler bei der Erstellung von traverseRegex: {ex.Message}"); continue; } foreach (FileSystemAccessRule acl in ACLs) { var searchString = acl.IdentityReference.Value; var aclSplit = searchString.Split('\\'); if (aclSplit.Length == 2) { searchString = aclSplit[1]; } DefaultLogger.LogEntry(LogLevels.Debug, $"Suche GroupPrincipal für: {searchString}"); var princ = GroupPrincipal.FindByIdentity(domainContext, searchString); if (princ != null) { DefaultLogger.LogEntry(LogLevels.Debug, $"Gefundene Gruppe: {princ.Name}"); } else { DefaultLogger.LogEntry(LogLevels.Debug, $"Keine Gruppe gefunden für: {searchString}"); } if (princ != null && Regex.IsMatch(princ.Name, traverseRegex, RegexOptions.IgnoreCase)) { parentTraverseGroup = princ; parentTraverseAclExists = true; DefaultLogger.LogEntry(LogLevels.Debug, $"parentTraverseGroup gesetzt: {parentTraverseGroup.Name}"); } if (parentTraverseGroup != null) break; } if (parentTraverseGroup == null && !string.IsNullOrEmpty(traverseNameTemplate)) { for (var loop = 0; loop < 20; loop++) { var candidateName = traverseNameTemplate.ReplaceLoopTag(loop); parentTraverseGroup = GroupPrincipal.FindByIdentity(domainContext, candidateName); if (parentTraverseGroup == null) continue; resultToken.reusedGroups.Add(candidateName); resultToken.ensuredTraverseGroups.Add(candidateName); DefaultLogger.LogEntry(LogLevels.Debug, $"Vorhandene Traverse-Gruppe wiederverwendet: {candidateName}"); break; } } if (parentTraverseGroup == null && !traverseGroupTemplate.NamingTemplate.Equals(string.Empty)) { DefaultLogger.LogEntry(LogLevels.Debug, "Erstelle neue TraverseGroup."); if (newSecurityGroups == null) { DefaultLogger.LogEntry(LogLevels.Error, "newSecurityGroups ist null."); continue; } IAM_SecurityGroup newTraverseGroup = null; var loop = 0; do { try { newTraverseGroup = new IAM_SecurityGroup() { Name = traverseNameTemplate.ReplaceLoopTag(loop), description = traverseDescriptionTemplate.ReplaceLoopTag(loop), technicalName = "CN=" + traverseNameTemplate.ReplaceLoopTag(loop) + "," + groupOUPath, Scope = traverseGroupTemplate.Scope }; DefaultLogger.LogEntry(LogLevels.Debug, $"Erstellte TraverseGroup: {newTraverseGroup.Name} (Loop: {loop})"); loop++; } catch (Exception ex) { DefaultLogger.LogEntry(LogLevels.Error, $"Fehler beim Erstellen von newTraverseGroup: {ex.Message}"); break; } } while (newSecurityGroups.GroupAllreadyExisting(newTraverseGroup.Name.ToUpper()) && loop < 20); if (newTraverseGroup != null) { if (string.IsNullOrEmpty(groupOUPath)) { DefaultLogger.LogEntry(LogLevels.Error, "groupOUPath ist null oder leer."); continue; } if (parent.Parent != null) { DefaultLogger.LogEntry(LogLevels.Debug, "Parent.Parent ist nicht null. Erstelle AD-Gruppe."); if (WhatIf) { resultToken.createdGroups.Add(newTraverseGroup.Name); resultToken.ensuredTraverseGroups.Add(newTraverseGroup.Name); resultToken.warnings.Add($"Traverse-Gruppe würde angelegt werden: {newTraverseGroup.Name}"); 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 { DefaultLogger.LogEntry(LogLevels.Debug, "Parent.Parent ist null. Traverse-ACL kann nicht gesetzt werden."); } } } if (parentTraverseGroup != null && !parentTraverseAclExists) { try { var accessControl = parent.GetAccessControl(); var rules = accessControl.GetAccessRules(true, true, typeof(SecurityIdentifier)).Cast(); var hasAcl = rules.Any(rule => rule.AccessControlType == AccessControlType.Allow && rule.IdentityReference.Value == parentTraverseGroup.Sid.Value && (rule.FileSystemRights & FileSystemRights.Read) == FileSystemRights.Read); if (hasAcl) { resultToken.skippedAclEntries.Add(parentTraverseGroup.Name); } else { if (!WhatIf) { accessControl.AddAccessRule(new FileSystemAccessRule(parentTraverseGroup.Sid, FileSystemRights.Read, InheritanceFlags.None, PropagationFlags.None, AccessControlType.Allow)); parent.SetAccessControl(accessControl); } resultToken.addedAclEntries.Add(parentTraverseGroup.Name); } } catch (Exception ex) { DefaultLogger.LogEntry(LogLevels.Error, $"Fehler beim Sicherstellen der Traverse-ACL: {ex.Message}"); continue; } } if (parentTraverseGroup != null) { if (i == lvl) { DefaultLogger.LogEntry(LogLevels.Debug, "Verarbeite SecurityGroups bei oberster Ebene."); foreach (var currentSecGroup in newSecurityGroups.IAM_SecurityGroups) { if (currentSecGroup == null) { 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 { if (traverseGroup != null && parentTraverseGroup != null) { try { if (!parentTraverseGroup.Members.Contains(traverseGroup)) { if (WhatIf) { resultToken.warnings.Add($"Traverse-Gruppe '{parentTraverseGroup.Name}' würde verschachtelte Gruppe '{traverseGroup.Name}' erhalten."); } else { if (!TryEnsureNestedTraverseGroupMembershipWithRetry(parentTraverseGroup, traverseGroup)) continue; } } } catch (Exception ex) { DefaultLogger.LogEntry(LogLevels.Error, $"Fehler beim Hinzufügen der Traverse-Gruppe: {ex.Message}"); continue; } } } try { if (!WhatIf) { parentTraverseGroup.Save(); DefaultLogger.LogEntry(LogLevels.Debug, $"parentTraverseGroup gespeichert: {parentTraverseGroup.Name}"); } } catch (Exception ex) { DefaultLogger.LogEntry(LogLevels.Error, $"Fehler beim Speichern der parentTraverseGroup: {ex.Message}"); } } else { DefaultLogger.LogEntry(LogLevels.Debug, "parentTraverseGroup ist null."); } if (parentTraverseGroup != null && !resultToken.ensuredTraverseGroups.Contains(parentTraverseGroup.Name)) resultToken.ensuredTraverseGroups.Add(parentTraverseGroup.Name); // Aktualisiere parent und lvl für die nächste Iteration parent = parent.Parent; if (parent != null) { lvl = DataArea.GetRelativePath(parent.FullName, baseFolder).Count(n => n == Path.DirectorySeparatorChar); DefaultLogger.LogEntry(LogLevels.Debug, $"Neue Ebene (lvl) nach Aktualisierung: {lvl}"); } else { DefaultLogger.LogEntry(LogLevels.Debug, "Parent nach Aktualisierung ist null."); } } return resultToken; } catch (Exception E) { cLogManager.DefaultLogger.LogException(E); throw; } finally { LogMethodEnd(MethodBase.GetCurrentMethod()); } } private bool TryEnsureGlobalGroupMembershipWithRetry(PrincipalContext domainContext, GroupPrincipal parentTraverseGroup, IAM_SecurityGroup currentSecGroup) { if (domainContext == null || parentTraverseGroup == null || currentSecGroup == null || string.IsNullOrWhiteSpace(currentSecGroup.UID)) return false; return RetryTraverseMembershipAction( currentSecGroup.Name, currentSecGroup.CreatedNewEntry, () => { using (var groupPrincipal = GroupPrincipal.FindByIdentity(domainContext, IdentityType.Sid, currentSecGroup.UID)) { if (groupPrincipal == null) return false; if (parentTraverseGroup.Members.Contains(groupPrincipal)) return true; DefaultLogger.LogEntry(LogLevels.Debug, $"Füge {groupPrincipal.DistinguishedName} zur Traverse-Gruppe {parentTraverseGroup.DistinguishedName} hinzu"); parentTraverseGroup.Members.Add(groupPrincipal); parentTraverseGroup.Save(); return true; } }); } private bool TryEnsureNestedTraverseGroupMembershipWithRetry(GroupPrincipal parentTraverseGroup, GroupPrincipal traverseGroup) { if (parentTraverseGroup == null || traverseGroup == null) return false; return RetryTraverseMembershipAction( traverseGroup.Name, true, () => { if (parentTraverseGroup.Members.Contains(traverseGroup)) return true; DefaultLogger.LogEntry(LogLevels.Debug, $"Füge {traverseGroup.DistinguishedName} zur Traverse-Gruppe {parentTraverseGroup.DistinguishedName} hinzu"); parentTraverseGroup.Members.Add(traverseGroup); parentTraverseGroup.Save(); return true; }); } private bool RetryTraverseMembershipAction(string memberName, bool allowRetry, Func tryAction) { if (tryAction == null) return false; var retryDelaysMs = allowRetry ? new[] { 0, 250, 500, 1000, 2000, 5000 } : new[] { 0 }; var delayIndex = 0; var maxWait = allowRetry ? TimeSpan.FromMinutes(3) : TimeSpan.Zero; var waitStopwatch = Stopwatch.StartNew(); Exception lastException = null; while (true) { var delayMs = retryDelaysMs[Math.Min(delayIndex, retryDelaysMs.Length - 1)]; if (delayMs > 0) System.Threading.Thread.Sleep(delayMs); if (delayIndex < retryDelaysMs.Length - 1) delayIndex++; try { if (tryAction()) { if (waitStopwatch.ElapsedMilliseconds > 0) DefaultLogger.LogEntry(LogLevels.Debug, $"Traverse-Mitgliedschaft für '{memberName}' nach {waitStopwatch.Elapsed.TotalSeconds:F1}s erfolgreich."); return true; } } catch (Exception ex) { lastException = ex; DefaultLogger.LogEntry(LogLevels.Debug, $"Traverse-Mitgliedschaft für '{memberName}' noch nicht möglich: {ex.Message}"); } if (!allowRetry || waitStopwatch.Elapsed >= maxWait) break; } var suffix = lastException == null ? string.Empty : $" Letzte Exception: {lastException.Message}"; DefaultLogger.LogEntry(LogLevels.Warning, $"Traverse-Mitgliedschaft für '{memberName}' konnte nach {waitStopwatch.Elapsed.TotalSeconds:F1}s nicht sichergestellt werden.{suffix}"); return false; } private ResultToken checkFolder() { LogMethodBegin(MethodBase.GetCurrentMethod()); try { ResultToken resultToken = new ResultToken(System.Reflection.MethodBase.GetCurrentMethod().ToString()); resultToken.resultErrorId = 0; if (Directory.Exists(newDataArea.IAM_Folders[0].technicalName)) { resultToken.resultMessage = "New folder " + newDataArea.IAM_Folders[0].technicalName + " already exists"; resultToken.resultErrorId = 30201; } return resultToken; } catch (Exception E) { cLogManager.DefaultLogger.LogException(E); throw; } finally { LogMethodEnd(MethodBase.GetCurrentMethod()); } } private static bool HasMatchingAccessRule(DirectoryInfo directory, SecurityIdentifier sid, FileSystemRights rights) { var rules = directory.GetAccessControl(AccessControlSections.Access) .GetAccessRules(true, true, typeof(SecurityIdentifier)) .Cast(); foreach (var rule in rules) { if (rule.AccessControlType != AccessControlType.Allow) continue; if (!(rule.IdentityReference is SecurityIdentifier ruleSid)) continue; if (!string.Equals(ruleSid.Value, sid.Value, StringComparison.OrdinalIgnoreCase)) continue; if ((rule.FileSystemRights & rights) == rights) return true; } return false; } private ResultToken ensureFolderPermissions(ResultToken resultToken) { LogMethodBegin(MethodBase.GetCurrentMethod()); try { var directory = new DirectoryInfo(newDataArea.IAM_Folders[0].technicalName); foreach (var currentSecGroup in newSecurityGroups.IAM_SecurityGroups) { if (WhatIf && string.IsNullOrWhiteSpace(currentSecGroup?.UID) && currentSecGroup?.CreatedNewEntry == true) { resultToken.addedAclEntries.Add(currentSecGroup.Name); continue; } if (string.IsNullOrWhiteSpace(currentSecGroup?.UID)) { resultToken.warnings.Add($"Keine SID für Gruppe '{currentSecGroup?.Name}' verfügbar."); continue; } if (!(groupPermissionStrategy == PermissionGroupStrategy.AGDLP && currentSecGroup.Scope == GroupScope.Local || groupPermissionStrategy == PermissionGroupStrategy.AGP && currentSecGroup.Scope == GroupScope.Global)) { continue; } var sid = new SecurityIdentifier(currentSecGroup.UID); if (HasMatchingAccessRule(directory, sid, currentSecGroup.rights)) { resultToken.skippedAclEntries.Add(currentSecGroup.Name); continue; } if (!WhatIf) DataArea.AddDirectorySecurity(newDataArea.IAM_Folders[0].baseFolder, newDataArea.IAM_Folders[0].technicalName, sid, currentSecGroup.rights, AccessControlType.Allow); resultToken.addedAclEntries.Add(currentSecGroup.Name); } return resultToken; } catch (Exception E) { cLogManager.DefaultLogger.LogException(E); throw; } finally { LogMethodEnd(MethodBase.GetCurrentMethod()); } } private void ensureADGroups(ResultToken resultToken) { LogMethodBegin(MethodBase.GetCurrentMethod()); try { 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); List owners = getUserPrincipalBySid(ownerUserSids); List writers = getUserPrincipalBySid(writerUserSids); List readers = getUserPrincipalBySid(readerUserSids); for (int i = 0; newSecurityGroups.IAM_SecurityGroups.Count > i; i++) { List users; if (newSecurityGroups.IAM_SecurityGroups[i].Name.ToUpper().EndsWith(groupOwnerTag.ToUpper())) users = owners; else if (newSecurityGroups.IAM_SecurityGroups[i].Name.ToUpper().EndsWith(groupWriteTag.ToUpper())) users = writers; else if (newSecurityGroups.IAM_SecurityGroups[i].Name.ToUpper().EndsWith(groupReadTag.ToUpper())) users = readers; else users = null; if (WhatIf) { var existingGroup = newSecurityGroups.PreviewADGroup(groupOUPath, newSecurityGroups.IAM_SecurityGroups[i], newDataArea.IAM_Folders[0].technicalName); newSecurityGroups.IAM_SecurityGroups[i].CreatedNewEntry = existingGroup == null; } else { newSecurityGroups.EnsureADGroup(groupOUPath, newSecurityGroups.IAM_SecurityGroups[i], users, newDataArea.IAM_Folders[0].technicalName); } if (newSecurityGroups.IAM_SecurityGroups[i].CreatedNewEntry) resultToken.createdGroups.Add(newSecurityGroups.IAM_SecurityGroups[i].Name); else resultToken.reusedGroups.Add(newSecurityGroups.IAM_SecurityGroups[i].Name); } } catch (Exception E) { cLogManager.DefaultLogger.LogException(E); throw; } finally { LogMethodEnd(MethodBase.GetCurrentMethod()); } } private ResultToken createFolder() { LogMethodBegin(MethodBase.GetCurrentMethod()); try { ResultToken resultToken = new ResultToken(System.Reflection.MethodBase.GetCurrentMethod().ToString()); resultToken.resultErrorId = 0; if (!Directory.Exists(newFolderParent)) { resultToken.resultMessage = "Übergeordnetesverzeichnis " + newDataArea.IAM_Folders[0].Parent + " des neuen Ordners " + newDataArea.IAM_Folders[0].technicalName + " existiert nicht"; resultToken.resultErrorId = 30202; return resultToken; } else { if (WhatIf) { newDataArea.IAM_Folders[0].UID = DataArea.GetUniqueDataAreaID(newDataArea.IAM_Folders[0].technicalName); resultToken.warnings.Add($"Verzeichnis würde erstellt werden: {newDataArea.IAM_Folders[0].technicalName}"); for (int i = 0; newSecurityGroups.IAM_SecurityGroups.Count > i; i++) { var currentSecGroup = newSecurityGroups.IAM_SecurityGroups[i]; if (groupPermissionStrategy == PermissionGroupStrategy.AGDLP && currentSecGroup.Scope == GroupScope.Local || groupPermissionStrategy == PermissionGroupStrategy.AGP && currentSecGroup.Scope == GroupScope.Global) { resultToken.addedAclEntries.Add(currentSecGroup.Name); } } resultToken.resultErrorId = 0; resultToken.resultMessage = "Verzeichnis-, Gruppen- und ACL-Vorschau erfolgreich erstellt"; return resultToken; } DefaultLogger.LogEntry(LogLevels.Debug, $"Creating folder: {newDataArea.IAM_Folders[0].technicalName}"); DirectoryInfo newDir = Directory.CreateDirectory(newDataArea.IAM_Folders[0].technicalName); newDataArea.IAM_Folders[0].UID = DataArea.GetUniqueDataAreaID(newDir.FullName); for (int i = 0; newSecurityGroups.IAM_SecurityGroups.Count > i; i++) { var currentSecGroup = newSecurityGroups.IAM_SecurityGroups[i]; var sid = new SecurityIdentifier(currentSecGroup.UID); if (groupPermissionStrategy == PermissionGroupStrategy.AGDLP && currentSecGroup.Scope == GroupScope.Local || groupPermissionStrategy == PermissionGroupStrategy.AGP && currentSecGroup.Scope == GroupScope.Global) DataArea.AddDirectorySecurity(newDataArea.IAM_Folders[0].baseFolder, newDataArea.IAM_Folders[0].technicalName, sid, currentSecGroup.rights, AccessControlType.Allow); } AddDirectorySecurityACLDenyFolderDeletion(newDataArea.IAM_Folders[0].technicalName); resultToken.resultErrorId = 0; resultToken.resultMessage = "Verzeichnis erfolgreich erstellt"; } return resultToken; } catch (Exception E) { cLogManager.DefaultLogger.LogException(E); throw; } finally { LogMethodEnd(MethodBase.GetCurrentMethod()); } } private void AddDirectorySecurityACLDenyFolderDeletion(string technicalName) { LogMethodBegin(MethodBase.GetCurrentMethod()); // Create a new DirectoryInfo object. DirectoryInfo dInfo = new DirectoryInfo(technicalName); //DirectoryInfo dInfoBaseFolder = new DirectoryInfo(baseFolderTechnicalName); // Get a DirectorySecurity object that represents the // current security settings. DirectorySecurity dSecurity = dInfo.GetAccessControl(); // Add the FileSystemAccessRule to the security settings. var everyone = new SecurityIdentifier(WellKnownSidType.WorldSid, null); dSecurity.AddAccessRule(new FileSystemAccessRule(everyone, FileSystemRights.Delete, InheritanceFlags.None, PropagationFlags.None, AccessControlType.Deny)); // Set the new access settings. dInfo.SetAccessControl(dSecurity); LogMethodEnd(MethodBase.GetCurrentMethod()); } private void createADGroups(ResultToken resultToken) { LogMethodBegin(MethodBase.GetCurrentMethod()); try { Dictionary currentGroupDIC = new Dictionary(); adGroupDic = new Dictionary(); var existingADGroupCount = 0; do { 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, existingADGroupCount); /* if (existingADGroupCount > 0 && !templates.All(t => t.Type == SecurityGroupType.Traverse || Regex.IsMatch(t.NamingTemplate, @"(?{{(?[^}]*)(?LOOP)(?[^{]*)}})"))) { var nt = templates.First(t => t.Type == SecurityGroupType.Traverse || Regex.IsMatch(t.NamingTemplate, @"(?{{(?[^}]*)(?LOOP)(?[^{]*)}})")).NamingTemplate; DefaultLogger.LogEntry(LogLevels.Debug, $"Naming template: {nt}"); throw new Exception("AD groups already existing and loop tag not found"); } */ existingADGroupCount++; } while (newSecurityGroups.GroupsAllreadyExisting(groupOUPath) && existingADGroupCount < 1000); if(newSecurityGroups.GroupsAllreadyExisting(groupOUPath) && existingADGroupCount>= 1000) { throw new Exception("Unique AD Group could not be generated after 1000 iterations"); } List users; List owners = getUserPrincipalBySid(ownerUserSids); List writers = getUserPrincipalBySid(writerUserSids); List readers = getUserPrincipalBySid(readerUserSids); for (int i = 0; newSecurityGroups.IAM_SecurityGroups.Count > i; i++) { if (newSecurityGroups.IAM_SecurityGroups[i].Name.ToUpper().EndsWith(groupOwnerTag.ToUpper())) users = owners; else if (newSecurityGroups.IAM_SecurityGroups[i].Name.ToUpper().EndsWith(groupWriteTag.ToUpper())) users = writers; else if (newSecurityGroups.IAM_SecurityGroups[i].Name.ToUpper().EndsWith(groupReadTag.ToUpper())) users = readers; else users = null; if (WhatIf) { var existingGroup = newSecurityGroups.PreviewADGroup(groupOUPath, newSecurityGroups.IAM_SecurityGroups[i], newDataArea.IAM_Folders[0].technicalName); newSecurityGroups.IAM_SecurityGroups[i].CreatedNewEntry = existingGroup == null; } else { newSecurityGroups.CreateADGroup(groupOUPath, newSecurityGroups.IAM_SecurityGroups[i], users); } if (resultToken != null) { if (newSecurityGroups.IAM_SecurityGroups[i].CreatedNewEntry) resultToken.createdGroups.Add(newSecurityGroups.IAM_SecurityGroups[i].Name); else resultToken.reusedGroups.Add(newSecurityGroups.IAM_SecurityGroups[i].Name); } } } catch (Exception E) { cLogManager.DefaultLogger.LogException(E); throw; } finally { LogMethodEnd(MethodBase.GetCurrentMethod()); } } private List getUserPrincipalBySid(ICollection UserSids) { LogMethodBegin(MethodBase.GetCurrentMethod()); try { List result = new List(); if (UserSids != null) { foreach (var sid in UserSids) { if (!string.IsNullOrEmpty(sid)) { UserPrincipal user = getUserPrincipalBySid(sid); if (user != null && !result.Any(item => item.DistinguishedName == user.DistinguishedName)) { result.Add(user); } } } } return result; } catch (Exception E) { cLogManager.DefaultLogger.LogException(E); throw; } finally { LogMethodEnd(MethodBase.GetCurrentMethod()); } } private UserPrincipal getUserPrincipalBySid(string sid) { LogMethodBegin(MethodBase.GetCurrentMethod()); try { PrincipalContext ctx = new PrincipalContext(ContextType.Domain, domainName, username, new NetworkCredential("", password).Password); UserPrincipal user; user = UserPrincipal.FindByIdentity(ctx, IdentityType.Sid, (sid)); return user; } catch (Exception E) { cLogManager.DefaultLogger.LogException(E); throw; } finally { LogMethodEnd(MethodBase.GetCurrentMethod()); } } private string getTraverseRegex() { return groupWildcard .Replace("{{PREFIX}}", groupPrefix) .Replace("{{SCOPETAG}}", groupDLTag) .Replace("{{GROUPTYPEPOSTFIX}}", groupTraverseTag); } } }