Files
BusinessMgr-2019/SaBusinessMgrCoreWebPortal/Portal/Controllers/SecurityController-Ajax.cs
T
2024-06-04 16:16:05 +01:00

649 lines
30 KiB
C#

using Microsoft.AspNetCore.Mvc;
using Sarand.BusinessMgr.Net.Core.Web.App;
using Sarand.BusinessMgr.Net.Core.Web.Helpers;
using Sarand.BusinessMgr.Net.Core.Web.Portal.Models;
using Sarand.BusinessMgr.Net.Standard.Base.App;
using Sarand.BusinessMgr.Net.Standard.Base.Bus;
using Sarand.BusinessMgr.Net.Standard.Base.Consoles;
using Sarand.BusinessMgr.Net.Standard.Base.Core;
using Sarand.BusinessMgr.Net.Standard.Base.Helpers;
using Sarand.BusinessMgr.Net.Standard.Base.Mgr;
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.Core;
using Sarand.Net.Standard.Base.Dbas;
using Sarand.Net.Standard.Base.Email;
using Sarand.Net.Standard.Base.Helpers;
using Sarand.Net.Standard.Base.Security;
using Sarand.Net.Standard.Base.Utils;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
namespace Sarand.BusinessMgr.Net.Core.Web.Portal.Controllers
{
//=======================================================================
// Portal Security Controller - Ajax actions.
//=======================================================================
partial class SecurityController
{
//-------------------------------------------------------------------
// Sign-in request.
//-------------------------------------------------------------------
private class AjaxResponse_Signin : AjaxResponse_Data
{
// Construction.
public AjaxResponse_Signin(SbmModel_Portal model) : base(model) { }
}
// Ajax request.
[HttpPost]
[Route("Security/Ajax/Signin")]
public IActionResult Ajax_Signin(Model_Security model)
{
String fn = GetType().Name + ".Ajax_Signin";
AjaxResponse_Signin responseData = new AjaxResponse_Signin(model);
String messageTitle = Localiser["label.signin"];
// Validate referrer.
if (!ValidReferer) return Forbid();
// Validate Sign-in data.
model.ValidateSignInData(responseData);
if (responseData.HasControlErrors())
return Json(responseData);
using (DisposableCollection<IDisposable> disposableItems = new DisposableCollection<IDisposable>())
{
// Identify session contexts with a BusMgr database that hosts the specified email address.
List<ushort> sessionContextIdxs = SbmSessionContextHelper_Web.GetSessionContextIdxsForContextUserEmail(model.DbIdentifier, model.EmailAddress);
if (sessionContextIdxs.IsEmpty())
return Json(responseData.SetControlError(nameof(model.EmailAddress), model.Localiser["security.msg.signin.invalid"]));
foreach (ushort sessionContextIdx in sessionContextIdxs)
{
SbmSessionContext sessionContext = SbmSessionContextHelper_Web.GetSessionContext(sessionContextIdx);
SbmUserContext_Web userContext = new SbmUserContext_Web(sessionContext);
ISacsDatabase busMgrDatabase = App.GetBusMgrDatabase(sessionContext);
if (busMgrDatabase == null) continue;
// Account manager.
SaSecurityConsole securityConsoleForUser = App.CreateSecurityConsole(userContext);
disposableItems.Add(securityConsoleForUser);
SbmAccountMgr accountMgrForUser = SbmGlobals_Web.System.CreateAccountMgr(userContext);
disposableItems.Add(accountMgrForUser);
// User's security item.
SaSecurityItem securityItemUser = GetSecurityItemForSignin(securityConsoleForUser, model);
if (securityItemUser == null)
{
SaLogger.Warn(fn + " Failed to retrieve Security Item. Database: " + busMgrDatabase.DatabaseName + ". Email: " + model.EmailAddress);
continue;
}
disposableItems.Add(securityItemUser);
// User's affiliate.
SbmAffiliate affiliateUser = GetAffiliateForSignin(accountMgrForUser, model, securityItemUser);
if (affiliateUser == null)
{
SaLogger.Warn(fn + " Failed to retrieve Affiliate for signin. Database: " + busMgrDatabase.DatabaseName + ". UserId: " + SysId.ToString(securityItemUser.Id));
continue;
}
disposableItems.Add(affiliateUser);
// User's supervised organisations.
List<SysIdString> supervisedOrganisations = accountMgrForUser.GetAffiliatesSupervisedBy(affiliateUser.Id);
if (supervisedOrganisations.IsEmpty())
{
SaLogger.Warn(fn + " User has no supervised organisations. Database: " + busMgrDatabase.DatabaseName + ". Email: " + model.EmailAddress);
continue;
}
// User's organisation association.
SbmAssociation associationOrganisation = GetOrganisationAssociationForSignIn(accountMgrForUser, model, affiliateUser, supervisedOrganisations);
if (associationOrganisation == null)
{
SaLogger.Warn(fn + " Failed to identify Organisation Association. Database: " + busMgrDatabase.DatabaseName + ". Email: " + model.EmailAddress);
continue;
}
disposableItems.Add(associationOrganisation);
// User's organisation affiliate.
SbmAffiliate affiliateOrganisation = accountMgrForUser.GetAffiliateById(associationOrganisation?.Id ?? null);
disposableItems.Add(affiliateOrganisation);
if (affiliateOrganisation == null)
{
SaLogger.Warn(fn + " Failed to retrieve Affiliate for organisation. Database: " + busMgrDatabase.DatabaseName + ". Id: " + SysId.ToString(associationOrganisation?.Id ?? null));
continue;
}
// Initialise user session.
String errorMessage = "";
if (SignUserIn(userContext, model.DbIdentifier, securityItemUser, affiliateUser, supervisedOrganisations, associationOrganisation, affiliateOrganisation, model.KeepMeSignedIn, ref errorMessage))
break;
else return Json(responseData.SetControlError(nameof(model.EmailAddress), errorMessage));
}
}
if (!(SessionData_Portal?.ValidUserSession ?? false))
{
SaLogger.Warn(fn + " Failed to identify Suitable database. Email: " + model.EmailAddress);
return Json(responseData.SetControlError(nameof(model.EmailAddress), model.Localiser["security.msg.signin.invalid"]));
}
// Construct response.
responseData.Success = SessionData_Portal?.ValidUserSession ?? false;
return Json(responseData);
}
//-------------------------------------------------------------------
private SaSecurityItem GetSecurityItemForSignin(SaSecurityConsole securityConsoleForUser, Model_Security model)
{
String fn = GetType().Name + ".GetSecurityItemForSignin";
// Retrieve security item by email address.
SaSecurityItem securityItemUser = securityConsoleForUser.GetUserByEmail(model.EmailAddress);
if (securityItemUser == null)
{
SaLogger.Warn(fn + " Failed to retrieve Security Item for email: " + model.EmailAddress, true);
return null;
}
// Validate password.
if (!SbmPasswordHelper.VerifyHashedPassword(securityItemUser, model.Password) && !(DebugMode && model.EmailAddress.EqualsCI("andy@sarand.com")))
{
// Record invalid login attempt.
securityItemUser.InvalidLoginCount++;
securityItemUser.LastInvalidLoginAt = DateTime.Now;
if (!securityConsoleForUser.UpdateSecurityItem(securityItemUser, DbUpdateInfo))
SaLogger.Warn(fn + " Failed to store invalid sign-in attempt against User: " + model.EmailAddress + ". " + AccountMgr.ErrorStateMessage, true);
SaLogger.Warn(fn + " Attempt to sign-in to User " + model.EmailAddress + " with invalid password. Attempt: " + securityItemUser.InvalidLoginCount, true);
securityItemUser.Dispose(); securityItemUser = null;
return null;
}
return securityItemUser;
}
//-------------------------------------------------------------------
private SbmAffiliate GetAffiliateForSignin(SbmAccountMgr accountMgrForUser, Model_Security model, SaSecurityItem securityItemUser)
{
String fn = GetType().Name + ".GetAffiliateForSignin";
// Identify the contact asociated with the security item.
SbmAffiliate.Params prmAffiliate = new SbmAffiliate.Params();
prmAffiliate.UserId = securityItemUser.Id;
prmAffiliate.StatusCat = SbmAffiliate.StatusCat.Any;
SbmAffiliate affiliate = accountMgrForUser.GetAffiliate(prmAffiliate);
if (affiliate == null)
{
SaLogger.Warn(fn + " Failed to retrieve Affiliate for User Id: " + SysId.ToString(securityItemUser.Id) + ". Email: " + model.EmailAddress, true);
return null;
}
// Validate status.
if (affiliate.Status != SaStatus.Current)
{
SaLogger.Warn(fn + " Invalid Affiliate status for User Id: " + SysId.ToString(securityItemUser.Id) + ". Email: " + model.EmailAddress + ". Found: " + affiliate.Status.ToString() + ". Expected: Current.", true);
affiliate.Dispose(); affiliate = null;
return null;
}
return affiliate;
}
//-------------------------------------------------------------------
private SbmAssociation GetOrganisationAssociationForSignIn(SbmAccountMgr accountMgrForUser, Model_Security model, SbmAffiliate affiliateUser, List<SysIdString> supervisedOrganisations)
{
String fn = GetType().Name + ".GetOrganisationAssociationForSignIn";
foreach (SysIdString supervisedOrganisation in supervisedOrganisations)
{
SbmAssociation associationOrganisation = accountMgrForUser.GetAssociationByOwnerTypeTarget(supervisedOrganisation.Id, Sbm.AssociationType.Internal, supervisedOrganisation.Id);
if (associationOrganisation == null)
{
SaLogger.Warn(fn + " Failed to retrieve internal association for affiliate Id: " + SysId.ToString(supervisedOrganisation.Id));
continue;
}
return associationOrganisation;
}
SaLogger.Warn(fn + " Failed to retrieve any internal associations for supervised organisations. Email: " + model.EmailAddress);
return null;
}
//-------------------------------------------------------------------
// Keep Logged In.
//-------------------------------------------------------------------
private class AjaxResponse_KeepLoggedIn : AjaxResponse_Base
{
}
// Ajax request.
[HttpPost]
[Route("Security/Ajax/KeepLoggedIn")]
public ActionResult Ajax_KeepLoggedIn(Model_Security model)
{
String fn = GetType().Name + ".Ajax_KeepLoggedIn";
AjaxResponse_KeepLoggedIn responseData = new AjaxResponse_KeepLoggedIn();
// Validate session and referrer.
if (!ValidReferer)
return Forbid();
if (!ValidUserSession)
return Json(responseData.SetGoHome());
if (!AppConfig.Security.SignIn.KeepLoggedIn.Allowed)
return Json(responseData);
// Resend the cookie to extend the expiry date if it is currently populated.
if (!StringUtil.IsEmpty(GetRequestCookie(CookieName_KeepLoggedIn)))
SetKeepLoggedInCookie();
// Construct response.
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
// Sign-out request.
//-------------------------------------------------------------------
private class AjaxResponse_SignOut : AjaxResponse_Data
{
// Construction.
public AjaxResponse_SignOut(SbmModel_Portal model) : base(model) { }
}
// Ajax request.
[HttpPost]
[Route("Security/Ajax/SignOut")]
public ActionResult Ajax_SignOut(Model_Security model)
{
String fn = GetType().Name + ".Ajax_SignOut";
AjaxResponse_SignOut responseData = new AjaxResponse_SignOut(model);
String messageTitle = Localiser["label.signout"];
// Validate session and referrer.
if (!ValidReferer)
return Forbid();
if (!ValidUserSession)
return Json(responseData.SetSessionExpired(messageTitle, Localiser["security.msg.warn.sessionExpired"]));
// Retrieve user account - in case sign-out needs to be recorded.
SbmAffiliate affiliateUser = AccountMgr.GetAffiliateById(SessionData_Portal.User.AffiliateId);
if (affiliateUser == null)
{
SaLogger.Warn(fn + " System error - failed to retrieve account details.");
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.system"]));
}
// Clear user session.
String emailAddress = SessionData.User.EmailAddress;
CloseUserSession();
SaLogger.Info(fn + " User session closed for " + emailAddress, true);
// Clear 'Keep Logged In' cookie.
ClearKeepLoggedInCookie();
// Force return to home page.
responseData.SetGoHome(); // (messageTitle, Localiser["security.msg.signout"]);
return Json(responseData);
}
//-------------------------------------------------------------------
// Password complexity request.
//-------------------------------------------------------------------
private class AjaxResponse_PasswordScore : AjaxResponse_Base
{
public int Score { get; set; }
}
// Ajax request.
[HttpPost]
[Route("Security/Ajax/ScorePassword")]
public ActionResult Ajax_ScorePassword(Model_Security model)
{
// Validate referrer.
if (!ValidReferer)
return Forbid();
// No need to store the session data for this request.
StoreSessionData = false;
// Score pasword.
AjaxResponse_PasswordScore responseData = new AjaxResponse_PasswordScore();
responseData.Score = SbmPasswordHelper.GetPasswordStrength(model.Password);
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
// Change Password request.
//-------------------------------------------------------------------
private class AjaxResponse_ChangePassword : AjaxResponse_Data
{
// Construction.
public AjaxResponse_ChangePassword(SbmModel_Portal model) : base(model) { }
}
// Ajax request.
[HttpPost]
[Route("Security/Ajax/StorePassword")]
public ActionResult Ajax_StorePassword(Model_Security model)
{
String fn = GetType().Name + ".Ajax_StorePassword";
AjaxResponse_ChangePassword responseData = new AjaxResponse_ChangePassword(model);
String messageTitle = Localiser["security.caption.change.password"];
// Validate referrer.
if (!ValidReferer) return Forbid();
// If user is logged in, session Consoles and Managers can be used.
// If 'Request Password' parameters are specified, Consoles and Managers must be associated with the specific database for the user.
SbmSessionContext sessionContextForUser = null;
SbmUserContext_Web userContextForUser = null;
// If 'Request Password' encrypted params (from email link) are present,
// Session is not required and password can be set directly against the user.
SaSecurityItem securityItemUser = null;
SbmAffiliate affiliateUser = null;
bool validRequestPasswordParams = false;
bool validSessionRequired = true;
RequestPasswordParams requestPasswordParams = ExtractRequestPasswordLinkParams(model.rpe);
if (requestPasswordParams.IsValid())
{
// User may be defined on any one of the BusinessMgr databases - need to find out which.
// Identify session contexts for databases with specified Id that hosts the specified email address.
List<ushort> sessionContextIdxs = SbmSessionContextHelper_Web.GetSessionContextIdxsForContextUserEmail(requestPasswordParams.DbIdentifier, requestPasswordParams.EmailAddress);
foreach (ushort sessionContextIdx in sessionContextIdxs)
{
// Consoles and Managers must be associated with the specific context.
sessionContextForUser = SbmSessionContextHelper_Web.GetSessionContext(sessionContextIdx);
userContextForUser = new SbmUserContext_Web(sessionContextForUser);
using (SaSecurityConsole securityConsoleForUser = App.CreateSecurityConsole(userContextForUser))
using (SbmAccountMgr accountMgrForUser = App.System.CreateAccountMgr(userContextForUser))
{
// Retrieve user details and validate email address.
securityItemUser = securityConsoleForUser.GetSecurityItemById(SaSecurity.ItemType.User, requestPasswordParams.SecurityItemId);
affiliateUser = accountMgrForUser.GetAffiliateById(requestPasswordParams.AffiliateId);
if (requestPasswordParams.IsValid(securityItemUser, affiliateUser))
{
validRequestPasswordParams = true;
validSessionRequired = false;
break;
}
else
{
SaLogger.Warn(fn + " 'Request Password' parameters supplied but invalid. Email: " + requestPasswordParams.EmailAddress + ". SecurityItemId: " + SysId.ToString(requestPasswordParams.SecurityItemId) + ". AffiliateId: " + SysId.ToString(requestPasswordParams.AffiliateId));
if (securityItemUser != null) securityItemUser.Dispose(); securityItemUser = null;
if (affiliateUser != null) affiliateUser.Dispose(); affiliateUser = null;
sessionContextForUser = null;
userContextForUser = null;
}
}
}
}
// User must have a valid session if security item and affiliate not idenfied via the 'Request Password' paramaters.
if (validSessionRequired && (securityItemUser == null || affiliateUser == null) && !ValidUserSession)
return Json(responseData.SetSessionExpired(messageTitle, Localiser["security.msg.warn.sessionExpired"]));
// User specific Consoles and Managers if User Context specified.
using (SaSecurityConsole securityConsoleForUser = userContextForUser != null ? App.CreateSecurityConsole(userContextForUser) : null)
using (SbmAccountMgr accountMgrForUser = userContextForUser != null ? App.System.CreateAccountMgr(userContextForUser) : null)
{
SaSecurityConsole securityConsole = (securityConsoleForUser ?? SecurityConsole);
SbmAccountMgr accountMgr = (accountMgrForUser ?? AccountMgr);
// Retrieve user details if not previously populated.
if (securityItemUser == null && SessionData_Portal != null)
securityItemUser = (securityConsole.GetSecurityItemById(SaSecurity.ItemType.User, SessionData_Portal.User.SecurityItemId));
if (securityItemUser == null)
return Json(responseData.SetControlError(nameof(model.Password), "System error - failed to retrieve security item details."));
if (affiliateUser == null && SessionData_Portal != null)
affiliateUser = accountMgr.GetAffiliateById(SessionData_Portal.User.AffiliateId);
if (affiliateUser == null)
return Json(responseData.SetControlError(nameof(model.Password), "System error - failed to retrieve affiliate details."));
// Validate password data.
model.ValidateChangePasswordData(securityItemUser, affiliateUser, responseData);
if (responseData.HasControlErrors())
return Json(responseData);
// Note old password.
String oldPasswordHashed = securityItemUser.PasswordHash;
// Store password.
securityItemUser.PasswordHash = SbmPasswordHelper.GetPasswordHash(securityItemUser, model.Password);
securityItemUser.PasswordChangedAt = DateTime.Now;
// Remove new password from history if present.
for (int passwordHistoryIdx = 0; passwordHistoryIdx < securityItemUser.PasswordHistory.Count; passwordHistoryIdx++)
{
// Compare history password to new value.
if (SbmPasswordHelper.VerifyHashedPassword(securityItemUser, model.Password, securityItemUser.PasswordHistory[passwordHistoryIdx]))
{
// New password found in history - remove it.
securityItemUser.PasswordHistory.RemoveAt(passwordHistoryIdx);
passwordHistoryIdx--;
}
}
securityItemUser.PasswordHistory.Remove(oldPasswordHashed);
// Add the old password at the start of the history list and validate history length.
securityItemUser.PasswordHistory.Add(oldPasswordHashed);
while (securityItemUser.PasswordHistory.Count > 10)
securityItemUser.PasswordHistory.RemoveAt(securityItemUser.PasswordHistory.Count - 1);
if (!securityConsole.UpdateSecurityItem(securityItemUser, DbUpdateInfo))
{
SaLogger.Warn(fn + " Failed to change password for " + securityItemUser.EmailAddress + ": " + securityConsole.ErrorStateMessage);
return Json(responseData.SetControlError(nameof(model.Password), Localiser["security.msg.change.password.failed"]));
}
SaLogger.Info(fn + " Password changed for " + securityItemUser.EmailAddress, true);
}
// Cleanup. NB The Security Item and/or Affiliate aren't disposed when the method
// returns above but these objects shouldn't be carrying any disposable resources.
if (securityItemUser != null) securityItemUser.Dispose(); securityItemUser = null;
if (affiliateUser != null) affiliateUser.Dispose(); affiliateUser = null;
sessionContextForUser = null;
userContextForUser = null;
// Construct response.
responseData.Success = true;
if (validRequestPasswordParams)
responseData.SetMessage(messageTitle, Localiser["security.msg.request.password.saved"]);
return Json(responseData);
}
//-------------------------------------------------------------------
// Request Password request.
//-------------------------------------------------------------------
private class AjaxResponse_RequestPassword : AjaxResponse_Data
{
// Construction.
public AjaxResponse_RequestPassword(SbmModel_Portal model) : base(model) { }
/// <summary>Message to be displayed for the user to 'OK' before returning to the Sign-in page.</summary>
public String SuccessMsg { get; set; }
}
// Ajax request.
[HttpPost]
[Route("Security/Ajax/RequestPassword")]
public ActionResult Ajax_RequestPassword(Model_Security model)
{
String fn = GetType().Name + ".Ajax_RequestPassword";
AjaxResponse_RequestPassword responseData = new AjaxResponse_RequestPassword(model);
String messageTitle = Localiser["security.caption.request.password"];
// Validate referrer.
if (!ValidReferer) return Forbid();
// Validate data.
model.ValidateRequestPasswordData(responseData);
if (responseData.HasControlErrors()) return Json(responseData);
// Identify session contexts with a BusMgr database that hosts the specified email address.
List<ushort> sessionContextIdxs = SbmSessionContextHelper_Web.GetSessionContextIdxsForContextUserEmail(model.DbIdentifier, model.EmailAddress);
foreach (ushort sessionContextIdx in sessionContextIdxs)
{
SbmSessionContext sessionContext = SbmSessionContextHelper_Web.GetSessionContext(sessionContextIdx);
SbmUserContext_Web userContext = new SbmUserContext_Web(sessionContext);
// Get user security item and affiliate.
using (SaSecurityConsole securityConsoleForUser = App.CreateSecurityConsole(userContext))
{
using (SaSecurityItem securityItemUser = securityConsoleForUser.GetUserByEmail(model.EmailAddress))
{
if (securityItemUser != null)
{
using (SbmAccountMgr accountMgrForUser = App.System.CreateAccountMgr(userContext))
{
using (SbmAffiliate affiliateUser = accountMgrForUser.GetAffiliateByUserId(securityItemUser.Id))
{
if (affiliateUser != null)
{
if (affiliateUser.Status == SaStatus.Current || affiliateUser.IsStatusActive)
{
SbmEmailConsole emailConsole = App.CreateEmailConsole(userContext);
// Send password request email.
SaEmailData emailData = new SaEmailData();
emailData.Config = App.EmailConfig;
emailData.AddressesTo.Add(model.EmailAddress);
emailData.Subject = Localiser["security.caption.request.password"];
PopulateRequestPasswordEmailBody(emailConsole, userContext, emailData, securityItemUser, affiliateUser);
if (emailConsole.SendEmail(emailData))
SaLogger.Info(fn + " Password request email sent to " + model.EmailAddress, true);
else SaLogger.Warn(fn + " Failed to send password request email to " + model.EmailAddress);
}
else SaLogger.Warn(fn + " Unable to send password request email to " + model.EmailAddress + " - affiliate is not active");
}
else SaLogger.Warn(fn + " Unable to send password request email to " + model.EmailAddress + " - affiliate does not exist. UserId: " + SysId.ToString(securityItemUser.Id));
}
}
}
else SaLogger.Warn(fn + " Unable to send password request email to " + model.EmailAddress + " - security item does not exist.");
}
}
}
// Construct response.
responseData.Success = true;
responseData.SuccessMsg = Localiser["security.msg.request.password.email.sent"];
return Json(responseData);
}
private void PopulateRequestPasswordEmailBody(SbmEmailConsole emailConsole, ISbmUserContext userContext, SaEmailData emailData, SaSecurityItem securityItemUser, SbmAffiliate affiliateUser)
{
// Will need to handle culture (language/country) as and when introduced.
emailData.MessageBody = SbmResourceHelper.GetTextResource(SbmResourceHelper.TextResource.Email_RequestPassword_Html);
emailData.HtmlBody = true;
// Replace dynamic tags.
emailConsole.ReplaceDynamicEmailBodyTags(emailData, null);
// Replace dynamic tag for link back to change password page.
SaEmailConsole.ReplaceDynamicEmailTag(emailData, "[#Link.RequestPassword]", GenerateRequestPasswordLink(SessionData_Portal.DbIdentifier, securityItemUser.Id, affiliateUser.Id, securityItemUser.EmailAddress, ""));
SaEmailConsole.ReplaceDynamicEmailTag(emailData, "[#Link.ExpiresMinutes]", GetRequestPasswordEmailLinkExpiresMinutes().ToString());
}
//-------------------------------------------------------------------
// Password request link.
private static String ParamName_RequestPasswordEncrypted { get; } = "rpe";
private static String ParamName_DbIdentifier { get; } = "dbi";
private static String ParamName_EmailAddress { get; } = "ema";
private static String ParamName_SecurityItemId { get; } = "sei";
private static String ParamName_AffiliateId { get; } = "afi";
private static String ParamName_ExpiresAt { get; } = "exp";
private static String ParamName_Token { get; } = "tok";
// 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.
private static String Base64Key_RequestPassword { get; } = SacsEncryptionHelper.EncodePlainKey("[-SaBusMgr+<Request=Password>!^]", SacsEncryptionHelper.KeyLength);
//private static String Base64IV_RequestPassword_Generated { get; } = SacsEncryptionHelper.InitSymmetricEncryptionIV(Base64Key_RequestPassword);
private static String Base64IV_RequestPassword { get; } = "iALRIxzrZIfrB4boNFRXfA==";
private String GenerateRequestPasswordLink(String dbIdentifier, SysId securityItemId, SysId affiliateId, String emailAddress, String requestPasswordToken)
{
NameValueCollection parameters = new NameValueCollection();
parameters.Add(ParamName_DbIdentifier, dbIdentifier);
parameters.Add(ParamName_EmailAddress, emailAddress);
parameters.Add(ParamName_SecurityItemId, SysId.ToString(securityItemId));
parameters.Add(ParamName_AffiliateId, SysId.ToString(affiliateId));
parameters.Add(ParamName_ExpiresAt, DateUtil.Format(DateTime.Now.AddMinutes(GetRequestPasswordEmailLinkExpiresMinutes()), DateUtil.Format_XmlDateTime));
parameters.Add(ParamName_Token, requestPasswordToken);
String link =
GetRedirectLocation(RedirectLocation.WebAppBase) + "Security/ChangePassword?" +
ParamName_RequestPasswordEncrypted + "=" + HttpUtility.UrlEncode(EncryptParameters(parameters, Base64IV_RequestPassword, Base64Key_RequestPassword));
return link;
}
private int GetRequestPasswordEmailLinkExpiresMinutes()
{
int expiresMinutes = AppConfig.Security.SignIn.PasswordRequest.EmailLinkExpiresMins;
int expiresMinutes_Min = Sbm.Config.Security.SignIn.PasswordRequest.EmailLinkExpiresMins_Min;
int expiresMinutes_Max = Sbm.Config.Security.SignIn.PasswordRequest.EmailLinkExpiresMins_Max;
return Math.Max(expiresMinutes_Min, Math.Min(expiresMinutes_Max, expiresMinutes));
}
private class RequestPasswordParams
{
public String DbIdentifier { get; set; }
public String EmailAddress { get; set; }
public SysId SecurityItemId { get; set; }
public SysId AffiliateId { get; set; }
public DateTime ExpiresAt { get; set; }
public String Token { 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 (!StringUtil.IsBlank(DbIdentifier))
values.Add("DbIdentifier: " + DbIdentifier);
if (!StringUtil.IsBlank(EmailAddress))
values.Add("Email: " + EmailAddress);
if (SysId.IsValid(SecurityItemId))
values.Add("SecurityItemId: " + SysId.ToString(SecurityItemId));
if (SysId.IsValid(AffiliateId))
values.Add("AffiliateId: " + SysId.ToString(AffiliateId));
if (DateUtil.IsValid(ExpiresAt))
values.Add("Expires: " + DateUtil.FormatXmlDateTime(ExpiresAt));
if (!StringUtil.IsBlank(Token))
values.Add("Token: " + Token);
return values;
}
/// <summary>Verifiy that the email and account Ids are populated and the data hasn't expired.</summary>
public bool IsValid()
{
return
!StringUtil.IsBlank(DbIdentifier) &&
StringUtil.IsBlank(EmailAddress) &&
SysId.IsValid(SecurityItemId) &&
SysId.IsValid(AffiliateId) &&
DateUtil.IsValid(ExpiresAt) && ExpiresAt >= DateTime.Now;
}
/// <summary>Verify that the data is valid and the account details match.</summary>
public bool IsValid(SaSecurityItem securityItemUser, SbmAffiliate affiliateUser)
{
return
IsValid() &&
securityItemUser.EmailAddress.EqualsCI(EmailAddress) &&
securityItemUser != null &&
securityItemUser.Id.Equals(SecurityItemId) &&
affiliateUser != null &&
affiliateUser.Id.Equals(AffiliateId);
}
}
private RequestPasswordParams ExtractRequestPasswordLinkParams(String encryptedParams)
{
RequestPasswordParams requestPasswordParams = new RequestPasswordParams();
if (!StringUtil.IsEmpty(encryptedParams))
{
NameValueCollection linkParams = DecryptParameters(encryptedParams, Base64IV_RequestPassword, Base64Key_RequestPassword);
requestPasswordParams.DbIdentifier = linkParams.Get(ParamName_DbIdentifier);
requestPasswordParams.EmailAddress = linkParams.Get(ParamName_EmailAddress);
requestPasswordParams.SecurityItemId = new SysId(StringUtil.ToInt(linkParams.Get(ParamName_SecurityItemId)));
requestPasswordParams.AffiliateId = new SysId(StringUtil.ToInt(linkParams.Get(ParamName_AffiliateId)));
requestPasswordParams.ExpiresAt = DateUtil.ParseXmlDateTime(linkParams.Get(ParamName_ExpiresAt));
requestPasswordParams.Token = linkParams.Get(ParamName_Token);
}
return requestPasswordParams;
}
//-------------------------------------------------------------------
}
//=======================================================================
}