From 2a61d337319f931913377e29fc9b080c02206c86 Mon Sep 17 00:00:00 2001 From: Jeremy Kuhne Date: Mon, 14 Oct 2019 21:01:25 -0700 Subject: [PATCH] Break the ACL classes into files (dotnet/corefx#41454) * Break the ACL classes into files * Fix NetFX build Commit migrated from https://github.com/dotnet/corefx/commit/226f57b4f2bf91822604aa73338070c601d7a0fe --- .../src/System.IO.FileSystem.AccessControl.csproj | 5 + .../src/System/IO/FileSystemAclExtensions.cs | 2 +- .../Security/AccessControl/DirectorySecurity.cs | 22 + .../System/Security/AccessControl/FileSecurity.cs | 627 +-------------------- .../Security/AccessControl/FileSystemAccessRule.cs | 135 +++++ .../Security/AccessControl/FileSystemAuditRule.cs | 99 ++++ .../Security/AccessControl/FileSystemRights.cs | 46 ++ .../Security/AccessControl/FileSystemSecurity.cs | 291 ++++++++++ 8 files changed, 602 insertions(+), 625 deletions(-) create mode 100644 src/libraries/System.IO.FileSystem.AccessControl/src/System/Security/AccessControl/DirectorySecurity.cs create mode 100644 src/libraries/System.IO.FileSystem.AccessControl/src/System/Security/AccessControl/FileSystemAccessRule.cs create mode 100644 src/libraries/System.IO.FileSystem.AccessControl/src/System/Security/AccessControl/FileSystemAuditRule.cs create mode 100644 src/libraries/System.IO.FileSystem.AccessControl/src/System/Security/AccessControl/FileSystemRights.cs create mode 100644 src/libraries/System.IO.FileSystem.AccessControl/src/System/Security/AccessControl/FileSystemSecurity.cs diff --git a/src/libraries/System.IO.FileSystem.AccessControl/src/System.IO.FileSystem.AccessControl.csproj b/src/libraries/System.IO.FileSystem.AccessControl/src/System.IO.FileSystem.AccessControl.csproj index d8041d3..2724830 100644 --- a/src/libraries/System.IO.FileSystem.AccessControl/src/System.IO.FileSystem.AccessControl.csproj +++ b/src/libraries/System.IO.FileSystem.AccessControl/src/System.IO.FileSystem.AccessControl.csproj @@ -11,6 +11,11 @@ + + + + + Common\Interop\Windows\mincore\NotImplemented.cs diff --git a/src/libraries/System.IO.FileSystem.AccessControl/src/System/IO/FileSystemAclExtensions.cs b/src/libraries/System.IO.FileSystem.AccessControl/src/System/IO/FileSystemAclExtensions.cs index d6c59b3..3f0a3a7 100644 --- a/src/libraries/System.IO.FileSystem.AccessControl/src/System/IO/FileSystemAclExtensions.cs +++ b/src/libraries/System.IO.FileSystem.AccessControl/src/System/IO/FileSystemAclExtensions.cs @@ -56,7 +56,7 @@ namespace System.IO { throw new ObjectDisposedException(null, SR.ObjectDisposed_FileClosed); } - return new FileSecurity(handle, fileStream.Name, AccessControlSections.Access | AccessControlSections.Owner | AccessControlSections.Group); + return new FileSecurity(handle, AccessControlSections.Access | AccessControlSections.Owner | AccessControlSections.Group); } public static void SetAccessControl(this FileStream fileStream, FileSecurity fileSecurity) diff --git a/src/libraries/System.IO.FileSystem.AccessControl/src/System/Security/AccessControl/DirectorySecurity.cs b/src/libraries/System.IO.FileSystem.AccessControl/src/System/Security/AccessControl/DirectorySecurity.cs new file mode 100644 index 0000000..48bb9f8 --- /dev/null +++ b/src/libraries/System.IO.FileSystem.AccessControl/src/System/Security/AccessControl/DirectorySecurity.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.IO; + +namespace System.Security.AccessControl +{ + public sealed class DirectorySecurity : FileSystemSecurity + { + public DirectorySecurity() + : base(true) + { + } + + public DirectorySecurity(string name, AccessControlSections includeSections) + : base(true, name, includeSections, true) + { + Path.GetFullPath(name); + } + } +} diff --git a/src/libraries/System.IO.FileSystem.AccessControl/src/System/Security/AccessControl/FileSecurity.cs b/src/libraries/System.IO.FileSystem.AccessControl/src/System/Security/AccessControl/FileSecurity.cs index 0503cf7..e2389c2 100644 --- a/src/libraries/System.IO.FileSystem.AccessControl/src/System/Security/AccessControl/FileSecurity.cs +++ b/src/libraries/System.IO.FileSystem.AccessControl/src/System/Security/AccessControl/FileSecurity.cs @@ -2,611 +2,13 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -/*============================================================ -** -** -** -** Purpose: Managed ACL wrapper for files & directories. -** -** -===========================================================*/ - -using Microsoft.Win32.SafeHandles; -using Microsoft.Win32; -using System.Collections; using System.IO; -using System.Runtime.InteropServices; -using System.Runtime.Versioning; -using System.Security.AccessControl; -using System.Security.Principal; -using System; +using Microsoft.Win32.SafeHandles; namespace System.Security.AccessControl { - // Constants 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(nameof(fileSystemRights), SR.Format(SR.Argument_InvalidEnumValue, fileSystemRights, nameof(AccessControl.FileSystemRights))); - - 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(nameof(fileSystemRights), SR.Format(SR.Argument_InvalidEnumValue, fileSystemRights, nameof(AccessControl.FileSystemRights))); - - 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 - - internal FileSystemSecurity(bool isContainer) - : base(isContainer, s_ResourceType, _HandleErrorCode, isContainer) - { - } - - internal FileSystemSecurity(bool isContainer, string name, AccessControlSections includeSections, bool isDirectory) - : base(isContainer, s_ResourceType, name, includeSections, _HandleErrorCode, isDirectory) - { - } - - internal FileSystemSecurity(bool isContainer, SafeFileHandle handle, AccessControlSections includeSections, bool isDirectory) - : base(isContainer, s_ResourceType, handle, includeSections, _HandleErrorCode, isDirectory) - { - } - - private static Exception _HandleErrorCode(int errorCode, string name, SafeHandle handle, object context) - { - System.Exception exception = null; - - switch (errorCode) - { - case Interop.Errors.ERROR_INVALID_NAME: - exception = new ArgumentException(SR.Argument_InvalidName, nameof(name)); - break; - - case Interop.Errors.ERROR_INVALID_HANDLE: - exception = new ArgumentException(SR.AccessControl_InvalidHandle); - break; - - case Interop.Errors.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; - } - - internal void Persist(string fullPath) - { - WriteLock(); - - try - { - AccessControlSections persistRules = GetAccessControlSectionsFromChanges(); - base.Persist(fullPath, persistRules); - OwnerModified = GroupModified = AuditRulesModified = AccessRulesModified = false; - } - finally - { - WriteUnlock(); - } - } - - internal void Persist(SafeFileHandle handle, string fullPath) - { - 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(nameof(rule)); - - // 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(nameof(rule)); - - // 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 { - #region Constructors - public FileSecurity() : base(false) { @@ -615,35 +17,12 @@ namespace System.Security.AccessControl public FileSecurity(string fileName, AccessControlSections includeSections) : base(false, fileName, includeSections, false) { - string fullPath = Path.GetFullPath(fileName); + Path.GetFullPath(fileName); } - // 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. - internal FileSecurity(SafeFileHandle handle, string fullPath, AccessControlSections includeSections) + internal FileSecurity(SafeFileHandle handle, AccessControlSections includeSections) : base(false, handle, includeSections, false) { } - #endregion - } - - - public sealed class DirectorySecurity : FileSystemSecurity - { - #region Constructors - - public DirectorySecurity() - : base(true) - { - } - - public DirectorySecurity(string name, AccessControlSections includeSections) - : base(true, name, includeSections, true) - { - string fullPath = Path.GetFullPath(name); - } - #endregion } } diff --git a/src/libraries/System.IO.FileSystem.AccessControl/src/System/Security/AccessControl/FileSystemAccessRule.cs b/src/libraries/System.IO.FileSystem.AccessControl/src/System/Security/AccessControl/FileSystemAccessRule.cs new file mode 100644 index 0000000..15ed6be --- /dev/null +++ b/src/libraries/System.IO.FileSystem.AccessControl/src/System/Security/AccessControl/FileSystemAccessRule.cs @@ -0,0 +1,135 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Security.Principal; + +namespace System.Security.AccessControl +{ + public sealed class FileSystemAccessRule : AccessRule + { + // + // 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) + { + } + + public FileSystemRights FileSystemRights + { + get { return RightsFromAccessMask(AccessMask); } + } + + // 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 < 0 || fileSystemRights > FileSystemRights.FullControl) + throw new ArgumentOutOfRangeException(nameof(fileSystemRights), SR.Format(SR.Argument_InvalidEnumValue, fileSystemRights, nameof(AccessControl.FileSystemRights))); + + 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; + } + } +} diff --git a/src/libraries/System.IO.FileSystem.AccessControl/src/System/Security/AccessControl/FileSystemAuditRule.cs b/src/libraries/System.IO.FileSystem.AccessControl/src/System/Security/AccessControl/FileSystemAuditRule.cs new file mode 100644 index 0000000..c7e34b0 --- /dev/null +++ b/src/libraries/System.IO.FileSystem.AccessControl/src/System/Security/AccessControl/FileSystemAuditRule.cs @@ -0,0 +1,99 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Security.Principal; + +namespace System.Security.AccessControl +{ + public sealed class FileSystemAuditRule : AuditRule + { + 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) + { + } + + private static int AccessMaskFromRights(FileSystemRights fileSystemRights) + { + if (fileSystemRights < 0 || fileSystemRights > FileSystemRights.FullControl) + throw new ArgumentOutOfRangeException(nameof(fileSystemRights), SR.Format(SR.Argument_InvalidEnumValue, fileSystemRights, nameof(AccessControl.FileSystemRights))); + + return (int)fileSystemRights; + } + + public FileSystemRights FileSystemRights + { + get { return FileSystemAccessRule.RightsFromAccessMask(AccessMask); } + } + } +} diff --git a/src/libraries/System.IO.FileSystem.AccessControl/src/System/Security/AccessControl/FileSystemRights.cs b/src/libraries/System.IO.FileSystem.AccessControl/src/System/Security/AccessControl/FileSystemRights.cs new file mode 100644 index 0000000..8f248fd --- /dev/null +++ b/src/libraries/System.IO.FileSystem.AccessControl/src/System/Security/AccessControl/FileSystemRights.cs @@ -0,0 +1,46 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Security.AccessControl +{ + // Constants 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, + } +} diff --git a/src/libraries/System.IO.FileSystem.AccessControl/src/System/Security/AccessControl/FileSystemSecurity.cs b/src/libraries/System.IO.FileSystem.AccessControl/src/System/Security/AccessControl/FileSystemSecurity.cs new file mode 100644 index 0000000..ef253d2 --- /dev/null +++ b/src/libraries/System.IO.FileSystem.AccessControl/src/System/Security/AccessControl/FileSystemSecurity.cs @@ -0,0 +1,291 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.IO; +using System.Runtime.InteropServices; +using System.Security.Principal; +using Microsoft.Win32.SafeHandles; + +namespace System.Security.AccessControl +{ + public abstract class FileSystemSecurity : NativeObjectSecurity + { + private const ResourceType s_ResourceType = ResourceType.FileObject; + + internal FileSystemSecurity(bool isContainer) + : base(isContainer, s_ResourceType, _HandleErrorCode, isContainer) + { + } + + internal FileSystemSecurity(bool isContainer, string name, AccessControlSections includeSections, bool isDirectory) + : base(isContainer, s_ResourceType, name, includeSections, _HandleErrorCode, isDirectory) + { + } + + internal FileSystemSecurity(bool isContainer, SafeFileHandle handle, AccessControlSections includeSections, bool isDirectory) + : base(isContainer, s_ResourceType, handle, includeSections, _HandleErrorCode, isDirectory) + { + } + + private static Exception _HandleErrorCode(int errorCode, string name, SafeHandle handle, object context) + { + Exception exception = null; + + switch (errorCode) + { + case Interop.Errors.ERROR_INVALID_NAME: + exception = new ArgumentException(SR.Argument_InvalidName, nameof(name)); + break; + + case Interop.Errors.ERROR_INVALID_HANDLE: + exception = new ArgumentException(SR.AccessControl_InvalidHandle); + break; + + case Interop.Errors.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; + } + + 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); + } + + 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; + } + + internal void Persist(string fullPath) + { + WriteLock(); + + try + { + AccessControlSections persistRules = GetAccessControlSectionsFromChanges(); + base.Persist(fullPath, persistRules); + OwnerModified = GroupModified = AuditRulesModified = AccessRulesModified = false; + } + finally + { + WriteUnlock(); + } + } + + internal void Persist(SafeFileHandle handle, string fullPath) + { + WriteLock(); + + try + { + AccessControlSections persistRules = GetAccessControlSectionsFromChanges(); + Persist(handle, persistRules); + OwnerModified = GroupModified = AuditRulesModified = AccessRulesModified = false; + } + finally + { + WriteUnlock(); + } + } + + 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(nameof(rule)); + + // 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(nameof(rule)); + + // 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); + } + + public override Type AccessRightType + { + get { return typeof(FileSystemRights); } + } + + public override Type AccessRuleType + { + get { return typeof(FileSystemAccessRule); } + } + + public override Type AuditRuleType + { + get { return typeof(FileSystemAuditRule); } + } + } +} -- 2.7.4