locked
The (In)Famous "Access Denied" problem RRS feed

  • Question

  • User-1037239328 posted

    I've been through literally dozens of articles and forum entries, and yet this problem hangs on. I am writing a simple ASP.NET UserControl  to allow our users to update their basic profile information, and change their password. The UserControl can read anything from Active Directory. Changes will not save. "Access is denied."

    • Credentials are good. We have a Service Account that is correctly delegated to update user profiles and passwords.
    • I have also tried using my own credentials. I am a Domain Admin.
    • I have tested without credentials. It won't even read without credentials, so the credentials ARE working.
    • I have turned Windows Authentication on and off in IIS 7.
    • I have turned Impersonation on and off in IIS 7.
    • I have used Impersonation in code.
    • I have run IIS7's process under accounts with "god" rights.
    • I have used the "old" DirectoryServices.DirectoryEntry approach, and the "new" AcccountManagement.PrincipalContext approach.
    • I have run the code on my home machine through a VPN connection.
    • I have run the code on my office development machine on the domain.
    • I have run the code on the Intranet web server, which is on the domain.
    • In all three environments, I can run "ADSI Edit" or "Active Directory Users and Computers," and change anything for any object. So the credentials and network are good.
    • I remembered that "way back when" there was a bug somewhere, that C# code would not work, but VB.NET code would. I figured that little bug was long gone, but thought I'd try anyway. So I re-wrote the UserControl in VB.NET. No joy.

    When the code runs-- no matter what-- the result is the same. "Access is denied."

    I wrote a similar ASP.NET UserControl "way back when" in 2001, for use on a Win2000 domain with multiple forests. It was simple to do and worked like a charm. This present environment is a "single forest" Win2008-R2 domain. Security is tougher, sure. But the .NET code is new and more robust. So what gives?

    Here is my present block of code. (Error occurs at line "up.Save();."

    protected void btnSave_Click(object sender, EventArgs e)
    {
    	// Get logged-in user's info to be updated.
    	SearchAD();
     
    	// Load the control's User object from web form.
    User.Location = lblLocation.Text; User.DisplayName = txtDisplayName.Text; User.Email = txtEmail.Text; User.FirstName = txtFirstName.Text; User.HomePhone = txtHomePhone.Text; User.LastName = txtLastName.Text; User.MobilePhone = txtMobilePhone.Text; User.WorkPhone = txtWorkPhone.Text;
    // Open an AD PrincipalContext and UserPrincipal object. PrincipalContext context = new PrincipalContext(ContextType.Domain, @"server.domainname.local"@"domain\delegate"@"password"); UserPrincipal up = UserPrincipal.FindByIdentity(context, User.LoginName);
    // Assign values to the Active Directory user
    // AccountManager properties up.DisplayName = NullString(User.DisplayName); up.EmailAddress = NullString(User.Email); up.GivenName = NullString(User.FirstName); up.Surname = NullString(User.LastName); up.VoiceTelephoneNumber = NullString(User.WorkPhone);
    // Properties not exposed by AccountManager
    DirectoryEntry details = (DirectoryEntry)up.GetUnderlyingObject(); details.Properties["homePhone"].Value = NullString(User.HomePhone); details.Properties["mobile"].Value = NullString(User.MobilePhone); details.Properties["telephoneNumber"].Value = NullString(User.WorkPhone); details.Properties["l"].Value = NullString(User.Location);
    // Save changes up.Save(); } private string NullString(string Value) { if (string.IsNullOrWhiteSpace(Value)) return null; else return Value; }

    This code was built and refned through many, many aritcles and forums. I am now wondering if there isn't some setting in our Active Directory that is blocking the Saves. We "inherited" our Active Directory from a team that, frankly, didn't know what they were doing. So I wouldn't be surprised.

    I know it's an old, heavily beaten dead horse. But does anybody have any ideas, either for code or in AD settings?

    Tuesday, April 30, 2013 4:26 PM

Answers

  • User-1037239328 posted

    I found the problem. The problem WAS in Active Directory. By design, yet.

    My UserControl is for users of the domain be able to update limited parts of their own profiles, and change their passwords. Since this is for the user to use on her own account, I was, of course, running against my own account. I am a Domain Admin.

    In Active Directory, at path cn=AdminSdHolder,cn=System,dc=yourdomain,dc=com, is an object named "AdminSdHolder." Very simply, this object "protects" certain groups and objects from being changed. One of the "protected" groups is Domain Admins.
    See: http://msmvps.com/blogs/ulfbsimonweidner/archive/2005/05/29/49659.aspx
    and: http://technet.microsoft.com/en-us/magazine/2009.09.sdadminholder.aspx 

    When delegate permissions are set on an OU, it is dependent on the objects in the OU to have the “Include inherited permissions from this object’s parent” checkbox checked. By default, this is NOT set for members of the Domain Admins group. Therefore, delegated permissions for profile/password changes are NOT delegated to Domain Admins members. You can change this on individual users, but when Active Directory replicates within an hour, the setting will go back to default.

    Since I was testing against my own account, of course the delegate did not have permissions. (Why it did not work for some time with my own credentials, I don't know. It started working and continues to work now.)

    It is possible, but not necessarily desirable, to change this behavior. So the answer I came up with is to test the UserPrincipal for group membership. If the user is a member of Domain Admins, then I reload the context using the user's own credentials, rather than the delegate's credentials. Then UserPrinciple.Save() works.

    PrincipalContext context = new PrincipalContext(ContextType.Domain, @"dcp1.cvacoop.local"@"domain\delegate"@"delegatepassword");
    UserPrincipal up = UserPrincipal.FindByIdentity(context, MojoUser.LoginName);
    PrincipalSearchResult<Principal> groups = up.GetGroups();
    bool isDomainAdmin = (groups.Where(g => g.Name == "Domain Admins").SingleOrDefault() != null);
    
    if (isDomainAdmin)
    {
    	context = new PrincipalContext(ContextType.Domain, @"dcp1.cvacoop.local"@"domain\" + UserLoginName, txtPassword.Text);
    	up = UserPrincipal.FindByIdentity(context, MojoUser.LoginName);
    }

    // ----- Changes applied here

    up.Save(); 

    I hope this helps people who run into this mystery in the future.

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Friday, May 3, 2013 12:09 PM

All replies

  • User1124521738 posted

    are you using LDAP:// or GC:// protocol?  I've always found that GC:// would provide less data than the same resource as LDAP:// due to replication and security so that might be in your way. 

    Try LDAP:// as GC:// has read only aspects http://technet.microsoft.com/en-us/library/cc728188%28v=ws.10%29.aspx you might be attempting a write that is erroneously hitting the read-only parts.

    I don't have them handy, but I've written ADSI code in the past back in the long ago MTS VB6 and then later C# dot net (abt 10 yrs ago in .Net 1.1), never really did VB.Net with AD and I've never seen this issue.  Most recent server config I used the AD code on was 2003 as the last few companies used homebrew or sqlmembershipprovider for the code I supported.  I do have access to a 2008R2 domain with a single domain controler at home so I might be able to scratch up some of my old code to see if I get different behavior between a 2003 AD domain control and a 2008R2 one.

    I'm pretty sure I used DirectoryEntry and didn't have the luxury of having the app pool user set, so I always had to have a configured service account passed in from an encrypted registry key that I would read out and assign to the component in code when making any and all queries to the domain for searches. http://msdn.microsoft.com/en-us/library/system.directoryservices.directoryentry.username%28v=vs.90%29.aspx

    If you aren't doing it, you might want to grab the ADsPath of the specific user node and load that object as LDAP http://msdn.microsoft.com/en-us/library/windows/desktop/aa746384%28v=vs.85%29.aspx

    The global catalog provides the ability to locate objects from any domain without having to know the domain name. A global catalog server is a domain controller that, in addition to its full, writable domain directory partition replica, also stores a partial, read-only replica of all other domain directory partitions in the forest. The additional domain directory partitions are partial because only a limited set of attributes is included for each object. By including only the attributes that are most used for searching, every object in every domain in even the largest forest can be represented in the database of a single global catalog server.

    Wednesday, May 1, 2013 5:56 PM
  • User-1037239328 posted

    I found the problem. The problem WAS in Active Directory. By design, yet.

    My UserControl is for users of the domain be able to update limited parts of their own profiles, and change their passwords. Since this is for the user to use on her own account, I was, of course, running against my own account. I am a Domain Admin.

    In Active Directory, at path cn=AdminSdHolder,cn=System,dc=yourdomain,dc=com, is an object named "AdminSdHolder." Very simply, this object "protects" certain groups and objects from being changed. One of the "protected" groups is Domain Admins.
    See: http://msmvps.com/blogs/ulfbsimonweidner/archive/2005/05/29/49659.aspx
    and: http://technet.microsoft.com/en-us/magazine/2009.09.sdadminholder.aspx 

    When delegate permissions are set on an OU, it is dependent on the objects in the OU to have the “Include inherited permissions from this object’s parent” checkbox checked. By default, this is NOT set for members of the Domain Admins group. Therefore, delegated permissions for profile/password changes are NOT delegated to Domain Admins members. You can change this on individual users, but when Active Directory replicates within an hour, the setting will go back to default.

    Since I was testing against my own account, of course the delegate did not have permissions. (Why it did not work for some time with my own credentials, I don't know. It started working and continues to work now.)

    It is possible, but not necessarily desirable, to change this behavior. So the answer I came up with is to test the UserPrincipal for group membership. If the user is a member of Domain Admins, then I reload the context using the user's own credentials, rather than the delegate's credentials. Then UserPrinciple.Save() works.

    PrincipalContext context = new PrincipalContext(ContextType.Domain, @"dcp1.cvacoop.local"@"domain\delegate"@"delegatepassword");
    UserPrincipal up = UserPrincipal.FindByIdentity(context, MojoUser.LoginName);
    PrincipalSearchResult<Principal> groups = up.GetGroups();
    bool isDomainAdmin = (groups.Where(g => g.Name == "Domain Admins").SingleOrDefault() != null);
    
    if (isDomainAdmin)
    {
    	context = new PrincipalContext(ContextType.Domain, @"dcp1.cvacoop.local"@"domain\" + UserLoginName, txtPassword.Text);
    	up = UserPrincipal.FindByIdentity(context, MojoUser.LoginName);
    }

    // ----- Changes applied here

    up.Save(); 

    I hope this helps people who run into this mystery in the future.

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Friday, May 3, 2013 12:09 PM