duplicati/Duplicati/WebserverCore/Endpoints/V1/RemoteControl.cs
2025-05-05 16:13:46 +02:00

120 lines
No EOL
6 KiB
C#

// Copyright (C) 2025, The Duplicati Team
// https://duplicati.com, hello@duplicati.com
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using Duplicati.WebserverCore.Abstractions;
using Duplicati.WebserverCore.Exceptions;
using Microsoft.AspNetCore.Mvc;
namespace Duplicati.WebserverCore.Endpoints.V1;
public class RemoteControl : IEndpointV1
{
public static void Map(RouteGroupBuilder group)
{
group.MapGet("/remotecontrol/status", ([FromServices] IRemoteControllerRegistration registration, [FromServices] IRemoteController remoteController)
=> GetStatus(registration, remoteController))
.RequireAuthorization();
}
public static void MapConditional(RouteGroupBuilder group, string origin)
{
// Don't allow these in agent-mode
if (origin == "Agent")
return;
group.MapPost("/remotecontrol/enable", ([FromServices] IRemoteControllerRegistration registration, [FromServices] IRemoteController remoteController)
=> EnableRemoteControl(registration, remoteController))
.RequireAuthorization();
group.MapPost("/remotecontrol/disable", ([FromServices] IRemoteControllerRegistration registration, [FromServices] IRemoteController remoteController)
=> DisableRemoteControl(registration, remoteController))
.RequireAuthorization();
group.MapDelete("/remotecontrol/registration", ([FromServices] IRemoteControllerRegistration registration, [FromServices] IRemoteController remoteController)
=> DeleteRegistration(registration, remoteController))
.RequireAuthorization();
group.MapPost("/remotecontrol/register", ([FromBody] Dto.StartRegistrationInput input, [FromServices] IRemoteControllerRegistration registration, [FromServices] IRemoteController remoteController, CancellationToken cancellationToken)
=> BeginRegisterMachine(registration, remoteController, input.RegistrationUrl, cancellationToken))
.RequireAuthorization();
group.MapDelete("/remotecontrol/register", ([FromServices] IRemoteControllerRegistration registration, [FromServices] IRemoteController remoteController)
=> CancelRegistration(registration, remoteController))
.RequireAuthorization();
}
private static async Task<Dto.RemoteControlStatusOutput> BeginRegisterMachine(IRemoteControllerRegistration registration, IRemoteController remoteController, string registrationUrl, CancellationToken cancellationToken)
{
if (remoteController.CanEnable)
throw new BadRequestException("Existing remote control must be removed before registering");
if (!registration.IsRegistering && string.IsNullOrWhiteSpace(registrationUrl))
throw new BadRequestException("Registration URL must be provided");
var task = registration.IsRegistering
? registration.WaitForRegistration()
: registration.RegisterMachine(registrationUrl);
// Wait for registration, or at most 5 seconds
// Client must poll if the registration is not completed by then
await Task.WhenAny(task, Task.Delay(TimeSpan.FromSeconds(4.5), cancellationToken));
// Rethrow any exceptions
if (task.IsCompleted)
await task;
return GetStatus(registration, remoteController);
}
private static Dto.RemoteControlStatusOutput EnableRemoteControl(IRemoteControllerRegistration registration, IRemoteController remoteController)
{
remoteController.Enable();
return GetStatus(registration, remoteController);
}
private static Dto.RemoteControlStatusOutput DisableRemoteControl(IRemoteControllerRegistration registration, IRemoteController remoteController)
{
remoteController.Disable();
return GetStatus(registration, remoteController);
}
private static Dto.RemoteControlStatusOutput DeleteRegistration(IRemoteControllerRegistration registration, IRemoteController remoteController)
{
remoteController.DeleteRegistration();
return GetStatus(registration, remoteController);
}
private static Dto.RemoteControlStatusOutput CancelRegistration(IRemoteControllerRegistration registration, IRemoteController remoteController)
{
registration.CancelRegisterMachine();
return GetStatus(registration, remoteController);
}
private static Dto.RemoteControlStatusOutput GetStatus(IRemoteControllerRegistration registration, IRemoteController remoteController)
=> new Dto.RemoteControlStatusOutput(
CanEnable: remoteController.CanEnable,
IsEnabled: remoteController.IsEnabled,
IsConnected: remoteController.Connected,
IsRegistering: registration.IsRegistering,
IsRegisteringFaulted: registration.IsRegistering && registration.WaitForRegistration().IsFaulted,
IsRegisteringCompleted: registration.IsRegistering && registration.WaitForRegistration().IsCompleted,
RegistrationUrl: registration.RegistrationUrl
);
}