diff --git a/DepartmentPortal/Common/DatabaseCore/SecurityManager.cs b/DepartmentPortal/Common/DatabaseCore/SecurityManager.cs index 8380231..26eaf17 100644 --- a/DepartmentPortal/Common/DatabaseCore/SecurityManager.cs +++ b/DepartmentPortal/Common/DatabaseCore/SecurityManager.cs @@ -4,20 +4,56 @@ using ModelTools.Interfaces; using ModelTools.Models; using SecurityBusinessLogic.BusinessLogics; using System; +using System.Collections.Generic; using System.Linq; using System.Security.Cryptography; using System.Text; +using System.Threading.Tasks; namespace DatabaseCore { public class SecurityManager : ISecurityManager { + private readonly int _countDayToBanned = 3; + + private readonly int _countMaxAttempt = 3; + + private static SecurityManager _securityManager; + + private static readonly object _lockObject = new(); + + private SecurityManager() { } + + /// + /// Singleton для класса + /// + public static SecurityManager GetInstance + { + get + { + if (_securityManager == null) + { + lock (_lockObject) + { + _securityManager = new SecurityManager(); + } + } + return _securityManager; + } + } + + public Guid? User { get; set; } + + public List Roles { get; set; } + public string ErrorMessage { get; set; } + public bool IsAuth => User != null; + public bool CheckAccess(SecurityManagerCheckAccessModel model) { using var context = DatabaseManager.GetContext; - Access access; + Access access = null; if (model != null) { // простой просмотр возможен @@ -26,9 +62,9 @@ namespace DatabaseCore return true; } // если не указан идентификатор пользователя, то смотрим, может он авторизован - if (!model.Model.UserId.HasValue && UserManager.GetInstance.User != null) + if (!model.Model.UserId.HasValue && User.HasValue) { - model.Model.UserId = UserManager.GetInstance.UserId; + model.Model.UserId = User.Value; } var roles = context.UserRoles.Where(x => x.UserId == model.Model.UserId).Select(x => x.Role).OrderByDescending(x => x.RolePriority).ToList(); @@ -39,9 +75,9 @@ namespace DatabaseCore } access = context.Accesses.FirstOrDefault(a => a.AccessOperation == model.Operation && roles.Contains(a.Role)); } - else + else if (Roles != null) { - access = context.Accesses.FirstOrDefault(a => a.AccessOperation == model.Operation && UserManager.GetInstance.Roles.Contains(a.RoleId)); + access = context.Accesses.FirstOrDefault(a => a.AccessOperation == model.Operation && Roles.Contains(a.RoleId)); } if (access != null) { @@ -121,5 +157,85 @@ namespace DatabaseCore transaction.Commit(); } + + public async Task LoginAsync(string login, string password) + { + var passwordHash = GetPasswordHash(password); + using var context = DatabaseManager.GetContext; + var user = context.Users.FirstOrDefault(x => x.UserName == login && x.PasswordHash == passwordHash); + await CheckUserAsync(login, user, context); + user.DateLastVisit = DateTime.Now; + user.CountAttempt = 0; + await context.SaveChangesAsync(); + + User = user.Id; + Roles = context.UserRoles.Where(x => x.UserId == user.Id).Select(x => x.RoleId).ToList(); + } + + public async Task LogoutAsync() + { + await Task.Run(() => + { + User = null; + Roles = null; + }); + } + + public async Task ChangePassword(string login, string oldPassword, string newPassword) + { + using var context = DatabaseManager.GetContext; + var user = context.Users.FirstOrDefault(x => x.UserName == login && x.PasswordHash == GetPasswordHash(oldPassword)); + await CheckUserAsync(login, user, context); + user.PasswordHash = GetPasswordHash(newPassword); + await context.SaveChangesAsync(); + } + + /// + /// Получение хеша пароля + /// + /// + /// + private static string GetPasswordHash(string password) => Encoding.ASCII.GetString((new MD5CryptoServiceProvider()).ComputeHash(Encoding.ASCII.GetBytes(password))); + + /// + /// Проверка пользователя при авторизации и при смене пароля + /// + /// + /// + /// + /// + private async Task CheckUserAsync(string login, User user, DatabaseContext context) + { + if (user == null) + { + user = context.Users.FirstOrDefault(x => x.UserName == login); + if (user != null) + { + user.CountAttempt++; + if (user.CountAttempt > _countMaxAttempt) + { + user.IsBanned = true; + user.DateBanned = DateTime.Now; + await context.SaveChangesAsync(); + + throw new Exception($"Введен неверный логин/пароль? учетная запись заблоикрована на {_countDayToBanned} дней(я)"); + } + } + + throw new Exception("Введен неверный логин/пароль"); + } + if (user.IsBanned) + { + if (user.DateBanned.Value.AddDays(_countDayToBanned) > DateTime.Now) + { + user.IsBanned = false; + await context.SaveChangesAsync(); + } + else + { + throw new Exception("Пользователь заблокирован"); + } + } + } } } \ No newline at end of file diff --git a/DepartmentPortal/Common/ModelTools/Interfaces/ISecurityManager.cs b/DepartmentPortal/Common/ModelTools/Interfaces/ISecurityManager.cs index 60d5726..169e44b 100644 --- a/DepartmentPortal/Common/ModelTools/Interfaces/ISecurityManager.cs +++ b/DepartmentPortal/Common/ModelTools/Interfaces/ISecurityManager.cs @@ -1,9 +1,27 @@ using ModelTools.Models; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; namespace ModelTools.Interfaces { public interface ISecurityManager { + /// + /// Аутентифицированный пользователь + /// + public Guid? User { get; set; } + + /// + /// Список ролей аутентифицированного пользователь + /// + public List Roles { get; set; } + + /// + /// Выполнена ли аутентификация + /// + public bool IsAuth { get; } + /// /// Сообщение с причиной не получения доступа /// @@ -20,5 +38,27 @@ namespace ModelTools.Interfaces /// Проверка наличия старотвых данных для работы с ситемой /// void CheckStartDataSource(); + + /// + /// Аутентификация пользователя + /// + /// + /// + /// + Task LoginAsync(string login, string password); + + /// + /// Выход из системы + /// + /// + Task LogoutAsync(); + + /// + /// Смена пароля + /// + /// + /// + /// + Task ChangePassword(string login, string oldPassword, string newPassword); } } \ No newline at end of file diff --git a/DepartmentPortal/DepartmentPortalDesctop/DepartmentPortalDesctop.csproj b/DepartmentPortal/DepartmentPortalDesctop/DepartmentPortalDesctop.csproj index ced7a4a..f35c2cf 100644 --- a/DepartmentPortal/DepartmentPortalDesctop/DepartmentPortalDesctop.csproj +++ b/DepartmentPortal/DepartmentPortalDesctop/DepartmentPortalDesctop.csproj @@ -16,7 +16,6 @@ - diff --git a/DepartmentPortal/DepartmentPortalDesctop/FormEnter.cs b/DepartmentPortal/DepartmentPortalDesctop/FormEnter.cs index 77a5c01..b81a362 100644 --- a/DepartmentPortal/DepartmentPortalDesctop/FormEnter.cs +++ b/DepartmentPortal/DepartmentPortalDesctop/FormEnter.cs @@ -1,5 +1,7 @@ using DesktopTools.BusinessLogics; +using ModelTools.BusinessLogics; using ModelTools.Extensions; +using ModelTools.Interfaces; using SecurityBusinessLogic.BusinessLogics; using System; using System.Collections.Generic; @@ -30,7 +32,8 @@ namespace DepartmentPortalDesctop } try { - Task.WaitAll(Task.Run(async () => await UserManager.GetInstance.LoginAsync(textBoxLogin.Text, textBoxPassword.Text))); + var securityManager = UnityContainerConfigurator.Resolve(); + Task.WaitAll(Task.Run(async () => await securityManager.LoginAsync(textBoxLogin.Text, textBoxPassword.Text))); DialogResult = DialogResult.OK; Close(); } diff --git a/DepartmentPortal/DepartmentPortalDesctop/Program.cs b/DepartmentPortal/DepartmentPortalDesctop/Program.cs index 1f9d016..472ce00 100644 --- a/DepartmentPortal/DepartmentPortalDesctop/Program.cs +++ b/DepartmentPortal/DepartmentPortalDesctop/Program.cs @@ -27,7 +27,7 @@ namespace DepartmentPortalDesctop var form = new FormEnter(); - if (form.ShowDialog() == DialogResult.OK && UserManager.GetInstance.IsAuth) + if (form.ShowDialog() == DialogResult.OK && securityManager.IsAuth) { Application.Run(UnityContainerConfigurator.Resolve()); } diff --git a/DepartmentPortal/ImplementationExtensions/net5.0/DatabaseCore.dll b/DepartmentPortal/ImplementationExtensions/net5.0/DatabaseCore.dll index d5e5315..626cb55 100644 Binary files a/DepartmentPortal/ImplementationExtensions/net5.0/DatabaseCore.dll and b/DepartmentPortal/ImplementationExtensions/net5.0/DatabaseCore.dll differ diff --git a/DepartmentPortal/ImplementationExtensions/net5.0/ModelTools.dll b/DepartmentPortal/ImplementationExtensions/net5.0/ModelTools.dll index 78f408e..71032a3 100644 Binary files a/DepartmentPortal/ImplementationExtensions/net5.0/ModelTools.dll and b/DepartmentPortal/ImplementationExtensions/net5.0/ModelTools.dll differ diff --git a/DepartmentPortal/ImplementationExtensions/net5.0/SecurityBusinessLogic.dll b/DepartmentPortal/ImplementationExtensions/net5.0/SecurityBusinessLogic.dll index 469e9d4..e13afce 100644 Binary files a/DepartmentPortal/ImplementationExtensions/net5.0/SecurityBusinessLogic.dll and b/DepartmentPortal/ImplementationExtensions/net5.0/SecurityBusinessLogic.dll differ diff --git a/DepartmentPortal/ImplementationExtensions/net5.0/SecurityImplementation.dll b/DepartmentPortal/ImplementationExtensions/net5.0/SecurityImplementation.dll index c87212d..9103c80 100644 Binary files a/DepartmentPortal/ImplementationExtensions/net5.0/SecurityImplementation.dll and b/DepartmentPortal/ImplementationExtensions/net5.0/SecurityImplementation.dll differ diff --git a/DepartmentPortal/Security/SecurityBusinessLogic/BindingModels/UserBindingModels.cs b/DepartmentPortal/Security/SecurityBusinessLogic/BindingModels/UserBindingModels.cs index daf0b13..41063ef 100644 --- a/DepartmentPortal/Security/SecurityBusinessLogic/BindingModels/UserBindingModels.cs +++ b/DepartmentPortal/Security/SecurityBusinessLogic/BindingModels/UserBindingModels.cs @@ -10,13 +10,6 @@ namespace SecurityBusinessLogic.BindingModels /// public class UserGetBindingModel : GetBindingModel { - public bool? IsBanned { get; set; } - - public string Login { get; set; } - - public string Password { get; set; } - - public List LecturerIds { get; set; } } /// diff --git a/DepartmentPortal/Security/SecurityBusinessLogic/BusinessLogics/UserManager.cs b/DepartmentPortal/Security/SecurityBusinessLogic/BusinessLogics/UserManager.cs deleted file mode 100644 index 491c729..0000000 --- a/DepartmentPortal/Security/SecurityBusinessLogic/BusinessLogics/UserManager.cs +++ /dev/null @@ -1,223 +0,0 @@ -using ModelTools.BusinessLogics; -using SecurityBusinessLogic.BindingModels; -using SecurityBusinessLogic.ViewModels; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Security.Cryptography; -using System.Text; -using System.Threading.Tasks; - -namespace SecurityBusinessLogic.BusinessLogics -{ - /// - /// Менеджер по работе с пользователем системы - /// - public class UserManager - { - private readonly int _countDayToBanned = 3; - - private readonly int _countMaxAttempt = 3; - - private readonly RoleBusinessLogic _roleBusinessLogic; - - private readonly UserBusinessLogic _userBusinessLogic; - - private static UserManager _userManager; - - private static readonly object _lockObject = new(); - - private UserManager() - { - _roleBusinessLogic = UnityContainerConfigurator.Resolve(); - _userBusinessLogic = UnityContainerConfigurator.Resolve(); - } - - public static UserManager GetInstance - { - get - { - if (_userManager == null) - { - lock (_lockObject) - { - _userManager = new UserManager(); - } - } - - return _userManager; - } - } - - /// - /// Аутентифицированный пользователь - /// - public UserViewModel User { get; private set; } - - /// - /// Список ролей аутентифицированного пользователь - /// - public List Roles { get; private set; } - - /// - /// Сообщение об ошибке - /// - public string ErrorMessage { get; private set; } - - /// - /// Идентификатор аутентифицированного пользователь - /// - public Guid? UserId => User?.Id; - - /// - /// Выполнена ли аутентификация - /// - public bool IsAuth => User != null; - - /// - /// Аутентификация пользователя - /// - /// - /// - /// - public async Task LoginAsync(string login, string password) - { - await Task.Run(() => - { - UserSetBindingModel model; - var passwordHash = GetPasswordHash(password); - var user = _userBusinessLogic.GetElement(new UserGetBindingModel - { - Login = login, - Password = passwordHash, - SkipCheck = true - }); - - if (user == null) - { - if(_userBusinessLogic.Errors.Count > 0) - { - throw new Exception(_userBusinessLogic.Errors[0].Message); - } - - user = _userBusinessLogic.GetElement(new UserGetBindingModel - { - Login = login, - IsBanned = true, - SkipCheck = true - }); - if (user != null) - { - user.CountAttempt++; - if (user.CountAttempt > _countMaxAttempt) - { - user.IsBanned = true; - user.DateBanned = DateTime.Now; - } - - model = GetSetBindingModel(user); - model.Password = passwordHash; - _userBusinessLogic.Update(model); - } - - if (_userBusinessLogic.Errors.Count > 0) - { - throw new Exception(_userBusinessLogic.Errors[0].Message); - } - - throw new Exception("Введен неверный логин/пароль"); - } - if (user.IsBanned) - { - if (user.DateBanned.Value.AddDays(_countDayToBanned) > DateTime.Now) - { - user.IsBanned = false; - } - else - { - throw new Exception("Пользователь заблокирован"); - } - } - - user.DateLastVisit = DateTime.Now; - user.CountAttempt = 0; - - model = GetSetBindingModel(user); - model.Password = passwordHash; - _userBusinessLogic.Update(model); - - User = user; - Roles = _roleBusinessLogic.GetList(new RoleGetBindingModel { UserId = User.Id }).List.Select(x => x.Id).ToList(); - }); - } - - /// - /// Выход из системы - /// - /// - public async Task LogoutAsync() - { - await Task.Run(() => - { - User = null; - Roles = null; - }); - } - - /// - /// Смена пароля - /// - /// - /// - /// - public void ChangePassword(string login, string oldPassword, string newPassword) - { - var user = _userBusinessLogic.GetElement(new UserGetBindingModel - { - Login = login, - Password = GetPasswordHash(oldPassword), - SkipCheck = true - }); - if (user == null) - { - throw new Exception("Введен неверный логин/пароль"); - } - if (user.IsBanned) - { - throw new Exception("Пользователь забаннен"); - } - var model = GetSetBindingModel(user); - model.Password = GetPasswordHash(newPassword); - _userBusinessLogic.Update(model); - } - - /// - /// Получение хеша пароля - /// - /// - /// - public static string GetPasswordHash(string password) => Encoding.ASCII.GetString((new MD5CryptoServiceProvider()).ComputeHash(Encoding.ASCII.GetBytes(password))); - - /// - /// Получение модели для сохранения из представления - /// - /// - /// - private static UserSetBindingModel GetSetBindingModel(UserViewModel model) - { - return new UserSetBindingModel - { - Id = model.Id, - Login = model.Login, - LecturerId = model.LecturerId, - StudentId = model.StudentId, - EmployeeId = model.EmployeeId, - Avatar = model.Avatar, - IsBanned = model.IsBanned, - DateBanned = model.DateBanned, - CountAttempt = model.CountAttempt, - DateLastVisit = model.DateLastVisit - }; - } - } -} \ No newline at end of file diff --git a/DepartmentPortal/Security/SecurityImplementation/Implementations/UserService.cs b/DepartmentPortal/Security/SecurityImplementation/Implementations/UserService.cs index 613e2d9..1ef49a4 100644 --- a/DepartmentPortal/Security/SecurityImplementation/Implementations/UserService.cs +++ b/DepartmentPortal/Security/SecurityImplementation/Implementations/UserService.cs @@ -2,7 +2,6 @@ using DatabaseCore.Models.Security; using ModelTools.BusinessLogics; using ModelTools.Enums; -using ModelTools.Extensions; using ModelTools.Models; using SecurityBusinessLogic.BindingModels; using SecurityBusinessLogic.Interfaces; @@ -99,35 +98,7 @@ namespace SecurityImplementation.Implementations return OperationResultModel.Success(Mapper.MapToClass(entity)); } - if (model.Login.IsNotEmpty() && model.Password.IsNotEmpty()) - { - var entity = context.Users.FirstOrDefault(x => x.UserName == model.Login && x.PasswordHash == model.Password); - if (entity == null) - { - return OperationResultModel.Error("Error:", "Элемент не найден", ResultServiceStatusCode.NotFound); - } - return OperationResultModel.Success(Mapper.MapToClass(entity)); - } - - if (model.Login.IsNotEmpty() && model.IsBanned.HasValue) - { - var entity = context.Users.FirstOrDefault(x => x.UserName == model.Login && x.IsBanned == model.IsBanned.Value); - if (entity == null) - { - return OperationResultModel.Error("Error:", "Элемент не найден", ResultServiceStatusCode.NotFound); - } - return OperationResultModel.Success(Mapper.MapToClass(entity)); - } - var query = context.Users.Where(x => !x.IsDeleted).AsQueryable(); - if (model.IsBanned.HasValue) - { - query = query.Where(x => x.IsBanned == model.IsBanned.Value); - } - if (model.LecturerIds != null) - { - query = query.Where(x => x.LecturerId.HasValue && model.LecturerIds.Contains(x.LecturerId.Value)); - } query = query.OrderBy(x => x.UserName); diff --git a/DepartmentPortal/Security/SecurityWindowsDesktop/SecurityWindowDesktopExtension.cs b/DepartmentPortal/Security/SecurityWindowsDesktop/SecurityWindowDesktopExtension.cs index cf83284..1b58e7e 100644 --- a/DepartmentPortal/Security/SecurityWindowsDesktop/SecurityWindowDesktopExtension.cs +++ b/DepartmentPortal/Security/SecurityWindowsDesktop/SecurityWindowDesktopExtension.cs @@ -20,7 +20,7 @@ namespace SecurityWindowsDesktop return null; } - if (!manager.CheckAccess(new SecurityManagerCheckAccessModel(new AccessBindingModel { UserId = UserManager.GetInstance.UserId }, + if (!manager.CheckAccess(new SecurityManagerCheckAccessModel(new AccessBindingModel { UserId = manager.User }, AccessOperation.Администрирование, AccessType.SimpleView, "Администрирование"))) { return null; @@ -40,7 +40,7 @@ namespace SecurityWindowsDesktop foreach (var cntrl in _controls) { - if (manager.CheckAccess(new SecurityManagerCheckAccessModel(new AccessBindingModel { UserId = UserManager.GetInstance.UserId }, + if (manager.CheckAccess(new SecurityManagerCheckAccessModel(new AccessBindingModel { UserId = manager.User }, cntrl.AccessOperation, AccessType.SimpleView, cntrl.Title))) { list.Add(new WindowDesktopExtensionControlModel diff --git a/DepartmentPortal/WindowDestopExtensions/net5.0-windows/DesktopTools.dll b/DepartmentPortal/WindowDestopExtensions/net5.0-windows/DesktopTools.dll index 64a76a6..12937ff 100644 Binary files a/DepartmentPortal/WindowDestopExtensions/net5.0-windows/DesktopTools.dll and b/DepartmentPortal/WindowDestopExtensions/net5.0-windows/DesktopTools.dll differ diff --git a/DepartmentPortal/WindowDestopExtensions/net5.0-windows/ModelTools.dll b/DepartmentPortal/WindowDestopExtensions/net5.0-windows/ModelTools.dll index 78f408e..71032a3 100644 Binary files a/DepartmentPortal/WindowDestopExtensions/net5.0-windows/ModelTools.dll and b/DepartmentPortal/WindowDestopExtensions/net5.0-windows/ModelTools.dll differ diff --git a/DepartmentPortal/WindowDestopExtensions/net5.0-windows/SecurityBusinessLogic.dll b/DepartmentPortal/WindowDestopExtensions/net5.0-windows/SecurityBusinessLogic.dll index 469e9d4..e13afce 100644 Binary files a/DepartmentPortal/WindowDestopExtensions/net5.0-windows/SecurityBusinessLogic.dll and b/DepartmentPortal/WindowDestopExtensions/net5.0-windows/SecurityBusinessLogic.dll differ diff --git a/DepartmentPortal/WindowDestopExtensions/net5.0-windows/SecurityWindowsDesktop.dll b/DepartmentPortal/WindowDestopExtensions/net5.0-windows/SecurityWindowsDesktop.dll index e88a3dd..c68b34d 100644 Binary files a/DepartmentPortal/WindowDestopExtensions/net5.0-windows/SecurityWindowsDesktop.dll and b/DepartmentPortal/WindowDestopExtensions/net5.0-windows/SecurityWindowsDesktop.dll differ