diff --git a/NDB.Extensions.Swagger/Class1.cs b/NDB.Extensions.Swagger/Class1.cs deleted file mode 100644 index 18a9456..0000000 --- a/NDB.Extensions.Swagger/Class1.cs +++ /dev/null @@ -1,8 +0,0 @@ -using System; - -namespace NDB.Extensions.Swagger -{ - public class Class1 - { - } -} diff --git a/NDB.Extensions.Swagger/Constants/AuthorizationType.cs b/NDB.Extensions.Swagger/Constants/AuthorizationType.cs new file mode 100644 index 0000000..3abf26f --- /dev/null +++ b/NDB.Extensions.Swagger/Constants/AuthorizationType.cs @@ -0,0 +1,8 @@ +namespace NDB.Extensions.Swagger.Constants +{ + public enum AuthorizationType + { + None, + Basic + } +} diff --git a/NDB.Extensions.Swagger/Filters/DtoSchemaFilter.cs b/NDB.Extensions.Swagger/Filters/DtoSchemaFilter.cs index 9c2dc43..8445013 100644 --- a/NDB.Extensions.Swagger/Filters/DtoSchemaFilter.cs +++ b/NDB.Extensions.Swagger/Filters/DtoSchemaFilter.cs @@ -1,6 +1,7 @@ -using System; -using System.Collections.Generic; -using System.Text; +using Microsoft.OpenApi.Models; +using NDB.Application.DataContracts; +using Swashbuckle.AspNetCore.SwaggerGen; +using System.Linq; namespace NDB.Extensions.Swagger.Filters { diff --git a/NDB.Extensions.Swagger/Filters/PathParamsOperationFilter.cs b/NDB.Extensions.Swagger/Filters/PathParamsOperationFilter.cs new file mode 100644 index 0000000..17da94d --- /dev/null +++ b/NDB.Extensions.Swagger/Filters/PathParamsOperationFilter.cs @@ -0,0 +1,42 @@ +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; +using System; +using System.Linq; +using System.Text.RegularExpressions; + +namespace NDB.Extensions.Swagger.Filters +{ + public class PathParamsOperationFilter : IOperationFilter + { + public void Apply(OpenApiOperation operation, OperationFilterContext context) + { + const string paramCaptureGroup = "param"; + + foreach (var parameter in operation.Parameters.ToList()) + { + if (parameter.Name.ToLowerInvariant().StartsWith("metadata")) + { + operation.Parameters.Remove(parameter); + } + } + + var openApiPathParameters = operation.Parameters.Where(param => param.In == ParameterLocation.Path).ToList(); + var pathParamRegEx = $@"\{{(?<{paramCaptureGroup}>[^\}}]+)\}}"; + + if (openApiPathParameters.Any()) + { + var pathParameterMatches = Regex.Matches(context.ApiDescription.RelativePath, pathParamRegEx, RegexOptions.Compiled); + var pathParameters = pathParameterMatches.Cast().Select(x => x.Groups[paramCaptureGroup].Value); + + foreach (var openApiPathParameter in openApiPathParameters) + { + var correspondingPathParameter = pathParameters.FirstOrDefault(x => + string.Equals(x, openApiPathParameter.Name, StringComparison.InvariantCultureIgnoreCase)); + + if (correspondingPathParameter != null) + openApiPathParameter.Name = correspondingPathParameter; + } + } + } + } +} diff --git a/NDB.Extensions.Swagger/NDB.Extensions.Swagger.csproj b/NDB.Extensions.Swagger/NDB.Extensions.Swagger.csproj index 9f5c4f4..02a4414 100644 --- a/NDB.Extensions.Swagger/NDB.Extensions.Swagger.csproj +++ b/NDB.Extensions.Swagger/NDB.Extensions.Swagger.csproj @@ -4,4 +4,9 @@ netstandard2.0 + + + + + diff --git a/NDB.Extensions.Swagger/ReverseProxy/ReverseProxyHelper.cs b/NDB.Extensions.Swagger/ReverseProxy/ReverseProxyHelper.cs new file mode 100644 index 0000000..499224c --- /dev/null +++ b/NDB.Extensions.Swagger/ReverseProxy/ReverseProxyHelper.cs @@ -0,0 +1,38 @@ +using Microsoft.AspNetCore.Http; +using System.Linq; + +namespace NDB.Extensions.Swagger.ReverseProxy +{ + public static class ReverseProxyHelper + { + public static (string host, string basePath, string scheme) GetUrlComponents(HttpRequest request) + { + var host = ExtractHost(request); + var basePath = ExtractBasePath(request); + var scheme = ExtractScheme(request); + + return (host, basePath, scheme); + } + + private static string ExtractHost(HttpRequest request) + { + if (request.Headers.ContainsKey("X-Forwarded-Host")) + return request.Headers["X-Forwarded-Host"].First(); + + return request.Host.Value; + } + + private static string ExtractBasePath(HttpRequest request) + { + if (request.Headers.ContainsKey("X-Forwarded-PathBase")) + return request.Headers["X-Forwarded-PathBase"].First(); + + return string.Empty; + } + + private static string ExtractScheme(HttpRequest request) + { + return request.Headers["X-Forwarded-Proto"].FirstOrDefault() ?? request.Scheme; + } + } +} diff --git a/NDB.Extensions.Swagger/SwaggerExtensions.cs b/NDB.Extensions.Swagger/SwaggerExtensions.cs new file mode 100644 index 0000000..dd4ee3b --- /dev/null +++ b/NDB.Extensions.Swagger/SwaggerExtensions.cs @@ -0,0 +1,102 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OpenApi.Models; +using NDB.Extensions.Swagger.Constants; +using NDB.Extensions.Swagger.Filters; +using NDB.Extensions.Swagger.ReverseProxy; +using Swashbuckle.AspNetCore.SwaggerGen; +using System; +using System.Collections.Generic; + +namespace NDB.Extensions.Swagger +{ + public static class SwaggerExtensions + { + public static IServiceCollection AddSwagger(this IServiceCollection services, string title, AuthorizationType authorizationType = AuthorizationType.Basic) + { + services.AddSwaggerGen(c => + { + c.SwaggerDoc("v1", + new OpenApiInfo + { + Title = title, + Version = "v1" + }); + + c.SetAuthorization(authorizationType); + c.OperationFilter(); + c.SchemaFilter(); + c.CustomSchemaIds(type => type.ToString()); + }); + + return services; + } + + private static void SetAuthorization(this SwaggerGenOptions options, AuthorizationType authorizationType) + { + switch (authorizationType) + { + case AuthorizationType.None: + return; + + case AuthorizationType.Basic: + options.AddSecurityDefinition("Basic", + new OpenApiSecurityScheme + { + In = ParameterLocation.Header, + Description = @"JWT Authorization header using the Basic scheme. Enter 'Basic' [space] and then your token in the text input below. Example: 'Basic 12345abcdef'", + Name = "Authorization", + Scheme = "Basic", + Type = SecuritySchemeType.ApiKey + }); + + options.AddSecurityRequirement(new OpenApiSecurityRequirement() + { + { + new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Type = ReferenceType.SecurityScheme, + Id = "Basic" + }, + Scheme = "Basic", + Name = "Authorization", + In = ParameterLocation.Header + }, + new List() + } + }); + break; + + default: + throw new NotImplementedException($"Swagger extensions: Authorization type '{authorizationType}' is not implemented."); + } + } + + public static IApplicationBuilder ConfigureSwagger(this IApplicationBuilder applicationBuilder, string endpointName) + { + applicationBuilder.UseSwagger(c => + { + c.PreSerializeFilters.Add((swagger, httpRequest) => + { + var (host, basePath, scheme) = ReverseProxyHelper.GetUrlComponents(httpRequest); + + swagger.Servers = new List + { + new OpenApiServer {Url = $"{scheme}://{host}{basePath}"} + }; + }); + c.RouteTemplate = "swagger/{documentName}/swagger.json"; + }); + + applicationBuilder.UseSwaggerUI(c => + { + c.SwaggerEndpoint("v1/swagger.json", endpointName); + c.RoutePrefix = $"swagger"; + }); + + return applicationBuilder; + } + } +}