Add support request desktop shortcut.

This commit is contained in:
Jared Goodwin 2020-04-29 22:51:12 -07:00
parent 50d407b8d8
commit f07abd16f5
17 changed files with 232 additions and 27 deletions

View File

@ -175,6 +175,17 @@
<ItemGroup>
<Resource Include="Assets\Remotely_Icon.png" />
</ItemGroup>
<ItemGroup>
<COMReference Include="IWshRuntimeLibrary">
<Guid>{F935DC20-1CF0-11D0-ADB9-00C04FD58A0B}</Guid>
<VersionMajor>1</VersionMajor>
<VersionMinor>0</VersionMinor>
<Lcid>0</Lcid>
<WrapperTool>tlbimp</WrapperTool>
<Isolated>False</Isolated>
<EmbedInteropTypes>True</EmbedInteropTypes>
</COMReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PostBuildEvent>if $(ConfigurationName) == Debug (

View File

@ -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<bool> 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<ConnectionInfo>(File.ReadAllText(connectionInfoPath));
connectionInfo = Serializer.Deserialize<ConnectionInfo>(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();

View File

@ -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;

View File

@ -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
{

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
{

View File

@ -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

View File

@ -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
{

View File

@ -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);
}
}
}

View File

@ -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
{

View File

@ -0,0 +1,68 @@
@page
@model Remotely.Server.Pages.GetSupportModel
@{
ViewData["Title"] = "Get Support";
}
@if (!Request.Query.ContainsKey("deviceUuid"))
{
<h3 class="mb-3">Get Support</h3>
<p>
Device ID is missing. Please use a valid shortcut to the support page, which will include the device ID.
</p>
}
else
{
<div class="col-sm-6 offset-sm-3">
@if (!string.IsNullOrWhiteSpace(Model.StatusMessage))
{
<div class="alert alert-success alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
@Model.StatusMessage
</div>
}
<h3 class="mb-3">Get Support</h3>
<form method="post">
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label class="col-form-label">Your Name:</label>
<br />
<input type="text" class="form-control" asp-for="Input.Name" />
<span asp-validation-for="Input.Name" class="text-danger"></span>
</div>
<div class="form-group">
<label class="col-form-label">Email:</label>
<br />
<input type="email" class="form-control" asp-for="Input.Email" />
<span asp-validation-for="Input.Email" class="text-danger"></span>
</div>
<div class="form-group">
<label class="col-form-label">Phone:</label>
<br />
<input type="tel" class="form-control" asp-for="Input.Phone" />
<span asp-validation-for="Input.Phone" class="text-danger"></span>
</div>
<div class="form-group">
<label class="col-form-label">Chat Response OK?</label>
<br />
<input type="checkbox" checked="checked" asp-for="Input.ChatResponseOk" />
<span asp-validation-for="Input.ChatResponseOk" class="text-danger"></span>
</div>
<div class="text-right">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</form>
</div>
<style>
input[type='checkbox'] {
width: 25px;
height: 25px;
}
</style>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}
}

View File

@ -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<IActionResult> 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; }
}
}
}

View File

@ -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

View File

@ -1,7 +1,7 @@
using System.Security.Cryptography;
using System;
namespace Remotely.Server.Auth
namespace Remotely.Shared.Utilities
{
public static class PasswordGenerator
{