mirror of
https://github.com/bitwarden/server.git
synced 2025-06-24 20:58:49 -05:00
182 lines
6.1 KiB
C#
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;
|
|
}
|
|
|
|
}
|