diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b75f52bd..a3a10778 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,7 +8,7 @@ ## Build -``` powershell +```powershell dotnet build ``` @@ -17,31 +17,30 @@ dotnet build This project is configured to generate test coverage every time tests are run and produces a HTML report at [./docfx/coverage/report](./docfx/coverage/report). - -``` powershell +```powershell dotnet test ``` ## Documentation -Documentation is generated into Html from Markdown using [docfx](https://https://dotnet.github.io/docfx/). +Documentation is generated into HTML from Markdown using [docfx](https://dotnet.github.io/docfx/). To build the docs: -``` powershell +```powershell dotnet build docfx ``` To preview the docs: -``` powershell +```powershell dotnet build docfx dotnet build docfx -t:Serve ``` To preview the docs with test coverage: -``` powershell +```powershell dotnet test dotnet build docfx dotnet build docfx -t:Serve diff --git a/src/CommonLib/Processors/LDAPPropertyProcessor.cs b/src/CommonLib/Processors/LDAPPropertyProcessor.cs index 5b3a181a..a23023c9 100644 --- a/src/CommonLib/Processors/LDAPPropertyProcessor.cs +++ b/src/CommonLib/Processors/LDAPPropertyProcessor.cs @@ -413,10 +413,14 @@ public static Dictionary ReadEnrollmentServiceProperties(ISearch return props; } - public static Dictionary ReadNTAuthStoreProperties(ISearchResultEntry entry) + public Dictionary ReadNTAuthStoreProperties(ISearchResultEntry entry) { - var props = GetCommonProps(entry); - return props; + var ntAuthStoreProps = new NTAuthStoreProperties + { + Props = GetCommonProps(entry) + }; + + return ntAuthStoreProps.Props; } public static Dictionary ReadCertTemplateProperties(ISearchResultEntry entry) @@ -638,4 +642,10 @@ public class ComputerProperties public TypedPrincipal[] SidHistory { get; set; } = Array.Empty(); public TypedPrincipal[] DumpSMSAPassword { get; set; } = Array.Empty(); } + + public class NTAuthStoreProperties + { + public Dictionary Props { get; set; } = new(); + public TypedPrincipal[] CertThumbprints { get; set; } = Array.Empty(); + } } \ No newline at end of file diff --git a/test/unit/ACLProcessorTest.cs b/test/unit/ACLProcessorTest.cs index f5654234..0aa6c1df 100644 --- a/test/unit/ACLProcessorTest.cs +++ b/test/unit/ACLProcessorTest.cs @@ -55,7 +55,7 @@ public void SanityCheck() public void ACLProcessor_IsACLProtected_NullNTSD_ReturnsFalse() { var processor = new ACLProcessor(new MockLDAPUtils(), true); - var result = processor.IsACLProtected((byte[]) null); + var result = processor.IsACLProtected((byte[])null); Assert.False(result); } @@ -206,7 +206,7 @@ public void ACLProcessor_ProcessGMSAReaders_Null_PrincipalID() var collection = new List(); mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); - mockRule.Setup(x => x.IdentityReference()).Returns((string) null); + mockRule.Setup(x => x.IdentityReference()).Returns((string)null); collection.Add(mockRule.Object); mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) @@ -267,7 +267,7 @@ public void ACLProcessor_ProcessACL_Null_SID() mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string) null); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); var processor = new ACLProcessor(mockLDAPUtils.Object, true); @@ -287,7 +287,7 @@ public void ACLProcessor_ProcessACL_Null_ACE() mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string) null); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); var processor = new ACLProcessor(mockLDAPUtils.Object, true); @@ -309,7 +309,7 @@ public void ACLProcessor_ProcessACL_Deny_ACE() mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string) null); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); var processor = new ACLProcessor(mockLDAPUtils.Object, true); @@ -332,7 +332,7 @@ public void ACLProcessor_ProcessACL_Unmatched_Inheritance_ACE() mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string) null); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); var processor = new ACLProcessor(mockLDAPUtils.Object, true); @@ -351,12 +351,12 @@ public void ACLProcessor_ProcessACL_Null_SID_ACE() var collection = new List(); mockRule.Setup(x => x.AccessControlType()).Returns(AccessControlType.Allow); mockRule.Setup(x => x.IsAceInheritedFrom(It.IsAny())).Returns(true); - mockRule.Setup(x => x.IdentityReference()).Returns((string) null); + mockRule.Setup(x => x.IdentityReference()).Returns((string)null); collection.Add(mockRule.Object); mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string) null); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); var processor = new ACLProcessor(mockLDAPUtils.Object, true); @@ -386,7 +386,7 @@ public void ACLProcessor_ProcessACL_GenericAll_Unmatched_Guid() mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string) null); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); @@ -417,7 +417,7 @@ public void ACLProcessor_ProcessACL_GenericAll() mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string) null); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); @@ -454,7 +454,7 @@ public void ACLProcessor_ProcessACL_WriteDacl() mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string) null); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); @@ -491,7 +491,7 @@ public void ACLProcessor_ProcessACL_WriteOwner() mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string) null); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); @@ -528,7 +528,7 @@ public void ACLProcessor_ProcessACL_Self() mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string) null); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); @@ -550,7 +550,6 @@ public void ACLProcessor_ProcessACL_ExtendedRight_Domain_Unmatched() { var expectedPrincipalType = Label.Group; var expectedPrincipalSID = "S-1-5-21-3130019616-2776909439-2417379446-512"; - var expectedRightName = EdgeNames.AddSelf; var mockLDAPUtils = new Mock(); var mockSecurityDescriptor = new Mock(MockBehavior.Loose, null); @@ -565,7 +564,7 @@ public void ACLProcessor_ProcessACL_ExtendedRight_Domain_Unmatched() mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string) null); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); @@ -597,7 +596,7 @@ public void ACLProcessor_ProcessACL_ExtendedRight_Domain_DSReplicationGetChanges mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string) null); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); @@ -634,7 +633,7 @@ public void ACLProcessor_ProcessACL_ExtendedRight_Domain_All() mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string) null); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); @@ -671,7 +670,7 @@ public void ACLProcessor_ProcessACL_ExtendedRight_Domain_DSReplicationGetChanges mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string) null); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); @@ -709,7 +708,7 @@ public void ACLProcessor_ProcessACL_ExtendedRight_User_Unmatched() mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string) null); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); @@ -741,7 +740,7 @@ public void ACLProcessor_ProcessACL_ExtendedRight_User_UserForceChangePassword() mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string) null); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); @@ -778,7 +777,7 @@ public void ACLProcessor_ProcessACL_ExtendedRight_User_All() mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string) null); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); @@ -815,7 +814,7 @@ public void ACLProcessor_ProcessACL_ExtendedRight_Computer_NoLAPS() mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string) null); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); @@ -847,7 +846,7 @@ public void ACLProcessor_ProcessACL_ExtendedRight_Computer_All() mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string) null); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); @@ -889,7 +888,7 @@ public void ACLProcessor_ProcessACL_GenericWrite_Unmatched() mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string) null); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); @@ -921,7 +920,7 @@ public void ACLProcessor_ProcessACL_GenericWrite_User_All() mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string) null); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); @@ -958,7 +957,7 @@ public void ACLProcessor_ProcessACL_GenericWrite_User_WriteMember() mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string) null); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); @@ -997,7 +996,7 @@ public void ACLProcessor_ProcessACL_GenericWrite_Computer_WriteAllowedToAct() mockSecurityDescriptor.Setup(m => m.GetAccessRules(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(collection); - mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string) null); + mockSecurityDescriptor.Setup(m => m.GetOwner(It.IsAny())).Returns((string)null); mockLDAPUtils.Setup(x => x.MakeSecurityDescriptor()).Returns(mockSecurityDescriptor.Object); mockLDAPUtils.Setup(x => x.ResolveIDAndType(It.IsAny(), It.IsAny())) .Returns(new TypedPrincipal(expectedPrincipalSID, expectedPrincipalType)); diff --git a/test/unit/Facades/MockSearchResultEntry.cs b/test/unit/Facades/MockSearchResultEntry.cs index e7165c07..7048a185 100644 --- a/test/unit/Facades/MockSearchResultEntry.cs +++ b/test/unit/Facades/MockSearchResultEntry.cs @@ -37,22 +37,40 @@ public string GetProperty(string propertyName) public byte[] GetByteProperty(string propertyName) { + //returning something not null specifically for these properties for the parseAllProperties tests + if (propertyName == "badpasswordtime" || propertyName == "domainsid") return new byte[] { 0x20 }; return _properties[propertyName] as byte[]; } public string[] GetArrayProperty(string propertyName) { - return _properties[propertyName] as string[]; + if (!_properties.Contains(propertyName)) + return Array.Empty(); + + var value = _properties[propertyName]; + Type valueType = value.GetType(); + + if (valueType.IsArray) + return value as string[]; + else + return new string[1] { (value ?? "").ToString() }; } public byte[][] GetByteArrayProperty(string propertyName) { - return _properties[propertyName] as byte[][]; + + if (!_properties.Contains(propertyName)) + return Array.Empty(); + + var byteArray = new byte[] { 0x20 }; + var byteArrayArray = new byte[][] { byteArray }; + + return byteArrayArray; } public bool GetIntProperty(string propertyName, out int value) { - value = _properties[propertyName] is int ? (int) _properties[propertyName] : 0; + value = _properties[propertyName] is int ? (int)_properties[propertyName] : 0; return true; } @@ -88,12 +106,16 @@ public string GetGuid() public int PropCount(string prop) { - throw new NotImplementedException(); + var count = 0; + + foreach (var property in _properties) count++; + + return count; } public IEnumerable PropertyNames() { - throw new NotImplementedException(); + foreach (var property in _properties.Keys) yield return property.ToString().ToLower(); } public bool IsMSA() diff --git a/test/unit/LDAPPropertyTests.cs b/test/unit/LDAPPropertyTests.cs index 7099d196..ae5b6580 100644 --- a/test/unit/LDAPPropertyTests.cs +++ b/test/unit/LDAPPropertyTests.cs @@ -120,7 +120,7 @@ public void LDAPPropertyProcessor_ReadGroupProperties_TestGoodData() Assert.Contains("description", test.Keys); Assert.Equal("Test", test["description"] as string); Assert.Contains("admincount", test.Keys); - Assert.True((bool) test["admincount"]); + Assert.True((bool)test["admincount"]); } [Fact] @@ -137,7 +137,7 @@ public void LDAPPropertyProcessor_ReadGroupProperties_TestGoodData_FalseAdminCou Assert.Contains("description", test.Keys); Assert.Equal("Test", test["description"] as string); Assert.Contains("admincount", test.Keys); - Assert.False((bool) test["admincount"]); + Assert.False((bool)test["admincount"]); } [Fact] @@ -153,7 +153,7 @@ public void LDAPPropertyProcessor_ReadGroupProperties_NullAdminCount() Assert.Contains("description", test.Keys); Assert.Equal("Test", test["description"] as string); Assert.Contains("admincount", test.Keys); - Assert.False((bool) test["admincount"]); + Assert.False((bool)test["admincount"]); } [Fact] @@ -250,7 +250,7 @@ public async Task LDAPPropertyProcessor_ReadUserProperties_NullAdminCount() var props = test.Props; var keys = props.Keys; Assert.Contains("admincount", keys); - Assert.False((bool) props["admincount"]); + Assert.False((bool)props["admincount"]); } [WindowsOnlyFact] @@ -289,33 +289,33 @@ public async Task LDAPPropertyProcessor_ReadUserProperties_HappyPath() Assert.Contains("description", keys); Assert.Equal("Test", props["description"] as string); Assert.Contains("admincount", keys); - Assert.True((bool) props["admincount"]); + Assert.True((bool)props["admincount"]); Assert.Contains("lastlogon", keys); - Assert.Equal(1622827514, (long) props["lastlogon"]); + Assert.Equal(1622827514, (long)props["lastlogon"]); Assert.Contains("lastlogontimestamp", keys); - Assert.Equal(1622558209, (long) props["lastlogontimestamp"]); + Assert.Equal(1622558209, (long)props["lastlogontimestamp"]); Assert.Contains("pwdlastset", keys); - Assert.Equal(1568693134, (long) props["pwdlastset"]); + Assert.Equal(1568693134, (long)props["pwdlastset"]); Assert.Contains("homedirectory", keys); Assert.Equal(@"\\win10\testdir", props["homedirectory"] as string); //UAC stuff Assert.Contains("sensitive", keys); - Assert.False((bool) props["sensitive"]); + Assert.False((bool)props["sensitive"]); Assert.Contains("dontreqpreauth", keys); - Assert.False((bool) props["dontreqpreauth"]); + Assert.False((bool)props["dontreqpreauth"]); Assert.Contains("passwordnotreqd", keys); - Assert.False((bool) props["passwordnotreqd"]); + Assert.False((bool)props["passwordnotreqd"]); Assert.Contains("unconstraineddelegation", keys); - Assert.False((bool) props["unconstraineddelegation"]); + Assert.False((bool)props["unconstraineddelegation"]); Assert.Contains("enabled", keys); - Assert.True((bool) props["enabled"]); + Assert.True((bool)props["enabled"]); Assert.Contains("trustedtoauth", keys); - Assert.False((bool) props["trustedtoauth"]); + Assert.False((bool)props["trustedtoauth"]); //SPN Assert.Contains("hasspn", keys); - Assert.True((bool) props["hasspn"]); + Assert.True((bool)props["hasspn"]); Assert.Contains("serviceprincipalnames", keys); Assert.Contains("MSSQLSVC/win10", props["serviceprincipalnames"] as string[]); @@ -367,7 +367,7 @@ public async Task LDAPPropertyProcessor_ReadUserProperties_TestBadPaths() Assert.Contains("sidhistory", keys); Assert.Empty(props["sidhistory"] as string[]); Assert.Contains("admincount", keys); - Assert.False((bool) props["admincount"]); + Assert.False((bool)props["admincount"]); Assert.Contains("sensitive", keys); Assert.Contains("dontreqpreauth", keys); Assert.Contains("passwordnotreqd", keys); @@ -375,13 +375,13 @@ public async Task LDAPPropertyProcessor_ReadUserProperties_TestBadPaths() Assert.Contains("pwdneverexpires", keys); Assert.Contains("enabled", keys); Assert.Contains("trustedtoauth", keys); - Assert.False((bool) props["trustedtoauth"]); - Assert.False((bool) props["sensitive"]); - Assert.False((bool) props["dontreqpreauth"]); - Assert.False((bool) props["passwordnotreqd"]); - Assert.False((bool) props["unconstraineddelegation"]); - Assert.False((bool) props["pwdneverexpires"]); - Assert.True((bool) props["enabled"]); + Assert.False((bool)props["trustedtoauth"]); + Assert.False((bool)props["sensitive"]); + Assert.False((bool)props["dontreqpreauth"]); + Assert.False((bool)props["passwordnotreqd"]); + Assert.False((bool)props["unconstraineddelegation"]); + Assert.False((bool)props["pwdneverexpires"]); + Assert.True((bool)props["enabled"]); } [WindowsOnlyFact] @@ -437,15 +437,15 @@ public async Task LDAPPropertyProcessor_ReadComputerProperties_HappyPath() Assert.Contains("lastlogon", keys); Assert.Contains("lastlogontimestamp", keys); Assert.Contains("pwdlastset", keys); - Assert.True((bool) props["enabled"]); - Assert.False((bool) props["unconstraineddelegation"]); + Assert.True((bool)props["enabled"]); + Assert.False((bool)props["unconstraineddelegation"]); Assert.Contains("lastlogon", keys); - Assert.Equal(1622827514, (long) props["lastlogon"]); + Assert.Equal(1622827514, (long)props["lastlogon"]); Assert.Contains("lastlogontimestamp", keys); - Assert.Equal(1622558209, (long) props["lastlogontimestamp"]); + Assert.Equal(1622558209, (long)props["lastlogontimestamp"]); Assert.Contains("pwdlastset", keys); - Assert.Equal(1568693134, (long) props["pwdlastset"]); + Assert.Equal(1568693134, (long)props["pwdlastset"]); //AllowedToDelegate Assert.Single(test.AllowedToDelegate); @@ -524,13 +524,14 @@ public async Task LDAPPropertyProcessor_ReadComputerProperties_TestBadPaths() Assert.Contains("unconstraineddelegation", keys); Assert.Contains("enabled", keys); Assert.Contains("trustedtoauth", keys); - Assert.False((bool) props["unconstraineddelegation"]); - Assert.True((bool) props["enabled"]); - Assert.False((bool) props["trustedtoauth"]); + Assert.False((bool)props["unconstraineddelegation"]); + Assert.True((bool)props["enabled"]); + Assert.False((bool)props["trustedtoauth"]); Assert.Contains("sidhistory", keys); Assert.Empty(props["sidhistory"] as string[]); } + [Fact] public async Task LDAPPropertyProcessor_ReadComputerProperties_TestDumpSMSAPassword() { @@ -602,6 +603,114 @@ public async Task LDAPPropertyProcessor_ReadComputerProperties_TestDumpSMSAPassw } - // //TODO: Add coverage for ParseAllProperties + + [Fact] + public void LDAPPropertyProcessor_ReadNTAuthStoreProperties() + { + var mock = new MockSearchResultEntry("CN\u003dNTAUTHCERTIFICATES,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dDUMPSTER,DC\u003dFIRE", + new Dictionary + { + {"description", null}, + {"domain", "DUMPSTER.FIRE"}, + {"name", "NTAUTHCERTIFICATES@DUMPSTER.FIRE"}, + {"domainsid", "S-1-5-21-2697957641-2271029196-387917394"}, + {"whencreated", 1683986131}, + }, "2F9F3630-F46A-49BF-B186-6629994EBCF9", Label.NTAuthStore); + + var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); + var props = processor.ReadNTAuthStoreProperties(mock); + var keys = props.Keys; + + Assert.Contains("description", keys); + Assert.Contains("whencreated", keys); + } + + // ReservedAttributes + + [Fact] + public void LDAPPropertyProcessor_ParseAllProperties() + { + var mock = new MockSearchResultEntry("CN\u003dNTAUTHCERTIFICATES,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dDUMPSTER,DC\u003dFIRE", + new Dictionary + { + {"description", null}, + {"domain", "DUMPSTER.FIRE"}, + {"name", "NTAUTHCERTIFICATES@DUMPSTER.FIRE"}, + {"domainsid", "S-1-5-21-2697957641-2271029196-387917394"}, + {"whencreated", 1683986131}, + }, "2F9F3630-F46A-49BF-B186-6629994EBCF9", Label.NTAuthStore); + + var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); + var props = processor.ParseAllProperties(mock); + var keys = props.Keys; + + //These are reserved properties and so they should be filtered out + Assert.DoesNotContain("description", keys); + Assert.DoesNotContain("whencreated", keys); + Assert.DoesNotContain("name", keys); + + Assert.Contains("domainsid", keys); + Assert.Contains("domain", keys); + } + + [Fact] + public void LDAPPropertyProcessor_ParseAllProperties_NoProperties() + { + var mock = new MockSearchResultEntry("CN\u003dNTAUTHCERTIFICATES,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dDUMPSTER,DC\u003dFIRE", + new Dictionary + { }, "2F9F3630-F46A-49BF-B186-6629994EBCF9", Label.NTAuthStore); + + var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); + var props = processor.ParseAllProperties(mock); + var keys = props.Keys; + + Assert.Empty(keys); + + } + + [Fact] + public void LDAPPropertyProcessor_ParseAllProperties_CollectionCountOne_NullString() + { + var mock = new MockSearchResultEntry("CN\u003dNTAUTHCERTIFICATES,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dDUMPSTER,DC\u003dFIRE", + new Dictionary + {{"domainsid", null} }, "2F9F3630-F46A-49BF-B186-6629994EBCF9", Label.NTAuthStore); + + var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); + var props = processor.ParseAllProperties(mock); + var keys = props.Keys; + + Assert.Empty(keys); + } + + [Fact] + public void LDAPPropertyProcessor_ParseAllProperties_CollectionCountOne_BadPasswordTime() + { + var mock = new MockSearchResultEntry("CN\u003dNTAUTHCERTIFICATES,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dDUMPSTER,DC\u003dFIRE", + new Dictionary + {{"badpasswordtime", "130435290000000000"} }, "2F9F3630-F46A-49BF-B186-6629994EBCF9", Label.NTAuthStore); + + var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); + var props = processor.ParseAllProperties(mock); + var keys = props.Keys; + + Assert.Contains("badpasswordtime", keys); + Assert.Single(keys); + } + + [Fact] + public void LDAPPropertyProcessor_ParseAllProperties_CollectionCountOne_NotBadPasswordTime() + { + var mock = new MockSearchResultEntry("CN\u003dNTAUTHCERTIFICATES,CN\u003dPUBLIC KEY SERVICES,CN\u003dSERVICES,CN\u003dCONFIGURATION,DC\u003dDUMPSTER,DC\u003dFIRE", + new Dictionary + {{"domainsid", "S-1-5-21-2697957641-2271029196-387917394"}}, "2F9F3630-F46A-49BF-B186-6629994EBCF9", Label.NTAuthStore); + + var processor = new LDAPPropertyProcessor(new MockLDAPUtils()); + var props = processor.ParseAllProperties(mock); + var keys = props.Keys; + + Assert.Contains("domainsid", keys); + Assert.Single(keys); + } + } } \ No newline at end of file diff --git a/test/unit/LDAPUtilsTest.cs b/test/unit/LDAPUtilsTest.cs index 701253eb..c0e637b7 100644 --- a/test/unit/LDAPUtilsTest.cs +++ b/test/unit/LDAPUtilsTest.cs @@ -2,14 +2,12 @@ using System.Collections.Generic; using System.DirectoryServices.ActiveDirectory; using System.DirectoryServices.Protocols; -using System.Linq; using System.Threading; using CommonLibTest.Facades; using Moq; using SharpHoundCommonLib; using SharpHoundCommonLib.Enums; using SharpHoundCommonLib.Exceptions; -using SharpHoundCommonLib.Processors; using Xunit; using Xunit.Abstractions; @@ -101,11 +99,11 @@ public void BuildLdapPath_BadDomain_ReturnsNull() var mock = new Mock(); //var mockDomain = MockableDomain.Construct("TESTLAB.LOCAL"); mock.Setup(x => x.GetDomain(It.IsAny())) - .Returns((Domain) null); + .Returns((Domain)null); var result = mock.Object.BuildLdapPath("TEST", "ABC"); Assert.Null(result); } - + [Fact] public void BuildLdapPath_HappyPath() { @@ -135,7 +133,7 @@ public void GetWellKnownPrincipal_WithDomain_ConvertsSID() Assert.Equal(Label.Group, typedPrincipal.ObjectType); Assert.Equal($"{_testDomainName}-S-1-5-32-544", typedPrincipal.ObjectIdentifier); } - + [Fact] public void DistinguishedNameToDomain_RegularObject_CorrectDomain() { @@ -151,7 +149,7 @@ public void DistinguishedNameToDomain_RegularObject_CorrectDomain() public void GetDomainRangeSize_BadDomain_ReturnsDefault() { var mock = new Mock(); - mock.Setup(x => x.GetDomain(It.IsAny())).Returns((Domain) null); + mock.Setup(x => x.GetDomain(It.IsAny())).Returns((Domain)null); var result = mock.Object.GetDomainRangeSize(); Assert.Equal(750, result); } @@ -160,13 +158,13 @@ public void GetDomainRangeSize_BadDomain_ReturnsDefault() public void GetDomainRangeSize_RespectsDefaultParam() { var mock = new Mock(); - mock.Setup(x => x.GetDomain(It.IsAny())).Returns((Domain) null); + mock.Setup(x => x.GetDomain(It.IsAny())).Returns((Domain)null); var result = mock.Object.GetDomainRangeSize(null, 1000); Assert.Equal(1000, result); } - [Fact] + [WindowsOnlyFact] public void GetDomainRangeSize_NoLdapEntry_ReturnsDefault() { var mock = new Mock(); @@ -180,7 +178,7 @@ public void GetDomainRangeSize_NoLdapEntry_ReturnsDefault() Assert.Equal(750, result); } - [Fact] + [WindowsOnlyFact] public void GetDomainRangeSize_ExpectedResults() { var mock = new Mock(); @@ -193,10 +191,10 @@ public void GetDomainRangeSize_ExpectedResults() "MaxPageSize=1250" }}, }, "abc123", Label.Base); - + mock.Setup(x => x.QueryLDAP(It.IsAny(), It.IsAny(), null, It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), - It.IsAny(), It.IsAny())).Returns(new List {searchResult}); + It.IsAny(), It.IsAny())).Returns(new List { searchResult }); var result = mock.Object.GetDomainRangeSize(); Assert.Equal(1250, result); }