From d2c6209954c614099da2c262b17fbb92c4925628 Mon Sep 17 00:00:00 2001 From: Dominic Villemure Date: Wed, 10 Jul 2024 01:38:27 -0400 Subject: [PATCH] Force contentType from the headers to be .png, .jpeg or .jpg --- .../Constants/CommonFileNames.cs | 6 ++-- .../Constants/ContentTypes.cs | 6 ---- .../Users/Commands/UploadBannerPicture.cs | 26 ++++++----------- .../Users/Commands/UploadProfilePicture.cs | 7 +++-- .../Users/Commands/UploadWebsiteIcon.cs | 7 +++-- .../Utils/HttpContextAccessorExtensions.cs | 29 +++++++++++++++++++ .../AzureBlob/AzureBlobStorageService.cs | 11 ++++--- src/Infrastructure/AzureBlob/ContentTypes.cs | 15 ++++++++++ 8 files changed, 73 insertions(+), 34 deletions(-) delete mode 100644 src/Application/AzureBlobStorage/Constants/ContentTypes.cs create mode 100644 src/Application/Utils/HttpContextAccessorExtensions.cs create mode 100644 src/Infrastructure/AzureBlob/ContentTypes.cs diff --git a/src/Application/AzureBlobStorage/Constants/CommonFileNames.cs b/src/Application/AzureBlobStorage/Constants/CommonFileNames.cs index 8ffc63c..b6d44aa 100644 --- a/src/Application/AzureBlobStorage/Constants/CommonFileNames.cs +++ b/src/Application/AzureBlobStorage/Constants/CommonFileNames.cs @@ -2,7 +2,7 @@ public static class CommonFileNames { - public static string ProfilePicture = "profilePicture.png"; - public static string BannerPicture = "bannerPicture.png"; - public static string WebsiteIcon = "websiteIcon.png"; + public static string ProfilePicture = "profilePicture"; + public static string BannerPicture = "bannerPicture"; + public static string WebsiteIcon = "websiteIcon"; } diff --git a/src/Application/AzureBlobStorage/Constants/ContentTypes.cs b/src/Application/AzureBlobStorage/Constants/ContentTypes.cs deleted file mode 100644 index ff3bcf7..0000000 --- a/src/Application/AzureBlobStorage/Constants/ContentTypes.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Hutopy.Application.AzureBlobStorage.Constants; - -public static class ContentTypes -{ - public static string ImagePng = "image/png"; -} diff --git a/src/Application/Users/Commands/UploadBannerPicture.cs b/src/Application/Users/Commands/UploadBannerPicture.cs index 96728b8..3bab003 100644 --- a/src/Application/Users/Commands/UploadBannerPicture.cs +++ b/src/Application/Users/Commands/UploadBannerPicture.cs @@ -1,5 +1,6 @@ using Hutopy.Application.AzureBlobStorage.Constants; using Hutopy.Application.Common.Interfaces; +using Hutopy.Application.Utils; using Microsoft.AspNetCore.Http; namespace Hutopy.Application.Users.Commands; @@ -13,10 +14,12 @@ public class UploadBannerPictureCommand : IRequest public string BannerPictureUrl { get; init; } = string.Empty; } -public class UploadBannerPictureCommandHandler(IIdentityService identityService, IAzureBlobStorageService azureBlobStorageService) : IRequestHandler +public class UploadBannerPictureCommandHandler(IHttpContextAccessor contextAccessor, IIdentityService identityService, IAzureBlobStorageService azureBlobStorageService) : IRequestHandler { public async Task Handle(UploadBannerPictureCommand request, CancellationToken cancellationToken) { + var contentType = contextAccessor.EnsureContentType(); + // If an url to the picture is provided, use it right away and don't upload anything. if (!string.IsNullOrEmpty(request.BannerPictureUrl)) { @@ -28,23 +31,12 @@ public class UploadBannerPictureCommandHandler(IIdentityService identityService, var currentUserId = new Guid(identityUser?.Id ?? "").ToString(); var blobName = $"{currentUserId}/{SubDirectoryNames.Profile}/{CommonFileNames.BannerPicture}"; - - try - { - var url = await azureBlobStorageService.UploadFileAsync(ContainerNames.Users, blobName, request.BannerPicture, ContentTypes.ImagePng); - - await identityService.UpdateCurrentUserBannerPictureUrlAsync(url); - return Results.Ok(url); - } - catch (InvalidOperationException ex) - { - return Results.BadRequest(ex.Message); - } - catch (Exception) - { - return Results.StatusCode(StatusCodes.Status500InternalServerError); - } + var url = await azureBlobStorageService.UploadFileAsync(ContainerNames.Users, blobName, request.BannerPicture, contentType); + + await identityService.UpdateCurrentUserBannerPictureUrlAsync(url); + + return Results.Ok(url); } } diff --git a/src/Application/Users/Commands/UploadProfilePicture.cs b/src/Application/Users/Commands/UploadProfilePicture.cs index daefbe7..0dbb394 100644 --- a/src/Application/Users/Commands/UploadProfilePicture.cs +++ b/src/Application/Users/Commands/UploadProfilePicture.cs @@ -1,5 +1,6 @@ using Hutopy.Application.AzureBlobStorage.Constants; using Hutopy.Application.Common.Interfaces; +using Hutopy.Application.Utils; using Microsoft.AspNetCore.Http; namespace Hutopy.Application.Users.Commands; @@ -13,10 +14,12 @@ public class UploadProfilePictureCommand : IRequest public string ProfilePictureUrl { get; init; } = string.Empty; } -public class UploadProfilePictureCommandHandler(IIdentityService identityService, IAzureBlobStorageService azureBlobStorageService) : IRequestHandler +public class UploadProfilePictureCommandHandler(IHttpContextAccessor contextAccessor, IIdentityService identityService, IAzureBlobStorageService azureBlobStorageService) : IRequestHandler { public async Task Handle(UploadProfilePictureCommand request, CancellationToken cancellationToken) { + var contentType = contextAccessor.EnsureContentType(); + // If an url to the picture is provided, use it right away and don't upload anything. if (!string.IsNullOrEmpty(request.ProfilePictureUrl)) { @@ -29,7 +32,7 @@ public class UploadProfilePictureCommandHandler(IIdentityService identityService var blobName = $"{currentUserId}/{SubDirectoryNames.Profile}/{CommonFileNames.ProfilePicture}"; - var url = await azureBlobStorageService.UploadFileAsync(ContainerNames.Users, blobName, request.ProfilePicture, ContentTypes.ImagePng); + var url = await azureBlobStorageService.UploadFileAsync(ContainerNames.Users, blobName, request.ProfilePicture, contentType); await identityService.UpdateCurrentUserProfilePictureUrlAsync(url); diff --git a/src/Application/Users/Commands/UploadWebsiteIcon.cs b/src/Application/Users/Commands/UploadWebsiteIcon.cs index 2c18756..29158b2 100644 --- a/src/Application/Users/Commands/UploadWebsiteIcon.cs +++ b/src/Application/Users/Commands/UploadWebsiteIcon.cs @@ -1,5 +1,6 @@ using Hutopy.Application.AzureBlobStorage.Constants; using Hutopy.Application.Common.Interfaces; +using Hutopy.Application.Utils; using Microsoft.AspNetCore.Http; namespace Hutopy.Application.Users.Commands; @@ -14,10 +15,12 @@ public class UploadWebsiteIconCommand : IRequest public string WebsitePictureUrl { get; init; } = string.Empty; } -public class UploadWebsiteIconCommandHandler(IIdentityService identityService, IAzureBlobStorageService azureBlobStorageService) : IRequestHandler +public class UploadWebsiteIconCommandHandler(IHttpContextAccessor contextAccessor, IIdentityService identityService, IAzureBlobStorageService azureBlobStorageService) : IRequestHandler { public async Task Handle(UploadWebsiteIconCommand request, CancellationToken cancellationToken) { + var contentType = contextAccessor.EnsureContentType(); + // If an url to the picture is provided, use it right away and don't upload anything. if (!string.IsNullOrEmpty(request.WebsitePictureUrl)) { @@ -30,7 +33,7 @@ public class UploadWebsiteIconCommandHandler(IIdentityService identityService, I var blobName = $"{currentUserId}/{SubDirectoryNames.Profile}/{CommonFileNames.WebsiteIcon}"; - var url = await azureBlobStorageService.UploadFileAsync(ContainerNames.Users, blobName, request.WebsiteIcon, ContentTypes.ImagePng); + var url = await azureBlobStorageService.UploadFileAsync(ContainerNames.Users, blobName, request.WebsiteIcon, contentType); await identityService.UpdateCurrentUserWebsiteIconUrlAsync(url); diff --git a/src/Application/Utils/HttpContextAccessorExtensions.cs b/src/Application/Utils/HttpContextAccessorExtensions.cs new file mode 100644 index 0000000..849e1a8 --- /dev/null +++ b/src/Application/Utils/HttpContextAccessorExtensions.cs @@ -0,0 +1,29 @@ +using Microsoft.AspNetCore.Http; + +namespace Hutopy.Application.Utils; + +public static class HttpContextAccessorExtensions +{ + public static HttpContext EnsureHttpContext(this IHttpContextAccessor httpContextAccessor) + { + if (httpContextAccessor.HttpContext == null) + { + throw new InvalidOperationException("HttpContext is null."); + } + + return httpContextAccessor.HttpContext; + } + + public static string EnsureContentType(this IHttpContextAccessor httpContextAccessor) + { + var httpContext = EnsureHttpContext(httpContextAccessor); + var contentType = httpContext.Request.ContentType; + + if (string.IsNullOrEmpty(contentType)) + { + throw new InvalidOperationException("Content-Type header is missing."); + } + + return contentType; + } +} diff --git a/src/Infrastructure/AzureBlob/AzureBlobStorageService.cs b/src/Infrastructure/AzureBlob/AzureBlobStorageService.cs index 275b5a3..a7598a7 100644 --- a/src/Infrastructure/AzureBlob/AzureBlobStorageService.cs +++ b/src/Infrastructure/AzureBlob/AzureBlobStorageService.cs @@ -1,6 +1,3 @@ -using System; -using System.IO; -using System.Threading.Tasks; using Azure; using Azure.Storage.Blobs; using Azure.Storage.Blobs.Models; @@ -16,7 +13,6 @@ public class AzureBlobStorageService : IAzureBlobStorageService private readonly ILogger _logger; private readonly long _maxUploadSize = 10 * 1024 * 1024; // 10 MB in bytes - public AzureBlobStorageService(IConfiguration configuration, ILogger logger) { _logger = logger; @@ -47,6 +43,13 @@ public class AzureBlobStorageService : IAzureBlobStorageService throw new InvalidOperationException($"Blob storage: File size exceeds the maximum allowed size of {_maxUploadSize} bytes."); } + // Validate content type + if (!ContentTypes.IsAllowed(contentType)) + { + _logger.LogInformation($"Blob storage: Unsupported file type {contentType}. Only PNG and JPEG are allowed."); + throw new InvalidOperationException("Unsupported file type. Only PNG and JPEG are allowed."); + } + try { // Get a reference to a container diff --git a/src/Infrastructure/AzureBlob/ContentTypes.cs b/src/Infrastructure/AzureBlob/ContentTypes.cs new file mode 100644 index 0000000..ae4261b --- /dev/null +++ b/src/Infrastructure/AzureBlob/ContentTypes.cs @@ -0,0 +1,15 @@ +namespace Hutopy.Infrastructure.AzureBlob; + +public static class ContentTypes +{ + private static string ImagePng = "image/png"; + private static string ImageJpeg = "image/jpeg"; + private static string ImageJpg = "image/jpg"; + + public static HashSet AllowedContentTypes = new HashSet { ImagePng, ImageJpeg, ImageJpg }; + + public static bool IsAllowed(string contentType) + { + return AllowedContentTypes.Contains(contentType); + } +}