1
0
mirror of https://github.com/bitwarden/server.git synced 2025-06-24 20:58:49 -05:00
bitwarden/src/Core/Tools/SendFeatures/Commands/NonAnonymousSendCommand.cs

182 lines
6.1 KiB
C#

using System.Text.Json;
using Bit.Core.Exceptions;
using Bit.Core.Platform.Push;
using Bit.Core.Tools.Entities;
using Bit.Core.Tools.Enums;
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;
using Microsoft.Extensions.Logging;
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 ISendCoreHelperService _sendCoreHelperService;
private readonly ILogger<NonAnonymousSendCommand> _logger;
public NonAnonymousSendCommand(ISendRepository sendRepository,
ISendFileStorageService sendFileStorageService,
IPushNotificationService pushNotificationService,
ISendAuthorizationService sendAuthorizationService,
ISendValidationService sendValidationService,
ISendCoreHelperService sendCoreHelperService,
ILogger<NonAnonymousSendCommand> logger)
{
_sendRepository = sendRepository;
_sendFileStorageService = sendFileStorageService;
_pushNotificationService = pushNotificationService;
_sendValidationService = sendValidationService;
_sendCoreHelperService = sendCoreHelperService;
_logger = logger;
}
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);
}
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.");
}
if (fileLength > SendFileSettingHelper.MAX_FILE_SIZE)
{
throw new BadRequestException($"Max file size is {SendFileSettingHelper.MAX_FILE_SIZE_READABLE}.");
}
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
{
_logger.LogWarning(
"Deleted file from {SendId} because an error occurred when creating the upload URL.",
send.Id
);
// 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 minimum = fileData.Size - SendFileSettingHelper.FILE_SIZE_LEEWAY;
var maximum = Math.Min(
fileData.Size + SendFileSettingHelper.FILE_SIZE_LEEWAY,
SendFileSettingHelper.MAX_FILE_SIZE
);
var (valid, size) = await _sendFileStorageService.ValidateFileAsync(send, fileData.Id, minimum, maximum);
// protect file service from upload hijacking by deleting invalid sends
if (!valid)
{
_logger.LogWarning(
"Deleted {SendId} because its reported size {Size} was outside the expected range ({Minimum} - {Maximum}).",
send.Id,
size,
minimum,
maximum
);
await DeleteSendAsync(send);
return false;
}
// replace expected size with validated size
fileData.Size = size;
fileData.Validated = true;
send.Data = JsonSerializer.Serialize(fileData, JsonHelpers.IgnoreWritingNull);
await SaveSendAsync(send);
return valid;
}
}