Portal application - initial.
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Sarand.BusinessMgr.Net.Core.Web.Middleware
|
||||
{
|
||||
//=======================================================================
|
||||
// Authentication handler required to allow actions to issue Forbid() responses.
|
||||
// Ref. https://stackoverflow.com/questions/54578426/why-do-i-need-to-set-a-defaultforbidscheme/54587054#54587054
|
||||
//=======================================================================
|
||||
public class SbmAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
// Data members.
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
public SbmAuthenticationHandler(IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) :
|
||||
base(options, logger, encoder, clock)
|
||||
{
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
protected override Task HandleForbiddenAsync(AuthenticationProperties properties)
|
||||
{
|
||||
Context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
return base.HandleForbiddenAsync(properties);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
|
||||
{
|
||||
Context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
|
||||
return AuthenticateResult.NoResult();
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
}
|
||||
//=======================================================================
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Sarand.BusinessMgr.Net.Core.Web.Middleware
|
||||
{
|
||||
//=======================================================================
|
||||
/// <summary>
|
||||
/// Force blank form values to be populated to the model as empty strings rather than null values.
|
||||
/// </summary>
|
||||
public class SbmMetadataProvider : IMetadataDetailsProvider, IDisplayMetadataProvider
|
||||
{
|
||||
public void CreateDisplayMetadata(DisplayMetadataProviderContext context)
|
||||
{
|
||||
if (context.Key.MetadataKind == ModelMetadataKind.Property)
|
||||
{
|
||||
context.DisplayMetadata.ConvertEmptyStringToNull = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
//=======================================================================
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
|
||||
using Sarand.BusinessMgr.Net.Standard.Base.App;
|
||||
using Sarand.Net.Standard.Base.App;
|
||||
using Sarand.Net.Standard.Base.Utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Sarand.BusinessMgr.Net.Core.Web.Middleware
|
||||
{
|
||||
//=======================================================================
|
||||
/// <summary>
|
||||
/// Maps a browser request to a data object. Default class provides a base implementation of a model binder.
|
||||
/// <para>Overridden to trim whitespace from input values.</para>
|
||||
/// </summary>
|
||||
public class SbmModelBinder : ComplexTypeModelBinder
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
// Construction.
|
||||
public SbmModelBinder(IDictionary<ModelMetadata, IModelBinder> propertyBinders, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) :
|
||||
base(propertyBinders, loggerFactory)
|
||||
{
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
/// <summary>
|
||||
/// Creates the model with one (or more) injected service parameters.
|
||||
/// </summary>
|
||||
protected override object CreateModel(ModelBindingContext bindingContext)
|
||||
{
|
||||
var services = bindingContext.HttpContext.RequestServices;
|
||||
var modelType = bindingContext.ModelType;
|
||||
var ctors = modelType.GetConstructors();
|
||||
foreach (var ctor in ctors)
|
||||
{
|
||||
var paramTypes = ctor.GetParameters().Select(p => p.ParameterType).ToList();
|
||||
var parameters = paramTypes.Select(p => services.GetService(p)).ToArray();
|
||||
if (parameters.All(p => p != null))
|
||||
{
|
||||
var model = ctor.Invoke(parameters);
|
||||
return model;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
/// <summary>
|
||||
/// Intercept String properties and trim whitespace.
|
||||
/// </summary>
|
||||
protected override void SetProperty(ModelBindingContext bindingContext, string modelName, ModelMetadata propertyMetadata, ModelBindingResult result)
|
||||
{
|
||||
string fn = GetType().Name + ".SetProperty";
|
||||
//if (SbmSystem.DebugMode) Logger.Debug(fn + " Form property " + modelName + ": '" + result.Model.ToString() + ".");
|
||||
object originalValue = result.Model;
|
||||
// Exclude properties that retain leading/trailing whitespace.
|
||||
if (result.Model is string)
|
||||
{
|
||||
if (!modelName.ContainsCI("Password"))
|
||||
{
|
||||
string value = result.Model as string;
|
||||
|
||||
// Trim leading and trailing whitespace.
|
||||
value = StringUtil.TrimAll(value);
|
||||
|
||||
// Check for multi-line.
|
||||
if (value.Contains('\n'))
|
||||
{
|
||||
// Simplify line delimiters.
|
||||
value = Regex.Replace(value, "\\r\\n", "\n");
|
||||
// Remove white space in front of newlines.
|
||||
value = Regex.Replace(value, "\\s+\\n", "\n");
|
||||
// Remove multiple newlines.
|
||||
value = Regex.Replace(value, "\\n+", "\n");
|
||||
}
|
||||
|
||||
// Email addresses must be lowercase to ensure match to encrypted data.
|
||||
if (modelName.EqualsCI("EmailAddress") || modelName.EqualsCI("AccountEmailAddress"))
|
||||
value = value.ToLower();
|
||||
|
||||
// Uppercase selected values.
|
||||
if (modelName.EqualsCI("Postcode") || modelName.EqualsCI("AccountPostcode") || modelName.EqualsCI("RegisteredAddressPostcode"))
|
||||
value = value.ToUpper();
|
||||
|
||||
// Assign resulting value.
|
||||
result = ModelBindingResult.Success(value);
|
||||
}
|
||||
}
|
||||
if (result.Model != null && SbmSystem.DebugMode)
|
||||
SaLogger.Debug(fn + " Form property " + modelName + ": '" + result.Model.ToString() + "'." + (originalValue != null && !originalValue.Equals(result.Model) ? " Original value: '" + originalValue.ToString() + "'" : ""));
|
||||
base.SetProperty(bindingContext, modelName, propertyMetadata, result);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
}
|
||||
//=======================================================================
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Sarand.BusinessMgr.Net.Core.Web.Middleware
|
||||
{
|
||||
//=======================================================================
|
||||
/// <summary>
|
||||
/// Maps a browser request to a data object. Default class provides a base implementation of a model binder.
|
||||
/// <para>Overridden to trim whitespace from input values.</para>
|
||||
/// </summary>
|
||||
public class SbmModelBinderProvider : IModelBinderProvider
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
// Data members.
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Construction.
|
||||
public SbmModelBinderProvider()
|
||||
{
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
public IModelBinder GetBinder(ModelBinderProviderContext context)
|
||||
{
|
||||
if (context == null) throw new ArgumentNullException(nameof(context));
|
||||
|
||||
if (context.Metadata.IsComplexType && !context.Metadata.IsCollectionType)
|
||||
{
|
||||
var propertyBinders = context.Metadata.Properties.ToDictionary(property => property, context.CreateBinder);
|
||||
ILoggerFactory loggerFactory = (ILoggerFactory)context.Services.GetService(typeof(ILoggerFactory));
|
||||
return new SbmModelBinder(propertyBinders, loggerFactory);
|
||||
}
|
||||
|
||||
// Default - return null so that default binding provider is used.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
//=======================================================================
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
using Microsoft.AspNetCore.Antiforgery;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Sarand.BusinessMgr.Net.Standard.Base.App;
|
||||
using Sarand.Net.Standard.Base.App;
|
||||
using Sarand.Net.Standard.Base.Utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Sarand.BusinessMgr.Net.Core.Web.Middleware
|
||||
{
|
||||
//=======================================================================
|
||||
/// <summary>
|
||||
/// Extend IApplicationBuilder class to use the anit-forgery token validation middleware.
|
||||
/// </summary>
|
||||
public static class ApplicationBuilderExtensions
|
||||
{
|
||||
public static IApplicationBuilder UseAntiforgeryTokens(this IApplicationBuilder app)
|
||||
{
|
||||
return app.UseMiddleware<ValidateAntiForgeryTokenMiddleware>();
|
||||
}
|
||||
}
|
||||
|
||||
//=======================================================================
|
||||
/// <summary>
|
||||
/// Ensure relevant actions validate the anti-forgery token, eg. post requests.
|
||||
/// </summary>
|
||||
public class ValidateAntiForgeryTokenMiddleware
|
||||
{
|
||||
private readonly RequestDelegate _next;
|
||||
private readonly IAntiforgery _antiforgery;
|
||||
|
||||
public ValidateAntiForgeryTokenMiddleware(RequestDelegate next, IAntiforgery antiforgery)
|
||||
{
|
||||
_next = next;
|
||||
_antiforgery = antiforgery;
|
||||
}
|
||||
|
||||
public async Task Invoke(HttpContext context)
|
||||
{
|
||||
String fn = GetType().Name + ".Invoke";
|
||||
|
||||
// Log action.
|
||||
//String rawTarget = context.Features.Get<IHttpRequestFeature>().RawTarget;
|
||||
if (SbmSystem.DebugMode) SaLogger.Debug(fn + " Action: '" + Regex.Replace(context.Request.Path.ToString(), "^/", "") + "'. Method: " + context.Request.Method);
|
||||
|
||||
// Intercept POST requests and apply anti-forgery test.
|
||||
if (HttpMethods.IsPost(context.Request.Method))
|
||||
{
|
||||
await _antiforgery.ValidateRequestAsync(context);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await _next(context);
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
SaLogger.Warn(fn + " " + ExceptionUtil.GetLogMessage(exc));
|
||||
throw exc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//=======================================================================
|
||||
}
|
||||
Reference in New Issue
Block a user