290 lines
13 KiB
C#
290 lines
13 KiB
C#
using Microsoft.Extensions.Logging;
|
|
using Newtonsoft.Json;
|
|
using Sarand.Net.Standard.Base.App;
|
|
using Sarand.Net.Standard.Base.Bus;
|
|
using Sarand.Net.Standard.Base.Core;
|
|
using Sarand.Net.Standard.Base.Helpers;
|
|
using Sarand.Net.Standard.Base.Utils;
|
|
using Saxonite.Net.Core.Web.Admin.App;
|
|
using Saxonite.Net.Core.Web.Admin.Consoles;
|
|
using Saxonite.Net.Core.Web.Base.App;
|
|
using Saxonite.Net.Core.Web.Base.Controllers;
|
|
using Saxonite.Net.Standard.Base.App;
|
|
using Saxonite.Net.Standard.Base.Bus;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.Specialized;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using System.Web;
|
|
|
|
namespace Saxonite.Net.Core.Web.Admin.Controllers
|
|
{
|
|
//=======================================================================
|
|
public class SaxController_Admin : SaxController_Web
|
|
{
|
|
//-------------------------------------------------------------------
|
|
// Data members.
|
|
|
|
//-------------------------------------------------------------------
|
|
// Protected construction.
|
|
protected SaxController_Admin(SaxLocaliser localiser, ILogger<HomeController> logger) : base(localiser)
|
|
{
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Session data.
|
|
//-------------------------------------------------------------------
|
|
/// <summary>Admin session data</summary>
|
|
protected SaxSessionData_Admin SessionData_Admin { get { return SessionData as SaxSessionData_Admin; } }
|
|
protected override SaxSessionData DeserializeSessionData(String sessionDataJson)
|
|
{
|
|
return JsonConvert.DeserializeObject<SaxSessionData_Admin>(sessionDataJson, SaxSessionDataJsonSerializerSettings);
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Globals.
|
|
//-------------------------------------------------------------------
|
|
protected SaxConsoleHelper_Admin ConsoleHelper_Admin { get { return ConsoleHelper as SaxConsoleHelper_Admin; } }
|
|
protected SaxProcessConsole_Admin GetProcessConsole_Admin(SaxSessionContext sessionContext)
|
|
{
|
|
return ConsoleHelper_Admin?.GetProcessConsole(sessionContext) as SaxProcessConsole_Admin;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// 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(SaxUserAccount userAccount, bool keepLoggedIn, ref String errorMessage)
|
|
{
|
|
String fn = GetType().Name + ".SignUserIn";
|
|
|
|
// Record disposable items as they are created so all can be disposed when needed.
|
|
using (DisposableCollection<SacsHierarchyItem> disposableItems = new DisposableCollection<SacsHierarchyItem>())
|
|
{
|
|
List<SaxUserRole> userRoles = AccountMgr.GetRolesForUser(userAccount.Id);
|
|
disposableItems.AddRange(userRoles);
|
|
|
|
// Validate User Account, just in case - should have been done before arriving here!
|
|
if (userAccount == null)
|
|
{
|
|
SaLogger.Error(fn + " Unable to sign account in - null user account.");
|
|
errorMessage = Localiser["security.msg.signin.invalid"];
|
|
return false;
|
|
}
|
|
if (!userAccount.IsStatusActive)
|
|
{
|
|
SaLogger.Error(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;
|
|
|
|
// Log user in - Admin details stored against System Admin role.
|
|
SaxUserRole userRole = userRoles.Find(item => item.UserRole == Sax.UserRole.SystemAdmin);
|
|
if (userRole == null)
|
|
{
|
|
SaLogger.Warn(fn + " System Admin role not found for " + userAccount.EmailAddress);
|
|
errorMessage = Localiser["security.msg.signin.invalid"];
|
|
return false;
|
|
}
|
|
userRole.KeepLoggedIn = keepLoggedIn;
|
|
userRole.LastLoginAt = DateTime.Now;
|
|
userRole.InvalidLoginCount = 0;
|
|
userRole.LastInvalidLoginAt = null;
|
|
if (!AccountMgr.UpdateUserRole(userRole, DbUpdateInfo))
|
|
{
|
|
SaLogger.Warn(fn + " Failed to update Admin user role for " + userAccount.EmailAddress + ": " + AccountMgr.ErrorStateMessage);
|
|
errorMessage = Localiser["security.msg.signin.invalid"];
|
|
return false;
|
|
}
|
|
SaLogger.Info(fn + " User session opened for " + userAccount.EmailAddress, true);
|
|
|
|
// Create session.
|
|
OpenUserSession(SessionContext, userAccount, userRoles);
|
|
|
|
// Send the 'Keep Logged In' cookie if requested.
|
|
if (userRole.KeepLoggedIn)
|
|
SetKeepLoggedInCookie();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
/// <summary>Start a new user session on sign-in.</summary>
|
|
protected bool OpenUserSession(SaxSessionContext sessionContext, SaxUserAccount userAccount, List<SaxUserRole> userRoles)
|
|
{
|
|
// Validate user properties.
|
|
if (!SysId.IsValid(userAccount.Id)) return false;
|
|
if (StringUtil.IsEmpty(userAccount.EmailAddress)) return false;
|
|
|
|
// Close previous session.
|
|
CloseUserSession();
|
|
|
|
// Open session.
|
|
return SetSessionData(new SaxSessionData_Admin(sessionContext, userAccount, userRoles));
|
|
}
|
|
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()
|
|
{
|
|
String fn = "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 (SysId.IsValid(keepLoggedInParams.UserAccountId))
|
|
{
|
|
using (SaxUserAccount userAccount = AccountMgr.GetUserAccountById(keepLoggedInParams.UserAccountId))
|
|
{
|
|
if (userAccount != null)
|
|
{
|
|
if (userAccount.IsStatusActive)
|
|
{
|
|
String errorMessage = "";
|
|
if (SignUserIn(userAccount, true, ref errorMessage))
|
|
{
|
|
// Successful login.
|
|
SaLogger.Info(fn + " Keep Logged In cookie found." + logSuffix);
|
|
return true;
|
|
}
|
|
else SaLogger.Warn(fn + " Keep Logged In cookie found but failed to sign user in: " + errorMessage + "." + logSuffix);
|
|
}
|
|
else SaLogger.Warn(fn + " Keep Logged In cookie found but UserAccount is not active." + logSuffix);
|
|
}
|
|
else SaLogger.Warn(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(Sax.Config.Security.SignIn.KeepLoggedIn.CookieExpiresDays_Min, Math.Min(Sax.Config.Security.SignIn.KeepLoggedIn.CookieExpiresDays_Max, AppConfig.Security.SignIn.KeepLoggedIn.CookieExpiresDays));
|
|
SetResponseCookie(KeepLoggedIn.CookieName, keepLoggedInCookieValue, DateTime.Now.AddDays(cookieExpiresDays), true);
|
|
SaLogger.Debug(this.GetType().Name + ".SetKeepLoggedInCookie '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 (StringUtil.IsEmpty(GetRequestCookie(KeepLoggedIn.CookieName))) return;
|
|
// Clear cookie by setting expiry date in the past.
|
|
SetResponseCookie(KeepLoggedIn.CookieName, "", DateTime.Today.AddDays(-7), true);
|
|
SaLogger.Info($"{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 = "SXKLI";
|
|
public static String Base64Key = SacsEncryptionHelper.EncodePlainKey("<=Saxonite^{kEEp+1Ogg3d-IN}~32*>", SacsEncryptionHelper.KeyLength);
|
|
public static String Base64IV = "YOX10FIPp1zM1pg5U6fW7A==";
|
|
private 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_Admin.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;
|
|
}
|
|
}
|
|
public KeepLoggedInParams ExtractKeepLoggedInParams(String encryptedParams)
|
|
{
|
|
KeepLoggedInParams keepLoggedInParams = new KeepLoggedInParams();
|
|
if (!StringUtil.IsEmpty(encryptedParams))
|
|
{
|
|
NameValueCollection linkParams = DecryptParameters(encryptedParams, KeepLoggedIn.Base64IV, KeepLoggedIn.Base64Key);
|
|
keepLoggedInParams.UserAccountId = new SysId(StringUtil.ToInt(linkParams.Get(ParamName_KeepLoggedIn_UserAccountId)));
|
|
}
|
|
if (DebugMode) SaLogger.Info($"{SysUtil.Fn} UserAccountId: " + SysId.ToString(keepLoggedInParams.UserAccountId));
|
|
return keepLoggedInParams;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Image utilities.
|
|
//-------------------------------------------------------------------
|
|
/// <summary>
|
|
/// Construct URL to display Organisation logo.
|
|
/// <para>- Option to allow for 'Upload' image if active.</para>
|
|
/// </summary>
|
|
protected String GetOrganisationLogoLink(SysId organisationId, bool uploadImage)
|
|
{
|
|
String imageLink = "Image/Organisation/Logo" + (uploadImage ? "/Upload" : "") + "?";
|
|
NameValueCollection parameters = new NameValueCollection();
|
|
parameters.Add(ImageController.ParamName_OrganisationId, SysId.ToString(organisationId));
|
|
return imageLink + ImageController.ParamName_OrganisationLogoEncrypted + "=" + EncryptSessionParams(parameters);
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// 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_AdminApp { get; } = SacsEncryptionHelper.EncodePlainKey("Saxonite&Admin#Encryption-" + DateTime.Today.Format("ddMMyy"), SacsEncryptionHelper.KeyLength);
|
|
protected String EncryptSessionParams(NameValueCollection parameters)
|
|
{
|
|
return HttpUtility.UrlEncode(EncryptParameters(parameters, SessionData_Admin.Base64IV_AdminApp, Base64Key_AdminApp));
|
|
}
|
|
public static String EncryptSessionParams(SaxController_Admin controller, NameValueCollection parameters)
|
|
{
|
|
return controller.EncryptSessionParams(parameters);
|
|
}
|
|
protected NameValueCollection ExtractSessionParams(String encryptedParams)
|
|
{
|
|
return DecryptParameters(encryptedParams, SessionData_Admin.Base64IV_AdminApp, Base64Key_AdminApp);
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
}
|
|
//=======================================================================
|
|
}
|