diff --git a/src/Files.App/Data/Contracts/IStorageSecurityService.cs b/src/Files.App/Data/Contracts/IStorageSecurityService.cs
new file mode 100644
index 000000000000..a408dda75983
--- /dev/null
+++ b/src/Files.App/Data/Contracts/IStorageSecurityService.cs
@@ -0,0 +1,52 @@
+// Copyright (c) 2024 Files Community
+// Licensed under the MIT License. See the LICENSE.
+
+using Windows.Win32.Foundation;
+
+namespace Files.App.Data.Contracts
+{
+ ///
+ /// Provides service to manage storage security objects on NTFS and ReFS.
+ ///
+ public interface IStorageSecurityService
+ {
+ ///
+ /// Get the owner of the object specified by the path.
+ ///
+ /// The file full path
+ /// The SID string of the owner
+ string GetOwner(string path);
+
+ ///
+ /// Set the owner of the object specified by the path.
+ ///
+ /// The file full path
+ /// The owner security identifier (SID)
+ ///
+ bool SetOwner(string path, string sid);
+
+ ///
+ /// Get information about an access control list (ACL).
+ ///
+ ///
+ ///
+ /// If the function succeeds, an instance of AccessControlList; otherwise, null. To get extended error information, call GetLastError.
+ WIN32_ERROR GetAcl(string path, bool isFolder, out AccessControlList acl);
+
+ ///
+ /// Add an default Access Control Entry (ACE) to the specified object's DACL
+ ///
+ /// The object's path to add an new ACE to its DACL
+ /// Principal's SID
+ /// If the function succeeds, the return value is ERROR_SUCCESS. If the function fails, the return value is a nonzero error code defined in WinError.h.
+ WIN32_ERROR AddAce(string szPath, bool isFolder, string szSid);
+
+ ///
+ /// Add an Access Control Entry (ACE) from the specified object's DACL
+ ///
+ /// The object's path to remove an ACE from its DACL
+ ///
+ ///
+ WIN32_ERROR DeleteAce(string szPath, uint dwAceIndex);
+ }
+}
diff --git a/src/Files.App/Utils/Storage/Security/AccessControlEntryFlags.cs b/src/Files.App/Data/Enums/AccessControlEntryFlags.cs
similarity index 97%
rename from src/Files.App/Utils/Storage/Security/AccessControlEntryFlags.cs
rename to src/Files.App/Data/Enums/AccessControlEntryFlags.cs
index 51fce3ab9560..1f11ab3ffd4d 100644
--- a/src/Files.App/Utils/Storage/Security/AccessControlEntryFlags.cs
+++ b/src/Files.App/Data/Enums/AccessControlEntryFlags.cs
@@ -1,7 +1,7 @@
// Copyright (c) 2024 Files Community
// Licensed under the MIT License. See the LICENSE.
-namespace Files.App.Utils.Storage
+namespace Files.App.Data.Enums
{
///
/// Represents inheritance flags of an ACE
diff --git a/src/Files.App/Utils/Storage/Security/AccessControlEntryType.cs b/src/Files.App/Data/Enums/AccessControlEntryType.cs
similarity index 90%
rename from src/Files.App/Utils/Storage/Security/AccessControlEntryType.cs
rename to src/Files.App/Data/Enums/AccessControlEntryType.cs
index 3d1307a6423c..ee3ecc6076f2 100644
--- a/src/Files.App/Utils/Storage/Security/AccessControlEntryType.cs
+++ b/src/Files.App/Data/Enums/AccessControlEntryType.cs
@@ -1,7 +1,7 @@
// Copyright (c) 2024 Files Community
// Licensed under the MIT License. See the LICENSE.
-namespace Files.App.Utils.Storage
+namespace Files.App.Data.Enums
{
///
/// Represents ACE type.
diff --git a/src/Files.App/Utils/Storage/Security/PrincipalType.cs b/src/Files.App/Data/Enums/AccessControlPrincipalType.cs
similarity index 78%
rename from src/Files.App/Utils/Storage/Security/PrincipalType.cs
rename to src/Files.App/Data/Enums/AccessControlPrincipalType.cs
index 85d926b0999f..1af4946348c4 100644
--- a/src/Files.App/Utils/Storage/Security/PrincipalType.cs
+++ b/src/Files.App/Data/Enums/AccessControlPrincipalType.cs
@@ -1,15 +1,15 @@
// Copyright (c) 2024 Files Community
// Licensed under the MIT License. See the LICENSE.
-namespace Files.App.Utils.Storage
+namespace Files.App.Data.Enums
{
///
/// Represents an ACL owner or an ACE principal type
///
- public enum PrincipalType
+ public enum AccessControlPrincipalType
{
///
- /// Unknwon principal type
+ /// Unknown principal type
///
Unknown,
diff --git a/src/Files.App/Utils/Storage/Security/AccessMaskFlags.cs b/src/Files.App/Data/Enums/AccessMaskFlags.cs
similarity index 98%
rename from src/Files.App/Utils/Storage/Security/AccessMaskFlags.cs
rename to src/Files.App/Data/Enums/AccessMaskFlags.cs
index d8059aec6b95..9b66811708b9 100644
--- a/src/Files.App/Utils/Storage/Security/AccessMaskFlags.cs
+++ b/src/Files.App/Data/Enums/AccessMaskFlags.cs
@@ -1,7 +1,7 @@
// Copyright (c) 2024 Files Community
// Licensed under the MIT License. See the LICENSE.
-namespace Files.App.Utils.Storage
+namespace Files.App.Data.Enums
{
///
/// Represents access mask flags of an ACE.
diff --git a/src/Files.App/Utils/Storage/Security/AccessControlEntry.cs b/src/Files.App/Data/Items/AccessControlEntry.cs
similarity index 92%
rename from src/Files.App/Utils/Storage/Security/AccessControlEntry.cs
rename to src/Files.App/Data/Items/AccessControlEntry.cs
index d88d2b775353..7462fc29a312 100644
--- a/src/Files.App/Utils/Storage/Security/AccessControlEntry.cs
+++ b/src/Files.App/Data/Items/AccessControlEntry.cs
@@ -1,7 +1,7 @@
// Copyright (c) 2024 Files Community
// Licensed under the MIT License. See the LICENSE.
-namespace Files.App.Utils.Storage
+namespace Files.App.Data.Items
{
///
/// Represents an access control entry (ACE).
@@ -17,7 +17,7 @@ public sealed class AccessControlEntry : ObservableObject
/// The owner in the security descriptor (SD).
/// NULL if the security descriptor has no owner SID.
///
- public Principal Principal { get; set; }
+ public AccessControlPrincipal Principal { get; set; }
///
/// Whether the ACE is inherited or not
@@ -336,16 +336,16 @@ public AccessControlEntry(bool isFolder, string ownerSid, AccessControlEntryType
{
AccessMaskItems = SecurityAdvancedAccessControlItemFactory.Initialize(this, AreAdvancedPermissionsShown, IsInherited, IsFolder);
- //ChangeAccessControlTypeCommand = new RelayCommand(x =>
- //{
- // AccessControlType = Enum.Parse(x);
- //});
+ ChangeAccessControlTypeCommand = new RelayCommand(x =>
+ {
+ //AccessControlType = Enum.Parse(x);
+ });
- //ChangeInheritanceFlagsCommand = new RelayCommand(x =>
- //{
- // var parts = x.Split(',');
- // InheritanceFlags = Enum.Parse(parts[0]);
- //});
+ ChangeInheritanceFlagsCommand = new RelayCommand(x =>
+ {
+ //var parts = x.Split(',');
+ //InheritanceFlags = Enum.Parse(parts[0]);
+ });
IsFolder = isFolder;
Principal = new(ownerSid);
@@ -402,5 +402,24 @@ private void ToggleDenyAccess(AccessMaskFlags accessMask, bool value)
DeniedAccessMaskFlags &= ~accessMask;
}
}
+
+ ///
+ /// Gets access control list (ACL) initialized with default data.
+ ///
+ ///
+ ///
+ /// If the function succeeds, an instance of AccessControlList; otherwise, null.
+ public static AccessControlEntry GetDefault(bool isFolder, string ownerSid)
+ {
+ return new(
+ isFolder,
+ ownerSid,
+ AccessControlEntryType.Allow,
+ AccessMaskFlags.ReadAndExecute,
+ false,
+ isFolder
+ ? AccessControlEntryFlags.ContainerInherit | AccessControlEntryFlags.ObjectInherit
+ : AccessControlEntryFlags.None);
+ }
}
}
diff --git a/src/Files.App/Utils/Storage/Security/AccessControlList.cs b/src/Files.App/Data/Items/AccessControlList.cs
similarity index 85%
rename from src/Files.App/Utils/Storage/Security/AccessControlList.cs
rename to src/Files.App/Data/Items/AccessControlList.cs
index 7506293da517..8e31c0476f52 100644
--- a/src/Files.App/Utils/Storage/Security/AccessControlList.cs
+++ b/src/Files.App/Data/Items/AccessControlList.cs
@@ -1,7 +1,7 @@
// Copyright (c) 2024 Files Community
// Licensed under the MIT License. See the LICENSE.
-namespace Files.App.Utils.Storage
+namespace Files.App.Data.Items
{
///
/// Represents an access control list (ACL).
@@ -22,7 +22,7 @@ public sealed class AccessControlList : ObservableObject
/// The owner in the security descriptor (SD).
/// NULL if the security descriptor has no owner SID.
///
- public Principal Owner { get; private set; }
+ public AccessControlPrincipal Owner { get; private set; }
///
/// Validates an access control list (ACL).
@@ -34,7 +34,7 @@ public sealed class AccessControlList : ObservableObject
///
public ObservableCollection AccessControlEntries { get; private set; }
- public AccessControlList(string path, bool isFolder, Principal owner, bool isValid)
+ public AccessControlList(string path, bool isFolder, AccessControlPrincipal owner, bool isValid)
{
Path = path;
IsFolder = isFolder;
diff --git a/src/Files.App/Utils/Storage/Security/Principal.cs b/src/Files.App/Data/Items/AccessControlPrincipal.cs
similarity index 60%
rename from src/Files.App/Utils/Storage/Security/Principal.cs
rename to src/Files.App/Data/Items/AccessControlPrincipal.cs
index be7367a2033d..9e99f94defb1 100644
--- a/src/Files.App/Utils/Storage/Security/Principal.cs
+++ b/src/Files.App/Data/Items/AccessControlPrincipal.cs
@@ -1,24 +1,24 @@
// Copyright (c) 2024 Files Community
// Licensed under the MIT License. See the LICENSE.
-using System.Text;
-using Vanara.PInvoke;
-using static Vanara.PInvoke.AdvApi32;
+using Windows.Win32;
+using Windows.Win32.Foundation;
+using Windows.Win32.Security;
-namespace Files.App.Utils.Storage
+namespace Files.App.Data.Items
{
///
/// Represents a principal of an ACE or an owner of an ACL.
///
- public sealed class Principal : ObservableObject
+ public sealed class AccessControlPrincipal : ObservableObject
{
///
/// Account type.
///
- public PrincipalType PrincipalType { get; private set; }
+ public AccessControlPrincipalType PrincipalType { get; private set; }
///
- /// Acount security identifier (SID).
+ /// Account security identifier (SID).
///
public string? Sid { get; private set; }
@@ -43,8 +43,8 @@ public sealed class Principal : ObservableObject
public string Glyph
=> PrincipalType switch
{
- PrincipalType.User => "\xE77B",
- PrincipalType.Group => "\xE902",
+ AccessControlPrincipalType.User => "\xE77B",
+ AccessControlPrincipalType.Group => "\xE902",
_ => "\xE716",
};
@@ -66,26 +66,34 @@ public string? FullNameHumanized
public string FullNameHumanizedWithBrackes
=> string.IsNullOrEmpty(Domain) ? string.Empty : $"({Domain}\\{Name})";
- public Principal(string sid)
+ public unsafe AccessControlPrincipal(string sid)
{
if (string.IsNullOrEmpty(sid))
return;
Sid = sid;
- var lpSid = ConvertStringSidToSid(sid);
+ PSID lpSid = default;
+ SID_NAME_USE snu = default;
- StringBuilder lpName = new(), lpDomain = new();
- int cchName = 0, cchDomainName = 0;
+ fixed (char* cSid = sid)
+ PInvoke.ConvertStringSidToSid(new PCWSTR(cSid), &lpSid);
+
+ PWSTR lpName = default;
+ PWSTR lpDomain = default;
+ uint cchName = 0, cchDomainName = 0;
// Get size of account name and domain name
- bool bResult = LookupAccountSid(null, lpSid, lpName, ref cchName, lpDomain, ref cchDomainName, out _);
+ bool bResult = PInvoke.LookupAccountSid(new PCWSTR(), lpSid, lpName, &cchName, lpDomain, &cchDomainName, null);
// Ensure requested capacity
- lpName.EnsureCapacity(cchName);
- lpDomain.EnsureCapacity(cchDomainName);
+ fixed (char* cName = new char[cchName])
+ lpName = new(cName);
+
+ fixed (char* cDomain = new char[cchDomainName])
+ lpDomain = new(cDomain);
// Get account name and domain
- bResult = LookupAccountSid(null, lpSid, lpName, ref cchName, lpDomain, ref cchDomainName, out var snu);
+ bResult = PInvoke.LookupAccountSid(new PCWSTR(), lpSid, lpName, &cchName, lpDomain, &cchDomainName, &snu);
if(!bResult)
return;
@@ -96,24 +104,24 @@ var x when
(x == SID_NAME_USE.SidTypeAlias ||
x == SID_NAME_USE.SidTypeGroup ||
x == SID_NAME_USE.SidTypeWellKnownGroup)
- => PrincipalType.Group,
+ => AccessControlPrincipalType.Group,
// User
SID_NAME_USE.SidTypeUser
- => PrincipalType.User,
+ => AccessControlPrincipalType.User,
// Unknown
- _ => PrincipalType.Unknown
+ _ => AccessControlPrincipalType.Unknown
};
- lpDomain.Clear();
-
// Replace domain name with computer name if the account type is user or alias type
if (snu == SID_NAME_USE.SidTypeUser || snu == SID_NAME_USE.SidTypeAlias)
{
- lpDomain = new(256, 256);
- uint size = (uint)lpDomain.Capacity;
- bResult = Kernel32.GetComputerName(lpDomain, ref size);
+ uint size = 256;
+ fixed (char* cDomain = new char[size])
+ lpDomain = new(cDomain);
+
+ bResult = PInvoke.GetComputerName(lpDomain, ref size);
if (!bResult)
return;
}
diff --git a/src/Files.App/Utils/Storage/Security/AccessMaskItem.cs b/src/Files.App/Data/Items/AccessMaskItem.cs
similarity index 97%
rename from src/Files.App/Utils/Storage/Security/AccessMaskItem.cs
rename to src/Files.App/Data/Items/AccessMaskItem.cs
index 6d6e9a8c7584..79b15c771bcb 100644
--- a/src/Files.App/Utils/Storage/Security/AccessMaskItem.cs
+++ b/src/Files.App/Data/Items/AccessMaskItem.cs
@@ -1,7 +1,7 @@
// Copyright (c) 2024 Files Community
// Licensed under the MIT License. See the LICENSE.
-namespace Files.App.Utils.Storage
+namespace Files.App.Data.Items
{
///
/// Represents an access mask details, such as its name and changeability.
diff --git a/src/Files.App/Helpers/Application/AppLifecycleHelper.cs b/src/Files.App/Helpers/Application/AppLifecycleHelper.cs
index 530f3991ab4a..4cfced5b98df 100644
--- a/src/Files.App/Helpers/Application/AppLifecycleHelper.cs
+++ b/src/Files.App/Helpers/Application/AppLifecycleHelper.cs
@@ -197,6 +197,7 @@ public static IHost ConfigureHost()
.AddSingleton()
.AddSingleton()
.AddSingleton()
+ .AddSingleton()
.AddSingleton()
// ViewModels
.AddSingleton()
diff --git a/src/Files.App/NativeMethods.json b/src/Files.App/NativeMethods.json
index 6355f2e8bbfb..f03b2ad6671d 100644
--- a/src/Files.App/NativeMethods.json
+++ b/src/Files.App/NativeMethods.json
@@ -1,28 +1,10 @@
-// Copyright (c) 2024 Files Community
-// Licensed under the MIT License. See the LICENSE.
{
"$schema": "https://aka.ms/CsWin32.schema.json",
-
- // Emit COM interfaces instead of structs, and allow generation of non-blittable structs for the sake of an easier to use API.
"allowMarshaling": true,
-
- // A value indicating whether to generate APIs judged to be unnecessary or redundant given the target framework.
- // This is useful for multi-targeting projects that need a consistent set of APIs across target frameworks
- // to avoid too many conditional compilation regions.
"multiTargetingFriendlyAPIs": false,
-
- // A value indicating whether friendly overloads should use safe handles.
"useSafeHandles": true,
-
- // Omit ANSI functions and remove `W` suffix from UTF-16 functions.
"wideCharOnly": true,
-
- // A value indicating whether to emit a single source file as opposed to types spread across many files.
"emitSingleFile": false,
-
- // The name of a single class under which all p/invoke methods and constants are generated, regardless of imported module.
"className": "PInvoke",
-
- // A value indicating whether to expose the generated APIs publicly (as opposed to internally).
"public": true
}
diff --git a/src/Files.App/NativeMethods.txt b/src/Files.App/NativeMethods.txt
index 07401d489bd7..92b8c114269d 100644
--- a/src/Files.App/NativeMethods.txt
+++ b/src/Files.App/NativeMethods.txt
@@ -70,3 +70,22 @@ D3D11CreateDevice
IDXGIDevice
DCompositionCreateDevice
IDCompositionDevice
+GetNamedSecurityInfo
+ConvertSidToStringSid
+ConvertStringSidToSid
+SetNamedSecurityInfo
+GetAclInformation
+IsValidAcl
+GetAce
+SetEntriesInAcl
+ACL_SIZE_INFORMATION
+DeleteAce
+EXPLICIT_ACCESS
+ACCESS_ALLOWED_ACE
+LookupAccountSid
+GetComputerName
+AddAccessAllowedAceEx
+LocalAlloc
+InitializeAcl
+AddAce
+LocalFree
diff --git a/src/Files.App/Services/Storage/StorageSecurityService.cs b/src/Files.App/Services/Storage/StorageSecurityService.cs
new file mode 100644
index 000000000000..c2ecf4386836
--- /dev/null
+++ b/src/Files.App/Services/Storage/StorageSecurityService.cs
@@ -0,0 +1,292 @@
+// Copyright (c) 2024 Files Community
+// Licensed under the MIT License. See the LICENSE.
+
+using System.Runtime.InteropServices;
+using Windows.Win32;
+using Windows.Win32.Foundation;
+using Windows.Win32.Security;
+using Windows.Win32.Security.Authorization;
+using Windows.Win32.System.Memory;
+using SystemSecurity = System.Security.AccessControl;
+
+namespace Files.App.Services
+{
+ ///
+ public class StorageSecurityService : IStorageSecurityService
+ {
+ ///
+ public unsafe string GetOwner(string path)
+ {
+ PInvoke.GetNamedSecurityInfo(
+ path,
+ SE_OBJECT_TYPE.SE_FILE_OBJECT,
+ OBJECT_SECURITY_INFORMATION.OWNER_SECURITY_INFORMATION,
+ out var pSidOwner,
+ out _,
+ null,
+ null,
+ out _);
+
+ PInvoke.ConvertSidToStringSid(pSidOwner, out var sid);
+
+ return sid.ToString();
+ }
+
+ ///
+ public unsafe bool SetOwner(string path, string sid)
+ {
+ PSID pSid = default;
+
+ // Get SID
+ fixed (char* cSid = sid)
+ PInvoke.ConvertStringSidToSid(new PCWSTR(cSid), &pSid);
+
+ WIN32_ERROR result = default;
+
+ fixed (char* cPath = path)
+ {
+ // Change owner
+ result = PInvoke.SetNamedSecurityInfo(
+ new PWSTR(cPath),
+ SE_OBJECT_TYPE.SE_FILE_OBJECT,
+ OBJECT_SECURITY_INFORMATION.OWNER_SECURITY_INFORMATION,
+ pSid,
+ new PSID((void*)0));
+ }
+
+ // Run PowerShell as Admin
+ if (result is not WIN32_ERROR.ERROR_SUCCESS)
+ {
+ return Win32Helper.RunPowershellCommand(
+ $"-command \"try {{ $path = '{path}'; $ID = new-object System.Security.Principal.SecurityIdentifier('{sid}'); $acl = get-acl $path; $acl.SetOwner($ID); set-acl -path $path -aclObject $acl }} catch {{ exit 1; }}\"",
+ true);
+ }
+
+ return true;
+ }
+
+ ///
+ public unsafe WIN32_ERROR GetAcl(string path, bool isFolder, out AccessControlList acl)
+ {
+ acl = new();
+ ACL* pDACL = default;
+
+ // Get DACL
+ var result = PInvoke.GetNamedSecurityInfo(
+ path,
+ SE_OBJECT_TYPE.SE_FILE_OBJECT,
+ OBJECT_SECURITY_INFORMATION.DACL_SECURITY_INFORMATION | OBJECT_SECURITY_INFORMATION.PROTECTED_DACL_SECURITY_INFORMATION,
+ out _,
+ out _,
+ &pDACL,
+ null,
+ out _);
+
+ if (result is not WIN32_ERROR.ERROR_SUCCESS || pDACL == null)
+ return result;
+
+ ACL_SIZE_INFORMATION aclSizeInfo = default;
+
+ // Get ACL size info
+ bool bResult = PInvoke.GetAclInformation(
+ *pDACL,
+ &aclSizeInfo,
+ (uint)Marshal.SizeOf(),
+ ACL_INFORMATION_CLASS.AclSizeInformation);
+
+ if (!bResult)
+ return (WIN32_ERROR)Marshal.GetLastPInvokeError();
+
+ // Get owner
+ var szOwnerSid = GetOwner(path);
+ var principal = new AccessControlPrincipal(szOwnerSid);
+
+ var isValidAcl = PInvoke.IsValidAcl(pDACL);
+
+ List aces = [];
+
+ // Get ACEs
+ for (uint i = 0; i < aclSizeInfo.AceCount; i++)
+ {
+ bResult = PInvoke.GetAce(*pDACL, i, out var pAce);
+ if (!bResult)
+ return (WIN32_ERROR)Marshal.GetLastPInvokeError();
+
+ if (pAce is null)
+ continue;
+
+ var ace = Marshal.PtrToStructure((nint)pAce);
+
+ PWSTR pszSid = default;
+
+ var offset = Marshal.SizeOf(typeof(ACE_HEADER)) + sizeof(uint);
+
+ //if (pAce.IsObjectAce())
+ // offset += sizeof(uint) + Marshal.SizeOf(typeof(Guid)) * 2;
+
+ nint pAcePtr = new((long)pAce + offset);
+ PInvoke.ConvertSidToStringSid((PSID)pAcePtr, &pszSid);
+
+ AccessControlEntryType type;
+ AccessControlEntryFlags inheritanceFlags = AccessControlEntryFlags.None;
+ AccessMaskFlags accessMaskFlags = (AccessMaskFlags)ace.Mask;
+
+ var header = ace.Header;
+ type = (SystemSecurity.AceType)header.AceType switch
+ {
+ SystemSecurity.AceType.AccessAllowed => AccessControlEntryType.Allow,
+ _ => AccessControlEntryType.Deny
+ };
+
+ var flags = (SystemSecurity.AceFlags)header.AceFlags;
+
+ bool isInherited = flags.HasFlag(SystemSecurity.AceFlags.InheritanceFlags);
+
+ if (flags.HasFlag(SystemSecurity.AceFlags.ContainerInherit))
+ inheritanceFlags |= AccessControlEntryFlags.ContainerInherit;
+ if (flags.HasFlag(SystemSecurity.AceFlags.ObjectInherit))
+ inheritanceFlags |= AccessControlEntryFlags.ObjectInherit;
+ if (flags.HasFlag(SystemSecurity.AceFlags.NoPropagateInherit))
+ inheritanceFlags |= AccessControlEntryFlags.NoPropagateInherit;
+ if (flags.HasFlag(SystemSecurity.AceFlags.InheritOnly))
+ inheritanceFlags |= AccessControlEntryFlags.InheritOnly;
+
+ // Initialize an ACE
+ aces.Add(new(isFolder, pszSid.ToString(), type, accessMaskFlags, isInherited, inheritanceFlags));
+ }
+
+ // Initialize with proper data
+ acl = new AccessControlList(path, isFolder, principal, isValidAcl);
+
+ // Set access control entries
+ foreach (var ace in aces)
+ acl.AccessControlEntries.Add(ace);
+
+ return (WIN32_ERROR)Marshal.GetLastPInvokeError();
+ }
+
+ ///
+ public unsafe WIN32_ERROR AddAce(string szPath, bool isFolder, string szSid)
+ {
+ ACL* pDACL = default;
+ ACL* pNewDACL = default;
+ PSID pSid = default;
+ ACL_SIZE_INFORMATION aclSizeInfo = default;
+ ACCESS_ALLOWED_ACE* pTempAce = default;
+
+ // Get DACL for the specified object
+ var result = PInvoke.GetNamedSecurityInfo(
+ szPath,
+ SE_OBJECT_TYPE.SE_FILE_OBJECT,
+ OBJECT_SECURITY_INFORMATION.DACL_SECURITY_INFORMATION | OBJECT_SECURITY_INFORMATION.PROTECTED_DACL_SECURITY_INFORMATION,
+ out _,
+ out _,
+ &pDACL,
+ null,
+ out _);
+
+ if (result is not WIN32_ERROR.ERROR_SUCCESS)
+ return result;
+
+ // Get ACL size info
+ bool bResult = PInvoke.GetAclInformation(
+ pDACL,
+ &aclSizeInfo,
+ (uint)Marshal.SizeOf(),
+ ACL_INFORMATION_CLASS.AclSizeInformation);
+
+ if (!bResult)
+ return (WIN32_ERROR)Marshal.GetLastPInvokeError();
+
+ var cbNewDACL = aclSizeInfo.AclBytesInUse + aclSizeInfo.AclBytesFree + Marshal.SizeOf() + 1024;
+
+ pNewDACL = (ACL*)PInvoke.LocalAlloc(LOCAL_ALLOC_FLAGS.LPTR, (nuint)cbNewDACL);
+ if (pNewDACL == default)
+ return (WIN32_ERROR)Marshal.GetLastPInvokeError();
+
+ // Initialize the new DACL
+ PInvoke.InitializeAcl(pNewDACL, (uint)cbNewDACL, ACE_REVISION.ACL_REVISION);
+
+ // Copy ACEs from the old DACL
+ for (uint dwAceIndex = 0u; dwAceIndex < aclSizeInfo.AceCount; dwAceIndex++)
+ {
+ bResult = PInvoke.GetAce(pDACL, dwAceIndex, (void**)&pTempAce);
+ PInvoke.AddAce(pNewDACL, ACE_REVISION.ACL_REVISION, uint.MaxValue, pTempAce, pTempAce->Header.AceSize);
+ }
+
+ // Get the principal's SID of the new ACE
+ fixed (char* cSid = szSid)
+ PInvoke.ConvertStringSidToSid(new PCWSTR(cSid), &pSid);
+
+ bResult = PInvoke.AddAccessAllowedAceEx(
+ pNewDACL,
+ ACE_REVISION.ACL_REVISION,
+ isFolder ? ACE_FLAGS.CONTAINER_INHERIT_ACE | ACE_FLAGS.OBJECT_INHERIT_ACE : ACE_FLAGS.NO_INHERITANCE,
+ 0x20000000 | 0x80000000 /* GENERIC_EXECUTE and GENERIC_READ */,
+ pSid);
+
+ if (!bResult)
+ return (WIN32_ERROR)Marshal.GetLastPInvokeError();
+
+ fixed (char* cPath = szPath)
+ {
+ // Set the new ACL
+ result = PInvoke.SetNamedSecurityInfo(
+ new PWSTR(cPath),
+ SE_OBJECT_TYPE.SE_FILE_OBJECT,
+ OBJECT_SECURITY_INFORMATION.DACL_SECURITY_INFORMATION | OBJECT_SECURITY_INFORMATION.PROTECTED_DACL_SECURITY_INFORMATION,
+ new PSID((void*)0),
+ new PSID((void*)0),
+ pNewDACL);
+ }
+
+ if (result is not WIN32_ERROR.ERROR_SUCCESS)
+ return result;
+
+ return result;
+ }
+
+ ///
+ public unsafe WIN32_ERROR DeleteAce(string szPath, uint dwAceIndex)
+ {
+ ACL* pDACL = default;
+
+ // Get DACL for the specified object
+ var result = PInvoke.GetNamedSecurityInfo(
+ szPath,
+ SE_OBJECT_TYPE.SE_FILE_OBJECT,
+ OBJECT_SECURITY_INFORMATION.DACL_SECURITY_INFORMATION | OBJECT_SECURITY_INFORMATION.PROTECTED_DACL_SECURITY_INFORMATION,
+ out _,
+ out _,
+ &pDACL,
+ null,
+ out _);
+
+ if (result is not WIN32_ERROR.ERROR_SUCCESS)
+ return result;
+
+ // Remove an ACE
+ bool bResult = PInvoke.DeleteAce(pDACL, dwAceIndex);
+
+ if (!bResult)
+ return (WIN32_ERROR)Marshal.GetLastPInvokeError();
+
+ fixed (char* cPath = szPath)
+ {
+ // Set the new ACL
+ result = PInvoke.SetNamedSecurityInfo(
+ new PWSTR(cPath),
+ SE_OBJECT_TYPE.SE_FILE_OBJECT,
+ OBJECT_SECURITY_INFORMATION.DACL_SECURITY_INFORMATION | OBJECT_SECURITY_INFORMATION.PROTECTED_DACL_SECURITY_INFORMATION,
+ new PSID((void*)0),
+ new PSID((void*)0),
+ pDACL);
+
+ if (result is not WIN32_ERROR.ERROR_SUCCESS)
+ return result;
+
+ return result;
+ }
+ }
+ }
+}
diff --git a/src/Files.App/Utils/Storage/Helpers/FileSecurityHelpers.cs b/src/Files.App/Utils/Storage/Helpers/FileSecurityHelpers.cs
deleted file mode 100644
index bbba2f4b4ce7..000000000000
--- a/src/Files.App/Utils/Storage/Helpers/FileSecurityHelpers.cs
+++ /dev/null
@@ -1,258 +0,0 @@
-// Copyright (c) 2024 Files Community
-// Licensed under the MIT License. See the LICENSE.
-
-using Vanara.PInvoke;
-using static Vanara.PInvoke.AdvApi32;
-using SystemSecurity = System.Security.AccessControl;
-
-namespace Files.App.Utils.Storage
-{
- ///
- /// Represents a helper for file security information.
- ///
- public static class FileSecurityHelpers
- {
- ///
- /// Get the owner of the object specified by the path.
- ///
- /// The file full path
- ///
- public static string GetOwner(string path)
- {
- GetNamedSecurityInfo(
- path,
- SE_OBJECT_TYPE.SE_FILE_OBJECT,
- SECURITY_INFORMATION.OWNER_SECURITY_INFORMATION,
- out var pSidOwner,
- out _,
- out _,
- out _,
- out _);
-
- var szSid = ConvertSidToStringSid(pSidOwner);
-
- return szSid;
- }
-
- ///
- /// Set the owner of the object specified by the path.
- ///
- /// The file full path
- /// The owner security identifier (SID)
- ///
- public static bool SetOwner(string path, string sid)
- {
- SECURITY_INFORMATION secInfo = SECURITY_INFORMATION.OWNER_SECURITY_INFORMATION;
-
- // Get PSID object from string sid
- var pSid = ConvertStringSidToSid(sid);
-
- // Change owner
- var result = SetNamedSecurityInfo(path, SE_OBJECT_TYPE.SE_FILE_OBJECT, secInfo, pSid);
-
- pSid.Dispose();
-
- // Run PowerShell as Admin
- if (result.Failed)
- {
- return Win32Helper.RunPowershellCommand(
- $"-command \"try {{ $path = '{path}'; $ID = new-object System.Security.Principal.SecurityIdentifier('{sid}'); $acl = get-acl $path; $acl.SetOwner($ID); set-acl -path $path -aclObject $acl }} catch {{ exit 1; }}\"",
- true);
- }
-
- return true;
- }
-
- ///
- /// Get information about an access control list (ACL).
- ///
- ///
- ///
- /// If the function succeeds, an instance of AccessControlList; otherwise, null. To get extended error information, call GetLastError.
- public static Win32Error GetAccessControlList(string path, bool isFolder, out AccessControlList acl)
- {
- acl = new();
-
- // Get DACL
- var win32Error = GetNamedSecurityInfo(
- path,
- SE_OBJECT_TYPE.SE_FILE_OBJECT,
- SECURITY_INFORMATION.DACL_SECURITY_INFORMATION | SECURITY_INFORMATION.PROTECTED_DACL_SECURITY_INFORMATION,
- out _,
- out _,
- out var pDacl,
- out _,
- out _);
-
- if (win32Error.Failed || pDacl == PACL.NULL)
- return win32Error;
-
- // Get ACL size info
- bool bResult = GetAclInformation(pDacl, out ACL_SIZE_INFORMATION aclSize);
- if (!bResult)
- return Kernel32.GetLastError();
-
- // Get owner
- var szOwnerSid = GetOwner(path);
- var principal = new Principal(szOwnerSid);
-
- var isValidAcl = IsValidAcl(pDacl);
-
- List aces = [];
-
- // Get ACEs
- for (uint i = 0; i < aclSize.AceCount; i++)
- {
- bResult = GetAce(pDacl, i, out var pAce);
- if (!bResult)
- return Kernel32.GetLastError();
-
- var szSid = ConvertSidToStringSid(pAce.GetSid());
-
- AccessControlEntryType type;
- AccessControlEntryFlags inheritanceFlags = AccessControlEntryFlags.None;
- AccessMaskFlags accessMaskFlags = (AccessMaskFlags)pAce.GetMask();
-
- var header = pAce.GetHeader();
- type = header.AceType switch
- {
- SystemSecurity.AceType.AccessAllowed => AccessControlEntryType.Allow,
- _ => AccessControlEntryType.Deny
- };
-
- bool isInherited = header.AceFlags.HasFlag(SystemSecurity.AceFlags.InheritanceFlags);
-
- if (header.AceFlags.HasFlag(SystemSecurity.AceFlags.ContainerInherit))
- inheritanceFlags |= AccessControlEntryFlags.ContainerInherit;
- if (header.AceFlags.HasFlag(SystemSecurity.AceFlags.ObjectInherit))
- inheritanceFlags |= AccessControlEntryFlags.ObjectInherit;
- if (header.AceFlags.HasFlag(SystemSecurity.AceFlags.NoPropagateInherit))
- inheritanceFlags |= AccessControlEntryFlags.NoPropagateInherit;
- if (header.AceFlags.HasFlag(SystemSecurity.AceFlags.InheritOnly))
- inheritanceFlags |= AccessControlEntryFlags.InheritOnly;
-
- // Initialize an ACE
- aces.Add(new(isFolder, szSid, type, accessMaskFlags, isInherited, inheritanceFlags));
- }
-
- // Initialize with proper data
- acl = new AccessControlList(path, isFolder, principal, isValidAcl);
-
- // Set access control entries
- foreach (var ace in aces)
- acl.AccessControlEntries.Add(ace);
-
- return Kernel32.GetLastError();
- }
-
- ///
- /// Get access control list (ACL) initialized with default data.
- ///
- ///
- ///
- /// If the function succeeds, an instance of AccessControlList; otherwise, null.
- public static AccessControlEntry InitializeDefaultAccessControlEntry(bool isFolder, string ownerSid)
- {
- return new(
- isFolder,
- ownerSid,
- AccessControlEntryType.Allow,
- AccessMaskFlags.ReadAndExecute,
- false,
- isFolder
- ? AccessControlEntryFlags.ContainerInherit | AccessControlEntryFlags.ObjectInherit
- : AccessControlEntryFlags.None);
- }
-
- ///
- /// Add an default Access Control Entry (ACE) to the specified object's DACL
- ///
- /// The object's path to add an new ACE to its DACL
- /// Principal's SID
- /// If the function succeeds, the return value is ERROR_SUCCESS. If the function fails, the return value is a nonzero error code defined in WinError.h.
- public static Win32Error AddAccessControlEntry(string szPath, string szSid)
- {
- // Get DACL for the specified object
- var result = GetNamedSecurityInfo(
- szPath,
- SE_OBJECT_TYPE.SE_FILE_OBJECT,
- SECURITY_INFORMATION.DACL_SECURITY_INFORMATION | SECURITY_INFORMATION.PROTECTED_DACL_SECURITY_INFORMATION,
- out _,
- out _,
- out var pDACL,
- out _,
- out _);
-
- if (result.Failed)
- return result;
-
- // Initialize default trustee
- var explicitAccess = new EXPLICIT_ACCESS
- {
- grfAccessMode = ACCESS_MODE.GRANT_ACCESS,
- grfAccessPermissions = ACCESS_MASK.GENERIC_READ | ACCESS_MASK.GENERIC_EXECUTE,
- grfInheritance = INHERIT_FLAGS.NO_INHERITANCE,
- Trustee = new TRUSTEE(new SafePSID(szSid)),
- };
-
- // Add an new ACE and get a new ACL
- result = SetEntriesInAcl(1, [explicitAccess], pDACL, out var pNewDACL);
-
- if (result.Failed)
- return result;
-
- // Set the new ACL
- result = SetNamedSecurityInfo(
- szPath,
- SE_OBJECT_TYPE.SE_FILE_OBJECT,
- SECURITY_INFORMATION.DACL_SECURITY_INFORMATION | SECURITY_INFORMATION.PROTECTED_DACL_SECURITY_INFORMATION,
- ppDacl: pNewDACL);
-
- if (result.Failed)
- return result;
-
- return result;
- }
-
- ///
- /// Add an Access Control Entry (ACE) from the specified object's DACL
- ///
- /// The object's path to remove an ACE from its DACL
- ///
- ///
- public static Win32Error RemoveAccessControlEntry(string szPath, uint dwAceIndex)
- {
- // Get DACL for the specified object
- var result = GetNamedSecurityInfo(
- szPath,
- SE_OBJECT_TYPE.SE_FILE_OBJECT,
- SECURITY_INFORMATION.DACL_SECURITY_INFORMATION | SECURITY_INFORMATION.PROTECTED_DACL_SECURITY_INFORMATION,
- out _,
- out _,
- out var pDACL,
- out _,
- out _);
-
- if (result.Failed)
- return result;
-
- // Remove an ACE
- bool bResult = DeleteAce(pDACL, dwAceIndex);
-
- if (!bResult)
- return Kernel32.GetLastError();
-
- // Set the new ACL
- result = SetNamedSecurityInfo(
- szPath,
- SE_OBJECT_TYPE.SE_FILE_OBJECT,
- SECURITY_INFORMATION.DACL_SECURITY_INFORMATION | SECURITY_INFORMATION.PROTECTED_DACL_SECURITY_INFORMATION,
- ppDacl: pDACL);
-
- if (result.Failed)
- return result;
-
- return result;
- }
- }
-}
diff --git a/src/Files.App/ViewModels/Properties/SecurityAdvancedViewModel.cs b/src/Files.App/ViewModels/Properties/SecurityAdvancedViewModel.cs
index 6233b591266d..82e85c16d1c6 100644
--- a/src/Files.App/ViewModels/Properties/SecurityAdvancedViewModel.cs
+++ b/src/Files.App/ViewModels/Properties/SecurityAdvancedViewModel.cs
@@ -2,13 +2,15 @@
// Licensed under the MIT License. See the LICENSE.
using Microsoft.UI.Xaml;
-using Vanara.PInvoke;
using Windows.Storage;
+using Windows.Win32.Foundation;
namespace Files.App.ViewModels.Properties
{
public sealed class SecurityAdvancedViewModel : ObservableObject
{
+ private readonly IStorageSecurityService StorageSecurityService = Ioc.Default.GetRequiredService();
+
private readonly PropertiesPageNavigationParameter _navigationParameter;
private readonly Window _window;
@@ -166,14 +168,16 @@ private void LoadShieldIconResource()
private void LoadAccessControlEntry()
{
- var error = FileSecurityHelpers.GetAccessControlList(_path, _isFolder, out _AccessControlList);
+ var error = StorageSecurityService.GetAcl(_path, _isFolder, out _AccessControlList);
+ OnPropertyChanged(nameof(AccessControlList));
+
SelectedAccessControlEntry = AccessControlList.AccessControlEntries.FirstOrDefault();
if (!AccessControlList.IsValid)
{
DisplayElements = false;
- if (error == Win32Error.ERROR_ACCESS_DENIED)
+ if (error is WIN32_ERROR.ERROR_ACCESS_DENIED)
{
ErrorMessage =
"SecurityRequireReadPermissions".GetLocalizedResource() +
@@ -185,7 +189,7 @@ private void LoadAccessControlEntry()
ErrorMessage =
"SecurityUnableToDisplayPermissions".GetLocalizedResource() +
"\r\n\r\n" +
- error.FormatMessage();
+ error.ToString();
}
}
else
@@ -204,7 +208,7 @@ private async Task ExecuteChangeOwnerCommandAsync()
await MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync(() =>
{
// Set owner
- FileSecurityHelpers.SetOwner(_path, sid);
+ StorageSecurityService.SetOwner(_path, sid);
// Reload
LoadAccessControlEntry();
@@ -221,10 +225,10 @@ private async Task ExecuteAddAccessControlEntryCommandAsync()
await MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync(() =>
{
// Run Win32API
- var win32Result = FileSecurityHelpers.AddAccessControlEntry(_path, sid);
+ var win32Result = StorageSecurityService.AddAce(_path, _isFolder, sid);
// Add a new ACE to the ACL
- var ace = FileSecurityHelpers.InitializeDefaultAccessControlEntry(_isFolder, sid);
+ var ace = AccessControlEntry.GetDefault(_isFolder, sid);
AccessControlList.AccessControlEntries.Insert(0, ace);
});
}
@@ -240,7 +244,7 @@ await MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync(() =>
var index = AccessControlList.AccessControlEntries.IndexOf(SelectedAccessControlEntry);
// Run Win32API
- var win32Result = FileSecurityHelpers.RemoveAccessControlEntry(_path, (uint)index);
+ var win32Result = StorageSecurityService.DeleteAce(_path, (uint)index);
// Remove the ACE
AccessControlList.AccessControlEntries.Remove(SelectedAccessControlEntry);
diff --git a/src/Files.App/ViewModels/Properties/SecurityViewModel.cs b/src/Files.App/ViewModels/Properties/SecurityViewModel.cs
index 4e162e96b6a7..9aa333129d12 100644
--- a/src/Files.App/ViewModels/Properties/SecurityViewModel.cs
+++ b/src/Files.App/ViewModels/Properties/SecurityViewModel.cs
@@ -3,13 +3,15 @@
using CommunityToolkit.WinUI;
using Microsoft.UI.Xaml;
-using Vanara.PInvoke;
using Windows.Storage;
+using Windows.Win32.Foundation;
namespace Files.App.ViewModels.Properties
{
public sealed class SecurityViewModel : ObservableObject
{
+ private readonly IStorageSecurityService StorageSecurityService = Ioc.Default.GetRequiredService();
+
private readonly PropertiesPageNavigationParameter _navigationParameter;
private readonly Window _window;
@@ -89,13 +91,13 @@ public SecurityViewModel(PropertiesPageNavigationParameter parameter)
break;
};
- var error = FileSecurityHelpers.GetAccessControlList(_path, _isFolder, out _AccessControlList);
+ var error = StorageSecurityService.GetAcl(_path, _isFolder, out _AccessControlList);
_SelectedAccessControlEntry = AccessControlList.AccessControlEntries.FirstOrDefault();
if (!AccessControlList.IsValid)
{
DisplayElements = false;
- ErrorMessage = error == Win32Error.ERROR_ACCESS_DENIED
+ ErrorMessage = error is WIN32_ERROR.ERROR_ACCESS_DENIED
? "SecurityRequireReadPermissions".GetLocalizedResource() + "\r\n" + "SecurityClickAdvancedPermissions".GetLocalizedResource()
: "SecurityUnableToDisplayPermissions".GetLocalizedResource();
}
@@ -119,10 +121,10 @@ private async Task ExecuteAddAccessControlEntryCommandAsync()
await MainWindow.Instance.DispatcherQueue.EnqueueAsync(() =>
{
// Run Win32API
- var win32Result = FileSecurityHelpers.AddAccessControlEntry(_path, sid);
+ var win32Result = StorageSecurityService.AddAce(_path, _isFolder, sid);
// Add a new ACE to the ACL
- var ace = FileSecurityHelpers.InitializeDefaultAccessControlEntry(_isFolder, sid);
+ var ace = AccessControlEntry.GetDefault(_isFolder, sid);
AccessControlList.AccessControlEntries.Insert(0, ace);
});
}
@@ -135,7 +137,7 @@ await MainWindow.Instance.DispatcherQueue.EnqueueAsync(() =>
var index = AccessControlList.AccessControlEntries.IndexOf(SelectedAccessControlEntry);
// Run Win32API
- var win32Result = FileSecurityHelpers.RemoveAccessControlEntry(_path, (uint)index);
+ var win32Result = StorageSecurityService.DeleteAce(_path, (uint)index);
// Remove the ACE
AccessControlList.AccessControlEntries.Remove(SelectedAccessControlEntry);
diff --git a/src/Files.App/Views/Properties/SecurityAdvancedPage.xaml b/src/Files.App/Views/Properties/SecurityAdvancedPage.xaml
index 79cd8fb80148..acf82608686b 100644
--- a/src/Files.App/Views/Properties/SecurityAdvancedPage.xaml
+++ b/src/Files.App/Views/Properties/SecurityAdvancedPage.xaml
@@ -5,11 +5,11 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:behaviors="using:Files.App.Data.Behaviors"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+ xmlns:dataitems="using:Files.App.Data.Items"
xmlns:extensions="using:Files.App.Extensions"
xmlns:helpers="using:Files.App.Helpers"
xmlns:i="using:Microsoft.Xaml.Interactivity"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
- xmlns:security="using:Files.App.Utils.Storage"
xmlns:toolkit="using:CommunityToolkit.WinUI.UI.Controls"
xmlns:vm="using:Files.App.ViewModels.Properties"
xmlns:wctconverters="using:CommunityToolkit.WinUI.UI.Converters"
@@ -347,7 +347,7 @@
-
+
@@ -507,7 +507,7 @@
-
+
-
+