(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 ExtensionMethods;
namespace WeatherForecast.Services.Abstractions {
namespace FileSecurityService {
/// <summary>
///
/// </summary>
public enum FileCategory {
public interface IFileSecurityService {
/// <summary>
///
/// </summary>
Document,
/// <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="fileName"></param>
/// <param name="bytes"></param>
/// <param name="contentType"></param>
/// <param name="fileCategory"></param>
public FileSignature(List<string> signatures, string contentType, FileCategory fileCategory) {
Signatures = signatures;
ContentType = contentType;
FileCategory = fileCategory;
}
/// <returns></returns>
(FileCategory?, IDomainResult) CheckFileSignature(string fileName, byte[] bytes, string contentType);
}
/// <summary>
///
/// </summary>
public abstract class FileServiceBase {
public class FileSecurityService : IFileSecurityService {
/// <summary>
/// https://gist.github.com/qti3e/6341245314bf3513abb080677cd1c93b
/// </summary>
private readonly Dictionary<string, FileSignature> _data = new() {
#region Documents
{
".pdf",
new FileSignature(new List<string> {
private readonly Dictionary<string, FileSignature> _data;
/// <summary>
///
/// </summary>
public FileSecurityService() {
_data = new Dictionary<string, FileSignature>() {
#region Documents
{
".pdf",
new FileSignature(new List<string> {
"0,25504446"
}, "application/pdf", FileCategory.Document)
},
{
".rtf",
new FileSignature(new List<string> {
},
{
".rtf",
new FileSignature(new List<string> {
"0,7B5C72746631"
}, "application/rtf", FileCategory.Document)
},
{
".doc",
new FileSignature(new List<string> {
},
{
".doc",
new FileSignature(new List<string> {
"0,0D444F43",
"0,CF11E0A1B11AE100",
"0,D0CF11E0A1B11AE1",
"0,DBA52D00",
"512,ECA5C100"
}, "application/msword", FileCategory.Document)
},
{
".xls",
new FileSignature(new List<string> {
},
{
".xls",
new FileSignature(new List<string> {
"512,0908100000060500",
"0,D0CF11E0A1B11AE1",
"512,FDFFFFFF04",
"512,FDFFFFFF20000000"
}, "application/vnd.ms-excel", FileCategory.Document)
},
{
".ppt",
new FileSignature(new List<string> {
},
{
".ppt",
new FileSignature(new List<string> {
"512,006E1EF0",
"512,0F00E803",
"512,A0461DF0",
"0,D0CF11E0A1B11AE1",
"512,FDFFFFFF04"
}, "application/vnd.ms-powerpoint", FileCategory.Document)
},
{
".docx",
new FileSignature(new List<string> {
},
{
".docx",
new FileSignature(new List<string> {
"0,504B030414000600"
}, "application/vnd.openxmlformats-officedocument.wordprocessingml.document", FileCategory.Document)
},
{
".xlsx",
new FileSignature(new List<string> {
},
{
".xlsx",
new FileSignature(new List<string> {
"0,504B030414000600"
}, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", FileCategory.Document)
},
{
".pptx",
new FileSignature(new List<string> {
},
{
".pptx",
new FileSignature(new List<string> {
"0,504B030414000600"
}, "application/vnd.openxmlformats-officedocument.presentationml.presentation", FileCategory.Document)
},
#endregion
},
#endregion
#region Images
{
".jpg",
new FileSignature(new List<string> {
#region Images
{
".jpg",
new FileSignature(new List<string> {
"0,FFD8"
}, "image/jpeg", FileCategory.Image)
},
{
".jpeg",
new FileSignature(new List<string> {
},
{
".jpeg",
new FileSignature(new List<string> {
"0,FFD8"
}, "image/jpeg", FileCategory.Image)
},
{
".jpe",
new FileSignature(new List<string> {
},
{
".jpe",
new FileSignature(new List<string> {
"0,FFD8"
}, "image/jpeg", FileCategory.Image)
},
{
".jfif",
new FileSignature(new List<string> {
},
{
".jfif",
new FileSignature(new List<string> {
"0,FFD8"
}, "image/jpeg", FileCategory.Image)
},
},
{
".png",
new FileSignature(new List<string> {
{
".png",
new FileSignature(new List<string> {
"0,89504E470D0A1A0A"
}, "image/png", FileCategory.Image)
},
},
{
".webp",
new FileSignature(new List<string> {
{
".webp",
new FileSignature(new List<string> {
"0,52494646"
}, "image/webp", FileCategory.Image)
}
#endregion
};
}
#endregion
};
}
/// <summary>
/// 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="contentType"></param>
/// <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;
@ -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<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
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}

View File

@ -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 {
/// <summary>
///
/// </summary>
public class FileService : FileServiceBase, IFileService {
public class FileService : IFileService {
private readonly ILogger<FilesService> _logger;
private readonly IFileSecurityService _fileSecurityService;
private readonly IImagesBucketDataProvider _imageBucketDataProvider;
/// <summary>
///
/// </summary>
/// <param name="logger"></param>
/// <param name="fileSecurityService"></param>
/// <param name="imageBucketDataProvider"></param>
public FileService(
ILogger<FilesService> 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<Guid?>();
@ -92,7 +95,7 @@ namespace WeatherForecast.Services {
break;
default:
return IDomainResult.Failed<Guid?>();
}
}
if (!result.IsSuccess || fileId == null)
return IDomainResult.Failed<Guid?>();

View File

@ -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 {
/// <summary>
///
/// </summary>
public class FilesService : FileServiceBase, IFilesService {
public class FilesService : IFilesService {
private readonly ILogger<FilesService> _logger;
private readonly IFileSecurityService _fileSecurityService;
private readonly IImagesBucketDataProvider _imageBucketDataProvider;
/// <summary>
@ -43,9 +44,11 @@ namespace WeatherForecast.Services {
/// <param name="imageBucketDataProvider"></param>
public FilesService(
ILogger<FilesService> 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<List<Guid>?>();
var bucketFile = new BucketFile(formFile.FileName, bytes, formFile.ContentType);
newFiles.Add(bucketFile);
}
var (list, result) = _imageBucketDataProvider.UploadMany(siteId, userId, newFiles);

View File

@ -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<IFilesService, FilesService>();
services.RegisterDataproviders(appSettings);
services.RegisterFileSecurityService();
#region Swagger
services.ConfigureSwaggerGen(options => {

View File

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