mirror of
https://github.com/bitwarden/android.git
synced 2024-12-25 18:38:27 +03:00
more cipher service functions
This commit is contained in:
parent
1103354de3
commit
567ebcd06e
1 changed files with 360 additions and 0 deletions
|
@ -3,9 +3,11 @@ using Bit.Core.Enums;
|
||||||
using Bit.Core.Models.Data;
|
using Bit.Core.Models.Data;
|
||||||
using Bit.Core.Models.Domain;
|
using Bit.Core.Models.Domain;
|
||||||
using Bit.Core.Models.View;
|
using Bit.Core.Models.View;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Bit.Core.Services
|
namespace Bit.Core.Services
|
||||||
|
@ -16,6 +18,8 @@ namespace Bit.Core.Services
|
||||||
private const string Keys_LocalData = "ciphersLocalData";
|
private const string Keys_LocalData = "ciphersLocalData";
|
||||||
private const string Keys_NeverDomains = "neverDomains";
|
private const string Keys_NeverDomains = "neverDomains";
|
||||||
|
|
||||||
|
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" };
|
||||||
private List<CipherView> _decryptedCipherCache;
|
private List<CipherView> _decryptedCipherCache;
|
||||||
private readonly ICryptoService _cryptoService;
|
private readonly ICryptoService _cryptoService;
|
||||||
private readonly IUserService _userService;
|
private readonly IUserService _userService;
|
||||||
|
@ -197,6 +201,362 @@ namespace Bit.Core.Services
|
||||||
return response.ToList();
|
return response.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: sequentialize?
|
||||||
|
public async Task<List<CipherView>> GetAllDecryptedAsync()
|
||||||
|
{
|
||||||
|
if(DecryptedCipherCache != null)
|
||||||
|
{
|
||||||
|
return DecryptedCipherCache;
|
||||||
|
}
|
||||||
|
var hashKey = await _cryptoService.HasKeyAsync();
|
||||||
|
if(!hashKey)
|
||||||
|
{
|
||||||
|
throw new Exception("No key.");
|
||||||
|
}
|
||||||
|
var decCiphers = new List<CipherView>();
|
||||||
|
var tasks = new List<Task>();
|
||||||
|
var ciphers = await GetAllAsync();
|
||||||
|
foreach(var cipher in ciphers)
|
||||||
|
{
|
||||||
|
tasks.Add(cipher.DecryptAsync().ContinueWith(async c => decCiphers.Add(await c)));
|
||||||
|
}
|
||||||
|
await Task.WhenAll(tasks);
|
||||||
|
// TODO: sort
|
||||||
|
DecryptedCipherCache = decCiphers;
|
||||||
|
return DecryptedCipherCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<CipherView>> GetAllDecryptedForGroupingAsync(string groupingId, bool folder = true)
|
||||||
|
{
|
||||||
|
var ciphers = await GetAllDecryptedAsync();
|
||||||
|
return ciphers.Where(cipher =>
|
||||||
|
{
|
||||||
|
if(folder && cipher.FolderId == groupingId)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if(!folder && cipher.CollectionIds != null && cipher.CollectionIds.Contains(groupingId))
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
if(string.IsNullOrWhiteSpace(url) && includeOtherTypes == null)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
|
||||||
|
var defaultMatch = await _storageService.GetAsync<UriMatchType?>(Constants.DefaultUriMatch);
|
||||||
|
if(defaultMatch == null)
|
||||||
|
{
|
||||||
|
defaultMatch = UriMatchType.Domain;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach(var cipher in ciphers)
|
||||||
|
{
|
||||||
|
if(cipher.Type != CipherType.Login && (includeOtherTypes?.Any(t => t == cipher.Type) ?? false))
|
||||||
|
{
|
||||||
|
others.Add(cipher);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(cipher.Type != CipherType.Login || cipher.Login?.Uris == null || !cipher.Login.Uris.Any())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach(var u in cipher.Login.Uris)
|
||||||
|
{
|
||||||
|
if(string.IsNullOrWhiteSpace(u.Uri))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
var match = false;
|
||||||
|
switch(u.Match)
|
||||||
|
{
|
||||||
|
case null:
|
||||||
|
case UriMatchType.Domain:
|
||||||
|
match = CheckDefaultUriMatch(cipher, u, matchingLogins, matchingFuzzyLogins,
|
||||||
|
matchingDomainsSet, matchingFuzzyDomainsSet, mobileApp, mobileAppSearchTerms);
|
||||||
|
if(match && u.Domain != null)
|
||||||
|
{
|
||||||
|
if(_domainMatchBlacklist.ContainsKey(u.Domain))
|
||||||
|
{
|
||||||
|
var domainUrlHost = CoreHelpers.GetHost(url);
|
||||||
|
if(_domainMatchBlacklist[u.Domain].Contains(domainUrlHost))
|
||||||
|
{
|
||||||
|
match = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case UriMatchType.Host:
|
||||||
|
var urlHost = CoreHelpers.GetHost(url);
|
||||||
|
match = urlHost != null && urlHost == CoreHelpers.GetHost(u.Uri);
|
||||||
|
if(match)
|
||||||
|
{
|
||||||
|
AddMatchingLogin(cipher, matchingLogins, matchingFuzzyLogins);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case UriMatchType.Exact:
|
||||||
|
match = url == u.Uri;
|
||||||
|
if(match)
|
||||||
|
{
|
||||||
|
AddMatchingLogin(cipher, matchingLogins, matchingFuzzyLogins);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case UriMatchType.StartsWith:
|
||||||
|
match = url.StartsWith(u.Uri);
|
||||||
|
if(match)
|
||||||
|
{
|
||||||
|
AddMatchingLogin(cipher, matchingLogins, matchingFuzzyLogins);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case UriMatchType.RegularExpression:
|
||||||
|
var regex = new Regex(u.Uri, RegexOptions.IgnoreCase, TimeSpan.FromSeconds(1));
|
||||||
|
match = regex.IsMatch(url);
|
||||||
|
if(match)
|
||||||
|
{
|
||||||
|
AddMatchingLogin(cipher, matchingLogins, matchingFuzzyLogins);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case UriMatchType.Never:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(match)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new Tuple<List<CipherView>, List<CipherView>, List<CipherView>>(
|
||||||
|
matchingLogins, matchingFuzzyLogins, others);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helpers
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
if(matchingDomainsSet.Contains(loginUriString))
|
||||||
|
{
|
||||||
|
AddMatchingLogin(cipher, matchingLogins, matchingFuzzyLogins);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if(mobileApp && matchingFuzzyDomainsSet.Contains(loginUriString))
|
||||||
|
{
|
||||||
|
AddMatchingFuzzyLogin(cipher, matchingLogins, matchingFuzzyLogins);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if(!mobileApp)
|
||||||
|
{
|
||||||
|
var info = InfoFromMobileAppUrl(loginUriString);
|
||||||
|
if(info?.Item1 != null && matchingDomainsSet.Contains(info.Item1))
|
||||||
|
{
|
||||||
|
AddMatchingFuzzyLogin(cipher, matchingLogins, matchingFuzzyLogins);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!loginUri.Uri.Contains("://") && loginUriString.Contains("."))
|
||||||
|
{
|
||||||
|
loginUriString = "http://" + loginUriString;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(loginUriDomain != null)
|
||||||
|
{
|
||||||
|
loginUriDomain = loginUriDomain.ToLowerInvariant();
|
||||||
|
if(matchingDomainsSet.Contains(loginUriDomain))
|
||||||
|
{
|
||||||
|
AddMatchingLogin(cipher, matchingLogins, matchingFuzzyLogins);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if(mobileApp && matchingFuzzyDomainsSet.Contains(loginUriDomain))
|
||||||
|
{
|
||||||
|
AddMatchingFuzzyLogin(cipher, matchingLogins, matchingFuzzyLogins);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mobileApp && (mobileAppSearchTerms?.Any() ?? false))
|
||||||
|
{
|
||||||
|
var addedFromSearchTerm = false;
|
||||||
|
var loginName = cipher.Name?.ToLowerInvariant();
|
||||||
|
foreach(var term in mobileAppSearchTerms)
|
||||||
|
{
|
||||||
|
addedFromSearchTerm = (loginUriDomain != null && loginUriDomain.Contains(term)) ||
|
||||||
|
(loginName != null && loginName.Contains(term));
|
||||||
|
if(!addedFromSearchTerm)
|
||||||
|
{
|
||||||
|
var domainTerm = loginUriDomain?.Split('.')[0];
|
||||||
|
addedFromSearchTerm =
|
||||||
|
(domainTerm != null && domainTerm.Length > 2 && term.Contains(domainTerm)) ||
|
||||||
|
(loginName != null && term.Contains(loginName));
|
||||||
|
}
|
||||||
|
if(addedFromSearchTerm)
|
||||||
|
{
|
||||||
|
AddMatchingFuzzyLogin(cipher, matchingLogins, matchingFuzzyLogins);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddMatchingLogin(CipherView cipher, List<CipherView> matchingLogins,
|
||||||
|
List<CipherView> matchingFuzzyLogins)
|
||||||
|
{
|
||||||
|
if(matchingFuzzyLogins.Contains(cipher))
|
||||||
|
{
|
||||||
|
matchingFuzzyLogins.Remove(cipher);
|
||||||
|
}
|
||||||
|
matchingLogins.Add(cipher);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddMatchingFuzzyLogin(CipherView cipher, List<CipherView> matchingLogins,
|
||||||
|
List<CipherView> matchingFuzzyLogins)
|
||||||
|
{
|
||||||
|
if(!matchingFuzzyLogins.Contains(cipher) && !matchingLogins.Contains(cipher))
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
foreach(var eqDomain in eqDomains)
|
||||||
|
{
|
||||||
|
var eqDomainSet = new HashSet<string>(eqDomain);
|
||||||
|
if(mobileApp)
|
||||||
|
{
|
||||||
|
if(eqDomainSet.Contains(url))
|
||||||
|
{
|
||||||
|
eqDomain.Select(d => matchingDomains.Add(d));
|
||||||
|
}
|
||||||
|
else if(mobileAppWebUriString != null && eqDomainSet.Contains(mobileAppWebUriString))
|
||||||
|
{
|
||||||
|
eqDomain.Select(d => matchingFuzzyDomains.Add(d));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(eqDomainSet.Contains(url))
|
||||||
|
{
|
||||||
|
eqDomain.Select(d => matchingDomains.Add(d));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!matchingDomains.Any())
|
||||||
|
{
|
||||||
|
matchingDomains.Add(mobileApp ? url : domain);
|
||||||
|
}
|
||||||
|
if(mobileApp && mobileAppWebUriString != null &&
|
||||||
|
!matchingFuzzyDomains.Any() && !matchingDomains.Contains(mobileAppWebUriString))
|
||||||
|
{
|
||||||
|
matchingFuzzyDomains.Add(mobileAppWebUriString);
|
||||||
|
}
|
||||||
|
return new Tuple<HashSet<string>, HashSet<string>>(matchingDomains, matchingFuzzyDomains);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Tuple<string, string[]> InfoFromMobileAppUrl(string mobileAppUrlString)
|
||||||
|
{
|
||||||
|
if(UrlIsAndroidApp(mobileAppUrlString))
|
||||||
|
{
|
||||||
|
return InfoFromAndroidAppUri(mobileAppUrlString);
|
||||||
|
}
|
||||||
|
else if(UrlIsiOSApp(mobileAppUrlString))
|
||||||
|
{
|
||||||
|
return InfoFromiOSAppUrl(mobileAppUrlString);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Tuple<string, string[]> InfoFromAndroidAppUri(string androidAppUrlString)
|
||||||
|
{
|
||||||
|
if(!UrlIsAndroidApp(androidAppUrlString))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
var androidUrlParts = androidAppUrlString.Replace(Constants.AndroidAppProtocol, string.Empty).Split('.');
|
||||||
|
if(androidUrlParts.Length >= 2)
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
if(!UrlIsiOSApp(iosAppUrlString))
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
private Task EncryptObjPropertyAsync<V, D>(V model, D obj, HashSet<string> map, SymmetricCryptoKey key)
|
private Task EncryptObjPropertyAsync<V, D>(V model, D obj, HashSet<string> map, SymmetricCryptoKey key)
|
||||||
where V : View
|
where V : View
|
||||||
where D : Domain
|
where D : Domain
|
||||||
|
|
Loading…
Reference in a new issue