From fc050631e729a3cae6d0722dae8bf251af1f3836 Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Sat, 26 Apr 2025 16:01:35 +0530 Subject: [PATCH] Implemented functionality to whitelist file extensions during upload to S3 --- Marco.Pms.Model/Dtos/Forum/AddCommentDto.cs | 1 - Marco.Pms.Model/Dtos/Forum/CreateTicketDto.cs | 1 - .../Dtos/Forum/ForumAttachmentDto.cs | 1 - .../Dtos/Forum/UpdateAttachmentDto.cs | 14 ++ .../Dtos/Forum/UpdateCommentDto.cs | 2 +- Marco.Pms.Model/Dtos/Forum/UpdateTicketDto.cs | 2 +- Marco.Pms.Model/Mapper/ForumMapper.cs | 24 +++ .../Controllers/AttendanceController.cs | 25 +-- .../Controllers/ForumController.cs | 163 ++++----------- .../Controllers/WeatherForecastController.cs | 23 +-- Marco.Pms.Services/Marco.Pms.Services.csproj | 2 + Marco.Pms.Services/Service/S3UploadService.cs | 186 +++++++++++++++--- Marco.Pms.Services/appsettings.json | 20 +- 13 files changed, 260 insertions(+), 204 deletions(-) create mode 100644 Marco.Pms.Model/Dtos/Forum/UpdateAttachmentDto.cs diff --git a/Marco.Pms.Model/Dtos/Forum/AddCommentDto.cs b/Marco.Pms.Model/Dtos/Forum/AddCommentDto.cs index 82ef1d8..d04ad41 100644 --- a/Marco.Pms.Model/Dtos/Forum/AddCommentDto.cs +++ b/Marco.Pms.Model/Dtos/Forum/AddCommentDto.cs @@ -2,7 +2,6 @@ { public class AddCommentDto { - public Guid Id { get; set; } = Guid.Empty; public Guid TicketId { get; set; } = Guid.Empty; public Guid AuthorId { get; set; } public string MessageText { get; set; } = string.Empty; diff --git a/Marco.Pms.Model/Dtos/Forum/CreateTicketDto.cs b/Marco.Pms.Model/Dtos/Forum/CreateTicketDto.cs index b81bf12..57b7cee 100644 --- a/Marco.Pms.Model/Dtos/Forum/CreateTicketDto.cs +++ b/Marco.Pms.Model/Dtos/Forum/CreateTicketDto.cs @@ -2,7 +2,6 @@ { public class CreateTicketDto { - public Guid Id { get; set; } = Guid.Empty; public string Subject { get; set; } = string.Empty; public string Description { get; set; } = string.Empty; public Guid StatusId { get; set; } diff --git a/Marco.Pms.Model/Dtos/Forum/ForumAttachmentDto.cs b/Marco.Pms.Model/Dtos/Forum/ForumAttachmentDto.cs index 7d7a233..bf86c0a 100644 --- a/Marco.Pms.Model/Dtos/Forum/ForumAttachmentDto.cs +++ b/Marco.Pms.Model/Dtos/Forum/ForumAttachmentDto.cs @@ -2,7 +2,6 @@ { public class ForumAttachmentDto { - public Guid Id { get; set; } = Guid.Empty; public Guid TicketId { get; set; } = Guid.Empty; public Guid? CommentId { get; set; } public string FileName { get; set; } = string.Empty; diff --git a/Marco.Pms.Model/Dtos/Forum/UpdateAttachmentDto.cs b/Marco.Pms.Model/Dtos/Forum/UpdateAttachmentDto.cs new file mode 100644 index 0000000..510f332 --- /dev/null +++ b/Marco.Pms.Model/Dtos/Forum/UpdateAttachmentDto.cs @@ -0,0 +1,14 @@ +namespace Marco.Pms.Model.Dtos.Forum +{ + public class UpdateAttachmentDto + { + public Guid Id { get; set; } + public Guid TicketId { get; set; } = Guid.Empty; + public Guid? CommentId { get; set; } + public string FileName { get; set; } = string.Empty; + public string? Base64Data { get; set; } + public int FileSize { get; set; } + public string ContentType { get; set; } = string.Empty; + public DateTime SentAt { get; set; } + } +} diff --git a/Marco.Pms.Model/Dtos/Forum/UpdateCommentDto.cs b/Marco.Pms.Model/Dtos/Forum/UpdateCommentDto.cs index d14b58c..5405481 100644 --- a/Marco.Pms.Model/Dtos/Forum/UpdateCommentDto.cs +++ b/Marco.Pms.Model/Dtos/Forum/UpdateCommentDto.cs @@ -8,6 +8,6 @@ public string MessageText { get; set; } = string.Empty; public DateTime SentAt { get; set; } public Guid? ParentMessageId { get; set; } // For threaded replies - public ICollection? Attachments { get; set; } + public ICollection? Attachments { get; set; } } } diff --git a/Marco.Pms.Model/Dtos/Forum/UpdateTicketDto.cs b/Marco.Pms.Model/Dtos/Forum/UpdateTicketDto.cs index ae8f524..730b9c7 100644 --- a/Marco.Pms.Model/Dtos/Forum/UpdateTicketDto.cs +++ b/Marco.Pms.Model/Dtos/Forum/UpdateTicketDto.cs @@ -11,7 +11,7 @@ public DateTime CreatedAt { get; set; } public int LinkedProjectId { get; set; } public int? LinkedActivityId { get; set; } // task or project ID - public ICollection? Attachments { get; set; } + public ICollection? Attachments { get; set; } public Guid PriorityId { get; set; } public ICollection? TagIds { get; set; } public int TenantId { get; set; } diff --git a/Marco.Pms.Model/Mapper/ForumMapper.cs b/Marco.Pms.Model/Mapper/ForumMapper.cs index efd16e5..70913ef 100644 --- a/Marco.Pms.Model/Mapper/ForumMapper.cs +++ b/Marco.Pms.Model/Mapper/ForumMapper.cs @@ -77,6 +77,16 @@ namespace Marco.Pms.Model.Mapper FileId = fileId, }; } + public static TicketAttachment ToTicketAttachmentFromUpdateAttachmentDto(this UpdateAttachmentDto AttachmentDto, Guid ticketId, Guid fileId, Guid? commentId = null) + { + return new TicketAttachment + { + TicketId = ticketId, + CommentId = commentId, + FileName = AttachmentDto.FileName, + FileId = fileId, + }; + } public static Document ToDocumentFromForumAttachmentDto(this ForumAttachmentDto AttachmentDto, string objectKey, string thumbS3Key, DateTime uploadedAt, int tenantId) { @@ -92,6 +102,20 @@ namespace Marco.Pms.Model.Mapper TenantId = tenantId }; } + public static Document ToDocumentFromUpdateAttachmentDto(this UpdateAttachmentDto AttachmentDto, string objectKey, string thumbS3Key, DateTime uploadedAt, int tenantId) + { + return new Document + { + FileName = AttachmentDto.FileName, + ContentType = AttachmentDto.ContentType, + S3Key = objectKey, + ThumbS3Key = thumbS3Key, + Base64Data = AttachmentDto.Base64Data, + FileSize = AttachmentDto.FileSize, + UploadedAt = uploadedAt, + TenantId = tenantId + }; + } public static ForumTicketVM ToForumTicketVMFromTicketForum(this TicketForum ticket, Employee employee) { return new ForumTicketVM diff --git a/Marco.Pms.Services/Controllers/AttendanceController.cs b/Marco.Pms.Services/Controllers/AttendanceController.cs index 466c236..4c172a1 100644 --- a/Marco.Pms.Services/Controllers/AttendanceController.cs +++ b/Marco.Pms.Services/Controllers/AttendanceController.cs @@ -57,7 +57,7 @@ namespace MarcoBMS.Services.Controllers foreach (var attendanceLog in lstAttendance) { string objectKey = attendanceLog.Document.S3Key; - string preSignedUrl = await _s3Service.GeneratePreSignedUrlAsync(objectKey); + string preSignedUrl = _s3Service.GeneratePreSignedUrlAsync(objectKey); attendanceLogVMs.Add(attendanceLog.ToAttendanceLogVMFromAttendanceLog(preSignedUrl, preSignedUrl)); } return Ok(ApiResponse.SuccessResponse(attendanceLogVMs, System.String.Format("{0} Attendance records fetched successfully", lstAttendance.Count), 200)); @@ -509,33 +509,16 @@ namespace MarcoBMS.Services.Controllers if (Image != null && Image.ContentType != null) { - byte[] fileBytes; if (string.IsNullOrEmpty(Image.Base64Data)) return BadRequest(ApiResponse.ErrorResponse("Base64 data is missing", "Base64 data is missing", 400)); - try - { - //If base64 has a data URI prefix, strip it - var base64 = Image.Base64Data.Contains(",") - ? Image.Base64Data.Substring(Image.Base64Data.IndexOf(",") + 1) - : Image.Base64Data; - fileBytes = Convert.FromBase64String(base64); - } - catch (Exception ex) - { - return BadRequest(ApiResponse.ErrorResponse(ex.Message, ex, 400)); ; - } - - using var stream = new MemoryStream(fileBytes); - - string fileName = _s3Service.GenerateFileName(Image.ContentType, TenantId, "Attendance"); - objectKey = await _s3Service.UploadFileAsync(stream, fileName, Image.ContentType); - preSignedUrl = await _s3Service.GeneratePreSignedUrlAsync(objectKey); + objectKey = await _s3Service.UploadFileAsync(Image.Base64Data, TenantId, "Attendance"); + preSignedUrl = _s3Service.GeneratePreSignedUrlAsync(objectKey); document = new Document { - FileName = Image.FileName ?? fileName, + FileName = Image.FileName ?? "", ContentType = Image.ContentType, S3Key = objectKey, Base64Data = Image.Base64Data, diff --git a/Marco.Pms.Services/Controllers/ForumController.cs b/Marco.Pms.Services/Controllers/ForumController.cs index a9b81fd..a9c2906 100644 --- a/Marco.Pms.Services/Controllers/ForumController.cs +++ b/Marco.Pms.Services/Controllers/ForumController.cs @@ -62,34 +62,14 @@ namespace Marco.Pms.Services.Controllers { foreach (var attachmentDto in createTicketDto.Attachments) { - byte[] fileBytes; var Image = attachmentDto; if (string.IsNullOrEmpty(Image.Base64Data)) { _logger.LogError("Base64 data is missing"); return BadRequest(ApiResponse.ErrorResponse("Base64 data is missing", "Base64 data is missing", 400)); } - try - { - //If base64 has a data URI prefix, strip it - var base64 = Image.Base64Data.Contains(",") - ? Image.Base64Data.Substring(Image.Base64Data.IndexOf(",") + 1) - : Image.Base64Data; - fileBytes = Convert.FromBase64String(base64); - } - catch (Exception ex) - { - _logger.LogError("{error}", ex.Message); - return BadRequest(ApiResponse.ErrorResponse(ex.Message, ex, 400)); ; - } - - using var stream = new MemoryStream(fileBytes); - - - string fileName = _s3Service.GenerateFileName(Image.ContentType, tenantId, string.Empty); - - var objectKey = await _s3Service.UploadFileAsync(stream, fileName, Image.ContentType); + var objectKey = await _s3Service.UploadFileAsync(Image.Base64Data, tenantId, "Forum"); Document document = attachmentDto.ToDocumentFromForumAttachmentDto(objectKey, objectKey, createTicketDto.CreatedAt, tenantId); _context.Documents.Add(document); @@ -139,7 +119,7 @@ namespace Marco.Pms.Services.Controllers string preSignedUrl = string.Empty; if (document != null) { - preSignedUrl = await _s3Service.GeneratePreSignedUrlAsync(document.S3Key); + preSignedUrl = _s3Service.GeneratePreSignedUrlAsync(document.S3Key); } attachmentVMs.Add(attachment.ToTicketAttachmentVMFromTicketAttachment(preSignedUrl, preSignedUrl)); } @@ -198,40 +178,20 @@ namespace Marco.Pms.Services.Controllers { if (!existingattachmentids.Contains(attachmentDto.Id) && attachmentDto.TicketId != updateTicketDto.Id) { - byte[] fileBytes; var Image = attachmentDto; if (string.IsNullOrEmpty(Image.Base64Data)) { _logger.LogError("Base64 data is missing"); return BadRequest(ApiResponse.ErrorResponse("Base64 data is missing", "Base64 data is missing", 400)); } - try - { - //If base64 has a data URI prefix, strip it - var base64 = Image.Base64Data.Contains(",") - ? Image.Base64Data.Substring(Image.Base64Data.IndexOf(",") + 1) - : Image.Base64Data; - fileBytes = Convert.FromBase64String(base64); - } - catch (Exception ex) - { - _logger.LogError("{error}", ex.Message); - return BadRequest(ApiResponse.ErrorResponse(ex.Message, ex, 400)); ; - } + var objectKey = await _s3Service.UploadFileAsync(Image.Base64Data, tenantId, "Forum"); - using var stream = new MemoryStream(fileBytes); - - - string fileName = _s3Service.GenerateFileName(Image.ContentType, tenantId, string.Empty); - - var objectKey = await _s3Service.UploadFileAsync(stream, fileName, Image.ContentType); - - Document document = attachmentDto.ToDocumentFromForumAttachmentDto(objectKey, objectKey, updateTicketDto.CreatedAt, tenantId); + Document document = attachmentDto.ToDocumentFromUpdateAttachmentDto(objectKey, objectKey, updateTicketDto.CreatedAt, tenantId); _context.Documents.Add(document); await _context.SaveChangesAsync(); - var attachment = attachmentDto.ToTicketAttachmentFromForumAttachmentDto(ticketForum.Id, document.Id); + var attachment = attachmentDto.ToTicketAttachmentFromUpdateAttachmentDto(ticketForum.Id, document.Id); attachments.Add(attachment); } } @@ -313,7 +273,7 @@ namespace Marco.Pms.Services.Controllers string preSignedUrl = string.Empty; if (document != null) { - preSignedUrl = await _s3Service.GeneratePreSignedUrlAsync(document.S3Key); + preSignedUrl = _s3Service.GeneratePreSignedUrlAsync(document.S3Key); } if (attachment.CommentId == null) { @@ -376,33 +336,14 @@ namespace Marco.Pms.Services.Controllers { foreach (var attachmentDto in addCommentDto.Attachments) { - byte[] fileBytes; var Image = attachmentDto; if (string.IsNullOrEmpty(Image.Base64Data)) { _logger.LogError("Base64 data is missing"); return BadRequest(ApiResponse.ErrorResponse("Base64 data is missing", "Base64 data is missing", 400)); } - try - { - //If base64 has a data URI prefix, strip it - var base64 = Image.Base64Data.Contains(",") - ? Image.Base64Data.Substring(Image.Base64Data.IndexOf(",") + 1) - : Image.Base64Data; - fileBytes = Convert.FromBase64String(base64); - } - catch (Exception ex) - { - _logger.LogError("{error}", ex.Message); - return BadRequest(ApiResponse.ErrorResponse(ex.Message, ex, 400)); ; - } - using var stream = new MemoryStream(fileBytes); - - - string fileName = _s3Service.GenerateFileName(Image.ContentType, tenantId, string.Empty); - - var objectKey = await _s3Service.UploadFileAsync(stream, fileName, Image.ContentType); + var objectKey = await _s3Service.UploadFileAsync(Image.Base64Data, tenantId, "Forum"); Document document = attachmentDto.ToDocumentFromForumAttachmentDto(objectKey, objectKey, addCommentDto.SentAt, tenantId); _context.Documents.Add(document); @@ -428,7 +369,7 @@ namespace Marco.Pms.Services.Controllers string preSignedUrl = string.Empty; if (document != null) { - preSignedUrl = await _s3Service.GeneratePreSignedUrlAsync(document.S3Key); + preSignedUrl = _s3Service.GeneratePreSignedUrlAsync(document.S3Key); } attachmentVMs.Add(attachment.ToTicketAttachmentVMFromTicketAttachment(preSignedUrl, preSignedUrl)); } @@ -461,46 +402,31 @@ namespace Marco.Pms.Services.Controllers List existingAttachments = await _context.TicketAttachments.Where(a => a.CommentId == updateComment.Id).ToListAsync(); var existingattachmentids = existingAttachments.Select(a => a.Id).ToList(); - var attachmentDtoids = updateCommentDto.Attachments.Select(a => a.Id).ToList(); - - foreach (var attachmentDto in updateCommentDto.Attachments) + List attachmentDtoids = new List(); + if (updateCommentDto.Attachments != null) { - if (!existingattachmentids.Contains(attachmentDto.Id) && attachmentDto.CommentId != updateComment.Id) + attachmentDtoids = updateCommentDto.Attachments.Select(a => a.Id).ToList(); + + foreach (var attachmentDto in updateCommentDto.Attachments) { - byte[] fileBytes; - var Image = attachmentDto; - if (string.IsNullOrEmpty(Image.Base64Data)) + if (!existingattachmentids.Contains(attachmentDto.Id) && attachmentDto.CommentId != updateComment.Id) { - _logger.LogError("Base64 data is missing"); - return BadRequest(ApiResponse.ErrorResponse("Base64 data is missing", "Base64 data is missing", 400)); + var Image = attachmentDto; + if (string.IsNullOrEmpty(Image.Base64Data)) + { + _logger.LogError("Base64 data is missing"); + return BadRequest(ApiResponse.ErrorResponse("Base64 data is missing", "Base64 data is missing", 400)); + } + + var objectKey = await _s3Service.UploadFileAsync(Image.Base64Data, tenantId, "Forum"); + + Document document = attachmentDto.ToDocumentFromUpdateAttachmentDto(objectKey, objectKey, existingComment.SentAt, tenantId); + _context.Documents.Add(document); + await _context.SaveChangesAsync(); + + var attachment = attachmentDto.ToTicketAttachmentFromUpdateAttachmentDto(existingComment.TicketId, document.Id, updateComment.Id); + attachments.Add(attachment); } - try - { - //If base64 has a data URI prefix, strip it - var base64 = Image.Base64Data.Contains(",") - ? Image.Base64Data.Substring(Image.Base64Data.IndexOf(",") + 1) - : Image.Base64Data; - - fileBytes = Convert.FromBase64String(base64); - } - catch (Exception ex) - { - _logger.LogError("{error}", ex.Message); - return BadRequest(ApiResponse.ErrorResponse(ex.Message, ex, 400)); ; - } - using var stream = new MemoryStream(fileBytes); - - - string fileName = _s3Service.GenerateFileName(Image.ContentType, tenantId, string.Empty); - - var objectKey = await _s3Service.UploadFileAsync(stream, fileName, Image.ContentType); - - Document document = attachmentDto.ToDocumentFromForumAttachmentDto(objectKey, objectKey, existingComment.SentAt, tenantId); - _context.Documents.Add(document); - await _context.SaveChangesAsync(); - - var attachment = attachmentDto.ToTicketAttachmentFromForumAttachmentDto(existingComment.TicketId, document.Id, updateComment.Id); - attachments.Add(attachment); } } if (attachments.Count != 0) @@ -537,7 +463,7 @@ namespace Marco.Pms.Services.Controllers string preSignedUrl = string.Empty; if (document != null) { - preSignedUrl = await _s3Service.GeneratePreSignedUrlAsync(document.S3Key); + preSignedUrl = _s3Service.GeneratePreSignedUrlAsync(document.S3Key); } attachmentVMs.Add(attachment.ToTicketAttachmentVMFromTicketAttachment(preSignedUrl, preSignedUrl)); } @@ -568,32 +494,13 @@ namespace Marco.Pms.Services.Controllers foreach (var forumAttachmentDto in forumAttachmentDtos) { - byte[] fileBytes; if (string.IsNullOrEmpty(forumAttachmentDto.Base64Data)) { _logger.LogError("Base64 data is missing"); return BadRequest(ApiResponse.ErrorResponse("Base64 data is missing", "Base64 data is missing", 400)); } - try - { - //If base64 has a data URI prefix, strip it - var base64 = forumAttachmentDto.Base64Data.Contains(",") - ? forumAttachmentDto.Base64Data.Substring(forumAttachmentDto.Base64Data.IndexOf(",") + 1) - : forumAttachmentDto.Base64Data; - fileBytes = Convert.FromBase64String(base64); - } - catch (Exception ex) - { - _logger.LogError("{error}", ex.Message); - return BadRequest(ApiResponse.ErrorResponse(ex.Message, ex, 400)); ; - } - using var stream = new MemoryStream(fileBytes); - - - string fileName = _s3Service.GenerateFileName(forumAttachmentDto.ContentType, tenantId, string.Empty); - - var objectKey = await _s3Service.UploadFileAsync(stream, fileName, forumAttachmentDto.ContentType); + var objectKey = await _s3Service.UploadFileAsync(forumAttachmentDto.Base64Data, tenantId, "Forum"); Document document = forumAttachmentDto.ToDocumentFromForumAttachmentDto(objectKey, objectKey, forumAttachmentDto.SentAt, tenantId); _context.Documents.Add(document); @@ -603,7 +510,7 @@ namespace Marco.Pms.Services.Controllers _context.TicketAttachments.Add(attachment); await _context.SaveChangesAsync(); - string preSignedUrl = await _s3Service.GeneratePreSignedUrlAsync(document.S3Key); + string preSignedUrl = _s3Service.GeneratePreSignedUrlAsync(document.S3Key); TicketAttachmentVM attachmentVM = attachment.ToTicketAttachmentVMFromTicketAttachment(preSignedUrl, preSignedUrl); ticketAttachmentVMs.Add(attachmentVM); @@ -668,7 +575,7 @@ namespace Marco.Pms.Services.Controllers string preSignedUrl = string.Empty; if (document != null) { - preSignedUrl = await _s3Service.GeneratePreSignedUrlAsync(document.S3Key); + preSignedUrl = _s3Service.GeneratePreSignedUrlAsync(document.S3Key); } if (attachment.CommentId == null) { @@ -743,7 +650,7 @@ namespace Marco.Pms.Services.Controllers string preSignedUrl = string.Empty; if (document != null) { - preSignedUrl = await _s3Service.GeneratePreSignedUrlAsync(document.S3Key); + preSignedUrl = _s3Service.GeneratePreSignedUrlAsync(document.S3Key); } if (attachment.CommentId == null) { @@ -842,7 +749,7 @@ namespace Marco.Pms.Services.Controllers string preSignedUrl = string.Empty; if (document != null) { - preSignedUrl = await _s3Service.GeneratePreSignedUrlAsync(document.S3Key); + preSignedUrl = _s3Service.GeneratePreSignedUrlAsync(document.S3Key); } if (attachment.CommentId == null) { diff --git a/Marco.Pms.Services/Controllers/WeatherForecastController.cs b/Marco.Pms.Services/Controllers/WeatherForecastController.cs index b83d7e3..f6dc72d 100644 --- a/Marco.Pms.Services/Controllers/WeatherForecastController.cs +++ b/Marco.Pms.Services/Controllers/WeatherForecastController.cs @@ -38,28 +38,9 @@ namespace MarcoBMS.Services.Controllers { if (string.IsNullOrEmpty(Image.Base64Data)) return BadRequest("Base64 data is missing"); - - byte[] fileBytes; - - try - { - //If base64 has a data URI prefix, strip it - var base64 = Image.Base64Data.Contains(",") - ? Image.Base64Data.Substring(Image.Base64Data.IndexOf(",") + 1) - : Image.Base64Data; - - fileBytes = Convert.FromBase64String(base64); - } - catch (Exception error) - { - //return BadRequest("Invalid base64 string."); - return BadRequest(error); - } - - using var stream = new MemoryStream(fileBytes); - var objectKey = await _s3Service.UploadFileAsync(stream, Image.FileName, Image.ContentType); + var objectKey = await _s3Service.UploadFileAsync(Image.Base64Data, 1, "Forum"); //var objectKey = await _s3Service.UploadFileAsync(Image.FileName, Image.ContentType); - var preSignedUrl = await _s3Service.GeneratePreSignedUrlAsync(objectKey); + var preSignedUrl = _s3Service.GeneratePreSignedUrlAsync(objectKey); return Ok(new { diff --git a/Marco.Pms.Services/Marco.Pms.Services.csproj b/Marco.Pms.Services/Marco.Pms.Services.csproj index 7219390..e65edaf 100644 --- a/Marco.Pms.Services/Marco.Pms.Services.csproj +++ b/Marco.Pms.Services/Marco.Pms.Services.csproj @@ -28,6 +28,8 @@ + + diff --git a/Marco.Pms.Services/Service/S3UploadService.cs b/Marco.Pms.Services/Service/S3UploadService.cs index cea43ea..6acfcc1 100644 --- a/Marco.Pms.Services/Service/S3UploadService.cs +++ b/Marco.Pms.Services/Service/S3UploadService.cs @@ -4,6 +4,7 @@ using Amazon.S3.Transfer; using Marco.Pms.Model.Utilities; using MarcoBMS.Services.Service; using Microsoft.Extensions.Options; +using MimeDetective; namespace Marco.Pms.Services.Service { @@ -13,10 +14,12 @@ namespace Marco.Pms.Services.Service private readonly IAmazonS3 _s3Client; private readonly string _bucketName = "your-bucket-name"; private readonly ILoggingService _logger; + private readonly IConfiguration _configuration; - public S3UploadService(IOptions awsOptions, ILoggingService logger) + public S3UploadService(IOptions awsOptions, ILoggingService logger, IConfiguration configuration) { _logger = logger; + _configuration = configuration; var settings = awsOptions.Value; var region = Amazon.RegionEndpoint.GetBySystemName(settings.Region); @@ -25,35 +28,57 @@ namespace Marco.Pms.Services.Service _s3Client = new AmazonS3Client(settings.AccessKey, settings.SecretKey, region); } //public async Task UploadFileAsync(string fileName, string contentType) - public async Task UploadFileAsync(Stream fileStream, string fileName, string contentType) + public async Task UploadFileAsync(string base64Data, int tenantId, string tag) { - // Generate a unique object key (you can customize this) - var objectKey = $"{fileName}"; + byte[] fileBytes; - var uploadRequest = new TransferUtilityUploadRequest + //If base64 has a data URI prefix, strip it + var base64 = base64Data.Contains(",") + ? base64Data.Substring(base64Data.IndexOf(",") + 1) + : base64Data; + var allowedFilesType = _configuration.GetSection("WhiteList:ContentType") + .GetChildren() + .Select(x => x.Value) + .ToList(); + string fileType = GetContentTypeFromBase64(base64); + if (allowedFilesType != null && allowedFilesType.Contains(fileType)) { - InputStream = fileStream, - Key = objectKey, - BucketName = _bucketName, - ContentType = contentType, - AutoCloseStream = true - }; - try - { - var transferUtility = new TransferUtility(_s3Client); - await transferUtility.UploadAsync(uploadRequest); - _logger.LogInfo("File uploaded to Amazon S3"); - return objectKey; - } - catch (Exception ex) - { - _logger.LogError("{error} while uploading file to S3", ex.Message); - return string.Empty; + string fileName = GenerateFileName(fileType, tenantId, tag); + + fileBytes = Convert.FromBase64String(base64); + + + using var fileStream = new MemoryStream(fileBytes); + + // Generate a unique object key (you can customize this) + var objectKey = $"{fileName}"; + + var uploadRequest = new TransferUtilityUploadRequest + { + InputStream = fileStream, + Key = objectKey, + BucketName = _bucketName, + ContentType = fileType, + AutoCloseStream = true + }; + try + { + var transferUtility = new TransferUtility(_s3Client); + await transferUtility.UploadAsync(uploadRequest); + _logger.LogInfo("File uploaded to Amazon S3"); + return objectKey; + } + catch (Exception ex) + { + _logger.LogError("{error} while uploading file to S3", ex.Message); + return string.Empty; + } } + throw new InvalidOperationException("Unsupported file type."); } - public async Task GeneratePreSignedUrlAsync(string objectKey) + public string GeneratePreSignedUrlAsync(string objectKey) { - int expiresInMinutes = 1; + int expiresInMinutes = 10; var request = new GetPreSignedUrlRequest { BucketName = _bucketName, @@ -95,11 +120,116 @@ namespace Marco.Pms.Services.Service } public string GenerateFileName(string contentType, int tenantId, string? name) { - string extenstion = contentType.Split("/")[1]; - if (string.IsNullOrEmpty(name)) - return $"{tenantId}_{DateTime.UtcNow:yyyyMMddHHmmssfff}.{extenstion}"; - return $"{name}_{tenantId}_{DateTime.UtcNow:yyyyMMddHHmmssfff}.{extenstion}"; + string extenstion = GetExtensionFromMimeType(contentType); + if (string.IsNullOrEmpty(name)) + return $"{tenantId}_{DateTime.UtcNow:yyyyMMddHHmmssfff}{extenstion}"; + return $"{name}_{tenantId}_{DateTime.UtcNow:yyyyMMddHHmmssfff}{extenstion}"; + + } + public string GetExtensionFromMimeType(string contentType) + { + switch (contentType.ToLowerInvariant()) + { + case "application/pdf": + return ".pdf"; + + case "application/msword": + return ".doc"; + + case "application/vnd.openxmlformats-officedocument.wordprocessingml.document": + return ".docx"; + + case "application/vnd.ms-excel": + return ".xls"; + + case "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": + return ".xlsx"; + + case "application/mspowerpoint": + return ".ppt"; + + case "application/vnd.openxmlformats-officedocument.presentationml.presentation": + return ".pptx"; + + case "text/plain": + return ".txt"; + + case "application/rtf": + return ".rtf"; + + case "image/jpeg": + return ".jpg"; + + case "image/png": + return ".png"; + + case "image/gif": + return ".gif"; + + case "image/bmp": + return ".bmp"; + + case "text/csv": + return ".csv"; + + default: + return ""; // or ".bin", or throw an error, based on your needs + } + } + public string GetContentTypeFromBase64(string base64String) + { + if (string.IsNullOrEmpty(base64String)) + { + return string.Empty; // Or handle the empty case as needed + } + + try + { + // 1. Decode the Base64 string to a byte array + byte[] decodedBytes = Convert.FromBase64String(base64String); + + // 2. Create a ContentInspector (using default definitions for this example) + var inspector = new ContentInspectorBuilder() + { + Definitions = MimeDetective.Definitions.DefaultDefinitions.All() + }.Build(); + + // 3. Inspect the byte array to determine the content type + var results = inspector.Inspect(decodedBytes); + + if (results.Any()) + { + var bestMatch = results + .OrderByDescending(r => r.Points) + .FirstOrDefault(); + if (bestMatch?.Definition?.File?.MimeType != null) + { + return bestMatch.Definition.File.MimeType; + } + else + { + _logger.LogError("Warning: Could not find MimeType, Type, or ContentType property in Definition."); + return "application/octet-stream"; + } + } + else + { + return "application/octet-stream"; // Default if type cannot be determined + } + } + catch (FormatException) + { + // Handle cases where the input string is not valid Base64 + Console.WriteLine("Error: Invalid Base64 string."); + return null; + } + catch (Exception ex) + { + // Handle other potential errors during decoding or inspection + Console.WriteLine($"An error occurred: {ex.Message}"); + return null; + } } } } \ No newline at end of file diff --git a/Marco.Pms.Services/appsettings.json b/Marco.Pms.Services/appsettings.json index 2162a7c..2ee7a17 100644 --- a/Marco.Pms.Services/appsettings.json +++ b/Marco.Pms.Services/appsettings.json @@ -86,6 +86,24 @@ "SecretKey": "NTS5XXgZINQbU6ctpNuLXtIY/Qk9GCgD9Rr5yNJP", "Region": "us-east-1", "BucketName": "testenv-marco-pms-documents" + }, + "WhiteList": { + "ContentType": [ + "application/pdf", // pdf + "application/msword", // Doc + "application/vnd.openxmlformats-officedocument.wordprocessingml.document", //docx + "application/vnd.ms-excel", //xls + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", //xlsx + "application/mspowerpoint", //ppt + "application/vnd.openxmlformats-officedocument.presentationml.presentation", //pptx + "text/plain", //txt + "application/rtf", //rtf + "text/csv", //csv + "image/jpg", //jpg + "image/jpeg", //jpeg + "image/png", //png + "image/gif", //gif + "image/bmp" //bmp + ] } - }