feat: ensure missing NTFS permission groups

This commit is contained in:
Meik
2026-03-10 08:51:59 +01:00
parent 58d7529329
commit 812deeaa74
6 changed files with 1049 additions and 375 deletions

View File

@@ -311,7 +311,7 @@ namespace C4IT.LIAM
return null;
}
public async Task<ResultToken> CreateDataAreaAsync(
public Task<ResultToken> CreateDataAreaAsync(
string newFolderPath,
string newFolderParent,
IDictionary<string, string> customTags,
@@ -320,27 +320,157 @@ namespace C4IT.LIAM
IEnumerable<string> 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<ResultToken> EnsureMissingPermissionGroupsAsync(
string folderPath,
IDictionary<string, string> customTags,
IEnumerable<string> ownerSids,
IEnumerable<string> readerSids,
IEnumerable<string> 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<string, string> customTags,
IEnumerable<string> ownerSids,
IEnumerable<string> readerSids,
IEnumerable<string> writerSids)
{
var mergedCustomTags = new Dictionary<string, string>(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<string>(),
readerUserSids = readerSids?.Where(i => !string.IsNullOrWhiteSpace(i)).Distinct(StringComparer.OrdinalIgnoreCase).ToList() ?? new List<string>(),
writerUserSids = writerSids?.Where(i => !string.IsNullOrWhiteSpace(i)).Distinct(StringComparer.OrdinalIgnoreCase).ToList() ?? new List<string>(),
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<IAM_SecurityGroupTemplate> BuildSecurityGroupTemplates()
{
var templates = new List<IAM_SecurityGroupTemplate>();
foreach (var namingConvention in NamingConventions ?? Enumerable.Empty<cLiamNamingConvention>())
{
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;
}

View File

@@ -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<FileSystemAccessRule>();
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<FileSystemAccessRule>();
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<UserPrincipal> owners = getUserPrincipalBySid(ownerUserSids);
List<UserPrincipal> writers = getUserPrincipalBySid(writerUserSids);
List<UserPrincipal> readers = getUserPrincipalBySid(readerUserSids);
for (int i = 0; newSecurityGroups.IAM_SecurityGroups.Count > i; i++)
{
List<UserPrincipal> 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());

View File

@@ -11,6 +11,12 @@ namespace C4IT_IAM_Engine
public string resultMessage;
public int resultErrorId;
public string resultFunction;
public List<string> createdGroups = new List<string>();
public List<string> reusedGroups = new List<string>();
public List<string> addedAclEntries = new List<string>();
public List<string> skippedAclEntries = new List<string>();
public List<string> ensuredTraverseGroups = new List<string>();
public List<string> warnings = new List<string>();
public ResultToken(string function)
{
this.resultFunction = function;

View File

@@ -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<UserPrincipal> 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<UserPrincipal> 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<UserPrincipal> 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<UserPrincipal> 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;
}

View File

@@ -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<bool> Success { get; set; }
[Category("Output")]
[DisplayName("Message")]
public OutArgument<string> Message { get; set; }
[Category("Output")]
[DisplayName("Error Code")]
public OutArgument<string> ErrorCode { get; set; }
[Category("Output")]
[DisplayName("Message")]
public OutArgument<string> Message { get; set; }
[Category("Output")]
[DisplayName("Error Code")]
public OutArgument<string> 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<Guid> ConfigID { get; set; }
[Category("Output")]
[DisplayName("DataAreas")]
public OutArgument<JsonArray> DataAreas { get; set; }
[Category("Output")]
[DisplayName("Success")]
public OutArgument<bool> Success { get; set; }
[Category("Output")]
[DisplayName("Error Code")]
public OutArgument<string> ErrorCode { get; set; }
[Category("Output")]
[DisplayName("Error Message")]
public OutArgument<string> ErrorMessage { get; set; }
[Category("Output")]
[DisplayName("DataAreas")]
public OutArgument<JsonArray> DataAreas { get; set; }
[Category("Output")]
[DisplayName("Success")]
public OutArgument<bool> Success { get; set; }
[Category("Output")]
[DisplayName("Error Code")]
public OutArgument<string> ErrorCode { get; set; }
[Category("Output")]
[DisplayName("Error Message")]
public OutArgument<string> 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<DataAreaEntry>();
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<DataAreaEntry>();
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<Guid> ConfigID { get; set; }
[Category("Output")]
[DisplayName("SecurityGroups")]
public OutArgument<JsonArray> SecurityGroups { get; set; }
[Category("Output")]
[DisplayName("Success")]
public OutArgument<bool> Success { get; set; }
[Category("Output")]
[DisplayName("Error Code")]
public OutArgument<string> ErrorCode { get; set; }
[Category("Output")]
[DisplayName("Error Message")]
public OutArgument<string> ErrorMessage { get; set; }
[Category("Output")]
[DisplayName("SecurityGroups")]
public OutArgument<JsonArray> SecurityGroups { get; set; }
[Category("Output")]
[DisplayName("Success")]
public OutArgument<bool> Success { get; set; }
[Category("Output")]
[DisplayName("Error Code")]
public OutArgument<string> ErrorCode { get; set; }
[Category("Output")]
[DisplayName("Error Message")]
public OutArgument<string> 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<SecurityGroupEntry>();
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<SecurityGroupEntry>();
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<cLiamUserInfo>();
var owners = getPersonsFromUsers(ownersInfo) ?? new List<Guid>();
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<cLiamUserInfo>();
var owners = getPersonsFromUsers(ownersInfo) ?? new List<Guid>();
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<Guid> ObjectGuid { get; set; }
[Category("Output")]
[DisplayName("Created Groups")]
public OutArgument<List<Tuple<string, string, string, string>>> CreatedGroups { get; set; }
[Category("Output")]
[DisplayName("Error Code")]
public OutArgument<string> ErrorCode { get; set; }
[Category("Output")]
[DisplayName("Error Message")]
public OutArgument<string> 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<List<Tuple<string, string, string, string>>> CreatedGroups { get; set; }
[Category("Output")]
[DisplayName("Error Code")]
public OutArgument<string> ErrorCode { get; set; }
[Category("Output")]
[DisplayName("Error Message")]
public OutArgument<string> 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<Guid> ObjectGuid { get; set; }
[Category("Output")]
[DisplayName("Created Groups")]
public OutArgument<List<Tuple<string, string, string, string>>> CreatedGroups { get; set; }
[Category("Output")]
[DisplayName("Error Code")]
public OutArgument<string> ErrorCode { get; set; }
[Category("Output")]
[DisplayName("Error Message")]
public OutArgument<string> 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<List<Tuple<string, string, string, string>>> CreatedGroups { get; set; }
[Category("Output")]
[DisplayName("Error Code")]
public OutArgument<string> ErrorCode { get; set; }
[Category("Output")]
[DisplayName("Error Message")]
public OutArgument<string> 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<Guid> ConfigID { get; set; }
[Category("Input")]
[DisplayName("Folder Path")]
[RequiredArgument]
public InArgument<string> FolderPath { get; set; }
[Category("Input")]
[DisplayName("Owner SIDs")]
public InArgument<string> OwnerSids { get; set; }
[Category("Input")]
[DisplayName("Reader SIDs")]
public InArgument<string> ReaderSids { get; set; }
[Category("Input")]
[DisplayName("Writer SIDs")]
public InArgument<string> WriterSids { get; set; }
[Category("Input")]
[DisplayName("Ensure Traverse")]
public InArgument<bool> EnsureTraverse { get; set; }
[Category("Output")]
[DisplayName("Success")]
public OutArgument<bool> Success { get; set; }
[Category("Output")]
[DisplayName("Result")]
public OutArgument<JsonValue> 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<string> ParseSidList(string raw)
{
if (string.IsNullOrWhiteSpace(raw))
return Enumerable.Empty<string>();
var trimmed = raw.Trim();
if (trimmed.StartsWith("["))
{
try
{
return JsonConvert.DeserializeObject<List<string>>(trimmed) ?? Enumerable.Empty<string>();
}
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<IExtensionExecutor>();
if (schemaReader == null)
schemaReader = executor.Get<ISchemaReaderProvider>();
if (dataProvider == null)
dataProvider = executor.Get<IDataReaderProvider>();
}
}
}

View File

@@ -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<C4ITLIAMEnsureNtfsPermissionGroupsActivity>
{
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));
}
}
}