mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-02-20 13:34:37 +01:00
Make sure the form is properly validated
This commit is contained in:
parent
4f65eb4d65
commit
5ff1a59a99
6 changed files with 76 additions and 48 deletions
|
@ -2,6 +2,7 @@ using System;
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
|
@ -19,16 +20,22 @@ public class Field
|
|||
// If this is the first the user sees the form, then value and original value are the same. Value changes as the user starts interacting with the form.
|
||||
public string Value;
|
||||
|
||||
// The field is considered "valid" if there are no validation errors
|
||||
public List<string> ValidationErrors = new List<string>();
|
||||
|
||||
public virtual bool IsValid()
|
||||
{
|
||||
return ValidationErrors.Count == 0 && Fields.All(field => field.IsValid());
|
||||
}
|
||||
|
||||
public bool Required;
|
||||
[JsonExtensionData] public IDictionary<string, JToken> AdditionalData { get; set; }
|
||||
public List<Field> Fields { get; set; } = new();
|
||||
|
||||
|
||||
|
||||
public virtual void Validate(ModelStateDictionary modelState)
|
||||
{
|
||||
if (Required && string.IsNullOrEmpty(Value))
|
||||
{
|
||||
modelState.AddModelError(Name, "This field is required");
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsValid()
|
||||
{
|
||||
ModelStateDictionary modelState = new ModelStateDictionary();
|
||||
Validate(modelState);
|
||||
return modelState.IsValid;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Abstractions.Form;
|
||||
|
@ -28,7 +29,7 @@ public class Form
|
|||
// Are all the fields valid in the form?
|
||||
public bool IsValid()
|
||||
{
|
||||
return Fields.All(field => field.IsValid());
|
||||
return Validate(null);
|
||||
}
|
||||
|
||||
public Field GetFieldByName(string name)
|
||||
|
@ -63,6 +64,17 @@ public class Form
|
|||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
#nullable enable
|
||||
public bool Validate(ModelStateDictionary? modelState)
|
||||
{
|
||||
modelState ??= new ModelStateDictionary();
|
||||
foreach (var field in Fields)
|
||||
field.Validate(modelState);
|
||||
return modelState.IsValid;
|
||||
}
|
||||
#nullable restore
|
||||
|
||||
public List<string> GetAllNames()
|
||||
{
|
||||
return GetAllNames(Fields);
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
|
||||
namespace BTCPayServer.Abstractions.Form;
|
||||
|
||||
public class HtmlInputField : Field
|
||||
|
@ -11,7 +13,6 @@ public class HtmlInputField : Field
|
|||
// A useful note shown below the field or via a tooltip / info icon. Should be translated for the user.
|
||||
public string HelpText;
|
||||
|
||||
public bool Required;
|
||||
public HtmlInputField(string label, string name, string value, bool required, string helpText, string type = "text")
|
||||
{
|
||||
Label = label;
|
||||
|
@ -22,6 +23,5 @@ public class HtmlInputField : Field
|
|||
HelpText = helpText;
|
||||
Type = type;
|
||||
}
|
||||
|
||||
// TODO JSON parsing from string to objects again probably won't work out of the box because of the different field types.
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ using System.Threading;
|
|||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Abstractions.Form;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data;
|
||||
|
@ -181,7 +182,7 @@ namespace BTCPayServer.Controllers
|
|||
[HttpGet("{payReqId}/form")]
|
||||
[HttpPost("{payReqId}/form")]
|
||||
[AllowAnonymous]
|
||||
public async Task<IActionResult> ViewPaymentRequestForm(string payReqId, [FromForm] string formId, [FromForm] string formData)
|
||||
public async Task<IActionResult> ViewPaymentRequestForm(string payReqId)
|
||||
{
|
||||
var result = await _PaymentRequestRepository.FindPaymentRequest(payReqId, GetUserId());
|
||||
if (result == null)
|
||||
|
@ -195,32 +196,37 @@ namespace BTCPayServer.Controllers
|
|||
{
|
||||
case null:
|
||||
case { } when string.IsNullOrEmpty(prFormId):
|
||||
break;
|
||||
|
||||
case { } when Request.Method == "GET" && prBlob.FormResponse is not null:
|
||||
return RedirectToAction("ViewPaymentRequest", new { payReqId });
|
||||
case { } when Request.Method == "GET" && prBlob.FormResponse is null:
|
||||
break;
|
||||
default:
|
||||
// POST case: Handle form submit
|
||||
if (!string.IsNullOrEmpty(formData) && formId == prFormId)
|
||||
var formData = Form.Parse(Forms.UIFormsController.GetFormData(prFormId).Config);
|
||||
formData.ApplyValuesFromForm(this.Request.Form);
|
||||
if (formData.IsValid())
|
||||
{
|
||||
prBlob.FormResponse = JObject.Parse(formData);
|
||||
prBlob.FormResponse = JObject.FromObject(formData.GetValues());
|
||||
result.SetBlob(prBlob);
|
||||
await _PaymentRequestRepository.CreateOrUpdatePaymentRequest(result);
|
||||
return RedirectToAction("PayPaymentRequest", new { payReqId });
|
||||
}
|
||||
|
||||
// GET or empty form data case: Redirect to form
|
||||
return View("PostRedirect", new PostRedirectViewModel
|
||||
{
|
||||
AspController = "UIForms",
|
||||
AspAction = "ViewPublicForm",
|
||||
FormParameters =
|
||||
break;
|
||||
}
|
||||
|
||||
return View("PostRedirect", new PostRedirectViewModel
|
||||
{
|
||||
AspController = "UIForms",
|
||||
AspAction = "ViewPublicForm",
|
||||
RouteParameters =
|
||||
{
|
||||
{ "formId", prFormId }
|
||||
},
|
||||
FormParameters =
|
||||
{
|
||||
{ "formId", prFormId },
|
||||
{ "redirectUrl", Request.GetCurrentUrl() }
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return RedirectToAction("ViewPaymentRequest", new { payReqId });
|
||||
});
|
||||
}
|
||||
|
||||
[HttpGet("{payReqId}/pay")]
|
||||
|
|
|
@ -45,38 +45,36 @@ public class UIFormsController : Controller
|
|||
[AllowAnonymous]
|
||||
[HttpPost("~/forms/{formId}")]
|
||||
public IActionResult SubmitForm(
|
||||
string formId, string? redirectUrl,
|
||||
string formId,
|
||||
string? redirectUrl,
|
||||
[FromServices] StoreRepository storeRepository,
|
||||
[FromServices] UIInvoiceController invoiceController)
|
||||
{
|
||||
var formData = GetFormData(formId);
|
||||
if (formData?.Config is null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
var conf = Form.Parse(formData.Config);
|
||||
conf.ApplyValuesFromForm(Request.Form);
|
||||
if (!conf.Validate(ModelState))
|
||||
return View("View", new FormViewModel() { FormData = formData, RedirectUrl = redirectUrl });
|
||||
|
||||
var dbForm = Form.Parse(formData.Config);
|
||||
dbForm.ApplyValuesFromForm(Request.Form);
|
||||
Dictionary<string, object> data = dbForm.GetValues();
|
||||
|
||||
var form = new MultiValueDictionary<string, string>();
|
||||
foreach (var kv in Request.Form)
|
||||
form.Add(kv.Key, kv.Value);
|
||||
// With redirect, the form comes from another entity that we need to send the data back to
|
||||
if (!string.IsNullOrEmpty(redirectUrl))
|
||||
{
|
||||
return View("PostRedirect", new PostRedirectViewModel
|
||||
{
|
||||
FormUrl = redirectUrl,
|
||||
FormParameters =
|
||||
{
|
||||
{ "formId", formId },
|
||||
{ "formData", JsonConvert.SerializeObject(data) }
|
||||
}
|
||||
FormParameters = form
|
||||
});
|
||||
}
|
||||
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
private FormData? GetFormData(string id)
|
||||
internal static FormData? GetFormData(string id)
|
||||
{
|
||||
FormData? form = id switch
|
||||
{
|
||||
|
|
|
@ -9,6 +9,7 @@ using System.Threading;
|
|||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Abstractions.Form;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Controllers;
|
||||
|
@ -118,8 +119,6 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
|
|||
string notificationUrl,
|
||||
string redirectUrl,
|
||||
string choiceKey,
|
||||
string formId = null,
|
||||
string formData = null,
|
||||
string posData = null,
|
||||
RequiresRefundEmail requiresRefundEmail = RequiresRefundEmail.InheritFromStore,
|
||||
CancellationToken cancellationToken = default)
|
||||
|
@ -230,9 +229,12 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
|
|||
|
||||
default:
|
||||
// POST case: Handle form submit
|
||||
if (!string.IsNullOrEmpty(formData) && formId == posFormId)
|
||||
var formData = Form.Parse(Forms.UIFormsController.GetFormData(posFormId).Config);
|
||||
formData.ApplyValuesFromForm(this.Request.Form);
|
||||
|
||||
if (formData.IsValid())
|
||||
{
|
||||
formResponse = JObject.Parse(formData);
|
||||
formResponse = JObject.FromObject(formData.GetValues());
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -247,9 +249,12 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
|
|||
{
|
||||
AspController = "UIForms",
|
||||
AspAction = "ViewPublicForm",
|
||||
RouteParameters =
|
||||
{
|
||||
{ "formId", posFormId }
|
||||
},
|
||||
FormParameters =
|
||||
{
|
||||
{ "formId", posFormId },
|
||||
{ "redirectUrl", Request.GetCurrentUrl() + query }
|
||||
}
|
||||
});
|
||||
|
|
Loading…
Add table
Reference in a new issue