Setting flags for users (Active Directory) - c#

at the moment I am trying to write some code to help our support and having some problems. I am creating a user in the OU, setting all needed attributes and having trouble setting the flags.
string ADPath1 = "LDAP://127.0.0.1/OU=TEST,DC=abc,DC=def,DC=local";
string ADUser = "admin";
string ADPassword = "somepw";
DirectoryEntry de = new DirectoryEntry(ADPath1, ADUser, ADPassword, AuthenticationTypes.Secure);
I am connecting, and setting everything up
DirectoryEntries users = de.Children;
DirectoryEntry newuser = users.Add("CN=" + "Artemij Voskobojnikov", "user");
newuser.Properties["property"].Add("XXX");
Now I'd like to setup the userAccountControl-propery and am trying to do the following:
const int UF_PASSWD_CANT_CHANGE = 0x0040;
const int UF_DONT_EXPIRE_PASSWD = 0x10000;
int user_flags = UF_PASSWD_CANT_CHANGE + UF_DONT_EXPIRE_PASSWD;
newuser.Properties["userAccountControl"].Value = user_flags
I am getting an error, something like "Server cannot execute the operation". Is there any way to do this or do I have to use UserPrincipal?
Kind regards

If you're on .NET 3.5 and up, you should check out the System.DirectoryServices.AccountManagement (S.DS.AM) namespace. Read all about it here:
Managing Directory Security Principals in the .NET Framework 3.5
MSDN docs on System.DirectoryServices.AccountManagement
Basically, you can define a domain context and easily find users and/or groups in AD:
// set up domain context and bind to the OU=Test container
using (PrincipalContext ctx = new PrincipalContext(ContextType.Domain, "YOURDOMAIN", "OU=TEST"))
{
// create a new user
UserPrincipal user = new UserPrincipal(ctx);
// set first name, last name, display name, SAM Account Name and other properties easily
user.DisplayName = "Artemij Voskobojnikov";
user.GivenName = "Artemij";
user.Surname = "Voskobojnikov";
user.SamAccountName = "AVoskobojnikov";
// set some flags as appropriate for your use
user.UserCannotChangePassword = true;
user.PasswordNeverExpires = true;
// save user
user.Save();
}
The new S.DS.AM makes it really easy to play around with users and groups in AD!

Related

Generate password for AD user account that meets complexity policy

I need to create AD users automatically. The problem is, making sure the generated password meets the password policy of AD. I don't know what the policy will be, is there a way to determine that at runtime? This is what I'm using but you can see the complexity is static to length=16 and 4 non-alphanumeric chars and might not always work. I am looking for a way to get the password policy from AD so the generated passwords are correct.
UserPrincipal up = new UserPrincipal(oPrincipalContext);
up.SamAccountName = userId;
up.SetPassword(System.Web.Security.Membership.GeneratePassword(16, 4));
up.Enabled = false;
up.ExpirePasswordNow();
up.Save();
This is what I'm using. I'm getting the password properties using DirectoryEntry/LDAP, then use those to create the password.
DirectoryEntry child = new DirectoryEntry("LDAP://machine/DC=domain,DC=com");
int minPwdLength = (int)child.Properties["minPwdLength"].Value;
int pwdProperties = (int)child.Properties["pwdProperties"].Value;
Use the properties when creating the password.
UserPrincipal up = new UserPrincipal(oPrincipalContext);
up.SamAccountName = userId;
up.SetPassword(System.Web.Security.Membership.GeneratePassword(minPwdLength,
pwdProperties));
up.Enabled = true;
up.ExpirePasswordNow();
up.Save();

Cannot access sn, givenName in LDAP

I am very new to C# and I'm using LDAP to 1) validate a user's login credentials and 2) obtain additional information about the user for my app. Our LDAP server doesn't allow some of the data I'm requesting to be given out anonymously, so I have to wait until I bind with the user's full credentials to pull the data. However, even then I have not been able to obtain simple fields like sn, and givenName. Using JXplorer I can see where these values are hidden during an anonymous connection, but with a full user/SSL/password combination I can see everytning in JXplorer. I just can't seem to do the same via my code.
If I loop through the properties after my first FindOne(), there are 9 properties found (none of which are the ones I'm looking for). If I loop through the properties after my second FindOne(), there are only 4 properties available. Neither results seems to be impacted by PropertiesToAdd.Add("...").
Any suggestions would be appreciated.
public string[] Authenticate(string user, string password)
{
string[] results = new string [2];
//Concatenate serverpath + username + container
//I.e. "LDAP://ldap.disney.com:636/CN=donaldDuck,ou=people,dc=la,dc=disney,dc=com"
DirectoryEntry de = new DirectoryEntry(_ldapserver + "cn=" + user + "," + _topContainer);
//User's password for initial verification
de.Password = password;
//initate anonymous bind
de.AuthenticationType = System.DirectoryServices.AuthenticationTypes.SecureSocketsLayer;
DirectorySearcher searcher = new DirectorySearcher(de);
searcher.SearchScope = System.DirectoryServices.SearchScope.Base;
//Search for first record
SearchResult result = searcher.FindOne();
//Check results
if (result == null) throw new Exception(ERR_NOT_FOUND);
de = result.GetDirectoryEntry();
//Return search results
//results[0] = (string)de.Properties["mail"].Value;
// results[1] = (string)de.Properties["givenName"].Value + " " + (string)de.Properties["sn"].Value;
// Distingushed Name of the found account
string DN = de.Path.Substring(de.Path.ToUpper().IndexOf("CN="));
// Close search connection
de.Close();
// now bind and verify the user's password,
de = new DirectoryEntry(_ldapserver + _topContainer);
de.Username = DN;
de.Password = password;
de.AuthenticationType = System.DirectoryServices.AuthenticationTypes.SecureSocketsLayer;
//Obtain additional information
searcher = new DirectorySearcher(de);
searcher.PropertiesToLoad.Add("sn");
searcher.PropertiesToLoad.Add("givenName");
SearchResult r = searcher.FindOne();
de = r.GetDirectoryEntry();
foreach (string property in de.Properties.PropertyNames)
{
Console.WriteLine("\t{0} : {1} ", property, de.Properties[property][0]);
}
//End obtain additional information
//Validate password
Object obj = de.NativeObject;
de.Close();
//if we made it here, we successfully authenticated
return results;
}
If you're on .NET 3.5 and up, you should check out the System.DirectoryServices.AccountManagement (S.DS.AM) namespace. Read all about it here:
Managing Directory Security Principals in the .NET Framework 3.5
MSDN docs on System.DirectoryServices.AccountManagement
Basically, you can define a domain context and easily find users and/or groups in AD:
// set up domain context
using (PrincipalContext ctx = new PrincipalContext(ContextType.Domain))
{
// validate given user credentials
bool isValid = ctx.ValidateCredentials(user, password);
// find a user
UserPrincipal user = UserPrincipal.FindByIdentity(ctx, "SomeUserName");
if(user != null)
{
string surname = user.Surname;
string givenName = user.GivenName;
}
}
The new S.DS.AM makes it really easy to play around with users and groups in AD!

Updating Name Field on UserPrincipal

When I try to update the Name field (corresponds to the CN) on UserPrincipal (Principal, really), I get an error "The server is unwilling to process the request" on the call to UserPrincipal.Save().
I've checked to make sure there isn't another object in the same OU with the same Name (CN).
The PrincipalContext I'm operating at is the domain root (not exactly at the OU level where the user account exists).
What reason might there be for this error? Is it something that might be security policy related (even though I'm able to update all the other fields)?
using (var context = new PrincipalContext(ContextType.Domain, ConfigurationManager.AppSettings["domain"], ConfigurationManager.AppSettings["rootDN"], ContextOptions.Negotiate, ConfigurationManager.AppSettings["username"], ConfigurationManager.AppSettings["password"])) {
var user = UserPrincipal.FindByIdentity(context, IdentityType.Sid, "..."); // SID abbreviated
user.Name = "Name, Test";
user.Save();
}
The user I am using to create the PrincipalContext has the security rights to modify AD objects. If I update any other of the other fields (e.g. Surname, GivenName), everything works fine.
EDIT:
I've been able to accomplish what I need to do (using ADSI), but I have to run the following code under impersonation. The impersonation code is ugly, and the code below breaks away from the other way I'm updating AD data (using DirectoryServices.AccountManagement), so I'd like to get a better solution.
using (var companyOU = new DirectoryEntry("LDAP://" + company.UserAccountOU)) {
companyOU.Invoke("MoveHere", "LDAP://" + user.DistinguishedName, "cn=Name\, Test");
}
This is a cleaner way
using (var context = new PrincipalContext(ContextType.Domain))
{
var group = GroupPrincipal.FindByIdentity(context, groupName);
group.SamAccountName = newGroupName;
group.DisplayName = newGroupName;
group.Save();
var dirEntry = (DirectoryEntry)group.GetUnderlyingObject();
dirEntry.Rename("CN=" + newGroupName);
dirEntry.CommitChanges();
}
The only way I've found to do this is in the EDIT section in my question. Basically, you cannot use the UserPrincipal class. There is something special about the CN attribute, and you need to drop down a level and use DirectoryEntry, an LDAP string, and invoke the "MoveHere" ADSI command to rename the user account.

Unlocking user Account

I am trying to set properties to unlock User accounts in AD and I am using the following code; the problem is that de does not contain userAccountControl and the code fails.
I can get the value of userAccountControl by using DirectorySearcher but that does not help me in setting the property using de. Could anyone please help me? Thanks in advance
String m_Path = "LDAP://" + distinguishedName;
using (DirectoryEntry de = new DirectoryEntry(m_Path))
{
if (de.Contains("userAccountControl")
{
int m_Val = (int)de.Properties["userAccountControl"][0].Value;
de.Properties["userAccountControl"].Value = m_Val | 0x0001
de.CommitChanges;
}
}
I would think you need to check whether de.Properties contains a value of userAccountControl!
string ldapPath = "LDAP://" + distinguishedName;
using(DirectoryEntry de = new DirectoryEntry(ldapPath))
{
// check to see if we have "userAccountControl" in the **properties** of de
if(de.Properties.Contains("userAccountControl")
{
int m_Val = (int)de.Properties["userAccountControl"][0].Value ;
de.Properties["userAccountControl"].Value = m_Val | 0x0001;
de.CommitChanges();
}
}
Also, if you're on .NET 3.5 and up, you should check out the System.DirectoryServices.AccountManagement (S.DS.AM) namespace. Read all about it here:
Managing Directory Security Principals in the .NET Framework 3.5
MSDN docs on System.DirectoryServices.AccountManagement
Basically, you can define a domain context and easily find and manipulate users and/or groups in AD:
// set up domain context
PrincipalContext ctx = new PrincipalContext(ContextType.Domain);
// find a user
UserPrincipal user = UserPrincipal.FindByIdentity(ctx, "SomeUserName");
if(user != null)
{
// unlock user
user.UnlockAccount();
}
The new S.DS.AM makes it really easy to play around with users and groups in AD!

Active Directory Search getting Object reference not set

The below code works on my local machine by returning the user's full name from Active Directory:
string principal = System.Web.HttpContext.Current.Request.LogonUserIdentity.Name.Remove(0, 12);
string filter = string.Format("(&(ObjectClass={0})(sAMAccountName={1}))", "person", principal);
string[] properties = new string[] { "fullname" };
DirectoryEntry adRoot = new DirectoryEntry("LDAP://myserver.com");
adRoot.AuthenticationType = AuthenticationTypes.Secure;
DirectorySearcher searcher = new DirectorySearcher(adRoot);
searcher.SearchScope = SearchScope.Subtree;
searcher.ReferralChasing = ReferralChasingOption.All;
searcher.PropertiesToLoad.AddRange(properties);
searcher.Filter = filter;
SearchResult result = searcher.FindOne();
DirectoryEntry directoryEntry = result.GetDirectoryEntry();
string displayName = directoryEntry.Properties["displayName"][0].ToString();
if (string.IsNullOrEmpty(displayName) == false)
{
return displayName;
}
When I publish it to the development server I get the following error:
System.NullReferenceException: Object reference not set to an instance
of an object.
The error is thrown on the following line:
DirectoryEntry directoryEntry = result.GetDirectoryEntry();
I have tried
DirectoryEntry adRoot = new DirectoryEntry("LDAP://" + domain, AdAdminUsername, AdAdminPassword, AuthenticationTypes.Secure);
but still no joy.
Any ideas?
Thanks!
Just from outside : your string called principal is built by the following :
string principal = System.Web.HttpContext.Current.Request.LogonUserIdentity.Name.Remove(0, 12);
Have you log 'System.Web.HttpContext.Current.Request.LogonUserIdentity.Name' on your dev server ? This static construction may be the begining of you troubles because if principal is'nt what you think it is, the result of FindOne may be NULL.
In your current code, you must check for NULL after the call to FindOne() - it could return a NULL value if no matching directory entry was found.
See the MSDN docs:
If more than one entry is found during the search, only the first
entry is returned. If no entries are found to match the search
criteria, a null reference (Nothing in Visual Basic) is returned.
Plus, you should also always check for the presence of a property before accessing it - it could be absent.....
Therefore, your code should be something like this:
SearchResult result = searcher.FindOne();
if(result != null)
{
DirectoryEntry directoryEntry = result.GetDirectoryEntry();
if(directoryEntry.Properties["displayName"] != null &&
directoryEntry.Properties["displayName"].Length > 0)
{
string displayName = directoryEntry.Properties["displayName"][0].ToString();
if (!string.IsNullOrEmpty(displayName))
{
return displayName;
}
}
}
But: if you're on .NET 3.5 or newer, you should check out the System.DirectoryServices.AccountManagement namespace which makes a lot of things a lot easier when dealing with Active Directory.
You can use a PrincipalSearcher and a "query-by-example" principal to do your searching:
// create your domain context
PrincipalContext ctx = new PrincipalContext(ContextType.Domain);
// define a "query-by-example" principal - here, we search for a UserPrincipal
// and with the first name (GivenName) of "Bruce"
UserPrincipal qbeUser = new UserPrincipal(ctx);
qbeUser.SamAccountName = "whatever you're looking for.....";
// create your principal searcher passing in the QBE principal
PrincipalSearcher srch = new PrincipalSearcher(qbeUser);
// find all matches
foreach(var found in srch.FindAll())
{
// do whatever here - "found" is of type "Principal" - it could be user, group, computer.....
}
If you haven't already - absolutely read the MSDN article Managing Directory Security Principals in the .NET Framework 3.5 which shows nicely how to make the best use of the new features in System.DirectoryServices.AccountManagement
Of course, depending on your need, you might want to specify other properties on that "query-by-example" user principal you create:
Surname (or last name)
DisplayName (typically: first name + space + last name)
SAM Account Name - your Windows/AD account name
User Principal Name - your "username#yourcompany.com" style name
You can specify any of the properties on the UserPrincipal and use those as "query-by-example" for your PrincipalSearcher.

Categories