From ddc4aeac6dc9d7b19c3a49312f4b2e8a1b2be532 Mon Sep 17 00:00:00 2001 From: stijnmoreels <9039753+stijnmoreels@users.noreply.github.com> Date: Tue, 14 Jul 2020 10:08:47 +0200 Subject: [PATCH 01/12] Feature - add operation filters for Shared Access Key and Certificate authentications in OpenAPI docs --- .../features/openapi/security-definitions.md | 64 +++++++++-- .../Arcus.WebApi.OpenApi.Extensions.csproj | 4 + ...ertificateAuthenticationOperationFilter.cs | 105 ++++++++++++++++++ ...dAccessKeyAuthenticationOperationFilter.cs | 104 +++++++++++++++++ .../SharedAccessKeyAuthenticationAttribute.cs | 2 +- .../Hosting/TestApiServer.cs | 2 + .../OpenApi/AuthenticationController.cs | 48 ++++++++ ... => AuthenticationOperationFilterTests.cs} | 79 ++++++++----- .../OpenApi/OAuthAuthorizeController.cs | 28 ----- 9 files changed, 366 insertions(+), 70 deletions(-) create mode 100644 src/Arcus.WebApi.OpenApi.Extensions/CertificateAuthenticationOperationFilter.cs create mode 100644 src/Arcus.WebApi.OpenApi.Extensions/SharedAccessKeyAuthenticationOperationFilter.cs create mode 100644 src/Arcus.WebApi.Tests.Unit/OpenApi/AuthenticationController.cs rename src/Arcus.WebApi.Tests.Unit/OpenApi/{OAuthAuthorizeOperationFilterTests.cs => AuthenticationOperationFilterTests.cs} (51%) delete mode 100644 src/Arcus.WebApi.Tests.Unit/OpenApi/OAuthAuthorizeController.cs diff --git a/docs/preview/features/openapi/security-definitions.md b/docs/preview/features/openapi/security-definitions.md index 122b5648..46f8986b 100644 --- a/docs/preview/features/openapi/security-definitions.md +++ b/docs/preview/features/openapi/security-definitions.md @@ -5,8 +5,12 @@ layout: default # Adding OAuth security definition to API operations -When an API is secured via OAuth, it is helpful if the Open API documentation makes this clear via a security scheme and the API operations that require authorization automatically inform the consumer that it is possible that a 401 Unauthorized or 403 Forbidden response is returned. -The `OAuthAuthorizeOperationFilter` that is part of this package exposes this functionality. +When an API is secured via OAuth, [Shared Access Key authentication](../../features/security/auth/shared-access-key), [Certificate authentication](../../features/security/auth/certificate), it is helpful if the Open API documentation makes this clear via a security scheme and the API operations that require authorization automatically inform the consumer that it is possible that a 401 Unauthorized or 403 Forbidden response is returned. + +These `IOperationFilter`'s that are part of this package exposes this functionality: +- [`OAuthAuthorizeOperationFilter`](#oauth) +- [`SharedAccessKeyAuthenticationOperationFilter`](#sharedaccesskey) +- [`CertificateAuthenticationOperationFilter`](#certificate) ## Installation @@ -18,21 +22,59 @@ PM > Install-Package Arcus.WebApi.OpenApi.Extensions ## Usage -To indicate that an API is protected by OAuth, you need to add `AuthorizeCheckOperationFilter` as an `OperationFilter` when configuring Swashbuckles Swagger generation: +### OAuth + +To indicate that an API is protected by OAuth, you need to add `OAuthAuthorizeOperationFilter` as an `IOperationFilter` when configuring Swashbuckles Swagger generation: + +```csharp +services.AddSwaggerGen(setupAction => +{ + setupAction.SwaggerDoc("v1", new Info { Title = "My API v1", Version = "v1" }); + + setupAction.AddSecurityDefinition("oauth2", new OAuth2Scheme + { + Flow = "implicit", + AuthorizationUrl = $"{authorityUrl}connect/authorize", + Scopes = scopes + }); + + setupAction.OperationFilter(new object[] { new[] { "myApiScope1", "myApiScope2" } }); +}); +``` + +### Shared Access Key + +To indicate that an API is protected by [Shared Access Key authentication](../../features/security/auth/shared-access-key), you need to add `SharedAccessKeyAuthenticationOperationFilter` as an `IOperationFilter` when configuring Swashbuckles Swagger generation: + +```csharp +services.AddSwaggerGen(setupAction => +{ + setupAction.SwaggerDoc("v1", new Info { Title = "My API v1", Version = "v1" }); + + setupAction.AddSecurityDefinition("sharedaccesskey", new OpenApiSecurityScheme + { + Type = SecuritySchemeType.ApiKey + }); + + setupAction.OperationFilter(new object[] { new[] { "myApiScope1", "myApiScope2" } }); +}); +``` + +### Shared Access Key + +To indicate that an API is protected by [Certificate authentication](../../features/security/auth/certificate), you need to add `CertificateAuthenticationOperationFilter` as an `IOperationFilter` when configuring Swashbuckles Swagger generation: ```csharp services.AddSwaggerGen(setupAction => { - setupAction.SwaggerDoc("v1", new Info { Title = "My API v1", Version = "v1" }); + setupAction.SwaggerDoc("v1", new Info { Title = "My API v1", Version = "v1" }); - setupAction.AddSecurityDefinition("oauth2", new OAuth2Scheme - { - Flow = "implicit", - AuthorizationUrl = $"{authorityUrl}connect/authorize", - Scopes = scopes - }); + setupAction.AddSecurityDefinition("certificate", new OpenApiSecurityScheme + { + Type = SecuritySchemeType.ApiKey + }); - setupAction.OperationFilter(new object[] {new [] {"myApiScope1", "myApiScope2"}); + setupAction.OperationFilter(new object[] { new[] { "myApiScope1", "myApiScope2" } }); }); ``` diff --git a/src/Arcus.WebApi.OpenApi.Extensions/Arcus.WebApi.OpenApi.Extensions.csproj b/src/Arcus.WebApi.OpenApi.Extensions/Arcus.WebApi.OpenApi.Extensions.csproj index 8940cfc4..fd2a9b59 100644 --- a/src/Arcus.WebApi.OpenApi.Extensions/Arcus.WebApi.OpenApi.Extensions.csproj +++ b/src/Arcus.WebApi.OpenApi.Extensions/Arcus.WebApi.OpenApi.Extensions.csproj @@ -23,5 +23,9 @@ + + + + diff --git a/src/Arcus.WebApi.OpenApi.Extensions/CertificateAuthenticationOperationFilter.cs b/src/Arcus.WebApi.OpenApi.Extensions/CertificateAuthenticationOperationFilter.cs new file mode 100644 index 00000000..4d8e56ce --- /dev/null +++ b/src/Arcus.WebApi.OpenApi.Extensions/CertificateAuthenticationOperationFilter.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Arcus.WebApi.Security.Authentication.Certificates; +using Arcus.WebApi.Security.Authentication.SharedAccessKey; +using GuardNet; +#if NETCOREAPP3_1 +using Microsoft.OpenApi.Models; +#endif +using Swashbuckle.AspNetCore.Swagger; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace Arcus.WebApi.OpenApi.Extensions +{ + /// + /// A Swashbuckle operation filter that adds certificate security definitions to authorized API operations. + /// + public class CertificateAuthenticationOperationFilter : IOperationFilter + { + private readonly IEnumerable _scopes; + + /// + /// Initializes a new instance of the class. + /// + /// A list of API scopes that is defined for the API that must be documented. + /// It is not possible right now to document the scopes on a fine grained operation-level. + /// When the are null. + /// When the has any elements that are null or blank. + public CertificateAuthenticationOperationFilter(IEnumerable scopes) + { + Guard.NotNull(scopes, nameof(scopes), "The sequence of scopes cannot be null"); + Guard.For(() => scopes.Any(String.IsNullOrWhiteSpace), "The sequence of scopes cannot contain a scope that is null or blank"); + + _scopes = scopes; + } + + /// + /// Applies the OperationFilter to the API . + /// + /// The operation instance on which the OperationFilter must be applied. + /// Provides meta-information on the instance. +#if NETCOREAPP3_1 + public void Apply(OpenApiOperation operation, OperationFilterContext context) +#else + public void Apply(Operation operation, OperationFilterContext context) +#endif + { + bool hasOperationAuthentication = + context.MethodInfo + .GetCustomAttributes(true) + .OfType() + .Any(); + + bool hasControllerAuthentication = + context.MethodInfo.DeclaringType != null + && context.MethodInfo.DeclaringType + .GetCustomAttributes(true) + .OfType() + .Any(); + + if (hasOperationAuthentication || hasControllerAuthentication) + { + if (operation.Responses.ContainsKey("401") == false) + { +#if NETCOREAPP3_1 + operation.Responses.Add("401", new OpenApiResponse { Description = "Unauthorized" }); +#else + operation.Responses.Add("401", new Response { Description = "Unauthorized" }); +#endif + + } + + if (operation.Responses.ContainsKey("403") == false) + { +#if NETCOREAPP3_1 + operation.Responses.Add("403", new OpenApiResponse { Description = "Forbidden" }); +#else + operation.Responses.Add("403", new Response { Description = "Forbidden" }); +#endif + } +#if NETCOREAPP3_1 + var scheme = new OpenApiSecurityScheme + { + Scheme = "certificate", + Type = SecuritySchemeType.ApiKey + }; + + operation.Security = new List + { + new OpenApiSecurityRequirement + { + [scheme] = _scopes.ToList() + } + }; +#else + operation.Security = new List>> + { + new Dictionary> { ["certificate"] = _scopes } + }; +#endif + } + } + } +} diff --git a/src/Arcus.WebApi.OpenApi.Extensions/SharedAccessKeyAuthenticationOperationFilter.cs b/src/Arcus.WebApi.OpenApi.Extensions/SharedAccessKeyAuthenticationOperationFilter.cs new file mode 100644 index 00000000..d20ec787 --- /dev/null +++ b/src/Arcus.WebApi.OpenApi.Extensions/SharedAccessKeyAuthenticationOperationFilter.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Arcus.WebApi.Security.Authentication.SharedAccessKey; +using GuardNet; +#if NETCOREAPP3_1 +using Microsoft.OpenApi.Models; +#endif +using Swashbuckle.AspNetCore.Swagger; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace Arcus.WebApi.OpenApi.Extensions +{ + /// + /// A Swashbuckle operation filter that adds shared access key security definitions to authorized API operations. + /// + public class SharedAccessKeyAuthenticationOperationFilter : IOperationFilter + { + private readonly IEnumerable _scopes; + + /// + /// Initializes a new instance of the class. + /// + /// A list of API scopes that is defined for the API that must be documented. + /// It is not possible right now to document the scopes on a fine grained operation-level. + /// When the are null. + /// When the has any elements that are null or blank. + public SharedAccessKeyAuthenticationOperationFilter(IEnumerable scopes) + { + Guard.NotNull(scopes, nameof(scopes), "The sequence of scopes cannot be null"); + Guard.For(() => scopes.Any(String.IsNullOrWhiteSpace), "The sequence of scopes cannot contain a scope that is null or blank"); + + _scopes = scopes; + } + + /// + /// Applies the OperationFilter to the API . + /// + /// The operation instance on which the OperationFilter must be applied. + /// Provides meta-information on the instance. +#if NETCOREAPP3_1 + public void Apply(OpenApiOperation operation, OperationFilterContext context) +#else + public void Apply(Operation operation, OperationFilterContext context) +#endif + { + bool hasOperationAuthentication = + context.MethodInfo + .GetCustomAttributes(true) + .OfType() + .Any(); + + bool hasControllerAuthentication = + context.MethodInfo.DeclaringType != null + && context.MethodInfo.DeclaringType + .GetCustomAttributes(true) + .OfType() + .Any(); + + if (hasOperationAuthentication || hasControllerAuthentication) + { + if (operation.Responses.ContainsKey("401") == false) + { +#if NETCOREAPP3_1 + operation.Responses.Add("401", new OpenApiResponse { Description = "Unauthorized" }); +#else + operation.Responses.Add("401", new Response { Description = "Unauthorized" }); +#endif + + } + + if (operation.Responses.ContainsKey("403") == false) + { +#if NETCOREAPP3_1 + operation.Responses.Add("403", new OpenApiResponse { Description = "Forbidden" }); +#else + operation.Responses.Add("403", new Response { Description = "Forbidden" }); +#endif + } +#if NETCOREAPP3_1 + var scheme = new OpenApiSecurityScheme + { + Scheme = "sharedaccesskey", + Type = SecuritySchemeType.ApiKey + }; + + operation.Security = new List + { + new OpenApiSecurityRequirement + { + [scheme] = _scopes.ToList() + } + }; +#else + operation.Security = new List>> + { + new Dictionary> { ["sharedaccesskey"] = _scopes } + }; +#endif + } + } + } +} diff --git a/src/Arcus.WebApi.Security/Authentication/SharedAccessKey/SharedAccessKeyAuthenticationAttribute.cs b/src/Arcus.WebApi.Security/Authentication/SharedAccessKey/SharedAccessKeyAuthenticationAttribute.cs index 2dc0d987..dbb46b76 100644 --- a/src/Arcus.WebApi.Security/Authentication/SharedAccessKey/SharedAccessKeyAuthenticationAttribute.cs +++ b/src/Arcus.WebApi.Security/Authentication/SharedAccessKey/SharedAccessKeyAuthenticationAttribute.cs @@ -24,7 +24,7 @@ public SharedAccessKeyAuthenticationAttribute(string secretName) : this(headerNa /// /// The name of the request header which value must match the stored secret. /// The name of the query parameter which value must match the stored secret. - /// The name of the secret that's being retrieved using the call. + /// The name of the secret that's being retrieved using the call. /// When the is null or blank. /// When the is null or blank. public SharedAccessKeyAuthenticationAttribute(string secretName, string headerName = null, string queryParameterName = null) : base(typeof(SharedAccessKeyAuthenticationFilter)) diff --git a/src/Arcus.WebApi.Tests.Unit/Hosting/TestApiServer.cs b/src/Arcus.WebApi.Tests.Unit/Hosting/TestApiServer.cs index ff9388a0..bb6b1830 100644 --- a/src/Arcus.WebApi.Tests.Unit/Hosting/TestApiServer.cs +++ b/src/Arcus.WebApi.Tests.Unit/Hosting/TestApiServer.cs @@ -116,6 +116,8 @@ protected override void ConfigureWebHost(IWebHostBuilder builder) swaggerGenerationOptions.SwaggerDoc("v1", openApiInformation); swaggerGenerationOptions.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, assemblyName + ".Open-Api.xml")); swaggerGenerationOptions.OperationFilter(new object[] { new[] { "myApiScope" } }); + swaggerGenerationOptions.OperationFilter(new object[] { new [] { "myApiScope" } }); + swaggerGenerationOptions.OperationFilter(new object[] { new [] { "myApiScope" } }); }); }); diff --git a/src/Arcus.WebApi.Tests.Unit/OpenApi/AuthenticationController.cs b/src/Arcus.WebApi.Tests.Unit/OpenApi/AuthenticationController.cs new file mode 100644 index 00000000..afc8bb17 --- /dev/null +++ b/src/Arcus.WebApi.Tests.Unit/OpenApi/AuthenticationController.cs @@ -0,0 +1,48 @@ +using System.Net.Http; +using Arcus.WebApi.Security.Authentication.Certificates; +using Arcus.WebApi.Security.Authentication.SharedAccessKey; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Arcus.WebApi.Tests.Unit.OpenApi +{ + [ApiController] + public class AuthenticationController : ControllerBase + { + public const string OAuthRoute = "openapi/auth/oauth", + SharedAccessKeyRoute = "openapi/auth/sharedaccesskey", + CertificateRoute = "openapi/auth/certificate", + NoneRoute = "openapi/auth/none"; + + [HttpGet] + [Route(OAuthRoute)] + [Authorize] + public IActionResult GetOAuthAuthorized() + { + return Ok(); + } + + [HttpGet] + [Route(CertificateRoute)] + [CertificateAuthentication] + public IActionResult GetCertificateAuthorized() + { + return Ok(); + } + + [HttpGet] + [Route(SharedAccessKeyRoute)] + [SharedAccessKeyAuthentication("secretName", "headerName")] + public IActionResult GetSharedAccessKeyAuthorized() + { + return Ok(); + } + + [HttpGet] + [Route(NoneRoute)] + public IActionResult GetNoneAuthorized() + { + return Ok(); + } + } +} diff --git a/src/Arcus.WebApi.Tests.Unit/OpenApi/OAuthAuthorizeOperationFilterTests.cs b/src/Arcus.WebApi.Tests.Unit/OpenApi/AuthenticationOperationFilterTests.cs similarity index 51% rename from src/Arcus.WebApi.Tests.Unit/OpenApi/OAuthAuthorizeOperationFilterTests.cs rename to src/Arcus.WebApi.Tests.Unit/OpenApi/AuthenticationOperationFilterTests.cs index 7ee11d16..bb0db74d 100644 --- a/src/Arcus.WebApi.Tests.Unit/OpenApi/OAuthAuthorizeOperationFilterTests.cs +++ b/src/Arcus.WebApi.Tests.Unit/OpenApi/AuthenticationOperationFilterTests.cs @@ -1,10 +1,10 @@ -using Arcus.WebApi.OpenApi.Extensions; -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; using System.Threading.Tasks; +using Arcus.WebApi.OpenApi.Extensions; using Arcus.WebApi.Tests.Unit.Hosting; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Readers; @@ -13,29 +13,24 @@ namespace Arcus.WebApi.Tests.Unit.OpenApi { - public class OAuthAuthorizeOperationFilterTests : IDisposable + public class AuthenticationOperationFilterTests : IDisposable { private readonly ITestOutputHelper _outputWriter; private readonly TestApiServer _testServer = new TestApiServer(); /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - public OAuthAuthorizeOperationFilterTests(ITestOutputHelper outputWriter) + public AuthenticationOperationFilterTests(ITestOutputHelper outputWriter) { _outputWriter = outputWriter; } [Theory] - [InlineData(new object[] { new[] { "valid scope", "" } })] - [InlineData(new object[] { new[] { "valid scope", null, "another scope" } })] - public void OAuthAuthorizeOperationFilter_ShouldFailWithInvalidScopeList(IEnumerable scopes) - { - Assert.Throws(() => new OAuthAuthorizeOperationFilter(scopes)); - } - - [Fact] - public async Task OAuthAuthorizeOperationFilter_ShouldIncludeSecurityDefinitionResponses_OnAuthorizedOperations() + [InlineData(AuthenticationController.OAuthRoute)] + [InlineData(AuthenticationController.SharedAccessKeyRoute)] + [InlineData(AuthenticationController.CertificateRoute)] + public async Task AuthenticationOperationFilter_ShouldIncludeSecurityDefinitionResponses_OnAuthorizedOperations(string authorizedRoute) { // Arrange using (var client = _testServer.CreateClient()) @@ -52,26 +47,26 @@ public async Task OAuthAuthorizeOperationFilter_ShouldIncludeSecurityDefinitionR _outputWriter.WriteLine(diagnostic.Errors.Count == 0 ? String.Empty : String.Join(", ", diagnostic.Errors.Select(e => e.Message + ": " + e.Pointer))); Assert.True( - swagger.Paths.TryGetValue("/oauth/authorize", out OpenApiPathItem oauthPath), - "Cannot find OAuth authorized path in Open API spec file"); + swagger.Paths.TryGetValue("/" + authorizedRoute, out OpenApiPathItem path), + $"Cannot find /{authorizedRoute} authorized path in Open API spec file"); Assert.True( - oauthPath.Operations.TryGetValue(OperationType.Get, out OpenApiOperation oauthOperation), - "Cannot find OAuth GET operation in Open API spec file"); + path.Operations.TryGetValue(OperationType.Get, out OpenApiOperation operation), + "Cannot find Shared Access Key GET operation in Open API spec file"); - OpenApiResponses oauthResponses = oauthOperation.Responses; - Assert.Contains(oauthResponses, r => r.Key == "401"); - Assert.Contains(oauthResponses, r => r.Key == "403"); + OpenApiResponses operationResponses = operation.Responses; + Assert.Contains(operationResponses, r => r.Key == "401"); + Assert.Contains(operationResponses, r => r.Key == "403"); } } } [Fact] - public async Task OAuthAuthorizeOperationFilter_ShouldNotIncludeSecurityDefinitionResponses_OnNonAuthorizedOperations() + public async Task AuthenticationOperationFilter_ShouldNotIncludeSecurityDefinitionResponses_OnNonAuthorizedOperations() { // Arrange using (var client = _testServer.CreateClient()) - // Act + // Act using (HttpResponseMessage response = await client.GetAsync("swagger/v1/swagger.json")) { // Assert @@ -84,20 +79,44 @@ public async Task OAuthAuthorizeOperationFilter_ShouldNotIncludeSecurityDefiniti _outputWriter.WriteLine(diagnostic.Errors.Count == 0 ? String.Empty : String.Join(", ", diagnostic.Errors.Select(e => e.Message + ": " + e.Pointer))); Assert.True( - swagger.Paths.TryGetValue("/oauth/none", out OpenApiPathItem oauthPath), - "Cannot find OAuth none authorized path in Open API spec file"); + swagger.Paths.TryGetValue("/" + AuthenticationController.NoneRoute, out OpenApiPathItem path), + $"Cannot find /{AuthenticationController.NoneRoute} none authorized path in Open API spec file"); Assert.True( - oauthPath.Operations.TryGetValue(OperationType.Get, out OpenApiOperation oauthOperation), - "Cannot find OAuth GET operation in Open API spec file"); + path.Operations.TryGetValue(OperationType.Get, out OpenApiOperation operation), + "Cannot find GET operation in Open API spec file"); - OpenApiResponses oauthResponses = oauthOperation.Responses; - Assert.DoesNotContain(oauthResponses, r => r.Key == "401"); - Assert.DoesNotContain(oauthResponses, r => r.Key == "403"); + OpenApiResponses operationResponses = operation.Responses; + Assert.DoesNotContain(operationResponses, r => r.Key == "401"); + Assert.DoesNotContain(operationResponses, r => r.Key == "403"); } } } + [Theory] + [InlineData(new object[] { new[] { "valid scope", "" } })] + [InlineData(new object[] { new[] { "valid scope", null, "another scope" } })] + public void OAuthAuthorizeOperationFilter_ShouldFailWithInvalidScopeList(IEnumerable scopes) + { + Assert.Throws(() => new OAuthAuthorizeOperationFilter(scopes)); + } + + [Theory] + [InlineData(new object[] { new[] { "valid scope", "" } })] + [InlineData(new object[] { new[] { "valid scope", null, "another scope" } })] + public void SharedAccessKeyAuthenticationOperationFilter_ShouldFailWithInvalidScopeList(IEnumerable scopes) + { + Assert.Throws(() => new SharedAccessKeyAuthenticationOperationFilter(scopes)); + } + + [Theory] + [InlineData(new object[] { new[] { "valid scope", "" } })] + [InlineData(new object[] { new[] { "valid scope", null, "another scope" } })] + public void CertificateAuthenticationOperationFilter_ShouldFailWithInvalidScopeList(IEnumerable scopes) + { + Assert.Throws(() => new CertificateAuthenticationOperationFilter(scopes)); + } + /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// diff --git a/src/Arcus.WebApi.Tests.Unit/OpenApi/OAuthAuthorizeController.cs b/src/Arcus.WebApi.Tests.Unit/OpenApi/OAuthAuthorizeController.cs deleted file mode 100644 index d2f01d81..00000000 --- a/src/Arcus.WebApi.Tests.Unit/OpenApi/OAuthAuthorizeController.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Net.Http; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; - -namespace Arcus.WebApi.Tests.Unit.OpenApi -{ - [ApiController] - public class OAuthAuthorizeController : ControllerBase - { - public const string AuthorizedRoute = "oauth/authorize", - NoneAuthorizedRoute = "oauth/none"; - - [HttpGet] - [Route(AuthorizedRoute)] - [Authorize] - public IActionResult GetAuthorized(HttpRequestMessage request) - { - return Ok(); - } - - [HttpGet] - [Route(NoneAuthorizedRoute)] - public IActionResult GetNoneAuthorized(HttpRequestMessage request) - { - return Ok(); - } - } -} From d38fcfb4a676dce2fec2f868ef083c3c1efeb2e5 Mon Sep 17 00:00:00 2001 From: stijnmoreels <9039753+stijnmoreels@users.noreply.github.com> Date: Tue, 14 Jul 2020 10:13:08 +0200 Subject: [PATCH 02/12] pr-style: remove blank line in operation filters --- .../CertificateAuthenticationOperationFilter.cs | 1 - .../SharedAccessKeyAuthenticationOperationFilter.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/src/Arcus.WebApi.OpenApi.Extensions/CertificateAuthenticationOperationFilter.cs b/src/Arcus.WebApi.OpenApi.Extensions/CertificateAuthenticationOperationFilter.cs index 4d8e56ce..392acfa4 100644 --- a/src/Arcus.WebApi.OpenApi.Extensions/CertificateAuthenticationOperationFilter.cs +++ b/src/Arcus.WebApi.OpenApi.Extensions/CertificateAuthenticationOperationFilter.cs @@ -68,7 +68,6 @@ public void Apply(Operation operation, OperationFilterContext context) #else operation.Responses.Add("401", new Response { Description = "Unauthorized" }); #endif - } if (operation.Responses.ContainsKey("403") == false) diff --git a/src/Arcus.WebApi.OpenApi.Extensions/SharedAccessKeyAuthenticationOperationFilter.cs b/src/Arcus.WebApi.OpenApi.Extensions/SharedAccessKeyAuthenticationOperationFilter.cs index d20ec787..00127286 100644 --- a/src/Arcus.WebApi.OpenApi.Extensions/SharedAccessKeyAuthenticationOperationFilter.cs +++ b/src/Arcus.WebApi.OpenApi.Extensions/SharedAccessKeyAuthenticationOperationFilter.cs @@ -67,7 +67,6 @@ public void Apply(Operation operation, OperationFilterContext context) #else operation.Responses.Add("401", new Response { Description = "Unauthorized" }); #endif - } if (operation.Responses.ContainsKey("403") == false) From 09ecf24db98d393b2daaabcf9d363e9e5dbf3204 Mon Sep 17 00:00:00 2001 From: stijnmoreels <9039753+stijnmoreels@users.noreply.github.com> Date: Mon, 20 Jul 2020 07:45:51 +0200 Subject: [PATCH 03/12] Update src/Arcus.WebApi.OpenApi.Extensions/CertificateAuthenticationOperationFilter.cs Co-authored-by: Tom Kerkhove --- .../CertificateAuthenticationOperationFilter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Arcus.WebApi.OpenApi.Extensions/CertificateAuthenticationOperationFilter.cs b/src/Arcus.WebApi.OpenApi.Extensions/CertificateAuthenticationOperationFilter.cs index 392acfa4..938e5cf2 100644 --- a/src/Arcus.WebApi.OpenApi.Extensions/CertificateAuthenticationOperationFilter.cs +++ b/src/Arcus.WebApi.OpenApi.Extensions/CertificateAuthenticationOperationFilter.cs @@ -6,7 +6,7 @@ using Arcus.WebApi.Security.Authentication.SharedAccessKey; using GuardNet; #if NETCOREAPP3_1 -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Models; #endif using Swashbuckle.AspNetCore.Swagger; using Swashbuckle.AspNetCore.SwaggerGen; From 2493d580cebd89781f045b7ddfa3d3ab0a2e8257 Mon Sep 17 00:00:00 2001 From: stijnmoreels <9039753+stijnmoreels@users.noreply.github.com> Date: Mon, 20 Jul 2020 07:47:35 +0200 Subject: [PATCH 04/12] Update docs/preview/features/openapi/security-definitions.md Co-authored-by: Tom Kerkhove --- docs/preview/features/openapi/security-definitions.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/preview/features/openapi/security-definitions.md b/docs/preview/features/openapi/security-definitions.md index 46f8986b..f0a031e9 100644 --- a/docs/preview/features/openapi/security-definitions.md +++ b/docs/preview/features/openapi/security-definitions.md @@ -53,7 +53,9 @@ services.AddSwaggerGen(setupAction => setupAction.AddSecurityDefinition("sharedaccesskey", new OpenApiSecurityScheme { - Type = SecuritySchemeType.ApiKey + Name = "X-API-Key", + Type = SecuritySchemeType.ApiKey, + In = ParameterLocation.Header }); setupAction.OperationFilter(new object[] { new[] { "myApiScope1", "myApiScope2" } }); From c447570d00d7c63b2ec99f0dfeb55fbd55c82428 Mon Sep 17 00:00:00 2001 From: stijnmoreels <9039753+stijnmoreels@users.noreply.github.com> Date: Mon, 20 Jul 2020 07:54:27 +0200 Subject: [PATCH 05/12] pr-sug: remove scopes in operation filters --- .../features/openapi/security-definitions.md | 4 +-- ...ertificateAuthenticationOperationFilter.cs | 30 ++----------------- ...dAccessKeyAuthenticationOperationFilter.cs | 29 ++---------------- 3 files changed, 8 insertions(+), 55 deletions(-) diff --git a/docs/preview/features/openapi/security-definitions.md b/docs/preview/features/openapi/security-definitions.md index 46f8986b..8399b462 100644 --- a/docs/preview/features/openapi/security-definitions.md +++ b/docs/preview/features/openapi/security-definitions.md @@ -56,7 +56,7 @@ services.AddSwaggerGen(setupAction => Type = SecuritySchemeType.ApiKey }); - setupAction.OperationFilter(new object[] { new[] { "myApiScope1", "myApiScope2" } }); + setupAction.OperationFilter(); }); ``` @@ -74,7 +74,7 @@ services.AddSwaggerGen(setupAction => Type = SecuritySchemeType.ApiKey }); - setupAction.OperationFilter(new object[] { new[] { "myApiScope1", "myApiScope2" } }); + setupAction.OperationFilter(); }); ``` diff --git a/src/Arcus.WebApi.OpenApi.Extensions/CertificateAuthenticationOperationFilter.cs b/src/Arcus.WebApi.OpenApi.Extensions/CertificateAuthenticationOperationFilter.cs index 392acfa4..32f89e4c 100644 --- a/src/Arcus.WebApi.OpenApi.Extensions/CertificateAuthenticationOperationFilter.cs +++ b/src/Arcus.WebApi.OpenApi.Extensions/CertificateAuthenticationOperationFilter.cs @@ -1,10 +1,6 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; -using System.Text; using Arcus.WebApi.Security.Authentication.Certificates; -using Arcus.WebApi.Security.Authentication.SharedAccessKey; -using GuardNet; #if NETCOREAPP3_1 using Microsoft.OpenApi.Models; #endif @@ -18,23 +14,6 @@ namespace Arcus.WebApi.OpenApi.Extensions /// public class CertificateAuthenticationOperationFilter : IOperationFilter { - private readonly IEnumerable _scopes; - - /// - /// Initializes a new instance of the class. - /// - /// A list of API scopes that is defined for the API that must be documented. - /// It is not possible right now to document the scopes on a fine grained operation-level. - /// When the are null. - /// When the has any elements that are null or blank. - public CertificateAuthenticationOperationFilter(IEnumerable scopes) - { - Guard.NotNull(scopes, nameof(scopes), "The sequence of scopes cannot be null"); - Guard.For(() => scopes.Any(String.IsNullOrWhiteSpace), "The sequence of scopes cannot contain a scope that is null or blank"); - - _scopes = scopes; - } - /// /// Applies the OperationFilter to the API . /// @@ -89,14 +68,11 @@ public void Apply(Operation operation, OperationFilterContext context) { new OpenApiSecurityRequirement { - [scheme] = _scopes.ToList() + [scheme] = new List() } }; #else - operation.Security = new List>> - { - new Dictionary> { ["certificate"] = _scopes } - }; + operation.Security = new List>>(); #endif } } diff --git a/src/Arcus.WebApi.OpenApi.Extensions/SharedAccessKeyAuthenticationOperationFilter.cs b/src/Arcus.WebApi.OpenApi.Extensions/SharedAccessKeyAuthenticationOperationFilter.cs index 00127286..3673f92d 100644 --- a/src/Arcus.WebApi.OpenApi.Extensions/SharedAccessKeyAuthenticationOperationFilter.cs +++ b/src/Arcus.WebApi.OpenApi.Extensions/SharedAccessKeyAuthenticationOperationFilter.cs @@ -1,9 +1,6 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; -using System.Text; using Arcus.WebApi.Security.Authentication.SharedAccessKey; -using GuardNet; #if NETCOREAPP3_1 using Microsoft.OpenApi.Models; #endif @@ -17,23 +14,6 @@ namespace Arcus.WebApi.OpenApi.Extensions /// public class SharedAccessKeyAuthenticationOperationFilter : IOperationFilter { - private readonly IEnumerable _scopes; - - /// - /// Initializes a new instance of the class. - /// - /// A list of API scopes that is defined for the API that must be documented. - /// It is not possible right now to document the scopes on a fine grained operation-level. - /// When the are null. - /// When the has any elements that are null or blank. - public SharedAccessKeyAuthenticationOperationFilter(IEnumerable scopes) - { - Guard.NotNull(scopes, nameof(scopes), "The sequence of scopes cannot be null"); - Guard.For(() => scopes.Any(String.IsNullOrWhiteSpace), "The sequence of scopes cannot contain a scope that is null or blank"); - - _scopes = scopes; - } - /// /// Applies the OperationFilter to the API . /// @@ -88,14 +68,11 @@ public void Apply(Operation operation, OperationFilterContext context) { new OpenApiSecurityRequirement { - [scheme] = _scopes.ToList() + [scheme] = new List() } }; #else - operation.Security = new List>> - { - new Dictionary> { ["sharedaccesskey"] = _scopes } - }; + operation.Security = new List>>(); #endif } } From 9105811f749ec8c4235ccfeb557cd6b05604a146 Mon Sep 17 00:00:00 2001 From: stijnmoreels <9039753+stijnmoreels@users.noreply.github.com> Date: Mon, 20 Jul 2020 07:56:23 +0200 Subject: [PATCH 06/12] pr-sug: update with tests --- .../Hosting/TestApiServer.cs | 4 ++-- .../AuthenticationOperationFilterTests.cs | 16 ++++++---------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/Arcus.WebApi.Tests.Unit/Hosting/TestApiServer.cs b/src/Arcus.WebApi.Tests.Unit/Hosting/TestApiServer.cs index bb6b1830..350a5270 100644 --- a/src/Arcus.WebApi.Tests.Unit/Hosting/TestApiServer.cs +++ b/src/Arcus.WebApi.Tests.Unit/Hosting/TestApiServer.cs @@ -116,8 +116,8 @@ protected override void ConfigureWebHost(IWebHostBuilder builder) swaggerGenerationOptions.SwaggerDoc("v1", openApiInformation); swaggerGenerationOptions.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, assemblyName + ".Open-Api.xml")); swaggerGenerationOptions.OperationFilter(new object[] { new[] { "myApiScope" } }); - swaggerGenerationOptions.OperationFilter(new object[] { new [] { "myApiScope" } }); - swaggerGenerationOptions.OperationFilter(new object[] { new [] { "myApiScope" } }); + swaggerGenerationOptions.OperationFilter(); + swaggerGenerationOptions.OperationFilter(); }); }); diff --git a/src/Arcus.WebApi.Tests.Unit/OpenApi/AuthenticationOperationFilterTests.cs b/src/Arcus.WebApi.Tests.Unit/OpenApi/AuthenticationOperationFilterTests.cs index bb0db74d..d7478dcc 100644 --- a/src/Arcus.WebApi.Tests.Unit/OpenApi/AuthenticationOperationFilterTests.cs +++ b/src/Arcus.WebApi.Tests.Unit/OpenApi/AuthenticationOperationFilterTests.cs @@ -101,20 +101,16 @@ public void OAuthAuthorizeOperationFilter_ShouldFailWithInvalidScopeList(IEnumer Assert.Throws(() => new OAuthAuthorizeOperationFilter(scopes)); } - [Theory] - [InlineData(new object[] { new[] { "valid scope", "" } })] - [InlineData(new object[] { new[] { "valid scope", null, "another scope" } })] - public void SharedAccessKeyAuthenticationOperationFilter_ShouldFailWithInvalidScopeList(IEnumerable scopes) + [Fact] + public void SharedAccessKeyAuthenticationOperationFilter_ShouldFailWithInvalidScopeList() { - Assert.Throws(() => new SharedAccessKeyAuthenticationOperationFilter(scopes)); + Assert.Throws(() => new SharedAccessKeyAuthenticationOperationFilter()); } - [Theory] - [InlineData(new object[] { new[] { "valid scope", "" } })] - [InlineData(new object[] { new[] { "valid scope", null, "another scope" } })] - public void CertificateAuthenticationOperationFilter_ShouldFailWithInvalidScopeList(IEnumerable scopes) + [Fact] + public void CertificateAuthenticationOperationFilter_ShouldFailWithInvalidScopeList() { - Assert.Throws(() => new CertificateAuthenticationOperationFilter(scopes)); + Assert.Throws(() => new CertificateAuthenticationOperationFilter()); } /// From f3e791a5232c9940df1a7a2a30edfa7c2eec5aa7 Mon Sep 17 00:00:00 2001 From: stijnmoreels <9039753+stijnmoreels@users.noreply.github.com> Date: Mon, 20 Jul 2020 10:38:55 +0200 Subject: [PATCH 07/12] pr-fix: remove tests with ctor creation --- .../OpenApi/AuthenticationOperationFilterTests.cs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/Arcus.WebApi.Tests.Unit/OpenApi/AuthenticationOperationFilterTests.cs b/src/Arcus.WebApi.Tests.Unit/OpenApi/AuthenticationOperationFilterTests.cs index d7478dcc..44e41448 100644 --- a/src/Arcus.WebApi.Tests.Unit/OpenApi/AuthenticationOperationFilterTests.cs +++ b/src/Arcus.WebApi.Tests.Unit/OpenApi/AuthenticationOperationFilterTests.cs @@ -101,18 +101,6 @@ public void OAuthAuthorizeOperationFilter_ShouldFailWithInvalidScopeList(IEnumer Assert.Throws(() => new OAuthAuthorizeOperationFilter(scopes)); } - [Fact] - public void SharedAccessKeyAuthenticationOperationFilter_ShouldFailWithInvalidScopeList() - { - Assert.Throws(() => new SharedAccessKeyAuthenticationOperationFilter()); - } - - [Fact] - public void CertificateAuthenticationOperationFilter_ShouldFailWithInvalidScopeList() - { - Assert.Throws(() => new CertificateAuthenticationOperationFilter()); - } - /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// From b57d0e6c61db7407352c27ed5e29aaa39e3a82ba Mon Sep 17 00:00:00 2001 From: stijnmoreels <9039753+stijnmoreels@users.noreply.github.com> Date: Mon, 27 Jul 2020 10:01:58 +0200 Subject: [PATCH 08/12] pr-sug: update w/ correct doc title --- docs/preview/features/openapi/security-definitions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/preview/features/openapi/security-definitions.md b/docs/preview/features/openapi/security-definitions.md index 3bf40f23..3c6e82bf 100644 --- a/docs/preview/features/openapi/security-definitions.md +++ b/docs/preview/features/openapi/security-definitions.md @@ -62,7 +62,7 @@ services.AddSwaggerGen(setupAction => }); ``` -### Shared Access Key +### Certificate To indicate that an API is protected by [Certificate authentication](../../features/security/auth/certificate), you need to add `CertificateAuthenticationOperationFilter` as an `IOperationFilter` when configuring Swashbuckles Swagger generation: From 8a945fbed3777171a0f68dd03d94eb1128d0606a Mon Sep 17 00:00:00 2001 From: stijnmoreels <9039753+stijnmoreels@users.noreply.github.com> Date: Thu, 27 Aug 2020 08:17:13 +0200 Subject: [PATCH 09/12] pr-sug: make security schema name configuratble --- .../features/openapi/security-definitions.md | 53 +++++++++++-------- ...ertificateAuthenticationOperationFilter.cs | 28 ++++++++-- .../OAuthAuthorizeOperationFilter.cs | 46 +++++++++++----- ...dAccessKeyAuthenticationOperationFilter.cs | 25 +++++++-- 4 files changed, 109 insertions(+), 43 deletions(-) diff --git a/docs/preview/features/openapi/security-definitions.md b/docs/preview/features/openapi/security-definitions.md index 3c6e82bf..e9380339 100644 --- a/docs/preview/features/openapi/security-definitions.md +++ b/docs/preview/features/openapi/security-definitions.md @@ -8,9 +8,9 @@ layout: default When an API is secured via OAuth, [Shared Access Key authentication](../../features/security/auth/shared-access-key), [Certificate authentication](../../features/security/auth/certificate), it is helpful if the Open API documentation makes this clear via a security scheme and the API operations that require authorization automatically inform the consumer that it is possible that a 401 Unauthorized or 403 Forbidden response is returned. These `IOperationFilter`'s that are part of this package exposes this functionality: +- [`CertificateAuthenticationOperationFilter`](#certificate) - [`OAuthAuthorizeOperationFilter`](#oauth) - [`SharedAccessKeyAuthenticationOperationFilter`](#sharedaccesskey) -- [`CertificateAuthenticationOperationFilter`](#certificate) ## Installation @@ -22,6 +22,27 @@ PM > Install-Package Arcus.WebApi.OpenApi.Extensions ## Usage +### Certificate + +To indicate that an API is protected by [Certificate authentication](../../features/security/auth/certificate), you need to add `CertificateAuthenticationOperationFilter` as an `IOperationFilter` when configuring Swashbuckles Swagger generation: + +```csharp +services.AddSwaggerGen(setupAction => +{ + setupAction.SwaggerDoc("v1", new Info { Title = "My API v1", Version = "v1" }); + + string securitySchemaName = "my-certificate"; + setupAction.AddSecurityDefinition(securitySchemaName, new OpenApiSecurityScheme + { + Type = SecuritySchemeType.ApiKey + }); + + setupAction.OperationFilter(securitySchemaName); +}); +``` + +> Note: the `CertificateAuthenticationOperationFilter` has by default `"certificate"` as `securitySchemaName`. + ### OAuth To indicate that an API is protected by OAuth, you need to add `OAuthAuthorizeOperationFilter` as an `IOperationFilter` when configuring Swashbuckles Swagger generation: @@ -31,17 +52,20 @@ services.AddSwaggerGen(setupAction => { setupAction.SwaggerDoc("v1", new Info { Title = "My API v1", Version = "v1" }); - setupAction.AddSecurityDefinition("oauth2", new OAuth2Scheme + string securitySchemaName = "my-oauth2"; + setupAction.AddSecurityDefinition(securitySchemaName, new OAuth2Scheme { Flow = "implicit", AuthorizationUrl = $"{authorityUrl}connect/authorize", Scopes = scopes }); - setupAction.OperationFilter(new object[] { new[] { "myApiScope1", "myApiScope2" } }); + setupAction.OperationFilter(securitySchemaName, new object[] { new[] { "myApiScope1", "myApiScope2" } }); }); ``` +> Note: the `OAuthAuthorizeOperationFilter` has by default `"oauth2"` as `securitySchemaName`. + ### Shared Access Key To indicate that an API is protected by [Shared Access Key authentication](../../features/security/auth/shared-access-key), you need to add `SharedAccessKeyAuthenticationOperationFilter` as an `IOperationFilter` when configuring Swashbuckles Swagger generation: @@ -51,33 +75,18 @@ services.AddSwaggerGen(setupAction => { setupAction.SwaggerDoc("v1", new Info { Title = "My API v1", Version = "v1" }); - setupAction.AddSecurityDefinition("sharedaccesskey", new OpenApiSecurityScheme + string securitySchemaName = "my-sharedaccesskey"; + setupAction.AddSecurityDefinition(securitySchemaName, new OpenApiSecurityScheme { Name = "X-API-Key", Type = SecuritySchemeType.ApiKey, In = ParameterLocation.Header }); - setupAction.OperationFilter(); + setupAction.OperationFilter(securitySchemaName); }); ``` -### Certificate - -To indicate that an API is protected by [Certificate authentication](../../features/security/auth/certificate), you need to add `CertificateAuthenticationOperationFilter` as an `IOperationFilter` when configuring Swashbuckles Swagger generation: - -```csharp -services.AddSwaggerGen(setupAction => -{ - setupAction.SwaggerDoc("v1", new Info { Title = "My API v1", Version = "v1" }); - - setupAction.AddSecurityDefinition("certificate", new OpenApiSecurityScheme - { - Type = SecuritySchemeType.ApiKey - }); - - setupAction.OperationFilter(); -}); -``` +> Note: the `SharedAccessKeyAuthenticationOperationFilter` has by default `"sharedaccesskey"` as `securitySchemaName`. [← back](/) diff --git a/src/Arcus.WebApi.OpenApi.Extensions/CertificateAuthenticationOperationFilter.cs b/src/Arcus.WebApi.OpenApi.Extensions/CertificateAuthenticationOperationFilter.cs index 2f2364b4..ce8d0f1d 100644 --- a/src/Arcus.WebApi.OpenApi.Extensions/CertificateAuthenticationOperationFilter.cs +++ b/src/Arcus.WebApi.OpenApi.Extensions/CertificateAuthenticationOperationFilter.cs @@ -1,6 +1,8 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using Arcus.WebApi.Security.Authentication.Certificates; +using GuardNet; #if NETCOREAPP3_1 using Microsoft.OpenApi.Models; #endif @@ -14,6 +16,20 @@ namespace Arcus.WebApi.OpenApi.Extensions /// public class CertificateAuthenticationOperationFilter : IOperationFilter { + private readonly string _securitySchemaName; + + /// + /// Initializes a new instance of the class. + /// + /// The name of the security schema. Default value is "sharedaccesskey". + /// Thrown when the is blank. + public CertificateAuthenticationOperationFilter(string securitySchemaName = "certificate") + { + Guard.NotNullOrWhitespace(securitySchemaName, nameof(securitySchemaName), "Requires a name for the certificate security schema"); + + _securitySchemaName = securitySchemaName; + } + /// /// Applies the OperationFilter to the API . /// @@ -60,8 +76,9 @@ public void Apply(Operation operation, OperationFilterContext context) #if NETCOREAPP3_1 var scheme = new OpenApiSecurityScheme { - Scheme = "certificate", - Type = SecuritySchemeType.ApiKey + Scheme = _securitySchemaName, + Type = SecuritySchemeType.ApiKey, + In = ParameterLocation.Header }; operation.Security = new List @@ -72,7 +89,10 @@ public void Apply(Operation operation, OperationFilterContext context) } }; #else - operation.Security = new List>>(); + operation.Security = new List>> + { + new Dictionary> { [_securitySchemaName] = Enumerable.Empty() } + }; #endif } } diff --git a/src/Arcus.WebApi.OpenApi.Extensions/OAuthAuthorizeOperationFilter.cs b/src/Arcus.WebApi.OpenApi.Extensions/OAuthAuthorizeOperationFilter.cs index a6de14f9..8e573c30 100644 --- a/src/Arcus.WebApi.OpenApi.Extensions/OAuthAuthorizeOperationFilter.cs +++ b/src/Arcus.WebApi.OpenApi.Extensions/OAuthAuthorizeOperationFilter.cs @@ -16,20 +16,26 @@ namespace Arcus.WebApi.OpenApi.Extensions /// public class OAuthAuthorizeOperationFilter : IOperationFilter { + private readonly string _securitySchemaName; private readonly IEnumerable _scopes; /// /// Initializes a new instance of the class. /// + /// The name of the security schema. Default value is "oauth2". /// A list of API scopes that is defined for the API that must be documented. /// It is not possible right now to document the scopes on a fine grained operation-level. - /// When the are null. - /// When the has any elements that are null or blank. - public OAuthAuthorizeOperationFilter(IEnumerable scopes) + /// Thrown when the is null. + /// + /// Thrown when the is blank or the has any elements that are null or blank. + /// + public OAuthAuthorizeOperationFilter(string securitySchemaName, IEnumerable scopes) { - Guard.NotNull(scopes, nameof(scopes), "The sequence of scopes cannot be null"); - Guard.For(() => scopes.Any(String.IsNullOrWhiteSpace), "The sequence of scopes cannot contain a scope that is null or blank"); - + Guard.NotNullOrWhitespace(securitySchemaName, nameof(securitySchemaName), "Requires a name for the OAuth2 security scheme"); + Guard.NotNull(scopes, nameof(scopes), "Requires a list of API scopes"); + Guard.For(() => scopes.Any(String.IsNullOrWhiteSpace), "Requires a list of non-blank API scopes"); + + _securitySchemaName = securitySchemaName; _scopes = scopes; } @@ -44,12 +50,24 @@ public void Apply(OpenApiOperation operation, OperationFilterContext context) public void Apply(Operation operation, OperationFilterContext context) #endif { - var hasAuthorize = context.MethodInfo.GetCustomAttributes(true).OfType().Any() || - ( - context.MethodInfo.DeclaringType != null && - context.MethodInfo.DeclaringType.GetCustomAttributes(true).OfType().Any() && - context.MethodInfo.GetCustomAttributes(false).OfType().Any() == false - ); + bool operationHasAuthorizeAttribute = + context.MethodInfo.GetCustomAttributes(inherit: true) + .OfType() + .Any(); + + bool controllerHasAuthorizeAttribute = + context.MethodInfo.DeclaringType != null + && context.MethodInfo.DeclaringType.GetCustomAttributes(inherit: true) + .OfType() + .Any(); + + bool operationHasAllowAnonymousAttribute = + context.MethodInfo.GetCustomAttributes(inherit: false) + .OfType().Any(); + + bool hasAuthorize = + operationHasAuthorizeAttribute + || controllerHasAuthorizeAttribute && !operationHasAllowAnonymousAttribute; if (hasAuthorize) { @@ -74,7 +92,7 @@ public void Apply(Operation operation, OperationFilterContext context) #if NETCOREAPP3_1 var oauth2Scheme = new OpenApiSecurityScheme { - Scheme = "oauth2", + Scheme = _securitySchemaName, Type = SecuritySchemeType.OAuth2 }; @@ -88,7 +106,7 @@ public void Apply(Operation operation, OperationFilterContext context) #else operation.Security = new List>> { - new Dictionary> { ["oauth2"] = _scopes } + new Dictionary> { [_securitySchemaName] = _scopes } }; #endif } diff --git a/src/Arcus.WebApi.OpenApi.Extensions/SharedAccessKeyAuthenticationOperationFilter.cs b/src/Arcus.WebApi.OpenApi.Extensions/SharedAccessKeyAuthenticationOperationFilter.cs index 3673f92d..00b4a6fa 100644 --- a/src/Arcus.WebApi.OpenApi.Extensions/SharedAccessKeyAuthenticationOperationFilter.cs +++ b/src/Arcus.WebApi.OpenApi.Extensions/SharedAccessKeyAuthenticationOperationFilter.cs @@ -1,6 +1,8 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using Arcus.WebApi.Security.Authentication.SharedAccessKey; +using GuardNet; #if NETCOREAPP3_1 using Microsoft.OpenApi.Models; #endif @@ -14,6 +16,20 @@ namespace Arcus.WebApi.OpenApi.Extensions /// public class SharedAccessKeyAuthenticationOperationFilter : IOperationFilter { + private readonly string _securitySchemaName; + + /// + /// Initializes a new instance of the class. + /// + /// The name of the security schema. Default value is "oauth2". + /// Thrown when the is blank. + public SharedAccessKeyAuthenticationOperationFilter(string securitySchemaName = "sharedaccesskey") + { + Guard.NotNullOrWhitespace(securitySchemaName, nameof(securitySchemaName), "Requires a name for the shared access key security scheme"); + + _securitySchemaName = securitySchemaName; + } + /// /// Applies the OperationFilter to the API . /// @@ -60,7 +76,7 @@ public void Apply(Operation operation, OperationFilterContext context) #if NETCOREAPP3_1 var scheme = new OpenApiSecurityScheme { - Scheme = "sharedaccesskey", + Scheme = _securitySchemaName, Type = SecuritySchemeType.ApiKey }; @@ -72,7 +88,10 @@ public void Apply(Operation operation, OperationFilterContext context) } }; #else - operation.Security = new List>>(); + operation.Security = new List>> + { + new Dictionary> { [_securitySchemaName] = Enumerable.Empty() } + }; #endif } } From dc8817856b35d579d1bf0e2f86f3ba1001ce913d Mon Sep 17 00:00:00 2001 From: stijnmoreels <9039753+stijnmoreels@users.noreply.github.com> Date: Thu, 27 Aug 2020 09:02:48 +0200 Subject: [PATCH 10/12] pr-sug: make security scheme type configurable --- ...ertificateAuthenticationOperationFilter.cs | 45 ++++++++++++++----- .../OAuthAuthorizeOperationFilter.cs | 8 ++-- ...dAccessKeyAuthenticationOperationFilter.cs | 40 +++++++++++++---- .../Hosting/TestApiServer.cs | 10 ++++- .../AuthenticationOperationFilterTests.cs | 8 ---- ...icateAuthenticationOperationFilterTests.cs | 42 +++++++++++++++++ .../OAuthAuthorizeOperationFilterTests.cs | 29 ++++++++++++ ...ssKeyAuthenticationOperationFilterTests.cs | 43 ++++++++++++++++++ 8 files changed, 192 insertions(+), 33 deletions(-) create mode 100644 src/Arcus.WebApi.Tests.Unit/OpenApi/CertificateAuthenticationOperationFilterTests.cs create mode 100644 src/Arcus.WebApi.Tests.Unit/OpenApi/OAuthAuthorizeOperationFilterTests.cs create mode 100644 src/Arcus.WebApi.Tests.Unit/OpenApi/SharedAccessKeyAuthenticationOperationFilterTests.cs diff --git a/src/Arcus.WebApi.OpenApi.Extensions/CertificateAuthenticationOperationFilter.cs b/src/Arcus.WebApi.OpenApi.Extensions/CertificateAuthenticationOperationFilter.cs index ce8d0f1d..5b6305f1 100644 --- a/src/Arcus.WebApi.OpenApi.Extensions/CertificateAuthenticationOperationFilter.cs +++ b/src/Arcus.WebApi.OpenApi.Extensions/CertificateAuthenticationOperationFilter.cs @@ -16,18 +16,42 @@ namespace Arcus.WebApi.OpenApi.Extensions /// public class CertificateAuthenticationOperationFilter : IOperationFilter { - private readonly string _securitySchemaName; + private const string DefaultSecuritySchemeName = "certificate"; + + private readonly string _securitySchemeName; +#if NETCOREAPP3_1 + private readonly SecuritySchemeType _securitySchemeType; +#endif /// /// Initializes a new instance of the class. /// - /// The name of the security schema. Default value is "sharedaccesskey". - /// Thrown when the is blank. - public CertificateAuthenticationOperationFilter(string securitySchemaName = "certificate") + /// The name of the security scheme. Default value is "certificate". +#if NETCOREAPP3_1 + /// The type of the security scheme. Default value is ApiKey. +#endif + public CertificateAuthenticationOperationFilter( +#if NETCOREAPP3_1 + string securitySchemeName = DefaultSecuritySchemeName, + SecuritySchemeType securitySchemeType = SecuritySchemeType.ApiKey +#else + string securitySchemeName = DefaultSecuritySchemeName +#endif + ) { - Guard.NotNullOrWhitespace(securitySchemaName, nameof(securitySchemaName), "Requires a name for the certificate security schema"); + Guard.NotNullOrWhitespace(securitySchemeName, + nameof(securitySchemeName), + "Requires a name for the Certificate security scheme"); + + _securitySchemeName = securitySchemeName; + +#if NETCOREAPP3_1 + Guard.For( + () => !Enum.IsDefined(typeof(SecuritySchemeType), securitySchemeType), + "Requires a security scheme type for the Certificate authentication that is within the bounds of the enumeration"); - _securitySchemaName = securitySchemaName; + _securitySchemeType = securitySchemeType; +#endif } /// @@ -73,11 +97,12 @@ public void Apply(Operation operation, OperationFilterContext context) operation.Responses.Add("403", new Response { Description = "Forbidden" }); #endif } + #if NETCOREAPP3_1 var scheme = new OpenApiSecurityScheme { - Scheme = _securitySchemaName, - Type = SecuritySchemeType.ApiKey, + Scheme = _securitySchemeName, + Type = _securitySchemeType, In = ParameterLocation.Header }; @@ -91,10 +116,10 @@ public void Apply(Operation operation, OperationFilterContext context) #else operation.Security = new List>> { - new Dictionary> { [_securitySchemaName] = Enumerable.Empty() } + new Dictionary> { [_securitySchemeName] = Enumerable.Empty() } }; #endif } } } -} +} \ No newline at end of file diff --git a/src/Arcus.WebApi.OpenApi.Extensions/OAuthAuthorizeOperationFilter.cs b/src/Arcus.WebApi.OpenApi.Extensions/OAuthAuthorizeOperationFilter.cs index 8e573c30..f7473565 100644 --- a/src/Arcus.WebApi.OpenApi.Extensions/OAuthAuthorizeOperationFilter.cs +++ b/src/Arcus.WebApi.OpenApi.Extensions/OAuthAuthorizeOperationFilter.cs @@ -22,18 +22,18 @@ public class OAuthAuthorizeOperationFilter : IOperationFilter /// /// Initializes a new instance of the class. /// - /// The name of the security schema. Default value is "oauth2". /// A list of API scopes that is defined for the API that must be documented. + /// The name of the security schema. Default value is "oauth2". /// It is not possible right now to document the scopes on a fine grained operation-level. /// Thrown when the is null. /// - /// Thrown when the is blank or the has any elements that are null or blank. + /// Thrown when the has any elements that are null or blank or the is blank. /// - public OAuthAuthorizeOperationFilter(string securitySchemaName, IEnumerable scopes) + public OAuthAuthorizeOperationFilter(IEnumerable scopes, string securitySchemaName = "oauth2") { - Guard.NotNullOrWhitespace(securitySchemaName, nameof(securitySchemaName), "Requires a name for the OAuth2 security scheme"); Guard.NotNull(scopes, nameof(scopes), "Requires a list of API scopes"); Guard.For(() => scopes.Any(String.IsNullOrWhiteSpace), "Requires a list of non-blank API scopes"); + Guard.NotNullOrWhitespace(securitySchemaName, nameof(securitySchemaName), "Requires a name for the OAuth2 security scheme"); _securitySchemaName = securitySchemaName; _scopes = scopes; diff --git a/src/Arcus.WebApi.OpenApi.Extensions/SharedAccessKeyAuthenticationOperationFilter.cs b/src/Arcus.WebApi.OpenApi.Extensions/SharedAccessKeyAuthenticationOperationFilter.cs index 00b4a6fa..a03b8860 100644 --- a/src/Arcus.WebApi.OpenApi.Extensions/SharedAccessKeyAuthenticationOperationFilter.cs +++ b/src/Arcus.WebApi.OpenApi.Extensions/SharedAccessKeyAuthenticationOperationFilter.cs @@ -16,18 +16,40 @@ namespace Arcus.WebApi.OpenApi.Extensions /// public class SharedAccessKeyAuthenticationOperationFilter : IOperationFilter { - private readonly string _securitySchemaName; + private const string DefaultSecuritySchemeName = "sharedaccesskey"; + + private readonly string _securitySchemeName; +#if NETCOREAPP3_1 + private readonly SecuritySchemeType _securitySchemeType; +#endif /// /// Initializes a new instance of the class. /// - /// The name of the security schema. Default value is "oauth2". - /// Thrown when the is blank. - public SharedAccessKeyAuthenticationOperationFilter(string securitySchemaName = "sharedaccesskey") + /// The name of the security scheme. Default value is "sharedaccesskey". +#if NETCOREAPP3_1 + /// The type of the security scheme. Default value is ApiKey. +#endif + public SharedAccessKeyAuthenticationOperationFilter( +#if NETCOREAPP3_1 + string securitySchemeName = DefaultSecuritySchemeName, + SecuritySchemeType securitySchemeType = SecuritySchemeType.ApiKey +#else + string securitySchemeName = DefaultSecuritySchemeName +#endif + ) { - Guard.NotNullOrWhitespace(securitySchemaName, nameof(securitySchemaName), "Requires a name for the shared access key security scheme"); + Guard.NotNullOrWhitespace(securitySchemeName, nameof(securitySchemeName), "Requires a name for the Shared Access Key security scheme"); + + _securitySchemeName = securitySchemeName; - _securitySchemaName = securitySchemaName; +#if NETCOREAPP3_1 + Guard.For( + () => !Enum.IsDefined(typeof(SecuritySchemeType), securitySchemeType), + "Requires a security scheme type for the Shared Access Key authentication that is within the bounds of the enumeration"); + + _securitySchemeType = securitySchemeType; +#endif } /// @@ -76,8 +98,8 @@ public void Apply(Operation operation, OperationFilterContext context) #if NETCOREAPP3_1 var scheme = new OpenApiSecurityScheme { - Scheme = _securitySchemaName, - Type = SecuritySchemeType.ApiKey + Scheme = _securitySchemeName, + Type = _securitySchemeType }; operation.Security = new List @@ -90,7 +112,7 @@ public void Apply(Operation operation, OperationFilterContext context) #else operation.Security = new List>> { - new Dictionary> { [_securitySchemaName] = Enumerable.Empty() } + new Dictionary> { [_securitySchemeName] = Enumerable.Empty() } }; #endif } diff --git a/src/Arcus.WebApi.Tests.Unit/Hosting/TestApiServer.cs b/src/Arcus.WebApi.Tests.Unit/Hosting/TestApiServer.cs index 350a5270..b688a6f4 100644 --- a/src/Arcus.WebApi.Tests.Unit/Hosting/TestApiServer.cs +++ b/src/Arcus.WebApi.Tests.Unit/Hosting/TestApiServer.cs @@ -116,8 +116,14 @@ protected override void ConfigureWebHost(IWebHostBuilder builder) swaggerGenerationOptions.SwaggerDoc("v1", openApiInformation); swaggerGenerationOptions.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, assemblyName + ".Open-Api.xml")); swaggerGenerationOptions.OperationFilter(new object[] { new[] { "myApiScope" } }); - swaggerGenerationOptions.OperationFilter(); - swaggerGenerationOptions.OperationFilter(); + +#if NETCOREAPP3_1 + swaggerGenerationOptions.OperationFilter("sharedaccesskey", SecuritySchemeType.ApiKey); + swaggerGenerationOptions.OperationFilter("certificate", SecuritySchemeType.ApiKey); +#else + swaggerGenerationOptions.OperationFilter("sharedaccesskey"); + swaggerGenerationOptions.OperationFilter("certificate"); +#endif }); }); diff --git a/src/Arcus.WebApi.Tests.Unit/OpenApi/AuthenticationOperationFilterTests.cs b/src/Arcus.WebApi.Tests.Unit/OpenApi/AuthenticationOperationFilterTests.cs index 44e41448..c81347f5 100644 --- a/src/Arcus.WebApi.Tests.Unit/OpenApi/AuthenticationOperationFilterTests.cs +++ b/src/Arcus.WebApi.Tests.Unit/OpenApi/AuthenticationOperationFilterTests.cs @@ -93,14 +93,6 @@ public async Task AuthenticationOperationFilter_ShouldNotIncludeSecurityDefiniti } } - [Theory] - [InlineData(new object[] { new[] { "valid scope", "" } })] - [InlineData(new object[] { new[] { "valid scope", null, "another scope" } })] - public void OAuthAuthorizeOperationFilter_ShouldFailWithInvalidScopeList(IEnumerable scopes) - { - Assert.Throws(() => new OAuthAuthorizeOperationFilter(scopes)); - } - /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// diff --git a/src/Arcus.WebApi.Tests.Unit/OpenApi/CertificateAuthenticationOperationFilterTests.cs b/src/Arcus.WebApi.Tests.Unit/OpenApi/CertificateAuthenticationOperationFilterTests.cs new file mode 100644 index 00000000..cabd3a5d --- /dev/null +++ b/src/Arcus.WebApi.Tests.Unit/OpenApi/CertificateAuthenticationOperationFilterTests.cs @@ -0,0 +1,42 @@ +using System; +using Arcus.WebApi.OpenApi.Extensions; +using Xunit; +#if NETCOREAPP3_1 +using Microsoft.OpenApi.Models; +#endif + +namespace Arcus.WebApi.Tests.Unit.OpenApi +{ + public class CertificateAuthenticationOperationFilterTests + { + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + public void OperationFilter_WithoutSecuritySchemaName_Throws(string securitySchemaName) + { + Assert.ThrowsAny(() => new CertificateAuthenticationOperationFilter(securitySchemaName)); + } + +#if NETCOREAPP3_1 + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + public void OperationFilterNetCoreApp31_WithoutSecuritySchemeName_Throws(string securitySchemeName) + { + Assert.ThrowsAny( + () => new CertificateAuthenticationOperationFilter(securitySchemeName: securitySchemeName)); + } + + [Theory] + [InlineData(SecuritySchemeType.Http | SecuritySchemeType.OAuth2)] + [InlineData((SecuritySchemeType) 4)] + public void OperationFilterNetCore31_WithOutOfBoundsSecuritySchemeType_Throws(SecuritySchemeType securitySchemeType) + { + Assert.ThrowsAny( + () => new CertificateAuthenticationOperationFilter(securitySchemeType: securitySchemeType)); + } +#endif + } +} diff --git a/src/Arcus.WebApi.Tests.Unit/OpenApi/OAuthAuthorizeOperationFilterTests.cs b/src/Arcus.WebApi.Tests.Unit/OpenApi/OAuthAuthorizeOperationFilterTests.cs new file mode 100644 index 00000000..77aad130 --- /dev/null +++ b/src/Arcus.WebApi.Tests.Unit/OpenApi/OAuthAuthorizeOperationFilterTests.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Arcus.WebApi.OpenApi.Extensions; +using Xunit; + +namespace Arcus.WebApi.Tests.Unit.OpenApi +{ + public class OAuthAuthorizeOperationFilterTests + { + [Theory] + [InlineData(new object[] { new[] { "valid scope", "" } })] + [InlineData(new object[] { new[] { "valid scope", null, "another scope" } })] + public void OAuthAuthorizeOperationFilter_ShouldFailWithInvalidScopeList(IEnumerable scopes) + { + Assert.Throws(() => new OAuthAuthorizeOperationFilter(scopes)); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + public void OAuthAuthorizeOperationFilter_WithoutSecuritySchemaName_Throws(string securitySchemaName) + { + Assert.ThrowsAny( + () => new OAuthAuthorizeOperationFilter(Enumerable.Empty(), securitySchemaName)); + } + } +} diff --git a/src/Arcus.WebApi.Tests.Unit/OpenApi/SharedAccessKeyAuthenticationOperationFilterTests.cs b/src/Arcus.WebApi.Tests.Unit/OpenApi/SharedAccessKeyAuthenticationOperationFilterTests.cs new file mode 100644 index 00000000..1235ae3f --- /dev/null +++ b/src/Arcus.WebApi.Tests.Unit/OpenApi/SharedAccessKeyAuthenticationOperationFilterTests.cs @@ -0,0 +1,43 @@ +using System; +using Arcus.WebApi.OpenApi.Extensions; +using Xunit; +#if NETCOREAPP3_1 +using Microsoft.OpenApi.Models; +#endif + +namespace Arcus.WebApi.Tests.Unit.OpenApi +{ + public class SharedAccessKeyAuthenticationOperationFilterTests + { + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + public void OperationFilter_WithoutSecuritySchemaName_Throws(string securitySchemaName) + { + Assert.ThrowsAny( + () => new SharedAccessKeyAuthenticationOperationFilter(securitySchemaName)); + } + +#if NETCOREAPP3_1 + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + public void OperationFilterNetCoreApp31_WithoutSecuritySchemeName_Throws(string securitySchemeName) + { + Assert.ThrowsAny( + () => new SharedAccessKeyAuthenticationOperationFilter(securitySchemeName: securitySchemeName)); + } + + [Theory] + [InlineData(SecuritySchemeType.Http | SecuritySchemeType.OAuth2)] + [InlineData((SecuritySchemeType) 4)] + public void OperationFilterNetCore31_WithOutOfBoundsSecuritySchemeType_Throws(SecuritySchemeType securitySchemeType) + { + Assert.ThrowsAny( + () => new SharedAccessKeyAuthenticationOperationFilter(securitySchemeType: securitySchemeType)); + } +#endif + } +} From 00db68ea73e1fda9cdb0bd721329a92d59a5df2c Mon Sep 17 00:00:00 2001 From: stijnmoreels <9039753+stijnmoreels@users.noreply.github.com> Date: Thu, 27 Aug 2020 09:37:03 +0200 Subject: [PATCH 11/12] pr-test: make sure we test for valid out of bound enumeration values --- .../CertificateAuthenticationOperationFilterTests.cs | 8 +++----- .../SharedAccessKeyAuthenticationOperationFilterTests.cs | 8 +++----- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/Arcus.WebApi.Tests.Unit/OpenApi/CertificateAuthenticationOperationFilterTests.cs b/src/Arcus.WebApi.Tests.Unit/OpenApi/CertificateAuthenticationOperationFilterTests.cs index cabd3a5d..b0aebebf 100644 --- a/src/Arcus.WebApi.Tests.Unit/OpenApi/CertificateAuthenticationOperationFilterTests.cs +++ b/src/Arcus.WebApi.Tests.Unit/OpenApi/CertificateAuthenticationOperationFilterTests.cs @@ -29,13 +29,11 @@ public void OperationFilterNetCoreApp31_WithoutSecuritySchemeName_Throws(string () => new CertificateAuthenticationOperationFilter(securitySchemeName: securitySchemeName)); } - [Theory] - [InlineData(SecuritySchemeType.Http | SecuritySchemeType.OAuth2)] - [InlineData((SecuritySchemeType) 4)] - public void OperationFilterNetCore31_WithOutOfBoundsSecuritySchemeType_Throws(SecuritySchemeType securitySchemeType) + [Fact] + public void OperationFilterNetCore31_WithOutOfBoundsSecuritySchemeType_Throws() { Assert.ThrowsAny( - () => new CertificateAuthenticationOperationFilter(securitySchemeType: securitySchemeType)); + () => new CertificateAuthenticationOperationFilter(securitySchemeType: (SecuritySchemeType) 12)); } #endif } diff --git a/src/Arcus.WebApi.Tests.Unit/OpenApi/SharedAccessKeyAuthenticationOperationFilterTests.cs b/src/Arcus.WebApi.Tests.Unit/OpenApi/SharedAccessKeyAuthenticationOperationFilterTests.cs index 1235ae3f..1cb55a8a 100644 --- a/src/Arcus.WebApi.Tests.Unit/OpenApi/SharedAccessKeyAuthenticationOperationFilterTests.cs +++ b/src/Arcus.WebApi.Tests.Unit/OpenApi/SharedAccessKeyAuthenticationOperationFilterTests.cs @@ -30,13 +30,11 @@ public void OperationFilterNetCoreApp31_WithoutSecuritySchemeName_Throws(string () => new SharedAccessKeyAuthenticationOperationFilter(securitySchemeName: securitySchemeName)); } - [Theory] - [InlineData(SecuritySchemeType.Http | SecuritySchemeType.OAuth2)] - [InlineData((SecuritySchemeType) 4)] - public void OperationFilterNetCore31_WithOutOfBoundsSecuritySchemeType_Throws(SecuritySchemeType securitySchemeType) + [Fact] + public void OperationFilterNetCore31_WithOutOfBoundsSecuritySchemeType_Throws() { Assert.ThrowsAny( - () => new SharedAccessKeyAuthenticationOperationFilter(securitySchemeType: securitySchemeType)); + () => new SharedAccessKeyAuthenticationOperationFilter(securitySchemeType: (SecuritySchemeType) 12)); } #endif } From c2c72dc892b990605b97bccba00c3eae9c783608 Mon Sep 17 00:00:00 2001 From: stijnmoreels <9039753+stijnmoreels@users.noreply.github.com> Date: Thu, 27 Aug 2020 09:38:15 +0200 Subject: [PATCH 12/12] pr-style: add paranthesis for AND and OR operation combinations --- .../OAuthAuthorizeOperationFilter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Arcus.WebApi.OpenApi.Extensions/OAuthAuthorizeOperationFilter.cs b/src/Arcus.WebApi.OpenApi.Extensions/OAuthAuthorizeOperationFilter.cs index f7473565..921e6ac0 100644 --- a/src/Arcus.WebApi.OpenApi.Extensions/OAuthAuthorizeOperationFilter.cs +++ b/src/Arcus.WebApi.OpenApi.Extensions/OAuthAuthorizeOperationFilter.cs @@ -67,7 +67,7 @@ public void Apply(Operation operation, OperationFilterContext context) bool hasAuthorize = operationHasAuthorizeAttribute - || controllerHasAuthorizeAttribute && !operationHasAllowAnonymousAttribute; + || (controllerHasAuthorizeAttribute && !operationHasAllowAnonymousAttribute); if (hasAuthorize) {