From 11d6588249042132818c62720d86f0e963969335 Mon Sep 17 00:00:00 2001 From: Nicolas Dorier Date: Mon, 24 Jan 2022 20:00:13 +0900 Subject: [PATCH] Add suggestion list for currency inputs (#3347) * Move tagHelpers in their own directory * Add suggestion list for currency inputs --- BTCPayServer.Rating/CurrencyNameTable.cs | 2 + BTCPayServer/Security/PermissionTagHelper.cs | 51 ------ BTCPayServer/TagHelpers.cs | 168 ------------------ BTCPayServer/TagHelpers/CSPA.cs | 29 +++ BTCPayServer/TagHelpers/CSPEventTagHelper.cs | 37 ++++ .../TagHelpers/CSPInlineScriptTagHelper.cs | 33 ++++ BTCPayServer/TagHelpers/CSPTemplate.cs | 24 +++ .../CurrenciesSuggestionsTagHelper.cs | 44 +++++ .../TagHelpers/PermissionTagHelper.cs | 50 ++++++ BTCPayServer/TagHelpers/SVGUse.cs | 26 +++ BTCPayServer/TagHelpers/SrvModel.cs | 30 ++++ .../Views/UIApps/UpdatePointOfSale.cshtml | 2 +- .../Views/UIInvoice/CreateInvoice.cshtml | 2 +- .../UIStorePullPayments/NewPullPayment.cshtml | 4 +- BTCPayServer/Views/UIStores/Payment.cshtml | 2 +- 15 files changed, 280 insertions(+), 224 deletions(-) delete mode 100644 BTCPayServer/Security/PermissionTagHelper.cs delete mode 100644 BTCPayServer/TagHelpers.cs create mode 100644 BTCPayServer/TagHelpers/CSPA.cs create mode 100644 BTCPayServer/TagHelpers/CSPEventTagHelper.cs create mode 100644 BTCPayServer/TagHelpers/CSPInlineScriptTagHelper.cs create mode 100644 BTCPayServer/TagHelpers/CSPTemplate.cs create mode 100644 BTCPayServer/TagHelpers/CurrenciesSuggestionsTagHelper.cs create mode 100644 BTCPayServer/TagHelpers/PermissionTagHelper.cs create mode 100644 BTCPayServer/TagHelpers/SVGUse.cs create mode 100644 BTCPayServer/TagHelpers/SrvModel.cs diff --git a/BTCPayServer.Rating/CurrencyNameTable.cs b/BTCPayServer.Rating/CurrencyNameTable.cs index 7f2e8ddd8..309830d81 100644 --- a/BTCPayServer.Rating/CurrencyNameTable.cs +++ b/BTCPayServer.Rating/CurrencyNameTable.cs @@ -143,6 +143,8 @@ namespace BTCPayServer.Services.Rates return currencies; } + public IEnumerable Currencies => _Currencies.Values; + public CurrencyData GetCurrencyData(string currency, bool useFallback) { ArgumentNullException.ThrowIfNull(currency); diff --git a/BTCPayServer/Security/PermissionTagHelper.cs b/BTCPayServer/Security/PermissionTagHelper.cs deleted file mode 100644 index ad7f70e04..000000000 --- a/BTCPayServer/Security/PermissionTagHelper.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System.Threading.Tasks; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Razor.TagHelpers; -using Microsoft.Extensions.Logging; - -namespace BTCPayServer.Security -{ - [HtmlTargetElement(Attributes = nameof(Permission))] - public class PermissionTagHelper : TagHelper - { - private readonly IAuthorizationService _authorizationService; - private readonly IHttpContextAccessor _httpContextAccessor; - private readonly ILogger _logger; - - public PermissionTagHelper(IAuthorizationService authorizationService, IHttpContextAccessor httpContextAccessor, ILogger logger) - { - _authorizationService = authorizationService; - _httpContextAccessor = httpContextAccessor; - _logger = logger; - } - - public string Permission { get; set; } - public string PermissionResource { get; set; } - - public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) - { - if (string.IsNullOrEmpty(Permission)) - { - return; - } - - var key = $"{Permission}_{PermissionResource}"; - if (!_httpContextAccessor.HttpContext.Items.TryGetValue(key, out var cachedResult)) - { - var result = await _authorizationService.AuthorizeAsync(_httpContextAccessor.HttpContext.User, - PermissionResource, - Permission); - - cachedResult = result; - _httpContextAccessor.HttpContext.Items.Add(key, result); - - } - if (!((AuthorizationResult)cachedResult).Succeeded) - { - output.SuppressOutput(); - } - - } - } -} diff --git a/BTCPayServer/TagHelpers.cs b/BTCPayServer/TagHelpers.cs deleted file mode 100644 index 898752b60..000000000 --- a/BTCPayServer/TagHelpers.cs +++ /dev/null @@ -1,168 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Text; -using System.Text.Encodings.Web; -using System.Threading.Tasks; -using BTCPayServer.Configuration; -using BTCPayServer.Security; -using BTCPayServer.Services; -using Microsoft.AspNetCore.Mvc.Razor.TagHelpers; -using Microsoft.AspNetCore.Mvc.Rendering; -using Microsoft.AspNetCore.Mvc.Routing; -using Microsoft.AspNetCore.Mvc.ViewFeatures; -using Microsoft.AspNetCore.Razor.TagHelpers; -using NBitcoin; -using NBitcoin.Crypto; - -namespace BTCPayServer.TagHelpers -{ - [HtmlTargetElement("srv-model")] - public class SrvModel : TagHelper - { - private readonly Safe _safe; - private readonly ContentSecurityPolicies _csp; - - public SrvModel(Safe safe, ContentSecurityPolicies csp) - { - _safe = safe; - _csp = csp; - } - public string VarName { get; set; } = "srvModel"; - public object Model { get; set; } - public override void Process(TagHelperContext context, TagHelperOutput output) - { - output.TagName = "script"; - output.TagMode = TagMode.StartTagAndEndTag; - output.Attributes.Add(new TagHelperAttribute("type", "text/javascript")); - var nonce = RandomUtils.GetUInt256().ToString().Substring(0, 32); - output.Attributes.Add(new TagHelperAttribute("nonce", nonce)); - _csp.Add("script-src", $"'nonce-{nonce}'"); - output.Content.SetHtmlContent($"var {VarName} = {_safe.Json(Model)};"); - } - } - - /// - /// Add a nonce-* so the inline-script can pass CSP rule when they are rendered server-side - /// - [HtmlTargetElement("script")] - public class CSPInlineScriptTagHelper : TagHelper - { - private readonly ContentSecurityPolicies _csp; - - public CSPInlineScriptTagHelper(ContentSecurityPolicies csp) - { - _csp = csp; - } - - public override void Process(TagHelperContext context, TagHelperOutput output) - { - if (output.Attributes.ContainsName("src")) - return; - if (output.Attributes.TryGetAttribute("type", out var attr)) - { - if (attr.Value?.ToString() != "text/javascript") - return; - } - var nonce = RandomUtils.GetUInt256().ToString().Substring(0, 32); - output.Attributes.Add(new TagHelperAttribute("nonce", nonce)); - _csp.Add("script-src", $"'nonce-{nonce}'"); - } - } - - /// - /// Add 'unsafe-hashes' and sha256- to allow inline event handlers in CSP - /// - [HtmlTargetElement(Attributes = "onclick")] - [HtmlTargetElement(Attributes = "onkeypress")] - [HtmlTargetElement(Attributes = "onchange")] - [HtmlTargetElement(Attributes = "onsubmit")] - public class CSPEventTagHelper : TagHelper - { - public const string EventNames = "onclick,onkeypress,onchange,onsubmit"; - private readonly ContentSecurityPolicies _csp; - - readonly static HashSet EventSet = EventNames.Split(',') - .ToHashSet(); - public CSPEventTagHelper(ContentSecurityPolicies csp) - { - _csp = csp; - } - public override void Process(TagHelperContext context, TagHelperOutput output) - { - foreach (var attr in output.Attributes) - { - var n = attr.Name.ToLowerInvariant(); - if (EventSet.Contains(n)) - { - _csp.AllowUnsafeHashes(attr.Value.ToString()); - } - } - } - } - - - /// - /// Add sha256- to allow inline event handlers in CSP - /// - [HtmlTargetElement("template", Attributes = "csp-allow")] - public class CSPTemplate : TagHelper - { - private readonly ContentSecurityPolicies _csp; - public CSPTemplate(ContentSecurityPolicies csp) - { - _csp = csp; - } - public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) - { - output.Attributes.RemoveAll("csp-allow"); - var childContent = await output.GetChildContentAsync(); - var content = childContent.GetContent(); - _csp.AllowInline(content); - } - } - - /// - /// Add sha256- to allow inline event handlers in a:href=javascript: - /// - [HtmlTargetElement("a", Attributes = "csp-allow")] - public class CSPA : TagHelper - { - private readonly ContentSecurityPolicies _csp; - public CSPA(ContentSecurityPolicies csp) - { - _csp = csp; - } - public override void Process(TagHelperContext context, TagHelperOutput output) - { - output.Attributes.RemoveAll("csp-allow"); - if (output.Attributes.TryGetAttribute("href", out var attr)) - { - var v = attr.Value.ToString(); - if (v.StartsWith("javascript:", StringComparison.OrdinalIgnoreCase)) - { - _csp.AllowUnsafeHashes(v); - } - } - } - } - - // Make sure that +/// Add sha256- to allow inline event handlers in a:href=javascript: +/// +[HtmlTargetElement("a", Attributes = "csp-allow")] +public class CSPA : TagHelper +{ + private readonly ContentSecurityPolicies _csp; + public CSPA(ContentSecurityPolicies csp) + { + _csp = csp; + } + public override void Process(TagHelperContext context, TagHelperOutput output) + { + output.Attributes.RemoveAll("csp-allow"); + if (output.Attributes.TryGetAttribute("href", out var attr)) + { + var v = attr.Value.ToString(); + if (v.StartsWith("javascript:", StringComparison.OrdinalIgnoreCase)) + { + _csp.AllowUnsafeHashes(v); + } + } + } +} diff --git a/BTCPayServer/TagHelpers/CSPEventTagHelper.cs b/BTCPayServer/TagHelpers/CSPEventTagHelper.cs new file mode 100644 index 000000000..5eadbda3e --- /dev/null +++ b/BTCPayServer/TagHelpers/CSPEventTagHelper.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; +using System.Linq; +using BTCPayServer.Security; +using Microsoft.AspNetCore.Razor.TagHelpers; +namespace BTCPayServer.TagHelpers; + + +/// +/// Add 'unsafe-hashes' and sha256- to allow inline event handlers in CSP +/// +[HtmlTargetElement(Attributes = "onclick")] +[HtmlTargetElement(Attributes = "onkeypress")] +[HtmlTargetElement(Attributes = "onchange")] +[HtmlTargetElement(Attributes = "onsubmit")] +public class CSPEventTagHelper : TagHelper +{ + public const string EventNames = "onclick,onkeypress,onchange,onsubmit"; + private readonly ContentSecurityPolicies _csp; + + readonly static HashSet EventSet = EventNames.Split(',') + .ToHashSet(); + public CSPEventTagHelper(ContentSecurityPolicies csp) + { + _csp = csp; + } + public override void Process(TagHelperContext context, TagHelperOutput output) + { + foreach (var attr in output.Attributes) + { + var n = attr.Name.ToLowerInvariant(); + if (EventSet.Contains(n)) + { + _csp.AllowUnsafeHashes(attr.Value.ToString()); + } + } + } +} diff --git a/BTCPayServer/TagHelpers/CSPInlineScriptTagHelper.cs b/BTCPayServer/TagHelpers/CSPInlineScriptTagHelper.cs new file mode 100644 index 000000000..af6ae5023 --- /dev/null +++ b/BTCPayServer/TagHelpers/CSPInlineScriptTagHelper.cs @@ -0,0 +1,33 @@ +using BTCPayServer.Security; +using Microsoft.AspNetCore.Razor.TagHelpers; +using NBitcoin; +namespace BTCPayServer.TagHelpers; + + +/// +/// Add a nonce-* so the inline-script can pass CSP rule when they are rendered server-side +/// +[HtmlTargetElement("script")] +public class CSPInlineScriptTagHelper : TagHelper +{ + private readonly ContentSecurityPolicies _csp; + + public CSPInlineScriptTagHelper(ContentSecurityPolicies csp) + { + _csp = csp; + } + + public override void Process(TagHelperContext context, TagHelperOutput output) + { + if (output.Attributes.ContainsName("src")) + return; + if (output.Attributes.TryGetAttribute("type", out var attr)) + { + if (attr.Value?.ToString() != "text/javascript") + return; + } + var nonce = RandomUtils.GetUInt256().ToString().Substring(0, 32); + output.Attributes.Add(new TagHelperAttribute("nonce", nonce)); + _csp.Add("script-src", $"'nonce-{nonce}'"); + } +} diff --git a/BTCPayServer/TagHelpers/CSPTemplate.cs b/BTCPayServer/TagHelpers/CSPTemplate.cs new file mode 100644 index 000000000..81343cbe5 --- /dev/null +++ b/BTCPayServer/TagHelpers/CSPTemplate.cs @@ -0,0 +1,24 @@ +using System.Threading.Tasks; +using BTCPayServer.Security; +using Microsoft.AspNetCore.Razor.TagHelpers; +namespace BTCPayServer.TagHelpers; + +/// +/// Add sha256- to allow inline event handlers in CSP +/// +[HtmlTargetElement("template", Attributes = "csp-allow")] +public class CSPTemplate : TagHelper +{ + private readonly ContentSecurityPolicies _csp; + public CSPTemplate(ContentSecurityPolicies csp) + { + _csp = csp; + } + public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + output.Attributes.RemoveAll("csp-allow"); + var childContent = await output.GetChildContentAsync(); + var content = childContent.GetContent(); + _csp.AllowInline(content); + } +} diff --git a/BTCPayServer/TagHelpers/CurrenciesSuggestionsTagHelper.cs b/BTCPayServer/TagHelpers/CurrenciesSuggestionsTagHelper.cs new file mode 100644 index 000000000..fd65c93dd --- /dev/null +++ b/BTCPayServer/TagHelpers/CurrenciesSuggestionsTagHelper.cs @@ -0,0 +1,44 @@ +using BTCPayServer.Services.Rates; +using System.Linq; +using Microsoft.AspNetCore.Razor.TagHelpers; +using System.Collections.Generic; + +namespace BTCPayServer.TagHelpers +{ + [HtmlTargetElement("input", Attributes = "currency-selection")] + public class CurrenciesSuggestionsTagHelper : TagHelper + { + private readonly CurrencyNameTable _currencies; + + public CurrenciesSuggestionsTagHelper(CurrencyNameTable currencies) + { + _currencies = currencies; + } + public override void Process(TagHelperContext context, TagHelperOutput output) + { + output.Attributes.RemoveAll("currency-selection"); + output.PostElement.AppendHtml(""); + var currencies = _currencies.Currencies.Where(c => !c.Crypto).Select(c => c.Code).OrderBy(c => c).ToList(); + int pos = 0; + InsertAt(currencies, "BTC", pos++); + InsertAt(currencies, "SATS", pos++); + InsertAt(currencies, "USD", pos++); + InsertAt(currencies, "EUR", pos++); + InsertAt(currencies, "JPY", pos++); + InsertAt(currencies, "CNY", pos++); + foreach (var curr in currencies) + { + output.PostElement.AppendHtml($""); + output.Attributes.Add("list", "currency-selection-suggestion"); + base.Process(context, output); + } + + private void InsertAt(List currencies, string curr, int idx) + { + currencies.Remove(curr); + currencies.Insert(idx, curr); + } + } +} diff --git a/BTCPayServer/TagHelpers/PermissionTagHelper.cs b/BTCPayServer/TagHelpers/PermissionTagHelper.cs new file mode 100644 index 000000000..78dd684d8 --- /dev/null +++ b/BTCPayServer/TagHelpers/PermissionTagHelper.cs @@ -0,0 +1,50 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Razor.TagHelpers; +using Microsoft.Extensions.Logging; +namespace BTCPayServer.TagHelpers; + + +[HtmlTargetElement(Attributes = nameof(Permission))] +public class PermissionTagHelper : TagHelper +{ + private readonly IAuthorizationService _authorizationService; + private readonly IHttpContextAccessor _httpContextAccessor; + private readonly ILogger _logger; + + public PermissionTagHelper(IAuthorizationService authorizationService, IHttpContextAccessor httpContextAccessor, ILogger logger) + { + _authorizationService = authorizationService; + _httpContextAccessor = httpContextAccessor; + _logger = logger; + } + + public string Permission { get; set; } + public string PermissionResource { get; set; } + + public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + if (string.IsNullOrEmpty(Permission)) + { + return; + } + + var key = $"{Permission}_{PermissionResource}"; + if (!_httpContextAccessor.HttpContext.Items.TryGetValue(key, out var cachedResult)) + { + var result = await _authorizationService.AuthorizeAsync(_httpContextAccessor.HttpContext.User, + PermissionResource, + Permission); + + cachedResult = result; + _httpContextAccessor.HttpContext.Items.Add(key, result); + + } + if (!((AuthorizationResult)cachedResult).Succeeded) + { + output.SuppressOutput(); + } + + } +} diff --git a/BTCPayServer/TagHelpers/SVGUse.cs b/BTCPayServer/TagHelpers/SVGUse.cs new file mode 100644 index 000000000..994e0ce1d --- /dev/null +++ b/BTCPayServer/TagHelpers/SVGUse.cs @@ -0,0 +1,26 @@ +using System.Text.Encodings.Web; +using Microsoft.AspNetCore.Mvc.Razor.TagHelpers; +using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.AspNetCore.Mvc.Routing; +using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Microsoft.AspNetCore.Razor.TagHelpers; + +namespace BTCPayServer.TagHelpers; + +// Make sure that
- +
diff --git a/BTCPayServer/Views/UIInvoice/CreateInvoice.cshtml b/BTCPayServer/Views/UIInvoice/CreateInvoice.cshtml index 62c34875c..33fe73ecd 100644 --- a/BTCPayServer/Views/UIInvoice/CreateInvoice.cshtml +++ b/BTCPayServer/Views/UIInvoice/CreateInvoice.cshtml @@ -55,7 +55,7 @@
- +
diff --git a/BTCPayServer/Views/UIStorePullPayments/NewPullPayment.cshtml b/BTCPayServer/Views/UIStorePullPayments/NewPullPayment.cshtml index 0cffae547..4a3b23846 100644 --- a/BTCPayServer/Views/UIStorePullPayments/NewPullPayment.cshtml +++ b/BTCPayServer/Views/UIStorePullPayments/NewPullPayment.cshtml @@ -1,4 +1,4 @@ -@using BTCPayServer.Abstractions.Extensions +@using BTCPayServer.Abstractions.Extensions @using BTCPayServer.Views.Stores @model BTCPayServer.Models.WalletViewModels.NewPullPaymentModel @{ @@ -27,7 +27,7 @@
- +
diff --git a/BTCPayServer/Views/UIStores/Payment.cshtml b/BTCPayServer/Views/UIStores/Payment.cshtml index a2a95555e..8e827468a 100644 --- a/BTCPayServer/Views/UIStores/Payment.cshtml +++ b/BTCPayServer/Views/UIStores/Payment.cshtml @@ -14,7 +14,7 @@

Payment

- +