I am using a WCF service to expose certain Active Directory management functions to our help desk staff without giving them the group membership required to manipulate AD directly. Adding users to and removing users from groups is working like a champ with existing users, but every time I create a new user it throws back this fun code:
The server is unwilling to process the request. (Exception from HRESULT: 0x80072035)
The code I use to add the user to the group is
public bool AddGroupToUser(string userDn, string groupDn)
{
try
{
DirectoryEntry groupEntry = LdapTools.GetDirectoryEntry(groupDn);
groupEntry.Properties["member"].Add(userDn);
groupEntry.CommitChanges();
groupEntry.Close();
return true;
}
catch (DirectoryServicesCOMException)
{
return false;
}
}
Everything I've read on the subject is rather vague and I can't seem to find out WHY the exception is being triggered. Any ideas?
UPDATE
This is the code I use to create the user in AD:
try
{
DirectoryEntry container = GetDirectoryEntry(storageOu);
DirectoryEntry newUser = container.Children.Add("CN=" + employee.FullName, "user");
newUser.Properties["sAMAccountName"].Value = employee.Username;
newUser.Properties["displayName"].Value = employee.FullName;
newUser.Properties["givenName"].Value = employee.FirstName;
newUser.Properties["sn"].Value = employee.LastName;
newUser.Properties["department"].Value = departmentName;
newUser.Properties["userPrincipalName"].Value = employee.Username + "#APEX.Local";
newUser.CommitChanges();
newUser.Invoke("SetPassword", new object[] { employee.Password });
newUser.CommitChanges();
AdsUserFlags userSettings = AdsUserFlags.NormalAccount;
newUser.Properties["userAccountControl"].Value = userSettings;
newUser.CommitChanges();
ldapPath = newUser.Path;
newUser.Close();
container.Close();
}
catch (DirectoryServicesCOMException e)
{
// Something went wrong... what???
}
catch (Exception e)
{
// Something else went wrong
}
The new user can login, and can be manipulated using the standard MS tools.
Apparently, unless I'm missing an important step here, the issue is time. When forcing the system to sleep for 8 seconds before attempting to add groups to the new user the process works. If I do it any sooner than the 8 second mark it fails.
I'm marking this answer as correct unless anybody can provide me a better solution.
Try:
public bool AddUserToGroup(string userName, string groupName)
{
bool done = false;
GroupPrincipal group = GroupPrincipal.FindByIdentity(context, groupName);
if (group == null)
{
group = new GroupPrincipal(context, groupName);
}
UserPrincipal user = UserPrincipal.FindByIdentity(context, userName);
if (user != null & group != null)
{
group.Members.Add(user);
group.Save();
done = (user.IsMemberOf(group));
}
return done;
}
Reference:
http://www.c-sharpcorner.com/UploadFile/dhananjaycoder/activedirectoryoperations11132009113015AM/activedirectoryoperations.aspx
As it mentioned in here, can you tell us that you set the password of the newly created users? In the reference, it says that you should SetPassword of the user before doing anything with it.
Time issue could occur from a Active Directory replication issue.
Once, I gave a product to my customer which creates user over Active Directory, which has over 10.000 records in it, with the given informations on a SharePoint form and then program adds the user to a SharePoint group. The problem was, SharePoint throws an error about the newly created user, it says user does not exist in AD.
So, one of out system engineer told us about the replication opearation on the Active Directory that it could be the source of the problem and it was true.
For the solution, program tries to do the job for 10 times with 1 second sleeps. No problem occurred so far. So as a solution, I would suggest you to check out the AD for the existence of replication method.
P.S: When I asked questions to my customers system engineers about the replication operation, they all rejected the existence of AD replication operation and told me that the program has the problem. They believed me when we created a new user in AD from a computer, we couldn't see the user for 5 seconds on an another computer.
Related
I have looked at many search results but I am struggling to find a way to programmatically (using C#) create a custom permission and a custom group in Active Directory.
I have an application that will need to have about 50 individual permissions, such as: can encrypt data, can decrypt data, can export private key, can delete keypair, etc. These permissions will be assigned to a custom group. For instance, the group may be called: standard user, security manager, etc.
Users will be assigned one or more of these groups. I need all of this to be managed through Active Directory. The software that is being written is in C#. The users will be in Active Directory.
The software will check that the user has a particular permission when a function on the application is to be executed. If the user does not have permission then they will be required to enter an override. This override is simply a prompt for the credentials of another user who DOES have the relevant permissions.
I want to emphasise that this needs to be managed through Active Directory because the software is running on a domain and the permissions will be managed by the Domain Administrator.
As such, I believe the ASP.Net Roles functionality is not sufficient? In addition, I am not sure if Azure AD is the same as Windows AD.
I would very much appreciate any guidance as to which .NET assembly/namespace will provide the following capability:
Create permission
Create group
Assign permission to group
Assign user to group
Remove user from group
Remove permission from group
I need to to do this programatically because the software will have an installer and will be responsible for adding the application-specific custom permissions and groups during installation if they do not already exist.
It may be possible that I am approaching this wrong so I am open to suggestions otherwise. As long as I am able to perform the above then great!
Thank you!
As I understood,
Here you can try below code
Try once
1) Create Group
PrincipalContext principalContext =
new PrincipalContext(ContextType.Domain, LDAPDomain, LDAPContainer,
LDAPAdmin, LDAPPassword);
GroupPrincipal group = GroupPrincipal.FindByIdentity(principalContext, "groupName");
if (group == null)
{
GroupPrincipal groupPrincipal = new GroupPrincipal(principalContext);
groupPrincipal.Name = "groupName";
groupPrincipal.SamAccountName = "samAccountName";
groupPrincipal.UserPrincipalName = "userPrincipleName";
groupPrincipal.GroupScope = GroupScope.Global;
groupPrincipal.Description = "groupNameDescription";
groupPrincipal.DisplayName = "groupNameDisplayName";
groupPrincipal.Save();
}
2) Add User To Group
GroupPrincipal group = GroupPrincipal.FindByIdentity(principalContext, "groupName");
UserPrincipal user = UserPrincipal.FindByIdentity(principalContext, "userName");
bool isUserAdded = false;
if (user != null & group != null)
{
if (user.IsMemberOf(group))
{
//Do Code
}
else
{
group.Members.Add(user);
group.Save();
isUserAdded = user.IsMemberOf(group);
}
}
if (isUserAdded)
{
//Do Code
}
3) Remove User From Group
GroupPrincipal group = GroupPrincipal.FindByIdentity(principalContext, "groupName");
UserPrincipal user = UserPrincipal.FindByIdentity(principalContext, "userName");
bool isUserRemoved = false;
if (user != null & group != null)
{
if (user.IsMemberOf(group))
{
group.Members.Remove(user);
group.Save();
isUserRemoved = user.IsMemberOf(group);
}
else
{
//Do Code
}
}
if (!isUserRemoved)
{
//Do Code
}
4) Add or Remove AccessRule(Permission) to Group
From my side, I have no clear idea about what actually your logic or implementation,
But Here I tried to give a solution for adding or remving access rule to group
//DirectoryEntry for OU Level
DirectoryEntry directoryEntry = new DirectoryEntry("LDAP://OU=MYOU,DC=MYDC,DC=COM");
NTAccount account = new NTAccount("MYDC", "groupName");
ActiveDirectoryAccessRule ruleRead = new ActiveDirectoryAccessRule(
account,
ActiveDirectoryRights.ReadProperty,
AccessControlType.Allow,
ActiveDirectorySecurityInheritance.None);
ActiveDirectoryAccessRule ruleWrite = new ActiveDirectoryAccessRule(
account,
ActiveDirectoryRights.WriteProperty,
AccessControlType.Deny,
ActiveDirectorySecurityInheritance.None);
if (Permission == "User shall be able to export private key from an RSA keypair")
{
directoryEntry.ObjectSecurity.AddAccessRule(ruleRead);
directoryEntry.ObjectSecurity.AddAccessRule(ruleWrite);
directoryEntry.Options.SecurityMasks = SecurityMasks.Dacl;
directoryEntry.CommitChanges();
Console.WriteLine("Added Deny Access to Read & Write.");
}
if (Permission == "User is able to decrypt imported data")
{
directoryEntry.ObjectSecurity.RemoveAccessRule(ruleRead);
directoryEntry.ObjectSecurity.RemoveAccessRule(ruleWrite);
directoryEntry.Options.SecurityMasks = SecurityMasks.Dacl;
directoryEntry.CommitChanges();
Console.WriteLine("Removed Deny Access to Read & Write.");
}
directoryEntry.Close();
directoryEntry.Dispose();
Note: Please test all above code in your test environment first.
You're trying to use AD as both your AuthZ store and your AuthZ engine if I follow the question correctly. The former (using it as a data store) makes perfect sense, but, I don't think it's the right tool to evaluate access for your app.
What I would do is layer your groups in two levels:
Level 1 - permission groups (e.g. can encrypt, can decrypt, etc.)
Level 2 - roles - these are members of various permission groups, and in turn users are added to these groups to grant them the roles. They will inherit the permissions the roles have when their logon token is built by Windows.
Assuming your app uses Windows Authentication, the WindowsTokenRoleProvider (https://msdn.microsoft.com/en-us/library/system.web.security.windowstokenroleprovider(v=vs.110).aspx) will surface all of the group memberships up into your app and you can then check if someone is in a permission group and let them do something (or not)...
I have an In-house C# application that will be run on lap-tops in several remote locations. The most common users will have admin rights to the lap-tops, but sometimes it will be run by users without admin rights. For operational reasons, we want just one copy of the application per computer, so it will be installed under Program Files instead of the user accounts.
I am creating an auto-update routine. I would like it to have this behavior:
It checks if there are any updates available.
If there are updates and the user has no admin rights, They will be informed of the updates.
If the user has admin rights, the updates will be loaded.
In all cases, the application will be launched. Non-admin users can decide if the updates warrant shutting down and finding someone with admin rights.
99% of the time, there will be no updates, and I would prefer not to request privileges in the manifest when they usually will not be needed. So I plan on starting a separate process to actually load the updates. But in that case, I'd rather not bother non-admin users with requests for admin privileges that they cannot provide (no - they will not have another account they themselves can log into that has admin privileges).
Is there some reliable way I can have it determine - once it has found updates - whether the current user is in the administrators group, so that it will know whether to bother with launching the update process, or just report updates available and move on?
I've been searching for hours, but have only turned up one method (checking if the user has a split token) that is apparently unreliable and warned against.
Edit:
For completeness, the final solution I found based on Wheels73's post with corrections for the error I was getting is:
bool CurrentUserIsAdmin()
{
UserPrincipal user = UserPrincipal.Current;
using (IEnumerator<Principal> groups = user.GetAuthorizationGroups().GetEnumerator())
{
while (groups.MoveNext())
{
try
{
if (groups.Current.ToString() == "Administrators")
{
return true;
}
}
catch (NoMatchingPrincipalException)
{
continue;
}
}
return false;
}
}
As discussed, this is the routine I use to list all the AD directories for a given login.
public List<string> GetUsersActiveDirectoryGroups(string windowsUserName)
{
var allUserGroups = new List<string>();
var domainConnection = new DirectoryEntry();
var samSearcher = new DirectorySearcher
{
SearchRoot = domainConnection,
Filter = "(samAccountName=" + windowsUserName + ")"
};
samSearcher.PropertiesToLoad.Add("displayName");
var samResult = samSearcher.FindOne();
if (samResult == null) //User not found
return allUserGroups;
//Get groups
var theUser = samResult.GetDirectoryEntry();
theUser.RefreshCache(new[] {"tokenGroups"});
foreach (byte[] resultBytes in theUser.Properties["tokenGroups"])
{
var mySid = new SecurityIdentifier(resultBytes, 0);
var sidSearcher = new DirectorySearcher
{
SearchRoot = domainConnection,
Filter = "(objectSid=" + mySid.Value + ")"
};
sidSearcher.PropertiesToLoad.Add("name");
var sidResult = sidSearcher.FindOne();
if (sidResult != null)
{
allUserGroups.Add(sidResult.Properties["name"][0].ToString());
}
}
return allUserGroups;
}
You could then check the contents of the groups to return a bool based upon the group name you are looking for.
var myUsersGroups = GetUsersActiveDirectoryGroups("YOURLOGINNAME");
var usersIsInAdmin = myUsersGroups.Any(g => g == "Administrator");
To detect if a user simply has loca admin rights, you can use the below
WindowsIdentity user = null;
user = WindowsIdentity.GetCurrent();
var principal = new WindowsPrincipal(user);
var isAdmin = principal.IsInRole(WindowsBuiltInRole.Administrator);
OK.. final shout :)
To find out if another user has local admin rights, you can do the below
var usersPrincipal = UserPrincipal.FindByIdentity(UserPrincipal.Current.Context, IdentityType.SamAccountName, "YOURLOGINNAME");
var otherUserIsAdmin = usersPrincipal.GetAuthorizationGroups().Any(p => p.ToString() == "Administrators");
Hope that helps.
I've created local windows account and added it to group Users using classes from System.DirectoryServices.AccountManagement namespace. Then I tried to log into that newly created account (manually not from C#) and got error 'user profile service failed user profile cannot be loaded'. My C# code is:
try
{
// Create context for server and create new user if one not exists
string userID = GetUserID(userName, userType);
PrincipalContext serverContext = GetDefaultPrincipalContext();
if (DoesExist(userID, serverContext))
{
throw new Exception("User already exists!");
}
UserPrincipal newUser = new UserPrincipal(serverContext, userID, userID, true);
// Get description of user from its privilege and save user if everything went OK
var description = GetDescriptionFromUserType(userType);
if (null != description)
{
newUser.Description = description;
newUser.Save();
// Add user to group so it is displayed in Control Panel
GroupPrincipal group = GroupPrincipal.FindByIdentity(serverContext, USERS_GROUP);
group.Members.Add(newUser);
group.Save();
group.Dispose();
newUser.Dispose();
serverContext.Dispose();
success = true;
}
}
catch (Exception e)
{
Log.LogError(TYPE, e.Message);
DeleteUser(userName, userType);
}
What i'm missing?
I've forgot to write, GetDefaultPrincipalContext() returns new PrincipalContext(Context.Machine, null)...
UPDATE:
Just to mention that I haven't made home directory and all the stuff inside it for new user on path C:\Users\NewUser... And what is strange (in my opinion), one isn't made automatically.
I'm writing an answer in case that anyone sees this post and thinks that the code above is no good. Actually code is working nicely on every other windows machine on which I tried to run it. I don't know what is the issue on my personal computer but the problem is definitely not in code... I will ask other question to resolve my windows problem...
public Object IsAuthenticated()
{
String domainAndUsername = strDomain + "\\" + strUser;
***DirectoryEntry entry = new DirectoryEntry(_path, domainAndUsername, strPass);***
SearchResult result;
try
{
//Bind to the native AdsObject to force authentication.
DirectorySearcher search = new DirectorySearcher(entry) { Filter = ("(SAMAccountName=" + strUser + ")") };
search.PropertiesToLoad.Add("givenName"); // First Name
search.PropertiesToLoad.Add("sn"); // Last Name
search.PropertiesToLoad.Add("cn"); // Last Name
result = search.FindOne();
if (null == result)
{
return null;
}
//Update the new path to the user in the directory.
_path = result.Path;
_filterAttribute = (String)result.Properties["cn"][0];
}
catch (Exception ex)
{
return new Exception("Error authenticating user. " + ex.Message);
}
return user;
}
In the above code segment, is there a way to retrieve the user's Windows login password so that the LDAP authentication works without asking the user his password another time?
Can the value for "strPass",that is being passed when DirectoryEntry object is being created, be retrieved by any way?
The password does not exist anywhere. It would be a big security hole if it did.
Also, BTW, get rid of the try/catch block. It's doing nothing but hiding the reason for the exception.
Use the ActiveDirectoryMemebershipProvider - You can authenticate without writing code, thus eliminating the login scenario you currently have.
You could set up Windows Authentication in your ASP.NET app.
http://msdn.microsoft.com/en-us/library/ff647405.aspx
Once you set this up, only authenticated users have access to the protected parts of your site.
This gives you access some key bits of information.
For example:
System.Web.HttpContext.Current.User.Identity.Name - gives the name (domain\username) of an authenticated user.
System.Web.HttpContext.Current.User.IsInRole("role_name_here") - will tell you if the authenticated user is in a given role.
Authentication can be tricky to do for the first time - please do not ask a user for their windows password - this is a security risk - allow IIS and the .NET framework take care of this for you. The article above may be a bit long and seem a bit complicated but it has a lot of good information in it.
I'm looking for a way to create Active Directory users and set their password, preferably without giving my application/service Domain Admin privileges.
I've tried the following:
DirectoryEntry newUser = _directoryEntry.Children.Add("CN=" + fullname, USER);
newUser.Properties["samAccountName"].Value = username;
newUser.Properties["userPassword"].Value = password;
newUser.Properties["mail"].Value = email;
newUser.CommitChanges();
The user is created, but it seems the password is never set on the user.
Does anyone have an idea on how to set the user's password initially when creating the user? I know about
.Invoke("SetPassword", new object[] { password })
But that requires my code to be run with Domain Admin privileges. As I don't really see the point to grant my code Domain Admin privileges, just to set the initial password (I also allow user password resets, but those run in the context of that particular user), I am hoping someone has a clever solution that doesn't require me to do so.
Thanks in advance!
You can do this whole process much easier now with System.DirectoryServices.AccountManagement (long as you're on .Net 3.5):
See here for a full rundown
Here's a quick example of your specific case:
using(var pc = new PrincipalContext(ContextType.Domain))
{
using(var up = new UserPrincipal(pc))
{
up.SamAccountName = username;
up.EmailAddress = email;
up.SetPassword(password);
up.Enabled = true;
up.ExpirePasswordNow();
up.Save();
}
}
I'd use #Nick's code (wrapped in using statements so the context and principal are disposed properly). As for privileges, you'll need to at least have enough privileges on the OU where you are creating the user to create and manage objects. I'd create a specific user under which your program will run and give it just enough privileges to do the tasks that it needs in that specific OU and no more.
Yes can also use below code to create bulk of users
DirectoryEntry ouEntry = new DirectoryEntry("LDAP://OU=TestOU,DC=TestDomain,DC=local");
for (int i = 0; i < 10; i++)
{
try
{
DirectoryEntry childEntry = ouEntry.Children.Add("CN=TestUser" + i, "user");
childEntry.CommitChanges();
ouEntry.CommitChanges();
childEntry.Invoke("SetPassword", new object[] { "password" });
childEntry.CommitChanges();
}
catch (Exception ex)
{
}
}
The actual attribute for the password is unicodePwd, which requires a specific format that is described in the documentation. But this is how you can do it:
newUser.Properties["unicodePwd"].Value = Encoding.Unicode.GetBytes("\"NewPassword\"");
Doing it this way, you can create the user with a password in one step.