From 1a033fb0e7389c1ec885bdb9836715eaeefdda8b Mon Sep 17 00:00:00 2001 From: Jared Goodwin Date: Mon, 31 Jul 2023 11:56:19 -0700 Subject: [PATCH 1/3] Moved code from Razor components to code-behind to fix Identity UI scaffolding. --- Agent/Agent.csproj | 14 +- .../Pages/Account/Manage/ManageNavPages.cs | 160 +++++++++++++----- .../Pages/_ValidationScriptsPartial.cshtml | 18 ++ .../ModalContents/EditDeviceGroup.razor | 59 +------ .../ModalContents/EditDeviceGroup.razor.cs | 63 +++++++ .../QuickScriptsSelector.razor.cs | 7 + Server/Data/AppDb.cs | 21 ++- Server/Data/AppDbFactory.cs | 18 +- Server/Data/PostgreSqlDbContext.cs | 6 +- Server/Data/SqlServerDbContext.cs | 6 +- Server/Data/SqliteDbContext.cs | 6 +- Server/Data/TestingDbContext.cs | 8 +- Server/Pages/ScriptsPage.razor | 134 --------------- Server/Pages/ScriptsPage.razor.cs | 148 ++++++++++++++++ Server/Program.cs | 37 +--- Server/Server.csproj | 18 +- Shared/Shared.csproj | 2 +- Tests/Server.Tests/Server.Tests.csproj | 10 +- Tests/Shared.Tests/Shared.Tests.csproj | 6 +- submodules/Immense.RemoteControl | 2 +- 20 files changed, 439 insertions(+), 304 deletions(-) create mode 100644 Server/Areas/Identity/Pages/_ValidationScriptsPartial.cshtml create mode 100644 Server/Components/ModalContents/EditDeviceGroup.razor.cs create mode 100644 Server/Components/ModalContents/QuickScriptsSelector.razor.cs create mode 100644 Server/Pages/ScriptsPage.razor.cs diff --git a/Agent/Agent.csproj b/Agent/Agent.csproj index 30150989e..fdf64ab5b 100644 --- a/Agent/Agent.csproj +++ b/Agent/Agent.csproj @@ -24,18 +24,18 @@ - - + + - - - - - + + + + + diff --git a/Server/Areas/Identity/Pages/Account/Manage/ManageNavPages.cs b/Server/Areas/Identity/Pages/Account/Manage/ManageNavPages.cs index 4d1010386..1419219b2 100644 --- a/Server/Areas/Identity/Pages/Account/Manage/ManageNavPages.cs +++ b/Server/Areas/Identity/Pages/Account/Manage/ManageNavPages.cs @@ -1,49 +1,123 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc.Rendering; +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +#nullable disable -namespace Remotely.Server.Areas.Identity.Pages.Account.Manage; +using System; +using Microsoft.AspNetCore.Mvc.Rendering; -public static class ManageNavPages +namespace Remotely.Server.Areas.Identity.Pages.Account.Manage { - public static string Index => "Index"; - - public static string Email => "Email"; - - public static string ChangePassword => "ChangePassword"; - - public static string DownloadPersonalData => "DownloadPersonalData"; - - public static string DeletePersonalData => "DeletePersonalData"; - - public static string ExternalLogins => "ExternalLogins"; - - public static string PersonalData => "PersonalData"; - - public static string TwoFactorAuthentication => "TwoFactorAuthentication"; - - public static string IndexNavClass(ViewContext viewContext) => PageNavClass(viewContext, Index); - - public static string EmailNavClass(ViewContext viewContext) => PageNavClass(viewContext, Email); - - public static string ChangePasswordNavClass(ViewContext viewContext) => PageNavClass(viewContext, ChangePassword); - - public static string DownloadPersonalDataNavClass(ViewContext viewContext) => PageNavClass(viewContext, DownloadPersonalData); - - public static string DeletePersonalDataNavClass(ViewContext viewContext) => PageNavClass(viewContext, DeletePersonalData); - - public static string ExternalLoginsNavClass(ViewContext viewContext) => PageNavClass(viewContext, ExternalLogins); - - public static string PersonalDataNavClass(ViewContext viewContext) => PageNavClass(viewContext, PersonalData); - - public static string TwoFactorAuthenticationNavClass(ViewContext viewContext) => PageNavClass(viewContext, TwoFactorAuthentication); - - private static string PageNavClass(ViewContext viewContext, string page) + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static class ManageNavPages { - var activePage = viewContext.ViewData["ActivePage"] as string - ?? System.IO.Path.GetFileNameWithoutExtension(viewContext.ActionDescriptor.DisplayName); - return string.Equals(activePage, page, StringComparison.OrdinalIgnoreCase) ? "active" : ""; + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static string Index => "Index"; + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static string Email => "Email"; + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static string ChangePassword => "ChangePassword"; + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static string DownloadPersonalData => "DownloadPersonalData"; + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static string DeletePersonalData => "DeletePersonalData"; + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static string ExternalLogins => "ExternalLogins"; + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static string PersonalData => "PersonalData"; + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static string TwoFactorAuthentication => "TwoFactorAuthentication"; + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static string IndexNavClass(ViewContext viewContext) => PageNavClass(viewContext, Index); + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static string EmailNavClass(ViewContext viewContext) => PageNavClass(viewContext, Email); + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static string ChangePasswordNavClass(ViewContext viewContext) => PageNavClass(viewContext, ChangePassword); + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static string DownloadPersonalDataNavClass(ViewContext viewContext) => PageNavClass(viewContext, DownloadPersonalData); + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static string DeletePersonalDataNavClass(ViewContext viewContext) => PageNavClass(viewContext, DeletePersonalData); + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static string ExternalLoginsNavClass(ViewContext viewContext) => PageNavClass(viewContext, ExternalLogins); + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static string PersonalDataNavClass(ViewContext viewContext) => PageNavClass(viewContext, PersonalData); + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static string TwoFactorAuthenticationNavClass(ViewContext viewContext) => PageNavClass(viewContext, TwoFactorAuthentication); + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static string PageNavClass(ViewContext viewContext, string page) + { + var activePage = viewContext.ViewData["ActivePage"] as string + ?? System.IO.Path.GetFileNameWithoutExtension(viewContext.ActionDescriptor.DisplayName); + return string.Equals(activePage, page, StringComparison.OrdinalIgnoreCase) ? "active" : null; + } } } diff --git a/Server/Areas/Identity/Pages/_ValidationScriptsPartial.cshtml b/Server/Areas/Identity/Pages/_ValidationScriptsPartial.cshtml new file mode 100644 index 000000000..efa2d880f --- /dev/null +++ b/Server/Areas/Identity/Pages/_ValidationScriptsPartial.cshtml @@ -0,0 +1,18 @@ + + + + + + + + diff --git a/Server/Components/ModalContents/EditDeviceGroup.razor b/Server/Components/ModalContents/EditDeviceGroup.razor index 3d81f6755..811f302ab 100644 --- a/Server/Components/ModalContents/EditDeviceGroup.razor +++ b/Server/Components/ModalContents/EditDeviceGroup.razor @@ -1,7 +1,5 @@ @attribute [Authorize] -@inherits AuthComponentBase -@inject IDataService DataService -@inject IToastService ToastService +@inherits AuthComponentBase
Editing @EditUser?.UserName @@ -10,55 +8,12 @@ @foreach (var group in DeviceGroups ?? Array.Empty()) {
- +
} - - -@code { - public static string EditUserPropName => nameof(EditUser); - public static string DeviceGroupsPropName => nameof(DeviceGroups); - - [Parameter] - public required RemotelyUser EditUser { get; set; } - - [Parameter] - public required DeviceGroup[] DeviceGroups { get; set; } - - - private bool DoesGroupContainUser(DeviceGroup group) - { - return group.Users.Any(x => x.Id == EditUser.Id); - } - - private async Task GroupCheckChanged(ChangeEventArgs args, DeviceGroup group) - { - if (!string.IsNullOrWhiteSpace(EditUser.UserName) && - args.Value is bool boolValue && - boolValue) - { - if (!DataService.AddUserToDeviceGroup(EditUser.OrganizationID, group.ID, EditUser.UserName, out var result)) - { - ToastService.ShowToast(result, classString: "bg-warning"); - } - else - { - ToastService.ShowToast("User added to group."); - } - - } - else - { - var result = await DataService.RemoveUserFromDeviceGroup(EditUser.OrganizationID, group.ID, EditUser.Id); - if (!result) - { - ToastService.ShowToast("Failed to remove from group.", classString: "bg-warning"); - } - else - { - ToastService.ShowToast("Removed user from group."); - } - } - } -} diff --git a/Server/Components/ModalContents/EditDeviceGroup.razor.cs b/Server/Components/ModalContents/EditDeviceGroup.razor.cs new file mode 100644 index 000000000..f032b6570 --- /dev/null +++ b/Server/Components/ModalContents/EditDeviceGroup.razor.cs @@ -0,0 +1,63 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Components; +using Remotely.Server.Services; +using Remotely.Shared.Entities; +using Remotely.Shared.Models; +using System.Linq; +using System.Threading.Tasks; + +namespace Remotely.Server.Components.ModalContents; + +[Authorize] +public partial class EditDeviceGroup : AuthComponentBase +{ + public static string DeviceGroupsPropName => nameof(DeviceGroups); + public static string EditUserPropName => nameof(EditUser); + [Parameter] + public required DeviceGroup[] DeviceGroups { get; set; } + + [Parameter] + public required RemotelyUser EditUser { get; set; } + + [Inject] + private IDataService DataService { get; init; } = null!; + + [Inject] + private IToastService ToastService { get; init; } = null!; + + + private bool DoesGroupContainUser(DeviceGroup group) + { + return group.Users.Any(x => x.Id == EditUser.Id); + } + + private async Task GroupCheckChanged(ChangeEventArgs args, DeviceGroup group) + { + if (!string.IsNullOrWhiteSpace(EditUser.UserName) && + args.Value is bool boolValue && + boolValue) + { + if (!DataService.AddUserToDeviceGroup(EditUser.OrganizationID, group.ID, EditUser.UserName, out var result)) + { + ToastService.ShowToast(result, classString: "bg-warning"); + } + else + { + ToastService.ShowToast("User added to group."); + } + + } + else + { + var result = await DataService.RemoveUserFromDeviceGroup(EditUser.OrganizationID, group.ID, EditUser.Id); + if (!result) + { + ToastService.ShowToast("Failed to remove from group.", classString: "bg-warning"); + } + else + { + ToastService.ShowToast("Removed user from group."); + } + } + } +} diff --git a/Server/Components/ModalContents/QuickScriptsSelector.razor.cs b/Server/Components/ModalContents/QuickScriptsSelector.razor.cs new file mode 100644 index 000000000..a01c7c5ba --- /dev/null +++ b/Server/Components/ModalContents/QuickScriptsSelector.razor.cs @@ -0,0 +1,7 @@ +using Microsoft.AspNetCore.Components; + +namespace Remotely.Server.Components.ModalContents; + +public partial class QuickScriptsSelector : ComponentBase +{ +} diff --git a/Server/Data/AppDb.cs b/Server/Data/AppDb.cs index b9112e81b..14a800805 100644 --- a/Server/Data/AppDb.cs +++ b/Server/Data/AppDb.cs @@ -1,10 +1,12 @@ -using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Remotely.Shared.Entities; using Remotely.Shared.Models; @@ -23,6 +25,13 @@ public class AppDb : IdentityDbContext c => c.Aggregate(0, (a, b) => HashCode.Combine(a, b.GetHashCode())), c => c.ToArray()); + private readonly IWebHostEnvironment _hostEnv; + + public AppDb(IWebHostEnvironment hostEnvironment) + { + _hostEnv = hostEnvironment; + } + public DbSet Alerts { get; set; } public DbSet ApiTokens { get; set; } @@ -31,10 +40,10 @@ public class AppDb : IdentityDbContext public DbSet Devices { get; set; } public DbSet InviteLinks { get; set; } public DbSet Organizations { get; set; } - public DbSet ScriptRuns { get; set; } public DbSet SavedScripts { get; set; } - public DbSet ScriptSchedules { get; set; } public DbSet ScriptResults { get; set; } + public DbSet ScriptRuns { get; set; } + public DbSet ScriptSchedules { get; set; } public DbSet SharedFiles { get; set; } public new DbSet Users { get; set; } @@ -43,6 +52,12 @@ protected override void OnConfiguring(DbContextOptionsBuilder options) { options.ConfigureWarnings(x => x.Ignore(RelationalEventId.MultipleCollectionIncludeWarning)); options.LogTo((message) => System.Diagnostics.Debug.Write(message)); + + if (_hostEnv.IsDevelopment()) + { + options.EnableDetailedErrors(); + options.EnableSensitiveDataLogging(); + } } protected override void OnModelCreating(ModelBuilder builder) diff --git a/Server/Data/AppDbFactory.cs b/Server/Data/AppDbFactory.cs index 7645c39a7..372987769 100644 --- a/Server/Data/AppDbFactory.cs +++ b/Server/Data/AppDbFactory.cs @@ -1,4 +1,5 @@ -using Microsoft.Extensions.Configuration; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; using Remotely.Server.Services; using System; @@ -13,21 +14,26 @@ public class AppDbFactory : IAppDbFactory { private readonly IApplicationConfig _appConfig; private readonly IConfiguration _configuration; + private readonly IWebHostEnvironment _hostEnv; - public AppDbFactory(IApplicationConfig appConfig, IConfiguration configuration) + public AppDbFactory( + IApplicationConfig appConfig, + IConfiguration configuration, + IWebHostEnvironment hostEnv) { _appConfig = appConfig; _configuration = configuration; + _hostEnv = hostEnv; } public AppDb GetContext() { return _appConfig.DBProvider.ToLower() switch { - "sqlite" => new SqliteDbContext(_configuration), - "sqlserver" => new SqlServerDbContext(_configuration), - "postgresql" => new PostgreSqlDbContext(_configuration), - "inmemory" => new TestingDbContext(), + "sqlite" => new SqliteDbContext(_configuration, _hostEnv), + "sqlserver" => new SqlServerDbContext(_configuration, _hostEnv), + "postgresql" => new PostgreSqlDbContext(_configuration, _hostEnv), + "inmemory" => new TestingDbContext(_hostEnv), _ => throw new ArgumentException("Unknown DB provider."), }; } diff --git a/Server/Data/PostgreSqlDbContext.cs b/Server/Data/PostgreSqlDbContext.cs index 919f605fa..f602b88a0 100644 --- a/Server/Data/PostgreSqlDbContext.cs +++ b/Server/Data/PostgreSqlDbContext.cs @@ -1,4 +1,5 @@ -using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.Extensions.Configuration; @@ -14,7 +15,8 @@ public class PostgreSqlDbContext : AppDb { private readonly IConfiguration _configuration; - public PostgreSqlDbContext(IConfiguration configuration) + public PostgreSqlDbContext(IConfiguration configuration, IWebHostEnvironment hostEnv) + : base(hostEnv) { _configuration = configuration; } diff --git a/Server/Data/SqlServerDbContext.cs b/Server/Data/SqlServerDbContext.cs index 6a82b872f..421453d72 100644 --- a/Server/Data/SqlServerDbContext.cs +++ b/Server/Data/SqlServerDbContext.cs @@ -1,4 +1,5 @@ -using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.Extensions.Configuration; @@ -13,7 +14,8 @@ public class SqlServerDbContext : AppDb { private readonly IConfiguration _configuration; - public SqlServerDbContext(IConfiguration configuration) + public SqlServerDbContext(IConfiguration configuration, IWebHostEnvironment hostEnv) + : base(hostEnv) { _configuration = configuration; } diff --git a/Server/Data/SqliteDbContext.cs b/Server/Data/SqliteDbContext.cs index 76f13c4d3..a0798b52b 100644 --- a/Server/Data/SqliteDbContext.cs +++ b/Server/Data/SqliteDbContext.cs @@ -1,4 +1,5 @@ -using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.Extensions.Configuration; @@ -13,7 +14,8 @@ public class SqliteDbContext : AppDb { private readonly IConfiguration _configuration; - public SqliteDbContext(IConfiguration configuration) + public SqliteDbContext(IConfiguration configuration, IWebHostEnvironment hostEnv) + : base(hostEnv) { _configuration = configuration; } diff --git a/Server/Data/TestingDbContext.cs b/Server/Data/TestingDbContext.cs index 8d379870d..593642f11 100644 --- a/Server/Data/TestingDbContext.cs +++ b/Server/Data/TestingDbContext.cs @@ -1,4 +1,5 @@ -using Microsoft.EntityFrameworkCore; +using Microsoft.AspNetCore.Hosting; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Remotely.Server.Data; using System; @@ -11,6 +12,11 @@ namespace Remotely.Server.Data; public class TestingDbContext : AppDb { + public TestingDbContext(IWebHostEnvironment hostEnvironment) + : base(hostEnvironment) + { + } + protected override void OnConfiguring(DbContextOptionsBuilder options) { options.UseInMemoryDatabase("Remotely"); diff --git a/Server/Pages/ScriptsPage.razor b/Server/Pages/ScriptsPage.razor index 6e7cfaf05..e9e4d2bac 100644 --- a/Server/Pages/ScriptsPage.razor +++ b/Server/Pages/ScriptsPage.razor @@ -1,8 +1,6 @@ @page "/scripts/{activeTab?}" @attribute [Authorize] @inherits AuthComponentBase -@using System.Collections -@inject IDataService DataService @@ -30,135 +28,3 @@ - - -@code { - private readonly List _treeNodes = new(); - private IEnumerable _allScripts = Enumerable.Empty(); - - private bool _showOnlyMyScripts = true; - - [Parameter] - public string? ActiveTab { get; set; } - - public bool ShowOnlyMyScripts - { - get => _showOnlyMyScripts; - set - { - _showOnlyMyScripts = value; - _treeNodes.Clear(); - } - } - - - public IEnumerable TreeNodes - { - get - { - if (_treeNodes.Any() == true) - { - return _treeNodes; - } - - RefreshTreeNodes(); - - return _treeNodes; - } - } - - public string GetItemIconCss(ScriptTreeNode viewModel) - { - if (viewModel.ItemType == TreeItemType.Folder) - { - return "oi oi-folder text-warning"; - } - return "oi oi-script text-success"; - } - - public async Task RefreshScripts() - { - _treeNodes.Clear(); - - _allScripts = await DataService.GetSavedScriptsWithoutContent(User.Id, User.OrganizationID); - } - - protected override async Task OnInitializedAsync() - { - await base.OnInitializedAsync(); - await RefreshScripts(); - } - - - private void CreateTreeNode(SavedScript script) - { - var root = _treeNodes; - ScriptTreeNode? targetParent = null; - - if (!string.IsNullOrWhiteSpace(script.FolderPath)) - { - var paths = script.FolderPath.Split("/", StringSplitOptions.RemoveEmptyEntries); - for (var i = 0; i < paths.Length; i++) - { - var existingParent = root.Find(x => x.Name == paths[i]); - - if (existingParent is null) - { - var newItem = new ScriptTreeNode() - { - Name = paths[i], - ItemType = TreeItemType.Folder, - ParentNode = existingParent - }; - root.Add(newItem); - root = newItem.ChildItems; - targetParent = newItem; - } - else - { - root = existingParent.ChildItems; - targetParent = existingParent; - } - } - } - - var scriptNode = new ScriptTreeNode() - { - Name = script.Name, - Script = script, - ItemType = TreeItemType.Item, - ParentNode = targetParent - }; - - root.Add(scriptNode); - } - - private void RefreshTreeNodes() - { - _treeNodes.Clear(); - - foreach (var script in _allScripts) - { - var showScript = ShowOnlyMyScripts ? - script.CreatorId == User.Id : - script.CreatorId == User.Id || script.IsPublic; - - if (!showScript) - { - continue; - } - - CreateTreeNode(script); - } - - _treeNodes.Sort((a, b) => - { - if (a.ItemType != b.ItemType) - { - return Comparer.Default.Compare(a.ItemType, b.ItemType); - } - - return Comparer.Default.Compare(a.Name, b.Name); - }); - } -} diff --git a/Server/Pages/ScriptsPage.razor.cs b/Server/Pages/ScriptsPage.razor.cs new file mode 100644 index 000000000..68e3aca7b --- /dev/null +++ b/Server/Pages/ScriptsPage.razor.cs @@ -0,0 +1,148 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Components; +using Remotely.Server.Components; +using Remotely.Server.Components.Scripts; +using Remotely.Server.Components.TreeView; +using Remotely.Server.Services; +using Remotely.Shared.Entities; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System; +using System.Collections; + +namespace Remotely.Server.Pages; + +[Authorize] +public partial class ScriptsPage : AuthComponentBase +{ + private readonly List _treeNodes = new(); + private IEnumerable _allScripts = Enumerable.Empty(); + private bool _showOnlyMyScripts = true; + + [Parameter] + public string? ActiveTab { get; set; } + + public bool ShowOnlyMyScripts + { + get => _showOnlyMyScripts; + set + { + _showOnlyMyScripts = value; + _treeNodes.Clear(); + } + } + + public IEnumerable TreeNodes + { + get + { + if (_treeNodes.Any() == true) + { + return _treeNodes; + } + + RefreshTreeNodes(); + + return _treeNodes; + } + } + + [Inject] + private IDataService DataService { get; init; } = null!; + + public string GetItemIconCss(ScriptTreeNode viewModel) + { + if (viewModel.ItemType == TreeItemType.Folder) + { + return "oi oi-folder text-warning"; + } + return "oi oi-script text-success"; + } + + public async Task RefreshScripts() + { + _treeNodes.Clear(); + + _allScripts = await DataService.GetSavedScriptsWithoutContent(User.Id, User.OrganizationID); + } + + protected override async Task OnInitializedAsync() + { + await base.OnInitializedAsync(); + await RefreshScripts(); + } + + + private void CreateTreeNode(SavedScript script) + { + var root = _treeNodes; + ScriptTreeNode? targetParent = null; + + if (!string.IsNullOrWhiteSpace(script.FolderPath)) + { + var paths = script.FolderPath.Split("/", StringSplitOptions.RemoveEmptyEntries); + for (var i = 0; i < paths.Length; i++) + { + var existingParent = root.Find(x => x.Name == paths[i]); + + if (existingParent is null) + { + var newItem = new ScriptTreeNode() + { + Name = paths[i], + ItemType = TreeItemType.Folder, + ParentNode = existingParent + }; + root.Add(newItem); + root = newItem.ChildItems; + targetParent = newItem; + } + else + { + root = existingParent.ChildItems; + targetParent = existingParent; + } + } + } + + var scriptNode = new ScriptTreeNode() + { + Name = script.Name, + Script = script, + ItemType = TreeItemType.Item, + ParentNode = targetParent + }; + + root.Add(scriptNode); + } + + private void RefreshTreeNodes() + { + _treeNodes.Clear(); + + foreach (var script in _allScripts) + { + var showScript = ShowOnlyMyScripts ? + script.CreatorId == User.Id : + script.CreatorId == User.Id || script.IsPublic; + + if (!showScript) + { + continue; + } + + CreateTreeNode(script); + } + + _treeNodes.Sort((a, b) => + { + if (a.ItemType != b.ItemType) + { + return Comparer.Default.Compare(a.ItemType, b.ItemType); + } + + return Comparer.Default.Compare(a.Name, b.Name); + }); + } +} diff --git a/Server/Program.cs b/Server/Program.cs index b5de741d2..67ba3b4b9 100644 --- a/Server/Program.cs +++ b/Server/Program.cs @@ -1,13 +1,10 @@ -using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Authorization; using Microsoft.AspNetCore.Components.Server.Circuits; -using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.HttpOverrides; using Microsoft.AspNetCore.Identity; -using Microsoft.AspNetCore.Identity.UI; using Microsoft.AspNetCore.Identity.UI.Services; using Microsoft.AspNetCore.StaticFiles; using Microsoft.EntityFrameworkCore; @@ -26,14 +23,10 @@ using System.IO; using System.Linq; using System.Net; -using Remotely.Shared.Utilities; using Immense.RemoteControl.Server.Extensions; using Remotely.Server.Services.RcImplementations; -using Immense.RemoteControl.Server.Abstractions; -using Microsoft.Extensions.DependencyInjection.Extensions; using Remotely.Shared.Services; using System; -using Immense.RemoteControl.Server.Services; using Serilog; using Nihs.SimpleMessenger; using Microsoft.AspNetCore.RateLimiting; @@ -63,37 +56,15 @@ if (dbProvider == "sqlite") { - services.AddDbContext(options => - { - options.UseSqlite(configuration.GetConnectionString("SQLite")); - }); + services.AddDbContext(); } else if (dbProvider == "sqlserver") { - services.AddDbContext(options => - { - options.UseSqlServer(configuration.GetConnectionString("SQLServer")); - }); + services.AddDbContext(); } else if (dbProvider == "postgresql") { - services.AddDbContext(options => - { - // Password should be set in User Secrets in dev environment. - // See https://docs.microsoft.com/en-us/aspnet/core/security/app-secrets?view=aspnetcore-3.1 - if (!string.IsNullOrWhiteSpace(configuration.GetValue("PostgresPassword"))) - { - var connectionBuilder = new NpgsqlConnectionStringBuilder(configuration.GetConnectionString("PostgreSQL")) - { - Password = configuration["PostgresPassword"] - }; - options.UseNpgsql(connectionBuilder.ConnectionString); - } - else - { - options.UseNpgsql(configuration.GetConnectionString("PostgreSQL")); - } - }); + services.AddDbContext(); } services.AddIdentity(options => diff --git a/Server/Server.csproj b/Server/Server.csproj index ec05af72e..baf9eee8c 100644 --- a/Server/Server.csproj +++ b/Server/Server.csproj @@ -17,21 +17,21 @@ - - - - - - - - + + + + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/Shared/Shared.csproj b/Shared/Shared.csproj index 7d0f55925..191838c32 100644 --- a/Shared/Shared.csproj +++ b/Shared/Shared.csproj @@ -10,7 +10,7 @@ - + diff --git a/Tests/Server.Tests/Server.Tests.csproj b/Tests/Server.Tests/Server.Tests.csproj index dfae76d61..4751992fe 100644 --- a/Tests/Server.Tests/Server.Tests.csproj +++ b/Tests/Server.Tests/Server.Tests.csproj @@ -13,12 +13,12 @@ - - - + + + - - + + diff --git a/Tests/Shared.Tests/Shared.Tests.csproj b/Tests/Shared.Tests/Shared.Tests.csproj index 3ede2fbf1..1bb091312 100644 --- a/Tests/Shared.Tests/Shared.Tests.csproj +++ b/Tests/Shared.Tests/Shared.Tests.csproj @@ -13,9 +13,9 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/submodules/Immense.RemoteControl b/submodules/Immense.RemoteControl index d571422a0..7a548c24c 160000 --- a/submodules/Immense.RemoteControl +++ b/submodules/Immense.RemoteControl @@ -1 +1 @@ -Subproject commit d571422a0e282fd009769bf14c7f6adf8867b0d8 +Subproject commit 7a548c24cc95c0d1c002672873516a59ecbde2c6 From 240bcc2f7584cfd513f943a4ef71593f85433417 Mon Sep 17 00:00:00 2001 From: Jared Goodwin Date: Mon, 31 Jul 2023 11:57:03 -0700 Subject: [PATCH 2/3] Import and override DeletePersonalData page. Use DataService instead of built-in UserManager to allow cascade deletes. --- .../Account/Manage/DeletePersonalData.cshtml | 33 ++++++ .../Manage/DeletePersonalData.cshtml.cs | 107 ++++++++++++++++++ 2 files changed, 140 insertions(+) create mode 100644 Server/Areas/Identity/Pages/Account/Manage/DeletePersonalData.cshtml create mode 100644 Server/Areas/Identity/Pages/Account/Manage/DeletePersonalData.cshtml.cs diff --git a/Server/Areas/Identity/Pages/Account/Manage/DeletePersonalData.cshtml b/Server/Areas/Identity/Pages/Account/Manage/DeletePersonalData.cshtml new file mode 100644 index 000000000..cecdd1d23 --- /dev/null +++ b/Server/Areas/Identity/Pages/Account/Manage/DeletePersonalData.cshtml @@ -0,0 +1,33 @@ +@page +@model DeletePersonalDataModel +@{ + ViewData["Title"] = "Delete Personal Data"; + ViewData["ActivePage"] = ManageNavPages.PersonalData; +} + +

@ViewData["Title"]

+ + + +
+
+ + @if (Model.RequirePassword) + { +
+ + + +
+ } + +
+
+ +@section Scripts { + +} diff --git a/Server/Areas/Identity/Pages/Account/Manage/DeletePersonalData.cshtml.cs b/Server/Areas/Identity/Pages/Account/Manage/DeletePersonalData.cshtml.cs new file mode 100644 index 000000000..20eba0685 --- /dev/null +++ b/Server/Areas/Identity/Pages/Account/Manage/DeletePersonalData.cshtml.cs @@ -0,0 +1,107 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +#nullable disable + +using System; +using System.ComponentModel.DataAnnotations; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.Extensions.Logging; +using Remotely.Server.Services; +using Remotely.Shared.Entities; + +namespace Remotely.Server.Areas.Identity.Pages.Account.Manage; + +public class DeletePersonalDataModel : PageModel +{ + private readonly UserManager _userManager; + private readonly SignInManager _signInManager; + private readonly IDataService _dataService; + private readonly ILogger _logger; + + public DeletePersonalDataModel( + UserManager userManager, + SignInManager signInManager, + IDataService dataService, + ILogger logger) + { + _userManager = userManager; + _signInManager = signInManager; + _dataService = dataService; + _logger = logger; + } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [BindProperty] + public InputModel Input { get; set; } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public class InputModel + { + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [Required] + [DataType(DataType.Password)] + public string Password { get; set; } + } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public bool RequirePassword { get; set; } + + public async Task OnGet() + { + var user = await _userManager.GetUserAsync(User); + if (user == null) + { + return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); + } + + RequirePassword = await _userManager.HasPasswordAsync(user); + return Page(); + } + + public async Task OnPostAsync() + { + var user = await _userManager.GetUserAsync(User); + if (user == null) + { + return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); + } + + RequirePassword = await _userManager.HasPasswordAsync(user); + if (RequirePassword) + { + if (!await _userManager.CheckPasswordAsync(user, Input.Password)) + { + ModelState.AddModelError(string.Empty, "Incorrect password."); + return Page(); + } + } + + var userId = user.Id; + var deleteResult = await _dataService.DeleteUser(user.OrganizationID, userId); + if (!deleteResult.IsSuccess) + { + throw new InvalidOperationException($"Unexpected error occurred deleting user."); + } + + await _signInManager.SignOutAsync(); + + _logger.LogInformation("User with ID '{UserId}' deleted themselves.", userId); + + return Redirect("~/"); + } +} From a86f6cab27d80cb1627181eb808f5e9bdb409622 Mon Sep 17 00:00:00 2001 From: Jared Goodwin Date: Mon, 31 Jul 2023 14:04:23 -0700 Subject: [PATCH 3/3] Update Immense.RemoteControl --- submodules/Immense.RemoteControl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/Immense.RemoteControl b/submodules/Immense.RemoteControl index 7a548c24c..d571422a0 160000 --- a/submodules/Immense.RemoteControl +++ b/submodules/Immense.RemoteControl @@ -1 +1 @@ -Subproject commit 7a548c24cc95c0d1c002672873516a59ecbde2c6 +Subproject commit d571422a0e282fd009769bf14c7f6adf8867b0d8