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