diff --git a/.openpublishing.redirection.json b/.openpublishing.redirection.json index 02c81da..2ba5164 100644 --- a/.openpublishing.redirection.json +++ b/.openpublishing.redirection.json @@ -564,6 +564,14 @@ { "source_path": "dotnet-desktop-guide/framework/wpf/properties/read-only-dependency-properties.md", "redirect_url": "/dotnet/desktop/wpf/advanced/read-only-dependency-properties?view=netframeworkdesktop-4.8" + }, + { + "source_path": "dotnet-desktop-guide/net/wpf/advanced/dependency-property-security.md", + "redirect_url": "/dotnet/desktop/wpf/properties/dependency-property-security?view=netdesktop-6.0" + }, + { + "source_path": "dotnet-desktop-guide/framework/wpf/properties/dependency-property-security.md", + "redirect_url": "/dotnet/desktop/wpf/advanced/dependency-property-security?view=netframeworkdesktop-4.8" } ] } diff --git a/dotnet-desktop-guide/net/wpf/properties/dependency-property-security.md b/dotnet-desktop-guide/net/wpf/properties/dependency-property-security.md new file mode 100644 index 0000000..3592823 --- /dev/null +++ b/dotnet-desktop-guide/net/wpf/properties/dependency-property-security.md @@ -0,0 +1,61 @@ +--- +title: "Dependency property security" +description: Learn about the dependency property accessibility and security in Windows Presentation Foundation (WPF). +ms.date: "12/03/2021" +dev_langs: + - "csharp" + - "vb" +helpviewer_keywords: + - "wrappers [WPF], access" + - "wrappers [WPF], security" + - "dependency properties [WPF], security" + - "security [WPF], wrappers" + - "validation [WPF], dependency properties" + - "dependency properties [WPF], access" + - "security [WPF], dependency properties" +--- + + +# Dependency property security (WPF .NET) + +The accessibility of read-write dependency properties through the Windows Presentation Foundation (WPF) property system effectively makes them public properties. As a result, it's not possible to make security guarantees about read-write dependency property values. The WPF property system provides more security for read-only dependency properties so that you can restrict write access. + +[!INCLUDE [desktop guide under construction](../../includes/desktop-guide-preview-note.md)] + +## Access and security of property wrappers + +A common language runtime (CLR) property wrapper is usually included in read-write dependency property implementations to simplify getting or setting property values. If included, the CLR property wrapper is a convenience method that implements the and static calls that interact with the underlying dependency property. Essentially, a CLR property wrapper exposes a dependency property as a CLR property backed by a dependency property rather than a private field. + +Applying security mechanisms and restricting access to the CLR property wrapper might prevent usage of the convenience method, but those techniques won't prevent direct calls to `GetValue` or `SetValue`. In other words, a read-write dependency property is always accessible through the WPF property system. If you're implementing a read-write dependency property, avoid restricting access to the CLR property wrapper. Instead, declare the CLR property wrapper as a public member so callers are aware of the true access level of the dependency property. + +## Property system exposure of dependency properties + +The WPF property system provides access to a read-write dependency property through its identifier. The identifier is usable in and calls. Even if the static identifier field is non-public, several aspects of the property system will return a `DependencyProperty` as it exists on an instance of a class or derived class. For example, the method returns identifiers for dependency property instances with a locally set value. Also, you can override the virtual method to receive event data that will report the `DependencyProperty` identifier for dependency properties that have changed value. To make callers aware of the true access level of a read-write dependency property, declare its identifier field as a public member. + +> [!NOTE] +> Although declaring a dependency property identifier field as `private` reduces the number of ways that a read-write dependency property is accessible, the property won't be [private](/dotnet/csharp/language-reference/keywords/private) according to the CLR language definition. + +### Validation security + +Applying a to a and expecting validation to fail on `Demand` failure, isn't an adequate security mechanism for restricting property value changes. Also, new value invalidation enforced through `ValidateValueCallback` can be suppressed by malicious callers, if those callers are operating within the application domain. + +## Access to read-only dependency properties + +To restrict access, register your property as a read-only dependency property by calling the method. The `RegisterReadOnly` method returns a , which you can assign to a non-public class field. For read-only dependency properties, the WPF property system will only provide write access to those who have a reference to the `DependencyPropertyKey`. To illustrate this behavior, the following test code: + +- Instantiates a class that implements both read-write and read-only dependency properties. +- Assigns a `private` access modifier to each identifier. +- Only implements `get` accessors. +- Uses the method to access the underlying dependency properties through the WPF property system. +- Calls and to test access to each dependency property value. + +:::code language="csharp" source="./snippets/dependency-property-security/csharp/MainWindow.xaml.cs" id="DependencyPropertyAccessTests"::: +:::code language="vb" source="./snippets/dependency-property-security/vb/MainWindow.xaml.vb" id="DependencyPropertyAccessTests"::: + +## See also + +- +- +- +- [Custom dependency properties](custom-dependency-properties.md) +- [Implement a Dependency property](how-to-implement-a-dependency-property.md) diff --git a/dotnet-desktop-guide/net/wpf/properties/snippets/dependency-property-security/csharp/App.xaml.cs b/dotnet-desktop-guide/net/wpf/properties/snippets/dependency-property-security/csharp/App.xaml.cs new file mode 100644 index 0000000..3128552 --- /dev/null +++ b/dotnet-desktop-guide/net/wpf/properties/snippets/dependency-property-security/csharp/App.xaml.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Data; +using System.Linq; +using System.Threading.Tasks; +using System.Windows; + +namespace CodeSampleCsharp +{ + /// + /// Interaction logic for App.xaml + /// + public partial class App : Application + { + } +} diff --git a/dotnet-desktop-guide/net/wpf/properties/snippets/dependency-property-security/csharp/AssemblyInfo.cs b/dotnet-desktop-guide/net/wpf/properties/snippets/dependency-property-security/csharp/AssemblyInfo.cs new file mode 100644 index 0000000..8b5504e --- /dev/null +++ b/dotnet-desktop-guide/net/wpf/properties/snippets/dependency-property-security/csharp/AssemblyInfo.cs @@ -0,0 +1,10 @@ +using System.Windows; + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] diff --git a/dotnet-desktop-guide/net/wpf/properties/snippets/dependency-property-security/csharp/CodeSampleCsharp.csproj b/dotnet-desktop-guide/net/wpf/properties/snippets/dependency-property-security/csharp/CodeSampleCsharp.csproj new file mode 100644 index 0000000..22d78ee --- /dev/null +++ b/dotnet-desktop-guide/net/wpf/properties/snippets/dependency-property-security/csharp/CodeSampleCsharp.csproj @@ -0,0 +1,24 @@ + + + + WinExe + net6.0-windows + true + + + + + True + True + Resources.resx + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + diff --git a/dotnet-desktop-guide/net/wpf/properties/snippets/dependency-property-security/csharp/MainWindow.xaml b/dotnet-desktop-guide/net/wpf/properties/snippets/dependency-property-security/csharp/MainWindow.xaml new file mode 100644 index 0000000..40e5615 --- /dev/null +++ b/dotnet-desktop-guide/net/wpf/properties/snippets/dependency-property-security/csharp/MainWindow.xaml @@ -0,0 +1,5 @@ + + diff --git a/dotnet-desktop-guide/net/wpf/properties/snippets/dependency-property-security/csharp/MainWindow.xaml.cs b/dotnet-desktop-guide/net/wpf/properties/snippets/dependency-property-security/csharp/MainWindow.xaml.cs new file mode 100644 index 0000000..c4ef0c5 --- /dev/null +++ b/dotnet-desktop-guide/net/wpf/properties/snippets/dependency-property-security/csharp/MainWindow.xaml.cs @@ -0,0 +1,100 @@ +using System; +using System.Diagnostics; +using System.Windows; + +namespace CodeSampleCsharp +{ + /// + /// Interaction logic for MainWindow.xaml. + /// + public partial class MainWindow : Window + { + public MainWindow() + { + InitializeComponent(); + + DependencyPropertyAccessTests(); + } + + // + /// + /// Test get/set access to dependency properties exposed through the WPF property system. + /// + public static void DependencyPropertyAccessTests() + { + // Instantiate a class that implements read-write and read-only dependency properties. + Aquarium _aquarium = new(); + // Access each dependency property using the LocalValueEnumerator method. + LocalValueEnumerator localValueEnumerator = _aquarium.GetLocalValueEnumerator(); + while (localValueEnumerator.MoveNext()) + { + DependencyProperty dp = localValueEnumerator.Current.Property; + string dpType = dp.ReadOnly ? "read-only" : "read-write"; + // Test read access. + Debug.WriteLine($"Attempting to get a {dpType} dependency property value..."); + Debug.WriteLine($"Value ({dpType}): {(int)_aquarium.GetValue(dp)}"); + // Test write access. + try + { + Debug.WriteLine($"Attempting to set a {dpType} dependency property value to 2..."); + _aquarium.SetValue(dp, 2); + } + catch (InvalidOperationException e) + { + Debug.WriteLine(e.Message); + } + finally + { + Debug.WriteLine($"Value ({dpType}): {(int)_aquarium.GetValue(dp)}"); + } + } + + // Test output: + + // Attempting to get a read-write dependency property value... + // Value (read-write): 1 + // Attempting to set a read-write dependency property value to 2... + // Value (read-write): 2 + + // Attempting to get a read-only dependency property value... + // Value (read-only): 1 + // Attempting to set a read-only dependency property value to 2... + // 'FishCountReadOnly' property was registered as read-only + // and cannot be modified without an authorization key. + // Value (read-only): 1 + } + } + + public class Aquarium : DependencyObject + { + public Aquarium() + { + // Assign locally-set values. + SetValue(FishCountProperty, 1); + SetValue(FishCountReadOnlyPropertyKey, 1); + } + + // Failed attempt to restrict write-access by assigning the + // DependencyProperty identifier to a non-public field. + private static readonly DependencyProperty FishCountProperty = + DependencyProperty.Register( + name: "FishCount", + propertyType: typeof(int), + ownerType: typeof(Aquarium), + typeMetadata: new PropertyMetadata()); + + // Successful attempt to restrict write-access by assigning the + // DependencyPropertyKey to a non-public field. + private static readonly DependencyPropertyKey FishCountReadOnlyPropertyKey = + DependencyProperty.RegisterReadOnly( + name: "FishCountReadOnly", + propertyType: typeof(int), + ownerType: typeof(Aquarium), + typeMetadata: new PropertyMetadata()); + + // Declare public get accessors. + public int FishCount => (int)GetValue(FishCountProperty); + public int FishCountReadOnly => (int)GetValue(FishCountReadOnlyPropertyKey.DependencyProperty); + } + // +} diff --git a/dotnet-desktop-guide/net/wpf/properties/snippets/dependency-property-security/csharp/Properties/Resources.Designer.cs b/dotnet-desktop-guide/net/wpf/properties/snippets/dependency-property-security/csharp/Properties/Resources.Designer.cs new file mode 100644 index 0000000..64552f9 --- /dev/null +++ b/dotnet-desktop-guide/net/wpf/properties/snippets/dependency-property-security/csharp/Properties/Resources.Designer.cs @@ -0,0 +1,63 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace CodeSampleCsharp.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("CodeSampleCsharp.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/dotnet-desktop-guide/net/wpf/properties/snippets/dependency-property-security/csharp/Properties/Resources.resx b/dotnet-desktop-guide/net/wpf/properties/snippets/dependency-property-security/csharp/Properties/Resources.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/dotnet-desktop-guide/net/wpf/properties/snippets/dependency-property-security/csharp/Properties/Resources.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/dotnet-desktop-guide/net/wpf/properties/snippets/dependency-property-security/csharp/app.xaml b/dotnet-desktop-guide/net/wpf/properties/snippets/dependency-property-security/csharp/app.xaml new file mode 100644 index 0000000..867d2f0 --- /dev/null +++ b/dotnet-desktop-guide/net/wpf/properties/snippets/dependency-property-security/csharp/app.xaml @@ -0,0 +1,9 @@ + + + + + diff --git a/dotnet-desktop-guide/net/wpf/properties/snippets/dependency-property-security/vb/Application.xaml b/dotnet-desktop-guide/net/wpf/properties/snippets/dependency-property-security/vb/Application.xaml new file mode 100644 index 0000000..f225972 --- /dev/null +++ b/dotnet-desktop-guide/net/wpf/properties/snippets/dependency-property-security/vb/Application.xaml @@ -0,0 +1,9 @@ + + + + + diff --git a/dotnet-desktop-guide/net/wpf/properties/snippets/dependency-property-security/vb/Application.xaml.vb b/dotnet-desktop-guide/net/wpf/properties/snippets/dependency-property-security/vb/Application.xaml.vb new file mode 100644 index 0000000..084cbe9 --- /dev/null +++ b/dotnet-desktop-guide/net/wpf/properties/snippets/dependency-property-security/vb/Application.xaml.vb @@ -0,0 +1,6 @@ +Class Application + + ' Application-level events, such as Startup, Exit, and DispatcherUnhandledException + ' can be handled in this file. + +End Class diff --git a/dotnet-desktop-guide/net/wpf/properties/snippets/dependency-property-security/vb/AssemblyInfo.vb b/dotnet-desktop-guide/net/wpf/properties/snippets/dependency-property-security/vb/AssemblyInfo.vb new file mode 100644 index 0000000..025ee72 --- /dev/null +++ b/dotnet-desktop-guide/net/wpf/properties/snippets/dependency-property-security/vb/AssemblyInfo.vb @@ -0,0 +1,11 @@ +Imports System.Windows + +'The ThemeInfo attribute describes where any theme specific and generic resource dictionaries can be found. +'1st parameter: where theme specific resource dictionaries are located +'(used if a resource is not found in the page, +' or application resource dictionaries) + +'2nd parameter: where the generic resource dictionary is located +'(used if a resource is not found in the page, +'app, and any theme specific resource dictionaries) + diff --git a/dotnet-desktop-guide/net/wpf/properties/snippets/dependency-property-security/vb/CodeSampleVb.vbproj b/dotnet-desktop-guide/net/wpf/properties/snippets/dependency-property-security/vb/CodeSampleVb.vbproj new file mode 100644 index 0000000..34db9b0 --- /dev/null +++ b/dotnet-desktop-guide/net/wpf/properties/snippets/dependency-property-security/vb/CodeSampleVb.vbproj @@ -0,0 +1,22 @@ + + + + WinExe + net6.0-windows + CodeSampleVb + true + + + + + + + + + + + + + + + diff --git a/dotnet-desktop-guide/net/wpf/properties/snippets/dependency-property-security/vb/MainWindow.xaml b/dotnet-desktop-guide/net/wpf/properties/snippets/dependency-property-security/vb/MainWindow.xaml new file mode 100644 index 0000000..30392c0 --- /dev/null +++ b/dotnet-desktop-guide/net/wpf/properties/snippets/dependency-property-security/vb/MainWindow.xaml @@ -0,0 +1,5 @@ + + diff --git a/dotnet-desktop-guide/net/wpf/properties/snippets/dependency-property-security/vb/MainWindow.xaml.vb b/dotnet-desktop-guide/net/wpf/properties/snippets/dependency-property-security/vb/MainWindow.xaml.vb new file mode 100644 index 0000000..3d1271c --- /dev/null +++ b/dotnet-desktop-guide/net/wpf/properties/snippets/dependency-property-security/vb/MainWindow.xaml.vb @@ -0,0 +1,100 @@ +Namespace CodeSampleVb + + ' + ' Interaction logic for MainWindow.xaml. + ' + Partial Public Class MainWindow + Inherits Window + + Public Sub New() + InitializeComponent() + DependencyPropertyAccessTests() + End Sub + + ' + ''' + ''' ' Test get/set access to dependency properties exposed through the WPF property system. + ''' + Public Shared Sub DependencyPropertyAccessTests() + ' Instantiate a class that implements read-write and read-only dependency properties. + Dim _aquarium As New Aquarium() + ' Access each dependency property using the LocalValueEnumerator method. + Dim localValueEnumerator As LocalValueEnumerator = _aquarium.GetLocalValueEnumerator() + While localValueEnumerator.MoveNext() + Dim dp As DependencyProperty = localValueEnumerator.Current.[Property] + Dim dpType As String = If(dp.[ReadOnly], "read-only", "read-write") + ' Test read access. + Debug.WriteLine($"Attempting to get a {dpType} dependency property value...") + Debug.WriteLine($"Value ({dpType}): {CInt(_aquarium.GetValue(dp))}") + ' Test write access. + Try + Debug.WriteLine($"Attempting to set a {dpType} dependency property value to 2...") + _aquarium.SetValue(dp, 2) + Catch e As InvalidOperationException + Debug.WriteLine(e.Message) + Finally + Debug.WriteLine($"Value ({dpType}): {CInt(_aquarium.GetValue(dp))}") + End Try + End While + + ' Test output + + ' Attempting to get a read-write dependency property value... + ' Value (read-write): 1 + ' Attempting to set a read-write dependency property value to 2... + ' Value (read-write): 2 + + ' Attempting to get a read-only dependency property value... + ' Value (read-only): 1 + ' Attempting to set a read-only dependency property value to 2... + ' 'FishCountReadOnly' property was registered as read-only + ' and cannot be modified without an authorization key. + ' Value (read-only): 1 + End Sub + + End Class + + Public Class Aquarium + Inherits DependencyObject + + Public Sub New() + ' Assign locally-set values. + SetValue(FishCountProperty, 1) + SetValue(FishCountReadOnlyPropertyKey, 1) + End Sub + + ' Failed attempt to restrict write-access by assigning the + ' DependencyProperty identifier to a non-public field. + Private Shared ReadOnly FishCountProperty As DependencyProperty = + DependencyProperty.Register( + name:="FishCount", + propertyType:=GetType(Integer), + ownerType:=GetType(Aquarium), + typeMetadata:=New PropertyMetadata()) + + ' Successful attempt to restrict write-access by assigning the + ' DependencyPropertyKey to a non-public field. + Private Shared ReadOnly FishCountReadOnlyPropertyKey As DependencyPropertyKey = + DependencyProperty.RegisterReadOnly( + name:="FishCountReadOnly", + propertyType:=GetType(Integer), + ownerType:=GetType(Aquarium), + typeMetadata:=New PropertyMetadata()) + + ' Declare public get accessors. + Public ReadOnly Property FishCount As Integer + Get + Return GetValue(FishCountProperty) + End Get + End Property + + Public ReadOnly Property FishCountReadOnly As Integer + Get + Return GetValue(FishCountReadOnlyPropertyKey.DependencyProperty) + End Get + End Property + + End Class + ' + +End Namespace diff --git a/dotnet-desktop-guide/net/wpf/toc.yml b/dotnet-desktop-guide/net/wpf/toc.yml index b5d70db..a737976 100644 --- a/dotnet-desktop-guide/net/wpf/toc.yml +++ b/dotnet-desktop-guide/net/wpf/toc.yml @@ -96,6 +96,8 @@ items: href: properties/dependency-property-callbacks-and-validation.md - name: Read-only dependency properties href: properties/read-only-dependency-properties.md + - name: Dependency property security + href: properties/dependency-property-security.md - name: Common tasks items: - name: Implement a dependency property diff --git a/redirects_generator/definitions.json b/redirects_generator/definitions.json index 8f8622e..6c8db5d 100644 --- a/redirects_generator/definitions.json +++ b/redirects_generator/definitions.json @@ -383,6 +383,10 @@ "SourceUrl": "/dotnet/desktop/wpf/advanced/read-only-dependency-properties?view=netframeworkdesktop-4.8", "TargetUrl": "/dotnet/desktop/wpf/properties/read-only-dependency-properties?view=netdesktop-6.0" }, + { + "SourceUrl": "/dotnet/desktop/wpf/advanced/dependency-property-security?view=netframeworkdesktop-4.8", + "TargetUrl": "/dotnet/desktop/wpf/properties/dependency-property-security?view=netdesktop-6.0" + }, // Systems - XAML {