From 1c33691ee6df2ada65563f49ac418e342335b431 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Tue, 14 Aug 2018 15:33:37 -0400 Subject: [PATCH] Test and fix configuration --- .../Components/StaticFiles/Configuration.cs | 88 +++++++++++++++---- .../Controllers/ConfigurationController.cs | 22 +++-- .../IO/ISynchronousIOManager.cs | 7 ++ .../IO/SynchronousIOManager.cs | 51 +++++++---- 4 files changed, 126 insertions(+), 42 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs b/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs index 9a874b912f..2c047d57f9 100644 --- a/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs +++ b/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs @@ -137,9 +137,14 @@ namespace Tgstation.Server.Host.Components.StaticFiles string ValidateConfigRelativePath(string configurationRelativePath) { - if (String.IsNullOrEmpty(configurationRelativePath)) + var nullOrEmptyCheck = String.IsNullOrEmpty(configurationRelativePath); + if (nullOrEmptyCheck) configurationRelativePath = "."; - return ioManager.ResolvePath(configurationRelativePath); + var resolved = ioManager.ResolvePath(configurationRelativePath); + var local = !nullOrEmptyCheck ? ioManager.ResolvePath(".") : null; + if (!nullOrEmptyCheck && resolved.Length < local.Length) //.. fuccbois + throw new InvalidOperationException("Attempted to access file outside of configuration manager!"); + return resolved; } /// @@ -152,17 +157,30 @@ namespace Tgstation.Server.Host.Components.StaticFiles void ListImpl() { - var enumerator = synchronousIOManager.GetDirectories(configurationRelativePath, cancellationToken); - result.AddRange(enumerator.Select(x => new ConfigurationFile + var enumerator = synchronousIOManager.GetDirectories(path, cancellationToken); + try { - IsDirectory = true, - Path = ioManager.ConcatPath(configurationRelativePath, x), - })); - enumerator = synchronousIOManager.GetFiles(configurationRelativePath, cancellationToken); + result.AddRange(enumerator.Select(x => new ConfigurationFile + { + IsDirectory = true, + Path = ioManager.ConcatPath(path, x), + })); + } + catch (UnauthorizedAccessException) + { + result = null; + return; + } + catch (DirectoryNotFoundException) + { + result = null; + return; + } + enumerator = synchronousIOManager.GetFiles(path, cancellationToken); result.AddRange(enumerator.Select(x => new ConfigurationFile { IsDirectory = false, - Path = ioManager.ConcatPath(configurationRelativePath, x), + Path = ioManager.ConcatPath(path, x), })); } @@ -201,15 +219,31 @@ namespace Tgstation.Server.Host.Components.StaticFiles Path = configurationRelativePath }; } - catch (FileNotFoundException) { } - catch (DirectoryNotFoundException) { } + catch (IOException e) + { + logger.LogWarning("IOException while reading {0}: {1}", path, e); + } catch (UnauthorizedAccessException) { + //this happens on windows, dunno about linux + bool isDirectory; + try + { + isDirectory = synchronousIOManager.IsDirectory(path); + } + catch + { + isDirectory = false; + } + result = new ConfigurationFile { - AccessDenied = true, Path = configurationRelativePath }; + if (!isDirectory) + result.AccessDenied = true; + else + result.IsDirectory = true; } } @@ -266,13 +300,15 @@ namespace Tgstation.Server.Host.Components.StaticFiles var success = synchronousIOManager.WriteFileChecked(path, data, previousHash, cancellationToken); if (!success) return; + string sha1String = null; if (data != null) + { postWriteHandler.HandleWrite(path); - string sha1String; #pragma warning disable CA5350 // Do not use insecure cryptographic algorithm SHA1. - using (var sha1 = new SHA1Managed()) + using (var sha1 = new SHA1Managed()) #pragma warning restore CA5350 // Do not use insecure cryptographic algorithm SHA1. - sha1String = String.Join("", sha1.ComputeHash(data).Select(b => b.ToString("x2", CultureInfo.InvariantCulture))); + sha1String = String.Join("", sha1.ComputeHash(data).Select(b => b.ToString("x2", CultureInfo.InvariantCulture))); + } result = new ConfigurationFile { Content = data, @@ -282,15 +318,31 @@ namespace Tgstation.Server.Host.Components.StaticFiles Path = configurationRelativePath }; } - catch (FileNotFoundException) { } - catch (DirectoryNotFoundException) { } + catch (IOException e) + { + logger.LogWarning("IOException while writing {0}: {1}", path, e); + } catch (UnauthorizedAccessException) { + //this happens on windows, dunno about linux + bool isDirectory; + try + { + isDirectory = synchronousIOManager.IsDirectory(path); + } + catch + { + isDirectory = false; + } + result = new ConfigurationFile { - AccessDenied = true, Path = configurationRelativePath }; + if (!isDirectory) + result.AccessDenied = true; + else + result.IsDirectory = true; } } diff --git a/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs b/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs index ceac94794d..faf1c595cc 100644 --- a/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs +++ b/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs @@ -69,20 +69,20 @@ namespace Tgstation.Server.Host.Controllers /// /// Get the contents of a file at a /// - /// The path of the file to get + /// The path of the file to get /// The for the operation /// A resulting in the for the operation - [HttpGet("/File/{path}")] + [HttpGet("File/{*filePath}")] [TgsAuthorize(ConfigurationRights.Read)] - public async Task File(string path, CancellationToken cancellationToken) + public async Task File(string filePath, CancellationToken cancellationToken) { if (ForbidDueToModeConflicts()) return Forbid(); try { - var result = await instanceManager.GetInstance(Instance).Configuration.Read(path, AuthenticationContext.SystemIdentity, cancellationToken).ConfigureAwait(false); - if (result == null || result.IsDirectory.Value) + var result = await instanceManager.GetInstance(Instance).Configuration.Read(filePath, AuthenticationContext.SystemIdentity, cancellationToken).ConfigureAwait(false); + if (result == null) return StatusCode((int)HttpStatusCode.Gone); return Json(result); @@ -96,19 +96,19 @@ namespace Tgstation.Server.Host.Controllers /// /// Get the contents of a directory at a /// - /// The path of the directory to get + /// The path of the directory to get /// The for the operation /// A resulting in the for the operation - [HttpGet("/List/{path}")] + [HttpGet("List/{*directoryPath}")] [TgsAuthorize(ConfigurationRights.List)] - public async Task Directory(string path, CancellationToken cancellationToken) + public async Task Directory(string directoryPath, CancellationToken cancellationToken) { if (ForbidDueToModeConflicts()) return Forbid(); try { - var result = await instanceManager.GetInstance(Instance).Configuration.ListDirectory(path, AuthenticationContext.SystemIdentity, cancellationToken).ConfigureAwait(false); + var result = await instanceManager.GetInstance(Instance).Configuration.ListDirectory(directoryPath, AuthenticationContext.SystemIdentity, cancellationToken).ConfigureAwait(false); if (result == null) return StatusCode((int)HttpStatusCode.Gone); @@ -123,5 +123,9 @@ namespace Tgstation.Server.Host.Controllers return Forbid(); } } + + /// + [TgsAuthorize(ConfigurationRights.List)] + public override Task List(CancellationToken cancellationToken) => Directory(null, cancellationToken); } } diff --git a/src/Tgstation.Server.Host/IO/ISynchronousIOManager.cs b/src/Tgstation.Server.Host/IO/ISynchronousIOManager.cs index b0d61e969b..617bac069f 100644 --- a/src/Tgstation.Server.Host/IO/ISynchronousIOManager.cs +++ b/src/Tgstation.Server.Host/IO/ISynchronousIOManager.cs @@ -40,5 +40,12 @@ namespace Tgstation.Server.Host.IO /// The for the operation /// on success, if the operation failed due to not matching the file's contents bool WriteFileChecked(string path, byte[] data, string previousSha1, CancellationToken cancellationToken); + + /// + /// Checks if a given is a directory + /// + /// The path to check + /// if is a directory, otherwise + bool IsDirectory(string path); } } diff --git a/src/Tgstation.Server.Host/IO/SynchronousIOManager.cs b/src/Tgstation.Server.Host/IO/SynchronousIOManager.cs index cc00821fdd..ec85b710d0 100644 --- a/src/Tgstation.Server.Host/IO/SynchronousIOManager.cs +++ b/src/Tgstation.Server.Host/IO/SynchronousIOManager.cs @@ -14,7 +14,6 @@ namespace Tgstation.Server.Host.IO /// public IEnumerable GetDirectories(string path, CancellationToken cancellationToken) { - cancellationToken.ThrowIfCancellationRequested(); foreach (var I in Directory.EnumerateDirectories(path)) { yield return I; @@ -25,7 +24,6 @@ namespace Tgstation.Server.Host.IO /// public IEnumerable GetFiles(string path, CancellationToken cancellationToken) { - cancellationToken.ThrowIfCancellationRequested(); foreach (var I in Directory.EnumerateFiles(path)) { yield return I; @@ -34,29 +32,54 @@ namespace Tgstation.Server.Host.IO } /// - public byte[] ReadFile(string path) => File.ReadAllBytes(path); + public bool IsDirectory(string path) + { + if (path == null) + throw new ArgumentNullException(nameof(path)); + return Directory.Exists(path); + } + + /// + public byte[] ReadFile(string path) + { + if (path == null) + throw new ArgumentNullException(nameof(path)); + return File.ReadAllBytes(path); + } /// public bool WriteFileChecked(string path, byte[] data, string previousSha1, CancellationToken cancellationToken) { + if (path == null) + throw new ArgumentNullException(nameof(path)); + cancellationToken.ThrowIfCancellationRequested(); + Directory.CreateDirectory(Path.GetDirectoryName(path)); cancellationToken.ThrowIfCancellationRequested(); using (var file = File.Open(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None)) { + cancellationToken.ThrowIfCancellationRequested(); + + //as nice as it would be to not have to arrayify the memory stream, we have to + //because, oddly enough sha1(memorystream) != sha1(memorystream.ToArray()) + // vOv + byte[] originalBytes; using (var readMs = new MemoryStream()) { - cancellationToken.ThrowIfCancellationRequested(); file.CopyTo(readMs); - if (readMs.Length != 0 && previousSha1 == null) - return false; //no sha1? no write - //suppressed due to only using for consistency checks + originalBytes = readMs.ToArray(); + } + if (originalBytes.Length != 0 && previousSha1 == null) + //no sha1? no write + return false; + + //suppressed due to only using for consistency checks #pragma warning disable CA5350 // Do not use insecure cryptographic algorithm SHA1. - using (var sha1 = new SHA1Managed()) + using (var sha1 = new SHA1Managed()) #pragma warning restore CA5350 // Do not use insecure cryptographic algorithm SHA1. - { - var sha1String = String.Join("", sha1.ComputeHash(readMs).Select(b => b.ToString("x2", CultureInfo.InvariantCulture))); - if (sha1String != previousSha1) - return false; - } + { + var sha1String = originalBytes.Length != 0 ? String.Join("", sha1.ComputeHash(originalBytes).Select(b => b.ToString("x2", CultureInfo.InvariantCulture))) : null; + if (sha1String != previousSha1) + return false; } cancellationToken.ThrowIfCancellationRequested(); @@ -66,8 +89,6 @@ namespace Tgstation.Server.Host.IO cancellationToken.ThrowIfCancellationRequested(); file.SetLength(data.Length); - - cancellationToken.ThrowIfCancellationRequested(); file.Write(data, 0, data.Length); } }