2020-11-13 06:01:51 +01:00
|
|
|
using System;
|
|
|
|
using System.Linq;
|
|
|
|
using System.Threading.Tasks;
|
2020-11-19 04:40:07 +01:00
|
|
|
using BTCPayServer.Abstractions.Constants;
|
2022-02-24 09:00:44 +01:00
|
|
|
using BTCPayServer.Abstractions.Extensions;
|
2020-11-13 06:01:51 +01:00
|
|
|
using BTCPayServer.Client;
|
|
|
|
using BTCPayServer.Client.Models;
|
|
|
|
using BTCPayServer.Data;
|
2023-12-01 10:50:05 +01:00
|
|
|
using BTCPayServer.HostedServices.Webhooks;
|
2020-11-13 06:01:51 +01:00
|
|
|
using BTCPayServer.Services.Stores;
|
|
|
|
using Microsoft.AspNetCore.Authorization;
|
|
|
|
using Microsoft.AspNetCore.Cors;
|
|
|
|
using Microsoft.AspNetCore.Mvc;
|
|
|
|
using Newtonsoft.Json.Linq;
|
|
|
|
|
2022-01-14 05:05:23 +01:00
|
|
|
namespace BTCPayServer.Controllers.Greenfield
|
2020-11-13 06:01:51 +01:00
|
|
|
{
|
|
|
|
[ApiController]
|
|
|
|
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Greenfield,
|
2024-09-12 05:29:10 +02:00
|
|
|
Policy = Policies.CanModifyWebhooks)]
|
2020-11-13 06:01:51 +01:00
|
|
|
[EnableCors(CorsPolicies.All)]
|
2022-01-07 04:17:59 +01:00
|
|
|
public class GreenfieldStoreWebhooksController : ControllerBase
|
2020-11-13 06:01:51 +01:00
|
|
|
{
|
2022-01-07 04:32:00 +01:00
|
|
|
public GreenfieldStoreWebhooksController(StoreRepository storeRepository, WebhookSender webhookSender)
|
2020-11-13 06:01:51 +01:00
|
|
|
{
|
|
|
|
StoreRepository = storeRepository;
|
2022-01-07 04:32:00 +01:00
|
|
|
WebhookSender = webhookSender;
|
2020-11-13 06:01:51 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public StoreRepository StoreRepository { get; }
|
2022-01-07 04:32:00 +01:00
|
|
|
public WebhookSender WebhookSender { get; }
|
2020-11-13 06:01:51 +01:00
|
|
|
|
|
|
|
[HttpGet("~/api/v1/stores/{storeId}/webhooks/{webhookId?}")]
|
2021-07-27 14:11:47 +02:00
|
|
|
public async Task<IActionResult> ListWebhooks(string storeId, string webhookId)
|
2020-11-13 06:01:51 +01:00
|
|
|
{
|
|
|
|
if (webhookId is null)
|
|
|
|
{
|
2021-01-05 04:38:12 +01:00
|
|
|
return Ok((await StoreRepository.GetWebhooks(CurrentStoreId))
|
2020-11-13 06:01:51 +01:00
|
|
|
.Select(o => FromModel(o, false))
|
2022-05-03 11:15:08 +02:00
|
|
|
.ToArray());
|
2020-11-13 06:01:51 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-01-05 04:38:12 +01:00
|
|
|
var w = await StoreRepository.GetWebhook(CurrentStoreId, webhookId);
|
2020-11-13 06:01:51 +01:00
|
|
|
if (w is null)
|
2021-04-27 08:38:42 +02:00
|
|
|
return WebhookNotFound();
|
2020-11-13 06:01:51 +01:00
|
|
|
return Ok(FromModel(w, false));
|
|
|
|
}
|
|
|
|
}
|
2021-01-05 04:38:12 +01:00
|
|
|
|
|
|
|
string CurrentStoreId
|
|
|
|
{
|
|
|
|
get
|
|
|
|
{
|
|
|
|
return this.HttpContext.GetStoreData()?.Id;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-13 06:01:51 +01:00
|
|
|
[HttpPost("~/api/v1/stores/{storeId}/webhooks")]
|
2021-07-27 14:11:47 +02:00
|
|
|
public async Task<IActionResult> CreateWebhook(string storeId, Client.Models.CreateStoreWebhookRequest create)
|
2020-11-13 06:01:51 +01:00
|
|
|
{
|
|
|
|
ValidateWebhookRequest(create);
|
|
|
|
if (!ModelState.IsValid)
|
|
|
|
return this.CreateValidationError(ModelState);
|
2021-01-05 04:38:12 +01:00
|
|
|
var webhookId = await StoreRepository.CreateWebhook(CurrentStoreId, ToModel(create));
|
|
|
|
var w = await StoreRepository.GetWebhook(CurrentStoreId, webhookId);
|
2020-11-13 06:01:51 +01:00
|
|
|
if (w is null)
|
2021-04-27 08:38:42 +02:00
|
|
|
return WebhookNotFound();
|
2020-11-13 06:01:51 +01:00
|
|
|
return Ok(FromModel(w, true));
|
|
|
|
}
|
|
|
|
|
|
|
|
private void ValidateWebhookRequest(StoreWebhookBaseData create)
|
|
|
|
{
|
2024-01-18 01:47:39 +01:00
|
|
|
if (!Uri.TryCreate(create?.Url, UriKind.Absolute, out _))
|
2020-11-13 06:01:51 +01:00
|
|
|
ModelState.AddModelError(nameof(Url), "Invalid Url");
|
|
|
|
}
|
|
|
|
|
|
|
|
[HttpPut("~/api/v1/stores/{storeId}/webhooks/{webhookId}")]
|
|
|
|
public async Task<IActionResult> UpdateWebhook(string storeId, string webhookId, Client.Models.UpdateStoreWebhookRequest update)
|
|
|
|
{
|
|
|
|
ValidateWebhookRequest(update);
|
|
|
|
if (!ModelState.IsValid)
|
|
|
|
return this.CreateValidationError(ModelState);
|
2021-01-05 04:38:12 +01:00
|
|
|
var w = await StoreRepository.GetWebhook(CurrentStoreId, webhookId);
|
2020-11-13 06:01:51 +01:00
|
|
|
if (w is null)
|
2021-04-27 08:38:42 +02:00
|
|
|
return WebhookNotFound();
|
2020-11-13 06:01:51 +01:00
|
|
|
await StoreRepository.UpdateWebhook(storeId, webhookId, ToModel(update));
|
2021-07-27 14:11:47 +02:00
|
|
|
return await ListWebhooks(storeId, webhookId);
|
2020-11-13 06:01:51 +01:00
|
|
|
}
|
|
|
|
[HttpDelete("~/api/v1/stores/{storeId}/webhooks/{webhookId}")]
|
2021-07-27 14:11:47 +02:00
|
|
|
public async Task<IActionResult> DeleteWebhook(string storeId, string webhookId)
|
2020-11-13 06:01:51 +01:00
|
|
|
{
|
2021-01-05 04:38:12 +01:00
|
|
|
var w = await StoreRepository.GetWebhook(CurrentStoreId, webhookId);
|
2020-11-13 06:01:51 +01:00
|
|
|
if (w is null)
|
2021-04-27 08:38:42 +02:00
|
|
|
return WebhookNotFound();
|
2021-01-05 04:38:12 +01:00
|
|
|
await StoreRepository.DeleteWebhook(CurrentStoreId, webhookId);
|
2020-11-13 06:01:51 +01:00
|
|
|
return Ok();
|
|
|
|
}
|
2021-04-27 08:38:42 +02:00
|
|
|
|
|
|
|
IActionResult WebhookNotFound()
|
|
|
|
{
|
|
|
|
return this.CreateAPIError(404, "webhook-not-found", "The webhook was not found");
|
|
|
|
}
|
|
|
|
IActionResult WebhookDeliveryNotFound()
|
|
|
|
{
|
|
|
|
return this.CreateAPIError(404, "webhookdelivery-not-found", "The webhook delivery was not found");
|
|
|
|
}
|
2020-11-13 06:01:51 +01:00
|
|
|
private WebhookBlob ToModel(StoreWebhookBaseData create)
|
|
|
|
{
|
|
|
|
return new WebhookBlob()
|
|
|
|
{
|
|
|
|
Active = create.Enabled,
|
|
|
|
Url = create.Url,
|
|
|
|
Secret = create.Secret,
|
|
|
|
AuthorizedEvents = create.AuthorizedEvents is Client.Models.StoreWebhookBaseData.AuthorizedEventsData aed ?
|
|
|
|
new AuthorizedWebhookEvents()
|
|
|
|
{
|
|
|
|
Everything = aed.Everything,
|
|
|
|
SpecificEvents = aed.SpecificEvents
|
2021-12-31 08:59:02 +01:00
|
|
|
} :
|
2020-11-13 06:01:51 +01:00
|
|
|
new AuthorizedWebhookEvents() { Everything = true },
|
2021-12-31 08:59:02 +01:00
|
|
|
AutomaticRedelivery = create.AutomaticRedelivery,
|
2020-11-13 06:01:51 +01:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2021-12-31 08:59:02 +01:00
|
|
|
|
2020-11-13 06:01:51 +01:00
|
|
|
[HttpGet("~/api/v1/stores/{storeId}/webhooks/{webhookId}/deliveries/{deliveryId?}")]
|
2021-07-27 14:11:47 +02:00
|
|
|
public async Task<IActionResult> ListDeliveries(string storeId, string webhookId, string deliveryId, int? count = null)
|
2020-11-13 06:01:51 +01:00
|
|
|
{
|
|
|
|
if (deliveryId is null)
|
|
|
|
{
|
2021-01-05 04:38:12 +01:00
|
|
|
return Ok((await StoreRepository.GetWebhookDeliveries(CurrentStoreId, webhookId, count))
|
2020-11-13 06:01:51 +01:00
|
|
|
.Select(o => FromModel(o))
|
|
|
|
.ToList());
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-01-05 04:38:12 +01:00
|
|
|
var delivery = await StoreRepository.GetWebhookDelivery(CurrentStoreId, webhookId, deliveryId);
|
2020-11-13 06:01:51 +01:00
|
|
|
if (delivery is null)
|
2021-04-27 08:38:42 +02:00
|
|
|
return WebhookDeliveryNotFound();
|
2020-11-13 06:01:51 +01:00
|
|
|
return Ok(FromModel(delivery));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
[HttpPost("~/api/v1/stores/{storeId}/webhooks/{webhookId}/deliveries/{deliveryId}/redeliver")]
|
2021-07-27 14:11:47 +02:00
|
|
|
public async Task<IActionResult> RedeliverWebhook(string storeId, string webhookId, string deliveryId)
|
2020-11-13 06:01:51 +01:00
|
|
|
{
|
2021-01-05 04:38:12 +01:00
|
|
|
var delivery = await StoreRepository.GetWebhookDelivery(CurrentStoreId, webhookId, deliveryId);
|
2020-11-13 06:01:51 +01:00
|
|
|
if (delivery is null)
|
2021-04-27 08:38:42 +02:00
|
|
|
return WebhookDeliveryNotFound();
|
2023-05-28 16:44:10 +02:00
|
|
|
if (delivery.GetBlob().IsPruned())
|
|
|
|
return WebhookDeliveryPruned();
|
2022-01-07 04:32:00 +01:00
|
|
|
return this.Ok(new JValue(await WebhookSender.Redeliver(deliveryId)));
|
2020-11-13 06:01:51 +01:00
|
|
|
}
|
|
|
|
|
2023-05-28 16:44:10 +02:00
|
|
|
private IActionResult WebhookDeliveryPruned()
|
|
|
|
{
|
|
|
|
return this.CreateAPIError(409, "webhookdelivery-pruned", "This webhook delivery has been pruned, so it can't be redelivered");
|
|
|
|
}
|
|
|
|
|
2020-11-13 06:01:51 +01:00
|
|
|
[HttpGet("~/api/v1/stores/{storeId}/webhooks/{webhookId}/deliveries/{deliveryId}/request")]
|
2021-07-27 14:11:47 +02:00
|
|
|
public async Task<IActionResult> GetDeliveryRequest(string storeId, string webhookId, string deliveryId)
|
2020-11-13 06:01:51 +01:00
|
|
|
{
|
2021-01-05 04:38:12 +01:00
|
|
|
var delivery = await StoreRepository.GetWebhookDelivery(CurrentStoreId, webhookId, deliveryId);
|
2020-11-13 06:01:51 +01:00
|
|
|
if (delivery is null)
|
2021-04-27 08:38:42 +02:00
|
|
|
return WebhookDeliveryNotFound();
|
2023-05-28 16:44:10 +02:00
|
|
|
if (delivery.GetBlob().IsPruned())
|
|
|
|
return WebhookDeliveryPruned();
|
2020-11-13 06:01:51 +01:00
|
|
|
return File(delivery.GetBlob().Request, "application/json");
|
|
|
|
}
|
|
|
|
|
|
|
|
private Client.Models.WebhookDeliveryData FromModel(Data.WebhookDeliveryData data)
|
|
|
|
{
|
|
|
|
var b = data.GetBlob();
|
|
|
|
return new Client.Models.WebhookDeliveryData()
|
|
|
|
{
|
|
|
|
Id = data.Id,
|
|
|
|
Timestamp = data.Timestamp,
|
|
|
|
Status = b.Status,
|
|
|
|
ErrorMessage = b.ErrorMessage,
|
|
|
|
HttpCode = b.HttpCode
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
Client.Models.StoreWebhookData FromModel(Data.WebhookData data, bool includeSecret)
|
|
|
|
{
|
|
|
|
var b = data.GetBlob();
|
|
|
|
return new Client.Models.StoreWebhookData()
|
|
|
|
{
|
|
|
|
Id = data.Id,
|
|
|
|
Url = b.Url,
|
|
|
|
Enabled = b.Active,
|
|
|
|
Secret = includeSecret ? b.Secret : null,
|
|
|
|
AutomaticRedelivery = b.AutomaticRedelivery,
|
|
|
|
AuthorizedEvents = new Client.Models.StoreWebhookData.AuthorizedEventsData()
|
|
|
|
{
|
|
|
|
Everything = b.AuthorizedEvents.Everything,
|
|
|
|
SpecificEvents = b.AuthorizedEvents.SpecificEvents
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|