Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,10 @@ public interface IOpenApiSecurityScheme : IOpenApiDescribedElement, IOpenApiRead
/// REQUIRED. OpenId Connect URL to discover OAuth2 configuration values.
/// </summary>
public Uri? OpenIdConnectUrl { get; }

/// <summary>
/// Specifies that a security scheme is deprecated and SHOULD be transitioned out of usage.
/// Note: This field is supported in OpenAPI 3.2.0+. For earlier versions, it will be serialized as x-oai-deprecated extension.
/// </summary>
public bool Deprecated { get; }
}
17 changes: 17 additions & 0 deletions src/Microsoft.OpenApi/Models/OpenApiSecurityScheme.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ public class OpenApiSecurityScheme : IOpenApiExtensible, IOpenApiSecurityScheme
/// <inheritdoc/>
public Uri? OpenIdConnectUrl { get; set; }

/// <inheritdoc/>
public bool Deprecated { get; set; }

/// <inheritdoc/>
public IDictionary<string, IOpenApiExtension>? Extensions { get; set; }

Expand All @@ -57,6 +60,7 @@ internal OpenApiSecurityScheme(IOpenApiSecurityScheme securityScheme)
BearerFormat = securityScheme.BearerFormat ?? BearerFormat;
Flows = securityScheme.Flows != null ? new(securityScheme.Flows) : null;
OpenIdConnectUrl = securityScheme.OpenIdConnectUrl != null ? new Uri(securityScheme.OpenIdConnectUrl.OriginalString, UriKind.RelativeOrAbsolute) : null;
Deprecated = securityScheme.Deprecated;
Extensions = securityScheme.Extensions != null ? new Dictionary<string, IOpenApiExtension>(securityScheme.Extensions) : null;
}
/// <summary>
Expand Down Expand Up @@ -124,6 +128,19 @@ private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version
break;
}

// deprecated - serialize as native field for v3.2+ or as extension for earlier versions
if (Deprecated)
{
if (version >= OpenApiSpecVersion.OpenApi3_2)
{
writer.WriteProperty(OpenApiConstants.Deprecated, Deprecated, false);
}
else
{
writer.WriteProperty("x-oai-deprecated", Deprecated, false);
}
}

// extensions
writer.WriteExtensions(Extensions, version);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ public string? Description
/// <inheritdoc/>
public SecuritySchemeType? Type { get => Target?.Type; }

/// <inheritdoc/>
public bool Deprecated { get => Target?.Deprecated ?? default; }

/// <inheritdoc/>
public override IOpenApiSecurityScheme CopyReferenceAsTargetElementWithOverrides(IOpenApiSecurityScheme source)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,21 @@ internal static partial class OpenApiV3Deserializer
private static readonly PatternFieldMap<OpenApiSecurityScheme> _securitySchemePatternFields =
new()
{
{s => s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))}
{s => s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase), (o, p, n, _) =>
{
if (p.Equals("x-oai-deprecated", StringComparison.OrdinalIgnoreCase))
{
var deprecated = n.GetScalarValue();
if (deprecated != null)
{
o.Deprecated = bool.Parse(deprecated);
}
}
else
{
o.AddExtension(p, LoadExtension(p,n));
}
}}
};

public static IOpenApiSecurityScheme LoadSecurityScheme(ParseNode node, OpenApiDocument hostDocument)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,37 @@ internal static partial class OpenApiV31Deserializer
{
o.Flows = LoadOAuthFlows(n, t);
}
},
{
"deprecated", (o, n, _) =>
{
var deprecated = n.GetScalarValue();
if (deprecated != null)
{
o.Deprecated = bool.Parse(deprecated);
}
}
}
};

private static readonly PatternFieldMap<OpenApiSecurityScheme> _securitySchemePatternFields =
new()
{
{s => s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase), (o, p, n, _) => o.AddExtension(p, LoadExtension(p,n))}
{s => s.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase), (o, p, n, _) =>
{
if (p.Equals("x-oai-deprecated", StringComparison.OrdinalIgnoreCase))
{
var deprecated = n.GetScalarValue();
if (deprecated != null)
{
o.Deprecated = bool.Parse(deprecated);
}
}
else
{
o.AddExtension(p, LoadExtension(p,n));
}
}}
};

public static IOpenApiSecurityScheme LoadSecurityScheme(ParseNode node, OpenApiDocument hostDocument)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,16 @@ internal static partial class OpenApiV32Deserializer
{
o.Flows = LoadOAuthFlows(n, t);
}
},
{
"deprecated", (o, n, _) =>
{
var deprecated = n.GetScalarValue();
if (deprecated != null)
{
o.Deprecated = bool.Parse(deprecated);
}
}
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,62 @@ public void ShouldDeserializeSecuritySchemeReferenceAnnotations()
Assert.Equal("This is a security scheme reference", resultReference.Description);
Assert.NotNull(resultReference.Target);
}

[Fact]
public void ShouldDeserializeSecuritySchemeWithXOaiDeprecatedExtension()
{
var json =
"""
{
"type": "apiKey",
"description": "This is a deprecated security scheme",
"name": "api_key",
"in": "header",
"x-oai-deprecated": true
}
""";

var hostDocument = new OpenApiDocument();
var jsonNode = JsonNode.Parse(json);
var parseNode = ParseNode.Create(new ParsingContext(new()), jsonNode);

var result = OpenApiV31Deserializer.LoadSecurityScheme(parseNode, hostDocument);

Assert.NotNull(result);
var resultScheme = Assert.IsType<OpenApiSecurityScheme>(result);

Assert.Equal(SecuritySchemeType.ApiKey, resultScheme.Type);
Assert.Equal("api_key", resultScheme.Name);
Assert.Equal(ParameterLocation.Header, resultScheme.In);
Assert.True(resultScheme.Deprecated);
}

[Fact]
public void ShouldDeserializeSecuritySchemeWithDeprecatedField()
{
var json =
"""
{
"type": "apiKey",
"description": "This is a deprecated security scheme",
"name": "api_key",
"in": "header",
"deprecated": true
}
""";

var hostDocument = new OpenApiDocument();
var jsonNode = JsonNode.Parse(json);
var parseNode = ParseNode.Create(new ParsingContext(new()), jsonNode);

var result = OpenApiV31Deserializer.LoadSecurityScheme(parseNode, hostDocument);

Assert.NotNull(result);
var resultScheme = Assert.IsType<OpenApiSecurityScheme>(result);

Assert.Equal(SecuritySchemeType.ApiKey, resultScheme.Type);
Assert.Equal("api_key", resultScheme.Name);
Assert.Equal(ParameterLocation.Header, resultScheme.In);
Assert.True(resultScheme.Deprecated);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,34 @@ public void ShouldDeserializeSecuritySchemeReferenceAnnotations()
Assert.Equal("This is a security scheme reference", resultReference.Description);
Assert.NotNull(resultReference.Target);
}

[Fact]
public void ShouldDeserializeSecuritySchemeWithDeprecatedField()
{
var json =
"""
{
"type": "apiKey",
"description": "This is a deprecated security scheme",
"name": "api_key",
"in": "header",
"deprecated": true
}
""";

var hostDocument = new OpenApiDocument();
var jsonNode = JsonNode.Parse(json);
var parseNode = ParseNode.Create(new ParsingContext(new()), jsonNode);

var result = OpenApiV32Deserializer.LoadSecurityScheme(parseNode, hostDocument);

Assert.NotNull(result);
var resultScheme = Assert.IsType<OpenApiSecurityScheme>(result);

Assert.Equal(SecuritySchemeType.ApiKey, resultScheme.Type);
Assert.Equal("api_key", resultScheme.Name);
Assert.Equal(ParameterLocation.Header, resultScheme.In);
Assert.True(resultScheme.Deprecated);
}
}

81 changes: 81 additions & 0 deletions test/Microsoft.OpenApi.Tests/Models/OpenApiSecuritySchemeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,15 @@ public class OpenApiSecuritySchemeTests
OpenIdConnectUrl = new("https://example.com/openIdConnect")
};

private static OpenApiSecurityScheme DeprecatedApiKeySecurityScheme => new()
{
Description = "description1",
Name = "parameterName",
Type = SecuritySchemeType.ApiKey,
In = ParameterLocation.Query,
Deprecated = true
};

[Fact]
public async Task SerializeApiKeySecuritySchemeAsV3JsonWorks()
{
Expand Down Expand Up @@ -329,5 +338,77 @@ public async Task SerializeReferencedSecuritySchemeAsV3JsonWithoutReferenceWorks
// Assert
await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput);
}

[Fact]
public async Task SerializeDeprecatedSecuritySchemeAsV32JsonWorks()
{
// Arrange
var expected =
"""
{
"type": "apiKey",
"description": "description1",
"name": "parameterName",
"in": "query",
"deprecated": true
}
""";

// Act
var actual = await DeprecatedApiKeySecurityScheme.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2);

// Assert
actual = actual.MakeLineBreaksEnvironmentNeutral();
expected = expected.MakeLineBreaksEnvironmentNeutral();
Assert.Equal(expected, actual);
}

[Fact]
public async Task SerializeDeprecatedSecuritySchemeAsV31JsonWorks()
{
// Arrange
var expected =
"""
{
"type": "apiKey",
"description": "description1",
"name": "parameterName",
"in": "query",
"x-oai-deprecated": true
}
""";

// Act
var actual = await DeprecatedApiKeySecurityScheme.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1);

// Assert
actual = actual.MakeLineBreaksEnvironmentNeutral();
expected = expected.MakeLineBreaksEnvironmentNeutral();
Assert.Equal(expected, actual);
}

[Fact]
public async Task SerializeDeprecatedSecuritySchemeAsV3JsonWorks()
{
// Arrange
var expected =
"""
{
"type": "apiKey",
"description": "description1",
"name": "parameterName",
"in": "query",
"x-oai-deprecated": true
}
""";

// Act
var actual = await DeprecatedApiKeySecurityScheme.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0);

// Assert
actual = actual.MakeLineBreaksEnvironmentNeutral();
expected = expected.MakeLineBreaksEnvironmentNeutral();
Assert.Equal(expected, actual);
}
}
}
3 changes: 3 additions & 0 deletions test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ namespace Microsoft.OpenApi
public interface IOpenApiSecurityScheme : Microsoft.OpenApi.IOpenApiDescribedElement, Microsoft.OpenApi.IOpenApiElement, Microsoft.OpenApi.IOpenApiReadOnlyExtensible, Microsoft.OpenApi.IOpenApiReferenceable, Microsoft.OpenApi.IOpenApiSerializable, Microsoft.OpenApi.IShallowCopyable<Microsoft.OpenApi.IOpenApiSecurityScheme>
{
string? BearerFormat { get; }
bool Deprecated { get; }
Microsoft.OpenApi.OpenApiOAuthFlows? Flows { get; }
Microsoft.OpenApi.ParameterLocation? In { get; }
string? Name { get; }
Expand Down Expand Up @@ -1299,6 +1300,7 @@ namespace Microsoft.OpenApi
{
public OpenApiSecurityScheme() { }
public string? BearerFormat { get; set; }
public bool Deprecated { get; set; }
public string? Description { get; set; }
public System.Collections.Generic.IDictionary<string, Microsoft.OpenApi.IOpenApiExtension>? Extensions { get; set; }
public Microsoft.OpenApi.OpenApiOAuthFlows? Flows { get; set; }
Expand All @@ -1317,6 +1319,7 @@ namespace Microsoft.OpenApi
{
public OpenApiSecuritySchemeReference(string referenceId, Microsoft.OpenApi.OpenApiDocument? hostDocument = null, string? externalResource = null) { }
public string? BearerFormat { get; }
public bool Deprecated { get; }
public string? Description { get; set; }
public System.Collections.Generic.IDictionary<string, Microsoft.OpenApi.IOpenApiExtension>? Extensions { get; }
public Microsoft.OpenApi.OpenApiOAuthFlows? Flows { get; }
Expand Down