diff --git a/src/Files.App/Filesystem/Security/AccessControlEntry.cs b/src/Files.App/Filesystem/Security/AccessControlEntry.cs index 1c9b90c45149..6917635b3ae5 100644 --- a/src/Files.App/Filesystem/Security/AccessControlEntry.cs +++ b/src/Files.App/Filesystem/Security/AccessControlEntry.cs @@ -13,56 +13,144 @@ namespace Files.App.Filesystem.Security { /// - /// Represents an ACE. + /// Represents an access control entry (ACE). /// public class AccessControlEntry : ObservableObject { - public bool IsFolder { get; set; } - + /// + /// Whether the path indicates folder or not + /// + public bool IsFolder { get; private set; } + + /// + /// The owner in the security descriptor (SD). + /// NULL if the security descriptor has no owner SID. + /// public Principal Principal { get; set; } - private AccessControlType _AccessControlType; - public AccessControlType AccessControlType + /// + /// Whether the ACE is inherited or not + /// + public bool IsInherited { get; private set; } + + /// + /// Whether the ACE is editable or not + /// + public bool IsEditable + => IsSelected && !IsInherited && false; + + /// + /// AccessControlTypeHumanized + /// + public string AccessControlTypeHumanized + => AccessControlType switch + { + AccessControlEntryType.Allow => "Allow", + _ => "Deny" // AccessControlType.Deny + }; + + /// + /// AccessControlTypeGlyph + /// + public string AccessControlTypeGlyph + => AccessControlType switch + { + AccessControlEntryType.Allow => "\xE73E", + _ => "\xF140" // AccessControlType.Deny + }; + + /// + /// AccessMaskFlagsHumanized + /// + public string AccessMaskFlagsHumanized { - get => _AccessControlType; - set + get { - // Update access control type glyph - if (SetProperty(ref _AccessControlType, value)) - OnPropertyChanged(nameof(AccessControlTypeGlyph)); + var accessMaskStrings = new List(); + + if (AccessMaskFlags == AccessMaskFlags.NULL) + accessMaskStrings.Add("None".GetLocalizedResource()); + + if (FullControlAccess) + accessMaskStrings.Add("SecurityFullControlLabel/Text".GetLocalizedResource()); + else if (ModifyAccess) + accessMaskStrings.Add("SecurityModifyLabel/Text".GetLocalizedResource()); + else if (ReadAndExecuteAccess) + accessMaskStrings.Add("SecurityReadAndExecuteLabel/Text".GetLocalizedResource()); + else if (ReadAccess) + accessMaskStrings.Add("SecurityReadLabel/Text".GetLocalizedResource()); + + if (!FullControlAccess && !ModifyAccess && WriteAccess) + accessMaskStrings.Add("Write".GetLocalizedResource()); + + if (SpecialAccess) + accessMaskStrings.Add("SecuritySpecialLabel/Text".GetLocalizedResource()); + + return string.Join(", ", accessMaskStrings); } } - private AccessMaskFlags _AccessMaskFlags; - public AccessMaskFlags AccessMaskFlags + /// + /// IsInheritedHumanized + /// + public string IsInheritedHumanized + => IsInherited ? "Yes".GetLocalizedResource() : "No".GetLocalizedResource(); + + /// + /// InheritanceFlagsHumanized + /// + public string InheritanceFlagsHumanized { - get => _AccessMaskFlags; - set + get { - if (SetProperty(ref _AccessMaskFlags, value)) - OnPropertyChanged(nameof(AccessMaskFlagsHumanized)); + var inheritanceStrings = new List(); + + if (AccessControlEntryFlags == AccessControlEntryFlags.None || + AccessControlEntryFlags == AccessControlEntryFlags.NoPropagateInherit) + inheritanceStrings.Add("SecurityAdvancedFlagsFolderLabel".GetLocalizedResource()); + + if (AccessControlEntryFlags.HasFlag(AccessControlEntryFlags.ContainerInherit)) + inheritanceStrings.Add("SecurityAdvancedFlagsSubfoldersLabel".GetLocalizedResource()); + + if (AccessControlEntryFlags.HasFlag(AccessControlEntryFlags.ObjectInherit)) + inheritanceStrings.Add("SecurityAdvancedFlagsFilesLabel".GetLocalizedResource()); + + // Capitalize the first letter + if (inheritanceStrings.Any()) + inheritanceStrings[0] = char.ToUpperInvariant(inheritanceStrings[0].First()) + inheritanceStrings[0][1..]; + + return string.Join(", ", inheritanceStrings); } } - private InheritanceFlags _InheritanceFlags; - public InheritanceFlags InheritanceFlags + /// + /// AccessMaskItems + /// + public ObservableCollection AccessMaskItems { get; set; } + + private AccessControlEntryType _AccessControlType; + public AccessControlEntryType AccessControlType { - get => _InheritanceFlags; + get => _AccessControlType; set { - if (SetProperty(ref _InheritanceFlags, value)) - OnPropertyChanged(nameof(InheritanceFlagsHumanized)); + if (SetProperty(ref _AccessControlType, value)) + { + OnPropertyChanged(nameof(AccessControlTypeGlyph)); + OnPropertyChanged(nameof(AccessControlTypeHumanized)); + } } } - private PropagationFlags _PropagationFlags; - public PropagationFlags PropagationFlags + #region Access Mask Properties + private AccessMaskFlags _AccessMaskFlags; + public AccessMaskFlags AccessMaskFlags { - get => _PropagationFlags; + get => _AccessMaskFlags; set { - if (SetProperty(ref _PropagationFlags, value)) - OnPropertyChanged(nameof(InheritanceFlagsHumanized)); + if (SetProperty(ref _AccessMaskFlags, value)) + OnPropertyChanged(nameof(AccessMaskFlagsHumanized)); } } @@ -102,9 +190,21 @@ public AccessMaskFlags DeniedAccessMaskFlags } } + private AccessControlEntryFlags _InheritanceFlags; + public AccessControlEntryFlags AccessControlEntryFlags + { + get => _InheritanceFlags; + set + { + if (SetProperty(ref _InheritanceFlags, value)) + OnPropertyChanged(nameof(InheritanceFlagsHumanized)); + } + } + public AccessMaskFlags InheritedAllowAccessMaskFlags { get; set; } public AccessMaskFlags InheritedDenyAccessMaskFlags { get; set; } + #endregion private bool _IsSelected; public bool IsSelected @@ -114,10 +214,9 @@ public bool IsSelected { if (SetProperty(ref _IsSelected, value)) { - if (!value) - AreAdvancedPermissionsShown = false; + AreAdvancedPermissionsShown = false; - OnPropertyChanged(nameof(IsEditEnabled)); + OnPropertyChanged(nameof(IsEditable)); } } } @@ -130,86 +229,10 @@ public bool AreAdvancedPermissionsShown { // Reinitialize list if (SetProperty(ref _AreAdvancedPermissionsShown, value)) - AccessMaskItemList = SecurityAdvancedAccessControlItemFactory.Initialize(this, AreAdvancedPermissionsShown, IsInherited, IsFolder); + AccessMaskItems = SecurityAdvancedAccessControlItemFactory.Initialize(this, value, IsInherited, IsFolder); } } - public bool IsInherited { get; set; } - - public bool IsEditEnabled - => IsSelected && !IsInherited; - - public string AccessControlTypeHumanized - => AccessControlType switch - { - AccessControlType.Allow => "Allow", - _ => "Deny" // AccessControlType.Deny - }; - - public string AccessControlTypeGlyph - => AccessControlType switch - { - AccessControlType.Allow => "\xE73E", - _ => "\xF140" // AccessControlType.Deny - }; - - public string AccessMaskFlagsHumanized - { - get - { - var accessMaskStrings = new List(); - - if (AccessMaskFlags == AccessMaskFlags.NULL) - accessMaskStrings.Add("None".GetLocalizedResource()); - - if (FullControlAccess) - accessMaskStrings.Add("SecurityFullControlLabel/Text".GetLocalizedResource()); - else if (ModifyAccess) - accessMaskStrings.Add("SecurityModifyLabel/Text".GetLocalizedResource()); - else if (ReadAndExecuteAccess) - accessMaskStrings.Add("SecurityReadAndExecuteLabel/Text".GetLocalizedResource()); - else if (ReadAccess) - accessMaskStrings.Add("SecurityReadLabel/Text".GetLocalizedResource()); - - if (!FullControlAccess && !ModifyAccess && WriteAccess) - accessMaskStrings.Add("Write".GetLocalizedResource()); - - if (SpecialAccess) - accessMaskStrings.Add("SecuritySpecialLabel/Text".GetLocalizedResource()); - - return string.Join(",", accessMaskStrings); - } - } - - public string IsInheritedHumanized - => IsInherited ? "Yes".GetLocalizedResource() : "No".GetLocalizedResource(); - - public string InheritanceFlagsHumanized - { - get - { - var inheritanceStrings = new List(); - - if (PropagationFlags == PropagationFlags.None || - PropagationFlags == PropagationFlags.NoPropagateInherit) - inheritanceStrings.Add("SecurityAdvancedFlagsFolderLabel".GetLocalizedResource()); - - if (InheritanceFlags.HasFlag(InheritanceFlags.ContainerInherit)) - inheritanceStrings.Add("SecurityAdvancedFlagsSubfoldersLabel".GetLocalizedResource()); - - if (InheritanceFlags.HasFlag(InheritanceFlags.ObjectInherit)) - inheritanceStrings.Add("SecurityAdvancedFlagsFilesLabel".GetLocalizedResource()); - - // Capitalize first letter - if (inheritanceStrings.Any()) - inheritanceStrings[0] = char.ToUpperInvariant(inheritanceStrings[0].First()) + inheritanceStrings[0][1..]; - - return string.Join(",", inheritanceStrings); - } - } - - public ObservableCollection AccessMaskItemList { get; set; } - #region Security page public bool WriteAccess => AccessMaskFlags.HasFlag(AccessMaskFlags.Write); public bool ReadAccess => AccessMaskFlags.HasFlag(AccessMaskFlags.Read); @@ -318,40 +341,37 @@ public bool DeniedFullControlAccess public IRelayCommand ChangeAccessControlTypeCommand { get; set; } public IRelayCommand ChangeInheritanceFlagsCommand { get; set; } - public AccessControlEntry(bool isFolder, string ownerSid, AccessControlType type, AccessMaskFlags accessMaskFlags, bool isInherited, InheritanceFlags inheritanceFlags, PropagationFlags propagationFlags) + public AccessControlEntry(bool isFolder, string ownerSid, AccessControlEntryType type, AccessMaskFlags accessMaskFlags, bool isInherited, AccessControlEntryFlags inheritanceFlags) { - AccessMaskItemList = SecurityAdvancedAccessControlItemFactory.Initialize(this, AreAdvancedPermissionsShown, IsInherited, IsFolder); - - ChangeAccessControlTypeCommand = new RelayCommand(x => - { - AccessControlType = Enum.Parse(x); - }); + AccessMaskItems = SecurityAdvancedAccessControlItemFactory.Initialize(this, AreAdvancedPermissionsShown, IsInherited, IsFolder); - ChangeInheritanceFlagsCommand = new RelayCommand(x => - { - var parts = x.Split(','); + //ChangeAccessControlTypeCommand = new RelayCommand(x => + //{ + // AccessControlType = Enum.Parse(x); + //}); - InheritanceFlags = Enum.Parse(parts[0]); - PropagationFlags = Enum.Parse(parts[1]); - }); + //ChangeInheritanceFlagsCommand = new RelayCommand(x => + //{ + // var parts = x.Split(','); + // InheritanceFlags = Enum.Parse(parts[0]); + //}); IsFolder = isFolder; - Principal = Principal.FromSid(ownerSid); + Principal = new(ownerSid); AccessControlType = type; AccessMaskFlags = accessMaskFlags; IsInherited = isInherited; - InheritanceFlags = inheritanceFlags; - PropagationFlags = propagationFlags; + AccessControlEntryFlags = inheritanceFlags; switch (AccessControlType) { - case AccessControlType.Allow: + case AccessControlEntryType.Allow: if (IsInherited) InheritedAllowAccessMaskFlags |= AccessMaskFlags; else AllowedAccessMaskFlags |= AccessMaskFlags; break; - case AccessControlType.Deny: + case AccessControlEntryType.Deny: if (IsInherited) InheritedDenyAccessMaskFlags |= AccessMaskFlags; else diff --git a/src/Files.App/Filesystem/Security/AccessControlEntryFlags.cs b/src/Files.App/Filesystem/Security/AccessControlEntryFlags.cs new file mode 100644 index 000000000000..da2cc69800df --- /dev/null +++ b/src/Files.App/Filesystem/Security/AccessControlEntryFlags.cs @@ -0,0 +1,63 @@ +// Copyright (c) 2023 Files Community +// Licensed under the MIT License. See the LICENSE. + +namespace Files.App.Filesystem.Security +{ + /// + /// Represents inheritance flags of an ACE + /// + [Flags] + public enum AccessControlEntryFlags + { + /// + /// No ACE flags are set. + /// + None = 0x0, + + /// + /// The access mask is propagated onto child leaf objects. + /// + ObjectInherit = 0x1, + + /// + /// The access mask is propagated to child container objects. + /// + ContainerInherit = 0x2, + + /// + /// The access checks do not apply to the object; they only apply to its children. + /// + NoPropagateInherit = 0x4, + + /// + /// The access mask is propagated only to child objects. This includes both container and leaf child objects. + /// + InheritOnly = 0x8, + + /// + /// A logical OR of System.Security.AccessControl.AceFlags.ObjectInherit, System.Security.AccessControl.AceFlags.ContainerInherit, + /// System.Security.AccessControl.AceFlags.NoPropagateInherit, and System.Security.AccessControl.AceFlags.InheritOnly. + /// + InheritanceFlags = 0xf, + + /// + /// An ACE is inherited from a parent container rather than being explicitly set for an object. + /// + Inherited = 0x10, + + /// + /// Successful access attempts are audited. + /// + SuccessfulAccess = 0x40, + + /// + /// The access mask is propagated onto child leaf objects. + /// + FailedAccess = 0x80, + + /// + /// All access attempts are audited. + /// + AuditFlags = 0xc0 + } +} diff --git a/src/Files.App/Filesystem/Security/AccessControlType.cs b/src/Files.App/Filesystem/Security/AccessControlEntryType.cs similarity index 74% rename from src/Files.App/Filesystem/Security/AccessControlType.cs rename to src/Files.App/Filesystem/Security/AccessControlEntryType.cs index 0c903c5a8098..a4e93da8533f 100644 --- a/src/Files.App/Filesystem/Security/AccessControlType.cs +++ b/src/Files.App/Filesystem/Security/AccessControlEntryType.cs @@ -6,15 +6,15 @@ namespace Files.App.Filesystem.Security /// /// Represents ACE type. /// - public enum AccessControlType + public enum AccessControlEntryType { /// - /// ACCESS_ALLOWED_ACE + /// ACCESS_ALLOWED_ACE type /// Allow, /// - /// ACCESS_DENIED_ACE + /// ACCESS_DENIED_ACE type /// Deny } diff --git a/src/Files.App/Filesystem/Security/AccessControlList.cs b/src/Files.App/Filesystem/Security/AccessControlList.cs index f95214c666f3..8d9235d8d2ef 100644 --- a/src/Files.App/Filesystem/Security/AccessControlList.cs +++ b/src/Files.App/Filesystem/Security/AccessControlList.cs @@ -1,47 +1,53 @@ -// Copyright (c) 2023 Files Community +// Copyright (c) 2023 Files Community // Licensed under the MIT License. See the LICENSE. -using System.Collections.ObjectModel; - namespace Files.App.Filesystem.Security { /// - /// Represents an ACL. + /// Represents an access control list (ACL). /// - public class AccessControlList + public class AccessControlList : ObservableObject { /// - /// File owner information + /// Object path. /// - public Principal Owner { get; set; } + public string Path { get; private set; } /// - /// Whether the DACL is protected + /// Whether the path indicates folder or not. /// - public bool IsProtected { get; set; } + public bool IsFolder { get; private set; } /// - /// Whether the DACL is valid one + /// The owner in the security descriptor (SD). + /// NULL if the security descriptor has no owner SID. /// - public bool IsValid { get; set; } + public Principal Owner { get; private set; } /// - /// File path which have this access control list + /// Validates an access control list (ACL). /// - public string Path { get; set; } + public bool IsValid { get; private set; } /// - /// Whether the path indicates folder or not + /// Access control entry (ACE) list /// - public bool IsFolder { get; set; } + public ObservableCollection AccessControlEntries { get; private set; } - /// - /// ACE list - /// - public ObservableCollection AccessControlEntries { get; set; } + public AccessControlList(string path, bool isFolder, Principal owner, bool isValid) + { + Path = path; + IsFolder = isFolder; + Owner = owner; + IsValid = isValid; + AccessControlEntries = new(); + } public AccessControlList() { + Path = string.Empty; + Owner = new(string.Empty); + AccessControlEntries = new(); } } } diff --git a/src/Files.App/Filesystem/Security/AccessMaskItem.cs b/src/Files.App/Filesystem/Security/AccessMaskItem.cs index 92f2e3ec312f..da090e64bcc9 100644 --- a/src/Files.App/Filesystem/Security/AccessMaskItem.cs +++ b/src/Files.App/Filesystem/Security/AccessMaskItem.cs @@ -48,7 +48,7 @@ private void ToggleAccess(AccessMaskFlags accessMask, bool value) _ace.AccessMaskFlags &= ~accessMask; } - private void AccessControlEntry_PropertyChanged(object sender, PropertyChangedEventArgs e) + private void AccessControlEntry_PropertyChanged(object? sender, PropertyChangedEventArgs e) { if (e.PropertyName == nameof(AccessControlEntry.AccessMaskFlags)) OnPropertyChanged(nameof(IsEnabled)); diff --git a/src/Files.App/Filesystem/Security/InheritanceFlags.cs b/src/Files.App/Filesystem/Security/InheritanceFlags.cs deleted file mode 100644 index 9ce98372c390..000000000000 --- a/src/Files.App/Filesystem/Security/InheritanceFlags.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) 2023 Files Community -// Licensed under the MIT License. See the LICENSE. - -using System; - -namespace Files.App.Filesystem.Security -{ - /// - /// Represents inheritance flags of an ACE - /// - [Flags] - public enum InheritanceFlags - { - /// - /// No inheritance flags - /// - None = 0, - - /// - /// Child objects that are containers, such as directories, inherit the ACE as an effective ACE. - /// The inherited ACE is inheritable unless the NO_PROPAGATE_INHERIT_ACE bit flag is also set. - /// - ContainerInherit = 1, - - /// - /// Noncontainer child objects inherit the ACE as an effective ACE. - /// For child objects that are containers, the ACE is inherited as an inherit-only ACE - /// unless the NO_PROPAGATE_INHERIT_ACE bit flag is also set. - /// - ObjectInherit = 2 - } -} diff --git a/src/Files.App/Filesystem/Security/Principal.cs b/src/Files.App/Filesystem/Security/Principal.cs index d4f02d30f735..f49d303330c9 100644 --- a/src/Files.App/Filesystem/Security/Principal.cs +++ b/src/Files.App/Filesystem/Security/Principal.cs @@ -1,9 +1,8 @@ // Copyright (c) 2023 Files Community // Licensed under the MIT License. See the LICENSE. -using CommunityToolkit.Mvvm.ComponentModel; -using System.Collections.Generic; using System.Text; +using Vanara.PInvoke; using static Vanara.PInvoke.AdvApi32; namespace Files.App.Filesystem.Security @@ -13,6 +12,34 @@ namespace Files.App.Filesystem.Security /// public class Principal : ObservableObject { + /// + /// Account type. + /// + public PrincipalType PrincipalType { get; private set; } + + /// + /// Acount security identifier (SID). + /// + public string? Sid { get; private set; } + + /// + /// A domain the account belongs to + /// + public string? Domain { get; private set; } + + /// + /// Account name + /// + public string? Name { get; private set; } + + /// + /// Indicates whether this instance is valid or not + /// + public bool IsValid { get; private set; } + + /// + /// Account type glyph. + /// public string Glyph => PrincipalType switch { @@ -21,78 +48,80 @@ public string Glyph _ => "\xE716", }; - public string? Sid { get; set; } - - public string? Domain { get; set; } - - public string? Name { get; set; } - + /// + /// Account display name + /// public string? DisplayName => string.IsNullOrEmpty(Name) ? Sid : Name; - public string? FullNameOrSid - => string.IsNullOrEmpty(Domain) ? Sid : $"{Domain}\\{Name}"; - + /// + /// Account full name or just name + /// public string? FullNameHumanized - => string.IsNullOrEmpty(Domain) ? string.Empty : $"({Domain}\\{Name})"; + => string.IsNullOrEmpty(Domain) ? Name : $"{Domain}\\{Name}"; - public List Groups { get; set; } - - private PrincipalType PrincipalType { get; set; } - - public Principal() - { - Groups = new(); - - PrincipalType = PrincipalType.Unknown; - } + /// + /// Account humanized full name. + /// + public string FullNameHumanizedWithBrackes + => string.IsNullOrEmpty(Domain) ? string.Empty : $"({Domain}\\{Name})"; - public static Principal FromSid(string sid) + public Principal(string sid) { - var userGroup = new Principal() - { - Sid = sid - }; - if (string.IsNullOrEmpty(sid)) - return userGroup; + return; - var lpSid = ConvertStringSidToSid(userGroup.Sid); + Sid = sid; + var lpSid = ConvertStringSidToSid(sid); - var lpName = new StringBuilder(); - var lpDomain = new StringBuilder(); - int cchName = 0; - int cchDomainName = 0; + StringBuilder lpName = new(), lpDomain = new(); + int cchName = 0, cchDomainName = 0; - LookupAccountSid(null, lpSid, lpName, ref cchName, lpDomain, ref cchDomainName, out _); + // Get size of account name and domain name + bool bResult = LookupAccountSid(null, lpSid, lpName, ref cchName, lpDomain, ref cchDomainName, out _); + // Ensure requested capacity lpName.EnsureCapacity(cchName); lpDomain.EnsureCapacity(cchDomainName); - if (LookupAccountSid(null, lpSid, lpName, ref cchName, lpDomain, ref cchDomainName, out var snu)) + // Get account name and domain + bResult = LookupAccountSid(null, lpSid, lpName, ref cchName, lpDomain, ref cchDomainName, out var snu); + if(!bResult) + return; + + PrincipalType = snu switch { - userGroup.PrincipalType = snu switch - { - // Group - var x when - (x == SID_NAME_USE.SidTypeAlias || - x == SID_NAME_USE.SidTypeGroup || - x == SID_NAME_USE.SidTypeWellKnownGroup) - => PrincipalType.Group, - - // User - SID_NAME_USE.SidTypeUser - => PrincipalType.User, - - // Unknown - _ => PrincipalType.Unknown - }; - - userGroup.Name = lpName.ToString(); - userGroup.Domain = lpDomain.ToString(); + // Group + var x when + (x == SID_NAME_USE.SidTypeAlias || + x == SID_NAME_USE.SidTypeGroup || + x == SID_NAME_USE.SidTypeWellKnownGroup) + => PrincipalType.Group, + + // User + SID_NAME_USE.SidTypeUser + => PrincipalType.User, + + // Unknown + _ => PrincipalType.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); + if (!bResult) + return; } - return userGroup; + Name = lpName.ToString(); + Domain = lpDomain.ToString().ToLower(); + + IsValid = true; } } } diff --git a/src/Files.App/Filesystem/Security/PropagationFlags.cs b/src/Files.App/Filesystem/Security/PropagationFlags.cs deleted file mode 100644 index 40f76059c416..000000000000 --- a/src/Files.App/Filesystem/Security/PropagationFlags.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) 2023 Files Community -// Licensed under the MIT License. See the LICENSE. - -using System; - -namespace Files.App.Filesystem.Security -{ - /// - /// Represents propagation flags of an ACE - /// - [Flags] - public enum PropagationFlags - { - /// - /// No propagation flags - /// - None = 0, - - /// - /// If the ACE is inherited by a child object, the system clears the OBJECT_INHERIT_ACE and CONTAINER_INHERIT_ACE flags in the inherited ACE. - /// This prevents the ACE from being inherited by subsequent generations of objects. - /// - NoPropagateInherit = 1, - - /// - /// Indicates an inherit-only ACE, which does not control access to the object to which it is attached. - /// If this flag is not set, the ACE is an effective ACE which controls access to the object to which it is attached. - /// Both effective and inherit-only ACEs can be inherited depending on the state of the other inheritance flags. - /// - InheritOnly = 2 - } -} diff --git a/src/Files.App/Helpers/FileOperationsHelpers.cs b/src/Files.App/Helpers/FileOperationsHelpers.cs index 6ec7d6dc3cb5..c2ad1c7f2ce9 100644 --- a/src/Files.App/Helpers/FileOperationsHelpers.cs +++ b/src/Files.App/Helpers/FileOperationsHelpers.cs @@ -627,15 +627,6 @@ public static bool SetLinkIcon(string filePath, string iconFile, int iconIndex) return false; } - public static AccessControlList GetFilePermissions(string filePath, bool isFolder) - => FileSecurityHelpers.GetAccessControlList(filePath, isFolder); - - public static bool SetFileOwner(string filePath, string ownerSid) - => FileSecurityHelpers.SetOwner(filePath, ownerSid); - - public static bool SetAccessRuleProtection(string filePath, bool isFolder, bool isProtected, bool preserveInheritance) - => FileSecurityHelpers.SetAccessControlProtection(filePath, isFolder, isProtected, preserveInheritance); - public static Task OpenObjectPickerAsync(long hWnd) { return Win32API.StartSTATask(() => @@ -649,6 +640,7 @@ public static bool SetAccessRuleProtection(string filePath, bool isFolder, bool MultiSelect = false, ShowAdvancedView = true }; + picker.AttributesToFetch.Add("objectSid"); using (picker) @@ -659,13 +651,9 @@ public static bool SetAccessRuleProtection(string filePath, bool isFolder, bool { var attribs = picker.SelectedObject.FetchedAttributes; if (attribs.Any() && attribs[0] is byte[] objectSid) - { return new SecurityIdentifier(objectSid, 0).Value; - } - } - catch - { } + catch {} } } diff --git a/src/Files.App/Helpers/FileSecurityHelpers.cs b/src/Files.App/Helpers/FileSecurityHelpers.cs index d317cc0b076c..cfce2fd78cf2 100644 --- a/src/Files.App/Helpers/FileSecurityHelpers.cs +++ b/src/Files.App/Helpers/FileSecurityHelpers.cs @@ -3,15 +3,9 @@ using Files.App.Filesystem.Security; using Files.App.Shell; -using System; -using System.IO; -using System.Runtime.InteropServices; -using System.Security.AccessControl; -using System.Security.Principal; -using System.Text; using Vanara.PInvoke; using static Vanara.PInvoke.AdvApi32; -using FilesSecurity = Files.App.Filesystem.Security; +using SystemSecurity = System.Security.AccessControl; namespace Files.App.Helpers { @@ -20,29 +14,11 @@ namespace Files.App.Helpers /// public static class FileSecurityHelpers { - 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 Win32API.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 the owner of the object specified by the path. + /// + /// The file full path + /// public static string GetOwner(string path) { GetNamedSecurityInfo( @@ -60,56 +36,47 @@ public static string GetOwner(string path) return szSid; } - public static bool GetAccessControlProtection(string path, bool isFolder) + /// + /// 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) { - FileSystemSecurity fileSystemSecurity; - - if (isFolder && Directory.Exists(path)) - { - fileSystemSecurity = FileSystemAclExtensions.GetAccessControl(new DirectoryInfo(path)); - return fileSystemSecurity.AreAccessRulesProtected; - } - else if (File.Exists(path)) - { - fileSystemSecurity = FileSystemAclExtensions.GetAccessControl(new FileInfo(path)); - return fileSystemSecurity.AreAccessRulesProtected; - } - else - { - return false; - } - } + SECURITY_INFORMATION secInfo = SECURITY_INFORMATION.OWNER_SECURITY_INFORMATION; - public static bool SetAccessControlProtection(string path, bool isFolder, bool isProtected, bool preserveInheritance) - { - FileSystemSecurity fileSystemSecurity; + // Get PSID object from string sid + var pSid = ConvertStringSidToSid(sid); - if (isFolder && Directory.Exists(path)) - { - fileSystemSecurity = FileSystemAclExtensions.GetAccessControl(new DirectoryInfo(path)); - fileSystemSecurity.SetAccessRuleProtection(isProtected, preserveInheritance); - FileSystemAclExtensions.SetAccessControl(new DirectoryInfo(path), (DirectorySecurity)fileSystemSecurity); + // Change owner + var result = SetNamedSecurityInfo(path, SE_OBJECT_TYPE.SE_FILE_OBJECT, secInfo, pSid); - return true; - } - else if (File.Exists(path)) - { - fileSystemSecurity = FileSystemAclExtensions.GetAccessControl(new FileInfo(path)); - fileSystemSecurity.SetAccessRuleProtection(isProtected, preserveInheritance); - FileSystemAclExtensions.SetAccessControl(new FileInfo(path), (FileSecurity)fileSystemSecurity); + pSid.Dispose(); - return true; - } - else + // Run PowerShell as Admin + if (result.Failed) { - return false; + return Win32API.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 static AccessControlList GetAccessControlList(string path, bool isFolder) + /// + /// 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 - GetNamedSecurityInfo( + var win32Error = GetNamedSecurityInfo( path, SE_OBJECT_TYPE.SE_FILE_OBJECT, SECURITY_INFORMATION.DACL_SECURITY_INFORMATION | SECURITY_INFORMATION.PROTECTED_DACL_SECURITY_INFORMATION, @@ -119,107 +86,175 @@ public static AccessControlList GetAccessControlList(string path, bool isFolder) out _, out _); + if (win32Error.Failed || pDacl == PACL.NULL) + return win32Error; + // Get ACL size info - GetAclInformation(pDacl, out ACL_SIZE_INFORMATION aclSize); + 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); - // Initialize - var acl = new AccessControlList() - { - Owner = Principal.FromSid(szOwnerSid), - IsProtected = GetAccessControlProtection(path, isFolder), - IsValid = true, - AccessControlEntries = new(), - Path = path, - IsFolder = isFolder - }; + var isValidAcl = IsValidAcl(pDacl); + + List aces = new(); // Get ACEs for (uint i = 0; i < aclSize.AceCount; i++) { - GetAce(pDacl, i, out var pAce); + bResult = GetAce(pDacl, i, out var pAce); + if (!bResult) + return Kernel32.GetLastError(); var szSid = ConvertSidToStringSid(pAce.GetSid()); - var header = pAce.GetHeader(); - - FilesSecurity.AccessControlType type; - FilesSecurity.InheritanceFlags inheritanceFlags = FilesSecurity.InheritanceFlags.None; - FilesSecurity.PropagationFlags propagationFlags = FilesSecurity.PropagationFlags.None; + AccessControlEntryType type; + AccessControlEntryFlags inheritanceFlags = AccessControlEntryFlags.None; AccessMaskFlags accessMaskFlags = (AccessMaskFlags)pAce.GetMask(); + var header = pAce.GetHeader(); type = header.AceType switch { - AceType.AccessAllowed => FilesSecurity.AccessControlType.Allow, - _ => FilesSecurity.AccessControlType.Deny + SystemSecurity.AceType.AccessAllowed => AccessControlEntryType.Allow, + _ => AccessControlEntryType.Deny }; - bool isInherited = header.AceFlags.HasFlag(AceFlags.InheritanceFlags); + bool isInherited = header.AceFlags.HasFlag(SystemSecurity.AceFlags.InheritanceFlags); - if (header.AceFlags.HasFlag(AceFlags.ContainerInherit)) - inheritanceFlags |= FilesSecurity.InheritanceFlags.ContainerInherit; - if (header.AceFlags.HasFlag(AceFlags.ObjectInherit)) - inheritanceFlags |= FilesSecurity.InheritanceFlags.ObjectInherit; - if (header.AceFlags.HasFlag(AceFlags.NoPropagateInherit)) - propagationFlags |= FilesSecurity.PropagationFlags.NoPropagateInherit; - if (header.AceFlags.HasFlag(AceFlags.InheritOnly)) - propagationFlags |= FilesSecurity.PropagationFlags.InheritOnly; + 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 - acl.AccessControlEntries.Add(new(isFolder, szSid, type, accessMaskFlags, isInherited, inheritanceFlags, propagationFlags)); + aces.Add(new(isFolder, szSid, type, accessMaskFlags, isInherited, inheritanceFlags)); } - return acl; - } + // Initialize with proper data + acl = new AccessControlList(path, isFolder, principal, isValidAcl); - public static bool SetAccessControlList(AccessControlList acl) - { - return false; + // 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, - FilesSecurity.AccessControlType.Allow, - FilesSecurity.AccessMaskFlags.ReadAndExecute, + AccessControlEntryType.Allow, + AccessMaskFlags.ReadAndExecute, false, isFolder - ? FilesSecurity.InheritanceFlags.ContainerInherit | FilesSecurity.InheritanceFlags.ObjectInherit - : FilesSecurity.InheritanceFlags.None, - FilesSecurity.PropagationFlags.None); + ? AccessControlEntryFlags.ContainerInherit | AccessControlEntryFlags.ObjectInherit + : AccessControlEntryFlags.None); } - public static bool AddAccessControlEntry(string path, AccessControlEntry entry) + /// + /// 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 - GetNamedSecurityInfo( - path, + // 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 var pDACL, out _, out _); - // Get ACL size info - GetAclInformation(pDacl, out ACL_SIZE_INFORMATION aclSize); - - uint revision = GetAclInformation(pDacl, out ACL_REVISION_INFORMATION aclRevision) ? aclRevision.AclRevision : 0U; + if (result.Failed) + return result; - // Get ACEs - for (uint i = 0; i < aclSize.AceCount; i++) + // Initialize default trustee + var explicitAccess = new EXPLICIT_ACCESS { - //GetAce(pDacl, i, out var pAce); + 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)), + }; - //AddAce(pDacl, revision, 0, (IntPtr)pAce, ((PACL)pDacl).Length() - (uint)Marshal.SizeOf(typeof(ACL))); - } + // Add an new ACE and get a new ACL + result = SetEntriesInAcl(1, new[] { 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 false; + return result; } } } diff --git a/src/Files.App/Strings/en-US/Resources.resw b/src/Files.App/Strings/en-US/Resources.resw index d31cc4ff4ec3..a576b494b6a8 100644 --- a/src/Files.App/Strings/en-US/Resources.resw +++ b/src/Files.App/Strings/en-US/Resources.resw @@ -3211,4 +3211,16 @@ Location: + + Click 'Advanced permissions' to continue. + + + You must have Read permissions to view the properties of this item. + + + To try taking ownership of the item, which includes permission to view its properties, click Change above. + + + Unable to display permissions. + \ No newline at end of file diff --git a/src/Files.App/ViewModels/Properties/SecurityAdvancedViewModel.cs b/src/Files.App/ViewModels/Properties/SecurityAdvancedViewModel.cs index f9fc0cba2859..f00caca3a1f3 100644 --- a/src/Files.App/ViewModels/Properties/SecurityAdvancedViewModel.cs +++ b/src/Files.App/ViewModels/Properties/SecurityAdvancedViewModel.cs @@ -1,218 +1,257 @@ -using CommunityToolkit.Mvvm.ComponentModel; -using CommunityToolkit.Mvvm.Input; -using Files.App.Data.Items; -using Files.App.Extensions; -using Files.App.Filesystem; +// Copyright (c) 2023 Files Community +// Licensed under the MIT License. See the LICENSE. + using Files.App.Filesystem.Security; -using Files.App.Helpers; using Microsoft.UI.Xaml; -using System.Threading.Tasks; +using Vanara.PInvoke; using Windows.Storage; namespace Files.App.ViewModels.Properties { public class SecurityAdvancedViewModel : ObservableObject { - public ListedItem Item { get; } + private readonly PropertiesPageNavigationParameter _navigationParameter; - private readonly Window Window; + private readonly Window _window; - private AccessControlList _accessControlList; - public AccessControlList AccessControlList + private readonly string _path; + + private readonly bool _isFolder; + + public bool IsAddAccessControlEntryButtonEnabled => + AccessControlList is not null && + AccessControlList.IsValid; + + public bool IsDeleteAccessControlEntryButtonEnabled => + AccessControlList is not null && + AccessControlList.IsValid && + SelectedAccessControlEntry is not null && + SelectedAccessControlEntry.IsInherited is false; + + public IconFileInfo ShieldIconFileInfo { get; private set; } + + public bool CurrentInstanceCanReadPermissions { get; private set; } + + public bool CurrentInstanceCanChangePermissions { get; private set; } + + public string DisableInheritanceOption { - get => _accessControlList; - set + get { - if (SetProperty(ref _accessControlList, value)) - { - ChangeOwnerCommand.NotifyCanExecuteChanged(); - AddAccessControlEntryCommand.NotifyCanExecuteChanged(); - RemoveAccessControlEntryCommand.NotifyCanExecuteChanged(); - DisableInheritanceCommand.NotifyCanExecuteChanged(); - ReplaceChildPermissionsCommand.NotifyCanExecuteChanged(); - } + //if (!_isProtected) + // return "SecurityAdvancedInheritedEnable/Text".GetLocalizedResource(); + //else if (_preserveInheritance) + // return "SecurityAdvancedInheritedConvert/Text".GetLocalizedResource(); + //else + // return "SecurityAdvancedInheritedRemove/Text".GetLocalizedResource(); + + return string.Empty; } } - private AccessControlEntry _selectedAccessControlEntry; - public AccessControlEntry SelectedAccessControlEntry + private AccessControlList _AccessControlList; + public AccessControlList AccessControlList { - get => _selectedAccessControlEntry; + get => _AccessControlList; + set => SetProperty(ref _AccessControlList, value); + } + + private AccessControlEntry? _SelectedAccessControlEntry; + public AccessControlEntry? SelectedAccessControlEntry + { + get => _SelectedAccessControlEntry; set { - if (_selectedAccessControlEntry is not null) - _selectedAccessControlEntry.IsSelected = false; + // Previous selection + if (_SelectedAccessControlEntry is not null) + _SelectedAccessControlEntry.IsSelected = false; - if (SetProperty(ref _selectedAccessControlEntry, value)) + if (SetProperty(ref _SelectedAccessControlEntry, value)) { - value.IsSelected = true; - RemoveAccessControlEntryCommand.NotifyCanExecuteChanged(); + if(value is not null) + value.IsSelected = true; + + OnPropertyChanged(nameof(IsDeleteAccessControlEntryButtonEnabled)); } } } - private bool _isFolder; - public bool IsFolder + private bool _DisplayElements; + public bool DisplayElements { - get => _isFolder; - set => SetProperty(ref _isFolder, value); + get => _DisplayElements; + set => SetProperty(ref _DisplayElements, value); } - public string DisableInheritanceOption + private string _ErrorMessage = string.Empty; + public string ErrorMessage { - get - { - if (!_isProtected) - return "SecurityAdvancedInheritedEnable/Text".GetLocalizedResource(); - else if (_preserveInheritance) - return "SecurityAdvancedInheritedConvert/Text".GetLocalizedResource(); - else - return "SecurityAdvancedInheritedRemove/Text".GetLocalizedResource(); - } + get => _ErrorMessage; + set => SetProperty(ref _ErrorMessage, value); } - private bool _isProtected; - - private bool _preserveInheritance; - - private GridLength _columnType = new(64d); - public GridLength ColumnType + private GridLength _ColumnTypeGridLength = new(64d); + public GridLength ColumnTypeGridLength { - get => _columnType; - set => SetProperty(ref _columnType, value); + get => _ColumnTypeGridLength; + set => SetProperty(ref _ColumnTypeGridLength, value); } - private GridLength _columnPrincipal = new(200d); - public GridLength ColumnPrincipal + private GridLength _ColumnPrincipalGridLength = new(200d); + public GridLength ColumnPrincipalGridLength { - get => _columnPrincipal; - set => SetProperty(ref _columnPrincipal, value); + get => _ColumnPrincipalGridLength; + set => SetProperty(ref _ColumnPrincipalGridLength, value); } - private GridLength _columnAccess = new(160d); - public GridLength ColumnAccess + private GridLength _ColumnAccessGridLength = new(160d); + public GridLength ColumnAccessGridLength { - get => _columnAccess; - set => SetProperty(ref _columnAccess, value); + get => _ColumnAccessGridLength; + set => SetProperty(ref _ColumnAccessGridLength, value); } - private GridLength _columnInherited = new(70d); - public GridLength ColumnInherited + private GridLength _ColumnInheritedGridLength = new(70d); + public GridLength ColumnInheritedGridLength { - get => _columnInherited; - set => SetProperty(ref _columnInherited, value); + get => _ColumnInheritedGridLength; + set => SetProperty(ref _ColumnInheritedGridLength, value); } - public RelayCommand ChangeOwnerCommand { get; set; } - public RelayCommand AddAccessControlEntryCommand { get; set; } - public RelayCommand RemoveAccessControlEntryCommand { get; set; } - public RelayCommand DisableInheritanceCommand { get; set; } - public RelayCommand SetDisableInheritanceOptionCommand { get; set; } - public RelayCommand ReplaceChildPermissionsCommand { get; set; } + public IAsyncRelayCommand ChangeOwnerCommand { get; set; } + public IAsyncRelayCommand AddAccessControlEntryCommand { get; set; } + public IAsyncRelayCommand RemoveAccessControlEntryCommand { get; set; } - public SecurityAdvancedViewModel(ListedItem item, Window window) + public SecurityAdvancedViewModel(PropertiesPageNavigationParameter parameter) { - IsFolder = item.PrimaryItemAttribute == StorageItemTypes.Folder && !item.IsShortcut; - Item = item; - Window = window; + _navigationParameter = parameter; + _window = parameter.Window; - InitializeCommands(); - GetAccessControlList(); - } - - public SecurityAdvancedViewModel(DriveItem item, Window window) - { - IsFolder = true; - Item = new ListedItem() + switch (parameter.Parameter) { - ItemNameRaw = item.Text, - ItemPath = item.Path, - PrimaryItemAttribute = StorageItemTypes.Folder + case ListedItem listedItem: + _path = listedItem.ItemPath; + _isFolder = listedItem.PrimaryItemAttribute == StorageItemTypes.Folder && !listedItem.IsShortcut; + break; + case DriveItem driveItem: + _path = driveItem.Path; + _isFolder = true; + break; + default: + var defaultlistedItem = (ListedItem)parameter.Parameter; + _path = defaultlistedItem.ItemPath; + _isFolder = defaultlistedItem.PrimaryItemAttribute == StorageItemTypes.Folder && !defaultlistedItem.IsShortcut; + break; }; - Window = window; - InitializeCommands(); - GetAccessControlList(); + LoadShieldIconResource(); + + LoadAccessControlEntry(); + + ChangeOwnerCommand = new AsyncRelayCommand(ExecuteChangeOwnerCommand); + AddAccessControlEntryCommand = new AsyncRelayCommand(ExecuteAddAccessControlEntryCommand); + RemoveAccessControlEntryCommand = new AsyncRelayCommand(ExecuteRemoveAccessControlEntryCommand); } - private void InitializeCommands() + private void LoadShieldIconResource() { - ChangeOwnerCommand = new RelayCommand(ChangeOwner, () => AccessControlList is not null); - AddAccessControlEntryCommand = new RelayCommand(AddAccessControlEntry, () => AccessControlList is not null && AccessControlList.IsValid); - RemoveAccessControlEntryCommand = new RelayCommand(RemoveAccessControlEntry, () => AccessControlList is not null && AccessControlList.IsValid && SelectedAccessControlEntry is not null); - DisableInheritanceCommand = new RelayCommand(DisableInheritance, () => AccessControlList is not null && AccessControlList.IsValid && (AccessControlList.IsProtected != _isProtected)); - SetDisableInheritanceOptionCommand = new RelayCommand(SetDisableInheritanceOption); - ReplaceChildPermissionsCommand = new RelayCommand(ReplaceChildPermissions, () => AccessControlList is not null && AccessControlList.IsValid); + string imageres = System.IO.Path.Combine(Constants.UserEnvironmentPaths.SystemRootPath, "System32", "imageres.dll"); + + var imageResList = Shell.Win32API.ExtractSelectedIconsFromDLL( + imageres, + new List() { Constants.ImageRes.ShieldIcon }, + 16); + + ShieldIconFileInfo = imageResList.First(); } - private async void ChangeOwner() + private void LoadAccessControlEntry() { - var pickedObject = await OpenObjectPicker(); - if (pickedObject is not null) + var error = FileSecurityHelpers.GetAccessControlList(_path, _isFolder, out _AccessControlList); + SelectedAccessControlEntry = AccessControlList.AccessControlEntries.FirstOrDefault(); + + if (!AccessControlList.IsValid) { - bool isFolder = Item.PrimaryItemAttribute == StorageItemTypes.Folder && !Item.IsShortcut; + DisplayElements = false; - // Set owner and refresh file permissions - if (FileOperationsHelpers.SetFileOwner(Item.ItemPath, pickedObject)) - GetAccessControlList(); + if (error == Win32Error.ERROR_ACCESS_DENIED) + { + ErrorMessage = + "SecurityRequireReadPermissions".GetLocalizedResource() + + "\r\n\r\n" + + "SecuritySuggestToTakeOwnership".GetLocalizedResource(); + } + else + { + ErrorMessage = + "SecurityUnableToDisplayPermissions".GetLocalizedResource() + + "\r\n\r\n" + + error.FormatMessage(); + } + } + else + { + DisplayElements = true; + ErrorMessage = string.Empty; } } - private async void AddAccessControlEntry() + private async Task ExecuteChangeOwnerCommand() { - // Pick an user/group - var sid = await OpenObjectPicker(); + var sid = await FileOperationsHelpers.OpenObjectPickerAsync(FilePropertiesHelpers.GetWindowHandle(_window).ToInt64()); if (string.IsNullOrEmpty(sid)) return; - // Initialize - var ace = FileSecurityHelpers.InitializeDefaultAccessControlEntry(IsFolder, sid); - AccessControlList.AccessControlEntries.Add(ace); + await App.Window.DispatcherQueue.EnqueueOrInvokeAsync(() => + { + // Set owner + FileSecurityHelpers.SetOwner(_path, sid); - // Save - FileSecurityHelpers.SetAccessControlList(AccessControlList); + // Reload + LoadAccessControlEntry(); + }); } - private void RemoveAccessControlEntry() + private async Task ExecuteAddAccessControlEntryCommand() { - // Remove - AccessControlList.AccessControlEntries.Remove(SelectedAccessControlEntry); + // Pick an user or a group with Object Picker UI + var sid = await FileOperationsHelpers.OpenObjectPickerAsync(FilePropertiesHelpers.GetWindowHandle(_window).ToInt64()); + if (string.IsNullOrEmpty(sid)) + return; - // Save - FileSecurityHelpers.SetAccessControlList(AccessControlList); - } + await App.Window.DispatcherQueue.EnqueueOrInvokeAsync(() => + { + // Run Win32API + var win32Result = FileSecurityHelpers.AddAccessControlEntry(_path, sid); - private void DisableInheritance() - { - // Update protection status and refresh access control - if (FileOperationsHelpers.SetAccessRuleProtection(Item.ItemPath, IsFolder, _isProtected, _preserveInheritance)) - GetAccessControlList(); + // Add a new ACE to the ACL + var ace = FileSecurityHelpers.InitializeDefaultAccessControlEntry(_isFolder, sid); + AccessControlList.AccessControlEntries.Insert(0, ace); + }); } - private void SetDisableInheritanceOption(string options) + private async Task ExecuteRemoveAccessControlEntryCommand() { - _isProtected = bool.Parse(options.Split(',')[0]); - _preserveInheritance = bool.Parse(options.Split(',')[1]); + if (SelectedAccessControlEntry is null) + return; - OnPropertyChanged(nameof(DisableInheritanceOption)); - DisableInheritanceCommand.NotifyCanExecuteChanged(); - } + await App.Window.DispatcherQueue.EnqueueOrInvokeAsync(() => + { + // Get index of the ACE + var index = AccessControlList.AccessControlEntries.IndexOf(SelectedAccessControlEntry); - private void ReplaceChildPermissions() - { - } + // Run Win32API + var win32Result = FileSecurityHelpers.RemoveAccessControlEntry(_path, (uint)index); - public void GetAccessControlList() - { - AccessControlList = FileOperationsHelpers.GetFilePermissions(Item.ItemPath, IsFolder); - } + // Remove the ACE + AccessControlList.AccessControlEntries.Remove(SelectedAccessControlEntry); - public bool SaveChangedAccessControlList() - { - return false; - } + if (AccessControlList.AccessControlEntries.Count == 0) + return; - public Task OpenObjectPicker() - => FileOperationsHelpers.OpenObjectPickerAsync(FilePropertiesHelpers.GetWindowHandle(Window).ToInt64()); + // Re-select item + SelectedAccessControlEntry = AccessControlList.AccessControlEntries.First(); + }); + } } } diff --git a/src/Files.App/ViewModels/Properties/SecurityViewModel.cs b/src/Files.App/ViewModels/Properties/SecurityViewModel.cs index 601b95e03e50..87d04e15aa51 100644 --- a/src/Files.App/ViewModels/Properties/SecurityViewModel.cs +++ b/src/Files.App/ViewModels/Properties/SecurityViewModel.cs @@ -1,29 +1,42 @@ -using CommunityToolkit.Mvvm.ComponentModel; -using CommunityToolkit.Mvvm.Input; -using Files.App.Data.Items; -using Files.App.Extensions; -using Files.App.Filesystem; +// Copyright (c) 2023 Files Community +// Licensed under the MIT License. See the LICENSE. + +using CommunityToolkit.WinUI; using Files.App.Filesystem.Security; -using Files.App.Helpers; using Microsoft.UI.Xaml; -using System.Linq; -using System.Threading.Tasks; +using Vanara.PInvoke; using Windows.Storage; namespace Files.App.ViewModels.Properties { public class SecurityViewModel : ObservableObject { - public string Path { get; set; } + private readonly PropertiesPageNavigationParameter _navigationParameter; - private readonly Window Window; + private readonly Window _window; - private bool _IsFolder; - public bool IsFolder - { - get => _IsFolder; - set => SetProperty(ref _IsFolder, value); - } + private readonly string _path; + + private readonly bool _isFolder; + + public bool DisplayElements { get; private set; } + + public string ErrorMessage { get; private set; } + + public bool IsAddAccessControlEntryButtonEnabled => + AccessControlList is not null && + AccessControlList.IsValid; + + public bool IsDeleteAccessControlEntryButtonEnabled => + AccessControlList is not null && + AccessControlList.IsValid && + SelectedAccessControlEntry is not null && + SelectedAccessControlEntry.IsInherited is false; + + public string SelectedItemHeaderText => + SelectedAccessControlEntry is null + ? "Permissions".GetLocalizedResource() + : string.Format("SecurityPermissionsHeaderText".GetLocalizedResource(), SelectedAccessControlEntry?.Principal.DisplayName); private AccessControlList _AccessControlList; public AccessControlList AccessControlList @@ -32,88 +45,108 @@ public AccessControlList AccessControlList set => SetProperty(ref _AccessControlList, value); } - private AccessControlEntry _SelectedAccessControlEntry; - public AccessControlEntry SelectedAccessControlEntry + private AccessControlEntry? _SelectedAccessControlEntry; + public AccessControlEntry? SelectedAccessControlEntry { get => _SelectedAccessControlEntry; set { + // Previous selection if (_SelectedAccessControlEntry is not null) _SelectedAccessControlEntry.IsSelected = false; - if (SetProperty(ref _SelectedAccessControlEntry, value)) + if (value is not null && SetProperty(ref _SelectedAccessControlEntry, value)) { value.IsSelected = true; - RemoveAccessControlEntryCommand?.NotifyCanExecuteChanged(); + + OnPropertyChanged(nameof(IsDeleteAccessControlEntryButtonEnabled)); OnPropertyChanged(nameof(SelectedItemHeaderText)); } } } - public string SelectedItemHeaderText - => string.Format("SecurityPermissionsHeaderText".GetLocalizedResource(), SelectedAccessControlEntry.Principal.DisplayName); - - public IRelayCommand AddAccessControlEntryCommand { get; set; } - public IRelayCommand RemoveAccessControlEntryCommand { get; set; } - - public SecurityViewModel(ListedItem item, Window window) - { - IsFolder = item.PrimaryItemAttribute == StorageItemTypes.Folder && !item.IsShortcut; - Path = item.ItemPath; - AccessControlList = FileSecurityHelpers.GetAccessControlList(Path, IsFolder); - SelectedAccessControlEntry = AccessControlList.AccessControlEntries.FirstOrDefault(); - Window = window; - - InitializeCommands(); - } + public IAsyncRelayCommand AddAccessControlEntryCommand { get; set; } + public IAsyncRelayCommand RemoveAccessControlEntryCommand { get; set; } - public SecurityViewModel(DriveItem item, Window window) + public SecurityViewModel(PropertiesPageNavigationParameter parameter) { - IsFolder = true; - Path = item.Path; - AccessControlList = FileSecurityHelpers.GetAccessControlList(Path, IsFolder); - SelectedAccessControlEntry = AccessControlList.AccessControlEntries.FirstOrDefault(); - Window = window; + _navigationParameter = parameter; + _window = parameter.Window; - InitializeCommands(); - } + switch (parameter.Parameter) + { + case ListedItem listedItem: + _path = listedItem.ItemPath; + _isFolder = listedItem.PrimaryItemAttribute == StorageItemTypes.Folder && !listedItem.IsShortcut; + break; + case DriveItem driveItem: + _path = driveItem.Path; + _isFolder = true; + break; + default: + var defaultlistedItem = (ListedItem)parameter.Parameter; + _path = defaultlistedItem.ItemPath; + _isFolder = defaultlistedItem.PrimaryItemAttribute == StorageItemTypes.Folder && !defaultlistedItem.IsShortcut; + break; + }; + + var error = FileSecurityHelpers.GetAccessControlList(_path, _isFolder, out _AccessControlList); + _SelectedAccessControlEntry = AccessControlList.AccessControlEntries.FirstOrDefault(); + + if (!AccessControlList.IsValid) + { + DisplayElements = false; + ErrorMessage = error == Win32Error.ERROR_ACCESS_DENIED + ? "SecurityRequireReadPermissions".GetLocalizedResource() + "\r\n" + "SecurityClickAdvancedPermissions".GetLocalizedResource() + : "SecurityUnableToDisplayPermissions".GetLocalizedResource(); + } + else + { + DisplayElements = true; + ErrorMessage = string.Empty; + } - private void InitializeCommands() - { - AddAccessControlEntryCommand = new AsyncRelayCommand(AddAccessControlEntry, () => AccessControlList is not null && AccessControlList.IsValid); - RemoveAccessControlEntryCommand = new RelayCommand(RemoveAccessControlEntry, () => AccessControlList is not null && AccessControlList.IsValid && SelectedAccessControlEntry is not null); + AddAccessControlEntryCommand = new AsyncRelayCommand(ExecuteAddAccessControlEntryCommand); + RemoveAccessControlEntryCommand = new AsyncRelayCommand(ExecuteRemoveAccessControlEntryCommand); } - private async Task AddAccessControlEntry() + private async Task ExecuteAddAccessControlEntryCommand() { - // Pick an user/group - var sid = await OpenObjectPicker(); + // Pick an user or a group with Object Picker UI + var sid = await FileOperationsHelpers.OpenObjectPickerAsync(FilePropertiesHelpers.GetWindowHandle(_window).ToInt64()); if (string.IsNullOrEmpty(sid)) return; - // Initialize - var ace = FileSecurityHelpers.InitializeDefaultAccessControlEntry(IsFolder, sid); - AccessControlList.AccessControlEntries.Add(ace); + await App.Window.DispatcherQueue.EnqueueAsync(() => + { + // Run Win32API + var win32Result = FileSecurityHelpers.AddAccessControlEntry(_path, sid); - // Save - FileSecurityHelpers.SetAccessControlList(AccessControlList); + // Add a new ACE to the ACL + var ace = FileSecurityHelpers.InitializeDefaultAccessControlEntry(_isFolder, sid); + AccessControlList.AccessControlEntries.Insert(0, ace); + }); } - private void RemoveAccessControlEntry() + private async Task ExecuteRemoveAccessControlEntryCommand() { - // Remove - AccessControlList.AccessControlEntries.Remove(SelectedAccessControlEntry); + await App.Window.DispatcherQueue.EnqueueAsync(() => + { + // Get index of the ACE + var index = AccessControlList.AccessControlEntries.IndexOf(SelectedAccessControlEntry); - // Save - FileSecurityHelpers.SetAccessControlList(AccessControlList); - } + // Run Win32API + var win32Result = FileSecurityHelpers.RemoveAccessControlEntry(_path, (uint)index); - public bool SaveChangedAccessControlList() - { - return false; - } + // Remove the ACE + AccessControlList.AccessControlEntries.Remove(SelectedAccessControlEntry); + + if (AccessControlList.AccessControlEntries.Count == 0) + return; - private Task OpenObjectPicker() - => FileOperationsHelpers.OpenObjectPickerAsync(FilePropertiesHelpers.GetWindowHandle(Window).ToInt64()); + // Re-select item + SelectedAccessControlEntry = AccessControlList.AccessControlEntries.First(); + }); + } } } diff --git a/src/Files.App/Views/Properties/SecurityAdvancedPage.xaml b/src/Files.App/Views/Properties/SecurityAdvancedPage.xaml index 10cf4c0035ab..535189479db8 100644 --- a/src/Files.App/Views/Properties/SecurityAdvancedPage.xaml +++ b/src/Files.App/Views/Properties/SecurityAdvancedPage.xaml @@ -10,8 +10,9 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:security="using:Files.App.Filesystem.Security" xmlns:toolkit="using:CommunityToolkit.WinUI.UI.Controls" - xmlns:toolkitconverters="using:CommunityToolkit.WinUI.UI.Converters" + xmlns:uc="using:Files.App.UserControls" xmlns:vm="using:Files.App.ViewModels.Properties" + xmlns:wctconverters="using:CommunityToolkit.WinUI.UI.Converters" DataContext="{x:Bind SecurityAdvancedViewModel, Mode=OneWay}" Tag="Security" mc:Ignorable="d"> @@ -40,15 +41,23 @@ - - + - + + @@ -64,8 +73,9 @@ + @@ -86,33 +96,41 @@ Orientation="Horizontal" Spacing="8"> + + + ToolTipService.ToolTip="{x:Bind SecurityAdvancedViewModel.AccessControlList.Owner.FullNameHumanized, Mode=OneWay}"> - + + + x:Load="{x:Bind SecurityAdvancedViewModel.AccessControlList.Owner.IsValid, Converter={StaticResource BoolNegationConverter}, Mode=OneWay}" + IsTextSelectionEnabled="True" + Style="{StaticResource BodyTextBlockStyle}" + Text="Unable to display current owner." /> - + + - + @@ -121,11 +139,25 @@ + + + @@ -137,6 +169,7 @@ + @@ -158,7 +191,8 @@ Padding="8" Background="Transparent" BorderBrush="Transparent" - Command="{x:Bind SecurityAdvancedViewModel.AddAccessControlEntryCommand, Mode=OneWay}"> + Command="{x:Bind SecurityAdvancedViewModel.AddAccessControlEntryCommand, Mode=OneWay}" + IsEnabled="{x:Bind SecurityAdvancedViewModel.IsAddAccessControlEntryButtonEnabled, Mode=OneWay}"> @@ -166,7 +200,8 @@ Padding="8" Background="Transparent" BorderBrush="Transparent" - Command="{x:Bind SecurityAdvancedViewModel.RemoveAccessControlEntryCommand, Mode=OneWay}"> + Command="{x:Bind SecurityAdvancedViewModel.RemoveAccessControlEntryCommand, Mode=OneWay}" + IsEnabled="{x:Bind SecurityAdvancedViewModel.IsDeleteAccessControlEntryButtonEnabled, Mode=OneWay}"> @@ -174,19 +209,21 @@ + + - + - - - - + + + + + + Glyph="{x:Bind Principal.Glyph, Mode=OneWay}" /> + + @@ -395,6 +435,7 @@ + - + + + @@ -456,7 +499,7 @@ - +