Skip to content

Commit 0164e63

Browse files
JasonPatch /3 (#35425)
* JasonPatch /3 * JasonPatch /3 * JasonPatch /3 * JasonPatch /3 * JasonPatch /3 * Update aspnetcore/release-notes/aspnetcore-10/includes/jsonPatch.md Co-authored-by: Mike Kistler <[email protected]> * JasonPatch /3 --------- Co-authored-by: Mike Kistler <[email protected]>
1 parent 89582f2 commit 0164e63

File tree

2 files changed

+243
-0
lines changed

2 files changed

+243
-0
lines changed

aspnetcore/release-notes/aspnetcore-10.0.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@ This section describes miscellaneous new features in ASP.NET Core 10.0.
8282

8383
[!INCLUDE[](~/release-notes/aspnetcore-10/includes/testAppsTopLevel.md)]
8484

85+
86+
[!INCLUDE[](~/release-notes/aspnetcore-10/includes/jsonPatch.md)]
87+
8588
### Detect if URL is local using `RedirectHttpResult.IsLocalUrl`
8689

8790
Use the new [`RedirectHttpResult.IsLocalUrl(url)`](https://source.dot.net/#Microsoft.AspNetCore.Http.Results/RedirectHttpResult.cs,c0ece2e6266cb369) helper method to detect if a URL is local. A URL is considered local if the following are true:
Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
### New JsonPatch Implementation with System.Text.Json
2+
3+
**[JSON Patch](https://jsonpatch.com/)**:
4+
5+
* Is a standard format for describing changes to apply to a JSON document.
6+
* Is defined in [RFC 6902] and is widely used in RESTful APIs to perform partial updates to JSON resources.
7+
* Represents a sequence of operations (for example, Add, Remove, Replace, Move, Copy, Test) that can be applied to modify a JSON document.
8+
9+
In web apps, JSON Patch is commonly used in a PATCH operation to perform partial updates of a resource. Rather than sending the entire resource for an update, clients can send a JSON Patch document containing only the changes. Patching reduces payload size and improves efficiency.
10+
11+
[RFC 6902]: https://tools.ietf.org/html/rfc6902
12+
13+
This release introduces a new implementation of `JsonPatch` based on `System.Text.Json` serialization. This feature:
14+
15+
* Aligns with modern .NET practices by leveraging the `System.Text.Json` library, which is optimized for .NET.
16+
* Provides improved performance and reduced memory usage compared to the legacy `Newtonsoft.Json`-based implementation.
17+
18+
The following benchmarks compare the performance of the new `System.Text.Json` implementation with the legacy `Newtonsoft.Json` implementation:
19+
20+
| Scenario | Implementation | Mean | Allocated Memory |
21+
|----------------------------|------------------------|------------|------------------|
22+
| **Application Benchmarks** | Newtonsoft.JsonPatch | 271.924 µs | 25 KB |
23+
| | System.Text.JsonPatch | 1.584 µs | 3 KB |
24+
| **Deserialization Benchmarks** | Newtonsoft.JsonPatch | 19.261 µs | 43 KB |
25+
| | System.Text.JsonPatch | 7.917 µs | 7 KB |
26+
27+
These benchmarks highlight significant performance gains and reduced memory usage with the new implementation.
28+
29+
Notes:
30+
* The new implementation isn't a drop-in replacement for the legacy implementation. In particular:
31+
* The new implementation doesn't support dynamic types, for example, [`ExpandoObject`](/dotnet/api/system.dynamic.expandoobject.
32+
* The JSON Patch standard has ***inherent security risks***. Since these risks are inherent to the JSON Patch standard, the new implementation ***doesn't attempt to mitigate inherent security risks***. It's the responsibility of the developer to ensure that the JSON Patch document is safe to apply to the target object. For more information, see the [Mitigating Security Risks](#mitigating-security-risks) section.
33+
34+
#### Usage
35+
36+
To enable JSON Patch support with `System.Text.Json`, install the [`Microsoft.AspNetCore.JsonPatch.SystemTextJson`](https://www.nuget.org/packages/Microsoft.AspNetCore.JsonPatch/) NuGet package.
37+
38+
```sh
39+
dotnet add package Microsoft.AspNetCore.JsonPatch.SystemTextJson --prerelease
40+
```
41+
42+
This package provides a `JsonPatchDocument#### T>` class to represent a JSON Patch document for objects of type `T` and custom logic for serializing and deserializing JSON Patch documents using `System.Text.Json`. The key method of the `JsonPatchDocument<T>` class is `ApplyTo`, which applies the patch operations to a target object of type `T`.
43+
44+
The following examples demonstrate how to use the `ApplyTo` method to apply a JSON Patch document to an object.
45+
46+
#### Example: Applying a JsonPatchDocument
47+
48+
The following example demonstrates:
49+
50+
1. The `add`, `replace`, and `remove` operations.
51+
2. Operations on nested properties.
52+
3. Adding a new item to an array.
53+
4. Using a JSON String Enum Converter in a JSON patch document.
54+
55+
```csharp
56+
// Original object
57+
var person = new Person {
58+
FirstName = "John",
59+
LastName = "Doe",
60+
Email = "[email protected]",
61+
PhoneNumbers = [new() {Number = "123-456-7890", Type = PhoneNumberType.Mobile}],
62+
Address = new Address
63+
{
64+
Street = "123 Main St",
65+
City = "Anytown",
66+
State = "TX"
67+
}
68+
};
69+
70+
// Raw JSON patch document
71+
string jsonPatch = """
72+
[
73+
{ "op": "replace", "path": "/FirstName", "value": "Jane" },
74+
{ "op": "remove", "path": "/Email"},
75+
{ "op": "add", "path": "/Address/ZipCode", "value": "90210" },
76+
{ "op": "add", "path": "/PhoneNumbers/-", "value": { "Number": "987-654-3210",
77+
"Type": "Work" } }
78+
]
79+
""";
80+
81+
// Deserialize the JSON patch document
82+
var patchDoc = JsonSerializer.Deserialize<JsonPatchDocument<Person>>(jsonPatch);
83+
84+
// Apply the JSON patch document
85+
patchDoc!.ApplyTo(person);
86+
87+
// Output updated object
88+
Console.WriteLine(JsonSerializer.Serialize(person, serializerOptions));
89+
90+
// Output:
91+
// {
92+
// "firstName": "Jane",
93+
// "lastName": "Doe",
94+
// "address": {
95+
// "street": "123 Main St",
96+
// "city": "Anytown",
97+
// "state": "TX",
98+
// "zipCode": "90210"
99+
// },
100+
// "phoneNumbers": [
101+
// {
102+
// "number": "123-456-7890",
103+
// "type": "Mobile"
104+
// },
105+
// {
106+
// "number": "987-654-3210",
107+
// "type": "Work"
108+
// }
109+
// ]
110+
// }
111+
```
112+
113+
The `ApplyTo` method generally follows the conventions and options of `System.Text.Json` for processing the `JsonPatchDocument`, including the behavior controlled by the following options:
114+
115+
* `NumberHandling`: Whether numeric properties can be read from strings.
116+
* `PropertyNameCaseInsensitive`: Whether property names are case-sensitive.
117+
118+
Key differences between `System.Text.Json` and the new `JsonPatchDocument<T>` implementation:
119+
120+
* The runtime type of the target object, not the declared type, determines which properties `ApplyTo` patches.
121+
* `System.Text.Json` deserialization relies on the declared type to identify eligible properties.
122+
123+
#### Example: Applying a JsonPatchDocument with error handling
124+
125+
There are various errors that can occur when applying a JSON Patch document. For example, the target object may not have the specified property, or the value specified might be incompatible with the property type.
126+
127+
JSON `Patch` also supports the `test` operation. The `test` operation checks if a specified value is equal to the target property, and if not, returns an error.
128+
129+
The following example demonstrates how to handle these errors gracefully.
130+
131+
> [!Important]
132+
> The object passed to the `ApplyTo` method is modified in place. It is the caller's responsiblity to discard these changes if any operation fails.
133+
134+
```csharp
135+
// Original object
136+
var person = new Person {
137+
FirstName = "John",
138+
LastName = "Doe",
139+
140+
};
141+
142+
// Raw JSON patch document
143+
string jsonPatch = """
144+
[
145+
{ "op": "replace", "path": "/Email", "value": "[email protected]"},
146+
{ "op": "test", "path": "/FirstName", "value": "Jane" },
147+
{ "op": "replace", "path": "/LastName", "value": "Smith" }
148+
]
149+
""";
150+
151+
// Deserialize the JSON patch document
152+
var patchDoc = JsonSerializer.Deserialize<JsonPatchDocument<Person>>(jsonPatch);
153+
154+
// Apply the JSON patch document, catching any errors
155+
Dictionary<string, string[]>? errors = null;
156+
patchDoc!.ApplyTo(person, jsonPatchError =>
157+
{
158+
errors ??= new ();
159+
var key = jsonPatchError.AffectedObject.GetType().Name;
160+
if (!errors.ContainsKey(key))
161+
{
162+
errors.Add(key, new string[] { });
163+
}
164+
errors[key] = errors[key].Append(jsonPatchError.ErrorMessage).ToArray();
165+
});
166+
if (errors != null)
167+
{
168+
// Print the errors
169+
foreach (var error in errors)
170+
{
171+
Console.WriteLine($"Error in {error.Key}: {string.Join(", ", error.Value)}");
172+
}
173+
}
174+
175+
// Output updated object
176+
Console.WriteLine(JsonSerializer.Serialize(person, serializerOptions));
177+
178+
// Output:
179+
// Error in Person: The current value 'John' at path 'FirstName' is not equal
180+
// to the test value 'Jane'.
181+
// {
182+
// "firstName": "John",
183+
// "lastName": "Smith", <<< Modified!
184+
// "email": "[email protected]", <<< Modified!
185+
// "phoneNumbers": []
186+
// }
187+
```
188+
189+
#### Mitigating security risks
190+
191+
When using the `Microsoft.AspNetCore.JsonPatch.SystemTextJson` package, it's critical to understand and mitigate potential security risks. The following sections outline the identified security risks associated with JSON Patch and provide recommended mitigations to ensure secure usage of the package.
192+
193+
> [!IMPORTANT]
194+
> ***This is not an exhaustive list of threats.*** app developers must conduct their own threat model reviews to determine an app-specific comprehensive list and come up with appropriate mitigations as needed. For example, apps which expose collections to patch operations should consider the potential for algorithmic complexity attacks if those operations insert or remove elements at the beginning of the collection.
195+
196+
By running comprehensive threat models for their own apps and addressing identified threats while following the recommended mitigations below, consumers of these packages can integrate JSON Patch functionality into their apps while minimizing security risks.
197+
198+
Consumers of these packages can integrate JSON Patch functionality into their apps while minimizing security risks, including:
199+
200+
* Run comprehensive threat models for their own apps.
201+
* Address identified threats.
202+
* Follow the recommended mitigations in the following sections.
203+
204+
##### Denial of Service (DoS) via memory amplification
205+
206+
* **Scenario**: A malicious client submits a `copy` operation that duplicates large object graphs multiple times, leading to excessive memory consumption.
207+
* **Impact**: Potential Out-Of-Memory (OOM) conditions, causing service disruptions.
208+
* **Mitigation**:
209+
* Validate incoming JSON Patch documents for size and structure before calling `ApplyTo`.
210+
* The validation needs to be app specific, but an example validation can look similar to the following:
211+
212+
```csharp
213+
public void Validate(JsonPatchDocument<T> patch)
214+
{
215+
// This is just an example. It's up to the developer to make sure that
216+
// this case is handled properly, based on the app needs.
217+
if (patch.Operations.Where(op=>op.OperationType == OperationType.Copy).Count()
218+
> MaxCopyOperationsCount)
219+
{
220+
throw new InvalidOperationException();
221+
}
222+
}
223+
```
224+
225+
##### Business Logic Subversion
226+
227+
* **Scenario**: Patch operations can manipulate fields with implicit invariants, (e.g., internal flags, IDs, or computed fields), violating business constraints.
228+
* **Impact**: Data integrity issues and unintended app behavior.
229+
* **Mitigation**:
230+
* Use POCO objects with explicitly defined properties that are safe to modify.
231+
* Avoid exposing sensitive or security-critical properties in the target object.
232+
* If no POCO object is used, validate the patched object after applying operations to ensure business rules and invariants aren't violated.
233+
234+
##### Authentication and authorization
235+
236+
* **Scenario**: Unauthenticated or unauthorized clients send malicious JSON Patch requests.
237+
* **Impact**: Unauthorized access to modify sensitive data or disrupt app behavior.
238+
* **Mitigation**:
239+
* Protect endpoints accepting JSON Patch requests with proper authentication and authorization mechanisms.
240+
* Restrict access to trusted clients or users with appropriate permissions.

0 commit comments

Comments
 (0)