Search code examples
c#active-directoryactive-directory-group

Why does Principal.IsMemberOf() return a false negative for some groups?


Why does Principal.IsMemberOf(GroupPrincipal) (MSDN) return a false negative for the Domain Computers group in the following test?

[TestMethod]
public void DomainComputerTest()
{
    var distinguishedName = "CN=MyMachine,DC=SomeDomain,DC=local";
    using( var pc = new PrincipalContext( ContextType.Domain, "SomeDomain.local", "UserName", "Password" ) )
    using( var computer = ComputerPrincipal.FindByIdentity( pc, IdentityType.DistinguishedName, distinguishedName ) )
    {
        Assert.IsNotNull( computer );
        // Get the groups for the computer.
        var groups = computer.GetGroups().Cast<GroupPrincipal>();
        foreach( var group in groups )
        {
            // Immediately turn around and test that the computer is a member of the groups it returned.
            Assert.IsTrue( computer.IsMemberOf( group ), "Computer is not member of group {0}", group.Name );
        }
    }
}

Result Message: Assert.IsTrue failed. Computer is not member of 'Domain Computers' group

The computer is indeed a member of the "Domain Computers" group, which the 'GetGroups()' method returned correctly. In fact, if you attempt to add the computer to the group, a PrincipalExistsException is thrown.

I can reproduce the exact same behavior with users and the "Domain Users" group. Is this because the groups are the Principal groups? Is it because these are "default" groups?

Edit to add: We're using .NET 4.5.1.


Solution

  • For other developers that find this, here's what I ended up doing. This is a simplified version of the code I deployed, but in summary, I'm assuming the issue is the Primary Group relationship. This may not be correct, but it is working for us for now.

    You can get the DirectoryEntry instance for a ComputerPrincipal like this.

    var entry = (DirectoryEntry)computerPrincipal.GetUnderlyingObject();
    

    And I'm using this extension method to check for the primary group relationship.

    public static bool IsPrimaryGroupFor( this GroupPrincipal group, DirectoryEntry target )
    {
        // .Value will return an int like "123", which is the last part of the group's SID
        var id = target.Properties[ "primaryGroupID" ].Value.ToString();
        // strip the account domain SID from the group SID.
        var groupId = group.Sid.Value.Remove( 0, group.Sid.AccountDomainSid.Value.Length + 1 );
    
        // If the 
        return id.Equals( groupId, StringComparison.OrdinalIgnoreCase );
    }
    

    We're syncing AD group memberships, so we discovered this issue in code similar to the following;

    public void AddComputerToGroups( ComputerPrincipal computer, ICollection<GroupPrincipal> groups )
    {
        var directoryEntry = (DirectoryEntry)computer.GetUnderlyingObject();
    
        foreach( var principal in groups.Where(g=> !computer.IsMemberOf(g) )
        {
            principal.Members.Add( computer );
            principal.Save(); // Exception thrown because computer already existed in the primary group.
        }
    }