Portal signin via Control Instance
This commit is contained in:
+422
@@ -0,0 +1,422 @@
|
||||
using DocumentFormat.OpenXml.Spreadsheet;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Sarand.BusinessMgr.Net.Standard.Base.Bus.Control;
|
||||
using Sarand.BusinessMgr.Web.Control.Models;
|
||||
using Sarand.BusinessMgr.Web.Control.Models.Account;
|
||||
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 System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Sarand.BusinessMgr.Web.Control.Controllers.Account
|
||||
{
|
||||
//=======================================================================
|
||||
partial class UserAccountController
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
// Definitions.
|
||||
|
||||
// User data.
|
||||
private class UserListData : 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 Phone { get; set; }
|
||||
public String LastLoginOn { get; set; }
|
||||
public String LastLoginAt { get; set; }
|
||||
}
|
||||
private class UserItemData : UserListData
|
||||
{
|
||||
public String Initials { get; set; }
|
||||
public String PhoneMain { get; set; }
|
||||
public String PhoneMobile { get; set; }
|
||||
public String PhoneAlt { get; set; }
|
||||
}
|
||||
private void PopulateUserResponseListData(SbmModel_Control model, UserListData data, SbmcUserAccount user)
|
||||
{
|
||||
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.Phone = StringUtil.OnBlank(user.PhoneMain, user.PhoneMobile, user.PhoneAlt);
|
||||
data.LastLoginOn = model.FormatDateForDisplay(user.LastLoginAt);
|
||||
data.LastLoginAt = model.FormatDateTimeForDisplay(user.LastLoginAt);
|
||||
data.StatusCode = user.StatusCode;
|
||||
data.StatusDesc = user.StatusDesc;
|
||||
}
|
||||
private void PopulateUserResponseItemData(SbmModel_Control model, UserItemData data, SbmcUserAccount user)
|
||||
{
|
||||
PopulateUserResponseListData(model, data, user);
|
||||
data.Initials = user.Initials;
|
||||
data.PhoneMain = user.PhoneMain;
|
||||
data.PhoneMobile = user.PhoneMobile;
|
||||
data.PhoneAlt = user.PhoneAlt;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Get User List ajax request.
|
||||
//-------------------------------------------------------------------
|
||||
private class AjaxResponse_UserList : AjaxResponse_Data
|
||||
{
|
||||
// Construction.
|
||||
public AjaxResponse_UserList(SbmModel_Control model) : base(model) { }
|
||||
// User list.
|
||||
public List<UserListData> Users { get; } = new List<UserListData>();
|
||||
// Reference data.
|
||||
public List<CodeDesc> Statuses { get; } = new List<CodeDesc>();
|
||||
public List<CodeDesc> StatusesCreate { get; } = new List<CodeDesc>();
|
||||
public String StatusCodeDefault { get; set; }
|
||||
}
|
||||
//-------------------------------------------------------------------
|
||||
[HttpPost]
|
||||
[Route("Account/UserAccount/Ajax/GetList")]
|
||||
[Route("Account/UserAccount/Ajax/GetUsers")]
|
||||
[Route("Account/UserAccount/Ajax/GetUserList")]
|
||||
public IActionResult Ajax_GetUserList(Model_UserAccount model)
|
||||
{
|
||||
String fn = GetType().Name + ".Ajax_GetUserList";
|
||||
String messageTitle = Localiser["label.users"];
|
||||
AjaxResponse_UserList responseData = new AjaxResponse_UserList(model);
|
||||
|
||||
// Validate session and referrer.
|
||||
if (!ValidReferer) return Forbid();
|
||||
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
|
||||
RefreshModel(model);
|
||||
|
||||
// Reference data - statuses.
|
||||
foreach (SaStatus status in SbmcUserAccount.ValidStatuses)
|
||||
{
|
||||
responseData.Statuses.Add(SacsSystem.GetStatusCode(status), Localiser.GetStatusCaption(status));
|
||||
if (status != SaStatus.Deleted)
|
||||
responseData.StatusesCreate.Add(SacsSystem.GetStatusCode(status), Localiser.GetStatusCaption(status));
|
||||
}
|
||||
responseData.StatusCodeDefault = SacsSystem.GetStatusCode(SbmcUserAccount.DefaultStatus);
|
||||
|
||||
// Item filter.
|
||||
SbmcUserAccount.Params prmUserAccount = new SbmcUserAccount.Params();
|
||||
prmUserAccount.Search = model.Filter.Search;
|
||||
prmUserAccount.StatusCat = model.Filter.GetStatusCat();
|
||||
model.Paging.ItemTotal = (ulong)ControlAccountMgr.GetUserAccountCount(prmUserAccount);
|
||||
model.PopulatePagingTo(prmUserAccount.ResultsPaging);
|
||||
|
||||
// Item columns.
|
||||
ColumnDfn colId = ControlAccountMgr.GetUserAccountColumnDfn(SbmcUserAccount.Column.Id);
|
||||
ColumnDfn colEmail = ControlAccountMgr.GetUserAccountColumnDfn(SbmcUserAccount.Column.EmailAddress);
|
||||
ColumnDfn colTitle = ControlAccountMgr.GetUserAccountColumnDfn(SbmcUserAccount.Column.Title);
|
||||
ColumnDfn colFirstName = ControlAccountMgr.GetUserAccountColumnDfn(SbmcUserAccount.Column.FirstName);
|
||||
ColumnDfn colLastName = ControlAccountMgr.GetUserAccountColumnDfn(SbmcUserAccount.Column.LastName);
|
||||
ColumnDfn colPhoneMain = ControlAccountMgr.GetUserAccountColumnDfn(SbmcUserAccount.Column.PhoneMain);
|
||||
ColumnDfn colPhoneMobile = ControlAccountMgr.GetUserAccountColumnDfn(SbmcUserAccount.Column.PhoneMobile);
|
||||
ColumnDfn colPhoneAlt = ControlAccountMgr.GetUserAccountColumnDfn(SbmcUserAccount.Column.PhoneAlt);
|
||||
ColumnDfn colLastLogin = ControlAccountMgr.GetUserAccountColumnDfn(SbmcUserAccount.Column.LastLoginAt);
|
||||
ColumnDfn colStatus = ControlAccountMgr.GetUserAccountColumnDfn(SbmcUserAccount.Column.Status);
|
||||
List<ColumnDfn> columns = new List<ColumnDfn>()
|
||||
{
|
||||
colId, colEmail, colTitle, colFirstName, colLastName,
|
||||
colPhoneMain, colPhoneMobile, colPhoneAlt, colLastLogin, colStatus
|
||||
};
|
||||
|
||||
// Query items.
|
||||
using (DbResultSet resultSet = ControlAccountMgr.ExecuteUserAccountQuery(columns, prmUserAccount))
|
||||
{
|
||||
while (resultSet != null && resultSet.HasRows && resultSet.NextRecord())
|
||||
{
|
||||
NamedValue.Map values = resultSet.GetRecordValues();
|
||||
UserListData userData = new UserListData()
|
||||
{
|
||||
Id = values.GetString(colId.DataName),
|
||||
EmailAddress = values.GetString(colEmail.DataName),
|
||||
Title = values.GetString(colTitle.DataName),
|
||||
FirstName = values.GetString(colFirstName.DataName),
|
||||
LastName = values.GetString(colLastName.DataName),
|
||||
Phone = StringUtil.OnBlank(values.GetString(colPhoneMain.DataName), values.GetString(colPhoneMobile.DataName), values.GetString(colPhoneAlt.DataName)),
|
||||
LastLoginOn = model.FormatDateForDisplay(values.GetDateTime(colLastLogin.DataName)),
|
||||
LastLoginAt = model.FormatDateTimeForDisplay(values.GetDateTime(colLastLogin.DataName)),
|
||||
StatusCode = values.GetString(colStatus.DataName),
|
||||
StatusDesc = SacsSystem.GetStatusDescForCode(values.GetString(colStatus.DataName))
|
||||
};
|
||||
userData.DisplayName = SbmcUserAccount.GetDisplayName(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(SbmModel_Control model) : base(model) { }
|
||||
// User details.
|
||||
public UserItemData user { get; } = new UserItemData();
|
||||
}
|
||||
//-------------------------------------------------------------------
|
||||
[HttpPost]
|
||||
[Route("Account/UserAccount/Ajax/GetItem")]
|
||||
[Route("Account/UserAccount/Ajax/GetUser")]
|
||||
public IActionResult Ajax_GetUser(Model_UserAccount model)
|
||||
{
|
||||
String fn = GetType().Name + ".Ajax_GetUser";
|
||||
String messageTitle = Localiser["label.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> disposables = new DisposableCollection<SacsHierarchyItem>())
|
||||
{
|
||||
|
||||
// Identify item.
|
||||
SysId userAccountId = model.User.Id.ToSysId();
|
||||
if (!SysId.IsValid(userAccountId))
|
||||
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.specified"]));
|
||||
|
||||
// Retrieve item.
|
||||
SbmcUserAccount user = ControlAccountMgr.GetUserAccountById(userAccountId);
|
||||
disposables.Add(user);
|
||||
if (user == null)
|
||||
{
|
||||
SaLogger.Warn($"{SysUtil.Fn} Failed to retrieve user for Id: '" + userAccountId + "'");
|
||||
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.found"]));
|
||||
}
|
||||
|
||||
// Construct response.
|
||||
PopulateUserResponseItemData(model, responseData.user, user);
|
||||
responseData.Success = true;
|
||||
}
|
||||
return Json(responseData);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Get User by email ajax request.
|
||||
//-------------------------------------------------------------------
|
||||
[HttpPost]
|
||||
[Route("Account/UserAccount/Ajax/GetUserByEmail")]
|
||||
public IActionResult Ajax_GetUserByEmail(Model_UserAccount model)
|
||||
{
|
||||
String fn = GetType().Name + ".Ajax_GetUserByEmail";
|
||||
String messageTitle = Localiser["label.user"];
|
||||
AjaxResponse_User responseData = new AjaxResponse_User(model);
|
||||
StoreSessionData = false;
|
||||
|
||||
// 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> disposables = new DisposableCollection<SacsHierarchyItem>())
|
||||
{
|
||||
|
||||
// Identify user.
|
||||
if (StringUtil.IsEmpty(model.Filter.EmailAddress))
|
||||
{
|
||||
SaLogger.Warn($"{SysUtil.Fn} Attempt to get user details with blank email.");
|
||||
return Json(responseData);
|
||||
}
|
||||
|
||||
// Retrieve user.
|
||||
SbmcUserAccount user = ControlAccountMgr.GetUserAccountByEmailAddress(model.Filter.EmailAddress);
|
||||
disposables.Add(user);
|
||||
if (user == null)
|
||||
{
|
||||
SaLogger.Warn($"{SysUtil.Fn} Failed to retrieve user for Email: " + model.Filter.EmailAddress);
|
||||
return Json(responseData);
|
||||
}
|
||||
|
||||
// Construct response.
|
||||
PopulateUserResponseItemData(model, responseData.user, user);
|
||||
responseData.Success = true;
|
||||
}
|
||||
return Json(responseData);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Store User ajax request.
|
||||
//-------------------------------------------------------------------
|
||||
[HttpPost]
|
||||
[Route("Account/UserAccount/Ajax/StoreItem")]
|
||||
[Route("Account/UserAccount/Ajax/StoreUser")]
|
||||
public IActionResult Ajax_StoreUser(Model_UserAccount model)
|
||||
{
|
||||
String fn = GetType().Name + ".Ajax_StoreUser";
|
||||
String messageTitle = Localiser["label.user"];
|
||||
AjaxResponse_User responseData = new AjaxResponse_User(model);
|
||||
|
||||
// Validate session and referrer.
|
||||
if (!ValidReferer) return Forbid();
|
||||
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
|
||||
RefreshModel(model);
|
||||
|
||||
// Validate item Id.
|
||||
SysId userAccountId = model.User.Id.ToSysId();
|
||||
if (StringUtil.ToBool(model.User.Exists) && !SysId.IsValid(userAccountId))
|
||||
{
|
||||
SaLogger.Warn($"{SysUtil.Fn} User Id not supplied for existing item.");
|
||||
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.system"]));
|
||||
}
|
||||
if (!StringUtil.ToBool(model.User.Exists) && SysId.IsValid(userAccountId))
|
||||
{
|
||||
SaLogger.Warn($"{SysUtil.Fn} User Id supplied for new item.");
|
||||
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.system"]));
|
||||
}
|
||||
|
||||
// Validate item properties.
|
||||
model.ValidateUserData(responseData);
|
||||
if (responseData.HasControlErrors())
|
||||
return Json(responseData);
|
||||
|
||||
// Record disposable items as they are created so all can be disposed when needed.
|
||||
using (DisposableCollection<SacsHierarchyItem> disposables = new DisposableCollection<SacsHierarchyItem>())
|
||||
{
|
||||
|
||||
// Retrieve existing item.
|
||||
SbmcUserAccount user = ControlAccountMgr.GetUserAccountById(userAccountId);
|
||||
disposables.Add(user);
|
||||
if (user == null && SysId.IsValid(userAccountId))
|
||||
{
|
||||
SaLogger.Warn($"{SysUtil.Fn} Failed to retrieve user for Id: '" + model.User.Id + "'");
|
||||
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.found"]));
|
||||
}
|
||||
|
||||
// Create new item if required.
|
||||
if (user == null)
|
||||
{
|
||||
user = ControlAccountMgr.CreateUserAccount();
|
||||
if (model.User.EmailAddress != null)
|
||||
user.EmailAddress = model.User.EmailAddress;
|
||||
}
|
||||
|
||||
// Populate item properties.
|
||||
if (model.User.Title != null)
|
||||
user.Title = model.User.Title;
|
||||
if (model.User.FirstName != null)
|
||||
user.FirstName = model.User.FirstName;
|
||||
if (model.User.Initials != null)
|
||||
user.Initials = model.User.Initials;
|
||||
if (model.User.LastName != null)
|
||||
user.LastName = model.User.LastName;
|
||||
if (model.User.PhoneMain != null)
|
||||
user.PhoneMain = model.User.PhoneMain;
|
||||
if (model.User.PhoneMobile != null)
|
||||
user.PhoneMobile = model.User.PhoneMobile;
|
||||
if (model.User.PhoneAlt != null)
|
||||
user.PhoneAlt = model.User.PhoneAlt;
|
||||
if (model.User.StatusCode != null)
|
||||
user.Status = model.User.GetStatus();
|
||||
|
||||
// Store item.
|
||||
if (!SysId.IsValid(user.Id))
|
||||
{
|
||||
if (!ControlAccountMgr.InsertUserAccount(user, DbUpdateInfo))
|
||||
{
|
||||
SaLogger.Warn($"{SysUtil.Fn} Failed to insert user: " + ControlAccountMgr.ErrorStateMessage);
|
||||
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.insert.failed"] + "\n\n" + ControlAccountMgr.ErrorStateMessage));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!ControlAccountMgr.UpdateUserAccount(user, DbUpdateInfo))
|
||||
{
|
||||
SaLogger.Warn($"{SysUtil.Fn} Failed to update user [Id:" + SysId.ToString(user.Id) + "]: " + ControlAccountMgr.ErrorStateMessage);
|
||||
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.update.failed"] + "\n\n" + ControlAccountMgr.ErrorStateMessage));
|
||||
}
|
||||
}
|
||||
|
||||
// Construct response.
|
||||
PopulateUserResponseItemData(model, responseData.user, user);
|
||||
responseData.Success = true;
|
||||
}
|
||||
return Json(responseData);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Send User Emails ajax request.
|
||||
//-------------------------------------------------------------------
|
||||
[HttpPost]
|
||||
[Route("Account/UserAccount/Ajax/SendEmails")]
|
||||
public IActionResult Ajax_SendEmails(Model_UserAccount model)
|
||||
{
|
||||
String fn = GetType().Name + ".Ajax_SendEmails";
|
||||
String messageTitle = Localiser["label.emails"];
|
||||
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> disposables = new DisposableCollection<SacsHierarchyItem>())
|
||||
{
|
||||
|
||||
// Retrieve user.
|
||||
SysId userAccountId = model.User.Id.ToSysId();
|
||||
SbmcUserAccount userAccount = ControlAccountMgr.GetUserAccountById(userAccountId);
|
||||
disposables.Add(userAccount);
|
||||
if (userAccount == null)
|
||||
{
|
||||
SaLogger.Warn($"{SysUtil.Fn} Failed to retrieve user for Id: '" + model.User.Id + "'");
|
||||
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.found"]));
|
||||
}
|
||||
|
||||
// Email types to send.
|
||||
bool sendAcountActivationPortal = StringUtil.ToBool(model.UserEmailAcountActivationPortal);
|
||||
bool sendRequestPasswordPortal = StringUtil.ToBool(model.UserEmailRequestPasswordPortal);
|
||||
bool sendRequestPasswordControl = StringUtil.ToBool(model.UserEmailRequestPasswordControl);
|
||||
if (!sendAcountActivationPortal && !sendRequestPasswordPortal && !sendRequestPasswordControl)
|
||||
return Json(responseData.SetMessage(messageTitle, Localiser["msg.validation.select.option"]));
|
||||
|
||||
// Send emails.
|
||||
int emailsSentCount = 0;
|
||||
if (sendAcountActivationPortal)
|
||||
{
|
||||
//if (!OrganisationController.SendAccountActivatedEmail(this, userAccount))
|
||||
return Json(responseData.SetMessage(messageTitle, "Failed to send Online Account Activation email."));
|
||||
//emailsSentCount++;
|
||||
}
|
||||
if (sendRequestPasswordPortal)
|
||||
{
|
||||
if (!SecurityController.SendRequestPasswordEmail(this, userAccount, true))
|
||||
return Json(responseData.SetMessage(messageTitle, "Failed to send Online Request Password email."));
|
||||
emailsSentCount++;
|
||||
}
|
||||
if (sendRequestPasswordControl)
|
||||
{
|
||||
if (!SecurityController.SendRequestPasswordEmail(this, userAccount, false))
|
||||
return Json(responseData.SetMessage(messageTitle, "Failed to send Control Request Password email."));
|
||||
emailsSentCount++;
|
||||
}
|
||||
|
||||
// Construct response.
|
||||
PopulateUserResponseItemData(model, responseData.user, userAccount);
|
||||
responseData.SetNotification(messageTitle, "Emails sent: " + emailsSentCount);
|
||||
responseData.Success = true;
|
||||
}
|
||||
return Json(responseData);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
}
|
||||
//=======================================================================
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Sarand.BusinessMgr.Net.Core.Web.App;
|
||||
using Sarand.BusinessMgr.Web.Control.Models.Account;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Sarand.BusinessMgr.Web.Control.Controllers.Account
|
||||
{
|
||||
//======================================================================
|
||||
public partial class UserAccountController : SbmController_Control
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
// Data members.
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Construction.
|
||||
public UserAccountController(SbmLocaliser localiser, ILogger<UserAccountController> logger) :
|
||||
base(localiser, logger)
|
||||
{
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
[Route("Account/UserAccount")]
|
||||
public IActionResult Account_UserAccount()
|
||||
{
|
||||
// Redirect to sign-in if not signed-in.
|
||||
if (!ValidUserSession)
|
||||
AutoLoginUser();
|
||||
if (!ValidUserSession)
|
||||
{
|
||||
ClearKeepLoggedInCookie();
|
||||
return RedirectToAction("", "Security");
|
||||
}
|
||||
|
||||
// UserAccount page.
|
||||
Model_UserAccount model = new Model_UserAccount(Localiser);
|
||||
return View("Views/Account/UserAccount/Index.cshtml", model);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
}
|
||||
//======================================================================
|
||||
}
|
||||
@@ -1,14 +1,14 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Sarand.BusinessMgr.Net.Core.Web.App;
|
||||
using Sarand.BusinessMgr.Net.Core.Web.Control.Models;
|
||||
using Sarand.BusinessMgr.Web.Control.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Sarand.BusinessMgr.Net.Core.Web.Control.Controllers
|
||||
namespace Sarand.BusinessMgr.Web.Control.Controllers
|
||||
{
|
||||
//======================================================================
|
||||
public class HomeController : SbmController_Control
|
||||
@@ -26,8 +26,18 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Control.Controllers
|
||||
//-------------------------------------------------------------------
|
||||
public IActionResult Index()
|
||||
{
|
||||
// Home page.
|
||||
Model_Home model = new Model_Home(Localiser);
|
||||
if (!ValidUserSession)
|
||||
AutoLoginUser();
|
||||
|
||||
// Redirect to sign-in if not signed-in.
|
||||
if (!ValidUserSession)
|
||||
{
|
||||
ClearKeepLoggedInCookie();
|
||||
return RedirectToAction("", "Security");
|
||||
}
|
||||
|
||||
// Home page.
|
||||
Model_Home model = new Model_Home(Localiser);
|
||||
return View(model);
|
||||
}
|
||||
|
||||
@@ -44,6 +54,8 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Control.Controllers
|
||||
{
|
||||
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
}
|
||||
//======================================================================
|
||||
//======================================================================
|
||||
}
|
||||
|
||||
@@ -0,0 +1,290 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Sarand.BusinessMgr.Net.Standard.Base.Bus.Control;
|
||||
using Sarand.BusinessMgr.Web.Control.Models;
|
||||
using Sarand.BusinessMgr.Web.Control.Models.Instance;
|
||||
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 System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Sarand.BusinessMgr.Web.Control.Controllers.Instance
|
||||
{
|
||||
//=======================================================================
|
||||
partial class OwnerController
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
// Definitions.
|
||||
|
||||
// Owner data.
|
||||
private class OwnerListData : AjaxResponse_Data.ItemData_Base
|
||||
{
|
||||
public String Name { get; set; }
|
||||
public String ContactName { get; set; }
|
||||
public String ContactEmailAddress { get; set; }
|
||||
public String ContactPhone { get; set; }
|
||||
public long? RepositoryCount { get; set; }
|
||||
}
|
||||
private class OwnerItemData : OwnerListData
|
||||
{
|
||||
public String ContactPhoneMain { get; set; }
|
||||
public String ContactPhoneAlt { get; set; }
|
||||
}
|
||||
private void PopulateOwnerResponseListData(SbmModel_Control model, OwnerListData data, SbmcOwner owner)
|
||||
{
|
||||
data.Id = SysId.ToString(owner.Id);
|
||||
data.Name = owner.Name;
|
||||
data.ContactName = owner.ContactName;
|
||||
data.ContactEmailAddress = owner.ContactEmailAddress;
|
||||
data.ContactPhone = StringUtil.OnBlank(owner.ContactPhoneMain, owner.ContactPhoneAlt);
|
||||
data.StatusCode = owner.StatusCode;
|
||||
data.StatusDesc = owner.StatusDesc;
|
||||
}
|
||||
private void PopulateOwnerResponseItemData(SbmModel_Control model, OwnerItemData data, SbmcOwner owner)
|
||||
{
|
||||
PopulateOwnerResponseListData(model, data, owner);
|
||||
data.ContactPhoneMain = owner.ContactPhoneMain;
|
||||
data.ContactPhoneAlt = owner.ContactPhoneAlt;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Get Owner List ajax request.
|
||||
//-------------------------------------------------------------------
|
||||
private class AjaxResponse_OwnerList : AjaxResponse_Data
|
||||
{
|
||||
// Construction.
|
||||
public AjaxResponse_OwnerList(SbmModel_Control model) : base(model) { }
|
||||
// Owner list.
|
||||
public List<OwnerListData> Owners { get; } = new List<OwnerListData>();
|
||||
// Reference data.
|
||||
public List<CodeDesc> Statuses { get; } = new List<CodeDesc>();
|
||||
public List<CodeDesc> StatusesCreate { get; } = new List<CodeDesc>();
|
||||
public String StatusCodeDefault { get; set; }
|
||||
}
|
||||
//-------------------------------------------------------------------
|
||||
[HttpPost]
|
||||
[Route("Instance/Owner/Ajax/GetList")]
|
||||
[Route("Instance/Owner/Ajax/GetOwners")]
|
||||
[Route("Instance/Owner/Ajax/GetOwnerList")]
|
||||
public IActionResult Ajax_GetOwnerList(Model_Owner model)
|
||||
{
|
||||
String fn = GetType().Name + ".Ajax_GetOwnerList";
|
||||
String messageTitle = Localiser["label.owners"];
|
||||
AjaxResponse_OwnerList responseData = new AjaxResponse_OwnerList(model);
|
||||
|
||||
// Validate session and referrer.
|
||||
if (!ValidReferer) return Forbid();
|
||||
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
|
||||
RefreshModel(model);
|
||||
|
||||
// Reference data - statuses.
|
||||
foreach (SaStatus status in SbmcOwner.ValidStatuses)
|
||||
{
|
||||
responseData.Statuses.Add(SacsSystem.GetStatusCode(status), Localiser.GetStatusCaption(status));
|
||||
if (status != SaStatus.Deleted)
|
||||
responseData.StatusesCreate.Add(SacsSystem.GetStatusCode(status), Localiser.GetStatusCaption(status));
|
||||
}
|
||||
responseData.StatusCodeDefault = SacsSystem.GetStatusCode(SbmcOwner.DefaultStatus);
|
||||
|
||||
// Item filter.
|
||||
SbmcOwner.Params prmOwner = new SbmcOwner.Params();
|
||||
prmOwner.Search = model.Filter.Search;
|
||||
prmOwner.StatusCat = model.Filter.GetStatusCat();
|
||||
model.Paging.ItemTotal = (ulong)ControlInstanceMgr.GetOwnerCount(prmOwner);
|
||||
model.PopulatePagingTo(prmOwner.ResultsPaging);
|
||||
|
||||
// Item columns.
|
||||
ColumnDfn colId = ControlInstanceMgr.GetOwnerColumnDfn(SbmcOwner.Column.Id);
|
||||
ColumnDfn colName = ControlInstanceMgr.GetOwnerColumnDfn(SbmcOwner.Column.Name);
|
||||
ColumnDfn colContactName = ControlInstanceMgr.GetOwnerColumnDfn(SbmcOwner.Column.ContactName);
|
||||
ColumnDfn colContactEmail = ControlInstanceMgr.GetOwnerColumnDfn(SbmcOwner.Column.ContactEmailAddress);
|
||||
ColumnDfn colContactPhoneMain = ControlInstanceMgr.GetOwnerColumnDfn(SbmcOwner.Column.ContactPhoneMain);
|
||||
ColumnDfn colContactPhoneAlt = ControlInstanceMgr.GetOwnerColumnDfn(SbmcOwner.Column.ContactPhoneAlt);
|
||||
ColumnDfn colRepoCount = ControlInstanceMgr.GetOwnerColumnDfn(SbmcOwner.Column.RepositoryCount);
|
||||
ColumnDfn colStatus = ControlInstanceMgr.GetOwnerColumnDfn(SbmcOwner.Column.Status);
|
||||
List<ColumnDfn> columns = new List<ColumnDfn>()
|
||||
{
|
||||
colId, colName, colContactName, colContactEmail,
|
||||
colContactPhoneMain, colContactPhoneAlt, colRepoCount, colStatus
|
||||
};
|
||||
|
||||
// Query items.
|
||||
using (DbResultSet resultSet = ControlInstanceMgr.ExecuteOwnerQuery(columns, prmOwner))
|
||||
{
|
||||
while (resultSet != null && resultSet.HasRows && resultSet.NextRecord())
|
||||
{
|
||||
NamedValue.Map values = resultSet.GetRecordValues();
|
||||
OwnerListData ownerData = new OwnerListData()
|
||||
{
|
||||
Id = values.GetString(colId.DataName),
|
||||
Name = values.GetString(colName.DataName),
|
||||
ContactName = values.GetString(colContactName.DataName),
|
||||
ContactEmailAddress = values.GetString(colContactEmail.DataName),
|
||||
ContactPhone = StringUtil.OnBlank(values.GetString(colContactPhoneMain.DataName), values.GetString(colContactPhoneAlt.DataName)),
|
||||
StatusCode = values.GetString(colStatus.DataName),
|
||||
StatusDesc = SacsSystem.GetStatusDescForCode(values.GetString(colStatus.DataName))
|
||||
};
|
||||
long repositoryCount = values.GetLong(colRepoCount.DataName);
|
||||
if (repositoryCount > 0)
|
||||
ownerData.RepositoryCount = repositoryCount;
|
||||
responseData.Owners.Add(ownerData);
|
||||
}
|
||||
}
|
||||
if (model.Paging.IsPaged)
|
||||
model.Paging.ItemCount = (ushort)responseData.Owners.Count;
|
||||
|
||||
// Complete response.
|
||||
responseData.SetPaging(model);
|
||||
responseData.Success = true;
|
||||
return Json(responseData);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Get Owner ajax request.
|
||||
//-------------------------------------------------------------------
|
||||
private class AjaxResponse_Owner : AjaxResponse_Data
|
||||
{
|
||||
// Construction.
|
||||
public AjaxResponse_Owner(SbmModel_Control model) : base(model) { }
|
||||
// Owner details.
|
||||
public OwnerItemData owner { get; } = new OwnerItemData();
|
||||
}
|
||||
//-------------------------------------------------------------------
|
||||
[HttpPost]
|
||||
[Route("Instance/Owner/Ajax/GetItem")]
|
||||
[Route("Instance/Owner/Ajax/GetOwner")]
|
||||
public IActionResult Ajax_GetOwner(Model_Owner model)
|
||||
{
|
||||
String fn = GetType().Name + ".Ajax_GetOwner";
|
||||
String messageTitle = Localiser["label.owner"];
|
||||
AjaxResponse_Owner responseData = new AjaxResponse_Owner(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> disposables = new DisposableCollection<SacsHierarchyItem>())
|
||||
{
|
||||
|
||||
// Identify item.
|
||||
SysId ownerId = model.Owner.Id.ToSysId();
|
||||
if (!SysId.IsValid(ownerId))
|
||||
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.specified"]));
|
||||
|
||||
// Retrieve item.
|
||||
SbmcOwner owner = ControlInstanceMgr.GetOwnerById(ownerId);
|
||||
disposables.Add(owner);
|
||||
if (owner == null)
|
||||
{
|
||||
SaLogger.Warn($"{SysUtil.Fn} Failed to retrieve owner for Id: '" + ownerId + "'");
|
||||
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.found"]));
|
||||
}
|
||||
|
||||
// Construct response.
|
||||
PopulateOwnerResponseItemData(model, responseData.owner, owner);
|
||||
responseData.Success = true;
|
||||
}
|
||||
return Json(responseData);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Store Owner ajax request.
|
||||
//-------------------------------------------------------------------
|
||||
[HttpPost]
|
||||
[Route("Instance/Owner/Ajax/StoreItem")]
|
||||
[Route("Instance/Owner/Ajax/StoreOwner")]
|
||||
public IActionResult Ajax_StoreOwner(Model_Owner model)
|
||||
{
|
||||
String fn = GetType().Name + ".Ajax_StoreOwner";
|
||||
String messageTitle = Localiser["label.owner"];
|
||||
AjaxResponse_Owner responseData = new AjaxResponse_Owner(model);
|
||||
|
||||
// Validate session and referrer.
|
||||
if (!ValidReferer) return Forbid();
|
||||
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
|
||||
RefreshModel(model);
|
||||
|
||||
// Validate item Id.
|
||||
SysId ownerId = model.Owner.Id.ToSysId();
|
||||
if (StringUtil.ToBool(model.Owner.Exists) && !SysId.IsValid(ownerId))
|
||||
{
|
||||
SaLogger.Warn($"{SysUtil.Fn} Owner Id not supplied for existing item.");
|
||||
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.system"]));
|
||||
}
|
||||
if (!StringUtil.ToBool(model.Owner.Exists) && SysId.IsValid(ownerId))
|
||||
{
|
||||
SaLogger.Warn($"{SysUtil.Fn} Owner Id supplied for new item.");
|
||||
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.system"]));
|
||||
}
|
||||
|
||||
// Validate item properties.
|
||||
model.ValidateOwnerData(responseData);
|
||||
if (responseData.HasControlErrors())
|
||||
return Json(responseData);
|
||||
|
||||
// Record disposable items as they are created so all can be disposed when needed.
|
||||
using (DisposableCollection<SacsHierarchyItem> disposables = new DisposableCollection<SacsHierarchyItem>())
|
||||
{
|
||||
|
||||
// Retrieve existing item.
|
||||
SbmcOwner owner = ControlInstanceMgr.GetOwnerById(ownerId);
|
||||
disposables.Add(owner);
|
||||
if (owner == null && SysId.IsValid(ownerId))
|
||||
{
|
||||
SaLogger.Warn($"{SysUtil.Fn} Failed to retrieve owner for Id: '" + model.Owner.Id + "'");
|
||||
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.found"]));
|
||||
}
|
||||
|
||||
// Create new item if required.
|
||||
if (owner == null)
|
||||
{
|
||||
owner = ControlInstanceMgr.CreateOwner();
|
||||
}
|
||||
|
||||
// Populate item properties.
|
||||
if (model.Owner.Name != null)
|
||||
owner.Name = model.Owner.Name;
|
||||
if (model.Owner.ContactName != null)
|
||||
owner.ContactName = model.Owner.ContactName;
|
||||
if (model.Owner.ContactEmailAddress != null)
|
||||
owner.ContactEmailAddress = model.Owner.ContactEmailAddress;
|
||||
if (model.Owner.ContactPhoneMain != null)
|
||||
owner.ContactPhoneMain = model.Owner.ContactPhoneMain;
|
||||
if (model.Owner.ContactPhoneAlt != null)
|
||||
owner.ContactPhoneAlt = model.Owner.ContactPhoneAlt;
|
||||
if (model.Owner.StatusCode != null)
|
||||
owner.Status = model.Owner.GetStatus();
|
||||
|
||||
// Store item.
|
||||
if (!SysId.IsValid(owner.Id))
|
||||
{
|
||||
if (!ControlInstanceMgr.InsertOwner(owner, DbUpdateInfo))
|
||||
{
|
||||
SaLogger.Warn($"{SysUtil.Fn} Failed to insert owner: " + ControlInstanceMgr.ErrorStateMessage);
|
||||
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.insert.failed"] + "\n\n" + ControlInstanceMgr.ErrorStateMessage));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!ControlInstanceMgr.UpdateOwner(owner, DbUpdateInfo))
|
||||
{
|
||||
SaLogger.Warn($"{SysUtil.Fn} Failed to update owner [Id:" + SysId.ToString(owner.Id) + "]: " + ControlInstanceMgr.ErrorStateMessage);
|
||||
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.update.failed"] + "\n\n" + ControlInstanceMgr.ErrorStateMessage));
|
||||
}
|
||||
}
|
||||
|
||||
// Construct response.
|
||||
PopulateOwnerResponseItemData(model, responseData.owner, owner);
|
||||
responseData.Success = true;
|
||||
}
|
||||
return Json(responseData);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
}
|
||||
//=======================================================================
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Sarand.BusinessMgr.Net.Core.Web.App;
|
||||
using Sarand.BusinessMgr.Web.Control.Models.Instance;
|
||||
|
||||
namespace Sarand.BusinessMgr.Web.Control.Controllers.Instance
|
||||
{
|
||||
//======================================================================
|
||||
public partial class OwnerController : SbmController_Control
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
// Data members.
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Construction.
|
||||
public OwnerController(SbmLocaliser localiser, ILogger<OwnerController> logger) :
|
||||
base(localiser, logger)
|
||||
{
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
[Route("Instance/Owner")]
|
||||
public IActionResult Index()
|
||||
{
|
||||
if (!ValidUserSession)
|
||||
AutoLoginUser();
|
||||
|
||||
// Redirect to sign-in if not signed-in.
|
||||
if (!ValidUserSession)
|
||||
{
|
||||
ClearKeepLoggedInCookie();
|
||||
return RedirectToAction("", "Security");
|
||||
}
|
||||
|
||||
// Owner page.
|
||||
Model_Owner model = new Model_Owner(Localiser);
|
||||
return View("Views/Instance/Owner/Index.cshtml", model);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
}
|
||||
//======================================================================
|
||||
}
|
||||
+313
@@ -0,0 +1,313 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Sarand.BusinessMgr.Net.Standard.Base.Bus.Control;
|
||||
using Sarand.BusinessMgr.Web.Control.Models;
|
||||
using Sarand.BusinessMgr.Web.Control.Models.Instance;
|
||||
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 System;
|
||||
using System.Collections.Generic;
|
||||
using static Sarand.BusinessMgr.Net.Standard.Base.App.Sbm.Config.Security;
|
||||
|
||||
namespace Sarand.BusinessMgr.Web.Control.Controllers.Instance
|
||||
{
|
||||
//=======================================================================
|
||||
partial class RepositoryController
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
// Definitions.
|
||||
private static String PasswordDummyValue = "********";
|
||||
|
||||
// Repository data.
|
||||
private class RepositoryListData : AjaxResponse_Data.ItemData_Base
|
||||
{
|
||||
public String OwnerName { get; set; }
|
||||
public String Context { get; set; }
|
||||
public String ServerName { get; set; }
|
||||
public String InstanceName { get; set; }
|
||||
public String DatabaseName { get; set; }
|
||||
}
|
||||
private class RepositoryItemData : RepositoryListData
|
||||
{
|
||||
public String OwnerId { get; set; }
|
||||
public String UserName { get; set; }
|
||||
public String PasswordDummy { get; } = PasswordDummyValue;
|
||||
}
|
||||
private void PopulateRepositoryResponseListData(SbmModel_Control model, RepositoryListData data, SbmcOwner owner, SbmcRepository repository)
|
||||
{
|
||||
data.Id = SysId.ToString(repository.Id);
|
||||
data.OwnerName = owner.Name;
|
||||
data.Context = repository.Context;
|
||||
data.ServerName = repository.ServerName;
|
||||
data.InstanceName = repository.InstanceName;
|
||||
data.DatabaseName = repository.DatabaseName;
|
||||
data.StatusCode = repository.StatusCode;
|
||||
data.StatusDesc = repository.StatusDesc;
|
||||
}
|
||||
private void PopulateRepositoryResponseItemData(SbmModel_Control model, RepositoryItemData data, SbmcOwner owner, SbmcRepository repository)
|
||||
{
|
||||
PopulateRepositoryResponseListData(model, data, owner, repository);
|
||||
data.OwnerId = SysId.ToString(repository.OwnerId);
|
||||
data.UserName = repository.UserName;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Get Repository List ajax request.
|
||||
//-------------------------------------------------------------------
|
||||
private class AjaxResponse_RepositoryList : AjaxResponse_Data
|
||||
{
|
||||
// Construction.
|
||||
public AjaxResponse_RepositoryList(SbmModel_Control model) : base(model) { }
|
||||
// Repository list.
|
||||
public List<RepositoryListData> Repositories { get; } = new List<RepositoryListData>();
|
||||
// Reference data.
|
||||
public List<CodeDesc> Statuses { get; } = new List<CodeDesc>();
|
||||
public List<CodeDesc> StatusesCreate { get; } = new List<CodeDesc>();
|
||||
public String StatusCodeDefault { get; set; }
|
||||
}
|
||||
//-------------------------------------------------------------------
|
||||
[HttpPost]
|
||||
[Route("Instance/Repository/Ajax/GetList")]
|
||||
[Route("Instance/Repository/Ajax/GetRepositories")]
|
||||
[Route("Instance/Repository/Ajax/GetRepositoryList")]
|
||||
public IActionResult Ajax_GetRepositoryList(Model_Repository model)
|
||||
{
|
||||
String fn = GetType().Name + ".Ajax_GetRepositoryList";
|
||||
String messageTitle = Localiser["label.repositories"];
|
||||
AjaxResponse_RepositoryList responseData = new AjaxResponse_RepositoryList(model);
|
||||
|
||||
// Validate session and referrer.
|
||||
if (!ValidReferer) return Forbid();
|
||||
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
|
||||
RefreshModel(model);
|
||||
|
||||
// Reference data - statuses.
|
||||
foreach (SaStatus status in SbmcRepository.ValidStatuses)
|
||||
{
|
||||
responseData.Statuses.Add(SacsSystem.GetStatusCode(status), Localiser.GetStatusCaption(status));
|
||||
if (status != SaStatus.Deleted)
|
||||
responseData.StatusesCreate.Add(SacsSystem.GetStatusCode(status), Localiser.GetStatusCaption(status));
|
||||
}
|
||||
responseData.StatusCodeDefault = SacsSystem.GetStatusCode(SbmcRepository.DefaultStatus);
|
||||
|
||||
// Item filter.
|
||||
SbmcRepository.Params prmRepository = new SbmcRepository.Params();
|
||||
prmRepository.Search = model.Filter.Search;
|
||||
prmRepository.StatusCat = model.Filter.GetStatusCat();
|
||||
model.Paging.ItemTotal = (ulong)ControlInstanceMgr.GetRepositoryCount(prmRepository);
|
||||
model.PopulatePagingTo(prmRepository.ResultsPaging);
|
||||
|
||||
// Item columns.
|
||||
ColumnDfn colId = ControlInstanceMgr.GetRepositoryColumnDfn(SbmcRepository.Column.Id);
|
||||
ColumnDfn colOwnerName = ControlInstanceMgr.GetRepositoryColumnDfn(SbmcRepository.Column.OwnerName);
|
||||
ColumnDfn colContext = ControlInstanceMgr.GetRepositoryColumnDfn(SbmcRepository.Column.Context);
|
||||
ColumnDfn colServerName = ControlInstanceMgr.GetRepositoryColumnDfn(SbmcRepository.Column.ServerName);
|
||||
ColumnDfn colInstanceName = ControlInstanceMgr.GetRepositoryColumnDfn(SbmcRepository.Column.InstanceName);
|
||||
ColumnDfn colDatabaseName = ControlInstanceMgr.GetRepositoryColumnDfn(SbmcRepository.Column.DatabaseName);
|
||||
ColumnDfn colStatus = ControlInstanceMgr.GetRepositoryColumnDfn(SbmcRepository.Column.Status);
|
||||
List<ColumnDfn> columns = new List<ColumnDfn>()
|
||||
{
|
||||
colId, colOwnerName, colContext, colServerName, colInstanceName,
|
||||
colDatabaseName, colStatus
|
||||
};
|
||||
|
||||
// Query items.
|
||||
using (DbResultSet resultSet = ControlInstanceMgr.ExecuteRepositoryQuery(columns, prmRepository))
|
||||
{
|
||||
while (resultSet != null && resultSet.HasRows && resultSet.NextRecord())
|
||||
{
|
||||
NamedValue.Map values = resultSet.GetRecordValues();
|
||||
RepositoryListData repositoryData = new RepositoryListData()
|
||||
{
|
||||
Id = values.GetString(colId.DataName),
|
||||
OwnerName = values.GetString(colOwnerName.DataName),
|
||||
Context = values.GetString(colContext.DataName),
|
||||
ServerName = values.GetString(colServerName.DataName),
|
||||
InstanceName = values.GetString(colInstanceName.DataName),
|
||||
DatabaseName = values.GetString(colDatabaseName.DataName),
|
||||
StatusCode = values.GetString(colStatus.DataName),
|
||||
StatusDesc = SacsSystem.GetStatusDescForCode(values.GetString(colStatus.DataName))
|
||||
};
|
||||
responseData.Repositories.Add(repositoryData);
|
||||
}
|
||||
}
|
||||
if (model.Paging.IsPaged)
|
||||
model.Paging.ItemCount = (ushort)responseData.Repositories.Count;
|
||||
|
||||
// Complete response.
|
||||
responseData.SetPaging(model);
|
||||
responseData.Success = true;
|
||||
return Json(responseData);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Get Repository ajax request.
|
||||
//-------------------------------------------------------------------
|
||||
private class AjaxResponse_Repository : AjaxResponse_Data
|
||||
{
|
||||
// Construction.
|
||||
public AjaxResponse_Repository(SbmModel_Control model) : base(model) { }
|
||||
// Repository details.
|
||||
public RepositoryItemData repository { get; } = new RepositoryItemData();
|
||||
}
|
||||
//-------------------------------------------------------------------
|
||||
[HttpPost]
|
||||
[Route("Instance/Repository/Ajax/GetItem")]
|
||||
[Route("Instance/Repository/Ajax/GetRepository")]
|
||||
public IActionResult Ajax_GetRepository(Model_Repository model)
|
||||
{
|
||||
String fn = GetType().Name + ".Ajax_GetRepository";
|
||||
String messageTitle = Localiser["label.repository"];
|
||||
AjaxResponse_Repository responseData = new AjaxResponse_Repository(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> disposables = new DisposableCollection<SacsHierarchyItem>())
|
||||
{
|
||||
|
||||
// Identify item.
|
||||
SysId repositoryId = model.Repository.Id.ToSysId();
|
||||
if (!SysId.IsValid(repositoryId))
|
||||
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.specified"]));
|
||||
|
||||
// Retrieve item.
|
||||
SbmcRepository repository = ControlInstanceMgr.GetRepositoryById(repositoryId);
|
||||
disposables.Add(repository);
|
||||
if (repository == null)
|
||||
{
|
||||
SaLogger.Warn($"{SysUtil.Fn} Failed to retrieve repository for Id: '" + repositoryId + "'");
|
||||
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.found"]));
|
||||
}
|
||||
|
||||
// Owner.
|
||||
SbmcOwner owner = ControlInstanceMgr.GetOwnerById(repository.OwnerId);
|
||||
disposables.Add(owner);
|
||||
if (owner == null)
|
||||
{
|
||||
SaLogger.Warn($"{SysUtil.Fn} Failed to retrieve owner for Id: '" + repository.OwnerId + "'");
|
||||
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.system"]));
|
||||
}
|
||||
|
||||
// Construct response.
|
||||
PopulateRepositoryResponseItemData(model, responseData.repository, owner, repository);
|
||||
responseData.Success = true;
|
||||
}
|
||||
return Json(responseData);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Store Repository ajax request.
|
||||
//-------------------------------------------------------------------
|
||||
[HttpPost]
|
||||
[Route("Instance/Repository/Ajax/StoreItem")]
|
||||
[Route("Instance/Repository/Ajax/StoreRepository")]
|
||||
public IActionResult Ajax_StoreRepository(Model_Repository model)
|
||||
{
|
||||
String fn = GetType().Name + ".Ajax_StoreRepository";
|
||||
String messageTitle = Localiser["label.repository"];
|
||||
AjaxResponse_Repository responseData = new AjaxResponse_Repository(model);
|
||||
|
||||
// Validate session and referrer.
|
||||
if (!ValidReferer) return Forbid();
|
||||
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
|
||||
RefreshModel(model);
|
||||
|
||||
// Validate item Id.
|
||||
SysId repositoryId = model.Repository.Id.ToSysId();
|
||||
if (StringUtil.ToBool(model.Repository.Exists) && !SysId.IsValid(repositoryId))
|
||||
{
|
||||
SaLogger.Warn($"{SysUtil.Fn} Repository Id not supplied for existing item.");
|
||||
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.system"]));
|
||||
}
|
||||
if (!StringUtil.ToBool(model.Repository.Exists) && SysId.IsValid(repositoryId))
|
||||
{
|
||||
SaLogger.Warn($"{SysUtil.Fn} Repository Id supplied for new item.");
|
||||
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.system"]));
|
||||
}
|
||||
|
||||
// Validate item properties.
|
||||
model.ValidateRepositoryData(responseData);
|
||||
if (responseData.HasControlErrors())
|
||||
return Json(responseData);
|
||||
|
||||
// Record disposable items as they are created so all can be disposed when needed.
|
||||
using (DisposableCollection<SacsHierarchyItem> disposables = new DisposableCollection<SacsHierarchyItem>())
|
||||
{
|
||||
|
||||
// Retrieve existing item.
|
||||
SbmcRepository repository = ControlInstanceMgr.GetRepositoryById(repositoryId);
|
||||
disposables.Add(repository);
|
||||
if (repository == null && SysId.IsValid(repositoryId))
|
||||
{
|
||||
SaLogger.Warn($"{SysUtil.Fn} Failed to retrieve repository for Id: '" + model.Repository.Id + "'");
|
||||
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.found"]));
|
||||
}
|
||||
|
||||
// Owner.
|
||||
SysId ownerId = repository?.OwnerId ?? StringUtil.ToSysId(model.Repository.OwnerId);
|
||||
SbmcOwner owner = ControlInstanceMgr.GetOwnerById(ownerId);
|
||||
disposables.Add(owner);
|
||||
if (owner == null)
|
||||
{
|
||||
SaLogger.Warn($"{SysUtil.Fn} Failed to retrieve owner for Id: '" + ownerId + "'");
|
||||
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.system"]));
|
||||
}
|
||||
|
||||
// Create new item if required.
|
||||
if (repository == null)
|
||||
{
|
||||
repository = ControlInstanceMgr.CreateRepository();
|
||||
}
|
||||
|
||||
// Populate item properties.
|
||||
repository.OwnerId = owner.Id;
|
||||
if (model.Repository.Context != null)
|
||||
repository.Context = model.Repository.Context;
|
||||
if (model.Repository.ServerName != null)
|
||||
repository.ServerName = model.Repository.ServerName;
|
||||
if (model.Repository.InstanceName != null)
|
||||
repository.InstanceName = model.Repository.InstanceName;
|
||||
if (model.Repository.DatabaseName != null)
|
||||
repository.DatabaseName = model.Repository.DatabaseName;
|
||||
if (model.Repository.UserName != null)
|
||||
repository.UserName = model.Repository.UserName;
|
||||
if (model.Repository.Password != null && !model.Repository.Password.Equals(PasswordDummyValue))
|
||||
repository.Password = model.Repository.Password;
|
||||
if (model.Repository.StatusCode != null)
|
||||
repository.Status = model.Repository.GetStatus();
|
||||
|
||||
// Store item.
|
||||
if (!SysId.IsValid(repository.Id))
|
||||
{
|
||||
if (!ControlInstanceMgr.InsertRepository(repository, DbUpdateInfo))
|
||||
{
|
||||
SaLogger.Warn($"{SysUtil.Fn} Failed to insert repository: " + ControlInstanceMgr.ErrorStateMessage);
|
||||
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.insert.failed"] + "\n\n" + ControlInstanceMgr.ErrorStateMessage));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!ControlInstanceMgr.UpdateRepository(repository, DbUpdateInfo))
|
||||
{
|
||||
SaLogger.Warn($"{SysUtil.Fn} Failed to update repository [Id:" + SysId.ToString(repository.Id) + "]: " + ControlInstanceMgr.ErrorStateMessage);
|
||||
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.update.failed"] + "\n\n" + ControlInstanceMgr.ErrorStateMessage));
|
||||
}
|
||||
}
|
||||
|
||||
// Construct response.
|
||||
PopulateRepositoryResponseItemData(model, responseData.repository, owner, repository);
|
||||
responseData.Success = true;
|
||||
}
|
||||
return Json(responseData);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
}
|
||||
//=======================================================================
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Sarand.BusinessMgr.Net.Core.Web.App;
|
||||
using Sarand.BusinessMgr.Web.Control.Models.Instance;
|
||||
|
||||
namespace Sarand.BusinessMgr.Web.Control.Controllers.Instance
|
||||
{
|
||||
//======================================================================
|
||||
public partial class RepositoryController : SbmController_Control
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
// Data members.
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Construction.
|
||||
public RepositoryController(SbmLocaliser localiser, ILogger<RepositoryController> logger) :
|
||||
base(localiser, logger)
|
||||
{
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
[Route("Instance/Repository")]
|
||||
public IActionResult Index()
|
||||
{
|
||||
if (!ValidUserSession)
|
||||
AutoLoginUser();
|
||||
|
||||
// Redirect to sign-in if not signed-in.
|
||||
if (!ValidUserSession)
|
||||
{
|
||||
ClearKeepLoggedInCookie();
|
||||
return RedirectToAction("", "Security");
|
||||
}
|
||||
|
||||
// Repository page.
|
||||
Model_Repository model = new Model_Repository(Localiser);
|
||||
return View("Views/Instance/Repository/Index.cshtml", model);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
}
|
||||
//======================================================================
|
||||
}
|
||||
@@ -0,0 +1,426 @@
|
||||
using DocumentFormat.OpenXml.InkML;
|
||||
using DocumentFormat.OpenXml.Wordprocessing;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Sarand.BusinessMgr.Net.Standard.Base.Bus.Control;
|
||||
using Sarand.BusinessMgr.Web.Control.Models;
|
||||
using Sarand.BusinessMgr.Web.Control.Models.Param;
|
||||
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 System;
|
||||
using System.Collections.Generic;
|
||||
using static Sarand.BusinessMgr.Net.Standard.Base.App.SbmSysParamCache;
|
||||
|
||||
namespace Sarand.BusinessMgr.Web.Control.Controllers.Param
|
||||
{
|
||||
//=======================================================================
|
||||
partial class ParamController
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
// Definitions.
|
||||
|
||||
// Param data.
|
||||
private class ParamListData : AjaxResponse_Data.ItemData_Base
|
||||
{
|
||||
public String ItemCaption { get; set; }
|
||||
public String ParamType { get; set; }
|
||||
public String ParamCaption { get; set; }
|
||||
public String Name { get; set; }
|
||||
public String Context { get; set; }
|
||||
public String Value { get; set; }
|
||||
public bool? Password { get; set; }
|
||||
}
|
||||
private class ParamItemData : ParamListData
|
||||
{
|
||||
public String ItemType { get; set; }
|
||||
public String ItemId { get; set; }
|
||||
}
|
||||
private void PopulateParamResponseListData(SbmModel_Control model, ParamListData data, SbmcItemParam itemParam)
|
||||
{
|
||||
data.Id = SysId.ToString(itemParam.Id);
|
||||
data.ItemCaption = itemParam.ItemId.Type.ToString() + ": " + itemParam.ItemId.Id;
|
||||
data.Name = itemParam.Name;
|
||||
data.Context = itemParam.Context;
|
||||
if (itemParam.IsPasswordValue())
|
||||
data.Password = true;
|
||||
else
|
||||
data.Value = itemParam.Value;
|
||||
data.StatusCode = itemParam.StatusCode;
|
||||
data.StatusDesc = itemParam.StatusDesc;
|
||||
}
|
||||
private void PopulateParamResponseItemData(SbmModel_Control model, ParamItemData data, SbmcItemParam itemParam)
|
||||
{
|
||||
PopulateParamResponseListData(model, data, itemParam);
|
||||
data.ItemType = itemParam.ItemId.Type.ToString();
|
||||
data.ItemId = SysId.ToString(itemParam.ItemId.Id);
|
||||
data.ItemCaption = ControlItemMgr.GetItemCaption(itemParam.ItemId);
|
||||
data.Notes = itemParam.Notes;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Get Filter Items ajax request.
|
||||
//-------------------------------------------------------------------
|
||||
private class AjaxResponse_FilterItemList : AjaxResponse_Data
|
||||
{
|
||||
// Construction.
|
||||
public AjaxResponse_FilterItemList(SbmModel_Control model) : base(model) { }
|
||||
// Items.
|
||||
public List<ItemIdName> Items { get; } = new List<ItemIdName>();
|
||||
}
|
||||
//-------------------------------------------------------------------
|
||||
[HttpPost]
|
||||
[Route("Param/Param/Ajax/GetFilterItems")]
|
||||
public IActionResult Ajax_GetParamFilterItemList(Model_Param model)
|
||||
{
|
||||
String messageTitle = Localiser["label.params"];
|
||||
AjaxResponse_FilterItemList responseData = new AjaxResponse_FilterItemList(model);
|
||||
|
||||
// Validate session and referrer.
|
||||
if (!ValidReferer) return Forbid();
|
||||
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
|
||||
RefreshModel(model);
|
||||
|
||||
SbmcHierarchyItem.ItemType itemType = EnumUtil.ParseEnum(model.Filter.ItemType, SbmcHierarchyItem.ItemType.Unknown);
|
||||
if (itemType == SbmcHierarchyItem.ItemType.Unknown)
|
||||
{
|
||||
SaLogger.Warn($"{SysUtil.Fn} Item type not specified.");
|
||||
return Json(responseData);
|
||||
}
|
||||
|
||||
if (itemType == SbmcHierarchyItem.ItemType.Owner)
|
||||
{
|
||||
// Item filter.
|
||||
SbmcOwner.Params prmOwner = new SbmcOwner.Params();
|
||||
prmOwner.StatusCat = SbmcOwner.StatusCat.ActiveOnly;
|
||||
//model.Paging.ItemTotal = (ulong)ControlInstanceMgr.GetOwnerCount(prmOwner);
|
||||
//model.PopulatePagingTo(prmOwner.ResultsPaging);
|
||||
|
||||
// Item columns.
|
||||
ColumnDfn colId = ControlInstanceMgr.GetOwnerColumnDfn(SbmcOwner.Column.Id);
|
||||
ColumnDfn colName = ControlInstanceMgr.GetOwnerColumnDfn(SbmcOwner.Column.Name);
|
||||
List<ColumnDfn> columns = new List<ColumnDfn>() { colId, colName };
|
||||
|
||||
// Query items.
|
||||
using (DbResultSet resultSet = ControlInstanceMgr.ExecuteOwnerQuery(columns, prmOwner))
|
||||
{
|
||||
while (resultSet != null && resultSet.HasRows && resultSet.NextRecord())
|
||||
{
|
||||
NamedValue.Map values = resultSet.GetRecordValues();
|
||||
AjaxResponse_Data.ItemIdName itemParamData = new AjaxResponse_Data.ItemIdName()
|
||||
{
|
||||
Id = values.GetString(colId.DataName),
|
||||
Name = values.GetString(colName.DataName),
|
||||
};
|
||||
responseData.Items.Add(itemParamData);
|
||||
}
|
||||
}
|
||||
//if (model.Paging.IsPaged)
|
||||
// model.Paging.ItemCount = (ushort)responseData.Items.Count;
|
||||
|
||||
// Complete response.
|
||||
responseData.SetPaging(model);
|
||||
responseData.Success = true;
|
||||
}
|
||||
else if (itemType == SbmcHierarchyItem.ItemType.Repository)
|
||||
{
|
||||
// Item filter.
|
||||
SbmcRepository.Params prmRepository = new SbmcRepository.Params();
|
||||
prmRepository.StatusCat = SbmcRepository.StatusCat.ActiveOnly;
|
||||
//model.Paging.ItemTotal = (ulong)ControlInstanceMgr.GetRepositoryCount(prmRepository);
|
||||
//model.PopulatePagingTo(prmRepository.ResultsPaging);
|
||||
|
||||
// Item columns.
|
||||
ColumnDfn colId = ControlInstanceMgr.GetRepositoryColumnDfn(SbmcRepository.Column.Id);
|
||||
ColumnDfn colName = ControlInstanceMgr.GetRepositoryColumnDfn(SbmcRepository.Column.Context);
|
||||
List<ColumnDfn> columns = new List<ColumnDfn>() { colId, colName };
|
||||
|
||||
// Query items.
|
||||
using (DbResultSet resultSet = ControlInstanceMgr.ExecuteRepositoryQuery(columns, prmRepository))
|
||||
{
|
||||
while (resultSet != null && resultSet.HasRows && resultSet.NextRecord())
|
||||
{
|
||||
NamedValue.Map values = resultSet.GetRecordValues();
|
||||
AjaxResponse_Data.ItemIdName itemParamData = new AjaxResponse_Data.ItemIdName()
|
||||
{
|
||||
Id = values.GetString(colId.DataName),
|
||||
Name = values.GetString(colName.DataName),
|
||||
};
|
||||
responseData.Items.Add(itemParamData);
|
||||
}
|
||||
}
|
||||
//if (model.Paging.IsPaged)
|
||||
// model.Paging.ItemCount = (ushort)responseData.Items.Count;
|
||||
|
||||
// Complete response.
|
||||
responseData.SetPaging(model);
|
||||
responseData.Success = true;
|
||||
}
|
||||
return Json(responseData);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Get Param List ajax request.
|
||||
//-------------------------------------------------------------------
|
||||
private class AjaxResponse_ParamList : AjaxResponse_Data
|
||||
{
|
||||
// Construction.
|
||||
public AjaxResponse_ParamList(SbmModel_Control model) : base(model) { }
|
||||
// Items.
|
||||
public List<ParamListData> Params { get; } = new List<ParamListData>();
|
||||
// Reference data.
|
||||
public List<CodeDesc> Statuses { get; } = new List<CodeDesc>();
|
||||
public List<CodeDesc> StatusesCreate { get; } = new List<CodeDesc>();
|
||||
public String StatusCodeDefault { get; set; }
|
||||
}
|
||||
//-------------------------------------------------------------------
|
||||
[HttpPost]
|
||||
[Route("Param/Param/Ajax/GetList")]
|
||||
[Route("Param/Param/Ajax/GetParams")]
|
||||
[Route("Param/Param/Ajax/GetParamList")]
|
||||
public IActionResult Ajax_GetParamList(Model_Param model)
|
||||
{
|
||||
String messageTitle = Localiser["label.params"];
|
||||
AjaxResponse_ParamList responseData = new AjaxResponse_ParamList(model);
|
||||
|
||||
// Validate session and referrer.
|
||||
if (!ValidReferer) return Forbid();
|
||||
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
|
||||
RefreshModel(model);
|
||||
|
||||
// Reference data - statuses.
|
||||
foreach (SaStatus status in SbmcItemParam.ValidStatuses)
|
||||
{
|
||||
responseData.Statuses.Add(SacsSystem.GetStatusCode(status), Localiser.GetStatusCaption(status));
|
||||
if (status != SaStatus.Deleted)
|
||||
responseData.StatusesCreate.Add(SacsSystem.GetStatusCode(status), Localiser.GetStatusCaption(status));
|
||||
}
|
||||
responseData.StatusCodeDefault = SacsSystem.GetStatusCode(SbmcItemParam.DefaultStatus);
|
||||
|
||||
// Item filter.
|
||||
SbmcItemParam.Params prmParam = new SbmcItemParam.Params();
|
||||
ItemId itemId = new ItemId(StringUtil.ToShort(model.Filter.ItemType), StringUtil.ToSysId(model.Filter.ItemId));
|
||||
if (ItemId.IsValid(itemId))
|
||||
prmParam.ItemIdsIncluded.Add(itemId);
|
||||
prmParam.Search = model.Filter.Search;
|
||||
prmParam.Search = model.Filter.Search;
|
||||
prmParam.StatusCat = model.Filter.GetStatusCat();
|
||||
model.Paging.ItemTotal = (ulong)ControlItemMgr.GetItemParamCount(prmParam);
|
||||
model.PopulatePagingTo(prmParam.ResultsPaging);
|
||||
|
||||
// Item columns.
|
||||
ColumnDfn colId = ControlItemMgr.GetItemParamColumnDfn(SbmcItemParam.Column.Id);
|
||||
ColumnDfn colItemCaption = ControlItemMgr.GetItemParamColumnDfn(SbmcItemParam.Column.Item_Caption);
|
||||
ColumnDfn colContext = ControlItemMgr.GetItemParamColumnDfn(SbmcItemParam.Column.Context);
|
||||
ColumnDfn colName = ControlItemMgr.GetItemParamColumnDfn(SbmcItemParam.Column.Name);
|
||||
ColumnDfn colValue = ControlItemMgr.GetItemParamColumnDfn(SbmcItemParam.Column.Value);
|
||||
ColumnDfn colFlags = ControlItemMgr.GetItemParamColumnDfn(SbmcItemParam.Column.Status);
|
||||
ColumnDfn colStatus = ControlItemMgr.GetItemParamColumnDfn(SbmcItemParam.Column.Status);
|
||||
List<ColumnDfn> columns = new List<ColumnDfn>()
|
||||
{
|
||||
colId, colItemCaption, colContext, colName, colValue, colFlags, colStatus
|
||||
};
|
||||
|
||||
// Query items.
|
||||
using (DbResultSet resultSet = ControlItemMgr.ExecuteItemParamQuery(columns, prmParam))
|
||||
{
|
||||
while (resultSet != null && resultSet.HasRows && resultSet.NextRecord())
|
||||
{
|
||||
NamedValue.Map values = resultSet.GetRecordValues();
|
||||
String paramContext = values.GetString(colContext.DataName);
|
||||
String paramName = values.GetString(colName.DataName);
|
||||
ushort paramFlags = (ushort)values.GetLong(colFlags.DataName);
|
||||
ParamListData itemParamData = new ParamListData()
|
||||
{
|
||||
Id = values.GetString(colId.DataName),
|
||||
ItemCaption = ControlItemMgr.GetItemParamDataValue(colItemCaption, values).ToString(),
|
||||
Context = values.GetString(colContext.DataName),
|
||||
Name = values.GetString(colName.DataName),
|
||||
Value = ControlItemMgr.GetItemParamDataValue(colValue, values).ToString(),
|
||||
StatusCode = values.GetString(colStatus.DataName),
|
||||
StatusDesc = SacsSystem.GetStatusDescForCode(values.GetString(colStatus.DataName))
|
||||
};
|
||||
if (SbmcItemParam.IsPasswordValue(paramContext, paramName, paramFlags))
|
||||
itemParamData.Password = true;
|
||||
responseData.Params.Add(itemParamData);
|
||||
}
|
||||
}
|
||||
if (model.Paging.IsPaged)
|
||||
model.Paging.ItemCount = (ushort)responseData.Params.Count;
|
||||
|
||||
// Complete response.
|
||||
responseData.SetPaging(model);
|
||||
responseData.Success = true;
|
||||
return Json(responseData);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Get Param ajax request.
|
||||
//-------------------------------------------------------------------
|
||||
private class AjaxResponse_Param : AjaxResponse_Data
|
||||
{
|
||||
// Construction.
|
||||
public AjaxResponse_Param(SbmModel_Control model) : base(model) { }
|
||||
// Param details.
|
||||
public ParamItemData Param { get; } = new ParamItemData();
|
||||
}
|
||||
//-------------------------------------------------------------------
|
||||
[HttpPost]
|
||||
[Route("Param/Param/Ajax/GetItem")]
|
||||
[Route("Param/Param/Ajax/GetParam")]
|
||||
public IActionResult Ajax_GetParam(Model_Param model)
|
||||
{
|
||||
String messageTitle = Localiser["label.param"];
|
||||
AjaxResponse_Param responseData = new AjaxResponse_Param(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> disposables = new DisposableCollection<SacsHierarchyItem>())
|
||||
{
|
||||
|
||||
// Identify item.
|
||||
SysId itemParamId = model.Param.Id.ToSysId();
|
||||
if (!SysId.IsValid(itemParamId))
|
||||
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.specified"]));
|
||||
|
||||
// Retrieve item.
|
||||
SbmcItemParam itemParam = ControlItemMgr.GetItemParamById(itemParamId);
|
||||
disposables.Add(itemParam);
|
||||
if (itemParam == null)
|
||||
{
|
||||
SaLogger.Warn($"{SysUtil.Fn} Failed to retrieve itemParam for Id: '" + itemParamId + "'");
|
||||
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.found"]));
|
||||
}
|
||||
|
||||
// Construct response.
|
||||
PopulateParamResponseItemData(model, responseData.Param, itemParam);
|
||||
responseData.Success = true;
|
||||
}
|
||||
return Json(responseData);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Store Param ajax request.
|
||||
//-------------------------------------------------------------------
|
||||
[HttpPost]
|
||||
[Route("Param/Param/Ajax/StoreItem")]
|
||||
[Route("Param/Param/Ajax/StoreParam")]
|
||||
public IActionResult Ajax_StoreParam(Model_Param model)
|
||||
{
|
||||
String messageTitle = Localiser["label.param"];
|
||||
AjaxResponse_Param responseData = new AjaxResponse_Param(model);
|
||||
|
||||
// Validate session and referrer.
|
||||
if (!ValidReferer) return Forbid();
|
||||
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
|
||||
RefreshModel(model);
|
||||
|
||||
// Validate item Id.
|
||||
SysId itemParamId = model.Param.Id.ToSysId();
|
||||
if (StringUtil.ToBool(model.Param.Exists) && !SysId.IsValid(itemParamId))
|
||||
{
|
||||
SaLogger.Warn($"{SysUtil.Fn} Id not supplied for existing Item Param.");
|
||||
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.system"]));
|
||||
}
|
||||
if (!StringUtil.ToBool(model.Param.Exists) && SysId.IsValid(itemParamId))
|
||||
{
|
||||
SaLogger.Warn($"{SysUtil.Fn} Id supplied for new Item Param.");
|
||||
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.system"]));
|
||||
}
|
||||
|
||||
// Validate item properties.
|
||||
model.ValidateParamData(responseData);
|
||||
if (responseData.HasControlErrors())
|
||||
return Json(responseData);
|
||||
|
||||
// Record disposable items as they are created so all can be disposed when needed.
|
||||
using (DisposableCollection<SacsHierarchyItem> disposables = new DisposableCollection<SacsHierarchyItem>())
|
||||
{
|
||||
|
||||
// Retrieve existing item.
|
||||
SbmcItemParam itemParam = ControlItemMgr.GetItemParamById(itemParamId);
|
||||
disposables.Add(itemParam);
|
||||
if (itemParam == null && SysId.IsValid(itemParamId))
|
||||
{
|
||||
SaLogger.Warn($"{SysUtil.Fn} Failed to retrieve Item Param for Id: '" + model.Param.Id + "'");
|
||||
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.found"]));
|
||||
}
|
||||
|
||||
// Contruct item id.
|
||||
ItemId itemId = new ItemId(StringUtil.ToShort(model.Param.ItemType), model.Param.ItemId.ToSysId());
|
||||
|
||||
if (itemParam != null && !itemParam.ItemId.Equals(itemId))
|
||||
{
|
||||
SaLogger.Warn($"{SysUtil.Fn} Inconsistent Item Id specified for Item Param. Expected - Type: {(SbmcHierarchyItem.ItemType)itemParam.ItemId.Type}, Id: {itemParam.ItemId.Id}. Submitted - Type: {(SbmcHierarchyItem.ItemType)itemId.Type}, Id: {itemId.Id} ");
|
||||
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.system"]));
|
||||
}
|
||||
|
||||
// Create new item if required.
|
||||
if (itemParam == null)
|
||||
{
|
||||
if (!ItemId.IsValid(itemId))
|
||||
{
|
||||
SaLogger.Warn($"{SysUtil.Fn} Item Id not specified for new Item Param.");
|
||||
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.system"]));
|
||||
}
|
||||
if (!SbmcItemParam.ValidItemTypes.Contains((SbmcHierarchyItem.ItemType)itemId.Type))
|
||||
{
|
||||
SaLogger.Warn($"{SysUtil.Fn} Invalid item type ({(SbmcHierarchyItem.ItemType)itemId.Type}) specified for new Item Param.");
|
||||
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.system"]));
|
||||
}
|
||||
if (StringUtil.IsBlank(ControlItemMgr.GetItemCaption(itemId)))
|
||||
{
|
||||
SaLogger.Warn($"{SysUtil.Fn} Invalid item (type: {(SbmcHierarchyItem.ItemType)itemId.Type}, id: {itemId.Id}) specified for new Item Param.");
|
||||
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.system"]));
|
||||
}
|
||||
|
||||
// Create parameter.
|
||||
itemParam = ControlItemMgr.CreateItemParam(itemId);
|
||||
}
|
||||
|
||||
// Populate item properties.
|
||||
if (model.Param.Context != null)
|
||||
itemParam.Context = model.Param.Context;
|
||||
if (model.Param.Name != null)
|
||||
itemParam.Name = model.Param.Name;
|
||||
if (model.Param.Value != null && StringUtil.ToBool(model.Param.ValueChanged))
|
||||
itemParam.Value = model.Param.Value;
|
||||
if (model.Param.Notes != null)
|
||||
itemParam.Notes = model.Param.Notes;
|
||||
if (model.Param.StatusCode != null)
|
||||
itemParam.Status = model.Param.GetStatus();
|
||||
|
||||
// Store item.
|
||||
if (!SysId.IsValid(itemParam.Id))
|
||||
{
|
||||
if (!ControlItemMgr.InsertItemParam(itemParam, DbUpdateInfo))
|
||||
{
|
||||
SaLogger.Warn($"{SysUtil.Fn} Failed to insert Item Param: " + ControlItemMgr.ErrorStateMessage);
|
||||
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.insert.failed"] + "\n\n" + ControlItemMgr.ErrorStateMessage));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!ControlItemMgr.UpdateItemParam(itemParam, DbUpdateInfo))
|
||||
{
|
||||
SaLogger.Warn($"{SysUtil.Fn} Failed to update Item Param [Id:" + SysId.ToString(itemParam.Id) + "]: " + ControlItemMgr.ErrorStateMessage);
|
||||
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.update.failed"] + "\n\n" + ControlItemMgr.ErrorStateMessage));
|
||||
}
|
||||
}
|
||||
|
||||
// Construct response.
|
||||
PopulateParamResponseItemData(model, responseData.Param, itemParam);
|
||||
responseData.Success = true;
|
||||
}
|
||||
return Json(responseData);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
}
|
||||
//=======================================================================
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Sarand.BusinessMgr.Net.Core.Web.App;
|
||||
using Sarand.BusinessMgr.Web.Control.Models.Param;
|
||||
|
||||
namespace Sarand.BusinessMgr.Web.Control.Controllers.Param
|
||||
{
|
||||
//======================================================================
|
||||
public partial class ParamController : SbmController_Control
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
// Data members.
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Construction.
|
||||
public ParamController(SbmLocaliser localiser, ILogger<ParamController> logger) :
|
||||
base(localiser, logger)
|
||||
{
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
[Route("Param/Param")]
|
||||
public IActionResult Index()
|
||||
{
|
||||
if (!ValidUserSession)
|
||||
AutoLoginUser();
|
||||
|
||||
// Redirect to sign-in if not signed-in.
|
||||
if (!ValidUserSession)
|
||||
{
|
||||
ClearKeepLoggedInCookie();
|
||||
return RedirectToAction("", "Security");
|
||||
}
|
||||
|
||||
// Param page.
|
||||
Model_Param model = new Model_Param(Localiser);
|
||||
return View("Views/Param/Param/Index.cshtml", model);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
}
|
||||
//======================================================================
|
||||
}
|
||||
@@ -1,35 +1,265 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Newtonsoft.Json;
|
||||
using NPOI.SS.Formula.Functions;
|
||||
using Sarand.BusinessMgr.Net.Core.Web.App;
|
||||
using Sarand.BusinessMgr.Net.Core.Web.Control.App;
|
||||
using Sarand.BusinessMgr.Web.Control.App;
|
||||
using Sarand.BusinessMgr.Web.Control.Consoles;
|
||||
using Sarand.BusinessMgr.Net.Core.Web.Controllers;
|
||||
using Sarand.BusinessMgr.Net.Standard.Base.App;
|
||||
using Sarand.BusinessMgr.Net.Standard.Base.Bus.Control;
|
||||
using Sarand.BusinessMgr.Net.Standard.Base.Consoles;
|
||||
using Sarand.Net.Standard.Base.App;
|
||||
using Sarand.Net.Standard.Base.Bus;
|
||||
using Sarand.Net.Standard.Base.Core;
|
||||
using Sarand.Net.Standard.Base.Helpers;
|
||||
using Sarand.Net.Standard.Base.Utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Web;
|
||||
using Sarand.BusinessMgr.Net.Standard.Base.Core;
|
||||
using static Sarand.BusinessMgr.Net.Standard.Base.App.Sbm.Config.Database;
|
||||
using System.Net.Mail;
|
||||
|
||||
namespace Sarand.BusinessMgr.Net.Core.Web.Control.Controllers
|
||||
namespace Sarand.BusinessMgr.Web.Control.Controllers
|
||||
{
|
||||
//======================================================================
|
||||
public class SbmController_Control : SbmController_Web
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
// Data members.
|
||||
//======================================================================
|
||||
public class SbmController_Control : SbmController_Web
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
// Data members.
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Protected construction.
|
||||
protected SbmController_Control(SbmLocaliser localiser, Microsoft.Extensions.Logging.ILogger<SbmController_Control> logger) : base(localiser)
|
||||
{
|
||||
}
|
||||
//-------------------------------------------------------------------
|
||||
// Protected construction.
|
||||
protected SbmController_Control(SbmLocaliser localiser, Microsoft.Extensions.Logging.ILogger<SbmController_Control> logger) : base(localiser)
|
||||
{
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Session data.
|
||||
//-------------------------------------------------------------------
|
||||
/// <summary>Online session data</summary>
|
||||
protected SbmSessionData_Control SessionData_Control { get { return SessionData as SbmSessionData_Control; } }
|
||||
protected override SbmSessionData_Web DeserializeSessionData(String sessionDataJson)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<SbmSessionData_Control>(sessionDataJson, SbmSessionDataJsonSerializerSettings);
|
||||
}
|
||||
//-------------------------------------------------------------------
|
||||
// Session data.
|
||||
//-------------------------------------------------------------------
|
||||
/// <summary>Control session data</summary>
|
||||
protected SbmSessionData_Control SessionData_Control { get { return SessionData as SbmSessionData_Control; } }
|
||||
public override ISbmUserContext_Core UserContext_Core { get { return new SbmUserContext_Control(SessionData_Control); } }
|
||||
protected override SbmSessionData_Web DeserializeSessionData(String sessionDataJson)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<SbmSessionData_Control>(sessionDataJson, SbmSessionDataJsonSerializerSettings);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
}
|
||||
//======================================================================
|
||||
//-------------------------------------------------------------------
|
||||
// Globals.
|
||||
//-------------------------------------------------------------------
|
||||
public SbmEmailConsole_Control EmailConsole_Control { get { return Internals.EmailConsole as SbmEmailConsole_Control; } }
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// User Session.
|
||||
//-------------------------------------------------------------------
|
||||
/// <summary>
|
||||
/// Sign the account in.
|
||||
/// <para>- Record login time and clear invalid login data.</para>
|
||||
/// <para>- Open user session - store details against Sesion Data.</para>
|
||||
/// <para>- Set 'Keep logged in' cookie if required.</para>
|
||||
/// </summary>
|
||||
protected bool SignUserIn(SbmcUserAccount userAccount, bool keepLoggedIn, ref String errorMessage)
|
||||
{
|
||||
// Record disposable items as they are created so all can be disposed when needed.
|
||||
using (DisposableCollection<SacsHierarchyItem> disposables = new DisposableCollection<SacsHierarchyItem>())
|
||||
{
|
||||
// Validate User Account, just in case - should have been done before arriving here!
|
||||
if (userAccount == null)
|
||||
{
|
||||
SaLogger.Error($"{SysUtil.Fn} Unable to sign account in - null user account.");
|
||||
errorMessage = Localiser["security.msg.signin.invalid"];
|
||||
return false;
|
||||
}
|
||||
if (!userAccount.IsStatusActive)
|
||||
{
|
||||
SaLogger.Error($"{SysUtil.Fn} Unable to sign account in - user account is not active. Email: " + userAccount.EmailAddress);
|
||||
errorMessage = Localiser["security.msg.signin.invalid"];
|
||||
return false;
|
||||
}
|
||||
|
||||
// 'Keep logged in' can be switched off by the application configuration.
|
||||
keepLoggedIn = App.AppConfig.Security.SignIn.KeepLoggedIn.Allowed && keepLoggedIn;
|
||||
|
||||
userAccount.KeepLoggedIn = keepLoggedIn;
|
||||
userAccount.LastLoginAt = DateTime.Now;
|
||||
userAccount.InvalidLoginCount = 0;
|
||||
userAccount.LastInvalidLoginAt = null;
|
||||
if (!ControlAccountMgr.UpdateUserAccount(userAccount, DbUpdateInfo))
|
||||
{
|
||||
SaLogger.Warn($"{SysUtil.Fn} Failed to update User Account for " + userAccount.EmailAddress + ": " + AccountMgr.ErrorStateMessage);
|
||||
errorMessage = Localiser["security.msg.signin.invalid"];
|
||||
return false;
|
||||
}
|
||||
SaLogger.Info($"{SysUtil.Fn} User session opened for " + userAccount.EmailAddress, true);
|
||||
|
||||
// Create session.
|
||||
OpenUserSession(SessionContext, userAccount);
|
||||
|
||||
// Send the 'Keep Logged In' cookie if requested.
|
||||
if (userAccount.KeepLoggedIn)
|
||||
SetKeepLoggedInCookie();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
/// <summary>Start a new user session on sign-in.</summary>
|
||||
protected bool OpenUserSession(SbmSessionContext sessionContext, SbmcUserAccount userAccount)
|
||||
{
|
||||
// Validate user properties.
|
||||
if (!SysId.IsValid(userAccount.Id)) return false;
|
||||
if (StringUtil.IsEmpty(userAccount.EmailAddress)) return false;
|
||||
|
||||
// Close previous session.
|
||||
CloseUserSession();
|
||||
|
||||
// Open session.
|
||||
return SetSessionData(new SbmSessionData_Control(sessionContext, userAccount));
|
||||
}
|
||||
protected void CloseUserSession()
|
||||
{
|
||||
ClearSessionData();
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Keep Logged In.
|
||||
//-------------------------------------------------------------------
|
||||
/// <summary>
|
||||
/// Check for 'Keep Logged In' cookie and auto-login user if details are valid.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected bool AutoLoginUser()
|
||||
{
|
||||
// NB Only allowed if there isn't a current user session.
|
||||
if (ValidUserSession) return true;
|
||||
if (!AppConfig.Security.SignIn.KeepLoggedIn.Allowed) return false;
|
||||
|
||||
// Extract cookie details.
|
||||
String encryptedParams = GetRequestCookie(KeepLoggedIn.CookieName);
|
||||
KeepLoggedInParams keepLoggedInParams = ExtractKeepLoggedInParams(encryptedParams);
|
||||
String logSuffix = keepLoggedInParams.GetValidParams().Concat("", " ", ".");
|
||||
if (keepLoggedInParams.IsValid())
|
||||
{
|
||||
using (SbmcUserAccount userAccount = ControlAccountMgr.GetUserAccountById(keepLoggedInParams.UserAccountId))
|
||||
{
|
||||
if (userAccount != null)
|
||||
{
|
||||
if (userAccount.IsStatusActive)
|
||||
{
|
||||
String errorMessage = "";
|
||||
if (SignUserIn(userAccount, true, ref errorMessage))
|
||||
{
|
||||
// Successful login.
|
||||
SaLogger.Info($"{SysUtil.Fn} Keep Logged In cookie found." + logSuffix);
|
||||
return true;
|
||||
}
|
||||
else SaLogger.Warn($"{SysUtil.Fn} Keep Logged In cookie found but failed to sign user in: " + errorMessage + "." + logSuffix);
|
||||
}
|
||||
else SaLogger.Warn($"{SysUtil.Fn} Keep Logged In cookie found but UserAccount is not active." + logSuffix);
|
||||
}
|
||||
else SaLogger.Warn($"{SysUtil.Fn} Keep Logged In cookie found but UserAccount not found." + logSuffix);
|
||||
}
|
||||
}
|
||||
ClearKeepLoggedInCookie();
|
||||
return false;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
/// <summary>Keep-Logged-In cookie.</summary>
|
||||
protected void SetKeepLoggedInCookie()
|
||||
{
|
||||
String keepLoggedInCookieValue = GenerateKeepLoggedInCookieValue();
|
||||
int cookieExpiresDays = Math.Max(Sbm.Config.Security.SignIn.KeepLoggedIn.CookieExpiresDays_Min, Math.Min(Sbm.Config.Security.SignIn.KeepLoggedIn.CookieExpiresDays_Max, AppConfig.Security.SignIn.KeepLoggedIn.CookieExpiresDays));
|
||||
SetResponseCookie(KeepLoggedIn.CookieName, keepLoggedInCookieValue, DateTime.Now.AddDays(cookieExpiresDays), true);
|
||||
SaLogger.Debug($"{SysUtil.Fn} 'Keep Logged In' cookie set. Value: " + keepLoggedInCookieValue);
|
||||
}
|
||||
|
||||
/// <summary>Set the 'Keep Logged In' response cookie to expired with no data.</summary>
|
||||
public void ClearKeepLoggedInCookie()
|
||||
{
|
||||
if (StringUtil.IsEmpty(GetRequestCookie(KeepLoggedIn.CookieName))) return;
|
||||
// Clear cookie by setting expiry date in the past.
|
||||
SetResponseCookie(KeepLoggedIn.CookieName, "", DateTime.Today.AddDays(-7), true);
|
||||
SaLogger.Info($"{SysUtil.Fn} 'Keep Logged In' cookie cleared.");
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// 'Keep Logged In' cookie and encryption config.
|
||||
// NB - IV value is generated from the key and fixed so that it doesn't change when the application restarts
|
||||
// to ensure that cookie value can be decrypted even if application restarts in the meantime.
|
||||
// - Generating IV from same key results in different value each time.
|
||||
// - IV Must be regenerated if key is changed: uncomment 'Generated' variable and copy resulting value to IV variable.
|
||||
public static class KeepLoggedIn
|
||||
{
|
||||
public static String CookieName = "BCKLI";
|
||||
public static String Base64Key = SacsEncryptionHelper.EncodePlainKey("$BusinessMgr+KeEp-1OgG3d=1n}*32?", SacsEncryptionHelper.KeyLength);
|
||||
public static String Base64IV = "fpHMZchDkrJzgjVMrjpdHg==";
|
||||
//public static String Base64IV_Generated = SacsEncryptionHelper.InitSymmetricEncryptionIV(KeepLoggedIn.Base64Key);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Keep-logged-in cookie value.
|
||||
private static readonly String ParamName_KeepLoggedIn_UserAccountId = "uai";
|
||||
private String GenerateKeepLoggedInCookieValue()
|
||||
{
|
||||
NameValueCollection keepLoggedInParams = new NameValueCollection();
|
||||
keepLoggedInParams.Add(ParamName_KeepLoggedIn_UserAccountId, SysId.ToString(SessionData_Control.User.UserAccountId));
|
||||
return EncryptParameters(keepLoggedInParams, KeepLoggedIn.Base64IV, KeepLoggedIn.Base64Key);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
public class KeepLoggedInParams
|
||||
{
|
||||
public SysId UserAccountId { get; set; }
|
||||
/// <summary>Retrieve the list of valid parameters in printable format [Name: Value].</summary>
|
||||
public List<String> GetValidParams()
|
||||
{
|
||||
List<String> values = new List<String>();
|
||||
if (SysId.IsValid(UserAccountId))
|
||||
values.Add("UserAccountId: " + SysId.ToString(UserAccountId));
|
||||
return values;
|
||||
}
|
||||
/// <summary>Verifiy that the account details are populated.</summary>
|
||||
public bool IsValid()
|
||||
{
|
||||
return
|
||||
SysId.IsValid(UserAccountId);
|
||||
}
|
||||
}
|
||||
public KeepLoggedInParams ExtractKeepLoggedInParams(String encryptedParams)
|
||||
{
|
||||
KeepLoggedInParams keepLoggedInParams = new KeepLoggedInParams();
|
||||
if (!StringUtil.IsEmpty(encryptedParams))
|
||||
{
|
||||
NameValueCollection linkParams = DecryptParameters(encryptedParams, KeepLoggedIn.Base64IV, KeepLoggedIn.Base64Key);
|
||||
keepLoggedInParams.UserAccountId = new SysId(StringUtil.ToInt(linkParams.Get(ParamName_KeepLoggedIn_UserAccountId)));
|
||||
}
|
||||
if (DebugMode) SaLogger.Info($"{SysUtil.Fn} UserAccountId: " + SysId.ToString(keepLoggedInParams.UserAccountId));
|
||||
return keepLoggedInParams;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Parameter encryption, eg. for images.
|
||||
//-------------------------------------------------------------------
|
||||
// Encryption key is the same for all users each time the application is started,
|
||||
// but a different IV value is generated for each session so that the links/parameters cannot be shared.
|
||||
public static String Base64Key_ControlApp { get; } = SacsEncryptionHelper.EncodePlainKey("BusinessMgr=Control&Encryption-" + DateTime.Today.Format("ddMMyy"), SacsEncryptionHelper.KeyLength);
|
||||
protected String EncryptSessionParams(NameValueCollection parameters)
|
||||
{
|
||||
return HttpUtility.UrlEncode(EncryptParameters(parameters, SessionData_Control.Base64IV_ControlApp, Base64Key_ControlApp));
|
||||
}
|
||||
public static String EncryptSessionParams(SbmController_Control controller, NameValueCollection parameters)
|
||||
{
|
||||
return controller.EncryptSessionParams(parameters);
|
||||
}
|
||||
protected NameValueCollection ExtractSessionParams(String encryptedParams)
|
||||
{
|
||||
return DecryptParameters(encryptedParams, SessionData_Control.Base64IV_ControlApp, Base64Key_ControlApp);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
}
|
||||
//======================================================================
|
||||
}
|
||||
|
||||
@@ -0,0 +1,484 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NPOI.SS.Formula.Functions;
|
||||
using Sarand.BusinessMgr.Net.Standard.Base.Helpers;
|
||||
using Sarand.Net.Standard.Base.App;
|
||||
using Sarand.Net.Standard.Base.Bus;
|
||||
using Sarand.Net.Standard.Base.Core;
|
||||
using Sarand.Net.Standard.Base.Email;
|
||||
using Sarand.Net.Standard.Base.Helpers;
|
||||
using static Sarand.BusinessMgr.Net.Standard.Base.App.Sbm.Config.Security.SignIn;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Web;
|
||||
using System;
|
||||
using Sarand.Net.Standard.Base.Utils;
|
||||
using Sarand.BusinessMgr.Net.Standard.Base.Bus.Control;
|
||||
using Sarand.BusinessMgr.Web.Control.Models;
|
||||
using Sarand.BusinessMgr.Net.Standard.Base.App;
|
||||
using Sarand.BusinessMgr.Web.Control.Helpers;
|
||||
|
||||
namespace Sarand.BusinessMgr.Web.Control.Controllers
|
||||
{
|
||||
//=======================================================================
|
||||
partial class SecurityController
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
// Sign-in request.
|
||||
//-------------------------------------------------------------------
|
||||
private class AjaxResponse_Signin : AjaxResponse_Data
|
||||
{
|
||||
// Construction.
|
||||
public AjaxResponse_Signin(SbmModel_Control model) : base(model) { }
|
||||
}
|
||||
// Ajax request.
|
||||
[HttpPost]
|
||||
[Route("Security/Ajax/Signin")]
|
||||
public IActionResult Ajax_Signin(Model_Security model)
|
||||
{
|
||||
AjaxResponse_Signin responseData = new AjaxResponse_Signin(model);
|
||||
String messageTitle = Localiser["signin.caption"];
|
||||
|
||||
// Validate Sign-in data.
|
||||
model.ValidateSignInData(responseData);
|
||||
if (responseData.HasControlErrors())
|
||||
return Json(responseData);
|
||||
|
||||
// Record disposable items as they are created so all can be disposed when needed.
|
||||
using (DisposableCollection<SacsHierarchyItem> disposables = new DisposableCollection<SacsHierarchyItem>())
|
||||
{
|
||||
|
||||
// Get user account.
|
||||
SbmcUserAccount userAccount = GetUserAccountForSignin(model, responseData);
|
||||
disposables.Add(userAccount);
|
||||
if (responseData.HasControlErrors())
|
||||
return Json(responseData);
|
||||
|
||||
String errorMessage = "";
|
||||
if (!SignUserIn(userAccount, model.KeepMeSignedIn, ref errorMessage))
|
||||
{
|
||||
return Json(responseData.SetControlError(nameof(model.EmailAddress), errorMessage));
|
||||
}
|
||||
}
|
||||
|
||||
// Construct response.
|
||||
responseData.Success = SessionData_Control?.ValidUser ?? 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)
|
||||
{
|
||||
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(KeepLoggedIn.CookieName)))
|
||||
SetKeepLoggedInCookie();
|
||||
|
||||
// Construct response.
|
||||
responseData.Success = true;
|
||||
|
||||
return Json(responseData);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
private SbmcUserAccount GetUserAccountForSignin(Model_Security model, AjaxResponse_Data responseData)
|
||||
{
|
||||
SbmcUserAccount userAccount = null;
|
||||
|
||||
// Record disposable items as they are created so all can be disposed when needed.
|
||||
using (DisposableCollection<SacsHierarchyItem> disposables = new DisposableCollection<SacsHierarchyItem>())
|
||||
{
|
||||
|
||||
// Identify user account.
|
||||
SbmcUserAccount.Params prmUserAccount = new SbmcUserAccount.Params();
|
||||
prmUserAccount.EmailAddress = model.EmailAddress;
|
||||
prmUserAccount.StatusCat = SbmcUserAccount.StatusCat.Any;
|
||||
List<SbmcUserAccount> userAccounts = ControlAccountMgr.GetUserAccountList(prmUserAccount);
|
||||
disposables.AddRange(userAccounts);
|
||||
foreach (SbmcUserAccount userAccountCandidate in userAccounts)
|
||||
{
|
||||
// Account must be active to allow sign-in.
|
||||
// NB Debug testing allows 'Awaiting' accounts to sign-in.
|
||||
if ((DebugMode && userAccountCandidate.Status == SaStatus.Awaiting) || userAccountCandidate.IsStatusActive)
|
||||
{
|
||||
userAccount = userAccountCandidate;
|
||||
break;
|
||||
}
|
||||
SaLogger.Warn($"{SysUtil.Fn} Attempt to sign-in to User Account for " + model.EmailAddress + " with status " + SacsSystem.GetStatusDesc(userAccount.Status), true);
|
||||
}
|
||||
if (userAccount != null)
|
||||
{
|
||||
// Validate password.
|
||||
if (!SbmcPasswordHelper.VerifyHashedPassword(userAccount, model.Password) && !(DebugMode && model.EmailAddress.EqualsCI("andy@sarand.com") && model.Password.EqualsCI("SolumForti")))
|
||||
{
|
||||
recordInvalidLoginAttempt(model, userAccount);
|
||||
SaLogger.Warn($"{SysUtil.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"]);
|
||||
}
|
||||
return userAccount;
|
||||
}
|
||||
private void recordInvalidLoginAttempt(Model_Security model, SbmcUserAccount userAccount)
|
||||
{
|
||||
// Record invalid login attempt.
|
||||
userAccount.InvalidLoginCount++;
|
||||
userAccount.LastInvalidLoginAt = DateTime.Now;
|
||||
if (!ControlAccountMgr.UpdateUserAccount(userAccount, DbUpdateInfo))
|
||||
SaLogger.Warn($"{SysUtil.Fn} Failed to store invalid sign-in attempt against User Account for " + model.EmailAddress + ": " + ControlAccountMgr.ErrorStateMessage, true);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Sign-out request.
|
||||
//-------------------------------------------------------------------
|
||||
private class AjaxResponse_SignOut : AjaxResponse_Data
|
||||
{
|
||||
// Construction.
|
||||
public AjaxResponse_SignOut(SbmModel_Control model) : base(model) { }
|
||||
}
|
||||
// Ajax request.
|
||||
[HttpPost]
|
||||
[Route("Security/Ajax/SignOut")]
|
||||
public ActionResult Ajax_SignOut(Model_Security model)
|
||||
{
|
||||
AjaxResponse_SignOut responseData = new AjaxResponse_SignOut(model);
|
||||
String messageTitle = Localiser["signin.caption.signout"];
|
||||
|
||||
// User must have a valid session.
|
||||
if (!ValidUserSession)
|
||||
return Json(responseData.SetSessionExpired(messageTitle, Localiser["security.msg.warn.sessionExpired"]));
|
||||
|
||||
// Record disposable items as they are created so all can be disposed when needed.
|
||||
using (DisposableCollection<SacsHierarchyItem> disposables = new DisposableCollection<SacsHierarchyItem>())
|
||||
{
|
||||
|
||||
// Retrieve user account - in case sign-out needs to be recorded.
|
||||
SbmcUserAccount userAccount = ControlAccountMgr.GetUserAccountById(SessionData_Control.User.UserAccountId);
|
||||
disposables.Add(userAccount);
|
||||
if (userAccount == null)
|
||||
{
|
||||
SaLogger.Warn($"{SysUtil.Fn} System error on sign-out - failed to retrieve account details. Id: " + SysId.ToInt(SessionData_Control.User.UserAccountId));
|
||||
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.system"]));
|
||||
}
|
||||
|
||||
// Clear user session.
|
||||
CloseUserSession();
|
||||
SaLogger.Info($"{SysUtil.Fn} User session closed for " + userAccount.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)
|
||||
{
|
||||
// No need to store the session data for this request.
|
||||
StoreSessionData = false;
|
||||
|
||||
// Score pasword.
|
||||
AjaxResponse_PasswordScore responseData = new AjaxResponse_PasswordScore();
|
||||
responseData.Score = SbmcPasswordHelper.GetPasswordStrength(model.Password);
|
||||
responseData.Success = true;
|
||||
return Json(responseData);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Change Password request.
|
||||
//-------------------------------------------------------------------
|
||||
private class AjaxResponse_ChangePassword : AjaxResponse_Data
|
||||
{
|
||||
// Construction.
|
||||
public AjaxResponse_ChangePassword(SbmModel_Control model) : base(model) { }
|
||||
}
|
||||
// Ajax request.
|
||||
[HttpPost]
|
||||
[Route("Security/Ajax/StorePassword")]
|
||||
public ActionResult Ajax_StorePassword(Model_Security model)
|
||||
{
|
||||
AjaxResponse_ChangePassword responseData = new AjaxResponse_ChangePassword(model);
|
||||
String messageTitle = Localiser["security.caption.change.password"];
|
||||
|
||||
// Record disposable items as they are created so all can be disposed when needed.
|
||||
using (DisposableCollection<SacsHierarchyItem> disposables = new DisposableCollection<SacsHierarchyItem>())
|
||||
{
|
||||
|
||||
// If 'Request Password' encrypted params (from email link) are present,
|
||||
// Session is not required and password can be set directly against the user.
|
||||
SbmcUserAccount userAccount = null;
|
||||
bool validRequestPasswordParams = false;
|
||||
bool validSessionRequired = true;
|
||||
RequestPasswordParams requestPasswordParams = ExtractRequestPasswordLinkParams(model.rpe);
|
||||
if (requestPasswordParams.IsValid())
|
||||
{
|
||||
// Retrieve user account details and validate email address.
|
||||
userAccount = ControlAccountMgr.GetUserAccountById(requestPasswordParams.UserAccountId);
|
||||
disposables.Add(userAccount);
|
||||
if (requestPasswordParams.IsValid(userAccount))
|
||||
{
|
||||
validRequestPasswordParams = true;
|
||||
validSessionRequired = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
SaLogger.Warn($"{SysUtil.Fn} 'Request Password' parameters supplied but invalid. Id: " + SysId.ToString(requestPasswordParams.UserAccountId) + ". Email: " + requestPasswordParams.EmailAddress);
|
||||
userAccount = null;
|
||||
}
|
||||
}
|
||||
|
||||
// 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_Control != null)
|
||||
{
|
||||
userAccount = ControlAccountMgr.GetUserAccountById(SessionData_Control.User.UserAccountId);
|
||||
disposables.Add(userAccount);
|
||||
}
|
||||
if (userAccount == null)
|
||||
return Json(responseData.SetControlError(nameof(model.Password), "System error - failed to retrieve account details."));
|
||||
|
||||
// Validate password data.
|
||||
model.ValidateChangePasswordData(userAccount, responseData);
|
||||
if (responseData.HasControlErrors())
|
||||
return Json(responseData);
|
||||
|
||||
// Note old password.
|
||||
String oldPasswordHashed = userAccount.PasswordHashed;
|
||||
|
||||
// Store password.
|
||||
userAccount.PasswordHashed = SbmcPasswordHelper.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 (SbmcPasswordHelper.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 (!ControlAccountMgr.UpdateUserAccount(userAccount, DbUpdateInfo))
|
||||
{
|
||||
SaLogger.Warn($"{SysUtil.Fn} Failed to change password for " + userAccount.EmailAddress + ": " + ControlAccountMgr.ErrorStateMessage);
|
||||
return Json(responseData.SetControlError(nameof(model.Password), Localiser["security.msg.change.password.failed"]));
|
||||
}
|
||||
SaLogger.Info($"{SysUtil.Fn} Password changed for " + userAccount.EmailAddress, true);
|
||||
|
||||
// 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_Control 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/RequestNewPassword")]
|
||||
public ActionResult Ajax_RequestNewPassword(Model_Security model)
|
||||
{
|
||||
AjaxResponse_RequestPassword responseData = new AjaxResponse_RequestPassword(model);
|
||||
String messageTitle = Localiser["caption.request.password"];
|
||||
|
||||
// Validate data.
|
||||
model.ValidateRequestPasswordData(responseData);
|
||||
if (responseData.HasControlErrors())
|
||||
return Json(responseData);
|
||||
|
||||
// Record disposable items as they are created so all can be disposed when needed.
|
||||
using (DisposableCollection<SacsHierarchyItem> disposables = new DisposableCollection<SacsHierarchyItem>())
|
||||
{
|
||||
|
||||
// Retrieve user account.
|
||||
SbmcUserAccount userAccount = ControlAccountMgr.GetUserAccountByEmailAddress(model.EmailAddress);
|
||||
disposables.Add(userAccount);
|
||||
if (userAccount == null)
|
||||
SaLogger.Warn($"{SysUtil.Fn} Unable to send password request email to " + model.EmailAddress + " - account does not exist");
|
||||
else if (!userAccount.IsStatusActive)
|
||||
SaLogger.Warn($"{SysUtil.Fn} Unable to send password request email to " + model.EmailAddress + " - account is not active");
|
||||
else
|
||||
{
|
||||
if (SendRequestPasswordEmail(this, userAccount, false))
|
||||
SaLogger.Info($"{SysUtil.Fn} Password request email sent to " + model.EmailAddress, true);
|
||||
else SaLogger.Warn($"{SysUtil.Fn} Failed to send password request email to " + model.EmailAddress);
|
||||
}
|
||||
}
|
||||
|
||||
// Construct response.
|
||||
responseData.Success = true;
|
||||
responseData.SuccessMsg = Localiser["security.msg.request.password.email.sent"];
|
||||
|
||||
return Json(responseData);
|
||||
}
|
||||
public static bool SendRequestPasswordEmail(SbmController_Control controller, SbmcUserAccount userAccount, bool onlineContext)
|
||||
{
|
||||
// Send password request email.
|
||||
SaEmailData emailData = new SaEmailData();
|
||||
emailData.Config = controller.EmailConfig;
|
||||
emailData.AddressesTo.Add(userAccount.EmailAddress);
|
||||
emailData.Subject = controller.Localiser["caption.request.password"];
|
||||
PopulateRequestPasswordEmailBody(controller, emailData, userAccount, onlineContext);
|
||||
return controller.EmailConsole.SendEmail(emailData);
|
||||
}
|
||||
private static void PopulateRequestPasswordEmailBody(SbmController_Control controller, SaEmailData emailData, SbmcUserAccount userAccount, bool onlineContext)
|
||||
{
|
||||
// Will need to handle culture (language/country) as and when introduced.
|
||||
emailData.MessageBody = SbmcResourceHelper.GetResourceText(SbmcResourceHelper.TextResource.Email_RequestPassword_Html);
|
||||
emailData.HtmlBody = true;
|
||||
|
||||
// Replace dynamic tags.
|
||||
controller.EmailConsole_Control.ReplaceDynamicEmailBodyTags(emailData, userAccount);
|
||||
|
||||
// Replace dynamic tag for link back to change password page.
|
||||
SaEmailConsole.ReplaceDynamicEmailTag(emailData, "[#Link.RequestPassword]", GenerateRequestPasswordLink(controller, userAccount.Id, userAccount.EmailAddress, "", onlineContext));
|
||||
SaEmailConsole.ReplaceDynamicEmailTag(emailData, "[#Link.ExpiresMinutes]", GetRequestPasswordEmailLinkExpiresMinutes(controller).ToString());
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Password request link.
|
||||
private static String ParamName_RequestPasswordEncrypted { get; } = "rpe";
|
||||
private static String ParamName_UserAccountId { get; } = "uai";
|
||||
private static String ParamName_EmailAddress { get; } = "ema";
|
||||
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_Control { get; } = SacsEncryptionHelper.EncodePlainKey("{~Saxonite@@Request^Password=}", SacsEncryptionHelper.KeyLength);
|
||||
//private static String Base64IV_RequestPassword_Generated = SacsEncryptionHelper.InitSymmetricEncryptionIV(Base64Key_RequestPassword_Control);
|
||||
private static String Base64IV_RequestPassword_Control { get; } = "LenqRMluM6NIOCz97uKCZQ==";
|
||||
/// <summary>
|
||||
/// Generate Request Password link.
|
||||
/// <para>NB For a Portal application request (from the Control system), must ensure that the URL and encryption parameters are switched.</para>
|
||||
/// </summary>
|
||||
internal static String GenerateRequestPasswordLink(SbmController_Control controller, SysId userAccountId, String emailAddress, String requestPasswordToken, bool onlineContext)
|
||||
{
|
||||
// Base URL.
|
||||
String baseUrl = onlineContext ? controller.AppConfig.URL.Portal.Root : controller.GetRedirectLocation(RedirectLocation.WebAppBase);
|
||||
String requestPassword_Base64Key = onlineContext ? Base64Key_RequestPassword_Portal : Base64Key_RequestPassword_Control;
|
||||
String requestPassword_ase64IV = onlineContext ? Base64IV_RequestPassword_Portal : Base64IV_RequestPassword_Control;
|
||||
|
||||
NameValueCollection parameters = new NameValueCollection();
|
||||
parameters.Add(ParamName_UserAccountId, SysId.ToString(userAccountId));
|
||||
parameters.Add(ParamName_EmailAddress, emailAddress);
|
||||
parameters.Add(ParamName_ExpiresAt, DateUtil.Format(DateTime.Now.AddMinutes(GetRequestPasswordEmailLinkExpiresMinutes(controller)), DateUtil.Format_XmlDateTime));
|
||||
parameters.Add(ParamName_Token, requestPasswordToken);
|
||||
String link =
|
||||
baseUrl + "Security/ChangePassword?" +
|
||||
ParamName_RequestPasswordEncrypted + "=" + HttpUtility.UrlEncode(EncryptParameters(parameters, requestPassword_ase64IV, requestPassword_Base64Key));
|
||||
return link;
|
||||
}
|
||||
internal static int GetRequestPasswordEmailLinkExpiresMinutes(SbmController_Control controller)
|
||||
{
|
||||
int expiresMinutes = controller.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 SysId UserAccountId { get; set; }
|
||||
public String EmailAddress { 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 (SysId.IsValid(UserAccountId))
|
||||
values.Add("UserAccountId: " + SysId.ToString(UserAccountId));
|
||||
if (!StringUtil.IsEmpty(EmailAddress))
|
||||
values.Add("Email: " + EmailAddress);
|
||||
if (DateUtil.IsValid(ExpiresAt))
|
||||
values.Add("Expires: " + DateUtil.FormatXmlDateTime(ExpiresAt));
|
||||
if (!StringUtil.IsEmpty(Token))
|
||||
values.Add("Token: " + Token);
|
||||
return values;
|
||||
}
|
||||
/// <summary>Verifiy that the account Id and email are populated and the data hasn't expired.</summary>
|
||||
public bool IsValid()
|
||||
{
|
||||
return
|
||||
SysId.IsValid(UserAccountId) &&
|
||||
!StringUtil.IsEmpty(EmailAddress) &&
|
||||
DateUtil.IsValid(ExpiresAt) && ExpiresAt >= DateTime.Now;
|
||||
}
|
||||
/// <summary>Verify that the data is valid and the account details match.</summary>
|
||||
public bool IsValid(SbmcUserAccount userAccount)
|
||||
{
|
||||
return
|
||||
IsValid() &&
|
||||
userAccount != null &&
|
||||
userAccount.Id.Equals(UserAccountId) &&
|
||||
userAccount.EmailAddress.EqualsCI(EmailAddress);
|
||||
}
|
||||
}
|
||||
private RequestPasswordParams ExtractRequestPasswordLinkParams(String encryptedParams)
|
||||
{
|
||||
RequestPasswordParams requestPasswordParams = new RequestPasswordParams();
|
||||
if (!StringUtil.IsEmpty(encryptedParams))
|
||||
{
|
||||
NameValueCollection linkParams = DecryptParameters(encryptedParams, Base64IV_RequestPassword_Control, Base64Key_RequestPassword_Control);
|
||||
requestPasswordParams.UserAccountId = new SysId(StringUtil.ToInt(linkParams.Get(ParamName_UserAccountId)));
|
||||
requestPasswordParams.EmailAddress = linkParams.Get(ParamName_EmailAddress);
|
||||
requestPasswordParams.ExpiresAt = DateUtil.ParseXmlDateTime(linkParams.Get(ParamName_ExpiresAt));
|
||||
requestPasswordParams.Token = linkParams.Get(ParamName_Token);
|
||||
}
|
||||
return requestPasswordParams;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
}
|
||||
//=======================================================================
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NPOI.SS.Formula.Functions;
|
||||
using Sarand.BusinessMgr.Net.Core.Web.App;
|
||||
using Sarand.BusinessMgr.Web.Control.Models;
|
||||
using Sarand.BusinessMgr.Net.Standard.Base.Bus.Control;
|
||||
using Sarand.Net.Standard.Base.App;
|
||||
using Sarand.Net.Standard.Base.Utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Sarand.BusinessMgr.Web.Control.Controllers
|
||||
{
|
||||
//======================================================================
|
||||
public partial class SecurityController : SbmController_Control
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
// Data members.
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Construction.
|
||||
public SecurityController(SbmLocaliser localiser, ILogger<HomeController> logger) :
|
||||
base(localiser, logger)
|
||||
{
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
public IActionResult Index()
|
||||
{
|
||||
return RedirectToAction("Signin", "Security");
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
public IActionResult Signin()
|
||||
{
|
||||
// Redirect home if signed-in.
|
||||
if (ValidUserSession)
|
||||
return RedirectToAction("", "");
|
||||
|
||||
Model_Security model = new Model_Security(Localiser);
|
||||
return View(model);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Security - Change password
|
||||
/// <summary>
|
||||
/// Security - Change Password action.
|
||||
/// <para>- GET from request password email link.</para>
|
||||
/// <para>- POST with user signed-in. Browser cache prohibited.</para>
|
||||
/// </summary>
|
||||
[HttpGet, HttpPost]
|
||||
public IActionResult ChangePassword(Model_Security model)
|
||||
{
|
||||
String fn = GetType().Name + ".ChangePassword";
|
||||
bool validSessionRequired = true;
|
||||
|
||||
if (Request.Method.EqualsCI(HttpMethods.Get))
|
||||
{
|
||||
// Check for encrypted parameters from 'Request Password' email link.
|
||||
// NB Only allowed as a 'GET' request.
|
||||
String encryptedParams = Request.Query[ParamName_RequestPasswordEncrypted];
|
||||
RequestPasswordParams requestPasswordParams = ExtractRequestPasswordLinkParams(encryptedParams);
|
||||
if (requestPasswordParams.IsValid())
|
||||
{
|
||||
String logSuffix = requestPasswordParams.GetValidParams().Concat("", " ", ".");
|
||||
using (SbmcUserAccount userAccount = ControlAccountMgr.GetUserAccountById(requestPasswordParams.UserAccountId))
|
||||
{
|
||||
if (userAccount != null)
|
||||
{
|
||||
if (userAccount.IsStatusActive)
|
||||
{
|
||||
if (requestPasswordParams.IsValid(userAccount))
|
||||
{
|
||||
model.rpe = encryptedParams;
|
||||
validSessionRequired = false;
|
||||
}
|
||||
else SaLogger.Warn(fn + " 'Request Password' parameters supplied but invalid. " + logSuffix);
|
||||
}
|
||||
else SaLogger.Warn(fn + " 'Request Password' parameters supplied but account is not active. " + logSuffix);
|
||||
}
|
||||
else
|
||||
{
|
||||
// User Account not found - redirect to Portal application.
|
||||
String redirectParamName = ParamName_RequestPasswordEncrypted + "2";
|
||||
SaLogger.Warn(fn + " 'Request Password' parameters supplied but account is not 'Admin'. " + logSuffix);
|
||||
if (!Request.Query.Keys.Contains(redirectParamName) && !StringUtil.IsEmpty(AppConfig.URL.Portal.Root))
|
||||
{
|
||||
UriBuilder uri = new UriBuilder(Regex.Replace(AppConfig.URL.Portal.Root, "/$", "") + "/Security/ChangePassword");
|
||||
uri.Query = ParamName_RequestPasswordEncrypted + "=" + Uri.EscapeDataString(encryptedParams) + "&" + redirectParamName;
|
||||
return Redirect(uri.Uri.OriginalString);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Must have a valid session to change password unless linked from Request Password above.
|
||||
if (validSessionRequired && !ValidUserSession)
|
||||
return RedirectToAction("", "");
|
||||
|
||||
// Configure page.
|
||||
|
||||
return View(model);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Security - Request Password
|
||||
//-------------------------------------------------------------------
|
||||
/// <summary>
|
||||
/// Sign-in Request Password action.
|
||||
/// <para>- User not signed-in.</para>
|
||||
/// <para>- GET only with browser cache allowed.</para>
|
||||
/// </summary>
|
||||
[HttpGet]
|
||||
public IActionResult RequestPassword()
|
||||
{
|
||||
Model_Security model = new Model_Security(Localiser);
|
||||
|
||||
// Configure page.
|
||||
model.ProhibitCache = false;
|
||||
|
||||
return View(model);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
}
|
||||
//======================================================================
|
||||
}
|
||||
Reference in New Issue
Block a user