11

I have two domains, in a trusted relationship, that I'm trying to manage from a C# web application. To do that, I have to impersonate two different technical users, but that works good, so I will not emphasize that part of the code.

To build proper and easy to manage ACLs for the file system, I must

  • Create a group in domainA (OK!)
  • Find a user in domainB (OK!)
  • Add the user to the group (FAILS when committing changes, error message: There is no such object on the server. (Exception from HRESULT: 0x80072030))

If I'm adding a user from the same domain, the code works perfectly, so I believe I'm only missing a small partial info here. I used this document as a reference and saw this question as well (and a few more citing this error message) but neither of them helped.

Code (try-catch block removed to make it simpler)

// de is a DirectoryEntry object of the AD group, received by the method as a parameter
// first impersonation to search in domainB
// works all right
if (impersonator.impersonateUser("techUser1", "domainB", "pass")) {
    DirectoryEntry dom = new DirectoryEntry("LDAP://domainB.company.com/OU=MyOU,DC=domainB,DC=company,DC=com", "techUser1", "pass");
    de.Invoke("Add", new object[] { "LDAP://domainB.company.com/CN=theUserIWantToAdd,OU=MyOU,DC=domainB,DC=company,DC=com" });
    // de.Invoke("Add", new object[] { "LDAP://domainA.company.com/CN=anotherUserFromDomainA,OU=AnotherOU,DC=domainB,DC=company,DC=com" });
    impersonator.undoImpersonation();
}

// second impersonation because the group (de) is in domainA
// and techUser2 has account operator privileges there
if (impersonator.impersonateUser("techUser2", "domainA", "pass"))
{
    de.CommitChanges();
    impersonator.undoImpersonation();
    return true;
}
else
{
    // second impersonation was unsuccessful, so return an empty object
    return false;
}

Line 6 works, if I debug it or force the properties to be written to HttpResponse, it is clearly there. So the LDAP queries seem to be OK.

Also, if I comment out line 6 and uncomment 7, so basically I add a user from the same domain, the whole thing works miraculously. With domainB, I'm stuck. Any good piece of advice?

Community
  • 1
  • 1
Laszlo T
  • 1,165
  • 10
  • 22
  • OK, I still don't have the code but I found one clue: from different domains, group members can be added as ForeignSecurityPincipals, so not the normal way. – Laszlo T Aug 13 '15 at 12:49
  • Are you only having issue when you try to add user from one domain to another domain group or doing some other operations as well? For testing purposes, can you try to update a user name while impersonating? – smr5 Aug 26 '15 at 01:24
  • I don't have account op privileges in domainB, so there's no need to try. In domainA, everything works, I am able to create groups, users, reset passwords, unlock accounts, etc. – Laszlo T Aug 26 '15 at 07:06

2 Answers2

6

Following your code, I see that you're getting de as a parameter, which is in Domain A. Then you're creating DirectoryEntry object dom, which is getting impersonated, but never getting used. However, you're trying to add an object from Domain B to de directly using LDAP. This line:

de.Invoke("Add", new object[{"LDAP://domainB.company.com/CN=theUserIWantToAdd,OU=MyOU,DC=domainB,DC=company,DC=com" }); 

is not getting impersonated.

Assuming your impersonation works correctly, use dom object which is already impersonated with DirectorySearcher to find the user in Domain B and then add the user object from Domain B to de.

...
using (DirectoryEntry dom = new DirectoryEntry("LDAP://domainB.company.com/OU=MyOU,DC=domainB,DC=company,DC=com", "techUser1", "pass"))
{
    using (DirectorySearcher searcher = new DirectorySearcher(dom))
    {
        searcher.Filter = "(&(objectClass=user)(CN=theUserIWantToAdd))";
        SearchResult result = searcher.FindOne();
        de.Invoke("Add", new object[] { result.Path });
    }
}
...

UDPATE

This example will show you how to get user SID from one domain, search group from another domain and add user to group using SID.

//GET THE USER FROM DOMAIN B
using (UserPrincipal userPrincipal = UserPrincipal.FindByIdentity(domainContext, UPN))
{
    if (userPrincipal != null)
    {
       //FIND THE GROUP IN DOMAIN A
       using (GroupPrincipal groupPrincipal = GroupPrincipal.FindByIdentity(domainContext, groupName))
       {
          if (groupPrincipal != null)
          {
             //CHECK TO MAKE SURE USER IS NOT IN THAT GROUP
             if (!userPrincipal.IsMemberOf(groupPrincipal))
             {
                string userSid = string.Format("<SID={0}>", userPrincipal.SID.ToString());
                DirectoryEntry groupDirectoryEntry = (DirectoryEntry)groupPrincipal.GetUnderlyingObject();
                groupDirectoryEntry.Properties["member"].Add(userSid);
                groupDirectoryEntry.CommitChanges();
              }
           }
        }
     }
 }

Please note that I skipped all the impersonation in the above code.

smr5
  • 2,593
  • 6
  • 39
  • 66
  • Works before the last line. So finds the user, I can see its full path or any other property, but fails adding it. Error message at de.Invoke: System.DirectoryServices.DirectoryServicesCOMException: There is no such object on the server. (Exception from HRESULT: 0x80072030). I've already came accross this error message with my original approach, I believe the problem is that I'm trying to save a group object in DomainA with a member from DomainB. That's why the foreignsecuritypricipal object must be involved somehow. – Laszlo T Aug 26 '15 at 09:48
  • But anyway, your comment and answer helped me to move to 4.5 from 4.0 and adding System.DirectoryServices.AccountManagement to my project, thanks for pointing those out! – Laszlo T Aug 26 '15 at 09:51
  • When I take a look at other groups in DomainA (created manually) using the SysInternals tool I see that members from domainB are added like "CN=,CN=ForeignSecurityPrincipals,DC=domainA,DC=company,DC=com". See the difference. So these users have an object in domainA too, I can even find them in the CN=ForeignSecurityPrincipals part of AD. The bad thing is that they contain no human readable properties, so I'm unable to find a user here by name or samaccountname. Haven't found a property that identifies the object in domainB ... – Laszlo T Aug 26 '15 at 10:04
  • Good. Can you post your updated code using `AccountManagement` namespace? Do you have an access to `ADSI` tool? – smr5 Aug 26 '15 at 17:07
  • Also, can you tell me about the group in `Domain A`. What kind of group is it? `User`, `Global`, `Local`? What kind of trust do you have between the two domains? I will update my answer shortly... – smr5 Aug 26 '15 at 17:38
  • Hello Burzum, finaly I tried exactly your code you posted above. – Laszlo T Aug 28 '15 at 12:37
  • I just wanted to point out something about the groups. Let's say your `Domain A` trusts your `Domain B`. Each domain has groups: `User`, `Global`, `Local`. Here's what you can't do. You can't add user from `Domain B` to `Global` group in `Domain A`. You can't add `Global` group from `Domain B` to `Global` group in `Domain A`. You can't add `Local` group from `Domain B` to `Local` group in `Domain A`. You can add `User` from `Domain B` directly to `Local` group in `Domain A`, but that's not a good practice. You should add `User` from `Domain B` to `Global` group in `Domain B` and then add – smr5 Aug 28 '15 at 16:43
  • `Global` group from `Domain B` to `Local` group in `Domain A`. And this structure goes both ways. If your `Domain B` had a trust with `Domain A`. And to answer your question about `ForeignSecurityPrincipals`. Those groups from one domain gets added to another domain using `SID` when you create the `Trust`. – smr5 Aug 28 '15 at 16:46
  • I don't want to lose this 100 points so I awarded your answer bc you gave me hints that I think were useful. However I still don't have a solution for this problem. I am probably closer now, there is this thread where Joe Kaplan has a solution for a similar problem: http://www.pcreview.co.uk/threads/add-group-members-from-trusted-domain-programmatically.1460084/ But this is not working for me either (System.DirectoryServices.DirectoryServicesCOMException: The server is unwilling to process the request.) – Laszlo T Sep 01 '15 at 07:29
  • Same error message when trying to add the domainA ForeignSecurityPrincipal object of the domainB user like this: CN=S-1-5-21-1573156469-53016239-2905339568-42905,CN=ForeignSecurityPrincipals,DC=domainA,DC=company,DC=com (I searched for it manually in the AD so it does exist and it's a correct path.) – Laszlo T Sep 01 '15 at 07:32
  • Can you update your post and show me how you're adding user using `sid`? Typically, when you get `Unwilling to process the request` signifies you have a wrong DN. – smr5 Sep 01 '15 at 07:46
  • I have a busy day but I will, yes, probably afternoon. Typically after these two lines: de.Properties["member"].Add(""); de.CommitChanges(); – Laszlo T Sep 01 '15 at 07:51
  • @LaszloTenki, I updated my post and added some example following `Joe's` example. Let me know if that helps you. – smr5 Sep 01 '15 at 08:03
  • Thanks, Burzum. Your code worked and was one crucial part of the final solution. But I will add one another because probably not everybody reads the comments if there is an accepted solution. – Laszlo T Sep 02 '15 at 09:40
0

What finally worked was using principals as Burzum suggested. The original code samples you can see in the MSDN article linked in the question did not work here. So the Principal-based approach is a must nut not enough. You need one more line before committing changes of the new group:

group.Properties["groupType"].Value = (-2147483644);

The default was 0x8000000 and I had to change it to 0x80000004 to enable it to accept FSPs from another domain.

So now the group exists, it has members, it is added to the ACL of the folder.

Laszlo T
  • 1,165
  • 10
  • 22
  • I know this is old, but I think clarification is need. Setting the [`groupType`](https://learn.microsoft.com/en-us/windows/win32/adschema/a-grouptype) to `0x80000004` will set the "Group scope" to "Domain Local" and the "Group type" to "Security". The important part is setting the "Group scope" to "Domain Local", since that is what will allow users from trusted domains to be added. This only needs to be done once, so it can be done manually in AD Users and Computers. – Gabriel Luci Oct 22 '19 at 13:57
  • @GabrielLuci Normally yes but this was a self-service web app where you could set up a new group file share and set the access rights according to your project needs. So it created the groups for each new file share: one for admins, one with R and another with RW access. I had to add the users to those groups. The groups and the users were in different but trusted domains. – Laszlo T Oct 23 '19 at 17:46
  • Yeah, that makes sense if you're programmatically creating the group too. – Gabriel Luci Oct 23 '19 at 19:58
  • And note that `-2147483644` can also be expressed as `unchecked((int) 0x80000004)`. The compiler will compile that into `-2147483644`, but the key is readability, since all of Microsoft's documentation shows the hex values. But in the end, both will work. – Gabriel Luci Oct 23 '19 at 20:02