Harden legacy file paths and Twilio webhook validation
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Moq;
|
||||
using PowderCoating.Application.Services;
|
||||
|
||||
namespace PowderCoating.UnitTests;
|
||||
|
||||
public class FileServiceTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task DeleteFileAsync_ReturnsError_WhenPathEscapesUploadsRoot()
|
||||
{
|
||||
using var harness = new FileServiceHarness();
|
||||
|
||||
var result = await harness.Service.DeleteFileAsync("uploads/../secrets.txt");
|
||||
|
||||
Assert.False(result.Success);
|
||||
Assert.Equal("Invalid file path.", result.ErrorMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetFileAsync_ReturnsError_WhenPathEscapesUploadsRoot()
|
||||
{
|
||||
using var harness = new FileServiceHarness();
|
||||
|
||||
var result = await harness.Service.GetFileAsync("uploads/../../appsettings.json");
|
||||
|
||||
Assert.False(result.Success);
|
||||
Assert.Equal("Invalid file path.", result.ErrorMessage);
|
||||
Assert.Empty(result.FileContent);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SaveFileAsync_ReturnsError_WhenSubfolderEscapesUploadsRoot()
|
||||
{
|
||||
using var harness = new FileServiceHarness();
|
||||
|
||||
var result = await harness.Service.SaveFileAsync(
|
||||
CreateFormFile("manual.pdf"),
|
||||
"../outside",
|
||||
new[] { ".pdf" },
|
||||
1024 * 1024);
|
||||
|
||||
Assert.False(result.Success);
|
||||
Assert.Equal("Invalid upload subfolder.", result.ErrorMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetFileAsync_ReturnsFile_WhenPathIsUnderUploadsRoot()
|
||||
{
|
||||
using var harness = new FileServiceHarness();
|
||||
var uploadsPath = Path.Combine(harness.WebRootPath, "uploads", "equipment-manuals");
|
||||
Directory.CreateDirectory(uploadsPath);
|
||||
|
||||
var fullPath = Path.Combine(uploadsPath, "manual.pdf");
|
||||
await File.WriteAllBytesAsync(fullPath, [1, 2, 3]);
|
||||
|
||||
var result = await harness.Service.GetFileAsync("uploads/equipment-manuals/manual.pdf");
|
||||
|
||||
Assert.True(result.Success);
|
||||
Assert.Equal("application/pdf", result.ContentType);
|
||||
Assert.Equal(new byte[] { 1, 2, 3 }, result.FileContent);
|
||||
}
|
||||
|
||||
private static IFormFile CreateFormFile(string fileName)
|
||||
{
|
||||
var bytes = new byte[] { 1, 2, 3, 4 };
|
||||
var stream = new MemoryStream(bytes);
|
||||
return new FormFile(stream, 0, bytes.Length, "file", fileName);
|
||||
}
|
||||
|
||||
private sealed class FileServiceHarness : IDisposable
|
||||
{
|
||||
public FileServiceHarness()
|
||||
{
|
||||
WebRootPath = Path.Combine(Path.GetTempPath(), "powdercoating-fileservice-tests", Guid.NewGuid().ToString("N"));
|
||||
Directory.CreateDirectory(WebRootPath);
|
||||
|
||||
var environment = Mock.Of<IWebHostEnvironment>(x => x.WebRootPath == WebRootPath);
|
||||
Service = new FileService(environment, Mock.Of<ILogger<FileService>>());
|
||||
}
|
||||
|
||||
public string WebRootPath { get; }
|
||||
public FileService Service { get; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Directory.Exists(WebRootPath))
|
||||
{
|
||||
Directory.Delete(WebRootPath, recursive: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user