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