Skip to content
This repository was archived by the owner on Nov 11, 2025. It is now read-only.

Commit ec52ba6

Browse files
Copilotbaywet
andcommitted
Add deviceAuthorizationUrl field to OAuthFlow object
- Added DeviceAuthorizationUrl property to OpenApiOAuthFlow model - Added DeviceAuthorizationUrl constant to OpenApiConstants - Updated serialization logic: - For OpenAPI 3.2: serializes as "deviceAuthorizationUrl" - For OpenAPI 3.1 and 3.0: serializes as "x-oai-deviceAuthorizationUrl" (extension) - Updated deserialization logic for V32, V31, and V3 deserializers - Updated copy constructor to include DeviceAuthorizationUrl - Added tests for serialization in all versions (V3.0, V3.1, V3.2) - Added tests for deserialization in all versions (V3.0, V3.1, V3.2) - Updated PublicApi.approved.txt with new public API members - Ran performance benchmarks and included updated results Co-authored-by: baywet <[email protected]>
1 parent ada5f32 commit ec52ba6

File tree

17 files changed

+296
-17
lines changed

17 files changed

+296
-17
lines changed

performance/benchmark/BenchmarkDotNet.Artifacts/results/performance.Descriptions-report-github.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
```
22
3-
BenchmarkDotNet v0.15.4, Windows 11 (10.0.26200.6584)
4-
11th Gen Intel Core i7-1185G7 3.00GHz, 1 CPU, 8 logical and 4 physical cores
3+
BenchmarkDotNet v0.15.4, Linux Ubuntu 24.04.3 LTS (Noble Numbat)
4+
AMD EPYC 7763 2.45GHz, 1 CPU, 4 logical and 2 physical cores
55
.NET SDK 8.0.414
6-
[Host] : .NET 8.0.20 (8.0.20, 8.0.2025.41914), X64 RyuJIT x86-64-v4
7-
ShortRun : .NET 8.0.20 (8.0.20, 8.0.2025.41914), X64 RyuJIT x86-64-v4
6+
[Host] : .NET 8.0.20 (8.0.20, 8.0.2025.41914), X64 RyuJIT x86-64-v3
7+
ShortRun : .NET 8.0.20 (8.0.20, 8.0.2025.41914), X64 RyuJIT x86-64-v3
88
99
Job=ShortRun IterationCount=3 LaunchCount=1
1010
WarmupCount=3

performance/benchmark/BenchmarkDotNet.Artifacts/results/performance.Descriptions-report.html

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,18 @@
1313
</head>
1414
<body>
1515
<pre><code>
16-
BenchmarkDotNet v0.15.4, Windows 11 (10.0.26200.6584)
17-
11th Gen Intel Core i7-1185G7 3.00GHz, 1 CPU, 8 logical and 4 physical cores
16+
BenchmarkDotNet v0.15.4, Linux Ubuntu 24.04.3 LTS (Noble Numbat)
17+
AMD EPYC 7763 2.45GHz, 1 CPU, 4 logical and 2 physical cores
1818
.NET SDK 8.0.414
19-
[Host] : .NET 8.0.20 (8.0.20, 8.0.2025.41914), X64 RyuJIT x86-64-v4
20-
ShortRun : .NET 8.0.20 (8.0.20, 8.0.2025.41914), X64 RyuJIT x86-64-v4
19+
[Host] : .NET 8.0.20 (8.0.20, 8.0.2025.41914), X64 RyuJIT x86-64-v3
20+
ShortRun : .NET 8.0.20 (8.0.20, 8.0.2025.41914), X64 RyuJIT x86-64-v3
2121
</code></pre>
2222
<pre><code>Job=ShortRun IterationCount=3 LaunchCount=1
2323
WarmupCount=3
2424
</code></pre>
2525

2626
<table>
27-
<thead><tr><th>Method</th><th>Mean </th><th>Error </th><th>StdDev</th><th>Gen0</th><th>Gen1</th><th>Gen2</th><th>Allocated</th>
27+
<thead><tr><th>Method</th><th>Mean </th><th>Error </th><th>StdDev</th><th>Gen0</th><th>Gen1</th><th>Gen2</th><th>Allocated</th>
2828
</tr>
2929
</thead><tbody><tr><td>PetStoreYaml</td><td>677.8 &mu;s</td><td>3,027.4 &mu;s</td><td>165.94 &mu;s</td><td>62.5000</td><td>11.7188</td><td>-</td><td>387.38 KB</td>
3030
</tr><tr><td>PetStoreJson</td><td>224.0 &mu;s</td><td>158.9 &mu;s</td><td>8.71 &mu;s</td><td>40.0391</td><td>8.7891</td><td>-</td><td>249.52 KB</td>

performance/benchmark/BenchmarkDotNet.Artifacts/results/performance.EmptyModels-report-github.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
```
22
3-
BenchmarkDotNet v0.15.4, Windows 11 (10.0.26200.6584)
4-
11th Gen Intel Core i7-1185G7 3.00GHz, 1 CPU, 8 logical and 4 physical cores
3+
BenchmarkDotNet v0.15.4, Linux Ubuntu 24.04.3 LTS (Noble Numbat)
4+
AMD EPYC 7763 2.45GHz, 1 CPU, 4 logical and 2 physical cores
55
.NET SDK 8.0.414
6-
[Host] : .NET 8.0.20 (8.0.20, 8.0.2025.41914), X64 RyuJIT x86-64-v4
7-
ShortRun : .NET 8.0.20 (8.0.20, 8.0.2025.41914), X64 RyuJIT x86-64-v4
6+
[Host] : .NET 8.0.20 (8.0.20, 8.0.2025.41914), X64 RyuJIT x86-64-v3
7+
ShortRun : .NET 8.0.20 (8.0.20, 8.0.2025.41914), X64 RyuJIT x86-64-v3
88
99
Job=ShortRun IterationCount=3 LaunchCount=1
1010
WarmupCount=3

performance/benchmark/BenchmarkDotNet.Artifacts/results/performance.EmptyModels-report.html

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@
1313
</head>
1414
<body>
1515
<pre><code>
16-
BenchmarkDotNet v0.15.4, Windows 11 (10.0.26200.6584)
17-
11th Gen Intel Core i7-1185G7 3.00GHz, 1 CPU, 8 logical and 4 physical cores
16+
BenchmarkDotNet v0.15.4, Linux Ubuntu 24.04.3 LTS (Noble Numbat)
17+
AMD EPYC 7763 2.45GHz, 1 CPU, 4 logical and 2 physical cores
1818
.NET SDK 8.0.414
19-
[Host] : .NET 8.0.20 (8.0.20, 8.0.2025.41914), X64 RyuJIT x86-64-v4
20-
ShortRun : .NET 8.0.20 (8.0.20, 8.0.2025.41914), X64 RyuJIT x86-64-v4
19+
[Host] : .NET 8.0.20 (8.0.20, 8.0.2025.41914), X64 RyuJIT x86-64-v3
20+
ShortRun : .NET 8.0.20 (8.0.20, 8.0.2025.41914), X64 RyuJIT x86-64-v3
2121
</code></pre>
2222
<pre><code>Job=ShortRun IterationCount=3 LaunchCount=1
2323
WarmupCount=3

src/Microsoft.OpenApi/Models/OpenApiConstants.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,11 @@ public static class OpenApiConstants
575575
/// </summary>
576576
public const string RefreshUrl = "refreshUrl";
577577

578+
/// <summary>
579+
/// Field: DeviceAuthorizationUrl
580+
/// </summary>
581+
public const string DeviceAuthorizationUrl = "deviceAuthorizationUrl";
582+
578583
/// <summary>
579584
/// Field: Scopes
580585
/// </summary>

src/Microsoft.OpenApi/Models/OpenApiOAuthFlow.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ public class OpenApiOAuthFlow : IOpenApiSerializable, IOpenApiExtensible
2828
/// </summary>
2929
public Uri? RefreshUrl { get; set; }
3030

31+
/// <summary>
32+
/// The URL to be used for device authorization (RFC 8628).
33+
/// </summary>
34+
public Uri? DeviceAuthorizationUrl { get; set; }
35+
3136
/// <summary>
3237
/// REQUIRED. A map between the scope name and a short description for it.
3338
/// </summary>
@@ -51,6 +56,7 @@ public OpenApiOAuthFlow(OpenApiOAuthFlow oAuthFlow)
5156
AuthorizationUrl = oAuthFlow?.AuthorizationUrl != null ? new Uri(oAuthFlow.AuthorizationUrl.OriginalString, UriKind.RelativeOrAbsolute) : null;
5257
TokenUrl = oAuthFlow?.TokenUrl != null ? new Uri(oAuthFlow.TokenUrl.OriginalString, UriKind.RelativeOrAbsolute) : null;
5358
RefreshUrl = oAuthFlow?.RefreshUrl != null ? new Uri(oAuthFlow.RefreshUrl.OriginalString, UriKind.RelativeOrAbsolute) : null;
59+
DeviceAuthorizationUrl = oAuthFlow?.DeviceAuthorizationUrl != null ? new Uri(oAuthFlow.DeviceAuthorizationUrl.OriginalString, UriKind.RelativeOrAbsolute) : null;
5460
Scopes = oAuthFlow?.Scopes != null ? new Dictionary<string, string>(oAuthFlow.Scopes) : null;
5561
Extensions = oAuthFlow?.Extensions != null ? new Dictionary<string, IOpenApiExtension>(oAuthFlow.Extensions) : null;
5662
}
@@ -97,6 +103,21 @@ private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version
97103
// refreshUrl
98104
writer.WriteProperty(OpenApiConstants.RefreshUrl, RefreshUrl?.ToString());
99105

106+
// deviceAuthorizationUrl
107+
// For OpenAPI 3.2, serialize as a regular field
108+
// For OpenAPI 3.1 and 3.0, serialize as an extension with x-oai- prefix
109+
if (DeviceAuthorizationUrl != null)
110+
{
111+
if (version == OpenApiSpecVersion.OpenApi3_2)
112+
{
113+
writer.WriteProperty(OpenApiConstants.DeviceAuthorizationUrl, DeviceAuthorizationUrl.ToString());
114+
}
115+
else
116+
{
117+
writer.WriteProperty("x-oai-deviceAuthorizationUrl", DeviceAuthorizationUrl.ToString());
118+
}
119+
}
120+
100121
// scopes
101122
writer.WriteRequiredMap(OpenApiConstants.Scopes, Scopes, (w, s) => w.WriteValue(s));
102123

src/Microsoft.OpenApi/Reader/V3/OpenApiOAuthFlowDeserializer.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,17 @@ internal static partial class OpenApiV3Deserializer
4848
}
4949
}
5050
},
51+
{
52+
"x-oai-deviceAuthorizationUrl",
53+
(o, n, _) =>
54+
{
55+
var url = n.GetScalarValue();
56+
if (url != null)
57+
{
58+
o.DeviceAuthorizationUrl = new(url, UriKind.RelativeOrAbsolute);
59+
}
60+
}
61+
},
5162
{"scopes", (o, n, _) => o.Scopes = n.CreateSimpleMap(LoadString).Where(kv => kv.Value is not null).ToDictionary(kv => kv.Key, kv => kv.Value!)}
5263
};
5364

src/Microsoft.OpenApi/Reader/V31/OpenApiOAuthFlowDeserializer.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,17 @@ internal static partial class OpenApiV31Deserializer
4545
}
4646
}
4747
},
48+
{
49+
"x-oai-deviceAuthorizationUrl",
50+
(o, n, _) =>
51+
{
52+
var url = n.GetScalarValue();
53+
if (url != null)
54+
{
55+
o.DeviceAuthorizationUrl = new(url, UriKind.RelativeOrAbsolute);
56+
}
57+
}
58+
},
4859
{"scopes", (o, n, _) => o.Scopes = n.CreateSimpleMap(LoadString).Where(kv => kv.Value is not null).ToDictionary(kv => kv.Key, kv => kv.Value!)}
4960
};
5061

src/Microsoft.OpenApi/Reader/V32/OpenApiOAuthFlowDeserializer.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,17 @@ internal static partial class OpenApiV32Deserializer
4545
}
4646
}
4747
},
48+
{
49+
"deviceAuthorizationUrl",
50+
(o, n, _) =>
51+
{
52+
var url = n.GetScalarValue();
53+
if (url != null)
54+
{
55+
o.DeviceAuthorizationUrl = new(url, UriKind.RelativeOrAbsolute);
56+
}
57+
}
58+
},
4859
{"scopes", (o, n, _) => o.Scopes = n.CreateSimpleMap(LoadString).Where(kv => kv.Value is not null).ToDictionary(kv => kv.Key, kv => kv.Value!)}
4960
};
5061

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT license.
3+
4+
using System;
5+
using System.IO;
6+
using System.Threading.Tasks;
7+
using Microsoft.OpenApi.Reader;
8+
using Xunit;
9+
10+
namespace Microsoft.OpenApi.Readers.Tests.V31Tests
11+
{
12+
[Collection("DefaultSettings")]
13+
public class OpenApiOAuthFlowTests
14+
{
15+
private const string SampleFolderPath = "V31Tests/Samples/OpenApiSecurityScheme/";
16+
17+
[Fact]
18+
public async Task ParseOAuth2SecuritySchemeWithDeviceAuthorizationUrlShouldSucceed()
19+
{
20+
// Act
21+
var securityScheme = await OpenApiModelFactory.LoadAsync<OpenApiSecurityScheme>(
22+
Path.Combine(SampleFolderPath, "oauth2SecuritySchemeWithDeviceUrl.yaml"),
23+
OpenApiSpecVersion.OpenApi3_1,
24+
new(),
25+
SettingsFixture.ReaderSettings);
26+
27+
// Assert
28+
Assert.NotNull(securityScheme);
29+
Assert.Equal(SecuritySchemeType.OAuth2, securityScheme.Type);
30+
Assert.NotNull(securityScheme.Flows?.AuthorizationCode);
31+
Assert.Equal(new Uri("https://example.com/api/oauth/dialog"), securityScheme.Flows.AuthorizationCode.AuthorizationUrl);
32+
Assert.Equal(new Uri("https://example.com/api/oauth/token"), securityScheme.Flows.AuthorizationCode.TokenUrl);
33+
Assert.Equal(new Uri("https://example.com/api/oauth/device"), securityScheme.Flows.AuthorizationCode.DeviceAuthorizationUrl);
34+
Assert.NotNull(securityScheme.Flows.AuthorizationCode.Scopes);
35+
Assert.Equal(2, securityScheme.Flows.AuthorizationCode.Scopes.Count);
36+
}
37+
}
38+
}

0 commit comments

Comments
 (0)