From bc690e8bebe6e643d8d2d4c8917ebc4554c7f790 Mon Sep 17 00:00:00 2001 From: Jared Goodwin Date: Wed, 14 Jun 2023 15:06:13 -0700 Subject: [PATCH] Create authorization policies for OrganizationAdmin and ServerAdmin. Use these within the routing system via Authorize attribute instead of directly checking on each page. --- Server/App.razor | 8 +- Server/Auth/OrganizationAdminRequirement.cs | 8 + .../OrganizationAdminRequirementHandler.cs | 34 + Server/Auth/PolicyNames.cs | 8 + Server/Auth/ServerAdminRequirement.cs | 7 + Server/Auth/ServerAdminRequirementHandler.cs | 36 + Server/Auth/TwoFactorRequiredRequirement.cs | 2 +- Server/Components/LoaderHarness.razor | 9 +- Server/Pages/ApiKeys.razor | 2 +- Server/Pages/Branding.razor | 112 ++- Server/Pages/ManageOrganization.razor | 2 +- Server/Pages/ScriptsPage.razor | 1 + Server/Pages/ServerConfig.razor | 744 +++++++++--------- Server/Pages/ServerLogs.razor | 124 +-- Server/Program.cs | 20 +- Server/Server.csproj | 1 + Server/Shared/MainLayout.razor | 2 +- Server/_Imports.razor | 3 +- 18 files changed, 612 insertions(+), 511 deletions(-) create mode 100644 Server/Auth/OrganizationAdminRequirement.cs create mode 100644 Server/Auth/OrganizationAdminRequirementHandler.cs create mode 100644 Server/Auth/PolicyNames.cs create mode 100644 Server/Auth/ServerAdminRequirement.cs create mode 100644 Server/Auth/ServerAdminRequirementHandler.cs diff --git a/Server/App.razor b/Server/App.razor index ff99df89..521401ca 100644 --- a/Server/App.razor +++ b/Server/App.razor @@ -1,7 +1,13 @@ - + + +
+

You are not authorized to access this resource.

+
+
+
diff --git a/Server/Auth/OrganizationAdminRequirement.cs b/Server/Auth/OrganizationAdminRequirement.cs new file mode 100644 index 00000000..4bb6a050 --- /dev/null +++ b/Server/Auth/OrganizationAdminRequirement.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Authorization; + +namespace Remotely.Server.Auth; + +public class OrganizationAdminRequirement : IAuthorizationRequirement +{ + +} diff --git a/Server/Auth/OrganizationAdminRequirementHandler.cs b/Server/Auth/OrganizationAdminRequirementHandler.cs new file mode 100644 index 00000000..540e6d91 --- /dev/null +++ b/Server/Auth/OrganizationAdminRequirementHandler.cs @@ -0,0 +1,34 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Identity; +using Remotely.Shared.Models; +using System.Threading.Tasks; + +namespace Remotely.Server.Auth; + +public class OrganizationAdminRequirementHandler : AuthorizationHandler +{ + private readonly UserManager _userManager; + + public OrganizationAdminRequirementHandler(UserManager userManager) + { + _userManager = userManager; + } + + protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, OrganizationAdminRequirement requirement) + { + if (context.User.Identity?.IsAuthenticated != true) + { + context.Fail(); + return; + } + + var user = await _userManager.GetUserAsync(context.User); + if (user?.IsAdministrator != true) + { + context.Fail(); + return; + } + + context.Succeed(requirement); + } +} diff --git a/Server/Auth/PolicyNames.cs b/Server/Auth/PolicyNames.cs new file mode 100644 index 00000000..c65178c7 --- /dev/null +++ b/Server/Auth/PolicyNames.cs @@ -0,0 +1,8 @@ +namespace Remotely.Server.Auth; + +public static class PolicyNames +{ + public const string TwoFactorRequired = nameof(TwoFactorRequired); + public const string OrganizationAdminRequired = nameof(OrganizationAdminRequired); + public const string ServerAdminRequired = nameof(ServerAdminRequired); +} diff --git a/Server/Auth/ServerAdminRequirement.cs b/Server/Auth/ServerAdminRequirement.cs new file mode 100644 index 00000000..4a3fd895 --- /dev/null +++ b/Server/Auth/ServerAdminRequirement.cs @@ -0,0 +1,7 @@ +using Microsoft.AspNetCore.Authorization; + +namespace Remotely.Server.Auth; + +public class ServerAdminRequirement : IAuthorizationRequirement +{ +} diff --git a/Server/Auth/ServerAdminRequirementHandler.cs b/Server/Auth/ServerAdminRequirementHandler.cs new file mode 100644 index 00000000..e7a9f203 --- /dev/null +++ b/Server/Auth/ServerAdminRequirementHandler.cs @@ -0,0 +1,36 @@ +#nullable enable + +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Identity; +using Remotely.Shared.Models; +using System.Threading.Tasks; + +namespace Remotely.Server.Auth; + +public class ServerAdminRequirementHandler : AuthorizationHandler +{ + private readonly UserManager _userManager; + + public ServerAdminRequirementHandler(UserManager userManager) + { + _userManager = userManager; + } + + protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, ServerAdminRequirement requirement) + { + if (context.User.Identity?.IsAuthenticated != true) + { + context.Fail(); + return; + } + + var user = await _userManager.GetUserAsync(context.User); + if (user?.IsServerAdmin != true) + { + context.Fail(); + return; + } + + context.Succeed(requirement); + } +} diff --git a/Server/Auth/TwoFactorRequiredRequirement.cs b/Server/Auth/TwoFactorRequiredRequirement.cs index 9b6bca7c..d518e9b8 100644 --- a/Server/Auth/TwoFactorRequiredRequirement.cs +++ b/Server/Auth/TwoFactorRequiredRequirement.cs @@ -8,6 +8,6 @@ namespace Remotely.Server.Auth { public class TwoFactorRequiredRequirement : IAuthorizationRequirement { - public const string PolicyName = "TwoFactorRequired"; + } } diff --git a/Server/Components/LoaderHarness.razor b/Server/Components/LoaderHarness.razor index efdd347d..66bd1e83 100644 --- a/Server/Components/LoaderHarness.razor +++ b/Server/Components/LoaderHarness.razor @@ -11,13 +11,10 @@ private bool _loaderShown; private string _statusMessage = string.Empty; - protected override Task OnAfterRenderAsync(bool firstRender) + protected override Task OnInitializedAsync() { - if (firstRender) - { - Messenger.Register(this, HandleShowLoaderMessage); - } - return base.OnAfterRenderAsync(firstRender); + Messenger.Register(this, HandleShowLoaderMessage); + return base.OnInitializedAsync(); } private async Task HandleShowLoaderMessage(ShowLoaderMessage message) diff --git a/Server/Pages/ApiKeys.razor b/Server/Pages/ApiKeys.razor index a3d1c075..7b5dd16f 100644 --- a/Server/Pages/ApiKeys.razor +++ b/Server/Pages/ApiKeys.razor @@ -1,5 +1,5 @@ @page "/api-keys" -@attribute [Authorize] +@attribute [Authorize(Policy = PolicyNames.OrganizationAdminRequired)] @inherits AuthComponentBase @inject IDataService DataService diff --git a/Server/Pages/Branding.razor b/Server/Pages/Branding.razor index 0a0175d2..ac747c9c 100644 --- a/Server/Pages/Branding.razor +++ b/Server/Pages/Branding.razor @@ -1,5 +1,6 @@ @page "/branding" @inherits AuthComponentBase +@attribute [Authorize(Policy = PolicyNames.OrganizationAdminRequired)] @inject IDataService DataService @inject IToastService ToastService @inject IJsInterop JsInterop @@ -8,70 +9,61 @@

Branding

+ -@if (User?.IsAdministrator != true) -{ -
Only organization administrators can view this page.
-} -else -{ - +
+
+
+ +
+ +
+
+
-
-
-
- -
- +
+
+ + + +
+ + +
-
+ + @if (!string.IsNullOrWhiteSpace(_base64Icon)) + { +
+ +
+ +
+ } + +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
- -
-
- - - -
- - - -
- - @if (!string.IsNullOrWhiteSpace(_base64Icon)) - { -
- -
- -
- } - -
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
-
-
-} - +
@code { private string _alertMessage; private string _base64Icon; diff --git a/Server/Pages/ManageOrganization.razor b/Server/Pages/ManageOrganization.razor index d0413f66..717f206f 100644 --- a/Server/Pages/ManageOrganization.razor +++ b/Server/Pages/ManageOrganization.razor @@ -1,5 +1,5 @@ @page "/manage-organization" -@attribute [Authorize] +@attribute [Authorize(Policy = PolicyNames.OrganizationAdminRequired)] @inherits AuthComponentBase

Manage Organization

diff --git a/Server/Pages/ScriptsPage.razor b/Server/Pages/ScriptsPage.razor index 885f5e4a..b9459bc7 100644 --- a/Server/Pages/ScriptsPage.razor +++ b/Server/Pages/ScriptsPage.razor @@ -1,4 +1,5 @@ @page "/scripts/{activeTab?}" +@attribute [Authorize] @inherits AuthComponentBase @using System.Collections @inject IDataService DataService diff --git a/Server/Pages/ServerConfig.razor b/Server/Pages/ServerConfig.razor index 3932c83b..417dadd6 100644 --- a/Server/Pages/ServerConfig.razor +++ b/Server/Pages/ServerConfig.razor @@ -1,395 +1,387 @@ @page "/server-config" -@attribute [Authorize] +@attribute [Authorize(Policy = PolicyNames.ServerAdminRequired)] @inherits AuthComponentBase -@if (User?.IsServerAdmin == true) -{ - + -

Server Info

+

Server Info

-
-
-
-
- -
- @ServiceSessionCache.GetAllDevices().Count -
- -
- -
+
+
+
+
+ +
+ @ServiceSessionCache.GetAllDevices().Count +
- - @(OutdatedDevices?.Count() ?? 0) - -
+
+ +
-
- -
- @TotalDevices -
-
- -
- @CircuitManager.Connections.Count -
+ + @(OutdatedDevices?.Count() ?? 0) + +
+ +
+ +
+ @TotalDevices +
+
+ +
+ @CircuitManager.Connections.Count
+
+ +
+
+

Server Admins

+ +
+ +
+ +
+ + Show my organization only +
+
+ + Show current admins only +
+ +
+
+ @foreach (var user in UserList) + { +
+ + @user.UserName +
+ } +
+
+
+
+ +
+
+ + + +

Application Settings

+ + -
-
-

Server Admins

- +
- -
- - Show my organization only -
-
- - Show current admins only -
- +
-
- @foreach (var user in UserList) - { -
- - @user.UserName -
- } -
+
-
+
+ +
+ + +
+ +
+
+ + +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ + + +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ This sometimes needs to be disabled for Let's Encrypt certificates. +
+
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+
+ +
+ +
+ +
+
+ +
+ + +
+ +
+
+ + + +
+ +
+
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +

Connection Strings

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

Application Settings

- - - - -
- -
- -
- -
-
- -
- - -
- -
-
- - -
- -
-
- -
- -
- -
-
- -
- -
- -
-
- -
- -
- -
-
- -
- -
- -
-
- -
- -
- -
-
- -
- -
- -
-
- - - -
- -
-
- -
- -
- -
-
- -
- -
- -
-
- -
- -
- -
-
- -
- -
- -
-
- -
- -
- -
-
- -
- -
- -
-
- -
- -
- -
-
- -
- -
- -
-
- -
- -
- -
-
- -
- -
- -
-
- -
- -
- -
-
- -
- -
- -
-
- -
- This sometimes needs to be disabled for Let's Encrypt certificates. -
-
- -
- -
-
- -
- -
- -
-
- -
- -
- -
-
- -
- -
- -
-
- -
-
- -
- -
- -
-
- -
- - -
- -
-
- - - -
- -
-
- -
- -
- -
- -
- -
- -
- -
- -

Connection Strings

- -
- -
- -
- -
- -
- -
- -
- -
- - -
- -
- -
- -
- - -
- -
-
-
-
- -} -else -{ -
Only organization administrators can view this page.
-} \ No newline at end of file +
\ No newline at end of file diff --git a/Server/Pages/ServerLogs.razor b/Server/Pages/ServerLogs.razor index 5f6156e8..f8f1c849 100644 --- a/Server/Pages/ServerLogs.razor +++ b/Server/Pages/ServerLogs.razor @@ -1,6 +1,6 @@ @page "/server-logs" @using System.Collections.ObjectModel; -@attribute [Authorize] +@attribute [Authorize(Policy = PolicyNames.ServerAdminRequired)] @inherits AuthComponentBase @inject IDataService DataService @@ -11,65 +11,57 @@

Server Logs

- -@if (User?.IsAdministrator == true) -{ -
-
- -
-
- -
-
- -
+
+
+
-
-
- Type: -
- -
-
- Filter: -
- -
-
- From: -
- -
-
- To: -
- -
+
+
+
+ +
+
+
+
+ Type: +
+ +
+
+ Filter: +
+ +
+
+ From: +
+ +
+
+ To: +
+ +
+
- -} -else -{ -
Only organization administrators can view this page.
-} + @code { private readonly string _messageDebounceKey = Guid.NewGuid().ToString(); @@ -98,14 +90,14 @@ else private LogLevel? LogLevel { get => _logLevel; - set + set { _logLevel = value; _ = RefreshLogs(); } } - private string MessageFilter + private string MessageFilter { get => _messageFilter; set @@ -113,7 +105,7 @@ else _messageFilter = value; Debouncer.Debounce( - TimeSpan.FromSeconds(1), + TimeSpan.FromSeconds(1), () => _ = RefreshLogs(), _messageDebounceKey); } @@ -136,10 +128,20 @@ else protected override async Task OnInitializedAsync() { - await RefreshLogs(); await base.OnInitializedAsync(); } + protected override async Task OnAfterRenderAsync(bool firstRender) + { + await base.OnAfterRenderAsync(firstRender); + if (firstRender) + { + await RefreshLogs(); + } + } + + + private async Task ClearAllLogs() { var result = await JsInterop.Confirm("Are you sure you want to delete all previous logs? Today's logs are retained."); diff --git a/Server/Program.cs b/Server/Program.cs index b40e5095..0b97c74f 100644 --- a/Server/Program.cs +++ b/Server/Program.cs @@ -100,12 +100,25 @@ services.AddIdentity(options => services.AddScoped(); +services.AddScoped(); +services.AddScoped(); + services.AddAuthorization(options => { - options.AddPolicy(TwoFactorRequiredRequirement.PolicyName, builder => + options.AddPolicy(PolicyNames.TwoFactorRequired, builder => { builder.Requirements.Add(new TwoFactorRequiredRequirement()); }); + + options.AddPolicy(PolicyNames.OrganizationAdminRequired, builder => + { + builder.Requirements.Add(new OrganizationAdminRequirement()); + }); + + options.AddPolicy(PolicyNames.ServerAdminRequired, builder => + { + builder.Requirements.Add(new ServerAdminRequirement()); + }); }); services.AddRazorPages(); @@ -337,13 +350,16 @@ void ConfigureSerilog(WebApplicationBuilder webAppBuilder) { loggerConfiguration .Enrich.FromLogContext() - .WriteTo.Console() + .Enrich.WithThreadId() + .WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} {Properties}{NewLine}{Exception}") .WriteTo.File($"{logPath}/Remotely_Server.log", rollingInterval: RollingInterval.Day, retainedFileTimeLimit: TimeSpan.FromDays(dataRetentionDays), + outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj} {Properties}{NewLine}{Exception}", shared: true); } + // https://github.com/serilog/serilog-aspnetcore#two-stage-initialization var loggerConfig = new LoggerConfiguration(); ApplySharedLoggerConfig(loggerConfig); Log.Logger = loggerConfig.CreateBootstrapLogger(); diff --git a/Server/Server.csproj b/Server/Server.csproj index df99d5f6..0a5cd866 100644 --- a/Server/Server.csproj +++ b/Server/Server.csproj @@ -34,6 +34,7 @@ + diff --git a/Server/Shared/MainLayout.razor b/Server/Shared/MainLayout.razor index 412c0663..5baabfab 100644 --- a/Server/Shared/MainLayout.razor +++ b/Server/Shared/MainLayout.razor @@ -2,7 +2,7 @@ @using Remotely.Server.Auth @inherits LayoutComponentBase - +