3.0.0.0 Initial deployment.

This commit is contained in:
Andy Howard
2025-02-02 18:06:57 +00:00
parent 7930ef5604
commit 0d8e7d5619
75 changed files with 345 additions and 211 deletions
@@ -0,0 +1,289 @@
using Newtonsoft.Json;
using Sarand.BusinessMgr.Net.Core.Web.App;
using Sarand.BusinessMgr.Net.Core.Web.Components;
using Sarand.BusinessMgr.Net.Standard.Base.App;
using Sarand.BusinessMgr.Net.Standard.Base.Bus.Control;
using Sarand.BusinessMgr.Net.Standard.Base.Consoles;
using Sarand.BusinessMgr.Net.Standard.Base.Core;
using Sarand.BusinessMgr.Net.Standard.Base.Mgr.Control;
using Sarand.BusinessMgr.Web.Control.App;
using Sarand.BusinessMgr.Web.Control.Consoles;
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.Core;
using Sarand.Net.Standard.Base.Email;
using Sarand.Net.Standard.Base.Helpers;
using Sarand.Net.Standard.Base.Utils;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Web;
namespace Sarand.BusinessMgr.Web.Control.Components
{
//======================================================================
public class SbmController_Control : SbmController_Web
{
//-------------------------------------------------------------------
// Data members.
private SbmInternals_Control internals_Control;
private SbmEmailConsole_Control emailConsole;
//-------------------------------------------------------------------
// Protected construction.
protected SbmController_Control(SbmLocaliser localiser, Microsoft.Extensions.Logging.ILogger<SbmController_Control> logger) : base(localiser)
{
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (internals_Control != null) internals_Control.Dispose(); internals_Control = null;
}
base.Dispose(disposing);
}
//-------------------------------------------------------------------
// Properties.
public SbmInternals_Control Internals_Control { get { if (internals_Control == null) { internals_Control = new SbmInternals_Control(); } return internals_Control; } }
protected SbmcAccountMgr ControlAccountMgr { get { return Internals_Control.ControlAccountMgr; } }
protected SbmcInstanceMgr ControlInstanceMgr { get { return Internals_Control.ControlInstanceMgr; } }
protected SbmcItemMgr ControlItemMgr { get { return Internals_Control.ControlItemMgr; } }
//-------------------------------------------------------------------
// Session data.
//-------------------------------------------------------------------
/// <summary>Control session data</summary>
protected SbmApplication_Control App_Control { get { return SbmApplication_Control.Instance; } }
protected SbmSessionData_Control SessionData_Control { get { return SessionData as SbmSessionData_Control; } }
protected override SbmSessionData_Web DeserializeSessionData(string sessionDataJson)
{
return JsonConvert.DeserializeObject<SbmSessionData_Control>(sessionDataJson, SessionDataJsonDeserializerSettings);
}
//-------------------------------------------------------------------
// Email console.
//-------------------------------------------------------------------
public SaEmailConfig EmailConfig { get { return App_Control.EmailConfig; } }
public SbmEmailConsole_Control EmailConsole_Control
{
get
{
if (emailConsole == null)
emailConsole = SbmApplication_Control.CreateEmailConsole_Control(App_Control.EmailConfig);
return emailConsole;
}
}
//-------------------------------------------------------------------
// User Session.
//-------------------------------------------------------------------
/// <summary>
/// Sign the account in.
/// <para>- Record login time and clear invalid login data.</para>
/// <para>- Open user session - store details against Sesion Data.</para>
/// <para>- Set 'Keep logged in' cookie if required.</para>
/// </summary>
protected bool SignUserIn(SbmcUserAccount userAccount, bool keepLoggedIn, ref string errorMessage)
{
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<SacsHierarchyItem> disposables = new DisposableCollection<SacsHierarchyItem>())
{
// Validate User Account, just in case - should have been done before arriving here!
if (userAccount == null)
{
SaLogger.Error($"{SysUtil.Fn} Unable to sign account in - null user account.");
errorMessage = Localiser["security.msg.signin.invalid"];
return false;
}
if (!userAccount.IsStatusActive)
{
SaLogger.Error($"{SysUtil.Fn} Unable to sign account in - user account is not active. Email: " + userAccount.EmailAddress);
errorMessage = Localiser["security.msg.signin.invalid"];
return false;
}
// 'Keep logged in' can be switched off by the application configuration.
keepLoggedIn = App.AppConfig.Security.SignIn.KeepLoggedIn.Allowed && keepLoggedIn;
userAccount.KeepLoggedIn = keepLoggedIn;
userAccount.LastLoginAt = DateTime.Now;
userAccount.InvalidLoginCount = 0;
userAccount.LastInvalidLoginAt = null;
if (!ControlAccountMgr.UpdateUserAccount(userAccount, DbUpdateInfo))
{
SaLogger.Warn($"{SysUtil.Fn} Failed to update User Account for " + userAccount.EmailAddress + ": " + ControlAccountMgr.ErrorStateMessage);
errorMessage = Localiser["security.msg.signin.invalid"];
return false;
}
SaLogger.Info($"{SysUtil.Fn} User session opened for " + userAccount.EmailAddress, true);
// Create session.
OpenUserSession(SessionContext, userAccount);
// Send the 'Keep Logged In' cookie if requested.
if (userAccount.KeepLoggedIn)
SetKeepLoggedInCookie();
}
return true;
}
//-------------------------------------------------------------------
/// <summary>Start a new user session on sign-in.</summary>
protected bool OpenUserSession(SbmSessionContext sessionContext, SbmcUserAccount userAccount)
{
// Validate user properties.
if (!SysId.IsValid(userAccount.Id)) return false;
if (userAccount.EmailAddress.IsEmpty()) return false;
// Close previous session.
CloseUserSession();
// Open session.
return SetSessionData(new SbmSessionData_Control(sessionContext, userAccount));
}
protected void CloseUserSession()
{
ClearSessionData();
}
//-------------------------------------------------------------------
// Keep Logged In.
//-------------------------------------------------------------------
/// <summary>
/// Check for 'Keep Logged In' cookie and auto-login user if details are valid.
/// </summary>
/// <returns></returns>
protected bool AutoLoginUser()
{
// NB Only allowed if there isn't a current user session.
if (ValidUserSession) return true;
if (!AppConfig.Security.SignIn.KeepLoggedIn.Allowed) return false;
// Extract cookie details.
string encryptedParams = GetRequestCookie(KeepLoggedIn.CookieName);
KeepLoggedInParams keepLoggedInParams = ExtractKeepLoggedInParams(encryptedParams);
string logSuffix = keepLoggedInParams.GetValidParams().Concat("", " ", ".");
if (keepLoggedInParams.IsValid())
{
using (SbmcUserAccount userAccount = ControlAccountMgr.GetUserAccountById(keepLoggedInParams.UserAccountId))
{
if (userAccount != null)
{
if (userAccount.IsStatusActive)
{
string errorMessage = "";
if (SignUserIn(userAccount, true, ref errorMessage))
{
// Successful login.
SaLogger.Info($"{SysUtil.Fn} Keep Logged In cookie found." + logSuffix);
return true;
}
else SaLogger.Warn($"{SysUtil.Fn} Keep Logged In cookie found but failed to sign user in: " + errorMessage + "." + logSuffix);
}
else SaLogger.Warn($"{SysUtil.Fn} Keep Logged In cookie found but UserAccount is not active." + logSuffix);
}
else SaLogger.Warn($"{SysUtil.Fn} Keep Logged In cookie found but UserAccount not found." + logSuffix);
}
}
ClearKeepLoggedInCookie();
return false;
}
//-------------------------------------------------------------------
/// <summary>Keep-Logged-In cookie.</summary>
protected void SetKeepLoggedInCookie()
{
string keepLoggedInCookieValue = GenerateKeepLoggedInCookieValue();
int cookieExpiresDays = Math.Max(Sbm.Config.Security.SignIn.KeepLoggedIn.CookieExpiresDays_Min, Math.Min(Sbm.Config.Security.SignIn.KeepLoggedIn.CookieExpiresDays_Max, AppConfig.Security.SignIn.KeepLoggedIn.CookieExpiresDays));
SetResponseCookie(KeepLoggedIn.CookieName, keepLoggedInCookieValue, DateTime.Now.AddDays(cookieExpiresDays), true);
SaLogger.Debug($"{SysUtil.Fn} 'Keep Logged In' cookie set. Value: " + keepLoggedInCookieValue);
}
/// <summary>Set the 'Keep Logged In' response cookie to expired with no data.</summary>
public void ClearKeepLoggedInCookie()
{
if (GetRequestCookie(KeepLoggedIn.CookieName).IsEmpty()) return;
// Clear cookie by setting expiry date in the past.
SetResponseCookie(KeepLoggedIn.CookieName, "", DateTime.Today.AddDays(-7), true);
SaLogger.Debug($"{SysUtil.Fn} 'Keep Logged In' cookie cleared.");
}
//-------------------------------------------------------------------
// 'Keep Logged In' cookie and encryption config.
// NB - IV value is generated from the key and fixed so that it doesn't change when the application restarts
// to ensure that cookie value can be decrypted even if application restarts in the meantime.
// - Generating IV from same key results in different value each time.
// - IV Must be regenerated if key is changed: uncomment 'Generated' variable and copy resulting value to IV variable.
public static class KeepLoggedIn
{
public static string CookieName = "BCKLI";
public static string Base64Key = SacsEncryptionHelper.EncodePlainKey("$BusinessMgr+KeEp-1OgG3d=1n}*32?", SacsEncryptionHelper.KeyLength);
public static string Base64IV = "fpHMZchDkrJzgjVMrjpdHg==";
//public static String Base64IV_Generated = SacsEncryptionHelper.InitSymmetricEncryptionIV(KeepLoggedIn.Base64Key);
}
//-------------------------------------------------------------------
// Keep-logged-in cookie value.
private static readonly string ParamName_KeepLoggedIn_UserAccountId = "uai";
private string GenerateKeepLoggedInCookieValue()
{
NameValueCollection keepLoggedInParams = new NameValueCollection();
keepLoggedInParams.Add(ParamName_KeepLoggedIn_UserAccountId, SysId.ToString(SessionData_Control.User.UserAccountId));
return EncryptParameters(keepLoggedInParams, KeepLoggedIn.Base64IV, KeepLoggedIn.Base64Key);
}
//-------------------------------------------------------------------
public class KeepLoggedInParams
{
public SysId UserAccountId { get; set; }
/// <summary>Retrieve the list of valid parameters in printable format [Name: Value].</summary>
public List<string> GetValidParams()
{
List<string> values = new List<string>();
if (SysId.IsValid(UserAccountId))
values.Add("UserAccountId: " + SysId.ToString(UserAccountId));
return values;
}
/// <summary>Verifiy that the account details are populated.</summary>
public bool IsValid()
{
return
SysId.IsValid(UserAccountId);
}
}
public KeepLoggedInParams ExtractKeepLoggedInParams(string encryptedParams)
{
KeepLoggedInParams keepLoggedInParams = new KeepLoggedInParams();
if (!encryptedParams.IsEmpty())
{
NameValueCollection linkParams = DecryptParameters(encryptedParams, KeepLoggedIn.Base64IV, KeepLoggedIn.Base64Key);
keepLoggedInParams.UserAccountId = new SysId(linkParams.Get(ParamName_KeepLoggedIn_UserAccountId).ToInt());
}
if (DebugMode) SaLogger.Info($"{SysUtil.Fn} UserAccountId: " + SysId.ToString(keepLoggedInParams.UserAccountId));
return keepLoggedInParams;
}
//-------------------------------------------------------------------
// Parameter encryption, eg. for images.
//-------------------------------------------------------------------
// Encryption key is the same for all users each time the application is started,
// but a different IV value is generated for each session so that the links/parameters cannot be shared.
public static string Base64Key_ControlApp { get; } = SacsEncryptionHelper.EncodePlainKey("BusinessMgr=Control&Encryption-" + DateTime.Today.Format("ddMMyy"), SacsEncryptionHelper.KeyLength);
protected string EncryptSessionParams(NameValueCollection parameters)
{
return HttpUtility.UrlEncode(EncryptParameters(parameters, SessionData_Control.Base64IV_ControlApp, Base64Key_ControlApp));
}
public static string EncryptSessionParams(SbmController_Control controller, NameValueCollection parameters)
{
return controller.EncryptSessionParams(parameters);
}
protected NameValueCollection ExtractSessionParams(string encryptedParams)
{
return DecryptParameters(encryptedParams, SessionData_Control.Base64IV_ControlApp, Base64Key_ControlApp);
}
//-------------------------------------------------------------------
}
//======================================================================
}
@@ -0,0 +1,70 @@
using Microsoft.AspNetCore.Mvc.Rendering;
using Sarand.BusinessMgr.Net.Core.Web.App;
using Sarand.BusinessMgr.Net.Core.Web.Components;
using Sarand.BusinessMgr.Net.Standard.Base.App;
using Sarand.BusinessMgr.Net.Standard.Base.Bus.Control;
using Sarand.BusinessMgr.Net.Standard.Base.Core;
using Sarand.BusinessMgr.Web.Control.App;
using Sarand.Net.Standard.Base.Core;
using System;
using System.Collections.Generic;
namespace Sarand.BusinessMgr.Web.Control.Components
{
//=======================================================================
public class SbmModel_Control : SbmModel_Base
{
//-------------------------------------------------------------------
// Protected construction.
protected SbmModel_Control(SbmLocaliser localiser) :
base(localiser)
{
}
//-------------------------------------------------------------------
// Properties.
protected SbmApplication_Control App_Control { get { return SbmApplication_Control.Instance; } }
protected SbmSessionData_Control SessionData_Control { get { return SessionData_Web as SbmSessionData_Control; } }
//-------------------------------------------------------------------
public IEnumerable<SelectListItem> GetInstanceContextsForSelect()
{
List<SelectListItem> items = new List<SelectListItem>();
SbmcRepository.Params prmRepository = new SbmcRepository.Params();
prmRepository.StatusCat = SbmcRepository.StatusCat.NotDeleted;
foreach (SbmcRepository repository in ControlInstanceMgr.GetRepositoryList(prmRepository))
items.Add(new SelectListItem(repository.Context, repository.Context));
return items;
}
//-------------------------------------------------------------------
/// <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>
public int GetControlMaxLength(SbmDbDfn_Control.ColumnIdx column)
{
int maxLength = (int)SbmDbDfn_Control.GetColTextLen(column);
switch (column)
{
case SbmDbDfn_Control.ColumnIdx.UserAccount_EmailAddress:
return Math.Min(maxLength, 100);
case SbmDbDfn_Control.ColumnIdx.UserAccount_FirstName:
case SbmDbDfn_Control.ColumnIdx.UserAccount_LastName:
return Math.Min(maxLength, 40);
case SbmDbDfn_Control.ColumnIdx.UserAccount_PhoneMain:
case SbmDbDfn_Control.ColumnIdx.UserAccount_PhoneMobile:
case SbmDbDfn_Control.ColumnIdx.UserAccount_PhoneAlt:
return Math.Min(maxLength, 20);
case SbmDbDfn_Control.ColumnIdx.ItemParam_Value:
if (maxLength == 0 || maxLength > 2048) return 2048;
break;
}
return maxLength;
}
//-------------------------------------------------------------------
}
//=======================================================================
}