diff --git a/src/Sddl.Parser/Ace.cs b/src/Sddl.Parser/Ace.cs
index 8b048bf..b5734bc 100644
--- a/src/Sddl.Parser/Ace.cs
+++ b/src/Sddl.Parser/Ace.cs
@@ -16,7 +16,11 @@ public class Ace : Acm
public string InheritObjectGuid { get; }
public Sid AceSid { get; }
- public Ace(string ace, SecurableObjectType type = SecurableObjectType.Unknown)
+ public Ace(string ace, SecurableObjectType type = SecurableObjectType.Unknown) : this(ace, type, null)
+ {
+ }
+
+ public Ace(string ace, SecurableObjectType type, SidResolverOptions sidResolverOptions)
{
Raw = ace;
@@ -90,7 +94,7 @@ public Ace(string ace, SecurableObjectType type = SecurableObjectType.Unknown)
// account_sid
if (parts.Length > 5 && parts[5].Length > 0)
{
- AceSid = new Sid(parts[5]);
+ AceSid = new Sid(parts[5], sidResolverOptions);
}
// resource_attribute
diff --git a/src/Sddl.Parser/Acl.cs b/src/Sddl.Parser/Acl.cs
index 6306cbf..853cbbc 100644
--- a/src/Sddl.Parser/Acl.cs
+++ b/src/Sddl.Parser/Acl.cs
@@ -11,7 +11,11 @@ public class Acl : Acm
public string[] Flags { get; }
public Ace[] Aces { get; }
- public Acl(string acl, SecurableObjectType type = SecurableObjectType.Unknown)
+ public Acl(string acl, SecurableObjectType type = SecurableObjectType.Unknown) : this(acl, type, null)
+ {
+ }
+
+ public Acl(string acl, SecurableObjectType type, SidResolverOptions sidResolverOptions)
{
Raw = acl;
@@ -55,7 +59,7 @@ public Acl(string acl, SecurableObjectType type = SecurableObjectType.Unknown)
}
if (balance == 0)
- aces.AddLast(new Ace(acl.Substring(begin + 1, length), type));
+ aces.AddLast(new Ace(acl.Substring(begin + 1, length), type, sidResolverOptions));
}
else if (balance <= 0)
{
diff --git a/src/Sddl.Parser/DirectoryServiceSidResolver.cs b/src/Sddl.Parser/DirectoryServiceSidResolver.cs
new file mode 100644
index 0000000..5b754bf
--- /dev/null
+++ b/src/Sddl.Parser/DirectoryServiceSidResolver.cs
@@ -0,0 +1,54 @@
+using System;
+using System.Security.Principal;
+
+namespace Sddl.Parser
+{
+ ///
+ /// Resolves SID strings using SecurityIdentifier.Translate (DirectoryService lookup)
+ ///
+ public class DirectoryServiceSidResolver : ISidResolver
+ {
+ ///
+ /// Attempts to resolve a SID string to a meaningful name using DirectoryService lookup
+ ///
+ /// The SID string to resolve
+ /// The resolved name, or null if resolution fails
+ public string ResolveSid(string sidString)
+ {
+ if (string.IsNullOrEmpty(sidString))
+ return null;
+
+ try
+ {
+ // Try to parse as a SecurityIdentifier
+ SecurityIdentifier sid;
+ try
+ {
+ sid = new SecurityIdentifier(sidString);
+ }
+ catch
+ {
+ // Not a valid SID format
+ return null;
+ }
+
+ // Attempt to translate to NTAccount using DirectoryService lookup
+ try
+ {
+ var account = (NTAccount)sid.Translate(typeof(NTAccount));
+ return account.Value;
+ }
+ catch
+ {
+ // Translation failed - SID may not exist or may not be translatable
+ return null;
+ }
+ }
+ catch
+ {
+ // Any other error during resolution
+ return null;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Sddl.Parser/ISidResolver.cs b/src/Sddl.Parser/ISidResolver.cs
new file mode 100644
index 0000000..8e9aa79
--- /dev/null
+++ b/src/Sddl.Parser/ISidResolver.cs
@@ -0,0 +1,15 @@
+namespace Sddl.Parser
+{
+ ///
+ /// Interface for resolving SID strings to meaningful names
+ ///
+ public interface ISidResolver
+ {
+ ///
+ /// Attempts to resolve a SID string to a meaningful name
+ ///
+ /// The SID string to resolve
+ /// The resolved name, or null if resolution fails
+ string ResolveSid(string sidString);
+ }
+}
\ No newline at end of file
diff --git a/src/Sddl.Parser/Sddl.Parser.csproj b/src/Sddl.Parser/Sddl.Parser.csproj
index 951a14f..93da280 100755
--- a/src/Sddl.Parser/Sddl.Parser.csproj
+++ b/src/Sddl.Parser/Sddl.Parser.csproj
@@ -8,4 +8,8 @@
Sddl.Parser
+
+
+
+
\ No newline at end of file
diff --git a/src/Sddl.Parser/Sddl.cs b/src/Sddl.Parser/Sddl.cs
index 8d92e19..3a6efd7 100644
--- a/src/Sddl.Parser/Sddl.cs
+++ b/src/Sddl.Parser/Sddl.cs
@@ -13,7 +13,11 @@ public class Sddl : Acm
public Acl Dacl { get; }
public Acl Sacl { get; }
- public Sddl(string sddl, SecurableObjectType type = SecurableObjectType.Unknown)
+ public Sddl(string sddl, SecurableObjectType type = SecurableObjectType.Unknown) : this(sddl, type, null)
+ {
+ }
+
+ public Sddl(string sddl, SecurableObjectType type, SidResolverOptions sidResolverOptions)
{
Raw = sddl;
@@ -40,25 +44,25 @@ public Sddl(string sddl, SecurableObjectType type = SecurableObjectType.Unknown)
if (components.TryGetValue(OwnerToken, out var owner))
{
- Owner = new Sid(owner);
+ Owner = new Sid(owner, sidResolverOptions);
components.Remove(OwnerToken);
}
if (components.TryGetValue(GroupToken, out var group))
{
- Group = new Sid(group);
+ Group = new Sid(group, sidResolverOptions);
components.Remove(GroupToken);
}
if (components.TryGetValue(DaclToken, out var dacl))
{
- Dacl = new Acl(dacl, type);
+ Dacl = new Acl(dacl, type, sidResolverOptions);
components.Remove(DaclToken);
}
if (components.TryGetValue(SaclToken, out var sacl))
{
- Sacl = new Acl(sacl, type);
+ Sacl = new Acl(sacl, type, sidResolverOptions);
components.Remove(SaclToken);
}
diff --git a/src/Sddl.Parser/Sid.cs b/src/Sddl.Parser/Sid.cs
index 5746ca3..8343903 100644
--- a/src/Sddl.Parser/Sid.cs
+++ b/src/Sddl.Parser/Sid.cs
@@ -8,7 +8,11 @@ public class Sid : Acm
public string Alias { get; }
- public Sid(string sid)
+ public Sid(string sid) : this(sid, null)
+ {
+ }
+
+ public Sid(string sid, SidResolverOptions options)
{
Raw = sid;
@@ -16,9 +20,18 @@ public Sid(string sid)
if (alias == null)
{
- Report(Error.SDP001.Format(sid));
-
- alias = Format.Unknown(sid);
+ // Try DirectoryService lookup if enabled
+ if (options?.EnableDirectoryServiceLookup == true)
+ {
+ var resolver = options.SidResolver ?? new DirectoryServiceSidResolver();
+ alias = resolver.ResolveSid(sid);
+ }
+
+ if (alias == null)
+ {
+ Report(Error.SDP001.Format(sid));
+ alias = Format.Unknown(sid);
+ }
}
Alias = alias;
diff --git a/src/Sddl.Parser/SidResolverOptions.cs b/src/Sddl.Parser/SidResolverOptions.cs
new file mode 100644
index 0000000..513f9d2
--- /dev/null
+++ b/src/Sddl.Parser/SidResolverOptions.cs
@@ -0,0 +1,19 @@
+namespace Sddl.Parser
+{
+ ///
+ /// Configuration options for SID resolution
+ ///
+ public class SidResolverOptions
+ {
+ ///
+ /// Gets or sets whether to enable DirectoryService lookup for unknown SIDs
+ ///
+ public bool EnableDirectoryServiceLookup { get; set; } = false;
+
+ ///
+ /// Gets or sets the SID resolver to use for DirectoryService lookups.
+ /// If null and EnableDirectoryServiceLookup is true, a default DirectoryServiceSidResolver will be used.
+ ///
+ public ISidResolver SidResolver { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/test/Sddl.Parser.Tests/SidResolverTests.cs b/test/Sddl.Parser.Tests/SidResolverTests.cs
new file mode 100644
index 0000000..ada0956
--- /dev/null
+++ b/test/Sddl.Parser.Tests/SidResolverTests.cs
@@ -0,0 +1,200 @@
+using System;
+using Xunit;
+
+namespace Sddl.Parser.Tests
+{
+ public class SidResolverTests
+ {
+ [Fact]
+ public void Sid_WithoutOptions_ShouldUseLegacyBehavior()
+ {
+ // Arrange
+ var unknownSidString = "S-1-5-21-1234567890-1234567890-1234567890-1001";
+
+ // Act
+ var sid = new Sid(unknownSidString);
+
+ // Assert
+ Assert.Equal(unknownSidString, sid.Raw);
+ Assert.Equal($"Unknown({unknownSidString})", sid.Alias);
+ Assert.False(sid.IsValid);
+ Assert.Single(sid.Errors);
+ }
+
+ [Fact]
+ public void Sid_WithDirectoryServiceDisabled_ShouldUseLegacyBehavior()
+ {
+ // Arrange
+ var unknownSidString = "S-1-5-21-1234567890-1234567890-1234567890-1001";
+ var options = new SidResolverOptions { EnableDirectoryServiceLookup = false };
+
+ // Act
+ var sid = new Sid(unknownSidString, options);
+
+ // Assert
+ Assert.Equal(unknownSidString, sid.Raw);
+ Assert.Equal($"Unknown({unknownSidString})", sid.Alias);
+ Assert.False(sid.IsValid);
+ Assert.Single(sid.Errors);
+ }
+
+ [Fact]
+ public void Sid_WithCustomResolver_ShouldUseCustomResolver()
+ {
+ // Arrange
+ var unknownSidString = "S-1-5-21-1234567890-1234567890-1234567890-1001";
+ var customResolver = new TestSidResolver();
+ var options = new SidResolverOptions
+ {
+ EnableDirectoryServiceLookup = true,
+ SidResolver = customResolver
+ };
+
+ // Act
+ var sid = new Sid(unknownSidString, options);
+
+ // Assert
+ Assert.Equal(unknownSidString, sid.Raw);
+ Assert.Equal("TestDomain\\TestUser", sid.Alias);
+ Assert.True(sid.IsValid);
+ Assert.Empty(sid.Errors);
+ }
+
+ [Fact]
+ public void Sid_WithCustomResolverReturningNull_ShouldFallbackToUnknown()
+ {
+ // Arrange
+ var unknownSidString = "S-1-5-21-9999999999-9999999999-9999999999-9999";
+ var customResolver = new TestSidResolver();
+ var options = new SidResolverOptions
+ {
+ EnableDirectoryServiceLookup = true,
+ SidResolver = customResolver
+ };
+
+ // Act
+ var sid = new Sid(unknownSidString, options);
+
+ // Assert
+ Assert.Equal(unknownSidString, sid.Raw);
+ Assert.Equal($"Unknown({unknownSidString})", sid.Alias);
+ Assert.False(sid.IsValid);
+ Assert.Single(sid.Errors);
+ }
+
+ [Fact]
+ public void Sid_KnownSid_ShouldIgnoreResolver()
+ {
+ // Arrange
+ var knownSidString = "SY";
+ var customResolver = new TestSidResolver();
+ var options = new SidResolverOptions
+ {
+ EnableDirectoryServiceLookup = true,
+ SidResolver = customResolver
+ };
+
+ // Act
+ var sid = new Sid(knownSidString, options);
+
+ // Assert
+ Assert.Equal(knownSidString, sid.Raw);
+ Assert.Equal("Local System", sid.Alias);
+ Assert.True(sid.IsValid);
+ Assert.Empty(sid.Errors);
+ }
+
+ [Fact]
+ public void Sddl_WithSidResolverOptions_ShouldPassOptionsToSids()
+ {
+ // Arrange
+ var sddlString = "O:S-1-5-21-1234567890-1234567890-1234567890-1001G:DAD:(A;;FA;;;S-1-5-21-1234567890-1234567890-1234567890-1001)";
+ var customResolver = new TestSidResolver();
+ var options = new SidResolverOptions
+ {
+ EnableDirectoryServiceLookup = true,
+ SidResolver = customResolver
+ };
+
+ // Act
+ var sddl = new Sddl(sddlString, SecurableObjectType.Unknown, options);
+
+ // Assert
+ Assert.NotNull(sddl.Owner);
+ Assert.Equal("TestDomain\\TestUser", sddl.Owner.Alias);
+ Assert.True(sddl.Owner.IsValid);
+
+ Assert.NotNull(sddl.Dacl);
+ Assert.NotEmpty(sddl.Dacl.Aces);
+ Assert.NotNull(sddl.Dacl.Aces[0].AceSid);
+ Assert.Equal("TestDomain\\TestUser", sddl.Dacl.Aces[0].AceSid.Alias);
+ Assert.True(sddl.Dacl.Aces[0].AceSid.IsValid);
+ }
+
+ [Fact]
+ public void DirectoryServiceSidResolver_WithValidSid_ShouldHandleGracefully()
+ {
+ // Arrange
+ var resolver = new DirectoryServiceSidResolver();
+ var validSidString = "S-1-1-0"; // Everyone - should be resolvable
+
+ // Act
+ var result = resolver.ResolveSid(validSidString);
+
+ // Assert - either returns a name or null, both are acceptable
+ // We can't assert specific behavior since it depends on the system
+ Assert.True(result == null || !string.IsNullOrEmpty(result));
+ }
+
+ [Fact]
+ public void DirectoryServiceSidResolver_WithInvalidSid_ShouldReturnNull()
+ {
+ // Arrange
+ var resolver = new DirectoryServiceSidResolver();
+ var invalidSidString = "not-a-sid";
+
+ // Act
+ var result = resolver.ResolveSid(invalidSidString);
+
+ // Assert
+ Assert.Null(result);
+ }
+
+ [Fact]
+ public void DirectoryServiceSidResolver_WithNullSid_ShouldReturnNull()
+ {
+ // Arrange
+ var resolver = new DirectoryServiceSidResolver();
+
+ // Act
+ var result = resolver.ResolveSid(null);
+
+ // Assert
+ Assert.Null(result);
+ }
+
+ [Fact]
+ public void DirectoryServiceSidResolver_WithEmptySid_ShouldReturnNull()
+ {
+ // Arrange
+ var resolver = new DirectoryServiceSidResolver();
+
+ // Act
+ var result = resolver.ResolveSid(string.Empty);
+
+ // Assert
+ Assert.Null(result);
+ }
+ }
+
+ // Test helper class
+ public class TestSidResolver : ISidResolver
+ {
+ public string ResolveSid(string sidString)
+ {
+ if (sidString == "S-1-5-21-1234567890-1234567890-1234567890-1001")
+ return "TestDomain\\TestUser";
+ return null;
+ }
+ }
+}
\ No newline at end of file