Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
21 changes: 21 additions & 0 deletions src/Microsoft.OpenApi/Models/OpenApiSecurityScheme.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version
Action<IOpenApiWriter, IOpenApiSerializable> callback)
{
Utils.CheckArgumentNull(writer);
EnsureRequiredPropertiesAreSet(version);

writer.WriteStartObject();

Expand Down Expand Up @@ -176,6 +177,7 @@ private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version
public virtual void SerializeAsV2(IOpenApiWriter writer)
{
Utils.CheckArgumentNull(writer);
EnsureRequiredPropertiesAreSet(OpenApiSpecVersion.OpenApi2_0);

if (Type == SecuritySchemeType.Http && Scheme != OpenApiConstants.Basic)
{
Expand Down Expand Up @@ -239,6 +241,25 @@ public virtual void SerializeAsV2(IOpenApiWriter writer)
writer.WriteEndObject();
}

private void EnsureRequiredPropertiesAreSet(OpenApiSpecVersion version)
{
switch (Type)
{
case null:
throw new OpenApiException("The 'type' property is required for security schemes.");
case SecuritySchemeType.ApiKey when string.IsNullOrEmpty(Name):
throw new OpenApiException("The 'name' property is required for apiKey security schemes.");
case SecuritySchemeType.ApiKey when In is null:
throw new OpenApiException("The 'in' property is required for apiKey security schemes.");
case SecuritySchemeType.Http when version >= OpenApiSpecVersion.OpenApi3_0 && string.IsNullOrEmpty(Scheme):
throw new OpenApiException("The 'scheme' property is required for http security schemes.");
case SecuritySchemeType.OAuth2 when Flows is null:
throw new OpenApiException("The 'flows' property is required for oauth2 security schemes.");
case SecuritySchemeType.OpenIdConnect when version >= OpenApiSpecVersion.OpenApi3_0 && OpenIdConnectUrl is null:
throw new OpenApiException("The 'openIdConnectUrl' property is required for openIdConnect security schemes.");
}
}

/// <summary>
/// Arbitrarily chooses one <see cref="OpenApiOAuthFlow"/> object from the <see cref="OpenApiOAuthFlows"/>
/// to populate in V2 security scheme.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ public OpenApiComponentsSerializationTests()
_components.Responses["200"] = _responseMock.Object;
_components.Parameters["limit"] = _parameterMock.Object;
_components.Headers["x-rate-limit"] = _headerMock.Object;
_securitySchemeMock.Object.Type = SecuritySchemeType.Http;
_securitySchemeMock.Object.Scheme = "bearer";
_components.SecuritySchemes["api_key"] = _securitySchemeMock.Object;
_components.Links["UserRepositories"] = _linkMock.Object;
_components.Callbacks["onData"] = _callbackMock.Object;
Expand Down
155 changes: 155 additions & 0 deletions test/Microsoft.OpenApi.Tests/Models/OpenApiSecuritySchemeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,155 @@ public async Task SerializeApiKeySecuritySchemeAsV3YamlWorks()
Assert.Equal(expected, actual);
}

[Theory]
[InlineData(OpenApiSpecVersion.OpenApi2_0)]
[InlineData(OpenApiSpecVersion.OpenApi3_0)]
[InlineData(OpenApiSpecVersion.OpenApi3_1)]
[InlineData(OpenApiSpecVersion.OpenApi3_2)]
public Task SerializeSecuritySchemeWithoutTypeThrows(OpenApiSpecVersion version)
{
// Arrange
var securityScheme = new OpenApiSecurityScheme();

// Act & Assert
return AssertRequiredPropertyThrowsAsync(
securityScheme,
version,
"The 'type' property is required for security schemes.");
}

[Theory]
[InlineData(OpenApiSpecVersion.OpenApi2_0)]
[InlineData(OpenApiSpecVersion.OpenApi3_0)]
[InlineData(OpenApiSpecVersion.OpenApi3_1)]
[InlineData(OpenApiSpecVersion.OpenApi3_2)]
public Task SerializeApiKeySecuritySchemeWithoutNameThrows(OpenApiSpecVersion version)
{
// Arrange
var securityScheme = new OpenApiSecurityScheme
{
Type = SecuritySchemeType.ApiKey,
In = ParameterLocation.Query
};

// Act & Assert
return AssertRequiredPropertyThrowsAsync(
securityScheme,
version,
"The 'name' property is required for apiKey security schemes.");
}

[Theory]
[InlineData(OpenApiSpecVersion.OpenApi2_0)]
[InlineData(OpenApiSpecVersion.OpenApi3_0)]
[InlineData(OpenApiSpecVersion.OpenApi3_1)]
[InlineData(OpenApiSpecVersion.OpenApi3_2)]
public Task SerializeApiKeySecuritySchemeWithoutInThrows(OpenApiSpecVersion version)
{
// Arrange
var securityScheme = new OpenApiSecurityScheme
{
Name = "parameterName",
Type = SecuritySchemeType.ApiKey
};

// Act & Assert
return AssertRequiredPropertyThrowsAsync(
securityScheme,
version,
"The 'in' property is required for apiKey security schemes.");
}

[Theory]
[InlineData(OpenApiSpecVersion.OpenApi3_0)]
[InlineData(OpenApiSpecVersion.OpenApi3_1)]
[InlineData(OpenApiSpecVersion.OpenApi3_2)]
public Task SerializeHttpSecuritySchemeWithoutSchemeThrows(OpenApiSpecVersion version)
{
// Arrange
var securityScheme = new OpenApiSecurityScheme
{
Type = SecuritySchemeType.Http
};

// Act & Assert
return AssertRequiredPropertyThrowsAsync(
securityScheme,
version,
"The 'scheme' property is required for http security schemes.");
}

[Fact]
public async Task SerializeHttpSecuritySchemeWithoutSchemeAsV2DoesNotThrow()
{
// Arrange
var securityScheme = new OpenApiSecurityScheme
{
Type = SecuritySchemeType.Http
};

// Act
var actual = await securityScheme.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi2_0);

// Assert
Assert.True(JsonNode.DeepEquals(JsonNode.Parse("{}"), JsonNode.Parse(actual)));
}

[Theory]
[InlineData(OpenApiSpecVersion.OpenApi2_0)]
[InlineData(OpenApiSpecVersion.OpenApi3_0)]
[InlineData(OpenApiSpecVersion.OpenApi3_1)]
[InlineData(OpenApiSpecVersion.OpenApi3_2)]
public Task SerializeOAuth2SecuritySchemeWithoutFlowsThrows(OpenApiSpecVersion version)
{
// Arrange
var securityScheme = new OpenApiSecurityScheme
{
Type = SecuritySchemeType.OAuth2
};

// Act & Assert
return AssertRequiredPropertyThrowsAsync(
securityScheme,
version,
"The 'flows' property is required for oauth2 security schemes.");
}

[Theory]
[InlineData(OpenApiSpecVersion.OpenApi3_0)]
[InlineData(OpenApiSpecVersion.OpenApi3_1)]
[InlineData(OpenApiSpecVersion.OpenApi3_2)]
public Task SerializeOpenIdConnectSecuritySchemeWithoutOpenIdConnectUrlThrows(OpenApiSpecVersion version)
{
// Arrange
var securityScheme = new OpenApiSecurityScheme
{
Type = SecuritySchemeType.OpenIdConnect
};

// Act & Assert
return AssertRequiredPropertyThrowsAsync(
securityScheme,
version,
"The 'openIdConnectUrl' property is required for openIdConnect security schemes.");
}

[Fact]
public async Task SerializeOpenIdConnectSecuritySchemeWithoutOpenIdConnectUrlAsV2DoesNotThrow()
{
// Arrange
var securityScheme = new OpenApiSecurityScheme
{
Type = SecuritySchemeType.OpenIdConnect
};

// Act
var actual = await securityScheme.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi2_0);

// Assert
Assert.True(JsonNode.DeepEquals(JsonNode.Parse("{}"), JsonNode.Parse(actual)));
}

[Fact]
public async Task SerializeHttpBasicSecuritySchemeAsV3JsonWorks()
{
Expand Down Expand Up @@ -498,5 +647,11 @@ public async Task SerializeDeprecatedSecuritySchemeAsV3JsonWorks()
// Assert
Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual)));
}

private static async Task AssertRequiredPropertyThrowsAsync(OpenApiSecurityScheme securityScheme, OpenApiSpecVersion version, string message)
{
var exception = await Assert.ThrowsAsync<OpenApiException>(() => securityScheme.SerializeAsJsonAsync(version));
Assert.Contains(message, exception.Message);
}
}
}
Loading