Portal signin via Control Instance

This commit is contained in:
Andy Howard
2024-10-29 08:40:47 +00:00
parent bf6bb966bc
commit c5ee285e27
467 changed files with 25771 additions and 3347 deletions
@@ -7,7 +7,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Sarand.BusinessMgr.Net.Core.Web.Control.App
namespace Sarand.BusinessMgr.Web.Control.App
{
//=======================================================================
public class Program
@@ -2,9 +2,12 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Sarand.BusinessMgr.Net.Core.Web.App;
using Sarand.BusinessMgr.Net.Core.Web.Consoles;
using Sarand.BusinessMgr.Web.Control.Consoles;
using Sarand.BusinessMgr.Net.Core.Web.Helpers;
using Sarand.BusinessMgr.Net.Standard.Base.App;
using Sarand.BusinessMgr.Net.Standard.Base.Consoles;
using Sarand.BusinessMgr.Net.Standard.Base.Core;
using Sarand.BusinessMgr.Net.Standard.Dbas.Utils;
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Core;
@@ -17,15 +20,15 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Sarand.BusinessMgr.Net.Core.Web.Control.App
namespace Sarand.BusinessMgr.Web.Control.App
{
//=======================================================================
public class SbmApplication_Control : SbmApplication_Web
{
//-------------------------------------------------------------------
// Data members.
private bool generateDatabaseDefinitions_Control = true;
private bool generateDatabaseDefinitions_BusMgr = true;
private bool generateDatabaseDefinitions_Control = false;
private bool generateDatabaseDefinitions_BusMgr = false;
private PropertySet propertySet;
private Dictionary<String, SbmAppConfig> appConfigs = new Dictionary<string, SbmAppConfig>();
private Dictionary<String, SbmEmailConfig> emailConfigs = new Dictionary<string, SbmEmailConfig>();
@@ -42,8 +45,8 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Control.App
SessionContext = sessionContext;
}
public SbmSessionContext_Web SessionContext { get; }
public List<string> DbIdentifiers { get; } = new List<string>();
public ISacsDatabase BusMgrDatabase { get; set; }
public HashSet<string> DbIdentifiers { get; } = new HashSet<string>();
public ISacsDatabase BusMgrDatabase { get; set; }
}
private readonly Dictionary<ushort, SessionContextData> sessionContexts = new Dictionary<ushort, SessionContextData>();
@@ -87,16 +90,23 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Control.App
{
return templateDatabase;
}
public override List<string> GetDbIdentifiers(SbmSessionContext sessionContext)
{
return sessionContexts.GetValue(sessionContext.Index, null)?.DbIdentifiers ?? new List<string>();
}
public override ISacsDatabase GetBusMgrDatabase(SbmSessionContext sessionContext)
public override HashSet<string> GetDbIdentifiers(SbmSessionContext sessionContext)
{
return sessionContexts.GetValue(sessionContext.Index, null)?.DbIdentifiers ?? new HashSet<string>();
}
public override ISacsDatabase GetBusMgrDatabase(SbmSessionContext sessionContext)
{
return sessionContexts.GetValue(sessionContext.Index, null)?.BusMgrDatabase;
}
public override SbmEmailConfig EmailConfig { get { return emailConfigs.Values.ToList().getConfigForSession(AppConfig); } }
//-------------------------------------------------------------------
// Create Consoles.
public override SbmEmailConsole CreateEmailConsole(ISbmUserContext_Core userContext)
{
return new SbmEmailConsole_Control(EmailConfig);
}
//-------------------------------------------------------------------
// Initialisation.
/// <summary>Initialise environment - from Startup.</summary>
@@ -182,8 +192,8 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Control.App
if (sessionContextIdx_Control < 1)
sessionContextIdx_Control = sessionContextIdx;
SessionContextData sessionContextData = new SessionContextData(new SbmSessionContext_Web(sessionContextIdx, Sbm.Application.WebControl));
sessionContextData.DbIdentifiers.AddRange(dbConfig.Identifiers);
sessionContextData.BusMgrDatabase = busMgrDatabase;
sessionContextData.DbIdentifiers.UnionWith(dbConfig.Identifiers);
sessionContextData.BusMgrDatabase = busMgrDatabase;
sessionContexts.Add(sessionContextData.SessionContext.Index, sessionContextData);
}
}
@@ -202,7 +212,7 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Control.App
config.location = AppConfig.TemplateDatabase.Location;
config.user = AppConfig.TemplateDatabase.User;
config.pword = AppConfig.TemplateDatabase.Password;
GenerateDatabaseDefinition(SbmDbDfnGenerator.DatabaseInstance.BusinessMgr, GetTemplateDatabase());
GenerateDatabaseDefinition(SbmDbDfnGenerator.DatabaseInstance.Core, GetTemplateDatabase());
}
}
@@ -210,7 +220,7 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Control.App
// Generate database definition.
private void GenerateDatabaseDefinition(SbmDbDfnGenerator.DatabaseInstance dbInstance, ISacsDatabase db)
{
if (!generateDatabaseDefinitions_BusMgr || !SbmSystem.DebugMode) return;
if (!SbmSystem.DebugMode) return;
if (!StringUtil.EmptyOnNull(EnvironmentName).EqualsCI("Development")) return;
if (db == null || StringUtil.IsEmpty(db.ConnectionString)) return;
@@ -219,7 +229,7 @@ namespace Sarand.BusinessMgr.Net.Core.Web.Control.App
}
private void GenerateDatabaseDefinition(SbmDbDfnGenerator.DatabaseInstance dbInstance, String connectionString)
{
if (!generateDatabaseDefinitions_BusMgr || !SbmSystem.DebugMode) return;
if (!SbmSystem.DebugMode) return;
if (!StringUtil.EmptyOnNull(EnvironmentName).EqualsCI("Development")) return;
if (StringUtil.IsEmpty(connectionString)) return;
@@ -5,7 +5,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Sarand.BusinessMgr.Net.Core.Web
namespace Sarand.BusinessMgr.Web
{
// <summary>
/// Dummy classes to group shared resources - named as per '.resx' file in root namespace.
@@ -19,7 +19,7 @@ namespace Sarand.BusinessMgr.Net.Core.Web
// Ref. Project Properties - Application.
}
namespace Sarand.BusinessMgr.Net.Core.Web.Control.App
namespace Sarand.BusinessMgr.Web.Control.App
{
//=======================================================================
public class SbmLocaliser_Control : SbmLocaliser
@@ -7,35 +7,110 @@ using static Sarand.BusinessMgr.Net.Standard.Base.App.Sbm.Config.Database;
using System.Collections.Generic;
using System;
using Newtonsoft.Json;
using Sarand.Net.Standard.Base.Helpers;
using Sarand.BusinessMgr.Web.Control.Controllers;
using Sarand.BusinessMgr.Net.Standard.Base.Bus.Control;
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Dbas;
using Sarand.Net.Standard.Base.Utils;
using DocumentFormat.OpenXml.Wordprocessing;
namespace Sarand.BusinessMgr.Net.Core.Web.Control.App
namespace Sarand.BusinessMgr.Web.Control.App
{
//=======================================================================
public class SbmSessionData_Control : SbmSessionData_Web
{
//-------------------------------------------------------------------
// Construction.
[JsonConstructor]
private SbmSessionData_Control(long sessionIdx, int itemIdx, SbmSessionContext sessionContext) :
base(sessionIdx, itemIdx, sessionContext)
{ }
/// <summary>
/// SessionData construction.
/// <para>SessionContext and UserAccount must be supplied.</para>
/// <para>Organisation is optional.</para>
/// </summary>
public SbmSessionData_Control(SbmSessionContext sessionContext) :
base(sessionContext)
{
}
//=======================================================================
public class SbmSessionData_Control : SbmSessionData_Web
{
//-------------------------------------------------------------------
// Construction.
[JsonConstructor]
private SbmSessionData_Control(long sessionIdx, int itemIdx, SbmSessionContext sessionContext) :
base(sessionIdx, itemIdx, sessionContext)
{ }
/// <summary>
/// SessionData construction.
/// <para>SessionContext and UserAccount must be supplied.</para>
/// </summary>
public SbmSessionData_Control(SbmSessionContext sessionContext, SbmcUserAccount userAccount) :
base(sessionContext)
{
User = new UserData_Control(sessionContext, userAccount);
}
//-------------------------------------------------------------------
protected override UserData CreateUserData(SbmSessionContext sessionContext, SaSecurityItem securityItemUser, SbmAffiliate affiliateUser, List<SysIdString> supervisedOrganisations)
{
return null;
}
//-------------------------------------------------------------------
// Properties.
//-------------------------------------------------------------------
}
//=======================================================================
[JsonIgnore]
public override String UserDisplayName { get { return User?.DisplayName; } }
[JsonIgnore]
public override String UserEmailAddress { get { return User?.EmailAddress; } }
[JsonIgnore]
public override bool ValidUser { get { return SysId.IsValid(User?.UserAccountId); } }
[JsonIgnore]
public override bool KeepLoggedIn { get { return User.KeepLoggedIn; } }
[JsonIgnore]
public override DbUpdateInfo DbUpdateInfo
{
get
{
return new DbUpdateInfo(User?.UserAccountId, StringUtil.OnEmpty(User.DisplayName, User.EmailAddress), SbmSystem.ComputerName);
}
}
//-------------------------------------------------------------------
// User details.
public class UserData_Control : UserData
{
[JsonConstructor]
private UserData_Control()
{ }
internal UserData_Control(SbmSessionContext sessionContext, SbmcUserAccount userAccount) :
base(sessionContext)
{
UserAccountId = userAccount.Id;
if (SysId.IsValid(UserAccountId))
{
UserAccountSupplied = true;
DisplayName = userAccount.GetDisplayName();
Title = userAccount.Title;
FirstName = userAccount.FirstName;
LastName = userAccount.LastName;
EmailAddress = userAccount.EmailAddress;
}
}
internal UserData_Control(UserData userData) :
base(userData)
{
UserData_Control userData_Control = userData as UserData_Control;
if (userData_Control != null)
{
UserAccountId = userData_Control.UserAccountId;
UserAccountSupplied = userData_Control.UserAccountSupplied;
KeepLoggedIn= userData_Control.KeepLoggedIn;
}
DisplayName = userData.DisplayName;
Title = userData.Title;
FirstName = userData.FirstName;
LastName = userData.LastName;
EmailAddress = userData.EmailAddress;
}
// Properties.
public SysId UserAccountId { get; private set; }
public bool UserAccountSupplied { get; private set; }
public bool KeepLoggedIn { get; private set; }
}
protected UserData CreateUserData(SbmSessionContext sessionContext, SbmcUserAccount userAccount)
{
return null;
}
/// <summary>User - JSON deserialisation route.</summary>
public UserData_Control User { get; private set; }
//-------------------------------------------------------------------
// Control application encryption IV value - for consistent encryption during session.
public String Base64IV_ControlApp { get; private set; } = SacsEncryptionHelper.InitSymmetricEncryptionIV(SbmController_Control.Base64Key_ControlApp);
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -11,7 +11,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Sarand.BusinessMgr.Net.Core.Web.Control.App
namespace Sarand.BusinessMgr.Web.Control.App
{
//=======================================================================
public class SbmStartup_Control : SbmStartup_Web
@@ -0,0 +1,87 @@
using Sarand.BusinessMgr.Net.Standard.Base.App;
using Sarand.BusinessMgr.Net.Standard.Base.Core;
using Sarand.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.Dbas;
using System;
namespace Sarand.BusinessMgr.Web.Control.App
{
//=======================================================================
/// <summary>
/// Wrapper class to allow access to core internals from Control application.
/// </summary>
public class SbmUserContext_Control : ISbmUserContext_Core
{
//-------------------------------------------------------------------
public SbmUserContext_Control(SbmSessionData_Control sessionData)
{
SessionData = sessionData;
}
//-------------------------------------------------------------------
// Properties.
private SbmSessionData_Control SessionData { get; }
//-------------------------------------------------------------------
/// <summary>Session Context - Identifies database connections etc.</summary>
public SbmSessionContext SessionContext { get { return null; } }
//-------------------------------------------------------------------
// User properties.
/// <summary>User Security Item Id.</summary>
public SysId UserSecurityItemId { get { return null; } }
/// <summary>User Affiliate Id.</summary>
public SysId UserAffiliateId { get { return null; } }
/// <summary>User display name.</summary>
public String UserDisplayName { get { return SessionData.UserDisplayName; } }
/// <summary>User email address.</summary>
public String UserEmailAddress { get { return SessionData.UserEmailAddress; } }
/// <summary>Does the context represent a valid user account.</summary>
public bool ValidUser { get { return SessionData.ValidUser; } }
/// <summary>Database update info - user session specific properties for database inserts/updates.</summary>
public DbUpdateInfo DbUpdateInfo { get { return SessionData.DbUpdateInfo; } }
//-------------------------------------------------------------------
/// <summary>Security profile - what the user is allowed to do based on security roles and groups.</summary>
public ISbmSecurityProfile_Core SecurityProfile_Core { get; }
//-------------------------------------------------------------------
// Organisation properties.
/// <summary>Organisation Affiliate Id.</summary>
public SysId OrganisationAffiliateId { get; }
/// <summary>Organisation Affiliate Type.</summary>
public Sbm.AffiliateType OrganisationAffiliateType { get; }
/// <summary>Organisation 'Internal' Association Id.</summary>
public SysId OrganisationAssociationId { get; }
/// <summary>Organisation display name.</summary>
public String OrganisationDisplayName { get; }
/// <summary>Organisation Interface context - to qualify interfaces belonging to the organisation.</summary>
public String OrganisationInterfaceContext { get; }
//-------------------------------------------------------------------
// Organisation configuration.
public String GetOrganisationOption(Sbm.OrganisationOption option, String context) { return ""; }
public String GetOrganisationOption(String optionName, String context) { return ""; }
public String GetOwnerOption_Value(Sbm.OrgOption_Value eCol) { return ""; }
public String GetOwnerOption_Folder(Sbm.OrgOption_Folder eCol) { return ""; }
public bool GetOwnerOption_AllowOption(Sbm.OrgOption_AllowOption eCol) { return false; }
public String GetOwnerOption_Caption(Sbm.OrgOption_Caption eCol, String defaultValue) { return ""; }
public bool GetOwnerOption_ShowField(SbmDbDfn.ColumnIdx eCol) { return false; }
public bool GetOwnerOption_ShowColumn(Sbm.OrgOption_ShowColumn eCol) { return false; }
public String GetOwnerOption_FieldLabel(SbmDbDfn.ColumnIdx eCol, String defautValue) { return ""; }
public int GetOwnerOption_Minutes(Sbm.OrgOption_Minutes eLimit, int defautValue) { return 0; }
public int GetOwnerOption_UpdateLimit(Sbm.OrgOption_PeriodDays eLimit, int defautValue) { return 0; }
public int GetOwnerOption_UpdateLimit(Sbm.OrgOption_PeriodWeeks eLimit, int defautValue) { return 0; }
public int GetOwnerOption_NumericDecimals(Sbm.OrgOption_NumericDecimals eNumDecimals, int defautValue = 2) { return 2; }
public Sbm.CommodityType GetOwnerOption_CommodityType(Sbm.OrgOption_CommodityType commodityTypeContext) { return Sbm.CommodityType.Unknown; }
public SysId GetOwnerOption_StockLocation(Sbm.OrgOption_StockLocation eLoc) { return null; }
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,38 @@
using Sarand.BusinessMgr.Net.Core.Web.Consoles;
using Sarand.BusinessMgr.Net.Standard.Base.Bus.Control;
using Sarand.BusinessMgr.Net.Standard.Base.Consoles;
using Sarand.BusinessMgr.Net.Standard.Base.Core;
using Sarand.BusinessMgr.Net.Standard.Base.Helpers;
using Sarand.Net.Standard.Base.Email;
namespace Sarand.BusinessMgr.Web.Control.Consoles
{
//=======================================================================
public class SbmEmailConsole_Control : SbmEmailConsole_Web
{
//-------------------------------------------------------------------
// Construction.
public SbmEmailConsole_Control(SbmEmailConfig emailConfig) :
base(emailConfig)
{
}
//-------------------------------------------------------------------
/// <summary>
/// Replace dynamic tags in email message body.
/// <para></para>
/// </summary>
public void ReplaceDynamicEmailBodyTags(SaEmailData emailData, SbmcUserAccount userAccount)
{
// Replace core tags, eg. main stylesheet.
SbmEmailConsole_Web.ReplaceDynamicEmailTags(emailData);
if (userAccount != null)
{
}
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -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);
}
//-------------------------------------------------------------------
}
//======================================================================
}
@@ -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);
}
//-------------------------------------------------------------------
}
//======================================================================
}
@@ -0,0 +1,28 @@
using Sarand.BusinessMgr.Net.Standard.Base.App;
using Sarand.BusinessMgr.Net.Standard.Base.Bus;
using Sarand.BusinessMgr.Net.Standard.Base.Bus.Control;
using Sarand.Net.Standard.Base.Email;
namespace Sarand.BusinessMgr.Web.Control.Helpers
{
//=======================================================================
public static class SbmcEmailConfigHelper
{
//-------------------------------------------------------------------
/// <summary>
/// Replace dynamic tags in email message body.
/// <para></para>
/// </summary>
public static void ReplaceDynamicEmailBodyTags(SaEmailData emailData, SbmSessionData sessionData, SbmcUserAccount userAccount)
{
// Replace core tags, eg. main stylesheet.
// ToDo. EmailConsole.ReplaceDynamicEmailTags(emailData);
// Replace tags known to the web application.
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,139 @@
using Microsoft.AspNetCore.Identity;
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.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace Sarand.Net.Standard.Base.Helpers
{
//=======================================================================
public class SbmcPasswordHelper
{
//-------------------------------------------------------------------
// Configuration parameters.
public class PasswordParams
{
public int MinLength = 8;
public int MinLengthNoComplexity = 12;
}
public static PasswordParams Params { get; } = new PasswordParams();
//-------------------------------------------------------------------
/// <summary>Validate password against parameters.</summary>
public static bool IsValidPassword(String password)
{
return GetPasswordStrength(password) >= 4;
}
/// <summary>
/// Assess the password strength - 4+ is valid.
/// <para>0 - Blank</para>
/// <para>1 - &lt; min length - 2 [red]</para>
/// <para>1 - &lt; min length and only 1 character type [red]</para>
/// <para>2 - &lt; min length and only 2 character types [red]</para>
/// <para>2 - &lt; min length without complexity - 2 and only 1 character type [red]</para>
/// <para>3 - &lt; min length, min length with 3 character types [amber]</para>
/// <para>3 - Min length without complexity - 2 [amber]</para>
/// <para>3 - Other invalid passwords [amber]</para>
/// <para>4 - Min length with 4 character types [green]</para>
/// <para>4 - Min length without complexity [green]</para>
/// <para>5 - Min lenth + 2 with 4 character types [green]</para>
/// <para>5 - Min length without complexity with 3 character types [green]</para>
/// </summary>
public static int GetPasswordStrength(String password)
{
String fn = "PasswordHelper.GetPasswordStrength";
// 0 - Blank.
if (StringUtil.IsEmpty(password)) return 0;
// Count number of different types of character.
int characterTypeCount = 0;
try
{
if (Regex.Matches(password, "[A-Z]").Count > 0) characterTypeCount++;
if (Regex.Matches(password, "[a-z]").Count > 0) characterTypeCount++;
if (Regex.Matches(password, "[0-9]").Count > 0) characterTypeCount++;
if (Regex.Matches(password, "[^a-zA-Z0-9]").Count > 0) characterTypeCount++;
}
catch (Exception exc)
{
SaLogger.Debug(fn + " " + ExceptionUtil.GetLogMessage(exc));
}
int passwordLength = StringUtil.GetLength(password);
// 1 - Less than min length - 2;
if (passwordLength < Params.MinLength - 2)
return 1;
// 1 - Less than min length and only 1 character type.
if (passwordLength < Params.MinLength && characterTypeCount == 1)
return 1;
// 2 - Less than min length and only 2 character types.
if (passwordLength < Params.MinLength && characterTypeCount == 2)
return 2;
// 2 - Less than min length without complexity - 2 and only 1 character type.
if (passwordLength < Params.MinLengthNoComplexity - 2 && characterTypeCount == 1)
return 2;
// 3 - Less than minimum length.
if (passwordLength < Params.MinLength)
return 3;
// 3 - Minimum length with only 3 character types.
if (passwordLength >= Params.MinLength && passwordLength < Params.MinLengthNoComplexity && characterTypeCount == 3)
return 3;
// 3 - Minimum length without complexity - 2 with less than 4 character types.
if (passwordLength >= Params.MinLengthNoComplexity - 2 && passwordLength < Params.MinLengthNoComplexity && characterTypeCount < 4)
return 3;
// 3 Catch-all - ensure only valid passwords score 4 or more.
if (passwordLength < Params.MinLengthNoComplexity && characterTypeCount < 4)
return 3;
// 4 - Min length with 4 character types.
if (passwordLength < Params.MinLengthNoComplexity)
return 4;
// 4 - Min length without complexity.
if (passwordLength >= Params.MinLengthNoComplexity && passwordLength < Params.MinLengthNoComplexity + 2 && characterTypeCount < 4)
return 4;
// 5 - Anything more complex.
return 5;
}
//-------------------------------------------------------------------
private static PasswordHasher<SbmcUserAccount> passwordHasher = new PasswordHasher<SbmcUserAccount>();
/// <summary>Get the hashed value that corresponds to the password.</summary>
public static String GetPasswordHash(SbmcUserAccount userAccount, String password)
{
return passwordHasher.HashPassword(userAccount, password);
}
/// <summary>Verify password against hashed value stored against user account.</summary>
public static bool VerifyHashedPassword(SbmcUserAccount userAccount, String password)
{
return passwordHasher.VerifyHashedPassword(userAccount, userAccount.PasswordHashed, password) != PasswordVerificationResult.Failed;
}
/// <summary>Verify password against hashed value with user account as reference.</summary>
public static bool VerifyHashedPassword(SbmcUserAccount userAccount, String password, String passwordHashed)
{
return passwordHasher.VerifyHashedPassword(userAccount, passwordHashed, password) != PasswordVerificationResult.Failed;
}
/// <summary>Check whether the password needs rehashing.</summary>
public static bool PasswordRequiresRehash(SbmcUserAccount userAccount, String password)
{
return passwordHasher.VerifyHashedPassword(userAccount, userAccount.PasswordHashed, password) == PasswordVerificationResult.SuccessRehashNeeded;
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,89 @@
using Sarand.BusinessMgr.Net.Standard.Base.Helpers;
using Sarand.Net.Standard.Base.Helpers;
using System.Reflection;
using System;
using Sarand.Net.Standard.Base.Utils;
namespace Sarand.BusinessMgr.Web.Control.Helpers
{
//=======================================================================
public static class SbmcResourceHelper
{
//-------------------------------------------------------------------
// Definitions.
public enum TextResource
{
// Email stylesheets.
Email_Main_Css,
// Email partial content.
Email_Header_Html,
Email_Footer_Html,
// Email templates.
Email_RequestPassword_Html,
}
public enum ImageResource
{
}
//-------------------------------------------------------------------
// Properties.
public static Assembly Assembly { get { return typeof(SacsResourceHelper).GetTypeInfo().Assembly; } }
//-------------------------------------------------------------------
// Text resources.
public static String GetResourceText(TextResource textResource)
{
String partialPath = "";
String resourceFilename = "";
switch (textResource)
{
// Email stylesheet.
case TextResource.Email_Main_Css:
resourceFilename = "sbmc-email-main.css";
break;
// Email partial content.
case TextResource.Email_Header_Html:
resourceFilename = "sbmc-email-header.html";
break;
case TextResource.Email_Footer_Html:
resourceFilename = "sbmc-email-footer.html";
break;
// Email templates.
case TextResource.Email_RequestPassword_Html:
resourceFilename = "sbmc-email-request-password.html";
break;
// Data.
}
if (!StringUtil.IsEmpty(resourceFilename))
{
String resourceText = SacsResourceHelper.GetManifestResourceAsText(Assembly, partialPath, resourceFilename);
if (!StringUtil.IsEmpty(resourceText))
return resourceText;
}
return "";
}
//-------------------------------------------------------------------
// Image resources.
public static byte[] GetResourceImage(ImageResource imageResource)
{
String partialPath = "";
String resourceFilename = "";
switch (imageResource)
{
default:
break;
}
if (!StringUtil.IsEmpty(resourceFilename))
{
byte[] resourceData = SacsResourceHelper.GetManifestResourceAsBinary(Assembly, partialPath, resourceFilename);
if (resourceData != null)
return resourceData;
}
return new byte[0];
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,189 @@
using DocumentFormat.OpenXml.Spreadsheet;
using Sarand.BusinessMgr.Net.Core.Web.App;
using Sarand.BusinessMgr.Net.Core.Web.Controllers;
using Sarand.BusinessMgr.Net.Standard.Base.App;
using Sarand.BusinessMgr.Net.Standard.Base.Bus.Control;
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.Diagnostics.Contracts;
namespace Sarand.BusinessMgr.Web.Control.Models.Account
{
//=======================================================================
public class Model_UserAccount : SbmModel_Control
{
//-------------------------------------------------------------------
// Construction.
public Model_UserAccount(SbmLocaliser localiser) :
base(localiser)
{
}
//-------------------------------------------------------------------
// Filter properties.
public class FilterData
{
public string Search { get; set; }
public string EmailAddress { get; set; }
public string StatusCat { get; set; }
public SbmcUserAccount.StatusCat GetStatusCat()
{
return EnumUtil.ParseEnum(StatusCat, SbmcUserAccount.StatusCat.NotDeleted);
}
}
public FilterData Filter { get; } = new FilterData();
//-------------------------------------------------------------------
// Item properties.
public class UserAccountData
{
public String Exists { get; set; }
public string Id { get; set; }
public string EmailAddress { get; set; }
public string Title { get; set; }
public string FirstName { get; set; }
public string Initials { get; set; }
public string LastName { get; set; }
public string PhoneMain { get; set; }
public string PhoneMobile { get; set; }
public string PhoneAlt { get; set; }
public string StatusCode { get; set; }
public SaStatus GetStatus() { return SacsSystem.GetStatusForCode(StatusCode); }
}
public UserAccountData User { get; } = new UserAccountData();
// User emails.
public string UserEmailAcountActivationPortal { get; set; }
public string UserEmailRequestPasswordPortal { get; set; }
public string UserEmailRequestPasswordControl { get; set; }
//-------------------------------------------------------------------
// Validation.
//-------------------------------------------------------------------
// User.
public bool ValidateUserData(Sacs.Web.Components.SaController.AjaxResponse_Data responseData)
{
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<SacsHierarchyItem> disposables = new DisposableCollection<SacsHierarchyItem>())
{
// User email.
if (User.EmailAddress != null)
{
string controlName = "UserEmailAddress";
User.EmailAddress = User.EmailAddress.ToLowerInvariant();
responseData.SetControlValue(controlName, User.EmailAddress);
if (User.EmailAddress.IsBlank())
responseData.SetControlError(controlName, Localiser["msg.validation.mandatory.value"]);
else if (StringUtil.GetLength(User.EmailAddress) > GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.UserAccount_EmailAddress))
responseData.SetControlError(controlName, Localiser["msg.validation.maximum.length", GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.UserAccount_EmailAddress)]);
else if (!SacsDataValidationHelper.IsValidEmailAddress(User.EmailAddress))
responseData.SetControlError(controlName, Localiser["msg.validation.invalid.value"]);
else
{
// Check for duplicate email address.
SbmcUserAccount.Params prmUserAccount = new SbmcUserAccount.Params();
SysId userAccountId = User.Id.ToSysId();
if (SysId.IsValid(userAccountId))
prmUserAccount.IdsExcluded.Add(userAccountId);
prmUserAccount.EmailAddress = User.EmailAddress;
prmUserAccount.StatusCat = SbmcUserAccount.StatusCat.Any;
if (ControlAccountMgr.GetUserAccountCount(prmUserAccount) > 0)
responseData.SetControlError(controlName, Localiser["msg.validation.duplicate.value"]);
}
}
// User title.
if (User.Title != null)
{
string controlName = "UserTitle";
responseData.SetControlValue(controlName, User.Title);
if (StringUtil.GetLength(User.Title) > GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.UserAccount_Title))
responseData.SetControlError(controlName, Localiser["msg.validation.maximum.length", GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.UserAccount_Title)]);
}
// User first name.
if (User.FirstName != null)
{
string controlName = "UserFirstName";
responseData.SetControlValue(controlName, User.FirstName);
if (User.FirstName.IsBlank())
responseData.SetControlError(controlName, Localiser["msg.validation.mandatory.value"]);
else if (StringUtil.GetLength(User.FirstName) > GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.UserAccount_FirstName))
responseData.SetControlError(controlName, Localiser["msg.validation.maximum.length", GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.UserAccount_FirstName)]);
}
// User title.
if (User.Initials != null)
{
string controlName = "UserInitials";
responseData.SetControlValue(controlName, User.Initials);
if (StringUtil.GetLength(User.Initials) > GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.UserAccount_Initials))
responseData.SetControlError(controlName, Localiser["msg.validation.maximum.length", GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.UserAccount_Initials)]);
}
// User last name.
if (User.LastName != null)
{
string controlName = "UserLastName";
responseData.SetControlValue(controlName, User.LastName);
if (User.LastName.IsBlank())
responseData.SetControlError(controlName, Localiser["msg.validation.mandatory.value"]);
else if (StringUtil.GetLength(User.LastName) > GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.UserAccount_LastName))
responseData.SetControlError(controlName, Localiser["msg.validation.maximum.length", GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.UserAccount_LastName)]);
}
// Phone.
if (User.PhoneMain != null)
{
string controlName = "UserPhoneMain";
responseData.SetControlValue(controlName, User.PhoneMain);
if (StringUtil.GetLength(User.PhoneMain) > GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.UserAccount_PhoneMain))
responseData.SetControlError(controlName, Localiser["msg.validation.maximum.length", GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.UserAccount_PhoneMain)]);
else if (!User.PhoneMain.IsEmpty() && !SacsDataValidationHelper.IsValidPhone(User.PhoneMain))
responseData.SetControlError(controlName, Localiser["msg.validation.invalid.value"]);
}
// Mobile Phone.
if (User.PhoneMobile != null)
{
string controlName = "UserPhoneMobile";
responseData.SetControlValue(controlName, User.PhoneMobile);
if (StringUtil.GetLength(User.PhoneMobile) > GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.UserAccount_PhoneMobile))
responseData.SetControlError(controlName, Localiser["msg.validation.maximum.length", GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.UserAccount_PhoneMobile)]);
else if (!User.PhoneMobile.IsEmpty() && !SacsDataValidationHelper.IsValidPhone(User.PhoneMobile))
responseData.SetControlError(controlName, Localiser["msg.validation.invalid.value"]);
}
// Alt Phone.
if (User.PhoneAlt != null)
{
string controlName = "UserPhoneAlt";
responseData.SetControlValue(controlName, User.PhoneAlt);
if (StringUtil.GetLength(User.PhoneAlt) > GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.UserAccount_PhoneAlt))
responseData.SetControlError(controlName, Localiser["msg.validation.maximum.length", GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.UserAccount_PhoneAlt)]);
else if (!User.PhoneAlt.IsEmpty() && !SacsDataValidationHelper.IsValidPhone(User.PhoneAlt))
responseData.SetControlError(controlName, Localiser["msg.validation.invalid.value"]);
}
// User status.
if (User.StatusCode != null)
{
string controlName = "UserStatus";
if (User.StatusCode.IsBlank())
responseData.SetControlError(controlName, Localiser["msg.validation.mandatory.value"]);
else if (!SbmcUserAccount.ValidStatuses.Contains(User.GetStatus()))
responseData.SetControlError(controlName, Localiser["msg.validation.invalid.value"]);
}
}
return !responseData.HasControlErrors();
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -1,6 +1,6 @@
using System;
namespace Sarand.BusinessMgr.Net.Core.Web.Control.Models
namespace Sarand.BusinessMgr.Web.Control.Models
{
public class ErrorViewModel
{
@@ -0,0 +1,141 @@
using DocumentFormat.OpenXml.Spreadsheet;
using Sarand.BusinessMgr.Net.Core.Web.App;
using Sarand.BusinessMgr.Net.Core.Web.Controllers;
using Sarand.BusinessMgr.Net.Standard.Base.App;
using Sarand.BusinessMgr.Net.Standard.Base.Bus.Control;
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;
namespace Sarand.BusinessMgr.Web.Control.Models.Instance
{
//=======================================================================
public class Model_Owner : SbmModel_Control
{
//-------------------------------------------------------------------
// Construction.
public Model_Owner(SbmLocaliser localiser) :
base(localiser)
{
}
//-------------------------------------------------------------------
// Filter properties.
public class FilterData
{
public String Search { get; set; }
public String StatusCat { get; set; }
public SbmcOwner.StatusCat GetStatusCat()
{
return EnumUtil.ParseEnum(StatusCat, SbmcOwner.StatusCat.NotDeleted);
}
}
public FilterData Filter { get; } = new FilterData();
//-------------------------------------------------------------------
// Item properties.
public class OwnerData
{
public String Exists { get; set; }
public String Id { get; set; }
public String Name { get; set; }
public String ContactName { get; set; }
public String ContactEmailAddress { get; set; }
public String ContactPhoneMain { get; set; }
public String ContactPhoneAlt { get; set; }
public String StatusCode { get; set; }
public SaStatus GetStatus() { return SacsSystem.GetStatusForCode(StatusCode); }
}
public OwnerData Owner { get; } = new OwnerData();
//-------------------------------------------------------------------
// Validation.
//-------------------------------------------------------------------
// Owner.
public bool ValidateOwnerData(SbmController_Base.AjaxResponse_Data responseData)
{
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<SacsHierarchyItem> disposables = new DisposableCollection<SacsHierarchyItem>())
{
// Owner name.
if (Owner.Name != null)
{
String controlName = "OwnerName";
responseData.SetControlValue(controlName, Owner.Name);
if (StringUtil.IsBlank(Owner.Name))
responseData.SetControlError(controlName, Localiser["msg.validation.mandatory.value"]);
else if (StringUtil.GetLength(Owner.Name) > GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.Owner_Name))
responseData.SetControlError(controlName, Localiser["msg.validation.maximum.length", GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.Owner_Name)]);
}
// Owner contact name.
if (Owner.ContactName != null)
{
String controlName = "OwnerContactName";
responseData.SetControlValue(controlName, Owner.ContactName);
if (!StringUtil.IsBlank(Owner.ContactName))
{
if (StringUtil.GetLength(Owner.ContactName) > GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.Owner_ContactName))
responseData.SetControlError(controlName, Localiser["msg.validation.maximum.length", GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.Owner_ContactName)]);
}
}
// Contact email.
if (Owner.ContactEmailAddress != null)
{
String controlName = "OwnerContactEmailAddress";
Owner.ContactEmailAddress = Owner.ContactEmailAddress.ToLowerInvariant();
responseData.SetControlValue(controlName, Owner.ContactEmailAddress);
if (!StringUtil.IsBlank(Owner.ContactEmailAddress))
{
if (StringUtil.GetLength(Owner.ContactEmailAddress) > GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.Owner_ContactEmailAddress))
responseData.SetControlError(controlName, Localiser["msg.validation.maximum.length", GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.Owner_ContactEmailAddress)]);
else if (!SacsDataValidationHelper.IsValidEmailAddress(Owner.ContactEmailAddress))
responseData.SetControlError(controlName, Localiser["msg.validation.invalid.value"]);
}
}
// Contact Phone.
if (Owner.ContactPhoneMain != null)
{
String controlName = "OwnerContactPhoneMain";
responseData.SetControlValue(controlName, Owner.ContactPhoneMain);
if (StringUtil.GetLength(Owner.ContactPhoneMain) > GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.Owner_ContactPhoneMain))
responseData.SetControlError(controlName, Localiser["msg.validation.maximum.length", GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.Owner_ContactPhoneMain)]);
else if (!StringUtil.IsEmpty(Owner.ContactPhoneMain) && !SacsDataValidationHelper.IsValidPhone(Owner.ContactPhoneMain))
responseData.SetControlError(controlName, Localiser["msg.validation.invalid.value"]);
}
// Contact Alt Phone.
if (Owner.ContactPhoneAlt != null)
{
String controlName = "OwnerContactPhoneAlt";
responseData.SetControlValue(controlName, Owner.ContactPhoneAlt);
if (StringUtil.GetLength(Owner.ContactPhoneAlt) > GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.Owner_ContactPhoneAlt))
responseData.SetControlError(controlName, Localiser["msg.validation.maximum.length", GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.Owner_ContactPhoneAlt)]);
else if (!StringUtil.IsEmpty(Owner.ContactPhoneAlt) && !SacsDataValidationHelper.IsValidPhone(Owner.ContactPhoneAlt))
responseData.SetControlError(controlName, Localiser["msg.validation.invalid.value"]);
}
// Owner status.
if (Owner.StatusCode != null)
{
String controlName = "OwnerStatus";
if (StringUtil.IsBlank(Owner.StatusCode))
responseData.SetControlError(controlName, Localiser["msg.validation.mandatory.value"]);
else if (!SbmcOwner.ValidStatuses.Contains(Owner.GetStatus()))
responseData.SetControlError(controlName, Localiser["msg.validation.invalid.value"]);
}
}
return !responseData.HasControlErrors();
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,171 @@
using DocumentFormat.OpenXml.Spreadsheet;
using Sarand.BusinessMgr.Net.Core.Web.App;
using Sarand.BusinessMgr.Net.Core.Web.Controllers;
using Sarand.BusinessMgr.Net.Standard.Base.App;
using Sarand.BusinessMgr.Net.Standard.Base.Bus.Control;
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;
namespace Sarand.BusinessMgr.Web.Control.Models.Instance
{
//=======================================================================
public class Model_Repository : SbmModel_Control
{
//-------------------------------------------------------------------
// Construction.
public Model_Repository(SbmLocaliser localiser) :
base(localiser)
{
}
//-------------------------------------------------------------------
// Filter properties.
public class FilterData
{
public String Search { get; set; }
public String StatusCat { get; set; }
public SbmcRepository.StatusCat GetStatusCat()
{
return EnumUtil.ParseEnum(StatusCat, SbmcRepository.StatusCat.NotDeleted);
}
}
public FilterData Filter { get; } = new FilterData();
//-------------------------------------------------------------------
// Item properties.
public class RepositoryData
{
public String Exists { get; set; }
public String Id { get; set; }
public String OwnerId { get; set; }
public String Context { get; set; }
public String ServerName { get; set; }
public String InstanceName { get; set; }
public String DatabaseName { get; set; }
public String UserName { get; set; }
public String Password { get; set; }
public String StatusCode { get; set; }
public SaStatus GetStatus() { return SacsSystem.GetStatusForCode(StatusCode); }
}
public RepositoryData Repository { get; } = new RepositoryData();
//-------------------------------------------------------------------
// Validation.
//-------------------------------------------------------------------
// Repository.
public bool ValidateRepositoryData(SbmController_Base.AjaxResponse_Data responseData)
{
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<SacsHierarchyItem> disposables = new DisposableCollection<SacsHierarchyItem>())
{
// Repository Owner.
if (Repository.OwnerId != null)
{
String controlName = "RepositoryOwner";
SysId ownerId = Repository.OwnerId.ToSysId();
if (!SysId.IsValid(ownerId))
responseData.SetControlError(controlName, Localiser["msg.validation.mandatory.value"]);
else
{
SbmcOwner owner = ControlInstanceMgr.GetOwnerById(ownerId);
if (owner == null)
responseData.SetControlError(controlName, Localiser["msg.validation.invalid.value"]);
}
}
// Repository Context.
if (Repository.Context != null)
{
String controlName = "RepositoryContext";
responseData.SetControlValue(controlName, Repository.Context);
if (StringUtil.IsBlank(Repository.Context))
responseData.SetControlError(controlName, Localiser["msg.validation.mandatory.value"]);
else if (StringUtil.GetLength(Repository.Context) > GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.Repository_Context))
responseData.SetControlError(controlName, Localiser["msg.validation.maximum.length", GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.Repository_Context)]);
else
{
// Check for duplicate context.
SbmcRepository.Params prmRepository = new SbmcRepository.Params();
SysId repositoryId = Repository.Id.ToSysId();
if (SysId.IsValid(repositoryId))
prmRepository.IdsExcluded.Add(repositoryId);
prmRepository.ContextsIncluded.Add(Repository.Context);
prmRepository.StatusCat = SbmcRepository.StatusCat.Any;
if (ControlInstanceMgr.GetRepositoryCount(prmRepository) > 0)
responseData.SetControlError(controlName, Localiser["msg.validation.duplicate.value"]);
}
}
// Repository Server name.
if (Repository.ServerName != null)
{
String controlName = "RepositoryServerName";
responseData.SetControlValue(controlName, Repository.ServerName);
if (StringUtil.IsBlank(Repository.ServerName))
responseData.SetControlError(controlName, Localiser["msg.validation.mandatory.value"]);
else if (StringUtil.GetLength(Repository.ServerName) > GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.Repository_ServerName))
responseData.SetControlError(controlName, Localiser["msg.validation.maximum.length", GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.Repository_ServerName)]);
}
// Repository Instance name.
if (Repository.InstanceName != null)
{
String controlName = "RepositoryInstanceName";
responseData.SetControlValue(controlName, Repository.InstanceName);
if (StringUtil.GetLength(Repository.InstanceName) > GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.Repository_InstanceName))
responseData.SetControlError(controlName, Localiser["msg.validation.maximum.length", GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.Repository_InstanceName)]);
}
// Repository Database name.
if (Repository.DatabaseName != null)
{
String controlName = "RepositoryDatabaseName";
responseData.SetControlValue(controlName, Repository.DatabaseName);
if (StringUtil.IsBlank(Repository.DatabaseName))
responseData.SetControlError(controlName, Localiser["msg.validation.mandatory.value"]);
else if (StringUtil.GetLength(Repository.DatabaseName) > GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.Repository_DatabaseName))
responseData.SetControlError(controlName, Localiser["msg.validation.maximum.length", GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.Repository_DatabaseName)]);
}
// Repository User name.
if (Repository.UserName != null)
{
String controlName = "RepositoryUserName";
responseData.SetControlValue(controlName, Repository.UserName);
if (StringUtil.GetLength(Repository.UserName) > GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.Repository_UserName))
responseData.SetControlError(controlName, Localiser["msg.validation.maximum.length", GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.Repository_UserName)]);
}
// Repository password.
if (Repository.Password != null)
{
String controlName = "RepositoryPassword";
responseData.SetControlValue(controlName, Repository.Password);
if (StringUtil.GetLength(Repository.Password) > GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.Repository_Password))
responseData.SetControlError(controlName, Localiser["msg.validation.maximum.length", GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.Repository_Password)]);
}
// Repository status.
if (Repository.StatusCode != null)
{
String controlName = "RepositoryStatus";
if (StringUtil.IsBlank(Repository.StatusCode))
responseData.SetControlError(controlName, Localiser["msg.validation.mandatory.value"]);
else if (!SbmcRepository.ValidStatuses.Contains(Repository.GetStatus()))
responseData.SetControlError(controlName, Localiser["msg.validation.invalid.value"]);
}
}
return !responseData.HasControlErrors();
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -1,6 +1,6 @@
using Sarand.BusinessMgr.Net.Core.Web.App;
namespace Sarand.BusinessMgr.Net.Core.Web.Control.Models
namespace Sarand.BusinessMgr.Web.Control.Models
{
//=======================================================================
public class Model_Home : SbmModel_Control
@@ -0,0 +1,106 @@
using Sarand.BusinessMgr.Net.Core.Web.App;
using Sarand.BusinessMgr.Net.Core.Web.Controllers;
using Sarand.BusinessMgr.Net.Standard.Base.Bus.Control;
using Sarand.BusinessMgr.Net.Standard.Base.Helpers;
using Sarand.Net.Standard.Base.Helpers;
using Sarand.Net.Standard.Base.Utils;
using System;
namespace Sarand.BusinessMgr.Web.Control.Models
{
//=======================================================================
public class Model_Security : SbmModel_Control
{
//-------------------------------------------------------------------
// Construction.
public Model_Security(SbmLocaliser localiser) :
base(localiser)
{
//AdditionalFiles_CSS.Add("css/sbc-sec.css");
AdditionalFiles_JS.Add("js/sbc-sec.js");
}
//-------------------------------------------------------------------
// Page configuration.
public bool RequestPasswordMode { get { return !StringUtil.IsEmpty(rpe); } }
//-------------------------------------------------------------------
// Form properties.
public String EmailAddress { get; set; }
public String PasswordCurrent { get; set; }
public String Password { get; set; }
public String PasswordConfirm { get; set; }
public bool KeepMeSignedIn { get; set; }
/// <summary>Request Password Encrypted params - to allow Change Password without session or requiring old password.</summary>
public String rpe { get; set; }
//-------------------------------------------------------------------
// Validation.
//-------------------------------------------------------------------
// Sign-in.
public bool ValidateSignInData(SbmController_Base.AjaxResponse_Data responseData)
{
// Email address.
responseData.SetControlValue(nameof(EmailAddress), EmailAddress);
if (StringUtil.IsEmpty(EmailAddress))
responseData.SetControlError(nameof(EmailAddress), Localiser["msg.validation.mandatory.value"]);
else if (!SacsDataValidationHelper.IsValidEmailAddress(EmailAddress))
responseData.SetControlError(nameof(EmailAddress), Localiser["msg.validation.invalid.value"]);
// Password.
// NB Do not return the password value in the response.
if (StringUtil.IsEmpty(Password))
responseData.SetControlError(nameof(Password), Localiser["msg.validation.mandatory.value"]);
return !responseData.HasControlErrors();
}
//-------------------------------------------------------------------
// Change password.
public bool ValidateChangePasswordData(SbmcUserAccount userAccount, SbmController_Base.AjaxResponse_Data responseData)
{
// Validate Passwords.
// NB Do not return the password values in the response.
// NB Only validate populated values - any null values are not part of the form for this request.
// - 'Current' password is not shown when changing password from 'Request Password' email link.
if (PasswordCurrent != null && StringUtil.IsEmpty(PasswordCurrent))
responseData.SetControlError(nameof(PasswordCurrent), Localiser["msg.validation.mandatory.value"]);
if (StringUtil.IsEmpty(Password))
responseData.SetControlError(nameof(Password), Localiser["msg.validation.mandatory.value"]);
if (StringUtil.IsEmpty(PasswordConfirm))
responseData.SetControlError(nameof(PasswordConfirm), Localiser["msg.validation.mandatory.value"]);
if (!StringUtil.IsEmpty(Password) && !StringUtil.IsEmpty(PasswordConfirm))
{
if (!Password.Equals(PasswordConfirm))
responseData.SetControlError(nameof(PasswordConfirm), Localiser["msg.validation.invalid.value"]);
if (!SbmcPasswordHelper.IsValidPassword(Password))
responseData.SetControlError(nameof(PasswordConfirm), Localiser.GetText("msg.warn.password.invalid", new object[] { SbmcPasswordHelper.Params.MinLength, SbmcPasswordHelper.Params.MinLengthNoComplexity }));
}
if (!responseData.HasControlErrors())
{
if (PasswordCurrent != null && !SbmcPasswordHelper.VerifyHashedPassword(userAccount, PasswordCurrent))
responseData.SetControlError(nameof(PasswordCurrent), Localiser["security.change.password.current.invalid"]);
if (SbmcPasswordHelper.VerifyHashedPassword(userAccount, Password))
responseData.SetControlError(nameof(Password), Localiser["security.change.password.not.changed"]);
}
return !responseData.HasControlErrors();
}
//-------------------------------------------------------------------
// Request Password.
public bool ValidateRequestPasswordData(SbmController_Base.AjaxResponse_Data responseData)
{
// Email address.
responseData.SetControlValue(nameof(EmailAddress), EmailAddress);
if (StringUtil.IsEmpty(EmailAddress))
responseData.SetControlError(nameof(EmailAddress), Localiser["msg.validation.mandatory.value"]);
else if (!SacsDataValidationHelper.IsValidEmailAddress(EmailAddress))
responseData.SetControlError(nameof(EmailAddress), Localiser["msg.validation.invalid.value"]);
return !responseData.HasControlErrors();
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,153 @@
using DocumentFormat.OpenXml.EMMA;
using DocumentFormat.OpenXml.Spreadsheet;
using Microsoft.AspNetCore.Mvc.Rendering;
using Sarand.BusinessMgr.Net.Core.Web.App;
using Sarand.BusinessMgr.Net.Core.Web.Controllers;
using Sarand.BusinessMgr.Net.Standard.Base.App;
using Sarand.BusinessMgr.Net.Standard.Base.Bus.Control;
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.CodeTypes;
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.Diagnostics.Contracts;
namespace Sarand.BusinessMgr.Web.Control.Models.Param
{
//=======================================================================
public class Model_Param : SbmModel_Control
{
//-------------------------------------------------------------------
// Construction.
public Model_Param(SbmLocaliser localiser) :
base(localiser)
{
}
//-------------------------------------------------------------------
/// <summary>List of Item Types for dropdown.</summary>
public IEnumerable<SelectListItem> GetParamItemTypesForSelect()
{
List<CodeDesc> codeTypes = new List<CodeDesc>();
foreach (var itemType in SbmcItemParam.ValidItemTypes)
codeTypes.Add(((int)itemType).ToString(), itemType.ToString());
return new SelectList(codeTypes, "Code", "Desc");
}
//-------------------------------------------------------------------
// Filter properties.
public class FilterData
{
public string ItemType { get; set; }
public string ItemId { get; set; }
public string Search { get; set; }
public string StatusCat { get; set; }
public SbmcItemParam.StatusCat GetStatusCat()
{
return EnumUtil.ParseEnum(StatusCat, SbmcItemParam.StatusCat.NotDeleted);
}
}
public FilterData Filter { get; } = new FilterData();
//-------------------------------------------------------------------
// Item properties.
public class ItemParamData
{
public String Exists { get; set; }
public string Id { get; set; }
public string ItemType { get; set; }
public string ItemId { get; set; }
public string Context { get; set; }
public string Name { get; set; }
public string Value { get; set; }
public string ValueChanged { get; set; }
public string Notes { get; set; }
public string StatusCode { get; set; }
public SaStatus GetStatus() { return SacsSystem.GetStatusForCode(StatusCode); }
}
public ItemParamData Param { get; } = new ItemParamData();
//-------------------------------------------------------------------
// Validation.
//-------------------------------------------------------------------
public bool ValidateParamData(Sacs.Web.Components.SaController.AjaxResponse_Data responseData)
{
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<SacsHierarchyItem> disposables = new DisposableCollection<SacsHierarchyItem>())
{
// Check whether data suggests that Param exists.
bool paramExists = StringUtil.ToBool(Param.Exists) && SysId.IsValid(Param.Id.ToSysId());
// Item.
if (Param.ItemType != null || Param.ItemId != null)
{
string controlName = "ParamItem";
ItemId itemId = new ItemId(StringUtil.ToShort(Param.ItemType), Param.ItemId.ToSysId());
if (!paramExists && (Param.ItemType.IsBlank() || Param.ItemId.IsBlank()))
responseData.SetControlError(controlName, Localiser["msg.validation.mandatory.value"]);
else if (StringUtil.IsBlank(ControlItemMgr.GetItemCaption(itemId)))
responseData.SetControlError(controlName, Localiser["msg.validation.invalid.value"]);
}
// Context.
if (Param.Context != null)
{
string controlName = "ParamContext";
responseData.SetControlValue(controlName, Param.Context);
if (Param.Context.IsBlank())
responseData.SetControlError(controlName, Localiser["msg.validation.mandatory.value"]);
else if (StringUtil.GetLength(Param.Context) > GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.ItemParam_Context))
responseData.SetControlError(controlName, Localiser["msg.validation.maximum.length", GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.ItemParam_Context)]);
}
// Name.
if (Param.Name != null)
{
string controlName = "ParamName";
responseData.SetControlValue(controlName, Param.Name);
if (Param.Name.IsBlank())
responseData.SetControlError(controlName, Localiser["msg.validation.mandatory.value"]);
else if (StringUtil.GetLength(Param.Name) > GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.ItemParam_Name))
responseData.SetControlError(controlName, Localiser["msg.validation.maximum.length", GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.ItemParam_Name)]);
}
// Value.
if (Param.Value != null)
{
string controlName = "ParamValue";
responseData.SetControlValue(controlName, Param.Value);
if (StringUtil.GetLength(Param.Value) > GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.ItemParam_Value))
responseData.SetControlError(controlName, Localiser["msg.validation.maximum.length", GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.ItemParam_Value)]);
}
// Notes.
if (Param.Notes != null)
{
string controlName = "ParamNotes";
responseData.SetControlValue(controlName, Param.Notes);
if (StringUtil.GetLength(Param.Notes) > GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.ItemParam_Notes))
responseData.SetControlError(controlName, Localiser["msg.validation.maximum.length", GetControlMaxLength(SbmDbDfn_Control.ColumnIdx.ItemParam_Notes)]);
}
// User status.
if (Param.StatusCode != null)
{
string controlName = "ParamStatus";
if (Param.StatusCode.IsBlank())
responseData.SetControlError(controlName, Localiser["msg.validation.mandatory.value"]);
else if (!SbmcItemParam.ValidStatuses.Contains(Param.GetStatus()))
responseData.SetControlError(controlName, Localiser["msg.validation.invalid.value"]);
}
}
return !responseData.HasControlErrors();
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -1,19 +1,55 @@
using Sarand.BusinessMgr.Net.Core.Web.App;
using Sarand.BusinessMgr.Net.Core.Web.Models;
using Sarand.BusinessMgr.Net.Standard.Base.App;
using Sarand.BusinessMgr.Net.Standard.Base.Core;
using Sarand.BusinessMgr.Web.Control.App;
using System;
namespace Sarand.BusinessMgr.Net.Core.Web.Control.Models
namespace Sarand.BusinessMgr.Web.Control.Models
{
//=======================================================================
public class SbmModel_Control : SbmModel_Base
{
//-------------------------------------------------------------------
// Protected construction.
protected SbmModel_Control(SbmLocaliser localiser) :
base(localiser)
{
}
//=======================================================================
public class SbmModel_Control : SbmModel_Base
{
//-------------------------------------------------------------------
// Protected construction.
protected SbmModel_Control(SbmLocaliser localiser) :
base(localiser)
{
}
//-------------------------------------------------------------------
}
//=======================================================================
//-------------------------------------------------------------------
// Properties.
public SbmSessionData_Control SessionData_Control { get { return SessionData as SbmSessionData_Control; } }
public override ISbmUserContext_Core UserContext_Core { get { return new SbmUserContext_Control(SessionData_Control); } }
//-------------------------------------------------------------------
/// <summary>
/// Form control value max length - BusinessMgr tables.
/// <para>In many cases the database columns allow plenty of extra space for the value, in case they need to be encrypted.</para>
/// <para>Hence, several cases where the value entered in the control isn't allowed the full database length.</para>
/// </summary>
public int GetControlMaxLength(SbmDbDfn_Control.ColumnIdx column)
{
int maxLength = (int)SbmDbDfn_Control.GetColTextLen(column);
switch (column)
{
case SbmDbDfn_Control.ColumnIdx.UserAccount_EmailAddress:
return Math.Min(maxLength, 100);
case SbmDbDfn_Control.ColumnIdx.UserAccount_FirstName:
case SbmDbDfn_Control.ColumnIdx.UserAccount_LastName:
return Math.Min(maxLength, 40);
case SbmDbDfn_Control.ColumnIdx.UserAccount_PhoneMain:
case SbmDbDfn_Control.ColumnIdx.UserAccount_PhoneMobile:
case SbmDbDfn_Control.ColumnIdx.UserAccount_PhoneAlt:
return Math.Min(maxLength, 20);
case SbmDbDfn_Control.ColumnIdx.ItemParam_Value:
if (maxLength == 0 || maxLength > 2048) return 2048;
break;
}
return maxLength;
}
//-------------------------------------------------------------------
}
//=======================================================================
}