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 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. //------------------------------------------------------------------- /// Control session data 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(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. //------------------------------------------------------------------- /// /// Sign the account in. /// - Record login time and clear invalid login data. /// - Open user session - store details against Sesion Data. /// - Set 'Keep logged in' cookie if required. /// 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 disposables = new DisposableCollection()) { // 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; } //------------------------------------------------------------------- /// Start a new user session on sign-in. 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. //------------------------------------------------------------------- /// /// Check for 'Keep Logged In' cookie and auto-login user if details are valid. /// /// 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; } //------------------------------------------------------------------- /// Keep-Logged-In cookie. 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); } /// Set the 'Keep Logged In' response cookie to expired with no data. 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; } /// Retrieve the list of valid parameters in printable format [Name: Value]. public List GetValidParams() { List values = new List(); if (SysId.IsValid(UserAccountId)) values.Add("UserAccountId: " + SysId.ToString(UserAccountId)); return values; } /// Verifiy that the account details are populated. 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); } //------------------------------------------------------------------- } //====================================================================== }