diff --git a/webapi/Services/FileSecurityService/Extensions/ServiceCollectionExtensions.cs b/webapi/Services/FileSecurityService/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..cfd97a8 --- /dev/null +++ b/webapi/Services/FileSecurityService/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,10 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace FileSecurityService.Extensions +{ + public static class ServiceCollectionExtensions { + public static void RegisterFileSecurityService(this IServiceCollection services) { + services.AddSingleton(); + } + } +} diff --git a/webapi/Services/FileSecurityService/FileCategory.cs b/webapi/Services/FileSecurityService/FileCategory.cs new file mode 100644 index 0000000..d255406 --- /dev/null +++ b/webapi/Services/FileSecurityService/FileCategory.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FileSecurityService { + + /// + /// + /// + public enum FileCategory { + /// + /// + /// + Document, + + /// + /// + /// + Image, + + /// + /// + /// + Video + } +} diff --git a/webapi/WeatherForecast/Services/Abstractions/FileServiceBase.cs b/webapi/Services/FileSecurityService/FileSecurityService.cs similarity index 58% rename from webapi/WeatherForecast/Services/Abstractions/FileServiceBase.cs rename to webapi/Services/FileSecurityService/FileSecurityService.cs index d1bb4ea..3267bf4 100644 --- a/webapi/WeatherForecast/Services/Abstractions/FileServiceBase.cs +++ b/webapi/Services/FileSecurityService/FileSecurityService.cs @@ -1,174 +1,138 @@ using DomainResults.Common; using ExtensionMethods; -namespace WeatherForecast.Services.Abstractions { +namespace FileSecurityService { /// /// /// - public enum FileCategory { + public interface IFileSecurityService { /// /// /// - Document, - - /// - /// - /// - Image, - - /// - /// - /// - Video - } - - /// - /// - /// - public class FileSignature { - - /// - /// - /// - public List Signatures { get; private set; } - - /// - /// - /// - public string ContentType { get; private set; } - - /// - /// - /// - public FileCategory FileCategory { get; private set; } - - /// - /// - /// - /// + /// + /// /// - /// - public FileSignature(List signatures, string contentType, FileCategory fileCategory) { - Signatures = signatures; - ContentType = contentType; - FileCategory = fileCategory; - } + /// + (FileCategory?, IDomainResult) CheckFileSignature(string fileName, byte[] bytes, string contentType); } - /// - /// - /// - public abstract class FileServiceBase { - + public class FileSecurityService : IFileSecurityService { /// /// https://gist.github.com/qti3e/6341245314bf3513abb080677cd1c93b /// - private readonly Dictionary _data = new() { - #region Documents - { - ".pdf", - new FileSignature(new List { + private readonly Dictionary _data; + + /// + /// + /// + public FileSecurityService() { + _data = new Dictionary() { + #region Documents + { + ".pdf", + new FileSignature(new List { "0,25504446" }, "application/pdf", FileCategory.Document) - }, - { - ".rtf", - new FileSignature(new List { + }, + { + ".rtf", + new FileSignature(new List { "0,7B5C72746631" }, "application/rtf", FileCategory.Document) - }, - { - ".doc", - new FileSignature(new List { + }, + { + ".doc", + new FileSignature(new List { "0,0D444F43", "0,CF11E0A1B11AE100", "0,D0CF11E0A1B11AE1", "0,DBA52D00", "512,ECA5C100" }, "application/msword", FileCategory.Document) - }, - { - ".xls", - new FileSignature(new List { + }, + { + ".xls", + new FileSignature(new List { "512,0908100000060500", "0,D0CF11E0A1B11AE1", "512,FDFFFFFF04", "512,FDFFFFFF20000000" }, "application/vnd.ms-excel", FileCategory.Document) - }, - { - ".ppt", - new FileSignature(new List { + }, + { + ".ppt", + new FileSignature(new List { "512,006E1EF0", "512,0F00E803", "512,A0461DF0", "0,D0CF11E0A1B11AE1", "512,FDFFFFFF04" }, "application/vnd.ms-powerpoint", FileCategory.Document) - }, - { - ".docx", - new FileSignature(new List { + }, + { + ".docx", + new FileSignature(new List { "0,504B030414000600" }, "application/vnd.openxmlformats-officedocument.wordprocessingml.document", FileCategory.Document) - }, - { - ".xlsx", - new FileSignature(new List { + }, + { + ".xlsx", + new FileSignature(new List { "0,504B030414000600" }, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", FileCategory.Document) - }, - { - ".pptx", - new FileSignature(new List { + }, + { + ".pptx", + new FileSignature(new List { "0,504B030414000600" }, "application/vnd.openxmlformats-officedocument.presentationml.presentation", FileCategory.Document) - }, - #endregion + }, + #endregion - #region Images - { - ".jpg", - new FileSignature(new List { + #region Images + { + ".jpg", + new FileSignature(new List { "0,FFD8" }, "image/jpeg", FileCategory.Image) - }, - { - ".jpeg", - new FileSignature(new List { + }, + { + ".jpeg", + new FileSignature(new List { "0,FFD8" }, "image/jpeg", FileCategory.Image) - }, - { - ".jpe", - new FileSignature(new List { + }, + { + ".jpe", + new FileSignature(new List { "0,FFD8" }, "image/jpeg", FileCategory.Image) - }, - { - ".jfif", - new FileSignature(new List { + }, + { + ".jfif", + new FileSignature(new List { "0,FFD8" }, "image/jpeg", FileCategory.Image) - }, + }, - { - ".png", - new FileSignature(new List { + { + ".png", + new FileSignature(new List { "0,89504E470D0A1A0A" }, "image/png", FileCategory.Image) - }, + }, - { - ".webp", - new FileSignature(new List { + { + ".webp", + new FileSignature(new List { "0,52494646" }, "image/webp", FileCategory.Image) - } - #endregion - }; - + } + #endregion + }; + + } /// /// Don't rely on or trust the FileName property without validation. @@ -177,7 +141,7 @@ namespace WeatherForecast.Services.Abstractions { /// /// /// - private protected (FileCategory?, IDomainResult) CheckFileSignature(string fileName, byte[] bytes, string contentType) { + public (FileCategory?, IDomainResult) CheckFileSignature(string fileName, byte[] bytes, string contentType) { var data = _data.SingleOrDefault(x => x.Key == Path.GetExtension(fileName).ToLower()).Value; @@ -208,10 +172,10 @@ namespace WeatherForecast.Services.Abstractions { x |= sample[i] ^ signBytes[i]; } - if (x == 0) return IDomainResult.Success(data.FileCategory); ; + if (x == 0) return IDomainResult.Success(data.FileCategory); } - return IDomainResult.Failed(); ; + return IDomainResult.Failed(); } } -} +} \ No newline at end of file diff --git a/webapi/Services/FileSecurityService/FileSecurityService.csproj b/webapi/Services/FileSecurityService/FileSecurityService.csproj new file mode 100644 index 0000000..c6ec36a --- /dev/null +++ b/webapi/Services/FileSecurityService/FileSecurityService.csproj @@ -0,0 +1,19 @@ + + + + net6.0 + enable + enable + + + + + + + + + + + + + diff --git a/webapi/Services/FileSecurityService/FileSignature.cs b/webapi/Services/FileSecurityService/FileSignature.cs new file mode 100644 index 0000000..e770e57 --- /dev/null +++ b/webapi/Services/FileSecurityService/FileSignature.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FileSecurityService { + + /// + /// + /// + internal class FileSignature { + /// + /// + /// + public List Signatures { get; private set; } + + /// + /// + /// + public string ContentType { get; private set; } + + /// + /// + /// + public FileCategory FileCategory { get; private set; } + + /// + /// + /// + /// + /// + /// + public FileSignature(List signatures, string contentType, FileCategory fileCategory) { + Signatures = signatures; + ContentType = contentType; + FileCategory = fileCategory; + } + } +} diff --git a/webapi/WeatherForecast.sln b/webapi/WeatherForecast.sln index d59ef98..eba69de 100644 --- a/webapi/WeatherForecast.sln +++ b/webapi/WeatherForecast.sln @@ -29,6 +29,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CoreTests", "Tests\Core\Cor EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageService", "Services\ImageService\ImageService.csproj", "{16552644-D7EE-4B4A-A725-79909A8103DE}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileSecurityService", "Services\FileSecurityService\FileSecurityService.csproj", "{AD515653-9145-4894-9017-0ABA5A5892F4}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -79,6 +81,10 @@ Global {16552644-D7EE-4B4A-A725-79909A8103DE}.Debug|Any CPU.Build.0 = Debug|Any CPU {16552644-D7EE-4B4A-A725-79909A8103DE}.Release|Any CPU.ActiveCfg = Release|Any CPU {16552644-D7EE-4B4A-A725-79909A8103DE}.Release|Any CPU.Build.0 = Release|Any CPU + {AD515653-9145-4894-9017-0ABA5A5892F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AD515653-9145-4894-9017-0ABA5A5892F4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AD515653-9145-4894-9017-0ABA5A5892F4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AD515653-9145-4894-9017-0ABA5A5892F4}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -89,6 +95,7 @@ Global {43315A1D-9E09-4398-84B9-A9D9D623AE5A} = {216302C2-64DE-4AE7-BC14-BDAC5B732472} {04CB9827-AA6D-4708-A26D-8420C842506D} = {216302C2-64DE-4AE7-BC14-BDAC5B732472} {16552644-D7EE-4B4A-A725-79909A8103DE} = {113EE574-E047-4727-AA36-841F845504D5} + {AD515653-9145-4894-9017-0ABA5A5892F4} = {113EE574-E047-4727-AA36-841F845504D5} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {E2805D02-2425-424C-921D-D97341B76F73} diff --git a/webapi/WeatherForecast/Services/FileService.cs b/webapi/WeatherForecast/Services/FileService.cs index 46f89a8..52f92c3 100644 --- a/webapi/WeatherForecast/Services/FileService.cs +++ b/webapi/WeatherForecast/Services/FileService.cs @@ -2,7 +2,7 @@ using DataProviders; using DataProviders.Buckets; using DomainResults.Common; -using WeatherForecast.Services.Abstractions; +using FileSecurityService; namespace WeatherForecast.Services { @@ -42,21 +42,25 @@ namespace WeatherForecast.Services { /// /// /// - public class FileService : FileServiceBase, IFileService { + public class FileService : IFileService { private readonly ILogger _logger; + private readonly IFileSecurityService _fileSecurityService; private readonly IImagesBucketDataProvider _imageBucketDataProvider; /// /// /// /// + /// /// public FileService( ILogger logger, + IFileSecurityService fileSecurityService, IImagesBucketDataProvider imageBucketDataProvider ) { _logger = logger; + _fileSecurityService = fileSecurityService; _imageBucketDataProvider = imageBucketDataProvider; } @@ -76,8 +80,7 @@ namespace WeatherForecast.Services { file.CopyTo(ms); var bytes = ms.ToArray(); - var (fileCategory, signatureResult) = CheckFileSignature(file.FileName, bytes, file.ContentType); - + var (fileCategory, signatureResult) = _fileSecurityService.CheckFileSignature(file.FileName, bytes, file.ContentType); if(!signatureResult.IsSuccess || fileCategory == null) return IDomainResult.Failed(); @@ -92,7 +95,7 @@ namespace WeatherForecast.Services { break; default: return IDomainResult.Failed(); - } + } if (!result.IsSuccess || fileId == null) return IDomainResult.Failed(); diff --git a/webapi/WeatherForecast/Services/FilesService.cs b/webapi/WeatherForecast/Services/FilesService.cs index 673cc9f..c790cfc 100644 --- a/webapi/WeatherForecast/Services/FilesService.cs +++ b/webapi/WeatherForecast/Services/FilesService.cs @@ -1,7 +1,7 @@ using DataProviders; using DataProviders.Buckets; using DomainResults.Common; -using WeatherForecast.Services.Abstractions; +using FileSecurityService; namespace WeatherForecast.Services { @@ -31,9 +31,10 @@ namespace WeatherForecast.Services { /// /// /// - public class FilesService : FileServiceBase, IFilesService { + public class FilesService : IFilesService { private readonly ILogger _logger; + private readonly IFileSecurityService _fileSecurityService; private readonly IImagesBucketDataProvider _imageBucketDataProvider; /// @@ -43,9 +44,11 @@ namespace WeatherForecast.Services { /// public FilesService( ILogger logger, + IFileSecurityService fileSecurityService, IImagesBucketDataProvider imageBucketDataProvider ) { _logger = logger; + _fileSecurityService = fileSecurityService; _imageBucketDataProvider = imageBucketDataProvider; } @@ -67,8 +70,15 @@ namespace WeatherForecast.Services { foreach (var formFile in files) { using var ms = new MemoryStream(); formFile.CopyTo(ms); + var bytes = ms.ToArray(); - newFiles.Add(new BucketFile(formFile.FileName, ms.ToArray(), formFile.ContentType)); + var (fileCategory, signatureResult) = _fileSecurityService.CheckFileSignature(formFile.FileName, bytes, formFile.ContentType); + if (!signatureResult.IsSuccess || fileCategory == null) + return IDomainResult.Failed?>(); + + var bucketFile = new BucketFile(formFile.FileName, bytes, formFile.ContentType); + + newFiles.Add(bucketFile); } var (list, result) = _imageBucketDataProvider.UploadMany(siteId, userId, newFiles); diff --git a/webapi/WeatherForecast/Startup.cs b/webapi/WeatherForecast/Startup.cs index 70d8b81..fd41914 100644 --- a/webapi/WeatherForecast/Startup.cs +++ b/webapi/WeatherForecast/Startup.cs @@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Authentication.JwtBearer; using WeatherForecast.Services; using DataProviders.Extensions; using System.Text.Json.Serialization; +using FileSecurityService.Extensions; namespace WeatherForecast { @@ -84,6 +85,7 @@ namespace WeatherForecast { services.AddScoped(); services.RegisterDataproviders(appSettings); + services.RegisterFileSecurityService(); #region Swagger services.ConfigureSwaggerGen(options => { diff --git a/webapi/WeatherForecast/WeatherForecast.csproj b/webapi/WeatherForecast/WeatherForecast.csproj index 3f4bd9c..21c30cf 100644 --- a/webapi/WeatherForecast/WeatherForecast.csproj +++ b/webapi/WeatherForecast/WeatherForecast.csproj @@ -28,6 +28,7 @@ +