Last post May 03, 2013 12:09 PM by dehaggard
Apr 30, 2013 04:26 PM|dehaggard|LINK
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."
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.
// 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
private string NullString(string 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?
May 01, 2013 05:56 PM|ninianne98|LINK
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.
If you aren't doing it, you might want to grab the ADsPath of the specific user node and load that object as LDAP
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.
May 03, 2013 12:09 PM|dehaggard|LINK
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.
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);
context = new PrincipalContext(ContextType.Domain, @"dcp1.cvacoop.local", @"domain\" + UserLoginName, txtPassword.Text);
up = UserPrincipal.FindByIdentity(context, MojoUser.LoginName);
}// ----- Changes applied hereup.Save();
I hope this helps people who run into this mystery in the future.