From cee2e76bdf9e7dcbc21b04cbbd109eadce7a401f Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sun, 22 Jul 2018 18:02:41 -0400 Subject: [PATCH] ConfigurationController --- .../Models/ConfigurationFile.cs | 34 +++++- .../Internal/ConfigurationFileMetadata.cs | 33 ----- .../Rights/InstanceManagerRights.cs | 2 +- .../Rights/InstanceUserRights.cs | 10 +- .../Components/IConfiguration.cs | 14 +-- .../Controllers/ConfigurationController.cs | 113 ++++++++++++++++++ 6 files changed, 151 insertions(+), 55 deletions(-) delete mode 100644 src/Tgstation.Server.Api/Models/Internal/ConfigurationFileMetadata.cs create mode 100644 src/Tgstation.Server.Host/Controllers/ConfigurationController.cs diff --git a/src/Tgstation.Server.Api/Models/ConfigurationFile.cs b/src/Tgstation.Server.Api/Models/ConfigurationFile.cs index 3b3ca37e59..a7e7aad737 100644 --- a/src/Tgstation.Server.Api/Models/ConfigurationFile.cs +++ b/src/Tgstation.Server.Api/Models/ConfigurationFile.cs @@ -1,16 +1,40 @@ using Tgstation.Server.Api.Models.Internal; +using Tgstation.Server.Api.Rights; namespace Tgstation.Server.Api.Models { /// - /// Represents a static game file. Create and delete actions uncerimonuously overwrite/delete files + /// Represents a game configuration file. Create and delete actions uncerimonuously overwrite/delete files /// -#pragma warning disable CA1724 // System.Configuration name conflict - public sealed class ConfigurationFile : ConfigurationFileMetadata -#pragma warning restore CA1724 // System.Configuration name conflict + [Model(RightsType.Configuration, CanCrud = true, CanList = true, RequiresInstance = true, ReadRight = ConfigurationRights.Read, WriteRight = ConfigurationRights.Write)] + public sealed class ConfigurationFile { /// - /// The content of the . Will be if is or during listing operations + /// The path to the file + /// + [Permissions(DenyWrite = true)] + public string Path { get; set; } + + /// + /// If read access to the file was denied + /// + [Permissions(DenyWrite = true)] + public bool? ReadDenied { get; set; } + + /// + /// If represents a directory. Will only be if is + /// + [Permissions(DenyWrite = true)] + public bool? IsDirectory { get; set; } + + /// + /// The MD5 hash of the file when last read by the user. Will be if is . If this doesn't match during update actions, the write will be denied with error code 409 + /// + [Permissions(DenyWrite = true)] + public string LastReadHash { get; set; } + + /// + /// The content of the . Will be if is or during listing operations /// public byte[] Content { get; set; } } diff --git a/src/Tgstation.Server.Api/Models/Internal/ConfigurationFileMetadata.cs b/src/Tgstation.Server.Api/Models/Internal/ConfigurationFileMetadata.cs deleted file mode 100644 index ea8f30c484..0000000000 --- a/src/Tgstation.Server.Api/Models/Internal/ConfigurationFileMetadata.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Tgstation.Server.Api.Rights; - -namespace Tgstation.Server.Api.Models.Internal -{ - /// - /// Metadata about a file - /// - [Model(RightsType.Configuration, CanCrud = true, CanList = true, RequiresInstance = true, ReadRight = ConfigurationRights.Read, WriteRight = ConfigurationRights.Write)] - public class ConfigurationFileMetadata - { - /// - /// The path to the file - /// - public string Path { get; set; } - - /// - /// If read access to the file was denied - /// - [Permissions(DenyWrite = true)] - public bool ReadDenied { get; set; } - - /// - /// If represents a directory. Will only be if is - /// - [Permissions(DenyWrite = true)] - public bool IsDirectory { get; set; } - - /// - /// The MD5 hash of the file when last read by the user. Will be if is . If this doesn't match during update actions, the write will be denied with error code 409 - /// - public string LastReadHash { get; set; } - } -} diff --git a/src/Tgstation.Server.Api/Rights/InstanceManagerRights.cs b/src/Tgstation.Server.Api/Rights/InstanceManagerRights.cs index fbec63f9d5..7d0a3c4ace 100644 --- a/src/Tgstation.Server.Api/Rights/InstanceManagerRights.cs +++ b/src/Tgstation.Server.Api/Rights/InstanceManagerRights.cs @@ -41,7 +41,7 @@ namespace Tgstation.Server.Api.Rights /// List = 64, /// - /// User can change + /// User can change /// SetConfiguration = 128, /// diff --git a/src/Tgstation.Server.Api/Rights/InstanceUserRights.cs b/src/Tgstation.Server.Api/Rights/InstanceUserRights.cs index 7c4e7586f8..25b85acfbd 100644 --- a/src/Tgstation.Server.Api/Rights/InstanceUserRights.cs +++ b/src/Tgstation.Server.Api/Rights/InstanceUserRights.cs @@ -19,14 +19,6 @@ namespace Tgstation.Server.Api.Rights /// /// Allow write access to for the /// - WriteUsers = 2, - /// - /// User can read configuration files if the instance allows it - /// - ReadConfiguration = 4, - /// - /// Users can write configuration files if the instance allows it - /// - EditConfiguration = 8, + WriteUsers = 2 } } diff --git a/src/Tgstation.Server.Host/Components/IConfiguration.cs b/src/Tgstation.Server.Host/Components/IConfiguration.cs index 4d0e88e7a9..7cf71657cd 100644 --- a/src/Tgstation.Server.Host/Components/IConfiguration.cs +++ b/src/Tgstation.Server.Host/Components/IConfiguration.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using Tgstation.Server.Api.Models.Internal; +using Tgstation.Server.Api.Models; using Tgstation.Server.Host.Security; namespace Tgstation.Server.Host.Components @@ -28,13 +28,13 @@ namespace Tgstation.Server.Host.Components Task SymlinkStaticFilesTo(string destination, CancellationToken cancellationToken); /// - /// Get for all items in a given + /// Get for all items in a given /// /// The relative path in the Configuration directory /// The for the operation. If , the operation will be performed as the user of the /// The for the operation - /// A resulting in the for the items in the directory - Task> ListDirectory(string configurationRelativePath, ISystemIdentity systemIdentity, CancellationToken cancellationToken); + /// A resulting in the s for the items in the directory. and will both be + Task> ListDirectory(string configurationRelativePath, ISystemIdentity systemIdentity, CancellationToken cancellationToken); /// /// Reads a given @@ -43,7 +43,7 @@ namespace Tgstation.Server.Host.Components /// The for the operation. If , the operation will be performed as the user of the /// The for the operation /// A resulting in the of the file - Task Read(string configurationRelativePath, ISystemIdentity systemIdentity, CancellationToken cancellationToken); + Task Read(string configurationRelativePath, ISystemIdentity systemIdentity, CancellationToken cancellationToken); /// /// Writes to a given @@ -52,7 +52,7 @@ namespace Tgstation.Server.Host.Components /// The for the operation. If , the operation will be performed as the user of the /// The data to write. If , the file is deleted /// The for the operation. Usage may result in partial writes - /// A resulting in if the operation succeeded, if it failed due to permission errors - Task Write(string configurationRelativePath, ISystemIdentity systemIdentity, byte[] data, CancellationToken cancellationToken); + /// A resulting in the value for + Task Write(string configurationRelativePath, ISystemIdentity systemIdentity, byte[] data, CancellationToken cancellationToken); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs b/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs new file mode 100644 index 0000000000..a587dddf54 --- /dev/null +++ b/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs @@ -0,0 +1,113 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using System; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Api.Models; +using Tgstation.Server.Api.Rights; +using Tgstation.Server.Host.Components; +using Tgstation.Server.Host.Models; +using Tgstation.Server.Host.Security; + +namespace Tgstation.Server.Host.Controllers +{ + /// + /// The for s + /// + [Route("/Configuration")] + public sealed class ConfigurationController : ModelController + { + /// + /// The for the + /// + readonly IInstanceManager instanceManager; + + /// + /// The for the + /// + readonly ILogger logger; + + /// + /// Construct a + /// + /// The for the + /// The for the + /// The value of + /// The value of + public ConfigurationController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IInstanceManager instanceManager, ILogger logger) : base(databaseContext, authenticationContextFactory) + { + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); + this.instanceManager = instanceManager ?? throw new ArgumentNullException(nameof(instanceManager)); + } + + /// + [TgsAuthorize(ConfigurationRights.Write)] + public override async Task Update([FromBody] ConfigurationFile model, CancellationToken cancellationToken) + { + if (Instance.ConfigurationType == ConfigurationType.Disallowed) + return Forbid(); + + var config = instanceManager.GetInstance(Instance).Configuration; + try + { + var originalFile = await config.Read(model.Path, AuthenticationContext.SystemIdentity, cancellationToken).ConfigureAwait(false); + + if (model.LastReadHash != originalFile.LastReadHash) + return Conflict(); + + originalFile.LastReadHash = await config.Write(model.Path, AuthenticationContext.SystemIdentity, model.Content, cancellationToken).ConfigureAwait(false); + originalFile.Content = null; + + return Json(originalFile); + } + catch (InvalidOperationException) + { + return BadRequest(new { message = "Attempted to delete required folder!" }); + } + catch (UnauthorizedAccessException) + { + return Forbid(); + } + } + + /// + /// Get the contents of a file at a + /// + /// The path of the file to get + /// The for the operation + /// A resulting in the for the operation + [HttpGet("/File/{path}")] + [TgsAuthorize(ConfigurationRights.Read)] + public async Task File(string path, CancellationToken cancellationToken) + { + if (Instance.ConfigurationType == ConfigurationType.Disallowed) + return Forbid(); + + var result = await instanceManager.GetInstance(Instance).Configuration.Read(path, AuthenticationContext.SystemIdentity, cancellationToken).ConfigureAwait(false); + if (result == null || result.IsDirectory.Value) + return NotFound(); + + return Json(result); + } + + /// + /// Get the contents of a directory at a + /// + /// The path of the directory to get + /// The for the operation + /// A resulting in the for the operation + [HttpGet("/Directory/{path}")] + [TgsAuthorize(ConfigurationRights.List)] + public async Task Directory(string path, CancellationToken cancellationToken) + { + if (Instance.ConfigurationType == ConfigurationType.Disallowed) + return Forbid(); + + var result = await instanceManager.GetInstance(Instance).Configuration.ListDirectory(path, AuthenticationContext.SystemIdentity, cancellationToken).ConfigureAwait(false); + if (result == null) + return NotFound(); + + return Json(result); + } + } +}