Skip to content

Commit 335cee5

Browse files
committed
feat(securityscheme): add oauth2 metadata url support
1 parent 615d661 commit 335cee5

File tree

8 files changed

+145
-1
lines changed

8 files changed

+145
-1
lines changed

src/Microsoft.OpenApi/Models/Interfaces/IOpenApiSecurityScheme.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@ public interface IOpenApiSecurityScheme : IOpenApiDescribedElement, IOpenApiRead
4646
/// </summary>
4747
public Uri? OpenIdConnectUrl { get; }
4848

49+
/// <summary>
50+
/// URL to the OAuth2 Authorization Server Metadata document (RFC 8414).
51+
/// Note: This field is supported in OpenAPI 3.2.0+ only.
52+
/// </summary>
53+
public Uri? OAuth2MetadataUrl { get; }
54+
4955
/// <summary>
5056
/// Specifies that a security scheme is deprecated and SHOULD be transitioned out of usage.
5157
/// Note: This field is supported in OpenAPI 3.2.0+. For earlier versions, it will be serialized as x-oai-deprecated extension.

src/Microsoft.OpenApi/Models/OpenApiConstants.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -695,6 +695,11 @@ public static class OpenApiConstants
695695
/// </summary>
696696
public const string Flows = "flows";
697697

698+
/// <summary>
699+
/// Field: Oauth2MetadataUrl
700+
/// </summary>
701+
public const string OAuth2MetadataUrl = "oauth2MetadataUrl";
702+
698703
/// <summary>
699704
/// Field: OpenIdConnectUrl
700705
/// </summary>

src/Microsoft.OpenApi/Models/OpenApiSecurityScheme.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ public class OpenApiSecurityScheme : IOpenApiExtensible, IOpenApiSecurityScheme
3535
/// <inheritdoc/>
3636
public Uri? OpenIdConnectUrl { get; set; }
3737

38+
/// <inheritdoc/>
39+
public Uri? OAuth2MetadataUrl { get; set; }
40+
3841
/// <inheritdoc/>
3942
public bool Deprecated { get; set; }
4043

@@ -60,6 +63,7 @@ internal OpenApiSecurityScheme(IOpenApiSecurityScheme securityScheme)
6063
BearerFormat = securityScheme.BearerFormat ?? BearerFormat;
6164
Flows = securityScheme.Flows != null ? new(securityScheme.Flows) : null;
6265
OpenIdConnectUrl = securityScheme.OpenIdConnectUrl != null ? new Uri(securityScheme.OpenIdConnectUrl.OriginalString, UriKind.RelativeOrAbsolute) : null;
66+
OAuth2MetadataUrl = securityScheme.OAuth2MetadataUrl != null ? new Uri(securityScheme.OAuth2MetadataUrl.OriginalString, UriKind.RelativeOrAbsolute) : null;
6367
Deprecated = securityScheme.Deprecated;
6468
Extensions = securityScheme.Extensions != null ? new Dictionary<string, IOpenApiExtension>(securityScheme.Extensions) : null;
6569
}
@@ -118,7 +122,12 @@ private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version
118122
break;
119123
case SecuritySchemeType.OAuth2:
120124
// This property apply to oauth2 type only.
125+
// oauth2MetadataUrl
121126
// flows
127+
if (version >= OpenApiSpecVersion.OpenApi3_2)
128+
{
129+
writer.WriteProperty(OpenApiConstants.OAuth2MetadataUrl, OAuth2MetadataUrl?.ToString());
130+
}
122131
writer.WriteOptionalObject(OpenApiConstants.Flows, Flows, callback);
123132
break;
124133
case SecuritySchemeType.OpenIdConnect:

src/Microsoft.OpenApi/Models/References/OpenApiSecuritySchemeReference.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ public string? Description
5454
/// <inheritdoc/>
5555
public Uri? OpenIdConnectUrl { get => Target?.OpenIdConnectUrl; }
5656

57+
/// <inheritdoc/>
58+
public Uri? OAuth2MetadataUrl { get => Target?.OAuth2MetadataUrl; }
59+
5760
/// <inheritdoc/>
5861
public IDictionary<string, IOpenApiExtension>? Extensions { get => Target?.Extensions; }
5962

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) Microsoft Corporation. All rights reserved.
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT license.
33

44
using System;
@@ -68,6 +68,16 @@ internal static partial class OpenApiV32Deserializer
6868
}
6969
}
7070
},
71+
{
72+
"oauth2MetadataUrl", (o, n, _) =>
73+
{
74+
var metadataUrl = n.GetScalarValue();
75+
if (metadataUrl != null)
76+
{
77+
o.OAuth2MetadataUrl = new(metadataUrl, UriKind.RelativeOrAbsolute);
78+
}
79+
}
80+
},
7181
{
7282
"flows", (o, n, t) =>
7383
{

test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiSecuritySchemeTests.cs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,36 @@ public async Task ParseOAuth2SecuritySchemeShouldSucceed()
8686
}, securityScheme);
8787
}
8888

89+
[Fact]
90+
public async Task ParseOAuth2SecuritySchemeWithMetadataUrlShouldSucceed()
91+
{
92+
// Act
93+
var securityScheme = await OpenApiModelFactory.LoadAsync<OpenApiSecurityScheme>(
94+
Path.Combine(SampleFolderPath, "oauth2SecuritySchemeWithMetadataUrl.yaml"),
95+
OpenApiSpecVersion.OpenApi3_2,
96+
new(),
97+
SettingsFixture.ReaderSettings);
98+
99+
// Assert
100+
Assert.Equivalent(
101+
new OpenApiSecurityScheme
102+
{
103+
Type = SecuritySchemeType.OAuth2,
104+
OAuth2MetadataUrl = new Uri("https://idp.example.com/.well-known/oauth-authorization-server"),
105+
Flows = new OpenApiOAuthFlows
106+
{
107+
ClientCredentials = new OpenApiOAuthFlow
108+
{
109+
TokenUrl = new Uri("https://idp.example.com/oauth/token"),
110+
Scopes = new System.Collections.Generic.Dictionary<string, string>
111+
{
112+
["scope:one"] = "Scope one"
113+
}
114+
}
115+
}
116+
}, securityScheme);
117+
}
118+
89119
[Fact]
90120
public async Task ParseOpenIdConnectSecuritySchemeShouldSucceed()
91121
{
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.2.0.md#securitySchemeObject
2+
type: oauth2
3+
oauth2MetadataUrl: https://idp.example.com/.well-known/oauth-authorization-server
4+
flows:
5+
clientCredentials:
6+
tokenUrl: https://idp.example.com/oauth/token
7+
scopes:
8+
scope:one: Scope one

test/Microsoft.OpenApi.Tests/Models/OpenApiSecuritySchemeTests.cs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,24 @@ public class OpenApiSecuritySchemeTests
9393
}
9494
};
9595

96+
private static OpenApiSecurityScheme OAuth2MetadataSecurityScheme => new()
97+
{
98+
Description = "description1",
99+
Type = SecuritySchemeType.OAuth2,
100+
OAuth2MetadataUrl = new("https://idp.example.com/.well-known/oauth-authorization-server"),
101+
Flows = new()
102+
{
103+
ClientCredentials = new()
104+
{
105+
TokenUrl = new("https://idp.example.com/oauth/token"),
106+
Scopes = new Dictionary<string, string>
107+
{
108+
["scope:one"] = "Scope one"
109+
}
110+
}
111+
}
112+
};
113+
96114
private static OpenApiSecurityScheme OpenIdConnectSecurityScheme => new()
97115
{
98116
Description = "description1",
@@ -257,6 +275,61 @@ public async Task SerializeOAuthSingleFlowSecuritySchemeAsV3JsonWorks()
257275
Assert.Equal(expected, actual);
258276
}
259277

278+
[Fact]
279+
public async Task SerializeOAuthSecuritySchemeWithMetadataUrlAsV32JsonWorks()
280+
{
281+
// Arrange
282+
var expected =
283+
"""
284+
{
285+
"type": "oauth2",
286+
"description": "description1",
287+
"oauth2MetadataUrl": "https://idp.example.com/.well-known/oauth-authorization-server",
288+
"flows": {
289+
"clientCredentials": {
290+
"tokenUrl": "https://idp.example.com/oauth/token",
291+
"scopes": {
292+
"scope:one": "Scope one"
293+
}
294+
}
295+
}
296+
}
297+
""";
298+
299+
// Act
300+
var actual = await OAuth2MetadataSecurityScheme.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2);
301+
302+
// Assert
303+
Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual)));
304+
}
305+
306+
[Fact]
307+
public async Task SerializeOAuthSecuritySchemeWithMetadataUrlAsV31JsonOmitsMetadataUrl()
308+
{
309+
// Arrange
310+
var expected =
311+
"""
312+
{
313+
"type": "oauth2",
314+
"description": "description1",
315+
"flows": {
316+
"clientCredentials": {
317+
"tokenUrl": "https://idp.example.com/oauth/token",
318+
"scopes": {
319+
"scope:one": "Scope one"
320+
}
321+
}
322+
}
323+
}
324+
""";
325+
326+
// Act
327+
var actual = await OAuth2MetadataSecurityScheme.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1);
328+
329+
// Assert
330+
Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual)));
331+
}
332+
260333
[Fact]
261334
public async Task SerializeOAuthMultipleFlowSecuritySchemeAsV3JsonWorks()
262335
{

0 commit comments

Comments
 (0)