This commit is contained in:
2022-02-16 10:47:45 +00:00
parent 5d15b28456
commit 4a394d3ff7
75 changed files with 3127 additions and 691 deletions
@@ -45,15 +45,21 @@ namespace Saxonite.Net.Core.Web.Admin.Controllers
if (responseData.HasControlErrors())
return Json(responseData);
// Get user account.
SaxUserAccount userAccount = GetUserAccountForSignin(model, responseData);
if (responseData.HasControlErrors())
return Json(responseData);
String errorMessage = "";
if (!SignUserIn(userAccount, AccountMgr.GetRolesForUser(userAccount.Id), model.KeepMeSignedIn, ref errorMessage))
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<SacsHierarchyItem> disposableItems = new DisposableCollection<SacsHierarchyItem>())
{
return Json(responseData.SetControlError(nameof(model.EmailAddress), errorMessage));
// Get user account.
SaxUserAccount userAccount = GetUserAccountForSignin(model, responseData);
disposableItems.Add(userAccount);
if (responseData.HasControlErrors())
return Json(responseData);
String errorMessage = "";
if (!SignUserIn(userAccount, AccountMgr.GetRolesForUser(userAccount.Id), model.KeepMeSignedIn, ref errorMessage))
{
return Json(responseData.SetControlError(nameof(model.EmailAddress), errorMessage));
}
}
// Construct response.
@@ -74,6 +80,7 @@ namespace Saxonite.Net.Core.Web.Admin.Controllers
public ActionResult Ajax_KeepLoggedIn(Model_Security model)
{
String fn = GetType().Name + ".Ajax_KeepLoggedIn";
if (DebugMode) SaLogger.Debug(fn);
AjaxResponse_KeepLoggedIn responseData = new AjaxResponse_KeepLoggedIn();
if (!ValidUserSession)
@@ -96,43 +103,50 @@ namespace Saxonite.Net.Core.Web.Admin.Controllers
private SaxUserAccount GetUserAccountForSignin(Model_Security model, AjaxResponse_Data responseData)
{
String fn = GetType().Name + ".GetUserAccountForSignin";
// Identify user account.
SaxUserAccount userAccount = null;
SaxUserAccount.Params prmUserAccount = new SaxUserAccount.Params();
prmUserAccount.EmailAddress = model.EmailAddress;
prmUserAccount.StatusCat = SaxUserAccount.StatusCat.Any;
foreach (SaxUserAccount userAccountDuplicate in AccountMgr.GetUserAccountList(prmUserAccount))
{
// Account must be active to allow sign-in.
// NB Debug testing allows 'Awaiting' accounts to sign-in.
if ((SaxSystem.DebugMode && userAccountDuplicate.Status == SaStatus.Awaiting) || userAccountDuplicate.IsStatusActive)
{
userAccount = userAccountDuplicate;
break;
}
SaLogger.Warn(fn + " Attempt to sign-in to User Account for " + model.EmailAddress + " with status " + SaxSystem.GetStatusDesc(userAccount.Status), true);
}
if (userAccount != null)
{
if (!SaxPasswordHelper.VerifyHashedPassword(userAccount, model.Password) && !(DebugMode && model.EmailAddress.EqualsCI("andy@sarand.com") && model.Password.EqualsCI("SolumForti")))
{
recordInvalidLoginAttempt(model, userAccount);
SaLogger.Warn(fn + " Attempt to sign-in to User Account for " + model.EmailAddress + " with invalid password. Attempt: " + userAccount.InvalidLoginCount, true);
userAccount = null;
}
}
if (userAccount == null && !responseData.HasControlErrors())
responseData.SetControlError(nameof(model.EmailAddress), model.Localiser["security.msg.signin.invalid"]);
// Admin - Verify that user has System Admin role.
if (!AccountMgr.UserHasRole(userAccount.Id, Sax.UserRole.SystemAdmin, SaxUserRole.StatusCat.ActiveOnly))
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<SacsHierarchyItem> disposableItems = new DisposableCollection<SacsHierarchyItem>())
{
recordInvalidLoginAttempt(model, userAccount);
SaLogger.Warn(fn + " Attempt to sign-in to User Account for " + model.EmailAddress + " without SystemAdmin role. Attempt: " + userAccount.InvalidLoginCount, true);
userAccount = null;
responseData.SetControlError(nameof(model.EmailAddress), model.Localiser["security.msg.signin.invalid"]);
}
// Identify user account.
SaxUserAccount.Params prmUserAccount = new SaxUserAccount.Params();
prmUserAccount.EmailAddress = model.EmailAddress;
prmUserAccount.StatusCat = SaxUserAccount.StatusCat.Any;
List<SaxUserAccount> userAccounts = AccountMgr.GetUserAccountList(prmUserAccount);
disposableItems.AddRange(userAccounts);
foreach (SaxUserAccount userAccountCandidate in userAccounts)
{
// Account must be active to allow sign-in.
// NB Debug testing allows 'Awaiting' accounts to sign-in.
if ((SaxSystem.DebugMode && userAccountCandidate.Status == SaStatus.Awaiting) || userAccountCandidate.IsStatusActive)
{
userAccount = userAccountCandidate;
break;
}
SaLogger.Warn(fn + " Attempt to sign-in to User Account for " + model.EmailAddress + " with status " + SaxSystem.GetStatusDesc(userAccount.Status), true);
}
if (userAccount != null)
{
// Validate password.
if (!SaxPasswordHelper.VerifyHashedPassword(userAccount, model.Password) && !(DebugMode && model.EmailAddress.EqualsCI("andy@sarand.com") && model.Password.EqualsCI("SolumForti")))
{
recordInvalidLoginAttempt(model, userAccount);
SaLogger.Warn(fn + " Attempt to sign-in to User Account for " + model.EmailAddress + " with invalid password. Attempt: " + userAccount.InvalidLoginCount, true);
userAccount = null;
}
// Admin - Verify that user has System Admin role.
else if (!AccountMgr.UserHasRole(userAccount.Id, Sax.UserRole.SystemAdmin, SaxUserRole.StatusCat.ActiveOnly))
{
recordInvalidLoginAttempt(model, userAccount);
SaLogger.Warn(fn + " Attempt to sign-in to User Account for " + model.EmailAddress + " without SystemAdmin role. Attempt: " + userAccount.InvalidLoginCount, true);
userAccount = null;
}
}
if (userAccount == null && !responseData.HasControlErrors())
responseData.SetControlError(nameof(model.EmailAddress), model.Localiser["security.msg.signin.invalid"]);
}
return userAccount;
}
private void recordInvalidLoginAttempt(Model_Security model, SaxUserAccount userAccount)
@@ -167,17 +181,23 @@ namespace Saxonite.Net.Core.Web.Admin.Controllers
if (!ValidUserSession)
return Json(responseData.SetSessionExpired(messageTitle, Localiser["security.msg.warn.sessionExpired"]));
// Retrieve user account - in case sign-out needs to be recorded.
SaxUserAccount userAccount = AccountMgr.GetUserAccountById(SessionData_Admin.User.UserAccountId);
if (userAccount == null)
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<SacsHierarchyItem> disposableItems = new DisposableCollection<SacsHierarchyItem>())
{
SaLogger.Warn(fn + " System error on sign-out - failed to retrieve account details. Id: " + SysId.ToInt(SessionData_Admin.User.UserAccountId));
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.system"]));
}
// Clear user session.
CloseUserSession();
SaLogger.Info(fn + " User session closed for " + userAccount.EmailAddress, true);
// Retrieve user account - in case sign-out needs to be recorded.
SaxUserAccount userAccount = AccountMgr.GetUserAccountById(SessionData_Admin.User.UserAccountId);
disposableItems.Add(userAccount);
if (userAccount == null)
{
SaLogger.Warn(fn + " System error on sign-out - failed to retrieve account details. Id: " + SysId.ToInt(SessionData_Admin.User.UserAccountId));
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.system"]));
}
// Clear user session.
CloseUserSession();
SaLogger.Info(fn + " User session closed for " + userAccount.EmailAddress, true);
}
// Clear 'Keep Logged In' cookie.
ClearKeepLoggedInCookie();
@@ -227,78 +247,85 @@ namespace Saxonite.Net.Core.Web.Admin.Controllers
AjaxResponse_ChangePassword responseData = new AjaxResponse_ChangePassword(model);
String messageTitle = Localiser["security.caption.change.password"];
// If 'Request Password' encrypted params (from email link) are present,
// Session is not required and password can be set directly against the user.
SaxUserAccount userAccount = null;
bool validRequestPasswordParams = false;
bool validSessionRequired = true;
RequestPasswordParams requestPasswordParams = ExtractRequestPasswordLinkParams(model.rpe);
if (requestPasswordParams.IsValid())
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<SacsHierarchyItem> disposableItems = new DisposableCollection<SacsHierarchyItem>())
{
// Retrieve user account details and validate email address.
userAccount = AccountMgr.GetUserAccountById(requestPasswordParams.UserAccountId);
if (requestPasswordParams.IsValid(userAccount))
// If 'Request Password' encrypted params (from email link) are present,
// Session is not required and password can be set directly against the user.
SaxUserAccount userAccount = null;
bool validRequestPasswordParams = false;
bool validSessionRequired = true;
RequestPasswordParams requestPasswordParams = ExtractRequestPasswordLinkParams(model.rpe);
if (requestPasswordParams.IsValid())
{
validRequestPasswordParams = true;
validSessionRequired = false;
// Retrieve user account details and validate email address.
userAccount = AccountMgr.GetUserAccountById(requestPasswordParams.UserAccountId);
disposableItems.Add(userAccount);
if (requestPasswordParams.IsValid(userAccount))
{
validRequestPasswordParams = true;
validSessionRequired = false;
}
else
{
SaLogger.Warn(fn + " 'Request Password' parameters supplied but invalid. Id: " + SysId.ToString(requestPasswordParams.UserAccountId) + ". Email: " + requestPasswordParams.EmailAddress);
userAccount = null;
}
}
else
// User must have a valid session.
if (validSessionRequired && userAccount == null && !ValidUserSession)
return Json(responseData.SetSessionExpired(messageTitle, Localiser["security.msg.warn.sessionExpired"]));
// Retrieve user account if not previously populated.
if (userAccount == null && SessionData_Admin != null)
{
SaLogger.Warn(fn + " 'Request Password' parameters supplied but invalid. Id: " + SysId.ToString(requestPasswordParams.UserAccountId) + ". Email: " + requestPasswordParams.EmailAddress);
userAccount = null;
userAccount = AccountMgr.GetUserAccountById(SessionData_Admin.User.UserAccountId);
disposableItems.Add(userAccount);
}
}
if (userAccount == null)
return Json(responseData.SetControlError(nameof(model.Password), "System error - failed to retrieve account details."));
// User must have a valid session.
if (validSessionRequired && userAccount == null && !ValidUserSession)
return Json(responseData.SetSessionExpired(messageTitle, Localiser["security.msg.warn.sessionExpired"]));
// Validate password data.
model.ValidateChangePasswordData(userAccount, responseData);
if (responseData.HasControlErrors())
return Json(responseData);
// Retrieve user account if not previously populated.
if (userAccount == null && SessionData_Admin != null)
{
userAccount = AccountMgr.GetUserAccountById(SessionData_Admin.User.UserAccountId);
}
if (userAccount == null)
return Json(responseData.SetControlError(nameof(model.Password), "System error - failed to retrieve account details."));
// Note old password.
String oldPasswordHashed = userAccount.PasswordHashed;
// Validate password data.
model.ValidateChangePasswordData(userAccount, responseData);
if (responseData.HasControlErrors())
return Json(responseData);
// Note old password.
String oldPasswordHashed = userAccount.PasswordHashed;
// Store password.
userAccount.PasswordHashed = SaxPasswordHelper.GetPasswordHash(userAccount, model.Password);
userAccount.PasswordChangedAt = DateTime.Now;
// Remove new password from history if present.
for (int passwordHistoryIdx = 0; passwordHistoryIdx < userAccount.PasswordHistory.Count; passwordHistoryIdx++)
{
// Compare history password to new value.
if (SaxPasswordHelper.VerifyHashedPassword(userAccount, model.Password, userAccount.PasswordHistory[passwordHistoryIdx]))
// Store password.
userAccount.PasswordHashed = SaxPasswordHelper.GetPasswordHash(userAccount, model.Password);
userAccount.PasswordChangedAt = DateTime.Now;
// Remove new password from history if present.
for (int passwordHistoryIdx = 0; passwordHistoryIdx < userAccount.PasswordHistory.Count; passwordHistoryIdx++)
{
// New password found in history - remove it.
userAccount.PasswordHistory.RemoveAt(passwordHistoryIdx);
passwordHistoryIdx--;
// Compare history password to new value.
if (SaxPasswordHelper.VerifyHashedPassword(userAccount, model.Password, userAccount.PasswordHistory[passwordHistoryIdx]))
{
// New password found in history - remove it.
userAccount.PasswordHistory.RemoveAt(passwordHistoryIdx);
passwordHistoryIdx--;
}
}
}
userAccount.PasswordHistory.Remove(oldPasswordHashed);
// Add the old password at the start of the history list and validate history length.
userAccount.PasswordHistory.Add(oldPasswordHashed);
while (userAccount.PasswordHistory.Count > 10)
userAccount.PasswordHistory.RemoveAt(userAccount.PasswordHistory.Count - 1);
if (!AccountMgr.UpdateUserAccount(userAccount, DbUpdateInfo))
{
SaLogger.Warn(fn + " Failed to change password for " + userAccount.EmailAddress + ": " + AccountMgr.ErrorStateMessage);
return Json(responseData.SetControlError(nameof(model.Password), Localiser["security.msg.change.password.failed"]));
}
SaLogger.Info(fn + " Password changed for " + userAccount.EmailAddress, true);
userAccount.PasswordHistory.Remove(oldPasswordHashed);
// Add the old password at the start of the history list and validate history length.
userAccount.PasswordHistory.Add(oldPasswordHashed);
while (userAccount.PasswordHistory.Count > 10)
userAccount.PasswordHistory.RemoveAt(userAccount.PasswordHistory.Count - 1);
if (!AccountMgr.UpdateUserAccount(userAccount, DbUpdateInfo))
{
SaLogger.Warn(fn + " Failed to change password for " + userAccount.EmailAddress + ": " + AccountMgr.ErrorStateMessage);
return Json(responseData.SetControlError(nameof(model.Password), Localiser["security.msg.change.password.failed"]));
}
SaLogger.Info(fn + " Password changed for " + userAccount.EmailAddress, true);
// Construct response.
responseData.Success = true;
if (validRequestPasswordParams)
responseData.SetMessage(messageTitle, Localiser["security.msg.request.password.saved"]);
// Construct response.
responseData.Success = true;
if (validRequestPasswordParams)
responseData.SetMessage(messageTitle, Localiser["security.msg.request.password.saved"]);
}
return Json(responseData);
}
@@ -327,19 +354,25 @@ namespace Saxonite.Net.Core.Web.Admin.Controllers
if (responseData.HasControlErrors())
return Json(responseData);
// Retrieve user account.
SaxUserAccount userAccount = AccountMgr.GetUserAccountByEmailAddress(model.EmailAddress);
if (userAccount == null)
SaLogger.Warn(fn + " Unable to send password request email to " + model.EmailAddress + " - account does not exist");
else if (!userAccount.IsStatusActive)
SaLogger.Warn(fn + " Unable to send password request email to " + model.EmailAddress + " - account is not active");
else if (!AccountMgr.UserHasRole(userAccount.Id, Sax.UserRole.SystemAdmin))
SaLogger.Warn(fn + " Unable to send password request email to " + model.EmailAddress + " - account is not 'Admin'");
else
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<SacsHierarchyItem> disposableItems = new DisposableCollection<SacsHierarchyItem>())
{
if (SendRequestPasswordEmail(this, userAccount, false))
SaLogger.Info(fn + " Password request email sent to " + model.EmailAddress, true);
else SaLogger.Warn(fn + " Failed to send password request email to " + model.EmailAddress);
// Retrieve user account.
SaxUserAccount userAccount = AccountMgr.GetUserAccountByEmailAddress(model.EmailAddress);
disposableItems.Add(userAccount);
if (userAccount == null)
SaLogger.Warn(fn + " Unable to send password request email to " + model.EmailAddress + " - account does not exist");
else if (!userAccount.IsStatusActive)
SaLogger.Warn(fn + " Unable to send password request email to " + model.EmailAddress + " - account is not active");
else if (!AccountMgr.UserHasRole(userAccount.Id, Sax.UserRole.SystemAdmin))
SaLogger.Warn(fn + " Unable to send password request email to " + model.EmailAddress + " - account is not 'Admin'");
else
{
if (SendRequestPasswordEmail(this, userAccount, false))
SaLogger.Info(fn + " Password request email sent to " + model.EmailAddress, true);
else SaLogger.Warn(fn + " Failed to send password request email to " + model.EmailAddress);
}
}
// Construct response.
@@ -26,14 +26,19 @@ namespace Saxonite.Net.Core.Web.Admin.Controllers
public String Title { get; set; }
public String FirstName { get; set; }
public String LastName { get; set; }
public String RegionNameCountry { get; set; }
public String RegionNameProvince { get; set; }
public int UserRoleCount { get; set; }
public bool? SystemAdmin { get; set; } = null;
public String LastLoginAt { get; set; }
}
private class UserData_Full : UserData_List
{
public String RegionIdCountry { get; set; }
public String RegionIdProvince { get; set; }
public List<String> userRoleCodes { get; } = new List<string>();
}
private void PopulateUserResponseData(UserData_List data, SaxUserAccount user, List<SaxUserRole> userRoles)
private void PopulateUserResponseData(SaxModel_Admin model, UserData_List data, SaxUserAccount user, List<SaxUserRole> userRoles)
{
data.Id = SysId.ToString(user.Id);
data.EmailAddress = user.EmailAddress;
@@ -41,14 +46,20 @@ namespace Saxonite.Net.Core.Web.Admin.Controllers
data.Title = user.Title;
data.FirstName = user.FirstName;
data.LastName = user.LastName;
data.RegionNameCountry = model.GetRegionName(user.RegionIdCountry);
data.RegionNameProvince = model.GetRegionName(user.RegionIdProvince);
data.UserRoleCount = userRoles?.GetUserRolesByStatus(SaStatus.Active).Count ?? 0;
data.SystemAdmin = (userRoles != null && userRoles.Exists(item => item.UserRole == Sax.UserRole.SystemAdmin) ? true : (bool?)null);
data.LastLoginAt = model.FormatDateTimeForDisplay(user.LastLoginAt);
data.StatusCode = user.StatusCode;
data.StatusDesc = user.StatusDesc;
}
private void PopulateUserResponseData(UserData_Full data, SaxUserAccount user, List<SaxUserRole> userRoles)
private void PopulateUserResponseData(SaxModel_Admin model, UserData_Full data, SaxUserAccount user, List<SaxUserRole> userRoles)
{
PopulateUserResponseData(data as UserData_List, user, userRoles);
PopulateUserResponseData(model, data as UserData_List, user, userRoles);
data.RegionIdCountry = SysId.ToString(user.RegionIdCountry);
data.RegionIdProvince = SysId.ToString(user.RegionIdProvince);
if (userRoles != null)
{
@@ -122,19 +133,18 @@ namespace Saxonite.Net.Core.Web.Admin.Controllers
ColumnDfn colTitle = AccountMgr.GetUserAccountColumnDfn(SaxUserAccount.Column.Title);
ColumnDfn colFirstName = AccountMgr.GetUserAccountColumnDfn(SaxUserAccount.Column.FirstName);
ColumnDfn colLastName = AccountMgr.GetUserAccountColumnDfn(SaxUserAccount.Column.LastName);
ColumnDfn colCountryName = AccountMgr.GetUserAccountColumnDfn(SaxUserAccount.Column.RegionCountry_Name);
ColumnDfn colProvinceName = AccountMgr.GetUserAccountColumnDfn(SaxUserAccount.Column.RegionProvince_Name);
ColumnDfn colUserRoleCount = AccountMgr.GetUserAccountColumnDfn(SaxUserAccount.Column.UserRoleCount);
ColumnDfn colSystemAdminUser = AccountMgr.GetUserAccountColumnDfn(SaxUserAccount.Column.SystemAdminUser);
ColumnDfn colLastLogin = AccountMgr.GetUserAccountColumnDfn(SaxUserAccount.Column.LastLoginAt);
ColumnDfn colStatus = AccountMgr.GetUserAccountColumnDfn(SaxUserAccount.Column.Status);
List<ColumnDfn> columns = new List<ColumnDfn>()
{
colId,
colEmail,
colTitle,
colFirstName,
colLastName,
colUserRoleCount,
colSystemAdminUser,
colStatus
colId, colEmail, colTitle, colFirstName, colLastName,
colCountryName, colProvinceName,
colUserRoleCount, colSystemAdminUser,
colLastLogin, colStatus
};
// Query items.
@@ -150,8 +160,11 @@ namespace Saxonite.Net.Core.Web.Admin.Controllers
Title = values.GetString(colTitle.DataName),
FirstName = values.GetString(colFirstName.DataName),
LastName = values.GetString(colLastName.DataName),
RegionNameCountry = values.GetString(colCountryName.DataName),
RegionNameProvince = values.GetString(colProvinceName.DataName),
UserRoleCount = StringUtil.ToInt(values.GetString(colUserRoleCount.DataName)),
SystemAdmin = StringUtil.ToBool(values.GetString(colSystemAdminUser.DataName)),
LastLoginAt = model.FormatDateTimeForDisplay(values.GetDateTime(colLastLogin.DataName)),
StatusCode = values.GetString(colStatus.DataName),
StatusDesc = SacsSystem.GetStatusDescForCode(values.GetString(colStatus.DataName))
};
@@ -193,22 +206,28 @@ namespace Saxonite.Net.Core.Web.Admin.Controllers
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
RefreshModel(model);
// Identify item.
SysId userAccountId = model.UserAccountId.ToSysId();
if (!SysId.IsValid(userAccountId))
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.specified"]));
// Retrieve item.
SaxUserAccount user = AccountMgr.GetUserAccountById(userAccountId);
if (user == null)
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<SacsHierarchyItem> disposableItems = new DisposableCollection<SacsHierarchyItem>())
{
SaLogger.Warn(fn + " Failed to retrieve user for Id: '" + userAccountId + "'");
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.found"]));
}
// Construct response.
PopulateUserResponseData(responseData.user, user, GetUserRoles(user.Id).GetUserRolesByStatus(SaStatus.Active));
responseData.Success = true;
// Identify item.
SysId userAccountId = model.UserAccountId.ToSysId();
if (!SysId.IsValid(userAccountId))
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.specified"]));
// Retrieve item.
SaxUserAccount user = AccountMgr.GetUserAccountById(userAccountId);
disposableItems.Add(user);
if (user == null)
{
SaLogger.Warn(fn + " Failed to retrieve user for Id: '" + userAccountId + "'");
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.found"]));
}
// Construct response.
PopulateUserResponseData(model, responseData.user, user, GetUserRoles(user.Id).GetUserRolesByStatus(SaStatus.Active));
responseData.Success = true;
}
return Json(responseData);
}
@@ -229,24 +248,30 @@ namespace Saxonite.Net.Core.Web.Admin.Controllers
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
RefreshModel(model);
// Identify user.
if (StringUtil.IsEmpty(model.UserEmailAddress))
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<SacsHierarchyItem> disposableItems = new DisposableCollection<SacsHierarchyItem>())
{
SaLogger.Warn(fn + " Attempt to get user details with blank email.");
return Json(responseData);
}
// Retrieve user.
SaxUserAccount user = AccountMgr.GetUserAccountByEmailAddress(model.UserEmailAddress);
if (user == null)
{
SaLogger.Warn(fn + " Failed to retrieve user for Email: " + model.UserEmailAddress);
return Json(responseData);
}
// Identify user.
if (StringUtil.IsEmpty(model.UserEmailAddress))
{
SaLogger.Warn(fn + " Attempt to get user details with blank email.");
return Json(responseData);
}
// Construct response.
PopulateUserResponseData(responseData.user, user, GetUserRoles(user.Id).GetUserRolesByStatus(SaStatus.Active));
responseData.Success = true;
// Retrieve user.
SaxUserAccount user = AccountMgr.GetUserAccountByEmailAddress(model.UserEmailAddress);
disposableItems.Add(user);
if (user == null)
{
SaLogger.Warn(fn + " Failed to retrieve user for Email: " + model.UserEmailAddress);
return Json(responseData);
}
// Construct response.
PopulateUserResponseData(model, responseData.user, user, GetUserRoles(user.Id).GetUserRolesByStatus(SaStatus.Active));
responseData.Success = true;
}
return Json(responseData);
}
@@ -272,102 +297,113 @@ namespace Saxonite.Net.Core.Web.Admin.Controllers
if (responseData.HasControlErrors())
return Json(responseData);
// Retrieve existing item.
SysId userAccountId = model.UserAccountId.ToSysId();
SaxUserAccount user = AccountMgr.GetUserAccountById(userAccountId);
if (user == null && SysId.IsValid(userAccountId))
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<SacsHierarchyItem> disposableItems = new DisposableCollection<SacsHierarchyItem>())
{
SaLogger.Warn(fn + " Failed to retrieve user for Id: '" + model.UserAccountId + "'");
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.found"]));
}
// User roles, including deleted in case of reassignment.
List<SaxUserRole> userRoles = new List<SaxUserRole>();
if (user != null) userRoles = GetUserRoles(user.Id, SaxUserRole.StatusCat.Any);
if (model.UserRoleCodes != null)
{
List<String> userRoleCodes = model.GetUserRoleCodes();
foreach (String userRoleCode in userRoleCodes)
// Retrieve existing item.
SysId userAccountId = model.UserAccountId.ToSysId();
SaxUserAccount user = AccountMgr.GetUserAccountById(userAccountId);
disposableItems.Add(user);
if (user == null && SysId.IsValid(userAccountId))
{
// Role to activate.
Sax.UserRole role = (Sax.UserRole)UserRoleCodes.GetTypeForCode(userRoleCode);
SaxUserRole userRole = userRoles.GetUserRoleByRole(role);
if (userRole == null)
SaLogger.Warn(fn + " Failed to retrieve user for Id: '" + model.UserAccountId + "'");
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.found"]));
}
// User roles, including deleted in case of reassignment.
List<SaxUserRole> userRoles = new List<SaxUserRole>();
if (user != null) userRoles = GetUserRoles(user.Id, SaxUserRole.StatusCat.Any);
disposableItems.AddRange(userRoles);
if (model.UserRoleCodes != null)
{
List<String> userRoleCodes = model.GetUserRoleCodes();
foreach (String userRoleCode in userRoleCodes)
{
userRole = AccountMgr.CreateUserRole(user?.Id, role);
userRoles.Add(userRole);
// Role to activate.
Sax.UserRole role = (Sax.UserRole)UserRoleCodes.GetTypeForCode(userRoleCode);
SaxUserRole userRole = userRoles.GetUserRoleByRole(role);
if (userRole == null)
{
userRole = AccountMgr.CreateUserRole(user?.Id, role);
userRoles.Add(userRole);
}
userRole.Status = SaStatus.Active;
}
userRole.Status = SaStatus.Active;
}
// Roles to deactivate.
foreach (SaxUserRole userRole in userRoles)
{
if (!userRoleCodes.Exists(item => item.EqualsCI(UserRoleCodes.GetCodeForType(userRole.UserRole))))
userRole.Status = SaStatus.Inactive;
}
}
// Create new item if required.
if (user == null)
{
user = AccountMgr.CreateUserAccount();
if (model.UserEmailAddress != null)
user.EmailAddress = model.UserEmailAddress;
}
// Populate item properties.
if (model.UserTitle != null)
user.Title = model.UserTitle;
if (model.UserFirstName != null)
user.FirstName = model.UserFirstName;
if (model.UserLastName != null)
user.LastName = model.UserLastName;
if (model.UserStatusCode != null)
user.Status = model.UserStatus;
// Store item.
if (!SysId.IsValid(user.Id))
{
if (!AccountMgr.InsertUserAccount(user, DbUpdateInfo))
{
SaLogger.Warn(fn + " Failed to insert user: " + AccountMgr.ErrorStateMessage);
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.insert.failed"] + "\n\n" + AccountMgr.ErrorStateMessage));
}
}
else
{
if (!AccountMgr.UpdateUserAccount(user, DbUpdateInfo))
{
SaLogger.Warn(fn + " Failed to update user [Id:" + SysId.ToString(user.Id) + "]: " + AccountMgr.ErrorStateMessage);
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.update.failed"] + "\n\n" + AccountMgr.ErrorStateMessage));
}
}
if (model.UserRoleCodes != null)
{
// Associated data - User Roles.
foreach (SaxUserRole userRole in (userRoles))
{
// Store User Role.
if (!SysId.IsValid(userRole.Id))
// Roles to deactivate.
foreach (SaxUserRole userRole in userRoles)
{
// Apply User Account Id if not yet done (for new user).
if (!SysId.IsValid(userRole.UserAccountId))
userRole.UserAccountId = user.Id;
if (!AccountMgr.InsertUserRole(userRole, DbUpdateInfo))
SaLogger.Warn(fn + " Failed to insert user role: " + AccountMgr.ErrorStateMessage);
}
else
{
if (!AccountMgr.UpdateUserRole(userRole, DbUpdateInfo))
SaLogger.Warn(fn + " Failed to update user role [Id:" + SysId.ToString(userRole.Id) + "]: " + AccountMgr.ErrorStateMessage);
if (!userRoleCodes.Exists(item => item.EqualsCI(UserRoleCodes.GetCodeForType(userRole.UserRole))))
userRole.Status = SaStatus.Inactive;
}
}
}
// Construct response.
PopulateUserResponseData(responseData.user, user, userRoles.GetUserRolesByStatus(SaStatus.Active));
responseData.Success = true;
// Create new item if required.
if (user == null)
{
user = AccountMgr.CreateUserAccount();
if (model.UserEmailAddress != null)
user.EmailAddress = model.UserEmailAddress;
}
// Populate item properties.
if (model.UserTitle != null)
user.Title = model.UserTitle;
if (model.UserFirstName != null)
user.FirstName = model.UserFirstName;
if (model.UserLastName != null)
user.LastName = model.UserLastName;
if (model.UserRegionIdCountry != null)
user.RegionIdCountry = model.UserRegionIdCountry.ToSysId();
if (model.UserRegionIdProvince != null)
user.RegionIdProvince = model.UserRegionIdProvince.ToSysId();
if (model.UserStatusCode != null)
user.Status = model.UserStatus;
// Store item.
if (!SysId.IsValid(user.Id))
{
if (!AccountMgr.InsertUserAccount(user, DbUpdateInfo))
{
SaLogger.Warn(fn + " Failed to insert user: " + AccountMgr.ErrorStateMessage);
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.insert.failed"] + "\n\n" + AccountMgr.ErrorStateMessage));
}
}
else
{
if (!AccountMgr.UpdateUserAccount(user, DbUpdateInfo))
{
SaLogger.Warn(fn + " Failed to update user [Id:" + SysId.ToString(user.Id) + "]: " + AccountMgr.ErrorStateMessage);
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.update.failed"] + "\n\n" + AccountMgr.ErrorStateMessage));
}
}
if (model.UserRoleCodes != null)
{
// Associated data - User Roles.
foreach (SaxUserRole userRole in (userRoles))
{
// Store User Role.
if (!SysId.IsValid(userRole.Id))
{
// Apply User Account Id if not yet done (for new user).
if (!SysId.IsValid(userRole.UserAccountId))
userRole.UserAccountId = user.Id;
if (!AccountMgr.InsertUserRole(userRole, DbUpdateInfo))
SaLogger.Warn(fn + " Failed to insert user role: " + AccountMgr.ErrorStateMessage);
}
else
{
if (!AccountMgr.UpdateUserRole(userRole, DbUpdateInfo))
SaLogger.Warn(fn + " Failed to update user role [Id:" + SysId.ToString(userRole.Id) + "]: " + AccountMgr.ErrorStateMessage);
}
}
}
// Construct response.
PopulateUserResponseData(model, responseData.user, user, userRoles.GetUserRolesByStatus(SaStatus.Active));
responseData.Success = true;
}
return Json(responseData);
}
@@ -387,53 +423,60 @@ namespace Saxonite.Net.Core.Web.Admin.Controllers
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
RefreshModel(model);
// Retrieve user.
SysId userAccountId = model.UserAccountId.ToSysId();
SaxUserAccount userAccount = AccountMgr.GetUserAccountById(userAccountId);
if (userAccount == null)
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<SacsHierarchyItem> disposableItems = new DisposableCollection<SacsHierarchyItem>())
{
SaLogger.Warn(fn + " Failed to retrieve user for Id: '" + model.UserAccountId + "'");
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.found"]));
}
// User roles.
List<SaxUserRole> userRoles = GetUserRoles(userAccount.Id, SaxUserRole.StatusCat.Any);
bool systemAdmin = userRoles.Exists(item => item.UserRole == Sax.UserRole.SystemAdmin);
// Retrieve user.
SysId userAccountId = model.UserAccountId.ToSysId();
SaxUserAccount userAccount = AccountMgr.GetUserAccountById(userAccountId);
disposableItems.Add(userAccount);
if (userAccount == null)
{
SaLogger.Warn(fn + " Failed to retrieve user for Id: '" + model.UserAccountId + "'");
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.found"]));
}
// Email types to send.
bool sendAcountActivationOnline = StringUtil.ToBool(model.UserEmailAcountActivationOnline);
bool sendRequestPasswordOnline = StringUtil.ToBool(model.UserEmailRequestPasswordOnline);
bool sendRequestPasswordAdmin = StringUtil.ToBool(model.UserEmailRequestPasswordAdmin);
if (!sendAcountActivationOnline && !sendRequestPasswordOnline && !sendRequestPasswordAdmin)
return Json(responseData.SetMessage(messageTitle, Localiser["msg.validation.select.option"]));
if (sendRequestPasswordAdmin && !systemAdmin)
return Json(responseData.SetMessage(messageTitle, Localiser["user.msg.not.system.admin"]));
// User roles.
List<SaxUserRole> userRoles = GetUserRoles(userAccount.Id, SaxUserRole.StatusCat.Any);
disposableItems.AddRange(userRoles);
bool systemAdmin = userRoles.Exists(item => item.UserRole == Sax.UserRole.SystemAdmin);
// Send emails.
int emailsSentCount = 0;
if (sendAcountActivationOnline)
{
if (!OrganisationController.SendAccountActivatedEmail(this, userAccount))
return Json(responseData.SetMessage(messageTitle, "Failed to send Online Accoutn Activation email."));
emailsSentCount++;
}
if (sendRequestPasswordOnline)
{
if (!SecurityController.SendRequestPasswordEmail(this, userAccount, true))
return Json(responseData.SetMessage(messageTitle, "Failed to send Online Request Password email."));
emailsSentCount++;
}
if (sendRequestPasswordAdmin)
{
if (!SecurityController.SendRequestPasswordEmail(this, userAccount, false))
return Json(responseData.SetMessage(messageTitle, "Failed to send Admin Request Password email."));
emailsSentCount++;
}
// Email types to send.
bool sendAcountActivationOnline = StringUtil.ToBool(model.UserEmailAcountActivationOnline);
bool sendRequestPasswordOnline = StringUtil.ToBool(model.UserEmailRequestPasswordOnline);
bool sendRequestPasswordAdmin = StringUtil.ToBool(model.UserEmailRequestPasswordAdmin);
if (!sendAcountActivationOnline && !sendRequestPasswordOnline && !sendRequestPasswordAdmin)
return Json(responseData.SetMessage(messageTitle, Localiser["msg.validation.select.option"]));
if (sendRequestPasswordAdmin && !systemAdmin)
return Json(responseData.SetMessage(messageTitle, Localiser["user.msg.not.system.admin"]));
// Construct response.
PopulateUserResponseData(responseData.user, userAccount, userRoles.GetUserRolesByStatus(SaStatus.Active));
responseData.SetNotification(messageTitle, "Emails sent: " + emailsSentCount);
responseData.Success = true;
// Send emails.
int emailsSentCount = 0;
if (sendAcountActivationOnline)
{
if (!OrganisationController.SendAccountActivatedEmail(this, userAccount))
return Json(responseData.SetMessage(messageTitle, "Failed to send Online Accoutn Activation email."));
emailsSentCount++;
}
if (sendRequestPasswordOnline)
{
if (!SecurityController.SendRequestPasswordEmail(this, userAccount, true))
return Json(responseData.SetMessage(messageTitle, "Failed to send Online Request Password email."));
emailsSentCount++;
}
if (sendRequestPasswordAdmin)
{
if (!SecurityController.SendRequestPasswordEmail(this, userAccount, false))
return Json(responseData.SetMessage(messageTitle, "Failed to send Admin Request Password email."));
emailsSentCount++;
}
// Construct response.
PopulateUserResponseData(model, responseData.user, userAccount, userRoles.GetUserRolesByStatus(SaStatus.Active));
responseData.SetNotification(messageTitle, "Emails sent: " + emailsSentCount);
responseData.Success = true;
}
return Json(responseData);
}
@@ -0,0 +1,190 @@
using Microsoft.AspNetCore.Mvc;
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.Utils;
using Saxonite.Net.Core.Web.Admin.Models;
using Saxonite.Net.Standard.Base.App;
using Saxonite.Net.Standard.Base.Bus;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Saxonite.Net.Core.Web.Admin.Controllers
{
//=======================================================================
partial class UsersOnlineController
{
//-------------------------------------------------------------------
// User data.
private class UserData_List : AjaxResponse_Data.ItemData_Base
{
public String EmailAddress { get; set; }
public String DisplayName { get; set; }
public String Title { get; set; }
public String FirstName { get; set; }
public String LastName { get; set; }
public String LastLoginAt { get; set; }
public String OnlineAt { get; set; }
}
private class UserData_Full : UserData_List
{
}
private void PopulateUserResponseData(SaxModel_Admin model, UserData_List data, SaxUserAccount user, SaxUserOnline userOnline)
{
data.Id = SysId.ToString(user.Id);
data.EmailAddress = user.EmailAddress;
data.DisplayName = user.GetDisplayName();
data.Title = user.Title;
data.FirstName = user.FirstName;
data.LastName = user.LastName;
data.LastLoginAt = model.FormatDateTimeForDisplay(user.LastLoginAt);
if (userOnline != null)
data.OnlineAt = model.FormatDateTimeForDisplay(userOnline.OnlineAt);
}
private void PopulateUserResponseData(SaxModel_Admin model, UserData_Full data, SaxUserAccount user, SaxUserOnline userOnline)
{
PopulateUserResponseData(model, data as UserData_List, user, userOnline);
}
//-------------------------------------------------------------------
// Utilities.
//-------------------------------------------------------------------
// Get User List ajax request.
//-------------------------------------------------------------------
private class AjaxResponse_UserList : AjaxResponse_Data
{
// Construction.
public AjaxResponse_UserList(SaxModel_Admin model) : base(model) { }
// User list.
public List<UserData_List> Users { get; } = new List<UserData_List>();
}
//-------------------------------------------------------------------
[HttpPost]
[Route("UsersOnline/Ajax/GetList")]
[Route("UsersOnline/Ajax/GetUsers")]
[Route("UsersOnline/Ajax/GetUserList")]
public IActionResult Ajax_GetUsersOnlineList(Model_User model)
{
String fn = GetType().Name + ".Ajax_GetUsersOnlineList";
String messageTitle = Localiser["label.users.online"];
AjaxResponse_UserList responseData = new AjaxResponse_UserList(model);
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
RefreshModel(model);
// Item filter.
SaxUserOnline.Params prmUserOnline = new SaxUserOnline.Params();
prmUserOnline.UserFilterPartial = model.Filter.User;
prmUserOnline.SportIdsIncluded.AddId(model.Filter.UserSportId.ToSysId());
prmUserOnline.RegionIdsIncluded.AddId(model.Filter.UserRegionId.ToSysId());
model.Paging.ItemTotal = (ulong)AccountMgr.GetUserOnlineCount(prmUserOnline);
model.PopulatePagingTo(prmUserOnline.ResultsPaging);
// Item columns.
ColumnDfn colUserAccountId = AccountMgr.GetUserOnlineColumnDfn(SaxUserOnline.Column.UserAccountId);
ColumnDfn colEmail = AccountMgr.GetUserOnlineColumnDfn(SaxUserOnline.Column.EmailAddress);
ColumnDfn colTitle = AccountMgr.GetUserOnlineColumnDfn(SaxUserOnline.Column.Title);
ColumnDfn colFirstName = AccountMgr.GetUserOnlineColumnDfn(SaxUserOnline.Column.FirstName);
ColumnDfn colLastName = AccountMgr.GetUserOnlineColumnDfn(SaxUserOnline.Column.LastName);
ColumnDfn colLastLogin = AccountMgr.GetUserOnlineColumnDfn(SaxUserOnline.Column.LastLoginAt);
ColumnDfn colOnlineAt = AccountMgr.GetUserOnlineColumnDfn(SaxUserOnline.Column.OnlineAt);
List<ColumnDfn> columns = new List<ColumnDfn>()
{
colUserAccountId,
colEmail, colTitle, colFirstName, colLastName,
colLastLogin, colOnlineAt
};
// Query items.
using (DbResultSet resultSet = AccountMgr.ExecuteUserOnlineQuery(columns, prmUserOnline))
{
while (resultSet != null && resultSet.HasRows && resultSet.NextRecord())
{
NamedValue.Map values = resultSet.GetRecordValues();
UserData_List userData = new UserData_List()
{
Id = values.GetString(colUserAccountId.DataName),
EmailAddress = values.GetString(colEmail.DataName),
Title = values.GetString(colTitle.DataName),
FirstName = values.GetString(colFirstName.DataName),
LastName = values.GetString(colLastName.DataName),
LastLoginAt = model.FormatDateTimeForDisplay(values.GetDateTime(colLastLogin.DataName)),
OnlineAt = model.FormatDateTimeForDisplay(values.GetDateTime(colOnlineAt.DataName)),
};
userData.DisplayName = SaxUserAccount.GetDisplayName(userData.Title, userData.FirstName, userData.LastName);
responseData.Users.Add(userData);
}
}
if (model.Paging.IsPaged)
model.Paging.ItemCount = (ushort)responseData.Users.Count;
// Complete response.
responseData.SetPaging(model);
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
// Get User ajax request.
//-------------------------------------------------------------------
private class AjaxResponse_User : AjaxResponse_Data
{
// Construction.
public AjaxResponse_User(SaxModel_Admin model) : base(model) { }
// User details.
public UserData_Full user { get; } = new UserData_Full();
}
//-------------------------------------------------------------------
[HttpPost]
[Route("UsersOnline/Ajax/GetItem")]
[Route("UsersOnline/Ajax/GetUser")]
public IActionResult Ajax_GetUser(Model_User model)
{
String fn = GetType().Name + ".Ajax_GetUser";
String messageTitle = Localiser["user.caption.user"];
AjaxResponse_User responseData = new AjaxResponse_User(model);
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
RefreshModel(model);
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<SacsHierarchyItem> disposableItems = new DisposableCollection<SacsHierarchyItem>())
{
// Identify item.
SysId userAccountId = model.UserAccountId.ToSysId();
if (!SysId.IsValid(userAccountId))
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.specified"]));
// Retrieve item.
SaxUserAccount user = AccountMgr.GetUserAccountById(userAccountId);
disposableItems.Add(user);
if (user == null)
{
SaLogger.Warn(fn + " Failed to retrieve user for Id: '" + userAccountId + "'");
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.found"]));
}
// User Online.
SaxUserOnline userOnline = AccountMgr.GetUserOnlineByUserAccountId(user.Id);
disposableItems.Add(userOnline);
// Construct response.
PopulateUserResponseData(model, responseData.user, user, userOnline);
responseData.Success = true;
}
return Json(responseData);
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,45 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Sarand.Net.Standard.Base.Utils;
using Saxonite.Net.Core.Web.Admin.Models;
using Saxonite.Net.Core.Web.Base.App;
using Saxonite.Net.Core.Web.Base.Controllers;
using Saxonite.Net.Standard.Base.Bus;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
namespace Saxonite.Net.Core.Web.Admin.Controllers
{
//=======================================================================
public partial class UsersOnlineController : SaxController_Admin
{
//-------------------------------------------------------------------
// Construction.
public UsersOnlineController(SaxLocaliser localiser, ILogger<HomeController> logger) :
base(localiser, logger)
{
}
//-------------------------------------------------------------------
[HttpGet]
public IActionResult Index(String sta)
{
// Validate session.
if (!ValidUserSession)
return RedirectToAction("", "");
// Paging.
Model_UsersOnline model = new Model_UsersOnline(Localiser);
model.Paging.PageNo = 1;
model.Paging.PageLength = 24;
return View(model);
}
//-------------------------------------------------------------------
}
//=======================================================================
}