Data Erasure plugin

This commit is contained in:
Kukks
2023-03-22 14:45:42 +01:00
parent 75337f364e
commit 37d68f1b3b
10 changed files with 373 additions and 0 deletions

View File

@@ -49,6 +49,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BTCPayServer.Tests", "submo
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BTCPayServer.Plugins.Tests", "BTCPayServer.Plugins.Tests\BTCPayServer.Plugins.Tests.csproj", "{C6E671F6-5417-4F2F-A5A2-0A7D307BA1F7}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BTCPayServer.Plugins.Tests", "BTCPayServer.Plugins.Tests\BTCPayServer.Plugins.Tests.csproj", "{C6E671F6-5417-4F2F-A5A2-0A7D307BA1F7}"
EndProject 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 Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU 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-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.ActiveCfg = Debug|Any CPU
{C6E671F6-5417-4F2F-A5A2-0A7D307BA1F7}.Altcoins-Release|Any CPU.Build.0 = 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 EndGlobalSection
GlobalSection(NestedProjects) = preSolution GlobalSection(NestedProjects) = preSolution
{B19C9F52-DC47-466D-8B5C-2D202B7B003F} = {9E04ECE9-E304-4FF2-9CBC-83256E6C6962} {B19C9F52-DC47-466D-8B5C-2D202B7B003F} = {9E04ECE9-E304-4FF2-9CBC-83256E6C6962}

View File

@@ -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>

View File

@@ -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);
}
}
}
}

View File

@@ -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);
}
}
}

View 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;
}
}
}

View File

@@ -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; }
}
}

View 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

View File

@@ -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>

View File

@@ -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>
}

View File

@@ -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