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.
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.
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?
I'm fairly new to file systems and permissions/rights access (aka Access Control List, ACL). While coding with regards to ACL, I am not able to set properties I want to the files. I'm unsure if my understanding of FileSystemRights members are wrong, or I'm totally doing the wrong thing. (And I'm spending quite some time on this part already)
What I'd like to do is change the rights of a file, so that it can only be soelly readable AND cannot be edited, renamed, deleted and copied elsewhere.
Using the MSDN's example, here's what I have so far:
try
{
//Get current identity
WindowsIdentity self = System.Security.Principal.WindowsIdentity.GetCurrent();
// Add the access control entry to the file.
AddFileSecurity(filename, self.Name, FileSystemRights.Modify, AccessControlType.Deny);
AddFileSecurity(filename, self.Name, FileSystemRights.Write, AccessControlType.Deny);
AddFileSecurity(filename, self.Name, FileSystemRights.ReadAndExecute, AccessControlType.Allow);
// Remove the access control entry from the file.
RemoveFileSecurity(filename, self.Name, FileSystemRights.ReadAndExecute, AccessControlType.Deny);
RemoveFileSecurity(filename, self.Name, FileSystemRights.Read, AccessControlType.Deny);
Console.WriteLine("Done.");
}
catch (Exception e)
{
Console.WriteLine(e);
}
My logic is that:
Add Deny Modify rights (Denying .Modify will cause the file to become unreadable)
Add Deny Write rights
Add Allow ReadAndExecute rights
Remove Deny entry on ReadAndExecute (As .Modify denies ReadAndExecute)
Remove Deny entry on Read (As .Modify denies Read)
Am I doing this part correctly? If not, please advise on what should I do to make the file only readable only and not editable, renamable, deletable and copiable. Many thanks in advance!
Please explain what it is doing wrong. The only thing I see that may be an issue is that you're setting the permissions for yourself, but not the other users, or groups. Perhaps you should iterate through all the groups (admins, users, etc) and disable all but read&execute. Although I think SYSTEM always has full control of all files.
I've noticed if you change the security settings for a particular directory, you can make that folder no longer "browsable" in windows. In particular, changing the "Read" permission for Administrators to "Deny" will make that folder inaccessible.
The question I now have, is how do I figure this out in code? I following gets me close, but it still ain't right:
/// <summary>
/// Takes in a directory and determines if the current user has read access to it (doesn't work for network drives)
/// THIS IS VERY HACKY
/// </summary>
/// <param name="dInfo">directoryInfo object to the directory to examine</param>
/// <returns>true if read access is available, false otherwise</returns>
public static bool IsDirectoryReadable(DirectoryInfo dInfo)
{
try
{
System.Security.AccessControl.DirectorySecurity dirSec = dInfo.GetAccessControl();
System.Security.Principal.WindowsIdentity self = System.Security.Principal.WindowsIdentity.GetCurrent();
System.Security.Principal.WindowsPrincipal selfGroup = new System.Security.Principal.WindowsPrincipal(self);
// Go through each access rule found for the directory
foreach (System.Security.AccessControl.FileSystemAccessRule ar in dirSec.GetAccessRules(true, true, typeof(System.Security.Principal.SecurityIdentifier)))
{
if (selfGroup.IsInRole((System.Security.Principal.SecurityIdentifier)ar.IdentityReference))
{
// See if the Read right is included
if ((ar.FileSystemRights & System.Security.AccessControl.FileSystemRights.Read) == System.Security.AccessControl.FileSystemRights.Read)
{
if (ar.AccessControlType == System.Security.AccessControl.AccessControlType.Allow)
{
// If all of the above are true, we do have read access to this directory
return true;
}
else
{
return false;
}
}
}
}
// If we didn't find anything
return false;
}
catch
{
// If anything goes wrong, assume false
return false;
}
}
I'm close with the above, but I'm still missing something huge. If I right click on a folder to set permissions, I see (in my example) 3 groups or user names: "Administrators, myUserName and SYSTEM". If I set the "Read" to Deny for either "Administrators" or "myUserName" I can no longer browse the directory. If I only set "System" to "Deny", I can still browse it.
There seems to be some sort of permission hierarchy implied, where either myUserName or Administrator supersedes the SYSTEM group/user.
The code above looks for the first Allow for "Read" it finds for my user identity and returns true. I could also write code that looks for the first "Deny" for Read and returns false.
I can set a folder to be Read - "Deny" for SYSTEM and Read - "Allow" for the other two accounts and still read the folder. If I change the code to look for Deny's and it encounters the SYSTEM user identity first, my function will return "false", which is... false. It might very well be Read - "Allow" for the other two accounts.
The problem that I still can't figure out is, how can I determine which user identity permission has priority over all of the others?
It gets very tricky because ACL's allow inheritance, but they also have a model of most restrictive access. In other words, if you have a DENY anywhere in your user chain to a resource, no matter how many other groups may give you an ALLOW, you are denied. There is a good article on the subect on MSDN.
The system group is related to the O/S process and not directly related to your user account. It's what the O/S would use to access the filesystem if there was no user context. Since your application is running as your "username" the permissions come from it and the groups it's in. Don't think you need to be checking System in this case unless I'm missing something.
Update: Keep in mind that Directory.Exists() will also check that you have permissions to read the directory.
The issue isn’t a permission hierarchy. The problem is that your code is returning true or false based on the first matching Role. You really need to evaluate all permissions.
I recently had to deal with this issue... I posted my code here.
We recently were forced to move to a new domain server half-way around the world. This may not seem like much of a change, but one of the processes that we run frequently has suddenly gone form a 2-second command to a 5-minute command.
The reason? We are updating the permissions on many directories based on a "template" directory structure.
We've discovered that XCOPY can update the majority of these settings in the same-old-two-second window. The remaining settings, of course, get left off.
What I'm trying to figure out is, how can XCopy do faster what .NET security classes should do natively? Obviously I'm missing something.
What is the best method for copying a directory's ACL information without pinging (or minimal pinging) the domain/Active Directory server?
Here's what I have:
...
DirectorySecurity TemplateSecurity = new DirectorySecurity(TemplateDir, AccessControlSections.All);
...
public static void UpdateSecurity(string destination, DirectorySecurity TemplateSecurity)
{
DirectorySecurity dSecurity = Directory.GetAccessControl(destination);
// Remove previous security settings. (We have all we need in the other TemplateSecurity setting!!)
AuthorizationRuleCollection acl_old = dSecurity.GetAccessRules(true, true, typeof(NTAccount));
foreach (FileSystemAccessRule ace in acl_old)
{
// REMOVE IT IF YOU CAN... if you can't don't worry about it.
try
{
dSecurity.RemoveAccessRule(ace);
}
catch { }
}
// Remove the inheritance for the folders...
// Per the business unit, we must specify permissions per folder.
dSecurity.SetAccessRuleProtection(true, true);
// Copy the permissions from TemplateSecurity to Destination folder.
AuthorizationRuleCollection acl = TemplateSecurity.GetAccessRules(true, true, typeof(NTAccount));
foreach (FileSystemAccessRule ace in acl)
{
// Set the existing security.
dSecurity.AddAccessRule(ace);
try
{
// Remove folder inheritance...
dSecurity.AddAccessRule(new FileSystemAccessRule(
ace.IdentityReference, ace.FileSystemRights,
InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit,
PropagationFlags.None,
ace.AccessControlType));
}
catch { }
}
// Apply the new changes.
Directory.SetAccessControl(destination, dSecurity);
}
Okay... I have a working prototype after A TON OF DIGGING on the internet. Needless to say, there's alot of mis-information about ACL online. I'm not exactly certain if this bit of info will be a godsend, or more mis-information. I'll have to leave that for you, the user, to decide.
What I ended up with is clean, slick, and very, very fast since it doesn't ever TOUCH the domain server. I'm copying the SDDL entries directly. Wait, you say... you can't do that on a directory because you get the dreaded SeSecurityPrivilege error!
Not if you restrict the copy to ONLY the Access Control Lists (ACL).
Here's the code:
public static void UpdateSecurity(string destination, DirectorySecurity templateSecurity)
{
DirectorySecurity dSecurity = Directory.GetAccessControl(destination);
string sddl = templateSecurity.GetSecurityDescriptorSddlForm(AccessControlSections.Access);
try
{
// TOTALLY REPLACE The existing access rights with the new ones.
dSecurity.SetSecurityDescriptorSddlForm(sddl, AccessControlSections.Access);
// Disable inheritance for this directory.
dSecurity.SetAccessRuleProtection(true, true);
// Apply these changes.
Directory.SetAccessControl(destination, dSecurity);
}
catch (Exception ex)
{
// Note the error on the console... we can formally log it later.
Console.WriteLine(pth1 + " : " + ex.Message);
}
// Do some other settings stuff here...
}
Note the AccessControlSections.Access flags on the SDDL methods. That was the magic key to make it all work.