mirror of
https://github.com/bitwarden/server
synced 2025-12-16 16:23:31 +00:00
PM-18939 refactoring send service to 'cqrs' (#5652)
* PM-18939 refactoring send service to 'cqrs' * PM-18939 fixing import issue with sendValidationService * PM-18939 fixing code based on PR comments * PM-18339 reverting to previous code in test * PM-18939 adding XMLdocs to services * PM-18939 reverting send validation methods * PM-18939 updating code to match main * PM-18939 reverting validateUserCanSaveAsync to match main * PM-18939 fill our param and return sections of XMLdocs * PM-18939 updating XMLdocs based on PR comments * Update src/Core/Tools/SendFeatures/Commands/Interfaces/IAnonymousSendCommand.cs Co-authored-by: ✨ Audrey ✨ <ajensen@bitwarden.com> * Update src/Core/Tools/SendFeatures/Commands/Interfaces/INonAnonymousSendCommand.cs Co-authored-by: ✨ Audrey ✨ <ajensen@bitwarden.com> * Update src/Core/Tools/SendFeatures/Commands/Interfaces/INonAnonymousSendCommand.cs Co-authored-by: ✨ Audrey ✨ <ajensen@bitwarden.com> * Update src/Core/Tools/SendFeatures/Services/Interfaces/ISendStorageService.cs Co-authored-by: ✨ Audrey ✨ <ajensen@bitwarden.com> * PM-18939 adding commits to change tuple to enum type * PM-18939 resetting stream position to 0 when uploading file * PM-18939 updating XMLdocs based on PR comments * PM-18939 updating XMLdocs * PM-18939 removing circular dependency * PM-18939 fixing based on comments * PM-18939 updating method name and documentation --------- Co-authored-by: ✨ Audrey ✨ <ajensen@bitwarden.com>
This commit is contained in:
180
src/Core/Tools/SendFeatures/Commands/NonAnonymousSendCommand.cs
Normal file
180
src/Core/Tools/SendFeatures/Commands/NonAnonymousSendCommand.cs
Normal file
@@ -0,0 +1,180 @@
|
||||
using System.Text.Json;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Platform.Push;
|
||||
using Bit.Core.Tools.Entities;
|
||||
using Bit.Core.Tools.Enums;
|
||||
using Bit.Core.Tools.Models.Business;
|
||||
using Bit.Core.Tools.Models.Data;
|
||||
using Bit.Core.Tools.Repositories;
|
||||
using Bit.Core.Tools.SendFeatures.Commands.Interfaces;
|
||||
using Bit.Core.Tools.Services;
|
||||
using Bit.Core.Utilities;
|
||||
|
||||
namespace Bit.Core.Tools.SendFeatures.Commands;
|
||||
|
||||
public class NonAnonymousSendCommand : INonAnonymousSendCommand
|
||||
{
|
||||
private readonly ISendRepository _sendRepository;
|
||||
private readonly ISendFileStorageService _sendFileStorageService;
|
||||
private readonly IPushNotificationService _pushNotificationService;
|
||||
private readonly ISendValidationService _sendValidationService;
|
||||
private readonly IReferenceEventService _referenceEventService;
|
||||
private readonly ICurrentContext _currentContext;
|
||||
private readonly ISendCoreHelperService _sendCoreHelperService;
|
||||
|
||||
public NonAnonymousSendCommand(ISendRepository sendRepository,
|
||||
ISendFileStorageService sendFileStorageService,
|
||||
IPushNotificationService pushNotificationService,
|
||||
ISendAuthorizationService sendAuthorizationService,
|
||||
ISendValidationService sendValidationService,
|
||||
IReferenceEventService referenceEventService,
|
||||
ICurrentContext currentContext,
|
||||
ISendCoreHelperService sendCoreHelperService)
|
||||
{
|
||||
_sendRepository = sendRepository;
|
||||
_sendFileStorageService = sendFileStorageService;
|
||||
_pushNotificationService = pushNotificationService;
|
||||
_sendValidationService = sendValidationService;
|
||||
_referenceEventService = referenceEventService;
|
||||
_currentContext = currentContext;
|
||||
_sendCoreHelperService = sendCoreHelperService;
|
||||
}
|
||||
|
||||
public async Task SaveSendAsync(Send send)
|
||||
{
|
||||
// Make sure user can save Sends
|
||||
await _sendValidationService.ValidateUserCanSaveAsync(send.UserId, send);
|
||||
|
||||
if (send.Id == default(Guid))
|
||||
{
|
||||
await _sendRepository.CreateAsync(send);
|
||||
await _pushNotificationService.PushSyncSendCreateAsync(send);
|
||||
await _referenceEventService.RaiseEventAsync(new ReferenceEvent
|
||||
{
|
||||
Id = send.UserId ?? default,
|
||||
Type = ReferenceEventType.SendCreated,
|
||||
Source = ReferenceEventSource.User,
|
||||
SendType = send.Type,
|
||||
MaxAccessCount = send.MaxAccessCount,
|
||||
HasPassword = !string.IsNullOrWhiteSpace(send.Password),
|
||||
SendHasNotes = send.Data?.Contains("Notes"),
|
||||
ClientId = _currentContext.ClientId,
|
||||
ClientVersion = _currentContext.ClientVersion
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
send.RevisionDate = DateTime.UtcNow;
|
||||
await _sendRepository.UpsertAsync(send);
|
||||
await _pushNotificationService.PushSyncSendUpdateAsync(send);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string> SaveFileSendAsync(Send send, SendFileData data, long fileLength)
|
||||
{
|
||||
if (send.Type != SendType.File)
|
||||
{
|
||||
throw new BadRequestException("Send is not of type \"file\".");
|
||||
}
|
||||
|
||||
if (fileLength < 1)
|
||||
{
|
||||
throw new BadRequestException("No file data.");
|
||||
}
|
||||
|
||||
var storageBytesRemaining = await _sendValidationService.StorageRemainingForSendAsync(send);
|
||||
|
||||
if (storageBytesRemaining < fileLength)
|
||||
{
|
||||
throw new BadRequestException("Not enough storage available.");
|
||||
}
|
||||
|
||||
var fileId = _sendCoreHelperService.SecureRandomString(32, useUpperCase: false, useSpecial: false);
|
||||
|
||||
try
|
||||
{
|
||||
data.Id = fileId;
|
||||
data.Size = fileLength;
|
||||
data.Validated = false;
|
||||
send.Data = JsonSerializer.Serialize(data,
|
||||
JsonHelpers.IgnoreWritingNull);
|
||||
await SaveSendAsync(send);
|
||||
return await _sendFileStorageService.GetSendFileUploadUrlAsync(send, fileId);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Clean up since this is not transactional
|
||||
await _sendFileStorageService.DeleteFileAsync(send, fileId);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
public async Task UploadFileToExistingSendAsync(Stream stream, Send send)
|
||||
{
|
||||
if (stream.Position > 0)
|
||||
{
|
||||
stream.Position = 0;
|
||||
}
|
||||
|
||||
if (send?.Data == null)
|
||||
{
|
||||
throw new BadRequestException("Send does not have file data");
|
||||
}
|
||||
|
||||
if (send.Type != SendType.File)
|
||||
{
|
||||
throw new BadRequestException("Not a File Type Send.");
|
||||
}
|
||||
|
||||
var data = JsonSerializer.Deserialize<SendFileData>(send.Data);
|
||||
|
||||
if (data.Validated)
|
||||
{
|
||||
throw new BadRequestException("File has already been uploaded.");
|
||||
}
|
||||
|
||||
await _sendFileStorageService.UploadNewFileAsync(stream, send, data.Id);
|
||||
|
||||
if (!await ConfirmFileSize(send))
|
||||
{
|
||||
throw new BadRequestException("File received does not match expected file length.");
|
||||
}
|
||||
}
|
||||
public async Task DeleteSendAsync(Send send)
|
||||
{
|
||||
await _sendRepository.DeleteAsync(send);
|
||||
if (send.Type == Enums.SendType.File)
|
||||
{
|
||||
var data = JsonSerializer.Deserialize<SendFileData>(send.Data);
|
||||
await _sendFileStorageService.DeleteFileAsync(send, data.Id);
|
||||
}
|
||||
await _pushNotificationService.PushSyncSendDeleteAsync(send);
|
||||
}
|
||||
|
||||
public async Task<bool> ConfirmFileSize(Send send)
|
||||
{
|
||||
var fileData = JsonSerializer.Deserialize<SendFileData>(send.Data);
|
||||
|
||||
var (valid, realSize) = await _sendFileStorageService.ValidateFileAsync(send, fileData.Id, fileData.Size, SendFileSettingHelper.FILE_SIZE_LEEWAY);
|
||||
|
||||
if (!valid || realSize > SendFileSettingHelper.FILE_SIZE_LEEWAY)
|
||||
{
|
||||
// File reported differs in size from that promised. Must be a rogue client. Delete Send
|
||||
await DeleteSendAsync(send);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Update Send data if necessary
|
||||
if (realSize != fileData.Size)
|
||||
{
|
||||
fileData.Size = realSize.Value;
|
||||
}
|
||||
fileData.Validated = true;
|
||||
send.Data = JsonSerializer.Serialize(fileData,
|
||||
JsonHelpers.IgnoreWritingNull);
|
||||
await SaveSendAsync(send);
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user