|
// ==++==
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// ==--==
/*============================================================
**
** Class: FileSecurity
**
**
** Purpose: Managed ACL wrapper for files & directories.
**
**
===========================================================*/
using System;
using System.Collections;
using System.Security.AccessControl;
using System.Security.Permissions;
using System.Security.Principal;
using Microsoft.Win32;
using Microsoft.Win32.SafeHandles;
using System.Runtime.InteropServices;
using System.IO;
using System.Runtime.Versioning;
using System.Diagnostics.Contracts;
namespace System.Security.AccessControl
{
// Constants from from winnt.h - search for FILE_WRITE_DATA, etc.
[Flags]
public enum FileSystemRights
{
// No None field - An ACE with the value 0 cannot grant nor deny.
ReadData = 0x000001,
ListDirectory = ReadData, // For directories
WriteData = 0x000002,
CreateFiles = WriteData, // For directories
AppendData = 0x000004,
CreateDirectories = AppendData, // For directories
ReadExtendedAttributes = 0x000008,
WriteExtendedAttributes = 0x000010,
ExecuteFile = 0x000020, // For files
Traverse = ExecuteFile, // For directories
// DeleteSubdirectoriesAndFiles only makes sense on directories, but
// the shell explicitly sets it for files in its UI. So we'll include
// it in FullControl.
DeleteSubdirectoriesAndFiles = 0x000040,
ReadAttributes = 0x000080,
WriteAttributes = 0x000100,
Delete = 0x010000,
ReadPermissions = 0x020000,
ChangePermissions = 0x040000,
TakeOwnership = 0x080000,
// From the Core File Services team, CreateFile always requires
// SYNCHRONIZE access. Very tricksy, CreateFile is.
Synchronize = 0x100000, // Can we wait on the handle?
FullControl = 0x1F01FF,
// These map to what Explorer sets, and are what most users want.
// However, an ACL editor will also want to set the Synchronize
// bit when allowing access, and exclude the synchronize bit when
// denying access.
Read = ReadData | ReadExtendedAttributes | ReadAttributes | ReadPermissions,
ReadAndExecute = Read | ExecuteFile,
Write = WriteData | AppendData | WriteExtendedAttributes | WriteAttributes,
Modify = ReadAndExecute | Write | Delete,
}
public sealed class FileSystemAccessRule : AccessRule
{
#region Constructors
//
// Constructor for creating access rules for file objects
//
public FileSystemAccessRule(
IdentityReference identity,
FileSystemRights fileSystemRights,
AccessControlType type )
: this(
identity,
AccessMaskFromRights( fileSystemRights, type ),
false,
InheritanceFlags.None,
PropagationFlags.None,
type )
{
}
public FileSystemAccessRule(
String identity,
FileSystemRights fileSystemRights,
AccessControlType type )
: this(
new NTAccount(identity),
AccessMaskFromRights( fileSystemRights, type ),
false,
InheritanceFlags.None,
PropagationFlags.None,
type )
{
}
//
// Constructor for creating access rules for folder objects
//
public FileSystemAccessRule(
IdentityReference identity,
FileSystemRights fileSystemRights,
InheritanceFlags inheritanceFlags,
PropagationFlags propagationFlags,
AccessControlType type )
: this(
identity,
AccessMaskFromRights( fileSystemRights, type ),
false,
inheritanceFlags,
propagationFlags,
type )
{
}
public FileSystemAccessRule(
String identity,
FileSystemRights fileSystemRights,
InheritanceFlags inheritanceFlags,
PropagationFlags propagationFlags,
AccessControlType type )
: this(
new NTAccount(identity),
AccessMaskFromRights( fileSystemRights, type ),
false,
inheritanceFlags,
propagationFlags,
type )
{
}
//
// Internal constructor to be called by public constructors
// and the access rule factory methods of {File|Folder}Security
//
internal FileSystemAccessRule(
IdentityReference identity,
int accessMask,
bool isInherited,
InheritanceFlags inheritanceFlags,
PropagationFlags propagationFlags,
AccessControlType type )
: base(
identity,
accessMask,
isInherited,
inheritanceFlags,
propagationFlags,
type )
{
}
#endregion
#region Public properties
public FileSystemRights FileSystemRights
{
get { return RightsFromAccessMask( base.AccessMask ); }
}
#endregion
#region Access mask to rights translation
// ACL's on files have a SYNCHRONIZE bit, and CreateFile ALWAYS
// asks for it. So for allows, let's always include this bit,
// and for denies, let's never include this bit unless we're denying
// full control. This is the right thing for users, even if it does
// make the model look asymmetrical from a purist point of view.
internal static int AccessMaskFromRights( FileSystemRights fileSystemRights, AccessControlType controlType )
{
if (fileSystemRights < (FileSystemRights) 0 || fileSystemRights > FileSystemRights.FullControl)
throw new ArgumentOutOfRangeException("fileSystemRights", Environment.GetResourceString("Argument_InvalidEnumValue", fileSystemRights, "FileSystemRights"));
Contract.EndContractBlock();
if (controlType == AccessControlType.Allow) {
fileSystemRights |= FileSystemRights.Synchronize;
}
else if (controlType == AccessControlType.Deny) {
if (fileSystemRights != FileSystemRights.FullControl &&
fileSystemRights != (FileSystemRights.FullControl & ~FileSystemRights.DeleteSubdirectoriesAndFiles))
fileSystemRights &= ~FileSystemRights.Synchronize;
}
return ( int )fileSystemRights;
}
internal static FileSystemRights RightsFromAccessMask( int accessMask )
{
return ( FileSystemRights )accessMask;
}
#endregion
}
public sealed class FileSystemAuditRule : AuditRule
{
#region Constructors
public FileSystemAuditRule(
IdentityReference identity,
FileSystemRights fileSystemRights,
AuditFlags flags )
: this(
identity,
fileSystemRights,
InheritanceFlags.None,
PropagationFlags.None,
flags )
{
}
public FileSystemAuditRule(
IdentityReference identity,
FileSystemRights fileSystemRights,
InheritanceFlags inheritanceFlags,
PropagationFlags propagationFlags,
AuditFlags flags )
: this(
identity,
AccessMaskFromRights( fileSystemRights ),
false,
inheritanceFlags,
propagationFlags,
flags )
{
}
public FileSystemAuditRule(
String identity,
FileSystemRights fileSystemRights,
AuditFlags flags )
: this(
new NTAccount(identity),
fileSystemRights,
InheritanceFlags.None,
PropagationFlags.None,
flags )
{
}
public FileSystemAuditRule(
String identity,
FileSystemRights fileSystemRights,
InheritanceFlags inheritanceFlags,
PropagationFlags propagationFlags,
AuditFlags flags )
: this(
new NTAccount(identity),
AccessMaskFromRights( fileSystemRights ),
false,
inheritanceFlags,
propagationFlags,
flags )
{
}
internal FileSystemAuditRule(
IdentityReference identity,
int accessMask,
bool isInherited,
InheritanceFlags inheritanceFlags,
PropagationFlags propagationFlags,
AuditFlags flags )
: base(
identity,
accessMask,
isInherited,
inheritanceFlags,
propagationFlags,
flags )
{
}
#endregion
#region Private methods
private static int AccessMaskFromRights( FileSystemRights fileSystemRights )
{
if (fileSystemRights < (FileSystemRights) 0 || fileSystemRights > FileSystemRights.FullControl)
throw new ArgumentOutOfRangeException("fileSystemRights", Environment.GetResourceString("Argument_InvalidEnumValue", fileSystemRights, "FileSystemRights"));
Contract.EndContractBlock();
return ( int )fileSystemRights;
}
#endregion
#region Public properties
public FileSystemRights FileSystemRights
{
get { return FileSystemAccessRule.RightsFromAccessMask( base.AccessMask ); }
}
#endregion
}
public abstract class FileSystemSecurity : NativeObjectSecurity
{
#region Member variables
private const ResourceType s_ResourceType = ResourceType.FileObject;
#endregion
[System.Security.SecurityCritical] // auto-generated
internal FileSystemSecurity( bool isContainer )
: base(isContainer, s_ResourceType, _HandleErrorCode, isContainer)
{
}
[System.Security.SecurityCritical] // auto-generated
[ResourceExposure(ResourceScope.Machine)]
[ResourceConsumption(ResourceScope.Machine)]
internal FileSystemSecurity( bool isContainer, String name, AccessControlSections includeSections, bool isDirectory )
: base( isContainer, s_ResourceType, name, includeSections, _HandleErrorCode, isDirectory )
{
}
[System.Security.SecurityCritical] // auto-generated
internal FileSystemSecurity( bool isContainer, SafeFileHandle handle, AccessControlSections includeSections, bool isDirectory )
: base( isContainer, s_ResourceType, handle, includeSections, _HandleErrorCode, isDirectory )
{
}
[System.Security.SecurityCritical] // auto-generated
private static Exception _HandleErrorCode(int errorCode, string name, SafeHandle handle, object context)
{
System.Exception exception = null;
switch (errorCode) {
case Win32Native.ERROR_INVALID_NAME:
exception = new ArgumentException(Environment.GetResourceString("Argument_InvalidName"),"name");
break;
case Win32Native.ERROR_INVALID_HANDLE:
exception = new ArgumentException(Environment.GetResourceString("AccessControl_InvalidHandle"));
break;
case Win32Native.ERROR_FILE_NOT_FOUND:
if ((context != null) && (context is bool) && ((bool)context)) { // DirectorySecurity
if ((name != null) && (name.Length != 0))
exception = new DirectoryNotFoundException(name);
else
exception = new DirectoryNotFoundException();
}
else {
if ((name != null) && (name.Length != 0))
exception = new FileNotFoundException(name);
else
exception = new FileNotFoundException();
}
break;
default:
break;
}
return exception;
}
#region Factories
public sealed override AccessRule AccessRuleFactory(
IdentityReference identityReference,
int accessMask,
bool isInherited,
InheritanceFlags inheritanceFlags,
PropagationFlags propagationFlags,
AccessControlType type )
{
return new FileSystemAccessRule(
identityReference,
accessMask,
isInherited,
inheritanceFlags,
propagationFlags,
type );
}
public sealed override AuditRule AuditRuleFactory(
IdentityReference identityReference,
int accessMask,
bool isInherited,
InheritanceFlags inheritanceFlags,
PropagationFlags propagationFlags,
AuditFlags flags )
{
return new FileSystemAuditRule(
identityReference,
accessMask,
isInherited,
inheritanceFlags,
propagationFlags,
flags );
}
#endregion
#region Internal Methods
internal AccessControlSections GetAccessControlSectionsFromChanges()
{
AccessControlSections persistRules = AccessControlSections.None;
if ( AccessRulesModified )
persistRules = AccessControlSections.Access;
if ( AuditRulesModified )
persistRules |= AccessControlSections.Audit;
if ( OwnerModified )
persistRules |= AccessControlSections.Owner;
if ( GroupModified )
persistRules |= AccessControlSections.Group;
return persistRules;
}
[System.Security.SecurityCritical] // auto-generated
[SecurityPermission(SecurityAction.Assert, UnmanagedCode=true)]
[ResourceExposure(ResourceScope.Machine)]
[ResourceConsumption(ResourceScope.Machine)]
internal void Persist(string fullPath)
{
FileIOPermission.QuickDemand(FileIOPermissionAccess.NoAccess, AccessControlActions.Change, fullPath);
WriteLock();
try
{
AccessControlSections persistRules = GetAccessControlSectionsFromChanges();
base.Persist( fullPath, persistRules );
OwnerModified = GroupModified = AuditRulesModified = AccessRulesModified = false;
}
finally
{
WriteUnlock();
}
}
[System.Security.SecuritySafeCritical] // auto-generated
[SecurityPermission(SecurityAction.Assert, UnmanagedCode=true)]
internal void Persist(SafeFileHandle handle, string fullPath)
{
if (fullPath != null)
FileIOPermission.QuickDemand(FileIOPermissionAccess.NoAccess, AccessControlActions.Change, fullPath);
else
FileIOPermission.QuickDemand(PermissionState.Unrestricted);
WriteLock();
try
{
AccessControlSections persistRules = GetAccessControlSectionsFromChanges();
base.Persist( handle, persistRules );
OwnerModified = GroupModified = AuditRulesModified = AccessRulesModified = false;
}
finally
{
WriteUnlock();
}
}
#endregion
#region Public Methods
public void AddAccessRule( FileSystemAccessRule rule )
{
base.AddAccessRule( rule );
//PersistIfPossible();
}
public void SetAccessRule( FileSystemAccessRule rule )
{
base.SetAccessRule( rule );
}
public void ResetAccessRule( FileSystemAccessRule rule )
{
base.ResetAccessRule( rule );
}
public bool RemoveAccessRule( FileSystemAccessRule rule )
{
if ( rule == null )
throw new ArgumentNullException("rule");
Contract.EndContractBlock();
// If the rule to be removed matches what is there currently then
// remove it unaltered. That is, don't mask off the Synchronize bit.
// This is to avoid dangling synchronize bit
AuthorizationRuleCollection rules = GetAccessRules(true, true, rule.IdentityReference.GetType());
for (int i=0; i<rules.Count; i++) {
FileSystemAccessRule fsrule = rules[i] as FileSystemAccessRule;
if ((fsrule != null) && (fsrule.FileSystemRights == rule.FileSystemRights)
&& (fsrule.IdentityReference == rule.IdentityReference)
&& (fsrule.AccessControlType == rule.AccessControlType)) {
return base.RemoveAccessRule( rule );
}
}
// Mask off the synchronize bit (that is automatically added for Allow)
// before removing the ACL. The logic here should be same as Deny and hence
// fake a call to AccessMaskFromRights as though the ACL is for Deny
FileSystemAccessRule ruleNew = new FileSystemAccessRule(
rule.IdentityReference,
FileSystemAccessRule.AccessMaskFromRights(rule.FileSystemRights, AccessControlType.Deny),
rule.IsInherited,
rule.InheritanceFlags,
rule.PropagationFlags,
rule.AccessControlType);
return base.RemoveAccessRule( ruleNew );
}
public void RemoveAccessRuleAll( FileSystemAccessRule rule )
{
// We don't need to worry about the synchronize bit here
// AccessMask is ignored anyways in a RemoveAll call
base.RemoveAccessRuleAll( rule );
}
public void RemoveAccessRuleSpecific( FileSystemAccessRule rule )
{
if ( rule == null )
throw new ArgumentNullException("rule");
Contract.EndContractBlock();
// If the rule to be removed matches what is there currently then
// remove it unaltered. That is, don't mask off the Synchronize bit
// This is to avoid dangling synchronize bit
AuthorizationRuleCollection rules = GetAccessRules(true, true, rule.IdentityReference.GetType());
for (int i=0; i<rules.Count; i++) {
FileSystemAccessRule fsrule = rules[i] as FileSystemAccessRule;
if ((fsrule != null) && (fsrule.FileSystemRights == rule.FileSystemRights)
&& (fsrule.IdentityReference == rule.IdentityReference)
&& (fsrule.AccessControlType == rule.AccessControlType)) {
base.RemoveAccessRuleSpecific( rule );
return;
}
}
// Mask off the synchronize bit (that is automatically added for Allow)
// before removing the ACL. The logic here should be same as Deny and hence
// fake a call to AccessMaskFromRights as though the ACL is for Deny
FileSystemAccessRule ruleNew = new FileSystemAccessRule(
rule.IdentityReference,
FileSystemAccessRule.AccessMaskFromRights(rule.FileSystemRights, AccessControlType.Deny),
rule.IsInherited,
rule.InheritanceFlags,
rule.PropagationFlags,
rule.AccessControlType);
base.RemoveAccessRuleSpecific( ruleNew );
}
public void AddAuditRule( FileSystemAuditRule rule )
{
base.AddAuditRule( rule );
}
public void SetAuditRule( FileSystemAuditRule rule )
{
base.SetAuditRule( rule );
}
public bool RemoveAuditRule( FileSystemAuditRule rule )
{
return base.RemoveAuditRule( rule );
}
public void RemoveAuditRuleAll( FileSystemAuditRule rule )
{
base.RemoveAuditRuleAll( rule );
}
public void RemoveAuditRuleSpecific( FileSystemAuditRule rule )
{
base.RemoveAuditRuleSpecific( rule );
}
#endregion
#region some overrides
public override Type AccessRightType
{
get { return typeof(System.Security.AccessControl.FileSystemRights); }
}
public override Type AccessRuleType
{
get { return typeof(System.Security.AccessControl.FileSystemAccessRule); }
}
public override Type AuditRuleType
{
get { return typeof(System.Security.AccessControl.FileSystemAuditRule); }
}
#endregion
}
public sealed class FileSecurity : FileSystemSecurity
{
[System.Security.SecuritySafeCritical] // auto-generated
public FileSecurity()
: base(false)
{
}
[System.Security.SecuritySafeCritical] // auto-generated
[SecurityPermission(SecurityAction.Assert, UnmanagedCode=true)]
[ResourceExposure(ResourceScope.Machine)]
[ResourceConsumption(ResourceScope.Machine)]
public FileSecurity(string fileName, AccessControlSections includeSections)
: base(false, fileName, includeSections, false)
{
string fullPath = Path.GetFullPathInternal(fileName);
FileIOPermission.QuickDemand(FileIOPermissionAccess.NoAccess, AccessControlActions.View, fullPath, checkForDuplicates: false, needFullPath: false);
}
// Warning! Be exceedingly careful with this constructor. Do not make
// it public. We don't want to get into a situation where someone can
// pass in the string foo.txt and a handle to bar.exe, and we do a
// demand on the wrong file name.
[System.Security.SecurityCritical] // auto-generated
[SecurityPermission(SecurityAction.Assert, UnmanagedCode=true)]
[ResourceExposure(ResourceScope.Machine)]
[ResourceConsumption(ResourceScope.Machine)]
internal FileSecurity(SafeFileHandle handle, string fullPath, AccessControlSections includeSections)
: base(false, handle, includeSections, false)
{
if (fullPath != null)
FileIOPermission.QuickDemand(FileIOPermissionAccess.NoAccess, AccessControlActions.View, fullPath);
else
FileIOPermission.QuickDemand(PermissionState.Unrestricted);
}
}
public sealed class DirectorySecurity : FileSystemSecurity
{
[System.Security.SecuritySafeCritical] // auto-generated
public DirectorySecurity()
: base(true)
{
}
[System.Security.SecuritySafeCritical] // auto-generated
[SecurityPermission(SecurityAction.Assert, UnmanagedCode=true)]
[ResourceExposure(ResourceScope.Machine)]
[ResourceConsumption(ResourceScope.Machine)]
public DirectorySecurity(string name, AccessControlSections includeSections)
: base(true, name, includeSections, true)
{
string fullPath = Path.GetFullPathInternal(name);
FileIOPermission.QuickDemand(FileIOPermissionAccess.NoAccess, AccessControlActions.View, fullPath, checkForDuplicates: false, needFullPath: false);
}
}
}
|