diff --git a/Directory.Build.props b/Directory.Build.props index 4a5c7bca..096a596a 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -43,7 +43,7 @@ - + diff --git a/README.md b/README.md index 8f6e6ca9..5939cb1c 100644 --- a/README.md +++ b/README.md @@ -386,6 +386,8 @@ If all path-items (operations) under a given path all have x-authentication-requ Authentication-Schemes and Authorize-Roles defined at path/controller level is taken into consideration when generating [Authorize] attributes for path-item/action/method level. +If no path-items (operations) under a given path have the x-authentication-required extension set, then no attributes will be generated for that given path/controller. If you want to force e.g [Authorize] or [AllowAnonymous], set the x-authentication-required extension to `true` or `false` respectively. + ### Example > NOTE: Tags, parameters, responses, request-bodies, schemas etc. are removed for brevity, so the references in spec below are not valid - The specification is only illustrating the various places the 3 new extension tags can be applied. diff --git a/src/Atc.CodeGeneration.CSharp/Atc.CodeGeneration.CSharp.csproj b/src/Atc.CodeGeneration.CSharp/Atc.CodeGeneration.CSharp.csproj index edd2a8bc..2e960b40 100644 --- a/src/Atc.CodeGeneration.CSharp/Atc.CodeGeneration.CSharp.csproj +++ b/src/Atc.CodeGeneration.CSharp/Atc.CodeGeneration.CSharp.csproj @@ -6,8 +6,8 @@ - - + + diff --git a/src/Atc.Rest.ApiGenerator.CLI/Atc.Rest.ApiGenerator.CLI.csproj b/src/Atc.Rest.ApiGenerator.CLI/Atc.Rest.ApiGenerator.CLI.csproj index 72c7e266..019132f2 100644 --- a/src/Atc.Rest.ApiGenerator.CLI/Atc.Rest.ApiGenerator.CLI.csproj +++ b/src/Atc.Rest.ApiGenerator.CLI/Atc.Rest.ApiGenerator.CLI.csproj @@ -13,8 +13,8 @@ - - + + diff --git a/src/Atc.Rest.ApiGenerator.Client.CSharp/ContentGenerators/ContentGeneratorClientEndpoint.cs b/src/Atc.Rest.ApiGenerator.Client.CSharp/ContentGenerators/ContentGeneratorClientEndpoint.cs index 4f28667d..dd4eaa1b 100644 --- a/src/Atc.Rest.ApiGenerator.Client.CSharp/ContentGenerators/ContentGeneratorClientEndpoint.cs +++ b/src/Atc.Rest.ApiGenerator.Client.CSharp/ContentGenerators/ContentGeneratorClientEndpoint.cs @@ -104,7 +104,7 @@ public string Generate() sb.AppendLine(8, "var responseBuilder = httpMessageFactory.FromResponse(response);"); var responseModels = parameters.ResponseModels - .AppendUnauthorizedIfNeeded(parameters.Authorization) + .AppendUnauthorizedIfNeeded(parameters.Authorization, parameters.IsAuthorizationRequiredFromPath) .AppendForbiddenIfNeeded(parameters.Authorization) .AppendBadRequestIfNeeded(parameters.ParameterName) .OrderBy(x => x.StatusCode) diff --git a/src/Atc.Rest.ApiGenerator.Client.CSharp/ContentGenerators/ContentGeneratorClientEndpointResult.cs b/src/Atc.Rest.ApiGenerator.Client.CSharp/ContentGenerators/ContentGeneratorClientEndpointResult.cs index f5082712..8c756e36 100644 --- a/src/Atc.Rest.ApiGenerator.Client.CSharp/ContentGenerators/ContentGeneratorClientEndpointResult.cs +++ b/src/Atc.Rest.ApiGenerator.Client.CSharp/ContentGenerators/ContentGeneratorClientEndpointResult.cs @@ -67,7 +67,7 @@ private void AppendContentIsStatus( StringBuilder sb) { var responseModels = parameters.ResponseModels - .AppendUnauthorizedIfNeeded(parameters.Authorization) + .AppendUnauthorizedIfNeeded(parameters.Authorization, parameters.IsAuthorizationRequiredFromPath) .AppendForbiddenIfNeeded(parameters.Authorization) .AppendBadRequestIfNeeded(parameters.HasParameterType) .OrderBy(x => x.StatusCode) @@ -202,7 +202,7 @@ private void AppendContentWithProblemDetails( StringBuilder sb) { var responseModels = parameters.ResponseModels - .AppendUnauthorizedIfNeeded(parameters.Authorization) + .AppendUnauthorizedIfNeeded(parameters.Authorization, parameters.IsAuthorizationRequiredFromPath) .AppendForbiddenIfNeeded(parameters.Authorization) .AppendBadRequestIfNeeded(parameters.HasParameterType) .OrderBy(x => x.StatusCode) @@ -301,7 +301,7 @@ private void AppendContentWithoutProblemDetails( StringBuilder sb) { var responseModels = parameters.ResponseModels - .AppendUnauthorizedIfNeeded(parameters.Authorization) + .AppendUnauthorizedIfNeeded(parameters.Authorization, parameters.IsAuthorizationRequiredFromPath) .AppendForbiddenIfNeeded(parameters.Authorization) .AppendBadRequestIfNeeded(parameters.HasParameterType) .OrderBy(x => x.StatusCode) diff --git a/src/Atc.Rest.ApiGenerator.Client.CSharp/ContentGenerators/ContentGeneratorClientEndpointResultInterface.cs b/src/Atc.Rest.ApiGenerator.Client.CSharp/ContentGenerators/ContentGeneratorClientEndpointResultInterface.cs index 74be0ad2..96c8033a 100644 --- a/src/Atc.Rest.ApiGenerator.Client.CSharp/ContentGenerators/ContentGeneratorClientEndpointResultInterface.cs +++ b/src/Atc.Rest.ApiGenerator.Client.CSharp/ContentGenerators/ContentGeneratorClientEndpointResultInterface.cs @@ -64,7 +64,7 @@ private void AppendContentIsStatus( StringBuilder sb) { var responseModels = parameters.ResponseModels - .AppendUnauthorizedIfNeeded(parameters.Authorization) + .AppendUnauthorizedIfNeeded(parameters.Authorization, parameters.IsAuthorizationRequiredFromPath) .AppendForbiddenIfNeeded(parameters.Authorization) .AppendBadRequestIfNeeded(parameters.HasParameterType) .OrderBy(x => x.StatusCode) @@ -183,7 +183,7 @@ private void AppendContentWithProblemDetails( StringBuilder sb) { var responseModels = parameters.ResponseModels - .AppendUnauthorizedIfNeeded(parameters.Authorization) + .AppendUnauthorizedIfNeeded(parameters.Authorization, parameters.IsAuthorizationRequiredFromPath) .AppendForbiddenIfNeeded(parameters.Authorization) .AppendBadRequestIfNeeded(parameters.HasParameterType) .OrderBy(x => x.StatusCode) @@ -276,7 +276,7 @@ private void AppendContentWithoutProblemDetails( StringBuilder sb) { var responseModels = parameters.ResponseModels - .AppendUnauthorizedIfNeeded(parameters.Authorization) + .AppendUnauthorizedIfNeeded(parameters.Authorization, parameters.IsAuthorizationRequiredFromPath) .AppendForbiddenIfNeeded(parameters.Authorization) .AppendBadRequestIfNeeded(parameters.HasParameterType) .OrderBy(x => x.StatusCode) diff --git a/src/Atc.Rest.ApiGenerator.CodingRules/Atc.Rest.ApiGenerator.CodingRules.csproj b/src/Atc.Rest.ApiGenerator.CodingRules/Atc.Rest.ApiGenerator.CodingRules.csproj index 8bb28475..656e87b9 100644 --- a/src/Atc.Rest.ApiGenerator.CodingRules/Atc.Rest.ApiGenerator.CodingRules.csproj +++ b/src/Atc.Rest.ApiGenerator.CodingRules/Atc.Rest.ApiGenerator.CodingRules.csproj @@ -6,7 +6,7 @@ - + diff --git a/src/Atc.Rest.ApiGenerator.Contracts/Atc.Rest.ApiGenerator.Contracts.csproj b/src/Atc.Rest.ApiGenerator.Contracts/Atc.Rest.ApiGenerator.Contracts.csproj index e599c1e5..ec4c2de8 100644 --- a/src/Atc.Rest.ApiGenerator.Contracts/Atc.Rest.ApiGenerator.Contracts.csproj +++ b/src/Atc.Rest.ApiGenerator.Contracts/Atc.Rest.ApiGenerator.Contracts.csproj @@ -6,8 +6,8 @@ - - + + diff --git a/src/Atc.Rest.ApiGenerator.Contracts/ContentGeneratorsParameters/Client/ContentGeneratorClientEndpointParameters.cs b/src/Atc.Rest.ApiGenerator.Contracts/ContentGeneratorsParameters/Client/ContentGeneratorClientEndpointParameters.cs index 9d6b7fa5..9dce8098 100644 --- a/src/Atc.Rest.ApiGenerator.Contracts/ContentGeneratorsParameters/Client/ContentGeneratorClientEndpointParameters.cs +++ b/src/Atc.Rest.ApiGenerator.Contracts/ContentGeneratorsParameters/Client/ContentGeneratorClientEndpointParameters.cs @@ -12,6 +12,7 @@ public record ContentGeneratorClientEndpointParameters( string ResultName, string? ParameterName, ApiAuthorizeModel? Authorization, + bool IsAuthorizationRequiredFromPath, IList ResponseModels, IList? Parameters); diff --git a/src/Atc.Rest.ApiGenerator.Contracts/ContentGeneratorsParameters/Client/ContentGeneratorClientEndpointResultInterfaceParameters.cs b/src/Atc.Rest.ApiGenerator.Contracts/ContentGeneratorsParameters/Client/ContentGeneratorClientEndpointResultInterfaceParameters.cs index 75fdd666..20417901 100644 --- a/src/Atc.Rest.ApiGenerator.Contracts/ContentGeneratorsParameters/Client/ContentGeneratorClientEndpointResultInterfaceParameters.cs +++ b/src/Atc.Rest.ApiGenerator.Contracts/ContentGeneratorsParameters/Client/ContentGeneratorClientEndpointResultInterfaceParameters.cs @@ -8,6 +8,7 @@ public record ContentGeneratorClientEndpointResultInterfaceParameters( string InheritInterfaceName, bool HasParameterType, ApiAuthorizeModel? Authorization, + bool IsAuthorizationRequiredFromPath, IList ResponseModels); public record ContentGeneratorClientEndpointResultInterfaceParametersParameters( diff --git a/src/Atc.Rest.ApiGenerator.Contracts/ContentGeneratorsParameters/Client/ContentGeneratorClientEndpointResultParameters.cs b/src/Atc.Rest.ApiGenerator.Contracts/ContentGeneratorsParameters/Client/ContentGeneratorClientEndpointResultParameters.cs index 68323ef1..420804b9 100644 --- a/src/Atc.Rest.ApiGenerator.Contracts/ContentGeneratorsParameters/Client/ContentGeneratorClientEndpointResultParameters.cs +++ b/src/Atc.Rest.ApiGenerator.Contracts/ContentGeneratorsParameters/Client/ContentGeneratorClientEndpointResultParameters.cs @@ -9,6 +9,7 @@ public record ContentGeneratorClientEndpointResultParameters( string InheritClassName, bool HasParameterType, ApiAuthorizeModel? Authorization, + bool IsAuthorizationRequiredFromPath, IList ResponseModels, IList? Parameters); diff --git a/src/Atc.Rest.ApiGenerator.Contracts/Extensions/ApiOperationResponseModelExtensions.cs b/src/Atc.Rest.ApiGenerator.Contracts/Extensions/ApiOperationResponseModelExtensions.cs index 1e729ab3..53eb66dc 100644 --- a/src/Atc.Rest.ApiGenerator.Contracts/Extensions/ApiOperationResponseModelExtensions.cs +++ b/src/Atc.Rest.ApiGenerator.Contracts/Extensions/ApiOperationResponseModelExtensions.cs @@ -36,17 +36,27 @@ public static class ApiOperationResponseModelExtensions public static IEnumerable AppendUnauthorizedIfNeeded( this IEnumerable responseModels, - ApiAuthorizeModel? authorization) + ApiAuthorizeModel? authorization, + bool isRequiredFromPath) { - if (authorization is null) + if (authorization is null && + !isRequiredFromPath) + { + return responseModels; + } + + if (authorization is not null && + authorization.UseAllowAnonymous) { return responseModels; } var models = responseModels.ToList(); + var useAllowAnonymous = authorization?.UseAllowAnonymous ?? false; + if (models.TrueForAll(x => x.StatusCode != HttpStatusCode.Unauthorized) && - !authorization.UseAllowAnonymous) + (isRequiredFromPath || !useAllowAnonymous)) { models.Add( new ApiOperationResponseModel( diff --git a/src/Atc.Rest.ApiGenerator.Framework.Minimal/Atc.Rest.ApiGenerator.Framework.Minimal.csproj b/src/Atc.Rest.ApiGenerator.Framework.Minimal/Atc.Rest.ApiGenerator.Framework.Minimal.csproj index 52020b02..29a7b39b 100644 --- a/src/Atc.Rest.ApiGenerator.Framework.Minimal/Atc.Rest.ApiGenerator.Framework.Minimal.csproj +++ b/src/Atc.Rest.ApiGenerator.Framework.Minimal/Atc.Rest.ApiGenerator.Framework.Minimal.csproj @@ -6,7 +6,7 @@ - + diff --git a/src/Atc.Rest.ApiGenerator.Framework.Minimal/ContentGenerators/ContentGeneratorServerEndpoints.cs b/src/Atc.Rest.ApiGenerator.Framework.Minimal/ContentGenerators/ContentGeneratorServerEndpoints.cs index 746e31a9..0907d845 100644 --- a/src/Atc.Rest.ApiGenerator.Framework.Minimal/ContentGenerators/ContentGeneratorServerEndpoints.cs +++ b/src/Atc.Rest.ApiGenerator.Framework.Minimal/ContentGenerators/ContentGeneratorServerEndpoints.cs @@ -186,7 +186,7 @@ private static void AppendProducesWithProblemDetails( ContentGeneratorServerEndpointMethodParameters item) { var responseModels = item.ResponseModels - .AppendUnauthorizedIfNeeded(item.Authorization) + .AppendUnauthorizedIfNeeded(item.Authorization, item.IsAuthorizationRequiredFromPath) .AppendForbiddenIfNeeded(item.Authorization) .AppendBadRequestIfNeeded(item.ParameterTypeName) .OrderBy(x => x.StatusCode) @@ -288,7 +288,7 @@ private static void AppendProducesWithoutProblemDetails( ContentGeneratorServerEndpointMethodParameters item) { var responseModels = item.ResponseModels - .AppendUnauthorizedIfNeeded(item.Authorization) + .AppendUnauthorizedIfNeeded(item.Authorization, item.IsAuthorizationRequiredFromPath) .AppendForbiddenIfNeeded(item.Authorization) .AppendBadRequestIfNeeded(item.ParameterTypeName) .OrderBy(x => x.StatusCode) diff --git a/src/Atc.Rest.ApiGenerator.Framework.Mvc/Atc.Rest.ApiGenerator.Framework.Mvc.csproj b/src/Atc.Rest.ApiGenerator.Framework.Mvc/Atc.Rest.ApiGenerator.Framework.Mvc.csproj index c5961ef7..165aa909 100644 --- a/src/Atc.Rest.ApiGenerator.Framework.Mvc/Atc.Rest.ApiGenerator.Framework.Mvc.csproj +++ b/src/Atc.Rest.ApiGenerator.Framework.Mvc/Atc.Rest.ApiGenerator.Framework.Mvc.csproj @@ -6,8 +6,8 @@ - - + + diff --git a/src/Atc.Rest.ApiGenerator.Framework.Mvc/ContentGenerators/ContentGeneratorServerController.cs b/src/Atc.Rest.ApiGenerator.Framework.Mvc/ContentGenerators/ContentGeneratorServerController.cs index 576d3b33..91891795 100644 --- a/src/Atc.Rest.ApiGenerator.Framework.Mvc/ContentGenerators/ContentGeneratorServerController.cs +++ b/src/Atc.Rest.ApiGenerator.Framework.Mvc/ContentGenerators/ContentGeneratorServerController.cs @@ -174,7 +174,7 @@ private static void AppendProducesWithProblemDetails( ContentGeneratorServerEndpointMethodParameters item) { var responseModels = item.ResponseModels - .AppendUnauthorizedIfNeeded(item.Authorization) + .AppendUnauthorizedIfNeeded(item.Authorization, item.IsAuthorizationRequiredFromPath) .AppendForbiddenIfNeeded(item.Authorization) .AppendBadRequestIfNeeded(item.ParameterTypeName) .OrderBy(x => x.StatusCode) @@ -265,7 +265,7 @@ private static void AppendProducesWithoutProblemDetails( ContentGeneratorServerEndpointMethodParameters item) { var responseModels = item.ResponseModels - .AppendUnauthorizedIfNeeded(item.Authorization) + .AppendUnauthorizedIfNeeded(item.Authorization, item.IsAuthorizationRequiredFromPath) .AppendForbiddenIfNeeded(item.Authorization) .AppendBadRequestIfNeeded(item.ParameterTypeName) .OrderBy(x => x.StatusCode) diff --git a/src/Atc.Rest.ApiGenerator.Framework/Atc.Rest.ApiGenerator.Framework.csproj b/src/Atc.Rest.ApiGenerator.Framework/Atc.Rest.ApiGenerator.Framework.csproj index 491c02f8..113ad074 100644 --- a/src/Atc.Rest.ApiGenerator.Framework/Atc.Rest.ApiGenerator.Framework.csproj +++ b/src/Atc.Rest.ApiGenerator.Framework/Atc.Rest.ApiGenerator.Framework.csproj @@ -26,9 +26,9 @@ - - - + + + diff --git a/src/Atc.Rest.ApiGenerator.Framework/ContentGeneratorsParameters/Server/ContentGeneratorServerEndpointMethodParameters.cs b/src/Atc.Rest.ApiGenerator.Framework/ContentGeneratorsParameters/Server/ContentGeneratorServerEndpointMethodParameters.cs index 9c9e9811..19a689c2 100644 --- a/src/Atc.Rest.ApiGenerator.Framework/ContentGeneratorsParameters/Server/ContentGeneratorServerEndpointMethodParameters.cs +++ b/src/Atc.Rest.ApiGenerator.Framework/ContentGeneratorsParameters/Server/ContentGeneratorServerEndpointMethodParameters.cs @@ -11,4 +11,5 @@ public record ContentGeneratorServerEndpointMethodParameters( long? MultipartBodyLengthLimit, string ResultName, ApiAuthorizeModel? Authorization, + bool IsAuthorizationRequiredFromPath, IEnumerable ResponseModels); \ No newline at end of file diff --git a/src/Atc.Rest.ApiGenerator.Framework/Factories/Parameters/Client/ContentGeneratorClientEndpointParametersFactory.cs b/src/Atc.Rest.ApiGenerator.Framework/Factories/Parameters/Client/ContentGeneratorClientEndpointParametersFactory.cs index 089c0611..0dd1d145 100644 --- a/src/Atc.Rest.ApiGenerator.Framework/Factories/Parameters/Client/ContentGeneratorClientEndpointParametersFactory.cs +++ b/src/Atc.Rest.ApiGenerator.Framework/Factories/Parameters/Client/ContentGeneratorClientEndpointParametersFactory.cs @@ -23,6 +23,7 @@ public static ContentGeneratorClientEndpointParameters Create( var modelNamespace = $"{projectName}.{ContentGeneratorConstants.Contracts}.{apiGroupName}"; var operationName = openApiOperation.GetOperationName(); + var controllerAuthorization = openApiPath.ExtractApiPathAuthorization(); var endpointAuthorization = openApiOperation.ExtractApiOperationAuthorization(openApiPath); var responseModels = openApiOperation.ExtractApiOperationResponseModels(modelNamespace).ToList(); @@ -41,6 +42,7 @@ public static ContentGeneratorClientEndpointParameters Create( ResultName: $"{operationName}{ContentGeneratorConstants.EndpointResult}", ParameterName: $"{operationName}{ContentGeneratorConstants.Parameters}", Authorization: endpointAuthorization, + IsAuthorizationRequiredFromPath: controllerAuthorization is not null, ResponseModels: responseModels, parameters); } @@ -57,6 +59,7 @@ public static ContentGeneratorClientEndpointParameters Create( ResultName: $"{operationName}{ContentGeneratorConstants.EndpointResult}", ParameterName: null, Authorization: endpointAuthorization, + IsAuthorizationRequiredFromPath: controllerAuthorization is not null, ResponseModels: responseModels, Parameters: null); } diff --git a/src/Atc.Rest.ApiGenerator.Framework/Factories/Parameters/Client/ContentGeneratorClientEndpointResultInterfaceParametersFactory.cs b/src/Atc.Rest.ApiGenerator.Framework/Factories/Parameters/Client/ContentGeneratorClientEndpointResultInterfaceParametersFactory.cs index 73b48bc1..8acba3a7 100644 --- a/src/Atc.Rest.ApiGenerator.Framework/Factories/Parameters/Client/ContentGeneratorClientEndpointResultInterfaceParametersFactory.cs +++ b/src/Atc.Rest.ApiGenerator.Framework/Factories/Parameters/Client/ContentGeneratorClientEndpointResultInterfaceParametersFactory.cs @@ -20,6 +20,7 @@ public static ContentGeneratorClientEndpointResultInterfaceParameters Create( var modelNamespace = $"{projectName}.{ContentGeneratorConstants.Contracts}.{apiGroupName}"; var operationName = openApiOperation.GetOperationName(); + var controllerAuthorization = openApiPath.ExtractApiPathAuthorization(); var endpointAuthorization = openApiOperation.ExtractApiOperationAuthorization(openApiPath); var responseModels = openApiOperation.ExtractApiOperationResponseModels(modelNamespace).ToList(); var hasParameterType = openApiPath.HasParameters() || openApiOperation.HasParametersOrRequestBody(); @@ -32,6 +33,7 @@ public static ContentGeneratorClientEndpointResultInterfaceParameters Create( InheritInterfaceName: "IEndpointResponse", HasParameterType: hasParameterType, Authorization: endpointAuthorization, + IsAuthorizationRequiredFromPath: controllerAuthorization is not null, ResponseModels: responseModels); } diff --git a/src/Atc.Rest.ApiGenerator.Framework/Factories/Parameters/Client/ContentGeneratorClientEndpointResultParametersFactory.cs b/src/Atc.Rest.ApiGenerator.Framework/Factories/Parameters/Client/ContentGeneratorClientEndpointResultParametersFactory.cs index 6c18f087..e25c85e6 100644 --- a/src/Atc.Rest.ApiGenerator.Framework/Factories/Parameters/Client/ContentGeneratorClientEndpointResultParametersFactory.cs +++ b/src/Atc.Rest.ApiGenerator.Framework/Factories/Parameters/Client/ContentGeneratorClientEndpointResultParametersFactory.cs @@ -20,6 +20,7 @@ public static ContentGeneratorClientEndpointResultParameters Create( var modelNamespace = $"{projectName}.{ContentGeneratorConstants.Contracts}.{apiGroupName}"; var operationName = openApiOperation.GetOperationName(); + var controllerAuthorization = openApiPath.ExtractApiPathAuthorization(); var endpointAuthorization = openApiOperation.ExtractApiOperationAuthorization(openApiPath); var responseModels = openApiOperation.ExtractApiOperationResponseModels(modelNamespace).ToList(); var hasParameterType = openApiPath.HasParameters() || openApiOperation.HasParametersOrRequestBody(); @@ -35,6 +36,7 @@ public static ContentGeneratorClientEndpointResultParameters Create( InheritClassName: ContentGeneratorConstants.EndpointResponse, HasParameterType: hasParameterType, Authorization: endpointAuthorization, + IsAuthorizationRequiredFromPath: controllerAuthorization is not null, ResponseModels: responseModels, parameters); } @@ -48,6 +50,7 @@ public static ContentGeneratorClientEndpointResultParameters Create( InheritClassName: ContentGeneratorConstants.EndpointResponse, HasParameterType: hasParameterType, Authorization: endpointAuthorization, + IsAuthorizationRequiredFromPath: controllerAuthorization is not null, ResponseModels: responseModels, Parameters: null); } diff --git a/src/Atc.Rest.ApiGenerator.Framework/Factories/Server/ContentGeneratorServerEndpointParametersFactory.cs b/src/Atc.Rest.ApiGenerator.Framework/Factories/Server/ContentGeneratorServerEndpointParametersFactory.cs index cf0aab3b..3c1a63dd 100644 --- a/src/Atc.Rest.ApiGenerator.Framework/Factories/Server/ContentGeneratorServerEndpointParametersFactory.cs +++ b/src/Atc.Rest.ApiGenerator.Framework/Factories/Server/ContentGeneratorServerEndpointParametersFactory.cs @@ -38,7 +38,8 @@ public static ContentGeneratorServerEndpointParameters Create( MultipartBodyLengthLimit: GetMultipartBodyLengthLimit(apiOperation.Value), ResultName: $"{operationName}{ContentGeneratorConstants.Result}", ResponseModels: responseModels, - Authorization: endpointAuthorization)); + Authorization: endpointAuthorization, + IsAuthorizationRequiredFromPath: controllerAuthorization is not null)); } } diff --git a/src/Atc.Rest.ApiGenerator.Nuget/Atc.Rest.ApiGenerator.Nuget.csproj b/src/Atc.Rest.ApiGenerator.Nuget/Atc.Rest.ApiGenerator.Nuget.csproj index 8bb28475..656e87b9 100644 --- a/src/Atc.Rest.ApiGenerator.Nuget/Atc.Rest.ApiGenerator.Nuget.csproj +++ b/src/Atc.Rest.ApiGenerator.Nuget/Atc.Rest.ApiGenerator.Nuget.csproj @@ -6,7 +6,7 @@ - + diff --git a/src/Atc.Rest.ApiGenerator.OpenApi/Atc.Rest.ApiGenerator.OpenApi.csproj b/src/Atc.Rest.ApiGenerator.OpenApi/Atc.Rest.ApiGenerator.OpenApi.csproj index c02c10b3..addd7968 100644 --- a/src/Atc.Rest.ApiGenerator.OpenApi/Atc.Rest.ApiGenerator.OpenApi.csproj +++ b/src/Atc.Rest.ApiGenerator.OpenApi/Atc.Rest.ApiGenerator.OpenApi.csproj @@ -6,9 +6,9 @@ - - - + + + diff --git a/src/Atc.Rest.ApiGenerator.OpenApi/Extensions/OpenApiOperationExtensions.cs b/src/Atc.Rest.ApiGenerator.OpenApi/Extensions/OpenApiOperationExtensions.cs index a850fbe8..dc94c739 100644 --- a/src/Atc.Rest.ApiGenerator.OpenApi/Extensions/OpenApiOperationExtensions.cs +++ b/src/Atc.Rest.ApiGenerator.OpenApi/Extensions/OpenApiOperationExtensions.cs @@ -66,15 +66,19 @@ public static CodeDocumentationTags ExtractDocumentationTags( this OpenApiOperation apiOperation, OpenApiPathItem apiPath) { + var authorizationRequiredForPath = apiPath.Extensions.ExtractAuthenticationRequired(); var authorizationRolesForPath = apiPath.Extensions.ExtractAuthorizationRoles(); var authenticationSchemesForPath = apiPath.Extensions.ExtractAuthenticationSchemes(); + var authorizationRequiredForOperation = apiOperation.Extensions.ExtractAuthenticationRequired(); var authorizationRolesForOperation = apiOperation.Extensions.ExtractAuthorizationRoles(); var authenticationSchemesForOperation = apiOperation.Extensions.ExtractAuthenticationSchemes(); - if (authorizationRolesForPath is null && - authenticationSchemesForPath is null && - authorizationRolesForOperation is null && - authenticationSchemesForOperation is null) + if (authorizationRequiredForPath.HasNoValueOrFalse() && + authorizationRolesForPath.Count == 0 && + authenticationSchemesForPath.Count == 0 && + authorizationRequiredForOperation.HasNoValue() && + authorizationRolesForOperation.Count == 0 && + authenticationSchemesForOperation.Count == 0) { return null; } @@ -82,7 +86,7 @@ authorizationRolesForOperation is null && IList? authorizationRoles = null; IList? authenticationSchemes = null; - if (authorizationRolesForPath is not null) + if (authorizationRolesForPath.Count > 0) { authorizationRoles = authorizationRolesForPath .Distinct(StringComparer.Ordinal) @@ -90,7 +94,7 @@ authorizationRolesForOperation is null && .ToList(); } - if (authorizationRolesForOperation is not null) + if (authorizationRolesForOperation.Count > 0) { authorizationRoles = authorizationRoles is null ? authorizationRolesForOperation @@ -104,7 +108,7 @@ authorizationRolesForOperation is null && .ToList(); } - if (authenticationSchemesForPath is not null) + if (authenticationSchemesForPath.Count > 0) { authenticationSchemes = authenticationSchemesForPath .Distinct(StringComparer.Ordinal) @@ -112,7 +116,7 @@ authorizationRolesForOperation is null && .ToList(); } - if (authorizationRolesForOperation is not null) + if (authenticationSchemesForOperation.Count > 0) { authenticationSchemes = authenticationSchemes is null ? authenticationSchemesForOperation @@ -126,10 +130,24 @@ authorizationRolesForOperation is null && .ToList(); } + var useAllowAnonymous = authorizationRequiredForPath.HasValueAndFalse() && + authorizationRequiredForOperation.HasValueAndFalse(); + + if (authorizationRoles?.Count > 0 || + authenticationSchemes?.Count > 0) + { + useAllowAnonymous = false; + } + + if (authorizationRequiredForOperation.HasValueAndFalse()) + { + useAllowAnonymous = true; + } + return new ApiAuthorizeModel( Roles: authorizationRoles, AuthenticationSchemes: authenticationSchemes, - UseAllowAnonymous: false); + UseAllowAnonymous: useAllowAnonymous); } public static IEnumerable ExtractApiOperationResponseModels( diff --git a/src/Atc.Rest.ApiGenerator.OpenApi/Extensions/OpenApiPathsExtensions.cs b/src/Atc.Rest.ApiGenerator.OpenApi/Extensions/OpenApiPathItemExtensions.cs similarity index 83% rename from src/Atc.Rest.ApiGenerator.OpenApi/Extensions/OpenApiPathsExtensions.cs rename to src/Atc.Rest.ApiGenerator.OpenApi/Extensions/OpenApiPathItemExtensions.cs index e98406ff..0ac111e8 100644 --- a/src/Atc.Rest.ApiGenerator.OpenApi/Extensions/OpenApiPathsExtensions.cs +++ b/src/Atc.Rest.ApiGenerator.OpenApi/Extensions/OpenApiPathItemExtensions.cs @@ -1,6 +1,6 @@ namespace Atc.Rest.ApiGenerator.OpenApi.Extensions; -public static class OpenApiPathsExtensions +public static class OpenApiPathItemExtensions { [SuppressMessage("Naming", "CA1867:Use 'string.Method(char)' instead of 'string.Method(string)' for string with single char", Justification = "OK.")] public static string GetApiGroupName( @@ -52,11 +52,18 @@ public static string GetApiGroupName( } } - var useAllowAnonymous = apiPath.Operations.Keys.Count != data.OfType().Count(); + var authenticationRequiredForPath = apiPath.Extensions.ExtractAuthenticationRequired(); + + if ((authorizationRoles is null || authorizationRoles.Count == 0) && + (authenticationSchemes is null || authenticationSchemes.Count == 0) && + authenticationRequiredForPath.HasValueAndFalse()) + { + return null; + } return new ApiAuthorizeModel( Roles: authorizationRoles, AuthenticationSchemes: authenticationSchemes, - UseAllowAnonymous: useAllowAnonymous); + UseAllowAnonymous: false); } } \ No newline at end of file diff --git a/src/Atc.Rest.ApiGenerator/Atc.Rest.ApiGenerator.csproj b/src/Atc.Rest.ApiGenerator/Atc.Rest.ApiGenerator.csproj index cc8dc222..18fb0d69 100644 --- a/src/Atc.Rest.ApiGenerator/Atc.Rest.ApiGenerator.csproj +++ b/src/Atc.Rest.ApiGenerator/Atc.Rest.ApiGenerator.csproj @@ -8,11 +8,11 @@ - - - - - + + + + + diff --git a/test/Atc.Rest.ApiGenerator.CLI.Tests/Atc.Rest.ApiGenerator.CLI.Tests.csproj b/test/Atc.Rest.ApiGenerator.CLI.Tests/Atc.Rest.ApiGenerator.CLI.Tests.csproj index c0bf2ff3..9f19d7f5 100644 --- a/test/Atc.Rest.ApiGenerator.CLI.Tests/Atc.Rest.ApiGenerator.CLI.Tests.csproj +++ b/test/Atc.Rest.ApiGenerator.CLI.Tests/Atc.Rest.ApiGenerator.CLI.Tests.csproj @@ -51,11 +51,11 @@ - + - - + + diff --git a/test/Atc.Rest.ApiGenerator.CLI.Tests/Scenarios/DemoSample/VerifyClient/WOPD/src/DemoSample.ApiClient.Generated/Endpoints/Orders/GetOrderByIdEndpoint.verified.cs b/test/Atc.Rest.ApiGenerator.CLI.Tests/Scenarios/DemoSample/VerifyClient/WOPD/src/DemoSample.ApiClient.Generated/Endpoints/Orders/GetOrderByIdEndpoint.verified.cs index 5f670a57..4923958e 100644 --- a/test/Atc.Rest.ApiGenerator.CLI.Tests/Scenarios/DemoSample/VerifyClient/WOPD/src/DemoSample.ApiClient.Generated/Endpoints/Orders/GetOrderByIdEndpoint.verified.cs +++ b/test/Atc.Rest.ApiGenerator.CLI.Tests/Scenarios/DemoSample/VerifyClient/WOPD/src/DemoSample.ApiClient.Generated/Endpoints/Orders/GetOrderByIdEndpoint.verified.cs @@ -1,4 +1,4 @@ -//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ // This code was auto-generated by ApiGenerator x.x.x.x. // // Changes to this file may cause incorrect behavior and will be lost if @@ -42,9 +42,7 @@ public async Task ExecuteAsync( var responseBuilder = httpMessageFactory.FromResponse(response); responseBuilder.AddSuccessResponse(HttpStatusCode.OK); responseBuilder.AddErrorResponse(HttpStatusCode.BadRequest); - responseBuilder.AddErrorResponse(HttpStatusCode.Unauthorized); - responseBuilder.AddErrorResponse(HttpStatusCode.Forbidden); responseBuilder.AddErrorResponse(HttpStatusCode.NotFound); return await responseBuilder.BuildResponseAsync(x => new GetOrderByIdEndpointResult(x), cancellationToken); } -} +} \ No newline at end of file diff --git a/test/Atc.Rest.ApiGenerator.CLI.Tests/Scenarios/DemoSample/VerifyClient/WOPD/src/DemoSample.ApiClient.Generated/Endpoints/Orders/GetOrderByIdEndpointResult.verified.cs b/test/Atc.Rest.ApiGenerator.CLI.Tests/Scenarios/DemoSample/VerifyClient/WOPD/src/DemoSample.ApiClient.Generated/Endpoints/Orders/GetOrderByIdEndpointResult.verified.cs index 9beab004..d2dc186c 100644 --- a/test/Atc.Rest.ApiGenerator.CLI.Tests/Scenarios/DemoSample/VerifyClient/WOPD/src/DemoSample.ApiClient.Generated/Endpoints/Orders/GetOrderByIdEndpointResult.verified.cs +++ b/test/Atc.Rest.ApiGenerator.CLI.Tests/Scenarios/DemoSample/VerifyClient/WOPD/src/DemoSample.ApiClient.Generated/Endpoints/Orders/GetOrderByIdEndpointResult.verified.cs @@ -1,4 +1,4 @@ -//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ // This code was auto-generated by ApiGenerator x.x.x.x. // // Changes to this file may cause incorrect behavior and will be lost if @@ -25,12 +25,6 @@ public bool IsOk public bool IsBadRequest => StatusCode == HttpStatusCode.BadRequest; - public bool IsUnauthorized - => StatusCode == HttpStatusCode.Unauthorized; - - public bool IsForbidden - => StatusCode == HttpStatusCode.Forbidden; - public bool IsNotFound => StatusCode == HttpStatusCode.NotFound; @@ -44,18 +38,8 @@ public string? BadRequestContent ? result : throw new InvalidOperationException("Content is not the expected type - please use the IsBadRequest property first."); - public string? UnauthorizedContent - => IsUnauthorized && ContentObject is string result - ? result - : throw new InvalidOperationException("Content is not the expected type - please use the IsUnauthorized property first."); - - public string? ForbiddenContent - => IsForbidden && ContentObject is string result - ? result - : throw new InvalidOperationException("Content is not the expected type - please use the IsForbidden property first."); - public string? NotFoundContent => IsNotFound && ContentObject is string result ? result : throw new InvalidOperationException("Content is not the expected type - please use the IsNotFound property first."); -} +} \ No newline at end of file diff --git a/test/Atc.Rest.ApiGenerator.CLI.Tests/Scenarios/DemoSample/VerifyClient/WOPD/src/DemoSample.ApiClient.Generated/Endpoints/Orders/Interfaces/IGetOrderByIdEndpointResult.verified.cs b/test/Atc.Rest.ApiGenerator.CLI.Tests/Scenarios/DemoSample/VerifyClient/WOPD/src/DemoSample.ApiClient.Generated/Endpoints/Orders/Interfaces/IGetOrderByIdEndpointResult.verified.cs index 0e51bffe..1561cc7a 100644 --- a/test/Atc.Rest.ApiGenerator.CLI.Tests/Scenarios/DemoSample/VerifyClient/WOPD/src/DemoSample.ApiClient.Generated/Endpoints/Orders/Interfaces/IGetOrderByIdEndpointResult.verified.cs +++ b/test/Atc.Rest.ApiGenerator.CLI.Tests/Scenarios/DemoSample/VerifyClient/WOPD/src/DemoSample.ApiClient.Generated/Endpoints/Orders/Interfaces/IGetOrderByIdEndpointResult.verified.cs @@ -1,4 +1,4 @@ -//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ // This code was auto-generated by ApiGenerator x.x.x.x. // // Changes to this file may cause incorrect behavior and will be lost if @@ -19,19 +19,11 @@ public interface IGetOrderByIdEndpointResult : IEndpointResponse bool IsBadRequest { get; } - bool IsUnauthorized { get; } - - bool IsForbidden { get; } - bool IsNotFound { get; } Order OkContent { get; } string? BadRequestContent { get; } - string? UnauthorizedContent { get; } - - string? ForbiddenContent { get; } - string? NotFoundContent { get; } -} +} \ No newline at end of file diff --git a/test/Atc.Rest.ApiGenerator.CLI.Tests/Scenarios/DemoSample/VerifyClient/WPD/src/DemoSample.ApiClient.Generated/Endpoints/Orders/GetOrderByIdEndpoint.verified.cs b/test/Atc.Rest.ApiGenerator.CLI.Tests/Scenarios/DemoSample/VerifyClient/WPD/src/DemoSample.ApiClient.Generated/Endpoints/Orders/GetOrderByIdEndpoint.verified.cs index 8b4900c1..b3044d3f 100644 --- a/test/Atc.Rest.ApiGenerator.CLI.Tests/Scenarios/DemoSample/VerifyClient/WPD/src/DemoSample.ApiClient.Generated/Endpoints/Orders/GetOrderByIdEndpoint.verified.cs +++ b/test/Atc.Rest.ApiGenerator.CLI.Tests/Scenarios/DemoSample/VerifyClient/WPD/src/DemoSample.ApiClient.Generated/Endpoints/Orders/GetOrderByIdEndpoint.verified.cs @@ -1,4 +1,4 @@ -//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ // This code was auto-generated by ApiGenerator x.x.x.x. // // Changes to this file may cause incorrect behavior and will be lost if @@ -42,9 +42,7 @@ public async Task ExecuteAsync( var responseBuilder = httpMessageFactory.FromResponse(response); responseBuilder.AddSuccessResponse(HttpStatusCode.OK); responseBuilder.AddErrorResponse(HttpStatusCode.BadRequest); - responseBuilder.AddErrorResponse(HttpStatusCode.Unauthorized); - responseBuilder.AddErrorResponse(HttpStatusCode.Forbidden); responseBuilder.AddErrorResponse(HttpStatusCode.NotFound); return await responseBuilder.BuildResponseAsync(x => new GetOrderByIdEndpointResult(x), cancellationToken); } -} +} \ No newline at end of file diff --git a/test/Atc.Rest.ApiGenerator.CLI.Tests/Scenarios/DemoSample/VerifyClient/WPD/src/DemoSample.ApiClient.Generated/Endpoints/Orders/GetOrderByIdEndpointResult.verified.cs b/test/Atc.Rest.ApiGenerator.CLI.Tests/Scenarios/DemoSample/VerifyClient/WPD/src/DemoSample.ApiClient.Generated/Endpoints/Orders/GetOrderByIdEndpointResult.verified.cs index d37d38e0..57a490d8 100644 --- a/test/Atc.Rest.ApiGenerator.CLI.Tests/Scenarios/DemoSample/VerifyClient/WPD/src/DemoSample.ApiClient.Generated/Endpoints/Orders/GetOrderByIdEndpointResult.verified.cs +++ b/test/Atc.Rest.ApiGenerator.CLI.Tests/Scenarios/DemoSample/VerifyClient/WPD/src/DemoSample.ApiClient.Generated/Endpoints/Orders/GetOrderByIdEndpointResult.verified.cs @@ -1,4 +1,4 @@ -//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ // This code was auto-generated by ApiGenerator x.x.x.x. // // Changes to this file may cause incorrect behavior and will be lost if @@ -25,12 +25,6 @@ public bool IsOk public bool IsBadRequest => StatusCode == HttpStatusCode.BadRequest; - public bool IsUnauthorized - => StatusCode == HttpStatusCode.Unauthorized; - - public bool IsForbidden - => StatusCode == HttpStatusCode.Forbidden; - public bool IsNotFound => StatusCode == HttpStatusCode.NotFound; @@ -44,18 +38,8 @@ public ValidationProblemDetails BadRequestContent ? result : throw new InvalidOperationException("Content is not the expected type - please use the IsBadRequest property first."); - public ProblemDetails UnauthorizedContent - => IsUnauthorized && ContentObject is ProblemDetails result - ? result - : throw new InvalidOperationException("Content is not the expected type - please use the IsUnauthorized property first."); - - public ProblemDetails ForbiddenContent - => IsForbidden && ContentObject is ProblemDetails result - ? result - : throw new InvalidOperationException("Content is not the expected type - please use the IsForbidden property first."); - public ProblemDetails NotFoundContent => IsNotFound && ContentObject is ProblemDetails result ? result : throw new InvalidOperationException("Content is not the expected type - please use the IsNotFound property first."); -} +} \ No newline at end of file diff --git a/test/Atc.Rest.ApiGenerator.CLI.Tests/Scenarios/DemoSample/VerifyClient/WPD/src/DemoSample.ApiClient.Generated/Endpoints/Orders/Interfaces/IGetOrderByIdEndpointResult.verified.cs b/test/Atc.Rest.ApiGenerator.CLI.Tests/Scenarios/DemoSample/VerifyClient/WPD/src/DemoSample.ApiClient.Generated/Endpoints/Orders/Interfaces/IGetOrderByIdEndpointResult.verified.cs index 2a0e1e44..a72ed6c7 100644 --- a/test/Atc.Rest.ApiGenerator.CLI.Tests/Scenarios/DemoSample/VerifyClient/WPD/src/DemoSample.ApiClient.Generated/Endpoints/Orders/Interfaces/IGetOrderByIdEndpointResult.verified.cs +++ b/test/Atc.Rest.ApiGenerator.CLI.Tests/Scenarios/DemoSample/VerifyClient/WPD/src/DemoSample.ApiClient.Generated/Endpoints/Orders/Interfaces/IGetOrderByIdEndpointResult.verified.cs @@ -1,4 +1,4 @@ -//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ // This code was auto-generated by ApiGenerator x.x.x.x. // // Changes to this file may cause incorrect behavior and will be lost if @@ -19,19 +19,11 @@ public interface IGetOrderByIdEndpointResult : IEndpointResponse bool IsBadRequest { get; } - bool IsUnauthorized { get; } - - bool IsForbidden { get; } - bool IsNotFound { get; } Order OkContent { get; } ValidationProblemDetails BadRequestContent { get; } - ProblemDetails UnauthorizedContent { get; } - - ProblemDetails ForbiddenContent { get; } - ProblemDetails NotFoundContent { get; } -} +} \ No newline at end of file diff --git a/test/Atc.Rest.ApiGenerator.CLI.Tests/Scenarios/DemoSample/VerifyServerAll/Mvc_WOPD/src/DemoSample.Api.Generated/Endpoints/OrdersController.verified.cs b/test/Atc.Rest.ApiGenerator.CLI.Tests/Scenarios/DemoSample/VerifyServerAll/Mvc_WOPD/src/DemoSample.Api.Generated/Endpoints/OrdersController.verified.cs index 4ba25664..1e771363 100644 --- a/test/Atc.Rest.ApiGenerator.CLI.Tests/Scenarios/DemoSample/VerifyServerAll/Mvc_WOPD/src/DemoSample.Api.Generated/Endpoints/OrdersController.verified.cs +++ b/test/Atc.Rest.ApiGenerator.CLI.Tests/Scenarios/DemoSample/VerifyServerAll/Mvc_WOPD/src/DemoSample.Api.Generated/Endpoints/OrdersController.verified.cs @@ -1,4 +1,4 @@ -//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ // This code was auto-generated by ApiGenerator x.x.x.x. // // Changes to this file may cause incorrect behavior and will be lost if @@ -34,12 +34,10 @@ public async Task GetOrders( /// Description: Get order by id. /// Operation: GetOrderById. /// - [Authorize(Roles = "operator")] + [AllowAnonymous] [HttpGet("{id}")] [ProducesResponseType(typeof(Order), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(typeof(string), StatusCodes.Status404NotFound)] public async Task GetOrderById( GetOrderByIdParameters parameters, @@ -65,4 +63,4 @@ public async Task PatchOrdersId( [FromServices] IPatchOrdersIdHandler handler, CancellationToken cancellationToken) => await handler.ExecuteAsync(parameters, cancellationToken); -} +} \ No newline at end of file diff --git a/test/Atc.Rest.ApiGenerator.CLI.Tests/Scenarios/DemoSample/VerifyServerAll/Mvc_WPD/src/DemoSample.Api.Generated/Endpoints/OrdersController.verified.cs b/test/Atc.Rest.ApiGenerator.CLI.Tests/Scenarios/DemoSample/VerifyServerAll/Mvc_WPD/src/DemoSample.Api.Generated/Endpoints/OrdersController.verified.cs index 29e66c98..2af7854b 100644 --- a/test/Atc.Rest.ApiGenerator.CLI.Tests/Scenarios/DemoSample/VerifyServerAll/Mvc_WPD/src/DemoSample.Api.Generated/Endpoints/OrdersController.verified.cs +++ b/test/Atc.Rest.ApiGenerator.CLI.Tests/Scenarios/DemoSample/VerifyServerAll/Mvc_WPD/src/DemoSample.Api.Generated/Endpoints/OrdersController.verified.cs @@ -1,4 +1,4 @@ -//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ // This code was auto-generated by ApiGenerator x.x.x.x. // // Changes to this file may cause incorrect behavior and will be lost if @@ -34,12 +34,10 @@ public async Task GetOrders( /// Description: Get order by id. /// Operation: GetOrderById. /// - [Authorize(Roles = "operator")] + [AllowAnonymous] [HttpGet("{id}")] [ProducesResponseType(typeof(Order), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] - [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status401Unauthorized)] - [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status403Forbidden)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] public async Task GetOrderById( GetOrderByIdParameters parameters, @@ -65,4 +63,4 @@ public async Task PatchOrdersId( [FromServices] IPatchOrdersIdHandler handler, CancellationToken cancellationToken) => await handler.ExecuteAsync(parameters, cancellationToken); -} +} \ No newline at end of file diff --git a/test/Atc.Rest.ApiGenerator.Nuget.Tests/Atc.Rest.ApiGenerator.Nuget.Tests.csproj b/test/Atc.Rest.ApiGenerator.Nuget.Tests/Atc.Rest.ApiGenerator.Nuget.Tests.csproj index 56174fab..667e24e8 100644 --- a/test/Atc.Rest.ApiGenerator.Nuget.Tests/Atc.Rest.ApiGenerator.Nuget.Tests.csproj +++ b/test/Atc.Rest.ApiGenerator.Nuget.Tests/Atc.Rest.ApiGenerator.Nuget.Tests.csproj @@ -6,7 +6,7 @@ - + diff --git a/test/Atc.Rest.ApiGenerator.OpenApi.Tests/Extensions/OpenApiOperationExtensionsTests.cs b/test/Atc.Rest.ApiGenerator.OpenApi.Tests/Extensions/OpenApiOperationExtensionsTests.cs new file mode 100644 index 00000000..ae98eda8 --- /dev/null +++ b/test/Atc.Rest.ApiGenerator.OpenApi.Tests/Extensions/OpenApiOperationExtensionsTests.cs @@ -0,0 +1,189 @@ +namespace Atc.Rest.ApiGenerator.OpenApi.Tests.Extensions; + +public sealed class OpenApiOperationExtensionsTests +{ + [Fact] + public void ExtractApiOperationAuthorization_ReturnsNull_WhenNoAuthorizationRequired() + { + // Arrange + var apiPath = new OpenApiPathItem(); + var apiOperation = new OpenApiOperation(); + + // Act + var result = apiOperation.ExtractApiOperationAuthorization(apiPath); + + // Assert + Assert.Null(result); + } + + [Fact] + public void ExtractApiOperationAuthorization_ReturnsModel_WithAuthorizationRolesForPath() + { + // Arrange + var apiPath = new OpenApiPathItem + { + Extensions = new Dictionary(StringComparer.Ordinal) + { + { SecurityExtensionNameConstants.AuthorizeRoles, new OpenApiArray { new OpenApiString("api.execute.all") } }, + }, + }; + var apiOperation = new OpenApiOperation(); + + // Act + var result = apiOperation.ExtractApiOperationAuthorization(apiPath); + + // Assert + Assert.NotNull(result); + Assert.Null(result.AuthenticationSchemes); + Assert.NotNull(result.Roles); + Assert.Contains("api.execute.all", result.Roles); + Assert.False(result.UseAllowAnonymous); + } + + [Fact] + public void ExtractApiOperationAuthorization_ReturnsModel_WithAuthorizationRolesForOperation() + { + // Arrange + var apiPath = new OpenApiPathItem(); + var apiOperation = new OpenApiOperation + { + Extensions = new Dictionary(StringComparer.Ordinal) + { + { SecurityExtensionNameConstants.AuthorizeRoles, new OpenApiArray { new OpenApiString("api.execute.all") } }, + }, + }; + + // Act + var result = apiOperation.ExtractApiOperationAuthorization(apiPath); + + // Assert + Assert.NotNull(result); + Assert.Null(result.AuthenticationSchemes); + Assert.NotNull(result.Roles); + Assert.Contains("api.execute.all", result.Roles); + Assert.False(result.UseAllowAnonymous); + } + + [Fact] + public void ExtractApiOperationAuthorization_ReturnsModel_WithAuthenticationSchemesForPath() + { + // Arrange + var apiPath = new OpenApiPathItem + { + Extensions = new Dictionary(StringComparer.Ordinal) + { + { SecurityExtensionNameConstants.AuthenticationSchemes, new OpenApiArray { new OpenApiString("scheme1") } }, + }, + }; + var apiOperation = new OpenApiOperation(); + + // Act + var result = apiOperation.ExtractApiOperationAuthorization(apiPath); + + // Assert + Assert.NotNull(result); + Assert.NotNull(result.AuthenticationSchemes); + Assert.Null(result.Roles); + Assert.Contains("scheme1", result.AuthenticationSchemes); + Assert.False(result.UseAllowAnonymous); + } + + [Fact] + public void ExtractApiOperationAuthorization_ReturnsModel_WithAuthenticationSchemesForOperation() + { + // Arrange + var apiPath = new OpenApiPathItem(); + var apiOperation = new OpenApiOperation + { + Extensions = new Dictionary(StringComparer.Ordinal) + { + { SecurityExtensionNameConstants.AuthenticationSchemes, new OpenApiArray { new OpenApiString("scheme1") } }, + }, + }; + + // Act + var result = apiOperation.ExtractApiOperationAuthorization(apiPath); + + // Assert + Assert.NotNull(result); + Assert.NotNull(result.AuthenticationSchemes); + Assert.Null(result.Roles); + Assert.Contains("scheme1", result.AuthenticationSchemes); + Assert.False(result.UseAllowAnonymous); + } + + [Theory] + [InlineData(false, false, true)] + [InlineData(true, false, true)] + [InlineData(false, true, false)] + [InlineData(true, true, false)] + public void ExtractApiOperationAuthorization_ReturnsModel_WithCorrectAllowAnonymous( + bool pathAuthenticationRequired, + bool operationAuthenticationRequired, + bool expectedUseAllowAnonymous) + { + // Arrange + var apiPath = new OpenApiPathItem + { + Extensions = new Dictionary(StringComparer.Ordinal) + { + { SecurityExtensionNameConstants.AuthenticationRequired, new OpenApiBoolean(pathAuthenticationRequired) }, + }, + }; + var apiOperation = new OpenApiOperation + { + Extensions = new Dictionary(StringComparer.Ordinal) + { + { SecurityExtensionNameConstants.AuthenticationRequired, new OpenApiBoolean(operationAuthenticationRequired) }, + }, + }; + + // Act + var result = apiOperation.ExtractApiOperationAuthorization(apiPath); + + // Assert + Assert.NotNull(result); + Assert.Null(result.AuthenticationSchemes); + Assert.Null(result.Roles); + Assert.Equal(expectedUseAllowAnonymous, result.UseAllowAnonymous); + } + + [Fact] + public void ExtractApiOperationAuthorization_ReturnsModel_WithMultipleRolesAndAuthenticationSchemes() + { + // Arrange + var apiPath = new OpenApiPathItem + { + Extensions = new Dictionary(StringComparer.Ordinal) + { + { SecurityExtensionNameConstants.AuthorizeRoles, new OpenApiArray { new OpenApiString("role1"), new OpenApiString("role2") } }, + { SecurityExtensionNameConstants.AuthenticationSchemes, new OpenApiArray { new OpenApiString("scheme1"), new OpenApiString("scheme2") } }, + }, + }; + var apiOperation = new OpenApiOperation + { + Extensions = new Dictionary(StringComparer.Ordinal) + { + { SecurityExtensionNameConstants.AuthorizeRoles, new OpenApiArray { new OpenApiString("role3"), new OpenApiString("role4") } }, + { SecurityExtensionNameConstants.AuthenticationSchemes, new OpenApiArray { new OpenApiString("scheme3"), new OpenApiString("scheme4") } }, + }, + }; + + // Act + var result = apiOperation.ExtractApiOperationAuthorization(apiPath); + + // Assert + Assert.NotNull(result); + Assert.NotNull(result.Roles); + Assert.Contains("role1", result.Roles); + Assert.Contains("role2", result.Roles); + Assert.Contains("role3", result.Roles); + Assert.Contains("role4", result.Roles); + Assert.NotNull(result.AuthenticationSchemes); + Assert.Contains("scheme1", result.AuthenticationSchemes); + Assert.Contains("scheme2", result.AuthenticationSchemes); + Assert.Contains("scheme3", result.AuthenticationSchemes); + Assert.Contains("scheme4", result.AuthenticationSchemes); + Assert.False(result.UseAllowAnonymous); + } +} \ No newline at end of file diff --git a/test/Atc.Rest.ApiGenerator.OpenApi.Tests/Extensions/OpenApiPathItemExtensionsTests.cs b/test/Atc.Rest.ApiGenerator.OpenApi.Tests/Extensions/OpenApiPathItemExtensionsTests.cs new file mode 100644 index 00000000..bd8a8318 --- /dev/null +++ b/test/Atc.Rest.ApiGenerator.OpenApi.Tests/Extensions/OpenApiPathItemExtensionsTests.cs @@ -0,0 +1,253 @@ +namespace Atc.Rest.ApiGenerator.OpenApi.Tests.Extensions; + +public sealed class OpenApiPathItemExtensionsTests +{ + [Fact] + public void ExtractApiPathAuthorization_ReturnsNull_WhenNoOperationsPresent() + { + // Arrange + var apiPath = new OpenApiPathItem(); + + // Act + var result = apiPath.ExtractApiPathAuthorization(); + + // Assert + Assert.Null(result); + } + + [Fact] + public void ExtractApiPathAuthorization_ReturnsModel_WithAuthorizationRolesForOperations() + { + // Arrange + var apiPath = new OpenApiPathItem + { + Operations = new Dictionary + { + { + OperationType.Get, new OpenApiOperation + { + Extensions = new Dictionary(StringComparer.Ordinal) + { + { SecurityExtensionNameConstants.AuthorizeRoles, new OpenApiArray { new OpenApiString("api.execute.all") } }, + }, + } + }, + }, + }; + + // Act + var result = apiPath.ExtractApiPathAuthorization(); + + // Assert + Assert.NotNull(result); + Assert.Null(result.AuthenticationSchemes); + Assert.NotNull(result.Roles); + Assert.Contains("api.execute.all", result.Roles); + Assert.False(result.UseAllowAnonymous); + } + + [Fact] + public void ExtractApiPathAuthorization_ReturnsModel_WithAuthenticationSchemesForOperations() + { + // Arrange + var apiPath = new OpenApiPathItem + { + Operations = new Dictionary + { + { + OperationType.Get, new OpenApiOperation + { + Extensions = new Dictionary(StringComparer.Ordinal) + { + { SecurityExtensionNameConstants.AuthenticationSchemes, new OpenApiArray { new OpenApiString("scheme1") } }, + }, + } + }, + }, + }; + + // Act + var result = apiPath.ExtractApiPathAuthorization(); + + // Assert + Assert.NotNull(result); + Assert.NotNull(result.AuthenticationSchemes); + Assert.Null(result.Roles); + Assert.Contains("scheme1", result.AuthenticationSchemes); + Assert.False(result.UseAllowAnonymous); + } + + [Theory] + [InlineData(false, false, null)] + [InlineData(true, false, false)] + [InlineData(false, true, null)] + [InlineData(true, true, false)] + public void ExtractApiPathAuthorization( + bool pathAuthenticationRequired, + bool operationAuthenticationRequired, + bool? expectedUseAllowAnonymous) + { + // Arrange + var apiPath = new OpenApiPathItem + { + Operations = new Dictionary + { + { + OperationType.Get, new OpenApiOperation + { + Extensions = new Dictionary(StringComparer.Ordinal) + { + { SecurityExtensionNameConstants.AuthenticationRequired, new OpenApiBoolean(operationAuthenticationRequired) }, + }, + } + }, + }, + Extensions = new Dictionary(StringComparer.Ordinal) + { + { SecurityExtensionNameConstants.AuthenticationRequired, new OpenApiBoolean(pathAuthenticationRequired) }, + }, + }; + + // Act + var result = apiPath.ExtractApiPathAuthorization(); + + // Assert + if (expectedUseAllowAnonymous is null) + { + Assert.Null(result); + } + else + { + Assert.NotNull(result); + Assert.Null(result.AuthenticationSchemes); + Assert.Null(result.Roles); + Assert.Equal(expectedUseAllowAnonymous, result.UseAllowAnonymous); + } + } + + [Fact] + public void ExtractApiPathAuthorization_MergesRolesFromMultipleOperations() + { + // Arrange + var apiPath = new OpenApiPathItem + { + Operations = new Dictionary + { + { + OperationType.Get, new OpenApiOperation + { + Extensions = new Dictionary(StringComparer.Ordinal) + { + { SecurityExtensionNameConstants.AuthorizeRoles, new OpenApiArray { new OpenApiString("role1") } }, + }, + } + }, + { + OperationType.Post, new OpenApiOperation + { + Extensions = new Dictionary(StringComparer.Ordinal) + { + { SecurityExtensionNameConstants.AuthorizeRoles, new OpenApiArray { new OpenApiString("role2") } }, + }, + } + }, + }, + }; + + // Act + var result = apiPath.ExtractApiPathAuthorization(); + + // Assert + Assert.NotNull(result); + Assert.Null(result.AuthenticationSchemes); + Assert.NotNull(result.Roles); + Assert.Contains("role1", result.Roles); + Assert.Contains("role2", result.Roles); + Assert.False(result.UseAllowAnonymous); + } + + [Fact] + public void ExtractApiPathAuthorization_MergesAuthenticationSchemesFromMultipleOperations() + { + // Arrange + var apiPath = new OpenApiPathItem + { + Operations = new Dictionary + { + { + OperationType.Get, new OpenApiOperation + { + Extensions = new Dictionary(StringComparer.Ordinal) + { + { SecurityExtensionNameConstants.AuthenticationSchemes, new OpenApiArray { new OpenApiString("scheme1") } }, + }, + } + }, + { + OperationType.Post, new OpenApiOperation + { + Extensions = new Dictionary(StringComparer.Ordinal) + { + { SecurityExtensionNameConstants.AuthenticationSchemes, new OpenApiArray { new OpenApiString("scheme2") } }, + }, + } + }, + }, + }; + + // Act + var result = apiPath.ExtractApiPathAuthorization(); + + // Assert + Assert.NotNull(result); + Assert.NotNull(result.AuthenticationSchemes); + Assert.Null(result.Roles); + Assert.Contains("scheme1", result.AuthenticationSchemes); + Assert.Contains("scheme2", result.AuthenticationSchemes); + Assert.False(result.UseAllowAnonymous); + } + + [Fact] + public void ExtractApiPathAuthorization_ReturnsModel_WithMultipleRolesAndAuthenticationSchemesForPathAndOperations() + { + // Arrange + var apiPath = new OpenApiPathItem + { + Extensions = new Dictionary(StringComparer.Ordinal) + { + { SecurityExtensionNameConstants.AuthorizeRoles, new OpenApiArray { new OpenApiString("role1"), new OpenApiString("role2") } }, + { SecurityExtensionNameConstants.AuthenticationSchemes, new OpenApiArray { new OpenApiString("scheme1"), new OpenApiString("scheme2") } }, + }, + Operations = new Dictionary + { + { + OperationType.Get, new OpenApiOperation + { + Extensions = new Dictionary(StringComparer.Ordinal) + { + { SecurityExtensionNameConstants.AuthorizeRoles, new OpenApiArray { new OpenApiString("role3"), new OpenApiString("role4") } }, + { SecurityExtensionNameConstants.AuthenticationSchemes, new OpenApiArray { new OpenApiString("scheme3"), new OpenApiString("scheme4") } }, + }, + } + }, + }, + }; + + // Act + var result = apiPath.ExtractApiPathAuthorization(); + + // Assert + Assert.NotNull(result); + Assert.NotNull(result.Roles); + Assert.Contains("role1", result.Roles); + Assert.Contains("role2", result.Roles); + Assert.Contains("role3", result.Roles); + Assert.Contains("role4", result.Roles); + Assert.NotNull(result.AuthenticationSchemes); + Assert.Contains("scheme1", result.AuthenticationSchemes); + Assert.Contains("scheme2", result.AuthenticationSchemes); + Assert.Contains("scheme3", result.AuthenticationSchemes); + Assert.Contains("scheme4", result.AuthenticationSchemes); + Assert.False(result.UseAllowAnonymous); + } +} \ No newline at end of file diff --git a/test/Atc.Rest.ApiGenerator.OpenApi.Tests/GlobalUsings.cs b/test/Atc.Rest.ApiGenerator.OpenApi.Tests/GlobalUsings.cs index 9705c293..1a2e4a3e 100644 --- a/test/Atc.Rest.ApiGenerator.OpenApi.Tests/GlobalUsings.cs +++ b/test/Atc.Rest.ApiGenerator.OpenApi.Tests/GlobalUsings.cs @@ -2,5 +2,6 @@ global using Atc.Rest.ApiGenerator.OpenApi.Extensions; global using Atc.Rest.ApiGenerator.OpenApi.Factories; - +global using Microsoft.OpenApi.Any; +global using Microsoft.OpenApi.Interfaces; global using Microsoft.OpenApi.Models; \ No newline at end of file