From 29ed5a47c82590445869f6611b781dd3fb961aeb Mon Sep 17 00:00:00 2001 From: anemeth Date: Wed, 10 Jan 2024 14:38:32 -0800 Subject: [PATCH 01/15] Prepare domain object attributes for ADCS ESC6 --- .../Processors/LDAPPropertyProcessor.cs | 76 ++++++++----------- src/CommonLib/SearchResultEntryWrapper.cs | 2 +- test/unit/LDAPPropertyTests.cs | 21 ++++- 3 files changed, 49 insertions(+), 50 deletions(-) diff --git a/src/CommonLib/Processors/LDAPPropertyProcessor.cs b/src/CommonLib/Processors/LDAPPropertyProcessor.cs index 1fe66830..87a09b7a 100644 --- a/src/CommonLib/Processors/LDAPPropertyProcessor.cs +++ b/src/CommonLib/Processors/LDAPPropertyProcessor.cs @@ -150,40 +150,23 @@ public async Task ReadUserProperties(ISearchResultEntry entry) var props = GetCommonProps(entry); var uac = entry.GetProperty(LDAPProperties.UserAccountControl); - bool enabled, trustedToAuth, sensitive, dontReqPreAuth, passwdNotReq, unconstrained, pwdNeverExpires; - if (int.TryParse(uac, out var flag)) + var uacFlags = (UacFlags)0; + if (entry.GetIntProperty(uac, out var flag)) { - var flags = (UacFlags)flag; - enabled = (flags & UacFlags.AccountDisable) == 0; - trustedToAuth = (flags & UacFlags.TrustedToAuthForDelegation) != 0; - sensitive = (flags & UacFlags.NotDelegated) != 0; - dontReqPreAuth = (flags & UacFlags.DontReqPreauth) != 0; - passwdNotReq = (flags & UacFlags.PasswordNotRequired) != 0; - unconstrained = (flags & UacFlags.TrustedForDelegation) != 0; - pwdNeverExpires = (flags & UacFlags.DontExpirePassword) != 0; - } - else - { - trustedToAuth = false; - enabled = true; - sensitive = false; - dontReqPreAuth = false; - passwdNotReq = false; - unconstrained = false; - pwdNeverExpires = false; + uacFlags = (UacFlags)flag; + props.Add("sensitive", uacFlags.HasFlag(UacFlags.NotDelegated)); + props.Add("dontreqpreauth", uacFlags.HasFlag(UacFlags.DontReqPreauth)); + props.Add("passwordnotreqd", uacFlags.HasFlag(UacFlags.PasswordNotRequired)); + props.Add("unconstraineddelegation", uacFlags.HasFlag(UacFlags.TrustedForDelegation)); + props.Add("pwdneverexpires", uacFlags.HasFlag(UacFlags.DontExpirePassword)); + props.Add("enabled", !uacFlags.HasFlag(UacFlags.AccountDisable)); + props.Add("trustedtoauth", uacFlags.HasFlag(UacFlags.TrustedToAuthForDelegation)); } - props.Add("sensitive", sensitive); - props.Add("dontreqpreauth", dontReqPreAuth); - props.Add("passwordnotreqd", passwdNotReq); - props.Add("unconstraineddelegation", unconstrained); - props.Add("pwdneverexpires", pwdNeverExpires); - props.Add("enabled", enabled); - props.Add("trustedtoauth", trustedToAuth); var domain = Helpers.DistinguishedNameToDomain(entry.DistinguishedName); var comps = new List(); - if (trustedToAuth) + if (uacFlags.HasFlag(UacFlags.TrustedToAuthForDelegation)) { var delegates = entry.GetArrayProperty(LDAPProperties.AllowedToDelegateTo); props.Add("allowedtodelegate", delegates); @@ -278,25 +261,20 @@ public async Task ReadComputerProperties(ISearchResultEntry var props = GetCommonProps(entry); var uac = entry.GetProperty(LDAPProperties.UserAccountControl); - bool enabled, unconstrained, trustedToAuth; - if (int.TryParse(uac, out var flag)) + var flags = (UacFlags)0; + if (entry.GetIntProperty(uac, out var flag)) { - var flags = (UacFlags)flag; - enabled = (flags & UacFlags.AccountDisable) == 0; - unconstrained = (flags & UacFlags.TrustedForDelegation) == UacFlags.TrustedForDelegation; - trustedToAuth = (flags & UacFlags.TrustedToAuthForDelegation) != 0; - } - else - { - unconstrained = false; - enabled = true; - trustedToAuth = false; + flags = (UacFlags)flag; + props.Add("enabled", !flags.HasFlag(UacFlags.AccountDisable)); + props.Add("unconstraineddelegation", flags.HasFlag(UacFlags.TrustedForDelegation)); + props.Add("trustedtoauth", flags.HasFlag(UacFlags.TrustedToAuthForDelegation)); + props.Add("isdc", flags.HasFlag(UacFlags.ServerTrustAccount)); } var domain = Helpers.DistinguishedNameToDomain(entry.DistinguishedName); var comps = new List(); - if (trustedToAuth) + if (flags.HasFlag(UacFlags.TrustedToAuthForDelegation)) { var delegates = entry.GetArrayProperty(LDAPProperties.AllowedToDelegateTo); props.Add("allowedtodelegate", delegates); @@ -332,15 +310,13 @@ public async Task ReadComputerProperties(ISearchResultEntry compProps.AllowedToAct = allowedToActPrincipals.ToArray(); - props.Add("enabled", enabled); - props.Add("unconstraineddelegation", unconstrained); - props.Add("trustedtoauth", trustedToAuth); props.Add("lastlogon", Helpers.ConvertFileTimeToUnixEpoch(entry.GetProperty(LDAPProperties.LastLogon))); props.Add("lastlogontimestamp", Helpers.ConvertFileTimeToUnixEpoch(entry.GetProperty(LDAPProperties.LastLogonTimestamp))); props.Add("pwdlastset", Helpers.ConvertFileTimeToUnixEpoch(entry.GetProperty(LDAPProperties.PasswordLastSet))); props.Add("serviceprincipalnames", entry.GetArrayProperty(LDAPProperties.ServicePrincipalNames)); + props.Add("email", entry.GetProperty(LDAPProperties.Email)); var os = entry.GetProperty(LDAPProperties.OperatingSystem); var sp = entry.GetProperty(LDAPProperties.ServicePack); @@ -516,6 +492,16 @@ public static Dictionary ReadCertTemplateProperties(ISearchResul nameFlags.HasFlag(PKICertificateNameFlag.ENROLLEE_SUPPLIES_SUBJECT)); props.Add("subjectaltrequireupn", nameFlags.HasFlag(PKICertificateNameFlag.SUBJECT_ALT_REQUIRE_UPN)); + props.Add("subjectaltrequiredns", + nameFlags.HasFlag(PKICertificateNameFlag.SUBJECT_ALT_REQUIRE_DNS)); + props.Add("subjectaltrequiredomaindns", + nameFlags.HasFlag(PKICertificateNameFlag.SUBJECT_ALT_REQUIRE_DOMAIN_DNS)); + props.Add("subjectaltrequireemail", + nameFlags.HasFlag(PKICertificateNameFlag.SUBJECT_ALT_REQUIRE_EMAIL)); + props.Add("subjectaltrequirespn", + nameFlags.HasFlag(PKICertificateNameFlag.SUBJECT_ALT_REQUIRE_SPN)); + props.Add("subjectrequireemail", + nameFlags.HasFlag(PKICertificateNameFlag.SUBJECT_REQUIRE_EMAIL)); } string[] ekus = entry.GetArrayProperty(LDAPProperties.ExtendedKeyUsage); @@ -761,4 +747,4 @@ public class ComputerProperties public TypedPrincipal[] SidHistory { get; set; } = Array.Empty(); public TypedPrincipal[] DumpSMSAPassword { get; set; } = Array.Empty(); } -} \ No newline at end of file +} diff --git a/src/CommonLib/SearchResultEntryWrapper.cs b/src/CommonLib/SearchResultEntryWrapper.cs index 0f0856e3..2e191c02 100644 --- a/src/CommonLib/SearchResultEntryWrapper.cs +++ b/src/CommonLib/SearchResultEntryWrapper.cs @@ -63,7 +63,7 @@ public ResolvedSearchResult ResolveBloodHoundInfo() if (int.TryParse(uac, out var flag)) { var flags = (UacFlags) flag; - if ((flags & UacFlags.ServerTrustAccount) != 0) + if (flags.HasFlag(UacFlags.ServerTrustAccount)) { _log.LogTrace("Marked {SID} as a domain controller", objectId); res.IsDomainController = true; diff --git a/test/unit/LDAPPropertyTests.cs b/test/unit/LDAPPropertyTests.cs index ab71cf83..86a979a9 100644 --- a/test/unit/LDAPPropertyTests.cs +++ b/test/unit/LDAPPropertyTests.cs @@ -264,6 +264,7 @@ public async Task LDAPPropertyProcessor_ReadUserProperties_HappyPath() {"lastlogon", "132673011142753043"}, {"lastlogontimestamp", "132670318095676525"}, {"homedirectory", @"\\win10\testdir"}, + {"email", "test@testdomain.com"}, { "serviceprincipalname", new[] { @@ -298,6 +299,8 @@ public async Task LDAPPropertyProcessor_ReadUserProperties_HappyPath() Assert.Equal(1568693134, (long)props["pwdlastset"]); Assert.Contains("homedirectory", keys); Assert.Equal(@"\\win10\testdir", props["homedirectory"] as string); + Assert.Contains("email", keys); + Assert.Equal("test@testdomain.com", props["email"] as string); //UAC stuff Assert.Contains("sensitive", keys); @@ -397,6 +400,7 @@ public async Task LDAPPropertyProcessor_ReadComputerProperties_HappyPath() {"lastlogontimestamp", "132670318095676525"}, {"operatingsystem", "Windows 10 Enterprise"}, {"operatingsystemservicepack", "1607"}, + {"email", "test@testdomain.com"}, {"admincount", "c"}, { "sidhistory", new[] @@ -434,11 +438,15 @@ public async Task LDAPPropertyProcessor_ReadComputerProperties_HappyPath() //UAC Assert.Contains("enabled", keys); Assert.Contains("unconstraineddelegation", keys); + Assert.Contains("trustedtoauth", keys); + Assert.Contains("isdc", keys); Assert.Contains("lastlogon", keys); Assert.Contains("lastlogontimestamp", keys); Assert.Contains("pwdlastset", keys); Assert.True((bool)props["enabled"]); Assert.False((bool)props["unconstraineddelegation"]); + Assert.False((bool)props["trustedtoauth"]); + Assert.False((bool)props["isdc"]); Assert.Contains("lastlogon", keys); Assert.Equal(1622827514, (long)props["lastlogon"]); @@ -462,6 +470,8 @@ public async Task LDAPPropertyProcessor_ReadComputerProperties_HappyPath() Assert.Equal("Windows 10 Enterprise 1607", props["operatingsystem"] as string); Assert.Contains("description", keys); Assert.Equal("Test", props["description"] as string); + Assert.Contains("email", keys); + Assert.Equal("test@testdomain.com", props["email"] as string); //SidHistory Assert.Contains("sidhistory", keys); @@ -700,9 +710,7 @@ public void LDAPPropertyProcessor_ReadCertTemplateProperties() {"oid", "1.3.6.1.4.1.311.21.8.4571196.1884641.3293620.10686285.12068043.134.1.30"}, {"enrollmentflag", 32}, {"requiresmanagerapproval", false}, - {"certificatenameflag", 134217728}, - {"enrolleesuppliessubject", false}, - {"subjectaltrequireupn", false}, + {"certificatenameflag", 0x8000000}, {"ekus", new[] {"1.3.6.1.5.5.7.3.2"} }, @@ -739,6 +747,11 @@ public void LDAPPropertyProcessor_ReadCertTemplateProperties() Assert.Contains("certificatenameflag", keys); Assert.Contains("enrolleesuppliessubject", keys); Assert.Contains("subjectaltrequireupn", keys); + Assert.Contains("subjectaltrequiredns", keys); + Assert.Contains("subjectaltrequiredomaindns", keys); + Assert.Contains("subjectaltrequireemail", keys); + Assert.Contains("subjectaltrequirespn", keys); + Assert.Contains("subjectrequireemail", keys); Assert.Contains("ekus", keys); Assert.Contains("certificateapplicationpolicy", keys); Assert.Contains("authorizedsignatures", keys); @@ -833,4 +846,4 @@ public void LDAPPropertyProcessor_ParseAllProperties_CollectionCountOne_NotBadPa } } -} \ No newline at end of file +} From 7b9a3918b6c419a7e0128d8d50ffc23a52ddc2da Mon Sep 17 00:00:00 2001 From: anemeth Date: Thu, 11 Jan 2024 12:39:07 -0800 Subject: [PATCH 02/15] Correct property parsing, add produce false properties for uac --- .../Processors/LDAPPropertyProcessor.cs | 36 ++++++++++--------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/src/CommonLib/Processors/LDAPPropertyProcessor.cs b/src/CommonLib/Processors/LDAPPropertyProcessor.cs index 87a09b7a..019cb1ea 100644 --- a/src/CommonLib/Processors/LDAPPropertyProcessor.cs +++ b/src/CommonLib/Processors/LDAPPropertyProcessor.cs @@ -148,20 +148,21 @@ public async Task ReadUserProperties(ISearchResultEntry entry) { var userProps = new UserProperties(); var props = GetCommonProps(entry); - - var uac = entry.GetProperty(LDAPProperties.UserAccountControl); + var uacFlags = (UacFlags)0; - if (entry.GetIntProperty(uac, out var flag)) + var uac = entry.GetProperty(LDAPProperties.UserAccountControl); + if (int.TryParse(uac, out var flag)) { uacFlags = (UacFlags)flag; - props.Add("sensitive", uacFlags.HasFlag(UacFlags.NotDelegated)); - props.Add("dontreqpreauth", uacFlags.HasFlag(UacFlags.DontReqPreauth)); - props.Add("passwordnotreqd", uacFlags.HasFlag(UacFlags.PasswordNotRequired)); - props.Add("unconstraineddelegation", uacFlags.HasFlag(UacFlags.TrustedForDelegation)); - props.Add("pwdneverexpires", uacFlags.HasFlag(UacFlags.DontExpirePassword)); - props.Add("enabled", !uacFlags.HasFlag(UacFlags.AccountDisable)); - props.Add("trustedtoauth", uacFlags.HasFlag(UacFlags.TrustedToAuthForDelegation)); } + + props.Add("sensitive", uacFlags.HasFlag(UacFlags.NotDelegated)); + props.Add("dontreqpreauth", uacFlags.HasFlag(UacFlags.DontReqPreauth)); + props.Add("passwordnotreqd", uacFlags.HasFlag(UacFlags.PasswordNotRequired)); + props.Add("unconstraineddelegation", uacFlags.HasFlag(UacFlags.TrustedForDelegation)); + props.Add("pwdneverexpires", uacFlags.HasFlag(UacFlags.DontExpirePassword)); + props.Add("enabled", !uacFlags.HasFlag(UacFlags.AccountDisable)); + props.Add("trustedtoauth", uacFlags.HasFlag(UacFlags.TrustedToAuthForDelegation)); var domain = Helpers.DistinguishedNameToDomain(entry.DistinguishedName); @@ -259,17 +260,18 @@ public async Task ReadComputerProperties(ISearchResultEntry { var compProps = new ComputerProperties(); var props = GetCommonProps(entry); - - var uac = entry.GetProperty(LDAPProperties.UserAccountControl); + var flags = (UacFlags)0; - if (entry.GetIntProperty(uac, out var flag)) + var uac = entry.GetProperty(LDAPProperties.UserAccountControl); + if (int.TryParse(uac, out var flag)) { flags = (UacFlags)flag; - props.Add("enabled", !flags.HasFlag(UacFlags.AccountDisable)); - props.Add("unconstraineddelegation", flags.HasFlag(UacFlags.TrustedForDelegation)); - props.Add("trustedtoauth", flags.HasFlag(UacFlags.TrustedToAuthForDelegation)); - props.Add("isdc", flags.HasFlag(UacFlags.ServerTrustAccount)); } + + props.Add("enabled", !flags.HasFlag(UacFlags.AccountDisable)); + props.Add("unconstraineddelegation", flags.HasFlag(UacFlags.TrustedForDelegation)); + props.Add("trustedtoauth", flags.HasFlag(UacFlags.TrustedToAuthForDelegation)); + props.Add("isdc", flags.HasFlag(UacFlags.ServerTrustAccount)); var domain = Helpers.DistinguishedNameToDomain(entry.DistinguishedName); From e5f03e7e653c3907dbaadf1ca1b4f57e19fa420a Mon Sep 17 00:00:00 2001 From: anemeth Date: Tue, 16 Jan 2024 10:56:59 -0800 Subject: [PATCH 03/15] Correct email tests for ReadUser and ReadComputer Properties --- test/unit/LDAPPropertyTests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/unit/LDAPPropertyTests.cs b/test/unit/LDAPPropertyTests.cs index 86a979a9..5da83d68 100644 --- a/test/unit/LDAPPropertyTests.cs +++ b/test/unit/LDAPPropertyTests.cs @@ -264,7 +264,7 @@ public async Task LDAPPropertyProcessor_ReadUserProperties_HappyPath() {"lastlogon", "132673011142753043"}, {"lastlogontimestamp", "132670318095676525"}, {"homedirectory", @"\\win10\testdir"}, - {"email", "test@testdomain.com"}, + {"mail", "test@testdomain.com"}, { "serviceprincipalname", new[] { @@ -400,7 +400,7 @@ public async Task LDAPPropertyProcessor_ReadComputerProperties_HappyPath() {"lastlogontimestamp", "132670318095676525"}, {"operatingsystem", "Windows 10 Enterprise"}, {"operatingsystemservicepack", "1607"}, - {"email", "test@testdomain.com"}, + {"mail", "test@testdomain.com"}, {"admincount", "c"}, { "sidhistory", new[] @@ -445,7 +445,7 @@ public async Task LDAPPropertyProcessor_ReadComputerProperties_HappyPath() Assert.Contains("pwdlastset", keys); Assert.True((bool)props["enabled"]); Assert.False((bool)props["unconstraineddelegation"]); - Assert.False((bool)props["trustedtoauth"]); + Assert.True((bool)props["trustedtoauth"]); Assert.False((bool)props["isdc"]); Assert.Contains("lastlogon", keys); From fc207bbce353714079e22f3232cc4453158985c5 Mon Sep 17 00:00:00 2001 From: anemeth Date: Tue, 16 Jan 2024 12:40:42 -0800 Subject: [PATCH 04/15] WIP LDAPPropertyProcessor refactor --- src/CommonLib/Extensions.cs | 8 + .../Processors/LDAPPropertyProcessor.cs | 225 +++++++++++++----- 2 files changed, 177 insertions(+), 56 deletions(-) diff --git a/src/CommonLib/Extensions.cs b/src/CommonLib/Extensions.cs index cfae61bd..8f2e67d9 100644 --- a/src/CommonLib/Extensions.cs +++ b/src/CommonLib/Extensions.cs @@ -393,6 +393,14 @@ public static Label GetLabel(this SearchResultEntry entry) return objectType; } + public static void AddRange(this Dictionary me, IEnumerable> keyValuePairs) + { + foreach (var kvp in keyValuePairs) + { + me.Add(kvp.Key, kvp.Value); + } + } + private const string GroupPolicyContainerClass = "groupPolicyContainer"; private const string OrganizationalUnitClass = "organizationalUnit"; private const string DomainClass = "domain"; diff --git a/src/CommonLib/Processors/LDAPPropertyProcessor.cs b/src/CommonLib/Processors/LDAPPropertyProcessor.cs index 019cb1ea..74336737 100644 --- a/src/CommonLib/Processors/LDAPPropertyProcessor.cs +++ b/src/CommonLib/Processors/LDAPPropertyProcessor.cs @@ -35,10 +35,10 @@ private static Dictionary GetCommonProps(ISearchResultEntry entr return new Dictionary { { - "description", entry.GetProperty(LDAPProperties.Description) + PropertyMap.GetPropertyName(LDAPProperties.Description), entry.GetProperty(LDAPProperties.Description) }, { - "whencreated", Helpers.ConvertTimestampToUnixEpoch(entry.GetProperty(LDAPProperties.WhenCreated)) + PropertyMap.GetPropertyName(LDAPProperties.WhenCreated), Helpers.ConvertTimestampToUnixEpoch(entry.GetProperty(LDAPProperties.WhenCreated)) } }; } @@ -51,37 +51,11 @@ private static Dictionary GetCommonProps(ISearchResultEntry entr public static Dictionary ReadDomainProperties(ISearchResultEntry entry) { var props = GetCommonProps(entry); - - if (!int.TryParse(entry.GetProperty(LDAPProperties.DomainFunctionalLevel), out var level)) level = -1; - - props.Add("functionallevel", FunctionalLevelToString(level)); + props.AddRange(PropertyMap.GetProperties(LDAPProperties.DomainFunctionalLevel, entry)); return props; } - /// - /// Converts a numeric representation of a functional level to its appropriate functional level string - /// - /// - /// - public static string FunctionalLevelToString(int level) - { - var functionalLevel = level switch - { - 0 => "2000 Mixed/Native", - 1 => "2003 Interim", - 2 => "2003", - 3 => "2008", - 4 => "2008 R2", - 5 => "2012", - 6 => "2012 R2", - 7 => "2016", - _ => "Unknown" - }; - - return functionalLevel; - } - /// /// Reads specific LDAP properties related to GPOs /// @@ -90,7 +64,7 @@ public static string FunctionalLevelToString(int level) public static Dictionary ReadGPOProperties(ISearchResultEntry entry) { var props = GetCommonProps(entry); - props.Add("gpcpath", entry.GetProperty(LDAPProperties.GPCFileSYSPath)?.ToUpper()); + props.AddRange(PropertyMap.GetProperties(LDAPProperties.GPCFileSYSPath, entry)); return props; } @@ -139,6 +113,53 @@ public static Dictionary ReadContainerProperties(ISearchResultEn return props; } + public static Dictionary ReadUacFlags(ISearchResultEntry entry) + { + var props = new Dictionary(); + + var uacFlags = (UacFlags)0; + var uac = entry.GetProperty(LDAPProperties.UserAccountControl); + if (int.TryParse(uac, out var flags)) + { + uacFlags = (UacFlags)flags; + } + + // var allFlags = ReadFlags(uacFlags); + // + // foreach (var flag in allFlags) + // { + // + // } + + props.Add("sensitive", uacFlags.HasFlag(UacFlags.NotDelegated)); + props.Add("dontreqpreauth", uacFlags.HasFlag(UacFlags.DontReqPreauth)); + props.Add("passwordnotreqd", uacFlags.HasFlag(UacFlags.PasswordNotRequired)); + props.Add("unconstraineddelegation", uacFlags.HasFlag(UacFlags.TrustedForDelegation)); + props.Add("pwdneverexpires", uacFlags.HasFlag(UacFlags.DontExpirePassword)); + props.Add("enabled", !uacFlags.HasFlag(UacFlags.AccountDisable)); + props.Add("trustedtoauth", uacFlags.HasFlag(UacFlags.TrustedToAuthForDelegation)); + props.Add("isdc", uacFlags.HasFlag(UacFlags.ServerTrustAccount)); + + return props; + } + + /// + /// + /// + /// + /// + /// + private static Dictionary ReadFlags(T flags) + where T : Enum + { + return Enum.GetValues(typeof(T)) + .Cast() + .ToDictionary( + val => val, + val => flags.HasFlag(val) + ); + } + /// /// Reads specific LDAP properties related to Users /// @@ -154,6 +175,7 @@ public async Task ReadUserProperties(ISearchResultEntry entry) if (int.TryParse(uac, out var flag)) { uacFlags = (UacFlags)flag; + var meow = ReadFlags(uacFlags); } props.Add("sensitive", uacFlags.HasFlag(UacFlags.NotDelegated)); @@ -169,8 +191,7 @@ public async Task ReadUserProperties(ISearchResultEntry entry) var comps = new List(); if (uacFlags.HasFlag(UacFlags.TrustedToAuthForDelegation)) { - var delegates = entry.GetArrayProperty(LDAPProperties.AllowedToDelegateTo); - props.Add("allowedtodelegate", delegates); + props.AddRange(PropertyMap.GetProperties(LDAPProperties.AllowedToDelegateTo, entry)); foreach (var d in delegates) { @@ -189,23 +210,19 @@ public async Task ReadUserProperties(ISearchResultEntry entry) userProps.AllowedToDelegate = comps.Distinct().ToArray(); - props.Add("lastlogon", Helpers.ConvertFileTimeToUnixEpoch(entry.GetProperty(LDAPProperties.LastLogon))); - props.Add("lastlogontimestamp", - Helpers.ConvertFileTimeToUnixEpoch(entry.GetProperty(LDAPProperties.LastLogonTimestamp))); - props.Add("pwdlastset", - Helpers.ConvertFileTimeToUnixEpoch(entry.GetProperty(LDAPProperties.PasswordLastSet))); - var spn = entry.GetArrayProperty(LDAPProperties.ServicePrincipalNames); - props.Add("serviceprincipalnames", spn); - props.Add("hasspn", spn.Length > 0); - props.Add("displayname", entry.GetProperty(LDAPProperties.DisplayName)); - props.Add("email", entry.GetProperty(LDAPProperties.Email)); - props.Add("title", entry.GetProperty(LDAPProperties.Title)); - props.Add("homedirectory", entry.GetProperty(LDAPProperties.HomeDirectory)); - props.Add("userpassword", entry.GetProperty(LDAPProperties.UserPassword)); - props.Add("unixpassword", entry.GetProperty(LDAPProperties.UnixUserPassword)); - props.Add("unicodepassword", entry.GetProperty(LDAPProperties.UnicodePassword)); - props.Add("sfupassword", entry.GetProperty(LDAPProperties.MsSFU30Password)); - props.Add("logonscript", entry.GetProperty(LDAPProperties.ScriptPath)); + props.AddRange(PropertyMap.GetProperties(LDAPProperties.LastLogon, entry)); + props.AddRange(PropertyMap.GetProperties(LDAPProperties.LastLogonTimestamp, entry)); + props.AddRange(PropertyMap.GetProperties(LDAPProperties.PasswordLastSet, entry)); + props.AddRange(PropertyMap.GetProperties(LDAPProperties.ServicePrincipalNames, entry)); + props.AddRange(PropertyMap.GetProperties(LDAPProperties.DisplayName, entry)); + props.AddRange(PropertyMap.GetProperties(LDAPProperties.Email, entry)); + props.AddRange(PropertyMap.GetProperties(LDAPProperties.Title, entry)); + props.AddRange(PropertyMap.GetProperties(LDAPProperties.HomeDirectory, entry)); + props.AddRange(PropertyMap.GetProperties(LDAPProperties.UserPassword, entry)); + props.AddRange(PropertyMap.GetProperties(LDAPProperties.UnixUserPassword, entry)); + props.AddRange(PropertyMap.GetProperties(LDAPProperties.UnicodePassword, entry)); + props.AddRange(PropertyMap.GetProperties(LDAPProperties.MsSFU30Password, entry)); + props.AddRange(PropertyMap.GetProperties(LDAPProperties.ScriptPath, entry)); var ac = entry.GetProperty(LDAPProperties.AdminCount); if (ac != null) @@ -312,13 +329,11 @@ public async Task ReadComputerProperties(ISearchResultEntry compProps.AllowedToAct = allowedToActPrincipals.ToArray(); - props.Add("lastlogon", Helpers.ConvertFileTimeToUnixEpoch(entry.GetProperty(LDAPProperties.LastLogon))); - props.Add("lastlogontimestamp", - Helpers.ConvertFileTimeToUnixEpoch(entry.GetProperty(LDAPProperties.LastLogonTimestamp))); - props.Add("pwdlastset", - Helpers.ConvertFileTimeToUnixEpoch(entry.GetProperty(LDAPProperties.PasswordLastSet))); - props.Add("serviceprincipalnames", entry.GetArrayProperty(LDAPProperties.ServicePrincipalNames)); - props.Add("email", entry.GetProperty(LDAPProperties.Email)); + props.AddRange(PropertyMap.GetProperties(LDAPProperties.LastLogon, entry)); + props.AddRange(PropertyMap.GetProperties(LDAPProperties.LastLogonTimestamp, entry)); + props.AddRange(PropertyMap.GetProperties(LDAPProperties.PasswordLastSet, entry)); + props.AddRange(PropertyMap.GetProperties(LDAPProperties.ServicePrincipalNames, entry)); + props.AddRange(PropertyMap.GetProperties(LDAPProperties.Email, entry)); var os = entry.GetProperty(LDAPProperties.OperatingSystem); var sp = entry.GetProperty(LDAPProperties.ServicePack); @@ -693,6 +708,104 @@ private enum IsTextUnicodeFlags } } + public static class PropertyMap + { + public static Dictionary GetProperties(string ldapProperty, ISearchResultEntry entry) + { + var props = new Dictionary(); + switch (ldapProperty) + { + case LDAPProperties.Description: + props.Add("description", entry.GetProperty(LDAPProperties.Description)); + break; + case LDAPProperties.WhenCreated: + props.Add("whencreated", Helpers.ConvertTimestampToUnixEpoch(entry.GetProperty(LDAPProperties.WhenCreated))); + break; + case LDAPProperties.DomainFunctionalLevel: + if (!int.TryParse(entry.GetProperty(LDAPProperties.DomainFunctionalLevel), out var level)) + level = -1; + props.Add("functionallevel", FunctionalLevelToString(level)); + break; + case LDAPProperties.GPCFileSYSPath: + props.Add("gpcpath", entry.GetProperty(LDAPProperties.GPCFileSYSPath)?.ToUpper()); + break; + case LDAPProperties.AllowedToDelegateTo: + var delegates = entry.GetArrayProperty(LDAPProperties.AllowedToDelegateTo); + props.Add("delegates", delegates); + break; + case LDAPProperties.LastLogon: + props.Add("lastlogon", Helpers.ConvertFileTimeToUnixEpoch(entry.GetProperty(LDAPProperties.LastLogon))); + break; + case LDAPProperties.LastLogonTimestamp: + props.Add("lastlogontimestamp", Helpers.ConvertFileTimeToUnixEpoch(entry.GetProperty(LDAPProperties.LastLogonTimestamp))); + break; + case LDAPProperties.PasswordLastSet: + props.Add("pwdlastset", Helpers.ConvertFileTimeToUnixEpoch(entry.GetProperty(LDAPProperties.PasswordLastSet))); + break; + case LDAPProperties.ServicePrincipalNames: + var spn = entry.GetArrayProperty(LDAPProperties.ServicePrincipalNames); + props.Add("serviceprincipalnames", spn); + props.Add("hasspn", spn.Length > 0); + break; + case LDAPProperties.DisplayName: + props.Add("displayname", entry.GetProperty(LDAPProperties.DisplayName)); + break; + case LDAPProperties.Email: + props.Add("email", entry.GetProperty(LDAPProperties.Email)); + break; + case LDAPProperties.Title: + props.Add("title", entry.GetProperty(LDAPProperties.Title)); + break; + case LDAPProperties.HomeDirectory: + props.Add("homedirectory", entry.GetProperty(LDAPProperties.HomeDirectory)); + break; + case LDAPProperties.UserPassword: + props.Add("userpassword", entry.GetProperty(LDAPProperties.UserPassword)); + break; + case LDAPProperties.UnixUserPassword: + props.Add("unixpassword", entry.GetProperty(LDAPProperties.UnixUserPassword)); + break; + case LDAPProperties.UnicodePassword: + props.Add("unicodepassword", entry.GetProperty(LDAPProperties.UnicodePassword)); + break; + case LDAPProperties.MsSFU30Password: + props.Add("sfupassword", entry.GetProperty(LDAPProperties.MsSFU30Password)); + break; + case LDAPProperties.ScriptPath: + props.Add("logonscript", entry.GetProperty(LDAPProperties.ScriptPath)); + break; + + default: + throw new ArgumentException("Cannot resolve to output property name.", ldapProperty); + } + + return props; + } + + /// + /// Converts a numeric representation of a functional level to its appropriate functional level string + /// + /// + /// + public static string FunctionalLevelToString(int level) + { + var functionalLevel = level switch + { + 0 => "2000 Mixed/Native", + 1 => "2003 Interim", + 2 => "2003", + 3 => "2008", + 4 => "2008 R2", + 5 => "2012", + 6 => "2012 R2", + 7 => "2016", + _ => "Unknown" + }; + + return functionalLevel; + } + } + public class ParsedCertificate { public string Thumbprint { get; set; } From 36f5748263c3e9fba4e6240c3062516463a3cad7 Mon Sep 17 00:00:00 2001 From: anemeth Date: Tue, 16 Jan 2024 13:38:32 -0800 Subject: [PATCH 05/15] Bring LDAPPropertyProcessor back into a builable state --- .../Processors/LDAPPropertyProcessor.cs | 194 +++++++----------- test/unit/LDAPPropertyTests.cs | 2 +- 2 files changed, 76 insertions(+), 120 deletions(-) diff --git a/src/CommonLib/Processors/LDAPPropertyProcessor.cs b/src/CommonLib/Processors/LDAPPropertyProcessor.cs index 74336737..3fb2ed08 100644 --- a/src/CommonLib/Processors/LDAPPropertyProcessor.cs +++ b/src/CommonLib/Processors/LDAPPropertyProcessor.cs @@ -32,15 +32,9 @@ public LDAPPropertyProcessor(ILDAPUtils utils) private static Dictionary GetCommonProps(ISearchResultEntry entry) { - return new Dictionary - { - { - PropertyMap.GetPropertyName(LDAPProperties.Description), entry.GetProperty(LDAPProperties.Description) - }, - { - PropertyMap.GetPropertyName(LDAPProperties.WhenCreated), Helpers.ConvertTimestampToUnixEpoch(entry.GetProperty(LDAPProperties.WhenCreated)) - } - }; + var props = PropertyMap.GetProperties(LDAPProperties.Description, entry); + props.AddRange(PropertyMap.GetProperties(LDAPProperties.WhenCreated, entry)); + return props; } /// @@ -87,17 +81,7 @@ public static Dictionary ReadOUProperties(ISearchResultEntry ent public static Dictionary ReadGroupProperties(ISearchResultEntry entry) { var props = GetCommonProps(entry); - - var ac = entry.GetProperty(LDAPProperties.AdminCount); - if (ac != null) - { - var a = int.Parse(ac); - props.Add("admincount", a != 0); - } - else - { - props.Add("admincount", false); - } + props.AddRange(PropertyMap.GetProperties(LDAPProperties.AdminCount, entry)); return props; } @@ -113,53 +97,6 @@ public static Dictionary ReadContainerProperties(ISearchResultEn return props; } - public static Dictionary ReadUacFlags(ISearchResultEntry entry) - { - var props = new Dictionary(); - - var uacFlags = (UacFlags)0; - var uac = entry.GetProperty(LDAPProperties.UserAccountControl); - if (int.TryParse(uac, out var flags)) - { - uacFlags = (UacFlags)flags; - } - - // var allFlags = ReadFlags(uacFlags); - // - // foreach (var flag in allFlags) - // { - // - // } - - props.Add("sensitive", uacFlags.HasFlag(UacFlags.NotDelegated)); - props.Add("dontreqpreauth", uacFlags.HasFlag(UacFlags.DontReqPreauth)); - props.Add("passwordnotreqd", uacFlags.HasFlag(UacFlags.PasswordNotRequired)); - props.Add("unconstraineddelegation", uacFlags.HasFlag(UacFlags.TrustedForDelegation)); - props.Add("pwdneverexpires", uacFlags.HasFlag(UacFlags.DontExpirePassword)); - props.Add("enabled", !uacFlags.HasFlag(UacFlags.AccountDisable)); - props.Add("trustedtoauth", uacFlags.HasFlag(UacFlags.TrustedToAuthForDelegation)); - props.Add("isdc", uacFlags.HasFlag(UacFlags.ServerTrustAccount)); - - return props; - } - - /// - /// - /// - /// - /// - /// - private static Dictionary ReadFlags(T flags) - where T : Enum - { - return Enum.GetValues(typeof(T)) - .Cast() - .ToDictionary( - val => val, - val => flags.HasFlag(val) - ); - } - /// /// Reads specific LDAP properties related to Users /// @@ -170,28 +107,16 @@ public async Task ReadUserProperties(ISearchResultEntry entry) var userProps = new UserProperties(); var props = GetCommonProps(entry); - var uacFlags = (UacFlags)0; - var uac = entry.GetProperty(LDAPProperties.UserAccountControl); - if (int.TryParse(uac, out var flag)) - { - uacFlags = (UacFlags)flag; - var meow = ReadFlags(uacFlags); - } - - props.Add("sensitive", uacFlags.HasFlag(UacFlags.NotDelegated)); - props.Add("dontreqpreauth", uacFlags.HasFlag(UacFlags.DontReqPreauth)); - props.Add("passwordnotreqd", uacFlags.HasFlag(UacFlags.PasswordNotRequired)); - props.Add("unconstraineddelegation", uacFlags.HasFlag(UacFlags.TrustedForDelegation)); - props.Add("pwdneverexpires", uacFlags.HasFlag(UacFlags.DontExpirePassword)); - props.Add("enabled", !uacFlags.HasFlag(UacFlags.AccountDisable)); - props.Add("trustedtoauth", uacFlags.HasFlag(UacFlags.TrustedToAuthForDelegation)); + props.AddRange(PropertyMap.GetProperties(LDAPProperties.UserAccountControl, entry)); var domain = Helpers.DistinguishedNameToDomain(entry.DistinguishedName); var comps = new List(); - if (uacFlags.HasFlag(UacFlags.TrustedToAuthForDelegation)) + var uacFlags = PropertyMap.GetUacFlags(entry); + if (uacFlags[UacFlags.TrustedToAuthForDelegation]) { - props.AddRange(PropertyMap.GetProperties(LDAPProperties.AllowedToDelegateTo, entry)); + var delegates = entry.GetArrayProperty(LDAPProperties.AllowedToDelegateTo); + props.Add("delegates", delegates); foreach (var d in delegates) { @@ -223,19 +148,7 @@ public async Task ReadUserProperties(ISearchResultEntry entry) props.AddRange(PropertyMap.GetProperties(LDAPProperties.UnicodePassword, entry)); props.AddRange(PropertyMap.GetProperties(LDAPProperties.MsSFU30Password, entry)); props.AddRange(PropertyMap.GetProperties(LDAPProperties.ScriptPath, entry)); - - var ac = entry.GetProperty(LDAPProperties.AdminCount); - if (ac != null) - { - if (int.TryParse(ac, out var parsed)) - props.Add("admincount", parsed != 0); - else - props.Add("admincount", false); - } - else - { - props.Add("admincount", false); - } + props.AddRange(PropertyMap.GetProperties(LDAPProperties.AdminCount, entry)); var sh = entry.GetByteArrayProperty(LDAPProperties.SIDHistory); var sidHistoryList = new List(); @@ -278,22 +191,13 @@ public async Task ReadComputerProperties(ISearchResultEntry var compProps = new ComputerProperties(); var props = GetCommonProps(entry); - var flags = (UacFlags)0; - var uac = entry.GetProperty(LDAPProperties.UserAccountControl); - if (int.TryParse(uac, out var flag)) - { - flags = (UacFlags)flag; - } - - props.Add("enabled", !flags.HasFlag(UacFlags.AccountDisable)); - props.Add("unconstraineddelegation", flags.HasFlag(UacFlags.TrustedForDelegation)); - props.Add("trustedtoauth", flags.HasFlag(UacFlags.TrustedToAuthForDelegation)); - props.Add("isdc", flags.HasFlag(UacFlags.ServerTrustAccount)); + props.AddRange(PropertyMap.GetProperties(LDAPProperties.UserAccountControl, entry)); var domain = Helpers.DistinguishedNameToDomain(entry.DistinguishedName); var comps = new List(); - if (flags.HasFlag(UacFlags.TrustedToAuthForDelegation)) + var uacFlags = PropertyMap.GetUacFlags(entry); + if (uacFlags[UacFlags.TrustedToAuthForDelegation]) { var delegates = entry.GetArrayProperty(LDAPProperties.AllowedToDelegateTo); props.Add("allowedtodelegate", delegates); @@ -334,12 +238,7 @@ public async Task ReadComputerProperties(ISearchResultEntry props.AddRange(PropertyMap.GetProperties(LDAPProperties.PasswordLastSet, entry)); props.AddRange(PropertyMap.GetProperties(LDAPProperties.ServicePrincipalNames, entry)); props.AddRange(PropertyMap.GetProperties(LDAPProperties.Email, entry)); - var os = entry.GetProperty(LDAPProperties.OperatingSystem); - var sp = entry.GetProperty(LDAPProperties.ServicePack); - - if (sp != null) os = $"{os} {sp}"; - - props.Add("operatingsystem", os); + props.AddRange(PropertyMap.GetProperties(LDAPProperties.OperatingSystem, entry)); var sh = entry.GetByteArrayProperty(LDAPProperties.SIDHistory); var sidHistoryList = new List(); @@ -729,10 +628,6 @@ public static Dictionary GetProperties(string ldapProperty, ISea case LDAPProperties.GPCFileSYSPath: props.Add("gpcpath", entry.GetProperty(LDAPProperties.GPCFileSYSPath)?.ToUpper()); break; - case LDAPProperties.AllowedToDelegateTo: - var delegates = entry.GetArrayProperty(LDAPProperties.AllowedToDelegateTo); - props.Add("delegates", delegates); - break; case LDAPProperties.LastLogon: props.Add("lastlogon", Helpers.ConvertFileTimeToUnixEpoch(entry.GetProperty(LDAPProperties.LastLogon))); break; @@ -774,6 +669,36 @@ public static Dictionary GetProperties(string ldapProperty, ISea case LDAPProperties.ScriptPath: props.Add("logonscript", entry.GetProperty(LDAPProperties.ScriptPath)); break; + case LDAPProperties.AdminCount: + var ac = entry.GetProperty(LDAPProperties.AdminCount); + if (ac != null) + { + var a = int.Parse(ac); + props.Add("admincount", a != 0); + } + else + { + props.Add("admincount", false); + } + break; + case LDAPProperties.UserAccountControl: + var allFlags = GetUacFlags(entry); + props.Add("sensitive", allFlags[UacFlags.NotDelegated]); + props.Add("dontreqpreauth", allFlags[UacFlags.DontReqPreauth]); + props.Add("passwordnotreqd", allFlags[UacFlags.PasswordNotRequired]); + props.Add("unconstraineddelegation", allFlags[UacFlags.TrustedForDelegation]); + props.Add("pwdneverexpires", allFlags[UacFlags.DontExpirePassword]); + props.Add("enabled", !allFlags[UacFlags.AccountDisable]); + props.Add("trustedtoauth", allFlags[UacFlags.TrustedToAuthForDelegation]); + props.Add("isdc", allFlags[UacFlags.ServerTrustAccount]); + break; + case LDAPProperties.OperatingSystem: + case LDAPProperties.ServicePack: + var os = entry.GetProperty(LDAPProperties.OperatingSystem); + var sp = entry.GetProperty(LDAPProperties.ServicePack); + if (sp != null) os = $"{os} {sp}"; + props.Add("operatingsystem", os); + break; default: throw new ArgumentException("Cannot resolve to output property name.", ldapProperty); @@ -804,6 +729,37 @@ public static string FunctionalLevelToString(int level) return functionalLevel; } + + public static Dictionary GetUacFlags(ISearchResultEntry entry) + { + var props = new Dictionary(); + + var uacFlags = (UacFlags)0; + var uac = entry.GetProperty(LDAPProperties.UserAccountControl); + if (int.TryParse(uac, out var flags)) + { + uacFlags = (UacFlags)flags; + } + + return ReadFlags(uacFlags); + } + + /// + /// + /// + /// + /// + /// + public static Dictionary ReadFlags(T flags) + where T : Enum + { + return Enum.GetValues(typeof(T)) + .Cast() + .ToDictionary( + val => val, + val => flags.HasFlag(val) + ); + } } public class ParsedCertificate diff --git a/test/unit/LDAPPropertyTests.cs b/test/unit/LDAPPropertyTests.cs index 5da83d68..fe784c4c 100644 --- a/test/unit/LDAPPropertyTests.cs +++ b/test/unit/LDAPPropertyTests.cs @@ -65,7 +65,7 @@ public void LDAPPropertyProcessor_FunctionalLevelToString_TestFunctionalLevels() }; foreach (var (key, value) in expected) - Assert.Equal(value, LDAPPropertyProcessor.FunctionalLevelToString(key)); + Assert.Equal(value, PropertyMap.FunctionalLevelToString(key)); } [Fact] From a36b351e0251b6ddcadf7f016b9412b5925175b8 Mon Sep 17 00:00:00 2001 From: anemeth Date: Tue, 16 Jan 2024 13:58:22 -0800 Subject: [PATCH 06/15] And passing tests --- src/CommonLib/Processors/LDAPPropertyProcessor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CommonLib/Processors/LDAPPropertyProcessor.cs b/src/CommonLib/Processors/LDAPPropertyProcessor.cs index 3fb2ed08..7f587f04 100644 --- a/src/CommonLib/Processors/LDAPPropertyProcessor.cs +++ b/src/CommonLib/Processors/LDAPPropertyProcessor.cs @@ -116,7 +116,7 @@ public async Task ReadUserProperties(ISearchResultEntry entry) if (uacFlags[UacFlags.TrustedToAuthForDelegation]) { var delegates = entry.GetArrayProperty(LDAPProperties.AllowedToDelegateTo); - props.Add("delegates", delegates); + props.Add("allowedtodelegate", delegates); foreach (var d in delegates) { @@ -673,7 +673,7 @@ public static Dictionary GetProperties(string ldapProperty, ISea var ac = entry.GetProperty(LDAPProperties.AdminCount); if (ac != null) { - var a = int.Parse(ac); + int.TryParse(ac, out var a); props.Add("admincount", a != 0); } else From b5416de05b6f3db2f3f94f5ac98f6a7df9a3a16c Mon Sep 17 00:00:00 2001 From: anemeth Date: Tue, 16 Jan 2024 15:22:07 -0800 Subject: [PATCH 07/15] Broke down ReadUser and ReadComputer Property methods --- .../Processors/LDAPPropertyProcessor.cs | 207 ++++++++++-------- 1 file changed, 116 insertions(+), 91 deletions(-) diff --git a/src/CommonLib/Processors/LDAPPropertyProcessor.cs b/src/CommonLib/Processors/LDAPPropertyProcessor.cs index 7f587f04..8970c1b1 100644 --- a/src/CommonLib/Processors/LDAPPropertyProcessor.cs +++ b/src/CommonLib/Processors/LDAPPropertyProcessor.cs @@ -107,34 +107,10 @@ public async Task ReadUserProperties(ISearchResultEntry entry) var userProps = new UserProperties(); var props = GetCommonProps(entry); - props.AddRange(PropertyMap.GetProperties(LDAPProperties.UserAccountControl, entry)); - - var domain = Helpers.DistinguishedNameToDomain(entry.DistinguishedName); - - var comps = new List(); - var uacFlags = PropertyMap.GetUacFlags(entry); - if (uacFlags[UacFlags.TrustedToAuthForDelegation]) - { - var delegates = entry.GetArrayProperty(LDAPProperties.AllowedToDelegateTo); - props.Add("allowedtodelegate", delegates); - - foreach (var d in delegates) - { - if (d == null) - continue; - - var resolvedHost = await _utils.ResolveHostToSid(d, domain); - if (resolvedHost != null && resolvedHost.Contains("S-1")) - comps.Add(new TypedPrincipal - { - ObjectIdentifier = resolvedHost, - ObjectType = Label.Computer - }); - } - } - - userProps.AllowedToDelegate = comps.Distinct().ToArray(); + props.AddRange((PropertyMap.GetProperties(LDAPProperties.AllowedToDelegateTo, entry))); + userProps.AllowedToDelegate = (await ReadPropertyDelegates(entry)).ToArray(); + props.AddRange(PropertyMap.GetProperties(LDAPProperties.UserAccountControl, entry)); props.AddRange(PropertyMap.GetProperties(LDAPProperties.LastLogon, entry)); props.AddRange(PropertyMap.GetProperties(LDAPProperties.LastLogonTimestamp, entry)); props.AddRange(PropertyMap.GetProperties(LDAPProperties.PasswordLastSet, entry)); @@ -150,31 +126,9 @@ public async Task ReadUserProperties(ISearchResultEntry entry) props.AddRange(PropertyMap.GetProperties(LDAPProperties.ScriptPath, entry)); props.AddRange(PropertyMap.GetProperties(LDAPProperties.AdminCount, entry)); - var sh = entry.GetByteArrayProperty(LDAPProperties.SIDHistory); - var sidHistoryList = new List(); - var sidHistoryPrincipals = new List(); - foreach (var sid in sh) - { - string sSid; - try - { - sSid = new SecurityIdentifier(sid, 0).Value; - } - catch - { - continue; - } - - sidHistoryList.Add(sSid); - - var res = _utils.ResolveIDAndType(sSid, domain); - - sidHistoryPrincipals.Add(res); - } - - userProps.SidHistory = sidHistoryPrincipals.Distinct().ToArray(); - - props.Add("sidhistory", sidHistoryList.ToArray()); + var sidHistory = ReadSidHistory(entry); + userProps.SidHistory = sidHistory.Principles.ToArray(); + props.Add("sidhistory", sidHistory.History.ToArray()); userProps.Props = props; @@ -193,21 +147,51 @@ public async Task ReadComputerProperties(ISearchResultEntry props.AddRange(PropertyMap.GetProperties(LDAPProperties.UserAccountControl, entry)); - var domain = Helpers.DistinguishedNameToDomain(entry.DistinguishedName); + props.AddRange((PropertyMap.GetProperties(LDAPProperties.AllowedToDelegateTo, entry))); + compProps.AllowedToDelegate = (await ReadPropertyDelegates(entry)).ToArray(); + + compProps.AllowedToAct = ReadAllowedToActPrinciples(entry).ToArray(); + props.AddRange(PropertyMap.GetProperties(LDAPProperties.LastLogon, entry)); + props.AddRange(PropertyMap.GetProperties(LDAPProperties.LastLogonTimestamp, entry)); + props.AddRange(PropertyMap.GetProperties(LDAPProperties.PasswordLastSet, entry)); + props.AddRange(PropertyMap.GetProperties(LDAPProperties.ServicePrincipalNames, entry)); + props.AddRange(PropertyMap.GetProperties(LDAPProperties.Email, entry)); + props.AddRange(PropertyMap.GetProperties(LDAPProperties.OperatingSystem, entry)); + + var sidHistory = ReadSidHistory(entry); + compProps.SidHistory = sidHistory.Principles.ToArray(); + props.Add("sidhistory", sidHistory.History.ToArray()); + + compProps.DumpSMSAPassword = ReadSmsaPrinciples(entry).ToArray(); + + compProps.Props = props; + + return compProps; + } + + /// + /// Reads principals user or computer may delegate. + /// + /// + /// + public async Task> ReadPropertyDelegates(ISearchResultEntry entry) + { var comps = new List(); + + var domain = Helpers.DistinguishedNameToDomain(entry.DistinguishedName); var uacFlags = PropertyMap.GetUacFlags(entry); if (uacFlags[UacFlags.TrustedToAuthForDelegation]) { var delegates = entry.GetArrayProperty(LDAPProperties.AllowedToDelegateTo); - props.Add("allowedtodelegate", delegates); foreach (var d in delegates) { - var hname = d.Contains("/") ? d.Split('/')[1] : d; - hname = hname.Split(':')[0]; - var resolvedHost = await _utils.ResolveHostToSid(hname, domain); - if (resolvedHost != null && (resolvedHost.Contains(".") || resolvedHost.Contains("S-1"))) + if (d == null) + continue; + + var resolvedHost = await _utils.ResolveHostToSid(d, domain); + if (resolvedHost != null && resolvedHost.Contains("S-1")) comps.Add(new TypedPrincipal { ObjectIdentifier = resolvedHost, @@ -216,30 +200,17 @@ public async Task ReadComputerProperties(ISearchResultEntry } } - compProps.AllowedToDelegate = comps.Distinct().ToArray(); - - var allowedToActPrincipals = new List(); - var rawAllowedToAct = entry.GetByteProperty(LDAPProperties.AllowedToActOnBehalfOfOtherIdentity); - if (rawAllowedToAct != null) - { - var sd = _utils.MakeSecurityDescriptor(); - sd.SetSecurityDescriptorBinaryForm(rawAllowedToAct, AccessControlSections.Access); - foreach (var rule in sd.GetAccessRules(true, true, typeof(SecurityIdentifier))) - { - var res = _utils.ResolveIDAndType(rule.IdentityReference(), domain); - allowedToActPrincipals.Add(res); - } - } - - compProps.AllowedToAct = allowedToActPrincipals.ToArray(); - - props.AddRange(PropertyMap.GetProperties(LDAPProperties.LastLogon, entry)); - props.AddRange(PropertyMap.GetProperties(LDAPProperties.LastLogonTimestamp, entry)); - props.AddRange(PropertyMap.GetProperties(LDAPProperties.PasswordLastSet, entry)); - props.AddRange(PropertyMap.GetProperties(LDAPProperties.ServicePrincipalNames, entry)); - props.AddRange(PropertyMap.GetProperties(LDAPProperties.Email, entry)); - props.AddRange(PropertyMap.GetProperties(LDAPProperties.OperatingSystem, entry)); + return comps.Distinct().ToList(); + } + /// + /// Reads history of SID for domain object. + /// + /// + /// + public SidHistory ReadSidHistory(ISearchResultEntry entry) + { + var domain = Helpers.DistinguishedNameToDomain(entry.DistinguishedName); var sh = entry.GetByteArrayProperty(LDAPProperties.SIDHistory); var sidHistoryList = new List(); var sidHistoryPrincipals = new List(); @@ -262,12 +233,43 @@ public async Task ReadComputerProperties(ISearchResultEntry sidHistoryPrincipals.Add(res); } - compProps.SidHistory = sidHistoryPrincipals.ToArray(); + return new SidHistory(sidHistoryList, sidHistoryPrincipals); + } + + /// + /// Read principals for identities domain object may act on behalf of. + /// + /// + /// + public List ReadAllowedToActPrinciples(ISearchResultEntry entry) + { + var allowedToActPrincipals = new List(); + + var domain = Helpers.DistinguishedNameToDomain(entry.DistinguishedName); + var rawAllowedToAct = entry.GetByteProperty(LDAPProperties.AllowedToActOnBehalfOfOtherIdentity); + if (rawAllowedToAct != null) + { + var sd = _utils.MakeSecurityDescriptor(); + sd.SetSecurityDescriptorBinaryForm(rawAllowedToAct, AccessControlSections.Access); + foreach (var rule in sd.GetAccessRules(true, true, typeof(SecurityIdentifier))) + { + var res = _utils.ResolveIDAndType(rule.IdentityReference(), domain); + allowedToActPrincipals.Add(res); + } + } - props.Add("sidhistory", sidHistoryList.ToArray()); + return allowedToActPrincipals; + } - var hsa = entry.GetArrayProperty(LDAPProperties.HostServiceAccount); + /// + /// Read Standalone Managed Service Accounts of domain object. + /// + /// + /// + public List ReadSmsaPrinciples(ISearchResultEntry entry) + { var smsaPrincipals = new List(); + var hsa = entry.GetArrayProperty(LDAPProperties.HostServiceAccount); if (hsa != null) { foreach (var dn in hsa) @@ -279,11 +281,7 @@ public async Task ReadComputerProperties(ISearchResultEntry } } - compProps.DumpSMSAPassword = smsaPrincipals.ToArray(); - - compProps.Props = props; - - return compProps; + return smsaPrincipals; } /// @@ -607,6 +605,9 @@ private enum IsTextUnicodeFlags } } + /// + /// Provides single-truth mapping of domain object properties. + /// public static class PropertyMap { public static Dictionary GetProperties(string ldapProperty, ISearchResultEntry entry) @@ -699,6 +700,10 @@ public static Dictionary GetProperties(string ldapProperty, ISea if (sp != null) os = $"{os} {sp}"; props.Add("operatingsystem", os); break; + case LDAPProperties.AllowedToDelegateTo: + var delegates = entry.GetArrayProperty(LDAPProperties.AllowedToDelegateTo); + props.Add("allowedtodelegate", delegates); + break; default: throw new ArgumentException("Cannot resolve to output property name.", ldapProperty); @@ -730,6 +735,11 @@ public static string FunctionalLevelToString(int level) return functionalLevel; } + /// + /// Returns all flags of User Account Control and whether or not they're active. + /// + /// + /// public static Dictionary GetUacFlags(ISearchResultEntry entry) { var props = new Dictionary(); @@ -745,12 +755,12 @@ public static Dictionary GetUacFlags(ISearchResultEntry entry) } /// - /// + /// Get all flags of a domain object by enumeration. /// /// /// /// - public static Dictionary ReadFlags(T flags) + private static Dictionary ReadFlags(T flags) where T : Enum { return Enum.GetValues(typeof(T)) @@ -818,4 +828,19 @@ public class ComputerProperties public TypedPrincipal[] SidHistory { get; set; } = Array.Empty(); public TypedPrincipal[] DumpSMSAPassword { get; set; } = Array.Empty(); } + + /// + /// Holds list of SID history and principles. + /// + public class SidHistory + { + public List History { get; set; } + public List Principles { get; set; } + + public SidHistory(List history, List principals) + { + History = history; + Principles = principals; + } + } } From baff724580c66357c345f8e78a13796abf37a428 Mon Sep 17 00:00:00 2001 From: anemeth Date: Tue, 16 Jan 2024 15:48:15 -0800 Subject: [PATCH 08/15] Better manage SidHistory property addition --- .../Processors/LDAPPropertyProcessor.cs | 105 ++++++++++++++---- 1 file changed, 81 insertions(+), 24 deletions(-) diff --git a/src/CommonLib/Processors/LDAPPropertyProcessor.cs b/src/CommonLib/Processors/LDAPPropertyProcessor.cs index 8970c1b1..cd0b678c 100644 --- a/src/CommonLib/Processors/LDAPPropertyProcessor.cs +++ b/src/CommonLib/Processors/LDAPPropertyProcessor.cs @@ -127,8 +127,8 @@ public async Task ReadUserProperties(ISearchResultEntry entry) props.AddRange(PropertyMap.GetProperties(LDAPProperties.AdminCount, entry)); var sidHistory = ReadSidHistory(entry); + props.Add(PropertyMap.GetPropertyName(LDAPProperties.SIDHistory), sidHistory.History.ToArray()); userProps.SidHistory = sidHistory.Principles.ToArray(); - props.Add("sidhistory", sidHistory.History.ToArray()); userProps.Props = props; @@ -160,8 +160,8 @@ public async Task ReadComputerProperties(ISearchResultEntry props.AddRange(PropertyMap.GetProperties(LDAPProperties.OperatingSystem, entry)); var sidHistory = ReadSidHistory(entry); + props.Add(PropertyMap.GetPropertyName(LDAPProperties.SIDHistory), sidHistory.History.ToArray()); compProps.SidHistory = sidHistory.Principles.ToArray(); - props.Add("sidhistory", sidHistory.History.ToArray()); compProps.DumpSMSAPassword = ReadSmsaPrinciples(entry).ToArray(); @@ -616,70 +616,70 @@ public static Dictionary GetProperties(string ldapProperty, ISea switch (ldapProperty) { case LDAPProperties.Description: - props.Add("description", entry.GetProperty(LDAPProperties.Description)); + props.Add(GetPropertyName(ldapProperty), entry.GetProperty(LDAPProperties.Description)); break; case LDAPProperties.WhenCreated: - props.Add("whencreated", Helpers.ConvertTimestampToUnixEpoch(entry.GetProperty(LDAPProperties.WhenCreated))); + props.Add(GetPropertyName(ldapProperty), Helpers.ConvertTimestampToUnixEpoch(entry.GetProperty(LDAPProperties.WhenCreated))); break; case LDAPProperties.DomainFunctionalLevel: if (!int.TryParse(entry.GetProperty(LDAPProperties.DomainFunctionalLevel), out var level)) level = -1; - props.Add("functionallevel", FunctionalLevelToString(level)); + props.Add(GetPropertyName(ldapProperty), FunctionalLevelToString(level)); break; case LDAPProperties.GPCFileSYSPath: - props.Add("gpcpath", entry.GetProperty(LDAPProperties.GPCFileSYSPath)?.ToUpper()); + props.Add(GetPropertyName(ldapProperty), entry.GetProperty(LDAPProperties.GPCFileSYSPath)?.ToUpper()); break; case LDAPProperties.LastLogon: - props.Add("lastlogon", Helpers.ConvertFileTimeToUnixEpoch(entry.GetProperty(LDAPProperties.LastLogon))); + props.Add(GetPropertyName(ldapProperty), Helpers.ConvertFileTimeToUnixEpoch(entry.GetProperty(LDAPProperties.LastLogon))); break; case LDAPProperties.LastLogonTimestamp: - props.Add("lastlogontimestamp", Helpers.ConvertFileTimeToUnixEpoch(entry.GetProperty(LDAPProperties.LastLogonTimestamp))); + props.Add(GetPropertyName(ldapProperty), Helpers.ConvertFileTimeToUnixEpoch(entry.GetProperty(LDAPProperties.LastLogonTimestamp))); break; case LDAPProperties.PasswordLastSet: - props.Add("pwdlastset", Helpers.ConvertFileTimeToUnixEpoch(entry.GetProperty(LDAPProperties.PasswordLastSet))); + props.Add(GetPropertyName(ldapProperty), Helpers.ConvertFileTimeToUnixEpoch(entry.GetProperty(LDAPProperties.PasswordLastSet))); break; case LDAPProperties.ServicePrincipalNames: var spn = entry.GetArrayProperty(LDAPProperties.ServicePrincipalNames); - props.Add("serviceprincipalnames", spn); + props.Add(GetPropertyName(ldapProperty), spn); props.Add("hasspn", spn.Length > 0); break; case LDAPProperties.DisplayName: - props.Add("displayname", entry.GetProperty(LDAPProperties.DisplayName)); + props.Add(GetPropertyName(ldapProperty), entry.GetProperty(LDAPProperties.DisplayName)); break; case LDAPProperties.Email: - props.Add("email", entry.GetProperty(LDAPProperties.Email)); + props.Add(GetPropertyName(ldapProperty), entry.GetProperty(LDAPProperties.Email)); break; case LDAPProperties.Title: - props.Add("title", entry.GetProperty(LDAPProperties.Title)); + props.Add(GetPropertyName(ldapProperty), entry.GetProperty(LDAPProperties.Title)); break; case LDAPProperties.HomeDirectory: - props.Add("homedirectory", entry.GetProperty(LDAPProperties.HomeDirectory)); + props.Add(GetPropertyName(ldapProperty), entry.GetProperty(LDAPProperties.HomeDirectory)); break; case LDAPProperties.UserPassword: - props.Add("userpassword", entry.GetProperty(LDAPProperties.UserPassword)); + props.Add(GetPropertyName(ldapProperty), entry.GetProperty(LDAPProperties.UserPassword)); break; case LDAPProperties.UnixUserPassword: - props.Add("unixpassword", entry.GetProperty(LDAPProperties.UnixUserPassword)); + props.Add(GetPropertyName(ldapProperty), entry.GetProperty(LDAPProperties.UnixUserPassword)); break; case LDAPProperties.UnicodePassword: - props.Add("unicodepassword", entry.GetProperty(LDAPProperties.UnicodePassword)); + props.Add(GetPropertyName(ldapProperty), entry.GetProperty(LDAPProperties.UnicodePassword)); break; case LDAPProperties.MsSFU30Password: - props.Add("sfupassword", entry.GetProperty(LDAPProperties.MsSFU30Password)); + props.Add(GetPropertyName(ldapProperty), entry.GetProperty(LDAPProperties.MsSFU30Password)); break; case LDAPProperties.ScriptPath: - props.Add("logonscript", entry.GetProperty(LDAPProperties.ScriptPath)); + props.Add(GetPropertyName(ldapProperty), entry.GetProperty(LDAPProperties.ScriptPath)); break; case LDAPProperties.AdminCount: var ac = entry.GetProperty(LDAPProperties.AdminCount); if (ac != null) { int.TryParse(ac, out var a); - props.Add("admincount", a != 0); + props.Add(GetPropertyName(ldapProperty), a != 0); } else { - props.Add("admincount", false); + props.Add(GetPropertyName(ldapProperty), false); } break; case LDAPProperties.UserAccountControl: @@ -694,15 +694,14 @@ public static Dictionary GetProperties(string ldapProperty, ISea props.Add("isdc", allFlags[UacFlags.ServerTrustAccount]); break; case LDAPProperties.OperatingSystem: - case LDAPProperties.ServicePack: var os = entry.GetProperty(LDAPProperties.OperatingSystem); var sp = entry.GetProperty(LDAPProperties.ServicePack); if (sp != null) os = $"{os} {sp}"; - props.Add("operatingsystem", os); + props.Add(GetPropertyName(ldapProperty), os); break; case LDAPProperties.AllowedToDelegateTo: var delegates = entry.GetArrayProperty(LDAPProperties.AllowedToDelegateTo); - props.Add("allowedtodelegate", delegates); + props.Add(GetPropertyName(ldapProperty), delegates); break; default: @@ -711,6 +710,64 @@ public static Dictionary GetProperties(string ldapProperty, ISea return props; } + + /// + /// Get output name of a domain object property. + /// + /// + /// + /// + public static string GetPropertyName(string ldapProperty) + { + switch (ldapProperty) + { + case LDAPProperties.Description: + return "description"; + case LDAPProperties.WhenCreated: + return "whencreated"; + case LDAPProperties.DomainFunctionalLevel: + return "functionallevel"; + case LDAPProperties.GPCFileSYSPath: + return "gpcpath"; + case LDAPProperties.LastLogon: + return "lastlogon"; + case LDAPProperties.LastLogonTimestamp: + return "lastlogontimestamp"; + case LDAPProperties.PasswordLastSet: + return "pwdlastset"; + case LDAPProperties.ServicePrincipalNames: + return "serviceprinciplenames"; + case LDAPProperties.DisplayName: + return "displayname"; + case LDAPProperties.Email: + return "email"; + case LDAPProperties.Title: + return "title"; + case LDAPProperties.HomeDirectory: + return "homedirectory"; + case LDAPProperties.UserPassword: + return "userpassword"; + case LDAPProperties.UnixUserPassword: + return "unixpassword"; + case LDAPProperties.UnicodePassword: + return "unicodepassword"; + case LDAPProperties.MsSFU30Password: + return "sfupassword"; + case LDAPProperties.ScriptPath: + return "logonscript"; + case LDAPProperties.AdminCount: + return "admincount"; + case LDAPProperties.OperatingSystem: + return "operatingsystem"; + case LDAPProperties.AllowedToDelegateTo: + return "allowedtodelegate"; + case LDAPProperties.SIDHistory: + return "sidhistory"; + + default: + throw new ArgumentException("Cannot resolve to output property name.", ldapProperty); + } + } /// /// Converts a numeric representation of a functional level to its appropriate functional level string From e4f8f711086ae6fc52f71326ddae8c703bce3d7b Mon Sep 17 00:00:00 2001 From: anemeth Date: Tue, 16 Jan 2024 16:16:26 -0800 Subject: [PATCH 09/15] Reorganizing GetProperties --- .../Processors/LDAPPropertyProcessor.cs | 398 ++++++++++-------- 1 file changed, 213 insertions(+), 185 deletions(-) diff --git a/src/CommonLib/Processors/LDAPPropertyProcessor.cs b/src/CommonLib/Processors/LDAPPropertyProcessor.cs index cd0b678c..30134100 100644 --- a/src/CommonLib/Processors/LDAPPropertyProcessor.cs +++ b/src/CommonLib/Processors/LDAPPropertyProcessor.cs @@ -32,8 +32,8 @@ public LDAPPropertyProcessor(ILDAPUtils utils) private static Dictionary GetCommonProps(ISearchResultEntry entry) { - var props = PropertyMap.GetProperties(LDAPProperties.Description, entry); - props.AddRange(PropertyMap.GetProperties(LDAPProperties.WhenCreated, entry)); + var props = GetProperties(LDAPProperties.Description, entry); + props.AddRange(GetProperties(LDAPProperties.WhenCreated, entry)); return props; } @@ -45,7 +45,7 @@ private static Dictionary GetCommonProps(ISearchResultEntry entr public static Dictionary ReadDomainProperties(ISearchResultEntry entry) { var props = GetCommonProps(entry); - props.AddRange(PropertyMap.GetProperties(LDAPProperties.DomainFunctionalLevel, entry)); + props.AddRange(GetProperties(LDAPProperties.DomainFunctionalLevel, entry)); return props; } @@ -58,7 +58,7 @@ public static Dictionary ReadDomainProperties(ISearchResultEntry public static Dictionary ReadGPOProperties(ISearchResultEntry entry) { var props = GetCommonProps(entry); - props.AddRange(PropertyMap.GetProperties(LDAPProperties.GPCFileSYSPath, entry)); + props.AddRange(GetProperties(LDAPProperties.GPCFileSYSPath, entry)); return props; } @@ -81,7 +81,7 @@ public static Dictionary ReadOUProperties(ISearchResultEntry ent public static Dictionary ReadGroupProperties(ISearchResultEntry entry) { var props = GetCommonProps(entry); - props.AddRange(PropertyMap.GetProperties(LDAPProperties.AdminCount, entry)); + props.AddRange(GetProperties(LDAPProperties.AdminCount, entry)); return props; } @@ -107,24 +107,24 @@ public async Task ReadUserProperties(ISearchResultEntry entry) var userProps = new UserProperties(); var props = GetCommonProps(entry); - props.AddRange((PropertyMap.GetProperties(LDAPProperties.AllowedToDelegateTo, entry))); + props.AddRange((GetProperties(LDAPProperties.AllowedToDelegateTo, entry))); userProps.AllowedToDelegate = (await ReadPropertyDelegates(entry)).ToArray(); - props.AddRange(PropertyMap.GetProperties(LDAPProperties.UserAccountControl, entry)); - props.AddRange(PropertyMap.GetProperties(LDAPProperties.LastLogon, entry)); - props.AddRange(PropertyMap.GetProperties(LDAPProperties.LastLogonTimestamp, entry)); - props.AddRange(PropertyMap.GetProperties(LDAPProperties.PasswordLastSet, entry)); - props.AddRange(PropertyMap.GetProperties(LDAPProperties.ServicePrincipalNames, entry)); - props.AddRange(PropertyMap.GetProperties(LDAPProperties.DisplayName, entry)); - props.AddRange(PropertyMap.GetProperties(LDAPProperties.Email, entry)); - props.AddRange(PropertyMap.GetProperties(LDAPProperties.Title, entry)); - props.AddRange(PropertyMap.GetProperties(LDAPProperties.HomeDirectory, entry)); - props.AddRange(PropertyMap.GetProperties(LDAPProperties.UserPassword, entry)); - props.AddRange(PropertyMap.GetProperties(LDAPProperties.UnixUserPassword, entry)); - props.AddRange(PropertyMap.GetProperties(LDAPProperties.UnicodePassword, entry)); - props.AddRange(PropertyMap.GetProperties(LDAPProperties.MsSFU30Password, entry)); - props.AddRange(PropertyMap.GetProperties(LDAPProperties.ScriptPath, entry)); - props.AddRange(PropertyMap.GetProperties(LDAPProperties.AdminCount, entry)); + props.AddRange(GetProperties(LDAPProperties.UserAccountControl, entry)); + props.AddRange(GetProperties(LDAPProperties.LastLogon, entry)); + props.AddRange(GetProperties(LDAPProperties.LastLogonTimestamp, entry)); + props.AddRange(GetProperties(LDAPProperties.PasswordLastSet, entry)); + props.AddRange(GetProperties(LDAPProperties.ServicePrincipalNames, entry)); + props.AddRange(GetProperties(LDAPProperties.DisplayName, entry)); + props.AddRange(GetProperties(LDAPProperties.Email, entry)); + props.AddRange(GetProperties(LDAPProperties.Title, entry)); + props.AddRange(GetProperties(LDAPProperties.HomeDirectory, entry)); + props.AddRange(GetProperties(LDAPProperties.UserPassword, entry)); + props.AddRange(GetProperties(LDAPProperties.UnixUserPassword, entry)); + props.AddRange(GetProperties(LDAPProperties.UnicodePassword, entry)); + props.AddRange(GetProperties(LDAPProperties.MsSFU30Password, entry)); + props.AddRange(GetProperties(LDAPProperties.ScriptPath, entry)); + props.AddRange(GetProperties(LDAPProperties.AdminCount, entry)); var sidHistory = ReadSidHistory(entry); props.Add(PropertyMap.GetPropertyName(LDAPProperties.SIDHistory), sidHistory.History.ToArray()); @@ -145,19 +145,19 @@ public async Task ReadComputerProperties(ISearchResultEntry var compProps = new ComputerProperties(); var props = GetCommonProps(entry); - props.AddRange(PropertyMap.GetProperties(LDAPProperties.UserAccountControl, entry)); + props.AddRange(GetProperties(LDAPProperties.UserAccountControl, entry)); - props.AddRange((PropertyMap.GetProperties(LDAPProperties.AllowedToDelegateTo, entry))); + props.AddRange((GetProperties(LDAPProperties.AllowedToDelegateTo, entry))); compProps.AllowedToDelegate = (await ReadPropertyDelegates(entry)).ToArray(); compProps.AllowedToAct = ReadAllowedToActPrinciples(entry).ToArray(); - props.AddRange(PropertyMap.GetProperties(LDAPProperties.LastLogon, entry)); - props.AddRange(PropertyMap.GetProperties(LDAPProperties.LastLogonTimestamp, entry)); - props.AddRange(PropertyMap.GetProperties(LDAPProperties.PasswordLastSet, entry)); - props.AddRange(PropertyMap.GetProperties(LDAPProperties.ServicePrincipalNames, entry)); - props.AddRange(PropertyMap.GetProperties(LDAPProperties.Email, entry)); - props.AddRange(PropertyMap.GetProperties(LDAPProperties.OperatingSystem, entry)); + props.AddRange(GetProperties(LDAPProperties.LastLogon, entry)); + props.AddRange(GetProperties(LDAPProperties.LastLogonTimestamp, entry)); + props.AddRange(GetProperties(LDAPProperties.PasswordLastSet, entry)); + props.AddRange(GetProperties(LDAPProperties.ServicePrincipalNames, entry)); + props.AddRange(GetProperties(LDAPProperties.Email, entry)); + props.AddRange(GetProperties(LDAPProperties.OperatingSystem, entry)); var sidHistory = ReadSidHistory(entry); props.Add(PropertyMap.GetPropertyName(LDAPProperties.SIDHistory), sidHistory.History.ToArray()); @@ -180,7 +180,7 @@ public async Task> ReadPropertyDelegates(ISearchResultEntry var comps = new List(); var domain = Helpers.DistinguishedNameToDomain(entry.DistinguishedName); - var uacFlags = PropertyMap.GetUacFlags(entry); + var uacFlags = GetUacFlags(entry); if (uacFlags[UacFlags.TrustedToAuthForDelegation]) { var delegates = entry.GetArrayProperty(LDAPProperties.AllowedToDelegateTo); @@ -489,6 +489,167 @@ public Dictionary ParseAllProperties(ISearchResultEntry entry) return props; } + + public static Dictionary GetProperties(string ldapProperty, ISearchResultEntry entry) + { + var props = new Dictionary(); + switch (ldapProperty) + { + case LDAPProperties.Description: + props.Add(PropertyMap.GetPropertyName(ldapProperty), entry.GetProperty(LDAPProperties.Description)); + break; + case LDAPProperties.WhenCreated: + props.Add(PropertyMap.GetPropertyName(ldapProperty), Helpers.ConvertTimestampToUnixEpoch(entry.GetProperty(LDAPProperties.WhenCreated))); + break; + case LDAPProperties.DomainFunctionalLevel: + if (!int.TryParse(entry.GetProperty(LDAPProperties.DomainFunctionalLevel), out var level)) + level = -1; + props.Add(PropertyMap.GetPropertyName(ldapProperty), FunctionalLevelToString(level)); + break; + case LDAPProperties.GPCFileSYSPath: + props.Add(PropertyMap.GetPropertyName(ldapProperty), entry.GetProperty(LDAPProperties.GPCFileSYSPath)?.ToUpper()); + break; + case LDAPProperties.LastLogon: + props.Add(PropertyMap.GetPropertyName(ldapProperty), Helpers.ConvertFileTimeToUnixEpoch(entry.GetProperty(LDAPProperties.LastLogon))); + break; + case LDAPProperties.LastLogonTimestamp: + props.Add(PropertyMap.GetPropertyName(ldapProperty), Helpers.ConvertFileTimeToUnixEpoch(entry.GetProperty(LDAPProperties.LastLogonTimestamp))); + break; + case LDAPProperties.PasswordLastSet: + props.Add(PropertyMap.GetPropertyName(ldapProperty), Helpers.ConvertFileTimeToUnixEpoch(entry.GetProperty(LDAPProperties.PasswordLastSet))); + break; + case LDAPProperties.ServicePrincipalNames: + var spn = entry.GetArrayProperty(LDAPProperties.ServicePrincipalNames); + props.Add(PropertyMap.GetPropertyName(ldapProperty), spn); + props.Add("hasspn", spn.Length > 0); + break; + case LDAPProperties.DisplayName: + props.Add(PropertyMap.GetPropertyName(ldapProperty), entry.GetProperty(LDAPProperties.DisplayName)); + break; + case LDAPProperties.Email: + props.Add(PropertyMap.GetPropertyName(ldapProperty), entry.GetProperty(LDAPProperties.Email)); + break; + case LDAPProperties.Title: + props.Add(PropertyMap.GetPropertyName(ldapProperty), entry.GetProperty(LDAPProperties.Title)); + break; + case LDAPProperties.HomeDirectory: + props.Add(PropertyMap.GetPropertyName(ldapProperty), entry.GetProperty(LDAPProperties.HomeDirectory)); + break; + case LDAPProperties.UserPassword: + props.Add(PropertyMap.GetPropertyName(ldapProperty), entry.GetProperty(LDAPProperties.UserPassword)); + break; + case LDAPProperties.UnixUserPassword: + props.Add(PropertyMap.GetPropertyName(ldapProperty), entry.GetProperty(LDAPProperties.UnixUserPassword)); + break; + case LDAPProperties.UnicodePassword: + props.Add(PropertyMap.GetPropertyName(ldapProperty), entry.GetProperty(LDAPProperties.UnicodePassword)); + break; + case LDAPProperties.MsSFU30Password: + props.Add(PropertyMap.GetPropertyName(ldapProperty), entry.GetProperty(LDAPProperties.MsSFU30Password)); + break; + case LDAPProperties.ScriptPath: + props.Add(PropertyMap.GetPropertyName(ldapProperty), entry.GetProperty(LDAPProperties.ScriptPath)); + break; + case LDAPProperties.AdminCount: + var ac = entry.GetProperty(LDAPProperties.AdminCount); + if (ac != null) + { + int.TryParse(ac, out var a); + props.Add(PropertyMap.GetPropertyName(ldapProperty), a != 0); + } + else + { + props.Add(PropertyMap.GetPropertyName(ldapProperty), false); + } + break; + case LDAPProperties.UserAccountControl: + var allFlags = GetUacFlags(entry); + props.Add(PropertyMap.GetUacPropertyName(UacFlags.NotDelegated), allFlags[UacFlags.NotDelegated]); + props.Add(PropertyMap.GetUacPropertyName(UacFlags.DontReqPreauth), allFlags[UacFlags.DontReqPreauth]); + props.Add(PropertyMap.GetUacPropertyName(UacFlags.PasswordNotRequired), allFlags[UacFlags.PasswordNotRequired]); + props.Add(PropertyMap.GetUacPropertyName(UacFlags.TrustedForDelegation), allFlags[UacFlags.TrustedForDelegation]); + props.Add(PropertyMap.GetUacPropertyName(UacFlags.DontExpirePassword), allFlags[UacFlags.DontExpirePassword]); + // Note that we flip the flag for Account Disable ("enabled" by resolved name) + props.Add(PropertyMap.GetUacPropertyName(UacFlags.AccountDisable), !allFlags[UacFlags.AccountDisable]); + props.Add(PropertyMap.GetUacPropertyName(UacFlags.TrustedToAuthForDelegation), allFlags[UacFlags.TrustedToAuthForDelegation]); + props.Add(PropertyMap.GetUacPropertyName(UacFlags.ServerTrustAccount), allFlags[UacFlags.ServerTrustAccount]); + break; + case LDAPProperties.OperatingSystem: + var os = entry.GetProperty(LDAPProperties.OperatingSystem); + var sp = entry.GetProperty(LDAPProperties.ServicePack); + if (sp != null) os = $"{os} {sp}"; + props.Add(PropertyMap.GetPropertyName(ldapProperty), os); + break; + case LDAPProperties.AllowedToDelegateTo: + var delegates = entry.GetArrayProperty(LDAPProperties.AllowedToDelegateTo); + props.Add(PropertyMap.GetPropertyName(ldapProperty), delegates); + break; + + default: + throw new ArgumentException("Cannot resolve to output property name.", ldapProperty); + } + + return props; + } + + /// + /// Converts a numeric representation of a functional level to its appropriate functional level string + /// + /// + /// + public static string FunctionalLevelToString(int level) + { + var functionalLevel = level switch + { + 0 => "2000 Mixed/Native", + 1 => "2003 Interim", + 2 => "2003", + 3 => "2008", + 4 => "2008 R2", + 5 => "2012", + 6 => "2012 R2", + 7 => "2016", + _ => "Unknown" + }; + + return functionalLevel; + } + + /// + /// Returns all flags of User Account Control and whether or not they're active. + /// + /// + /// + public static Dictionary GetUacFlags(ISearchResultEntry entry) + { + var props = new Dictionary(); + + var uacFlags = (UacFlags)0; + var uac = entry.GetProperty(LDAPProperties.UserAccountControl); + if (int.TryParse(uac, out var flags)) + { + uacFlags = (UacFlags)flags; + } + + return ReadFlags(uacFlags); + } + + /// + /// Get all flags of a domain object by enumeration. + /// + /// + /// + /// + private static Dictionary ReadFlags(T flags) + where T : Enum + { + return Enum.GetValues(typeof(T)) + .Cast() + .ToDictionary( + val => val, + val => flags.HasFlag(val) + ); + } /// /// Does a best guess conversion of the property to a type useable by the UI @@ -610,107 +771,6 @@ private enum IsTextUnicodeFlags /// public static class PropertyMap { - public static Dictionary GetProperties(string ldapProperty, ISearchResultEntry entry) - { - var props = new Dictionary(); - switch (ldapProperty) - { - case LDAPProperties.Description: - props.Add(GetPropertyName(ldapProperty), entry.GetProperty(LDAPProperties.Description)); - break; - case LDAPProperties.WhenCreated: - props.Add(GetPropertyName(ldapProperty), Helpers.ConvertTimestampToUnixEpoch(entry.GetProperty(LDAPProperties.WhenCreated))); - break; - case LDAPProperties.DomainFunctionalLevel: - if (!int.TryParse(entry.GetProperty(LDAPProperties.DomainFunctionalLevel), out var level)) - level = -1; - props.Add(GetPropertyName(ldapProperty), FunctionalLevelToString(level)); - break; - case LDAPProperties.GPCFileSYSPath: - props.Add(GetPropertyName(ldapProperty), entry.GetProperty(LDAPProperties.GPCFileSYSPath)?.ToUpper()); - break; - case LDAPProperties.LastLogon: - props.Add(GetPropertyName(ldapProperty), Helpers.ConvertFileTimeToUnixEpoch(entry.GetProperty(LDAPProperties.LastLogon))); - break; - case LDAPProperties.LastLogonTimestamp: - props.Add(GetPropertyName(ldapProperty), Helpers.ConvertFileTimeToUnixEpoch(entry.GetProperty(LDAPProperties.LastLogonTimestamp))); - break; - case LDAPProperties.PasswordLastSet: - props.Add(GetPropertyName(ldapProperty), Helpers.ConvertFileTimeToUnixEpoch(entry.GetProperty(LDAPProperties.PasswordLastSet))); - break; - case LDAPProperties.ServicePrincipalNames: - var spn = entry.GetArrayProperty(LDAPProperties.ServicePrincipalNames); - props.Add(GetPropertyName(ldapProperty), spn); - props.Add("hasspn", spn.Length > 0); - break; - case LDAPProperties.DisplayName: - props.Add(GetPropertyName(ldapProperty), entry.GetProperty(LDAPProperties.DisplayName)); - break; - case LDAPProperties.Email: - props.Add(GetPropertyName(ldapProperty), entry.GetProperty(LDAPProperties.Email)); - break; - case LDAPProperties.Title: - props.Add(GetPropertyName(ldapProperty), entry.GetProperty(LDAPProperties.Title)); - break; - case LDAPProperties.HomeDirectory: - props.Add(GetPropertyName(ldapProperty), entry.GetProperty(LDAPProperties.HomeDirectory)); - break; - case LDAPProperties.UserPassword: - props.Add(GetPropertyName(ldapProperty), entry.GetProperty(LDAPProperties.UserPassword)); - break; - case LDAPProperties.UnixUserPassword: - props.Add(GetPropertyName(ldapProperty), entry.GetProperty(LDAPProperties.UnixUserPassword)); - break; - case LDAPProperties.UnicodePassword: - props.Add(GetPropertyName(ldapProperty), entry.GetProperty(LDAPProperties.UnicodePassword)); - break; - case LDAPProperties.MsSFU30Password: - props.Add(GetPropertyName(ldapProperty), entry.GetProperty(LDAPProperties.MsSFU30Password)); - break; - case LDAPProperties.ScriptPath: - props.Add(GetPropertyName(ldapProperty), entry.GetProperty(LDAPProperties.ScriptPath)); - break; - case LDAPProperties.AdminCount: - var ac = entry.GetProperty(LDAPProperties.AdminCount); - if (ac != null) - { - int.TryParse(ac, out var a); - props.Add(GetPropertyName(ldapProperty), a != 0); - } - else - { - props.Add(GetPropertyName(ldapProperty), false); - } - break; - case LDAPProperties.UserAccountControl: - var allFlags = GetUacFlags(entry); - props.Add("sensitive", allFlags[UacFlags.NotDelegated]); - props.Add("dontreqpreauth", allFlags[UacFlags.DontReqPreauth]); - props.Add("passwordnotreqd", allFlags[UacFlags.PasswordNotRequired]); - props.Add("unconstraineddelegation", allFlags[UacFlags.TrustedForDelegation]); - props.Add("pwdneverexpires", allFlags[UacFlags.DontExpirePassword]); - props.Add("enabled", !allFlags[UacFlags.AccountDisable]); - props.Add("trustedtoauth", allFlags[UacFlags.TrustedToAuthForDelegation]); - props.Add("isdc", allFlags[UacFlags.ServerTrustAccount]); - break; - case LDAPProperties.OperatingSystem: - var os = entry.GetProperty(LDAPProperties.OperatingSystem); - var sp = entry.GetProperty(LDAPProperties.ServicePack); - if (sp != null) os = $"{os} {sp}"; - props.Add(GetPropertyName(ldapProperty), os); - break; - case LDAPProperties.AllowedToDelegateTo: - var delegates = entry.GetArrayProperty(LDAPProperties.AllowedToDelegateTo); - props.Add(GetPropertyName(ldapProperty), delegates); - break; - - default: - throw new ArgumentException("Cannot resolve to output property name.", ldapProperty); - } - - return props; - } - /// /// Get output name of a domain object property. /// @@ -768,64 +828,32 @@ public static string GetPropertyName(string ldapProperty) throw new ArgumentException("Cannot resolve to output property name.", ldapProperty); } } - - /// - /// Converts a numeric representation of a functional level to its appropriate functional level string - /// - /// - /// - public static string FunctionalLevelToString(int level) - { - var functionalLevel = level switch - { - 0 => "2000 Mixed/Native", - 1 => "2003 Interim", - 2 => "2003", - 3 => "2008", - 4 => "2008 R2", - 5 => "2012", - 6 => "2012 R2", - 7 => "2016", - _ => "Unknown" - }; - return functionalLevel; - } - - /// - /// Returns all flags of User Account Control and whether or not they're active. - /// - /// - /// - public static Dictionary GetUacFlags(ISearchResultEntry entry) + public static string GetUacPropertyName(UacFlags flag) { - var props = new Dictionary(); - - var uacFlags = (UacFlags)0; - var uac = entry.GetProperty(LDAPProperties.UserAccountControl); - if (int.TryParse(uac, out var flags)) + switch (flag) { - uacFlags = (UacFlags)flags; + case UacFlags.NotDelegated: + return "sensitive"; + case UacFlags.DontReqPreauth: + return "dontreqpreauth"; + case UacFlags.PasswordNotRequired: + return "passwordnotreqd"; + case UacFlags.TrustedForDelegation: + return "unconstraineddelegation"; + case UacFlags.DontExpirePassword: + return "pwdneverexpires"; + // Note that we flip the flag for output + case UacFlags.AccountDisable: + return "enabled"; + case UacFlags.TrustedToAuthForDelegation: + return "trustedtoauth"; + case UacFlags.ServerTrustAccount: + return "isdc"; + + default: + throw new ArgumentException("Cannot resolve to output property name.", Enum.GetName(typeof(UacFlags), flag)); } - - return ReadFlags(uacFlags); - } - - /// - /// Get all flags of a domain object by enumeration. - /// - /// - /// - /// - private static Dictionary ReadFlags(T flags) - where T : Enum - { - return Enum.GetValues(typeof(T)) - .Cast() - .ToDictionary( - val => val, - val => flags.HasFlag(val) - ); } } From 7b28a4b97f3541fcb308e01bb7ec863a2f6407d1 Mon Sep 17 00:00:00 2001 From: anemeth Date: Wed, 17 Jan 2024 08:18:39 -0800 Subject: [PATCH 10/15] Correct FunctionalLevelToString placement to satisfy build --- test/unit/LDAPPropertyTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/LDAPPropertyTests.cs b/test/unit/LDAPPropertyTests.cs index fe784c4c..5da83d68 100644 --- a/test/unit/LDAPPropertyTests.cs +++ b/test/unit/LDAPPropertyTests.cs @@ -65,7 +65,7 @@ public void LDAPPropertyProcessor_FunctionalLevelToString_TestFunctionalLevels() }; foreach (var (key, value) in expected) - Assert.Equal(value, PropertyMap.FunctionalLevelToString(key)); + Assert.Equal(value, LDAPPropertyProcessor.FunctionalLevelToString(key)); } [Fact] From 2be6f38f558a7500d7e7fb7a4ec297c7ecf496cd Mon Sep 17 00:00:00 2001 From: anemeth Date: Wed, 17 Jan 2024 15:13:13 -0800 Subject: [PATCH 11/15] Break ReadSidHistory into composite functions --- src/CommonLib/Extensions.cs | 9 +- .../Processors/LDAPPropertyProcessor.cs | 102 ++++++++---------- test/unit/LDAPPropertyTests.cs | 31 ++++++ 3 files changed, 86 insertions(+), 56 deletions(-) diff --git a/src/CommonLib/Extensions.cs b/src/CommonLib/Extensions.cs index 8f2e67d9..295a501c 100644 --- a/src/CommonLib/Extensions.cs +++ b/src/CommonLib/Extensions.cs @@ -393,7 +393,14 @@ public static Label GetLabel(this SearchResultEntry entry) return objectType; } - public static void AddRange(this Dictionary me, IEnumerable> keyValuePairs) + /// + /// Add multiple entries to a dictionary. + /// + /// Dictionary (this) + /// KeyValuePairs to add + /// Key + /// Value + public static void AddMany(this Dictionary me, IEnumerable> keyValuePairs) { foreach (var kvp in keyValuePairs) { diff --git a/src/CommonLib/Processors/LDAPPropertyProcessor.cs b/src/CommonLib/Processors/LDAPPropertyProcessor.cs index 30134100..7160128d 100644 --- a/src/CommonLib/Processors/LDAPPropertyProcessor.cs +++ b/src/CommonLib/Processors/LDAPPropertyProcessor.cs @@ -33,7 +33,7 @@ public LDAPPropertyProcessor(ILDAPUtils utils) private static Dictionary GetCommonProps(ISearchResultEntry entry) { var props = GetProperties(LDAPProperties.Description, entry); - props.AddRange(GetProperties(LDAPProperties.WhenCreated, entry)); + props.AddMany(GetProperties(LDAPProperties.WhenCreated, entry)); return props; } @@ -45,7 +45,7 @@ private static Dictionary GetCommonProps(ISearchResultEntry entr public static Dictionary ReadDomainProperties(ISearchResultEntry entry) { var props = GetCommonProps(entry); - props.AddRange(GetProperties(LDAPProperties.DomainFunctionalLevel, entry)); + props.AddMany(GetProperties(LDAPProperties.DomainFunctionalLevel, entry)); return props; } @@ -58,7 +58,7 @@ public static Dictionary ReadDomainProperties(ISearchResultEntry public static Dictionary ReadGPOProperties(ISearchResultEntry entry) { var props = GetCommonProps(entry); - props.AddRange(GetProperties(LDAPProperties.GPCFileSYSPath, entry)); + props.AddMany(GetProperties(LDAPProperties.GPCFileSYSPath, entry)); return props; } @@ -81,7 +81,7 @@ public static Dictionary ReadOUProperties(ISearchResultEntry ent public static Dictionary ReadGroupProperties(ISearchResultEntry entry) { var props = GetCommonProps(entry); - props.AddRange(GetProperties(LDAPProperties.AdminCount, entry)); + props.AddMany(GetProperties(LDAPProperties.AdminCount, entry)); return props; } @@ -107,28 +107,28 @@ public async Task ReadUserProperties(ISearchResultEntry entry) var userProps = new UserProperties(); var props = GetCommonProps(entry); - props.AddRange((GetProperties(LDAPProperties.AllowedToDelegateTo, entry))); + props.AddMany((GetProperties(LDAPProperties.AllowedToDelegateTo, entry))); userProps.AllowedToDelegate = (await ReadPropertyDelegates(entry)).ToArray(); - props.AddRange(GetProperties(LDAPProperties.UserAccountControl, entry)); - props.AddRange(GetProperties(LDAPProperties.LastLogon, entry)); - props.AddRange(GetProperties(LDAPProperties.LastLogonTimestamp, entry)); - props.AddRange(GetProperties(LDAPProperties.PasswordLastSet, entry)); - props.AddRange(GetProperties(LDAPProperties.ServicePrincipalNames, entry)); - props.AddRange(GetProperties(LDAPProperties.DisplayName, entry)); - props.AddRange(GetProperties(LDAPProperties.Email, entry)); - props.AddRange(GetProperties(LDAPProperties.Title, entry)); - props.AddRange(GetProperties(LDAPProperties.HomeDirectory, entry)); - props.AddRange(GetProperties(LDAPProperties.UserPassword, entry)); - props.AddRange(GetProperties(LDAPProperties.UnixUserPassword, entry)); - props.AddRange(GetProperties(LDAPProperties.UnicodePassword, entry)); - props.AddRange(GetProperties(LDAPProperties.MsSFU30Password, entry)); - props.AddRange(GetProperties(LDAPProperties.ScriptPath, entry)); - props.AddRange(GetProperties(LDAPProperties.AdminCount, entry)); + props.AddMany(GetProperties(LDAPProperties.UserAccountControl, entry)); + props.AddMany(GetProperties(LDAPProperties.LastLogon, entry)); + props.AddMany(GetProperties(LDAPProperties.LastLogonTimestamp, entry)); + props.AddMany(GetProperties(LDAPProperties.PasswordLastSet, entry)); + props.AddMany(GetProperties(LDAPProperties.ServicePrincipalNames, entry)); + props.AddMany(GetProperties(LDAPProperties.DisplayName, entry)); + props.AddMany(GetProperties(LDAPProperties.Email, entry)); + props.AddMany(GetProperties(LDAPProperties.Title, entry)); + props.AddMany(GetProperties(LDAPProperties.HomeDirectory, entry)); + props.AddMany(GetProperties(LDAPProperties.UserPassword, entry)); + props.AddMany(GetProperties(LDAPProperties.UnixUserPassword, entry)); + props.AddMany(GetProperties(LDAPProperties.UnicodePassword, entry)); + props.AddMany(GetProperties(LDAPProperties.MsSFU30Password, entry)); + props.AddMany(GetProperties(LDAPProperties.ScriptPath, entry)); + props.AddMany(GetProperties(LDAPProperties.AdminCount, entry)); var sidHistory = ReadSidHistory(entry); - props.Add(PropertyMap.GetPropertyName(LDAPProperties.SIDHistory), sidHistory.History.ToArray()); - userProps.SidHistory = sidHistory.Principles.ToArray(); + props.Add(PropertyMap.GetPropertyName(LDAPProperties.SIDHistory), sidHistory.ToArray()); + userProps.SidHistory = sidHistory.Select(ssid => ReadSidPrinciple(entry, ssid)).ToArray(); userProps.Props = props; @@ -145,23 +145,23 @@ public async Task ReadComputerProperties(ISearchResultEntry var compProps = new ComputerProperties(); var props = GetCommonProps(entry); - props.AddRange(GetProperties(LDAPProperties.UserAccountControl, entry)); + props.AddMany(GetProperties(LDAPProperties.UserAccountControl, entry)); - props.AddRange((GetProperties(LDAPProperties.AllowedToDelegateTo, entry))); + props.AddMany((GetProperties(LDAPProperties.AllowedToDelegateTo, entry))); compProps.AllowedToDelegate = (await ReadPropertyDelegates(entry)).ToArray(); compProps.AllowedToAct = ReadAllowedToActPrinciples(entry).ToArray(); - props.AddRange(GetProperties(LDAPProperties.LastLogon, entry)); - props.AddRange(GetProperties(LDAPProperties.LastLogonTimestamp, entry)); - props.AddRange(GetProperties(LDAPProperties.PasswordLastSet, entry)); - props.AddRange(GetProperties(LDAPProperties.ServicePrincipalNames, entry)); - props.AddRange(GetProperties(LDAPProperties.Email, entry)); - props.AddRange(GetProperties(LDAPProperties.OperatingSystem, entry)); + props.AddMany(GetProperties(LDAPProperties.LastLogon, entry)); + props.AddMany(GetProperties(LDAPProperties.LastLogonTimestamp, entry)); + props.AddMany(GetProperties(LDAPProperties.PasswordLastSet, entry)); + props.AddMany(GetProperties(LDAPProperties.ServicePrincipalNames, entry)); + props.AddMany(GetProperties(LDAPProperties.Email, entry)); + props.AddMany(GetProperties(LDAPProperties.OperatingSystem, entry)); var sidHistory = ReadSidHistory(entry); - props.Add(PropertyMap.GetPropertyName(LDAPProperties.SIDHistory), sidHistory.History.ToArray()); - compProps.SidHistory = sidHistory.Principles.ToArray(); + props.Add(PropertyMap.GetPropertyName(LDAPProperties.SIDHistory), sidHistory.ToArray()); + compProps.SidHistory = sidHistory.Select(ssid => ReadSidPrinciple(entry, ssid)).ToArray(); compProps.DumpSMSAPassword = ReadSmsaPrinciples(entry).ToArray(); @@ -204,16 +204,15 @@ public async Task> ReadPropertyDelegates(ISearchResultEntry } /// - /// Reads history of SID for domain object. + /// Reads history of SIDs for domain object. /// /// /// - public SidHistory ReadSidHistory(ISearchResultEntry entry) + public List ReadSidHistory(ISearchResultEntry entry) { var domain = Helpers.DistinguishedNameToDomain(entry.DistinguishedName); var sh = entry.GetByteArrayProperty(LDAPProperties.SIDHistory); var sidHistoryList = new List(); - var sidHistoryPrincipals = new List(); foreach (var sid in sh) { string sSid; @@ -227,13 +226,21 @@ public SidHistory ReadSidHistory(ISearchResultEntry entry) } sidHistoryList.Add(sSid); - - var res = _utils.ResolveIDAndType(sSid, domain); - - sidHistoryPrincipals.Add(res); } - return new SidHistory(sidHistoryList, sidHistoryPrincipals); + return sidHistoryList; + } + + /// + /// Get SID principal. + /// + /// + /// + /// + public TypedPrincipal ReadSidPrinciple(ISearchResultEntry entry, string sidHistoryItem) + { + var domain = Helpers.DistinguishedNameToDomain(entry.DistinguishedName); + return _utils.ResolveIDAndType(sidHistoryItem, domain); } /// @@ -913,19 +920,4 @@ public class ComputerProperties public TypedPrincipal[] SidHistory { get; set; } = Array.Empty(); public TypedPrincipal[] DumpSMSAPassword { get; set; } = Array.Empty(); } - - /// - /// Holds list of SID history and principles. - /// - public class SidHistory - { - public List History { get; set; } - public List Principles { get; set; } - - public SidHistory(List history, List principals) - { - History = history; - Principles = principals; - } - } } diff --git a/test/unit/LDAPPropertyTests.cs b/test/unit/LDAPPropertyTests.cs index 5da83d68..a1e9b807 100644 --- a/test/unit/LDAPPropertyTests.cs +++ b/test/unit/LDAPPropertyTests.cs @@ -1,7 +1,9 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using CommonLibTest.Facades; +using SharpHoundCommonLib; using SharpHoundCommonLib.Enums; using SharpHoundCommonLib.OutputTypes; using SharpHoundCommonLib.Processors; @@ -845,5 +847,34 @@ public void LDAPPropertyProcessor_ParseAllProperties_CollectionCountOne_NotBadPa Assert.Single(keys); } + [Fact] + public async Task LDAPPropertyProcessor_ReadPropertyDelegates_ReturnsPoplatedList() + { + var mock = new MockSearchResultEntry("CN\u003dWIN10,OU\u003dTestOU,DC\u003dtestlab,DC\u003dlocal", + new Dictionary + { + {"useraccountcontrol", 0x1000000.ToString()}, + { + "msds-allowedtodelegateto", new[] + { + "ldap/PRIMARY.testlab.local/testlab.local", + "ldap/PRIMARY.testlab.local", + "ldap/PRIMARY", + "ldap/WIN10" + } + } + }, "S-1-5-21-3130019616-2776909439-2417379446-1101", Label.Computer); + + var utils = new MockLDAPUtils(); + var processor = new LDAPPropertyProcessor(utils); + var props = await processor.ReadPropertyDelegates(mock); + var delegates = props.Select(d => d.ObjectIdentifier); + + foreach (var principal in mock.GetArrayProperty("msds-allowedtodelegateto")) + { + var host = await utils.ResolveHostToSid(principal, mock.GetProperty(LDAPProperties.DistinguishedName)); + Assert.Single(delegates, host); + } + } } } From 6b9a70085201a3b1a2e3a852a5ab751178308d99 Mon Sep 17 00:00:00 2001 From: anemeth Date: Thu, 18 Jan 2024 09:16:46 -0800 Subject: [PATCH 12/15] Begin adding tests for new functions --- .../Processors/LDAPPropertyProcessor.cs | 8 ++-- test/unit/LDAPPropertyTests.cs | 38 +++++++++++++++++++ 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/src/CommonLib/Processors/LDAPPropertyProcessor.cs b/src/CommonLib/Processors/LDAPPropertyProcessor.cs index 7160128d..b7f61326 100644 --- a/src/CommonLib/Processors/LDAPPropertyProcessor.cs +++ b/src/CommonLib/Processors/LDAPPropertyProcessor.cs @@ -128,7 +128,7 @@ public async Task ReadUserProperties(ISearchResultEntry entry) var sidHistory = ReadSidHistory(entry); props.Add(PropertyMap.GetPropertyName(LDAPProperties.SIDHistory), sidHistory.ToArray()); - userProps.SidHistory = sidHistory.Select(ssid => ReadSidPrinciple(entry, ssid)).ToArray(); + userProps.SidHistory = sidHistory.Select(ssid => ReadSidPrincipal(entry, ssid)).ToArray(); userProps.Props = props; @@ -161,7 +161,7 @@ public async Task ReadComputerProperties(ISearchResultEntry var sidHistory = ReadSidHistory(entry); props.Add(PropertyMap.GetPropertyName(LDAPProperties.SIDHistory), sidHistory.ToArray()); - compProps.SidHistory = sidHistory.Select(ssid => ReadSidPrinciple(entry, ssid)).ToArray(); + compProps.SidHistory = sidHistory.Select(ssid => ReadSidPrincipal(entry, ssid)).ToArray(); compProps.DumpSMSAPassword = ReadSmsaPrinciples(entry).ToArray(); @@ -237,7 +237,7 @@ public List ReadSidHistory(ISearchResultEntry entry) /// /// /// - public TypedPrincipal ReadSidPrinciple(ISearchResultEntry entry, string sidHistoryItem) + public TypedPrincipal ReadSidPrincipal(ISearchResultEntry entry, string sidHistoryItem) { var domain = Helpers.DistinguishedNameToDomain(entry.DistinguishedName); return _utils.ResolveIDAndType(sidHistoryItem, domain); @@ -803,7 +803,7 @@ public static string GetPropertyName(string ldapProperty) case LDAPProperties.PasswordLastSet: return "pwdlastset"; case LDAPProperties.ServicePrincipalNames: - return "serviceprinciplenames"; + return "serviceprincipalnames"; case LDAPProperties.DisplayName: return "displayname"; case LDAPProperties.Email: diff --git a/test/unit/LDAPPropertyTests.cs b/test/unit/LDAPPropertyTests.cs index a1e9b807..cdc918d0 100644 --- a/test/unit/LDAPPropertyTests.cs +++ b/test/unit/LDAPPropertyTests.cs @@ -876,5 +876,43 @@ public async Task LDAPPropertyProcessor_ReadPropertyDelegates_ReturnsPoplatedLis Assert.Single(delegates, host); } } + + [WindowsOnlyFact] + public void LDAPPropertyProcessor_ReadSidHistory_ReturnsPopulatedList() + { + var mock = new MockSearchResultEntry("CN\u003dWIN10,OU\u003dTestOU,DC\u003dtestlab,DC\u003dlocal", + new Dictionary + { + { + "sidhistory", new[] + { + Helpers.B64ToBytes("AQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQUQQAAA==") + } + }, + }, "S-1-5-21-3130019616-2776909439-2417379446-1101", Label.Computer); + + var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); + var sids = processor.ReadSidHistory(mock); + + Assert.Contains("S-1-5-21-3130019616-2776909439-2417379446-1105", sids); + Assert.Single(sids); + } + + [Fact] + public void LDAPPropertyProcessor_ReadSidPrincipal_GetPrincipal() + { + var mock = new MockSearchResultEntry("CN\u003dWIN10,OU\u003dTestOU,DC\u003dtestlab,DC\u003dlocal", + new Dictionary(), "S-1-5-21-3130019616-2776909439-2417379446-1101", Label.Computer); + + var sid = "S-1-5-21-3130019616-2776909439-2417379446-1105"; + var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); + var principal = processor.ReadSidPrincipal(mock, sid); + + Assert.Equal(new TypedPrincipal + { + ObjectIdentifier = "S-1-5-21-3130019616-2776909439-2417379446-1105", + ObjectType = Label.User + }, principal); + } } } From 8f226d070fa8297bfe6a3e01e5795a76a97b72d2 Mon Sep 17 00:00:00 2001 From: anemeth Date: Thu, 18 Jan 2024 14:50:58 -0800 Subject: [PATCH 13/15] Bolster LdapPropertyProcessor tests --- .../Processors/LDAPPropertyProcessor.cs | 8 +- test/unit/LDAPPropertyTests.cs | 146 ++++++++++++++++++ 2 files changed, 150 insertions(+), 4 deletions(-) diff --git a/src/CommonLib/Processors/LDAPPropertyProcessor.cs b/src/CommonLib/Processors/LDAPPropertyProcessor.cs index b7f61326..665b5404 100644 --- a/src/CommonLib/Processors/LDAPPropertyProcessor.cs +++ b/src/CommonLib/Processors/LDAPPropertyProcessor.cs @@ -150,7 +150,7 @@ public async Task ReadComputerProperties(ISearchResultEntry props.AddMany((GetProperties(LDAPProperties.AllowedToDelegateTo, entry))); compProps.AllowedToDelegate = (await ReadPropertyDelegates(entry)).ToArray(); - compProps.AllowedToAct = ReadAllowedToActPrinciples(entry).ToArray(); + compProps.AllowedToAct = ReadAllowedToActPrincipals(entry).ToArray(); props.AddMany(GetProperties(LDAPProperties.LastLogon, entry)); props.AddMany(GetProperties(LDAPProperties.LastLogonTimestamp, entry)); @@ -163,7 +163,7 @@ public async Task ReadComputerProperties(ISearchResultEntry props.Add(PropertyMap.GetPropertyName(LDAPProperties.SIDHistory), sidHistory.ToArray()); compProps.SidHistory = sidHistory.Select(ssid => ReadSidPrincipal(entry, ssid)).ToArray(); - compProps.DumpSMSAPassword = ReadSmsaPrinciples(entry).ToArray(); + compProps.DumpSMSAPassword = ReadSmsaPrincipals(entry).ToArray(); compProps.Props = props; @@ -248,7 +248,7 @@ public TypedPrincipal ReadSidPrincipal(ISearchResultEntry entry, string sidHisto /// /// /// - public List ReadAllowedToActPrinciples(ISearchResultEntry entry) + public List ReadAllowedToActPrincipals(ISearchResultEntry entry) { var allowedToActPrincipals = new List(); @@ -273,7 +273,7 @@ public List ReadAllowedToActPrinciples(ISearchResultEntry entry) /// /// /// - public List ReadSmsaPrinciples(ISearchResultEntry entry) + public List ReadSmsaPrincipals(ISearchResultEntry entry) { var smsaPrincipals = new List(); var hsa = entry.GetArrayProperty(LDAPProperties.HostServiceAccount); diff --git a/test/unit/LDAPPropertyTests.cs b/test/unit/LDAPPropertyTests.cs index cdc918d0..4183a23b 100644 --- a/test/unit/LDAPPropertyTests.cs +++ b/test/unit/LDAPPropertyTests.cs @@ -1,8 +1,13 @@ using System; using System.Collections.Generic; +using System.DirectoryServices; using System.Linq; +using System.Security.AccessControl; +using System.Security.Principal; using System.Threading.Tasks; using CommonLibTest.Facades; +using FluentAssertions.Specialized; +using Moq; using SharpHoundCommonLib; using SharpHoundCommonLib.Enums; using SharpHoundCommonLib.OutputTypes; @@ -914,5 +919,146 @@ public void LDAPPropertyProcessor_ReadSidPrincipal_GetPrincipal() ObjectType = Label.User }, principal); } + + // [Fact] + // public void LDAPPropertyProcessor_ReadAllowedToActPrincipals_ReturnsPopulatedList() + // { + // var mock = new MockSearchResultEntry("CN\u003dWIN10,OU\u003dTestOU,DC\u003dtestlab,DC\u003dlocal", + // new Dictionary + // { + // { + // "msds-allowedtoactonbehalfofotheridentity", + // Helpers.B64ToBytes("AQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQUQQAAA==") + // } + // }, "S-1-5-21-3130019616-2776909439-2417379446-1101", Label.Computer); + // + // var mockUtils = new Mock(); + // var mockSecurityDescriptor = new Mock(); + // var mockRuleDescriptor = new Mock(MockBehavior.Loose); + // mockRuleDescriptor.Setup(m => m.IdentityReference()).Returns("S-1-5-21-3130019616-2776909439-2417379446-1105"); + // + // mockUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); + // mockSecurityDescriptor.Setup(m => m.GetAccessRules( + // It.IsAny(), + // It.IsAny(), + // It.IsAny())) + // .Returns(new List + // { + // mockRuleDescriptor.Object + // }); + // + // var processor = new LDAPPropertyProcessor(mockUtils.Object); + // var principals = processor.ReadAllowedToActPrincipals(mock); + // + // Assert.Contains("S-1-5-21-3130019616-2776909439-2417379446-1105", principals.Select(p => p.ObjectIdentifier)); + // Assert.Single(principals); + // } + + [Fact] + public void LDAPPropertyProcessor_ReadSmsaPrincipals_ReturnsPopulatedList() + { + var mock = new MockSearchResultEntry("CN\u003dWIN10,OU\u003dTestOU,DC\u003dtestlab,DC\u003dlocal", + new Dictionary + { + { + "msds-hostserviceaccount", new[] + { + "CN=dfm,CN=Users,DC=testlab,DC=local", + "CN=krbtgt,CN=Users,DC=testlab,DC=local" + } + } + }, "S-1-5-21-3130019616-2776909439-2417379446-1101", Label.Computer); + + var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); + var smsaPrincipals = processor.ReadSmsaPrincipals(mock); + var sids = smsaPrincipals.Select(p => p.ObjectIdentifier); + + Assert.Single("S-1-5-21-3130019616-2776909439-2417379446-1105", sids); + Assert.Single("S-1-5-21-3130019616-2776909439-2417379446-502", sids); + } + + public static IEnumerable ServicePrincipalNamesData => + new List + { + new object[] + { + new[] + { + "WSMAN/WIN10", + "WSMAN/WIN10.testlab.local", + "RestrictedKrbHost/WIN10", + "HOST/WIN10", + "RestrictedKrbHost/WIN10.testlab.local", + "HOST/WIN10.testlab.local" + }, + true + }, + new object[] + { + new string[] { }, + false + } + }; + + [Theory] + [MemberData(nameof(ServicePrincipalNamesData))] + public void LDAPPropertyProcessor_GetProperties_ServicePrincipalNames(object property, bool expectedHasspn) + { + var mock = new MockSearchResultEntry("CN\u003dWIN10,OU\u003dTestOU,DC\u003dtestlab,DC\u003dlocal", + new Dictionary + { + { + "serviceprincipalname", property + } + }, "S-1-5-21-3130019616-2776909439-2417379446-1101", Label.Computer); + + var props = LDAPPropertyProcessor.GetProperties(LDAPProperties.ServicePrincipalNames, mock); + + Assert.Single(props.Keys, "serviceprincipalnames"); + var propPrincipals = props["serviceprincipalnames"] as string[]; + foreach (var principal in mock.GetArrayProperty("serviceprincipalname")) + { + Assert.Single(propPrincipals, principal); + } + + Assert.Single(props.Keys, "hasspn"); + Assert.Equal(expectedHasspn, (bool)props["hasspn"]); + } + + public static IEnumerable UserAccessControlData => + new List + { + new object[] + { + ((int)(UacFlags.NotDelegated | UacFlags.AccountDisable)).ToString(), + new Dictionary {{ "sensitive", true }, { "enabled", false }} + }, + new object[] + { + ((int)(UacFlags.ServerTrustAccount | UacFlags.PasswordNotRequired | UacFlags.TrustedForDelegation)).ToString(), + new Dictionary {{ "isdc", true }, { "passwordnotreqd", true }, { "unconstraineddelegation", true }, { "enabled", true }} + }, + }; + + [Theory] + [MemberData(nameof(UserAccessControlData))] + public void LDAPPropertyProcessor_GetProperties_UserAccountControl(string property, Dictionary expectedFlags) + { + var mock = new MockSearchResultEntry("CN\u003dWIN10,OU\u003dTestOU,DC\u003dtestlab,DC\u003dlocal", + new Dictionary + { + { + "useraccountcontrol", property + } + }, "S-1-5-21-3130019616-2776909439-2417379446-1101", Label.User); + + var props = LDAPPropertyProcessor.GetProperties(LDAPProperties.UserAccountControl, mock); + + foreach (var flag in props) + { + var expectedFlag = expectedFlags.ContainsKey(flag.Key) && expectedFlags[flag.Key]; + Assert.Equal(expectedFlag, (bool)flag.Value); + } + } } } From 01189bba991bd04c4faed41acc8e7089f3b1ddd1 Mon Sep 17 00:00:00 2001 From: anemeth Date: Thu, 18 Jan 2024 15:04:06 -0800 Subject: [PATCH 14/15] Correct ReadSmsaPrincipals test --- test/unit/LDAPPropertyTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/unit/LDAPPropertyTests.cs b/test/unit/LDAPPropertyTests.cs index 4183a23b..0587b271 100644 --- a/test/unit/LDAPPropertyTests.cs +++ b/test/unit/LDAPPropertyTests.cs @@ -973,8 +973,8 @@ public void LDAPPropertyProcessor_ReadSmsaPrincipals_ReturnsPopulatedList() var smsaPrincipals = processor.ReadSmsaPrincipals(mock); var sids = smsaPrincipals.Select(p => p.ObjectIdentifier); - Assert.Single("S-1-5-21-3130019616-2776909439-2417379446-1105", sids); - Assert.Single("S-1-5-21-3130019616-2776909439-2417379446-502", sids); + Assert.Single(sids, "S-1-5-21-3130019616-2776909439-2417379446-1105"); + Assert.Single(sids, "S-1-5-21-3130019616-2776909439-2417379446-502"); } public static IEnumerable ServicePrincipalNamesData => From 0089492740c983da3b66416b7522a81222092c91 Mon Sep 17 00:00:00 2001 From: anemeth Date: Mon, 22 Jan 2024 09:16:33 -0800 Subject: [PATCH 15/15] Add simple property tests --- test/unit/LDAPPropertyTests.cs | 255 ++++++++++++++++++++++----------- 1 file changed, 174 insertions(+), 81 deletions(-) diff --git a/test/unit/LDAPPropertyTests.cs b/test/unit/LDAPPropertyTests.cs index 0587b271..2f6fbb4a 100644 --- a/test/unit/LDAPPropertyTests.cs +++ b/test/unit/LDAPPropertyTests.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.DirectoryServices; using System.Linq; +using System.Runtime.Serialization; using System.Security.AccessControl; using System.Security.Principal; using System.Threading.Tasks; @@ -919,41 +920,7 @@ public void LDAPPropertyProcessor_ReadSidPrincipal_GetPrincipal() ObjectType = Label.User }, principal); } - - // [Fact] - // public void LDAPPropertyProcessor_ReadAllowedToActPrincipals_ReturnsPopulatedList() - // { - // var mock = new MockSearchResultEntry("CN\u003dWIN10,OU\u003dTestOU,DC\u003dtestlab,DC\u003dlocal", - // new Dictionary - // { - // { - // "msds-allowedtoactonbehalfofotheridentity", - // Helpers.B64ToBytes("AQUAAAAAAAUVAAAAIE+Qun9GhKV2SBaQUQQAAA==") - // } - // }, "S-1-5-21-3130019616-2776909439-2417379446-1101", Label.Computer); - // - // var mockUtils = new Mock(); - // var mockSecurityDescriptor = new Mock(); - // var mockRuleDescriptor = new Mock(MockBehavior.Loose); - // mockRuleDescriptor.Setup(m => m.IdentityReference()).Returns("S-1-5-21-3130019616-2776909439-2417379446-1105"); - // - // mockUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); - // mockSecurityDescriptor.Setup(m => m.GetAccessRules( - // It.IsAny(), - // It.IsAny(), - // It.IsAny())) - // .Returns(new List - // { - // mockRuleDescriptor.Object - // }); - // - // var processor = new LDAPPropertyProcessor(mockUtils.Object); - // var principals = processor.ReadAllowedToActPrincipals(mock); - // - // Assert.Contains("S-1-5-21-3130019616-2776909439-2417379446-1105", principals.Select(p => p.ObjectIdentifier)); - // Assert.Single(principals); - // } - + [Fact] public void LDAPPropertyProcessor_ReadSmsaPrincipals_ReturnsPopulatedList() { @@ -977,87 +944,213 @@ public void LDAPPropertyProcessor_ReadSmsaPrincipals_ReturnsPopulatedList() Assert.Single(sids, "S-1-5-21-3130019616-2776909439-2417379446-502"); } - public static IEnumerable ServicePrincipalNamesData => + public static IEnumerable UserAccessControlData => new List { new object[] { - new[] - { - "WSMAN/WIN10", - "WSMAN/WIN10.testlab.local", - "RestrictedKrbHost/WIN10", - "HOST/WIN10", - "RestrictedKrbHost/WIN10.testlab.local", - "HOST/WIN10.testlab.local" - }, - true + ((int)(UacFlags.NotDelegated | UacFlags.AccountDisable)).ToString(), + new Dictionary {{ "sensitive", true }, { "enabled", false }} }, new object[] { - new string[] { }, - false - } + ((int)(UacFlags.ServerTrustAccount | UacFlags.PasswordNotRequired | UacFlags.TrustedForDelegation)).ToString(), + new Dictionary {{ "isdc", true }, { "passwordnotreqd", true }, { "unconstraineddelegation", true }, { "enabled", true }} + }, }; [Theory] - [MemberData(nameof(ServicePrincipalNamesData))] - public void LDAPPropertyProcessor_GetProperties_ServicePrincipalNames(object property, bool expectedHasspn) + [MemberData(nameof(UserAccessControlData))] + public void LDAPPropertyProcessor_GetProperties_UserAccountControl(string property, Dictionary expectedFlags) { var mock = new MockSearchResultEntry("CN\u003dWIN10,OU\u003dTestOU,DC\u003dtestlab,DC\u003dlocal", new Dictionary { { - "serviceprincipalname", property + "useraccountcontrol", property } - }, "S-1-5-21-3130019616-2776909439-2417379446-1101", Label.Computer); - - var props = LDAPPropertyProcessor.GetProperties(LDAPProperties.ServicePrincipalNames, mock); + }, "S-1-5-21-3130019616-2776909439-2417379446-1101", Label.User); - Assert.Single(props.Keys, "serviceprincipalnames"); - var propPrincipals = props["serviceprincipalnames"] as string[]; - foreach (var principal in mock.GetArrayProperty("serviceprincipalname")) + var props = LDAPPropertyProcessor.GetProperties(LDAPProperties.UserAccountControl, mock); + + foreach (var flag in props) { - Assert.Single(propPrincipals, principal); + var expectedFlag = expectedFlags.ContainsKey(flag.Key) && expectedFlags[flag.Key]; + Assert.Equal(expectedFlag, (bool)flag.Value); } - - Assert.Single(props.Keys, "hasspn"); - Assert.Equal(expectedHasspn, (bool)props["hasspn"]); } - - public static IEnumerable UserAccessControlData => + + public static IEnumerable SimplePropertyTestData => new List { new object[] { - ((int)(UacFlags.NotDelegated | UacFlags.AccountDisable)).ToString(), - new Dictionary {{ "sensitive", true }, { "enabled", false }} + LDAPProperties.Description, + new Dictionary {{ LDAPProperties.Description, "test desc" }}, + new Dictionary {{ "description", "test desc" }}, }, new object[] { - ((int)(UacFlags.ServerTrustAccount | UacFlags.PasswordNotRequired | UacFlags.TrustedForDelegation)).ToString(), - new Dictionary {{ "isdc", true }, { "passwordnotreqd", true }, { "unconstraineddelegation", true }, { "enabled", true }} + LDAPProperties.DomainFunctionalLevel, + new Dictionary {{ LDAPProperties.DomainFunctionalLevel, "1" }}, + new Dictionary {{ "functionallevel", "2003 Interim" }}, + }, + new object[] + { + LDAPProperties.DomainFunctionalLevel, + new Dictionary {{ LDAPProperties.DomainFunctionalLevel, "nope" }}, + new Dictionary {{ "functionallevel", "Unknown" }}, + }, + new object[] + { + LDAPProperties.GPCFileSYSPath, + new Dictionary {{ LDAPProperties.GPCFileSYSPath, "/test/testy/test" }}, + new Dictionary {{ "gpcpath", "/TEST/TESTY/TEST" }}, + }, + new object[] + { + LDAPProperties.DisplayName, + new Dictionary {{ LDAPProperties.DisplayName, "one test of a display name" }}, + new Dictionary {{ "displayname", "one test of a display name" }}, + }, + new object[] + { + LDAPProperties.Email, + new Dictionary {{ LDAPProperties.Email, "test@testdomain.com" }}, + new Dictionary {{ "email", "test@testdomain.com" }}, + }, + new object[] + { + LDAPProperties.Title, + new Dictionary {{ LDAPProperties.Title, "Test Title" }}, + new Dictionary {{ "title", "Test Title" }}, + }, + new object[] + { + LDAPProperties.HomeDirectory, + new Dictionary {{ LDAPProperties.HomeDirectory, "/users/test" }}, + new Dictionary {{ "homedirectory", "/users/test" }}, + }, + new object[] + { + LDAPProperties.UserPassword, + new Dictionary {{ LDAPProperties.UserPassword, "1234" }}, + new Dictionary {{ "userpassword", "1234" }}, + }, + new object[] + { + LDAPProperties.UnixUserPassword, + new Dictionary {{ LDAPProperties.UnixUserPassword, "1234" }}, + new Dictionary {{ "unixpassword", "1234" }}, + }, + new object[] + { + LDAPProperties.UnicodePassword, + new Dictionary {{ LDAPProperties.UnicodePassword, "1234" }}, + new Dictionary {{ "unicodepassword", "1234" }}, + }, + new object[] + { + LDAPProperties.MsSFU30Password, + new Dictionary {{ LDAPProperties.MsSFU30Password, "1234" }}, + new Dictionary {{ "sfupassword", "1234" }}, + }, + new object[] + { + LDAPProperties.ScriptPath, + new Dictionary {{ LDAPProperties.ScriptPath, "/scripts" }}, + new Dictionary {{ "logonscript", "/scripts" }}, + }, + new object[] + { + LDAPProperties.AdminCount, + new Dictionary {{ LDAPProperties.AdminCount, "1" }}, + new Dictionary {{ "admincount", true }}, + }, + new object[] + { + LDAPProperties.AdminCount, + new Dictionary {{ LDAPProperties.AdminCount, "0" }}, + new Dictionary {{ "admincount", false }}, + }, + new object[] + { + LDAPProperties.AdminCount, + new Dictionary {{ LDAPProperties.AdminCount, "nope" }}, + new Dictionary {{ "admincount", false }}, + }, + new object[] + { + LDAPProperties.OperatingSystem, + new Dictionary {{ LDAPProperties.OperatingSystem, "TestOS" }, { LDAPProperties.ServicePack, "SP1" }}, + new Dictionary {{ "operatingsystem", "TestOS SP1" }}, + }, + new object[] + { + LDAPProperties.AllowedToDelegateTo, + new Dictionary {{ LDAPProperties.AllowedToDelegateTo, new[] { "test1", "test2", "test3" } }}, + new Dictionary {{ "allowedtodelegate", new[] { "test1", "test2", "test3" } }}, + }, + new object[] + { + LDAPProperties.ServicePrincipalNames, + new Dictionary {{ + LDAPProperties.ServicePrincipalNames, + new[] + { + "WSMAN/WIN10", + "WSMAN/WIN10.testlab.local", + "RestrictedKrbHost/WIN10", + "HOST/WIN10", + "RestrictedKrbHost/WIN10.testlab.local", + "HOST/WIN10.testlab.local", + } + }}, + new Dictionary {{ + "serviceprincipalnames", + new[] + { + "WSMAN/WIN10", + "WSMAN/WIN10.testlab.local", + "RestrictedKrbHost/WIN10", + "HOST/WIN10", + "RestrictedKrbHost/WIN10.testlab.local", + "HOST/WIN10.testlab.local", + }}, + { "hasspn", true } + } + }, + new object[] + { + LDAPProperties.ServicePrincipalNames, + new Dictionary {{ + LDAPProperties.ServicePrincipalNames, + new string[] {} + }}, + new Dictionary {{ + "serviceprincipalnames", + new string[] {} + }, + { "hasspn", false } + } }, }; - + [Theory] - [MemberData(nameof(UserAccessControlData))] - public void LDAPPropertyProcessor_GetProperties_UserAccountControl(string property, Dictionary expectedFlags) + [MemberData(nameof(SimplePropertyTestData))] + public void LDAPPropertyProcessor_GetProperties_SimplePropertyTest(string ldapPropertyName, Dictionary testInput, Dictionary expectedOutput) { var mock = new MockSearchResultEntry("CN\u003dWIN10,OU\u003dTestOU,DC\u003dtestlab,DC\u003dlocal", - new Dictionary - { - { - "useraccountcontrol", property - } - }, "S-1-5-21-3130019616-2776909439-2417379446-1101", Label.User); + testInput, + "S-1-5-21-3130019616-2776909439-2417379446-1101", Label.User); - var props = LDAPPropertyProcessor.GetProperties(LDAPProperties.UserAccountControl, mock); + var resolvedProps = LDAPPropertyProcessor.GetProperties(ldapPropertyName, mock); - foreach (var flag in props) + Assert.Equal(resolvedProps.Count, expectedOutput.Count); + foreach (var expected in expectedOutput) { - var expectedFlag = expectedFlags.ContainsKey(flag.Key) && expectedFlags[flag.Key]; - Assert.Equal(expectedFlag, (bool)flag.Value); + Assert.Single(resolvedProps.Keys, expected.Key); + Assert.Equal(expected.Value, resolvedProps[expected.Key]); } } }