diff --git a/LiamNtfs/C4IT.LIAM.Ntfs.cs b/LiamNtfs/C4IT.LIAM.Ntfs.cs index d2858fb..b60e3e7 100644 --- a/LiamNtfs/C4IT.LIAM.Ntfs.cs +++ b/LiamNtfs/C4IT.LIAM.Ntfs.cs @@ -311,7 +311,7 @@ namespace C4IT.LIAM return null; } - public async Task CreateDataAreaAsync( + public Task CreateDataAreaAsync( string newFolderPath, string newFolderParent, IDictionary customTags, @@ -320,27 +320,157 @@ namespace C4IT.LIAM IEnumerable writerSids ) { - // 1) Instanziere DataArea_FileSystem und fülle Konfiguration: + var engine = CreateFilesystemEngine( + newFolderPath, + newFolderParent, + customTags, + ownerSids, + readerSids, + writerSids); + var result = engine.createDataArea(); + return Task.FromResult(result); + } + + public Task EnsureMissingPermissionGroupsAsync( + string folderPath, + IDictionary customTags, + IEnumerable ownerSids, + IEnumerable readerSids, + IEnumerable writerSids, + bool ensureTraverseGroups = false) + { + var parentPath = Directory.GetParent(folderPath)?.FullName; + var engine = CreateFilesystemEngine( + folderPath, + parentPath, + customTags, + ownerSids, + readerSids, + writerSids); + + return Task.FromResult(engine.ensureDataAreaPermissions(ensureTraverseGroups)); + } + + private DataArea_FileSystem CreateFilesystemEngine( + string folderPath, + string parentFolderPath, + IDictionary customTags, + IEnumerable ownerSids, + IEnumerable readerSids, + IEnumerable writerSids) + { + var mergedCustomTags = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var tag in CustomTags) + mergedCustomTags[tag.Key] = tag.Value; + if (customTags != null) + { + foreach (var tag in customTags) + mergedCustomTags[tag.Key] = tag.Value; + } + var engine = new DataArea_FileSystem { + ConfigID = "manual", domainName = this.Domain, username = this.Credential.Identification, password = new NetworkCredential("", this.Credential.Secret).SecurePassword, baseFolder = this.RootPath, - newFolderPath = newFolderPath, - newFolderParent = newFolderParent, - groupPrefix = CustomTags["Filesystem_GroupPrefixTag"], + newFolderPath = folderPath, + newFolderParent = parentFolderPath, + groupPrefix = GetRequiredCustomTag("Filesystem_GroupPrefixTag"), groupOUPath = this.GroupPath, groupPermissionStrategy = (C4IT_IAM_GET.PermissionGroupStrategy)this.GroupStrategy, - groupCustomTags = customTags, - ownerUserSids = ownerSids?.ToList(), - readerUserSids = readerSids?.ToList(), - writerUserSids = writerSids?.ToList(), - // Templates aus NamingConventions übernehmen… + groupCustomTags = mergedCustomTags, + ownerUserSids = ownerSids?.Where(i => !string.IsNullOrWhiteSpace(i)).Distinct(StringComparer.OrdinalIgnoreCase).ToList() ?? new List(), + readerUserSids = readerSids?.Where(i => !string.IsNullOrWhiteSpace(i)).Distinct(StringComparer.OrdinalIgnoreCase).ToList() ?? new List(), + writerUserSids = writerSids?.Where(i => !string.IsNullOrWhiteSpace(i)).Distinct(StringComparer.OrdinalIgnoreCase).ToList() ?? new List(), + groupOwnerTag = GetRequiredCustomTag("Filesystem_GroupOwnerTag"), + groupWriteTag = GetRequiredCustomTag("Filesystem_GroupWriteTag"), + groupReadTag = GetRequiredCustomTag("Filesystem_GroupReadTag"), + groupTraverseTag = GetRequiredCustomTag("Filesystem_GroupTraverseTag"), + groupDLTag = GetRequiredCustomTag("Filesystem_GroupDomainLocalTag"), + groupGTag = GetRequiredCustomTag("Filesystem_GroupGlobalTag") }; - // 2) Engine starten - var result = engine.createDataArea(); - return result; + + foreach (var template in BuildSecurityGroupTemplates()) + engine.templates.Add(template); + + return engine; + } + + private IEnumerable BuildSecurityGroupTemplates() + { + var templates = new List(); + foreach (var namingConvention in NamingConventions ?? Enumerable.Empty()) + { + if (!TryMapSecurityGroupType(namingConvention.AccessRole, out var securityGroupType)) + continue; + + if (!TryMapGroupScope(namingConvention.Scope, securityGroupType, out var groupScope)) + continue; + + templates.Add(new IAM_SecurityGroupTemplate( + namingConvention.NamingTemplate, + namingConvention.DescriptionTemplate, + namingConvention.Wildcard, + securityGroupType, + groupScope)); + } + + return templates; + } + + private bool TryMapSecurityGroupType(eLiamAccessRoles accessRole, out SecurityGroupType securityGroupType) + { + securityGroupType = SecurityGroupType.Read; + switch (accessRole) + { + case eLiamAccessRoles.Owner: + securityGroupType = SecurityGroupType.Owner; + return true; + case eLiamAccessRoles.Write: + securityGroupType = SecurityGroupType.Write; + return true; + case eLiamAccessRoles.Read: + securityGroupType = SecurityGroupType.Read; + return true; + case eLiamAccessRoles.Traverse: + securityGroupType = SecurityGroupType.Traverse; + return true; + default: + return false; + } + } + + private bool TryMapGroupScope(eLiamAccessRoleScopes scope, SecurityGroupType type, out GroupScope groupScope) + { + groupScope = GroupScope.Global; + switch (scope) + { + case eLiamAccessRoleScopes.Global: + groupScope = GroupScope.Global; + return true; + case eLiamAccessRoleScopes.DomainLocal: + groupScope = GroupScope.Local; + return true; + case eLiamAccessRoleScopes.Unknown: + if (type == SecurityGroupType.Traverse) + { + groupScope = this.GroupStrategy == eLiamGroupStrategies.Ntfs_AGDLP ? GroupScope.Local : GroupScope.Global; + return true; + } + return false; + default: + return false; + } + } + + private string GetRequiredCustomTag(string key) + { + if (!CustomTags.TryGetValue(key, out var value)) + throw new InvalidOperationException($"Missing NTFS custom tag '{key}'."); + + return value; } diff --git a/LiamNtfs/C4IT_IAM_SET/DataArea_FileSystem.cs b/LiamNtfs/C4IT_IAM_SET/DataArea_FileSystem.cs index f0c925b..97ac84f 100644 --- a/LiamNtfs/C4IT_IAM_SET/DataArea_FileSystem.cs +++ b/LiamNtfs/C4IT_IAM_SET/DataArea_FileSystem.cs @@ -225,6 +225,134 @@ namespace C4IT_IAM_SET } } + 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 + }; + } + + 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) + { + 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 = "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()); @@ -349,30 +477,33 @@ namespace C4IT_IAM_SET continue; } - GroupPrincipal parentTraverseGroup = null; - 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))); - - string traverseRegex = null; - try - { - traverseRegex = Helper.ApplyTemplatePlaceholders(traverseGroupTemplate.WildcardTemplate, true, relativePath, sanitizedSegments, folderName); - DefaultLogger.LogEntry(LogLevels.Debug, $"traverseRegex: {traverseRegex}"); - } - catch (Exception ex) - { - DefaultLogger.LogEntry(LogLevels.Error, $"Fehler bei der Erstellung von traverseRegex: {ex.Message}"); - 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 traverseNameTemplate = Helper.ApplyTemplatePlaceholders(traverseGroupTemplate.NamingTemplate, true, relativePath, sanitizedSegments, folderName); + var traverseDescriptionTemplate = Helper.ApplyTemplatePlaceholders(traverseGroupTemplate.DescriptionTemplate, true, relativePath, sanitizedSegments, folderName); + + string traverseRegex = null; + try + { + traverseRegex = Helper.ApplyTemplatePlaceholders(traverseGroupTemplate.WildcardTemplate, true, relativePath, sanitizedSegments, folderName); + 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) { @@ -397,12 +528,29 @@ namespace C4IT_IAM_SET 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."); @@ -412,24 +560,22 @@ namespace C4IT_IAM_SET continue; } - IAM_SecurityGroup newTraverseGroup = null; - var traverseNameTemplate = Helper.ApplyTemplatePlaceholders(traverseGroupTemplate.NamingTemplate, true, relativePath, sanitizedSegments, folderName); - var traverseDescriptionTemplate = Helper.ApplyTemplatePlaceholders(traverseGroupTemplate.DescriptionTemplate, true, relativePath, sanitizedSegments, folderName); - 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++; - } + 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}"); @@ -452,6 +598,8 @@ namespace C4IT_IAM_SET { 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) { @@ -474,6 +622,8 @@ namespace C4IT_IAM_SET 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) { @@ -488,6 +638,37 @@ namespace C4IT_IAM_SET } } + 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 + { + 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) @@ -566,6 +747,9 @@ namespace C4IT_IAM_SET 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) @@ -620,6 +804,136 @@ namespace C4IT_IAM_SET 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 (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; + } + + 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; + + var groupAlreadyExists = newSecurityGroups.GroupAllreadyExisting(newSecurityGroups.IAM_SecurityGroups[i].Name.ToUpper()); + newSecurityGroups.EnsureADGroup(groupOUPath, newSecurityGroups.IAM_SecurityGroups[i], users); + if (groupAlreadyExists) + resultToken.reusedGroups.Add(newSecurityGroups.IAM_SecurityGroups[i].Name); + else + resultToken.createdGroups.Add(newSecurityGroups.IAM_SecurityGroups[i].Name); + } + } + catch (Exception E) + { + cLogManager.DefaultLogger.LogException(E); + throw; + } + finally + { + LogMethodEnd(MethodBase.GetCurrentMethod()); + } + } + private ResultToken createFolder() { LogMethodBegin(MethodBase.GetCurrentMethod()); diff --git a/LiamNtfs/C4IT_IAM_SET/ResultToken.cs b/LiamNtfs/C4IT_IAM_SET/ResultToken.cs index 33da293..a0d496a 100644 --- a/LiamNtfs/C4IT_IAM_SET/ResultToken.cs +++ b/LiamNtfs/C4IT_IAM_SET/ResultToken.cs @@ -11,6 +11,12 @@ namespace C4IT_IAM_Engine public string resultMessage; public int resultErrorId; public string resultFunction; + public List createdGroups = new List(); + public List reusedGroups = new List(); + public List addedAclEntries = new List(); + public List skippedAclEntries = new List(); + public List ensuredTraverseGroups = new List(); + public List warnings = new List(); public ResultToken(string function) { this.resultFunction = function; diff --git a/LiamNtfs/C4IT_IAM_SET/SecurityGroup.cs b/LiamNtfs/C4IT_IAM_SET/SecurityGroup.cs index bfe31b2..b0f8170 100644 --- a/LiamNtfs/C4IT_IAM_SET/SecurityGroup.cs +++ b/LiamNtfs/C4IT_IAM_SET/SecurityGroup.cs @@ -134,19 +134,19 @@ namespace C4IT_IAM_Engine try { - var relativePathRaw = DataArea.GetRelativePath(newFolderPath, baseFolder).Trim(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); - relativePathRaw = relativePathRaw.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); - 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; - var folderName = sanitizedSegments.Length > 0 - ? sanitizedSegments[sanitizedSegments.Length - 1] - : Helper.SanitizePathSegment(Path.GetFileName(newFolderPath.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar))); - - foreach (var template in templates) - { - var GroupTypeTag = ""; - switch (template.Type) + var relativePathRaw = DataArea.GetRelativePath(newFolderPath, baseFolder).Trim(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); + relativePathRaw = relativePathRaw.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); + 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; + var folderName = sanitizedSegments.Length > 0 + ? sanitizedSegments[sanitizedSegments.Length - 1] + : Helper.SanitizePathSegment(Path.GetFileName(newFolderPath.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar))); + + foreach (var template in templates) + { + var GroupTypeTag = ""; + switch (template.Type) { case SecurityGroupType.Owner: GroupTypeTag = groupOwnerTag; @@ -181,20 +181,20 @@ namespace C4IT_IAM_Engine tags.Add("GROUPTYPEPOSTFIX", GroupTypeTag); tags.Add("SCOPETAG", GroupScopeTag); - template.NamingTemplate = Helper.ApplyTemplatePlaceholders(template.NamingTemplate, template.Type != SecurityGroupType.Traverse, relativePath, sanitizedSegments, folderName) - .ReplaceTags(customTags).ReplaceTags(tags) - .ToUpper(); - - template.DescriptionTemplate = Helper.ApplyTemplatePlaceholders(template.DescriptionTemplate, template.Type != SecurityGroupType.Traverse, relativePath, sanitizedSegments, folderName) - .ReplaceTags(customTags).ReplaceTags(tags) - .ToUpper(); - - - template.WildcardTemplate = Helper.ApplyTemplatePlaceholders(template.WildcardTemplate, template.Type != SecurityGroupType.Traverse, relativePath, sanitizedSegments, folderName) - .ReplaceTags(customTags).ReplaceTags(tags) - .ToUpper(); - - } + template.NamingTemplate = Helper.ApplyTemplatePlaceholders(template.NamingTemplate, template.Type != SecurityGroupType.Traverse, relativePath, sanitizedSegments, folderName) + .ReplaceTags(customTags).ReplaceTags(tags) + .ToUpper(); + + template.DescriptionTemplate = Helper.ApplyTemplatePlaceholders(template.DescriptionTemplate, template.Type != SecurityGroupType.Traverse, relativePath, sanitizedSegments, folderName) + .ReplaceTags(customTags).ReplaceTags(tags) + .ToUpper(); + + + template.WildcardTemplate = Helper.ApplyTemplatePlaceholders(template.WildcardTemplate, template.Type != SecurityGroupType.Traverse, relativePath, sanitizedSegments, folderName) + .ReplaceTags(customTags).ReplaceTags(tags) + .ToUpper(); + + } IAM_SecurityGroupTemplate ownerGlobal = templates.First(t => t.Scope.Equals(GroupScope.Global) && t.Type.Equals(SecurityGroupType.Owner)); IAM_SecurityGroup osecGroup = new IAM_SecurityGroup() @@ -359,9 +359,111 @@ namespace C4IT_IAM_Engine LogMethodEnd(MethodBase.GetCurrentMethod()); } - } - - public DirectoryEntry CreateADGroup(string ouPath, IAM_SecurityGroup secGroup, List users) + } + + private DirectoryEntry FindGroupEntry(string groupName) + { + DirectoryEntry entry = new DirectoryEntry + { + Path = "LDAP://" + domainName, + Username = username, + Password = new NetworkCredential("", password).Password, + AuthenticationType = AuthenticationTypes.Secure | AuthenticationTypes.Sealing + }; + + DirectorySearcher search = new DirectorySearcher(entry) + { + Filter = "(&(objectClass=group)(sAMAccountName=" + groupName.ToUpper() + "))" + }; + search.PageSize = 100000; + + var result = search.FindOne(); + if (result == null) + { + entry.Dispose(); + search.Dispose(); + return null; + } + + var groupEntry = result.GetDirectoryEntry(); + search.Dispose(); + entry.Dispose(); + return groupEntry; + } + + private static bool HasMember(PropertyValueCollection members, string distinguishedName) + { + foreach (var member in members) + { + if (string.Equals(member?.ToString(), distinguishedName, StringComparison.OrdinalIgnoreCase)) + return true; + } + + return false; + } + + private void AddMissingMembers(DirectoryEntry group, IAM_SecurityGroup secGroup, List users) + { + if (group == null) + return; + + var changed = false; + if (users != null && secGroup.Scope == GroupScope.Global) + { + foreach (var user in users.Where(u => u != null && !string.IsNullOrWhiteSpace(u.DistinguishedName))) + { + if (HasMember(group.Properties["member"], user.DistinguishedName)) + continue; + + DefaultLogger.LogEntry(LogLevels.Debug, $"Adding missing member: {user.DistinguishedName}"); + group.Properties["member"].Add(user.DistinguishedName); + changed = true; + } + } + + if (secGroup.Scope == GroupScope.Local) + { + foreach (var innerGroup in secGroup.memberGroups.Where(g => g != null && !string.IsNullOrWhiteSpace(g.technicalName))) + { + if (HasMember(group.Properties["member"], innerGroup.technicalName)) + continue; + + DefaultLogger.LogEntry(LogLevels.Debug, $"Adding missing nested group: {innerGroup.technicalName}"); + group.Properties["member"].Add(innerGroup.technicalName); + changed = true; + } + } + + if (changed) + group.CommitChanges(); + } + + public DirectoryEntry EnsureADGroup(string ouPath, IAM_SecurityGroup secGroup, List users) + { + LogMethodBegin(MethodBase.GetCurrentMethod()); + try + { + var existingGroup = FindGroupEntry(secGroup.Name); + if (existingGroup == null) + return CreateADGroup(ouPath, secGroup, users); + + AddMissingMembers(existingGroup, secGroup, users); + var objectid = getSID(existingGroup); + secGroup.UID = objectid; + return existingGroup; + } + catch (Exception E) + { + cLogManager.DefaultLogger.LogException(E); + throw; + } + finally + { + LogMethodEnd(MethodBase.GetCurrentMethod()); + } + } + + public DirectoryEntry CreateADGroup(string ouPath, IAM_SecurityGroup secGroup, List users) { LogMethodBegin(MethodBase.GetCurrentMethod()); try @@ -406,9 +508,13 @@ namespace C4IT_IAM_Engine } else { - DirectoryEntry e = new DirectoryEntry("LDAP://" + domainName + "/" + "CN =" + secGroup.Name.ToUpper() + "," + ouPath, username, new NetworkCredential("", password).Password, AuthenticationTypes.Secure | AuthenticationTypes.Sealing); + DirectoryEntry e = FindGroupEntry(secGroup.Name); + if (e == null) + return null; var objectid = getSID(e); secGroup.UID = objectid; + AddMissingMembers(e, secGroup, users); + return e; } return null; } diff --git a/LiamWorkflowActivities/C4IT.LIAM.WorkflowActivities.cs b/LiamWorkflowActivities/C4IT.LIAM.WorkflowActivities.cs index 2bc59a1..2c217ad 100644 --- a/LiamWorkflowActivities/C4IT.LIAM.WorkflowActivities.cs +++ b/LiamWorkflowActivities/C4IT.LIAM.WorkflowActivities.cs @@ -10,16 +10,17 @@ using Matrix42.Workflows.Contracts; using System.Linq; using Matrix42.Contracts.Platform.General; using System.Json; -using Newtonsoft.Json; -using System.Collections.Generic; -using Matrix42.Workflows.Activities.Common.Data; -using System.Activities.Validation; -using System.Threading.Tasks; -using LiamWorkflowActivities; -using System.Runtime.CompilerServices; -using System.Security.Principal; -using static LiamAD.ADServiceGroupCreator; -using C4IT.LIAM; +using Newtonsoft.Json; +using System.Collections.Generic; +using Matrix42.Workflows.Activities.Common.Data; +using System.Activities.Validation; +using System.Threading.Tasks; +using LiamWorkflowActivities; +using System.Runtime.CompilerServices; +using System.Security.Principal; +using static LiamAD.ADServiceGroupCreator; +using C4IT.LIAM; +using C4IT_IAM_Engine; namespace C4IT.LIAM.Activities { @@ -38,13 +39,13 @@ namespace C4IT.LIAM.Activities [DisplayName("Success")] public OutArgument Success { get; set; } - [Category("Output")] - [DisplayName("Message")] - public OutArgument Message { get; set; } - - [Category("Output")] - [DisplayName("Error Code")] - public OutArgument ErrorCode { get; set; } + [Category("Output")] + [DisplayName("Message")] + public OutArgument Message { get; set; } + + [Category("Output")] + [DisplayName("Error Code")] + public OutArgument ErrorCode { get; set; } protected override void Execute(NativeActivityContext context) { @@ -56,14 +57,14 @@ namespace C4IT.LIAM.Activities { LogEntry($"Executing activity '{GetType().Name}':", LogLevels.Info); LogEntry($" Local server name: {Environment.MachineName}", LogLevels.Info); - LogEntry($" Activity input string: {ConfigID.Get(context)}", LogLevels.Info); - - EnsureDataProviders(context); - ErrorCode.Set(context, string.Empty); - Message.Set(context, string.Empty); - - SecureData secureData = Password.Get(context); - var configEOID = ConfigID.Get(context); + LogEntry($" Activity input string: {ConfigID.Get(context)}", LogLevels.Info); + + EnsureDataProviders(context); + ErrorCode.Set(context, string.Empty); + Message.Set(context, string.Empty); + + SecureData secureData = Password.Get(context); + var configEOID = ConfigID.Get(context); var dataMain = dataProvider.GetDataList(constFragmentNameConfigProviderMain, "ID, [Expression-ObjectId] as EOID, GCCDomain, GCCTarget, GCCMaxDepth, " + @@ -82,31 +83,31 @@ namespace C4IT.LIAM.Activities "NamingConvention.Name as Name, NamingConvention.NamingTemplate as NamingTemplate, NamingConvention.Wildcard as Wildcard", $"[Expression-ObjectID] = '{configEOID}'"); - var DataProvider = createProvider(dataMain.First(), dataBase.First(), dataAdditional, dataNamingConvention, dataCustomTag, secureData); - - var validLogon = DataProvider.LogonAsync().GetAwaiter().GetResult(); - if (validLogon) - { - AddCache(ConfigClassId, configEOID, DataProvider); - } - else - { - Message.Set(context, DataProvider.GetLastErrorMessage()); - if (DataProvider is cLiamProviderExchange exProvider) - ErrorCode.Set(context, exProvider.GetLastErrorCode()); - else - ErrorCode.Set(context, "WF_PROVIDER_LOGON_FAILED"); - } - - Success.Set(context, validLogon); - } - catch (Exception E) - { - LogException(E); - Success.Set(context, false); - ErrorCode.Set(context, "WF_ACTIVITY_EXCEPTION"); - Message.Set(context, E.Message); - } + var DataProvider = createProvider(dataMain.First(), dataBase.First(), dataAdditional, dataNamingConvention, dataCustomTag, secureData); + + var validLogon = DataProvider.LogonAsync().GetAwaiter().GetResult(); + if (validLogon) + { + AddCache(ConfigClassId, configEOID, DataProvider); + } + else + { + Message.Set(context, DataProvider.GetLastErrorMessage()); + if (DataProvider is cLiamProviderExchange exProvider) + ErrorCode.Set(context, exProvider.GetLastErrorCode()); + else + ErrorCode.Set(context, "WF_PROVIDER_LOGON_FAILED"); + } + + Success.Set(context, validLogon); + } + catch (Exception E) + { + LogException(E); + Success.Set(context, false); + ErrorCode.Set(context, "WF_ACTIVITY_EXCEPTION"); + Message.Set(context, E.Message); + } finally { LogMethodEnd(CM); @@ -131,21 +132,21 @@ namespace C4IT.LIAM.Activities [RequiredArgument] public InArgument ConfigID { get; set; } - [Category("Output")] - [DisplayName("DataAreas")] - public OutArgument DataAreas { get; set; } - - [Category("Output")] - [DisplayName("Success")] - public OutArgument Success { get; set; } - - [Category("Output")] - [DisplayName("Error Code")] - public OutArgument ErrorCode { get; set; } - - [Category("Output")] - [DisplayName("Error Message")] - public OutArgument ErrorMessage { get; set; } + [Category("Output")] + [DisplayName("DataAreas")] + public OutArgument DataAreas { get; set; } + + [Category("Output")] + [DisplayName("Success")] + public OutArgument Success { get; set; } + + [Category("Output")] + [DisplayName("Error Code")] + public OutArgument ErrorCode { get; set; } + + [Category("Output")] + [DisplayName("Error Message")] + public OutArgument ErrorMessage { get; set; } protected override void Execute(NativeActivityContext context) { @@ -157,32 +158,32 @@ namespace C4IT.LIAM.Activities { LogEntry($"Executing activity '{GetType().Name}':", LogLevels.Info); LogEntry($" Local server name: {Environment.MachineName}", LogLevels.Info); - LogEntry($" Activity input string: {ConfigID.Get(context)}", LogLevels.Info); - - EnsureDataProviders(context); - Success.Set(context, false); - ErrorCode.Set(context, string.Empty); - ErrorMessage.Set(context, string.Empty); - - var dataAreas = getDataAreasFromProvider(ConfigID.Get(context)).GetAwaiter().GetResult() ?? Enumerable.Empty(); - var dataAreaJson = dataAreas.Select(da => JsonValue.Parse(JsonConvert.SerializeObject(da))); - DataAreas.Set(context, new JsonArray(dataAreaJson)); - Success.Set(context, true); - - if (!string.IsNullOrWhiteSpace(LastOperationErrorCode)) - { - Success.Set(context, false); - ErrorCode.Set(context, LastOperationErrorCode); - ErrorMessage.Set(context, LastOperationErrorMessage); - } - } - catch (Exception E) - { - LogException(E); - Success.Set(context, false); - ErrorCode.Set(context, "WF_ACTIVITY_EXCEPTION"); - ErrorMessage.Set(context, E.Message); - } + LogEntry($" Activity input string: {ConfigID.Get(context)}", LogLevels.Info); + + EnsureDataProviders(context); + Success.Set(context, false); + ErrorCode.Set(context, string.Empty); + ErrorMessage.Set(context, string.Empty); + + var dataAreas = getDataAreasFromProvider(ConfigID.Get(context)).GetAwaiter().GetResult() ?? Enumerable.Empty(); + var dataAreaJson = dataAreas.Select(da => JsonValue.Parse(JsonConvert.SerializeObject(da))); + DataAreas.Set(context, new JsonArray(dataAreaJson)); + Success.Set(context, true); + + if (!string.IsNullOrWhiteSpace(LastOperationErrorCode)) + { + Success.Set(context, false); + ErrorCode.Set(context, LastOperationErrorCode); + ErrorMessage.Set(context, LastOperationErrorMessage); + } + } + catch (Exception E) + { + LogException(E); + Success.Set(context, false); + ErrorCode.Set(context, "WF_ACTIVITY_EXCEPTION"); + ErrorMessage.Set(context, E.Message); + } finally { LogMethodEnd(CM); @@ -207,21 +208,21 @@ namespace C4IT.LIAM.Activities [RequiredArgument] public InArgument ConfigID { get; set; } - [Category("Output")] - [DisplayName("SecurityGroups")] - public OutArgument SecurityGroups { get; set; } - - [Category("Output")] - [DisplayName("Success")] - public OutArgument Success { get; set; } - - [Category("Output")] - [DisplayName("Error Code")] - public OutArgument ErrorCode { get; set; } - - [Category("Output")] - [DisplayName("Error Message")] - public OutArgument ErrorMessage { get; set; } + [Category("Output")] + [DisplayName("SecurityGroups")] + public OutArgument SecurityGroups { get; set; } + + [Category("Output")] + [DisplayName("Success")] + public OutArgument Success { get; set; } + + [Category("Output")] + [DisplayName("Error Code")] + public OutArgument ErrorCode { get; set; } + + [Category("Output")] + [DisplayName("Error Message")] + public OutArgument ErrorMessage { get; set; } protected override void Execute(NativeActivityContext context) { @@ -233,32 +234,32 @@ namespace C4IT.LIAM.Activities { LogEntry($"Executing activity '{GetType().Name}':", LogLevels.Info); LogEntry($" Local server name: {Environment.MachineName}", LogLevels.Info); - LogEntry($" Activity input string: {ConfigID.Get(context)}", LogLevels.Info); - - EnsureDataProviders(context); - Success.Set(context, false); - ErrorCode.Set(context, string.Empty); - ErrorMessage.Set(context, string.Empty); - - var securityGroups = getSecurityGroupsFromProvider(ConfigID.Get(context)).GetAwaiter().GetResult() ?? Enumerable.Empty(); - var securityGroupJson = securityGroups.Select(sg => JsonValue.Parse(JsonConvert.SerializeObject(sg))); - SecurityGroups.Set(context, new JsonArray(securityGroupJson)); - Success.Set(context, true); - - if (!string.IsNullOrWhiteSpace(LastOperationErrorCode)) - { - Success.Set(context, false); - ErrorCode.Set(context, LastOperationErrorCode); - ErrorMessage.Set(context, LastOperationErrorMessage); - } - } - catch (Exception E) - { - LogException(E); - Success.Set(context, false); - ErrorCode.Set(context, "WF_ACTIVITY_EXCEPTION"); - ErrorMessage.Set(context, E.Message); - } + LogEntry($" Activity input string: {ConfigID.Get(context)}", LogLevels.Info); + + EnsureDataProviders(context); + Success.Set(context, false); + ErrorCode.Set(context, string.Empty); + ErrorMessage.Set(context, string.Empty); + + var securityGroups = getSecurityGroupsFromProvider(ConfigID.Get(context)).GetAwaiter().GetResult() ?? Enumerable.Empty(); + var securityGroupJson = securityGroups.Select(sg => JsonValue.Parse(JsonConvert.SerializeObject(sg))); + SecurityGroups.Set(context, new JsonArray(securityGroupJson)); + Success.Set(context, true); + + if (!string.IsNullOrWhiteSpace(LastOperationErrorCode)) + { + Success.Set(context, false); + ErrorCode.Set(context, LastOperationErrorCode); + ErrorMessage.Set(context, LastOperationErrorMessage); + } + } + catch (Exception E) + { + LogException(E); + Success.Set(context, false); + ErrorCode.Set(context, "WF_ACTIVITY_EXCEPTION"); + ErrorMessage.Set(context, E.Message); + } finally { LogMethodEnd(CM); @@ -301,10 +302,10 @@ namespace C4IT.LIAM.Activities EnsureDataProviders(context); - var ownersInfo = getOwnerInfosFromDataArea(DataAreaID.Get(context)).GetAwaiter().GetResult() ?? new List(); - var owners = getPersonsFromUsers(ownersInfo) ?? new List(); - var ownerJson = owners.Select(user => JsonValue.Parse(JsonConvert.SerializeObject(user))); - OwnerUserIDs.Set(context, new JsonArray(ownerJson)); + var ownersInfo = getOwnerInfosFromDataArea(DataAreaID.Get(context)).GetAwaiter().GetResult() ?? new List(); + var owners = getPersonsFromUsers(ownersInfo) ?? new List(); + var ownerJson = owners.Select(user => JsonValue.Parse(JsonConvert.SerializeObject(user))); + OwnerUserIDs.Set(context, new JsonArray(ownerJson)); } catch (Exception E) { @@ -579,83 +580,83 @@ namespace C4IT.LIAM.Activities [DisplayName("Object GUID")] public OutArgument ObjectGuid { get; set; } - [Category("Output")] - [DisplayName("Created Groups")] - public OutArgument>> CreatedGroups { get; set; } - - [Category("Output")] - [DisplayName("Error Code")] - public OutArgument ErrorCode { get; set; } - - [Category("Output")] - [DisplayName("Error Message")] - public OutArgument ErrorMessage { get; set; } - - protected override void Execute(NativeActivityContext context) - { - if (!IsInitialized) Initialize(context); - var CM = MethodBase.GetCurrentMethod(); - LogMethodBegin(CM); - try - { - LogEntry($"Executing activity '{GetType().Name}'", LogLevels.Info); - - EnsureDataProviders(context); - ErrorCode.Set(context, string.Empty); - ErrorMessage.Set(context, string.Empty); - - var entry = getDataProvider(ConfigID.Get(context)); - if (entry != null && entry.Provider is cLiamProviderExchange ex) - { - var result = ex.exchangeManager.CreateDistributionGroupWithOwnershipGroups( - Name.Get(context), - Alias.Get(context), - DistributionListDisplayName.Get(context), - PrimarySmtpAddress.Get(context), - out string errorCode, - out string errorMessage - ); - ErrorCode.Set(context, errorCode); - ErrorMessage.Set(context, errorMessage); - - if (result != null) - { - Success.Set(context, true); - ObjectGuid.Set(context, result.Item1); - CreatedGroups.Set(context, result.Item2); - LogEntry( - $"Distribution group creation succeeded. ObjectGuid='{result.Item1}', CreatedGroups='{result.Item2?.Count ?? 0}'", - LogLevels.Info); - } - else - { - Success.Set(context, false); - LogEntry( - $"Distribution group creation failed [{errorCode}] {errorMessage}", - LogLevels.Error); - } - } - else - { - Success.Set(context, false); - ErrorCode.Set(context, "WF_PROVIDER_INVALID"); - ErrorMessage.Set(context, $"Provider is not a cLiamProviderExchange for config '{ConfigID.Get(context)}'."); - LogEntry( - $"Distribution group creation failed [WF_PROVIDER_INVALID] Provider is not a cLiamProviderExchange for config '{ConfigID.Get(context)}'.", - LogLevels.Error); - } - } - catch (Exception e) - { - LogException(e); - Success.Set(context, false); - ErrorCode.Set(context, "WF_ACTIVITY_EXCEPTION"); - ErrorMessage.Set(context, e.Message); - } - finally - { - LogMethodEnd(CM); - } + [Category("Output")] + [DisplayName("Created Groups")] + public OutArgument>> CreatedGroups { get; set; } + + [Category("Output")] + [DisplayName("Error Code")] + public OutArgument ErrorCode { get; set; } + + [Category("Output")] + [DisplayName("Error Message")] + public OutArgument ErrorMessage { get; set; } + + protected override void Execute(NativeActivityContext context) + { + if (!IsInitialized) Initialize(context); + var CM = MethodBase.GetCurrentMethod(); + LogMethodBegin(CM); + try + { + LogEntry($"Executing activity '{GetType().Name}'", LogLevels.Info); + + EnsureDataProviders(context); + ErrorCode.Set(context, string.Empty); + ErrorMessage.Set(context, string.Empty); + + var entry = getDataProvider(ConfigID.Get(context)); + if (entry != null && entry.Provider is cLiamProviderExchange ex) + { + var result = ex.exchangeManager.CreateDistributionGroupWithOwnershipGroups( + Name.Get(context), + Alias.Get(context), + DistributionListDisplayName.Get(context), + PrimarySmtpAddress.Get(context), + out string errorCode, + out string errorMessage + ); + ErrorCode.Set(context, errorCode); + ErrorMessage.Set(context, errorMessage); + + if (result != null) + { + Success.Set(context, true); + ObjectGuid.Set(context, result.Item1); + CreatedGroups.Set(context, result.Item2); + LogEntry( + $"Distribution group creation succeeded. ObjectGuid='{result.Item1}', CreatedGroups='{result.Item2?.Count ?? 0}'", + LogLevels.Info); + } + else + { + Success.Set(context, false); + LogEntry( + $"Distribution group creation failed [{errorCode}] {errorMessage}", + LogLevels.Error); + } + } + else + { + Success.Set(context, false); + ErrorCode.Set(context, "WF_PROVIDER_INVALID"); + ErrorMessage.Set(context, $"Provider is not a cLiamProviderExchange for config '{ConfigID.Get(context)}'."); + LogEntry( + $"Distribution group creation failed [WF_PROVIDER_INVALID] Provider is not a cLiamProviderExchange for config '{ConfigID.Get(context)}'.", + LogLevels.Error); + } + } + catch (Exception e) + { + LogException(e); + Success.Set(context, false); + ErrorCode.Set(context, "WF_ACTIVITY_EXCEPTION"); + ErrorMessage.Set(context, e.Message); + } + finally + { + LogMethodEnd(CM); + } } private void EnsureDataProviders(NativeActivityContext context) @@ -702,82 +703,82 @@ namespace C4IT.LIAM.Activities [DisplayName("Object GUID")] public OutArgument ObjectGuid { get; set; } - [Category("Output")] - [DisplayName("Created Groups")] - public OutArgument>> CreatedGroups { get; set; } - - [Category("Output")] - [DisplayName("Error Code")] - public OutArgument ErrorCode { get; set; } - - [Category("Output")] - [DisplayName("Error Message")] - public OutArgument ErrorMessage { get; set; } - - protected override void Execute(NativeActivityContext context) - { - if (!IsInitialized) Initialize(context); - var CM = MethodBase.GetCurrentMethod(); - LogMethodBegin(CM); - try - { - LogEntry($"Executing activity '{GetType().Name}'", LogLevels.Info); - - EnsureDataProviders(context); - ErrorCode.Set(context, string.Empty); - ErrorMessage.Set(context, string.Empty); - - var entry = getDataProvider(ConfigID.Get(context)); - if (entry != null && entry.Provider is cLiamProviderExchange ex) - { - var result = ex.exchangeManager.CreateSharedMailboxWithOwnershipGroups( - Name.Get(context), - Alias.Get(context), - MailboxDisplayName.Get(context), - PrimarySmtpAddress.Get(context), - out string errorCode, - out string errorMessage - ); - ErrorCode.Set(context, errorCode); - ErrorMessage.Set(context, errorMessage); - - if (result != null) - { - Success.Set(context, true); - ObjectGuid.Set(context, result.Item1); - CreatedGroups.Set(context, result.Item2); - LogEntry( - $"Shared mailbox creation succeeded. ObjectGuid='{result.Item1}', CreatedGroups='{result.Item2?.Count ?? 0}'", - LogLevels.Info); - } - else - { - Success.Set(context, false); - LogEntry( - $"Shared mailbox creation failed [{errorCode}] {errorMessage}", - LogLevels.Error); - } - } - else - { - Success.Set(context, false); - ErrorCode.Set(context, "WF_PROVIDER_INVALID"); - ErrorMessage.Set(context, $"Provider is not a cLiamProviderExchange for config '{ConfigID.Get(context)}'."); - LogEntry( - $"Shared mailbox creation failed [WF_PROVIDER_INVALID] Provider is not a cLiamProviderExchange for config '{ConfigID.Get(context)}'.", - LogLevels.Error); - } - } - catch (Exception e) - { - LogException(e); - Success.Set(context, false); - ErrorCode.Set(context, "WF_ACTIVITY_EXCEPTION"); - ErrorMessage.Set(context, e.Message); - } - finally - { - LogMethodEnd(CM); + [Category("Output")] + [DisplayName("Created Groups")] + public OutArgument>> CreatedGroups { get; set; } + + [Category("Output")] + [DisplayName("Error Code")] + public OutArgument ErrorCode { get; set; } + + [Category("Output")] + [DisplayName("Error Message")] + public OutArgument ErrorMessage { get; set; } + + protected override void Execute(NativeActivityContext context) + { + if (!IsInitialized) Initialize(context); + var CM = MethodBase.GetCurrentMethod(); + LogMethodBegin(CM); + try + { + LogEntry($"Executing activity '{GetType().Name}'", LogLevels.Info); + + EnsureDataProviders(context); + ErrorCode.Set(context, string.Empty); + ErrorMessage.Set(context, string.Empty); + + var entry = getDataProvider(ConfigID.Get(context)); + if (entry != null && entry.Provider is cLiamProviderExchange ex) + { + var result = ex.exchangeManager.CreateSharedMailboxWithOwnershipGroups( + Name.Get(context), + Alias.Get(context), + MailboxDisplayName.Get(context), + PrimarySmtpAddress.Get(context), + out string errorCode, + out string errorMessage + ); + ErrorCode.Set(context, errorCode); + ErrorMessage.Set(context, errorMessage); + + if (result != null) + { + Success.Set(context, true); + ObjectGuid.Set(context, result.Item1); + CreatedGroups.Set(context, result.Item2); + LogEntry( + $"Shared mailbox creation succeeded. ObjectGuid='{result.Item1}', CreatedGroups='{result.Item2?.Count ?? 0}'", + LogLevels.Info); + } + else + { + Success.Set(context, false); + LogEntry( + $"Shared mailbox creation failed [{errorCode}] {errorMessage}", + LogLevels.Error); + } + } + else + { + Success.Set(context, false); + ErrorCode.Set(context, "WF_PROVIDER_INVALID"); + ErrorMessage.Set(context, $"Provider is not a cLiamProviderExchange for config '{ConfigID.Get(context)}'."); + LogEntry( + $"Shared mailbox creation failed [WF_PROVIDER_INVALID] Provider is not a cLiamProviderExchange for config '{ConfigID.Get(context)}'.", + LogLevels.Error); + } + } + catch (Exception e) + { + LogException(e); + Success.Set(context, false); + ErrorCode.Set(context, "WF_ACTIVITY_EXCEPTION"); + ErrorMessage.Set(context, e.Message); + } + finally + { + LogMethodEnd(CM); } } @@ -959,5 +960,107 @@ namespace C4IT.LIAM.Activities } } + + public class C4ITLIAMEnsureNtfsPermissionGroupsActivity : cLIAMM42BaseActivity + { + [Category("Input")] + [DisplayName("Config Id")] + [RequiredArgument] + public InArgument ConfigID { get; set; } + + [Category("Input")] + [DisplayName("Folder Path")] + [RequiredArgument] + public InArgument FolderPath { get; set; } + + [Category("Input")] + [DisplayName("Owner SIDs")] + public InArgument OwnerSids { get; set; } + + [Category("Input")] + [DisplayName("Reader SIDs")] + public InArgument ReaderSids { get; set; } + + [Category("Input")] + [DisplayName("Writer SIDs")] + public InArgument WriterSids { get; set; } + + [Category("Input")] + [DisplayName("Ensure Traverse")] + public InArgument EnsureTraverse { get; set; } + + [Category("Output")] + [DisplayName("Success")] + public OutArgument Success { get; set; } + + [Category("Output")] + [DisplayName("Result")] + public OutArgument ResultToken { get; set; } + + protected override void Execute(NativeActivityContext context) + { + EnsureDataProviders(context); + + var cfgId = ConfigID.Get(context); + var providerEntry = getDataProvider(cfgId); + var provider = providerEntry?.Provider as cLiamProviderNtfs; + var folderPath = FolderPath.Get(context); + if (provider == null || string.IsNullOrWhiteSpace(folderPath)) + { + Success.Set(context, false); + ResultToken.Set(context, JsonValue.Parse(JsonConvert.SerializeObject(new ResultToken(GetType().Name) + { + resultErrorId = 1, + resultMessage = provider == null ? "Configured provider is not NTFS or not initialized." : "Folder path is missing." + }))); + return; + } + + var result = provider.EnsureMissingPermissionGroupsAsync( + folderPath, + null, + ParseSidList(OwnerSids.Get(context)), + ParseSidList(ReaderSids.Get(context)), + ParseSidList(WriterSids.Get(context)), + EnsureTraverse.Get(context)).GetAwaiter().GetResult(); + + Success.Set(context, result != null && result.resultErrorId == 0); + ResultToken.Set(context, JsonValue.Parse(JsonConvert.SerializeObject(result))); + } + + private IEnumerable ParseSidList(string raw) + { + if (string.IsNullOrWhiteSpace(raw)) + return Enumerable.Empty(); + + var trimmed = raw.Trim(); + if (trimmed.StartsWith("[")) + { + try + { + return JsonConvert.DeserializeObject>(trimmed) ?? Enumerable.Empty(); + } + catch + { + } + } + + return trimmed + .Split(new[] { ';', ',', '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries) + .Select(i => i.Trim()) + .Where(i => !string.IsNullOrWhiteSpace(i)) + .Distinct(StringComparer.OrdinalIgnoreCase); + } + + private void EnsureDataProviders(NativeActivityContext context) + { + if (executor == null) + executor = context.GetExtension(); + if (schemaReader == null) + schemaReader = executor.Get(); + if (dataProvider == null) + dataProvider = executor.Get(); + } + } } diff --git a/LiamWorkflowActivitiesDesign/C4IT.LIAM.WorkflowActivities.Design.cs b/LiamWorkflowActivitiesDesign/C4IT.LIAM.WorkflowActivities.Design.cs index 4c4f5cc..695036e 100644 --- a/LiamWorkflowActivitiesDesign/C4IT.LIAM.WorkflowActivities.Design.cs +++ b/LiamWorkflowActivitiesDesign/C4IT.LIAM.WorkflowActivities.Design.cs @@ -52,4 +52,19 @@ namespace C4IT.LIAM.Activities.Design.Metadata builder.AddCustomAttributes(base.Activity, "CreatedTeamId", new EditorAttribute(typeof(VariablesEditor), typeof(DialogPropertyValueEditor)), new VariablesEditorArgumentsAttribute(allowCreateNew: true, ArgumentDirection.Out)); } } + + public class C4ITLIAMEnsureNtfsPermissionGroupsActivityMetadata : ActivityMetadata + { + public override void RegisterMetadata(AttributeTableBuilder builder) + { + builder.AddCustomAttributes(base.Activity, "ConfigID", new EditorAttribute(typeof(VariablesEditor), typeof(DialogPropertyValueEditor)), new VariablesEditorArgumentsAttribute(allowCreateNew: true, ArgumentDirection.In)); + builder.AddCustomAttributes(base.Activity, "FolderPath", new EditorAttribute(typeof(VariablesEditor), typeof(DialogPropertyValueEditor)), new VariablesEditorArgumentsAttribute(allowCreateNew: true, ArgumentDirection.In)); + builder.AddCustomAttributes(base.Activity, "OwnerSids", new EditorAttribute(typeof(VariablesEditor), typeof(DialogPropertyValueEditor)), new VariablesEditorArgumentsAttribute(allowCreateNew: true, ArgumentDirection.In)); + builder.AddCustomAttributes(base.Activity, "ReaderSids", new EditorAttribute(typeof(VariablesEditor), typeof(DialogPropertyValueEditor)), new VariablesEditorArgumentsAttribute(allowCreateNew: true, ArgumentDirection.In)); + builder.AddCustomAttributes(base.Activity, "WriterSids", new EditorAttribute(typeof(VariablesEditor), typeof(DialogPropertyValueEditor)), new VariablesEditorArgumentsAttribute(allowCreateNew: true, ArgumentDirection.In)); + builder.AddCustomAttributes(base.Activity, "EnsureTraverse", new EditorAttribute(typeof(VariablesEditor), typeof(DialogPropertyValueEditor)), new VariablesEditorArgumentsAttribute(allowCreateNew: true, ArgumentDirection.In)); + builder.AddCustomAttributes(base.Activity, "Success", new EditorAttribute(typeof(VariablesEditor), typeof(DialogPropertyValueEditor)), new VariablesEditorArgumentsAttribute(allowCreateNew: true, ArgumentDirection.Out)); + builder.AddCustomAttributes(base.Activity, "ResultToken", new EditorAttribute(typeof(VariablesEditor), typeof(DialogPropertyValueEditor)), new VariablesEditorArgumentsAttribute(allowCreateNew: true, ArgumentDirection.Out)); + } + } }