2019-04-12 17:06:47 +03:00
using Bit.Core.Abstractions ;
2019-04-15 18:46:20 +03:00
using Bit.Core.Enums ;
2019-04-17 00:34:59 +03:00
using Bit.Core.Exceptions ;
2019-04-15 18:46:20 +03:00
using Bit.Core.Models.Data ;
2019-04-12 17:06:47 +03:00
using Bit.Core.Models.Domain ;
2019-04-17 00:34:59 +03:00
using Bit.Core.Models.Request ;
using Bit.Core.Models.Response ;
2019-04-12 17:06:47 +03:00
using Bit.Core.Models.View ;
2019-04-16 14:59:53 +03:00
using Bit.Core.Utilities ;
2019-04-12 17:06:47 +03:00
using System ;
using System.Collections.Generic ;
2019-04-17 00:34:59 +03:00
using System.IO ;
2019-04-15 18:46:20 +03:00
using System.Linq ;
2019-04-17 00:34:59 +03:00
using System.Net.Http ;
2019-04-16 14:59:53 +03:00
using System.Text.RegularExpressions ;
2019-04-12 17:06:47 +03:00
using System.Threading.Tasks ;
namespace Bit.Core.Services
{
2019-04-17 06:30:52 +03:00
public class CipherService : ICipherService
2019-04-12 17:06:47 +03:00
{
private const string Keys_CiphersFormat = "ciphers_{0}" ;
private const string Keys_LocalData = "ciphersLocalData" ;
private const string Keys_NeverDomains = "neverDomains" ;
2019-04-16 14:59:53 +03:00
private readonly string [ ] _ignoredSearchTerms = new string [ ] { "com" , "net" , "org" , "android" ,
"io" , "co" , "uk" , "au" , "nz" , "fr" , "de" , "tv" , "info" , "app" , "apps" , "eu" , "me" , "dev" , "jp" , "mobile" } ;
2019-04-12 17:06:47 +03:00
private List < CipherView > _decryptedCipherCache ;
private readonly ICryptoService _cryptoService ;
private readonly IUserService _userService ;
private readonly ISettingsService _settingsService ;
private readonly IApiService _apiService ;
2021-03-31 02:42:43 +03:00
private readonly IFileUploadService _fileUploadService ;
2019-04-12 17:06:47 +03:00
private readonly IStorageService _storageService ;
2019-04-17 00:34:59 +03:00
private readonly II18nService _i18nService ;
2019-04-24 21:52:26 +03:00
private readonly Func < ISearchService > _searchService ;
2020-10-13 22:39:36 +03:00
private readonly string _clearCipherCacheKey ;
private readonly string [ ] _allClearCipherCacheKeys ;
2019-04-12 17:06:47 +03:00
private Dictionary < string , HashSet < string > > _domainMatchBlacklist = new Dictionary < string , HashSet < string > >
{
["google.com"] = new HashSet < string > { "script.google.com" }
} ;
2019-04-17 00:34:59 +03:00
private readonly HttpClient _httpClient = new HttpClient ( ) ;
2019-04-19 23:38:20 +03:00
private Task < List < CipherView > > _getAllDecryptedTask ;
2019-04-12 17:06:47 +03:00
public CipherService (
ICryptoService cryptoService ,
IUserService userService ,
ISettingsService settingsService ,
IApiService apiService ,
2021-03-31 02:42:43 +03:00
IFileUploadService fileUploadService ,
2019-04-12 17:06:47 +03:00
IStorageService storageService ,
2019-04-24 21:52:26 +03:00
II18nService i18nService ,
2020-10-13 22:39:36 +03:00
Func < ISearchService > searchService ,
string clearCipherCacheKey ,
string [ ] allClearCipherCacheKeys )
2019-04-12 17:06:47 +03:00
{
_cryptoService = cryptoService ;
_userService = userService ;
_settingsService = settingsService ;
_apiService = apiService ;
2021-03-31 02:42:43 +03:00
_fileUploadService = fileUploadService ;
2019-04-12 17:06:47 +03:00
_storageService = storageService ;
2019-04-17 00:34:59 +03:00
_i18nService = i18nService ;
2019-04-24 21:52:26 +03:00
_searchService = searchService ;
2020-10-13 22:39:36 +03:00
_clearCipherCacheKey = clearCipherCacheKey ;
_allClearCipherCacheKeys = allClearCipherCacheKeys ;
2019-04-12 17:06:47 +03:00
}
private List < CipherView > DecryptedCipherCache
{
get = > _decryptedCipherCache ;
set
{
2020-03-28 16:16:28 +03:00
if ( value = = null )
2019-04-12 17:06:47 +03:00
{
2019-05-07 20:58:50 +03:00
_decryptedCipherCache ? . Clear ( ) ;
2019-04-12 17:06:47 +03:00
}
_decryptedCipherCache = value ;
2020-03-28 16:16:28 +03:00
if ( _searchService ! = null )
2019-04-24 21:52:26 +03:00
{
2020-03-28 16:16:28 +03:00
if ( value = = null )
2019-04-24 21:52:26 +03:00
{
_searchService ( ) . ClearIndex ( ) ;
}
else
{
_searchService ( ) . IndexCiphersAsync ( ) ;
}
}
2019-04-12 17:06:47 +03:00
}
}
2020-10-13 22:39:36 +03:00
public async Task ClearCacheAsync ( )
2019-04-12 17:06:47 +03:00
{
DecryptedCipherCache = null ;
2020-10-13 22:39:36 +03:00
if ( _allClearCipherCacheKeys ! = null & & _allClearCipherCacheKeys . Length > 0 )
{
foreach ( var key in _allClearCipherCacheKeys )
{
await _storageService . SaveAsync ( key , true ) ;
}
}
2019-04-12 17:06:47 +03:00
}
2019-04-17 00:34:59 +03:00
public async Task < Cipher > EncryptAsync ( CipherView model , SymmetricCryptoKey key = null ,
2019-04-12 17:06:47 +03:00
Cipher originalCipher = null )
{
// Adjust password history
2020-03-28 16:16:28 +03:00
if ( model . Id ! = null )
2019-04-12 17:06:47 +03:00
{
2020-03-28 16:16:28 +03:00
if ( originalCipher = = null )
2019-04-15 18:46:20 +03:00
{
originalCipher = await GetAsync ( model . Id ) ;
}
2020-03-28 16:16:28 +03:00
if ( originalCipher ! = null )
2019-04-15 18:46:20 +03:00
{
var existingCipher = await originalCipher . DecryptAsync ( ) ;
2020-03-28 16:16:28 +03:00
if ( model . PasswordHistory = = null )
2019-04-15 18:46:20 +03:00
{
model . PasswordHistory = new List < PasswordHistoryView > ( ) ;
}
2020-03-28 16:16:28 +03:00
if ( model . Type = = CipherType . Login & & existingCipher . Type = = CipherType . Login )
2019-04-15 18:46:20 +03:00
{
2020-03-28 16:16:28 +03:00
if ( ! string . IsNullOrWhiteSpace ( existingCipher . Login . Password ) & &
2019-04-15 18:46:20 +03:00
existingCipher . Login . Password ! = model . Login . Password )
{
var now = DateTime . UtcNow ;
var ph = new PasswordHistoryView
{
Password = existingCipher . Login . Password ,
LastUsedDate = now
} ;
model . Login . PasswordRevisionDate = now ;
model . PasswordHistory . Insert ( 0 , ph ) ;
}
else
{
2019-05-07 20:58:50 +03:00
model . Login . PasswordRevisionDate = existingCipher . Login . PasswordRevisionDate ;
2019-04-15 18:46:20 +03:00
}
}
2020-03-28 16:16:28 +03:00
if ( existingCipher . HasFields )
2019-04-15 18:46:20 +03:00
{
var existingHiddenFields = existingCipher . Fields . Where ( f = >
f . Type = = FieldType . Hidden & & ! string . IsNullOrWhiteSpace ( f . Name ) & &
! string . IsNullOrWhiteSpace ( f . Value ) ) ;
var hiddenFields = model . Fields ? . Where ( f = >
f . Type = = FieldType . Hidden & & ! string . IsNullOrWhiteSpace ( f . Name ) ) ? ?
new List < FieldView > ( ) ;
2020-03-28 16:16:28 +03:00
foreach ( var ef in existingHiddenFields )
2019-04-15 18:46:20 +03:00
{
var matchedField = hiddenFields . FirstOrDefault ( f = > f . Name = = ef . Name ) ;
2020-03-28 16:16:28 +03:00
if ( matchedField = = null | | matchedField . Value ! = ef . Value )
2019-04-15 18:46:20 +03:00
{
var ph = new PasswordHistoryView
{
Password = string . Format ( "{0}: {1}" , ef . Name , ef . Value ) ,
LastUsedDate = DateTime . UtcNow
} ;
model . PasswordHistory . Insert ( 0 , ph ) ;
}
}
}
}
2020-03-28 16:16:28 +03:00
if ( ! model . PasswordHistory ? . Any ( ) ? ? false )
2019-04-15 18:46:20 +03:00
{
model . PasswordHistory = null ;
}
2020-03-28 16:16:28 +03:00
else if ( model . PasswordHistory ! = null & & model . PasswordHistory . Count > 5 )
2019-04-15 18:46:20 +03:00
{
model . PasswordHistory = model . PasswordHistory . Take ( 5 ) . ToList ( ) ;
}
2019-04-12 17:06:47 +03:00
}
2019-04-15 18:46:20 +03:00
var cipher = new Cipher
{
Id = model . Id ,
FolderId = model . FolderId ,
Favorite = model . Favorite ,
OrganizationId = model . OrganizationId ,
Type = model . Type ,
2020-11-23 23:41:43 +03:00
CollectionIds = model . CollectionIds ,
RevisionDate = model . RevisionDate
2019-04-15 18:46:20 +03:00
} ;
2019-04-12 17:06:47 +03:00
2020-03-28 16:16:28 +03:00
if ( key = = null & & cipher . OrganizationId ! = null )
2019-04-12 17:06:47 +03:00
{
key = await _cryptoService . GetOrgKeyAsync ( cipher . OrganizationId ) ;
2020-03-28 16:16:28 +03:00
if ( key = = null )
2019-04-12 17:06:47 +03:00
{
throw new Exception ( "Cannot encrypt cipher for organization. No key." ) ;
}
}
2019-04-15 18:46:20 +03:00
var tasks = new List < Task >
2019-04-12 17:06:47 +03:00
{
2019-04-15 18:46:20 +03:00
EncryptObjPropertyAsync ( model , cipher , new HashSet < string >
{
"Name" ,
"Notes"
} , key ) ,
EncryptCipherDataAsync ( cipher , model , key ) ,
2019-04-19 23:15:29 +03:00
EncryptFieldsAsync ( model . Fields , key , cipher ) ,
EncryptPasswordHistoriesAsync ( model . PasswordHistory , key , cipher ) ,
EncryptAttachmentsAsync ( model . Attachments , key , cipher )
2019-04-15 18:46:20 +03:00
} ;
2019-04-12 17:06:47 +03:00
await Task . WhenAll ( tasks ) ;
return cipher ;
}
2019-04-15 18:46:20 +03:00
public async Task < Cipher > GetAsync ( string id )
{
var userId = await _userService . GetUserIdAsync ( ) ;
var localData = await _storageService . GetAsync < Dictionary < string , Dictionary < string , object > > > (
Keys_LocalData ) ;
var ciphers = await _storageService . GetAsync < Dictionary < string , CipherData > > (
string . Format ( Keys_CiphersFormat , userId ) ) ;
2020-03-28 16:16:28 +03:00
if ( ! ciphers ? . ContainsKey ( id ) ? ? true )
2019-04-15 18:46:20 +03:00
{
return null ;
}
return new Cipher ( ciphers [ id ] , false ,
localData ? . ContainsKey ( id ) ? ? false ? localData [ id ] : null ) ;
}
public async Task < List < Cipher > > GetAllAsync ( )
{
var userId = await _userService . GetUserIdAsync ( ) ;
var localData = await _storageService . GetAsync < Dictionary < string , Dictionary < string , object > > > (
Keys_LocalData ) ;
var ciphers = await _storageService . GetAsync < Dictionary < string , CipherData > > (
string . Format ( Keys_CiphersFormat , userId ) ) ;
2019-04-19 22:18:26 +03:00
var response = ciphers ? . Select ( c = > new Cipher ( c . Value , false ,
2019-04-15 18:46:20 +03:00
localData ? . ContainsKey ( c . Key ) ? ? false ? localData [ c . Key ] : null ) ) ;
2019-04-19 22:18:26 +03:00
return response ? . ToList ( ) ? ? new List < Cipher > ( ) ;
2019-04-15 18:46:20 +03:00
}
2020-10-13 22:39:36 +03:00
public async Task < List < CipherView > > GetAllDecryptedAsync ( )
2019-04-16 14:59:53 +03:00
{
2020-10-13 22:39:36 +03:00
if ( _clearCipherCacheKey ! = null )
{
var clearCache = await _storageService . GetAsync < bool? > ( _clearCipherCacheKey ) ;
if ( clearCache . GetValueOrDefault ( ) )
{
DecryptedCipherCache = null ;
await _storageService . RemoveAsync ( _clearCipherCacheKey ) ;
}
}
2020-03-28 16:16:28 +03:00
if ( DecryptedCipherCache ! = null )
2019-04-16 14:59:53 +03:00
{
2020-10-13 22:39:36 +03:00
return DecryptedCipherCache ;
2019-04-16 14:59:53 +03:00
}
2020-03-28 16:16:28 +03:00
if ( _getAllDecryptedTask ! = null & & ! _getAllDecryptedTask . IsCompleted & & ! _getAllDecryptedTask . IsFaulted )
2019-04-16 14:59:53 +03:00
{
2020-10-13 22:39:36 +03:00
return await _getAllDecryptedTask ;
2019-04-16 14:59:53 +03:00
}
2019-04-19 23:38:20 +03:00
async Task < List < CipherView > > doTask ( )
2019-04-19 23:15:29 +03:00
{
2019-04-19 23:38:20 +03:00
try
{
var hashKey = await _cryptoService . HasKeyAsync ( ) ;
2020-03-28 16:16:28 +03:00
if ( ! hashKey )
2019-04-19 23:38:20 +03:00
{
throw new Exception ( "No key." ) ;
}
var decCiphers = new List < CipherView > ( ) ;
async Task decryptAndAddCipherAsync ( Cipher cipher )
{
var c = await cipher . DecryptAsync ( ) ;
decCiphers . Add ( c ) ;
}
var tasks = new List < Task > ( ) ;
var ciphers = await GetAllAsync ( ) ;
2020-03-28 16:16:28 +03:00
foreach ( var cipher in ciphers )
2019-04-19 23:38:20 +03:00
{
tasks . Add ( decryptAndAddCipherAsync ( cipher ) ) ;
}
await Task . WhenAll ( tasks ) ;
decCiphers = decCiphers . OrderBy ( c = > c , new CipherLocaleComparer ( _i18nService ) ) . ToList ( ) ;
DecryptedCipherCache = decCiphers ;
return DecryptedCipherCache ;
}
2019-06-10 19:58:54 +03:00
finally { }
2019-04-16 14:59:53 +03:00
}
2019-04-19 23:38:20 +03:00
_getAllDecryptedTask = doTask ( ) ;
2020-10-13 22:39:36 +03:00
return await _getAllDecryptedTask ;
2019-04-16 14:59:53 +03:00
}
public async Task < List < CipherView > > GetAllDecryptedForGroupingAsync ( string groupingId , bool folder = true )
{
var ciphers = await GetAllDecryptedAsync ( ) ;
return ciphers . Where ( cipher = >
{
2020-05-20 20:35:20 +03:00
if ( cipher . IsDeleted )
{
return false ;
}
2020-03-28 16:16:28 +03:00
if ( folder & & cipher . FolderId = = groupingId )
2019-04-16 14:59:53 +03:00
{
return true ;
}
2020-03-28 16:16:28 +03:00
if ( ! folder & & cipher . CollectionIds ! = null & & cipher . CollectionIds . Contains ( groupingId ) )
2019-04-16 14:59:53 +03:00
{
return true ;
}
return false ;
} ) . ToList ( ) ;
}
public async Task < List < CipherView > > GetAllDecryptedForUrlAsync ( string url )
{
var all = await GetAllDecryptedByUrlAsync ( url ) ;
return all . Item1 ;
}
public async Task < Tuple < List < CipherView > , List < CipherView > , List < CipherView > > > GetAllDecryptedByUrlAsync (
string url , List < CipherType > includeOtherTypes = null )
{
2020-03-28 16:16:28 +03:00
if ( string . IsNullOrWhiteSpace ( url ) & & includeOtherTypes = = null )
2019-04-16 14:59:53 +03:00
{
return new Tuple < List < CipherView > , List < CipherView > , List < CipherView > > (
new List < CipherView > ( ) , new List < CipherView > ( ) , new List < CipherView > ( ) ) ;
}
var domain = CoreHelpers . GetDomain ( url ) ;
var mobileApp = UrlIsMobileApp ( url ) ;
var mobileAppInfo = InfoFromMobileAppUrl ( url ) ;
var mobileAppWebUriString = mobileAppInfo ? . Item1 ;
var mobileAppSearchTerms = mobileAppInfo ? . Item2 ;
var matchingDomainsTask = GetMatchingDomainsAsync ( url , domain , mobileApp , mobileAppWebUriString ) ;
var ciphersTask = GetAllDecryptedAsync ( ) ;
await Task . WhenAll ( new List < Task >
{
matchingDomainsTask ,
ciphersTask
} ) ;
var matchingDomains = await matchingDomainsTask ;
var matchingDomainsSet = matchingDomains . Item1 ;
var matchingFuzzyDomainsSet = matchingDomains . Item2 ;
var matchingLogins = new List < CipherView > ( ) ;
var matchingFuzzyLogins = new List < CipherView > ( ) ;
var others = new List < CipherView > ( ) ;
var ciphers = await ciphersTask ;
2019-06-04 19:34:29 +03:00
var defaultMatch = ( UriMatchType ? ) ( await _storageService . GetAsync < int? > ( Constants . DefaultUriMatch ) ) ;
2020-03-28 16:16:28 +03:00
if ( defaultMatch = = null )
2019-04-16 14:59:53 +03:00
{
defaultMatch = UriMatchType . Domain ;
}
2020-03-28 16:16:28 +03:00
foreach ( var cipher in ciphers )
2019-04-16 14:59:53 +03:00
{
2020-05-20 20:35:20 +03:00
if ( cipher . IsDeleted )
{
continue ;
}
2020-03-28 16:16:28 +03:00
if ( cipher . Type ! = CipherType . Login & & ( includeOtherTypes ? . Any ( t = > t = = cipher . Type ) ? ? false ) )
2019-04-16 14:59:53 +03:00
{
others . Add ( cipher ) ;
continue ;
}
2020-03-28 16:16:28 +03:00
if ( cipher . Type ! = CipherType . Login | | cipher . Login ? . Uris = = null | | ! cipher . Login . Uris . Any ( ) )
2019-04-16 14:59:53 +03:00
{
continue ;
}
2020-03-28 16:16:28 +03:00
foreach ( var u in cipher . Login . Uris )
2019-04-16 14:59:53 +03:00
{
2020-03-28 16:16:28 +03:00
if ( string . IsNullOrWhiteSpace ( u . Uri ) )
2019-04-16 14:59:53 +03:00
{
continue ;
}
var match = false ;
2020-04-14 21:56:57 +03:00
var toMatch = defaultMatch ;
if ( u . Match ! = null )
{
toMatch = u . Match ;
}
switch ( toMatch )
2019-04-16 14:59:53 +03:00
{
case null :
case UriMatchType . Domain :
match = CheckDefaultUriMatch ( cipher , u , matchingLogins , matchingFuzzyLogins ,
matchingDomainsSet , matchingFuzzyDomainsSet , mobileApp , mobileAppSearchTerms ) ;
2020-03-28 16:16:28 +03:00
if ( match & & u . Domain ! = null )
2019-04-16 14:59:53 +03:00
{
2020-03-28 16:16:28 +03:00
if ( _domainMatchBlacklist . ContainsKey ( u . Domain ) )
2019-04-16 14:59:53 +03:00
{
var domainUrlHost = CoreHelpers . GetHost ( url ) ;
2020-03-28 16:16:28 +03:00
if ( _domainMatchBlacklist [ u . Domain ] . Contains ( domainUrlHost ) )
2019-04-16 14:59:53 +03:00
{
match = false ;
}
}
}
break ;
case UriMatchType . Host :
var urlHost = CoreHelpers . GetHost ( url ) ;
match = urlHost ! = null & & urlHost = = CoreHelpers . GetHost ( u . Uri ) ;
2020-03-28 16:16:28 +03:00
if ( match )
2019-04-16 14:59:53 +03:00
{
AddMatchingLogin ( cipher , matchingLogins , matchingFuzzyLogins ) ;
}
break ;
case UriMatchType . Exact :
match = url = = u . Uri ;
2020-03-28 16:16:28 +03:00
if ( match )
2019-04-16 14:59:53 +03:00
{
AddMatchingLogin ( cipher , matchingLogins , matchingFuzzyLogins ) ;
}
break ;
case UriMatchType . StartsWith :
match = url . StartsWith ( u . Uri ) ;
2020-03-28 16:16:28 +03:00
if ( match )
2019-04-16 14:59:53 +03:00
{
AddMatchingLogin ( cipher , matchingLogins , matchingFuzzyLogins ) ;
}
break ;
case UriMatchType . RegularExpression :
2019-06-11 03:47:30 +03:00
try
2019-04-16 14:59:53 +03:00
{
2019-06-11 03:47:30 +03:00
var regex = new Regex ( u . Uri , RegexOptions . IgnoreCase , TimeSpan . FromSeconds ( 1 ) ) ;
match = regex . IsMatch ( url ) ;
2020-03-28 16:16:28 +03:00
if ( match )
2019-06-11 03:47:30 +03:00
{
AddMatchingLogin ( cipher , matchingLogins , matchingFuzzyLogins ) ;
}
2019-04-16 14:59:53 +03:00
}
2020-03-28 16:16:28 +03:00
catch ( ArgumentException ) { }
2019-04-16 14:59:53 +03:00
break ;
case UriMatchType . Never :
default :
break ;
}
2020-03-28 16:16:28 +03:00
if ( match )
2019-04-16 14:59:53 +03:00
{
break ;
}
}
}
return new Tuple < List < CipherView > , List < CipherView > , List < CipherView > > (
matchingLogins , matchingFuzzyLogins , others ) ;
}
2019-04-17 00:34:59 +03:00
public async Task < CipherView > GetLastUsedForUrlAsync ( string url )
{
var ciphers = await GetAllDecryptedForUrlAsync ( url ) ;
return ciphers . OrderBy ( c = > c , new CipherLastUsedComparer ( ) ) . FirstOrDefault ( ) ;
}
public async Task UpdateLastUsedDateAsync ( string id )
{
var ciphersLocalData = await _storageService . GetAsync < Dictionary < string , Dictionary < string , object > > > (
Keys_LocalData ) ;
2020-03-28 16:16:28 +03:00
if ( ciphersLocalData = = null )
2019-04-17 00:34:59 +03:00
{
ciphersLocalData = new Dictionary < string , Dictionary < string , object > > ( ) ;
}
2020-03-28 16:16:28 +03:00
if ( ! ciphersLocalData . ContainsKey ( id ) )
2019-04-17 00:34:59 +03:00
{
ciphersLocalData . Add ( id , new Dictionary < string , object > ( ) ) ;
}
2020-03-28 16:16:28 +03:00
if ( ciphersLocalData [ id ] . ContainsKey ( "lastUsedDate" ) )
2019-04-17 00:34:59 +03:00
{
ciphersLocalData [ id ] [ "lastUsedDate" ] = DateTime . UtcNow ;
}
else
{
ciphersLocalData [ id ] . Add ( "lastUsedDate" , DateTime . UtcNow ) ;
}
await _storageService . SaveAsync ( Keys_LocalData , ciphersLocalData ) ;
// Update cache
2020-03-28 16:16:28 +03:00
if ( DecryptedCipherCache = = null )
2019-04-17 00:34:59 +03:00
{
return ;
}
var cached = DecryptedCipherCache . FirstOrDefault ( c = > c . Id = = id ) ;
2020-03-28 16:16:28 +03:00
if ( cached ! = null )
2019-04-17 00:34:59 +03:00
{
cached . LocalData = ciphersLocalData [ id ] ;
}
}
public async Task SaveNeverDomainAsync ( string domain )
{
2020-03-28 16:16:28 +03:00
if ( string . IsNullOrWhiteSpace ( domain ) )
2019-04-17 00:34:59 +03:00
{
return ;
}
var domains = await _storageService . GetAsync < HashSet < string > > ( Keys_NeverDomains ) ;
2020-03-28 16:16:28 +03:00
if ( domains = = null )
2019-04-17 00:34:59 +03:00
{
domains = new HashSet < string > ( ) ;
}
domains . Add ( domain ) ;
await _storageService . SaveAsync ( Keys_NeverDomains , domains ) ;
}
public async Task SaveWithServerAsync ( Cipher cipher )
{
CipherResponse response ;
2020-03-28 16:16:28 +03:00
if ( cipher . Id = = null )
2019-04-17 00:34:59 +03:00
{
2020-03-28 16:16:28 +03:00
if ( cipher . CollectionIds ! = null )
2019-04-17 00:34:59 +03:00
{
var request = new CipherCreateRequest ( cipher ) ;
response = await _apiService . PostCipherCreateAsync ( request ) ;
}
else
{
var request = new CipherRequest ( cipher ) ;
response = await _apiService . PostCipherAsync ( request ) ;
}
cipher . Id = response . Id ;
}
else
{
var request = new CipherRequest ( cipher ) ;
response = await _apiService . PutCipherAsync ( cipher . Id , request ) ;
}
var userId = await _userService . GetUserIdAsync ( ) ;
var data = new CipherData ( response , userId , cipher . CollectionIds ) ;
await UpsertAsync ( data ) ;
}
public async Task ShareWithServerAsync ( CipherView cipher , string organizationId , HashSet < string > collectionIds )
{
var attachmentTasks = new List < Task > ( ) ;
2020-03-28 16:16:28 +03:00
if ( cipher . Attachments ! = null )
2019-04-17 00:34:59 +03:00
{
2020-03-28 16:16:28 +03:00
foreach ( var attachment in cipher . Attachments )
2019-04-17 00:34:59 +03:00
{
2020-03-28 16:16:28 +03:00
if ( attachment . Key = = null )
2019-04-17 00:34:59 +03:00
{
attachmentTasks . Add ( ShareAttachmentWithServerAsync ( attachment , cipher . Id , organizationId ) ) ;
}
}
}
await Task . WhenAll ( attachmentTasks ) ;
cipher . OrganizationId = organizationId ;
cipher . CollectionIds = collectionIds ;
var encCipher = await EncryptAsync ( cipher ) ;
var request = new CipherShareRequest ( encCipher ) ;
var response = await _apiService . PutShareCipherAsync ( cipher . Id , request ) ;
var userId = await _userService . GetUserIdAsync ( ) ;
var data = new CipherData ( response , userId , collectionIds ) ;
await UpsertAsync ( data ) ;
}
public async Task < Cipher > SaveAttachmentRawWithServerAsync ( Cipher cipher , string filename , byte [ ] data )
{
2021-03-31 02:42:43 +03:00
var orgKey = await _cryptoService . GetOrgKeyAsync ( cipher . OrganizationId ) ;
var encFileName = await _cryptoService . EncryptAsync ( filename , orgKey ) ;
var ( attachmentKey , orgEncAttachmentKey ) = await _cryptoService . MakeEncKeyAsync ( orgKey ) ;
var encFileData = await _cryptoService . EncryptToBytesAsync ( data , attachmentKey ) ;
CipherResponse response ;
try
{
var request = new AttachmentRequest
{
Key = orgEncAttachmentKey . EncryptedString ,
FileName = encFileName . EncryptedString ,
2021-04-21 23:27:14 +03:00
FileSize = encFileData . Buffer . Length ,
2021-03-31 02:42:43 +03:00
} ;
var uploadDataResponse = await _apiService . PostCipherAttachmentAsync ( cipher . Id , request ) ;
response = uploadDataResponse . CipherResponse ;
await _fileUploadService . UploadCipherAttachmentFileAsync ( uploadDataResponse , encFileName . EncryptedString , encFileData ) ;
}
catch ( ApiException e ) when ( e . Error . StatusCode = = System . Net . HttpStatusCode . NotFound | | e . Error . StatusCode = = System . Net . HttpStatusCode . MethodNotAllowed )
{
response = await LegacyServerAttachmentFileUploadAsync ( cipher . Id , encFileName , encFileData , orgEncAttachmentKey ) ;
}
2019-04-17 00:34:59 +03:00
var userId = await _userService . GetUserIdAsync ( ) ;
var cData = new CipherData ( response , userId , cipher . CollectionIds ) ;
await UpsertAsync ( cData ) ;
return new Cipher ( cData ) ;
}
2021-03-31 02:42:43 +03: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 < CipherResponse > LegacyServerAttachmentFileUploadAsync ( string cipherId ,
2021-04-21 23:27:14 +03:00
EncString encFileName , EncByteArray encFileData , EncString key )
2021-03-31 02:42:43 +03:00
{
var boundary = string . Concat ( "--BWMobileFormBoundary" , DateTime . UtcNow . Ticks ) ;
var fd = new MultipartFormDataContent ( boundary ) ;
fd . Add ( new StringContent ( key . EncryptedString ) , "key" ) ;
2021-04-21 23:27:14 +03:00
fd . Add ( new StreamContent ( new MemoryStream ( encFileData . Buffer ) ) , "data" , encFileName . EncryptedString ) ;
2021-03-31 02:42:43 +03:00
return await _apiService . PostCipherAttachmentLegacyAsync ( cipherId , fd ) ;
}
2019-04-17 03:43:54 +03:00
public async Task SaveCollectionsWithServerAsync ( Cipher cipher )
{
var request = new CipherCollectionsRequest ( cipher . CollectionIds ? . ToList ( ) ) ;
await _apiService . PutCipherCollectionsAsync ( cipher . Id , request ) ;
var userId = await _userService . GetUserIdAsync ( ) ;
var data = cipher . ToCipherData ( userId ) ;
await UpsertAsync ( data ) ;
}
2019-04-17 00:34:59 +03:00
public async Task UpsertAsync ( CipherData cipher )
{
var userId = await _userService . GetUserIdAsync ( ) ;
var storageKey = string . Format ( Keys_CiphersFormat , userId ) ;
var ciphers = await _storageService . GetAsync < Dictionary < string , CipherData > > ( storageKey ) ;
2020-03-28 16:16:28 +03:00
if ( ciphers = = null )
2019-04-17 00:34:59 +03:00
{
ciphers = new Dictionary < string , CipherData > ( ) ;
}
2020-03-28 16:16:28 +03:00
if ( ! ciphers . ContainsKey ( cipher . Id ) )
2019-04-17 00:34:59 +03:00
{
ciphers . Add ( cipher . Id , null ) ;
}
ciphers [ cipher . Id ] = cipher ;
await _storageService . SaveAsync ( storageKey , ciphers ) ;
2020-10-13 22:39:36 +03:00
await ClearCacheAsync ( ) ;
2019-04-17 00:34:59 +03:00
}
public async Task UpsertAsync ( List < CipherData > cipher )
{
var userId = await _userService . GetUserIdAsync ( ) ;
var storageKey = string . Format ( Keys_CiphersFormat , userId ) ;
var ciphers = await _storageService . GetAsync < Dictionary < string , CipherData > > ( storageKey ) ;
2020-03-28 16:16:28 +03:00
if ( ciphers = = null )
2019-04-17 00:34:59 +03:00
{
ciphers = new Dictionary < string , CipherData > ( ) ;
}
2020-03-28 16:16:28 +03:00
foreach ( var c in cipher )
2019-04-17 00:34:59 +03:00
{
2020-03-28 16:16:28 +03:00
if ( ! ciphers . ContainsKey ( c . Id ) )
2019-04-17 00:34:59 +03:00
{
ciphers . Add ( c . Id , null ) ;
}
ciphers [ c . Id ] = c ;
}
await _storageService . SaveAsync ( storageKey , ciphers ) ;
2020-10-13 22:39:36 +03:00
await ClearCacheAsync ( ) ;
2019-04-17 00:34:59 +03:00
}
2019-04-17 06:30:52 +03:00
public async Task ReplaceAsync ( Dictionary < string , CipherData > ciphers )
2019-04-17 00:34:59 +03:00
{
var userId = await _userService . GetUserIdAsync ( ) ;
await _storageService . SaveAsync ( string . Format ( Keys_CiphersFormat , userId ) , ciphers ) ;
2020-10-13 22:39:36 +03:00
await ClearCacheAsync ( ) ;
2019-04-17 00:34:59 +03:00
}
public async Task ClearAsync ( string userId )
{
await _storageService . RemoveAsync ( string . Format ( Keys_CiphersFormat , userId ) ) ;
2020-10-13 22:39:36 +03:00
await ClearCacheAsync ( ) ;
2019-04-17 00:34:59 +03:00
}
2019-04-17 03:43:54 +03:00
public async Task DeleteAsync ( string id )
{
var userId = await _userService . GetUserIdAsync ( ) ;
var cipherKey = string . Format ( Keys_CiphersFormat , userId ) ;
var ciphers = await _storageService . GetAsync < Dictionary < string , CipherData > > ( cipherKey ) ;
2020-03-28 16:16:28 +03:00
if ( ciphers = = null )
2019-04-17 03:43:54 +03:00
{
return ;
}
2020-03-28 16:16:28 +03:00
if ( ! ciphers . ContainsKey ( id ) )
2019-04-17 03:43:54 +03:00
{
return ;
}
ciphers . Remove ( id ) ;
await _storageService . SaveAsync ( cipherKey , ciphers ) ;
2020-10-13 22:39:36 +03:00
await ClearCacheAsync ( ) ;
2019-04-17 03:43:54 +03:00
}
public async Task DeleteAsync ( List < string > ids )
{
var userId = await _userService . GetUserIdAsync ( ) ;
var cipherKey = string . Format ( Keys_CiphersFormat , userId ) ;
var ciphers = await _storageService . GetAsync < Dictionary < string , CipherData > > ( cipherKey ) ;
2020-03-28 16:16:28 +03:00
if ( ciphers = = null )
2019-04-17 03:43:54 +03:00
{
return ;
}
2020-03-28 16:16:28 +03:00
foreach ( var id in ids )
2019-04-17 03:43:54 +03:00
{
2020-03-28 16:16:28 +03:00
if ( ! ciphers . ContainsKey ( id ) )
2019-04-17 03:43:54 +03:00
{
return ;
}
ciphers . Remove ( id ) ;
}
await _storageService . SaveAsync ( cipherKey , ciphers ) ;
2020-10-13 22:39:36 +03:00
await ClearCacheAsync ( ) ;
2019-04-17 03:43:54 +03:00
}
public async Task DeleteWithServerAsync ( string id )
{
await _apiService . DeleteCipherAsync ( id ) ;
await DeleteAsync ( id ) ;
}
public async Task DeleteAttachmentAsync ( string id , string attachmentId )
{
var userId = await _userService . GetUserIdAsync ( ) ;
var cipherKey = string . Format ( Keys_CiphersFormat , userId ) ;
var ciphers = await _storageService . GetAsync < Dictionary < string , CipherData > > ( cipherKey ) ;
2020-03-28 16:16:28 +03:00
if ( ciphers = = null | | ! ciphers . ContainsKey ( id ) | | ciphers [ id ] . Attachments = = null )
2019-04-17 03:43:54 +03:00
{
return ;
}
var attachment = ciphers [ id ] . Attachments . FirstOrDefault ( a = > a . Id = = attachmentId ) ;
2020-03-28 16:16:28 +03:00
if ( attachment ! = null )
2019-04-17 03:43:54 +03:00
{
ciphers [ id ] . Attachments . Remove ( attachment ) ;
}
await _storageService . SaveAsync ( cipherKey , ciphers ) ;
2020-10-13 22:39:36 +03:00
await ClearCacheAsync ( ) ;
2019-04-17 03:43:54 +03:00
}
public async Task DeleteAttachmentWithServerAsync ( string id , string attachmentId )
{
try
{
await _apiService . DeleteCipherAttachmentAsync ( id , attachmentId ) ;
2019-05-11 06:43:35 +03:00
await DeleteAttachmentAsync ( id , attachmentId ) ;
2019-04-17 03:43:54 +03:00
}
2020-03-28 16:16:28 +03:00
catch ( ApiException e )
2019-04-17 03:43:54 +03:00
{
2019-05-11 06:43:35 +03:00
await DeleteAttachmentAsync ( id , attachmentId ) ;
throw e ;
2019-04-17 03:43:54 +03:00
}
}
2021-03-31 02:42:43 +03:00
public async Task < byte [ ] > DownloadAndDecryptAttachmentAsync ( string cipherId , AttachmentView attachment , string organizationId )
2019-04-29 23:09:27 +03:00
{
2021-03-31 02:42:43 +03:00
string url ;
try
{
var attachmentDownloadResponse = await _apiService . GetAttachmentData ( cipherId , attachment . Id ) ;
url = attachmentDownloadResponse . Url ;
}
// TODO: Delete this catch when all Servers are updated to respond to the above method
catch ( ApiException e ) when ( e . Error . StatusCode = = System . Net . HttpStatusCode . NotFound )
{
url = attachment . Url ;
}
2019-04-29 23:09:27 +03:00
try
{
2021-03-31 02:42:43 +03:00
var response = await _httpClient . GetAsync ( new Uri ( url ) ) ;
2020-03-28 16:16:28 +03:00
if ( ! response . IsSuccessStatusCode )
2019-04-29 23:09:27 +03:00
{
return null ;
}
var data = await response . Content . ReadAsByteArrayAsync ( ) ;
2020-03-28 16:16:28 +03:00
if ( data = = null )
2019-04-29 23:09:27 +03:00
{
return null ;
}
var key = attachment . Key ? ? await _cryptoService . GetOrgKeyAsync ( organizationId ) ;
return await _cryptoService . DecryptFromBytesAsync ( data , key ) ;
}
catch { }
return null ;
}
2020-05-20 20:35:20 +03:00
public async Task SoftDeleteWithServerAsync ( string id )
{
var userId = await _userService . GetUserIdAsync ( ) ;
var cipherKey = string . Format ( Keys_CiphersFormat , userId ) ;
var ciphers = await _storageService . GetAsync < Dictionary < string , CipherData > > ( cipherKey ) ;
if ( ciphers = = null )
{
return ;
}
if ( ! ciphers . ContainsKey ( id ) )
{
return ;
}
await _apiService . PutDeleteCipherAsync ( id ) ;
ciphers [ id ] . DeletedDate = DateTime . UtcNow ;
await _storageService . SaveAsync ( cipherKey , ciphers ) ;
2020-10-13 22:39:36 +03:00
await ClearCacheAsync ( ) ;
2020-05-20 20:35:20 +03:00
}
public async Task RestoreWithServerAsync ( string id )
{
var userId = await _userService . GetUserIdAsync ( ) ;
var cipherKey = string . Format ( Keys_CiphersFormat , userId ) ;
var ciphers = await _storageService . GetAsync < Dictionary < string , CipherData > > ( cipherKey ) ;
if ( ciphers = = null )
{
return ;
}
if ( ! ciphers . ContainsKey ( id ) )
{
return ;
}
2021-01-08 17:53:45 +03:00
var response = await _apiService . PutRestoreCipherAsync ( id ) ;
2020-05-20 20:35:20 +03:00
ciphers [ id ] . DeletedDate = null ;
2021-01-08 17:53:45 +03:00
ciphers [ id ] . RevisionDate = response . RevisionDate ;
2020-05-20 20:35:20 +03:00
await _storageService . SaveAsync ( cipherKey , ciphers ) ;
2020-10-13 22:39:36 +03:00
await ClearCacheAsync ( ) ;
2020-05-20 20:35:20 +03:00
}
2019-04-16 14:59:53 +03:00
// Helpers
2019-04-17 00:34:59 +03:00
private async Task ShareAttachmentWithServerAsync ( AttachmentView attachmentView , string cipherId ,
string organizationId )
{
var attachmentResponse = await _httpClient . GetAsync ( attachmentView . Url ) ;
2020-03-28 16:16:28 +03:00
if ( ! attachmentResponse . IsSuccessStatusCode )
2019-04-17 00:34:59 +03:00
{
throw new Exception ( "Failed to download attachment: " + attachmentResponse . StatusCode ) ;
}
var bytes = await attachmentResponse . Content . ReadAsByteArrayAsync ( ) ;
var decBytes = await _cryptoService . DecryptFromBytesAsync ( bytes , null ) ;
var key = await _cryptoService . GetOrgKeyAsync ( organizationId ) ;
var encFileName = await _cryptoService . EncryptAsync ( attachmentView . FileName , key ) ;
var dataEncKey = await _cryptoService . MakeEncKeyAsync ( key ) ;
var encData = await _cryptoService . EncryptToBytesAsync ( decBytes , dataEncKey . Item1 ) ;
2019-05-11 06:43:35 +03:00
var boundary = string . Concat ( "--BWMobileFormBoundary" , DateTime . UtcNow . Ticks ) ;
var fd = new MultipartFormDataContent ( boundary ) ;
fd . Add ( new StringContent ( dataEncKey . Item2 . EncryptedString ) , "key" ) ;
2021-04-21 23:27:14 +03:00
fd . Add ( new StreamContent ( new MemoryStream ( encData . Buffer ) ) , "data" , encFileName . EncryptedString ) ;
2019-05-11 06:43:35 +03:00
await _apiService . PostShareCipherAttachmentAsync ( cipherId , attachmentView . Id , fd , organizationId ) ;
2019-04-17 00:34:59 +03:00
}
2019-04-16 14:59:53 +03:00
private bool CheckDefaultUriMatch ( CipherView cipher , LoginUriView loginUri ,
List < CipherView > matchingLogins , List < CipherView > matchingFuzzyLogins ,
HashSet < string > matchingDomainsSet , HashSet < string > matchingFuzzyDomainsSet ,
bool mobileApp , string [ ] mobileAppSearchTerms )
{
var loginUriString = loginUri . Uri ;
var loginUriDomain = loginUri . Domain ;
2020-03-28 16:16:28 +03:00
if ( matchingDomainsSet . Contains ( loginUriString ) )
2019-04-16 14:59:53 +03:00
{
AddMatchingLogin ( cipher , matchingLogins , matchingFuzzyLogins ) ;
return true ;
}
2020-03-28 16:16:28 +03:00
else if ( mobileApp & & matchingFuzzyDomainsSet . Contains ( loginUriString ) )
2019-04-16 14:59:53 +03:00
{
AddMatchingFuzzyLogin ( cipher , matchingLogins , matchingFuzzyLogins ) ;
return false ;
}
2020-03-28 16:16:28 +03:00
else if ( ! mobileApp )
2019-04-16 14:59:53 +03:00
{
var info = InfoFromMobileAppUrl ( loginUriString ) ;
2020-03-28 16:16:28 +03:00
if ( info ? . Item1 ! = null & & matchingDomainsSet . Contains ( info . Item1 ) )
2019-04-16 14:59:53 +03:00
{
AddMatchingFuzzyLogin ( cipher , matchingLogins , matchingFuzzyLogins ) ;
return false ;
}
}
2020-03-28 16:16:28 +03:00
if ( ! loginUri . Uri . Contains ( "://" ) & & loginUriString . Contains ( "." ) )
2019-04-16 14:59:53 +03:00
{
loginUriString = "http://" + loginUriString ;
}
2020-03-28 16:16:28 +03:00
if ( loginUriDomain ! = null )
2019-04-16 14:59:53 +03:00
{
loginUriDomain = loginUriDomain . ToLowerInvariant ( ) ;
2020-03-28 16:16:28 +03:00
if ( matchingDomainsSet . Contains ( loginUriDomain ) )
2019-04-16 14:59:53 +03:00
{
AddMatchingLogin ( cipher , matchingLogins , matchingFuzzyLogins ) ;
return true ;
}
2020-03-28 16:16:28 +03:00
else if ( mobileApp & & matchingFuzzyDomainsSet . Contains ( loginUriDomain ) )
2019-04-16 14:59:53 +03:00
{
AddMatchingFuzzyLogin ( cipher , matchingLogins , matchingFuzzyLogins ) ;
return false ;
}
}
2020-03-28 16:16:28 +03:00
if ( mobileApp & & ( mobileAppSearchTerms ? . Any ( ) ? ? false ) )
2019-04-16 14:59:53 +03:00
{
var addedFromSearchTerm = false ;
var loginName = cipher . Name ? . ToLowerInvariant ( ) ;
2020-03-28 16:16:28 +03:00
foreach ( var term in mobileAppSearchTerms )
2019-04-16 14:59:53 +03:00
{
addedFromSearchTerm = ( loginUriDomain ! = null & & loginUriDomain . Contains ( term ) ) | |
( loginName ! = null & & loginName . Contains ( term ) ) ;
2020-03-28 16:16:28 +03:00
if ( ! addedFromSearchTerm )
2019-04-16 14:59:53 +03:00
{
var domainTerm = loginUriDomain ? . Split ( '.' ) [ 0 ] ;
addedFromSearchTerm =
( domainTerm ! = null & & domainTerm . Length > 2 & & term . Contains ( domainTerm ) ) | |
( loginName ! = null & & term . Contains ( loginName ) ) ;
}
2020-03-28 16:16:28 +03:00
if ( addedFromSearchTerm )
2019-04-16 14:59:53 +03:00
{
AddMatchingFuzzyLogin ( cipher , matchingLogins , matchingFuzzyLogins ) ;
return false ;
}
}
}
return false ;
}
private void AddMatchingLogin ( CipherView cipher , List < CipherView > matchingLogins ,
List < CipherView > matchingFuzzyLogins )
{
2020-03-28 16:16:28 +03:00
if ( matchingFuzzyLogins . Contains ( cipher ) )
2019-04-16 14:59:53 +03:00
{
matchingFuzzyLogins . Remove ( cipher ) ;
}
matchingLogins . Add ( cipher ) ;
}
private void AddMatchingFuzzyLogin ( CipherView cipher , List < CipherView > matchingLogins ,
List < CipherView > matchingFuzzyLogins )
{
2020-03-28 16:16:28 +03:00
if ( ! matchingFuzzyLogins . Contains ( cipher ) & & ! matchingLogins . Contains ( cipher ) )
2019-04-16 14:59:53 +03:00
{
matchingFuzzyLogins . Add ( cipher ) ;
}
}
private async Task < Tuple < HashSet < string > , HashSet < string > > > GetMatchingDomainsAsync ( string url ,
string domain , bool mobileApp , string mobileAppWebUriString )
{
var matchingDomains = new HashSet < string > ( ) ;
var matchingFuzzyDomains = new HashSet < string > ( ) ;
var eqDomains = await _settingsService . GetEquivalentDomainsAsync ( ) ;
2020-03-28 16:16:28 +03:00
foreach ( var eqDomain in eqDomains )
2019-04-16 14:59:53 +03:00
{
var eqDomainSet = new HashSet < string > ( eqDomain ) ;
2020-03-28 16:16:28 +03:00
if ( mobileApp )
2019-04-16 14:59:53 +03:00
{
2020-03-28 16:16:28 +03:00
if ( eqDomainSet . Contains ( url ) )
2019-04-16 14:59:53 +03:00
{
2020-03-28 16:16:28 +03:00
foreach ( var d in eqDomain )
2019-07-01 23:06:52 +03:00
{
matchingDomains . Add ( d ) ;
}
2019-04-16 14:59:53 +03:00
}
2020-03-28 16:16:28 +03:00
else if ( mobileAppWebUriString ! = null & & eqDomainSet . Contains ( mobileAppWebUriString ) )
2019-04-16 14:59:53 +03:00
{
2020-03-28 16:16:28 +03:00
foreach ( var d in eqDomain )
2019-07-01 23:06:52 +03:00
{
matchingFuzzyDomains . Add ( d ) ;
}
2019-04-16 14:59:53 +03:00
}
}
2020-03-28 16:16:28 +03:00
else if ( eqDomainSet . Contains ( domain ) )
2019-04-16 14:59:53 +03:00
{
2020-03-28 16:16:28 +03:00
foreach ( var d in eqDomain )
2019-07-01 23:06:52 +03:00
{
matchingDomains . Add ( d ) ;
}
2019-04-16 14:59:53 +03:00
}
}
2020-03-28 16:16:28 +03:00
if ( ! matchingDomains . Any ( ) )
2019-04-16 14:59:53 +03:00
{
matchingDomains . Add ( mobileApp ? url : domain ) ;
}
2020-03-28 16:16:28 +03:00
if ( mobileApp & & mobileAppWebUriString ! = null & &
2019-04-16 14:59:53 +03:00
! matchingFuzzyDomains . Any ( ) & & ! matchingDomains . Contains ( mobileAppWebUriString ) )
{
matchingFuzzyDomains . Add ( mobileAppWebUriString ) ;
}
return new Tuple < HashSet < string > , HashSet < string > > ( matchingDomains , matchingFuzzyDomains ) ;
}
private Tuple < string , string [ ] > InfoFromMobileAppUrl ( string mobileAppUrlString )
{
2020-03-28 16:16:28 +03:00
if ( UrlIsAndroidApp ( mobileAppUrlString ) )
2019-04-16 14:59:53 +03:00
{
return InfoFromAndroidAppUri ( mobileAppUrlString ) ;
}
2020-03-28 16:16:28 +03:00
else if ( UrlIsiOSApp ( mobileAppUrlString ) )
2019-04-16 14:59:53 +03:00
{
return InfoFromiOSAppUrl ( mobileAppUrlString ) ;
}
return null ;
}
private Tuple < string , string [ ] > InfoFromAndroidAppUri ( string androidAppUrlString )
{
2020-03-28 16:16:28 +03:00
if ( ! UrlIsAndroidApp ( androidAppUrlString ) )
2019-04-16 14:59:53 +03:00
{
return null ;
}
var androidUrlParts = androidAppUrlString . Replace ( Constants . AndroidAppProtocol , string . Empty ) . Split ( '.' ) ;
2020-03-28 16:16:28 +03:00
if ( androidUrlParts . Length > = 2 )
2019-04-16 14:59:53 +03:00
{
var webUri = string . Join ( "." , androidUrlParts [ 1 ] , androidUrlParts [ 0 ] ) ;
var searchTerms = androidUrlParts . Where ( p = > ! _ignoredSearchTerms . Contains ( p ) )
. Select ( p = > p . ToLowerInvariant ( ) ) . ToArray ( ) ;
return new Tuple < string , string [ ] > ( webUri , searchTerms ) ;
}
return null ;
}
private Tuple < string , string [ ] > InfoFromiOSAppUrl ( string iosAppUrlString )
{
2020-03-28 16:16:28 +03:00
if ( ! UrlIsiOSApp ( iosAppUrlString ) )
2019-04-16 14:59:53 +03:00
{
return null ;
}
var webUri = iosAppUrlString . Replace ( Constants . iOSAppProtocol , string . Empty ) ;
return new Tuple < string , string [ ] > ( webUri , null ) ;
}
private bool UrlIsMobileApp ( string url )
{
return UrlIsAndroidApp ( url ) | | UrlIsiOSApp ( url ) ;
}
private bool UrlIsAndroidApp ( string url )
{
return url . StartsWith ( Constants . AndroidAppProtocol ) ;
}
private bool UrlIsiOSApp ( string url )
{
return url . StartsWith ( Constants . iOSAppProtocol ) ;
}
2019-04-12 17:06:47 +03:00
private Task EncryptObjPropertyAsync < V , D > ( V model , D obj , HashSet < string > map , SymmetricCryptoKey key )
where V : View
where D : Domain
{
var modelType = model . GetType ( ) ;
var objType = obj . GetType ( ) ;
2019-04-19 23:15:29 +03:00
async Task makeAndSetCs ( string propName )
2019-04-12 17:06:47 +03:00
{
var modelPropInfo = modelType . GetProperty ( propName ) ;
var modelProp = modelPropInfo . GetValue ( model ) as string ;
2021-04-21 23:27:14 +03:00
EncString val = null ;
2020-03-28 16:16:28 +03:00
if ( ! string . IsNullOrWhiteSpace ( modelProp ) )
2019-04-12 17:06:47 +03:00
{
2019-04-19 23:15:29 +03:00
val = await _cryptoService . EncryptAsync ( modelProp , key ) ;
2019-04-12 17:06:47 +03:00
}
var objPropInfo = objType . GetProperty ( propName ) ;
objPropInfo . SetValue ( obj , val , null ) ;
} ;
var tasks = new List < Task > ( ) ;
2020-03-28 16:16:28 +03:00
foreach ( var prop in map )
2019-04-12 17:06:47 +03:00
{
2019-04-19 23:15:29 +03:00
tasks . Add ( makeAndSetCs ( prop ) ) ;
2019-04-12 17:06:47 +03:00
}
return Task . WhenAll ( tasks ) ;
}
2019-04-15 18:46:20 +03:00
2019-04-19 23:15:29 +03:00
private async Task EncryptAttachmentsAsync ( List < AttachmentView > attachmentsModel , SymmetricCryptoKey key ,
Cipher cipher )
2019-04-15 18:46:20 +03:00
{
2020-03-28 16:16:28 +03:00
if ( ! attachmentsModel ? . Any ( ) ? ? true )
2019-04-15 18:46:20 +03:00
{
2019-04-19 23:15:29 +03:00
cipher . Attachments = null ;
2019-05-07 20:58:50 +03:00
return ;
2019-04-15 18:46:20 +03:00
}
var tasks = new List < Task > ( ) ;
var encAttachments = new List < Attachment > ( ) ;
2019-04-19 23:15:29 +03:00
async Task encryptAndAddAttachmentAsync ( AttachmentView model , Attachment attachment )
{
await EncryptObjPropertyAsync ( model , attachment , new HashSet < string >
{
"FileName"
} , key ) ;
2020-03-28 16:16:28 +03:00
if ( model . Key ! = null )
2019-04-19 23:15:29 +03:00
{
attachment . Key = await _cryptoService . EncryptAsync ( model . Key . Key , key ) ;
}
encAttachments . Add ( attachment ) ;
}
2020-03-28 16:16:28 +03:00
foreach ( var model in attachmentsModel )
2019-04-15 18:46:20 +03:00
{
2019-04-19 23:15:29 +03:00
tasks . Add ( encryptAndAddAttachmentAsync ( model , new Attachment
2019-04-15 18:46:20 +03:00
{
Id = model . Id ,
Size = model . Size ,
SizeName = model . SizeName ,
Url = model . Url
2019-04-19 23:15:29 +03:00
} ) ) ;
2019-04-15 18:46:20 +03:00
}
await Task . WhenAll ( tasks ) ;
2019-04-19 23:15:29 +03:00
cipher . Attachments = encAttachments ;
2019-04-15 18:46:20 +03:00
}
private async Task EncryptCipherDataAsync ( Cipher cipher , CipherView model , SymmetricCryptoKey key )
{
2020-03-28 16:16:28 +03:00
switch ( cipher . Type )
2019-04-15 18:46:20 +03:00
{
2019-04-17 00:34:59 +03:00
case CipherType . Login :
2019-04-15 18:46:20 +03:00
cipher . Login = new Login
{
PasswordRevisionDate = model . Login . PasswordRevisionDate
} ;
await EncryptObjPropertyAsync ( model . Login , cipher . Login , new HashSet < string >
{
"Username" ,
"Password" ,
"Totp"
} , key ) ;
2020-03-28 16:16:28 +03:00
if ( model . Login . Uris ! = null )
2019-04-15 18:46:20 +03:00
{
cipher . Login . Uris = new List < LoginUri > ( ) ;
2020-03-28 16:16:28 +03:00
foreach ( var uri in model . Login . Uris )
2019-04-15 18:46:20 +03:00
{
var loginUri = new LoginUri
{
Match = uri . Match
} ;
await EncryptObjPropertyAsync ( uri , loginUri , new HashSet < string > { "Uri" } , key ) ;
cipher . Login . Uris . Add ( loginUri ) ;
}
}
break ;
2019-04-17 00:34:59 +03:00
case CipherType . SecureNote :
2019-04-15 18:46:20 +03:00
cipher . SecureNote = new SecureNote
{
Type = model . SecureNote . Type
} ;
break ;
2019-04-17 00:34:59 +03:00
case CipherType . Card :
2019-04-15 18:46:20 +03:00
cipher . Card = new Card ( ) ;
await EncryptObjPropertyAsync ( model . Card , cipher . Card , new HashSet < string >
{
"CardholderName" ,
"Brand" ,
"Number" ,
"ExpMonth" ,
"ExpYear" ,
"Code"
} , key ) ;
break ;
2019-04-17 00:34:59 +03:00
case CipherType . Identity :
2019-04-15 18:46:20 +03:00
cipher . Identity = new Identity ( ) ;
await EncryptObjPropertyAsync ( model . Identity , cipher . Identity , new HashSet < string >
{
"Title" ,
"FirstName" ,
"MiddleName" ,
"LastName" ,
"Address1" ,
"Address2" ,
"Address3" ,
"City" ,
"State" ,
"PostalCode" ,
"Country" ,
"Company" ,
"Email" ,
"Phone" ,
"SSN" ,
"Username" ,
"PassportNumber" ,
"LicenseNumber"
} , key ) ;
break ;
default :
throw new Exception ( "Unknown cipher type." ) ;
}
}
2019-04-19 23:15:29 +03:00
private async Task EncryptFieldsAsync ( List < FieldView > fieldsModel , SymmetricCryptoKey key ,
Cipher cipher )
2019-04-15 18:46:20 +03:00
{
2020-03-28 16:16:28 +03:00
if ( ! fieldsModel ? . Any ( ) ? ? true )
2019-04-15 18:46:20 +03:00
{
2019-04-19 23:15:29 +03:00
cipher . Fields = null ;
return ;
2019-04-15 18:46:20 +03:00
}
var tasks = new List < Task > ( ) ;
var encFields = new List < Field > ( ) ;
2019-04-19 23:15:29 +03:00
async Task encryptAndAddFieldAsync ( FieldView model , Field field )
{
await EncryptObjPropertyAsync ( model , field , new HashSet < string >
{
"Name" ,
"Value"
} , key ) ;
encFields . Add ( field ) ;
}
2020-03-28 16:16:28 +03:00
foreach ( var model in fieldsModel )
2019-04-15 18:46:20 +03:00
{
var field = new Field
{
Type = model . Type
} ;
// normalize boolean type field values
2020-03-28 16:16:28 +03:00
if ( model . Type = = FieldType . Boolean & & model . Value ! = "true" )
2019-04-15 18:46:20 +03:00
{
model . Value = "false" ;
}
2019-04-19 23:15:29 +03:00
tasks . Add ( encryptAndAddFieldAsync ( model , field ) ) ;
2019-04-15 18:46:20 +03:00
}
await Task . WhenAll ( tasks ) ;
2019-04-19 23:15:29 +03:00
cipher . Fields = encFields ;
2019-04-15 18:46:20 +03:00
}
2019-04-19 23:15:29 +03:00
private async Task EncryptPasswordHistoriesAsync ( List < PasswordHistoryView > phModels ,
SymmetricCryptoKey key , Cipher cipher )
2019-04-15 18:46:20 +03:00
{
2020-03-28 16:16:28 +03:00
if ( ! phModels ? . Any ( ) ? ? true )
2019-04-15 18:46:20 +03:00
{
2019-04-19 23:15:29 +03:00
cipher . PasswordHistory = null ;
2019-05-07 20:58:50 +03:00
return ;
2019-04-15 18:46:20 +03:00
}
var tasks = new List < Task > ( ) ;
var encPhs = new List < PasswordHistory > ( ) ;
2019-04-19 23:15:29 +03:00
async Task encryptAndAddHistoryAsync ( PasswordHistoryView model , PasswordHistory ph )
2019-04-15 18:46:20 +03:00
{
2019-04-19 23:15:29 +03:00
await EncryptObjPropertyAsync ( model , ph , new HashSet < string >
2019-04-15 18:46:20 +03:00
{
"Password"
2019-04-19 23:15:29 +03:00
} , key ) ;
encPhs . Add ( ph ) ;
}
2020-03-28 16:16:28 +03:00
foreach ( var model in phModels )
2019-04-19 23:15:29 +03:00
{
tasks . Add ( encryptAndAddHistoryAsync ( model , new PasswordHistory
2019-04-15 18:46:20 +03:00
{
2019-04-19 23:15:29 +03:00
LastUsedDate = model . LastUsedDate
} ) ) ;
2019-04-15 18:46:20 +03:00
}
await Task . WhenAll ( tasks ) ;
2019-04-19 23:15:29 +03:00
cipher . PasswordHistory = encPhs ;
2019-04-15 18:46:20 +03:00
}
2019-04-17 00:34:59 +03:00
private class CipherLocaleComparer : IComparer < CipherView >
{
private readonly II18nService _i18nService ;
public CipherLocaleComparer ( II18nService i18nService )
{
_i18nService = i18nService ;
}
public int Compare ( CipherView a , CipherView b )
{
2019-04-19 22:33:55 +03:00
var aName = a ? . Name ;
var bName = b ? . Name ;
2020-03-28 16:16:28 +03:00
if ( aName = = null & & bName ! = null )
2019-04-17 00:34:59 +03:00
{
return - 1 ;
}
2020-03-28 16:16:28 +03:00
if ( aName ! = null & & bName = = null )
2019-04-17 00:34:59 +03:00
{
return 1 ;
}
2020-03-28 16:16:28 +03:00
if ( aName = = null & & bName = = null )
2019-04-17 00:34:59 +03:00
{
return 0 ;
}
var result = _i18nService . StringComparer . Compare ( aName , bName ) ;
2020-03-28 16:16:28 +03:00
if ( result ! = 0 | | a . Type ! = CipherType . Login | | b . Type ! = CipherType . Login )
2019-04-17 00:34:59 +03:00
{
return result ;
}
2020-03-28 16:16:28 +03:00
if ( a . Login . Username ! = null )
2019-04-17 00:34:59 +03:00
{
aName + = a . Login . Username ;
}
2020-03-28 16:16:28 +03:00
if ( b . Login . Username ! = null )
2019-04-17 00:34:59 +03:00
{
bName + = b . Login . Username ;
}
return _i18nService . StringComparer . Compare ( aName , bName ) ;
}
}
private class CipherLastUsedComparer : IComparer < CipherView >
{
public int Compare ( CipherView a , CipherView b )
{
var aLastUsed = a . LocalData ! = null & & a . LocalData . ContainsKey ( "lastUsedDate" ) ?
a . LocalData [ "lastUsedDate" ] as DateTime ? : null ;
var bLastUsed = b . LocalData ! = null & & b . LocalData . ContainsKey ( "lastUsedDate" ) ?
b . LocalData [ "lastUsedDate" ] as DateTime ? : null ;
var bothNotNull = aLastUsed ! = null & & bLastUsed ! = null ;
2020-03-28 16:16:28 +03:00
if ( bothNotNull & & aLastUsed . Value < bLastUsed . Value )
2019-04-17 00:34:59 +03:00
{
return 1 ;
}
2020-03-28 16:16:28 +03:00
if ( aLastUsed ! = null & & bLastUsed = = null )
2019-04-17 00:34:59 +03:00
{
return - 1 ;
}
2020-03-28 16:16:28 +03:00
if ( bothNotNull & & aLastUsed . Value > bLastUsed . Value )
2019-04-17 00:34:59 +03:00
{
return - 1 ;
}
2020-03-28 16:16:28 +03:00
if ( bLastUsed ! = null & & aLastUsed = = null )
2019-04-17 00:34:59 +03:00
{
return 1 ;
}
return 0 ;
}
}
private class CipherLastUsedThenNameComparer : IComparer < CipherView >
{
private CipherLastUsedComparer _cipherLastUsedComparer ;
private CipherLocaleComparer _cipherLocaleComparer ;
public CipherLastUsedThenNameComparer ( II18nService i18nService )
{
_cipherLastUsedComparer = new CipherLastUsedComparer ( ) ;
_cipherLocaleComparer = new CipherLocaleComparer ( i18nService ) ;
}
public int Compare ( CipherView a , CipherView b )
{
var result = _cipherLastUsedComparer . Compare ( a , b ) ;
2020-03-28 16:16:28 +03:00
if ( result ! = 0 )
2019-04-17 00:34:59 +03:00
{
return result ;
}
return _cipherLocaleComparer . Compare ( a , b ) ;
}
}
2019-04-12 17:06:47 +03:00
}
}