2022-06-29 19:46:41 -04:00
using System.Text.Json ;
2021-02-04 12:54:21 -06:00
using Bit.Core.Context ;
2022-01-11 10:40:51 +01:00
using Bit.Core.Entities ;
2021-02-04 12:54:21 -06:00
using Bit.Core.Enums ;
2020-11-02 15:55:49 -05:00
using Bit.Core.Exceptions ;
2022-06-08 08:44:28 -05:00
using Bit.Core.Models.Data.Organizations.Policies ;
2020-11-02 15:55:49 -05:00
using Bit.Core.Repositories ;
2023-04-18 14:05:17 +02:00
using Bit.Core.Services ;
2021-02-22 15:35:16 -06:00
using Bit.Core.Settings ;
2023-04-18 14:05:17 +02:00
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 ;
2021-09-28 06:54:28 +10:00
using Bit.Core.Utilities ;
2020-11-02 15:55:49 -05:00
using Microsoft.AspNetCore.Identity ;
2023-04-18 14:05:17 +02:00
namespace Bit.Core.Tools.Services ;
2022-08-29 16:06:55 -04:00
2020-11-02 15:55:49 -05:00
public class SendService : ISendService
{
public const long MAX_FILE_SIZE = Constants . FileSize501mb ;
public const string MAX_FILE_SIZE_READABLE = "500 MB" ;
private readonly ISendRepository _sendRepository ;
private readonly IUserRepository _userRepository ;
2021-02-04 12:54:21 -06:00
private readonly IPolicyRepository _policyRepository ;
2020-11-02 15:55:49 -05:00
private readonly IUserService _userService ;
2021-01-22 16:16:40 -05:00
private readonly IOrganizationRepository _organizationRepository ;
2020-11-02 15:55:49 -05:00
private readonly ISendFileStorageService _sendFileStorageService ;
private readonly IPasswordHasher < User > _passwordHasher ;
2021-01-22 16:16:40 -05:00
private readonly IPushNotificationService _pushService ;
2021-02-25 13:40:26 -05:00
private readonly IReferenceEventService _referenceEventService ;
2020-11-02 15:55:49 -05:00
private readonly GlobalSettings _globalSettings ;
2021-02-04 12:54:21 -06:00
private readonly ICurrentContext _currentContext ;
2021-02-25 13:40:26 -05:00
private const long _fileSizeLeeway = 1024L * 1024L ; // 1MB
2022-08-29 16:06:55 -04:00
2020-11-02 15:55:49 -05:00
public SendService (
2021-02-25 13:40:26 -05:00
ISendRepository sendRepository ,
2020-11-02 15:55:49 -05:00
IUserRepository userRepository ,
IUserService userService ,
IOrganizationRepository organizationRepository ,
2021-02-25 13:40:26 -05:00
ISendFileStorageService sendFileStorageService ,
IPasswordHasher < User > passwordHasher ,
2020-11-02 15:55:49 -05:00
IPushNotificationService pushService ,
2021-02-25 13:40:26 -05:00
IReferenceEventService referenceEventService ,
2020-11-02 15:55:49 -05:00
GlobalSettings globalSettings ,
2021-02-04 12:54:21 -06:00
IPolicyRepository policyRepository ,
ICurrentContext currentContext )
2022-08-29 16:06:55 -04:00
{
2020-11-02 15:55:49 -05:00
_sendRepository = sendRepository ;
_userRepository = userRepository ;
_userService = userService ;
2021-02-04 12:54:21 -06:00
_policyRepository = policyRepository ;
2021-01-22 16:16:40 -05:00
_organizationRepository = organizationRepository ;
_sendFileStorageService = sendFileStorageService ;
_passwordHasher = passwordHasher ;
_pushService = pushService ;
_referenceEventService = referenceEventService ;
2020-11-02 15:55:49 -05:00
_globalSettings = globalSettings ;
2021-01-22 16:16:40 -05:00
_currentContext = currentContext ;
2022-08-29 16:06:55 -04:00
}
2021-03-21 23:01:19 -05:00
public async Task SaveSendAsync ( Send send )
2020-11-02 15:55:49 -05:00
{
// Make sure user can save Sends
2021-01-22 16:16:40 -05:00
await ValidateUserCanSaveAsync ( send . UserId , send ) ;
2022-08-29 16:06:55 -04:00
2021-02-25 13:40:26 -05:00
if ( send . Id = = default ( Guid ) )
2020-11-02 15:55:49 -05:00
{
await _sendRepository . CreateAsync ( send ) ;
2021-01-22 16:16:40 -05:00
await _pushService . PushSyncSendCreateAsync ( send ) ;
await RaiseReferenceEventAsync ( send , ReferenceEventType . SendCreated ) ;
2020-11-02 15:55:49 -05:00
}
2021-03-21 23:01:19 -05:00
else
2020-11-02 15:55:49 -05:00
{
send . RevisionDate = DateTime . UtcNow ;
2021-03-01 15:01:04 -06:00
await _sendRepository . UpsertAsync ( send ) ;
await _pushService . PushSyncSendUpdateAsync ( send ) ;
2020-11-02 15:55:49 -05:00
}
2022-08-29 16:06:55 -04:00
}
2020-11-02 15:55:49 -05:00
2021-03-21 23:01:19 -05:00
public async Task < string > SaveFileSendAsync ( Send send , SendFileData data , long fileLength )
2022-08-29 16:06:55 -04:00
{
2020-11-02 15:55:49 -05:00
if ( send . Type ! = SendType . File )
{
throw new BadRequestException ( "Send is not of type \"file\"." ) ;
}
if ( fileLength < 1 )
2022-08-29 15:53:48 -04:00
{
2020-11-02 15:55:49 -05:00
throw new BadRequestException ( "No file data." ) ;
2022-08-29 15:53:48 -04:00
}
2020-11-02 15:55:49 -05:00
2021-03-21 23:01:19 -05:00
var storageBytesRemaining = await StorageRemainingForSendAsync ( send ) ;
2022-01-21 09:36:25 -05:00
if ( storageBytesRemaining < fileLength )
2022-08-29 14:53:16 -04:00
{
2022-01-21 09:36:25 -05:00
throw new BadRequestException ( "Not enough storage available." ) ;
2022-08-29 14:53:16 -04:00
}
2021-03-21 23:01:19 -05:00
2021-08-17 09:37:00 -04:00
var fileId = Utilities . CoreHelpers . SecureRandomString ( 32 , upper : false , special : false ) ;
2022-08-29 15:53:48 -04:00
try
{
2020-11-02 15:55:49 -05:00
data . Id = fileId ;
2021-03-21 23:01:19 -05:00
data . Size = fileLength ;
data . Validated = false ;
send . Data = JsonSerializer . Serialize ( data ,
2022-01-21 09:36:25 -05:00
JsonHelpers . IgnoreWritingNull ) ;
2020-11-02 15:55:49 -05:00
await SaveSendAsync ( send ) ;
2021-03-01 15:01:04 -06:00
return await _sendFileStorageService . GetSendFileUploadUrlAsync ( send , fileId ) ;
2022-08-29 15:53:48 -04:00
}
2022-08-29 16:06:55 -04:00
catch
2021-03-21 23:01:19 -05:00
{
// Clean up since this is not transactional
await _sendFileStorageService . DeleteFileAsync ( send , fileId ) ;
throw ;
2022-08-29 16:06:55 -04:00
}
2022-08-29 15:53:48 -04:00
}
2022-08-29 14:53:16 -04:00
2021-03-21 23:01:19 -05:00
public async Task UploadFileToExistingSendAsync ( Stream stream , Send send )
2022-08-29 16:06:55 -04:00
{
2021-03-21 23:01:19 -05:00
if ( send ? . Data = = null )
2022-08-29 16:06:55 -04:00
{
2021-03-21 23:01:19 -05:00
throw new BadRequestException ( "Send does not have file data" ) ;
2022-08-29 16:06:55 -04:00
}
2021-03-21 23:01:19 -05:00
if ( send . Type ! = SendType . File )
2022-08-29 14:53:16 -04:00
{
2021-03-21 23:01:19 -05:00
throw new BadRequestException ( "Not a File Type Send." ) ;
2022-08-29 15:53:48 -04:00
}
2021-03-21 23:01:19 -05:00
var data = JsonSerializer . Deserialize < SendFileData > ( send . Data ) ;
if ( data . Validated )
{
2022-01-21 09:36:25 -05:00
throw new BadRequestException ( "File has already been uploaded." ) ;
2022-08-29 14:53:16 -04:00
}
2021-03-21 23:01:19 -05:00
await _sendFileStorageService . UploadNewFileAsync ( stream , send , data . Id ) ;
2022-08-29 16:06:55 -04:00
2021-03-21 23:01:19 -05:00
if ( ! await ValidateSendFile ( send ) )
2022-08-29 14:53:16 -04:00
{
2022-01-21 09:36:25 -05:00
throw new BadRequestException ( "File received does not match expected file length." ) ;
2022-08-29 16:06:55 -04:00
}
}
2021-03-21 23:01:19 -05:00
2021-03-01 15:01:04 -06:00
public async Task < bool > ValidateSendFile ( Send send )
2022-08-29 16:06:55 -04:00
{
2021-03-01 15:01:04 -06:00
var fileData = JsonSerializer . Deserialize < SendFileData > ( send . Data ) ;
2022-08-29 14:53:16 -04:00
2022-01-21 09:36:25 -05:00
var ( valid , realSize ) = await _sendFileStorageService . ValidateFileAsync ( send , fileData . Id , fileData . Size , _fileSizeLeeway ) ;
2020-11-02 15:55:49 -05:00
if ( ! valid | | realSize > MAX_FILE_SIZE )
2022-08-29 15:53:48 -04:00
{
2020-11-02 15:55:49 -05:00
// File reported differs in size from that promised. Must be a rogue client. Delete Send
await DeleteSendAsync ( send ) ;
2021-01-22 16:16:40 -05:00
return false ;
2022-08-29 16:06:55 -04:00
}
2022-08-29 15:53:48 -04:00
2020-11-02 15:55:49 -05:00
// Update Send data if necessary
if ( realSize ! = fileData . Size )
2022-08-29 16:06:55 -04:00
{
2020-11-02 15:55:49 -05:00
fileData . Size = realSize . Value ;
2022-08-29 14:53:16 -04:00
}
2020-11-02 15:55:49 -05:00
fileData . Validated = true ;
send . Data = JsonSerializer . Serialize ( fileData ,
JsonHelpers . IgnoreWritingNull ) ;
await SaveSendAsync ( send ) ;
2022-08-29 15:53:48 -04:00
2021-03-01 15:01:04 -06:00
return valid ;
2022-08-29 16:06:55 -04:00
}
2020-11-02 15:55:49 -05:00
public async Task DeleteSendAsync ( Send send )
2022-08-29 16:06:55 -04:00
{
2020-11-02 15:55:49 -05:00
await _sendRepository . DeleteAsync ( send ) ;
if ( send . Type = = Enums . SendType . File )
2022-08-29 14:53:16 -04:00
{
2020-11-02 15:55:49 -05:00
var data = JsonSerializer . Deserialize < SendFileData > ( send . Data ) ;
await _sendFileStorageService . DeleteFileAsync ( send , data . Id ) ;
2022-08-29 15:53:48 -04:00
}
2020-11-02 15:55:49 -05:00
await _pushService . PushSyncSendDeleteAsync ( send ) ;
2022-08-29 16:06:55 -04:00
}
2022-08-29 15:53:48 -04:00
2020-11-02 15:55:49 -05:00
public ( bool grant , bool passwordRequiredError , bool passwordInvalidError ) SendCanBeAccessed ( Send send ,
string password )
2022-08-29 16:06:55 -04:00
{
2020-11-02 15:55:49 -05:00
var now = DateTime . UtcNow ;
if ( send = = null | | send . MaxAccessCount . GetValueOrDefault ( int . MaxValue ) < = send . AccessCount | |
send . ExpirationDate . GetValueOrDefault ( DateTime . MaxValue ) < now | | send . Disabled | |
send . DeletionDate < now )
2022-08-29 15:53:48 -04:00
{
2020-11-02 15:55:49 -05:00
return ( false , false , false ) ;
2022-08-29 16:06:55 -04:00
}
2020-11-02 15:55:49 -05:00
if ( ! string . IsNullOrWhiteSpace ( send . Password ) )
2022-08-29 16:06:55 -04:00
{
2020-11-02 15:55:49 -05:00
if ( string . IsNullOrWhiteSpace ( password ) )
2022-08-29 16:06:55 -04:00
{
2020-11-02 15:55:49 -05:00
return ( false , true , false ) ;
2022-08-29 16:06:55 -04:00
}
2020-11-02 15:55:49 -05:00
var passwordResult = _passwordHasher . VerifyHashedPassword ( new User ( ) , send . Password , password ) ;
if ( passwordResult = = PasswordVerificationResult . SuccessRehashNeeded )
{
2021-03-01 15:01:04 -06:00
send . Password = HashPassword ( password ) ;
2020-11-02 15:55:49 -05:00
}
if ( passwordResult = = PasswordVerificationResult . Failed )
{
2021-03-01 15:01:04 -06:00
return ( false , false , true ) ;
2020-11-02 15:55:49 -05:00
}
2022-08-29 16:06:55 -04:00
}
2021-03-01 15:01:04 -06:00
return ( true , false , false ) ;
2022-08-29 16:06:55 -04:00
}
2021-03-01 15:01:04 -06:00
// Response: Send, password required, password invalid
public async Task < ( string , bool , bool ) > GetSendFileDownloadUrlAsync ( Send send , string fileId , string password )
2022-08-29 16:06:55 -04:00
{
2021-03-01 15:01:04 -06:00
if ( send . Type ! = SendType . File )
2022-08-29 16:06:55 -04:00
{
2021-03-01 15:01:04 -06:00
throw new BadRequestException ( "Can only get a download URL for a file type of Send" ) ;
}
var ( grantAccess , passwordRequired , passwordInvalid ) = SendCanBeAccessed ( send , password ) ;
2022-08-29 16:06:55 -04:00
2021-03-01 15:01:04 -06:00
if ( ! grantAccess )
{
return ( null , passwordRequired , passwordInvalid ) ;
2022-08-29 14:53:16 -04:00
}
2021-03-01 15:01:04 -06:00
send . AccessCount + + ;
await _sendRepository . ReplaceAsync ( send ) ;
await _pushService . PushSyncSendUpdateAsync ( send ) ;
return ( await _sendFileStorageService . GetSendFileDownloadUrlAsync ( send , fileId ) , false , false ) ;
2022-08-29 16:06:55 -04:00
}
2021-03-01 15:01:04 -06:00
// Response: Send, password required, password invalid
public async Task < ( Send , bool , bool ) > AccessAsync ( Guid sendId , string password )
2022-08-29 16:06:55 -04:00
{
2021-03-01 15:01:04 -06:00
var send = await _sendRepository . GetByIdAsync ( sendId ) ;
var ( grantAccess , passwordRequired , passwordInvalid ) = SendCanBeAccessed ( send , password ) ;
if ( ! grantAccess )
2022-08-29 16:06:55 -04:00
{
2021-03-01 15:01:04 -06:00
return ( null , passwordRequired , passwordInvalid ) ;
2022-08-29 14:53:16 -04:00
}
2021-03-01 15:01:04 -06:00
// TODO: maybe move this to a simple ++ sproc?
if ( send . Type ! = SendType . File )
2022-08-29 14:53:16 -04:00
{
2021-03-01 15:01:04 -06:00
// File sends are incremented during file download
2021-02-25 13:40:26 -05:00
send . AccessCount + + ;
2022-08-29 16:06:55 -04:00
}
2021-03-01 15:01:04 -06:00
2020-11-02 15:55:49 -05:00
await _sendRepository . ReplaceAsync ( send ) ;
2021-02-25 13:40:26 -05:00
await _pushService . PushSyncSendUpdateAsync ( send ) ;
2020-11-02 15:55:49 -05:00
await RaiseReferenceEventAsync ( send , ReferenceEventType . SendAccessed ) ;
return ( send , false , false ) ;
}
2021-02-25 13:40:26 -05:00
private async Task RaiseReferenceEventAsync ( Send send , ReferenceEventType eventType )
2022-08-29 16:06:55 -04:00
{
2020-11-02 15:55:49 -05:00
await _referenceEventService . RaiseEventAsync ( new ReferenceEvent
2022-08-29 16:06:55 -04:00
{
2021-02-25 13:40:26 -05:00
Id = send . UserId ? ? default ,
Type = eventType ,
Source = ReferenceEventSource . User ,
SendType = send . Type ,
2021-03-01 15:01:04 -06:00
MaxAccessCount = send . MaxAccessCount ,
2020-11-02 15:55:49 -05:00
HasPassword = ! string . IsNullOrWhiteSpace ( send . Password ) ,
2021-03-01 15:01:04 -06:00
} ) ;
2022-08-29 15:53:48 -04:00
}
2021-02-25 13:40:26 -05:00
public string HashPassword ( string password )
2022-08-29 16:06:55 -04:00
{
2021-02-25 13:40:26 -05:00
return _passwordHasher . HashPassword ( new User ( ) , password ) ;
}
2020-11-02 15:55:49 -05:00
private async Task ValidateUserCanSaveAsync ( Guid ? userId , Send send )
2022-08-29 16:06:55 -04:00
{
2020-11-02 15:55:49 -05:00
if ( ! userId . HasValue | | ( ! _currentContext . Organizations ? . Any ( ) ? ? true ) )
{
return ;
}
2021-02-04 12:54:21 -06:00
2021-03-29 07:56:56 +10:00
var disableSendPolicyCount = await _policyRepository . GetCountByTypeApplicableToUserIdAsync ( userId . Value ,
PolicyType . DisableSend ) ;
if ( disableSendPolicyCount > 0 )
2021-02-04 12:54:21 -06:00
{
throw new BadRequestException ( "Due to an Enterprise Policy, you are only able to delete an existing Send." ) ;
2022-08-29 14:53:16 -04:00
}
2021-02-04 12:54:21 -06:00
2021-09-28 06:54:28 +10:00
if ( send . HideEmail . GetValueOrDefault ( ) )
2022-08-29 14:53:16 -04:00
{
2021-09-28 06:54:28 +10:00
var sendOptionsPolicies = await _policyRepository . GetManyByTypeApplicableToUserIdAsync ( userId . Value , PolicyType . SendOptions ) ;
if ( sendOptionsPolicies . Any ( p = > p . GetDataModel < SendOptionsPolicyData > ( ) ? . DisableHideEmail ? ? false ) )
2021-02-04 12:54:21 -06:00
{
2021-09-28 06:54:28 +10:00
throw new BadRequestException ( "Due to an Enterprise Policy, you are not allowed to hide your email address from recipients when creating or editing a Send." ) ;
2021-02-04 12:54:21 -06:00
}
2022-08-29 16:06:55 -04:00
}
}
2021-03-29 07:56:56 +10:00
private async Task < long > StorageRemainingForSendAsync ( Send send )
2022-08-29 16:06:55 -04:00
{
2021-03-29 07:56:56 +10:00
var storageBytesRemaining = 0L ;
2021-03-21 23:01:19 -05:00
if ( send . UserId . HasValue )
2022-08-29 16:06:55 -04:00
{
2021-03-29 07:56:56 +10:00
var user = await _userRepository . GetByIdAsync ( send . UserId . Value ) ;
if ( ! await _userService . CanAccessPremium ( user ) )
{
2022-06-08 08:44:28 -05:00
throw new BadRequestException ( "You must have premium status to use file Sends." ) ;
2021-03-29 07:56:56 +10:00
}
2021-03-21 23:01:19 -05:00
if ( ! user . EmailVerified )
{
throw new BadRequestException ( "You must confirm your email to use file Sends." ) ;
2022-08-29 14:53:16 -04:00
}
2022-08-29 15:53:48 -04:00
2021-03-21 23:01:19 -05:00
if ( user . Premium )
{
storageBytesRemaining = user . StorageBytesRemaining ( ) ;
}
2021-02-25 13:40:26 -05:00
else
2022-08-29 15:53:48 -04:00
{
2021-03-21 23:01:19 -05:00
// Users that get access to file storage/premium from their organization get the default
// 1 GB max storage.
storageBytesRemaining = user . StorageBytesRemaining (
_globalSettings . SelfHosted ? ( short ) 10240 : ( short ) 1 ) ;
2022-08-29 16:06:55 -04:00
}
}
2021-02-25 13:40:26 -05:00
else if ( send . OrganizationId . HasValue )
2022-08-29 16:06:55 -04:00
{
2021-03-21 23:01:19 -05:00
var org = await _organizationRepository . GetByIdAsync ( send . OrganizationId . Value ) ;
if ( ! org . MaxStorageGb . HasValue )
2022-08-29 15:53:48 -04:00
{
2021-09-28 06:54:28 +10:00
throw new BadRequestException ( "This organization cannot use file sends." ) ;
2021-03-21 23:01:19 -05:00
}
2022-08-29 14:53:16 -04:00
2021-03-21 23:01:19 -05:00
storageBytesRemaining = org . StorageBytesRemaining ( ) ;
2022-08-29 15:53:48 -04:00
}
2022-08-29 16:06:55 -04:00
2021-03-21 23:01:19 -05:00
return storageBytesRemaining ;
2020-11-02 15:55:49 -05:00
}
}