Added auth requirement on all endpoints. Added SignIn-, Access- and Refresh-Tokens based on JWT. Upgraded stored password to be based on PBKDF2. Added default password assignment. Updated logic to support auth-header and re-sign-in for tray-icon and web-ui.
160 lines
6.7 KiB
C#
160 lines
6.7 KiB
C#
using Duplicati.Server.Database;
|
|
using Duplicati.WebserverCore.Abstractions;
|
|
using Duplicati.WebserverCore.Exceptions;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
|
|
namespace Duplicati.WebserverCore.Endpoints.V1;
|
|
|
|
public class ServerSetting : IEndpointV1
|
|
{
|
|
public static void Map(RouteGroupBuilder group)
|
|
{
|
|
group.MapGet("/serversettings", ([FromServices] Connection connection) => GetSettings(connection)).RequireAuthorization();
|
|
group.MapPatch("/serversettings", ([FromServices] Connection connection, [FromBody] Dictionary<string, object?> values) => UpdateSettings(connection, values)).RequireAuthorization();
|
|
|
|
group.MapGet("/serversetting/{key}", ([FromRoute] string key, [FromServices] Connection connection, [FromServices] ISettingsService settingsService) => GetSetting(key, connection, settingsService)).RequireAuthorization();
|
|
group.MapPut("/serversetting/{key}", ([FromRoute] string key, [FromBody] string value, [FromServices] Connection connection) => UpdateSetting(key, value, connection)).RequireAuthorization();
|
|
}
|
|
|
|
// Remove sensitive information from the output
|
|
private static readonly string[] GUARDED_OUTPUT = [
|
|
Server.Database.ServerSettings.CONST.JWT_CONFIG,
|
|
Server.Database.ServerSettings.CONST.PBKDF_CONFIG,
|
|
// Not used anymore, but not completely removed
|
|
Server.Database.ServerSettings.CONST.SERVER_PASSPHRASE,
|
|
Server.Database.ServerSettings.CONST.SERVER_PASSPHRASE_SALT,
|
|
// Completely removed, but no need to expose
|
|
"server-passphrase-trayicon-hash",
|
|
"server-passphrase-trayicon-salt"
|
|
];
|
|
|
|
private static readonly string[] GUARDED_INPUT = [
|
|
Server.Database.ServerSettings.CONST.JWT_CONFIG,
|
|
Server.Database.ServerSettings.CONST.PBKDF_CONFIG,
|
|
Server.Database.ServerSettings.CONST.SERVER_PASSPHRASE,
|
|
Server.Database.ServerSettings.CONST.SERVER_PASSPHRASE_SALT,
|
|
Server.Database.ServerSettings.CONST.SERVER_SSL_CERTIFICATE,
|
|
"ServerSSLCertificate",
|
|
"server-passphrase-trayicon-hash",
|
|
"server-passphrase-trayicon-salt"
|
|
];
|
|
|
|
|
|
private static Dictionary<string, string> GetSettings(Connection connection)
|
|
{
|
|
// Join server settings and global settings
|
|
var dict =
|
|
connection.GetSettings(Connection.SERVER_SETTINGS_ID)
|
|
.Where(x => !string.IsNullOrWhiteSpace(x.Name))
|
|
.Union(
|
|
connection.Settings
|
|
.Where(x => !string.IsNullOrWhiteSpace(x.Name) && x.Name.StartsWith("--", StringComparison.Ordinal))
|
|
)
|
|
.DistinctBy(x => x.Name)
|
|
.ToDictionary(x => x.Name, x => x.Value);
|
|
|
|
// Patch cert to boolean
|
|
dict.TryGetValue("server-ssl-certificate", out var sslcert);
|
|
dict["server-ssl-certificate"] = (!string.IsNullOrWhiteSpace(sslcert)).ToString();
|
|
|
|
foreach (var key in GUARDED_OUTPUT)
|
|
dict.Remove(key);
|
|
|
|
return dict;
|
|
}
|
|
|
|
private static void UpdateSettings(Connection connection, Dictionary<string, object?>? values)
|
|
{
|
|
if (values == null)
|
|
throw new BadRequestException("No values provided");
|
|
|
|
var passphrase = values.GetValueOrDefault(Server.Database.ServerSettings.CONST.SERVER_PASSPHRASE)?.ToString();
|
|
foreach (var key in GUARDED_INPUT)
|
|
values.Remove(key);
|
|
|
|
// Split into server settings and global settings
|
|
var serversettings = values.Where(x => !string.IsNullOrWhiteSpace(x.Key) && !x.Key.StartsWith("--", StringComparison.Ordinal))
|
|
.ToDictionary(x => x.Key, x => x.Value?.ToString());
|
|
|
|
var globalsettings = values.Where(x => !string.IsNullOrWhiteSpace(x.Key) && x.Key.StartsWith("--", StringComparison.Ordinal));
|
|
|
|
if (!string.IsNullOrWhiteSpace(passphrase))
|
|
connection.ApplicationSettings.SetWebserverPassword(passphrase);
|
|
if (serversettings.Any())
|
|
connection.ApplicationSettings.UpdateSettings(serversettings, false);
|
|
|
|
if (globalsettings.Any())
|
|
{
|
|
// Update based on inputs
|
|
var existing = connection.Settings.ToDictionary(x => x.Name, x => x);
|
|
foreach (var g in globalsettings)
|
|
if (g.Value == null)
|
|
existing.Remove(g.Key);
|
|
else
|
|
{
|
|
if (existing.ContainsKey(g.Key))
|
|
existing[g.Key].Value = g.Value?.ToString();
|
|
else
|
|
existing[g.Key] = new Setting() { Name = g.Key, Value = g.Value?.ToString() };
|
|
}
|
|
|
|
connection.Settings = existing.Select(x => x.Value).ToArray();
|
|
}
|
|
}
|
|
|
|
private static string? GetSetting(string key, Connection connection, ISettingsService settingsService)
|
|
{
|
|
if (key.Equals("server-ssl-certificate", StringComparison.OrdinalIgnoreCase) || key.Equals("ServerSSLCertificate", StringComparison.OrdinalIgnoreCase))
|
|
return settingsService.GetSettings().HasSSLCertificate.ToString();
|
|
|
|
if (GUARDED_OUTPUT.Any(x => string.Equals(x, key, StringComparison.OrdinalIgnoreCase)))
|
|
throw new NotFoundException("Key not found");
|
|
|
|
|
|
if (key.StartsWith("--", StringComparison.Ordinal))
|
|
{
|
|
return connection.Settings.FirstOrDefault(x => string.Equals(key, x.Name, StringComparison.OrdinalIgnoreCase))?.Value;
|
|
}
|
|
else
|
|
{
|
|
var prop = connection.ApplicationSettings.GetType().GetProperty(key);
|
|
if (prop == null)
|
|
throw new NotFoundException("Key not found");
|
|
|
|
return prop.GetValue(connection.ApplicationSettings)?.ToString();
|
|
}
|
|
}
|
|
|
|
private static void UpdateSetting(string key, string value, Connection connection)
|
|
{
|
|
if (GUARDED_INPUT.Any(x => string.Equals(x, key, StringComparison.OrdinalIgnoreCase)))
|
|
throw new BadRequestException($"Cannot update {key} setting");
|
|
|
|
if (key.StartsWith("--", StringComparison.Ordinal))
|
|
{
|
|
var settings = connection.Settings.ToList();
|
|
|
|
var prop = settings.FirstOrDefault(x => string.Equals(key, x.Name, StringComparison.OrdinalIgnoreCase));
|
|
if (prop == null)
|
|
settings.Add(prop = new Server.Database.Setting() { Name = key, Value = value });
|
|
else
|
|
prop.Value = value;
|
|
|
|
connection.Settings = settings.ToArray();
|
|
}
|
|
else
|
|
{
|
|
var prop = connection.ApplicationSettings.GetType().GetProperty(key);
|
|
if (prop == null)
|
|
throw new NotFoundException("Key not found");
|
|
|
|
var dict = new Dictionary<string, string>
|
|
{
|
|
{ key, value }
|
|
};
|
|
connection.ApplicationSettings.UpdateSettings(dict, false);
|
|
}
|
|
}
|
|
|
|
}
|