2021-01-25 21:27:38 +01:00
using System ;
using System.Collections.Generic ;
using System.Linq ;
2021-03-29 16:45:04 +02:00
using System.Net ;
2021-01-25 21:27:38 +01:00
using System.Net.Http ;
using System.Threading.Tasks ;
using Bit.Core.Abstractions ;
using Bit.Core.Enums ;
2021-03-29 16:45:04 +02:00
using Bit.Core.Exceptions ;
2021-01-25 21:27:38 +01:00
using Bit.Core.Models.Data ;
using Bit.Core.Models.Domain ;
using Bit.Core.Models.Request ;
using Bit.Core.Models.Response ;
using Bit.Core.Models.View ;
using Newtonsoft.Json ;
namespace Bit.Core.Services
{
public class SendService : ISendService
{
private List < SendView > _decryptedSendsCache ;
private readonly ICryptoService _cryptoService ;
private readonly IUserService _userService ;
private readonly IApiService _apiService ;
private readonly IStorageService _storageService ;
private readonly II18nService _i18nService ;
private readonly ICryptoFunctionService _cryptoFunctionService ;
private Task < List < SendView > > _getAllDecryptedTask ;
2021-03-29 16:45:04 +02:00
private readonly IFileUploadService _fileUploadService ;
2021-01-25 21:27:38 +01:00
public SendService (
ICryptoService cryptoService ,
IUserService userService ,
IApiService apiService ,
2021-03-29 16:45:04 +02:00
IFileUploadService fileUploadService ,
2021-01-25 21:27:38 +01:00
IStorageService storageService ,
II18nService i18nService ,
ICryptoFunctionService cryptoFunctionService )
{
_cryptoService = cryptoService ;
_userService = userService ;
_apiService = apiService ;
2021-03-29 16:45:04 +02:00
_fileUploadService = fileUploadService ;
2021-01-25 21:27:38 +01:00
_storageService = storageService ;
_i18nService = i18nService ;
_cryptoFunctionService = cryptoFunctionService ;
}
public static string GetSendKey ( string userId ) = > string . Format ( "sends_{0}" , userId ) ;
public async Task ClearAsync ( string userId )
{
await _storageService . RemoveAsync ( GetSendKey ( userId ) ) ;
ClearCache ( ) ;
}
public void ClearCache ( ) = > _decryptedSendsCache = null ;
public async Task DeleteAsync ( params string [ ] ids )
{
var userId = await _userService . GetUserIdAsync ( ) ;
var sends = await _storageService . GetAsync < Dictionary < string , SendData > > ( GetSendKey ( userId ) ) ;
if ( sends = = null )
{
return ;
}
foreach ( var id in ids )
{
sends . Remove ( id ) ;
}
await _storageService . SaveAsync ( GetSendKey ( userId ) , sends ) ;
ClearCache ( ) ;
}
public async Task DeleteWithServerAsync ( string id )
{
await _apiService . DeleteSendAsync ( id ) ;
await DeleteAsync ( id ) ;
}
2021-02-11 01:50:10 +01:00
public async Task < ( Send send , byte [ ] encryptedFileData ) > EncryptAsync ( SendView model , byte [ ] fileData ,
2021-01-25 21:27:38 +01:00
string password , SymmetricCryptoKey key = null )
{
if ( model . Key = = null )
{
model . Key = _cryptoFunctionService . RandomBytes ( 16 ) ;
model . CryptoKey = await _cryptoService . MakeSendKeyAsync ( model . Key ) ;
}
var send = new Send
{
Id = model . Id ,
Type = model . Type ,
Disabled = model . Disabled ,
2021-02-11 01:50:10 +01:00
DeletionDate = model . DeletionDate ,
ExpirationDate = model . ExpirationDate ,
2021-01-25 21:27:38 +01:00
MaxAccessCount = model . MaxAccessCount ,
Key = await _cryptoService . EncryptAsync ( model . Key , key ) ,
Name = await _cryptoService . EncryptAsync ( model . Name , model . CryptoKey ) ,
Notes = await _cryptoService . EncryptAsync ( model . Notes , model . CryptoKey ) ,
} ;
2021-02-11 01:50:10 +01:00
byte [ ] encryptedFileData = null ;
2021-01-25 21:27:38 +01:00
if ( password ! = null )
{
var passwordHash = await _cryptoFunctionService . Pbkdf2Async ( password , model . Key ,
2021-03-13 18:40:41 +01:00
CryptoHashAlgorithm . Sha256 , 100000 ) ;
2021-01-25 21:27:38 +01:00
send . Password = Convert . ToBase64String ( passwordHash ) ;
}
switch ( send . Type )
{
case SendType . Text :
send . Text = new SendText
{
Text = await _cryptoService . EncryptAsync ( model . Text . Text , model . CryptoKey ) ,
Hidden = model . Text . Hidden
} ;
break ;
case SendType . File :
send . File = new SendFile ( ) ;
if ( fileData ! = null )
{
send . File . FileName = await _cryptoService . EncryptAsync ( model . File . FileName , model . CryptoKey ) ;
2021-02-11 01:50:10 +01:00
encryptedFileData = await _cryptoService . EncryptToBytesAsync ( fileData , model . CryptoKey ) ;
2021-01-25 21:27:38 +01:00
}
break ;
default :
break ;
}
return ( send , encryptedFileData ) ;
}
public async Task < List < Send > > GetAllAsync ( )
{
var userId = await _userService . GetUserIdAsync ( ) ;
var sends = await _storageService . GetAsync < Dictionary < string , SendData > > ( GetSendKey ( userId ) ) ;
2021-02-11 01:50:10 +01:00
return sends ? . Select ( kvp = > new Send ( kvp . Value ) ) . ToList ( ) ? ? new List < Send > ( ) ;
2021-01-25 21:27:38 +01:00
}
public async Task < List < SendView > > GetAllDecryptedAsync ( )
{
if ( _decryptedSendsCache ! = null )
{
return _decryptedSendsCache ;
}
var hasKey = await _cryptoService . HasKeyAsync ( ) ;
if ( ! hasKey )
{
throw new Exception ( "No Key." ) ;
}
if ( _getAllDecryptedTask ! = null & & ! _getAllDecryptedTask . IsCompleted & & ! _getAllDecryptedTask . IsFaulted )
{
return await _getAllDecryptedTask ;
}
async Task < List < SendView > > doTask ( )
{
var decSends = new List < SendView > ( ) ;
async Task decryptAndAddSendAsync ( Send send ) = > decSends . Add ( await send . DecryptAsync ( ) ) ;
await Task . WhenAll ( ( await GetAllAsync ( ) ) . Select ( s = > decryptAndAddSendAsync ( s ) ) ) ;
2021-02-11 01:50:10 +01:00
decSends = decSends . OrderBy ( s = > s , new SendLocaleComparer ( _i18nService ) ) . ToList ( ) ;
2021-01-25 21:27:38 +01:00
_decryptedSendsCache = decSends ;
return _decryptedSendsCache ;
}
_getAllDecryptedTask = doTask ( ) ;
return await _getAllDecryptedTask ;
}
public async Task < Send > GetAsync ( string id )
{
var userId = await _userService . GetUserIdAsync ( ) ;
var sends = await _storageService . GetAsync < Dictionary < string , SendData > > ( GetSendKey ( userId ) ) ;
if ( sends = = null | | ! sends . ContainsKey ( id ) )
{
return null ;
}
return new Send ( sends [ id ] ) ;
}
public async Task ReplaceAsync ( Dictionary < string , SendData > sends )
{
var userId = await _userService . GetUserIdAsync ( ) ;
await _storageService . SaveAsync ( GetSendKey ( userId ) , sends ) ;
_decryptedSendsCache = null ;
}
2021-02-11 01:50:10 +01:00
public async Task < string > SaveWithServerAsync ( Send send , byte [ ] encryptedFileData )
2021-01-25 21:27:38 +01:00
{
2021-03-02 17:09:26 +01:00
var request = new SendRequest ( send , encryptedFileData ? . LongLength ) ;
2021-03-29 16:45:04 +02:00
SendResponse response = default ;
2021-01-25 21:27:38 +01:00
if ( send . Id = = null )
{
switch ( send . Type )
{
case SendType . Text :
response = await _apiService . PostSendAsync ( request ) ;
break ;
case SendType . File :
2021-03-29 16:45:04 +02:00
try {
var uploadDataResponse = await _apiService . PostFileTypeSendAsync ( request ) ;
response = uploadDataResponse . SendResponse ;
2021-01-25 21:27:38 +01:00
2021-03-29 16:45:04 +02:00
await _fileUploadService . UploadSendFileAsync ( uploadDataResponse , send . File . FileName , encryptedFileData ) ;
}
catch ( ApiException e ) when ( e . Error . StatusCode = = HttpStatusCode . NotFound )
{
response = await LegacyServerSendFileUpload ( request , send , encryptedFileData ) ;
}
catch ( Exception e )
{
if ( response ! = default ) {
await _apiService . DeleteSendAsync ( response . Id ) ;
}
throw e ;
}
2021-01-25 21:27:38 +01:00
break ;
default :
throw new NotImplementedException ( $"Cannot save unknown Send type {send.Type}" ) ;
}
send . Id = response . Id ;
}
else
{
response = await _apiService . PutSendAsync ( send . Id , request ) ;
}
var userId = await _userService . GetUserIdAsync ( ) ;
await UpsertAsync ( new SendData ( response , userId ) ) ;
2021-02-11 01:50:10 +01:00
return response . Id ;
2021-01-25 21:27:38 +01:00
}
2021-03-29 16:45:04 +02:00
[Obsolete("Mar 25 2021: This method has been deprecated in favor of direct uploads. This method still exists for backward compatibility with old server versions.")]
private async Task < SendResponse > LegacyServerSendFileUpload ( SendRequest request , Send send , byte [ ] encryptedFileData ) {
var fd = new MultipartFormDataContent ( $"--BWMobileFormBoundary{DateTime.UtcNow.Ticks}" )
{
{ new StringContent ( JsonConvert . SerializeObject ( request ) ) , "model" } ,
{ new ByteArrayContent ( encryptedFileData ) , "data" , send . File . FileName . EncryptedString }
} ;
return await _apiService . PostSendFileAsync ( fd ) ;
}
2021-01-25 21:27:38 +01:00
public async Task UpsertAsync ( params SendData [ ] sends )
{
var userId = await _userService . GetUserIdAsync ( ) ;
var knownSends = await _storageService . GetAsync < Dictionary < string , SendData > > ( GetSendKey ( userId ) ) ? ?
new Dictionary < string , SendData > ( ) ;
foreach ( var send in sends )
{
knownSends [ send . Id ] = send ;
}
await _storageService . SaveAsync ( GetSendKey ( userId ) , knownSends ) ;
_decryptedSendsCache = null ;
}
public async Task RemovePasswordWithServerAsync ( string id )
{
var response = await _apiService . PutSendRemovePasswordAsync ( id ) ;
var userId = await _userService . GetUserIdAsync ( ) ;
await UpsertAsync ( new SendData ( response , userId ) ) ;
}
private class SendLocaleComparer : IComparer < SendView >
{
private readonly II18nService _i18nService ;
public SendLocaleComparer ( II18nService i18nService )
{
_i18nService = i18nService ;
}
public int Compare ( SendView a , SendView b )
{
var aName = a ? . Name ;
var bName = b ? . Name ;
if ( aName = = null & & bName ! = null )
{
return - 1 ;
}
if ( aName ! = null & & bName = = null )
{
return 1 ;
}
if ( aName = = null & & bName = = null )
{
return 0 ;
}
return _i18nService . StringComparer . Compare ( aName , bName ) ;
}
}
}
}