using C4IT_IAM_GET; using System; using System.Collections.Generic; using System.DirectoryServices; using System.DirectoryServices.AccountManagement; using System.IO; using System.Linq; using System.Net; using System.Security; using System.Security.AccessControl; using System.Security.Principal; using System.Text.RegularExpressions; using System.Xml.Serialization; using static C4IT.Logging.cLogManager; using System.Reflection; using C4IT.Logging; namespace C4IT_IAM_Engine { public class SecurityGroups { public string domainName; public string username; public SecureString password; public bool AllowExistingGroupWildcardMatch; public List IAM_SecurityGroups; public string rootUID; public SecurityGroups() { IAM_SecurityGroups = new List(); } public bool GroupsAllreadyExisting(string ouPath) { LogMethodBegin(MethodBase.GetCurrentMethod()); try { int groupCount = 0; if (IAM_SecurityGroups != null) foreach (var s in IAM_SecurityGroups) { if (s.securityGroupType != SecurityGroupType.Traverse) { DirectoryEntry entry = new DirectoryEntry { Path = "LDAP://" + domainName, Username = username, Password = new NetworkCredential("", password).Password, AuthenticationType = AuthenticationTypes.Secure | AuthenticationTypes.Sealing }; DirectorySearcher dSearch = new DirectorySearcher(entry) { Filter = "(&(CN=" + s.Name.ToUpper() + ")(objectClass=group))" }; dSearch.PageSize = 100000; SearchResultCollection sr = dSearch.FindAll(); groupCount += sr.Count; } } return groupCount > 0; } catch (Exception E) { cLogManager.DefaultLogger.LogException(E); throw; } finally { LogMethodEnd(MethodBase.GetCurrentMethod()); } } public bool GroupAllreadyExisting(string CN) { LogMethodBegin(MethodBase.GetCurrentMethod()); try { int groupCount = 0; if (CN != string.Empty) { DirectoryEntry entry = new DirectoryEntry { Path = "LDAP://" + domainName, Username = username, Password = new NetworkCredential("", password).Password, AuthenticationType = AuthenticationTypes.Secure | AuthenticationTypes.Sealing }; DirectorySearcher dSearch = new DirectorySearcher(entry) { Filter = "(&(CN=" + CN.ToUpper() + ")(objectClass=group))" }; dSearch.PageSize = 100000; SearchResultCollection sr = dSearch.FindAll(); groupCount += sr.Count; } return groupCount > 0; } catch (Exception E) { cLogManager.DefaultLogger.LogException(E); throw; } finally { LogMethodEnd(MethodBase.GetCurrentMethod()); } } public void GenerateNewSecurityGroups( string baseFolder, string newFolderPath, string groupPrefix, string ouPath, PermissionGroupStrategy groupPermissionStrategy, string groupTraverseTag, string groupReadTag, string groupWriteTag, string groupOwnerTag, string groupDLTag, string groupGTag, IDictionary customTags, List templates, int readACLPermission, int writeACLPermission, int ownerACLPermission, int loop = 0, int existingADGroupCount = 0) { LogMethodBegin(MethodBase.GetCurrentMethod()); try { var resolvedTemplates = (templates ?? new List()) .Select(template => new IAM_SecurityGroupTemplate( template.NamingTemplate, template.DescriptionTemplate, template.WildcardTemplate, template.Type, template.Scope)) .ToList(); 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 resolvedTemplates) { var GroupTypeTag = ""; switch (template.Type) { case SecurityGroupType.Owner: GroupTypeTag = groupOwnerTag; break; case SecurityGroupType.Write: GroupTypeTag = groupWriteTag; break; case SecurityGroupType.Read: GroupTypeTag = groupReadTag; break; case SecurityGroupType.Traverse: GroupTypeTag = groupTraverseTag; break; default: break; } var GroupScopeTag = ""; switch (template.Scope) { case GroupScope.Global: GroupScopeTag = groupGTag; break; case GroupScope.Local: GroupScopeTag = groupDLTag; break; default: break; } var tags = new Dictionary(); tags.Add("PREFIX", groupPrefix); 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(); } IAM_SecurityGroupTemplate ownerGlobal = resolvedTemplates.First(t => t.Scope.Equals(GroupScope.Global) && t.Type.Equals(SecurityGroupType.Owner)); IAM_SecurityGroup osecGroup = new IAM_SecurityGroup() { Name = ownerGlobal.NamingTemplate, description = ownerGlobal.DescriptionTemplate, WildcardPattern = ownerGlobal.WildcardTemplate, technicalName = "CN=" + ownerGlobal.NamingTemplate + "," + ouPath, targetTyp = (int)IAM_TargetType.FileSystem, rights = (FileSystemRights)ownerACLPermission, Scope = GroupScope.Global }; IAM_SecurityGroups.Add(osecGroup); IAM_SecurityGroupTemplate writeGlobal = resolvedTemplates.First(t => t.Scope.Equals(GroupScope.Global) && t.Type.Equals(SecurityGroupType.Write)); IAM_SecurityGroup wsecGroup = new IAM_SecurityGroup() { Name = writeGlobal.NamingTemplate, description = writeGlobal.DescriptionTemplate, WildcardPattern = writeGlobal.WildcardTemplate, technicalName = "CN=" + writeGlobal.NamingTemplate + "," + ouPath, targetTyp = (int)IAM_TargetType.FileSystem, rights = (FileSystemRights)writeACLPermission, Scope = GroupScope.Global }; IAM_SecurityGroups.Add(wsecGroup); IAM_SecurityGroupTemplate readGlobal = resolvedTemplates.First(t => t.Scope.Equals(GroupScope.Global) && t.Type.Equals(SecurityGroupType.Read)); IAM_SecurityGroup rsecGroup = new IAM_SecurityGroup() { Name = readGlobal.NamingTemplate, description = readGlobal.DescriptionTemplate, WildcardPattern = readGlobal.WildcardTemplate, technicalName = "CN=" + readGlobal.NamingTemplate + "," + ouPath, targetTyp = (int)IAM_TargetType.FileSystem, rights = (FileSystemRights)readACLPermission, Scope = GroupScope.Global }; IAM_SecurityGroups.Add(rsecGroup); // if (groupPermissionStrategy == PermissionGroupStrategy.AGDLP) { IAM_SecurityGroupTemplate ownerDL = resolvedTemplates.First(t => t.Scope.Equals(GroupScope.Local) && t.Type.Equals(SecurityGroupType.Owner)); IAM_SecurityGroup osecDLGroup = new IAM_SecurityGroup() { Name = ownerDL.NamingTemplate, description = ownerDL.DescriptionTemplate, WildcardPattern = ownerDL.WildcardTemplate, technicalName = "CN=" + ownerDL.NamingTemplate + "," + ouPath, targetTyp = (int)IAM_TargetType.FileSystem, rights = (FileSystemRights)ownerACLPermission, Scope = GroupScope.Local }; osecDLGroup.memberGroups.Add(osecGroup); IAM_SecurityGroups.Add(osecDLGroup); IAM_SecurityGroupTemplate writeDL = resolvedTemplates.First(t => t.Scope.Equals(GroupScope.Local) && t.Type.Equals(SecurityGroupType.Write)); IAM_SecurityGroup wsecDLGroup = new IAM_SecurityGroup() { Name = writeDL.NamingTemplate, description = writeDL.DescriptionTemplate, WildcardPattern = writeDL.WildcardTemplate, technicalName = "CN=" + writeDL.NamingTemplate + "," + ouPath, targetTyp = (int)IAM_TargetType.FileSystem, rights = (FileSystemRights)writeACLPermission, Scope = GroupScope.Local }; wsecDLGroup.memberGroups.Add(wsecGroup); IAM_SecurityGroups.Add(wsecDLGroup); IAM_SecurityGroupTemplate readDL = resolvedTemplates.First(t => t.Scope.Equals(GroupScope.Local) && t.Type.Equals(SecurityGroupType.Read)); IAM_SecurityGroup rsecDLGroup = new IAM_SecurityGroup() { Name = readDL.NamingTemplate, description = readDL.DescriptionTemplate, WildcardPattern = readDL.WildcardTemplate, technicalName = "CN=" + readDL.NamingTemplate + "," + ouPath, targetTyp = (int)IAM_TargetType.FileSystem, rights = (FileSystemRights)readACLPermission, Scope = GroupScope.Local }; rsecDLGroup.memberGroups.Add(rsecGroup); IAM_SecurityGroups.Add(rsecDLGroup); } foreach (var secGroup in IAM_SecurityGroups) { secGroup.description = secGroup.description.ReplaceLoopTag(0); secGroup.Name = secGroup.Name.ReplaceLoopTag(loop); secGroup.technicalName = secGroup.technicalName.ReplaceLoopTag(loop); secGroup.WildcardPattern = secGroup.WildcardPattern.ReplaceLoopTag(loop); DefaultLogger.LogEntry(LogLevels.Debug, $"Security group generated: {secGroup.technicalName}"); } } catch (Exception E) { cLogManager.DefaultLogger.LogException(E); throw; } finally { LogMethodEnd(MethodBase.GetCurrentMethod()); } } public static string GetRightPartOfPath(string path, string startAfterPart) { LogMethodBegin(MethodBase.GetCurrentMethod()); try { // use the correct seperator for the environment var pathParts = path.Split(Path.DirectorySeparatorChar); if (startAfterPart.EndsWith(Path.DirectorySeparatorChar.ToString())) { startAfterPart = startAfterPart.Substring(0, startAfterPart.Length - 1); } var startAfter = startAfterPart.Split(Path.DirectorySeparatorChar); string newPath = String.Empty; if (pathParts.Length > startAfter.Length) { for (int i = startAfter.Length; pathParts.Length > i; i++) { newPath += pathParts[i] + Path.DirectorySeparatorChar; } } if (newPath.EndsWith(Path.DirectorySeparatorChar.ToString())) { newPath = newPath.Substring(0, newPath.Length - 1); } // try and work out if last part was a directory - if not, drop the last part as we don't want the filename return newPath; } catch (Exception E) { cLogManager.DefaultLogger.LogException(E); throw; } finally { LogMethodEnd(MethodBase.GetCurrentMethod()); } } public static string getSID(DirectoryEntry ent) { LogMethodBegin(MethodBase.GetCurrentMethod()); try { var usrId = (byte[])ent.Properties["objectSid"][0]; return (new SecurityIdentifier(usrId, 0)).ToString(); } catch (Exception E) { cLogManager.DefaultLogger.LogException(E); throw; } finally { LogMethodEnd(MethodBase.GetCurrentMethod()); } } 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 DirectoryEntry FindGroupEntryByWildcard(string ouPath, string wildcardPattern) { if (string.IsNullOrWhiteSpace(wildcardPattern)) return null; Regex wildcardRegex; try { wildcardRegex = new Regex(wildcardPattern, RegexOptions.IgnoreCase); } catch (Exception E) { cLogManager.DefaultLogger.LogException(E); return null; } var basePath = "LDAP://" + domainName; if (!string.IsNullOrWhiteSpace(ouPath)) basePath += "/" + ouPath; DirectoryEntry entry = new DirectoryEntry { Path = basePath, Username = username, Password = new NetworkCredential("", password).Password, AuthenticationType = AuthenticationTypes.Secure | AuthenticationTypes.Sealing }; DirectorySearcher search = new DirectorySearcher(entry) { Filter = "(objectClass=group)" }; search.PageSize = 100000; search.PropertiesToLoad.Add("sAMAccountName"); search.PropertiesToLoad.Add("distinguishedName"); string matchedName = null; string matchedDistinguishedName = null; var matchCount = 0; foreach (SearchResult result in search.FindAll()) { if (!result.Properties.Contains("sAMAccountName") || result.Properties["sAMAccountName"].Count == 0) continue; var samAccountName = result.Properties["sAMAccountName"][0]?.ToString(); if (string.IsNullOrWhiteSpace(samAccountName) || !wildcardRegex.IsMatch(samAccountName)) continue; matchCount++; if (matchCount > 1) { DefaultLogger.LogEntry(LogLevels.Warning, $"Multiple AD groups matched wildcard '{wildcardPattern}' in '{basePath}'. Regex-based reuse is skipped."); search.Dispose(); entry.Dispose(); return null; } matchedName = samAccountName; matchedDistinguishedName = result.Properties.Contains("distinguishedName") && result.Properties["distinguishedName"].Count > 0 ? result.Properties["distinguishedName"][0]?.ToString() : null; } search.Dispose(); entry.Dispose(); if (string.IsNullOrWhiteSpace(matchedDistinguishedName)) return null; DefaultLogger.LogEntry(LogLevels.Debug, $"Reusing existing AD group '{matchedName}' via wildcard '{wildcardPattern}'."); return new DirectoryEntry("LDAP://" + domainName + "/" + matchedDistinguishedName, username, new NetworkCredential("", password).Password, AuthenticationTypes.Secure | AuthenticationTypes.Sealing); } private void ApplyExistingGroup(IAM_SecurityGroup secGroup, DirectoryEntry existingGroup) { secGroup.ReusedExistingEntry = true; secGroup.UID = getSID(existingGroup); if (existingGroup.Properties.Contains("sAMAccountName") && existingGroup.Properties["sAMAccountName"].Count > 0) secGroup.Name = existingGroup.Properties["sAMAccountName"][0]?.ToString(); if (existingGroup.Properties.Contains("distinguishedName") && existingGroup.Properties["distinguishedName"].Count > 0) secGroup.technicalName = existingGroup.Properties["distinguishedName"][0]?.ToString(); } 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 { secGroup.ReusedExistingEntry = false; var existingGroup = FindGroupEntry(secGroup.Name); if (existingGroup == null && AllowExistingGroupWildcardMatch) existingGroup = FindGroupEntryByWildcard(ouPath, secGroup.WildcardPattern); if (existingGroup == null) return CreateADGroup(ouPath, secGroup, users); AddMissingMembers(existingGroup, secGroup, users); ApplyExistingGroup(secGroup, existingGroup); 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 { secGroup.ReusedExistingEntry = false; if (!GroupAllreadyExisting(secGroup.Name.ToUpper())) { DirectoryEntry entry = new DirectoryEntry("LDAP://" + domainName + "/" + ouPath, username, new NetworkCredential("", password).Password, AuthenticationTypes.Secure | AuthenticationTypes.Sealing); DefaultLogger.LogEntry(LogLevels.Debug, $"Creating ad entry with CN / sAmAccountName: {secGroup.Name.ToUpper()}"); DirectoryEntry group = entry.Children.Add("CN=" + secGroup.Name.ToUpper(), "group"); group.Properties["sAmAccountName"].Value = secGroup.Name.ToUpper(); if (users != null && secGroup.Scope == GroupScope.Global) { foreach (var user in users) { DefaultLogger.LogEntry(LogLevels.Debug, $"Adding member: {user.DistinguishedName}"); group.Properties["member"].Add(user.DistinguishedName); } } if(!String.IsNullOrEmpty(secGroup.description)) { DefaultLogger.LogEntry(LogLevels.Debug, $"Setting description: {secGroup.description}"); group.Properties["description"].Value = secGroup.description; } var groupType = secGroup.Scope == GroupScope.Global ? GroupScopeValues.Global : GroupScopeValues.Local; DefaultLogger.LogEntry(LogLevels.Debug, $"Setting groupType to: {groupType}"); group.Properties["groupType"].Value = groupType; if (secGroup.Scope == GroupScope.Local) foreach (var iGroup in secGroup.memberGroups) { DefaultLogger.LogEntry(LogLevels.Debug, $"Adding member: {iGroup.technicalName}"); group.Properties["member"].Add(iGroup.technicalName); } group.CommitChanges(); DirectoryEntry ent = new DirectoryEntry("LDAP://" + domainName + "/" + "CN =" + secGroup.Name.ToUpper() + "," + ouPath, username, new NetworkCredential("", password).Password, AuthenticationTypes.Secure | AuthenticationTypes.Sealing); var objectid = SecurityGroups.getSID(ent); DefaultLogger.LogEntry(LogLevels.Debug, $"Security group created in ad: {secGroup.technicalName}"); secGroup.UID = objectid; return ent; } else { DirectoryEntry e = FindGroupEntry(secGroup.Name); if (e == null) return null; AddMissingMembers(e, secGroup, users); ApplyExistingGroup(secGroup, e); return e; } return null; } catch (Exception E) { cLogManager.DefaultLogger.LogException(E); throw; } finally { LogMethodEnd(MethodBase.GetCurrentMethod()); } } } public enum GroupScopeValues : int { Global = -2147483646, Local = -2147483644 } public class IAM_SecurityGroupTemplate { private string namingTemplate; private string descriptionTemplate; private string wildcardTemplate; private SecurityGroupType type; private GroupScope scope; public IAM_SecurityGroupTemplate(string namingTemplate, string descriptionTemplate, string wildcardTemplate, SecurityGroupType type, GroupScope scope) { NamingTemplate = namingTemplate; DescriptionTemplate = descriptionTemplate; WildcardTemplate = wildcardTemplate; Type = type; Scope = scope; } public string NamingTemplate { get => namingTemplate; set { namingTemplate = value == null ? "" : value; } } public string DescriptionTemplate { get => descriptionTemplate; set { descriptionTemplate = value == null ? "" : value; } } public string WildcardTemplate { get => wildcardTemplate; set { wildcardTemplate = value == null ? "" : value; } } public SecurityGroupType Type { get => type; set => type = value; } public GroupScope Scope { get => scope; set => scope = value; } } public class IAM_SecurityGroup { public string UID; public string Parent = ""; public string description; public string WildcardPattern; public bool ReusedExistingEntry; public List memberGroups; public string Name; public string technicalName; public SecurityGroupType securityGroupType; public int targetTyp; public GroupScope Scope; public FileSystemRights rights; public IAM_SecurityGroup() { memberGroups = new List(); } } public enum SecurityGroupType { [XmlEnum(Name = "0")] Owner, [XmlEnum(Name = "1")] Write, [XmlEnum(Name = "2")] Read, [XmlEnum(Name = "3")] Traverse } }