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 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 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; 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 = templates.First(t => t.Scope.Equals(GroupScope.Global) && t.Type.Equals(SecurityGroupType.Owner)); IAM_SecurityGroup osecGroup = new IAM_SecurityGroup() { Name = ownerGlobal.NamingTemplate, description = ownerGlobal.DescriptionTemplate, technicalName = "CN=" + ownerGlobal.NamingTemplate + "," + ouPath, targetTyp = (int)IAM_TargetType.FileSystem, rights = (FileSystemRights)ownerACLPermission, Scope = GroupScope.Global }; IAM_SecurityGroups.Add(osecGroup); IAM_SecurityGroupTemplate writeGlobal = templates.First(t => t.Scope.Equals(GroupScope.Global) && t.Type.Equals(SecurityGroupType.Write)); IAM_SecurityGroup wsecGroup = new IAM_SecurityGroup() { Name = writeGlobal.NamingTemplate, description = writeGlobal.DescriptionTemplate, technicalName = "CN=" + writeGlobal.NamingTemplate + "," + ouPath, targetTyp = (int)IAM_TargetType.FileSystem, rights = (FileSystemRights)writeACLPermission, Scope = GroupScope.Global }; IAM_SecurityGroups.Add(wsecGroup); IAM_SecurityGroupTemplate readGlobal = templates.First(t => t.Scope.Equals(GroupScope.Global) && t.Type.Equals(SecurityGroupType.Read)); IAM_SecurityGroup rsecGroup = new IAM_SecurityGroup() { Name = readGlobal.NamingTemplate, description = readGlobal.DescriptionTemplate, 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 = templates.First(t => t.Scope.Equals(GroupScope.Local) && t.Type.Equals(SecurityGroupType.Owner)); IAM_SecurityGroup osecDLGroup = new IAM_SecurityGroup() { Name = ownerDL.NamingTemplate, description = ownerDL.DescriptionTemplate, 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 = templates.First(t => t.Scope.Equals(GroupScope.Local) && t.Type.Equals(SecurityGroupType.Write)); IAM_SecurityGroup wsecDLGroup = new IAM_SecurityGroup() { Name = writeDL.NamingTemplate, description = writeDL.DescriptionTemplate, 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 = templates.First(t => t.Scope.Equals(GroupScope.Local) && t.Type.Equals(SecurityGroupType.Read)); IAM_SecurityGroup rsecDLGroup = new IAM_SecurityGroup() { Name = readDL.NamingTemplate, description = readDL.DescriptionTemplate, 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); 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 static bool HasMember(PropertyValueCollection members, string distinguishedName) { foreach (var member in members) { if (string.Equals(member?.ToString(), distinguishedName, StringComparison.OrdinalIgnoreCase)) return true; } return false; } private void AddMissingMembers(DirectoryEntry group, IAM_SecurityGroup secGroup, List users) { if (group == null) return; var changed = false; if (users != null && secGroup.Scope == GroupScope.Global) { foreach (var user in users.Where(u => u != null && !string.IsNullOrWhiteSpace(u.DistinguishedName))) { if (HasMember(group.Properties["member"], user.DistinguishedName)) continue; DefaultLogger.LogEntry(LogLevels.Debug, $"Adding missing member: {user.DistinguishedName}"); group.Properties["member"].Add(user.DistinguishedName); changed = true; } } if (secGroup.Scope == GroupScope.Local) { foreach (var innerGroup in secGroup.memberGroups.Where(g => g != null && !string.IsNullOrWhiteSpace(g.technicalName))) { if (HasMember(group.Properties["member"], innerGroup.technicalName)) continue; DefaultLogger.LogEntry(LogLevels.Debug, $"Adding missing nested group: {innerGroup.technicalName}"); group.Properties["member"].Add(innerGroup.technicalName); changed = true; } } if (changed) group.CommitChanges(); } public DirectoryEntry EnsureADGroup(string ouPath, IAM_SecurityGroup secGroup, List users) { LogMethodBegin(MethodBase.GetCurrentMethod()); try { var existingGroup = FindGroupEntry(secGroup.Name); if (existingGroup == null) return CreateADGroup(ouPath, secGroup, users); AddMissingMembers(existingGroup, secGroup, users); var objectid = getSID(existingGroup); secGroup.UID = objectid; return existingGroup; } catch (Exception E) { cLogManager.DefaultLogger.LogException(E); throw; } finally { LogMethodEnd(MethodBase.GetCurrentMethod()); } } public DirectoryEntry CreateADGroup(string ouPath, IAM_SecurityGroup secGroup, List users) { LogMethodBegin(MethodBase.GetCurrentMethod()); try { 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; var objectid = getSID(e); secGroup.UID = objectid; AddMissingMembers(e, secGroup, users); 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 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 } }