mirror of
https://github.com/aljazceru/BTCPayServerPlugins.git
synced 2025-12-17 15:44:26 +01:00
Data Erasure plugin
This commit is contained in:
@@ -49,6 +49,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BTCPayServer.Tests", "submo
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BTCPayServer.Plugins.Tests", "BTCPayServer.Plugins.Tests\BTCPayServer.Plugins.Tests.csproj", "{C6E671F6-5417-4F2F-A5A2-0A7D307BA1F7}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BTCPayServer.Plugins.DataErasure", "Plugins\BTCPayServer.Plugins.DataErasure\BTCPayServer.Plugins.DataErasure.csproj", "{034D1487-81C2-4250-A26E-162579C43C18}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -233,6 +235,14 @@ Global
|
||||
{C6E671F6-5417-4F2F-A5A2-0A7D307BA1F7}.Altcoins-Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C6E671F6-5417-4F2F-A5A2-0A7D307BA1F7}.Altcoins-Release|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{C6E671F6-5417-4F2F-A5A2-0A7D307BA1F7}.Altcoins-Release|Any CPU.Build.0 = Debug|Any CPU
|
||||
{034D1487-81C2-4250-A26E-162579C43C18}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{034D1487-81C2-4250-A26E-162579C43C18}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{034D1487-81C2-4250-A26E-162579C43C18}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{034D1487-81C2-4250-A26E-162579C43C18}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{034D1487-81C2-4250-A26E-162579C43C18}.Altcoins-Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{034D1487-81C2-4250-A26E-162579C43C18}.Altcoins-Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{034D1487-81C2-4250-A26E-162579C43C18}.Altcoins-Release|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{034D1487-81C2-4250-A26E-162579C43C18}.Altcoins-Release|Any CPU.Build.0 = Debug|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{B19C9F52-DC47-466D-8B5C-2D202B7B003F} = {9E04ECE9-E304-4FF2-9CBC-83256E6C6962}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Razor">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<LangVersion>10</LangVersion>
|
||||
</PropertyGroup>
|
||||
<!-- -->
|
||||
<!-- Plugin specific properties -->
|
||||
<PropertyGroup>
|
||||
<Product>Data Erasure</Product>
|
||||
<Description>Allows you to erase user data from invoices after a period of time.</Description>
|
||||
<Version>1.0.0</Version>
|
||||
</PropertyGroup>
|
||||
<!-- Plugin development properties -->
|
||||
<PropertyGroup>
|
||||
<AddRazorSupportForMvc>true</AddRazorSupportForMvc>
|
||||
<PreserveCompilationContext>false</PreserveCompilationContext>
|
||||
<GenerateEmbeddedFilesManifest>true</GenerateEmbeddedFilesManifest>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- This will make sure that referencing BTCPayServer doesn't put any artifact in the published directory -->
|
||||
<ItemDefinitionGroup>
|
||||
<ProjectReference>
|
||||
<Properties>StaticWebAssetsEnabled=false</Properties>
|
||||
<Private>false</Private>
|
||||
<ExcludeAssets>runtime;native;build;buildTransitive;contentFiles</ExcludeAssets>
|
||||
</ProjectReference>
|
||||
</ItemDefinitionGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Resources\**" />
|
||||
<ProjectReference Include="..\..\submodules\btcpayserver\BTCPayServer\BTCPayServer.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Resources\js" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,60 @@
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Client;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace BTCPayServer.Plugins.DataErasure
|
||||
{
|
||||
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
[Route("plugins/{storeId}/DataErasure")]
|
||||
public class DataErasureController : Controller
|
||||
{
|
||||
private readonly DataErasureService _dataErasureService;
|
||||
public DataErasureController(DataErasureService dataErasureService)
|
||||
{
|
||||
_dataErasureService = dataErasureService;
|
||||
}
|
||||
|
||||
[HttpGet("")]
|
||||
public async Task<IActionResult> Update(string storeId)
|
||||
{
|
||||
var vm = await _dataErasureService.Get(storeId) ?? new DataErasureSettings();
|
||||
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
[HttpPost("")]
|
||||
public async Task<IActionResult> Update(string storeId, DataErasureSettings vm,
|
||||
string command)
|
||||
{
|
||||
if (_dataErasureService.IsRunning)
|
||||
{
|
||||
TempData["ErrorMessage"] = "Data erasure is currently running and cannot be changed. Please try again later.";
|
||||
}
|
||||
|
||||
|
||||
if (vm.Enabled)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return View(vm);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
switch (command)
|
||||
{
|
||||
|
||||
case "save":
|
||||
await _dataErasureService.Set(storeId, vm);
|
||||
TempData["SuccessMessage"] = "Data erasure settings modified";
|
||||
return RedirectToAction(nameof(Update), new {storeId});
|
||||
|
||||
default:
|
||||
return View(vm);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using BTCPayServer.Abstractions.Contracts;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.Abstractions.Services;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace BTCPayServer.Plugins.DataErasure
|
||||
{
|
||||
public class DataErasurePlugin : BaseBTCPayServerPlugin
|
||||
{
|
||||
public override IBTCPayServerPlugin.PluginDependency[] Dependencies { get; } =
|
||||
{
|
||||
new() { Identifier = nameof(BTCPayServer), Condition = ">=1.8.0" }
|
||||
};
|
||||
public override void Execute(IServiceCollection applicationBuilder)
|
||||
{
|
||||
applicationBuilder.AddSingleton<DataErasureService>();
|
||||
applicationBuilder.AddHostedService( sp => sp.GetRequiredService<DataErasureService>());
|
||||
applicationBuilder.AddSingleton<IUIExtension>(new UIExtension("DataErasure/DataErasureNav",
|
||||
"store-integrations-nav"));
|
||||
base.Execute(applicationBuilder);
|
||||
}
|
||||
}
|
||||
}
|
||||
132
Plugins/BTCPayServer.Plugins.DataErasure/DataErasureService.cs
Normal file
132
Plugins/BTCPayServer.Plugins.DataErasure/DataErasureService.cs
Normal file
@@ -0,0 +1,132 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Contracts;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace BTCPayServer.Plugins.DataErasure
|
||||
{
|
||||
public class DataErasureService : IHostedService
|
||||
{
|
||||
private readonly IStoreRepository _storeRepository;
|
||||
private readonly ILogger<DataErasureService> _logger;
|
||||
private readonly InvoiceRepository _invoiceRepository;
|
||||
|
||||
public DataErasureService(IStoreRepository storeRepository, ILogger<DataErasureService> logger,
|
||||
InvoiceRepository invoiceRepository)
|
||||
{
|
||||
_storeRepository = storeRepository;
|
||||
_logger = logger;
|
||||
_invoiceRepository = invoiceRepository;
|
||||
}
|
||||
|
||||
public async Task<DataErasureSettings> Get(string storeId)
|
||||
{
|
||||
return await _storeRepository.GetSettingAsync<DataErasureSettings>(storeId,
|
||||
nameof(DataErasureSettings));
|
||||
}
|
||||
|
||||
public async Task Set(string storeId, DataErasureSettings settings)
|
||||
{
|
||||
await _runningLock.WaitAsync();
|
||||
var existing = await Get(storeId);
|
||||
settings.LastRunCutoff = existing?.LastRunCutoff;
|
||||
await SetCore(storeId, settings);
|
||||
_runningLock.Release();
|
||||
}
|
||||
|
||||
private async Task SetCore(string storeId, DataErasureSettings settings)
|
||||
{
|
||||
await _storeRepository.UpdateSetting(storeId, nameof(DataErasureSettings), settings);
|
||||
}
|
||||
|
||||
public bool IsRunning { get; private set; }
|
||||
private readonly SemaphoreSlim _runningLock = new(1, 1);
|
||||
|
||||
private async Task Run(CancellationToken cancellationToken)
|
||||
{
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
await _runningLock.WaitAsync(cancellationToken);
|
||||
IsRunning = true;
|
||||
|
||||
|
||||
var settings =
|
||||
await _storeRepository.GetSettingsAsync<DataErasureSettings>(nameof(DataErasureSettings));
|
||||
foreach (var setting in settings.Where(setting => setting.Value.Enabled))
|
||||
{
|
||||
var skip = 0;
|
||||
var count = 0;
|
||||
var cutoffDate = DateTimeOffset.UtcNow.Subtract(TimeSpan.FromDays(setting.Value.DaysToKeep));
|
||||
while (true)
|
||||
{
|
||||
var invoices = await _invoiceRepository.GetInvoices(new InvoiceQuery()
|
||||
{
|
||||
StartDate = setting.Value.LastRunCutoff,
|
||||
EndDate = cutoffDate,
|
||||
StoreId = new[] {setting.Key},
|
||||
Skip = skip,
|
||||
Take = 100
|
||||
});
|
||||
foreach (var invoice in invoices)
|
||||
{
|
||||
//replace all buyer info with "erased"
|
||||
if (!string.IsNullOrEmpty(invoice.Metadata.BuyerAddress1))
|
||||
invoice.Metadata.BuyerAddress1 = "erased";
|
||||
if (!string.IsNullOrEmpty(invoice.Metadata.BuyerAddress2))
|
||||
invoice.Metadata.BuyerAddress2 = "erased";
|
||||
if (!string.IsNullOrEmpty(invoice.Metadata.BuyerCity))
|
||||
invoice.Metadata.BuyerCity = "erased";
|
||||
if (!string.IsNullOrEmpty(invoice.Metadata.BuyerCountry))
|
||||
invoice.Metadata.BuyerCountry = "erased";
|
||||
if (!string.IsNullOrEmpty(invoice.Metadata.BuyerEmail))
|
||||
invoice.Metadata.BuyerEmail = "erased";
|
||||
if (!string.IsNullOrEmpty(invoice.Metadata.BuyerName))
|
||||
invoice.Metadata.BuyerName = "erased";
|
||||
if (!string.IsNullOrEmpty(invoice.Metadata.BuyerPhone))
|
||||
invoice.Metadata.BuyerPhone = "erased";
|
||||
if (!string.IsNullOrEmpty(invoice.Metadata.BuyerState))
|
||||
invoice.Metadata.BuyerState = "erased";
|
||||
if (!string.IsNullOrEmpty(invoice.Metadata.BuyerZip))
|
||||
invoice.Metadata.BuyerZip = "erased";
|
||||
await _invoiceRepository.UpdateInvoiceMetadata(invoice.Id, invoice.StoreId,
|
||||
invoice.Metadata.ToJObject());
|
||||
count++;
|
||||
}
|
||||
|
||||
if (invoices.Length < 100)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
skip += 100;
|
||||
}
|
||||
|
||||
_logger.LogInformation($"Erased {count} invoice data for store {setting.Key}");
|
||||
setting.Value.LastRunCutoff = cutoffDate;
|
||||
await SetCore(setting.Key, setting.Value);
|
||||
}
|
||||
|
||||
IsRunning = false;
|
||||
|
||||
|
||||
_runningLock.Release();
|
||||
await Task.Delay(TimeSpan.FromMinutes(1), cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_ = Run(cancellationToken);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
|
||||
namespace BTCPayServer.Plugins.DataErasure
|
||||
{
|
||||
public class DataErasureSettings
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
public int DaysToKeep { get; set; }
|
||||
public DateTimeOffset? LastRunCutoff { get; set; }
|
||||
}
|
||||
}
|
||||
2
Plugins/BTCPayServer.Plugins.DataErasure/Pack.ps1
Normal file
2
Plugins/BTCPayServer.Plugins.DataErasure/Pack.ps1
Normal file
@@ -0,0 +1,2 @@
|
||||
dotnet publish -c Release -o bin/publish/BTCPayServer.Plugins.DataErasure
|
||||
dotnet run -p ../../BTCPayServer.PluginPacker bin/publish/BTCPayServer.Plugins.DataErasure BTCPayServer.Plugins.DataErasure ../packed
|
||||
@@ -0,0 +1,38 @@
|
||||
@using BTCPayServer.Abstractions.Extensions
|
||||
@using Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
@model BTCPayServer.Plugins.DataErasure.DataErasureSettings
|
||||
@{
|
||||
ViewData.SetActivePage("DataErasure", "Data Erasure", "DataErasure");
|
||||
}
|
||||
|
||||
<partial name="_StatusMessage"/>
|
||||
|
||||
<h2 class="mb-4">@ViewData["Title"]</h2>
|
||||
|
||||
<div class="alert alert-warning">
|
||||
<p>
|
||||
This plugin erases buyer information on your store's invoices based on when they were created. THERE IS NO UNDOING THIS ACTION ONCE IT HAS EXECUTED.
|
||||
</p>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-10">
|
||||
<form method="post">
|
||||
<div class="form-group">
|
||||
<div class="d-flex align-items-center">
|
||||
<input asp-for="Enabled" type="checkbox" class="btcpay-toggle me-2"/>
|
||||
<label asp-for="Enabled" class="form-label mb-0 me-1"></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="DaysToKeep" class="form-label">Days to keep data</label>
|
||||
<input asp-for="DaysToKeep" type="number" class="form-control"/>
|
||||
|
||||
</div>
|
||||
@if (Model.LastRunCutoff != null)
|
||||
{
|
||||
<div>Cleared data up to @Model.LastRunCutoff.Value.ToString("g")</div>
|
||||
}
|
||||
<button name="command" type="submit" value="save" class="btn btn-primary">Submit</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,47 @@
|
||||
@using BTCPayServer.Abstractions.Contracts
|
||||
@using BTCPayServer.Abstractions.Extensions
|
||||
@using Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
@inject IScopeProvider ScopeProvider
|
||||
@{
|
||||
var storeId = ScopeProvider.GetCurrentStoreId();
|
||||
}
|
||||
@if (!string.IsNullOrEmpty(storeId))
|
||||
{
|
||||
<li class="nav-item">
|
||||
<a asp-area="" asp-controller="DataErasure" asp-action="Update" asp-route-storeId="@storeId" class="nav-link @ViewData.IsActivePage("DataErasure")" id="Nav-DataErasure">
|
||||
<svg
|
||||
style="margin: 5px"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 511.99999 511.99999"
|
||||
id="svg2"
|
||||
version="1.1">
|
||||
<g
|
||||
id="layer1"
|
||||
transform="translate(0,-540.36219)">
|
||||
<path
|
||||
stroke="currentColor"
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke-width:24;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
d="m 338.76367,578.92612 148.25878,123.01999 c 12.78743,10.61057 14.53806,29.44624 3.92517,42.23243 L 312.10496,959.64465 c -17.3194,15.8938 -96.44756,23.22811 -117.54045,2.99071 L 91.97017,876.25805 C 79.259029,865.55614 77.432098,846.81181 88.044982,834.02562 L 296.52482,582.85324 c 10.61288,-12.78619 29.45142,-14.53769 42.23885,-3.92712 z"
|
||||
id="rect4136" />
|
||||
<rect
|
||||
fill="currentColor"
|
||||
style="opacity:1;fill-opacity:1;stroke:none;stroke-width:16;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="rect4138"
|
||||
width="367.39352"
|
||||
height="25.711227"
|
||||
x="0.13772964"
|
||||
y="1027.0753" />
|
||||
<path
|
||||
fill="currentColor"
|
||||
style="opacity:1;fill-opacity:1;stroke:none;stroke-width:16;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
d="m 344.97498,582.49293 135.37673,115.03875 c 12.6466,10.74667 14.39592,29.76299 3.92221,42.6375 L 381.24318,866.81692 187.35744,725.83714 303.2416,586.33378 c 10.60501,-12.76652 29.08678,-14.58751 41.73338,-3.84085 z"
|
||||
id="rect4156"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
<span>Data Erasure</span>
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
@using BTCPayServer.Abstractions.Extensions
|
||||
@inject BTCPayServer.Abstractions.Services.Safe Safe
|
||||
@addTagHelper *, BTCPayServer.Abstractions
|
||||
@addTagHelper *, BTCPayServer.TagHelpers
|
||||
@addTagHelper *, BTCPayServer.Views.TagHelpers
|
||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
|
||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
@addTagHelper *, BTCPayServer
|
||||
Reference in New Issue
Block a user