(feat): file security service

This commit is contained in:
Maksym Sadovnychyy 2022-09-06 21:27:59 +02:00
parent d6fcf34bf6
commit 2c137947c6
10 changed files with 209 additions and 125 deletions

View File

@ -0,0 +1,10 @@
using Microsoft.Extensions.DependencyInjection;
namespace FileSecurityService.Extensions
{
public static class ServiceCollectionExtensions {
public static void RegisterFileSecurityService(this IServiceCollection services) {
services.AddSingleton<IFileSecurityService, FileSecurityService>();
}
}
}

View File

@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace FileSecurityService {
/// <summary>
///
/// </summary>
public enum FileCategory {
/// <summary>
///
/// </summary>
Document,
/// <summary>
///
/// </summary>
Image,
/// <summary>
///
/// </summary>
Video
}
}

View File

@ -1,174 +1,138 @@
using DomainResults.Common; using DomainResults.Common;
using ExtensionMethods; using ExtensionMethods;
namespace WeatherForecast.Services.Abstractions { namespace FileSecurityService {
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public enum FileCategory { public interface IFileSecurityService {
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
Document, /// <param name="fileName"></param>
/// <param name="bytes"></param>
/// <summary>
///
/// </summary>
Image,
/// <summary>
///
/// </summary>
Video
}
/// <summary>
///
/// </summary>
public class FileSignature {
/// <summary>
///
/// </summary>
public List<string> Signatures { get; private set; }
/// <summary>
///
/// </summary>
public string ContentType { get; private set; }
/// <summary>
///
/// </summary>
public FileCategory FileCategory { get; private set; }
/// <summary>
///
/// </summary>
/// <param name="signatures"></param>
/// <param name="contentType"></param> /// <param name="contentType"></param>
/// <param name="fileCategory"></param> /// <returns></returns>
public FileSignature(List<string> signatures, string contentType, FileCategory fileCategory) { (FileCategory?, IDomainResult) CheckFileSignature(string fileName, byte[] bytes, string contentType);
Signatures = signatures;
ContentType = contentType;
FileCategory = fileCategory;
}
} }
/// <summary> public class FileSecurityService : IFileSecurityService {
///
/// </summary>
public abstract class FileServiceBase {
/// <summary> /// <summary>
/// https://gist.github.com/qti3e/6341245314bf3513abb080677cd1c93b /// https://gist.github.com/qti3e/6341245314bf3513abb080677cd1c93b
/// </summary> /// </summary>
private readonly Dictionary<string, FileSignature> _data = new() { private readonly Dictionary<string, FileSignature> _data;
#region Documents
{ /// <summary>
".pdf", ///
new FileSignature(new List<string> { /// </summary>
public FileSecurityService() {
_data = new Dictionary<string, FileSignature>() {
#region Documents
{
".pdf",
new FileSignature(new List<string> {
"0,25504446" "0,25504446"
}, "application/pdf", FileCategory.Document) }, "application/pdf", FileCategory.Document)
}, },
{ {
".rtf", ".rtf",
new FileSignature(new List<string> { new FileSignature(new List<string> {
"0,7B5C72746631" "0,7B5C72746631"
}, "application/rtf", FileCategory.Document) }, "application/rtf", FileCategory.Document)
}, },
{ {
".doc", ".doc",
new FileSignature(new List<string> { new FileSignature(new List<string> {
"0,0D444F43", "0,0D444F43",
"0,CF11E0A1B11AE100", "0,CF11E0A1B11AE100",
"0,D0CF11E0A1B11AE1", "0,D0CF11E0A1B11AE1",
"0,DBA52D00", "0,DBA52D00",
"512,ECA5C100" "512,ECA5C100"
}, "application/msword", FileCategory.Document) }, "application/msword", FileCategory.Document)
}, },
{ {
".xls", ".xls",
new FileSignature(new List<string> { new FileSignature(new List<string> {
"512,0908100000060500", "512,0908100000060500",
"0,D0CF11E0A1B11AE1", "0,D0CF11E0A1B11AE1",
"512,FDFFFFFF04", "512,FDFFFFFF04",
"512,FDFFFFFF20000000" "512,FDFFFFFF20000000"
}, "application/vnd.ms-excel", FileCategory.Document) }, "application/vnd.ms-excel", FileCategory.Document)
}, },
{ {
".ppt", ".ppt",
new FileSignature(new List<string> { new FileSignature(new List<string> {
"512,006E1EF0", "512,006E1EF0",
"512,0F00E803", "512,0F00E803",
"512,A0461DF0", "512,A0461DF0",
"0,D0CF11E0A1B11AE1", "0,D0CF11E0A1B11AE1",
"512,FDFFFFFF04" "512,FDFFFFFF04"
}, "application/vnd.ms-powerpoint", FileCategory.Document) }, "application/vnd.ms-powerpoint", FileCategory.Document)
}, },
{ {
".docx", ".docx",
new FileSignature(new List<string> { new FileSignature(new List<string> {
"0,504B030414000600" "0,504B030414000600"
}, "application/vnd.openxmlformats-officedocument.wordprocessingml.document", FileCategory.Document) }, "application/vnd.openxmlformats-officedocument.wordprocessingml.document", FileCategory.Document)
}, },
{ {
".xlsx", ".xlsx",
new FileSignature(new List<string> { new FileSignature(new List<string> {
"0,504B030414000600" "0,504B030414000600"
}, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", FileCategory.Document) }, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", FileCategory.Document)
}, },
{ {
".pptx", ".pptx",
new FileSignature(new List<string> { new FileSignature(new List<string> {
"0,504B030414000600" "0,504B030414000600"
}, "application/vnd.openxmlformats-officedocument.presentationml.presentation", FileCategory.Document) }, "application/vnd.openxmlformats-officedocument.presentationml.presentation", FileCategory.Document)
}, },
#endregion #endregion
#region Images #region Images
{ {
".jpg", ".jpg",
new FileSignature(new List<string> { new FileSignature(new List<string> {
"0,FFD8" "0,FFD8"
}, "image/jpeg", FileCategory.Image) }, "image/jpeg", FileCategory.Image)
}, },
{ {
".jpeg", ".jpeg",
new FileSignature(new List<string> { new FileSignature(new List<string> {
"0,FFD8" "0,FFD8"
}, "image/jpeg", FileCategory.Image) }, "image/jpeg", FileCategory.Image)
}, },
{ {
".jpe", ".jpe",
new FileSignature(new List<string> { new FileSignature(new List<string> {
"0,FFD8" "0,FFD8"
}, "image/jpeg", FileCategory.Image) }, "image/jpeg", FileCategory.Image)
}, },
{ {
".jfif", ".jfif",
new FileSignature(new List<string> { new FileSignature(new List<string> {
"0,FFD8" "0,FFD8"
}, "image/jpeg", FileCategory.Image) }, "image/jpeg", FileCategory.Image)
}, },
{ {
".png", ".png",
new FileSignature(new List<string> { new FileSignature(new List<string> {
"0,89504E470D0A1A0A" "0,89504E470D0A1A0A"
}, "image/png", FileCategory.Image) }, "image/png", FileCategory.Image)
}, },
{ {
".webp", ".webp",
new FileSignature(new List<string> { new FileSignature(new List<string> {
"0,52494646" "0,52494646"
}, "image/webp", FileCategory.Image) }, "image/webp", FileCategory.Image)
} }
#endregion #endregion
}; };
}
/// <summary> /// <summary>
/// Don't rely on or trust the FileName property without validation. /// Don't rely on or trust the FileName property without validation.
@ -177,7 +141,7 @@ namespace WeatherForecast.Services.Abstractions {
/// <param name="bytes"></param> /// <param name="bytes"></param>
/// <param name="contentType"></param> /// <param name="contentType"></param>
/// <returns></returns> /// <returns></returns>
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; 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]; x |= sample[i] ^ signBytes[i];
} }
if (x == 0) return IDomainResult.Success(data.FileCategory); ; if (x == 0) return IDomainResult.Success(data.FileCategory);
} }
return IDomainResult.Failed<FileCategory?>(); ; return IDomainResult.Failed<FileCategory?>();
} }
} }
} }

View File

@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="DomainResult.Common" Version="3.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
<PackageReference Include="nClam" Version="7.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Extensions\Extensions.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace FileSecurityService {
/// <summary>
///
/// </summary>
internal class FileSignature {
/// <summary>
///
/// </summary>
public List<string> Signatures { get; private set; }
/// <summary>
///
/// </summary>
public string ContentType { get; private set; }
/// <summary>
///
/// </summary>
public FileCategory FileCategory { get; private set; }
/// <summary>
///
/// </summary>
/// <param name="signatures"></param>
/// <param name="contentType"></param>
/// <param name="fileCategory"></param>
public FileSignature(List<string> signatures, string contentType, FileCategory fileCategory) {
Signatures = signatures;
ContentType = contentType;
FileCategory = fileCategory;
}
}
}

View File

@ -29,6 +29,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CoreTests", "Tests\Core\Cor
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageService", "Services\ImageService\ImageService.csproj", "{16552644-D7EE-4B4A-A725-79909A8103DE}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageService", "Services\ImageService\ImageService.csproj", "{16552644-D7EE-4B4A-A725-79909A8103DE}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileSecurityService", "Services\FileSecurityService\FileSecurityService.csproj", "{AD515653-9145-4894-9017-0ABA5A5892F4}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU 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}.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.ActiveCfg = Release|Any CPU
{16552644-D7EE-4B4A-A725-79909A8103DE}.Release|Any CPU.Build.0 = 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 EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -89,6 +95,7 @@ Global
{43315A1D-9E09-4398-84B9-A9D9D623AE5A} = {216302C2-64DE-4AE7-BC14-BDAC5B732472} {43315A1D-9E09-4398-84B9-A9D9D623AE5A} = {216302C2-64DE-4AE7-BC14-BDAC5B732472}
{04CB9827-AA6D-4708-A26D-8420C842506D} = {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} {16552644-D7EE-4B4A-A725-79909A8103DE} = {113EE574-E047-4727-AA36-841F845504D5}
{AD515653-9145-4894-9017-0ABA5A5892F4} = {113EE574-E047-4727-AA36-841F845504D5}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {E2805D02-2425-424C-921D-D97341B76F73} SolutionGuid = {E2805D02-2425-424C-921D-D97341B76F73}

View File

@ -2,7 +2,7 @@
using DataProviders; using DataProviders;
using DataProviders.Buckets; using DataProviders.Buckets;
using DomainResults.Common; using DomainResults.Common;
using WeatherForecast.Services.Abstractions; using FileSecurityService;
namespace WeatherForecast.Services { namespace WeatherForecast.Services {
@ -42,21 +42,25 @@ namespace WeatherForecast.Services {
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public class FileService : FileServiceBase, IFileService { public class FileService : IFileService {
private readonly ILogger<FilesService> _logger; private readonly ILogger<FilesService> _logger;
private readonly IFileSecurityService _fileSecurityService;
private readonly IImagesBucketDataProvider _imageBucketDataProvider; private readonly IImagesBucketDataProvider _imageBucketDataProvider;
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
/// <param name="logger"></param> /// <param name="logger"></param>
/// <param name="fileSecurityService"></param>
/// <param name="imageBucketDataProvider"></param> /// <param name="imageBucketDataProvider"></param>
public FileService( public FileService(
ILogger<FilesService> logger, ILogger<FilesService> logger,
IFileSecurityService fileSecurityService,
IImagesBucketDataProvider imageBucketDataProvider IImagesBucketDataProvider imageBucketDataProvider
) { ) {
_logger = logger; _logger = logger;
_fileSecurityService = fileSecurityService;
_imageBucketDataProvider = imageBucketDataProvider; _imageBucketDataProvider = imageBucketDataProvider;
} }
@ -76,8 +80,7 @@ namespace WeatherForecast.Services {
file.CopyTo(ms); file.CopyTo(ms);
var bytes = ms.ToArray(); 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) if(!signatureResult.IsSuccess || fileCategory == null)
return IDomainResult.Failed<Guid?>(); return IDomainResult.Failed<Guid?>();

View File

@ -1,7 +1,7 @@
using DataProviders; using DataProviders;
using DataProviders.Buckets; using DataProviders.Buckets;
using DomainResults.Common; using DomainResults.Common;
using WeatherForecast.Services.Abstractions; using FileSecurityService;
namespace WeatherForecast.Services { namespace WeatherForecast.Services {
@ -31,9 +31,10 @@ namespace WeatherForecast.Services {
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public class FilesService : FileServiceBase, IFilesService { public class FilesService : IFilesService {
private readonly ILogger<FilesService> _logger; private readonly ILogger<FilesService> _logger;
private readonly IFileSecurityService _fileSecurityService;
private readonly IImagesBucketDataProvider _imageBucketDataProvider; private readonly IImagesBucketDataProvider _imageBucketDataProvider;
/// <summary> /// <summary>
@ -43,9 +44,11 @@ namespace WeatherForecast.Services {
/// <param name="imageBucketDataProvider"></param> /// <param name="imageBucketDataProvider"></param>
public FilesService( public FilesService(
ILogger<FilesService> logger, ILogger<FilesService> logger,
IFileSecurityService fileSecurityService,
IImagesBucketDataProvider imageBucketDataProvider IImagesBucketDataProvider imageBucketDataProvider
) { ) {
_logger = logger; _logger = logger;
_fileSecurityService = fileSecurityService;
_imageBucketDataProvider = imageBucketDataProvider; _imageBucketDataProvider = imageBucketDataProvider;
} }
@ -67,8 +70,15 @@ namespace WeatherForecast.Services {
foreach (var formFile in files) { foreach (var formFile in files) {
using var ms = new MemoryStream(); using var ms = new MemoryStream();
formFile.CopyTo(ms); 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<List<Guid>?>();
var bucketFile = new BucketFile(formFile.FileName, bytes, formFile.ContentType);
newFiles.Add(bucketFile);
} }
var (list, result) = _imageBucketDataProvider.UploadMany(siteId, userId, newFiles); var (list, result) = _imageBucketDataProvider.UploadMany(siteId, userId, newFiles);

View File

@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Authentication.JwtBearer;
using WeatherForecast.Services; using WeatherForecast.Services;
using DataProviders.Extensions; using DataProviders.Extensions;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using FileSecurityService.Extensions;
namespace WeatherForecast { namespace WeatherForecast {
@ -84,6 +85,7 @@ namespace WeatherForecast {
services.AddScoped<IFilesService, FilesService>(); services.AddScoped<IFilesService, FilesService>();
services.RegisterDataproviders(appSettings); services.RegisterDataproviders(appSettings);
services.RegisterFileSecurityService();
#region Swagger #region Swagger
services.ConfigureSwaggerGen(options => { services.ConfigureSwaggerGen(options => {

View File

@ -28,6 +28,7 @@
<ProjectReference Include="..\Core\Core.csproj" /> <ProjectReference Include="..\Core\Core.csproj" />
<ProjectReference Include="..\DataProviders\DataProviders.csproj" /> <ProjectReference Include="..\DataProviders\DataProviders.csproj" />
<ProjectReference Include="..\Extensions\Extensions.csproj" /> <ProjectReference Include="..\Extensions\Extensions.csproj" />
<ProjectReference Include="..\Services\FileSecurityService\FileSecurityService.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>