From 68a0cc0d7ea3a0761be91d130f2667eeecb13c03 Mon Sep 17 00:00:00 2001 From: Jared Goodwin Date: Tue, 24 Mar 2020 07:49:13 -0700 Subject: [PATCH] Create AlertsController and DataService methods. --- Server/API/AlertsController.cs | 100 ++++++++++++-- Server/Data/ApplicationDbContext.cs | 8 ++ Server/Services/DataService.cs | 200 +++++++++++++++------------- Shared/Models/Alert.cs | 9 ++ Shared/Models/Device.cs | 1 + Shared/Models/Organization.cs | 2 + 6 files changed, 217 insertions(+), 103 deletions(-) diff --git a/Server/API/AlertsController.cs b/Server/API/AlertsController.cs index 81594d09f..4cf877700 100644 --- a/Server/API/AlertsController.cs +++ b/Server/API/AlertsController.cs @@ -1,9 +1,15 @@ -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; using Remotely.Server.Auth; +using Remotely.Server.Services; using Remotely.Shared.Models; using System; using System.Collections.Generic; +using System.IO; using System.Linq; +using System.Net; +using System.Net.Http; +using System.Text.Json; using System.Threading.Tasks; namespace Remotely.Server.API @@ -13,27 +19,103 @@ namespace Remotely.Server.API [ServiceFilter(typeof(ApiAuthorizationFilter))] public class AlertsController : ControllerBase { + public AlertsController(DataService dataService, IEmailSenderEx emailSender) + { + DataService = dataService; + EmailSender = emailSender; + } + + private DataService DataService { get; } + private IEmailSenderEx EmailSender { get; } + [HttpPost("Create")] + [ServiceFilter(typeof(ApiAuthorizationFilter))] public async Task Create(AlertOptions alertOptions) { Request.Headers.TryGetValue("OrganizationID", out var orgID); + DataService.WriteEvent("Alert created. Alert Options: " + JsonSerializer.Serialize(alertOptions), orgID); + if (alertOptions.ShouldAlert) { - var alert = new Alert() + try + { + var alert = new Alert() + { + CreatedOn = DateTimeOffset.Now, + DeviceID = alertOptions.AlertDeviceID, + Message = alertOptions.AlertMessage, + OrganizationID = orgID + }; + await DataService.AddAlert(alert); + } + catch (Exception ex) { - CreatedOn = DateTimeOffset.Now, - DeviceID = alertOptions.AlertDeviceID, - Message = alertOptions.AlertMessage - }; - // TODO: Add alert. + DataService.WriteEvent(ex, orgID); + } } - // TODO: Email. + if (alertOptions.ShouldEmail) + { + try + { + await EmailSender.SendEmailAsync(alertOptions.EmailTo, + alertOptions.EmailSubject, + alertOptions.EmailBody, + orgID); + } + catch (Exception ex) + { + DataService.WriteEvent(ex, orgID); + } + + } - // TODO: API request. + if (alertOptions.ShouldSendApiRequest) + { + try + { + var httpRequest = WebRequest.CreateHttp(alertOptions.ApiRequestUrl); + httpRequest.Method = alertOptions.ApiRequestMethod; + httpRequest.ContentType = "application/json"; + foreach (var header in alertOptions.ApiRequestHeaders) + { + httpRequest.Headers.Add(header.Key, header.Value); + } + using (var rs = httpRequest.GetRequestStream()) + using (var sw = new StreamWriter(rs)) + { + sw.Write(alertOptions.ApiRequestBody); + } + var response = (HttpWebResponse)httpRequest.GetResponse(); + DataService.WriteEvent($"Alert API Response Status: {response.StatusCode}.", orgID); + } + catch (Exception ex) + { + DataService.WriteEvent(ex, orgID); + } + + } return Ok(); } + + [HttpPost("Delete/{alertID}")] + [ServiceFilter(typeof(ApiAuthorizationFilter))] + public async Task Delete(string alertID) + { + Request.Headers.TryGetValue("OrganizationID", out var orgID); + + var alert = await DataService.GetAlert(alertID); + + if (alert?.OrganizationID == orgID) + { + await DataService.DeleteAlert(alert); + + return Ok(); + } + + return Unauthorized(); + } } } diff --git a/Server/Data/ApplicationDbContext.cs b/Server/Data/ApplicationDbContext.cs index 51df27161..fdec54c8a 100644 --- a/Server/Data/ApplicationDbContext.cs +++ b/Server/Data/ApplicationDbContext.cs @@ -18,6 +18,8 @@ public ApplicationDbContext(DbContextOptions context) { } + public DbSet Alerts { get; set; } + public DbSet ApiTokens { get; set; } public DbSet CommandResults { get; set; } @@ -74,6 +76,9 @@ protected override void OnModelCreating(ModelBuilder builder) builder.Entity() .HasMany(x => x.ApiTokens) .WithOne(x => x.Organization); + builder.Entity() + .HasMany(x => x.Alerts) + .WithOne(x => x.Organization); builder.Entity() @@ -122,6 +127,9 @@ protected override void OnModelCreating(ModelBuilder builder) builder.Entity() .HasIndex(x => x.DeviceName); + builder.Entity() + .HasMany(x => x.Alerts) + .WithOne(x => x.Device); builder.Entity() .HasIndex(x => x.Token); diff --git a/Server/Services/DataService.cs b/Server/Services/DataService.cs index 94bb17271..406a71be7 100644 --- a/Server/Services/DataService.cs +++ b/Server/Services/DataService.cs @@ -34,6 +34,12 @@ public DataService(ApplicationDbContext context, private ApplicationDbContext RemotelyContext { get; } private UserManager UserManager { get; } + public async Task AddAlert(Alert alert) + { + RemotelyContext.Alerts.Add(alert); + await RemotelyContext.SaveChangesAsync(); + } + public bool AddDeviceGroup(string orgID, DeviceGroup deviceGroup, out string deviceGroupID, out string errorMessage) { deviceGroupID = null; @@ -64,12 +70,9 @@ public bool AddDeviceGroup(string orgID, DeviceGroup deviceGroup, out string dev return true; } - public List GetServerAdmins() + public async Task GetAlert(string alertID) { - return RemotelyContext.Users - .Where(x => x.IsServerAdmin) - .Select(x => x.UserName) - .ToList(); + return await RemotelyContext.Alerts.FirstOrDefaultAsync(x => x.ID == alertID); } public InviteLink AddInvite(string orgID, Invite invite) @@ -109,7 +112,6 @@ public void AddOrUpdateCommandResult(CommandResult commandResult) RemotelyContext.SaveChanges(); } - public bool AddOrUpdateDevice(Device device, out Device updatedDevice) { var existingDevice = RemotelyContext.Devices.Find(device.ID); @@ -161,31 +163,6 @@ public bool AddOrUpdateDevice(Device device, out Device updatedDevice) return true; } - public async Task UpdateServerAdmins(List serverAdmins, string callerUserName) - { - var currentAdmins = RemotelyContext.Users.Where(x => x.IsServerAdmin).ToList(); - - var removeAdmins = currentAdmins.Where(currentAdmin => - !serverAdmins.Contains(currentAdmin.UserName.Trim().ToLower()) && - currentAdmin.UserName.Trim().ToLower() != callerUserName.Trim().ToLower()); - - foreach (var removeAdmin in removeAdmins) - { - removeAdmin.IsServerAdmin = false; - } - - var newAdmins = RemotelyContext.Users.Where(user => - serverAdmins.Contains(user.UserName.Trim().ToLower()) && - !user.IsServerAdmin); - - foreach (var newAdmin in newAdmins) - { - newAdmin.IsServerAdmin = true; - } - - await RemotelyContext.SaveChangesAsync(); - } - public string AddSharedFile(IFormFile file, string organizationID) { var expirationDate = DateTimeOffset.Now.AddDays(-AppConfig.DataRetentionInDays); @@ -212,6 +189,60 @@ public string AddSharedFile(IFormFile file, string organizationID) return newEntity.Entity.ID; } + public bool AddUserToDeviceGroup(string orgID, string groupID, string userName, out string resultMessage) + { + resultMessage = string.Empty; + + var deviceGroup = RemotelyContext.DeviceGroups + .Include(x => x.PermissionLinks) + .FirstOrDefault(x => + x.ID == groupID && + x.OrganizationID == orgID); + + if (deviceGroup == null) + { + resultMessage = "Device group not found."; + return false; + } + + userName = userName.Trim().ToLower(); + + var user = RemotelyContext.Users + .Include(x => x.PermissionLinks) + .FirstOrDefault(x => + x.UserName.ToLower() == userName && + x.OrganizationID == orgID); + + if (user == null) + { + resultMessage = "User not found."; + return false; + } + + deviceGroup.PermissionLinks = deviceGroup.PermissionLinks ?? new List(); + user.PermissionLinks = user.PermissionLinks ?? new List(); + + if (deviceGroup.PermissionLinks.Any(x => x.UserID == user.Id)) + { + resultMessage = "User already in group."; + return false; + } + + var link = new UserDevicePermission() + { + DeviceGroup = deviceGroup, + DeviceGroupID = deviceGroup.ID, + User = user, + UserID = user.Id + }; + + deviceGroup.PermissionLinks.Add(link); + user.PermissionLinks.Add(link); + RemotelyContext.SaveChanges(); + resultMessage = user.Id; + return true; + } + public void ChangeUserIsAdmin(string organizationID, string targetUserID, bool isAdmin) { var targetUser = RemotelyContext.Users.FirstOrDefault(x => @@ -267,7 +298,6 @@ public async Task CreateApiToken(string userName, string tokenName, st return newToken; } - public async Task CreateUser(string userEmail, bool isAdmin, string organizationID) { try @@ -280,8 +310,8 @@ public async Task CreateUser(string userEmail, bool isAdmin, string organi OrganizationID = organizationID }; var org = RemotelyContext.Organizations - .Include(x=>x.RemotelyUsers) - .FirstOrDefault(x=>x.ID == organizationID); + .Include(x => x.RemotelyUsers) + .FirstOrDefault(x => x.ID == organizationID); org.RemotelyUsers.Add(user); await RemotelyContext.SaveChangesAsync(); return true; @@ -294,6 +324,11 @@ public async Task CreateUser(string userEmail, bool isAdmin, string organi } + public async Task DeleteAlert(Alert alert) + { + RemotelyContext.Alerts.Remove(alert); + await RemotelyContext.SaveChangesAsync(); + } public async Task DeleteApiToken(string userName, string tokenId) { @@ -401,7 +436,7 @@ public string[] FilterDeviceIDsByUserPermission(string[] deviceIDs, RemotelyUser return RemotelyContext.Devices .Include(x => x.DeviceGroup) .ThenInclude(x => x.PermissionLinks) - .Where(device => + .Where(device => device.OrganizationID == remotelyUser.OrganizationID && deviceIDs.Contains(device.ID) && ( @@ -437,7 +472,6 @@ public string[] FilterUsersByDevicePermission(IEnumerable userIDs, strin .ToArray(); } - public IEnumerable GetAllApiTokens(string userID) { var user = RemotelyContext.Users.FirstOrDefault(x => x.Id == userID); @@ -540,12 +574,12 @@ public IEnumerable GetDeviceGroups(string username) return RemotelyContext.DeviceGroups .Include(x => x.PermissionLinks) .ThenInclude(x => x.User) - .Where(x => - x.OrganizationID == user.OrganizationID && + .Where(x => + x.OrganizationID == user.OrganizationID && ( user.IsAdministrator || x.PermissionLinks.Count == 0 || - x.PermissionLinks.Any(x=>x.UserID == user.Id) + x.PermissionLinks.Any(x => x.UserID == user.Id) ) ) ?? Enumerable.Empty(); } @@ -558,7 +592,7 @@ public IEnumerable GetDevicesForUser(string userName) return RemotelyContext.Devices .Include(x => x.DeviceGroup) .ThenInclude(x => x.PermissionLinks) - .Where(x => + .Where(x => x.OrganizationID == user.OrganizationID && ( user.IsAdministrator || @@ -599,6 +633,14 @@ public string GetOrganizationNameById(string organizationID) { return RemotelyContext.Organizations.FirstOrDefault(x => x.ID == organizationID)?.OrganizationName; } + + public List GetServerAdmins() + { + return RemotelyContext.Users + .Where(x => x.IsServerAdmin) + .Select(x => x.UserName) + .ToList(); + } public SharedFile GetSharedFiled(string fileID) { return RemotelyContext.SharedFiles.Find(fileID); @@ -692,60 +734,6 @@ public async Task RemoveUserFromDeviceGroup(string orgID, string groupID, return false; } - public bool AddUserToDeviceGroup(string orgID, string groupID, string userName, out string resultMessage) - { - resultMessage = string.Empty; - - var deviceGroup = RemotelyContext.DeviceGroups - .Include(x => x.PermissionLinks) - .FirstOrDefault(x => - x.ID == groupID && - x.OrganizationID == orgID); - - if (deviceGroup == null) - { - resultMessage = "Device group not found."; - return false; - } - - userName = userName.Trim().ToLower(); - - var user = RemotelyContext.Users - .Include(x => x.PermissionLinks) - .FirstOrDefault(x => - x.UserName.ToLower() == userName && - x.OrganizationID == orgID); - - if (user == null) - { - resultMessage = "User not found."; - return false; - } - - deviceGroup.PermissionLinks = deviceGroup.PermissionLinks ?? new List(); - user.PermissionLinks = user.PermissionLinks ?? new List(); - - if (deviceGroup.PermissionLinks.Any(x => x.UserID == user.Id)) - { - resultMessage = "User already in group."; - return false; - } - - var link = new UserDevicePermission() - { - DeviceGroup = deviceGroup, - DeviceGroupID = deviceGroup.ID, - User = user, - UserID = user.Id - }; - - deviceGroup.PermissionLinks.Add(link); - user.PermissionLinks.Add(link); - RemotelyContext.SaveChanges(); - resultMessage = user.Id; - return true; - } - public async Task RemoveUserFromOrganization(string orgID, string targetUserID) { var target = RemotelyContext.Users @@ -845,6 +833,30 @@ public void UpdateOrganizationName(string orgID, string organizationName) RemotelyContext.SaveChanges(); } + public async Task UpdateServerAdmins(List serverAdmins, string callerUserName) + { + var currentAdmins = RemotelyContext.Users.Where(x => x.IsServerAdmin).ToList(); + + var removeAdmins = currentAdmins.Where(currentAdmin => + !serverAdmins.Contains(currentAdmin.UserName.Trim().ToLower()) && + currentAdmin.UserName.Trim().ToLower() != callerUserName.Trim().ToLower()); + + foreach (var removeAdmin in removeAdmins) + { + removeAdmin.IsServerAdmin = false; + } + + var newAdmins = RemotelyContext.Users.Where(user => + serverAdmins.Contains(user.UserName.Trim().ToLower()) && + !user.IsServerAdmin); + + foreach (var newAdmin in newAdmins) + { + newAdmin.IsServerAdmin = true; + } + + await RemotelyContext.SaveChangesAsync(); + } public void UpdateTags(string deviceID, string tags) { var device = RemotelyContext.Devices.Find(deviceID); diff --git a/Shared/Models/Alert.cs b/Shared/Models/Alert.cs index 384dbb12a..150a844ed 100644 --- a/Shared/Models/Alert.cs +++ b/Shared/Models/Alert.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Text; +using System.Text.Json.Serialization; namespace Remotely.Shared.Models { @@ -10,9 +11,17 @@ public class Alert [Key] public string ID { get; set; } = Guid.NewGuid().ToString(); public DateTimeOffset CreatedOn { get; set; } = DateTimeOffset.Now; + + [JsonIgnore] public Device Device { get; set; } public string DeviceID { get; set; } public string Message { get; set; } + + [JsonIgnore] + public Organization Organization { get; set; } + + public string OrganizationID { get; set; } + public ICollection SeenBy { get; set; } } } diff --git a/Shared/Models/Device.cs b/Shared/Models/Device.cs index 7881aacbb..0ea6e9a05 100644 --- a/Shared/Models/Device.cs +++ b/Shared/Models/Device.cs @@ -15,6 +15,7 @@ namespace Remotely.Shared.Models public class Device { public string AgentVersion { get; set; } + public ICollection Alerts { get; set; } [StringLength(100)] public string Alias { get; set; } public double CpuUtilization { get; set; } diff --git a/Shared/Models/Organization.cs b/Shared/Models/Organization.cs index e873067c4..808f16c5e 100644 --- a/Shared/Models/Organization.cs +++ b/Shared/Models/Organization.cs @@ -7,6 +7,8 @@ namespace Remotely.Shared.Models { public class Organization { + public ICollection Alerts { get; set; } + public ICollection ApiTokens { get; set; } public ICollection CommandResults { get; set; }