From 96855a9290bd2f4e6a461586afee0cdc1ff2a685 Mon Sep 17 00:00:00 2001 From: Shawn Jackson Date: Wed, 28 Aug 2024 22:45:20 -0700 Subject: [PATCH 1/2] CU-3wen6jw Working on new Permission Matrices and some pkg updates. --- Common/Resgrid/Postmark/GetMessageStatus.bru | 16 + .../OutboundEmailServerConfig.cs | 1 + Core/Resgrid.Config/ServiceBusConfig.cs | 4 +- Core/Resgrid.Framework/Logging.cs | 15 +- .../Resgrid.Framework.csproj | 8 +- Core/Resgrid.Model/PermissionTypes.cs | 3 +- .../Resgrid.Model/Providers/ICacheProvider.cs | 1 + .../IDepartmentGroupMembersRepository.cs | 14 + Core/Resgrid.Model/SecurityCacheTypes.cs | 10 + .../Services/IAuthorizationService.cs | 8 + .../Services/IDepartmentGroupsService.cs | 2 + Core/Resgrid.Model/VisibilityPayloadUnits.cs | 15 + Core/Resgrid.Model/VisibilityPayloadUsers.cs | 16 + Core/Resgrid.Services/AuthorizationService.cs | 100 ++- .../DepartmentGroupsService.cs | 7 + Core/Resgrid.Services/UsersService.cs | 55 -- .../AzureRedisCacheProvider.cs | 10 + .../PostmarkTemplateProvider.cs | 2 +- .../Configs/SqlConfiguration.cs | 1 + .../DepartmentGroupMembersRepository.cs | 44 + .../SelectGroupAdminsByDidQuery.cs | 47 + .../SqlServer/SqlServerConfiguration.cs | 5 + .../Args/SecurityRefreshArgs.cs | 7 + .../Commands/SecurityRefreshCommand.cs | 48 ++ .../Properties/launchSettings.json | 2 +- .../Resgrid.Web.Eventing.csproj | 4 +- .../Controllers/v4/MappingController.cs | 11 +- Web/Resgrid.Web.ServicesCore/Program.cs | 5 +- .../Resgrid.Web.ServicesCore.csproj | 6 +- .../User/Controllers/DepartmentController.cs | 5 - .../Areas/User/Controllers/NotesController.cs | 5 - .../User/Controllers/SecurityController.cs | 19 +- .../User/Models/Security/PermissionsView.cs | 4 + .../User/Views/Account/DeleteAccount.cshtml | 11 +- .../Areas/User/Views/Calendar/Edit.cshtml | 5 +- .../Areas/User/Views/Calendar/New.cshtml | 645 +++++++------- .../Areas/User/Views/Connect/Profile.cshtml | 771 +++++++++-------- .../User/Views/Department/CallSettings.cshtml | 83 +- .../Views/Department/DeleteDepartment.cshtml | 7 +- .../Views/Dispatch/AddArchivedCall.cshtml | 9 +- .../User/Views/Dispatch/CloseCall.cshtml | 5 +- .../User/Views/Dispatch/Dashboard.cshtml | 2 + .../User/Views/Dispatch/UpdateCall.cshtml | 7 +- .../User/Views/Groups/DeleteGroup.cshtml | 7 +- .../User/Views/Home/EditUserProfile.cshtml | 17 +- .../Areas/User/Views/Links/New.cshtml | 24 +- .../Areas/User/Views/Security/Index.cshtml | 813 +++++++++--------- Web/Resgrid.WebCore/Resgrid.WebCore.csproj | 6 +- .../security/resgrid.security.permissions.js | 65 ++ Workers/Resgrid.Workers.Console/App.config | 2 +- .../SecurityRefreshScheduleCommand.cs | 19 + Workers/Resgrid.Workers.Console/Program.cs | 42 +- .../Tasks/SecurityRefreshScheduleTask.cs | 45 + .../Logic/SecurityLogic.cs | 656 ++++++++++++++ .../Workers/Security/SecurityQueueItem.cs | 10 + 55 files changed, 2435 insertions(+), 1316 deletions(-) create mode 100644 Common/Resgrid/Postmark/GetMessageStatus.bru create mode 100644 Core/Resgrid.Model/SecurityCacheTypes.cs create mode 100644 Core/Resgrid.Model/VisibilityPayloadUnits.cs create mode 100644 Core/Resgrid.Model/VisibilityPayloadUsers.cs create mode 100644 Repositories/Resgrid.Repositories.DataRepository/Queries/DepartmentGroups/SelectGroupAdminsByDidQuery.cs create mode 100644 Tools/Resgrid.Console/Args/SecurityRefreshArgs.cs create mode 100644 Tools/Resgrid.Console/Commands/SecurityRefreshCommand.cs create mode 100644 Workers/Resgrid.Workers.Console/Commands/SecurityRefreshScheduleCommand.cs create mode 100644 Workers/Resgrid.Workers.Console/Tasks/SecurityRefreshScheduleTask.cs create mode 100644 Workers/Resgrid.Workers.Framework/Logic/SecurityLogic.cs create mode 100644 Workers/Resgrid.Workers.Framework/Workers/Security/SecurityQueueItem.cs diff --git a/Common/Resgrid/Postmark/GetMessageStatus.bru b/Common/Resgrid/Postmark/GetMessageStatus.bru new file mode 100644 index 00000000..ef2c228f --- /dev/null +++ b/Common/Resgrid/Postmark/GetMessageStatus.bru @@ -0,0 +1,16 @@ +meta { + name: GetMessageStatus + type: http + seq: 1 +} + +get { + url: https://api.postmarkapp.com/messages/outbound/8bea308e-cc9c-4801-85e5-b98f8af9de1d/details + body: none + auth: none +} + +headers { + Accept: application/json + X-Postmark-Server-Token: +} diff --git a/Core/Resgrid.Config/OutboundEmailServerConfig.cs b/Core/Resgrid.Config/OutboundEmailServerConfig.cs index e19bf5fb..65d363c1 100644 --- a/Core/Resgrid.Config/OutboundEmailServerConfig.cs +++ b/Core/Resgrid.Config/OutboundEmailServerConfig.cs @@ -11,6 +11,7 @@ public static class OutboundEmailServerConfig public static string Password = ""; public static string PostmarkApiKey = ""; + public static string PostmarkMessageStream = ""; public static string AwsAccessKey = ""; public static string AwsSecretKey = ""; diff --git a/Core/Resgrid.Config/ServiceBusConfig.cs b/Core/Resgrid.Config/ServiceBusConfig.cs index 39730ca4..a10812c3 100644 --- a/Core/Resgrid.Config/ServiceBusConfig.cs +++ b/Core/Resgrid.Config/ServiceBusConfig.cs @@ -39,8 +39,8 @@ public static class ServiceBusConfig #region RabbitMQ Bus Values public static string RabbitHostname = "rgdevinfaserver"; - public static string RabbitHostname2 = "rgdevinfaserver2"; // For 3 host cluster, node 2 - public static string RabbitHostname3 = "rgdevinfaserver3"; // For 3 host cluster, node 3 + public static string RabbitHostname2 = ""; // For 3 host cluster, node 2 + public static string RabbitHostname3 = ""; // For 3 host cluster, node 3 public static string RabbitUsername = ""; public static string RabbbitPassword = ""; public static string RabbbitExchange = ""; diff --git a/Core/Resgrid.Framework/Logging.cs b/Core/Resgrid.Framework/Logging.cs index 1c0520b2..9dc4229f 100644 --- a/Core/Resgrid.Framework/Logging.cs +++ b/Core/Resgrid.Framework/Logging.cs @@ -3,12 +3,12 @@ using Serilog; using Serilog.Core; using Serilog.Events; -using Serilog.Sinks.Elasticsearch; using System; using System.Net; using System.Net.Mail; using System.Reflection; using System.Runtime.CompilerServices; +using System.Security.Cryptography; namespace Resgrid.Framework { @@ -20,7 +20,7 @@ public static class Logging public static void Initialize(string key) { - if (_isInitialized == false) + if (!_isInitialized) { if (SystemBehaviorConfig.ErrorLoggerType == ErrorLoggerTypes.Sentry) { @@ -45,14 +45,13 @@ public static void Initialize(string key) o.ProfilesSampleRate = 0.0; }).CreateLogger(); } - else if (SystemBehaviorConfig.ErrorLoggerType == ErrorLoggerTypes.Elk) + else { _logger = new LoggerConfiguration() - .WriteTo.Elasticsearch(new ElasticsearchSinkOptions(new Uri(ExternalErrorConfig.ElkServiceUrl)) - { - AutoRegisterTemplate = true, - AutoRegisterTemplateVersion = AutoRegisterTemplateVersion.ESv6 - }).CreateLogger(); + .Enrich.FromLogContext() + .MinimumLevel.Debug() + .WriteTo.Console() + .CreateLogger(); } _isInitialized = true; diff --git a/Core/Resgrid.Framework/Resgrid.Framework.csproj b/Core/Resgrid.Framework/Resgrid.Framework.csproj index 23acb18b..b16b5eae 100644 --- a/Core/Resgrid.Framework/Resgrid.Framework.csproj +++ b/Core/Resgrid.Framework/Resgrid.Framework.csproj @@ -15,10 +15,10 @@ - - - - + + + + diff --git a/Core/Resgrid.Model/PermissionTypes.cs b/Core/Resgrid.Model/PermissionTypes.cs index 4314f99a..bd362e8d 100644 --- a/Core/Resgrid.Model/PermissionTypes.cs +++ b/Core/Resgrid.Model/PermissionTypes.cs @@ -19,7 +19,8 @@ public enum PermissionTypes ViewGroupUsers, DeleteCall, CloseCall, - AddCallData + AddCallData, + ViewGroupUnits } } diff --git a/Core/Resgrid.Model/Providers/ICacheProvider.cs b/Core/Resgrid.Model/Providers/ICacheProvider.cs index 28eb7b8b..9a13f365 100644 --- a/Core/Resgrid.Model/Providers/ICacheProvider.cs +++ b/Core/Resgrid.Model/Providers/ICacheProvider.cs @@ -12,5 +12,6 @@ public interface ICacheProvider bool IsConnected(); Task SetStringAsync(string cacheKey, string value, TimeSpan expiration); Task GetStringAsync(string cacheKey); + Task GetAsync(string cacheKey) where T : class; } } diff --git a/Core/Resgrid.Model/Repositories/IDepartmentGroupMembersRepository.cs b/Core/Resgrid.Model/Repositories/IDepartmentGroupMembersRepository.cs index 0cd2cb51..8b4d64b3 100644 --- a/Core/Resgrid.Model/Repositories/IDepartmentGroupMembersRepository.cs +++ b/Core/Resgrid.Model/Repositories/IDepartmentGroupMembersRepository.cs @@ -27,6 +27,20 @@ public interface IDepartmentGroupMembersRepository: IRepository> GetAllGroupMembersByUserAndDepartmentAsync(string userId, int departmentId); + /// + /// Delete group by group identifier asynchronous. + /// + /// The group identifier. + /// The department identifier. + /// Task<IEnumerable<DepartmentGroupMember>>. Task DeleteGroupMembersByGroupIdAsync(int groupId, int departmentId, CancellationToken cancellationToken = default(CancellationToken)); + + + /// + /// Gets all group admins department asynchronous. + /// + /// The department identifier. + /// Task<IEnumerable<DepartmentGroupMember>>. + Task> GetAllGroupAdminsByDepartmentIdAsync(int departmentId); } } diff --git a/Core/Resgrid.Model/SecurityCacheTypes.cs b/Core/Resgrid.Model/SecurityCacheTypes.cs new file mode 100644 index 00000000..64ccb5a6 --- /dev/null +++ b/Core/Resgrid.Model/SecurityCacheTypes.cs @@ -0,0 +1,10 @@ +namespace Resgrid.Model +{ + public enum SecurityCacheTypes + { + WhoCanViewUnits = 1, + WhoCanViewUnitLocations = 2, + WhoCanViewPersonnel = 3, + WhoCanViewPersonnelLocations = 4, + } +} diff --git a/Core/Resgrid.Model/Services/IAuthorizationService.cs b/Core/Resgrid.Model/Services/IAuthorizationService.cs index 49179f4b..32a2391b 100644 --- a/Core/Resgrid.Model/Services/IAuthorizationService.cs +++ b/Core/Resgrid.Model/Services/IAuthorizationService.cs @@ -308,5 +308,13 @@ public interface IAuthorizationService Task CanUserAddNoteAsync(string userId); Task CanUserEditNoteAsync(string userId, int noteId); + + Task CanUserViewPersonViaMatrixAsync(string userToView, string userId, int departmentId); + + Task CanUserViewPersonLocationViaMatrixAsync(string userToView, string userId, int departmentId); + + Task CanUserViewUnitViaMatrixAsync(int unitToView, string userId, int departmentId); + + Task CanUserViewUnitLocationViaMatrixAsync(int unitToView, string userId, int departmentId); } } diff --git a/Core/Resgrid.Model/Services/IDepartmentGroupsService.cs b/Core/Resgrid.Model/Services/IDepartmentGroupsService.cs index e5084a73..d11ab399 100644 --- a/Core/Resgrid.Model/Services/IDepartmentGroupsService.cs +++ b/Core/Resgrid.Model/Services/IDepartmentGroupsService.cs @@ -224,5 +224,7 @@ Task MoveUserIntoGroupAsync(string userId, int groupId, b Task> GetAllGroupsForDepartmentUnlimitedThinAsync(int departmentId); Task DeleteAllMembersFromGroupAsync(DepartmentGroup departmentGroup, CancellationToken cancellationToken = default(CancellationToken)); + + Task> GetAllGroupAdminsByDepartmentIdAsync(int departmentId); } } diff --git a/Core/Resgrid.Model/VisibilityPayloadUnits.cs b/Core/Resgrid.Model/VisibilityPayloadUnits.cs new file mode 100644 index 00000000..296085f6 --- /dev/null +++ b/Core/Resgrid.Model/VisibilityPayloadUnits.cs @@ -0,0 +1,15 @@ +using ProtoBuf; +using System.Collections.Generic; + +namespace Resgrid.Model +{ + [ProtoContract] + public class VisibilityPayloadUnits + { + [ProtoMember(1)] + public bool EveryoneNoGroupLock { get; set; } + + [ProtoMember(2)] + public Dictionary> Units { get; set; } + } +} diff --git a/Core/Resgrid.Model/VisibilityPayloadUsers.cs b/Core/Resgrid.Model/VisibilityPayloadUsers.cs new file mode 100644 index 00000000..0da9ba0e --- /dev/null +++ b/Core/Resgrid.Model/VisibilityPayloadUsers.cs @@ -0,0 +1,16 @@ +using ProtoBuf; +using System.Collections.Generic; + + +namespace Resgrid.Model +{ + [ProtoContract] + public class VisibilityPayloadUsers + { + [ProtoMember(1)] + public bool EveryoneNoGroupLock { get; set; } + + [ProtoMember(2)] + public Dictionary> Users { get; set; } + } +} diff --git a/Core/Resgrid.Services/AuthorizationService.cs b/Core/Resgrid.Services/AuthorizationService.cs index 9b5ae967..82449a3a 100644 --- a/Core/Resgrid.Services/AuthorizationService.cs +++ b/Core/Resgrid.Services/AuthorizationService.cs @@ -2,7 +2,9 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.VisualBasic; +using MongoDB.Driver; using Resgrid.Model; +using Resgrid.Model.Providers; using Resgrid.Model.Services; namespace Resgrid.Services @@ -27,12 +29,19 @@ public class AuthorizationService : IAuthorizationService private readonly ICertificationService _certificationService; private readonly IDocumentsService _documentsService; private readonly INotesService _notesService; + private readonly ICacheProvider _cacheProvider; + + private static string WhoCanViewUnitsCacheKey = "ViewUnitsSecurityMaxtix_{0}"; + private static string WhoCanViewUnitLocationsCacheKey = "ViewUnitLocationsSecurityMaxtix_{0}"; + private static string WhoCanViewPersonnelCacheKey = "ViewUsersSecurityMaxtix_{0}"; + private static string WhoCanViewPersonnelLocationsCacheKey = "ViewUserLocationsSecurityMaxtix_{0}"; public AuthorizationService(IDepartmentsService departmentsService, IInvitesService invitesService, ICallsService callsService, IMessageService messageService, IWorkLogsService workLogsService, ISubscriptionsService subscriptionsService, IDepartmentGroupsService departmentGroupsService, IPersonnelRolesService personnelRolesService, IUnitsService unitsService, IPermissionsService permissionsService, ICalendarService calendarService, IProtocolsService protocolsService, - IShiftsService shiftsService, ICustomStateService customStateService, ICertificationService certificationService, IDocumentsService documentsService, INotesService notesService) + IShiftsService shiftsService, ICustomStateService customStateService, ICertificationService certificationService, + IDocumentsService documentsService, INotesService notesService, ICacheProvider cacheProvider) { _departmentsService = departmentsService; _invitesService = invitesService; @@ -51,6 +60,7 @@ public AuthorizationService(IDepartmentsService departmentsService, IInvitesServ _certificationService = certificationService; _documentsService = documentsService; _notesService = notesService; + _cacheProvider = cacheProvider; } #endregion Private Members and Constructors @@ -1188,5 +1198,93 @@ public async Task CanUserEditNoteAsync(string userId, int noteId) return false; } + + public async Task CanUserViewPersonViaMatrixAsync(string userToView, string userId, int departmentId) + { + var matrix = await _cacheProvider.GetAsync(string.Format(WhoCanViewPersonnelCacheKey, departmentId)); + + // Fail open if the cache is not available for now. -SJ 8-26-2024 + if (matrix == null) + return true; + + if (matrix.EveryoneNoGroupLock) + return true; + + if (!matrix.Users.ContainsKey(userToView)) + return true; + + var userViewList = matrix.Users[userToView]; + + if (userViewList.Contains(userId)) + return true; + + return false; + } + + public async Task CanUserViewPersonLocationViaMatrixAsync(string userToView, string userId, int departmentId) + { + var matrix = await _cacheProvider.GetAsync(string.Format(WhoCanViewPersonnelLocationsCacheKey, departmentId)); + + // Fail open if the cache is not available for now. -SJ 8-26-2024 + if (matrix == null) + return true; + + if (matrix.EveryoneNoGroupLock) + return true; + + if (!matrix.Users.ContainsKey(userToView)) + return true; + + var userViewList = matrix.Users[userToView]; + + if (userViewList.Contains(userId)) + return true; + + return false; + } + + public async Task CanUserViewUnitViaMatrixAsync(int unitToView, string userId, int departmentId) + { + var matrix = await _cacheProvider.GetAsync(string.Format(WhoCanViewUnitsCacheKey, departmentId)); + + // Fail open if the cache is not available for now. -SJ 8-26-2024 + if (matrix == null) + return true; + + if (matrix.EveryoneNoGroupLock) + return true; + + if (!matrix.Units.ContainsKey(unitToView)) + return true; + + var userViewList = matrix.Units[unitToView]; + + if (userViewList.Contains(userId)) + return true; + + return false; + } + + public async Task CanUserViewUnitLocationViaMatrixAsync(int unitToView, string userId, int departmentId) + { + var matrix = await _cacheProvider.GetAsync(string.Format(WhoCanViewUnitLocationsCacheKey, departmentId)); + + // Fail open if the cache is not available for now. -SJ 8-26-2024 + if (matrix == null) + return true; + + if (matrix.EveryoneNoGroupLock) + return true; + + if (!matrix.Units.ContainsKey(unitToView)) + return true; + + var userViewList = matrix.Units[unitToView]; + + if (userViewList.Contains(userId)) + return true; + + return false; + } } } diff --git a/Core/Resgrid.Services/DepartmentGroupsService.cs b/Core/Resgrid.Services/DepartmentGroupsService.cs index 11513838..302e1f85 100644 --- a/Core/Resgrid.Services/DepartmentGroupsService.cs +++ b/Core/Resgrid.Services/DepartmentGroupsService.cs @@ -505,5 +505,12 @@ public List GetAllUsersForGroup(int groupId) { return await _departmentGroupMembersRepository.DeleteGroupMembersByGroupIdAsync(groupId, departmentId, cancellationToken); } + + public async Task> GetAllGroupAdminsByDepartmentIdAsync(int departmentId) + { + var admins = await _departmentGroupMembersRepository.GetAllGroupAdminsByDepartmentIdAsync(departmentId); + + return admins.ToList(); + } } } diff --git a/Core/Resgrid.Services/UsersService.cs b/Core/Resgrid.Services/UsersService.cs index 705528ad..5cfc46b8 100644 --- a/Core/Resgrid.Services/UsersService.cs +++ b/Core/Resgrid.Services/UsersService.cs @@ -280,63 +280,8 @@ public async Task SavePersonnelLocationAsync(PersonnelLocatio public async Task> GetLatestLocationsForDepartmentPersonnelAsync(int departmentId) { - /* - var pipeline = new BsonDocument[] - { - new BsonDocument("$match", - new BsonDocument - { - { "departmentId", departmentId } - }), - new BsonDocument("$sort", - new BsonDocument("timestamp", -1)), - new BsonDocument("$group", - new BsonDocument - { - { "_id", - new BsonDocument - { - { "userId", "$userId" }, - { "departmentId", "$departmentId" } - } - }, - { "Carddetails", - new BsonDocument("$first", "$Carddetails") - } - } - ), - new BsonDocument("$project", - new BsonDocument - { - { "_id", 0 }, - { "studentid", "$_id.studentid" }, - { "dept", "$_id.dept" }, - { "Carddetails", "$Carddetails" } - } - ) - }; - - var result = await _personnelLocationRepository.Aggregate(pipeline); - */ - try { - //var locations = _personnelLocationRepository.AsQueryable().Where(x => x.DepartmentId == departmentId).OrderByDescending(y => y.Timestamp) - // .GroupBy(x => x.UserId).FirstOrDefault(); - - //var layers = await _personnelLocationRepository.FilterByAsync(filter => filter.DepartmentId == departmentId && filter.Type == (int)type && filter.IsDeleted == false); - - - //var locations = _personnelLocationRepository.AsQueryable().Where(x => x.DepartmentId == departmentId).ToList();//.OrderByDescending(y => y.Timestamp);//.GroupBy(x => x.UnitId).ToList();//.FirstOrDefault(); - - //var layers = await _personnelLocationRepository.FilterByAsync(filter => filter.DepartmentId == departmentId && filter.Type == (int)type && filter.IsDeleted == false); - - //if (locations != null) - // return locations.OrderBy(y => y.Timestamp) - // .GroupBy(x => x.UserId) - // .Select(y => y.First()).ToList(); - - var locations = _personnelLocationRepository .AsQueryable() .Where(x => x.DepartmentId == departmentId).OrderByDescending(y => y.Timestamp) diff --git a/Providers/Resgrid.Providers.Cache/AzureRedisCacheProvider.cs b/Providers/Resgrid.Providers.Cache/AzureRedisCacheProvider.cs index 52dc0269..5884010b 100644 --- a/Providers/Resgrid.Providers.Cache/AzureRedisCacheProvider.cs +++ b/Providers/Resgrid.Providers.Cache/AzureRedisCacheProvider.cs @@ -203,6 +203,16 @@ public async Task GetStringAsync(string cacheKey) return null; } + public async Task GetAsync(string cacheKey) where T : class + { + var cacheValue = await GetStringAsync(cacheKey); + + if (!String.IsNullOrWhiteSpace(cacheValue)) + return ObjectSerialization.Deserialize(cacheValue); + + return null; + } + public async Task RemoveAsync(string cacheKey) { try diff --git a/Providers/Resgrid.Providers.Email/PostmarkTemplateProvider.cs b/Providers/Resgrid.Providers.Email/PostmarkTemplateProvider.cs index 5d940d5a..005a1481 100644 --- a/Providers/Resgrid.Providers.Email/PostmarkTemplateProvider.cs +++ b/Providers/Resgrid.Providers.Email/PostmarkTemplateProvider.cs @@ -14,7 +14,7 @@ public class PostmarkTemplateProvider : IEmailProvider { private readonly IEmailSender _emailSender; - private static string FROM_EMAIL = OutboundEmailServerConfig.ToMail; + private static string FROM_EMAIL = OutboundEmailServerConfig.FromMail; private static string DONOTREPLY_EMAIL = OutboundEmailServerConfig.FromMail; private static string LOGIN_URL = $"{SystemBehaviorConfig.ResgridBaseUrl}/Account/LogOn"; private static string LIVECHAT_URL = $"https://resgrid.com/contact"; diff --git a/Repositories/Resgrid.Repositories.DataRepository/Configs/SqlConfiguration.cs b/Repositories/Resgrid.Repositories.DataRepository/Configs/SqlConfiguration.cs index 8aef5dd7..0f259889 100644 --- a/Repositories/Resgrid.Repositories.DataRepository/Configs/SqlConfiguration.cs +++ b/Repositories/Resgrid.Repositories.DataRepository/Configs/SqlConfiguration.cs @@ -349,6 +349,7 @@ protected SqlConfiguration() { } public string SelectGroupByMessageCodeQuery { get; set; } public string SelectGroupByGroupIdQuery { get; set; } public string DeleteGroupMembersByGroupIdDidQuery { get; set; } + public string SelectGroupAdminsByDidQuery { get; set; } #endregion Department Groups #region Payments diff --git a/Repositories/Resgrid.Repositories.DataRepository/DepartmentGroupMembersRepository.cs b/Repositories/Resgrid.Repositories.DataRepository/DepartmentGroupMembersRepository.cs index 9634b9fa..d958b871 100644 --- a/Repositories/Resgrid.Repositories.DataRepository/DepartmentGroupMembersRepository.cs +++ b/Repositories/Resgrid.Repositories.DataRepository/DepartmentGroupMembersRepository.cs @@ -165,5 +165,49 @@ public async Task> GetAllGroupMembersByUserAn throw; } } + + + public async Task> GetAllGroupAdminsByDepartmentIdAsync(int departmentId) + { + try + { + var selectFunction = new Func>>(async x => + { + var dynamicParameters = new DynamicParameters(); + dynamicParameters.Add("DepartmentId", departmentId); + + var query = _queryFactory.GetQuery(); + + return await x.QueryAsync(sql: query, + param: dynamicParameters, + transaction: _unitOfWork.Transaction, + map: (dgm, dg) => { dgm.DepartmentGroup = dg; return dgm; }, + splitOn: "DepartmentGroupId"); + }); + + DbConnection conn = null; + if (_unitOfWork?.Connection == null) + { + using (conn = _connectionProvider.Create()) + { + await conn.OpenAsync(); + + return await selectFunction(conn); + } + } + else + { + conn = _unitOfWork.CreateOrGetConnection(); + + return await selectFunction(conn); + } + } + catch (Exception ex) + { + Logging.LogException(ex); + + throw; + } + } } } diff --git a/Repositories/Resgrid.Repositories.DataRepository/Queries/DepartmentGroups/SelectGroupAdminsByDidQuery.cs b/Repositories/Resgrid.Repositories.DataRepository/Queries/DepartmentGroups/SelectGroupAdminsByDidQuery.cs new file mode 100644 index 00000000..1379115b --- /dev/null +++ b/Repositories/Resgrid.Repositories.DataRepository/Queries/DepartmentGroups/SelectGroupAdminsByDidQuery.cs @@ -0,0 +1,47 @@ +using Resgrid.Model; +using Resgrid.Model.Repositories.Queries.Contracts; +using Resgrid.Repositories.DataRepository.Configs; +using Resgrid.Repositories.DataRepository.Extensions; + +namespace Resgrid.Repositories.DataRepository.Queries.DepartmentGroups +{ + public class SelectGroupAdminsByDidQuery : ISelectQuery + { + private readonly SqlConfiguration _sqlConfiguration; + public SelectGroupAdminsByDidQuery(SqlConfiguration sqlConfiguration) + { + _sqlConfiguration = sqlConfiguration; + } + + public string GetQuery() + { + var query = _sqlConfiguration.SelectGroupAdminsByDidQuery + .ReplaceQueryParameters(_sqlConfiguration.SchemaName, + string.Empty, + _sqlConfiguration.ParameterNotation, + new string[] { + "%DID%" + }, + new string[] { + "DepartmentId", + }, + new string[] { + "%GROUPSTABLE%", + "%GROUPMEMBERSSTABLE%" + }, + new string[] { + _sqlConfiguration.DepartmentGroupsTable, + _sqlConfiguration.DepartmentGroupMembersTable + } + ); + + + return query; + } + + public string GetQuery() where TEntity : class, IEntity + { + throw new System.NotImplementedException(); + } + } +} diff --git a/Repositories/Resgrid.Repositories.DataRepository/Servers/SqlServer/SqlServerConfiguration.cs b/Repositories/Resgrid.Repositories.DataRepository/Servers/SqlServer/SqlServerConfiguration.cs index 8e4b1552..5747fd3b 100644 --- a/Repositories/Resgrid.Repositories.DataRepository/Servers/SqlServer/SqlServerConfiguration.cs +++ b/Repositories/Resgrid.Repositories.DataRepository/Servers/SqlServer/SqlServerConfiguration.cs @@ -1142,6 +1142,11 @@ LEFT JOIN (SELECT dgm1.* DeleteGroupMembersByGroupIdDidQuery = @" DELETE FROM %SCHEMA%.%TABLENAME% WHERE DepartmentId = %DID% AND DepartmentGroupId = %ID%"; + SelectGroupAdminsByDidQuery = @" + SELECT dgm.*, dg.* + FROM %SCHEMA%.%GROUPMEMBERSSTABLE% dgm + INNER JOIN %SCHEMA%.%GROUPSTABLE% dg ON dg.[DepartmentGroupId] = dgm.[DepartmentGroupId] + WHERE dgm.[IsAdmin] = 1 AND dgm.[DepartmentId] = %DID%"; #endregion Department Groups #region Payments diff --git a/Tools/Resgrid.Console/Args/SecurityRefreshArgs.cs b/Tools/Resgrid.Console/Args/SecurityRefreshArgs.cs new file mode 100644 index 00000000..b2f5cb1d --- /dev/null +++ b/Tools/Resgrid.Console/Args/SecurityRefreshArgs.cs @@ -0,0 +1,7 @@ +namespace Resgrid.Console.Args +{ + public class SecurityRefreshArgs + { + public bool SecurityRefresh { get; set; } + } +} diff --git a/Tools/Resgrid.Console/Commands/SecurityRefreshCommand.cs b/Tools/Resgrid.Console/Commands/SecurityRefreshCommand.cs new file mode 100644 index 00000000..0cf22ce4 --- /dev/null +++ b/Tools/Resgrid.Console/Commands/SecurityRefreshCommand.cs @@ -0,0 +1,48 @@ +using Resgrid.Console.Args; +using System; +using Consolas2.Core; +using Resgrid.Workers.Framework.Logic; +using Amazon.Runtime.Internal.Util; +using System.Diagnostics; + +namespace Resgrid.Console.Commands +{ + public class SecurityRefreshCommand : Command + { + private readonly IConsole _console; + + public SecurityRefreshCommand(IConsole console) + { + _console = console; + } + + public string Execute(SecurityRefreshArgs args) + { + _console.WriteLine("Starting the Security Refresh Process"); + _console.WriteLine(DateTime.Now.ToString("MM/dd/yy H:mm:ss zzz")); + _console.WriteLine("Please Wait..."); + + try + { + Stopwatch timer = new Stopwatch(); + var security = new SecurityLogic(); + timer.Start(); + + var result = AsyncHelpers.RunSync>(() => security.UpdatedCachedSecurityForAllDepartments()); + + timer.Stop(); + + _console.WriteLine(DateTime.Now.ToString("MM/dd/yy H:mm:ss zzz")); + _console.WriteLine($"Completed the Security Refresh Process in {timer.Elapsed.ToString("mm\\:ss\\.ff")}!"); + } + catch (Exception ex) + { + _console.WriteLine("There was an error trying to run the Security Refresh Process, see the error output below:"); + _console.WriteLine(DateTime.Now.ToString("MM/dd/yy H:mm:ss zzz")); + _console.WriteLine(ex.ToString()); + } + + return ""; + } + } +} diff --git a/Tools/Resgrid.Console/Properties/launchSettings.json b/Tools/Resgrid.Console/Properties/launchSettings.json index 61390679..0a37016e 100644 --- a/Tools/Resgrid.Console/Properties/launchSettings.json +++ b/Tools/Resgrid.Console/Properties/launchSettings.json @@ -2,7 +2,7 @@ "profiles": { "Resgrid.Console": { "commandName": "Project", - "commandLineArgs": "-DbUpdate" + "commandLineArgs": "--SecurityRefresh" } } } \ No newline at end of file diff --git a/Web/Resgrid.Web.Eventing/Resgrid.Web.Eventing.csproj b/Web/Resgrid.Web.Eventing/Resgrid.Web.Eventing.csproj index 16d1d8c7..c9c0713a 100644 --- a/Web/Resgrid.Web.Eventing/Resgrid.Web.Eventing.csproj +++ b/Web/Resgrid.Web.Eventing/Resgrid.Web.Eventing.csproj @@ -44,8 +44,8 @@ - - + + diff --git a/Web/Resgrid.Web.ServicesCore/Controllers/v4/MappingController.cs b/Web/Resgrid.Web.ServicesCore/Controllers/v4/MappingController.cs index bbee8dcf..8d6417f8 100644 --- a/Web/Resgrid.Web.ServicesCore/Controllers/v4/MappingController.cs +++ b/Web/Resgrid.Web.ServicesCore/Controllers/v4/MappingController.cs @@ -42,6 +42,7 @@ public class MappingController : V4AuthenticatedApiControllerbase private readonly IDepartmentSettingsService _departmentSettingsService; private readonly IGeoLocationProvider _geoLocationProvider; private readonly IMappingService _mappingService; + private readonly Model.Services.IAuthorizationService _authorizationService; public MappingController( IUsersService usersService, @@ -56,7 +57,8 @@ public MappingController( ICustomStateService customStateService, IDepartmentSettingsService departmentSettingsService, IGeoLocationProvider geoLocationProvider, - IMappingService mappingService + IMappingService mappingService, + Model.Services.IAuthorizationService authorizationService ) { _usersService = usersService; @@ -72,6 +74,7 @@ IMappingService mappingService _departmentSettingsService = departmentSettingsService; _geoLocationProvider = geoLocationProvider; _mappingService = mappingService; + _authorizationService = authorizationService; } #endregion Members and Constructors @@ -296,6 +299,9 @@ public async Task> GetMapDataAndMarkers() { foreach (var unit in units) { + if (!await _authorizationService.CanUserViewUnitLocationViaMatrixAsync(unit.UnitId, UserId, DepartmentId)) + continue; + var latestLocation = unitLocations.FirstOrDefault(x => x.UnitId == unit.UnitId); var state = unitStates.FirstOrDefault(x => x.UnitId == unit.UnitId); @@ -371,6 +377,9 @@ public async Task> GetMapDataAndMarkers() { foreach (var person in personnelNames) { + if (!await _authorizationService.CanUserViewPersonLocationViaMatrixAsync(person.UserId, UserId, DepartmentId)) + continue; + var latestLocation = personnelLocations.FirstOrDefault(x => x.UserId == person.UserId); var state = personnelStates.FirstOrDefault(x => x.UserId == person.UserId); diff --git a/Web/Resgrid.Web.ServicesCore/Program.cs b/Web/Resgrid.Web.ServicesCore/Program.cs index 5a5bcb17..48f86bff 100644 --- a/Web/Resgrid.Web.ServicesCore/Program.cs +++ b/Web/Resgrid.Web.ServicesCore/Program.cs @@ -27,11 +27,8 @@ public static IHostBuilder CreateHostBuilder(string[] args) => logging.ClearProviders(); logging.AddConsole(); }) - //.UseIISIntegration() .ConfigureWebHostDefaults(webBuilder => { - webBuilder.UseStartup(); - if (!string.IsNullOrWhiteSpace(Config.ExternalErrorConfig.ExternalErrorServiceUrlForApi)) { webBuilder.UseSentry(options => @@ -79,6 +76,8 @@ public static IHostBuilder CreateHostBuilder(string[] args) => }; }); } + + webBuilder.UseStartup(); }); } } diff --git a/Web/Resgrid.Web.ServicesCore/Resgrid.Web.ServicesCore.csproj b/Web/Resgrid.Web.ServicesCore/Resgrid.Web.ServicesCore.csproj index 158281e5..79ad1f88 100644 --- a/Web/Resgrid.Web.ServicesCore/Resgrid.Web.ServicesCore.csproj +++ b/Web/Resgrid.Web.ServicesCore/Resgrid.Web.ServicesCore.csproj @@ -64,9 +64,9 @@ - - - + + + diff --git a/Web/Resgrid.WebCore/Areas/User/Controllers/DepartmentController.cs b/Web/Resgrid.WebCore/Areas/User/Controllers/DepartmentController.cs index f31325dd..50f0df6b 100644 --- a/Web/Resgrid.WebCore/Areas/User/Controllers/DepartmentController.cs +++ b/Web/Resgrid.WebCore/Areas/User/Controllers/DepartmentController.cs @@ -11,7 +11,6 @@ using Resgrid.Model; using Resgrid.Model.Events; using Resgrid.Model.Services; -using Resgrid.Providers.Bus; using Resgrid.Providers.Claims; using Resgrid.Web.Areas.User.Models; using Resgrid.Web.Areas.User.Models.Departments; @@ -24,10 +23,6 @@ using Microsoft.AspNetCore.Authorization; using Resgrid.Web.Models.AccountViewModels; using Resgrid.Model.Providers; -using Resgrid.WebCore.Areas.User.Models.Dispatch; -using Resgrid.Web.Areas.User.Models.Dispatch; -using Elasticsearch.Net; -using Microsoft.AspNetCore.Identity; using AuditEvent = Resgrid.Model.Events.AuditEvent; using Microsoft.AspNetCore.Http; diff --git a/Web/Resgrid.WebCore/Areas/User/Controllers/NotesController.cs b/Web/Resgrid.WebCore/Areas/User/Controllers/NotesController.cs index bfa34205..fd83f4e9 100644 --- a/Web/Resgrid.WebCore/Areas/User/Controllers/NotesController.cs +++ b/Web/Resgrid.WebCore/Areas/User/Controllers/NotesController.cs @@ -1,18 +1,13 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Threading; using System.Threading.Tasks; -using Elasticsearch.Net; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Rendering; using Resgrid.Framework; -using Resgrid.Localization.Areas.User.Notes; using Resgrid.Model; -using Resgrid.Model.Events; using Resgrid.Model.Providers; using Resgrid.Model.Services; -using Resgrid.Services; using Resgrid.Web.Areas.User.Models.Notes; using Resgrid.Web.Helpers; using AuditEvent = Resgrid.Model.Events.AuditEvent; diff --git a/Web/Resgrid.WebCore/Areas/User/Controllers/SecurityController.cs b/Web/Resgrid.WebCore/Areas/User/Controllers/SecurityController.cs index fd85c946..192a20cf 100644 --- a/Web/Resgrid.WebCore/Areas/User/Controllers/SecurityController.cs +++ b/Web/Resgrid.WebCore/Areas/User/Controllers/SecurityController.cs @@ -51,8 +51,8 @@ public async Task Index() if (permissions.Any(x => x.PermissionType == (int)PermissionTypes.RemovePersonnel)) model.RemoveUsers = permissions.First(x => x.PermissionType == (int)PermissionTypes.RemovePersonnel).Action; - if (permissions.Any(x => x.PermissionType == (int) PermissionTypes.CreateCall)) - model.CreateCall = permissions.First(x => x.PermissionType == (int) PermissionTypes.CreateCall).Action; + if (permissions.Any(x => x.PermissionType == (int)PermissionTypes.CreateCall)) + model.CreateCall = permissions.First(x => x.PermissionType == (int)PermissionTypes.CreateCall).Action; else model.CreateCall = 3; @@ -267,6 +267,21 @@ public async Task Index() addCallDataPermissions.Add(new { Id = 2, Name = "Department Admins and Select Roles" }); model.AddCallDataPermissions = new SelectList(addCallDataPermissions, "Id", "Name"); + if (permissions.Any(x => x.PermissionType == (int)PermissionTypes.ViewGroupUnits)) + { + model.ViewGroupsUnits = permissions.First(x => x.PermissionType == (int)PermissionTypes.ViewGroupUnits).Action; + model.LockViewGroupsUnitsToGroup = permissions.First(x => x.PermissionType == (int)PermissionTypes.ViewGroupUnits).LockToGroup; + } + else + model.ViewGroupsUnits = 3; + + var viewGroupUnitsPermissions = new List(); + viewGroupUnitsPermissions.Add(new { Id = 3, Name = "Everyone" }); + viewGroupUnitsPermissions.Add(new { Id = 0, Name = "Department Admins" }); + viewGroupUnitsPermissions.Add(new { Id = 1, Name = "Department and Group Admins" }); + viewGroupUnitsPermissions.Add(new { Id = 2, Name = "Department Admins and Select Roles" }); + model.ViewGrouUnitsPermissions = new SelectList(viewGroupUnitsPermissions, "Id", "Name"); + return View(model); } diff --git a/Web/Resgrid.WebCore/Areas/User/Models/Security/PermissionsView.cs b/Web/Resgrid.WebCore/Areas/User/Models/Security/PermissionsView.cs index e841ec4b..4ec15403 100644 --- a/Web/Resgrid.WebCore/Areas/User/Models/Security/PermissionsView.cs +++ b/Web/Resgrid.WebCore/Areas/User/Models/Security/PermissionsView.cs @@ -65,5 +65,9 @@ public class PermissionsView public int AddCallData { get; set; } public bool LockAddCallDataToGroup { get; set; } public SelectList AddCallDataPermissions { get; set; } + + public int ViewGroupsUnits { get; set; } + public bool LockViewGroupsUnitsToGroup { get; set; } + public SelectList ViewGrouUnitsPermissions { get; set; } } } diff --git a/Web/Resgrid.WebCore/Areas/User/Views/Account/DeleteAccount.cshtml b/Web/Resgrid.WebCore/Areas/User/Views/Account/DeleteAccount.cshtml index b6498452..dff7ea5e 100644 --- a/Web/Resgrid.WebCore/Areas/User/Views/Account/DeleteAccount.cshtml +++ b/Web/Resgrid.WebCore/Areas/User/Views/Account/DeleteAccount.cshtml @@ -55,10 +55,7 @@
-
- - -
+
@@ -83,12 +80,12 @@ @section Scripts - { +{ +{ + } diff --git a/Web/Resgrid.WebCore/Areas/User/Views/Connect/Profile.cshtml b/Web/Resgrid.WebCore/Areas/User/Views/Connect/Profile.cshtml index edb5412c..d9e62e37 100644 --- a/Web/Resgrid.WebCore/Areas/User/Views/Connect/Profile.cshtml +++ b/Web/Resgrid.WebCore/Areas/User/Views/Connect/Profile.cshtml @@ -2,414 +2,411 @@ @using Resgrid.Web.Helpers @model Resgrid.Web.Areas.User.Models.Connect.ProfileView @{ - ViewBag.Title = "Resgrid | Connect Profile"; + ViewBag.Title = "Resgrid | Connect Profile"; } @section Styles { - + }
- @Html.AntiForgeryToken() - @Html.HiddenFor(m => m.Profile.DepartmentProfileId) - @Html.HiddenFor(m => m.Profile.AddressId) - @Html.HiddenFor(m => m.Profile.Code) + @Html.AntiForgeryToken() + @Html.HiddenFor(m => m.Profile.DepartmentProfileId) + @Html.HiddenFor(m => m.Profile.AddressId) + @Html.HiddenFor(m => m.Profile.Code) -
-
-

Connect Profile

- -
-
-
-
-
-
-
-
-
-

This form allows you to configure how your department looks to people using the Resgrid Connect app. You can hide your department from Connect users by checking the Disable Profile checkbox and saving the form. Fields in blue italics are required.

-
-
-
- -
-
- - -
-
-
-
- -
-
-
- -
-
+
+
+

Connect Profile

+ +
+
+
+
+
+
+
+
+
+

This form allows you to configure how your department looks to people using the Resgrid Connect app. You can hide your department from Connect users by checking the Disable Profile checkbox and saving the form. Fields in blue italics are required.

+
+
+
+ +
+ +
+
+
+ +
+
+
+ +
+
-
- -
-
-
- -
-
-
-
-
- -
-
-
- @Html.TextBoxFor(m => m.Profile.InCaseOfEmergency, new { @class = "form-control" }) -
-
-
-
- Resgrid Connect is not intended for life threatening or emergency communications. How can your users communicate in emergency situations? -
-
-
-
-
- -
-
-
- -
-
-
-
-
- -
-
-
- @Html.TextBoxFor(m => m.Profile.Keywords, new { @class = "form-control" }) -
-
-
-
- Tags or Keywords that describe your department separated by commas i.e.: Fire, Firefighters, New York, NYC, EMS -
-
-
-
-
- -
-
-
- @Html.TextBoxFor(m => m.Profile.ServicesProvided, new { @class = "form-control" }) -
-
-
-
-
+
+ +
+
+
+ +
+
+
+
+
+ +
+
+
+ @Html.TextBoxFor(m => m.Profile.InCaseOfEmergency, new { @class = "form-control" }) +
+
+
+
+ Resgrid Connect is not intended for life threatening or emergency communications. How can your users communicate in emergency situations? +
+
+
+
+
+ +
+
+
+ +
+
+
+
+
+ +
+
+
+ @Html.TextBoxFor(m => m.Profile.Keywords, new { @class = "form-control" }) +
+
+
+
+ Tags or Keywords that describe your department separated by commas i.e.: Fire, Firefighters, New York, NYC, EMS +
+
+
+
+
+ +
+
+
+ @Html.TextBoxFor(m => m.Profile.ServicesProvided, new { @class = "form-control" }) +
+
+
+
+
-

Location

-
- -
-
-
- @Html.TextBoxFor(m => m.Profile.ServiceArea, new { @class = "form-control" }) -
-
-
-
-
- +
+
+
+ Supply a location that will help users find your department based on location/proximity. Please enter an Address or GPS Coordinates or a what3words address. +
+
+
+
+
+ +
+
+
+ @Html.TextBoxFor(m => m.Address1, new { @class = "form-control" }) +
+
+
+
+
+ +
+
+
+ @Html.TextBoxFor(m => m.City, new { @class = "form-control" }) +
+
+
+
+
+ +
+
+
+ @Html.TextBoxFor(m => m.State, new { @class = "form-control" }) +
+
+
+
+
+ +
+
+
+ @Html.TextBoxFor(m => m.PostalCode, new { @class = "form-control" }) +
+
+
+
+
+ +
+
+
+ @Html.DropDownListFor(m => m.Country, new SelectList(Countries.CountryNames), new { @class = "form-control", style = "width: 300px" }) +
+
+
+
+
+ +
+
+ @Html.TextBoxFor(m => m.Profile.Latitude, new { @class = "form-control" }) + Latitude (Decimal Notation: i.e. 39.1517) +
+
+ @Html.TextBoxFor(m => m.Profile.Longitude, new { @class = "form-control" }) + Longitude (Decimal Notation: i.e. -119.4571) +
+
+
+
+ +
+
+ @Html.TextBoxFor(m => m.Profile.What3Words, new { @class = "form-control", @style = "width:250px;", autocomplete = "off", placeholder = "what.three.words", tabindex = "8" }) + w3w Address (i.e. humble.echo.sticky) +
+
+
+
-

Volunteer Info

-
- -
-
-
- @Html.CheckBoxFor(m => m.Profile.VolunteerPositionsAvailable) -
-
-
-
-
- -
-
-
- @Html.TextBoxFor(m => m.Profile.VolunteerContactName, new { @class = "form-control" }) -
-
-
-
-
- -
-
-
- @Html.TextBoxFor(m => m.Profile.VolunteerContactInfo, new { @class = "form-control" }) -
-
-
-
-
- -
-
-
- @Html.TextAreaFor(m => m.Profile.VolunteerDescription, new { @class = "form-control", rows = "5" }) -
-
-
-
-
- -
-
-
- @Html.TextBoxFor(m => m.Profile.VolunteerKeywords, new { @class = "form-control" }) -
-
-
-
- Tags or Keywords that describe your volunteer opportunities separated by commas i.e.: Firefighter, CERT, SAR, EMT -
-
-
-
-
+

Volunteer Info

+
+ +
+
+
+ @Html.CheckBoxFor(m => m.Profile.VolunteerPositionsAvailable) +
+
+
+
+
+ +
+
+
+ @Html.TextBoxFor(m => m.Profile.VolunteerContactName, new { @class = "form-control" }) +
+
+
+
+
+ +
+
+
+ @Html.TextBoxFor(m => m.Profile.VolunteerContactInfo, new { @class = "form-control" }) +
+
+
+
+
+ +
+
+
+ @Html.TextAreaFor(m => m.Profile.VolunteerDescription, new { @class = "form-control", rows = "5" }) +
+
+
+
+
+ +
+
+
+ @Html.TextBoxFor(m => m.Profile.VolunteerKeywords, new { @class = "form-control" }) +
+
+
+
+ Tags or Keywords that describe your volunteer opportunities separated by commas i.e.: Firefighter, CERT, SAR, EMT +
+
+
+
+
-
-
- Cancel - -
-
-
+
+
+ Cancel + +
+
+
-
-
-
-
-
-
-
-
-
- -
-
 
-
click here to set a picture
-
picture changes may take up to 24 hours
-
-
-
+
+
+
+
+
+
+
+
+
+ +
+
 
+
click here to set a picture
+
picture changes may take up to 24 hours
+
+
+
-
-
-
Features
-
-
-
- -
-
-
- - -
-
-
-
-
- -
-
-
- - -
-
-
-
-
- -
-
-
- - -
-
- If your department is private people will need to use your access code (@Model.Profile.Code) to follow your department. -
-
-
-
-
-
-
+
+
+
Features
+
+
+
+ +
+
+
+ + +
+
+
+
+
+ +
+
+
+ + +
+
+
+
+
+ +
+
+
+ + +
+
+ If your department is private people will need to use your access code (@Model.Profile.Code) to follow your department. +
+
+
+
+
+
+
} @section Scripts { - - - + + + } diff --git a/Web/Resgrid.WebCore/Areas/User/Views/Department/CallSettings.cshtml b/Web/Resgrid.WebCore/Areas/User/Views/Department/CallSettings.cshtml index 95080c3e..aa3ffdb0 100644 --- a/Web/Resgrid.WebCore/Areas/User/Views/Department/CallSettings.cshtml +++ b/Web/Resgrid.WebCore/Areas/User/Views/Department/CallSettings.cshtml @@ -4,37 +4,37 @@ ViewBag.Title = "Resgrid | " + @localizer["CallSettingsHeader"]; } @section Styles - { +{ } -
-
-

@localizer["CallSettingsHeader"]

- -
+
+
+

@localizer["CallSettingsHeader"]

+
+
-
-
-
-
-
-
+
+
+
+
+
+ - @Html.AntiForgeryToken() - @Html.HiddenFor(m => m.EmailSettings.DepartmentCallEmailId) - @Html.HiddenFor(m => m.CallType) - @Html.HiddenFor(m => m.TextCallType) -
- @if (!String.IsNullOrEmpty(Model.Message)) + @Html.AntiForgeryToken() + @Html.HiddenFor(m => m.EmailSettings.DepartmentCallEmailId) + @Html.HiddenFor(m => m.CallType) + @Html.HiddenFor(m => m.TextCallType) +
+ @if (!String.IsNullOrEmpty(Model.Message)) {
@Model.Message @@ -51,10 +51,7 @@
-
- - -
+ @localizer["PruneCallsHelp"]
@@ -181,7 +178,7 @@
@section Scripts - { +{ +

@localizer["AddArchivedCallHeader"]

@@ -200,11 +202,8 @@
-
- - - @localizer["RegenerateCallWarning"] -
+ + @localizer["RegenerateCallWarning"]
diff --git a/Web/Resgrid.WebCore/Areas/User/Views/Dispatch/CloseCall.cshtml b/Web/Resgrid.WebCore/Areas/User/Views/Dispatch/CloseCall.cshtml index 3bf8c04f..bbfe24f3 100644 --- a/Web/Resgrid.WebCore/Areas/User/Views/Dispatch/CloseCall.cshtml +++ b/Web/Resgrid.WebCore/Areas/User/Views/Dispatch/CloseCall.cshtml @@ -48,10 +48,7 @@
-
- - -
+
diff --git a/Web/Resgrid.WebCore/Areas/User/Views/Dispatch/Dashboard.cshtml b/Web/Resgrid.WebCore/Areas/User/Views/Dispatch/Dashboard.cshtml index d9948842..034529bd 100644 --- a/Web/Resgrid.WebCore/Areas/User/Views/Dispatch/Dashboard.cshtml +++ b/Web/Resgrid.WebCore/Areas/User/Views/Dispatch/Dashboard.cshtml @@ -100,6 +100,8 @@ @section Scripts { + + diff --git a/Web/Resgrid.WebCore/Areas/User/Views/Dispatch/UpdateCall.cshtml b/Web/Resgrid.WebCore/Areas/User/Views/Dispatch/UpdateCall.cshtml index 27147696..2330b360 100644 --- a/Web/Resgrid.WebCore/Areas/User/Views/Dispatch/UpdateCall.cshtml +++ b/Web/Resgrid.WebCore/Areas/User/Views/Dispatch/UpdateCall.cshtml @@ -191,11 +191,8 @@
-
- - - @localizer["RedispatchCallHelp"] -
+ + @localizer["RedispatchCallHelp"]
diff --git a/Web/Resgrid.WebCore/Areas/User/Views/Groups/DeleteGroup.cshtml b/Web/Resgrid.WebCore/Areas/User/Views/Groups/DeleteGroup.cshtml index aba938b2..2b22c92b 100644 --- a/Web/Resgrid.WebCore/Areas/User/Views/Groups/DeleteGroup.cshtml +++ b/Web/Resgrid.WebCore/Areas/User/Views/Groups/DeleteGroup.cshtml @@ -95,10 +95,7 @@
-
- - -
+
@@ -117,6 +114,6 @@
@section Scripts - { +{ } diff --git a/Web/Resgrid.WebCore/Areas/User/Views/Home/EditUserProfile.cshtml b/Web/Resgrid.WebCore/Areas/User/Views/Home/EditUserProfile.cshtml index 79c1392c..4afbe511 100644 --- a/Web/Resgrid.WebCore/Areas/User/Views/Home/EditUserProfile.cshtml +++ b/Web/Resgrid.WebCore/Areas/User/Views/Home/EditUserProfile.cshtml @@ -134,10 +134,7 @@
-
- - -
+
@@ -180,7 +177,7 @@
-
+
@@ -229,19 +226,13 @@
-
- - -
+
-
- - -
+
diff --git a/Web/Resgrid.WebCore/Areas/User/Views/Links/New.cshtml b/Web/Resgrid.WebCore/Areas/User/Views/Links/New.cshtml index 9ff70e6a..c65612a8 100644 --- a/Web/Resgrid.WebCore/Areas/User/Views/Links/New.cshtml +++ b/Web/Resgrid.WebCore/Areas/User/Views/Links/New.cshtml @@ -60,25 +60,19 @@
- This is the color that will be applied to identifiy Calls\Units\Personnel for this department link + This is the color that will be applied to identify Calls\Units\Personnel for this department link
-
- - -
+
- +
-
- - -
+
@@ -86,10 +80,7 @@ Share Personnel
-
- - -
+
@@ -97,10 +88,7 @@ Share Orders
-
- - -
+
diff --git a/Web/Resgrid.WebCore/Areas/User/Views/Security/Index.cshtml b/Web/Resgrid.WebCore/Areas/User/Views/Security/Index.cshtml index a5af2c1a..bfac4bab 100644 --- a/Web/Resgrid.WebCore/Areas/User/Views/Security/Index.cshtml +++ b/Web/Resgrid.WebCore/Areas/User/Views/Security/Index.cshtml @@ -28,408 +28,429 @@
-
-
-
-
-
-
-

Here you can set the permissions for your department, for example which users or roles can create calls, or who is authorized to create and remove users. Changes to the permissions will take effect on the next login to the Resgrid web application.

-
-
-@if (ClaimsAuthorizationHelper.IsUserDepartmentAdmin()) -{ -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
+
+
+
+
+
+

Here you can set the permissions for your department, for example which users or roles can create calls, or who is authorized to create and remove users. Changes to the permissions will take effect on the next login to the Resgrid web application.

+
+
+ @if (ClaimsAuthorizationHelper.IsUserDepartmentAdmin()) + { +
+
- Permission - - Note - - Selection - - Group Only - - Roles -
- Who can Add Users - - This option determines who can add users/personnel to the department. By default only Department Administrators (and the managing member) can add users. - But Group Admins can also be allowed to add users (limited only to the group they are an admin of). - - @Html.DropDownListFor(m => m.AddUsers, Model.AddUserPermissions) - - N/A - - No Roles -
- Who can Remove Users - - This option determines who can remove users/personnel from the department. By default only Department Administrators (and the managing member) can remove users. - But Group Admins can also be allowed to remove users (limited only to the users in the group they are an admin of). - - @Html.DropDownListFor(m => m.RemoveUsers, Model.RemoveUserPermissions) - - N/A - - No Roles -
- Who can Create Calls - - This option determines who can manually create calls from the Resgrid system. By default Everyone can create calls. - - @Html.DropDownListFor(m => m.CreateCall, Model.CreateCallPermissions) - - N/A - - No Roles - -
- Who can Delete Calls - - This option determines who can delete calls from the Resgrid system. By default Everyone can delete calls. - - @Html.DropDownListFor(m => m.DeleteCall, Model.DeleteCallPermissions) - - - - No Roles - -
- Who can Close Calls - - This option determines who can close calls from the Resgrid system. By default Everyone can close calls. - - @Html.DropDownListFor(m => m.CloseCall, Model.CloseCallPermissions) - - - - No Roles - -
- Who can Add Data To Calls Calls - - This option determines who can add data; like images, notes and files, to calls from the Resgrid system. By default Everyone can add data to calls. - - @Html.DropDownListFor(m => m.AddCallData, Model.AddCallDataPermissions) - - - - No Roles - -
- Who can Create Trainings - - This option determines who can create trainings. By default only Department Admins can create trainings. - - @Html.DropDownListFor(m => m.CreateTraining, Model.CreateTrainingPermissions) - - N/A - - No Roles - -
- Who can Add Documents - - This option determines who can add documents. By default Everyone can add documents - - @Html.DropDownListFor(m => m.CreateDocument, Model.CreateDocumentPermissions) - - N/A - - No Roles - -
- Who can Create Calendar Entries - - This option determines who can create calendar entires. By default Everyone can create calendar entries. - - @Html.DropDownListFor(m => m.CreateCalendarEntry, Model.CreateCalendarEntryPermissions) - - N/A - - No Roles - -
- Who can Create Notes - - This option determines who can create calendar entires. By default Everyone can create calendar entries. - - @Html.DropDownListFor(m => m.CreateNote, Model.CreateNotePermissions) - - N/A - - No Roles - -
- Who can Add Log Entries - - This option determines who can add log entries. By default Everyone can add log entries. - - @Html.DropDownListFor(m => m.CreateLog, Model.CreateLogPermissions) - - N/A - - No Roles - -
- Who can Create Shifts - - This option determines who can create and edit shifts. By default only Department Admins can create and edit shifts. - - @Html.DropDownListFor(m => m.CreateShift, Model.CreateShiftPermissions) - - N/A - - No Roles - -
- Who can View Personal Info - - This option determines who can view personal information (PII) about personnel in the system. For example: Email Address, Phone Numbers, etc. By default Everyone can view this information. - - @Html.DropDownListFor(m => m.ViewPersonalInfo, Model.ViewPersonalInfoPermissions) - - N/A - - No Roles - -
- Who can Adjust Inventory - - This option determines who can adjust inventory levels in the system. By default Everyone can adjust inventory. - - @Html.DropDownListFor(m => m.AdjustInventory, Model.AdjustInventoryPermissions) - - N/A - - No Roles - -
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - + + + + + + + - - - - - - - + + + + + + + - - - - - - - + + + + + + + - - - - - - - - -
+ Permission + + Note + + Selection + + Group Only + + Roles +
+ Who can Add Users + + This option determines who can add users/personnel to the department. By default only Department Administrators (and the managing member) can add users. + But Group Admins can also be allowed to add users (limited only to the group they are an admin of). + + @Html.DropDownListFor(m => m.AddUsers, Model.AddUserPermissions) + + N/A + + No Roles +
+ Who can Remove Users + + This option determines who can remove users/personnel from the department. By default only Department Administrators (and the managing member) can remove users. + But Group Admins can also be allowed to remove users (limited only to the users in the group they are an admin of). + + @Html.DropDownListFor(m => m.RemoveUsers, Model.RemoveUserPermissions) + + N/A + + No Roles +
+ Who can Create Calls + + This option determines who can manually create calls from the Resgrid system. By default Everyone can create calls. + + @Html.DropDownListFor(m => m.CreateCall, Model.CreateCallPermissions) + + N/A + + No Roles + +
+ Who can Delete Calls + + This option determines who can delete calls from the Resgrid system. By default Everyone can delete calls. + + @Html.DropDownListFor(m => m.DeleteCall, Model.DeleteCallPermissions) + + + + No Roles + +
+ Who can Close Calls + + This option determines who can close calls from the Resgrid system. By default Everyone can close calls. + + @Html.DropDownListFor(m => m.CloseCall, Model.CloseCallPermissions) + + + + No Roles + +
+ Who can Add Data To Calls Calls + + This option determines who can add data; like images, notes and files, to calls from the Resgrid system. By default Everyone can add data to calls. + + @Html.DropDownListFor(m => m.AddCallData, Model.AddCallDataPermissions) + + + + No Roles + +
+ Who can Create Trainings + + This option determines who can create trainings. By default only Department Admins can create trainings. + + @Html.DropDownListFor(m => m.CreateTraining, Model.CreateTrainingPermissions) + + N/A + + No Roles + +
+ Who can Add Documents + + This option determines who can add documents. By default Everyone can add documents + + @Html.DropDownListFor(m => m.CreateDocument, Model.CreateDocumentPermissions) + + N/A + + No Roles + +
+ Who can Create Calendar Entries + + This option determines who can create calendar entires. By default Everyone can create calendar entries. + + @Html.DropDownListFor(m => m.CreateCalendarEntry, Model.CreateCalendarEntryPermissions) + + N/A + + No Roles + +
+ Who can Create Notes + + This option determines who can create calendar entires. By default Everyone can create calendar entries. + + @Html.DropDownListFor(m => m.CreateNote, Model.CreateNotePermissions) + + N/A + + No Roles + +
+ Who can Add Log Entries + + This option determines who can add log entries. By default Everyone can add log entries. + + @Html.DropDownListFor(m => m.CreateLog, Model.CreateLogPermissions) + + N/A + + No Roles + +
+ Who can Create Shifts + + This option determines who can create and edit shifts. By default only Department Admins can create and edit shifts. + + @Html.DropDownListFor(m => m.CreateShift, Model.CreateShiftPermissions) + + N/A + + No Roles + +
+ Who can View Personal Info + + This option determines who can view personal information (PII) about personnel in the system. For example: Email Address, Phone Numbers, etc. By default Everyone can view this information. + + @Html.DropDownListFor(m => m.ViewPersonalInfo, Model.ViewPersonalInfoPermissions) + + N/A + + No Roles + +
+ Who can Adjust Inventory + + This option determines who can adjust inventory levels in the system. By default Everyone can adjust inventory. + + @Html.DropDownListFor(m => m.AdjustInventory, Model.AdjustInventoryPermissions) + + N/A + + No Roles + +
- Who can see the Location of Personnel - - This option determines who can see the location of personnel on the maps. To lock the option to just group admins and roles within a group you need to check the Group Only option. - - @Html.DropDownListFor(m => m.ViewPersonnelLocation, Model.ViewPersonnelLocationPermissions) - - - - No Roles - -
+ Who can see the Location of Personnel + + This option determines who can see the location of personnel on the maps. To lock the option to just group admins and roles within a group you need to check the Group Only option. + + @Html.DropDownListFor(m => m.ViewPersonnelLocation, Model.ViewPersonnelLocationPermissions) + + + + No Roles + +
- Who can see the Location of Units - - This option determines who can see the location of units on the maps. To lock the option to just group admins and roles within a group you need to check the Group Only option. - - @Html.DropDownListFor(m => m.ViewUnitLocation, Model.ViewUnitLocationPermissions) - - - - No Roles - -
+ Who can see the Location of Units + + This option determines who can see the location of units on the maps. To lock the option to just group admins and roles within a group you need to check the Group Only option. + + @Html.DropDownListFor(m => m.ViewUnitLocation, Model.ViewUnitLocationPermissions) + + + + No Roles + +
- Who can send messages - - This option determines who can create and send messages (in-system mail). By Default everyone can create and send messages. - - @Html.DropDownListFor(m => m.CreateMessage, Model.CreateMessagePermissions) - - N/A - - No Roles - -
+ Who can send messages + + This option determines who can create and send messages (in-system mail). By Default everyone can create and send messages. + + @Html.DropDownListFor(m => m.CreateMessage, Model.CreateMessagePermissions) + + N/A + + No Roles + +
- Who can view groups and users - - By default all users can see all other users and groups in the system. This option allows you to - - @Html.DropDownListFor(m => m.ViewGroupsUsers, Model.ViewGroupUsersPermissions) - - - - No Roles - -
+ + + Who can view users + + + By default all users can see all other users in the system. This option allows you to limit who can see users in the system. + + + @Html.DropDownListFor(m => m.ViewGroupsUsers, Model.ViewGroupUsersPermissions) + + + + + + No Roles + + + + + + + Who can view groups and units + + + By default all users can see all units in the system. This option allows you to limit who can units in the system. + + + @Html.DropDownListFor(m => m.ViewGroupsUnits, Model.ViewGrouUnitsPermissions) + + + + + + No Roles + + + + + +
+ } +
+
+
-} -
-
-
-
@section Scripts diff --git a/Web/Resgrid.WebCore/Resgrid.WebCore.csproj b/Web/Resgrid.WebCore/Resgrid.WebCore.csproj index 19697a8f..8a78d7a2 100644 --- a/Web/Resgrid.WebCore/Resgrid.WebCore.csproj +++ b/Web/Resgrid.WebCore/Resgrid.WebCore.csproj @@ -58,9 +58,9 @@ - - - + + + diff --git a/Web/Resgrid.WebCore/wwwroot/js/app/internal/security/resgrid.security.permissions.js b/Web/Resgrid.WebCore/wwwroot/js/app/internal/security/resgrid.security.permissions.js index 8acd96da..90e913ea 100644 --- a/Web/Resgrid.WebCore/wwwroot/js/app/internal/security/resgrid.security.permissions.js +++ b/Web/Resgrid.WebCore/wwwroot/js/app/internal/security/resgrid.security.permissions.js @@ -911,6 +911,71 @@ var resgrid; }); }); //////////////////////////////////////////////////////// + + // View Groups Units + //////////////////////////////////////////////////////// + $('#ViewGroupsUnits').change(function () { + var val = this.value; + $.ajax({ + url: resgrid.absoluteBaseUrl + '/User/Security/SetPermission?type=18&perm=' + val + '&lockToGroup=' + $('#LockViewGroupsUnitsToGroup').is(':checked'), + type: 'GET' + }).done(function (results) { + }); + if ($("#ViewGroupsUnits").val() === "2") { + $('#viewUnitsRolesSpan').hide(); + $('#viewUnitsRolesDiv').show(); + } + else { + $('#viewUnitsRolesSpan').show(); + $('#viewUnitsRolesDiv').hide(); + } + }); + if ($("#ViewGroupsUnits").val() === "2") { + $('#viewUnitsRolesSpan').hide(); + $('#viewUnitsRolesDiv').show(); + } + else { + $('#viewUnitsRolesSpan').show(); + $('#viewUnitsRolesDiv').hide(); + } + $("#viewUnitsRoles").kendoMultiSelect({ + placeholder: "Select roles...", + dataTextField: "Name", + dataValueField: "RoleId", + change: function () { + var multiSelect = $("#viewUnitsRoles").data("kendoMultiSelect"); + $.ajax({ + url: resgrid.absoluteBaseUrl + '/User/Security/SetPermissionData?type=18&data=' + encodeURIComponent(multiSelect.value()), + type: 'GET' + }).done(function (results) { + }); + }, + autoBind: false, + dataSource: { + transport: { + read: resgrid.absoluteBaseUrl + '/User/Personnel/GetRoles' + } + } + }); + $.ajax({ + url: resgrid.absoluteBaseUrl + '/User/Security/GetRolesForPermission?type=18', + contentType: 'application/json', + type: 'GET' + }).done(function (data) { + if (data) { + var multiSelect = $("#viewUnitsRoles").data("kendoMultiSelect"); + multiSelect.value(data.split(",")); + } + }); + $('#LockViewGroupsUnitsToGroup').change(function () { + $.ajax({ + url: resgrid.absoluteBaseUrl + '/User/Security/SetPermission?type=18&perm=' + $('#ViewGroupsUnits').val() + '&lockToGroup=' + $('#LockViewGroupsUnitsToGroup').is(':checked'), + type: 'GET' + }).done(function (results) { + }); + }); + //////////////////////////////////////////////////////// + }); })(permissions = security.permissions || (security.permissions = {})); })(security = resgrid.security || (resgrid.security = {})); diff --git a/Workers/Resgrid.Workers.Console/App.config b/Workers/Resgrid.Workers.Console/App.config index 90f86607..96872196 100644 --- a/Workers/Resgrid.Workers.Console/App.config +++ b/Workers/Resgrid.Workers.Console/App.config @@ -4,7 +4,7 @@ - + diff --git a/Workers/Resgrid.Workers.Console/Commands/SecurityRefreshScheduleCommand.cs b/Workers/Resgrid.Workers.Console/Commands/SecurityRefreshScheduleCommand.cs new file mode 100644 index 00000000..435ef9b1 --- /dev/null +++ b/Workers/Resgrid.Workers.Console/Commands/SecurityRefreshScheduleCommand.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using Quidjibo.Attributes; +using Quidjibo.Commands; + +namespace Resgrid.Workers.Console.Commands +{ + public class SecurityRefreshScheduleCommand : IQuidjiboCommand + { + public int Id { get; } + public Guid? CorrelationId { get; set; } + public Dictionary Metadata { get; set; } + + public SecurityRefreshScheduleCommand(int id) + { + Id = id; + } + } +} diff --git a/Workers/Resgrid.Workers.Console/Program.cs b/Workers/Resgrid.Workers.Console/Program.cs index f73f6106..837d622c 100644 --- a/Workers/Resgrid.Workers.Console/Program.cs +++ b/Workers/Resgrid.Workers.Console/Program.cs @@ -29,6 +29,7 @@ using FluentMigrator.Runner; using Resgrid.Providers.Migrations.Migrations; using Resgrid.Model.Repositories; +using System.Reflection; namespace Resgrid.Workers.Console { @@ -123,9 +124,38 @@ private static void SetConnectionString() var config = System.Configuration.ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None); var connectionStringsSection = (ConnectionStringsSection)config.GetSection("connectionStrings"); - //var test = Configuration["ConnectionStrings:ResgridContext"]; + string configPath = Configuration["AppOptions:ConfigPath"]; - ConfigProcessor.LoadAndProcessEnvVariables(Configuration.AsEnumerable()); + if (string.IsNullOrWhiteSpace(configPath)) + configPath = "C:\\Resgrid\\Config\\ResgridConfig.json"; + + bool configResult = ConfigProcessor.LoadAndProcessConfig(configPath); + if (configResult) + { + System.Console.WriteLine($"Loaded Config: {configPath}"); + } + + var settings = System.Configuration.ConfigurationManager.ConnectionStrings; + var element = typeof(ConfigurationElement).GetField("_readOnly", BindingFlags.Instance | BindingFlags.NonPublic); + var collection = typeof(ConfigurationElementCollection).GetField("_readOnly", BindingFlags.Instance | BindingFlags.NonPublic); + + element.SetValue(settings, false); + collection.SetValue(settings, false); + + if (!configResult) + { + if (settings["ResgridContext"] == null) + { + settings.Add(new System.Configuration.ConnectionStringSettings("ResgridContext", Configuration["ConnectionStrings:ResgridContext"])); + } + } + else + { + if (settings["ResgridContext"] == null) + { + settings.Add(new ConnectionStringSettings("ResgridContext", DataConfig.ConnectionString)); + } + } if (connectionStringsSection.ConnectionStrings["ResgridContext"] != null) connectionStringsSection.ConnectionStrings["ResgridContext"].ConnectionString = DataConfig.ConnectionString; @@ -134,6 +164,8 @@ private static void SetConnectionString() config.Save(); System.Configuration.ConfigurationManager.RefreshSection("connectionStrings"); + collection.SetValue(settings, true); + element.SetValue(settings, true); } } @@ -276,6 +308,12 @@ await client.ScheduleAsync("System SQL Queue", new Commands.SystemSqlQueueCommand(14), Cron.Daily(3, 0), stoppingToken); + + _logger.Log(LogLevel.Information, "Scheduling Security Refresh"); + await client.ScheduleAsync("Security Refresh", + new Commands.SecurityRefreshScheduleCommand(15), + Cron.Daily(2, 0), + stoppingToken); } else { diff --git a/Workers/Resgrid.Workers.Console/Tasks/SecurityRefreshScheduleTask.cs b/Workers/Resgrid.Workers.Console/Tasks/SecurityRefreshScheduleTask.cs new file mode 100644 index 00000000..75b6b8f7 --- /dev/null +++ b/Workers/Resgrid.Workers.Console/Tasks/SecurityRefreshScheduleTask.cs @@ -0,0 +1,45 @@ +using Autofac; +using Microsoft.Extensions.Logging; +using Quidjibo.Handlers; +using Quidjibo.Misc; +using Resgrid.Model.Services; +using Resgrid.Workers.Console.Commands; +using Resgrid.Workers.Framework; +using Resgrid.Workers.Framework.Logic; +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Resgrid.Workers.Console.Tasks +{ + public class SecurityRefreshScheduleTask : IQuidjiboHandler + { + public string Name => "Security Refresh"; + public int Priority => 1; + public ILogger _logger; + + public SecurityRefreshScheduleTask(ILogger logger) + { + _logger = logger; + } + + public async Task ProcessAsync(SecurityRefreshScheduleCommand command, IQuidjiboProgress progress, CancellationToken cancellationToken) + { + try + { + progress.Report(1, $"Starting the {Name} Task"); + + SecurityLogic logic = new SecurityLogic(); + await logic.UpdatedCachedSecurityForAllDepartments(); + + progress.Report(100, $"Finishing the {Name} Task"); + } + catch (Exception ex) + { + Resgrid.Framework.Logging.LogException(ex); + _logger.LogError(ex.ToString()); + } + } + } +} diff --git a/Workers/Resgrid.Workers.Framework/Logic/SecurityLogic.cs b/Workers/Resgrid.Workers.Framework/Logic/SecurityLogic.cs new file mode 100644 index 00000000..cfb0fbff --- /dev/null +++ b/Workers/Resgrid.Workers.Framework/Logic/SecurityLogic.cs @@ -0,0 +1,656 @@ +using Resgrid.Model.Services; +using System; +using System.Threading.Tasks; +using Autofac; +using Resgrid.Model; +using Resgrid.Model.Repositories; +using System.Linq; +using System.Collections.Generic; +using Resgrid.Model.Providers; +using Resgrid.Workers.Framework.Workers.Security; + +namespace Resgrid.Workers.Framework.Logic +{ + public class SecurityLogic + { + private static TimeSpan Day30CacheLength = TimeSpan.FromDays(30); + private static string WhoCanViewUnitsCacheKey = "ViewUnitsSecurityMaxtix_{0}"; + private static string WhoCanViewUnitLocationsCacheKey = "ViewUnitLocationsSecurityMaxtix_{0}"; + private static string WhoCanViewPersonnelCacheKey = "ViewUsersSecurityMaxtix_{0}"; + private static string WhoCanViewPersonnelLocationsCacheKey = "ViewUserLocationsSecurityMaxtix_{0}"; + + private IDepartmentMembersRepository _departmentMembersRepository; + private IUserProfileService _userProfileService; + private IUsersService _usersService; + private IDepartmentsService _departmentsService; + private IScheduledTasksService _scheduledTasksService; + private ICallsRepository _callsRepository; + private IPermissionsService _permissionsService; + private IUnitsService _unitsService; + private IDepartmentGroupsService _departmentGroupsService; + private IPersonnelRolesService _personnelRolesService; + private ICacheProvider _cacheProvider; + private IAuthorizationService _authorizationService; + + public SecurityLogic() + { + _departmentMembersRepository = Bootstrapper.GetKernel().Resolve(); + _userProfileService = Bootstrapper.GetKernel().Resolve(); + _usersService = Bootstrapper.GetKernel().Resolve(); + _departmentsService = Bootstrapper.GetKernel().Resolve(); + _scheduledTasksService = Bootstrapper.GetKernel().Resolve(); + _callsRepository = Bootstrapper.GetKernel().Resolve(); + _permissionsService = Bootstrapper.GetKernel().Resolve(); + _unitsService = Bootstrapper.GetKernel().Resolve(); + _departmentGroupsService = Bootstrapper.GetKernel().Resolve(); + _personnelRolesService = Bootstrapper.GetKernel().Resolve(); + _cacheProvider = Bootstrapper.GetKernel().Resolve(); + _authorizationService = Bootstrapper.GetKernel().Resolve(); + } + + public async Task> Process(SecurityQueueItem item) + { + bool success = true; + string result = String.Empty; + + var department = await _departmentsService.GetDepartmentByIdAsync(item.DepartmentId); + + if (item.Type == SecurityCacheTypes.WhoCanViewUnits) + { + async Task getWhoCanViewUnits() + { + var permission = await _permissionsService.GetPermissionByDepartmentTypeAsync(item.DepartmentId, PermissionTypes.ViewGroupUnits); + var unitsPayload = new VisibilityPayloadUnits(); + var units = await _unitsService.GetUnitsForDepartmentAsync(item.DepartmentId); + + if (permission == null || (permission.Action == (int)PermissionActions.Everyone && !permission.LockToGroup)) + { + unitsPayload.EveryoneNoGroupLock = true; + } + else + { + unitsPayload.Units = new Dictionary>(); + + if (permission.Action == (int)PermissionActions.DepartmentAdminsOnly) + { // Department Admins only + foreach (var unit in units) + { + unitsPayload.Units.Add(unit.UnitId, department.AdminUsers); + } + } + else if (permission.Action == (int)PermissionActions.DepartmentAndGroupAdmins && !permission.LockToGroup) + { // Department and group Admins (not locked to group) + foreach (var unit in units) + { + List users = new List(); + users.AddRange(department.AdminUsers); + users.AddRange((await _departmentGroupsService.GetAllGroupAdminsByDepartmentIdAsync(item.DepartmentId)).Select(x => x.UserId)); + + unitsPayload.Units.Add(unit.UnitId, users); + } + } + else if (permission.Action == (int)PermissionActions.DepartmentAndGroupAdmins && permission.LockToGroup) + { // Department and group Admins (locked to group) + foreach (var unit in units) + { + List users = new List(); + users.AddRange(department.AdminUsers); + + if (unit.StationGroupId.HasValue) + { + users.AddRange((await _departmentGroupsService.GetAllAdminsForGroupAsync(unit.StationGroupId.Value)).Select(x => x.UserId)); + } + + unitsPayload.Units.Add(unit.UnitId, users); + } + } + else if (permission.Action == (int)PermissionActions.DepartmentAdminsAndSelectRoles && !permission.LockToGroup) + { // Department admins selected roles (not locked to group) + List users = new List(); + users.AddRange(department.AdminUsers); + + if (!String.IsNullOrWhiteSpace(permission.Data)) + { + var roleIds = permission.Data.Split(char.Parse(",")).Select(int.Parse); + + if (roleIds != null && roleIds.Any()) + { + foreach (var roleId in roleIds) + { + var rolePersons = await _personnelRolesService.GetAllMembersOfRoleAsync(roleId); + + if (rolePersons != null && rolePersons.Any()) + users.AddRange(rolePersons.Select(x => x.UserId)); + } + } + } + + foreach (var unit in units) + { + unitsPayload.Units.Add(unit.UnitId, users); + } + } + else if (permission.Action == (int)PermissionActions.DepartmentAdminsAndSelectRoles && permission.LockToGroup) + { + foreach (var unit in units) + { + List users = new List(); + users.AddRange(department.AdminUsers); + + if (unit.StationGroupId.HasValue) + { + var usersInGroup = await _departmentGroupsService.GetAllMembersForGroupAsync(unit.StationGroupId.Value); + + if (usersInGroup != null && usersInGroup.Any()) + { + if (!String.IsNullOrWhiteSpace(permission.Data)) + { + var roleIds = permission.Data.Split(char.Parse(",")).Select(int.Parse); + + if (roleIds != null && roleIds.Any()) + { + foreach (var roleId in roleIds) + { + var rolePersons = await _personnelRolesService.GetAllMembersOfRoleAsync(roleId); + + if (rolePersons != null && rolePersons.Any()) + { + foreach (var rolePerson in rolePersons) + { + if (usersInGroup.Any(x => x.UserId == rolePerson.UserId)) + users.Add(rolePerson.UserId); + } + } + } + } + } + } + } + + unitsPayload.Units.Add(unit.UnitId, users); + } + } + else if (permission.Action == (int)PermissionActions.Everyone && permission.LockToGroup) + { // Everyone in the same group have access to locked to group + foreach (var unit in units) + { + List users = new List(); + users.AddRange(department.AdminUsers); + + if (unit.StationGroupId.HasValue) + { + users.AddRange((await _departmentGroupsService.GetAllMembersForGroupAsync(unit.StationGroupId.Value)).Select(x => x.UserId)); + } + + unitsPayload.Units.Add(unit.UnitId, users); + } + } + } + + return unitsPayload; + } + + if (Config.SystemBehaviorConfig.CacheEnabled) + { + await _cacheProvider.RemoveAsync(string.Format(WhoCanViewUnitsCacheKey, item.DepartmentId)); + await _cacheProvider.RetrieveAsync(string.Format(WhoCanViewUnitsCacheKey, item.DepartmentId), getWhoCanViewUnits, Day30CacheLength); + } + } + else if (item.Type == SecurityCacheTypes.WhoCanViewUnitLocations) + { + async Task getWhoCanViewUnitLocations() + { + var permission = await _permissionsService.GetPermissionByDepartmentTypeAsync(item.DepartmentId, PermissionTypes.CanSeeUnitLocations); + var unitsPayload = new VisibilityPayloadUnits(); + var units = await _unitsService.GetUnitsForDepartmentAsync(item.DepartmentId); + + if (permission == null || (permission.Action == (int)PermissionActions.Everyone && !permission.LockToGroup)) + { + unitsPayload.EveryoneNoGroupLock = true; + } + else + { + unitsPayload.Units = new Dictionary>(); + + if (permission.Action == (int)PermissionActions.DepartmentAdminsOnly) + { // Department Admins only + foreach (var unit in units) + { + unitsPayload.Units.Add(unit.UnitId, department.AdminUsers); + } + } + else if (permission.Action == (int)PermissionActions.DepartmentAndGroupAdmins && !permission.LockToGroup) + { // Department and group Admins (not locked to group) + foreach (var unit in units) + { + List users = new List(); + users.AddRange(department.AdminUsers); + users.AddRange((await _departmentGroupsService.GetAllGroupAdminsByDepartmentIdAsync(item.DepartmentId)).Select(x => x.UserId)); + + unitsPayload.Units.Add(unit.UnitId, users); + } + } + else if (permission.Action == (int)PermissionActions.DepartmentAndGroupAdmins && permission.LockToGroup) + { // Department and group Admins (locked to group) + foreach (var unit in units) + { + List users = new List(); + users.AddRange(department.AdminUsers); + + if (unit.StationGroupId.HasValue) + { + users.AddRange((await _departmentGroupsService.GetAllAdminsForGroupAsync(unit.StationGroupId.Value)).Select(x => x.UserId)); + } + + unitsPayload.Units.Add(unit.UnitId, users); + } + } + else if (permission.Action == (int)PermissionActions.DepartmentAdminsAndSelectRoles && !permission.LockToGroup) + { // Department admins selected roles (not locked to group) + List users = new List(); + users.AddRange(department.AdminUsers); + + if (!String.IsNullOrWhiteSpace(permission.Data)) + { + var roleIds = permission.Data.Split(char.Parse(",")).Select(int.Parse); + + if (roleIds != null && roleIds.Any()) + { + foreach (var roleId in roleIds) + { + var rolePersons = await _personnelRolesService.GetAllMembersOfRoleAsync(roleId); + + if (rolePersons != null && rolePersons.Any()) + users.AddRange(rolePersons.Select(x => x.UserId)); + } + } + } + + foreach (var unit in units) + { + unitsPayload.Units.Add(unit.UnitId, users); + } + } + else if (permission.Action == (int)PermissionActions.DepartmentAdminsAndSelectRoles && permission.LockToGroup) + { + foreach (var unit in units) + { + List users = new List(); + users.AddRange(department.AdminUsers); + + if (unit.StationGroupId.HasValue) + { + var usersInGroup = await _departmentGroupsService.GetAllMembersForGroupAsync(unit.StationGroupId.Value); + + if (usersInGroup != null && usersInGroup.Any()) + { + if (!String.IsNullOrWhiteSpace(permission.Data)) + { + var roleIds = permission.Data.Split(char.Parse(",")).Select(int.Parse); + + if (roleIds != null && roleIds.Any()) + { + foreach (var roleId in roleIds) + { + var rolePersons = await _personnelRolesService.GetAllMembersOfRoleAsync(roleId); + + if (rolePersons != null && rolePersons.Any()) + { + foreach (var rolePerson in rolePersons) + { + if (usersInGroup.Any(x => x.UserId == rolePerson.UserId)) + users.Add(rolePerson.UserId); + } + } + } + } + } + } + } + + unitsPayload.Units.Add(unit.UnitId, users); + } + } + else if (permission.Action == (int)PermissionActions.Everyone && permission.LockToGroup) + { // Everyone in the same group have access to locked to group + foreach (var unit in units) + { + List users = new List(); + users.AddRange(department.AdminUsers); + + if (unit.StationGroupId.HasValue) + { + users.AddRange((await _departmentGroupsService.GetAllMembersForGroupAsync(unit.StationGroupId.Value)).Select(x => x.UserId)); + } + + unitsPayload.Units.Add(unit.UnitId, users); + } + } + } + + return unitsPayload; + } + + if (Config.SystemBehaviorConfig.CacheEnabled) + { + await _cacheProvider.RemoveAsync(string.Format(WhoCanViewUnitLocationsCacheKey, item.DepartmentId)); + await _cacheProvider.RetrieveAsync(string.Format(WhoCanViewUnitLocationsCacheKey, item.DepartmentId), getWhoCanViewUnitLocations, Day30CacheLength); + } + } + else if (item.Type == SecurityCacheTypes.WhoCanViewPersonnel) + { + async Task getWhoCanViewUsers() + { + var permission = await _permissionsService.GetPermissionByDepartmentTypeAsync(item.DepartmentId, PermissionTypes.ViewGroupUsers); + var usersPayload = new VisibilityPayloadUsers(); + var allUsers = await _departmentMembersRepository.GetAllDepartmentMembersUnlimitedAsync(item.DepartmentId); + + if (permission == null || (permission.Action == (int)PermissionActions.Everyone && !permission.LockToGroup)) + { + usersPayload.EveryoneNoGroupLock = true; + } + else + { + usersPayload.Users = new Dictionary>(); + + if (permission.Action == (int)PermissionActions.DepartmentAdminsOnly) + { // Department Admins only + foreach (var user in allUsers) + { + usersPayload.Users.Add(user.UserId, department.AdminUsers); + } + } + else if (permission.Action == (int)PermissionActions.DepartmentAndGroupAdmins && !permission.LockToGroup) + { // Department and group Admins (not locked to group) + foreach (var user in allUsers) + { + List users = new List(); + users.AddRange(department.AdminUsers); + users.AddRange((await _departmentGroupsService.GetAllGroupAdminsByDepartmentIdAsync(item.DepartmentId)).Select(x => x.UserId)); + + usersPayload.Users.Add(user.UserId, users); + } + } + else if (permission.Action == (int)PermissionActions.DepartmentAndGroupAdmins && permission.LockToGroup) + { // Department and group Admins (locked to group) + foreach (var user in allUsers) + { + List users = new List(); + users.AddRange(department.AdminUsers); + + var group = await _departmentGroupsService.GetGroupForUserAsync(user.UserId, item.DepartmentId); + + if (group != null) + { + users.AddRange((await _departmentGroupsService.GetAllAdminsForGroupAsync(group.DepartmentGroupId)).Select(x => x.UserId)); + } + + usersPayload.Users.Add(user.UserId, users); + } + } + else if (permission.Action == (int)PermissionActions.DepartmentAdminsAndSelectRoles && !permission.LockToGroup) + { // Department admins selected roles (not locked to group) + List users = new List(); + users.AddRange(department.AdminUsers); + + if (!String.IsNullOrWhiteSpace(permission.Data)) + { + var roleIds = permission.Data.Split(char.Parse(",")).Select(int.Parse); + + if (roleIds != null && roleIds.Any()) + { + foreach (var roleId in roleIds) + { + var rolePersons = await _personnelRolesService.GetAllMembersOfRoleAsync(roleId); + + if (rolePersons != null && rolePersons.Any()) + users.AddRange(rolePersons.Select(x => x.UserId)); + } + } + } + + foreach (var user in allUsers) + { + usersPayload.Users.Add(user.UserId, users); + } + } + else if (permission.Action == (int)PermissionActions.DepartmentAdminsAndSelectRoles && permission.LockToGroup) + { + foreach (var user in allUsers) + { + List users = new List(); + users.AddRange(department.AdminUsers); + + var group = await _departmentGroupsService.GetGroupForUserAsync(user.UserId, item.DepartmentId); + + if (group != null) + { + var usersInGroup = await _departmentGroupsService.GetAllMembersForGroupAsync(group.DepartmentGroupId); + + if (usersInGroup != null && usersInGroup.Any()) + { + if (!String.IsNullOrWhiteSpace(permission.Data)) + { + var roleIds = permission.Data.Split(char.Parse(",")).Select(int.Parse); + + if (roleIds != null && roleIds.Any()) + { + foreach (var roleId in roleIds) + { + var rolePersons = await _personnelRolesService.GetAllMembersOfRoleAsync(roleId); + + if (rolePersons != null && rolePersons.Any()) + { + foreach (var rolePerson in rolePersons) + { + if (usersInGroup.Any(x => x.UserId == rolePerson.UserId)) + users.Add(rolePerson.UserId); + } + } + } + } + } + } + } + + usersPayload.Users.Add(user.UserId, users); + } + } + else if (permission.Action == (int)PermissionActions.Everyone && permission.LockToGroup) + { // Everyone in the same group have access to locked to group + foreach (var user in allUsers) + { + List users = new List(); + users.AddRange(department.AdminUsers); + + var group = await _departmentGroupsService.GetGroupForUserAsync(user.UserId, item.DepartmentId); + + if (group != null) + { + users.AddRange((await _departmentGroupsService.GetAllMembersForGroupAsync(group.DepartmentGroupId)).Select(x => x.UserId)); + } + + usersPayload.Users.Add(user.UserId, users); + } + } + } + + return usersPayload; + } + + if (Config.SystemBehaviorConfig.CacheEnabled) + { + await _cacheProvider.RemoveAsync(string.Format(WhoCanViewPersonnelCacheKey, item.DepartmentId)); + await _cacheProvider.RetrieveAsync(string.Format(WhoCanViewPersonnelCacheKey, item.DepartmentId), getWhoCanViewUsers, Day30CacheLength); + } + } + else if (item.Type == SecurityCacheTypes.WhoCanViewPersonnelLocations) + { + async Task getWhoCanViewUserLocations() + { + var permission = await _permissionsService.GetPermissionByDepartmentTypeAsync(item.DepartmentId, PermissionTypes.CanSeePersonnelLocations); + var usersPayload = new VisibilityPayloadUsers(); + var allUsers = await _departmentMembersRepository.GetAllDepartmentMembersUnlimitedAsync(item.DepartmentId); + + if (permission == null || (permission.Action == (int)PermissionActions.Everyone && !permission.LockToGroup)) + { + usersPayload.EveryoneNoGroupLock = true; + } + else + { + usersPayload.Users = new Dictionary>(); + + if (permission.Action == (int)PermissionActions.DepartmentAdminsOnly) + { // Department Admins only + foreach (var user in allUsers) + { + usersPayload.Users.Add(user.UserId, department.AdminUsers); + } + } + else if (permission.Action == (int)PermissionActions.DepartmentAndGroupAdmins && !permission.LockToGroup) + { // Department and group Admins (not locked to group) + foreach (var user in allUsers) + { + List users = new List(); + users.AddRange(department.AdminUsers); + users.AddRange((await _departmentGroupsService.GetAllGroupAdminsByDepartmentIdAsync(item.DepartmentId)).Select(x => x.UserId)); + + usersPayload.Users.Add(user.UserId, users); + } + } + else if (permission.Action == (int)PermissionActions.DepartmentAndGroupAdmins && permission.LockToGroup) + { // Department and group Admins (locked to group) + foreach (var user in allUsers) + { + List users = new List(); + users.AddRange(department.AdminUsers); + + var group = await _departmentGroupsService.GetGroupForUserAsync(user.UserId, item.DepartmentId); + + if (group != null) + { + users.AddRange((await _departmentGroupsService.GetAllAdminsForGroupAsync(group.DepartmentGroupId)).Select(x => x.UserId)); + } + + usersPayload.Users.Add(user.UserId, users); + } + } + else if (permission.Action == (int)PermissionActions.DepartmentAdminsAndSelectRoles && !permission.LockToGroup) + { // Department admins selected roles (not locked to group) + List users = new List(); + users.AddRange(department.AdminUsers); + + if (!String.IsNullOrWhiteSpace(permission.Data)) + { + var roleIds = permission.Data.Split(char.Parse(",")).Select(int.Parse); + + if (roleIds != null && roleIds.Any()) + { + foreach (var roleId in roleIds) + { + var rolePersons = await _personnelRolesService.GetAllMembersOfRoleAsync(roleId); + + if (rolePersons != null && rolePersons.Any()) + users.AddRange(rolePersons.Select(x => x.UserId)); + } + } + } + + foreach (var user in allUsers) + { + usersPayload.Users.Add(user.UserId, users); + } + } + else if (permission.Action == (int)PermissionActions.DepartmentAdminsAndSelectRoles && permission.LockToGroup) + { + foreach (var user in allUsers) + { + List users = new List(); + users.AddRange(department.AdminUsers); + + var group = await _departmentGroupsService.GetGroupForUserAsync(user.UserId, item.DepartmentId); + + if (group != null) + { + var usersInGroup = await _departmentGroupsService.GetAllMembersForGroupAsync(group.DepartmentGroupId); + + if (usersInGroup != null && usersInGroup.Any()) + { + if (!String.IsNullOrWhiteSpace(permission.Data)) + { + var roleIds = permission.Data.Split(char.Parse(",")).Select(int.Parse); + + if (roleIds != null && roleIds.Any()) + { + foreach (var roleId in roleIds) + { + var rolePersons = await _personnelRolesService.GetAllMembersOfRoleAsync(roleId); + + if (rolePersons != null && rolePersons.Any()) + { + foreach (var rolePerson in rolePersons) + { + if (usersInGroup.Any(x => x.UserId == rolePerson.UserId)) + users.Add(rolePerson.UserId); + } + } + } + } + } + } + } + + usersPayload.Users.Add(user.UserId, users); + } + } + else if (permission.Action == (int)PermissionActions.Everyone && permission.LockToGroup) + { // Everyone in the same group have access to locked to group + foreach (var user in allUsers) + { + List users = new List(); + users.AddRange(department.AdminUsers); + + var group = await _departmentGroupsService.GetGroupForUserAsync(user.UserId, item.DepartmentId); + + if (group != null) + { + users.AddRange((await _departmentGroupsService.GetAllMembersForGroupAsync(group.DepartmentGroupId)).Select(x => x.UserId)); + } + + usersPayload.Users.Add(user.UserId, users); + } + } + } + + return usersPayload; + } + + if (Config.SystemBehaviorConfig.CacheEnabled) + { + await _cacheProvider.RemoveAsync(string.Format(WhoCanViewPersonnelLocationsCacheKey, item.DepartmentId)); + await _cacheProvider.RetrieveAsync(string.Format(WhoCanViewPersonnelLocationsCacheKey, item.DepartmentId), getWhoCanViewUserLocations, Day30CacheLength); + } + } + + return new Tuple(success, result); + } + + public async Task> UpdatedCachedSecurityForAllDepartments() + { + bool success = true; + string result = String.Empty; + + var departments = await _departmentsService.GetAllAsync(); + + foreach (var department in departments) + { + await Process(new SecurityQueueItem() { DepartmentId = department.DepartmentId, Type = SecurityCacheTypes.WhoCanViewUnits }); + await Process(new SecurityQueueItem() { DepartmentId = department.DepartmentId, Type = SecurityCacheTypes.WhoCanViewUnitLocations }); + await Process(new SecurityQueueItem() { DepartmentId = department.DepartmentId, Type = SecurityCacheTypes.WhoCanViewPersonnel }); + await Process(new SecurityQueueItem() { DepartmentId = department.DepartmentId, Type = SecurityCacheTypes.WhoCanViewPersonnelLocations }); + } + + return new Tuple(success, result); + } + } +} diff --git a/Workers/Resgrid.Workers.Framework/Workers/Security/SecurityQueueItem.cs b/Workers/Resgrid.Workers.Framework/Workers/Security/SecurityQueueItem.cs new file mode 100644 index 00000000..c49ff21e --- /dev/null +++ b/Workers/Resgrid.Workers.Framework/Workers/Security/SecurityQueueItem.cs @@ -0,0 +1,10 @@ +using Resgrid.Model; + +namespace Resgrid.Workers.Framework.Workers.Security +{ + public class SecurityQueueItem : QueueItem + { + public SecurityCacheTypes Type { get; set; } + public int DepartmentId { get; set; } + } +} From d3e383bce82bfbfb080c802f14f2fb5b4be9d412 Mon Sep 17 00:00:00 2001 From: Shawn Jackson Date: Thu, 29 Aug 2024 09:39:23 -0700 Subject: [PATCH 2/2] CU-3wen6jw added logic to schedule permission matrix update --- Core/Resgrid.Config/ServiceBusConfig.cs | 2 + Core/Resgrid.Framework/Logging.cs | 5 -- .../Events/SecurityRefreshEvent.cs | 23 ++++++ .../Providers/IOutboundQueueProvider.cs | 1 + Core/Resgrid.Model/Resgrid.Model.csproj | 2 +- .../RabbitConnection.cs | 6 ++ .../RabbitInboundQueueProvider.cs | 55 +++++++++++--- .../RabbitOutboundQueueProvider.cs | 7 ++ .../OutboundEventProvider.cs | 76 +++++++++++-------- .../OutboundQueueProvider.cs | 5 ++ .../Resgrid.Providers.Email.csproj | 2 +- .../Resgrid.Web.Eventing.csproj | 2 +- .../Resgrid.Web.ServicesCore.csproj | 6 +- .../User/Controllers/SecurityController.cs | 58 ++++++++++++++ Web/Resgrid.WebCore/Resgrid.WebCore.csproj | 2 +- Workers/Resgrid.Workers.Console/Program.cs | 22 +++++- .../Tasks/QueuesProcessorTask.cs | 19 +++++ 17 files changed, 237 insertions(+), 56 deletions(-) create mode 100644 Core/Resgrid.Model/Events/SecurityRefreshEvent.cs diff --git a/Core/Resgrid.Config/ServiceBusConfig.cs b/Core/Resgrid.Config/ServiceBusConfig.cs index a10812c3..041195f0 100644 --- a/Core/Resgrid.Config/ServiceBusConfig.cs +++ b/Core/Resgrid.Config/ServiceBusConfig.cs @@ -16,6 +16,7 @@ public static class ServiceBusConfig public static string AuditQueueName = "audittest"; public static string UnitLoactionQueueName = "unitlocationtest"; public static string PersonnelLoactionQueueName = "personnellocationtest"; + public static string SecurityRefreshQueueName = "securityrefreshtest"; #else public static string CallBroadcastQueueName = "callbroadcast"; public static string MessageBroadcastQueueName = "messagebroadcast"; @@ -27,6 +28,7 @@ public static class ServiceBusConfig public static string AuditQueueName = "audit"; public static string UnitLoactionQueueName = "unitlocation"; public static string PersonnelLoactionQueueName = "personnellocation"; + public static string SecurityRefreshQueueName = "securityrefresh"; #endif #region Azure Service Bus Values diff --git a/Core/Resgrid.Framework/Logging.cs b/Core/Resgrid.Framework/Logging.cs index 9dc4229f..dc01dea6 100644 --- a/Core/Resgrid.Framework/Logging.cs +++ b/Core/Resgrid.Framework/Logging.cs @@ -36,13 +36,8 @@ public static void Initialize(string key) o.MinimumBreadcrumbLevel = LogEventLevel.Debug; o.MinimumEventLevel = LogEventLevel.Error; o.Dsn = dsn; - o.AttachStacktrace = true; - o.SendDefaultPii = true; - o.AutoSessionTracking = true; - o.TracesSampleRate = ExternalErrorConfig.SentryPerfSampleRate; o.Environment = ExternalErrorConfig.Environment; o.Release = Assembly.GetEntryAssembly().GetName().Version.ToString(); - o.ProfilesSampleRate = 0.0; }).CreateLogger(); } else diff --git a/Core/Resgrid.Model/Events/SecurityRefreshEvent.cs b/Core/Resgrid.Model/Events/SecurityRefreshEvent.cs new file mode 100644 index 00000000..9d2b06a3 --- /dev/null +++ b/Core/Resgrid.Model/Events/SecurityRefreshEvent.cs @@ -0,0 +1,23 @@ +using ProtoBuf; +using System; + +namespace Resgrid.Model.Events +{ + [ProtoContract] + public class SecurityRefreshEvent + { + [ProtoMember(1)] + public string EventId { get; set; } + + [ProtoMember(2)] + public int DepartmentId { get; set; } + + [ProtoMember(3)] + public SecurityCacheTypes Type { get; set; } + + public SecurityRefreshEvent() + { + EventId = Guid.NewGuid().ToString(); + } + } +} diff --git a/Core/Resgrid.Model/Providers/IOutboundQueueProvider.cs b/Core/Resgrid.Model/Providers/IOutboundQueueProvider.cs index a3ae97e2..148e51c3 100644 --- a/Core/Resgrid.Model/Providers/IOutboundQueueProvider.cs +++ b/Core/Resgrid.Model/Providers/IOutboundQueueProvider.cs @@ -12,5 +12,6 @@ public interface IOutboundQueueProvider Task EnqueueShiftNotification(ShiftQueueItem shiftQueueItem); Task EnqueueDistributionList(DistributionListQueueItem distributionListQueue); Task EnqueueAuditEvent(AuditEvent auditEvent); + Task EnqueueSecurityRefreshEvent(SecurityRefreshEvent securityRefreshEvent); } } diff --git a/Core/Resgrid.Model/Resgrid.Model.csproj b/Core/Resgrid.Model/Resgrid.Model.csproj index 822f9f29..18f49e13 100644 --- a/Core/Resgrid.Model/Resgrid.Model.csproj +++ b/Core/Resgrid.Model/Resgrid.Model.csproj @@ -38,7 +38,7 @@ - + diff --git a/Providers/Resgrid.Providers.Bus.Rabbit/RabbitConnection.cs b/Providers/Resgrid.Providers.Bus.Rabbit/RabbitConnection.cs index 0f8b8dcc..fd129329 100644 --- a/Providers/Resgrid.Providers.Bus.Rabbit/RabbitConnection.cs +++ b/Providers/Resgrid.Providers.Bus.Rabbit/RabbitConnection.cs @@ -136,6 +136,12 @@ public static bool VerifyAndCreateClients() autoDelete: false, arguments: null); + channel.QueueDeclare(queue: SetQueueNameForEnv(ServiceBusConfig.SecurityRefreshQueueName), + durable: false, + exclusive: false, + autoDelete: false, + arguments: null); + return true; } catch (Exception ex) diff --git a/Providers/Resgrid.Providers.Bus.Rabbit/RabbitInboundQueueProvider.cs b/Providers/Resgrid.Providers.Bus.Rabbit/RabbitInboundQueueProvider.cs index 34ab2314..ce004750 100644 --- a/Providers/Resgrid.Providers.Bus.Rabbit/RabbitInboundQueueProvider.cs +++ b/Providers/Resgrid.Providers.Bus.Rabbit/RabbitInboundQueueProvider.cs @@ -25,6 +25,7 @@ public class RabbitInboundQueueProvider public Func AuditEventQueueReceived; public Func UnitLocationEventQueueReceived; public Func PersonnelLocationEventQueueReceived; + public Func SecurityRefreshEventQueueReceived; public RabbitInboundQueueProvider() { @@ -447,7 +448,6 @@ private async Task StartMonitoring() } catch (Exception ex) { - //_channel.BasicNack(ea.DeliveryTag, false, false); Logging.LogException(ex, Encoding.UTF8.GetString(ea.Body.ToArray())); } @@ -458,15 +458,12 @@ private async Task StartMonitoring() if (UnitLocationEventQueueReceived != null) { await UnitLocationEventQueueReceived.Invoke(unitLocation); - //_channel.BasicAck(ea.DeliveryTag, false); } } } catch (Exception ex) { - // Discard unit location events. Logging.LogException(ex); - //_channel.BasicNack(ea.DeliveryTag, false, true); } } }; @@ -477,7 +474,7 @@ private async Task StartMonitoring() consumer: unitLocationQueueReceivedConsumer); } - if (UnitLocationEventQueueReceived != null) + if (PersonnelLocationEventQueueReceived != null) { var personnelLocationQueueReceivedConsumer = new EventingBasicConsumer(_channel); personnelLocationQueueReceivedConsumer.Received += async (model, ea) => @@ -493,7 +490,6 @@ private async Task StartMonitoring() } catch (Exception ex) { - //_channel.BasicNack(ea.DeliveryTag, false, false); Logging.LogException(ex, Encoding.UTF8.GetString(ea.Body.ToArray())); } @@ -501,18 +497,15 @@ private async Task StartMonitoring() { if (personnelLocation != null) { - if (UnitLocationEventQueueReceived != null) + if (PersonnelLocationEventQueueReceived != null) { await PersonnelLocationEventQueueReceived.Invoke(personnelLocation); - //_channel.BasicAck(ea.DeliveryTag, false); } } } catch (Exception ex) { - // Discard unit location events. Logging.LogException(ex); - //_channel.BasicNack(ea.DeliveryTag, false, true); } } }; @@ -522,6 +515,48 @@ private async Task StartMonitoring() autoAck: true, consumer: personnelLocationQueueReceivedConsumer); } + + if (SecurityRefreshEventQueueReceived != null) + { + var securityRefreshEventQueueReceivedConsumer = new EventingBasicConsumer(_channel); + securityRefreshEventQueueReceivedConsumer.Received += async (model, ea) => + { + if (ea != null && ea.Body.Length > 0) + { + SecurityRefreshEvent securityRefresh = null; + try + { + var body = ea.Body; + var message = Encoding.UTF8.GetString(body.ToArray()); + securityRefresh = ObjectSerialization.Deserialize(message); + } + catch (Exception ex) + { + Logging.LogException(ex, Encoding.UTF8.GetString(ea.Body.ToArray())); + } + + try + { + if (securityRefresh != null) + { + if (SecurityRefreshEventQueueReceived != null) + { + await SecurityRefreshEventQueueReceived.Invoke(securityRefresh); + } + } + } + catch (Exception ex) + { + Logging.LogException(ex); + } + } + }; + + String securityRefreshEventQueueReceivedConsumerTag = _channel.BasicConsume( + queue: RabbitConnection.SetQueueNameForEnv(ServiceBusConfig.SecurityRefreshQueueName), + autoAck: true, + consumer: securityRefreshEventQueueReceivedConsumer); + } } } diff --git a/Providers/Resgrid.Providers.Bus.Rabbit/RabbitOutboundQueueProvider.cs b/Providers/Resgrid.Providers.Bus.Rabbit/RabbitOutboundQueueProvider.cs index f911e5e9..fdee23ec 100644 --- a/Providers/Resgrid.Providers.Bus.Rabbit/RabbitOutboundQueueProvider.cs +++ b/Providers/Resgrid.Providers.Bus.Rabbit/RabbitOutboundQueueProvider.cs @@ -90,6 +90,13 @@ public bool EnqueuePersonnelLocationEvent(PersonnelLocationEvent personnelLocati return SendMessage(ServiceBusConfig.PersonnelLoactionQueueName, serializedObject, false, "300000"); } + public bool EnqueueSecurityRefreshEvent(SecurityRefreshEvent securityRefreshEvent) + { + var serializedObject = ObjectSerialization.Serialize(securityRefreshEvent); + + return SendMessage(ServiceBusConfig.SecurityRefreshQueueName, serializedObject, false, "300000"); + } + public bool VerifyAndCreateClients() { return RabbitConnection.VerifyAndCreateClients(); diff --git a/Providers/Resgrid.Providers.Bus/OutboundEventProvider.cs b/Providers/Resgrid.Providers.Bus/OutboundEventProvider.cs index 0c39c5f5..9cfd49e2 100644 --- a/Providers/Resgrid.Providers.Bus/OutboundEventProvider.cs +++ b/Providers/Resgrid.Providers.Bus/OutboundEventProvider.cs @@ -49,6 +49,7 @@ public OutboundEventProvider(IEventAggregator eventAggregator, IOutboundQueuePro _eventAggregator.AddListener(shiftUpdatedEventHandler); _eventAggregator.AddListener(shiftDaysAddedEventHandler); _eventAggregator.AddListener(auditEventHandler); + _eventAggregator.AddListener(securityRefreshEventHandler); // Topics (SignalR Integration) _eventAggregator.AddListener(personnelStatusChangedTopicHandler); @@ -61,7 +62,7 @@ public OutboundEventProvider(IEventAggregator eventAggregator, IOutboundQueuePro _eventAggregator.AddListener(unitLocationUpdatedTopicHandler); } - public Action unitStatusHandler = async delegate(UnitStatusEvent message) + public Action unitStatusHandler = async delegate (UnitStatusEvent message) { var nqi = new NotificationItem(); @@ -87,7 +88,7 @@ public OutboundEventProvider(IEventAggregator eventAggregator, IOutboundQueuePro } }; - public Action unitTypeGroupAvailabilityHandler = async delegate(UnitStatusEvent message) + public Action unitTypeGroupAvailabilityHandler = async delegate (UnitStatusEvent message) { var nqi = new NotificationItem(); @@ -105,7 +106,7 @@ public OutboundEventProvider(IEventAggregator eventAggregator, IOutboundQueuePro await _outboundQueueProvider.EnqueueNotification(nqi); }; - public Action unitTypeDepartmentAvailabilityHandler = async delegate(UnitStatusEvent message) + public Action unitTypeDepartmentAvailabilityHandler = async delegate (UnitStatusEvent message) { var nqi = new NotificationItem(); @@ -123,7 +124,7 @@ public OutboundEventProvider(IEventAggregator eventAggregator, IOutboundQueuePro await _outboundQueueProvider.EnqueueNotification(nqi); }; - public Action userStaffingHandler = async delegate(UserStaffingEvent message) + public Action userStaffingHandler = async delegate (UserStaffingEvent message) { var nqi = new NotificationItem(); @@ -141,7 +142,7 @@ public OutboundEventProvider(IEventAggregator eventAggregator, IOutboundQueuePro await _outboundQueueProvider.EnqueueNotification(nqi); }; - public Action userRoleGroupAvailabilityHandler = async delegate(UserStaffingEvent message) + public Action userRoleGroupAvailabilityHandler = async delegate (UserStaffingEvent message) { var nqi = new NotificationItem(); @@ -160,7 +161,7 @@ public OutboundEventProvider(IEventAggregator eventAggregator, IOutboundQueuePro await _outboundQueueProvider.EnqueueNotification(nqi); }; - public Action userRoleDepartmentAvailabilityHandler = async delegate(UserStaffingEvent message) + public Action userRoleDepartmentAvailabilityHandler = async delegate (UserStaffingEvent message) { var nqi = new NotificationItem(); @@ -180,7 +181,7 @@ public OutboundEventProvider(IEventAggregator eventAggregator, IOutboundQueuePro await _signalrProvider.PersonnelStaffingUpdated(message.Staffing.DepartmentId, message.Staffing); }; - public Action personnelStatusChangedHandler = async delegate(UserStatusEvent message) + public Action personnelStatusChangedHandler = async delegate (UserStatusEvent message) { var nqi = new NotificationItem(); @@ -198,7 +199,7 @@ public OutboundEventProvider(IEventAggregator eventAggregator, IOutboundQueuePro await _signalrProvider.PersonnelStatusUpdated(message.Status.DepartmentId, message.Status); }; - public Action userCreatedHandler = async delegate(UserCreatedEvent message) + public Action userCreatedHandler = async delegate (UserCreatedEvent message) { var nqi = new NotificationItem(); @@ -210,7 +211,7 @@ public OutboundEventProvider(IEventAggregator eventAggregator, IOutboundQueuePro await _outboundQueueProvider.EnqueueNotification(nqi); }; - public Action userAssignedToGroupHandler = async delegate(UserAssignedToGroupEvent message) + public Action userAssignedToGroupHandler = async delegate (UserAssignedToGroupEvent message) { var nqi = new NotificationItem(); @@ -228,7 +229,7 @@ public OutboundEventProvider(IEventAggregator eventAggregator, IOutboundQueuePro await _outboundQueueProvider.EnqueueNotification(nqi); }; - public Action calendarEventUpcomingHandler = async delegate(CalendarEventUpcomingEvent message) + public Action calendarEventUpcomingHandler = async delegate (CalendarEventUpcomingEvent message) { var nqi = new NotificationItem(); @@ -240,7 +241,7 @@ public OutboundEventProvider(IEventAggregator eventAggregator, IOutboundQueuePro await _outboundQueueProvider.EnqueueNotification(nqi); }; - public Action calendarEventAddedHandler = async delegate(CalendarEventAddedEvent message) + public Action calendarEventAddedHandler = async delegate (CalendarEventAddedEvent message) { var nqi = new NotificationItem(); @@ -252,7 +253,7 @@ public OutboundEventProvider(IEventAggregator eventAggregator, IOutboundQueuePro await _outboundQueueProvider.EnqueueNotification(nqi); }; - public Action calendarEventUpdatedHandler = async delegate(CalendarEventUpdatedEvent message) + public Action calendarEventUpdatedHandler = async delegate (CalendarEventUpdatedEvent message) { var nqi = new NotificationItem(); @@ -264,7 +265,7 @@ public OutboundEventProvider(IEventAggregator eventAggregator, IOutboundQueuePro await _outboundQueueProvider.EnqueueNotification(nqi); }; - public Action documentAddedHandler = async delegate(DocumentAddedEvent message) + public Action documentAddedHandler = async delegate (DocumentAddedEvent message) { var nqi = new NotificationItem(); @@ -276,7 +277,7 @@ public OutboundEventProvider(IEventAggregator eventAggregator, IOutboundQueuePro await _outboundQueueProvider.EnqueueNotification(nqi); }; - public Action noteAddedHandler = async delegate(NoteAddedEvent message) + public Action noteAddedHandler = async delegate (NoteAddedEvent message) { var nqi = new NotificationItem(); @@ -288,7 +289,7 @@ public OutboundEventProvider(IEventAggregator eventAggregator, IOutboundQueuePro await _outboundQueueProvider.EnqueueNotification(nqi); }; - public Action unitAddedHandler = async delegate(UnitAddedEvent message) + public Action unitAddedHandler = async delegate (UnitAddedEvent message) { var nqi = new NotificationItem(); @@ -300,7 +301,7 @@ public OutboundEventProvider(IEventAggregator eventAggregator, IOutboundQueuePro await _outboundQueueProvider.EnqueueNotification(nqi); }; - public Action logAddedHandler = async delegate(LogAddedEvent message) + public Action logAddedHandler = async delegate (LogAddedEvent message) { var nqi = new NotificationItem(); @@ -312,7 +313,7 @@ public OutboundEventProvider(IEventAggregator eventAggregator, IOutboundQueuePro await _outboundQueueProvider.EnqueueNotification(nqi); }; - public Action resourceOrderAddedHandler = async delegate(ResourceOrderAddedEvent message) + public Action resourceOrderAddedHandler = async delegate (ResourceOrderAddedEvent message) { var nqi = new NotificationItem(); @@ -324,7 +325,7 @@ public OutboundEventProvider(IEventAggregator eventAggregator, IOutboundQueuePro await _outboundQueueProvider.EnqueueNotification(nqi); }; - public Action shiftTradeRequestedHandler = async delegate(ShiftTradeRequestedEvent message) + public Action shiftTradeRequestedHandler = async delegate (ShiftTradeRequestedEvent message) { if (_outboundQueueProvider == null) _outboundQueueProvider = new OutboundQueueProvider(); @@ -338,7 +339,7 @@ public OutboundEventProvider(IEventAggregator eventAggregator, IOutboundQueuePro await _outboundQueueProvider.EnqueueShiftNotification(item); }; - public Action shiftTradeRejectedEventHandler = async delegate(ShiftTradeRejectedEvent message) + public Action shiftTradeRejectedEventHandler = async delegate (ShiftTradeRejectedEvent message) { if (_outboundQueueProvider == null) _outboundQueueProvider = new OutboundQueueProvider(); @@ -353,7 +354,7 @@ public OutboundEventProvider(IEventAggregator eventAggregator, IOutboundQueuePro await _outboundQueueProvider.EnqueueShiftNotification(item); }; - public Action shiftTradeProposedEventHandler = async delegate(ShiftTradeProposedEvent message) + public Action shiftTradeProposedEventHandler = async delegate (ShiftTradeProposedEvent message) { if (_outboundQueueProvider == null) _outboundQueueProvider = new OutboundQueueProvider(); @@ -368,7 +369,7 @@ public OutboundEventProvider(IEventAggregator eventAggregator, IOutboundQueuePro await _outboundQueueProvider.EnqueueShiftNotification(item); }; - public Action shiftTradeFilledEventHandler = async delegate(ShiftTradeFilledEvent message) + public Action shiftTradeFilledEventHandler = async delegate (ShiftTradeFilledEvent message) { if (_outboundQueueProvider == null) _outboundQueueProvider = new OutboundQueueProvider(); @@ -383,7 +384,7 @@ public OutboundEventProvider(IEventAggregator eventAggregator, IOutboundQueuePro await _outboundQueueProvider.EnqueueShiftNotification(item); }; - public Action shiftCreatedEventHandler = async delegate(ShiftCreatedEvent message) + public Action shiftCreatedEventHandler = async delegate (ShiftCreatedEvent message) { if (_outboundQueueProvider == null) _outboundQueueProvider = new OutboundQueueProvider(); @@ -397,7 +398,7 @@ public OutboundEventProvider(IEventAggregator eventAggregator, IOutboundQueuePro await _outboundQueueProvider.EnqueueShiftNotification(item); }; - public Action shiftUpdatedEventHandler = async delegate(ShiftUpdatedEvent message) + public Action shiftUpdatedEventHandler = async delegate (ShiftUpdatedEvent message) { if (_outboundQueueProvider == null) _outboundQueueProvider = new OutboundQueueProvider(); @@ -411,7 +412,7 @@ public OutboundEventProvider(IEventAggregator eventAggregator, IOutboundQueuePro await _outboundQueueProvider.EnqueueShiftNotification(item); }; - public Action shiftDaysAddedEventHandler = async delegate(ShiftDaysAddedEvent message) + public Action shiftDaysAddedEventHandler = async delegate (ShiftDaysAddedEvent message) { if (_outboundQueueProvider == null) _outboundQueueProvider = new OutboundQueueProvider(); @@ -427,11 +428,22 @@ public OutboundEventProvider(IEventAggregator eventAggregator, IOutboundQueuePro private Action auditEventHandler = async delegate (AuditEvent message) { + if (_outboundQueueProvider == null) + _outboundQueueProvider = new OutboundQueueProvider(); + await _outboundQueueProvider.EnqueueAuditEvent(message); }; + private Action securityRefreshEventHandler = async delegate (SecurityRefreshEvent message) + { + if (_outboundQueueProvider == null) + _outboundQueueProvider = new OutboundQueueProvider(); + + await _outboundQueueProvider.EnqueueSecurityRefreshEvent(message); + }; + #region Topic Based Events - public Action departmentSettingsChangedHandler = async delegate(DepartmentSettingsChangedEvent message) + public Action departmentSettingsChangedHandler = async delegate (DepartmentSettingsChangedEvent message) { var nqi = new NotificationItem(); @@ -443,14 +455,14 @@ public OutboundEventProvider(IEventAggregator eventAggregator, IOutboundQueuePro await _outboundQueueProvider.EnqueueNotification(nqi); }; - public Action workerHeartbeatHandler = async delegate(WorkerHeartbeatEvent message) + public Action workerHeartbeatHandler = async delegate (WorkerHeartbeatEvent message) { - + }; - public Action dListCheckHandler = async delegate(DistributionListCheckEvent message) + public Action dListCheckHandler = async delegate (DistributionListCheckEvent message) { - + }; public Action personnelStatusChangedTopicHandler = async delegate (UserStatusEvent message) @@ -459,18 +471,18 @@ public OutboundEventProvider(IEventAggregator eventAggregator, IOutboundQueuePro }; - public Action personnelStaffingChangedTopicHandler = async delegate(UserStaffingEvent message) + public Action personnelStaffingChangedTopicHandler = async delegate (UserStaffingEvent message) { if (SystemBehaviorConfig.ServiceBusType == ServiceBusTypes.Rabbit) _rabbitTopicProvider.PersonnelStaffingChanged(message); }; - public Action unitStatusTopicHandler = async delegate(UnitStatusEvent message) + public Action unitStatusTopicHandler = async delegate (UnitStatusEvent message) { _rabbitTopicProvider.UnitStatusChanged(message); }; - public Action callAddedTopicHandler = async delegate(CallAddedEvent message) + public Action callAddedTopicHandler = async delegate (CallAddedEvent message) { _rabbitTopicProvider.CallAdded(message); }; diff --git a/Providers/Resgrid.Providers.Bus/OutboundQueueProvider.cs b/Providers/Resgrid.Providers.Bus/OutboundQueueProvider.cs index a2109d01..75fb457f 100644 --- a/Providers/Resgrid.Providers.Bus/OutboundQueueProvider.cs +++ b/Providers/Resgrid.Providers.Bus/OutboundQueueProvider.cs @@ -60,5 +60,10 @@ public async Task EnqueueAuditEvent(AuditEvent auditEvent) { return _rabbitOutboundQueueProvider.EnqueueAuditEvent(auditEvent); } + + public async Task EnqueueSecurityRefreshEvent(SecurityRefreshEvent securityRefreshEvent) + { + return _rabbitOutboundQueueProvider.EnqueueSecurityRefreshEvent(securityRefreshEvent); + } } } diff --git a/Providers/Resgrid.Providers.Email/Resgrid.Providers.Email.csproj b/Providers/Resgrid.Providers.Email/Resgrid.Providers.Email.csproj index dba63b44..94755008 100644 --- a/Providers/Resgrid.Providers.Email/Resgrid.Providers.Email.csproj +++ b/Providers/Resgrid.Providers.Email/Resgrid.Providers.Email.csproj @@ -33,7 +33,7 @@ - + diff --git a/Web/Resgrid.Web.Eventing/Resgrid.Web.Eventing.csproj b/Web/Resgrid.Web.Eventing/Resgrid.Web.Eventing.csproj index c9c0713a..ea4e7049 100644 --- a/Web/Resgrid.Web.Eventing/Resgrid.Web.Eventing.csproj +++ b/Web/Resgrid.Web.Eventing/Resgrid.Web.Eventing.csproj @@ -42,7 +42,7 @@ - + diff --git a/Web/Resgrid.Web.ServicesCore/Resgrid.Web.ServicesCore.csproj b/Web/Resgrid.Web.ServicesCore/Resgrid.Web.ServicesCore.csproj index 79ad1f88..85c94c01 100644 --- a/Web/Resgrid.Web.ServicesCore/Resgrid.Web.ServicesCore.csproj +++ b/Web/Resgrid.Web.ServicesCore/Resgrid.Web.ServicesCore.csproj @@ -57,17 +57,17 @@ - + - + - + diff --git a/Web/Resgrid.WebCore/Areas/User/Controllers/SecurityController.cs b/Web/Resgrid.WebCore/Areas/User/Controllers/SecurityController.cs index 192a20cf..bde9d258 100644 --- a/Web/Resgrid.WebCore/Areas/User/Controllers/SecurityController.cs +++ b/Web/Resgrid.WebCore/Areas/User/Controllers/SecurityController.cs @@ -358,6 +358,35 @@ public async Task SetPermission(int type, int perm, bool? lockToG auditEvent.UserAgent = $"{Request.Headers["User-Agent"]} {Request.Headers["Accept-Language"]}"; _eventAggregator.SendMessage(auditEvent); + if (type == (int)PermissionTypes.CanSeePersonnelLocations) + { + var securityEvent = new SecurityRefreshEvent(); + securityEvent.DepartmentId = DepartmentId; + securityEvent.Type = SecurityCacheTypes.WhoCanViewPersonnelLocations; + _eventAggregator.SendMessage(securityEvent); + } + else if (type == (int)PermissionTypes.CanSeeUnitLocations) + { + var securityEvent = new SecurityRefreshEvent(); + securityEvent.DepartmentId = DepartmentId; + securityEvent.Type = SecurityCacheTypes.WhoCanViewUnitLocations; + _eventAggregator.SendMessage(securityEvent); + } + else if (type == (int)PermissionTypes.ViewGroupUnits) + { + var securityEvent = new SecurityRefreshEvent(); + securityEvent.DepartmentId = DepartmentId; + securityEvent.Type = SecurityCacheTypes.WhoCanViewUnits; + _eventAggregator.SendMessage(securityEvent); + } + else if (type == (int)PermissionTypes.ViewGroupUsers) + { + var securityEvent = new SecurityRefreshEvent(); + securityEvent.DepartmentId = DepartmentId; + securityEvent.Type = SecurityCacheTypes.WhoCanViewPersonnel; + _eventAggregator.SendMessage(securityEvent); + } + return new StatusCodeResult((int)HttpStatusCode.OK); } @@ -383,6 +412,35 @@ public async Task SetPermissionData(int type, string data, bool? auditEvent.UserAgent = $"{Request.Headers["User-Agent"]} {Request.Headers["Accept-Language"]}"; _eventAggregator.SendMessage(auditEvent); + if (type == (int)PermissionTypes.CanSeePersonnelLocations) + { + var securityEvent = new SecurityRefreshEvent(); + securityEvent.DepartmentId = DepartmentId; + securityEvent.Type = SecurityCacheTypes.WhoCanViewPersonnelLocations; + _eventAggregator.SendMessage(securityEvent); + } + else if (type == (int)PermissionTypes.CanSeeUnitLocations) + { + var securityEvent = new SecurityRefreshEvent(); + securityEvent.DepartmentId = DepartmentId; + securityEvent.Type = SecurityCacheTypes.WhoCanViewUnitLocations; + _eventAggregator.SendMessage(securityEvent); + } + else if (type == (int)PermissionTypes.ViewGroupUnits) + { + var securityEvent = new SecurityRefreshEvent(); + securityEvent.DepartmentId = DepartmentId; + securityEvent.Type = SecurityCacheTypes.WhoCanViewUnits; + _eventAggregator.SendMessage(securityEvent); + } + else if (type == (int)PermissionTypes.ViewGroupUsers) + { + var securityEvent = new SecurityRefreshEvent(); + securityEvent.DepartmentId = DepartmentId; + securityEvent.Type = SecurityCacheTypes.WhoCanViewPersonnel; + _eventAggregator.SendMessage(securityEvent); + } + return new StatusCodeResult((int)HttpStatusCode.OK); } diff --git a/Web/Resgrid.WebCore/Resgrid.WebCore.csproj b/Web/Resgrid.WebCore/Resgrid.WebCore.csproj index 8a78d7a2..a4dc5ced 100644 --- a/Web/Resgrid.WebCore/Resgrid.WebCore.csproj +++ b/Web/Resgrid.WebCore/Resgrid.WebCore.csproj @@ -61,7 +61,7 @@ - + diff --git a/Workers/Resgrid.Workers.Console/Program.cs b/Workers/Resgrid.Workers.Console/Program.cs index 837d622c..2daad860 100644 --- a/Workers/Resgrid.Workers.Console/Program.cs +++ b/Workers/Resgrid.Workers.Console/Program.cs @@ -30,6 +30,7 @@ using Resgrid.Providers.Migrations.Migrations; using Resgrid.Model.Repositories; using System.Reflection; +using Sentry; namespace Resgrid.Workers.Console { @@ -48,6 +49,25 @@ static async Task Main(string[] args) System.Console.WriteLine("-----------------------------------------"); LoadConfiguration(args); + + Resgrid.Framework.Logging.Initialize(ExternalErrorConfig.ExternalErrorServiceUrlForWebjobs); + + if (!String.IsNullOrWhiteSpace(ExternalErrorConfig.ExternalErrorServiceUrlForWebjobs)) + { + SentrySdk.Init(options => + { + options.Dsn = Config.ExternalErrorConfig.ExternalErrorServiceUrlForWebjobs; + options.AttachStacktrace = true; + options.SendDefaultPii = true; + options.AutoSessionTracking = true; + options.TracesSampleRate = ExternalErrorConfig.SentryPerfSampleRate; + options.ProfilesSampleRate = ExternalErrorConfig.SentryProfilingSampleRate; + options.IsGlobalModeEnabled = true; + options.Environment = ExternalErrorConfig.Environment; + options.Release = Assembly.GetEntryAssembly().GetName().Version.ToString(); + }); + } + Prime(); var builder = new HostBuilder() @@ -95,8 +115,6 @@ private static void Prime() Bootstrapper.Initialize(); - Resgrid.Framework.Logging.Initialize(ExternalErrorConfig.ExternalErrorServiceUrlForWebjobs); - var eventAggragator = Bootstrapper.GetKernel().Resolve(); var outbound = Bootstrapper.GetKernel().Resolve(); var coreEventService = Bootstrapper.GetKernel().Resolve(); diff --git a/Workers/Resgrid.Workers.Console/Tasks/QueuesProcessorTask.cs b/Workers/Resgrid.Workers.Console/Tasks/QueuesProcessorTask.cs index 77fc1ee9..8354f690 100644 --- a/Workers/Resgrid.Workers.Console/Tasks/QueuesProcessorTask.cs +++ b/Workers/Resgrid.Workers.Console/Tasks/QueuesProcessorTask.cs @@ -7,6 +7,7 @@ using Resgrid.Providers.Bus.Rabbit; using Resgrid.Workers.Console.Commands; using Resgrid.Workers.Framework.Logic; +using Resgrid.Workers.Framework.Workers.Security; using System; using System.Threading; using System.Threading.Tasks; @@ -23,6 +24,8 @@ public class QueuesProcessorTask : IQuidjiboHandler public ILogger _logger; private CancellationToken _cancellationToken; + private SecurityLogic _securityLogic; + public QueuesProcessorTask(ILogger logger) { _logger = logger; @@ -46,6 +49,7 @@ public async Task ProcessAsync(QueuesProcessorCommand command, IQuidjiboProgress queue.AuditEventQueueReceived += OnAuditEventQueueReceived; queue.UnitLocationEventQueueReceived += OnUnitLocationEventQueueReceived; queue.PersonnelLocationEventQueueReceived += OnPersonnelLocationEventQueueReceived; + queue.SecurityRefreshEventQueueReceived += OnSecurityRefreshEventQueueReceived; await queue.Start(); @@ -127,5 +131,20 @@ private async Task OnPersonnelLocationEventQueueReceived(PersonnelLocationEvent await PersonnelLocationQueueLogic.ProcessPersonnelLocationQueueItem(personnelLocationEvent); _logger.LogInformation($"{Name}: Finished processing of Personnel Location queue item with an id of {personnelLocationEvent.EventId}."); } + + private async Task OnSecurityRefreshEventQueueReceived(SecurityRefreshEvent securityRefreshEvent) + { + _logger.LogInformation($"{Name}: Security Refresh Queue Received with an id of {securityRefreshEvent.EventId}, starting processing..."); + + if (_securityLogic == null) + _securityLogic = new SecurityLogic(); + + SecurityQueueItem item = new SecurityQueueItem(); + item.DepartmentId = securityRefreshEvent.DepartmentId; + item.Type = securityRefreshEvent.Type; + + await _securityLogic.Process(item); + _logger.LogInformation($"{Name}: Finished processing of Security Refresh queue item with an id of {securityRefreshEvent.EventId}."); + } } }