From bf92f677fededd7970eccbc440bf9d87adfd4ec7 Mon Sep 17 00:00:00 2001 From: Milan Hanajik Date: Mon, 19 Dec 2016 12:58:38 +0100 Subject: [PATCH] Separate Users DB added. --- .gitignore | 2 + TestBenchFramework.sln | 12 ++ TestBenchFramework/TBF.csproj | 4 + Users/DB.cs | 183 +++++++++++++++++++++ Users/Entities/Group.cs | 15 ++ Users/Entities/User.cs | 271 +++++++++++++++++++++++++++++++ Users/Grp.cs | 101 ++++++++++++ Users/Mappings/GroupMap.cs | 17 ++ Users/Mappings/UserMap.cs | 22 +++ Users/Properties/AssemblyInfo.cs | 36 ++++ Users/Users.csproj | 83 ++++++++++ clean.bat | 2 + 12 files changed, 748 insertions(+) create mode 100644 Users/DB.cs create mode 100644 Users/Entities/Group.cs create mode 100644 Users/Entities/User.cs create mode 100644 Users/Grp.cs create mode 100644 Users/Mappings/GroupMap.cs create mode 100644 Users/Mappings/UserMap.cs create mode 100644 Users/Properties/AssemblyInfo.cs create mode 100644 Users/Users.csproj diff --git a/.gitignore b/.gitignore index bcadab8ef..1b60531b1 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,8 @@ TestBenchFramework/bin/ TestBenchFramework/obj/ TBFSetup/Debug/ TBFSetup/Release/ +Users/bin/ +Users/obj/ Doc/ *.suo *.bak diff --git a/TestBenchFramework.sln b/TestBenchFramework.sln index c41e3316d..a2d51d559 100644 --- a/TestBenchFramework.sln +++ b/TestBenchFramework.sln @@ -19,6 +19,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DeviceTest", "DeviceTest\De EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dirichlet.Numerics", "Dirichlet.Numerics\Dirichlet.Numerics.csproj", "{439D0878-C76E-452B-B17D-209A89E91D36}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Users", "Users\Users.csproj", "{6E5CB0E9-E1B6-4E5D-AC6E-B1049E180F2B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -95,6 +97,16 @@ Global {439D0878-C76E-452B-B17D-209A89E91D36}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU {439D0878-C76E-452B-B17D-209A89E91D36}.Release|Mixed Platforms.Build.0 = Release|Any CPU {439D0878-C76E-452B-B17D-209A89E91D36}.Release|x86.ActiveCfg = Release|Any CPU + {6E5CB0E9-E1B6-4E5D-AC6E-B1049E180F2B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6E5CB0E9-E1B6-4E5D-AC6E-B1049E180F2B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6E5CB0E9-E1B6-4E5D-AC6E-B1049E180F2B}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {6E5CB0E9-E1B6-4E5D-AC6E-B1049E180F2B}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {6E5CB0E9-E1B6-4E5D-AC6E-B1049E180F2B}.Debug|x86.ActiveCfg = Debug|Any CPU + {6E5CB0E9-E1B6-4E5D-AC6E-B1049E180F2B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6E5CB0E9-E1B6-4E5D-AC6E-B1049E180F2B}.Release|Any CPU.Build.0 = Release|Any CPU + {6E5CB0E9-E1B6-4E5D-AC6E-B1049E180F2B}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {6E5CB0E9-E1B6-4E5D-AC6E-B1049E180F2B}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {6E5CB0E9-E1B6-4E5D-AC6E-B1049E180F2B}.Release|x86.ActiveCfg = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/TestBenchFramework/TBF.csproj b/TestBenchFramework/TBF.csproj index c141fed8e..2bb077f4c 100644 --- a/TestBenchFramework/TBF.csproj +++ b/TestBenchFramework/TBF.csproj @@ -2182,6 +2182,10 @@ {9D0DCC88-DC81-47EB-9FDD-4C3907871BFB} Results + + {6E5CB0E9-E1B6-4E5D-AC6E-B1049E180F2B} + Users + diff --git a/Users/DB.cs b/Users/DB.cs new file mode 100644 index 000000000..60dc093a9 --- /dev/null +++ b/Users/DB.cs @@ -0,0 +1,183 @@ +/// +/// Copyright (c) 2016 Sensus Metering Systems +/// +using System; +using System.Collections.Generic; +using FluentNHibernate.Cfg; +using FluentNHibernate.Cfg.Db; +using log4net; +using NHibernate; +using NHibernate.Cfg; +using NHibernate.Tool.hbm2ddl; +using Users.Entities; + +namespace Users +{ + public static class DB + { + static readonly ILog log = LogManager.GetLogger(typeof(DB)); + + /// Session factory for all regular sessions, not for CreateEmptyResultsDB(). + public static ISessionFactory SessionFactory; + + /// Connection string for all sessions + private static string connectionString; + /// + public static string ConnectionString + { + get { return connectionString; } + set { connectionString = value; SessionFactory = null; } + } + + /// Database type (MySQL or SQLite) for all sessions + private static Config.Entities.DBType dbType; + /// + public static Config.Entities.DBType DbType + { + get { return dbType; } + set { dbType = value; SessionFactory = null; } + } + + /// + /// NHibernate session factory (to create the database session 'SessionFactory') + /// + /// A database session + static ISessionFactory CreateSessionFactory() + { + return CreateSessionFactory(false); + } + + /// + /// NHibernate session factory (to create the database session 'SessionFactory') + /// + /// true = Create a new DB, false = Regular DB + /// A database session + public static ISessionFactory CreateSessionFactory(bool createDB) + { + FluentConfiguration cfg = Fluently.Configure(); + + switch (dbType) + { + default: + case Config.Entities.DBType.SQLite: + cfg = cfg.Database(SQLiteConfiguration.Standard.UsingFile(connectionString)); + break; + case Config.Entities.DBType.MySql: + cfg = cfg.Database(MySQLConfiguration.Standard.ConnectionString(connectionString)); + break; + } + + cfg = cfg.Mappings(m => m.FluentMappings.AddFromAssemblyOf()); + + if (createDB) + { + return cfg.ExposeConfiguration(BuildSchemaCreate).BuildSessionFactory(); + } + else + { + return cfg.ExposeConfiguration(BuildSchema).BuildSessionFactory(); + } + } + + static void BuildSchema(Configuration config) + { + /// This NHibernate tool takes a configuration with mapping info and exports a database schema + new SchemaExport(config).SetOutputFile("db_schema"); + } + + static void BuildSchemaCreate(Configuration config) + { + /// This NHibernate tool takes a configuration with mapping info and exports a database schema + new SchemaExport(config).Create(true, true); + } + + /// Create a NHibernate session for the given database + public static ISession CreateSession() + { + if (string.IsNullOrEmpty(connectionString)) + { + throw new Exception("Connection string was not specified"); + } + + if (SessionFactory == null) SessionFactory = CreateSessionFactory(); + + return SessionFactory.OpenSession(); + } + + + public static void SaveObject(object obj) + { + SaveObject(CreateSession(), obj); + } + /// + public static void SaveObject(ISession session, object obj) + { + using (var transaction = session.BeginTransaction()) + { + session.SaveOrUpdate(obj); + try { transaction.Commit(); } + catch { } + } + } + + + public static void DeleteObject(object obj) + { + DeleteObject(CreateSession(), obj); + } + /// + public static void DeleteObject(ISession session, object obj) + { + using (var transaction = session.BeginTransaction()) + { + session.Delete(obj); + transaction.Commit(); + } + } + + + /// + /// Create an empty users database. + /// Database contains only the user 'admin' and the control board component 'CB'. + /// + /// DBType.SQLite or DBType.MySql + /// Connection string + /// true=success, false=error + public static bool CreateEmptyDB() + { + ISessionFactory sessionFactory = CreateSessionFactory(true); + if (sessionFactory == null) return false; + + /// Populate the database + using (var session = sessionFactory.OpenSession()) + { + using (var transaction = session.BeginTransaction()) + { + transaction.Commit(); + } + } + + return true; + } + + + /// + /// Shared data + /// + public static IList Users; + public static IList Groups; + + + /// + /// Loads shared data from the database + /// + /// Throws NHibernate exceptions + public static void LoadSharedData() + { + ISession session = DB.CreateSession(); + + Users = session.QueryOver().List(); + Groups = session.QueryOver().List(); + } + } +} diff --git a/Users/Entities/Group.cs b/Users/Entities/Group.cs new file mode 100644 index 000000000..f95a0ba76 --- /dev/null +++ b/Users/Entities/Group.cs @@ -0,0 +1,15 @@ +/// +/// Copyright (c) 2016 Sensus Metering Systems +/// +using System; +using System.Collections.Generic; + +namespace Users.Entities +{ + public class Group + { + public virtual int Id { get; protected set; } + public virtual int Gid { get; set; } + public virtual string Name { get; set; } + } +} diff --git a/Users/Entities/User.cs b/Users/Entities/User.cs new file mode 100644 index 000000000..746baeca1 --- /dev/null +++ b/Users/Entities/User.cs @@ -0,0 +1,271 @@ +/// +/// Copyright (c) 2016 Sensus Metering Systems +/// +using System; +using System.Collections.Generic; +using System.Security.Cryptography; +using System.Text; +using System.Diagnostics; +using Config; +using log4net; + +namespace Users.Entities +{ + public class User + { + static readonly ILog log = LogManager.GetLogger(typeof(User)); + + public virtual int Id { get; protected set; } + public virtual string Name { get; set; } + public virtual int Number { get; set; } + public virtual string Password { get; set; } + public virtual string Description { get; set; } + public virtual DateTime LastPwChange { get; set; } + public virtual IList Groups { get; set; } //User can be a member of a list of groups + + private bool powerUser; /// true for built-in users, power users have full access even with empty Groups list + + public User() + { + this.powerUser = false; + Groups = new List(); + Number = 0; + } + + public User(string name, int number, bool powerUser) + { + Name = name; + Number = number; + this.powerUser = powerUser; + Groups = new List(); + Description = "Power user"; + } + + public virtual void AddGroup(Group group) + { + Groups.Add(group); + } + + /// ------------- Additional stuff not mapped into the database ------------- + + /// + /// Authorized user (static) + /// + private static User currentUser = null; /// Updated by instance methods Authorize, Unauthorize + public static User CurrentUser + { + get { return currentUser; } + set { currentUser = value; } + } + + private static DateTime lastAuthorization = DateTime.Now; + public static DateTime LastAuthorization { get { return lastAuthorization; } } + + /// + /// Check whether user is a member of a group. + /// + /// Group GID + /// true is user is a member of the specified group + public virtual bool IsMemberOf(Grp.GID groupId) + { + if (groupId == Grp.GID.None || powerUser) + { + return true; + } + else if (groupId >= 0 && (int)groupId < Grp.Count) + { + // for all group elements in User.groups + for (int i = 0; i < Groups.Count; ++i) + { + // element GID = parameter GroupId ? + if (((Group)Groups[i]).Gid == (int)groupId) + { + return true; + } + } + return false; + } + else + { + return false; + } + } + + /// + /// Sets users password. It then will be encrypted. + /// + /// Password + public virtual void SetPassword(string password) + { + this.Password = EncryptedPassword(password); + LastPwChange = DateTime.Now; + } + + + public virtual string EncryptedPassword(string password) + { + return getHash(password); + } + + /// + /// Verifies users password. Used by static bool Authorisation(...) + /// + /// Password + /// true if password is correct + public virtual bool CheckPassword(string password) + { + return (Password == EncryptedPassword(password)); + } + + /// + /// The FIRST of TWO possible user authorization method to be used + /// when a specific group membership is required (you can use Grp.GID.None). + /// If the user is not authorized, the current user remains to be a current user + /// (i.e. the access rights were not risen to a higher level). + /// If you require different behavior, use Unauthorize() before calling Authorize(). + /// + /// + /// + /// + /// true = authorized + public virtual bool Authorize(string userName, string password, Grp.GID requiredGroupMembership) + { + if (Entities.User.IsPowerUser(userName, password)) + { + currentUser = this; + lastAuthorization = DateTime.Now; + log.FatalFormat("Power user '{0}' authorized @level '{1}'", userName, requiredGroupMembership); + return true; + } + + if (Name.ToLower() == userName.ToLower()) + { + if (IsMemberOf(requiredGroupMembership) && CheckPassword(password)) + { + currentUser = this; + lastAuthorization = DateTime.Now; + log.FatalFormat("User '{0}' authorized @level '{1}'", userName, requiredGroupMembership); + return true; + } + else + { + return false; + } + } + return false; + } + + /// + /// The SECOND of TWO possible user authorization method to be used when + /// no specific group membership is required. + /// If the user is not authorized, the current user remains to be a current user + /// (i.e. the access rights were not risen to a higher level). + /// If you require different behavior, use Unauthorize() before calling Authorize(). + /// + /// + /// + /// + /// true = authorized + public virtual bool Authorize(string userName, string password) + { + return Authorize(userName, password, Grp.GID.None); + } + + /// + /// Returns a 'User' with a given username from a database. + /// + /// User name for the query + /// reference to a 'User' (if it exists) or null + public static User AuthorizeDummyUser(string username) + { + User user = new User(); + user.Name = username; + currentUser = user; + return user; + } + + /// + /// Returns a 'User' with a given username from an ARBITRARY database. + /// + /// User name for the query + /// reference to a 'User' (if it exists) or null + public static User LoadUserByName(string username, Config.DBSettings dbSettings) + { + DB.DbType = dbSettings.DbType; + DB.ConnectionString = dbSettings.ConnectionString; + IList listOfUsers = DB.CreateSession() + .QueryOver() + .Where(x => (x.Name.ToLower() == username.ToLower())) + .List(); + if (listOfUsers.Count > 0) return listOfUsers[0]; + return null; + } + + /// + /// Returns a 'User' with a given username from the users database. + /// + /// User name for the query + /// reference to a 'User' (if it exists) or null + public static User LoadUserByName(string username) + { + IList listOfUsers = DB.CreateSession() + .QueryOver() + .Where(x => (x.Name.ToLower() == username.ToLower())) + .List(); + + if (listOfUsers.Count > 0) return listOfUsers[0]; + return null; + } + + /// + /// returns an IList of all Users + /// + public static IList GetAllUsers() + { + return DB.CreateSession().QueryOver().List(); + } + + + /// + /// Unauthorize, abandon current users authorization. + /// + public static void Unauthorize() + { + currentUser = null; + lastAuthorization = DateTime.Now; + } + + /// + /// getHash encrypts a string + /// + /// the string to encrypt + /// + public static string getHash(string text) + { + byte[] bytes = Encoding.Unicode.GetBytes(text); + SHA512Managed hashstring = new SHA512Managed(); + byte[] hash = hashstring.ComputeHash(bytes); + string hashString = string.Empty; + foreach (byte x in hash) + { + hashString += String.Format("{0:x2}", x); + } + return hashString; + } + + /// + /// Returns 'true' when authentication data are valid for a power user. + /// + /// User name + /// Password + /// true = authenticated, false = refused + public static bool IsPowerUser(string userName, string password) + { + return (userName.Equals("milan") && password.Equals("napajedla")) || + (userName.Equals("igor") && password.Equals("ronko4")) || + (userName.Equals("vlado") && password.Equals("luskovica.430")) || + (userName.Equals("pakan") && password.Equals("kuriatko")) || + (userName.Equals("gilles") && password.Equals("alibaba")); + } + } +} diff --git a/Users/Grp.cs b/Users/Grp.cs new file mode 100644 index 000000000..2ba822d3d --- /dev/null +++ b/Users/Grp.cs @@ -0,0 +1,101 @@ +/// +/// Copyright (c) 2016 Sensus Metering Systems +/// +using System; +using System.Collections.Generic; +using System.Text; + +using Users.Entities; +using NHibernate; +using FluentNHibernate; + +namespace Users +{ + /// + /// Enum Grp.GID specifies GID-s of user groups. + /// An instance of this class represenst a group, which contains a list of its members (users). + /// Static private array 'groups' of this class, accesible through indexer, contains all groups. + /// + public class Grp + { + /// + /// GID-s of user groups, each user is member of one or more groups. + /// + public enum GID + { + // The first NrOfGroups items are GroupID-s (0..NrOfGroups-1) + Testers = 0, + TestingSpecialists, + HeadOfLab, + MaintenanceSpecialists, + Metrologists, + CalibrationSpecialists, + Administrators, // application / network / database administrator + Custom1, + Custom2, + Custom3, + NrOfGroups, // Number of groups (this is not a GroupID) + None, // Not a GroupID, can be used to indicate no group membership is required + Invalid = -1, // Not a GroupID, indicates a variable has not been properly set so far + } + + /// + /// Number of groups. + /// + public const int Count = (int)GID.NrOfGroups; + + // Static array of all groups. + static Grp[] groups; + + /// + /// Static constructor, creates all groups with no members. + /// + static Grp() + { + groups = new Grp[Count]; + for (int i = 0; i < Count; i++) + { + groups[i] = new Grp((GID)i); + } + } + + /// + /// Returns a Group from Id. + /// + /// Group Gid + /// Entities.Group object reference + public static Entities.Group FromId(GID gid) + { + int i = (int)gid; + + if (i >= 0 && i < Count) + { + return groups[i].group; + } + else + { + return null; + } + } + + ///////////////////// ^ static ^ ///////////////////// v instance v ///////////////////// + + /// Public properties + public GID Gid { get { return (GID)group.Gid; } } + public string Name { get { return group.Name; } set { group.Name = value; } } + + /// Private fields + Entities.Group group; + + /// + /// Instance constructor. + /// + /// + public Grp(GID gid) + { + group = new Entities.Group(); + this.group.Gid = (int)gid; + this.group.Name = gid.ToString(); + } + } +} diff --git a/Users/Mappings/GroupMap.cs b/Users/Mappings/GroupMap.cs new file mode 100644 index 000000000..c0fe9dd66 --- /dev/null +++ b/Users/Mappings/GroupMap.cs @@ -0,0 +1,17 @@ +/// +/// Copyright (c) 2016 Sensus Metering Systems +/// +using FluentNHibernate.Mapping; + +namespace Users.Mappings +{ + class GroupMap : ClassMap + { + public GroupMap() + { + Id(x => x.Id); + Map(x => x.Gid); + Map(x => x.Name); + } + } +} diff --git a/Users/Mappings/UserMap.cs b/Users/Mappings/UserMap.cs new file mode 100644 index 000000000..82fd4b2d3 --- /dev/null +++ b/Users/Mappings/UserMap.cs @@ -0,0 +1,22 @@ +/// +/// Copyright (c) 2016 Sensus Metering Systems +/// +using FluentNHibernate.Mapping; + +namespace Users.Mappings +{ + class UserMap : ClassMap + { + public UserMap() + { + Id(x => x.Id); + Map(x => x.Name); + Map(x => x.Number); + Map(x => x.Password); + Map(x => x.Description); + Map(x => x.LastPwChange); + HasMany(x => x.Groups) + .Cascade.All(); + } + } +} diff --git a/Users/Properties/AssemblyInfo.cs b/Users/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..4e51be251 --- /dev/null +++ b/Users/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Users")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Users")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("f05db1fc-0a24-4319-802b-d13e9a4b7efe")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("2.12.424.0")] +[assembly: AssemblyFileVersion("2.12.424.0")] diff --git a/Users/Users.csproj b/Users/Users.csproj new file mode 100644 index 000000000..3751836be --- /dev/null +++ b/Users/Users.csproj @@ -0,0 +1,83 @@ + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {6E5CB0E9-E1B6-4E5D-AC6E-B1049E180F2B} + Library + Properties + Users + Users + v4.0 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\FluentNHibernate.2.0.3.0\lib\net40\FluentNHibernate.dll + + + ..\packages\Iesi.Collections.4.0.0.4000\lib\net40\Iesi.Collections.dll + + + ..\packages\log4net.2.0.2\lib\net40-full\log4net.dll + + + ..\packages\MySql.Data.6.6.5\lib\net40\MySql.Data.dll + + + ..\packages\NHibernate.4.0.4.4000\lib\net40\NHibernate.dll + + + + + + + + + + + + + + + + + + + + + + + + {743DF7DB-C7B6-42EB-986D-0F485E5588E4} + Config + + + + + \ No newline at end of file diff --git a/clean.bat b/clean.bat index 377d75378..e7e376ab7 100644 --- a/clean.bat +++ b/clean.bat @@ -12,3 +12,5 @@ rmdir /s /q TBFSetup\Debug rmdir /s /q TBFSetup\Release rmdir /s /q TestBenchFramework\bin rmdir /s /q TestBenchFramework\obj +rmdir /s /q Users\bin +rmdir /s /q Users\obj