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; return null;
} }
public async Task<ResultToken> CreateDataAreaAsync( public Task<ResultToken> CreateDataAreaAsync(
string newFolderPath, string newFolderPath,
string newFolderParent, string newFolderParent,
IDictionary<string, string> customTags, IDictionary<string, string> customTags,
@@ -320,27 +320,157 @@ namespace C4IT.LIAM
IEnumerable<string> writerSids 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 var engine = new DataArea_FileSystem
{ {
ConfigID = "manual",
domainName = this.Domain, domainName = this.Domain,
username = this.Credential.Identification, username = this.Credential.Identification,
password = new NetworkCredential("", this.Credential.Secret).SecurePassword, password = new NetworkCredential("", this.Credential.Secret).SecurePassword,
baseFolder = this.RootPath, baseFolder = this.RootPath,
newFolderPath = newFolderPath, newFolderPath = folderPath,
newFolderParent = newFolderParent, newFolderParent = parentFolderPath,
groupPrefix = CustomTags["Filesystem_GroupPrefixTag"], groupPrefix = GetRequiredCustomTag("Filesystem_GroupPrefixTag"),
groupOUPath = this.GroupPath, groupOUPath = this.GroupPath,
groupPermissionStrategy = (C4IT_IAM_GET.PermissionGroupStrategy)this.GroupStrategy, groupPermissionStrategy = (C4IT_IAM_GET.PermissionGroupStrategy)this.GroupStrategy,
groupCustomTags = customTags, groupCustomTags = mergedCustomTags,
ownerUserSids = ownerSids?.ToList(), ownerUserSids = ownerSids?.Where(i => !string.IsNullOrWhiteSpace(i)).Distinct(StringComparer.OrdinalIgnoreCase).ToList() ?? new List<string>(),
readerUserSids = readerSids?.ToList(), readerUserSids = readerSids?.Where(i => !string.IsNullOrWhiteSpace(i)).Distinct(StringComparer.OrdinalIgnoreCase).ToList() ?? new List<string>(),
writerUserSids = writerSids?.ToList(), writerUserSids = writerSids?.Where(i => !string.IsNullOrWhiteSpace(i)).Distinct(StringComparer.OrdinalIgnoreCase).ToList() ?? new List<string>(),
// Templates aus NamingConventions übernehmen… 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(); foreach (var template in BuildSecurityGroupTemplates())
return result; 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() private ResultToken SetTraversePermissions()
{ {
LogMethodBegin(MethodBase.GetCurrentMethod()); LogMethodBegin(MethodBase.GetCurrentMethod());
@@ -350,6 +478,7 @@ namespace C4IT_IAM_SET
} }
GroupPrincipal parentTraverseGroup = null; GroupPrincipal parentTraverseGroup = null;
var parentTraverseAclExists = false;
string relativePathRaw = DataArea.GetRelativePath(parent.FullName, baseFolder).Trim(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); string relativePathRaw = DataArea.GetRelativePath(parent.FullName, baseFolder).Trim(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
relativePathRaw = relativePathRaw.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); relativePathRaw = relativePathRaw.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
DefaultLogger.LogEntry(LogLevels.Debug, $"relativePath vor Normalisierung: {relativePathRaw}"); DefaultLogger.LogEntry(LogLevels.Debug, $"relativePath vor Normalisierung: {relativePathRaw}");
@@ -361,6 +490,8 @@ namespace C4IT_IAM_SET
var folderName = sanitizedSegments.Length > 0 var folderName = sanitizedSegments.Length > 0
? sanitizedSegments[sanitizedSegments.Length - 1] ? sanitizedSegments[sanitizedSegments.Length - 1]
: Helper.SanitizePathSegment(Path.GetFileName(parent.FullName.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar))); : 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; string traverseRegex = null;
try try
@@ -397,12 +528,29 @@ namespace C4IT_IAM_SET
if (princ != null && Regex.IsMatch(princ.Name, traverseRegex, RegexOptions.IgnoreCase)) if (princ != null && Regex.IsMatch(princ.Name, traverseRegex, RegexOptions.IgnoreCase))
{ {
parentTraverseGroup = princ; parentTraverseGroup = princ;
parentTraverseAclExists = true;
DefaultLogger.LogEntry(LogLevels.Debug, $"parentTraverseGroup gesetzt: {parentTraverseGroup.Name}"); DefaultLogger.LogEntry(LogLevels.Debug, $"parentTraverseGroup gesetzt: {parentTraverseGroup.Name}");
} }
if (parentTraverseGroup != null) if (parentTraverseGroup != null)
break; 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)) if (parentTraverseGroup == null && !traverseGroupTemplate.NamingTemplate.Equals(string.Empty))
{ {
DefaultLogger.LogEntry(LogLevels.Debug, "Erstelle neue TraverseGroup."); DefaultLogger.LogEntry(LogLevels.Debug, "Erstelle neue TraverseGroup.");
@@ -413,8 +561,6 @@ namespace C4IT_IAM_SET
} }
IAM_SecurityGroup newTraverseGroup = null; 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; var loop = 0;
do do
{ {
@@ -452,6 +598,8 @@ namespace C4IT_IAM_SET
{ {
newSecurityGroups.CreateADGroup(groupOUPath, newTraverseGroup, null); newSecurityGroups.CreateADGroup(groupOUPath, newTraverseGroup, null);
DefaultLogger.LogEntry(LogLevels.Debug, $"AD-Gruppe erstellt: {newTraverseGroup.Name}"); DefaultLogger.LogEntry(LogLevels.Debug, $"AD-Gruppe erstellt: {newTraverseGroup.Name}");
resultToken.createdGroups.Add(newTraverseGroup.Name);
resultToken.ensuredTraverseGroups.Add(newTraverseGroup.Name);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -474,6 +622,8 @@ namespace C4IT_IAM_SET
AccessControlType.Allow)); AccessControlType.Allow));
DefaultLogger.LogEntry(LogLevels.Debug, $"Setze Traverse-ACL auf: {parent.FullName} für {parentTraverseGroup.DistinguishedName}"); DefaultLogger.LogEntry(LogLevels.Debug, $"Setze Traverse-ACL auf: {parent.FullName} für {parentTraverseGroup.DistinguishedName}");
parent.SetAccessControl(accesscontrol); parent.SetAccessControl(accesscontrol);
resultToken.addedAclEntries.Add(parentTraverseGroup.Name);
parentTraverseAclExists = true;
} }
catch (Exception ex) 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 (parentTraverseGroup != null)
{ {
if (i == lvl) if (i == lvl)
@@ -566,6 +747,9 @@ namespace C4IT_IAM_SET
DefaultLogger.LogEntry(LogLevels.Debug, "parentTraverseGroup ist null."); DefaultLogger.LogEntry(LogLevels.Debug, "parentTraverseGroup ist null.");
} }
if (parentTraverseGroup != null && !resultToken.ensuredTraverseGroups.Contains(parentTraverseGroup.Name))
resultToken.ensuredTraverseGroups.Add(parentTraverseGroup.Name);
// Aktualisiere parent und lvl für die nächste Iteration // Aktualisiere parent und lvl für die nächste Iteration
parent = parent.Parent; parent = parent.Parent;
if (parent != null) if (parent != null)
@@ -620,6 +804,136 @@ namespace C4IT_IAM_SET
LogMethodEnd(MethodBase.GetCurrentMethod()); 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() private ResultToken createFolder()
{ {
LogMethodBegin(MethodBase.GetCurrentMethod()); LogMethodBegin(MethodBase.GetCurrentMethod());

View File

@@ -11,6 +11,12 @@ namespace C4IT_IAM_Engine
public string resultMessage; public string resultMessage;
public int resultErrorId; public int resultErrorId;
public string resultFunction; 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) public ResultToken(string function)
{ {
this.resultFunction = function; this.resultFunction = function;

View File

@@ -361,6 +361,108 @@ namespace C4IT_IAM_Engine
} }
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) public DirectoryEntry CreateADGroup(string ouPath, IAM_SecurityGroup secGroup, List<UserPrincipal> users)
{ {
LogMethodBegin(MethodBase.GetCurrentMethod()); LogMethodBegin(MethodBase.GetCurrentMethod());
@@ -406,9 +508,13 @@ namespace C4IT_IAM_Engine
} }
else 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); var objectid = getSID(e);
secGroup.UID = objectid; secGroup.UID = objectid;
AddMissingMembers(e, secGroup, users);
return e;
} }
return null; return null;
} }

View File

@@ -20,6 +20,7 @@ using System.Runtime.CompilerServices;
using System.Security.Principal; using System.Security.Principal;
using static LiamAD.ADServiceGroupCreator; using static LiamAD.ADServiceGroupCreator;
using C4IT.LIAM; using C4IT.LIAM;
using C4IT_IAM_Engine;
namespace C4IT.LIAM.Activities namespace C4IT.LIAM.Activities
{ {
@@ -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)); 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));
}
}
} }