Skip to content

Code Quality: Introduced IStorageSecurityService #15760

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jul 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions src/Files.App/Data/Contracts/IStorageSecurityService.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Provides service to manage storage security objects on NTFS and ReFS.
/// </summary>
public interface IStorageSecurityService
{
/// <summary>
/// Get the owner of the object specified by the path.
/// </summary>
/// <param name="path">The file full path</param>
/// <returns>The SID string of the owner</returns>
string GetOwner(string path);

/// <summary>
/// Set the owner of the object specified by the path.
/// </summary>
/// <param name="path">The file full path</param>
/// <param name="sid">The owner security identifier (SID)</param>
/// <returns></returns>
bool SetOwner(string path, string sid);

/// <summary>
/// Get information about an access control list (ACL).
/// </summary>
/// <param name="path"></param>
/// <param name="isFolder"></param>
/// <returns>If the function succeeds, an instance of AccessControlList; otherwise, null. To get extended error information, call GetLastError.</returns>
WIN32_ERROR GetAcl(string path, bool isFolder, out AccessControlList acl);

/// <summary>
/// Add an default Access Control Entry (ACE) to the specified object's DACL
/// </summary>
/// <param name="path">The object's path to add an new ACE to its DACL</param>
/// <param name="sid">Principal's SID</param>
/// <returns> 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.</returns>
WIN32_ERROR AddAce(string szPath, bool isFolder, string szSid);

/// <summary>
/// Add an Access Control Entry (ACE) from the specified object's DACL
/// </summary>
/// <param name="szPath">The object's path to remove an ACE from its DACL</param>
/// <param name="dwAceIndex"></param>
/// <returns></returns>
WIN32_ERROR DeleteAce(string szPath, uint dwAceIndex);
}
}
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Represents inheritance flags of an ACE
Expand Down
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Represents ACE type.
Expand Down
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Represents an ACL owner or an ACE principal type
/// </summary>
public enum PrincipalType
public enum AccessControlPrincipalType
{
/// <summary>
/// Unknwon principal type
/// Unknown principal type
/// </summary>
Unknown,

Expand Down
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Represents access mask flags of an ACE.
Expand Down
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Represents an access control entry (ACE).
Expand All @@ -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.
/// </summary>
public Principal Principal { get; set; }
public AccessControlPrincipal Principal { get; set; }

/// <summary>
/// Whether the ACE is inherited or not
Expand Down Expand Up @@ -336,16 +336,16 @@ public AccessControlEntry(bool isFolder, string ownerSid, AccessControlEntryType
{
AccessMaskItems = SecurityAdvancedAccessControlItemFactory.Initialize(this, AreAdvancedPermissionsShown, IsInherited, IsFolder);

//ChangeAccessControlTypeCommand = new RelayCommand<string>(x =>
//{
// AccessControlType = Enum.Parse<AccessControlType>(x);
//});
ChangeAccessControlTypeCommand = new RelayCommand<string>(x =>
{
//AccessControlType = Enum.Parse<AccessControlType>(x);
});

//ChangeInheritanceFlagsCommand = new RelayCommand<string>(x =>
//{
// var parts = x.Split(',');
// InheritanceFlags = Enum.Parse<AccessControlEntryFlags>(parts[0]);
//});
ChangeInheritanceFlagsCommand = new RelayCommand<string>(x =>
{
//var parts = x.Split(',');
//InheritanceFlags = Enum.Parse<AccessControlEntryFlags>(parts[0]);
});

IsFolder = isFolder;
Principal = new(ownerSid);
Expand Down Expand Up @@ -402,5 +402,24 @@ private void ToggleDenyAccess(AccessMaskFlags accessMask, bool value)
DeniedAccessMaskFlags &= ~accessMask;
}
}

/// <summary>
/// Gets access control list (ACL) initialized with default data.
/// </summary>
/// <param name="isFolder"></param>
/// <param name="ownerSid"></param>
/// <returns>If the function succeeds, an instance of AccessControlList; otherwise, null.</returns>
public static AccessControlEntry GetDefault(bool isFolder, string ownerSid)
{
return new(
isFolder,
ownerSid,
AccessControlEntryType.Allow,
AccessMaskFlags.ReadAndExecute,
false,
isFolder
? AccessControlEntryFlags.ContainerInherit | AccessControlEntryFlags.ObjectInherit
: AccessControlEntryFlags.None);
}
}
}
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Represents an access control list (ACL).
Expand All @@ -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.
/// </summary>
public Principal Owner { get; private set; }
public AccessControlPrincipal Owner { get; private set; }

/// <summary>
/// Validates an access control list (ACL).
Expand All @@ -34,7 +34,7 @@ public sealed class AccessControlList : ObservableObject
/// </summary>
public ObservableCollection<AccessControlEntry> 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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Represents a principal of an ACE or an owner of an ACL.
/// </summary>
public sealed class Principal : ObservableObject
public sealed class AccessControlPrincipal : ObservableObject
{
/// <summary>
/// Account type.
/// </summary>
public PrincipalType PrincipalType { get; private set; }
public AccessControlPrincipalType PrincipalType { get; private set; }

/// <summary>
/// Acount security identifier (SID).
/// Account security identifier (SID).
/// </summary>
public string? Sid { get; private set; }

Expand All @@ -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",
};

Expand All @@ -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;

Expand All @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Represents an access mask details, such as its name and changeability.
Expand Down
1 change: 1 addition & 0 deletions src/Files.App/Helpers/Application/AppLifecycleHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ public static IHost ConfigureHost()
.AddSingleton<IStartMenuService, StartMenuService>()
.AddSingleton<IStorageCacheService, StorageCacheService>()
.AddSingleton<IStorageArchiveService, StorageArchiveService>()
.AddSingleton<IStorageSecurityService, StorageSecurityService>()
.AddSingleton<IWindowsCompatibilityService, WindowsCompatibilityService>()
// ViewModels
.AddSingleton<MainPageViewModel>()
Expand Down
18 changes: 0 additions & 18 deletions src/Files.App/NativeMethods.json
Original file line number Diff line number Diff line change
@@ -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
}
19 changes: 19 additions & 0 deletions src/Files.App/NativeMethods.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading