From 4f91faae8b64a749f3fb9b91e844419db05d6602 Mon Sep 17 00:00:00 2001 From: Andy Howard Date: Thu, 9 Jan 2025 16:31:16 +0000 Subject: [PATCH] BusinessMgr --- SacsCoreWeb/Web/Components/SaController.cs | 4 +- SacsMobileMaui/SacsMobileMaui.csproj | 1 - SacsMobileXamarin/SacsMobileXamarin.csproj | 2 +- SacsStandardBase/Base/Bus/SaErrorStateItem.cs | 9 +- SacsStandardBase/Base/Bus/SysId.cs | 7 ++ SacsStandardBase/Base/Dbas/IDbDataTable.cs | 9 -- SacsStandardBase/Base/Dbas/ISacsDatabase.cs | 42 ++++--- .../Base/Helpers/SacsRandomHelper.cs | 19 +++- .../Base/Security/SaSecurityConsole.cs | 45 ++++++-- .../Base/Security/SaSecurityItem.cs | 100 ++++++----------- .../Base/Security/SaSecurityMgr.cs | 13 ++- .../Base/Security/SaSecurityProfile.cs | 96 +++++++++------- SacsStandardBase/Base/Utils/StringUtil.cs | 31 +++--- SacsStandardBase/SacsStandardBase.csproj | 4 +- SacsStandardDbas/Dbas/Base/SacsDatabase.cs | 79 ++++++++++++- .../Dbas/Base/SacsDatabaseConsole.cs | 44 ++++---- .../CodeTypes/SaCodeTypeDbDfnGenerator.cs | 2 +- SacsStandardDbas/Dbas/Data/DbDataTable.cs | 28 ----- .../Dbas/Data/SacsDatabaseOdbc.cs | 15 +-- .../Interfaces/SaInterfaceDbDfnGenerator.cs | 2 +- .../Dbas/ItemData/SaItemDataDbDfnGenerator.cs | 2 +- .../Dbas/ItemDocs/SaItemDocsDbDfnGenerator.cs | 2 +- .../SaItemOptionsDbDfnGenerator.cs | 2 +- .../SaReferenceDataDbDfnGenerator.cs | 2 +- .../Dbas/Security/SaSecurityConsole_Dbas.cs | 66 ++++++++--- .../Dbas/Security/SaSecurityDbDfnGenerator.cs | 2 +- .../SaSecurityMgr_Dbas-SecurityItem.cs | 4 + .../Dbas/Security/SaSecurityMgr_Dbas.cs | 104 +++++++++++++++++- .../UpdateLog/SaUpdateLogDbDfnGenerator.cs | 2 +- SacsStandardProc/SacsStandardProc.csproj | 2 +- 30 files changed, 475 insertions(+), 265 deletions(-) delete mode 100644 SacsStandardBase/Base/Dbas/IDbDataTable.cs delete mode 100644 SacsStandardDbas/Dbas/Data/DbDataTable.cs diff --git a/SacsCoreWeb/Web/Components/SaController.cs b/SacsCoreWeb/Web/Components/SaController.cs index 0adac01..2fbd838 100644 --- a/SacsCoreWeb/Web/Components/SaController.cs +++ b/SacsCoreWeb/Web/Components/SaController.cs @@ -135,7 +135,7 @@ namespace Sacs.Web.Components // Serialise parameters as if in a URL query string - so that they can be deserialised in DecryptParameters() below. // Start with a random length parameter name at start of list to obscure the encryption. - string queryString = SacsRandomHelper.getRandomLengthNumber(4) + "&"; + string queryString = SacsRandomHelper.GetRandomLengthNumber(4) + "&"; queryString += String.Join("&", paramKeys.Select(a => a + "=" + HttpUtility.UrlEncode(parametersToEncrypt[a]))); // Encrypt data. @@ -286,7 +286,7 @@ namespace Sacs.Web.Components ConfirmMessage = new ConfirmMessageData() { Title = title, Text = text, Actions = actions }; return this; } - public bool HasConfirmMessage { get { return ConfirmMessage != null; } } + public bool? HasConfirmMessage { get { return ConfirmMessage != null ? true : null; } } } //------------------------------------------------------------------- diff --git a/SacsMobileMaui/SacsMobileMaui.csproj b/SacsMobileMaui/SacsMobileMaui.csproj index 3def31e..114683f 100644 --- a/SacsMobileMaui/SacsMobileMaui.csproj +++ b/SacsMobileMaui/SacsMobileMaui.csproj @@ -22,7 +22,6 @@ - diff --git a/SacsMobileXamarin/SacsMobileXamarin.csproj b/SacsMobileXamarin/SacsMobileXamarin.csproj index a74f7c4..4189384 100644 --- a/SacsMobileXamarin/SacsMobileXamarin.csproj +++ b/SacsMobileXamarin/SacsMobileXamarin.csproj @@ -7,7 +7,7 @@ - + diff --git a/SacsStandardBase/Base/Bus/SaErrorStateItem.cs b/SacsStandardBase/Base/Bus/SaErrorStateItem.cs index 288e7c1..539be0d 100644 --- a/SacsStandardBase/Base/Bus/SaErrorStateItem.cs +++ b/SacsStandardBase/Base/Bus/SaErrorStateItem.cs @@ -43,11 +43,16 @@ namespace Sarand.Net.Standard.Base.Bus //------------------------------------------------------------------- // Properties. + /// OK - No error state message. + public bool OK { get { return !HasErrorState(); } } + //------------------------------------------------------------------- - // E r r o r S t a t e + // Error State. //------------------------------------------------------------------- + private SacsErrorState errorState = null; - public bool HasErrorState () + + public bool HasErrorState() { return errorState != null; } diff --git a/SacsStandardBase/Base/Bus/SysId.cs b/SacsStandardBase/Base/Bus/SysId.cs index a09b694..c7b2c75 100644 --- a/SacsStandardBase/Base/Bus/SysId.cs +++ b/SacsStandardBase/Base/Bus/SysId.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; using System.Linq; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -111,6 +112,12 @@ namespace Sarand.Net.Standard.Base.Bus return (IsValid(sysId) ? sysId.id.ToString() : invalidValue); } + // From String. + public static SysId ToSysId(String value) + { + return new SysId(StringUtil.ToLong(value)); + } + //------------------------------------------------------------------- /// /// JSON Converter to serialise/deserialise SysId to/from simple integer value. diff --git a/SacsStandardBase/Base/Dbas/IDbDataTable.cs b/SacsStandardBase/Base/Dbas/IDbDataTable.cs deleted file mode 100644 index eb86c35..0000000 --- a/SacsStandardBase/Base/Dbas/IDbDataTable.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Sarand.Net.Standard.Base.Dbas -{ - //======================================================================= - public interface IDbDataTable - { - } - - //======================================================================= -} diff --git a/SacsStandardBase/Base/Dbas/ISacsDatabase.cs b/SacsStandardBase/Base/Dbas/ISacsDatabase.cs index 45a99e0..2ebba41 100644 --- a/SacsStandardBase/Base/Dbas/ISacsDatabase.cs +++ b/SacsStandardBase/Base/Dbas/ISacsDatabase.cs @@ -68,27 +68,39 @@ namespace Sarand.Net.Standard.Base.Dbas //------------------------------------------------------------------- // Database schema. - List GetTableNames (bool refresh = false); - List GetTableDfns (bool refresh = false); - DbDfnTable GetTableDfn (String name, bool refresh = false); - bool TableExists (String tableName); - bool ColumnExists (String tableName, String columnName); - bool IndexExists (String tableName, String indexName); + List GetTableNames(bool refresh = false); + List GetTableDfns(bool refresh = false); + DbDfnTable GetTableDfn(String name, bool refresh = false); + bool TableExists(String tableName); + bool ColumnExists(String tableName, String columnName); + bool IndexExists(String tableName, String indexName); // Database info - schema summary. IDatabaseInfo CreateDbInfo(); + //------------------------------------------------------------------- + // Password utilities. + public enum PasswordHashMethod { MsSql } + /// Generate password hash using the specified method. + String EncodePassword_Hex(String password, PasswordHashMethod method = PasswordHashMethod.MsSql); + /// Compare password to hashed value using the specified method. + bool ComparePassword_Hex(String password, String passwordHashHex, ISacsDatabase.PasswordHashMethod method = ISacsDatabase.PasswordHashMethod.MsSql); + //------------------------------------------------------------------- // Query records. - String GetSelectQueryPrefix (); - IDbDataTable ExecuteQuery (String query); - int ExecuteNonQuery (String query); - int ExecuteInsert (String tableName, List columnNameValues); - int ExecuteUpdate (String tableName, List columnNameValues, String filter); - int ExecuteDelete (String tableName, String filter); + String GetSelectQueryPrefix(); + /// + /// Execute query directly against database. + /// - NB Check for null response and error state. + /// + DbResultSet ExecuteQuery(String query); + int ExecuteNonQuery(String query); + int ExecuteInsert(String tableName, List columnNameValues); + int ExecuteUpdate(String tableName, List columnNameValues, String filter); + int ExecuteDelete(String tableName, String filter); // Specific queries. - long GetRowCount (String tableName, String filter); - DataValue GetRowValue (String tableName, String columnName, String filter); - DataValue GetRowValue (String tableName, String columnName, String filter, DbGetValueType getValueType); + long GetRowCount(String tableName, String filter); + DataValue GetRowValue(String tableName, String columnName, String filter); + DataValue GetRowValue(String tableName, String columnName, String filter, DbGetValueType getValueType); //------------------------------------------------------------------- } diff --git a/SacsStandardBase/Base/Helpers/SacsRandomHelper.cs b/SacsStandardBase/Base/Helpers/SacsRandomHelper.cs index 96624a9..576a83d 100644 --- a/SacsStandardBase/Base/Helpers/SacsRandomHelper.cs +++ b/SacsStandardBase/Base/Helpers/SacsRandomHelper.cs @@ -1,6 +1,7 @@ using Sarand.Net.Standard.Base.Utils; using System; using System.Collections.Generic; +using System.IO; using System.Security.Cryptography; using System.Text; @@ -52,19 +53,27 @@ namespace Sarand.Net.Standard.Base.Helpers } //------------------------------------------------------------------- - public static String getRandomLengthNumber(ushort maxLength=4) + public static String GetRandomLengthNumber(ushort maxLength = 4) { + String fn = "getRandomLengthNumber"; String value = ""; int len = GetInt32(1, maxLength + 1); for (int i = 0; i < len; i++) - value += GetInt32(0,10); - if (StringUtil.IsEmpty(value)) - throw new NotSupportedException("getRandomLengthNumber Generated blank value"); + value += GetInt32(0, 10); + if (StringUtil.IsBlank(value)) + throw new NotSupportedException($"{fn} Generated blank value"); if (value.Length > maxLength) - throw new NotSupportedException("getRandomLengthNumber Generated excess length value"); + throw new NotSupportedException($"{fn} Generated excess length value"); return value; } + //------------------------------------------------------------------- + /// Returns filename in 8.3 format. + public static String GetRandomFileName() + { + return Path.GetRandomFileName(); + } + //------------------------------------------------------------------- } //======================================================================= diff --git a/SacsStandardBase/Base/Security/SaSecurityConsole.cs b/SacsStandardBase/Base/Security/SaSecurityConsole.cs index f01368d..f2a45f5 100644 --- a/SacsStandardBase/Base/Security/SaSecurityConsole.cs +++ b/SacsStandardBase/Base/Security/SaSecurityConsole.cs @@ -21,6 +21,12 @@ namespace Sarand.Net.Standard.Base.Security Group = 3, Right = 4, } + // Password storage methods - must be kept up to date with legacy application. + public enum PasswordMethod + { + Legacy = 0, + MsSql = 10, + } public enum FilterAttrType { Code, // Partial code. @@ -29,14 +35,6 @@ namespace Sarand.Net.Standard.Base.Security Group_Exclude, // Item doesn't belong to group. }; } - // Password configuration. - public class PasswordConfig - { - /// Minimum password length. < 1 = no limit. - public int MinimumLength { get; protected set; } = 6; - /// Maximum password length. < 1 = no limit. - public int MaximumLength { get; protected set; } = 6; - } //======================================================================= public abstract class SaSecurityConsole : SaErrorStateItem @@ -58,8 +56,8 @@ namespace Sarand.Net.Standard.Base.Security //------------------------------------------------------------------- // Data members. - protected static String GroupSuperCode = "SUPER"; - protected static String AdminUserCode = "admin"; + public static String GroupCode_Super = "SUPER"; + public static String UserCode_Admin = "admin"; private static List securityItemTypes = new List(); //------------------------------------------------------------------- @@ -116,6 +114,12 @@ namespace Sarand.Net.Standard.Base.Security return userIdDefault; } + protected void ClearRegisteredSecurityRights() + { + RegisteredRights.Clear(); + DataItemDfns.Clear(); + } + //------------------------------------------------------------------- // L o g i n //------------------------------------------------------------------- @@ -140,9 +144,28 @@ namespace Sarand.Net.Standard.Base.Security /// Security Group modified event. public abstract bool OnSecurityGroupModified(SysId userId); + //------------------------------------------------------------------- + // Password utilities. //------------------------------------------------------------------- // Password configuration. - public PasswordConfig PasswordConfig { get; protected set; } = new PasswordConfig(); + public class SaPasswordConfig + { + /// Minimum password length. < 1 = no limit. + public int MinimumLength { get; protected set; } = 6; + /// Maximum password length. < 1 = no limit. + public int MaximumLength { get; protected set; } = 6; + } + public SaPasswordConfig PasswordConfig { get; protected set; } = new SaPasswordConfig(); + + /// Store the password against against the user. + public abstract bool SetPassword(SaSecurityItem securityItemUser, String password); + + /// Does the password match that currently defined against the user. + public abstract bool PasswordMatches(SaSecurityItem securityItemUser, String password); + + /// Is the password currently defined against the user blank. + public abstract bool PasswordIsBlank(SaSecurityItem securityItemUser); + /// Show form to change password for currently logged in user. /// Flag to indicate password changed public abstract bool ShowChangePasswordForm(DbUpdateInfo dbUpdateInfo); diff --git a/SacsStandardBase/Base/Security/SaSecurityItem.cs b/SacsStandardBase/Base/Security/SaSecurityItem.cs index 127244f..1248b74 100644 --- a/SacsStandardBase/Base/Security/SaSecurityItem.cs +++ b/SacsStandardBase/Base/Security/SaSecurityItem.cs @@ -12,6 +12,11 @@ namespace Sarand.Net.Standard.Base.Security //======================================================================= public abstract class SaSecurityItem : SacsHierarchyItem { + //------------------------------------------------------------------- + // Definitions. + // Password Salt constants - must be kept up to date with legacy application. + private static String PasswordSalt_MsSql = "MsSql"; + //------------------------------------------------------------------- // Columns. public enum Column @@ -67,7 +72,7 @@ namespace Sarand.Net.Standard.Base.Security // Data members. private readonly DataProperties data = new DataProperties(); // Construction. - public Data () + public Data() { Status = DefaultStatus; } @@ -161,7 +166,7 @@ namespace Sarand.Net.Standard.Base.Security set { SetFlag(Flag_AuthenticatedUser, value); } } // Population. - internal void PopulateFrom (Data data) + internal void PopulateFrom(Data data) { ItemType = data.ItemType; Code = data.Code; @@ -182,7 +187,7 @@ namespace Sarand.Net.Standard.Base.Security InvalidLoginCount = data.InvalidLoginCount; base.PopulateFrom(data); } - public Data Clone () + public Data Clone() { Data data = new Data(); data.PopulateFrom(this); @@ -219,7 +224,7 @@ namespace Sarand.Net.Standard.Base.Security //------------------------------------------------------------------- // Construction. - public SaSecurityItem (SysId id, SaSecurity.ItemType securityItemType) : + public SaSecurityItem(SysId id, SaSecurity.ItemType securityItemType) : base(id) { base.SetData(data); @@ -238,7 +243,7 @@ namespace Sarand.Net.Standard.Base.Security public static SaStatus EnabledStatus { get { return SaStatus.Enabled; } } public static SaStatus DisabledStatus { get { return SaStatus.Disabled; } } public static List ValidStatuses { get { return new List(securityItemStatuses); } } - public static DataValue.TriStateType GetStatusTriState (SaStatus status) + public static DataValue.TriStateType GetStatusTriState(SaStatus status) { switch (status) { @@ -254,8 +259,8 @@ namespace Sarand.Net.Standard.Base.Security //------------------------------------------------------------------- // Data properties. - public Data GetData () { return data.Clone(); } - public void SetData (Data data) + public Data GetData() { return data.Clone(); } + public void SetData(Data data) { // Properties that cannot be changed if previously specified. if (SysId.IsValid(Id) && SysId.ToInt(Id) != SysId.ToInt(data.Id)) @@ -359,68 +364,31 @@ namespace Sarand.Net.Standard.Base.Security //------------------------------------------------------------------- // Password utilities. - private static readonly Encoding Encoding_Ansi1252 = Encoding.GetEncoding(1252); - public bool IsPasswordBlank () - { - return EncryptPassword("").Equals(PasswordEncrypted); - } - public bool PasswordMatches (String password) - { - return PasswordEncrypted.Equals(EncryptPassword(password)); - } - public static String EncryptPassword (String password) - { - // We know this is not very water-tight, but it at least - // hides the contents of the password from prying eyes. - String toBeEncrypted = password; - // Password length goes in as first character: - toBeEncrypted = ByteToString((byte)(128 - password.Length)) + toBeEncrypted; - toBeEncrypted = string.Concat(toBeEncrypted.Reverse()); - // Pad out to fixed length (minimum 16, multiples of 8): - int fixedLength = 16; - if (password.Length / 8 > 2) fixedLength = 8 * (int)(password.Length / 8); - while (toBeEncrypted.Length < fixedLength) - { - char charToAdd = (char)(password.Length > 0 ? password[(toBeEncrypted.Length % password.Length)] : 'A' + (toBeEncrypted.Length * 13) % 23); - if (toBeEncrypted.Length % 2 != 0) - toBeEncrypted += charToAdd; - else toBeEncrypted = charToAdd + toBeEncrypted; - } - // Now perform a simple algorythm on each character to hide its true value: - byte[] encryptedChars = new byte[toBeEncrypted.Length]; - for (int i = 0; i < toBeEncrypted.Length; i++) - { - int iData = toBeEncrypted[i]; - iData += 17 + i; - if (iData > 255) iData -= (255 + 32); - encryptedChars[i] = (byte)iData; - } - String result = Encoding_Ansi1252.GetString(encryptedChars); -#if DEBUG - // Password: mgbgt - // To encrypt: tbmggtgbgm{btggm - // Encrypted: 133, 116, 128, 123, 124, 138, 126, 122, 128, 135, 150, 126, 145, 133, 134, 141 - //Logger.Info(fn + "\n\tPassword: " + password + "\n\tTo encrypt: " + toBeEncrypted + "\n\tEncrypted: " + encryptedChars.Select(i => i.ToString()).ToList().Concat(", ")); - //Logger.Debug(password + ": " + result); -#endif - return result; - } - private byte[] PasswordStringToBytes (String value) + /// Extract password storage method from data. + public SaSecurity.PasswordMethod GetPasswordMethod() { - byte[] values = new byte[value.Length]; - for (int idx = 0; idx < value.Length; idx++) - { - char c = value[idx]; - UInt32 ui = Convert.ToUInt32(c); - SaLogger.Info(idx + ". " + ui.ToString("x8")); - values[idx] = (byte)(c); - } - return values; + return SaSecurity.PasswordMethod.Legacy; } - private static String ByteToString (byte value) + + public void SetEncodedPassword(SaSecurity.PasswordMethod method, String encodedPassword) { - return Encoding_Ansi1252.GetString(new[] { value }); + if (method == SaSecurity.PasswordMethod.MsSql) + { +#pragma warning disable CS0618 // Deprecated property + PasswordEncrypted = ""; +#pragma warning restore CS0618 // Deprecated property + PasswordSalt = PasswordSalt_MsSql; + PasswordHash = encodedPassword; + } + else + { +#pragma warning disable CS0618 // Deprecated property + PasswordEncrypted = encodedPassword; +#pragma warning restore CS0618 // Deprecated property + PasswordSalt = ""; + PasswordHash = ""; + } } //------------------------------------------------------------------- @@ -455,7 +423,7 @@ namespace Sarand.Net.Standard.Base.Security //======================================================================= public static class SaSecurityItemList { - public static SaSecurityItem GetSecItemById (this List secItems, SysId secItemId) + public static SaSecurityItem GetSecItemById(this List secItems, SysId secItemId) { if (!SysId.IsValid(secItemId)) return null; foreach (SaSecurityItem secItem in secItems) diff --git a/SacsStandardBase/Base/Security/SaSecurityMgr.cs b/SacsStandardBase/Base/Security/SaSecurityMgr.cs index 6eac2f6..34e91f7 100644 --- a/SacsStandardBase/Base/Security/SaSecurityMgr.cs +++ b/SacsStandardBase/Base/Security/SaSecurityMgr.cs @@ -31,10 +31,19 @@ namespace Sarand.Net.Standard.Base.Security // Database properties. public abstract bool DatabaseIsValid { get; } public abstract bool CanUpdateDatabase { get; } + public abstract bool ColumnExists (SaSecurityDbDfn.ColumnIdx columnIdx); //------------------------------------------------------------------- - // Utilities. - public abstract bool ColumnExists (SaSecurityDbDfn.ColumnIdx columnIdx); + // Password utilities. + + /// Store the password against against the user. + public abstract bool SetPassword(SaSecurityItem securityItemUser, String password); + + /// Does the password match that currently defined against the user. + public abstract bool PasswordMatches(SaSecurityItem securityItemUser, String password); + + /// Is the password currently defined against the user blank. + public abstract bool PasswordIsBlank(SaSecurityItem securityItemUser); //------------------------------------------------------------------- } diff --git a/SacsStandardBase/Base/Security/SaSecurityProfile.cs b/SacsStandardBase/Base/Security/SaSecurityProfile.cs index 9d964eb..0e7701a 100644 --- a/SacsStandardBase/Base/Security/SaSecurityProfile.cs +++ b/SacsStandardBase/Base/Security/SaSecurityProfile.cs @@ -10,40 +10,52 @@ namespace Sarand.Net.Standard.Base.Security public class SaSecurityProfile { //------------------------------------------------------------------- + /// Security Group definition. public class GroupData { - public readonly List rights = new List(); - public GroupData (SaSecurityItem secGroup, List groupRights) + private readonly List rights = new List(); + public GroupData(SaSecurityItem secGroup, List groupRights) { - if (secGroup.ItemType != SaSecurity.ItemType.Group) throw new NotSupportedException("SaSecurityProfile.GroupData - Invalid security item type: "+ secGroup.ItemType.ToString()); - GroupId = secGroup.Id; - GroupCode = secGroup.Code; - GroupDesc = secGroup.Description; + if (secGroup.ItemType != SaSecurity.ItemType.Group) throw new NotSupportedException("SaSecurityProfile.GroupData - Invalid security item type: " + secGroup.ItemType.ToString()); + Id = secGroup.Id; + Code = secGroup.Code; + Desc = secGroup.Description; rights.AddRange(groupRights); } - protected GroupData () { } - public SysId GroupId { get; protected set; } - public String GroupCode { get; protected set; } - public String GroupDesc { get; protected set; } + protected GroupData() { } + public SysId Id { get; } + public String Code { get; } + public String Desc { get; } + // Security rights. public List Rights { get { return new List(rights); } } + /// + /// Retrieve the list of data items for which the user is explicitly granted the + /// specified security right, or where the right is explicitly denied if the flag is false. + /// + public List GetDataItemsForRight(String rightCode, bool granted = true) { return rights.GetDataItemsForRight(rightCode, granted); } + /// Is the Security Right code granted access for the Data item (if specified). + public bool IsSecurityRightGranted(String rightCode, SysId dataItemId = null) { return rights.IsSecurityRightGranted(rightCode, dataItemId); } + /// Is the Security Right code denined access for the Data item (if specified). + public bool IsSecurityRightDenied(String rightCode, SysId dataItemId = null) { return rights.IsSecurityRightDenied(rightCode, dataItemId); } } + /// Security Right definition. public class RightData { - public RightData (SaSecurityItem secRight, SaSecurityLink secRightLink) + public RightData(SaSecurityItem secRight, SaSecurityLink secRightLink) { if (secRight.ItemType != SaSecurity.ItemType.Right) throw new NotSupportedException("SaSecurityProfile.RightData - Invalid security item type: " + secRight.ItemType.ToString()); - RightId = secRight.Id; - RightCode = secRight.Code; - RightDesc = secRight.Description; + Id = secRight.Id; + Code = secRight.Code; + Desc = secRight.Description; DataItemId = secRightLink.ItemIdContext; Granted = (secRightLink.Status == SaStatus.Granted); } - protected RightData () { } - public SysId RightId { get; protected set; } - public String RightCode { get; protected set; } - public String RightDesc { get; protected set; } - public SysId DataItemId { get; protected set; } - public bool Granted { get; protected set; } + protected RightData() { } + public SysId Id { get; } + public String Code { get; } + public String Desc { get; } + public SysId DataItemId { get; } + public bool Granted { get; } } //------------------------------------------------------------------- @@ -53,7 +65,7 @@ namespace Sarand.Net.Standard.Base.Security //------------------------------------------------------------------- // Construction. - internal SaSecurityProfile (SaSecurityItem secItem, List profileGroups, List profileRights, bool systemAdmin) + internal SaSecurityProfile(SaSecurityItem secItem, List profileGroups, List profileRights, bool systemAdmin) { Clear(); if (secItem != null) @@ -115,9 +127,9 @@ namespace Sarand.Net.Standard.Base.Security //------------------------------------------------------------------- // Utilities. - public bool IsMemberOfSecurityGroup (SysId secGroupId) + public bool IsMemberOfSecurityGroup(SysId secGroupId) { - return securityGroups.Exists(item => item.GroupId.Equals(secGroupId)); + return securityGroups.Exists(item => item.Id.Equals(secGroupId)); } //------------------------------------------------------------------- @@ -125,14 +137,14 @@ namespace Sarand.Net.Standard.Base.Security /// Retrieve the list of data items for which the user is explicitly granted the /// specified security right, or where the right is explicitly denied if the flag is false. /// - public List GetDataItemsForRight (String rightCode, bool granted = true) + public List GetDataItemsForRight(String rightCode, bool granted = true) { return securityGroups.GetDataItemsForRight(rightCode, granted); } //------------------------------------------------------------------- /// Is the Security Right code granted access for the Data item (if specified). - public bool IsSecurityRightGranted (String rightCode, SysId dataItemId = null) + public bool IsSecurityRightGranted(String rightCode, SysId dataItemId = null) { if (SystemAdmin) return true; return (securityGroups.IsSecurityRightGranted(rightCode, dataItemId) || securityRights.IsSecurityRightGranted(rightCode, dataItemId)); @@ -140,7 +152,7 @@ namespace Sarand.Net.Standard.Base.Security //------------------------------------------------------------------- /// Is the Security Right code denined access for the Data item (if specified). - public bool IsSecurityRightDenied (String rightCode, SysId dataItemId = null) + public bool IsSecurityRightDenied(String rightCode, SysId dataItemId = null) { if (SystemAdmin) return false; return (securityGroups.IsSecurityRightDenied(rightCode, dataItemId) || securityRights.IsSecurityRightDenied(rightCode, dataItemId)); @@ -156,22 +168,22 @@ namespace Sarand.Net.Standard.Base.Security /// Retrieve the list of data items for which the user is explicitly granted the /// specified security right, or where the right is explicitly denied if the flag is false. /// - public static List GetDataItemsForRight (this List groups, String rightCode, bool granted = true) + public static List GetDataItemsForRight(this List groups, String rightCode, bool granted = true) { - List dataItemIds = new List(); + HashSet dataItemIds = new HashSet(); foreach (var securityGroup in groups) - dataItemIds.AddIds(securityGroup.rights.GetDataItemsForRight(rightCode,granted)); - return dataItemIds; + dataItemIds.AddIds(securityGroup.GetDataItemsForRight(rightCode, granted)); + return new List(dataItemIds); } /// Is the Security Right code granted access for the Data item (if specified). - public static bool IsSecurityRightGranted (this List groups, String rightCode, SysId dataItemId = null) + public static bool IsSecurityRightGranted(this List groups, String rightCode, SysId dataItemId = null) { - return groups.Exists(item => item.rights.IsSecurityRightGranted(rightCode, dataItemId)); + return groups.Exists(group => group.IsSecurityRightGranted(rightCode, dataItemId)); } /// Is the Security Right code denined access for the Data item (if specified). - public static bool IsSecurityRightDenied (this List groups, String rightCode, SysId dataItemId = null) + public static bool IsSecurityRightDenied(this List groups, String rightCode, SysId dataItemId = null) { - return groups.Exists(item => item.rights.IsSecurityRightDenied(rightCode, dataItemId)); + return groups.Exists(group => group.IsSecurityRightDenied(rightCode, dataItemId)); } } @@ -182,17 +194,17 @@ namespace Sarand.Net.Standard.Base.Security /// Retrieve the list of data items for which the user is explicitly granted the /// specified security right, or where the right is explicitly denied if the flag is false. /// - public static List GetDataItemsForRight (this List rights, String rightCode, bool granted = true) + public static List GetDataItemsForRight(this List rights, String rightCode, bool granted = true) { - List dataItemIds = new List(); - foreach (var securityRight in rights.FindAll(x => x.RightCode.EqualsCI(rightCode) && x.Granted == granted)) + HashSet dataItemIds = new HashSet(); + foreach (var securityRight in rights.FindAll(x => x.Code.EqualsCI(rightCode) && x.Granted == granted)) dataItemIds.AddId(securityRight.DataItemId); - return dataItemIds; + return new List(dataItemIds); } /// Is the Security Right code granted access for the Data item (if specified). - public static bool IsSecurityRightGranted (this List rights, String rightCode, SysId dataItemId = null) + public static bool IsSecurityRightGranted(this List rights, String rightCode, SysId dataItemId = null) { - foreach (var userSecurityRight in rights.FindAll(item => item.RightCode.EqualsCI(rightCode))) + foreach (var userSecurityRight in rights.FindAll(item => item.Code.EqualsCI(rightCode))) { if (SysId.ToInt(dataItemId) != SysId.ToInt(userSecurityRight.DataItemId)) continue; return userSecurityRight.Granted; @@ -200,9 +212,9 @@ namespace Sarand.Net.Standard.Base.Security return false; } /// Is the Security Right code denined access for the Data item (if specified). - public static bool IsSecurityRightDenied (this List rights, String rightCode, SysId dataItemId = null) + public static bool IsSecurityRightDenied(this List rights, String rightCode, SysId dataItemId = null) { - foreach (var userSecurityRight in rights.FindAll(item => item.RightCode.EqualsCI(rightCode))) + foreach (var userSecurityRight in rights.FindAll(item => item.Code.EqualsCI(rightCode))) { if (SysId.ToInt(dataItemId) != SysId.ToInt(userSecurityRight.DataItemId)) continue; return !userSecurityRight.Granted; diff --git a/SacsStandardBase/Base/Utils/StringUtil.cs b/SacsStandardBase/Base/Utils/StringUtil.cs index 60e6163..9c15cad 100644 --- a/SacsStandardBase/Base/Utils/StringUtil.cs +++ b/SacsStandardBase/Base/Utils/StringUtil.cs @@ -474,14 +474,16 @@ namespace Sarand.Net.Standard.Base.Utils } public static long ToLong(this string value, long defaultValue = 0) { - if (String.IsNullOrEmpty(value)) return defaultValue; - try + if (!String.IsNullOrEmpty(value)) { - Double returnValue = defaultValue; - if (Double.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out returnValue)) - return (Int64)returnValue; + try + { + Double returnValue = defaultValue; + if (Double.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out returnValue)) + return (Int64)returnValue; + } + catch (Exception) { } } - catch (Exception) { } return defaultValue; } public static short ToShort(Object value, short defaultValue = 0) @@ -494,22 +496,17 @@ namespace Sarand.Net.Standard.Base.Utils } public static long ToLong(Object value, long defaultValue = 0) { - if (value == null) return defaultValue; - try + if (value != null) { - return ToLong(value.ToString()); + try + { + return ToLong(value.ToString(), defaultValue); + } + catch (Exception) { } } - catch (Exception) { } return defaultValue; } - //--------------------------------------------------------------------------- - // SysId. - public static SysId ToSysId(this String value) - { - return new SysId(StringUtil.ToLong(value)); - } - //--------------------------------------------------------------------------- // Hex values to integer. /// Convert hex character to integer value. diff --git a/SacsStandardBase/SacsStandardBase.csproj b/SacsStandardBase/SacsStandardBase.csproj index c283e94..c0a6bbc 100644 --- a/SacsStandardBase/SacsStandardBase.csproj +++ b/SacsStandardBase/SacsStandardBase.csproj @@ -7,11 +7,11 @@ - + - + diff --git a/SacsStandardDbas/Dbas/Base/SacsDatabase.cs b/SacsStandardDbas/Dbas/Base/SacsDatabase.cs index 9f1b1ea..59620c1 100644 --- a/SacsStandardDbas/Dbas/Base/SacsDatabase.cs +++ b/SacsStandardDbas/Dbas/Base/SacsDatabase.cs @@ -61,6 +61,83 @@ namespace Sarand.Net.Standard.Dbas.Base public abstract int GetNextIdFromTable(string objectName, string idColumn = "Id"); public abstract bool SetMinNextId(String objectName, SysId id, DbUpdateInfo dbUpdateInfo, bool databaseTable = true, String idColumn = "Id", bool force = false); + //------------------------------------------------------------------- + // Password utilities. + //------------------------------------------------------------------- + /// + /// Generate password hash using the specified method. + /// - Returns hex notation as a string. + /// + /// + /// String of hex digits corresponding to encrypted password. + public String EncodePassword_Hex(String password, ISacsDatabase.PasswordHashMethod method = ISacsDatabase.PasswordHashMethod.MsSql) + { + ClearErrorState(); + if (method == ISacsDatabase.PasswordHashMethod.MsSql) + { + if (DbmsType == DbmsType.MsSql) + { + try + { + String colAlias = "DataValue"; + String query = $"Select {colAlias}=Convert(VarChar(2000),PwdEncrypt('{DbQuery.FormatForCondition(password)}'),2);"; + using (DbResultSet dbResultSet = DbResultSet_DataReader.ExecuteQuery(this, query)) + { + if (dbResultSet.NextRecord()) + return dbResultSet.GetRecordValues().GetValue(colAlias).ToString(); + else if (dbResultSet.HasErrorState()) + SetErrorState(dbResultSet.ErrorStateMessage); + } + } + catch (Exception exc) + { + SaLogger.Error($"{SysUtil.Fn} {ExceptionUtil.GetLogMessage(exc, "hashing password")}", true); + SetErrorState(exc.Message); + } + } + else SaLogger.Warn($"{SysUtil.Fn} Unable to encode password by method 'SQL'. Database is '{DbmsType}'."); + } + else SaLogger.Warn($"{SysUtil.Fn} Unable to encode password. Unrecognised method: '{method}'."); + return ""; + } + + /// + /// Compare the password to the encrypted hash using the specified method. + /// + /// + /// + /// True if the password matches the encrypted hash value. + public bool ComparePassword_Hex(String password, String passwordHashHex, ISacsDatabase.PasswordHashMethod method = ISacsDatabase.PasswordHashMethod.MsSql) + { + ClearErrorState(); + if (method == ISacsDatabase.PasswordHashMethod.MsSql) + { + if (DbmsType == DbmsType.MsSql) + { + try + { + String colAlias = "PwdCompare"; + String query = $"Select {colAlias}=PwdCompare('{DbQuery.FormatForCondition(password)}', Convert(VarBinary(1000),'{DbQuery.FormatForCondition(passwordHashHex)}',2));"; + using (DbResultSet dbResultSet = DbResultSet_DataReader.ExecuteQuery(this, query)) + { + if (dbResultSet.NextRecord()) + return StringUtil.ToBool(dbResultSet.GetRecordValues().GetValue(colAlias).ToString()); + else if (dbResultSet.HasErrorState()) + SetErrorState(dbResultSet.ErrorStateMessage); + } + } + catch (Exception exc) + { + SaLogger.Error($"{SysUtil.Fn} {ExceptionUtil.GetLogMessage(exc, "comparing password")}", true); + SetErrorState(exc.Message); + } + } + else SaLogger.Warn($"{SysUtil.Fn} Unable to compare password by method 'SQL'. Database is '{DbmsType}'."); + } + else SaLogger.Warn($"{SysUtil.Fn} Unable to compare password. Unrecognised method: '{method}'."); + return false; + } + //------------------------------------------------------------------- // O D B C D r i v e r s //------------------------------------------------------------------- @@ -184,7 +261,7 @@ namespace Sarand.Net.Standard.Dbas.Base } return ""; } - public abstract IDbDataTable ExecuteQuery(String query); + public abstract DbResultSet ExecuteQuery(String query); public abstract int ExecuteNonQuery(String query); public abstract int ExecuteInsert(String tableName, List columnNameValues); public abstract int ExecuteUpdate(String tableName, List columnNameValues, String filter); diff --git a/SacsStandardDbas/Dbas/Base/SacsDatabaseConsole.cs b/SacsStandardDbas/Dbas/Base/SacsDatabaseConsole.cs index f4e43ed..6e25857 100644 --- a/SacsStandardDbas/Dbas/Base/SacsDatabaseConsole.cs +++ b/SacsStandardDbas/Dbas/Base/SacsDatabaseConsole.cs @@ -17,9 +17,9 @@ namespace Sarand.Net.Standard.Dbas.Base // Database configuration public class OdbcConfig { - public DbmsType dbms = DbmsType.General; - public String connectionString; // Raw connection string. - public String location, user, pword; // Application properties. + public DbmsType Dbms = DbmsType.General; + public String ConnectionString; // Raw connection string. + public String Location, User, Password; // Connection properties. /// Database name populated when connection string is generated if not done previously. public String DatabaseName { get; set; } } @@ -42,9 +42,9 @@ namespace Sarand.Net.Standard.Dbas.Base //------------------------------------------------------------------- public static DbmsType GetDbmsType(OdbcConfig config) { - if (config.dbms == DbmsType.MsSql || config.location.Left(6).EqualsCI("MsSql:")) + if (config.Dbms == DbmsType.MsSql || config.Location.Left(6).EqualsCI("MsSql:")) return DbmsType.MsSql; - return config.dbms; + return config.Dbms; } public static String GenerateConnectionString(OdbcConfig config, bool dbmsSpecificConnectionString = false) { @@ -58,7 +58,7 @@ namespace Sarand.Net.Standard.Dbas.Base // MsSql:Server:Port\Instance.Database dbms = DbmsType.MsSql; String sqlDriver = OdbcUtil.GetOdbcSqlDriverName(); - String sqlLocation = config.location.Left(6).EqualsCI("MsSql:") ? config.location.Substring(6) : config.location; + String sqlLocation = config.Location.Left(6).EqualsCI("MsSql:") ? config.Location.Substring(6) : config.Location; String sqlServer = "."; /* Assume local host */ String sqlInstance = ""; String sqlPort = ""; @@ -99,17 +99,17 @@ namespace Sarand.Net.Standard.Dbas.Base // WsId=; // Server=[protocol:]Server[,port][\instancename]; // Database=; - // Trusted_Connection=; + // Trusted_Connection=; [or Integrated Security=SSPI;] // UseProcForPrepare=2 connectionString = "Driver={" + sqlDriver + "};"; connectionString += "Server=" + sqlServer + ";"; connectionString += "Database=" + sqlDatabase + ";"; - if (!config.user.IsEmpty()) + if (!config.User.IsEmpty()) { - connectionString += "UID=" + config.user + ";"; - connectionString += "PWD=" + config.pword + ";"; + connectionString += "UID=" + config.User + ";"; + connectionString += "PWD=" + config.Password + ";"; } - connectionString += "Trusted_Connection=" + (config.user.IsEmpty() ? "Yes" : "No") + ";"; + connectionString += "Trusted_Connection=" + (config.User.IsEmpty() ? "Yes" : "No") + ";"; // Mar24 Added to avoid 'The certificate chain was issued by an authority that is not trusted'. connectionString += "TrustServerCertificate=Yes;"; // Multiple Active Result Sets. @@ -125,23 +125,23 @@ namespace Sarand.Net.Standard.Dbas.Base connectionString = ""; connectionString += "Server=" + sqlServer + ";"; connectionString += "Database=" + sqlDatabase + ";"; - if (!config.user.IsEmpty()) + if (!config.User.IsEmpty()) { - connectionString += "User Id=" + config.user + ";"; - connectionString += "Password=" + config.pword + ";"; + connectionString += "User Id=" + config.User + ";"; + connectionString += "Password=" + config.Password + ";"; } - connectionString += "Trusted_Connection=" + (config.user.IsEmpty() ? "Yes" : "No") + ";"; + connectionString += "Trusted_Connection=" + (config.User.IsEmpty() ? "Yes" : "No") + ";"; // Mar24 Added to avoid 'The certificate chain was issued by an authority that is not trusted'. connectionString += "TrustServerCertificate=Yes;"; } // Populate configuration values if blank. - if (config.dbms <= 0) - config.dbms = dbms; + if (config.Dbms <= 0) + config.Dbms = dbms; if (StringUtil.IsEmpty(config.DatabaseName)) config.DatabaseName = sqlDatabase; - SaLogger.Info($"{SysUtil.Fn} Driver: " + sqlDriver + ". Server: " + sqlServer + ". Database: " + sqlDatabase + ". Connection: " + connectionString); + //SaLogger.Debug($"{SysUtil.Fn} Driver: " + sqlDriver + ". Server: " + sqlServer + ". Database: " + sqlDatabase + ". Connection: " + connectionString); } return connectionString; } @@ -152,17 +152,17 @@ namespace Sarand.Net.Standard.Dbas.Base public static SacsDatabase OpenOdbcDatabase(OdbcConfig config) { // Populate configuration. - config.dbms = GetDbmsType(config); + config.Dbms = GetDbmsType(config); // Connection strings. - String connectionString = config.connectionString; + String connectionString = config.ConnectionString; if (StringUtil.IsEmpty(connectionString)) connectionString = GenerateConnectionString(config); String sqlConnectionString = GenerateConnectionString(config, true); // Open database. - DbGlobalDbms.Type = config.dbms; - SacsDatabaseOdbc database = new SacsDatabaseOdbc(config.dbms, config.DatabaseName, connectionString); + DbGlobalDbms.Type = config.Dbms; + SacsDatabaseOdbc database = new SacsDatabaseOdbc(config.Dbms, config.DatabaseName, connectionString); database.SelectLockMode = SelectLockMode.ReadUncommitted; database.SqlConnectionString = sqlConnectionString; return database; diff --git a/SacsStandardDbas/Dbas/CodeTypes/SaCodeTypeDbDfnGenerator.cs b/SacsStandardDbas/Dbas/CodeTypes/SaCodeTypeDbDfnGenerator.cs index 09708bd..65f669d 100644 --- a/SacsStandardDbas/Dbas/CodeTypes/SaCodeTypeDbDfnGenerator.cs +++ b/SacsStandardDbas/Dbas/CodeTypes/SaCodeTypeDbDfnGenerator.cs @@ -29,7 +29,7 @@ namespace Sarand.Net.Standard.Dbas.CodeType // Open database. // NB Must be ODBC connection - especially to retrieve index columns. SacsDatabaseConsole.OdbcConfig config = new SacsDatabaseConsole.OdbcConfig(); - config.connectionString = connectionString; + config.ConnectionString = connectionString; SacsDatabase database = SacsDatabaseConsole.OpenOdbcDatabase(config); // Generate database definition. diff --git a/SacsStandardDbas/Dbas/Data/DbDataTable.cs b/SacsStandardDbas/Dbas/Data/DbDataTable.cs deleted file mode 100644 index 1900419..0000000 --- a/SacsStandardDbas/Dbas/Data/DbDataTable.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Data; -using Sarand.Net.Standard.Base.Dbas; - -namespace Sarand.Net.Standard.Dbas.Data -{ - //======================================================================= - /// - /// Wrapper for System.Data.DataTable - /// - public class DbDataTable : IDbDataTable - { - //------------------------------------------------------------------- - // Data members. - private DataTable dataTable; - - //------------------------------------------------------------------- - // Construction. - public DbDataTable (DataTable dataTable) - { - this.dataTable = dataTable; - } - - //------------------------------------------------------------------- - - //------------------------------------------------------------------- - } - //======================================================================= -} diff --git a/SacsStandardDbas/Dbas/Data/SacsDatabaseOdbc.cs b/SacsStandardDbas/Dbas/Data/SacsDatabaseOdbc.cs index 955fc2d..9799229 100644 --- a/SacsStandardDbas/Dbas/Data/SacsDatabaseOdbc.cs +++ b/SacsStandardDbas/Dbas/Data/SacsDatabaseOdbc.cs @@ -171,29 +171,20 @@ namespace Sarand.Net.Standard.Dbas.Data // Q u e r y D a t a b a s e //------------------------------------------------------------------- // Query records. - public override IDbDataTable ExecuteQuery(String query) + public override DbResultSet ExecuteQuery(String query) { ClearErrorState(); - DataTable dataTable = null; Log(SysUtil.Fn, "Query: " + query, SaMessageLevel.Debug); try { - using (DbConnection dbConnection = CreateConnection()) - using (var dbCommand = dbConnection.CreateCommand()) - { - dbConnection.Open(); - dbCommand.CommandType = System.Data.CommandType.Text; - dbCommand.CommandText = query; - dataTable = new DataTable(); - dataTable.Load(dbCommand.ExecuteReader()); - } + return DbResultSet_DataReader.ExecuteQuery(this, query); } catch (Exception exc) { SaLogger.Error($"{SysUtil.Fn} {ExceptionUtil.GetLogMessage(exc)}\nQuery:\n\t{String.Join("\n\t", query.ReplaceAll("\n\r", "\n").Split("\n"))}", true); SetErrorState(exc.Message); } - return new DbDataTable(dataTable); + return null; } //------------------------------------------------------------------- /// Execute a non-query statement and return the row count. diff --git a/SacsStandardDbas/Dbas/Interfaces/SaInterfaceDbDfnGenerator.cs b/SacsStandardDbas/Dbas/Interfaces/SaInterfaceDbDfnGenerator.cs index 25e757e..5c022da 100644 --- a/SacsStandardDbas/Dbas/Interfaces/SaInterfaceDbDfnGenerator.cs +++ b/SacsStandardDbas/Dbas/Interfaces/SaInterfaceDbDfnGenerator.cs @@ -32,7 +32,7 @@ namespace Sarand.Net.Standard.Dbas.Interface // Open database. // NB Must be ODBC connection - especially to retrieve index columns. SacsDatabaseConsole.OdbcConfig config = new SacsDatabaseConsole.OdbcConfig(); - config.connectionString = connectionString; + config.ConnectionString = connectionString; SacsDatabase database = SacsDatabaseConsole.OpenOdbcDatabase(config); // Generate database definition. diff --git a/SacsStandardDbas/Dbas/ItemData/SaItemDataDbDfnGenerator.cs b/SacsStandardDbas/Dbas/ItemData/SaItemDataDbDfnGenerator.cs index 7296233..20e4bd1 100644 --- a/SacsStandardDbas/Dbas/ItemData/SaItemDataDbDfnGenerator.cs +++ b/SacsStandardDbas/Dbas/ItemData/SaItemDataDbDfnGenerator.cs @@ -31,7 +31,7 @@ namespace Sarand.Net.Standard.Dbas.ItemData // Open database. // NB Must be ODBC connection - especially to retrieve index columns. SacsDatabaseConsole.OdbcConfig config = new SacsDatabaseConsole.OdbcConfig(); - config.connectionString = connectionString; + config.ConnectionString = connectionString; SacsDatabase database = SacsDatabaseConsole.OpenOdbcDatabase(config); // Generate database definition. diff --git a/SacsStandardDbas/Dbas/ItemDocs/SaItemDocsDbDfnGenerator.cs b/SacsStandardDbas/Dbas/ItemDocs/SaItemDocsDbDfnGenerator.cs index 667ab36..2ad09ba 100644 --- a/SacsStandardDbas/Dbas/ItemDocs/SaItemDocsDbDfnGenerator.cs +++ b/SacsStandardDbas/Dbas/ItemDocs/SaItemDocsDbDfnGenerator.cs @@ -35,7 +35,7 @@ namespace Sarand.Net.Standard.Dbas.ItemDocs // Open database. // NB Must be ODBC connection - especially to retrieve index columns. SacsDatabaseConsole.OdbcConfig config = new SacsDatabaseConsole.OdbcConfig(); - config.connectionString = connectionString; + config.ConnectionString = connectionString; SacsDatabase database = SacsDatabaseConsole.OpenOdbcDatabase(config); // Generate database definition. diff --git a/SacsStandardDbas/Dbas/ItemOptions/SaItemOptionsDbDfnGenerator.cs b/SacsStandardDbas/Dbas/ItemOptions/SaItemOptionsDbDfnGenerator.cs index 51d3a36..e5e3612 100644 --- a/SacsStandardDbas/Dbas/ItemOptions/SaItemOptionsDbDfnGenerator.cs +++ b/SacsStandardDbas/Dbas/ItemOptions/SaItemOptionsDbDfnGenerator.cs @@ -31,7 +31,7 @@ namespace Sarand.Net.Standard.Dbas.ItemOptions // Open database. // NB Must be ODBC connection - especially to retrieve index columns. SacsDatabaseConsole.OdbcConfig config = new SacsDatabaseConsole.OdbcConfig(); - config.connectionString = connectionString; + config.ConnectionString = connectionString; SacsDatabase database = SacsDatabaseConsole.OpenOdbcDatabase(config); // Generate database definition. diff --git a/SacsStandardDbas/Dbas/ReferenceData/SaReferenceDataDbDfnGenerator.cs b/SacsStandardDbas/Dbas/ReferenceData/SaReferenceDataDbDfnGenerator.cs index a024e5b..ce9c93f 100644 --- a/SacsStandardDbas/Dbas/ReferenceData/SaReferenceDataDbDfnGenerator.cs +++ b/SacsStandardDbas/Dbas/ReferenceData/SaReferenceDataDbDfnGenerator.cs @@ -31,7 +31,7 @@ namespace Sarand.Net.Standard.Dbas.ReferenceData // Open database. // NB Must be ODBC connection - especially to retrieve index columns. SacsDatabaseConsole.OdbcConfig config = new SacsDatabaseConsole.OdbcConfig(); - config.connectionString = connectionString; + config.ConnectionString = connectionString; SacsDatabase database = SacsDatabaseConsole.OpenOdbcDatabase(config); // Generate database definition. diff --git a/SacsStandardDbas/Dbas/Security/SaSecurityConsole_Dbas.cs b/SacsStandardDbas/Dbas/Security/SaSecurityConsole_Dbas.cs index d358b58..e25b3ae 100644 --- a/SacsStandardDbas/Dbas/Security/SaSecurityConsole_Dbas.cs +++ b/SacsStandardDbas/Dbas/Security/SaSecurityConsole_Dbas.cs @@ -73,6 +73,28 @@ namespace Sarand.Net.Standard.Dbas.Security } //------------------------------------------------------------------- + // Password utilities. + //------------------------------------------------------------------- + + /// Store the password against against the user. + public override bool SetPassword(SaSecurityItem securityItemUser, String password) + { + return securityMgr.SetPassword(securityItemUser, password); + } + + /// Does the password match that currently defined against the user. + public override bool PasswordMatches(SaSecurityItem securityItemUser, String password) + { + return securityMgr.PasswordMatches(securityItemUser, password); + } + + /// Is the password currently defined against the user blank. + public override bool PasswordIsBlank(SaSecurityItem securityItemUser) + { + return securityMgr.PasswordIsBlank(securityItemUser); + } + + /// Change Password form - desktop only. public override bool ShowChangePasswordForm(DbUpdateInfo dbUpdateInfo) { ClearErrorState(); @@ -112,16 +134,16 @@ namespace Sarand.Net.Standard.Dbas.Security if (!HasErrorState()) PopulateSuperGroup(dbUpdateInfo); // Create the 'admin' user. if (!HasErrorState()) PopulateAdminUser(dbUpdateInfo); - return !HasErrorState(); + return OK; } private bool PopulateSuperGroup(DbUpdateInfo dbUpdateInfo) { ClearErrorState(); - SaSecurityItem superGroup = securityMgr.GetSecurityItemByTypeCode(SaSecurity.ItemType.Group, GroupSuperCode); + SaSecurityItem superGroup = securityMgr.GetSecurityItemByTypeCode(SaSecurity.ItemType.Group, GroupCode_Super); if (superGroup == null) { superGroup = securityMgr.CreateSecurityItem(SaSecurity.ItemType.Group); - superGroup.Code = GroupSuperCode; + superGroup.Code = GroupCode_Super; superGroup.Description = "Superuser"; if (!securityMgr.InsertSecurityItem(superGroup, dbUpdateInfo)) return SetErrorState(securityMgr.ErrorStateMessage); @@ -143,29 +165,29 @@ namespace Sarand.Net.Standard.Dbas.Security } } } - return !HasErrorState(); + return OK; } private bool PopulateAdminUser(DbUpdateInfo dbUpdateInfo) { ClearErrorState(); SaSecurityItem user = null; - if (!securityMgr.SecurityItemExistsByTypeCode(SaSecurity.ItemType.User, AdminUserCode)) + if (!securityMgr.SecurityItemExistsByTypeCode(SaSecurity.ItemType.User, UserCode_Admin)) { // Create the admin user. user = securityMgr.CreateSecurityItem(SaSecurity.ItemType.User); - user.Code = AdminUserCode; + user.Code = UserCode_Admin; user.Description = ""; user.Location = ""; - user.PasswordEncrypted = SaSecurityItem.EncryptPassword(AdminUserCode); + securityMgr.SetPassword(user, UserCode_Admin); if (!securityMgr.InsertSecurityItem(user, dbUpdateInfo)) return SetErrorState(securityMgr.ErrorStateMessage); } // Retrieve the user (even if just created). - user = securityMgr.GetSecurityItemByTypeCode(SaSecurity.ItemType.User, AdminUserCode); + user = securityMgr.GetSecurityItemByTypeCode(SaSecurity.ItemType.User, UserCode_Admin); if (user != null) { // Give this user superuser rights. - SaSecurityItem superGroup = securityMgr.GetSecurityItemByTypeCode(SaSecurity.ItemType.Group, GroupSuperCode); + SaSecurityItem superGroup = securityMgr.GetSecurityItemByTypeCode(SaSecurity.ItemType.Group, GroupCode_Super); if (superGroup != null) { if (!securityMgr.SecurityItemHasLinksWith(user.Id, superGroup.Id)) @@ -176,7 +198,7 @@ namespace Sarand.Net.Standard.Dbas.Security } } } - return !HasErrorState(); + return OK; } //------------------------------------------------------------------- @@ -234,7 +256,7 @@ namespace Sarand.Net.Standard.Dbas.Security } OnLogin(secItem != null ? secItem.Id : null); - return !HasErrorState(); + return OK; } //------------------------------------------------------------------- @@ -534,19 +556,35 @@ namespace Sarand.Net.Standard.Dbas.Security if (!securityMgr.InsertSecurityItem(secItem, dbUpdateInfo)) return SetErrorState(securityMgr.ErrorStateMessage); - return !HasErrorState(); + return OK; } public override bool AssignUserToGroup(string userCode, string groupCode, DbUpdateInfo dbUpdateInfo) { ClearErrorState(); - throw new NotImplementedException(); + // Validate environment. + if (securityMgr == null) return SetErrorState("security manager is not available"); + if (!securityMgr.DatabaseIsValid) return SetErrorState("database has an invalid security profile"); + // User security item. + SaSecurityItem securityItemUser = securityMgr.GetSecurityItemByTypeCode(SaSecurity.ItemType.User, userCode); + if (securityItemUser == null) return SetErrorState(("user does not exist")); + // Group security item. + SysId securityGroupId = GetIdForItemCode(SaSecurity.ItemType.Group, groupCode); + if (!SysId.IsValid(securityGroupId)) return SetErrorState(("invalid group code")); + if (!ItemsAreLinked(securityItemUser.Id, securityGroupId)) + { + SaSecurityLink securityLinkUserGroup = securityMgr.CreateSecurityLink(securityItemUser.Id, securityGroupId, new SysId()); + if (securityLinkUserGroup == null) return SetErrorState($"failed to create user-group ({securityMgr.ErrorStateMessage})"); + if (!securityMgr.InsertSecurityLink(securityLinkUserGroup, dbUpdateInfo)) + return SetErrorState($"failed to add user to group ({securityMgr.ErrorStateMessage})"); + } + return OK; } public override bool UpdateSecurityItem(SaSecurityItem securityItem, DbUpdateInfo dbUpdateInfo) { ClearErrorState(); if (!securityMgr.UpdateSecurityItem(securityItem, dbUpdateInfo)) return SetErrorState(securityMgr.ErrorStateMessage); - return !HasErrorState(); + return OK; } //------------------------------------------------------------------- diff --git a/SacsStandardDbas/Dbas/Security/SaSecurityDbDfnGenerator.cs b/SacsStandardDbas/Dbas/Security/SaSecurityDbDfnGenerator.cs index 43603f1..e0a9b7c 100644 --- a/SacsStandardDbas/Dbas/Security/SaSecurityDbDfnGenerator.cs +++ b/SacsStandardDbas/Dbas/Security/SaSecurityDbDfnGenerator.cs @@ -31,7 +31,7 @@ namespace Sarand.Net.Standard.Dbas.Security // Open database. // NB Must be ODBC connection - especially to retrieve index columns. SacsDatabaseConsole.OdbcConfig config = new SacsDatabaseConsole.OdbcConfig(); - config.connectionString = connectionString; + config.ConnectionString = connectionString; SacsDatabase database = SacsDatabaseConsole.OpenOdbcDatabase(config); // Generate database definition. diff --git a/SacsStandardDbas/Dbas/Security/SaSecurityMgr_Dbas-SecurityItem.cs b/SacsStandardDbas/Dbas/Security/SaSecurityMgr_Dbas-SecurityItem.cs index 1065248..6edb43b 100644 --- a/SacsStandardDbas/Dbas/Security/SaSecurityMgr_Dbas-SecurityItem.cs +++ b/SacsStandardDbas/Dbas/Security/SaSecurityMgr_Dbas-SecurityItem.cs @@ -278,7 +278,9 @@ namespace Sarand.Net.Standard.Dbas.Security dbSecurityItem._Code = securityItem.Code; dbSecurityItem._Description = securityItem.Description; dbSecurityItem._Location = securityItem.Location; +#pragma warning disable CS0618 // Deprecated property dbSecurityItem._EncPword = securityItem.PasswordEncrypted; +#pragma warning restore CS0618 // Deprecated property dbSecurityItem._PwordSalt = securityItem.PasswordSalt; dbSecurityItem._PwordHash = securityItem.PasswordHash; dbSecurityItem._LastLogin = securityItem.LastLogin; @@ -304,7 +306,9 @@ namespace Sarand.Net.Standard.Dbas.Security securityItem.EmailAddress = dbSecurityItem._EmailAddress; securityItem.Description = dbSecurityItem._Description; securityItem.Location = dbSecurityItem._Location; +#pragma warning disable CS0618 // Deprecated property securityItem.PasswordEncrypted = dbSecurityItem._EncPword; +#pragma warning restore CS0618 // Deprecated property securityItem.PasswordSalt = dbSecurityItem._PwordSalt; securityItem.PasswordHash = dbSecurityItem._PwordHash; securityItem.LastLogin = dbSecurityItem._LastLogin; diff --git a/SacsStandardDbas/Dbas/Security/SaSecurityMgr_Dbas.cs b/SacsStandardDbas/Dbas/Security/SaSecurityMgr_Dbas.cs index a6fec1f..6ca2266 100644 --- a/SacsStandardDbas/Dbas/Security/SaSecurityMgr_Dbas.cs +++ b/SacsStandardDbas/Dbas/Security/SaSecurityMgr_Dbas.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text; +using Sarand.Net.Standard.Base.App; using Sarand.Net.Standard.Base.Dbas; using Sarand.Net.Standard.Base.Security; using Sarand.Net.Standard.Dbas.Data; @@ -15,7 +17,7 @@ namespace Sarand.Net.Standard.Dbas.Security //------------------------------------------------------------------- // Construction. - internal SaSecurityMgr_Dbas (SaSecurityConsole_Dbas securityConsole) : base() + internal SaSecurityMgr_Dbas(SaSecurityConsole_Dbas securityConsole) : base() { SecurityConsole = securityConsole; DbInfo = new DbDataItem.DatabaseInfo(securityConsole.Database); @@ -31,15 +33,109 @@ namespace Sarand.Net.Standard.Dbas.Security public override bool DatabaseIsValid { get { return (Database != null && Database.GetTableNames().Contains(TableName_SecItem) && Database.GetTableNames().Contains(TableName_SecLink)); } } public override bool CanUpdateDatabase { get { return DatabaseIsValid; } } - //------------------------------------------------------------------- - // Utilities. - public override bool ColumnExists (SaSecurityDbDfn.ColumnIdx columnIdx) + // Database utilities. + public override bool ColumnExists(SaSecurityDbDfn.ColumnIdx columnIdx) { String tableName = SaSecurityDbDfn.GetTableName(SaSecurityDbDfn.GetTableForColumn(columnIdx), false); String columnName = SaSecurityDbDfn.GetColumnName(columnIdx, false); return Database.ColumnExists(tableName, columnName); } + //------------------------------------------------------------------- + // Password utilities. + + public override bool PasswordIsBlank(SaSecurityItem securityItemUser) + { + if (securityItemUser == null) return false; + SaSecurity.PasswordMethod method = securityItemUser.GetPasswordMethod(); + // MsSql method. + if (method == SaSecurity.PasswordMethod.MsSql) + { + //... + } + // Legacy. +#pragma warning disable CS0618 // Deprecated property + return EncryptPassword_Legacy("").Equals(securityItemUser.PasswordEncrypted); +#pragma warning restore CS0618 // Deprecated property + } + + public override bool PasswordMatches(SaSecurityItem securityItemUser, String password) + { + if (securityItemUser == null) return false; + SaSecurity.PasswordMethod method = securityItemUser.GetPasswordMethod(); + // MsSql method. + if (method == SaSecurity.PasswordMethod.MsSql) + { + //... + } + // Legacy. +#pragma warning disable CS0618 // Deprecated property + return (EncryptPassword_Legacy(password).Equals(securityItemUser.PasswordEncrypted)); +#pragma warning restore CS0618 // Deprecated property + } + + /// Store the password against against the user. + public override bool SetPassword(SaSecurityItem securityItemUser, String password) + { + return false; + } + + internal static String EncryptPassword_Legacy(String password) + { + // We know this is not very water-tight, but it at least + // hides the contents of the password from prying eyes. + String toBeEncrypted = password; + // Password length goes in as first character: + toBeEncrypted = ByteToString((byte)(128 - password.Length)) + toBeEncrypted; + toBeEncrypted = string.Concat(toBeEncrypted.Reverse()); + // Pad out to fixed length (minimum 16, multiples of 8): + int fixedLength = 16; + if (password.Length / 8 > 2) fixedLength = 8 * (int)(password.Length / 8); + while (toBeEncrypted.Length < fixedLength) + { + char charToAdd = (char)(password.Length > 0 ? password[(toBeEncrypted.Length % password.Length)] : 'A' + (toBeEncrypted.Length * 13) % 23); + if (toBeEncrypted.Length % 2 != 0) + toBeEncrypted += charToAdd; + else toBeEncrypted = charToAdd + toBeEncrypted; + } + + // Now perform a simple algorythm on each character to hide its true value: + byte[] encryptedChars = new byte[toBeEncrypted.Length]; + for (int i = 0; i < toBeEncrypted.Length; i++) + { + int iData = toBeEncrypted[i]; + iData += 17 + i; + if (iData > 255) iData -= (255 + 32); + encryptedChars[i] = (byte)iData; + } + String result = Encoding_Ansi1252.GetString(encryptedChars); +#if DEBUG + // Password: mgbgt + // To encrypt: tbmggtgbgm{btggm + // Encrypted: 133, 116, 128, 123, 124, 138, 126, 122, 128, 135, 150, 126, 145, 133, 134, 141 + //Logger.Info(fn + "\n\tPassword: " + password + "\n\tTo encrypt: " + toBeEncrypted + "\n\tEncrypted: " + encryptedChars.Select(i => i.ToString()).ToList().Concat(", ")); + //Logger.Debug(password + ": " + result); +#endif + return result; + } + private static byte[] PasswordStringToBytes(String value) + { + byte[] values = new byte[value.Length]; + for (int idx = 0; idx < value.Length; idx++) + { + char c = value[idx]; + UInt32 ui = Convert.ToUInt32(c); + SaLogger.Info(idx + ". " + ui.ToString("x8")); + values[idx] = (byte)(c); + } + return values; + } + private static readonly Encoding Encoding_Ansi1252 = Encoding.GetEncoding(1252); + private static String ByteToString(byte value) + { + return Encoding_Ansi1252.GetString(new[] { value }); + } + //------------------------------------------------------------------- } //======================================================================= diff --git a/SacsStandardDbas/Dbas/UpdateLog/SaUpdateLogDbDfnGenerator.cs b/SacsStandardDbas/Dbas/UpdateLog/SaUpdateLogDbDfnGenerator.cs index 63c436f..f5c83f9 100644 --- a/SacsStandardDbas/Dbas/UpdateLog/SaUpdateLogDbDfnGenerator.cs +++ b/SacsStandardDbas/Dbas/UpdateLog/SaUpdateLogDbDfnGenerator.cs @@ -31,7 +31,7 @@ namespace Sarand.Net.Standard.Dbas.UpdateLog // Open database. // NB Must be ODBC connection - especially to retrieve index columns. SacsDatabaseConsole.OdbcConfig config = new SacsDatabaseConsole.OdbcConfig(); - config.connectionString = connectionString; + config.ConnectionString = connectionString; SacsDatabase database = SacsDatabaseConsole.OpenOdbcDatabase(config); // Generate database definition. diff --git a/SacsStandardProc/SacsStandardProc.csproj b/SacsStandardProc/SacsStandardProc.csproj index 252978d..c3edb22 100644 --- a/SacsStandardProc/SacsStandardProc.csproj +++ b/SacsStandardProc/SacsStandardProc.csproj @@ -10,7 +10,7 @@ - +