From f07abd16f5476f3096bd2c653ba41dcfa301335d Mon Sep 17 00:00:00 2001 From: Jared Goodwin Date: Wed, 29 Apr 2020 22:51:12 -0700 Subject: [PATCH] Add support request desktop shortcut. --- .../Agent.Installer.Win.csproj | 11 +++ .../Services/InstallerService.cs | 54 +++++++++++---- Server/API/AlertsController.cs | 2 +- Server/API/ClientDownloadsController.cs | 2 +- Server/API/CommandsController.cs | 2 +- Server/API/DevicesController.cs | 2 +- .../API/OrganizationManagementController.cs | 2 +- Server/API/RemoteControlController.cs | 2 +- Server/API/ScriptingController.cs | 2 +- Server/API/ServerLogsController.cs | 2 +- .../Pages/Account/Manage/ApiTokens.cshtml.cs | 2 +- .../Attributes/ActionRateLimiterAttribute.cs | 35 ++++++++++ .../ApiAuthorizationFilter.cs | 2 +- Server/Pages/GetSupport.cshtml | 68 +++++++++++++++++++ Server/Pages/GetSupport.cshtml.cs | 67 ++++++++++++++++++ Server/Startup.cs | 2 +- .../Utilities}/PasswordGenerator.cs | 2 +- 17 files changed, 232 insertions(+), 27 deletions(-) create mode 100644 Server/Attributes/ActionRateLimiterAttribute.cs rename Server/{Auth => Attributes}/ApiAuthorizationFilter.cs (97%) create mode 100644 Server/Pages/GetSupport.cshtml create mode 100644 Server/Pages/GetSupport.cshtml.cs rename {Server/Auth => Shared/Utilities}/PasswordGenerator.cs (91%) diff --git a/Agent.Installer.Win/Agent.Installer.Win.csproj b/Agent.Installer.Win/Agent.Installer.Win.csproj index 5bb5f90b..47ecefb4 100644 --- a/Agent.Installer.Win/Agent.Installer.Win.csproj +++ b/Agent.Installer.Win/Agent.Installer.Win.csproj @@ -175,6 +175,17 @@ + + + {F935DC20-1CF0-11D0-ADB9-00C04FD58A0B} + 1 + 0 + 0 + tlbimp + False + True + + if $(ConfigurationName) == Debug ( diff --git a/Agent.Installer.Win/Services/InstallerService.cs b/Agent.Installer.Win/Services/InstallerService.cs index 05bda673..098989a3 100644 --- a/Agent.Installer.Win/Services/InstallerService.cs +++ b/Agent.Installer.Win/Services/InstallerService.cs @@ -1,4 +1,5 @@ -using Microsoft.VisualBasic.FileIO; +using IWshRuntimeLibrary; +using Microsoft.VisualBasic.FileIO; using Microsoft.Win32; using Remotely.Shared.Models; using System; @@ -15,6 +16,7 @@ using System.ServiceProcess; using System.Threading.Tasks; using System.Web.Script.Serialization; using System.Windows; +using FileIO = System.IO.File; namespace Remotely.Agent.Installer.Win.Services { @@ -55,9 +57,9 @@ namespace Remotely.Agent.Installer.Win.Services await DownloadRemotelyAgent(serverUrl); - File.WriteAllText(Path.Combine(InstallPath, "ConnectionInfo.json"), Serializer.Serialize(connectionInfo)); + FileIO.WriteAllText(Path.Combine(InstallPath, "ConnectionInfo.json"), Serializer.Serialize(connectionInfo)); - File.Copy(Assembly.GetExecutingAssembly().Location, Path.Combine(InstallPath, "Remotely_Installer.exe")); + FileIO.Copy(Assembly.GetExecutingAssembly().Location, Path.Combine(InstallPath, "Remotely_Installer.exe")); CreateDeviceSetupOptions(deviceGroup, deviceAlias); @@ -66,6 +68,8 @@ namespace Remotely.Agent.Installer.Win.Services InstallService(); CreateUninstallKey(); + + CreateSupportShortcut(serverUrl, deviceUuid); return true; } @@ -78,6 +82,26 @@ namespace Remotely.Agent.Installer.Win.Services } + private void CreateSupportShortcut(string serverUrl, string deviceUuid) + { + var systemRoot = Path.GetPathRoot(Environment.SystemDirectory); + var shortcutLocation = Path.Combine(systemRoot, "Users", "Public", "Desktop", "Get Support.lnk"); + var shell = new WshShell(); + var shortcut = (IWshShortcut)shell.CreateShortcut(shortcutLocation); + shortcut.Description = "Get IT support"; + shortcut.IconLocation = Path.Combine(InstallPath, "Remotely_Agent.exe"); + shortcut.TargetPath = serverUrl.TrimEnd('/') + $"/GetSupport?deviceID={deviceUuid}"; + shortcut.Save(); + + + shortcutLocation = Path.Combine(InstallPath, "Get Support.lnk"); + shortcut = (IWshShortcut)shell.CreateShortcut(shortcutLocation); + shortcut.Description = "Get IT support"; + shortcut.IconLocation = Path.Combine(InstallPath, "Remotely_Agent.exe"); + shortcut.TargetPath = serverUrl.TrimEnd('/') + $"/GetSupport?deviceID={deviceUuid}"; + shortcut.Save(); + } + public async Task Uninstall() { try @@ -124,9 +148,9 @@ namespace Remotely.Agent.Installer.Win.Services Logger.Write("Backing up current installation."); ProgressMessageChanged?.Invoke(this, "Backing up current installation."); var backupPath = Path.Combine(Path.GetTempPath(), "Remotely_Backup.zip"); - if (File.Exists(backupPath)) + if (FileIO.Exists(backupPath)) { - File.Delete(backupPath); + FileIO.Delete(backupPath); } ZipFile.CreateFromDirectory(InstallPath, backupPath, CompressionLevel.Fastest, false); } @@ -152,9 +176,9 @@ namespace Remotely.Agent.Installer.Win.Services { try { - if (File.Exists(entry)) + if (FileIO.Exists(entry)) { - File.Delete(entry); + FileIO.Delete(entry); } else if (Directory.Exists(entry)) { @@ -180,7 +204,7 @@ namespace Remotely.Agent.Installer.Win.Services DeviceAlias = deviceAlias }; - File.WriteAllText(Path.Combine(InstallPath, "DeviceSetupOptions.json"), Serializer.Serialize(setupOptions)); + FileIO.WriteAllText(Path.Combine(InstallPath, "DeviceSetupOptions.json"), Serializer.Serialize(setupOptions)); } } @@ -207,7 +231,7 @@ namespace Remotely.Agent.Installer.Win.Services if (CommandLineParser.CommandLineArgs.TryGetValue("path", out var result)) { - File.Copy(result, targetFile, true); + FileIO.Copy(result, targetFile, true); } else { @@ -239,7 +263,7 @@ namespace Remotely.Agent.Installer.Win.Services var wr = WebRequest.CreateHttp($"{serverUrl}/Downloads/Remotely-Win10-{Platform}.zip"); wr.Method = "Head"; var response = (HttpWebResponse)await wr.GetResponseAsync(); - File.WriteAllText(Path.Combine(InstallPath, "etag.txt"), response.Headers["ETag"]); + FileIO.WriteAllText(Path.Combine(InstallPath, "etag.txt"), response.Headers["ETag"]); ZipFile.ExtractToDirectory(targetFile, tempDir); var fileSystemEntries = Directory.GetFileSystemEntries(tempDir); @@ -249,9 +273,9 @@ namespace Remotely.Agent.Installer.Win.Services { ProgressValueChanged?.Invoke(this, (int)((double)i / (double)fileSystemEntries.Length * 100d)); var entry = fileSystemEntries[i]; - if (File.Exists(entry)) + if (FileIO.Exists(entry)) { - File.Copy(entry, Path.Combine(InstallPath, Path.GetFileName(entry)), true); + FileIO.Copy(entry, Path.Combine(InstallPath, Path.GetFileName(entry)), true); } else if (Directory.Exists(entry)) { @@ -271,9 +295,9 @@ namespace Remotely.Agent.Installer.Win.Services { ConnectionInfo connectionInfo; var connectionInfoPath = Path.Combine(InstallPath, "ConnectionInfo.json"); - if (File.Exists(connectionInfoPath)) + if (FileIO.Exists(connectionInfoPath)) { - connectionInfo = Serializer.Deserialize(File.ReadAllText(connectionInfoPath)); + connectionInfo = Serializer.Deserialize(FileIO.ReadAllText(connectionInfoPath)); connectionInfo.ServerVerificationToken = null; } else @@ -393,7 +417,7 @@ namespace Remotely.Agent.Installer.Win.Services try { var backupPath = Path.Combine(Path.GetTempPath(), "Remotely_Backup.zip"); - if (File.Exists(backupPath)) + if (FileIO.Exists(backupPath)) { Logger.Write("Restoring backup."); ClearInstallDirectory(); diff --git a/Server/API/AlertsController.cs b/Server/API/AlertsController.cs index cad22101..b789e6fd 100644 --- a/Server/API/AlertsController.cs +++ b/Server/API/AlertsController.cs @@ -1,5 +1,5 @@ using Microsoft.AspNetCore.Mvc; -using Remotely.Server.Auth; +using Remotely.Server.Attributes; using Remotely.Server.Services; using Remotely.Shared.Models; using System; diff --git a/Server/API/ClientDownloadsController.cs b/Server/API/ClientDownloadsController.cs index add7750e..2ea73b2e 100644 --- a/Server/API/ClientDownloadsController.cs +++ b/Server/API/ClientDownloadsController.cs @@ -6,10 +6,10 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Hosting; using Remotely.Server.Services; -using Remotely.Server.Auth; using System.Text; using Microsoft.Extensions.Configuration; using System.Threading; +using Remotely.Server.Attributes; namespace Remotely.Server.API { diff --git a/Server/API/CommandsController.cs b/Server/API/CommandsController.cs index 05568e39..8a1dd9b4 100644 --- a/Server/API/CommandsController.cs +++ b/Server/API/CommandsController.cs @@ -7,7 +7,7 @@ using System.Threading.Tasks; using Remotely.Shared.Models; using Remotely.Server.Services; using Microsoft.AspNetCore.Mvc; -using Remotely.Server.Auth; +using Remotely.Server.Attributes; // For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860 diff --git a/Server/API/DevicesController.cs b/Server/API/DevicesController.cs index 83e5bab3..a8b41c0e 100644 --- a/Server/API/DevicesController.cs +++ b/Server/API/DevicesController.cs @@ -3,7 +3,7 @@ using Remotely.Shared.Models; using Remotely.Server.Services; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; -using Remotely.Server.Auth; +using Remotely.Server.Attributes; // For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860 diff --git a/Server/API/OrganizationManagementController.cs b/Server/API/OrganizationManagementController.cs index f6d97d4d..be6672b7 100644 --- a/Server/API/OrganizationManagementController.cs +++ b/Server/API/OrganizationManagementController.cs @@ -7,7 +7,7 @@ using Microsoft.AspNetCore.Mvc; using Remotely.Shared.ViewModels.Organization; using System.Text; using Microsoft.AspNetCore.WebUtilities; -using Remotely.Server.Auth; +using Remotely.Server.Attributes; // For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860 diff --git a/Server/API/RemoteControlController.cs b/Server/API/RemoteControlController.cs index cb40d97b..c0ddc353 100644 --- a/Server/API/RemoteControlController.cs +++ b/Server/API/RemoteControlController.cs @@ -8,7 +8,7 @@ using Microsoft.AspNetCore.SignalR; using Remotely.Shared.Models; using Remotely.Server.Models; using Remotely.Server.Services; -using Remotely.Server.Auth; +using Remotely.Server.Attributes; using Remotely.Shared.Helpers; // For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860 diff --git a/Server/API/ScriptingController.cs b/Server/API/ScriptingController.cs index cb8852d4..64197418 100644 --- a/Server/API/ScriptingController.cs +++ b/Server/API/ScriptingController.cs @@ -9,7 +9,7 @@ using System.Linq; using System.Threading.Tasks; using Remotely.Shared.Helpers; using System.IO; -using Remotely.Server.Auth; +using Remotely.Server.Attributes; namespace Remotely.Server.API { diff --git a/Server/API/ServerLogsController.cs b/Server/API/ServerLogsController.cs index 0811f6f3..c92cbc09 100644 --- a/Server/API/ServerLogsController.cs +++ b/Server/API/ServerLogsController.cs @@ -1,7 +1,7 @@ using System.Text; using System.Text.Json; using Microsoft.AspNetCore.Mvc; -using Remotely.Server.Auth; +using Remotely.Server.Attributes; using Remotely.Server.Services; namespace Remotely.Server.API diff --git a/Server/Areas/Identity/Pages/Account/Manage/ApiTokens.cshtml.cs b/Server/Areas/Identity/Pages/Account/Manage/ApiTokens.cshtml.cs index 467c7e01..7adb7344 100644 --- a/Server/Areas/Identity/Pages/Account/Manage/ApiTokens.cshtml.cs +++ b/Server/Areas/Identity/Pages/Account/Manage/ApiTokens.cshtml.cs @@ -6,9 +6,9 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; -using Remotely.Server.Auth; using Remotely.Server.Services; using Remotely.Shared.Models; +using Remotely.Shared.Utilities; namespace Remotely.Server.Areas.Identity.Pages.Account.Manage { diff --git a/Server/Attributes/ActionRateLimiterAttribute.cs b/Server/Attributes/ActionRateLimiterAttribute.cs new file mode 100644 index 00000000..2c6fc873 --- /dev/null +++ b/Server/Attributes/ActionRateLimiterAttribute.cs @@ -0,0 +1,35 @@ +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.Caching.Memory; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Threading.Tasks; + +namespace Remotely.Server.Attributes +{ + [AttributeUsage(AttributeTargets.Method)] + public class ActionRateLimiterAttribute : ActionFilterAttribute + { + public string Action { get; set; } + public int TimeoutInSeconds { get; set; } = 5; + private static MemoryCache RequestCache { get; } = new MemoryCache(new MemoryCacheOptions()); + + + public override void OnActionExecuting(ActionExecutingContext context) + { + var ip = context.HttpContext.Request.HttpContext.Connection.RemoteIpAddress; + var key = $"Action-{ip}"; + + if (!RequestCache.TryGetValue(key, out _)) + { + RequestCache.Set(key, true, TimeSpan.FromSeconds(TimeoutInSeconds)); + } + else + { + context.HttpContext.Response.StatusCode = (int)HttpStatusCode.TooManyRequests; + } + base.OnActionExecuting(context); + } + } +} diff --git a/Server/Auth/ApiAuthorizationFilter.cs b/Server/Attributes/ApiAuthorizationFilter.cs similarity index 97% rename from Server/Auth/ApiAuthorizationFilter.cs rename to Server/Attributes/ApiAuthorizationFilter.cs index b2f19649..e99a4811 100644 --- a/Server/Auth/ApiAuthorizationFilter.cs +++ b/Server/Attributes/ApiAuthorizationFilter.cs @@ -2,7 +2,7 @@ using Microsoft.AspNetCore.Mvc.Filters; using Remotely.Server.Services; -namespace Remotely.Server.Auth +namespace Remotely.Server.Attributes { public class ApiAuthorizationFilter : ActionFilterAttribute, IAuthorizationFilter { diff --git a/Server/Pages/GetSupport.cshtml b/Server/Pages/GetSupport.cshtml new file mode 100644 index 00000000..2c8f519d --- /dev/null +++ b/Server/Pages/GetSupport.cshtml @@ -0,0 +1,68 @@ +@page +@model Remotely.Server.Pages.GetSupportModel +@{ + ViewData["Title"] = "Get Support"; +} + +@if (!Request.Query.ContainsKey("deviceUuid")) +{ +

Get Support

+

+ Device ID is missing. Please use a valid shortcut to the support page, which will include the device ID. +

+} +else +{ +
+ @if (!string.IsNullOrWhiteSpace(Model.StatusMessage)) + { + + } +

Get Support

+
+
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+
+
+ + + + @section Scripts { + + } + +} diff --git a/Server/Pages/GetSupport.cshtml.cs b/Server/Pages/GetSupport.cshtml.cs new file mode 100644 index 00000000..e5d9d042 --- /dev/null +++ b/Server/Pages/GetSupport.cshtml.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Remotely.Server.Services; + +namespace Remotely.Server.Pages +{ + public class GetSupportModel : PageModel + { + public GetSupportModel(DataService dataService) + { + DataService = dataService; + } + + + private DataService DataService { get; } + + [TempData] + public string StatusMessage { get; set; } + + [BindProperty] + public InputModel Input { get; set; } + + public IActionResult OnGet() + { + return Page(); + } + + public async Task OnPost(string deviceUuid) + { + if (!ModelState.IsValid) + { + return Page(); + } + + var orgID = DataService.GetDevice(deviceUuid)?.OrganizationID; + + await DataService.AddAlert(new Remotely.Shared.Models.AlertOptions() + { + AlertDeviceID = deviceUuid, + AlertMessage = $"{Input.Name} is requesting support. " + + $"Email: {Input.Email}. " + + $"Phone: {Input.Phone}. " + + $"Chat OK: {Input.ChatResponseOk}.", + ShouldAlert = true + }, orgID); + + StatusMessage = "We got it! Someone will contact you soon."; + + return RedirectToPage("GetSupport", new { deviceUuid }); + } + + public class InputModel + { + [StringLength(150)] + [Required] + public string Name { get; set; } + public string Email { get; set; } + public string Phone { get; set; } + public bool ChatResponseOk { get; set; } + } + } +} \ No newline at end of file diff --git a/Server/Startup.cs b/Server/Startup.cs index d684485e..7dfb7326 100644 --- a/Server/Startup.cs +++ b/Server/Startup.cs @@ -19,7 +19,7 @@ using Microsoft.AspNetCore.HttpOverrides; using System.Net; using Microsoft.Extensions.Hosting; using Microsoft.OpenApi.Models; -using Remotely.Server.Auth; +using Remotely.Server.Attributes; using Npgsql; namespace Remotely.Server diff --git a/Server/Auth/PasswordGenerator.cs b/Shared/Utilities/PasswordGenerator.cs similarity index 91% rename from Server/Auth/PasswordGenerator.cs rename to Shared/Utilities/PasswordGenerator.cs index 9ca21cba..b3249645 100644 --- a/Server/Auth/PasswordGenerator.cs +++ b/Shared/Utilities/PasswordGenerator.cs @@ -1,7 +1,7 @@ using System.Security.Cryptography; using System; -namespace Remotely.Server.Auth +namespace Remotely.Shared.Utilities { public static class PasswordGenerator {