Skip to content

Commit f31b192

Browse files
authored
fix: validate required properties of security scheme before serialization (#2952)
* fix: validate required properties of security scheme before serialization Signed-off-by: Vincent Biret <vibiret@microsoft.com> * chore: fixes mock setup Signed-off-by: Vincent Biret <vibiret@microsoft.com> --------- Signed-off-by: Vincent Biret <vibiret@microsoft.com>
1 parent 63fc55d commit f31b192

3 files changed

Lines changed: 178 additions & 0 deletions

File tree

src/Microsoft.OpenApi/Models/OpenApiSecurityScheme.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version
9797
Action<IOpenApiWriter, IOpenApiSerializable> callback)
9898
{
9999
Utils.CheckArgumentNull(writer);
100+
EnsureRequiredPropertiesAreSet(version);
100101

101102
writer.WriteStartObject();
102103

@@ -176,6 +177,7 @@ private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version
176177
public virtual void SerializeAsV2(IOpenApiWriter writer)
177178
{
178179
Utils.CheckArgumentNull(writer);
180+
EnsureRequiredPropertiesAreSet(OpenApiSpecVersion.OpenApi2_0);
179181

180182
if (Type == SecuritySchemeType.Http && Scheme != OpenApiConstants.Basic)
181183
{
@@ -239,6 +241,25 @@ public virtual void SerializeAsV2(IOpenApiWriter writer)
239241
writer.WriteEndObject();
240242
}
241243

244+
private void EnsureRequiredPropertiesAreSet(OpenApiSpecVersion version)
245+
{
246+
switch (Type)
247+
{
248+
case null:
249+
throw new OpenApiException("The 'type' property is required for security schemes.");
250+
case SecuritySchemeType.ApiKey when string.IsNullOrEmpty(Name):
251+
throw new OpenApiException("The 'name' property is required for apiKey security schemes.");
252+
case SecuritySchemeType.ApiKey when In is null:
253+
throw new OpenApiException("The 'in' property is required for apiKey security schemes.");
254+
case SecuritySchemeType.Http when version >= OpenApiSpecVersion.OpenApi3_0 && string.IsNullOrEmpty(Scheme):
255+
throw new OpenApiException("The 'scheme' property is required for http security schemes.");
256+
case SecuritySchemeType.OAuth2 when Flows is null:
257+
throw new OpenApiException("The 'flows' property is required for oauth2 security schemes.");
258+
case SecuritySchemeType.OpenIdConnect when version >= OpenApiSpecVersion.OpenApi3_0 && OpenIdConnectUrl is null:
259+
throw new OpenApiException("The 'openIdConnectUrl' property is required for openIdConnect security schemes.");
260+
}
261+
}
262+
242263
/// <summary>
243264
/// Arbitrarily chooses one <see cref="OpenApiOAuthFlow"/> object from the <see cref="OpenApiOAuthFlows"/>
244265
/// to populate in V2 security scheme.

test/Microsoft.OpenApi.Tests/Mocks/OpenApiComponentsSerializationTests.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ public OpenApiComponentsSerializationTests()
2626
_components.Responses["200"] = _responseMock.Object;
2727
_components.Parameters["limit"] = _parameterMock.Object;
2828
_components.Headers["x-rate-limit"] = _headerMock.Object;
29+
_securitySchemeMock.Object.Type = SecuritySchemeType.Http;
30+
_securitySchemeMock.Object.Scheme = "bearer";
2931
_components.SecuritySchemes["api_key"] = _securitySchemeMock.Object;
3032
_components.Links["UserRepositories"] = _linkMock.Object;
3133
_components.Callbacks["onData"] = _callbackMock.Object;

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

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,155 @@ public async Task SerializeApiKeySecuritySchemeAsV3YamlWorks()
187187
Assert.Equal(expected, actual);
188188
}
189189

190+
[Theory]
191+
[InlineData(OpenApiSpecVersion.OpenApi2_0)]
192+
[InlineData(OpenApiSpecVersion.OpenApi3_0)]
193+
[InlineData(OpenApiSpecVersion.OpenApi3_1)]
194+
[InlineData(OpenApiSpecVersion.OpenApi3_2)]
195+
public Task SerializeSecuritySchemeWithoutTypeThrows(OpenApiSpecVersion version)
196+
{
197+
// Arrange
198+
var securityScheme = new OpenApiSecurityScheme();
199+
200+
// Act & Assert
201+
return AssertRequiredPropertyThrowsAsync(
202+
securityScheme,
203+
version,
204+
"The 'type' property is required for security schemes.");
205+
}
206+
207+
[Theory]
208+
[InlineData(OpenApiSpecVersion.OpenApi2_0)]
209+
[InlineData(OpenApiSpecVersion.OpenApi3_0)]
210+
[InlineData(OpenApiSpecVersion.OpenApi3_1)]
211+
[InlineData(OpenApiSpecVersion.OpenApi3_2)]
212+
public Task SerializeApiKeySecuritySchemeWithoutNameThrows(OpenApiSpecVersion version)
213+
{
214+
// Arrange
215+
var securityScheme = new OpenApiSecurityScheme
216+
{
217+
Type = SecuritySchemeType.ApiKey,
218+
In = ParameterLocation.Query
219+
};
220+
221+
// Act & Assert
222+
return AssertRequiredPropertyThrowsAsync(
223+
securityScheme,
224+
version,
225+
"The 'name' property is required for apiKey security schemes.");
226+
}
227+
228+
[Theory]
229+
[InlineData(OpenApiSpecVersion.OpenApi2_0)]
230+
[InlineData(OpenApiSpecVersion.OpenApi3_0)]
231+
[InlineData(OpenApiSpecVersion.OpenApi3_1)]
232+
[InlineData(OpenApiSpecVersion.OpenApi3_2)]
233+
public Task SerializeApiKeySecuritySchemeWithoutInThrows(OpenApiSpecVersion version)
234+
{
235+
// Arrange
236+
var securityScheme = new OpenApiSecurityScheme
237+
{
238+
Name = "parameterName",
239+
Type = SecuritySchemeType.ApiKey
240+
};
241+
242+
// Act & Assert
243+
return AssertRequiredPropertyThrowsAsync(
244+
securityScheme,
245+
version,
246+
"The 'in' property is required for apiKey security schemes.");
247+
}
248+
249+
[Theory]
250+
[InlineData(OpenApiSpecVersion.OpenApi3_0)]
251+
[InlineData(OpenApiSpecVersion.OpenApi3_1)]
252+
[InlineData(OpenApiSpecVersion.OpenApi3_2)]
253+
public Task SerializeHttpSecuritySchemeWithoutSchemeThrows(OpenApiSpecVersion version)
254+
{
255+
// Arrange
256+
var securityScheme = new OpenApiSecurityScheme
257+
{
258+
Type = SecuritySchemeType.Http
259+
};
260+
261+
// Act & Assert
262+
return AssertRequiredPropertyThrowsAsync(
263+
securityScheme,
264+
version,
265+
"The 'scheme' property is required for http security schemes.");
266+
}
267+
268+
[Fact]
269+
public async Task SerializeHttpSecuritySchemeWithoutSchemeAsV2DoesNotThrow()
270+
{
271+
// Arrange
272+
var securityScheme = new OpenApiSecurityScheme
273+
{
274+
Type = SecuritySchemeType.Http
275+
};
276+
277+
// Act
278+
var actual = await securityScheme.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi2_0);
279+
280+
// Assert
281+
Assert.True(JsonNode.DeepEquals(JsonNode.Parse("{}"), JsonNode.Parse(actual)));
282+
}
283+
284+
[Theory]
285+
[InlineData(OpenApiSpecVersion.OpenApi2_0)]
286+
[InlineData(OpenApiSpecVersion.OpenApi3_0)]
287+
[InlineData(OpenApiSpecVersion.OpenApi3_1)]
288+
[InlineData(OpenApiSpecVersion.OpenApi3_2)]
289+
public Task SerializeOAuth2SecuritySchemeWithoutFlowsThrows(OpenApiSpecVersion version)
290+
{
291+
// Arrange
292+
var securityScheme = new OpenApiSecurityScheme
293+
{
294+
Type = SecuritySchemeType.OAuth2
295+
};
296+
297+
// Act & Assert
298+
return AssertRequiredPropertyThrowsAsync(
299+
securityScheme,
300+
version,
301+
"The 'flows' property is required for oauth2 security schemes.");
302+
}
303+
304+
[Theory]
305+
[InlineData(OpenApiSpecVersion.OpenApi3_0)]
306+
[InlineData(OpenApiSpecVersion.OpenApi3_1)]
307+
[InlineData(OpenApiSpecVersion.OpenApi3_2)]
308+
public Task SerializeOpenIdConnectSecuritySchemeWithoutOpenIdConnectUrlThrows(OpenApiSpecVersion version)
309+
{
310+
// Arrange
311+
var securityScheme = new OpenApiSecurityScheme
312+
{
313+
Type = SecuritySchemeType.OpenIdConnect
314+
};
315+
316+
// Act & Assert
317+
return AssertRequiredPropertyThrowsAsync(
318+
securityScheme,
319+
version,
320+
"The 'openIdConnectUrl' property is required for openIdConnect security schemes.");
321+
}
322+
323+
[Fact]
324+
public async Task SerializeOpenIdConnectSecuritySchemeWithoutOpenIdConnectUrlAsV2DoesNotThrow()
325+
{
326+
// Arrange
327+
var securityScheme = new OpenApiSecurityScheme
328+
{
329+
Type = SecuritySchemeType.OpenIdConnect
330+
};
331+
332+
// Act
333+
var actual = await securityScheme.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi2_0);
334+
335+
// Assert
336+
Assert.True(JsonNode.DeepEquals(JsonNode.Parse("{}"), JsonNode.Parse(actual)));
337+
}
338+
190339
[Fact]
191340
public async Task SerializeHttpBasicSecuritySchemeAsV3JsonWorks()
192341
{
@@ -498,5 +647,11 @@ public async Task SerializeDeprecatedSecuritySchemeAsV3JsonWorks()
498647
// Assert
499648
Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual)));
500649
}
650+
651+
private static async Task AssertRequiredPropertyThrowsAsync(OpenApiSecurityScheme securityScheme, OpenApiSpecVersion version, string message)
652+
{
653+
var exception = await Assert.ThrowsAsync<OpenApiException>(() => securityScheme.SerializeAsJsonAsync(version));
654+
Assert.Contains(message, exception.Message);
655+
}
501656
}
502657
}

0 commit comments

Comments
 (0)