diff --git a/src/Files.App/DataModels/NavigationViewItemButtonStyleItem.cs b/src/Files.App/DataModels/NavigationViewItemButtonStyleItem.cs new file mode 100644 index 000000000000..c720df06cccd --- /dev/null +++ b/src/Files.App/DataModels/NavigationViewItemButtonStyleItem.cs @@ -0,0 +1,34 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using Files.Backend.Enums; +using Microsoft.UI.Xaml; + +namespace Files.App.DataModels +{ + public class NavigationViewItemButtonStyleItem : ObservableObject + { + public string? Name; + + public PropertiesNavigationViewItemType ItemType; + + private Style _OpacityIconStyle = (Style)Application.Current.Resources["ColorIconGeneralProperties"]; + public Style OpacityIconStyle + { + get => _OpacityIconStyle; + set => SetProperty(ref _OpacityIconStyle, value); + } + + private bool _IsSelected; + public bool IsSelected + { + get => _IsSelected; + set => SetProperty(ref _IsSelected, value); + } + + private bool _IsCompact; + public bool IsCompact + { + get => _IsCompact; + set => SetProperty(ref _IsCompact, value); + } + } +} diff --git a/src/Files.App/DataModels/PropertiesNavigationViewItemFactory.cs b/src/Files.App/DataModels/PropertiesNavigationViewItemFactory.cs new file mode 100644 index 000000000000..2c67572803d8 --- /dev/null +++ b/src/Files.App/DataModels/PropertiesNavigationViewItemFactory.cs @@ -0,0 +1,139 @@ +using System.Collections.ObjectModel; +using Files.App.DataModels.NavigationControlItems; +using Files.App.Extensions; +using Files.App.Filesystem; +using Files.Backend.Enums; +using Files.Backend.Helpers; +using Microsoft.UI.Xaml; +using System.Collections.Generic; +using System.Linq; + +namespace Files.App.DataModels +{ + public static class PropertiesNavigationViewItemFactory + { + public static ObservableCollection Initialize(object item) + { + ObservableCollection PropertiesNavigationViewItems = new(); + + var generalItem = new NavigationViewItemButtonStyleItem() + { + Name = "General".GetLocalizedResource(), + ItemType = PropertiesNavigationViewItemType.General, + OpacityIconStyle = (Style)Application.Current.Resources["ColorIconGeneralProperties"], + }; + var securityItem = new NavigationViewItemButtonStyleItem() + { + Name = "Security".GetLocalizedResource(), + ItemType = PropertiesNavigationViewItemType.Security, + OpacityIconStyle = (Style)Application.Current.Resources["ColorIconSecurityProperties"], + }; + var hashesItem = new NavigationViewItemButtonStyleItem() + { + Name = "Hashes".GetLocalizedResource(), + ItemType = PropertiesNavigationViewItemType.Hashes, + OpacityIconStyle = (Style)Application.Current.Resources["ColorIconHashesProperties"], + }; + var shortcutItem = new NavigationViewItemButtonStyleItem() + { + Name = "Shortcut".GetLocalizedResource(), + ItemType = PropertiesNavigationViewItemType.Shortcut, + OpacityIconStyle = (Style)Application.Current.Resources["ColorIconShortcutProperties"], + }; + var libraryItem = new NavigationViewItemButtonStyleItem() + { + Name = "Library".GetLocalizedResource(), + ItemType = PropertiesNavigationViewItemType.Library, + OpacityIconStyle = (Style)Application.Current.Resources["ColorIconLibraryProperties"], + }; + var detailsItem = new NavigationViewItemButtonStyleItem() + { + Name = "Details".GetLocalizedResource(), + ItemType = PropertiesNavigationViewItemType.Details, + OpacityIconStyle = (Style)Application.Current.Resources["ColorIconDetailsProperties"], + }; + var customizationItem = new NavigationViewItemButtonStyleItem() + { + Name = "Customization".GetLocalizedResource(), + ItemType = PropertiesNavigationViewItemType.Customization, + OpacityIconStyle = (Style)Application.Current.Resources["ColorIconCustomizationProperties"], + }; + var compatibilityItem = new NavigationViewItemButtonStyleItem() + { + Name = "Compatibility".GetLocalizedResource(), + ItemType = PropertiesNavigationViewItemType.Compatibility, + OpacityIconStyle = (Style)Application.Current.Resources["ColorIconCompatibilityProperties"], + }; + + PropertiesNavigationViewItems.Add(generalItem); + PropertiesNavigationViewItems.Add(securityItem); + PropertiesNavigationViewItems.Add(hashesItem); + PropertiesNavigationViewItems.Add(shortcutItem); + PropertiesNavigationViewItems.Add(libraryItem); + PropertiesNavigationViewItems.Add(detailsItem); + PropertiesNavigationViewItems.Add(customizationItem); + PropertiesNavigationViewItems.Add(compatibilityItem); + + if (item is List listedItems) + { + var commonFileExt = listedItems.Select(x => x.FileExtension).Distinct().Count() == 1 ? listedItems.First().FileExtension : null; + var compatibilityItemEnabled = listedItems.All(listedItem => FileExtensionHelpers.IsExecutableFile(listedItem is ShortcutItem sht ? sht.TargetPath : commonFileExt, true)); + + if (!compatibilityItemEnabled) + PropertiesNavigationViewItems.Remove(compatibilityItem); + + PropertiesNavigationViewItems.Remove(libraryItem); + PropertiesNavigationViewItems.Remove(shortcutItem); + PropertiesNavigationViewItems.Remove(detailsItem); + PropertiesNavigationViewItems.Remove(securityItem); + PropertiesNavigationViewItems.Remove(customizationItem); + PropertiesNavigationViewItems.Remove(hashesItem); + } + else if (item is ListedItem listedItem) + { + var isShortcut = listedItem.IsShortcut; + var isLibrary = listedItem.IsLibrary; + var fileExt = listedItem.FileExtension; + var isFolder = listedItem.PrimaryItemAttribute == Windows.Storage.StorageItemTypes.Folder; + + var securityItemEnabled = !isLibrary && !listedItem.IsRecycleBinItem; + var hashItemEnabled = !(isFolder && !listedItem.IsArchive) && !isLibrary && !listedItem.IsRecycleBinItem; + var detailsItemEnabled = fileExt is not null && !isShortcut && !isLibrary; + var customizationItemEnabled = !isLibrary && ((isFolder && !listedItem.IsArchive) || (isShortcut && !listedItem.IsLinkItem)); + var compatibilityItemEnabled = FileExtensionHelpers.IsExecutableFile(listedItem is ShortcutItem sht ? sht.TargetPath : fileExt, true); + + if (!securityItemEnabled) + PropertiesNavigationViewItems.Remove(securityItem); + + if (!hashItemEnabled) + PropertiesNavigationViewItems.Remove(hashesItem); + + if (!isShortcut) + PropertiesNavigationViewItems.Remove(shortcutItem); + + if (!isLibrary) + PropertiesNavigationViewItems.Remove(libraryItem); + + if (!detailsItemEnabled) + PropertiesNavigationViewItems.Remove(detailsItem); + + if (!customizationItemEnabled) + PropertiesNavigationViewItems.Remove(customizationItem); + + if (!compatibilityItemEnabled) + PropertiesNavigationViewItems.Remove(compatibilityItem); + } + else if (item is DriveItem) + { + PropertiesNavigationViewItems.Remove(hashesItem); + PropertiesNavigationViewItems.Remove(shortcutItem); + PropertiesNavigationViewItems.Remove(libraryItem); + PropertiesNavigationViewItems.Remove(detailsItem); + PropertiesNavigationViewItems.Remove(customizationItem); + PropertiesNavigationViewItems.Remove(compatibilityItem); + } + + return PropertiesNavigationViewItems; + } + } +} diff --git a/src/Files.App/DataModels/PropertiesPageNavigationParameter.cs b/src/Files.App/DataModels/PropertiesPageNavigationParameter.cs new file mode 100644 index 000000000000..3f94d79f9b69 --- /dev/null +++ b/src/Files.App/DataModels/PropertiesPageNavigationParameter.cs @@ -0,0 +1,19 @@ +using Microsoft.UI.Windowing; +using Microsoft.UI.Xaml; +using System.Threading; + +namespace Files.App.DataModels +{ + public class PropertiesPageNavigationParameter + { + public CancellationTokenSource CancellationTokenSource; + + public object Parameter; + + public IShellPage AppInstance; + + public Window Window; + + public AppWindow AppWindow; + } +} diff --git a/src/Files.App/DataModels/SecurityAdvancedAccessControlItemFactory.cs b/src/Files.App/DataModels/SecurityAdvancedAccessControlItemFactory.cs new file mode 100644 index 000000000000..810f1d467208 --- /dev/null +++ b/src/Files.App/DataModels/SecurityAdvancedAccessControlItemFactory.cs @@ -0,0 +1,210 @@ +using Files.App.Extensions; +using Files.App.Filesystem.Security; +using System.Collections.Generic; +using System.Collections.ObjectModel; + +namespace Files.App.DataModels +{ + public static class SecurityAdvancedAccessControlItemFactory + { + /// + /// Returned list list will be shown in an ACE item in security advanced page + /// + /// + /// + /// + /// + /// + public static ObservableCollection Initialize(AccessControlEntry current, bool isAdvanced, bool isInherited, bool isFolder) + { + List accessControls; + + if (isAdvanced) + { + accessControls = new() + { + new(current) + { + AccessMask = AccessMaskFlags.FullControl, + AccessMaskName = "SecurityFullControlLabel/Text".GetLocalizedResource(), + IsEditable = !isInherited + }, + new(current) + { + AccessMask = AccessMaskFlags.Traverse, + AccessMaskName = "SecurityTraverseLabel/Text".GetLocalizedResource(), + IsEditable = !isInherited + }, + new(current) + { + AccessMask = AccessMaskFlags.ExecuteFile, + AccessMaskName = "SecurityExecuteFileLabel/Text".GetLocalizedResource(), + IsEditable = !isInherited + }, + new(current) + { + AccessMask = AccessMaskFlags.ListDirectory, + AccessMaskName = "SecurityListDirectoryLabel/Text".GetLocalizedResource(), + IsEditable = !isInherited + }, + new(current) + { + AccessMask = AccessMaskFlags.ReadData, + AccessMaskName = "SecurityReadDataLabel/Text".GetLocalizedResource(), + IsEditable = !isInherited + }, + new(current) + { + AccessMask = AccessMaskFlags.ReadAttributes, + AccessMaskName = "SecurityReadAttributesLabel/Text".GetLocalizedResource(), + IsEditable = !isInherited + }, + new(current) + { + AccessMask = AccessMaskFlags.ReadExtendedAttributes, + AccessMaskName = "SecurityReadExtendedAttributesLabel/Text".GetLocalizedResource(), + IsEditable = !isInherited + }, + new(current) + { + AccessMask = AccessMaskFlags.CreateFiles, + AccessMaskName = "SecurityCreateFilesLabel/Text".GetLocalizedResource(), + IsEditable = !isInherited + }, + new(current) + { + AccessMask = AccessMaskFlags.CreateDirectories, + AccessMaskName = "SecurityCreateDirectoriesLabel/Text".GetLocalizedResource(), + IsEditable = !isInherited + }, + new(current) + { + AccessMask = AccessMaskFlags.WriteData, + AccessMaskName = "SecurityWriteDataLabel/Text".GetLocalizedResource(), + IsEditable = !isInherited + }, + new(current) + { + AccessMask = AccessMaskFlags.AppendData, + AccessMaskName = "SecurityAppendDataLabel/Text".GetLocalizedResource(), + IsEditable = !isInherited + }, + new(current) + { + AccessMask = AccessMaskFlags.WriteAttributes, + AccessMaskName = "SecurityWriteAttributesLabel/Text".GetLocalizedResource(), + IsEditable = !isInherited + }, + new(current) + { + AccessMask = AccessMaskFlags.WriteExtendedAttributes, + AccessMaskName = "SecurityWriteExtendedAttributesLabel/Text".GetLocalizedResource(), + IsEditable = !isInherited + }, + new(current) + { + AccessMask = AccessMaskFlags.DeleteSubdirectoriesAndFiles, + AccessMaskName = "SecurityDeleteSubdirectoriesAndFilesLabel/Text".GetLocalizedResource(), + IsEditable = !isInherited + }, + new(current) + { + AccessMask = AccessMaskFlags.Delete, + AccessMaskName = "Delete".GetLocalizedResource(), + IsEditable = !isInherited + }, + new(current) + { + AccessMask = AccessMaskFlags.ReadPermissions, + AccessMaskName = "SecurityReadPermissionsLabel/Text".GetLocalizedResource(), + IsEditable = !isInherited + }, + new(current) + { + AccessMask = AccessMaskFlags.ChangePermissions, + AccessMaskName = "SecurityChangePermissionsLabel/Text".GetLocalizedResource(), + IsEditable = !isInherited + }, + new(current) + { + AccessMask = AccessMaskFlags.TakeOwnership, + AccessMaskName = "SecurityTakeOwnershipLabel/Text".GetLocalizedResource(), + IsEditable = !isInherited + } + }; + + if (isFolder) + { + accessControls.RemoveAll(x => + x.AccessMask == AccessMaskFlags.ExecuteFile || + x.AccessMask == AccessMaskFlags.ReadData || + x.AccessMask == AccessMaskFlags.WriteData || + x.AccessMask == AccessMaskFlags.AppendData); + } + else + { + accessControls.RemoveAll(x => + x.AccessMask == AccessMaskFlags.Traverse || + x.AccessMask == AccessMaskFlags.ListDirectory || + x.AccessMask == AccessMaskFlags.CreateFiles || + x.AccessMask == AccessMaskFlags.CreateDirectories || + x.AccessMask == AccessMaskFlags.DeleteSubdirectoriesAndFiles); + } + } + else + { + accessControls = new() + { + new(current) + { + AccessMask = AccessMaskFlags.FullControl, + AccessMaskName = "SecurityFullControlLabel/Text".GetLocalizedResource(), + IsEditable = !isInherited + }, + new(current) + { + AccessMask = AccessMaskFlags.Modify, + AccessMaskName = "SecurityModifyLabel/Text".GetLocalizedResource(), + IsEditable = !isInherited + }, + new(current) + { + AccessMask = AccessMaskFlags.ReadAndExecute, + AccessMaskName = "SecurityReadAndExecuteLabel/Text".GetLocalizedResource(), + IsEditable = !isInherited + }, + new(current) + { + AccessMask = AccessMaskFlags.ListDirectory, + AccessMaskName = "SecurityListDirectoryLabel/Text".GetLocalizedResource(), + IsEditable = !isInherited + }, + new(current) + { + AccessMask = AccessMaskFlags.Read, + AccessMaskName = "SecurityReadLabel/Text".GetLocalizedResource(), + IsEditable = !isInherited + }, + new(current) + { + AccessMask = AccessMaskFlags.Write, + AccessMaskName = "Write".GetLocalizedResource(), + IsEditable = !isInherited + }, + new(current, false) + { + AccessMaskName = "SecuritySpecialLabel/Text".GetLocalizedResource() + } + }; + + if (!isFolder) + { + accessControls.RemoveAll(x => + x.AccessMask == AccessMaskFlags.ListDirectory); + } + } + + return new ObservableCollection(accessControls); + } + } +} diff --git a/src/Files.App/Filesystem/Security/AccessControlEntry.cs b/src/Files.App/Filesystem/Security/AccessControlEntry.cs index 191b4c1f5a64..7a4c1404bcb3 100644 --- a/src/Files.App/Filesystem/Security/AccessControlEntry.cs +++ b/src/Files.App/Filesystem/Security/AccessControlEntry.cs @@ -1,8 +1,10 @@ using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; +using Files.App.DataModels; using Files.App.Extensions; using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; namespace Files.App.Filesystem.Security @@ -12,72 +14,142 @@ namespace Files.App.Filesystem.Security /// public class AccessControlEntry : ObservableObject { - public readonly bool IsFolder; - - public string? PrincipalSid { get; set; } + public bool IsFolder { get; set; } public Principal Principal { get; set; } - private AccessControlType _accessControlType; + private AccessControlType _AccessControlType; public AccessControlType AccessControlType { - get => _accessControlType; + get => _AccessControlType; set { - if (SetProperty(ref _accessControlType, value)) + // Update access control type glyph + if (SetProperty(ref _AccessControlType, value)) OnPropertyChanged(nameof(AccessControlTypeGlyph)); } } - public string AccessControlTypeHumanized - => AccessControlType switch - { - AccessControlType.Allow => "Allow", - _ => "Deny" // AccessControlType.Deny - }; - - public string AccessControlTypeGlyph - => AccessControlType switch + private AccessMaskFlags _AccessMaskFlags; + public AccessMaskFlags AccessMaskFlags + { + get => _AccessMaskFlags; + set { - AccessControlType.Allow => "\xE73E", - _ => "\xF140" // AccessControlType.Deny - }; - - public bool IsInherited { get; set; } + if (SetProperty(ref _AccessMaskFlags, value)) + OnPropertyChanged(nameof(AccessMaskFlagsHumanized)); + } + } - private InheritanceFlags _inheritanceFlags; + private InheritanceFlags _InheritanceFlags; public InheritanceFlags InheritanceFlags { - get => _inheritanceFlags; + get => _InheritanceFlags; set { - if (SetProperty(ref _inheritanceFlags, value)) + if (SetProperty(ref _InheritanceFlags, value)) OnPropertyChanged(nameof(InheritanceFlagsHumanized)); } } - private PropagationFlags _propagationFlags; + private PropagationFlags _PropagationFlags; public PropagationFlags PropagationFlags { - get => _propagationFlags; + get => _PropagationFlags; set { - if (SetProperty(ref _propagationFlags, value)) + if (SetProperty(ref _PropagationFlags, value)) OnPropertyChanged(nameof(InheritanceFlagsHumanized)); } } - private AccessMaskFlags _accessMaskFlags; - public AccessMaskFlags AccessMaskFlags + private AccessMaskFlags _AllowedAccessMaskFlags; + public AccessMaskFlags AllowedAccessMaskFlags { - get => _accessMaskFlags; + get => _AllowedAccessMaskFlags; set { - if (SetProperty(ref _accessMaskFlags, value)) - OnPropertyChanged(nameof(AccessMaskFlagsHumanized)); + if (SetProperty(ref _AllowedAccessMaskFlags, value)) + { + OnPropertyChanged(nameof(AllowedWriteAccess)); + OnPropertyChanged(nameof(AllowedFullControlAccess)); + OnPropertyChanged(nameof(AllowedListDirectoryAccess)); + OnPropertyChanged(nameof(AllowedModifyAccess)); + OnPropertyChanged(nameof(AllowedReadAccess)); + OnPropertyChanged(nameof(AllowedReadAndExecuteAccess)); + } } } + private AccessMaskFlags _DeniedAccessMaskFlags; + public AccessMaskFlags DeniedAccessMaskFlags + { + get => _DeniedAccessMaskFlags; + set + { + if (SetProperty(ref _DeniedAccessMaskFlags, value)) + { + OnPropertyChanged(nameof(DeniedWriteAccess)); + OnPropertyChanged(nameof(DeniedFullControlAccess)); + OnPropertyChanged(nameof(DeniedListDirectoryAccess)); + OnPropertyChanged(nameof(DeniedModifyAccess)); + OnPropertyChanged(nameof(DeniedReadAccess)); + OnPropertyChanged(nameof(DeniedReadAndExecuteAccess)); + } + } + } + + public AccessMaskFlags InheritedAllowAccessMaskFlags { get; set; } + + public AccessMaskFlags InheritedDenyAccessMaskFlags { get; set; } + + private bool _IsSelected; + public bool IsSelected + { + get => _IsSelected; + set + { + if (SetProperty(ref _IsSelected, value)) + { + if (!value) + AreAdvancedPermissionsShown = false; + + OnPropertyChanged(nameof(IsEditEnabled)); + } + } + } + + private bool _AreAdvancedPermissionsShown; + public bool AreAdvancedPermissionsShown + { + get => _AreAdvancedPermissionsShown; + set + { + // Reinitialize list + if (SetProperty(ref _AreAdvancedPermissionsShown, value)) + AccessMaskItemList = SecurityAdvancedAccessControlItemFactory.Initialize(this, AreAdvancedPermissionsShown, 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 @@ -133,86 +205,9 @@ public string InheritanceFlagsHumanized } } - private bool _isSelected; - public bool IsSelected - { - get => _isSelected; - set - { - if (SetProperty(ref _isSelected, value)) - { - if (!value) - AreAdvancedPermissionsShown = false; - - OnPropertyChanged(nameof(IsEditEnabled)); - } - } - } - - private bool _areAdvancedPermissionsShown; - public bool AreAdvancedPermissionsShown - { - get => _areAdvancedPermissionsShown; - set - { - // Reconstruct list - if (SetProperty(ref _areAdvancedPermissionsShown, value)) - AccessMaskItemList = GetAllAccessMaskList(); - } - } - - public bool IsEditEnabled - => IsSelected && !IsInherited; - - private List _accessMaskItemList; - public List AccessMaskItemList - { - get => _accessMaskItemList; - set => SetProperty(ref _accessMaskItemList, value); - } - - public RelayCommand ChangeAccessControlTypeCommand { get; set; } - public RelayCommand ChangeInheritanceFlagsCommand { get; set; } - - public AccessMaskFlags InheritedAllowAccessMaskFlags { get; set; } - public AccessMaskFlags InheritedDenyAccessMaskFlags { get; set; } - - private AccessMaskFlags _allowedAccessMaskFlags; - public AccessMaskFlags AllowedAccessMaskFlags - { - get => _allowedAccessMaskFlags; - set - { - if (SetProperty(ref _allowedAccessMaskFlags, value)) - { - OnPropertyChanged(nameof(AllowedWriteAccess)); - OnPropertyChanged(nameof(AllowedFullControlAccess)); - OnPropertyChanged(nameof(AllowedListDirectoryAccess)); - OnPropertyChanged(nameof(AllowedModifyAccess)); - OnPropertyChanged(nameof(AllowedReadAccess)); - OnPropertyChanged(nameof(AllowedReadAndExecuteAccess)); - } - } - } - - private AccessMaskFlags _deniedAccessMaskFlags; - public AccessMaskFlags DeniedAccessMaskFlags - { - get => _deniedAccessMaskFlags; - set - { - if (SetProperty(ref _deniedAccessMaskFlags, value)) - { - OnPropertyChanged(nameof(DeniedWriteAccess)); - OnPropertyChanged(nameof(DeniedFullControlAccess)); - OnPropertyChanged(nameof(DeniedListDirectoryAccess)); - OnPropertyChanged(nameof(DeniedModifyAccess)); - OnPropertyChanged(nameof(DeniedReadAccess)); - OnPropertyChanged(nameof(DeniedReadAndExecuteAccess)); - } - } - } + public ObservableCollection AccessMaskItemList { get; set; } + #region Security page public bool WriteAccess => AccessMaskFlags.HasFlag(AccessMaskFlags.Write); public bool ReadAccess => AccessMaskFlags.HasFlag(AccessMaskFlags.Read); public bool ListDirectoryAccess => AccessMaskFlags.HasFlag(AccessMaskFlags.ListDirectory); @@ -228,20 +223,22 @@ public bool SpecialAccess (ReadAccess ? ~AccessMaskFlags.Read : AccessMaskFlags.FullControl) & (WriteAccess ? ~AccessMaskFlags.Write : AccessMaskFlags.FullControl)) != 0; - public bool AllowedInheritedWriteAccess => InheritedAllowAccessMaskFlags.HasFlag(AccessMaskFlags.Write); - public bool AllowedInheritedReadAccess => InheritedAllowAccessMaskFlags.HasFlag(AccessMaskFlags.Read); - public bool AllowedInheritedListDirectoryAccess => InheritedAllowAccessMaskFlags.HasFlag(AccessMaskFlags.ListDirectory); + public bool AllowedInheritedWriteAccess => InheritedAllowAccessMaskFlags.HasFlag(AccessMaskFlags.Write); + public bool AllowedInheritedReadAccess => InheritedAllowAccessMaskFlags.HasFlag(AccessMaskFlags.Read); + public bool AllowedInheritedListDirectoryAccess => InheritedAllowAccessMaskFlags.HasFlag(AccessMaskFlags.ListDirectory); public bool AllowedInheritedReadAndExecuteAccess => InheritedAllowAccessMaskFlags.HasFlag(AccessMaskFlags.ReadAndExecute); - public bool AllowedInheritedModifyAccess => InheritedAllowAccessMaskFlags.HasFlag(AccessMaskFlags.Modify); - public bool AllowedInheritedFullControlAccess => InheritedAllowAccessMaskFlags.HasFlag(AccessMaskFlags.FullControl); - - public bool DeniedInheritedWriteAccess => InheritedDenyAccessMaskFlags.HasFlag(AccessMaskFlags.Write); - public bool DeniedInheritedReadAccess => InheritedDenyAccessMaskFlags.HasFlag(AccessMaskFlags.Read); - public bool DeniedInheritedListDirectoryAccess => InheritedDenyAccessMaskFlags.HasFlag(AccessMaskFlags.ListDirectory); - public bool DeniedInheritedReadAndExecuteAccess => InheritedDenyAccessMaskFlags.HasFlag(AccessMaskFlags.ReadAndExecute); - public bool DeniedInheritedModifyAccess => InheritedDenyAccessMaskFlags.HasFlag(AccessMaskFlags.Modify); - public bool DeniedInheritedFullControlAccess => InheritedDenyAccessMaskFlags.HasFlag(AccessMaskFlags.FullControl); - + public bool AllowedInheritedModifyAccess => InheritedAllowAccessMaskFlags.HasFlag(AccessMaskFlags.Modify); + public bool AllowedInheritedFullControlAccess => InheritedAllowAccessMaskFlags.HasFlag(AccessMaskFlags.FullControl); + + public bool DeniedInheritedWriteAccess => InheritedDenyAccessMaskFlags.HasFlag(AccessMaskFlags.Write); + public bool DeniedInheritedReadAccess => InheritedDenyAccessMaskFlags.HasFlag(AccessMaskFlags.Read); + public bool DeniedInheritedListDirectoryAccess => InheritedDenyAccessMaskFlags.HasFlag(AccessMaskFlags.ListDirectory); + public bool DeniedInheritedReadAndExecuteAccess => InheritedDenyAccessMaskFlags.HasFlag(AccessMaskFlags.ReadAndExecute); + public bool DeniedInheritedModifyAccess => InheritedDenyAccessMaskFlags.HasFlag(AccessMaskFlags.Modify); + public bool DeniedInheritedFullControlAccess => InheritedDenyAccessMaskFlags.HasFlag(AccessMaskFlags.FullControl); + #endregion + + #region SecurityAdvanced page public bool AllowedWriteAccess { get => AllowedAccessMaskFlags.HasFlag(AccessMaskFlags.Write) || AllowedInheritedWriteAccess; @@ -313,12 +310,14 @@ public bool DeniedFullControlAccess get => DeniedAccessMaskFlags.HasFlag(AccessMaskFlags.FullControl) || DeniedInheritedFullControlAccess; set => ToggleDenyAccess(AccessMaskFlags.FullControl, value); } + #endregion - public AccessControlEntry(AccessControlEntryPrimitiveMapping accessRule, bool isFolder) - { - IsFolder = isFolder; + public IRelayCommand ChangeAccessControlTypeCommand { get; set; } + public IRelayCommand ChangeInheritanceFlagsCommand { get; set; } - AccessMaskItemList = GetAllAccessMaskList(); + public AccessControlEntry(bool isFolder, string ownerSid, AccessControlType type, AccessMaskFlags accessMaskFlags, bool isInherited, InheritanceFlags inheritanceFlags, PropagationFlags propagationFlags) + { + AccessMaskItemList = SecurityAdvancedAccessControlItemFactory.Initialize(this, AreAdvancedPermissionsShown, IsInherited, IsFolder); ChangeAccessControlTypeCommand = new RelayCommand(x => { @@ -333,13 +332,13 @@ public AccessControlEntry(AccessControlEntryPrimitiveMapping accessRule, bool is PropagationFlags = Enum.Parse(parts[1]); }); - AccessControlType = (AccessControlType)accessRule.AccessControlType; - AccessMaskFlags = (AccessMaskFlags)accessRule.FileSystemRights; - PrincipalSid = accessRule.PrincipalSid; - Principal = Principal.FromSid(accessRule.PrincipalSid); - IsInherited = accessRule.IsInherited; - InheritanceFlags = (InheritanceFlags)accessRule.InheritanceFlags; - PropagationFlags = (PropagationFlags)accessRule.PropagationFlags; + IsFolder = isFolder; + Principal = Principal.FromSid(ownerSid); + AccessControlType = type; + AccessMaskFlags = accessMaskFlags; + IsInherited = isInherited; + InheritanceFlags = inheritanceFlags; + PropagationFlags = propagationFlags; switch (AccessControlType) { @@ -360,229 +359,34 @@ public AccessControlEntry(AccessControlEntryPrimitiveMapping accessRule, bool is private void ToggleAllowAccess(AccessMaskFlags accessMask, bool value) { - if (value && !AllowedAccessMaskFlags.HasFlag(accessMask) && !InheritedAllowAccessMaskFlags.HasFlag(accessMask)) + if (value && + !AllowedAccessMaskFlags.HasFlag(accessMask) && + !InheritedAllowAccessMaskFlags.HasFlag(accessMask)) { AllowedAccessMaskFlags |= accessMask; DeniedAccessMaskFlags &= ~accessMask; } - else if (!value && AllowedAccessMaskFlags.HasFlag(accessMask)) + else if (!value && + AllowedAccessMaskFlags.HasFlag(accessMask)) { AllowedAccessMaskFlags &= ~accessMask; } - - UpdateAccessControlEntry(); } private void ToggleDenyAccess(AccessMaskFlags accessMask, bool value) { - if (value && !DeniedAccessMaskFlags.HasFlag(accessMask) && !InheritedDenyAccessMaskFlags.HasFlag(accessMask)) + if (value && + !DeniedAccessMaskFlags.HasFlag(accessMask) && + !InheritedDenyAccessMaskFlags.HasFlag(accessMask)) { DeniedAccessMaskFlags |= accessMask; AllowedAccessMaskFlags &= ~accessMask; } - else if (!value && DeniedAccessMaskFlags.HasFlag(accessMask)) + else if (!value && + DeniedAccessMaskFlags.HasFlag(accessMask)) { DeniedAccessMaskFlags &= ~accessMask; } - - UpdateAccessControlEntry(); - } - - private void UpdateAccessControlEntry() - { - } - - private List GetAllAccessMaskList() - { - // This list will be shown in an ACE item in security advanced page - List accessControls; - - if (AreAdvancedPermissionsShown) - { - accessControls = new() - { - new AccessMaskItem(this) - { - AccessMask = AccessMaskFlags.FullControl, - AccessMaskName = "SecurityFullControlLabel/Text".GetLocalizedResource(), - IsEditable = !IsInherited - }, - new AccessMaskItem(this) - { - AccessMask = AccessMaskFlags.Traverse, - AccessMaskName = "SecurityTraverseLabel/Text".GetLocalizedResource(), - IsEditable = !IsInherited - }, - new AccessMaskItem(this) - { - AccessMask = AccessMaskFlags.ExecuteFile, - AccessMaskName = "SecurityExecuteFileLabel/Text".GetLocalizedResource(), - IsEditable = !IsInherited - }, - new AccessMaskItem(this) - { - AccessMask = AccessMaskFlags.ListDirectory, - AccessMaskName = "SecurityListDirectoryLabel/Text".GetLocalizedResource(), - IsEditable = !IsInherited - }, - new AccessMaskItem(this) - { - AccessMask = AccessMaskFlags.ReadData, - AccessMaskName = "SecurityReadDataLabel/Text".GetLocalizedResource(), - IsEditable = !IsInherited - }, - new AccessMaskItem(this) - { - AccessMask = AccessMaskFlags.ReadAttributes, - AccessMaskName = "SecurityReadAttributesLabel/Text".GetLocalizedResource(), - IsEditable = !IsInherited - }, - new AccessMaskItem(this) - { - AccessMask = AccessMaskFlags.ReadExtendedAttributes, - AccessMaskName = "SecurityReadExtendedAttributesLabel/Text".GetLocalizedResource(), - IsEditable = !IsInherited - }, - new AccessMaskItem(this) - { - AccessMask = AccessMaskFlags.CreateFiles, - AccessMaskName = "SecurityCreateFilesLabel/Text".GetLocalizedResource(), - IsEditable = !IsInherited - }, - new AccessMaskItem(this) - { - AccessMask = AccessMaskFlags.CreateDirectories, - AccessMaskName = "SecurityCreateDirectoriesLabel/Text".GetLocalizedResource(), - IsEditable = !IsInherited - }, - new AccessMaskItem(this) - { - AccessMask = AccessMaskFlags.WriteData, - AccessMaskName = "SecurityWriteDataLabel/Text".GetLocalizedResource(), - IsEditable = !IsInherited - }, - new AccessMaskItem(this) - { - AccessMask = AccessMaskFlags.AppendData, - AccessMaskName = "SecurityAppendDataLabel/Text".GetLocalizedResource(), - IsEditable = !IsInherited - }, - new AccessMaskItem(this) - { - AccessMask = AccessMaskFlags.WriteAttributes, - AccessMaskName = "SecurityWriteAttributesLabel/Text".GetLocalizedResource(), - IsEditable = !IsInherited - }, - new AccessMaskItem(this) - { - AccessMask = AccessMaskFlags.WriteExtendedAttributes, - AccessMaskName = "SecurityWriteExtendedAttributesLabel/Text".GetLocalizedResource(), - IsEditable = !IsInherited - }, - new AccessMaskItem(this) - { - AccessMask = AccessMaskFlags.DeleteSubdirectoriesAndFiles, - AccessMaskName = "SecurityDeleteSubdirectoriesAndFilesLabel/Text".GetLocalizedResource(), - IsEditable = !IsInherited - }, - new AccessMaskItem(this) - { - AccessMask = AccessMaskFlags.Delete, - AccessMaskName = "Delete".GetLocalizedResource(), - IsEditable = !IsInherited - }, - new AccessMaskItem(this) - { - AccessMask = AccessMaskFlags.ReadPermissions, - AccessMaskName = "SecurityReadPermissionsLabel/Text".GetLocalizedResource(), - IsEditable = !IsInherited - }, - new AccessMaskItem(this) - { - AccessMask = AccessMaskFlags.ChangePermissions, - AccessMaskName = "SecurityChangePermissionsLabel/Text".GetLocalizedResource(), - IsEditable = !IsInherited - }, - new AccessMaskItem(this) - { - AccessMask = AccessMaskFlags.TakeOwnership, - AccessMaskName = "SecurityTakeOwnershipLabel/Text".GetLocalizedResource(), - IsEditable = !IsInherited - } - }; - - if (IsFolder) - { - accessControls.RemoveAll(x => - x.AccessMask == AccessMaskFlags.ExecuteFile || - x.AccessMask == AccessMaskFlags.ReadData || - x.AccessMask == AccessMaskFlags.WriteData || - x.AccessMask == AccessMaskFlags.AppendData); - } - else - { - accessControls.RemoveAll(x => - x.AccessMask == AccessMaskFlags.Traverse || - x.AccessMask == AccessMaskFlags.ListDirectory || - x.AccessMask == AccessMaskFlags.CreateFiles || - x.AccessMask == AccessMaskFlags.CreateDirectories || - x.AccessMask == AccessMaskFlags.DeleteSubdirectoriesAndFiles); - } - } - else - { - accessControls = new() - { - new AccessMaskItem(this) - { - AccessMask = AccessMaskFlags.FullControl, - AccessMaskName = "SecurityFullControlLabel/Text".GetLocalizedResource(), - IsEditable = !IsInherited - }, - new AccessMaskItem(this) - { - AccessMask = AccessMaskFlags.Modify, - AccessMaskName = "SecurityModifyLabel/Text".GetLocalizedResource(), - IsEditable = !IsInherited - }, - new AccessMaskItem(this) - { - AccessMask = AccessMaskFlags.ReadAndExecute, - AccessMaskName = "SecurityReadAndExecuteLabel/Text".GetLocalizedResource(), - IsEditable = !IsInherited - }, - new AccessMaskItem(this) - { - AccessMask = AccessMaskFlags.ListDirectory, - AccessMaskName = "SecurityListDirectoryLabel/Text".GetLocalizedResource(), - IsEditable = !IsInherited - }, - new AccessMaskItem(this) - { - AccessMask = AccessMaskFlags.Read, - AccessMaskName = "SecurityReadLabel/Text".GetLocalizedResource(), - IsEditable = !IsInherited - }, - new AccessMaskItem(this) - { - AccessMask = AccessMaskFlags.Write, - AccessMaskName = "Write".GetLocalizedResource(), - IsEditable = !IsInherited - }, - new AccessMaskItem(this, false) - { - AccessMaskName = "SecuritySpecialLabel/Text".GetLocalizedResource() - } - }; - - if (!IsFolder) - { - accessControls.RemoveAll(x => - x.AccessMask == AccessMaskFlags.ListDirectory); - } - } - - return accessControls; } } } diff --git a/src/Files.App/Filesystem/Security/AccessControlEntryPrimitiveMapping.cs b/src/Files.App/Filesystem/Security/AccessControlEntryPrimitiveMapping.cs deleted file mode 100644 index 2f58ded862b7..000000000000 --- a/src/Files.App/Filesystem/Security/AccessControlEntryPrimitiveMapping.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System.Security.AccessControl; -using System.Security.Principal; - -namespace Files.App.Filesystem.Security -{ - /// - /// Represents a primitive ACE for mapping C# classes. - /// - public class AccessControlEntryPrimitiveMapping - { - public System.Security.AccessControl.AccessControlType AccessControlType { get; set; } - - public FileSystemRights FileSystemRights { get; set; } - - public string? PrincipalSid { get; set; } - - public bool IsInherited { get; set; } - - public System.Security.AccessControl.InheritanceFlags InheritanceFlags { get; set; } - - public System.Security.AccessControl.PropagationFlags PropagationFlags { get; set; } - - public static AccessControlEntryPrimitiveMapping FromFileSystemAccessRule(FileSystemAccessRule accessControl) - { - return new() - { - AccessControlType = accessControl.AccessControlType, - FileSystemRights = accessControl.FileSystemRights, - IsInherited = accessControl.IsInherited, - PrincipalSid = accessControl.IdentityReference.Value, - InheritanceFlags = accessControl.InheritanceFlags, - PropagationFlags = accessControl.PropagationFlags - }; - } - - public FileSystemAccessRule ToFileSystemAccessRule() - { - return new( - identity: new SecurityIdentifier(PrincipalSid), - fileSystemRights: FileSystemRights, - inheritanceFlags: InheritanceFlags, - propagationFlags: PropagationFlags, - type: AccessControlType); - } - } -} diff --git a/src/Files.App/Filesystem/Security/AccessControlList.cs b/src/Files.App/Filesystem/Security/AccessControlList.cs index be748fc8d75e..780e81cab190 100644 --- a/src/Files.App/Filesystem/Security/AccessControlList.cs +++ b/src/Files.App/Filesystem/Security/AccessControlList.cs @@ -1,11 +1,4 @@ -using Files.App.Shell; -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.IO; -using System.Linq; -using System.Security.AccessControl; -using System.Security.Principal; +using System.Collections.ObjectModel; namespace Files.App.Filesystem.Security { @@ -14,163 +7,38 @@ namespace Files.App.Filesystem.Security /// public class AccessControlList { - public string? OwnerSID { get; private set; } - - public Principal Owner { get; private set; } - - public bool IsAccessControlListProtected { get; private set; } - - public bool CanReadAccessControl { get; private set; } - - public ObservableCollection AccessControlEntries { get; private set; } - - public List AccessControlEntryPrimitiveMappings { get; private set; } - - private readonly string _path; - - private readonly bool _isFolder; - - public AccessControlList(string path, bool isFolder) + /// + /// File owner information + /// + public Principal Owner { get; set; } + + /// + /// Whether the DACL is protected + /// + public bool IsProtected { get; set; } + + /// + /// Whether the DACL is valid one + /// + public bool IsValid { get; set; } + + /// + /// File path which have this access control list + /// + public string Path { get; set; } + + /// + /// Whether the path indicates folder or not + /// + public bool IsFolder { get; set; } + + /// + /// ACE list + /// + public ObservableCollection AccessControlEntries { get; set; } + + public AccessControlList() { - _path = path; - _isFolder = isFolder; - - AccessControlEntryPrimitiveMappings = new(); - } - - public bool SetAccessControl() - { - if (GetAccessControl(_path, _isFolder, out var fileSystemSecurity)) - { - try - { - var accessRules = fileSystemSecurity.GetAccessRules(true, true, typeof(SecurityIdentifier)); - - // Remove all existing rules - foreach (var existingRule in accessRules.Cast().Where(x => !x.IsInherited)) - fileSystemSecurity.RemoveAccessRule(existingRule); - - // Bring back removed rules, including changed rules - foreach (var rule in AccessControlEntryPrimitiveMappings.Where(x => !x.IsInherited)) - fileSystemSecurity.AddAccessRule(rule.ToFileSystemAccessRule()); - - // Save changes - if (_isFolder) - FileSystemAclExtensions.SetAccessControl(new DirectoryInfo(_path), (DirectorySecurity)fileSystemSecurity); - else - FileSystemAclExtensions.SetAccessControl(new FileInfo(_path), (FileSecurity)fileSystemSecurity); - - return true; - } - catch (UnauthorizedAccessException) { } - catch (Exception) { } - } - - return false; - } - - public static AccessControlList FromPath(string path, bool isFolder) - { - // Get ACL - var result = GetAccessControl(path, isFolder, out var fileSystemSecurity); - if (result) - { - var ownerSid = fileSystemSecurity.GetOwner(typeof(SecurityIdentifier)).Value; - - var acl = new AccessControlList(path, isFolder) - { - IsAccessControlListProtected = fileSystemSecurity.AreAccessRulesProtected, - OwnerSID = ownerSid, - Owner = Principal.FromSid(ownerSid), - CanReadAccessControl = true, - }; - - // Get all ACEs and map them with primitive class - var accessRules = fileSystemSecurity.GetAccessRules(true, true, typeof(SecurityIdentifier)); - var rules = new List(); - foreach (var accessRule in accessRules) - rules.Add(AccessControlEntryPrimitiveMapping.FromFileSystemAccessRule((FileSystemAccessRule)accessRule)); - - acl.AccessControlEntryPrimitiveMappings.AddRange(rules); - acl.AccessControlEntries = new(rules.Select(x => new AccessControlEntry(x, isFolder))); - - return acl; - } - else - { - return new(path, isFolder); - } - } - - public static bool SetOwner(string path, bool isFolder, string sid) - { - if (GetAccessControl(path, isFolder, out var acs)) - { - try - { - // Set owner - acs.SetOwner(new SecurityIdentifier(sid)); - - // Save changes - if (isFolder) - FileSystemAclExtensions.SetAccessControl(new DirectoryInfo(path), (DirectorySecurity)acs); - else - FileSystemAclExtensions.SetAccessControl(new FileInfo(path), (FileSecurity)acs); - - return true; - } - catch (UnauthorizedAccessException) { } - catch (Exception) { } - } - - // If previous operation was unauthorized access, try again through elevated PowerShell - 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); - } - - public static bool SetAccessControlProtection(string path, bool isFolder, bool isProtected, bool preserveInheritance) - { - if (GetAccessControl(path, isFolder, out var acs)) - { - try - { - acs.SetAccessRuleProtection(isProtected, preserveInheritance); - - // Save changes - if (isFolder) - FileSystemAclExtensions.SetAccessControl(new DirectoryInfo(path), (DirectorySecurity)acs); - else - FileSystemAclExtensions.SetAccessControl(new FileInfo(path), (FileSecurity)acs); - - return true; - } - catch (UnauthorizedAccessException) { } - catch (Exception) { } - } - - return false; - } - - private static bool GetAccessControl(string path, bool isFolder, out FileSystemSecurity fileSystemSecurity) - { - try - { - if (isFolder && Directory.Exists(path)) - { - fileSystemSecurity = FileSystemAclExtensions.GetAccessControl(new DirectoryInfo(path)); - return true; - } - else if (File.Exists(path)) - { - fileSystemSecurity = FileSystemAclExtensions.GetAccessControl(new FileInfo(path)); - return true; - } - } - catch (UnauthorizedAccessException) { } - catch (Exception) { } - - fileSystemSecurity = null; - - return false; } } } diff --git a/src/Files.App/Helpers/FileOperationsHelpers.cs b/src/Files.App/Helpers/FileOperationsHelpers.cs index 4599da865a62..5e477c58e76e 100644 --- a/src/Files.App/Helpers/FileOperationsHelpers.cs +++ b/src/Files.App/Helpers/FileOperationsHelpers.cs @@ -636,13 +636,13 @@ public static bool SetLinkIcon(string filePath, string iconFile, int iconIndex) } public static AccessControlList GetFilePermissions(string filePath, bool isFolder) - => AccessControlList.FromPath(filePath, isFolder); + => FileSecurityHelpers.GetAccessControlList(filePath, isFolder); - public static bool SetFileOwner(string filePath, bool isFolder, string ownerSid) - => AccessControlList.SetOwner(filePath, isFolder, ownerSid); + public static bool SetFileOwner(string filePath, string ownerSid) + => FileSecurityHelpers.SetOwner(filePath, ownerSid); public static bool SetAccessRuleProtection(string filePath, bool isFolder, bool isProtected, bool preserveInheritance) - => AccessControlList.SetAccessControlProtection(filePath, isFolder, isProtected, preserveInheritance); + => FileSecurityHelpers.SetAccessControlProtection(filePath, isFolder, isProtected, preserveInheritance); public static Task OpenObjectPickerAsync(long hWnd) { diff --git a/src/Files.App/Helpers/FilePropertiesHelpers.cs b/src/Files.App/Helpers/FilePropertiesHelpers.cs index 834ff3d72d19..903a30ac358f 100644 --- a/src/Files.App/Helpers/FilePropertiesHelpers.cs +++ b/src/Files.App/Helpers/FilePropertiesHelpers.cs @@ -1,136 +1,146 @@ using CommunityToolkit.Mvvm.DependencyInjection; -using Files.App.Dialogs; +using Files.App.DataModels; using Files.App.Extensions; using Files.App.ViewModels; using Microsoft.UI; using Microsoft.UI.Windowing; +using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Media.Animation; +using Microsoft.Windows.ApplicationModel.Resources; using System; using System.IO; -using System.Threading.Tasks; using Windows.ApplicationModel; -using Windows.Foundation.Metadata; using Windows.Graphics; -using static Files.App.Views.Properties.MainPropertiesPage; namespace Files.App.Helpers { + /// + /// Represents a helper class that helps users open and handle item properties window + /// public static class FilePropertiesHelpers { - private static readonly bool isUniversal = - ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", majorVersion: 8); - - private static readonly Lazy logoPath = new(GetFilesLogoPath); - public static string LogoPath => logoPath.Value; + /// + /// Whether LayoutDirection (FlowDirection) is set to right-to-left (RTL) + /// + public static readonly bool FlowDirectionSettingIsRightToLeft = + new ResourceManager().CreateResourceContext().QualifierValues["LayoutDirection"] == "RTL"; + + /// + /// App logo location to use as window popup icon and title bar icon + /// + public static string LogoPath + => Path.Combine(Package.Current.InstalledLocation.Path, App.LogoPath); - public static async Task ShowProperties(IShellPage associatedInstance) + /// + /// Get window handle (hWnd) of the given properties window instance + /// + /// Window instance + /// + public static nint GetWindowHandle(Window w) + => WinRT.Interop.WindowNative.GetWindowHandle(w); + + /// + /// Open properties window + /// + /// Associated main window instance + public static void OpenPropertiesWindow(IShellPage associatedInstance) { - var item = GetItem(associatedInstance); - await OpenPropertiesWindowAsync(item, associatedInstance); + object item; - static object GetItem(IShellPage instance) - { - var page = instance.SlimContentPage; - if (page.IsItemSelected) - { - return page.SelectedItems?.Count is 1 - ? page.SelectedItem! - : page.SelectedItems!; - } + var page = associatedInstance.SlimContentPage; - var folder = instance.FilesystemViewModel.CurrentFolder; + // Item(s) selected + if (page.IsItemSelected) + { + // Selected item(s) + item = page.SelectedItems?.Count is 1 + ? page.SelectedItem! + : page.SelectedItems!; + } + // No items selected + else + { + // Instance's current folder + var folder = associatedInstance.FilesystemViewModel.CurrentFolder; + item = folder; var drivesViewModel = Ioc.Default.GetRequiredService(); - var drives = drivesViewModel.Drives; foreach (var drive in drives) { + // Current folder is drive if (drive.Path.Equals(folder.ItemPath)) - return drive; + { + item = drive; + break; + } } - - return folder; } + + OpenPropertiesWindow(item, associatedInstance); } - public static async Task OpenPropertiesWindowAsync(object item, IShellPage associatedInstance) + /// + /// Open properties window with an explicitly specified item + /// + /// An item to view properties + /// Associated main window instance + public static void OpenPropertiesWindow(object item, IShellPage associatedInstance) { if (item is null) return; - if (isUniversal) + var frame = new Frame { - var frame = new Frame - { - RequestedTheme = ThemeHelper.RootTheme - }; - Navigate(frame); - - var propertiesWindow = new WinUIEx.WindowEx - { - IsMinimizable = false, - IsMaximizable = false, - MinWidth = 460, - MinHeight = 550, - Width = 800, - Height = 550, - Content = frame, - Backdrop = new WinUIEx.MicaSystemBackdrop(), - }; - - var appWindow = propertiesWindow.AppWindow; - appWindow.Title = "Properties".GetLocalizedResource(); - appWindow.TitleBar.ExtendsContentIntoTitleBar = true; - appWindow.TitleBar.ButtonBackgroundColor = Colors.Transparent; - appWindow.TitleBar.ButtonInactiveBackgroundColor = Colors.Transparent; - - appWindow.SetIcon(LogoPath); - - if (frame.Content is Views.Properties.MainPropertiesPage properties) - { - properties.Window = propertiesWindow; - properties.AppWindow = appWindow; - } + RequestedTheme = ThemeHelper.RootTheme + }; - appWindow.Show(); - - if (true) // WINUI3: move window to cursor position + var propertiesWindow = new WinUIEx.WindowEx + { + IsMinimizable = false, + IsMaximizable = false, + MinWidth = 460, + MinHeight = 550, + Width = 800, + Height = 550, + Content = frame, + Backdrop = new WinUIEx.MicaSystemBackdrop(), + }; + + var appWindow = propertiesWindow.AppWindow; + appWindow.Title = "Properties".GetLocalizedResource(); + appWindow.TitleBar.ExtendsContentIntoTitleBar = true; + appWindow.TitleBar.ButtonBackgroundColor = Colors.Transparent; + appWindow.TitleBar.ButtonInactiveBackgroundColor = Colors.Transparent; + + appWindow.SetIcon(LogoPath); + + frame.Navigate( + typeof(Views.Properties.MainPropertiesPage), + new PropertiesPageNavigationParameter { - UWPToWinAppSDKUpgradeHelpers.InteropHelpers.GetCursorPos(out var pointerPosition); - var displayArea = DisplayArea.GetFromPoint(new PointInt32(pointerPosition.X, pointerPosition.Y), DisplayAreaFallback.Nearest); - var appWindowPos = new PointInt32 - { - X = displayArea.WorkArea.X - + Math.Max(0, Math.Min(displayArea.WorkArea.Width - appWindow.Size.Width, pointerPosition.X - displayArea.WorkArea.X)), - Y = displayArea.WorkArea.Y - + Math.Max(0, Math.Min(displayArea.WorkArea.Height - appWindow.Size.Height, pointerPosition.Y - displayArea.WorkArea.Y)), - }; - - appWindow.Move(appWindowPos); - } - } - else + Parameter = item, + AppInstance = associatedInstance, + AppWindow = appWindow, + Window = propertiesWindow + }, + new SuppressNavigationTransitionInfo()); + + appWindow.Show(); + + // WINUI3: Move window to cursor position + UWPToWinAppSDKUpgradeHelpers.InteropHelpers.GetCursorPos(out var pointerPosition); + var displayArea = DisplayArea.GetFromPoint(new PointInt32(pointerPosition.X, pointerPosition.Y), DisplayAreaFallback.Nearest); + var appWindowPos = new PointInt32 { - var dialog = new PropertiesDialog(); - dialog.propertiesFrame.Tag = dialog; - Navigate(dialog.propertiesFrame); - - await dialog.ShowAsync(ContentDialogPlacement.Popup); - } + X = displayArea.WorkArea.X + + Math.Max(0, Math.Min(displayArea.WorkArea.Width - appWindow.Size.Width, pointerPosition.X - displayArea.WorkArea.X)), + Y = displayArea.WorkArea.Y + + Math.Max(0, Math.Min(displayArea.WorkArea.Height - appWindow.Size.Height, pointerPosition.Y - displayArea.WorkArea.Y)), + }; - void Navigate(Frame frame) - { - var argument = new PropertiesPageNavigationArguments - { - Item = item, - AppInstanceArgument = associatedInstance, - }; - frame.Navigate(typeof(Views.Properties.MainPropertiesPage), argument, new SuppressNavigationTransitionInfo()); - } + appWindow.Move(appWindowPos); } - - private static string GetFilesLogoPath() - => Path.Combine(Package.Current.InstalledLocation.Path, App.LogoPath); } } diff --git a/src/Files.App/Helpers/FileSecurityHelpers.cs b/src/Files.App/Helpers/FileSecurityHelpers.cs new file mode 100644 index 000000000000..5eff0ce7dc67 --- /dev/null +++ b/src/Files.App/Helpers/FileSecurityHelpers.cs @@ -0,0 +1,222 @@ +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; + +namespace Files.App.Helpers +{ + /// + /// Represents a helper for file security information. + /// + 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; + } + + 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; + } + + public static bool GetAccessControlProtection(string path, bool isFolder) + { + 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; + } + } + + public static bool SetAccessControlProtection(string path, bool isFolder, bool isProtected, bool preserveInheritance) + { + FileSystemSecurity fileSystemSecurity; + + if (isFolder && Directory.Exists(path)) + { + fileSystemSecurity = FileSystemAclExtensions.GetAccessControl(new DirectoryInfo(path)); + fileSystemSecurity.SetAccessRuleProtection(isProtected, preserveInheritance); + FileSystemAclExtensions.SetAccessControl(new DirectoryInfo(path), (DirectorySecurity)fileSystemSecurity); + + return true; + } + else if (File.Exists(path)) + { + fileSystemSecurity = FileSystemAclExtensions.GetAccessControl(new FileInfo(path)); + fileSystemSecurity.SetAccessRuleProtection(isProtected, preserveInheritance); + FileSystemAclExtensions.SetAccessControl(new FileInfo(path), (FileSecurity)fileSystemSecurity); + + return true; + } + else + { + return false; + } + } + + public static AccessControlList GetAccessControlList(string path, bool isFolder) + { + // Get DACL + 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 _); + + // Get ACL size info + GetAclInformation(pDacl, out ACL_SIZE_INFORMATION aclSize); + + // Get owner + var szOwnerSid = GetOwner(path); + + // Initialize + var acl = new AccessControlList() + { + Owner = Principal.FromSid(szOwnerSid), + IsProtected = GetAccessControlProtection(path, isFolder), + IsValid = true, + AccessControlEntries = new(), + Path = path, + IsFolder = isFolder + }; + + // Get ACEs + for (uint i = 0; i < aclSize.AceCount; i++) + { + GetAce(pDacl, i, out var pAce); + + var szSid = ConvertSidToStringSid(pAce.GetSid()); + + var header = pAce.GetHeader(); + + FilesSecurity.AccessControlType type; + FilesSecurity.InheritanceFlags inheritanceFlags = FilesSecurity.InheritanceFlags.None; + FilesSecurity.PropagationFlags propagationFlags = FilesSecurity.PropagationFlags.None; + AccessMaskFlags accessMaskFlags = (AccessMaskFlags)pAce.GetMask(); + + type = header.AceType switch + { + AceType.AccessAllowed => FilesSecurity.AccessControlType.Allow, + _ => FilesSecurity.AccessControlType.Deny + }; + + bool isInherited = header.AceFlags.HasFlag(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; + + // Initialize an ACE + acl.AccessControlEntries.Add(new(isFolder, szSid, type, accessMaskFlags, isInherited, inheritanceFlags, propagationFlags)); + } + + return acl; + } + + public static bool SetAccessControlList(AccessControlList acl) + { + return false; + } + + public static AccessControlEntry InitializeDefaultAccessControlEntry(bool isFolder, string ownerSid) + { + return new( + isFolder, + ownerSid, + FilesSecurity.AccessControlType.Allow, + FilesSecurity.AccessMaskFlags.ReadAndExecute, + false, + isFolder + ? FilesSecurity.InheritanceFlags.ContainerInherit | FilesSecurity.InheritanceFlags.ObjectInherit + : FilesSecurity.InheritanceFlags.None, + FilesSecurity.PropagationFlags.None); + } + + public static bool AddAccessControlEntry(string path, AccessControlEntry entry) + { + // Get DACL + 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 _); + + // Get ACL size info + GetAclInformation(pDacl, out ACL_SIZE_INFORMATION aclSize); + + uint revision = GetAclInformation(pDacl, out ACL_REVISION_INFORMATION aclRevision) ? aclRevision.AclRevision : 0U; + + // Get ACEs + for (uint i = 0; i < aclSize.AceCount; i++) + { + //GetAce(pDacl, i, out var pAce); + + //AddAce(pDacl, revision, 0, (IntPtr)pAce, ((PACL)pDacl).Length() - (uint)Marshal.SizeOf(typeof(ACL))); + } + + return false; + } + } +} diff --git a/src/Files.App/Interacts/BaseLayoutCommandImplementationModel.cs b/src/Files.App/Interacts/BaseLayoutCommandImplementationModel.cs index f60b79e774c0..bb5e3afceb21 100644 --- a/src/Files.App/Interacts/BaseLayoutCommandImplementationModel.cs +++ b/src/Files.App/Interacts/BaseLayoutCommandImplementationModel.cs @@ -81,19 +81,19 @@ public virtual void ShowProperties(RoutedEventArgs e) else if (SlimContentPage.BaseContextMenuFlyout.IsOpen) SlimContentPage.BaseContextMenuFlyout.Closed += OpenPropertiesFromBaseContextMenuFlyout; else - FilePropertiesHelpers.ShowProperties(associatedInstance); + FilePropertiesHelpers.OpenPropertiesWindow(associatedInstance); } private void OpenPropertiesFromItemContextMenuFlyout(object sender, object e) { SlimContentPage.ItemContextMenuFlyout.Closed -= OpenPropertiesFromItemContextMenuFlyout; - FilePropertiesHelpers.ShowProperties(associatedInstance); + FilePropertiesHelpers.OpenPropertiesWindow(associatedInstance); } private void OpenPropertiesFromBaseContextMenuFlyout(object sender, object e) { SlimContentPage.BaseContextMenuFlyout.Closed -= OpenPropertiesFromBaseContextMenuFlyout; - FilePropertiesHelpers.ShowProperties(associatedInstance); + FilePropertiesHelpers.OpenPropertiesWindow(associatedInstance); } public virtual async Task OpenDirectoryInNewTab(RoutedEventArgs e) diff --git a/src/Files.App/ResourceDictionaries/NavigationViewItemButtonStyle.xaml b/src/Files.App/ResourceDictionaries/NavigationViewItemButtonStyle.xaml index ab241370b9e8..4ed2252c265a 100644 --- a/src/Files.App/ResourceDictionaries/NavigationViewItemButtonStyle.xaml +++ b/src/Files.App/ResourceDictionaries/NavigationViewItemButtonStyle.xaml @@ -56,7 +56,7 @@ 1,1,1,1 - 40 + 36 2,0,0,0 2,0,0,2 diff --git a/src/Files.App/Strings/en-US/Resources.resw b/src/Files.App/Strings/en-US/Resources.resw index c3928f928242..25bd1c8f74b3 100644 --- a/src/Files.App/Strings/en-US/Resources.resw +++ b/src/Files.App/Strings/en-US/Resources.resw @@ -1590,11 +1590,11 @@ List directory contents - + Modify - - Permissions for + + Permissions for {0} Read and execute @@ -1644,9 +1644,6 @@ Owner - - Advanced permissions for {0} - Special @@ -3181,6 +3178,9 @@ Show checkboxes when selecting items + + No groups or users have permission to access this object. However, the owner of this object can assign permissions. + Open the item's location diff --git a/src/Files.App/UserControls/Widgets/DrivesWidget.xaml.cs b/src/Files.App/UserControls/Widgets/DrivesWidget.xaml.cs index c6a2f4cf650d..23087a2d576e 100644 --- a/src/Files.App/UserControls/Widgets/DrivesWidget.xaml.cs +++ b/src/Files.App/UserControls/Widgets/DrivesWidget.xaml.cs @@ -292,7 +292,7 @@ private void OpenProperties(DriveCardItem item) flyoutClosed = async (s, e) => { ItemContextMenuFlyout.Closed -= flyoutClosed; - await FilePropertiesHelpers.OpenPropertiesWindowAsync(item.Item, associatedInstance); + FilePropertiesHelpers.OpenPropertiesWindow(item.Item, associatedInstance); }; ItemContextMenuFlyout.Closed += flyoutClosed; } diff --git a/src/Files.App/UserControls/Widgets/FileTagsWidget.xaml.cs b/src/Files.App/UserControls/Widgets/FileTagsWidget.xaml.cs index cee6d80f245e..1798f53ddc16 100644 --- a/src/Files.App/UserControls/Widgets/FileTagsWidget.xaml.cs +++ b/src/Files.App/UserControls/Widgets/FileTagsWidget.xaml.cs @@ -87,7 +87,7 @@ private void OpenProperties(WidgetCardItem? item) PrimaryItemAttribute = StorageItemTypes.Folder, ItemType = "Folder".GetLocalizedResource(), }; - await FilePropertiesHelpers.OpenPropertiesWindowAsync(listedItem, AppInstance); + FilePropertiesHelpers.OpenPropertiesWindow(listedItem, AppInstance); }; ItemContextMenuFlyout.Closed += flyoutClosed; } diff --git a/src/Files.App/UserControls/Widgets/RecentFilesWidget.xaml.cs b/src/Files.App/UserControls/Widgets/RecentFilesWidget.xaml.cs index fc9c45e3ae5c..4828e21e8276 100644 --- a/src/Files.App/UserControls/Widgets/RecentFilesWidget.xaml.cs +++ b/src/Files.App/UserControls/Widgets/RecentFilesWidget.xaml.cs @@ -235,7 +235,7 @@ private void OpenProperties(RecentItem item) { ItemContextMenuFlyout.Closed -= flyoutClosed; var listedItem = await UniversalStorageEnumerator.AddFileAsync(await BaseStorageFile.GetFileFromPathAsync(item.Path), null, default); - await FilePropertiesHelpers.OpenPropertiesWindowAsync(listedItem, associatedInstance); + FilePropertiesHelpers.OpenPropertiesWindow(listedItem, associatedInstance); }; ItemContextMenuFlyout.Closed += flyoutClosed; } diff --git a/src/Files.App/ViewModels/Properties/BasePropertiesPage.cs b/src/Files.App/ViewModels/Properties/BasePropertiesPage.cs index 4a2b276f2d52..6bea63db7b08 100644 --- a/src/Files.App/ViewModels/Properties/BasePropertiesPage.cs +++ b/src/Files.App/ViewModels/Properties/BasePropertiesPage.cs @@ -1,3 +1,4 @@ +using Files.App.DataModels; using Files.App.DataModels.NavigationControlItems; using Files.App.Filesystem; using Microsoft.UI.Xaml; @@ -16,45 +17,36 @@ public abstract class BasePropertiesPage : Page, IDisposable public BaseProperties BaseProperties { get; set; } - public SelectedItemsPropertiesViewModel ViewModel { get; set; } + public SelectedItemsPropertiesViewModel ViewModel { get; set; } = new(); protected virtual void Properties_Loaded(object sender, RoutedEventArgs e) { - if (BaseProperties is not null) - { - BaseProperties.GetSpecialProperties(); - } + BaseProperties?.GetSpecialProperties(); } protected override void OnNavigatedTo(NavigationEventArgs e) { - var np = e.Parameter as Views.Properties.MainPropertiesPage.PropertyNavParam; + var np = (PropertiesPageNavigationParameter)e.Parameter; + AppInstance = np.AppInstance; - AppInstance = np.AppInstanceArgument; - ViewModel = new SelectedItemsPropertiesViewModel(); - - if (np.navParameter is LibraryItem library) - { - BaseProperties = new LibraryProperties(ViewModel, np.tokenSource, DispatcherQueue, library, AppInstance); - } - else if (np.navParameter is ListedItem item) + // Library + if (np.Parameter is LibraryItem library) + BaseProperties = new LibraryProperties(ViewModel, np.CancellationTokenSource, DispatcherQueue, library, AppInstance); + // Drive + else if (np.Parameter is DriveItem drive) + BaseProperties = new DriveProperties(ViewModel, drive, AppInstance); + // Storage objects (multi-selected) + else if (np.Parameter is List items) + BaseProperties = new CombinedProperties(ViewModel, np.CancellationTokenSource, DispatcherQueue, items, AppInstance); + // A storage object + else if (np.Parameter is ListedItem item) { + // File or Archive if (item.PrimaryItemAttribute == StorageItemTypes.File || item.IsArchive) - { - BaseProperties = new FileProperties(ViewModel, np.tokenSource, DispatcherQueue, item, AppInstance); - } + BaseProperties = new FileProperties(ViewModel, np.CancellationTokenSource, DispatcherQueue, item, AppInstance); + // Folder else if (item.PrimaryItemAttribute == StorageItemTypes.Folder) - { - BaseProperties = new FolderProperties(ViewModel, np.tokenSource, DispatcherQueue, item, AppInstance); - } - } - else if (np.navParameter is List items) - { - BaseProperties = new CombinedProperties(ViewModel, np.tokenSource, DispatcherQueue, items, AppInstance); - } - else if (np.navParameter is DriveItem drive) - { - BaseProperties = new DriveProperties(ViewModel, drive, AppInstance); + BaseProperties = new FolderProperties(ViewModel, np.CancellationTokenSource, DispatcherQueue, item, AppInstance); } base.OnNavigatedTo(e); @@ -62,7 +54,8 @@ protected override void OnNavigatedTo(NavigationEventArgs e) protected override void OnNavigatedFrom(NavigationEventArgs e) { - if (BaseProperties is not null && BaseProperties.TokenSource is not null) + if (BaseProperties is not null && + BaseProperties.TokenSource is not null) { //BaseProperties.TokenSource.Cancel(); } @@ -71,11 +64,14 @@ protected override void OnNavigatedFrom(NavigationEventArgs e) } /// - /// Tries to save changed properties to file. + /// Try to save changed properties to the file. /// - /// Returns true if properties have been saved successfully. + /// Returns true if properties have been saved successfully public abstract Task SaveChangesAsync(); + /// + /// Dispose unmanaged resources. + /// public abstract void Dispose(); } } diff --git a/src/Files.App/ViewModels/Properties/CustomizationViewModel.cs b/src/Files.App/ViewModels/Properties/CustomizationViewModel.cs index 7bf28e6a7972..b6cf06f4e78e 100644 --- a/src/Files.App/ViewModels/Properties/CustomizationViewModel.cs +++ b/src/Files.App/ViewModels/Properties/CustomizationViewModel.cs @@ -12,12 +12,13 @@ using System.IO; using System.Threading.Tasks; using Windows.Storage.Pickers; +using Microsoft.UI.Windowing; namespace Files.App.ViewModels.Properties { public class CustomizationViewModel : ObservableObject { - public CustomizationViewModel(IShellPage appInstance, BaseProperties baseProperties) + public CustomizationViewModel(IShellPage appInstance, BaseProperties baseProperties, AppWindow appWindow) { Filesystem.ListedItem item; @@ -29,6 +30,7 @@ public CustomizationViewModel(IShellPage appInstance, BaseProperties basePropert return; AppInstance = appInstance; + AppWindow = appWindow; IconResourceItemPath = Path.Combine(CommonPaths.SystemRootPath, "System32", "SHELL32.dll"); IsShortcut = item.IsShortcut; SelectedItemPath = item.ItemPath; @@ -40,9 +42,11 @@ public CustomizationViewModel(IShellPage appInstance, BaseProperties basePropert LoadIconsForPath(IconResourceItemPath); RestoreDefaultIconCommand = new AsyncRelayCommand(ExecuteRestoreDefaultIconAsync); - PickDllFileCommand = new AsyncRelayCommand(ExecuteOpenFilePickerAsync); + PickDllFileCommand = new AsyncRelayCommand(ExecuteOpenFilePickerAsync); } + private AppWindow AppWindow; + private string? _selectedItemPath; public string? SelectedItemPath { @@ -95,7 +99,7 @@ public IconFileInfo SelectedDllIcon } public IAsyncRelayCommand RestoreDefaultIconCommand { get; private set; } - public IAsyncRelayCommand PickDllFileCommand { get; private set; } + public IAsyncRelayCommand PickDllFileCommand { get; private set; } private async Task ExecuteRestoreDefaultIconAsync() { @@ -118,11 +122,8 @@ await App.Window.DispatcherQueue.EnqueueAsync(() => } } - private async Task ExecuteOpenFilePickerAsync(XamlRoot? xamlRoot) + private async Task ExecuteOpenFilePickerAsync() { - if (xamlRoot is null) - return; - // Initialize picker FileOpenPicker picker = new() { @@ -135,7 +136,7 @@ private async Task ExecuteOpenFilePickerAsync(XamlRoot? xamlRoot) picker.FileTypeFilter.Add(".ico"); // WINUI3: Create and initialize new window - var parentWindowId = ((MainPropertiesPage)((Frame)xamlRoot.Content).Content).AppWindow.Id; + var parentWindowId = AppWindow.Id; var handle = Microsoft.UI.Win32Interop.GetWindowFromWindowId(parentWindowId); WinRT.Interop.InitializeWithWindow.Initialize(picker, handle); diff --git a/src/Files.App/ViewModels/Properties/BaseProperties.cs b/src/Files.App/ViewModels/Properties/Items/BaseProperties.cs similarity index 100% rename from src/Files.App/ViewModels/Properties/BaseProperties.cs rename to src/Files.App/ViewModels/Properties/Items/BaseProperties.cs diff --git a/src/Files.App/ViewModels/Properties/CombinedProperties.cs b/src/Files.App/ViewModels/Properties/Items/CombinedProperties.cs similarity index 100% rename from src/Files.App/ViewModels/Properties/CombinedProperties.cs rename to src/Files.App/ViewModels/Properties/Items/CombinedProperties.cs diff --git a/src/Files.App/ViewModels/Properties/DriveProperties.cs b/src/Files.App/ViewModels/Properties/Items/DriveProperties.cs similarity index 100% rename from src/Files.App/ViewModels/Properties/DriveProperties.cs rename to src/Files.App/ViewModels/Properties/Items/DriveProperties.cs diff --git a/src/Files.App/ViewModels/Properties/FileProperties.cs b/src/Files.App/ViewModels/Properties/Items/FileProperties.cs similarity index 100% rename from src/Files.App/ViewModels/Properties/FileProperties.cs rename to src/Files.App/ViewModels/Properties/Items/FileProperties.cs diff --git a/src/Files.App/ViewModels/Properties/FileProperty.cs b/src/Files.App/ViewModels/Properties/Items/FileProperty.cs similarity index 100% rename from src/Files.App/ViewModels/Properties/FileProperty.cs rename to src/Files.App/ViewModels/Properties/Items/FileProperty.cs diff --git a/src/Files.App/ViewModels/Properties/FilePropertySection.cs b/src/Files.App/ViewModels/Properties/Items/FilePropertySection.cs similarity index 100% rename from src/Files.App/ViewModels/Properties/FilePropertySection.cs rename to src/Files.App/ViewModels/Properties/Items/FilePropertySection.cs diff --git a/src/Files.App/ViewModels/Properties/FolderProperties.cs b/src/Files.App/ViewModels/Properties/Items/FolderProperties.cs similarity index 100% rename from src/Files.App/ViewModels/Properties/FolderProperties.cs rename to src/Files.App/ViewModels/Properties/Items/FolderProperties.cs diff --git a/src/Files.App/ViewModels/Properties/LibraryProperties.cs b/src/Files.App/ViewModels/Properties/Items/LibraryProperties.cs similarity index 100% rename from src/Files.App/ViewModels/Properties/LibraryProperties.cs rename to src/Files.App/ViewModels/Properties/Items/LibraryProperties.cs diff --git a/src/Files.App/ViewModels/Properties/MainPropertiesViewModel.cs b/src/Files.App/ViewModels/Properties/MainPropertiesViewModel.cs new file mode 100644 index 000000000000..6ad8dee6b929 --- /dev/null +++ b/src/Files.App/ViewModels/Properties/MainPropertiesViewModel.cs @@ -0,0 +1,162 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.DependencyInjection; +using CommunityToolkit.Mvvm.Input; +using Files.App.DataModels; +using Files.App.Views.Properties; +using Files.Backend.Enums; +using Microsoft.UI.Windowing; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using System; +using System.Collections.ObjectModel; +using System.Threading.Tasks; +using System.Linq; +using System.Threading; +using Microsoft.UI.Xaml.Media.Animation; + +namespace Files.App.ViewModels.Properties +{ + public class MainPropertiesViewModel : ObservableObject + { + public CancellationTokenSource ChangedPropertiesCancellationTokenSource { get; } + + public ObservableCollection NavigationViewItems { get; } + + private NavigationViewItemButtonStyleItem _SelectedNavigationViewItem; + public NavigationViewItemButtonStyleItem SelectedNavigationViewItem + { + get => _SelectedNavigationViewItem; + set + { + if (SetProperty(ref _SelectedNavigationViewItem, value) && + !_selectionChangedAutomatically && + _previousPageName != value.ItemType.ToString()) + { + var parameter = new PropertiesPageNavigationParameter() + { + AppInstance = _parameter.AppInstance, + CancellationTokenSource = ChangedPropertiesCancellationTokenSource, + Parameter = _parameter.Parameter, + Window = Window, + AppWindow = AppWindow, + }; + + var page = value.ItemType switch + { + PropertiesNavigationViewItemType.General => typeof(GeneralPage), + PropertiesNavigationViewItemType.Shortcut => typeof(ShortcutPage), + PropertiesNavigationViewItemType.Library => typeof(LibraryPage), + PropertiesNavigationViewItemType.Details => typeof(DetailsPage), + PropertiesNavigationViewItemType.Security => typeof(SecurityPage), + PropertiesNavigationViewItemType.Customization => typeof(CustomizationPage), + PropertiesNavigationViewItemType.Compatibility => typeof(CompatibilityPage), + PropertiesNavigationViewItemType.Hashes => typeof(HashesPage), + _ => typeof(GeneralPage), + }; + + _mainFrame?.Navigate(page, parameter, new EntranceNavigationTransitionInfo()); + } + + _selectionChangedAutomatically = false; + } + } + + //public string TitleBarText + //{ + // get + // { + // // Library + // if (_baseProperties is LibraryProperties library) + // return library.Library.Name; + // // Drive + // else if (_baseProperties is DriveProperties drive) + // return drive.Drive.Text; + // // Storage objects (multi-selected) + // else if (_baseProperties is CombinedProperties combined) + // return string.Join(", ", combined.List.Select(x => x.Name)); + // // File + // else if (_baseProperties is FileProperties file) + // return file.Item.Name; + // // Folder + // else if (_baseProperties is FolderProperties folder) + // return folder.Item.Name; + // else + // return string.Empty; + // } + //} + + private readonly Window Window; + + private readonly AppWindow AppWindow; + + private readonly Frame _mainFrame; + + private readonly BaseProperties _baseProperties; + + private readonly PropertiesPageNavigationParameter _parameter; + + private bool _selectionChangedAutomatically { get; set; } + + private string _previousPageName { get; set; } + + public IRelayCommand DoBackwardNavigationCommand { get; } + public IAsyncRelayCommand SaveChangedPropertiesCommand { get; } + public IRelayCommand CancelChangedPropertiesCommand { get; } + + public MainPropertiesViewModel(Window window, AppWindow appWindow, Frame mainFrame, BaseProperties baseProperties, PropertiesPageNavigationParameter parameter) + { + ChangedPropertiesCancellationTokenSource = new(); + + Window = window; + AppWindow = appWindow; + _mainFrame = mainFrame; + _parameter = parameter; + _baseProperties = baseProperties; + + DoBackwardNavigationCommand = new RelayCommand(ExecuteDoBackwardNavigationCommand); + SaveChangedPropertiesCommand = new AsyncRelayCommand(ExecuteSaveChangedPropertiesCommand); + CancelChangedPropertiesCommand = new RelayCommand(ExecuteCancelChangedPropertiesCommand); + + NavigationViewItems = PropertiesNavigationViewItemFactory.Initialize(parameter.Parameter); + SelectedNavigationViewItem = NavigationViewItems.Where(x => x.ItemType == PropertiesNavigationViewItemType.General).First(); + } + + private void ExecuteDoBackwardNavigationCommand() + { + if (NavigationViewItems is null || + NavigationViewItems.Count == 0 || + _mainFrame is null) + return; + + if (_mainFrame.CanGoBack) + _mainFrame.GoBack(); + + var pageTag = ((Page)_mainFrame.Content).Tag.ToString(); + + _selectionChangedAutomatically = true; + _previousPageName = pageTag; + + // Move selection indicator + SelectedNavigationViewItem = + NavigationViewItems.First(x => string.Equals(x.ItemType.ToString(), pageTag, StringComparison.CurrentCultureIgnoreCase)) + ?? NavigationViewItems.First(); + } + + private async Task ExecuteSaveChangedPropertiesCommand() + { + await ApplyChanges(); + Window.Close(); + } + + private void ExecuteCancelChangedPropertiesCommand() + { + Window.Close(); + } + + private async Task ApplyChanges() + { + if (_mainFrame is not null && _mainFrame.Content is not null) + await ((BasePropertiesPage)_mainFrame.Content).SaveChangesAsync(); + } + } +} diff --git a/src/Files.App/ViewModels/Properties/SecurityAdvancedViewModel.cs b/src/Files.App/ViewModels/Properties/SecurityAdvancedViewModel.cs new file mode 100644 index 000000000000..0d89937d5232 --- /dev/null +++ b/src/Files.App/ViewModels/Properties/SecurityAdvancedViewModel.cs @@ -0,0 +1,218 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using Files.App.DataModels.NavigationControlItems; +using Files.App.Extensions; +using Files.App.Filesystem; +using Files.App.Filesystem.Security; +using Files.App.Helpers; +using Microsoft.UI.Xaml; +using System.Threading.Tasks; +using Windows.Storage; + +namespace Files.App.ViewModels.Properties +{ + public class SecurityAdvancedViewModel : ObservableObject + { + public ListedItem Item { get; } + + private readonly Window Window; + + private AccessControlList _accessControlList; + public AccessControlList AccessControlList + { + get => _accessControlList; + set + { + if (SetProperty(ref _accessControlList, value)) + { + ChangeOwnerCommand.NotifyCanExecuteChanged(); + AddAccessControlEntryCommand.NotifyCanExecuteChanged(); + RemoveAccessControlEntryCommand.NotifyCanExecuteChanged(); + DisableInheritanceCommand.NotifyCanExecuteChanged(); + ReplaceChildPermissionsCommand.NotifyCanExecuteChanged(); + } + } + } + + private AccessControlEntry _selectedAccessControlEntry; + public AccessControlEntry SelectedAccessControlEntry + { + get => _selectedAccessControlEntry; + set + { + if (_selectedAccessControlEntry is not null) + _selectedAccessControlEntry.IsSelected = false; + + if (SetProperty(ref _selectedAccessControlEntry, value)) + { + value.IsSelected = true; + RemoveAccessControlEntryCommand.NotifyCanExecuteChanged(); + } + } + } + + private bool _isFolder; + public bool IsFolder + { + get => _isFolder; + set => SetProperty(ref _isFolder, value); + } + + public string DisableInheritanceOption + { + get + { + if (!_isProtected) + return "SecurityAdvancedInheritedEnable/Text".GetLocalizedResource(); + else if (_preserveInheritance) + return "SecurityAdvancedInheritedConvert/Text".GetLocalizedResource(); + else + return "SecurityAdvancedInheritedRemove/Text".GetLocalizedResource(); + } + } + + private bool _isProtected; + + private bool _preserveInheritance; + + private GridLength _columnType = new(64d); + public GridLength ColumnType + { + get => _columnType; + set => SetProperty(ref _columnType, value); + } + + private GridLength _columnPrincipal = new(200d); + public GridLength ColumnPrincipal + { + get => _columnPrincipal; + set => SetProperty(ref _columnPrincipal, value); + } + + private GridLength _columnAccess = new(160d); + public GridLength ColumnAccess + { + get => _columnAccess; + set => SetProperty(ref _columnAccess, value); + } + + private GridLength _columnInherited = new(70d); + public GridLength ColumnInherited + { + get => _columnInherited; + set => SetProperty(ref _columnInherited, 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 SecurityAdvancedViewModel(ListedItem item, Window window) + { + IsFolder = item.PrimaryItemAttribute == StorageItemTypes.Folder && !item.IsShortcut; + Item = item; + Window = window; + + InitializeCommands(); + GetAccessControlList(); + } + + public SecurityAdvancedViewModel(DriveItem item, Window window) + { + IsFolder = true; + Item = new ListedItem() + { + ItemNameRaw = item.Text, + ItemPath = item.Path, + PrimaryItemAttribute = StorageItemTypes.Folder + }; + Window = window; + + InitializeCommands(); + GetAccessControlList(); + } + + private void InitializeCommands() + { + 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); + } + + private async void ChangeOwner() + { + var pickedObject = await OpenObjectPicker(); + if (pickedObject is not null) + { + bool isFolder = Item.PrimaryItemAttribute == StorageItemTypes.Folder && !Item.IsShortcut; + + // Set owner and refresh file permissions + if (FileOperationsHelpers.SetFileOwner(Item.ItemPath, pickedObject)) + GetAccessControlList(); + } + } + + private async void AddAccessControlEntry() + { + // Pick an user/group + var sid = await OpenObjectPicker(); + if (string.IsNullOrEmpty(sid)) + return; + + // Initialize + var ace = FileSecurityHelpers.InitializeDefaultAccessControlEntry(IsFolder, sid); + AccessControlList.AccessControlEntries.Add(ace); + + // Save + FileSecurityHelpers.SetAccessControlList(AccessControlList); + } + + private void RemoveAccessControlEntry() + { + // Remove + AccessControlList.AccessControlEntries.Remove(SelectedAccessControlEntry); + + // Save + FileSecurityHelpers.SetAccessControlList(AccessControlList); + } + + private void DisableInheritance() + { + // Update protection status and refresh access control + if (FileOperationsHelpers.SetAccessRuleProtection(Item.ItemPath, IsFolder, _isProtected, _preserveInheritance)) + GetAccessControlList(); + } + + private void SetDisableInheritanceOption(string options) + { + _isProtected = bool.Parse(options.Split(',')[0]); + _preserveInheritance = bool.Parse(options.Split(',')[1]); + + OnPropertyChanged(nameof(DisableInheritanceOption)); + DisableInheritanceCommand.NotifyCanExecuteChanged(); + } + + private void ReplaceChildPermissions() + { + } + + public void GetAccessControlList() + { + AccessControlList = FileOperationsHelpers.GetFilePermissions(Item.ItemPath, IsFolder); + } + + public bool SaveChangedAccessControlList() + { + return false; + } + + public Task OpenObjectPicker() + => FileOperationsHelpers.OpenObjectPickerAsync(FilePropertiesHelpers.GetWindowHandle(Window).ToInt64()); + } +} diff --git a/src/Files.App/ViewModels/Properties/SecurityViewModel.cs b/src/Files.App/ViewModels/Properties/SecurityViewModel.cs index 0df510716428..880ecf6e52aa 100644 --- a/src/Files.App/ViewModels/Properties/SecurityViewModel.cs +++ b/src/Files.App/ViewModels/Properties/SecurityViewModel.cs @@ -6,6 +6,7 @@ using Files.App.Filesystem.Security; using Files.App.Helpers; using Microsoft.UI.Xaml; +using System.Linq; using System.Threading.Tasks; using Windows.Storage; @@ -13,218 +14,106 @@ namespace Files.App.ViewModels.Properties { public class SecurityViewModel : ObservableObject { - public SecurityViewModel(ListedItem item) - { - IsFolder = item.PrimaryItemAttribute == StorageItemTypes.Folder && !item.IsShortcut; - Item = item; + public string Path { get; set; } - InitializeCommands(); - GetAccessControlList(); - } + private readonly Window Window; - public SecurityViewModel(DriveItem item) + private bool _IsFolder; + public bool IsFolder { - IsFolder = true; - Item = new ListedItem() - { - ItemNameRaw = item.Text, - ItemPath = item.Path, - PrimaryItemAttribute = StorageItemTypes.Folder - }; - - InitializeCommands(); - GetAccessControlList(); + get => _IsFolder; + set => SetProperty(ref _IsFolder, value); } - public ListedItem Item { get; } - - private AccessControlList _accessControlList; + private AccessControlList _AccessControlList; public AccessControlList AccessControlList { - get => _accessControlList; - set - { - if (SetProperty(ref _accessControlList, value)) - { - ChangeOwnerCommand.NotifyCanExecuteChanged(); - AddAccessControlEntryCommand.NotifyCanExecuteChanged(); - RemoveAccessControlEntryCommand.NotifyCanExecuteChanged(); - DisableInheritanceCommand.NotifyCanExecuteChanged(); - ReplaceChildPermissionsCommand.NotifyCanExecuteChanged(); - } - } + get => _AccessControlList; + set => SetProperty(ref _AccessControlList, value); } - private AccessControlEntry _selectedAccessControlEntry; + private AccessControlEntry _SelectedAccessControlEntry; public AccessControlEntry SelectedAccessControlEntry { - get => _selectedAccessControlEntry; + get => _SelectedAccessControlEntry; set { - if (_selectedAccessControlEntry is not null) - _selectedAccessControlEntry.IsSelected = false; + if (_SelectedAccessControlEntry is not null) + _SelectedAccessControlEntry.IsSelected = false; - if (SetProperty(ref _selectedAccessControlEntry, value)) + if (SetProperty(ref _SelectedAccessControlEntry, value)) { value.IsSelected = true; - RemoveAccessControlEntryCommand.NotifyCanExecuteChanged(); + RemoveAccessControlEntryCommand?.NotifyCanExecuteChanged(); + OnPropertyChanged(nameof(SelectedItemHeaderText)); } } } - private bool _isFolder; - public bool IsFolder - { - get => _isFolder; - set => SetProperty(ref _isFolder, value); - } - - public string DisableInheritanceOption - { - get - { - if (!_isProtected) - return "SecurityAdvancedInheritedEnable/Text".GetLocalizedResource(); - else if (_preserveInheritance) - return "SecurityAdvancedInheritedConvert/Text".GetLocalizedResource(); - else - return "SecurityAdvancedInheritedRemove/Text".GetLocalizedResource(); - } - } - - private bool _isProtected; + public string SelectedItemHeaderText + => string.Format("SecurityPermissionsHeaderText".GetLocalizedResource(), SelectedAccessControlEntry.Principal.DisplayName); - private bool _preserveInheritance; + public IRelayCommand AddAccessControlEntryCommand { get; set; } + public IRelayCommand RemoveAccessControlEntryCommand { get; set; } - private GridLength _columnType = new(64d); - public GridLength ColumnType + public SecurityViewModel(ListedItem item, Window window) { - get => _columnType; - set => SetProperty(ref _columnType, value); - } + IsFolder = item.PrimaryItemAttribute == StorageItemTypes.Folder && !item.IsShortcut; + Path = item.ItemPath; + AccessControlList = FileSecurityHelpers.GetAccessControlList(Path, IsFolder); + SelectedAccessControlEntry = AccessControlList.AccessControlEntries.FirstOrDefault(); + Window = window; - private GridLength _columnPrincipal = new(200d); - public GridLength ColumnPrincipal - { - get => _columnPrincipal; - set => SetProperty(ref _columnPrincipal, value); + InitializeCommands(); } - private GridLength _columnAccess = new(160d); - public GridLength ColumnAccess + public SecurityViewModel(DriveItem item, Window window) { - get => _columnAccess; - set => SetProperty(ref _columnAccess, value); - } + IsFolder = true; + Path = item.Path; + AccessControlList = FileSecurityHelpers.GetAccessControlList(Path, IsFolder); + SelectedAccessControlEntry = AccessControlList.AccessControlEntries.FirstOrDefault(); + Window = window; - private GridLength _columnInherited = new(70d); - public GridLength ColumnInherited - { - get => _columnInherited; - set => SetProperty(ref _columnInherited, value); + InitializeCommands(); } - public AsyncRelayCommand ChangeOwnerCommand { get; set; } - public AsyncRelayCommand AddAccessControlEntryCommand { get; set; } - public RelayCommand RemoveAccessControlEntryCommand { get; set; } - public RelayCommand DisableInheritanceCommand { get; set; } - public RelayCommand SetDisableInheritanceOptionCommand { get; set; } - public RelayCommand ReplaceChildPermissionsCommand { get; set; } - private void InitializeCommands() { - ChangeOwnerCommand = new AsyncRelayCommand(ChangeOwner, () => AccessControlList is not null); - AddAccessControlEntryCommand = new AsyncRelayCommand(AddAccessControlEntry, () => AccessControlList is not null && AccessControlList.CanReadAccessControl); - RemoveAccessControlEntryCommand = new RelayCommand(RemoveAccessControlEntry, () => AccessControlList is not null && AccessControlList.CanReadAccessControl && SelectedAccessControlEntry is not null); - DisableInheritanceCommand = new RelayCommand(DisableInheritance, () => AccessControlList is not null && AccessControlList.CanReadAccessControl && (AccessControlList.IsAccessControlListProtected != _isProtected)); - SetDisableInheritanceOptionCommand = new RelayCommand(SetDisableInheritanceOption); - ReplaceChildPermissionsCommand = new RelayCommand(ReplaceChildPermissions, () => AccessControlList is not null && AccessControlList.CanReadAccessControl); + AddAccessControlEntryCommand = new AsyncRelayCommand(AddAccessControlEntry, () => AccessControlList is not null && AccessControlList.IsValid); + RemoveAccessControlEntryCommand = new RelayCommand(RemoveAccessControlEntry, () => AccessControlList is not null && AccessControlList.IsValid && SelectedAccessControlEntry is not null); } - private async Task ChangeOwner() + private async Task AddAccessControlEntry() { - var pickedObject = await OpenObjectPicker(); - if (pickedObject is not null) - { - bool isFolder = Item.PrimaryItemAttribute == StorageItemTypes.Folder && !Item.IsShortcut; + // Pick an user/group + var sid = await OpenObjectPicker(); + if (string.IsNullOrEmpty(sid)) + return; - // Set owner and refresh file permissions - if (FileOperationsHelpers.SetFileOwner(Item.ItemPath, isFolder, pickedObject)) - GetAccessControlList(); - } - } + // Initialize + var ace = FileSecurityHelpers.InitializeDefaultAccessControlEntry(IsFolder, sid); + AccessControlList.AccessControlEntries.Add(ace); - private async Task AddAccessControlEntry() - { - var pickedSid = await OpenObjectPicker(); - if (pickedSid is not null) - { - var mapping = new AccessControlEntryPrimitiveMapping() - { - AccessControlType = System.Security.AccessControl.AccessControlType.Allow, - FileSystemRights = System.Security.AccessControl.FileSystemRights.ReadAndExecute, - InheritanceFlags = IsFolder - ? System.Security.AccessControl.InheritanceFlags.ContainerInherit | System.Security.AccessControl.InheritanceFlags.ObjectInherit - : System.Security.AccessControl.InheritanceFlags.None, - IsInherited = false, - PrincipalSid = pickedSid, - PropagationFlags = System.Security.AccessControl.PropagationFlags.None, - }; - - AccessControlList.AccessControlEntryPrimitiveMappings.Add(mapping); - AccessControlList.AccessControlEntries.Add(new(mapping, IsFolder)); - - SaveChangedAccessControlList(); - } + // Save + FileSecurityHelpers.SetAccessControlList(AccessControlList); } private void RemoveAccessControlEntry() { - AccessControlList.AccessControlEntryPrimitiveMappings.RemoveAll(x => - x.AccessControlType == (System.Security.AccessControl.AccessControlType)SelectedAccessControlEntry.AccessControlType && - x.FileSystemRights == (System.Security.AccessControl.FileSystemRights)SelectedAccessControlEntry.AccessMaskFlags && - x.InheritanceFlags == (System.Security.AccessControl.InheritanceFlags)SelectedAccessControlEntry.InheritanceFlags && - x.IsInherited == SelectedAccessControlEntry.IsInherited && - x.PrincipalSid == SelectedAccessControlEntry.PrincipalSid && - x.PropagationFlags == (System.Security.AccessControl.PropagationFlags)SelectedAccessControlEntry.PropagationFlags); + // Remove AccessControlList.AccessControlEntries.Remove(SelectedAccessControlEntry); - SaveChangedAccessControlList(); - } - - private void DisableInheritance() - { - // Update protection status and refresh access control - if (FileOperationsHelpers.SetAccessRuleProtection(Item.ItemPath, IsFolder, _isProtected, _preserveInheritance)) - GetAccessControlList(); - } - - private void SetDisableInheritanceOption(string options) - { - _isProtected = bool.Parse(options.Split(',')[0]); - _preserveInheritance = bool.Parse(options.Split(',')[1]); - - OnPropertyChanged(nameof(DisableInheritanceOption)); - DisableInheritanceCommand.NotifyCanExecuteChanged(); - } - - private void ReplaceChildPermissions() - { - } - - public void GetAccessControlList() - { - AccessControlList = FileOperationsHelpers.GetFilePermissions(Item.ItemPath, IsFolder); + // Save + FileSecurityHelpers.SetAccessControlList(AccessControlList); } public bool SaveChangedAccessControlList() { - return AccessControlList.SetAccessControl(); + return false; } - public Task OpenObjectPicker() - { - return FileOperationsHelpers.OpenObjectPickerAsync(NativeWinApiHelper.CoreWindowHandle.ToInt64()); - } + private Task OpenObjectPicker() + => FileOperationsHelpers.OpenObjectPickerAsync(FilePropertiesHelpers.GetWindowHandle(Window).ToInt64()); } } diff --git a/src/Files.App/Views/HomePage.xaml.cs b/src/Files.App/Views/HomePage.xaml.cs index f093f6d96fe6..8b9eb0be19bc 100644 --- a/src/Files.App/Views/HomePage.xaml.cs +++ b/src/Files.App/Views/HomePage.xaml.cs @@ -201,7 +201,7 @@ private async void QuickAccessWidget_CardPropertiesInvoked(object sender, QuickA ItemType = "Folder".GetLocalizedResource(), }; - await FilePropertiesHelpers.OpenPropertiesWindowAsync(listedItem, AppInstance); + FilePropertiesHelpers.OpenPropertiesWindow(listedItem, AppInstance); } private void DrivesWidget_DrivesWidgetNewPaneInvoked(object sender, DrivesWidget.DrivesWidgetInvokedEventArgs e) diff --git a/src/Files.App/Views/LayoutModes/ColumnViewBase.xaml.cs b/src/Files.App/Views/LayoutModes/ColumnViewBase.xaml.cs index 9cfbd6dfb1b6..e0f09a826bff 100644 --- a/src/Files.App/Views/LayoutModes/ColumnViewBase.xaml.cs +++ b/src/Files.App/Views/LayoutModes/ColumnViewBase.xaml.cs @@ -264,7 +264,7 @@ protected override async void FileList_PreviewKeyDown(object sender, KeyRoutedEv } else if (e.Key == VirtualKey.Enter && e.KeyStatus.IsMenuKeyDown) { - FilePropertiesHelpers.ShowProperties(ParentShellPageInstance); + FilePropertiesHelpers.OpenPropertiesWindow(ParentShellPageInstance); e.Handled = true; } else if (e.Key == VirtualKey.Space) diff --git a/src/Files.App/Views/LayoutModes/DetailsLayoutBrowser.xaml.cs b/src/Files.App/Views/LayoutModes/DetailsLayoutBrowser.xaml.cs index bdbad3f222c2..6f7a993ce2e8 100644 --- a/src/Files.App/Views/LayoutModes/DetailsLayoutBrowser.xaml.cs +++ b/src/Files.App/Views/LayoutModes/DetailsLayoutBrowser.xaml.cs @@ -336,7 +336,7 @@ protected override async void FileList_PreviewKeyDown(object sender, KeyRoutedEv } else if (e.Key == VirtualKey.Enter && e.KeyStatus.IsMenuKeyDown) { - FilePropertiesHelpers.ShowProperties(ParentShellPageInstance); + FilePropertiesHelpers.OpenPropertiesWindow(ParentShellPageInstance); e.Handled = true; } else if (e.Key == VirtualKey.Space) diff --git a/src/Files.App/Views/LayoutModes/GridViewBrowser.xaml.cs b/src/Files.App/Views/LayoutModes/GridViewBrowser.xaml.cs index 04d35f3720b5..826c84387de4 100644 --- a/src/Files.App/Views/LayoutModes/GridViewBrowser.xaml.cs +++ b/src/Files.App/Views/LayoutModes/GridViewBrowser.xaml.cs @@ -294,7 +294,7 @@ protected override async void FileList_PreviewKeyDown(object sender, KeyRoutedEv } else if (e.Key == VirtualKey.Enter && e.KeyStatus.IsMenuKeyDown) { - FilePropertiesHelpers.ShowProperties(ParentShellPageInstance); + FilePropertiesHelpers.OpenPropertiesWindow(ParentShellPageInstance); e.Handled = true; } else if (e.Key == VirtualKey.Space) diff --git a/src/Files.App/Views/MainPage.xaml.cs b/src/Files.App/Views/MainPage.xaml.cs index 1b9b3e2828cd..47e4dacc1f66 100644 --- a/src/Files.App/Views/MainPage.xaml.cs +++ b/src/Files.App/Views/MainPage.xaml.cs @@ -282,9 +282,9 @@ private async void SidebarControl_SidebarItemDropped(object sender, SidebarItemD private async void SidebarControl_SidebarItemPropertiesInvoked(object sender, SidebarItemPropertiesInvokedEventArgs e) { if (e.InvokedItemDataContext is DriveItem) - await FilePropertiesHelpers.OpenPropertiesWindowAsync(e.InvokedItemDataContext, SidebarAdaptiveViewModel.PaneHolder.ActivePane); + FilePropertiesHelpers.OpenPropertiesWindow(e.InvokedItemDataContext, SidebarAdaptiveViewModel.PaneHolder.ActivePane); else if (e.InvokedItemDataContext is LibraryLocationItem library) - await FilePropertiesHelpers.OpenPropertiesWindowAsync(new LibraryItem(library), SidebarAdaptiveViewModel.PaneHolder.ActivePane); + FilePropertiesHelpers.OpenPropertiesWindow(new LibraryItem(library), SidebarAdaptiveViewModel.PaneHolder.ActivePane); else if (e.InvokedItemDataContext is LocationItem locationItem) { ListedItem listedItem = new ListedItem(null!) @@ -295,7 +295,7 @@ private async void SidebarControl_SidebarItemPropertiesInvoked(object sender, Si ItemType = "Folder".GetLocalizedResource(), }; - await FilePropertiesHelpers.OpenPropertiesWindowAsync(listedItem, SidebarAdaptiveViewModel.PaneHolder.ActivePane); + FilePropertiesHelpers.OpenPropertiesWindow(listedItem, SidebarAdaptiveViewModel.PaneHolder.ActivePane); } } diff --git a/src/Files.App/Views/Properties/CompatibilityPage.xaml b/src/Files.App/Views/Properties/CompatibilityPage.xaml index 0ee8f7a39bd2..823d76f373c0 100644 --- a/src/Files.App/Views/Properties/CompatibilityPage.xaml +++ b/src/Files.App/Views/Properties/CompatibilityPage.xaml @@ -9,11 +9,11 @@ xmlns:settingsuc="using:Files.App.UserControls.Settings" xmlns:vm="using:Files.App.ViewModels.Properties" Loaded="Properties_Loaded" + Tag="Compatibility" mc:Ignorable="d"> - @@ -24,9 +24,10 @@ - + + + + + + + + + + SaveChangesAsync() diff --git a/src/Files.App/Views/Properties/CustomizationPage.xaml b/src/Files.App/Views/Properties/CustomizationPage.xaml index f96bed2e2460..76834e577d73 100644 --- a/src/Files.App/Views/Properties/CustomizationPage.xaml +++ b/src/Files.App/Views/Properties/CustomizationPage.xaml @@ -10,6 +10,7 @@ xmlns:uc="using:Files.App.UserControls" xmlns:vm="using:Files.App.ViewModels.Properties" DataContext="{x:Bind CustomizationViewModel, Mode=OneWay}" + Tag="Customization" mc:Ignorable="d"> @@ -40,6 +41,7 @@ + @@ -62,12 +64,14 @@ + + + - diff --git a/src/Files.App/Views/Properties/CustomizationPage.xaml.cs b/src/Files.App/Views/Properties/CustomizationPage.xaml.cs index 3c158d6dc1cc..f0f56fc11789 100644 --- a/src/Files.App/Views/Properties/CustomizationPage.xaml.cs +++ b/src/Files.App/Views/Properties/CustomizationPage.xaml.cs @@ -1,3 +1,4 @@ +using Files.App.DataModels; using Files.App.ViewModels.Properties; using Microsoft.UI.Xaml.Navigation; using System.Threading.Tasks; @@ -15,8 +16,11 @@ public CustomizationPage() protected override void OnNavigatedTo(NavigationEventArgs e) { + var parameter = (PropertiesPageNavigationParameter)e.Parameter; + base.OnNavigatedTo(e); - CustomizationViewModel = new(AppInstance, BaseProperties); + + CustomizationViewModel = new(AppInstance, BaseProperties, parameter.AppWindow); } public override Task SaveChangesAsync() diff --git a/src/Files.App/Views/Properties/DetailsPage.xaml b/src/Files.App/Views/Properties/DetailsPage.xaml index ab231537411e..e35bf9100de7 100644 --- a/src/Files.App/Views/Properties/DetailsPage.xaml +++ b/src/Files.App/Views/Properties/DetailsPage.xaml @@ -7,6 +7,7 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:vm="using:Files.App.ViewModels.Properties" Loaded="Properties_Loaded" + Tag="Details" mc:Ignorable="d"> @@ -20,12 +21,22 @@ + + + + + + + + + @@ -66,6 +79,7 @@ IsReadOnly="{x:Bind IsReadOnly, Mode=OneWay}" Style="{StaticResource PropertyValueTextBox}" Text="{x:Bind ValueText, Mode=TwoWay}" /> + @@ -76,18 +90,14 @@ + + - - - - + - + + + - + + Height="1" + Margin="-16,0" + Background="{ThemeResource DividerStrokeColorDefault}" /> - + + + + + + + + + + + + SelectedItem="{x:Bind MainPropertiesViewModel.SelectedNavigationViewItem, Mode=TwoWay}"> + - - + + @@ -85,8 +133,9 @@ Grid.Column="1" Margin="8,0,0,0" VerticalAlignment="Center" + Foreground="{ThemeResource TextFillColorSecondaryBrush}" Style="{StaticResource BodyTextBlockStyle}" - Text="{x:Bind Name}" + Text="{x:Bind Name, Mode=OneWay}" Visibility="{x:Bind IsCompact, Converter={StaticResource BoolToVisibilityInverseConverter}, Mode=OneWay}" /> @@ -103,15 +152,17 @@ - + + + @@ -119,14 +170,14 @@ + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Height="1" + Margin="-12,8,-12,4" + Background="{ThemeResource DividerStrokeColorDefaultBrush}" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + TextWrapping="NoWrap"> + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + TextWrapping="NoWrap" + Visibility="{x:Bind IsFolder, Mode=OneWay}" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - - - - - - - - - - - - - - - - - - - - - - - - + - + + + - + + + + + - + - + + - - - - - - - + - + + - + diff --git a/src/Files.App/Views/Properties/SecurityAdvancedPage.xaml.cs b/src/Files.App/Views/Properties/SecurityAdvancedPage.xaml.cs index b5bbe0f78ce7..7a9312da0359 100644 --- a/src/Files.App/Views/Properties/SecurityAdvancedPage.xaml.cs +++ b/src/Files.App/Views/Properties/SecurityAdvancedPage.xaml.cs @@ -1,143 +1,37 @@ -using CommunityToolkit.Mvvm.DependencyInjection; -using CommunityToolkit.WinUI; +using Files.App.DataModels; using Files.App.DataModels.NavigationControlItems; -using Files.App.Extensions; using Files.App.Filesystem; -using Files.App.Helpers; -using Files.App.ViewModels; using Files.App.ViewModels.Properties; -using Microsoft.UI; -using Microsoft.UI.Windowing; -using Microsoft.UI.Xaml; -using Microsoft.UI.Xaml.Controls; -using Microsoft.UI.Xaml.Input; using Microsoft.UI.Xaml.Navigation; -using Microsoft.Windows.ApplicationModel.Resources; -using System; -using Windows.Foundation.Metadata; -using Windows.System; -using Windows.UI; +using System.Threading.Tasks; namespace Files.App.Views.Properties { - public sealed partial class SecurityAdvancedPage : Page + public sealed partial class SecurityAdvancedPage : BasePropertiesPage { - private readonly SettingsViewModel AppSettings; + private SecurityAdvancedViewModel SecurityAdvancedViewModel { get; set; } public SecurityAdvancedPage() { InitializeComponent(); - AppSettings = Ioc.Default.GetRequiredService(); - _isWinUI3 = ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 8); - - AppSettings.ThemeModeChanged += AppSettings_ThemeModeChanged; - - var flowDirectionSetting = new ResourceManager().CreateResourceContext().QualifierValues["LayoutDirection"]; - if (flowDirectionSetting == "RTL") - FlowDirection = FlowDirection.RightToLeft; } - public string WindowTitle - => string.Format("SecurityAdvancedPermissionsTitle".GetLocalizedResource(), ViewModel?.Item.Name); - - public SecurityViewModel? ViewModel { get; set; } - - public Window window; - - public AppWindow? appWindow; - - private readonly bool _isWinUI3; - protected override void OnNavigatedTo(NavigationEventArgs e) { - var args = (PropertiesPageNavigationArguments)e.Parameter; - - if (args.Item is ListedItem listedItem) - ViewModel = new SecurityViewModel(listedItem); - else if (args.Item is DriveItem driveitem) - ViewModel = new SecurityViewModel(driveitem); + var np = (PropertiesPageNavigationParameter)e.Parameter; + if (np.Parameter is ListedItem listedItem) + SecurityAdvancedViewModel = new(listedItem, np.Window); + else if (np.Parameter is DriveItem driveitem) + SecurityAdvancedViewModel = new(driveitem, np.Window); base.OnNavigatedTo(e); } - private async void SecurityAdvancedPage_Loaded(object sender, RoutedEventArgs e) - { - if (_isWinUI3 && appWindow is not null) - { - appWindow.Destroying += AppWindow_Destroying; - - // Update theme - await App.Window.DispatcherQueue.EnqueueAsync(() => AppSettings.UpdateThemeElements.Execute(null)); - } - } - - private void AppWindow_Destroying(AppWindow sender, object args) - { - AppSettings.ThemeModeChanged -= AppSettings_ThemeModeChanged; - sender.Destroying -= AppWindow_Destroying; - } - - private async void AppSettings_ThemeModeChanged(object sender, EventArgs e) - { - var selectedTheme = ThemeHelper.RootTheme; - - await DispatcherQueue.EnqueueAsync(() => - { - ((Frame)Parent).RequestedTheme = selectedTheme; - - if (_isWinUI3 && appWindow is not null) - { - switch (selectedTheme) - { - case ElementTheme.Default: - appWindow.TitleBar.ButtonHoverBackgroundColor = (Color)Application.Current.Resources["SystemBaseLowColor"]; - appWindow.TitleBar.ButtonForegroundColor = (Color)Application.Current.Resources["SystemBaseHighColor"]; - break; - case ElementTheme.Light: - appWindow.TitleBar.ButtonHoverBackgroundColor = Color.FromArgb(51, 0, 0, 0); - appWindow.TitleBar.ButtonForegroundColor = Colors.Black; - break; - case ElementTheme.Dark: - appWindow.TitleBar.ButtonHoverBackgroundColor = Color.FromArgb(51, 255, 255, 255); - appWindow.TitleBar.ButtonForegroundColor = Colors.White; - break; - } - } - }); - } - - private void OKButton_Click(object sender, RoutedEventArgs e) - { - if (_isWinUI3 && appWindow is not null) - { - ViewModel?.SaveChangedAccessControlList(); - - // AppWindow.Destroy() doesn't seem to work well. (#11461) - window.Close(); - } - } - - private void CancelButton_Click(object sender, RoutedEventArgs e) - { - if (_isWinUI3 && appWindow is not null) - { - // AppWindow.Destroy() doesn't seem to work well. (#11461) - window.Close(); - } - } - - private void SecurityAdvancedPage_KeyDown(object sender, KeyRoutedEventArgs e) - { - if (e.Key.Equals(VirtualKey.Escape) && _isWinUI3 && appWindow is not null) - { - // AppWindow.Destroy() doesn't seem to work well. (#11461) - window.Close(); - } - } + public async override Task SaveChangesAsync() + => await Task.FromResult(SecurityAdvancedViewModel is null || SecurityAdvancedViewModel.SaveChangedAccessControlList()); - public class PropertiesPageNavigationArguments + public override void Dispose() { - public object? Item { get; set; } } } } diff --git a/src/Files.App/Views/Properties/SecurityPage.xaml b/src/Files.App/Views/Properties/SecurityPage.xaml index c0b0ff0c5d33..b8a61561f6bf 100644 --- a/src/Files.App/Views/Properties/SecurityPage.xaml +++ b/src/Files.App/Views/Properties/SecurityPage.xaml @@ -3,11 +3,13 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:fssecurity="using:Files.App.Filesystem.Security" xmlns:helpers="using:Files.App.Helpers" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:security="using:Files.App.Filesystem.Security" xmlns:vm="using:Files.App.ViewModels.Properties" xmlns:wctconverters="using:CommunityToolkit.WinUI.UI.Converters" + DataContext="{x:Bind SecurityViewModel, Mode=OneWay}" + Tag="Security" mc:Ignorable="d"> @@ -17,22 +19,26 @@ + - + - + + @@ -80,16 +87,18 @@ + + - + @@ -135,9 +144,16 @@ + + + - + + @@ -162,19 +179,18 @@ Grid.Column="0" Margin="4" VerticalAlignment="Center" + Text="{x:Bind SecurityViewModel.SelectedItemHeaderText, Mode=OneWay}" TextTrimming="CharacterEllipsis" TextWrapping="NoWrap" - ToolTipService.ToolTip="{x:Bind SecurityViewModel.SelectedAccessControlEntry.Principal.DisplayName, Mode=OneWay}"> - - - + ToolTipService.ToolTip="{x:Bind SecurityViewModel.SelectedItemHeaderText, Mode=OneWay}" />