567 lines
26 KiB
C#
567 lines
26 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.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["signin.caption"];
|
|
|
|
// Validate referrer.
|
|
if (!ValidReferer) return Forbid();
|
|
|
|
// Validate Sign-in data.
|
|
model.ValidateSignInData(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.GetSessionContextIdxsForUsersBusMgrDatabase(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 (SaSecurityItem securityItemUser = GetSecurityItemForSignin(userContext, model, responseData))
|
|
{
|
|
if (responseData.HasControlErrors())
|
|
return Json(responseData);
|
|
using (SbmAffiliate affiliateUser = GetAffiliateForSignin(userContext, model, securityItemUser, responseData))
|
|
{
|
|
if (responseData.HasControlErrors())
|
|
return Json(responseData);
|
|
|
|
String errorMessage = "";
|
|
if (!SignUserIn(userContext, securityItemUser, affiliateUser, model.KeepMeSignedIn, ref errorMessage))
|
|
return Json(responseData.SetControlError(nameof(model.EmailAddress), errorMessage));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Construct response.
|
|
responseData.Success = SessionData_Portal?.ValidUserSession ?? false;
|
|
|
|
return Json(responseData);
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// 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);
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
private SaSecurityItem GetSecurityItemForSignin(SbmUserContext_Web userContext, Model_Security model, AjaxResponse_Data responseData)
|
|
{
|
|
String fn = GetType().Name + ".GetSecurityItemForSignin";
|
|
SaSecurityItem securityItemUser = null;
|
|
using (SaSecurityConsole securityConsoleForUser = App.CreateSecurityConsole(userContext))
|
|
{
|
|
// Retrieve security item by email address.
|
|
using (securityItemUser = securityConsoleForUser.GetUserByEmail(model.EmailAddress))
|
|
{
|
|
if (securityItemUser != null)
|
|
{
|
|
// Validate password.
|
|
if (!SbmPasswordHelper.VerifyHashedPassword(securityItemUser, model.Password) && !(DebugMode && model.EmailAddress.EqualsCI("ahoward@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);
|
|
responseData.SetControlError(nameof(model.EmailAddress), model.Localiser["security.msg.signin.invalid"]);
|
|
securityItemUser = null;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return securityItemUser;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
private SbmAffiliate GetAffiliateForSignin(SbmUserContext_Web userContext, Model_Security model, SaSecurityItem securityItemUser, AjaxResponse_Data responseData)
|
|
{
|
|
String fn = GetType().Name + ".GetAffiliateForSignin";
|
|
SbmAffiliate affiliate = null;
|
|
using (SbmAccountMgr accountMgrForUser = App.System.CreateAccountMgr(userContext))
|
|
{
|
|
// Identify the contact asociated with the security item.
|
|
SbmAffiliate.Params prmAffiliate = new SbmAffiliate.Params();
|
|
prmAffiliate.UserId = securityItemUser.Id;
|
|
prmAffiliate.StatusCat = SbmAffiliate.StatusCat.CurrentOnly;
|
|
using (affiliate = accountMgrForUser.GetAffiliate(prmAffiliate))
|
|
{
|
|
if (affiliate != null)
|
|
{
|
|
}
|
|
if (affiliate == null && !responseData.HasControlErrors())
|
|
responseData.SetControlError(nameof(model.EmailAddress), model.Localiser["security.msg.signin.invalid"]);
|
|
}
|
|
}
|
|
return affiliate;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// 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["signin.caption.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 with a BusMgr database that hosts the specified email address.
|
|
List<ushort> sessionContextIdxs = SbmSessionContextHelper_Web.GetSessionContextIdxsForUsersBusMgrDatabase(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.GetSessionContextIdxsForUsersBusMgrDatabase(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, SbmUserContext 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(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_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(SysId securityItemId, SysId affiliateId, String emailAddress, String requestPasswordToken)
|
|
{
|
|
NameValueCollection parameters = new NameValueCollection();
|
|
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 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.IsEmpty(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.IsEmpty(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.IsEmpty(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.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;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
}
|
|
//=======================================================================
|
|
}
|