When doing a conversion from a SID to an NTAccount, I use the following code:
DirectorySecurity folder_sec = Directory.GetAccessControl("c:\\test", AccessControlSections.All);
AuthorizationRuleCollection rules = folder_sec.GetAccessRules(true, true, typeof(SecurityIdentifier));
foreach (FileSystemAccessRule rule in rules)
{
SecurityIdentifier sid = new SecurityIdentifier(rule.IdentityReference.Value);
IdentityReference name = sid.Translate(typeof(NTAccount));
string output = name + " | " + sid.tostring();
}
Yes I realize you can get an NTAccount from from the folder_sec.GetAccessRules method but I have discovered that the same subroutine for SecurityIdentifier.Translate is being used and the same bug happens. At the end of the day, ACLs are just SID arrays.
The bug is, when you have two active directory objects (Group, User, etc) with the exact same name but are in two separate domains (trusted, not sub), the translate method returns the wrong NTAccount. It ends up returning the NTAccount with the same name in the domain the machine that is executing the code is on. Getting NTAccounts from other domains that don't happen to share the same name as another object in your domain return fine.
Say you have a directory on a machine that is in domain_frank and this is the ACL:
domain_frank\IT Team
domain_bob\IT Team
domain_frank\Dave
domain_bob\Ted
if you run it though the code above output would look like:
domain_frank\IT Team | S-1-5-21-4000000000-4000000000-2000000000-28480
domain_frank\IT Team | S-1-5-21-1000000000-8000000000-3000000000-81912
domain_frank\Dave | S-1-5-21-4000000000-4000000000-2000000000-86875
domain_bob\Ted | S-1-5-21-1000000000-8000000000-3000000000-96521
Assuming an object named Dave isn't in domain_bob and an object named Ted isn't in domain_frank. But if you look at the SIDs, you can clearly see that the domain section is completely different so you know the correct object is in the ACL at least in SID. Something to do with the look up is breaking.
The result for me is I had to write my own algorithm to look at the SID and do an active directory look up on the domain the SID belongs to. Very very slow, and a total pain.
Is this a known bug and is there a satisfactory solution for this?
Related
When I create a folder inside ProgramData folder say Test then I'm seeing below permission for the folder by default for the Users group,
Question, can I remove all the permission for Users group?
I tried below code, but nothing no permission removed,
// This gets the "Authenticated Users" group, no matter what it's called
SecurityIdentifier sid = new SecurityIdentifier(WellKnownSidType.AuthenticatedUserSid, null);
// Create the rules
FileSystemAccessRule fullControlRule = new FileSystemAccessRule(sid, FileSystemRights.FullControl, AccessControlType.Allow);
if (Directory.Exists("C:\\ProgramData\\Test"))
{
// Get your file's ACL
DirectorySecurity fsecurity = Directory.GetAccessControl("C:\\ProgramData\\Test");
// remove the rule to the ACL
fsecurity.RemoveAccessRuleAll(fullControlRule);
// Set the ACL back to the file
Directory.SetAccessControl("C:\\ProgramData\\Test", fsecurity);
}
First, code which should work for your requirement (just tested it myself):
using System.IO;
using System.Security.AccessControl;
using System.Security.Principal;
...
...
var directoryInfo = new DirectoryInfo(#"C:\ProgramData\Test");
// get the ACL of the directory
var dirSec = directoryInfo.GetAccessControl();
// remove inheritance, copying all entries so that they are direct ACEs
dirSec.SetAccessRuleProtection(true, true);
// do the operation on the directory
directoryInfo.SetAccessControl(dirSec);
// reread the ACL
dirSec = directoryInfo.GetAccessControl();
// get the well known SID for "Users"
var sid = new SecurityIdentifier(WellKnownSidType.BuiltinUsersSid, null);
// loop through every ACE in the ACL
foreach (FileSystemAccessRule rule in dirSec.GetAccessRules(true, false, typeof(SecurityIdentifier)))
{
// if the current entry is one with the identity of "Users", remove it
if (rule.IdentityReference == sid)
dirSec.RemoveAccessRule(rule);
}
// do the operation on the directory
directoryInfo.SetAccessControl(dirSec);
And now to the details:
First, your idea was good to use a well-known SID and not directly the string. But the Users group is NOT Authenticated Users, so we have to use BuiltinUsersSid
Then, we have to remove the inheritance. In your above screen shot, the entries are grayed out, so not directly changeable. We first have to migrate the inherited entries to direct ones, preserving old entries (if not, the ACL would be empty afterwards).
Then, there can be more than one entry for Users (in fact, there are two). We have to loop through all entries and check if it is one which has the Users sid. Then we remove it.
Some final words:
The ACL / permissioning logic is very complex, and escpecially the inheritance can lead to many problems. But it's getting better now.
I remember the first years after the introduction of inheritance (NT4 => Windows 2000), when many tools (even MS own ones) did not handle it correctly, which produced all sort of problems with invalid / damaged ACLs.
I have a C# WPF applicaiton that I am trying to perform a light check with the Active Directory Server and am running into serious performance issues of 20-30 seconds for the funciton to run. Using the identical code, I can place it in a Winforms application and it takes about 1 second or less. Since it is a big AD, I am guessing it is pulling all properties for the end user, but I really only want the first and last name of the person (for other purposes), and to ensure the user is in Active Directory.
Here is the code:
public string DomainUserNameGet(string ActiveDirectoryServer, string WindowsUserID) {
/// queries AD to get logged on user's name
string results = "";
try {
// create your domain context
PrincipalContext oPrincipalContext = new PrincipalContext(
ContextType.Domain
, ActiveDirectoryServer
);
UserPrincipal oUserPrincipal = UserPrincipal.FindByIdentity(
oPrincipalContext
, IdentityType.SamAccountName
, ActiveDirectoryServer + #"\" + WindowsUserID
);
results =
oUserPrincipal.GivenName.ToString()
+ " "
+ oUserPrincipal.Surname.ToString();
} catch { }
return results;
}
The crazy thing is I can do the following via command line and get the response in about 1 second:
NET USER /DOMAIN LANID | find "LANID" /c
Any ideas on how I can improve performance?
RenniePet had it right; Turns out there was a DNS issue; I am unsure why this showed up in WPF vs. win forms though.
How can i Test if and Organizational Unit Exists in Active Directory before creating it with C# ?
There's a .Exists() method on the DirectoryEntry which you can use - assuming you have the correct LDAP path for your OU!
if (DirectoryEntry.Exists("LDAP://" + objectPath))
{
// ......
}
Your main problem will be: the path you're using is wrong - the Users is a generic container and thus needs to be addressed like this:
LDAP://192.168.0.1/CN=Users
Note the CN= prefix. If you had an actual organizational unit, it would have to be prefixed with OU=
For a great resource, check out Howto: (almost) everything in Active Directory
I wrote a simple code to retrieve security information of a folder
the information contain User and groups and the rights they have on the folder
public void GetSecurityRules(DirectoryInfo directoryInfo)
{
DirectorySecurity DSecurity = directoryInfo.GetAccessControl();
AuthorizationRuleCollection Rules = DSecurity.GetAccessRules(true, true, typeof(NTAccount));
foreach (FileSystemAccessRule fileSystemAccessRule in Rules)
{
Console.WriteLine("User/Group name {0}",fileSystemAccessRule.IdentityReference.Value);
Console.WriteLine("Permissions: {0}", fileSystemAccessRule.FileSystemRights.ToString());
}
}
In the line fileSystemAccessRule.IdentityReference.Value I got both Users and Groups but how can i know if the value represent a user or a group?
To the best of my knowledge, the CLR does not expose this information. You will have to p/invoke LsaLookupSids manually and examine the SID_NAME_USE value it will return. CLR calls this function too in order to translate SIDs to account names, but it throws away the SID_NAME_USE values. For code, break out your Reflector, open mscorlib and see how the internal TranslateToNTAccounts function in System.Security.Principal.SecurityIdentifier works.
As an alternative, if you are not going to do such lookups repeatedly, it might be easier to use WMI — query a Win32_Account by SID and examine the SIDType member.
Each of our users is assigned to a primary organizational unit (OU) based on which global office they are in. So the "Chicago" OU contains all the associates in our Chicago office.
Using c# and .net 3.5, my task is to extract all of these users.
Unless the users are in a satellite or home office, their street address, city, state, etc. are empty, but the OU contains these details. When in Windows' Active Directory interface, right clicking on the OU and selecting properties gives a place to put all of this information just as on a user. However, when I try to access these properties like I do a user, I get an object reference error, suggesting these attributes do not exist the same way for an OU that they do for a user.
How do/can I access these location parameters from an OU object?
Here is a sample of the code I am using, showing streetaddress as an example, the statement trying to assign the value of streetaddress from the OU fails, where the assignment from associate succeeds.
foreach (SearchResult subOU in results)
{
ResultPropertyValueCollection subColl = subOU.Properties["distinguishedname"];
string subPath = subColl[0].ToString();
DirectoryEntry subEntry = new DirectoryEntry("LDAP://" + subPath);
DirectorySearcher userSearcher = new DirectorySearcher(subEntry);
userSearcher.SearchScope = SearchScope.OneLevel;
userSearcher.Filter = "(objectClass=user)";
foreach (SearchResult user in userSearcher.FindAll())
{
ResultPropertyValueCollection userColl = user.Properties["distinguishedname"];
string userPath = userColl[0].ToString();
DirectoryEntry userEntry = new DirectoryEntry("LDAP://" + userPath);
PropertyCollection associateProperties = userEntry.Properties;
PropertyCollection ouProperties = subEntry.Properties;
string streetAddress = string.Empty;
if (associateProperties["streetaddress"].Value == null)
{ streetAddress = ouProperties["streetaddress"].Value.ToString(); }
else
{ streetAddress = associateProperties["streetaddress"].Value.ToString(); }
}
}
If you change the Street-field on the General-tab in Active Directory Users & Computers for a user the value is stored in the streetAddress-attribute in the directory. If however you change the same field for an OU that value is stored in the street-attribute of that OU in the directory.
This is because OU objects are not (as defined in the Active Directory default schema) permitted to contain the streetAddress-attribute.
So (not having analyzed your code further) if you change ouProperties["streetaddress"] to ouProperties["street"] you'll might get the result you're looking for.
To avoid the ObjectReference exception you should check the collection contains the required attribute using the Contains(string) method. See here
I believe that AD will only stored valued attributes on an object, if a particular attribute has never been assigned a value it won't be available.
I found the AD schema references at:
http://download.microsoft.com/download/a/e/6/ae6e4142-aa58-45c6-8dcf-a657e5900cd3/%5BMS-ADA1%5D.pdf A-L
http://download.microsoft.com/download/a/e/6/ae6e4142-aa58-45c6-8dcf-a657e5900cd3/%5BMS-ADA2%5D.pdf Just M
http://download.microsoft.com/download/a/e/6/ae6e4142-aa58-45c6-8dcf-a657e5900cd3/%5BMS-ADA3%5D.pdf N-Z
http://download.microsoft.com/download/a/e/6/ae6e4142-aa58-45c6-8dcf-a657e5900cd3/%5BMS-ADTS%5D.pdf AD technical info
That would answer this question for you.
Also, the Win2K8 ADUC MMC snapin if you go to View, select Advanced Features, (enable the tick) then you get the Attribute Editor. (Something ConsoleOne for eDirectory has had for probably close to a decade now!).
One small note, in AD schema, first character is always lower case, and I run at sufficiently high res that the lower case L's are hard to see as L's. (Need a better screen font, but mea culpa).