Merge pull request #5541 from duplicati/feature/fix-swagger-output

Fix Swagger exposed API.
This commit is contained in:
Kenneth Skovhede
2024-09-06 11:49:32 +02:00
committed by GitHub
5 changed files with 17 additions and 88 deletions
@@ -1,32 +0,0 @@
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.AspNetCore.Mvc.Controllers;
namespace Duplicati.WebserverCore;
//Useful for debugging ASP.net magically loading controllers
public class ApplicationPartsLogger(ILogger<ApplicationPartsLogger> logger, ApplicationPartManager partManager)
: IHostedService
{
public Task StartAsync(CancellationToken cancellationToken)
{
// Get the names of all the application parts. This is the short assembly name for AssemblyParts
var applicationParts = partManager.ApplicationParts.Select(x => x.Name);
// Create a controller feature, and populate it from the application parts
var controllerFeature = new ControllerFeature();
partManager.PopulateFeature(controllerFeature);
// Get the names of all of the controllers
var controllers = controllerFeature.Controllers.Select(x => x.Name);
// Log the application parts and controllers
logger.LogInformation(
"Found the following application parts: '{ApplicationParts}' with the following controllers: '{Controllers}'",
string.Join(", ", applicationParts), string.Join(", ", controllers));
return Task.CompletedTask;
}
// Required by the interface
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}
+8 -15
View File
@@ -1,4 +1,4 @@
using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography.X509Certificates;
using System.Text.Json;
using System.Text.Json.Serialization;
using Duplicati.Server.Database;
@@ -9,6 +9,7 @@ using Duplicati.WebserverCore.Middlewares;
using Duplicati.WebserverCore.Services;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Http.Json;
using Microsoft.Extensions.Configuration.Json;
using Microsoft.OpenApi.Models;
@@ -70,24 +71,13 @@ public partial class DuplicatiWebserver
});
});
//builder.Host.UseRESTHandlers();
builder.Services.ConfigureHttpJsonOptions(opt =>
builder.Services.Configure<JsonOptions>(opt =>
{
opt.SerializerOptions.PropertyNamingPolicy = null;
opt.SerializerOptions.Converters.Add(new JsonStringEnumConverter());
opt.SerializerOptions.Converters.Add(new DayOfWeekStringEnumConverter());
opt.SerializerOptions.Converters.Add(new JsonStringEnumConverter());
});
builder.Services.AddControllers()
// This app gets launched by a different assembly, so we need to tell it to look in this one
.AddApplicationPart(GetType().Assembly)
.AddJsonOptions(opt =>
{
opt.JsonSerializerOptions.PropertyNamingPolicy = null;
opt.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
opt.JsonSerializerOptions.Converters.Add(new DayOfWeekStringEnumConverter());
});
// Generate JWTConfig with signing key if not present
if (string.IsNullOrWhiteSpace(connection.ApplicationSettings.JWTConfig))
connection.ApplicationSettings.JWTConfig = JsonSerializer.Serialize(JWTConfig.Create());
@@ -96,7 +86,6 @@ public partial class DuplicatiWebserver
?? throw new Exception("Failed to deserialize JWTConfig");
builder.Services
.AddHostedService<ApplicationPartsLogger>()
#if DEBUG
.AddEndpointsApiExplorer()
.AddSwaggerGen(c =>
@@ -115,6 +104,7 @@ public partial class DuplicatiWebserver
.AddHttpContextAccessor()
.AddSingleton<IHostnameValidator>(new HostnameValidator(settings.AllowedHostnames))
.AddSingleton(jwtConfig)
.AddAuthorization()
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
@@ -163,6 +153,9 @@ public partial class DuplicatiWebserver
App = builder.Build();
Provider = App.Services;
App.UseAuthentication();
App.UseAuthorization();
#if DEBUG
App.UseSwagger();
App.UseSwaggerUI(c =>
@@ -14,24 +14,9 @@ public class BackupPutDelete : IEndpointV1
{
public static void Map(RouteGroupBuilder group)
{
// TODO: Figure out why the JSON deserialization is not working here
// group.MapPut("/backup/{id}", ([FromServices] Connection connection, [FromRoute] string id, [FromBody] Dto.BackupAndScheduleInputDto input)
// => ExecutePut(connection, input);
group.MapPut("/backup/{id}", async ([FromServices] Connection connection, [FromServices] IHttpContextAccessor httpContextAccessor, [FromRoute] string id) =>
{
var opts = new JsonSerializerOptions()
{
Converters = { new DayOfWeekStringEnumConverter() }
};
var input = (await JsonSerializer.DeserializeAsync<Dto.BackupAndScheduleInputDto>(httpContextAccessor.HttpContext!.Request.Body, opts))
?? throw new BadRequestException("No data found in request body");
ExecutePut(GetBackup(connection, id), connection, input);
})
.RequireAuthorization();
group.MapPut("/backup/{id}", ([FromServices] Connection connection, [FromRoute] string id, [FromBody] Dto.BackupAndScheduleInputDto input)
=> ExecutePut(GetBackup(connection, id), connection, input))
.RequireAuthorization();
group.MapDelete("/backup/{id}", ([FromServices] Connection connection, [FromServices] IWorkerThreadsManager workerThreadsManager, [FromServices] ICaptchaProvider captchaProvider, [FromServices] LiveControls liveControls, [FromServices] IHttpContextAccessor httpContextAccessor, [FromRoute] string id, [FromQuery(Name = "delete-remote-files")] bool? delete_remote_files, [FromQuery(Name = "delete-local-db")] bool? delete_local_db, [FromQuery(Name = "captcha-token")] string? captcha_token, [FromQuery(Name = "captcha-answer")] string? captcha_answer, [FromQuery] bool? force) =>
{
@@ -41,7 +26,6 @@ public class BackupPutDelete : IEndpointV1
return res;
})
.RequireAuthorization();
}
private static IBackup GetBackup(Connection connection, string id)
@@ -16,20 +16,8 @@ public class Backups : IEndpointV1
=> ExecuteGet(connection))
.RequireAuthorization();
// TODO: Figure out why the JSON deserialization is not working here
// group.MapPost("/backups", ([FromServices] Connection connection, [FromBody] Dto.BackupAndScheduleInputDto input, [FromQuery] bool? temporary, [FromQuery] bool? existingdb)
// => ExecuteAdd(connection, input, temporary ?? false, existingdb ?? false)).RequireAuthorization();
group.MapPost("/backups", async ([FromServices] Connection connection, [FromQuery] bool? temporary, [FromQuery] bool? existingdb, [FromServices] IHttpContextAccessor httpContextAccessor) =>
{
var opts = new JsonSerializerOptions()
{
Converters = { new DayOfWeekStringEnumConverter() }
};
var input = await JsonSerializer.DeserializeAsync<Dto.BackupAndScheduleInputDto>(httpContextAccessor.HttpContext!.Request.Body, opts)
?? throw new BadRequestException("No data found in request body");
return ExecuteAdd(connection, input, temporary ?? false, existingdb ?? false);
})
group.MapPost("/backups", ([FromServices] Connection connection, [FromBody] Dto.BackupAndScheduleInputDto input, [FromQuery] bool? temporary, [FromQuery] bool? existingdb)
=> ExecuteAdd(connection, input, temporary ?? false, existingdb ?? false))
.RequireAuthorization();
group.MapPost("/backups/import", ([FromBody] Dto.ImportBackupInputDto input, [FromServices] IJWTTokenProvider jWTTokenProvider, [FromServices] Connection connection, [FromServices] IHttpContextAccessor httpContextAccessor) =>
@@ -18,17 +18,13 @@ public static class WebApplicationExtensions
typeof(WebApplicationExtensions).Assembly.DefinedTypes
.Where(t => t.ImplementedInterfaces.Contains(mapperInterfaceType))
.ToArray();
if (endpoints.Length == 0)
{
return application;
}
var group = application.MapGroup("/api/v1")
.AddEndpointFilter<LanguageFilter>()
.AddEndpointFilter<HostnameFilter>();
foreach (var endpoint in endpoints)
{
var group = application.MapGroup("/api/v1")
.AddEndpointFilter<LanguageFilter>()
.AddEndpointFilter<HostnameFilter>();
var methodMap = endpoint.GetMethod(nameof(IEndpointV1.Map), BindingFlags.Static | BindingFlags.Public);
methodMap!.Invoke(null, [group]);
}