Thursday, December 6, 2007

Programming Headache - Access Denied when calling SetPassword

Thought I'd post this as a public service after I spent a day trying to figure it out:

Scenario:
I was trying to create a windows domain user and set their password in ASP.net 2.0. I was using the DirectoryServices namespace to do this. The creation of the user worked fine using this code:

DirectoryEntry objDirEnt = new DirectoryEntry(@"LDAP://123.123.12.12/OU=Customers,DC=acme,DC=com",@"domainadminusername",@"domainadminpassword");
DirectoryEntry newDirectoryEntry = objDirEnt.Children.Add("CN=testuser", "user");
newDirectoryEntry.Properties["samAccountName"].Value = "testuser";
newDirectoryEntry.CommitChanges();

The "invoke" call to SetPassword would work when running through the Visual Studio debugger but would fail when running the published site via IIS:

newDirectoryEntry.Invoke("SetPassword", new object[] { "testpassword"});
newDirectoryEntry.CommitChanges();

The error happend on the Invoke call and the message was System.UnauthorizedAccessException: Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED))

I tried testing this code under 1.1 and it worked fine so it seems to only be a 2.0 problem.

The security for my application was set up to use impersonation, so my web.config file had this setting (yeah, I know, it's bad to impersonate a domain admin, but I was just trying to get it to work):

<authentication mode="Windows"/>
<identity impersonate="true" userName="domainadminusername" password="domainadminpassword"/>

I googled this problem and a lot of people had the same issue, but there were no solutions offered that worked for me. After pulling out my hair for an entire day, I finally figured out the solution.

I had to remove the server from my ldap path when I did the LDAP bind to the container. So the new code looks like this:

DirectoryEntry objDirEnt = new DirectoryEntry(@"LDAP://OU=Customers,DC=acme,DC=com",@"domainadminusername",@"domainadminpassword");

After removing the server, everything worked fine. I think this happens because the underlying setpassword API's use the format of the ldap path to determine which type of password authentication to try. Maybe someone smarter than me can explain this. Here is a Microsoft article with a cryptic reference to the serverless bind: http://msdn2.microsoft.com/en-us/library/aa746344.aspx