using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Net.Http.Headers; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using Sarand.Net.Standard.Base.App; using Sarand.Net.Standard.Base.Bus; using Sarand.Net.Standard.Base.Dbas; using Sarand.Net.Standard.Base.Helpers; using Sarand.Net.Standard.Base.Utils; using Saxonite.Net.Core.Web.Base.App; using Saxonite.Net.Core.Web.Base.Models; using Saxonite.Net.Standard.Base.App; using Saxonite.Net.Standard.Base.Consoles; using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; namespace Saxonite.Net.Core.Web.Base.Controllers { //======================================================================= public abstract class SaxController_Web : SaxController_Base { //------------------------------------------------------------------- // Data members. //------------------------------------------------------------------- // Protected construction. protected SaxController_Web(SaxLocaliser localiser) : base(localiser) { } protected override void Dispose(bool disposing) { if (disposing) { } base.Dispose(disposing); } //------------------------------------------------------------------- // Properties. //------------------------------------------------------------------- // Action execution events. //------------------------------------------------------------------- public override void OnActionExecuting(ActionExecutingContext filterContext) { //if (DebugMode) SaLogger.Debug($"{SysUtil.Fn}" + (ViewData.Model != null ? " Model: " + ViewData.Model : "")); // Refreh model properties. SaxModel_Base baseModel = ViewData.Model as SaxModel_Base; RefreshModel(baseModel); base.OnActionExecuting(filterContext); } public override void OnActionExecuted(ActionExecutedContext filterContext) { //if (DebugMode) SaLogger.Debug($"{SysUtil.Fn}" + (ViewData.Model != null ? " Model: " + ViewData.Model : "")); // Refreh model properties. SaxModel_Base baseModel = ViewData.Model as SaxModel_Base; RefreshModel(baseModel); // Store session data if present - as potentially modfied. if (SessionData != null) { // Most actions will store the session data in case it's been modified. // Some actions (eg. score password) don't need to. if (StoreSessionData) { String sessionDataJson = JsonConvert.SerializeObject(SessionData); HttpContext.Session.Set(SessionStorage.SessionKey_SessionData, SessionStorage.sessionDataEncoding.GetBytes(sessionDataJson)); } } else HttpContext.Session.Remove(SessionStorage.SessionKey_SessionData); // Response headers. if (baseModel != null && baseModel.ProhibitCache) { if (Response.Headers.ContainsKey(HeaderNames.CacheControl)) Response.Headers.Remove(HeaderNames.CacheControl); Response.Headers.Add(HeaderNames.CacheControl, "no-cache, no-store, must-revalidate"); Response.Headers.Add(HeaderNames.Pragma, "no-cache"); Response.Headers.Add(HeaderNames.Expires, "0"); } base.OnActionExecuted(filterContext); } //------------------------------------------------------------------- // Model. //------------------------------------------------------------------- protected void RefreshModel(SaxModel_Base model) { if (model == null) return; // Session data. model.Controller = this; model.SessionData = SessionData; // Redirect locations. model.RedirectLocation.Base = GetRedirectLocation(RedirectLocation.WebAppBase); model.RedirectLocation.Home = GetRedirectLocation(RedirectLocation.WebAppHome); model.RedirectLocation.Home_SessionExpired = GetRedirectLocation(RedirectLocation.WebAppHome_SessionExpired); } //------------------------------------------------------------------- // Parameters. //------------------------------------------------------------------- // Online Request Password encryption parameters (key & IV) are defined in // the base Controller so that they can be shared with the Adnin application. // NB IV value is generated from the key and fixed so that it doesn't change when the application restarts. // IV Must be regenerated if key is changed. protected static String Base64Key_RequestPassword_Online { get; } = SacsEncryptionHelper.EncodePlainKey("{~Saxonite@@Request^Password=}", SacsEncryptionHelper.KeyLength); //private static String Base64IV_RequestPassword_Generated_Online { get; } = SacsEncryptionHelper.InitSymmetricEncryptionIV(Base64Key_RequestPassword_Online); protected static String Base64IV_RequestPassword_Online { get; } = "1VAWYTW6d72n/6HDNhbqow=="; //------------------------------------------------------------------- // Session. //------------------------------------------------------------------- private bool UserActiveRecorded { get; set; } = false; public bool ValidUserSession { get { if (SessionData == null || !SessionData.ValidUserSession) return false; SaxSessionDataConsole sessionDataConsole = SessionDataConsole; if (sessionDataConsole != null && !UserActiveRecorded) { // Record the user as active once per request. sessionDataConsole.RecordUserActive(SessionUserAccountId, CreatedAt); UserActiveRecorded = true; } return true; } } public SysId SessionUserAccountId { get { return SessionData?.User?.UserAccountId ?? null; } } public DbUpdateInfo DbUpdateInfo { get { return SessionData?.DbUpdateInfo ?? new DbUpdateInfo(null, "", SaxSystem.ComputerName); } } // Referrer. public String Referer { get { return Request.Headers["Referer"].ToString(); } } public bool ValidReferer { get { return !StringUtil.IsEmpty(Referer); } } /// Store session data, eg. to start a new user session on sign-in. protected bool SetSessionData(SaxSessionData sessionData) { if (sessionStorage.SessionData != null) return false; sessionStorage.SessionData = sessionData; return true; } protected void ClearSessionData() { if (HttpContext.Session.IsAvailable) HttpContext.Session.Clear(); sessionStorage.SessionData = null; } //------------------------------------------------------------------- // Session storage. public class SessionStorage { public SaxSessionData SessionData { get; set; } public static String SessionKey_SessionData { get; } = "SaxSessionData"; public static Encoding sessionDataEncoding { get; } = Encoding.UTF8; //JSON Serialisation. public class SaxSessionDataContractResolver : DefaultContractResolver { // To allow private properties to be deserialised. // NB Only applies to top level properties in the serialised object. protected override List GetSerializableMembers(Type objectType) { var flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; MemberInfo[] fields = objectType.GetFields(flags); return fields .Concat(objectType.GetProperties(flags).Where(propInfo => propInfo.CanWrite)) .ToList(); } protected override IList CreateProperties(Type type, MemberSerialization memberSerialization) { return base.CreateProperties(type, MemberSerialization.Fields); } }; public static JsonSerializerSettings JsonDeserialisationSettings = new JsonSerializerSettings { ContractResolver = new SaxSessionDataContractResolver(), // Force all deserialised objects to be replaced - to avoid proxy lists not populating. ObjectCreationHandling = ObjectCreationHandling.Replace }; } private readonly SessionStorage sessionStorage = new SessionStorage(); protected JsonSerializerSettings SaxSessionDataJsonSerializerSettings { get { return SessionStorage.JsonDeserialisationSettings; } } //------------------------------------------------------------------- public SaxSessionData SessionData { get { try { // Retrieve session data if not already in controller. if (sessionStorage.SessionData == null) { byte[] sessionDataBytes = null; HttpContext.Session.TryGetValue(SessionStorage.SessionKey_SessionData, out sessionDataBytes); if (sessionDataBytes != null) { String sessionDataJson = SessionStorage.sessionDataEncoding.GetString(sessionDataBytes); sessionStorage.SessionData = DeserializeSessionData(sessionDataJson); } } return sessionStorage.SessionData; } catch (Exception exc) { SaLogger.Warn($"{SysUtil.Fn} {exc.GetType().Name} - {exc.Message}{exc.GetStackEntries().Concat("", "\n\t")}"); } return null; } } protected abstract SaxSessionData DeserializeSessionData(String sessionDataJson); //------------------------------------------------------------------- } //======================================================================= }