Web app logon.

This commit is contained in:
Andy Howard
2024-06-04 16:16:05 +01:00
parent f5e0b925fb
commit 0c44e9ddd3
70 changed files with 2624 additions and 1331 deletions
+18 -18
View File
@@ -1,25 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<RootNamespace>Sarand.BusinessMgr.Net.Core</RootNamespace>
</PropertyGroup>
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<RootNamespace>Sarand.BusinessMgr.Net.Core</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="3.1.16" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="6.0.31" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\Common-VS\Workspace\SacsCoreWeb\SacsCoreWeb.csproj" />
<ProjectReference Include="..\..\..\Common-VS\Workspace\SacsStandardBase\SacsStandardBase.csproj" />
<ProjectReference Include="..\..\..\Common-VS\Workspace\SacsStandardDbas\SacsStandardDbas.csproj" />
<ProjectReference Include="..\..\..\Common-VS\Workspace\SacsStandardProc\SacsStandardProc.csproj" />
<ProjectReference Include="..\SaBusMgrStandardBase\SaBusMgrStandardBase.csproj" />
<ProjectReference Include="..\SaBusMgrStandardDbas\SaBusMgrStandardDbas.csproj" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\Common-VS\Workspace\SacsCoreWeb\SacsCoreWeb.csproj" />
<ProjectReference Include="..\..\..\Common-VS\Workspace\SacsStandardBase\SacsStandardBase.csproj" />
<ProjectReference Include="..\..\..\Common-VS\Workspace\SacsStandardDbas\SacsStandardDbas.csproj" />
<ProjectReference Include="..\..\..\Common-VS\Workspace\SacsStandardProc\SacsStandardProc.csproj" />
<ProjectReference Include="..\SaBusMgrStandardBase\SaBusMgrStandardBase.csproj" />
<ProjectReference Include="..\SaBusMgrStandardDbas\SaBusMgrStandardDbas.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Web\Core\" />
</ItemGroup>
<ItemGroup>
<Folder Include="Web\Core\" />
</ItemGroup>
</Project>
+67 -34
View File
@@ -38,61 +38,87 @@ namespace Sarand.BusinessMgr.Net.Core.Web.App
if (configSection != null)
{
// Environments.
foreach (ConfigurationSection environment in configSection.GetSection("Environments").GetChildren())
environments.Add(StringUtil.TrimAll(environment.Value));
foreach (ConfigurationSection environmentSection in configSection.GetSection("Environments").GetChildren())
environments.Add(StringUtil.TrimAll(environmentSection.Value));
Instance = StringUtil.OnEmpty(configSection["Instance"], Instance);
// Security.
IConfigurationSection security = configSection.GetSection("Security");
if (security.Exists())
IConfigurationSection securitySection = configSection.GetSection("Security");
if (securitySection.Exists())
{
// Security - Password.
IConfigurationSection securityPassword = security.GetSection("Password");
if (securityPassword.Exists())
IConfigurationSection securityPasswordSection = securitySection.GetSection("Password");
if (securityPasswordSection.Exists())
{
Security.Password.MinLength = StringUtil.ToInt(securityPassword["MinLength"], Security.Password.MinLength);
Security.Password.MinLength = StringUtil.ToInt(securityPasswordSection["MinLength"], Security.Password.MinLength);
}
// Security - Sign-Up.
IConfigurationSection securitySignUp = security.GetSection("SignUp");
if (securitySignUp.Exists())
IConfigurationSection securitySignUpSection = securitySection.GetSection("SignUp");
if (securitySignUpSection.Exists())
{
}
// Security - Sign-In.
IConfigurationSection securitySignIn = security.GetSection("SignIn");
if (securitySignIn.Exists())
IConfigurationSection securitySignInSection = securitySection.GetSection("SignIn");
if (securitySignInSection.Exists())
{
IConfigurationSection keepLoggedIn = securitySignIn.GetSection("SignIn");
if (keepLoggedIn.Exists())
IConfigurationSection keepLoggedInSection = securitySignInSection.GetSection("SignIn");
if (keepLoggedInSection.Exists())
{
Security.SignIn.KeepLoggedIn.Allowed = StringUtil.ToBool(StringUtil.OnEmpty(keepLoggedIn["Allowed"], Security.SignIn.KeepLoggedIn.Allowed.ToString()));
Security.SignIn.KeepLoggedIn.CookieExpiresDays = StringUtil.ToInt(keepLoggedIn["CookieExpiresDays"], Security.SignIn.KeepLoggedIn.CookieExpiresDays);
Security.SignIn.KeepLoggedIn.Allowed = StringUtil.ToBool(StringUtil.OnEmpty(keepLoggedInSection["Allowed"], Security.SignIn.KeepLoggedIn.Allowed.ToString()));
Security.SignIn.KeepLoggedIn.CookieExpiresDays = StringUtil.ToInt(keepLoggedInSection["CookieExpiresDays"], Security.SignIn.KeepLoggedIn.CookieExpiresDays);
}
}
}
// Email.
IConfigurationSection email = configSection.GetSection("Email");
if (email.Exists())
IConfigurationSection emailSection = configSection.GetSection("Email");
if (emailSection.Exists())
{
Email.Context = email["Context"];
PopulateEmailConfig(Email.ContactUs, email.GetSection("ContactUs"));
PopulateEmailConfig(Email.Signup_Activate, email.GetSection("Signup").GetSection("Activate"));
PopulateEmailConfig(Email.Signup_Welcome, email.GetSection("Signup").GetSection("Welcome"));
Email.Context = emailSection["Context"];
PopulateEmailConfig(Email.ContactUs, emailSection.GetSection("ContactUs"));
PopulateEmailConfig(Email.Signup_Activate, emailSection.GetSection("Signup").GetSection("Activate"));
PopulateEmailConfig(Email.Signup_Welcome, emailSection.GetSection("Signup").GetSection("Welcome"));
}
// Documents.
IConfigurationSection documents = configSection.GetSection("Documents");
if (documents.Exists())
IConfigurationSection documentsSection = configSection.GetSection("Documents");
if (documentsSection.Exists())
{
Documents.RootFolder = StringUtil.OnEmpty(documents["RootFolder"], Documents.RootFolder);
Documents.RootFolder = StringUtil.OnEmpty(documentsSection["RootFolder"], Documents.RootFolder);
}
// Logging.
IConfigurationSection logging = configSection.GetSection("Logging");
if (logging.Exists())
IConfigurationSection loggingSection = configSection.GetSection("Logging");
if (loggingSection.Exists())
{
Logging.RootFolder = StringUtil.OnEmpty(logging["RootFolder"], Logging.RootFolder);
Logging.RootFolder = StringUtil.OnEmpty(loggingSection["RootFolder"], Logging.RootFolder);
}
// Databases.
extractDatabaseConfig(ControlDatabase, configSection.GetSection("ControlDatabase"));
extractDatabaseConfig(BusMgrDatabase, configSection.GetSection("BusMgrDatabase"));
extractDatabaseConfig(TemplateDatabase, configSection.GetSection("TemplateDatabase"));
// Control database.
IConfigurationSection controlDatabaseSection = configSection.GetSection("ControlDatabase");
if (controlDatabaseSection.Exists())
{
ControlDatabase = new DatabaseConfig();
extractDatabaseConfig(ControlDatabase, controlDatabaseSection);
}
// Template database.
IConfigurationSection templateDatabaseSection = configSection.GetSection("TemplateDatabase");
if (templateDatabaseSection.Exists())
{
TemplateDatabase = new DatabaseConfig();
extractDatabaseConfig(TemplateDatabase, templateDatabaseSection);
}
// BusinessMgr databases.
IConfigurationSection busMgrDatabasesSection = configSection.GetSection("BusMgrDatabases");
if (busMgrDatabasesSection.Exists())
{
foreach (IConfigurationSection busMgrDbSection in busMgrDatabasesSection.GetChildren())
{
SbmAppConfig.DatabaseConfig dbConfig = new SbmAppConfig.DatabaseConfig();
extractDatabaseConfig(dbConfig, busMgrDbSection);
BusMgrDatabases.Add(dbConfig);
}
}
// Variants.
foreach (ConfigurationSection variantSection in configSection.GetSection("Variants").GetChildren())
{
@@ -117,6 +143,12 @@ namespace Sarand.BusinessMgr.Net.Core.Web.App
private void extractDatabaseConfig(DatabaseConfig database, IConfigurationSection databaseSection)
{
if (!databaseSection.Exists()) return;
IConfigurationSection identifiersSection = databaseSection.GetSection("Identifiers");
if (identifiersSection.Exists())
{
foreach (var identifier in identifiersSection.GetChildren())
database.Identifiers.Add(StringUtil.TrimAll(identifier.Value));
}
database.Location = StringUtil.OnEmpty(databaseSection["Location"], database.Location);
database.User = StringUtil.OnEmpty(databaseSection["User"], database.User);
database.Password = StringUtil.OnEmpty(databaseSection["Password"], database.Password);
@@ -179,13 +211,14 @@ namespace Sarand.BusinessMgr.Net.Core.Web.App
// Databases.
public class DatabaseConfig
{
public List<String> Identifiers { get; } = new List<string>();
public String Location { get; internal set; }
public String User { get; internal set; }
public String Password { get; internal set; }
}
public DatabaseConfig ControlDatabase { get; } = new DatabaseConfig();
public DatabaseConfig BusMgrDatabase { get; } = new DatabaseConfig();
public DatabaseConfig TemplateDatabase { get; } = new DatabaseConfig();
public DatabaseConfig ControlDatabase { get; private set; }
public DatabaseConfig TemplateDatabase { get; private set; }
public List<DatabaseConfig> BusMgrDatabases { get; } = new List<DatabaseConfig>();
// Documents.
public class DocumentsConfig
{
@@ -68,7 +68,8 @@ namespace Sarand.BusinessMgr.Net.Core.Web.App
applicationParams.Add("Configuration", SbmSystem.DebugMode ? "Debug" : "Release");
applicationParams.Add("Environment", EnvironmentName);
applicationParams.Add("Control database", AppConfig.ControlDatabase.Location);
applicationParams.Add("BusMgr database", AppConfig.BusMgrDatabase.Location);
foreach (SbmAppConfig.DatabaseConfig dbConfig in AppConfig.BusMgrDatabases)
applicationParams.Add("BusMgr database", (dbConfig.Identifiers.Any() ? $"[{String.Join(",", dbConfig.Identifiers)}] " : "") + dbConfig.Location);
applicationParams.Add("Application root", SbmSystem.ApplicationFolder);
if (!StringUtil.IsEmpty(AppConfig.Documents.RootFolder))
applicationParams.Add("Documents root", AppConfig.Documents.RootFolder);
+15 -9
View File
@@ -20,16 +20,22 @@ namespace Sarand.BusinessMgr.Net.Core.Web.App
{
securityProfile = new SbmSecurityProfile(this, false);
}
// Protected construction.
protected SbmSessionData_Web(SbmSessionContext sessionContext, SaSecurityItem securityItemUser, SbmAffiliate affiliateUser, List<SysIdString> supervisedOrganisations, SbmAssociation associationOrganisation, SbmAffiliate affiliateOrganisation) :
base(sessionContext, securityItemUser, affiliateUser, supervisedOrganisations, associationOrganisation, affiliateOrganisation)
{
securityProfile = new SbmSecurityProfile(this, false);
}
// Protected construction.
protected SbmSessionData_Web(SbmSessionContext sessionContext) :
base(sessionContext)
{
securityProfile = new SbmSecurityProfile(this, false);
}
// Protected construction.
protected SbmSessionData_Web(SbmSessionContext sessionContext, SaSecurityItem securityItemUser, SbmAffiliate affiliateUser, List<SysIdString> supervisedOrganisations, SbmAssociation associationOrganisation, SbmAffiliate affiliateOrganisation) :
base(sessionContext, securityItemUser, affiliateUser, supervisedOrganisations, associationOrganisation, affiliateOrganisation)
{
securityProfile = new SbmSecurityProfile(this, false);
}
//-------------------------------------------------------------------
// Security Profile.
private readonly SbmSecurityProfile securityProfile;
//-------------------------------------------------------------------
// Security Profile.
private readonly SbmSecurityProfile securityProfile;
public ISbmSecurityProfile SecurityProfile { get { return securityProfile; } }
//-------------------------------------------------------------------
@@ -9,7 +9,7 @@ namespace Sarand.BusinessMgr.Net.Core.Web.App
public class SbmUserContext_Web : SbmUserContext
{
//-------------------------------------------------------------------
// Data memebrs.
// Data members.
//-------------------------------------------------------------------
// Construction.
@@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Routing;
using Microsoft.Net.Http.Headers;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using Sacs.Web.Components;
using Sarand.BusinessMgr.Net.Core.Web.App;
using Sarand.BusinessMgr.Net.Core.Web.Models;
using Sarand.BusinessMgr.Net.Standard.Base.App;
@@ -40,7 +41,7 @@ using System.Web;
namespace Sarand.BusinessMgr.Net.Core.Web.Controllers
{
//=======================================================================
public abstract class SbmController_Base : Controller
public abstract class SbmController_Base : SaController
{
//-------------------------------------------------------------------
// Data members.
@@ -67,27 +68,11 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Controllers
//-------------------------------------------------------------------
// Properties.
public bool DebugMode { get; } = SacsSystem.DebugMode;
protected SbmApplication_Web App { get { return SbmGlobals_Web.Application; } }
public SbmAppConfig AppConfig { get { return App.AppConfig; } }
public SbmSessionContext SessionContext { get { return SessionData?.SessionContext ?? null; } }
public ISbmUserContext UserContext { get { return SessionData; } }
protected SbmLocaliser Localiser { get; }
/// <summary>
/// Indicate whether session data should be stored by action.
/// <para>Any action that might modify the session data must store it.</para>
/// <para>Some actions such as scoring a password's complexity don't need to.</para>
/// </summary>
public virtual bool StoreSessionData { get; protected set; } = true;
// Culture.
private string LanguageIso2
{
get
{
var feature = HttpContext.Features.Get<IRequestCultureFeature>();
return feature.RequestCulture.UICulture.TwoLetterISOLanguageName.ToLower();
}
}
// Business components.
public SbmInternals Internals { get { if (internals == null) internals = new SbmInternals(UserContext); return internals; } }
// Managers.
@@ -119,36 +104,6 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Controllers
public SaEmailConfig EmailConfig { get { return Internals.EmailConfig; } }
public SbmEmailConsole EmailConsole { get { return Internals.EmailConsole; } }
//-------------------------------------------------------------------
// Redirection locations.
//-------------------------------------------------------------------
public static readonly String ParamName_SessionExpired = "SessionExpired";
protected enum RedirectLocation
{
WebAppBase,
WebAppHome,
WebAppHome_SessionExpired,
}
protected String GetRedirectLocation(RedirectLocation redirectLocation)
{
string protocol = Request.Scheme;
var parameters = new RouteValueDictionary();
String homeAction = Url.Action("Index", "Home", parameters, protocol);
switch (redirectLocation)
{
case RedirectLocation.WebAppBase:
return Regex.Replace(homeAction, "\\/$", "") + "/";
case RedirectLocation.WebAppHome:
return homeAction;
case RedirectLocation.WebAppHome_SessionExpired:
// Set the session expired parameter so that the page can report it.
// NB Paramater must have a value to be included in the URL query string.
parameters.Add(ParamName_SessionExpired, "1");
return homeAction;
}
return "";
}
//-------------------------------------------------------------------
// Action execution events.
//-------------------------------------------------------------------
@@ -205,6 +160,7 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Controllers
if (model == null) return;
// Session data.
model.Controller = this;
model.SessionData_Web = SessionData;
// Redirect locations.
@@ -213,73 +169,6 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Controllers
model.RedirectLocation.Home_SessionExpired = GetRedirectLocation(RedirectLocation.WebAppHome_SessionExpired);
}
//-------------------------------------------------------------------
// Parameters.
//-------------------------------------------------------------------
public String EncryptParameters(NameValueCollection parameters, String base64IV, String base64Key)
{
String fn = nameof(ControllerBase) + ".EncryptParameters";
try
{
// Copy the list of parameters, adding a random name/value pair to randomise the encryption.
NameValueCollection parametersToEncrypt = new NameValueCollection(parameters);
// NB Range 10-189 intended to have equal chance of 2 or 3 digit number.
parametersToEncrypt.Add("!" + SacsRandomHelper.GetInt32(10, 190).ToString(), SacsRandomHelper.GetInt32(10, 190).ToString());
// Sort the keys to ensure that the random '!' param is first in the query string.
String[] paramKeys = parametersToEncrypt.AllKeys;
Array.Sort(paramKeys);
// Serialise parameters as if in a URL query string - so that they can be deserialised in DecryptParameters() below.
string queryString = String.Join("&", paramKeys.Select(a => a + "=" + HttpUtility.UrlEncode(parametersToEncrypt[a])));
String encrypted = SacsEncryptionHelper.Encrypt(queryString, base64IV, base64Key);
if (DebugMode)
{
SaLogger.Debug(fn + " Encrypted: " + encrypted);
NameValueCollection decryptedParams = DecryptParameters(encrypted, base64IV, base64Key);
}
return encrypted;
}
catch (Exception exc) { SaLogger.Warn(fn + " " + ExceptionUtil.GetErrorStateMessage(exc)); }
return "";
}
public NameValueCollection DecryptParameters(String encrypted, String base64IV, String base64Key)
{
String fn = nameof(ControllerBase) + ".DecryptParameters";
if (DebugMode) SaLogger.Debug(fn + " \n\tEncrypted: " + encrypted + "\n\tIV: " + base64IV + "\n\tKey: " + base64Key);
try
{
String queryString = SacsEncryptionHelper.Decrypt(encrypted, base64IV, base64Key);
return HttpUtility.ParseQueryString(queryString);
}
catch (Exception exc) { SaLogger.Warn(fn + " " + ExceptionUtil.GetErrorStateMessage(exc)); }
return new NameValueCollection();
}
//-------------------------------------------------------------------
// Cookies.
//-------------------------------------------------------------------
/// <summary>Retrieve the value from the named request cookie.</summary>
public String GetRequestCookie(String key)
{
if (Request.Cookies.ContainsKey(key))
return Request.Cookies[key];
return null;
}
/// <summary>Set the value against the named response cookie.</summary>
public void SetResponseCookie(string key, string value, DateTime? expiresAt = null, bool isEssential = false)
{
CookieOptions options = new CookieOptions();
options.HttpOnly = true;
options.Secure = true;
options.SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Strict;
if (expiresAt.HasValue)
options.Expires = expiresAt;
if (isEssential)
options.IsEssential = true;
Response.Cookies.Append(key, value, options);
}
//-------------------------------------------------------------------
// Session.
//-------------------------------------------------------------------
@@ -366,193 +255,6 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Controllers
}
protected abstract SbmSessionData_Web DeserializeSessionData(String sessionDataJson);
//-------------------------------------------------------------------
// Ajax actions.
//-------------------------------------------------------------------
protected static readonly Encoding ajaxResponseEncoding = Encoding.UTF8;
protected IActionResult GetAjaxResult_GoHome(AjaxResponse_Base responseData)
{
responseData.SetGoHome();
return Json(responseData);
}
protected IActionResult GetAjaxResult_InvalidSession(AjaxResponse_Base responseData)
{
responseData.SetGoHome();
return Json(responseData);
}
//-------------------------------------------------------------------
// Base Ajax Response Data.
//-------------------------------------------------------------------
public class AjaxResponse_Base
{
// Response status.
public bool? Success { get; set; } = null;
public bool? Error { get; set; } = null;
public bool? SessionExpired { get; protected set; } = null;
public bool? GoHome { get; protected set; } = null;
public AjaxResponse_Base SetSessionExpired(String title, String text)
{
SessionExpired = true;
return SetMessage(title, text);
}
public AjaxResponse_Base SetGoHome()
{
GoHome = true;
return this;
}
public AjaxResponse_Base SetGoHome(String title, String text)
{
GoHome = true;
return SetMessage(title, text);
}
// Message - displays alert popup.
public class MessageData
{
public String Title { get; set; }
public String Text { get; set; }
}
public MessageData Message { get; private set; }
public AjaxResponse_Base SetMessage(String title, String text)
{
if (!StringUtil.IsEmpty(text))
Message = new MessageData() { Title = title, Text = text };
return this;
}
}
//-------------------------------------------------------------------
// Ajax Response Data.
//-------------------------------------------------------------------
public class AjaxResponse_Data : AjaxResponse_Base
{
//---------------------------------------------------------------
// Construction.
protected AjaxResponse_Data(SbmModel_Base model)
{
}
//---------------------------------------------------------------
/// <summary>
/// Common item response data properties.
/// <para>- Null values are ignored by the JSON serialiser.</para>
/// <para>- Return null for false boolean values where possible.</para>
/// <para>- Return null for blank string values where possible.</para>
/// </summary>
public class ItemData
{
public String Id { get; set; }
private String notes;
public String Notes { get { return StringUtil.OnEmpty(notes, null); } set { notes = StringUtil.EmptyOnNull(value).Trim(); } }
public String StatusCode { get; set; }
public String StatusDesc { get; set; }
public bool? Awaiting { get { return StatusCode.EqualsCI(SacsSystem.GetStatusCode(SaStatus.Awaiting)) ? true : (bool?)null; } }
public bool? Active { get { return StatusCode.EqualsCI(SacsSystem.GetStatusCode(SaStatus.Active)) ? true : (bool?)null; } }
public bool? Deleted { get { return StatusCode.EqualsCI(SacsSystem.GetStatusCode(SaStatus.Deleted)) ? true : (bool?)null; } }
public String CreatedOn { get; set; }
public String CreatedOnFmt { get; set; }
public String UpdatedOn { get; set; }
public String UpdatedOnFmt { get; set; }
}
//---------------------------------------------------------------
// Form data.
public class FormControlValue
{
public FormControlValue(String controlName, String value, String error)
{
ControlName = controlName;
Value = value;
Error = error;
}
public String ControlName { get; }
public String Value { get; set; }
public String Error { get; set; }
}
private readonly Dictionary<String, FormControlValue> controlValues = new Dictionary<String, FormControlValue>();
public List<FormControlValue> ControlValues { get { return controlValues.Count > 0 ? controlValues.Values.ToList() : null; } }
public void SetControlValue(String controlName, String value, String error = null)
{
if (controlValues.ContainsKey(controlName))
{
// Modify existing entry in the list.
FormControlValue controlValue = controlValues[controlName];
if (value != null) controlValue.Value = value;
if (error != null) controlValue.Error = error;
}
else
{
// Add new control to list.
controlValues.Add(controlName, new FormControlValue(controlName, value, error));
}
}
/// <summary>Assign the error to the control by name and return this response data.</summary>
public AjaxResponse_Data SetControlError(String controlName, String error)
{
return SetControlError(controlName, error, this);
}
/// <summary>
/// Assign the error to the control by name and return the specified value.
/// <para>- Allows alternative return values to be specified (eg. 'false') to avoid separate return statement in calling routine.</para>
/// </summary>
public T SetControlError<T>(String controlName, String error, T returnValue)
{
SetControlValue(controlName, null, error);
return returnValue;
}
public bool HasControlErrors()
{
foreach (FormControlValue controlValue in controlValues.Values)
{
if (!StringUtil.IsEmpty(controlValue.Error)) return true;
}
return false;
}
//---------------------------------------------------------------
// Notification - briefly displays after response received.
public class NotificationData
{
public String Title { get; set; }
public String Message { get; set; }
}
public NotificationData Notification { get; private set; }
public AjaxResponse_Data SetNotification(String title, String message)
{
Notification = new NotificationData() { Title = title, Message = message };
return this;
}
//---------------------------------------------------------------
// Paged data.
public class PagingData
{
public PagingData(SbmModel_Base model) :
this(model.Paging.PageNo, model.Paging.PageLength, model.Paging.ItemCount, model.Paging.ItemTotal)
{ }
public PagingData(uint pageNo, ushort pageLength, ushort itemCount, ulong itemTotal)
{
PageNo = pageNo;
PageLength = pageLength;
ItemCount = itemCount;
ItemTotal = itemTotal;
}
public bool Paged { get { return (PageNo > 0 && PageLength > 0 && ItemTotal > PageLength); } }
public uint PageNo { get; } = 0;
public ushort PageLength { get; } = 0;
public uint PageCount { get { return (PageLength > 0 ? (uint)Math.Ceiling((decimal)ItemTotal / PageLength) : 0); } }
public ushort ItemCount { get; } = 0;
public ulong ItemTotal { get; } = 0;
}
public PagingData Paging { get; private set; }
public void SetPaging(SbmModel_Base model)
{
Paging = new PagingData(model);
}
//---------------------------------------------------------------
}
//-------------------------------------------------------------------
}
//=======================================================================
@@ -37,13 +37,13 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Helpers
/// Identify the session context that corresponds with the BusinessMgr
/// database for the specified user by email address.
/// </summary>
public static List<ushort> GetSessionContextIdxsForUsersBusMgrDatabase(String emailAddress)
public static List<ushort> GetSessionContextIdxsForContextUserEmail(String dbIdentifier, String emailAddress)
{
String fn = className + ".GetSessionContextIdxForUsersBusMgrDatabase";
String alias_SecurityItem = "SecItm";
String alias_Affiliate = "Affil";
List<ushort> sessionContextIdxs = new List<ushort>();
if (StringUtil.IsEmpty(emailAddress)) return sessionContextIdxs;
if (SacsSystem.DebugMode) SaLogger.Info($"{SysUtil.Fn} Searching for database. Identifier: {dbIdentifier}. Email address: {emailAddress}");
// Get the session context indices defined by the application.
SbmApplication_Web app = SbmGlobals_Web.Application;
@@ -53,16 +53,20 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Helpers
SbmSessionContext sessionContext = app.GetSessionContext(sessionContextIdx);
if (sessionContext == null)
{
SaLogger.Warn(fn + " Failed to retrieve SessionContext for idx: " + sessionContextIdx);
SaLogger.Warn($"{SysUtil.Fn} Failed to retrieve SessionContext for idx: " + sessionContextIdx);
continue;
}
// Retrieve the BusinessMgr database.
ISacsDatabase db = app.GetBusMgrDatabase(sessionContext);
// Verify Db Identifier.
List<string> dbIdentifiers = app.GetDbIdentifiers(sessionContext);
if (!dbIdentifiers.ContainsCI(dbIdentifier))
continue;
ISacsDatabase db = app.GetBusMgrDatabase(sessionContext);
if (db == null)
{
SaLogger.Warn(fn + " Failed to retrieve SessionContext for idx: " + sessionContextIdx);
SaLogger.Warn($"{SysUtil.Fn} Failed to retrieve Database for idx: " + sessionContextIdx);
continue;
}
if (SacsSystem.DebugMode) SaLogger.Info($"{SysUtil.Fn} Database: " + db.DatabaseName);
// Query to check for Security Item matching the email with associated Affiliate.
// NB This is done independently for each database in case there are different
@@ -70,6 +74,7 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Helpers
DbQuery qry = new DbQuery(db.DbmsType);
qry.Tables.Add(SaSecurityDbDfn.GetTableName(SaSecurityDbDfn.TableIdx.SaSecItem), alias_SecurityItem);
qry.Columns.Add(SaSecurityDbDfn.GetColumnNameQual(SaSecurityDbDfn.ColumnIdx.SaSecItem_Id, alias_SecurityItem));
qry.Columns.Add(SaSecurityDbDfn.GetColumnNameQual(SaSecurityDbDfn.ColumnIdx.SaSecItem_Id, alias_SecurityItem));
qry.Filters.Add(SaSecurityDbDfn.GetColumnNameQual(SaSecurityDbDfn.ColumnIdx.SaSecItem_EmailAddress, alias_SecurityItem) + "='" + DbQuery.FormatForCondition(emailAddress) + "'");
// Join to the Affiliate table.
DbQueryJoin joinAffiliate = new DbQueryJoin(DbJoinType.FullOuter);
@@ -80,7 +85,10 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Helpers
using (DbResultSet resultSet = DbResultSet_DataReader.ExecuteQuery(db, qry.BuildQuery()))
{
if (resultSet != null && resultSet.NextRecord())
{
if (SacsSystem.DebugMode) SaLogger.Info($"{SysUtil.Fn} Database contains email: " + db.DatabaseName);
sessionContextIdxs.Add(sessionContextIdx);
}
}
}
return sessionContextIdxs;
@@ -36,6 +36,7 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Middleware
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
Context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
await Task.CompletedTask;
return AuthenticateResult.NoResult();
}
@@ -6,6 +6,7 @@ using Sarand.Net.Standard.Base.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.PortableExecutable;
using System.Text;
using System.Text.RegularExpressions;
@@ -56,10 +57,12 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Middleware
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)
// Anything that looks like a password will be left as is.
bool password = modelName.ContainsCI("Password");
// Exclude properties that retain leading/trailing whitespace.
if (result.Model is string)
{
if (!modelName.ContainsCI("Password"))
if (!password)
{
string value = result.Model as string;
@@ -89,9 +92,16 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Middleware
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);
// Log property details.
if (result.Model != null && SbmSystem.DebugMode &&
// Ignore passwords.
!password &&
// Ignore items that correspond with objects in the model rather than values.
!result.Model.ToString().Equals(result.Model.GetType().FullName) &&
// Ignore form files.
!result.Model.ToString().ContainsCI("Microsoft.AspNetCore.Http.IFormFile"))
SaLogger.Debug(SysUtil.Fn + " Form property " + modelName + ": '" + result.Model.ToString() + "'." + (originalValue != null && !originalValue.Equals(result.Model) ? " Original value: '" + originalValue.ToString() + "'" : ""));
base.SetProperty(bindingContext, modelName, propertyMetadata, result);
}
//-------------------------------------------------------------------
@@ -60,7 +60,7 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Middleware
catch (Exception exc)
{
SaLogger.Warn(fn + " " + ExceptionUtil.GetLogMessage(exc));
throw exc;
throw;
}
}
}
+38 -103
View File
@@ -1,6 +1,8 @@
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Mvc.Rendering;
using Sacs.Web.Components;
using Sarand.BusinessMgr.Net.Core.Web.App;
using Sarand.BusinessMgr.Net.Core.Web.Controllers;
using Sarand.BusinessMgr.Net.Standard.Base.App;
using Sarand.BusinessMgr.Net.Standard.Base.Bus;
using Sarand.BusinessMgr.Net.Standard.Base.Mgr;
@@ -18,19 +20,17 @@ using Sarand.Net.Standard.Base.UpdateLog;
using Sarand.Net.Standard.Base.Utils;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
namespace Sarand.BusinessMgr.Net.Core.Web.Models
{
//=======================================================================
public class SbmModel_Base
public class SbmModel_Base : SaModel
{
//-------------------------------------------------------------------
// Data members.
private SbmInternals internals;
private readonly List<String> infoMessages = new List<string>();
private readonly List<String> warningMessages = new List<string>();
private readonly List<String> errorMessages = new List<string>();
//-------------------------------------------------------------------
// Protected construction.
@@ -49,16 +49,14 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Models
public SbmSessionData_Web SessionData_Web { get; set; }
public SbmSessionContext SessionContext { get { return SessionData?.SessionContext ?? null; } }
public DbUpdateInfo DbUpdateInfo { get { return SessionData?.DbUpdateInfo ?? new DbUpdateInfo(null, "", SbmSystem.ComputerName); } }
public bool DebugMode { get { return SbmSystem.DebugMode; } }
public SbmLocaliser Localiser { get; }
// Request properties.
public SbmController_Base Controller { get; set; }
public CultureInfo SessionCulture { get { return Controller?.SessionCulture ?? CultureInfo.InvariantCulture; } }
//-------------------------------------------------------------------
// Page configuration.
public bool OK { get { return !HasMessages(); } }
public virtual bool ProhibitCache { get; set; } = true;
public String PageCaption { get; set; }
public String InstanceVersion { get { return !StringUtil.IsEmpty(App.AppConfig.Instance) ? App.AppConfig.Instance + " v" + Sbm.Version.GetFullVersion() : "v" + Sbm.Version.GetAppVersion(); } }
public bool IncludeGoogleAnalytics { get; set; } = false;
//-------------------------------------------------------------------
// Business components.
@@ -100,7 +98,23 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Models
//-------------------------------------------------------------------
/// <summary>
/// Form control value max length.
/// Form control value max length - Security tables.
/// <para>In many cases the database columns allow plenty of extra space for the value, in case they need to be encrypted.</para>
/// <para>Hence, several cases where the value entered in the control isn't allowed the full database length.</para>
/// </summary>
public int GetControlMaxLength(SaSecurityDbDfn.ColumnIdx column)
{
int maxLength = (int)SaSecurityDbDfn.GetColTextLen(column);
switch (column)
{
case SaSecurityDbDfn.ColumnIdx.SaSecItem_EmailAddress:
return Math.Min(maxLength, 100);
}
return maxLength;
}
/// <summary>
/// Form control value max length - BusinessMgr tables.
/// <para>In many cases the database columns allow plenty of extra space for the value, in case they need to be encrypted.</para>
/// <para>Hence, several cases where the value entered in the control isn't allowed the full database length.</para>
/// </summary>
@@ -110,7 +124,7 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Models
switch (column)
{
case SbmDbDfn.ColumnIdx.Affiliate_Email:
return Math.Min(maxLength, 60);
return Math.Min(maxLength, 100);
case SbmDbDfn.ColumnIdx.Affiliate_FirstName:
case SbmDbDfn.ColumnIdx.Affiliate_LastName:
return Math.Min(maxLength, 40);
@@ -138,6 +152,17 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Models
}
}
//-------------------------------------------------------------------
// Display data - Dates.
public String FormatDateForDisplay(DateTime value)
{
return DateUtil.Format(value, "d-MMM-yy", SessionCulture);
}
public String FormatDateTimeForDisplay(DateTime value)
{
return StringUtil.TrimAll(DateUtil.Format(value, "d-MMM-yy HH:mm", SessionCulture));
}
//-------------------------------------------------------------------
// Form data - Dates.
// Format may depend on locale, but as long as these methods are used consistently,
@@ -208,71 +233,10 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Models
/// <summary>List of Commodity Types for dropdown.</summary>
public IEnumerable<SelectListItem> GetCommodityTypeCodesForSelect()
{
return new SelectList(GetCodesOfType(Sbm.SystemCode.CommodityType), "Code", "Desc");
}
//-------------------------------------------------------------------
// Version parameter for includes.
public String HrefVersion
{
get
{
DateTime versionDate = SbmSystem.AssemblyBuildAt;
if (!DateUtil.IsValid(versionDate) || SbmSystem.DebugMode)
versionDate = DateTime.Now;
return DateUtil.Format(versionDate, "yyyyMMdd" /*+ (SacsSystem.Debug ? "HHmm" : "")*/);
}
}
// Additional includes.
public List<String> AdditionalFiles_CSS { get; set; } = new List<string>();
public List<String> AdditionalFiles_JS { get; set; } = new List<string>();
//-------------------------------------------------------------------
// Redirection locations.
public class RedirectLocationData
{
// Properties populated in Controller_Base.OnActionExecuted.
public String Base { get; internal set; }
public String Home { get; internal set; }
public String Home_SessionExpired { get; internal set; }
}
public RedirectLocationData RedirectLocation { get; } = new RedirectLocationData();
//-------------------------------------------------------------------
// Paging properties.
public class DataPaging
{
private DbQuery.Paging paging { get; } = new DbQuery.Paging();
public void PopulatePagingTo(DbQuery.Paging resultsPaging)
{
resultsPaging.PageLength = paging.PageLength;
resultsPaging.PageNo = paging.PageNo;
}
public bool IsPaged { get { return paging.Paged; } }
public uint PageNo { get { return paging.PageNo; } set { paging.PageNo = value; } }
public ushort PageLength { get { return paging.PageLength; } set { paging.PageLength = value; } }
public uint PageCount { get { return (PageLength > 0 ? (uint)Math.Ceiling((decimal)ItemTotal / PageLength) : 0); } }
public ushort ItemCount { get; set; }
private ulong itemTotal = 0;
public ulong ItemTotal
{
get { return itemTotal; }
set
{
itemTotal = value;
// Check whether the total number of items is less than the current page and reduce page no if needed.
if (IsPaged && (PageNo - 1) * PageLength > itemTotal)
PageNo = (uint)((itemTotal - 1) / PageLength + 1);
}
}
}
public DataPaging Paging { get; } = new DataPaging();
public void PopulatePagingTo(DbQuery.Paging resultsPaging)
{
Paging.PopulatePagingTo(resultsPaging);
}
//-------------------------------------------------------------------
// Form properties.
/// <summary>
@@ -280,38 +244,9 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Models
/// <para>Equivalent to the Site index on the login form.</para>
/// <para>Populates value on logout form to identify default site for next login.</para>
/// </summary>
public ushort SessionIdx { get { return SessionContext.Index; } }
public ushort SessionIdx { get { return SessionContext?.Index ?? 0; } }
public String FormAction { get; set; }
//-------------------------------------------------------------------
// Messages.
//-------------------------------------------------------------------
public bool HasMessages()
{
return infoMessages.Count > 0 || warningMessages.Count > 0 || errorMessages.Count > 0;
}
public List<String> InfoMessages { get { return new List<string>(infoMessages); } }
public List<String> WarningMessages { get { return new List<string>(warningMessages); } }
public List<String> ErrorMessages { get { return new List<string>(errorMessages); } }
//-------------------------------------------------------------------
// Form messages.
public void AddInfoMessage(String message)
{
if (StringUtil.IsEmpty(StringUtil.TrimAll(message))) return;
infoMessages.Add(message);
}
public void AddWarningMessage(String message)
{
if (StringUtil.IsEmpty(StringUtil.TrimAll(message))) return;
warningMessages.Add(message);
}
public void AddErrorMessage(String message)
{
if (StringUtil.IsEmpty(StringUtil.TrimAll(message))) return;
errorMessages.Add(message);
}
//-------------------------------------------------------------------
}
//=======================================================================