Files
BusinessMgr-2019/SaBusinessMgrCoreWebControl/Control/Components/SbmController_Control.cs
T

276 lines
12 KiB
C#

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.Data;
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 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)
{
}
base.Dispose(disposing);
}
//-------------------------------------------------------------------
// Properties.
//-------------------------------------------------------------------
// 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<ISacsDisposable> disposables = new DisposableCollection<ISacsDisposable>())
{
// 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())
{
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.
public static class KeepLoggedIn
{
public static string CookieName = "BCKLI";
public static string Base64Key = SacsEncryptionHelper.EncodePlainKey("$BusinessMgr+KeEp-1OgG3d=1n}*32?", SacsEncryptionHelper.KeyLength);
}
//-------------------------------------------------------------------
// 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.Base64Key, SbmEncryptionPurpose.ControlKeepLoggedIn);
}
//-------------------------------------------------------------------
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.ToLong(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.Base64Key, SbmEncryptionPurpose.ControlKeepLoggedIn);
keepLoggedInParams.UserAccountId = new SysId(linkParams.Get(ParamName_KeepLoggedIn_UserAccountId).ToInt());
}
if (DebugMode && SysId.IsValid(keepLoggedInParams.UserAccountId)) SaLogger.Info($"{SysUtil.Fn} UserAccountId: {SysId.ToLong(keepLoggedInParams.UserAccountId)}.");
return keepLoggedInParams;
}
//-------------------------------------------------------------------
// Parameter encryption, eg. for images.
//-------------------------------------------------------------------
// Encryption key is the same for all users each day. Purpose-bound authentication prevents cross-context reuse.
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, Base64Key_ControlApp, SbmEncryptionPurpose.ControlSessionParameters));
}
public static string EncryptSessionParams(SbmController_Control controller, NameValueCollection parameters)
{
return controller.EncryptSessionParams(parameters);
}
protected NameValueCollection ExtractSessionParams(string encryptedParams)
{
return DecryptParameters(encryptedParams, Base64Key_ControlApp, SbmEncryptionPurpose.ControlSessionParameters);
}
//-------------------------------------------------------------------
}
//======================================================================
}