using System; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; using System.Threading.Tasks; using Bit.Core.Abstractions; using Bit.Core.Models.Data; using Bit.Core.Models.Domain; using Bit.Core.Models.View; using Bit.Core.Utilities; namespace Bit.Core.Services { public class CollectionService : ICollectionService { private const char NestingDelimiter = '/'; private List _decryptedCollectionCache; private readonly ICryptoService _cryptoService; private readonly IStateService _stateService; private readonly II18nService _i18nService; public CollectionService( ICryptoService cryptoService, IStateService stateService, II18nService i18nService) { _cryptoService = cryptoService; _stateService = stateService; _i18nService = i18nService; } public void ClearCache() { _decryptedCollectionCache = null; } public async Task EncryptAsync(CollectionView model) { if (model.OrganizationId == null) { throw new Exception("Collection has no organization id."); } var key = await _cryptoService.GetOrgKeyAsync(model.OrganizationId); if (key == null) { throw new Exception("No key for this collection's organization."); } var collection = new Collection { Id = model.Id, OrganizationId = model.OrganizationId, ReadOnly = model.ReadOnly, Name = await _cryptoService.EncryptAsync(model.Name, key) }; return collection; } public async Task> DecryptManyAsync(List collections) { if (collections == null) { return new List(); } var decCollections = new List(); async Task decryptAndAddCollectionAsync(Collection collection) { var c = await collection.DecryptAsync(); decCollections.Add(c); } var tasks = new List(); foreach (var collection in collections) { tasks.Add(decryptAndAddCollectionAsync(collection)); } await Task.WhenAll(tasks); return decCollections.OrderBy(c => c, new CollectionLocaleComparer(_i18nService)).ToList(); } public async Task GetAsync(string id) { var collections = await _stateService.GetEncryptedCollectionsAsync(); if (!collections?.ContainsKey(id) ?? true) { return null; } return new Collection(collections[id]); } public async Task> GetAllAsync() { var collections = await _stateService.GetEncryptedCollectionsAsync(); var response = collections?.Select(c => new Collection(c.Value)); return response?.ToList() ?? new List(); } // TODO: sequentialize? public async Task> GetAllDecryptedAsync() { if (_decryptedCollectionCache != null) { return _decryptedCollectionCache; } var hasKey = await _cryptoService.HasKeyAsync(); if (!hasKey) { throw new Exception("No key."); } var collections = await GetAllAsync(); _decryptedCollectionCache = await DecryptManyAsync(collections); return _decryptedCollectionCache; } public async Task>> GetAllNestedAsync(List collections = null) { if (collections == null) { collections = await GetAllDecryptedAsync(); } var nodes = new List>(); foreach (var c in collections) { var collectionCopy = new CollectionView { Id = c.Id, OrganizationId = c.OrganizationId }; var parts = c.Name != null ? Regex.Replace(c.Name, "^\\/+|\\/+$", string.Empty).Split(NestingDelimiter) : new string[] { }; CoreHelpers.NestedTraverse(nodes, 0, parts, collectionCopy, null, NestingDelimiter); } return nodes; } public async Task> GetNestedAsync(string id) { var collections = await GetAllNestedAsync(); return CoreHelpers.GetTreeNodeObject(collections, id); } public async Task UpsertAsync(CollectionData collection) { var collections = await _stateService.GetEncryptedCollectionsAsync(); if (collections == null) { collections = new Dictionary(); } if (!collections.ContainsKey(collection.Id)) { collections.Add(collection.Id, null); } collections[collection.Id] = collection; await _stateService.SetEncryptedCollectionsAsync(collections); _decryptedCollectionCache = null; } public async Task UpsertAsync(List collection) { var collections = await _stateService.GetEncryptedCollectionsAsync(); if (collections == null) { collections = new Dictionary(); } foreach (var c in collection) { if (!collections.ContainsKey(c.Id)) { collections.Add(c.Id, null); } collections[c.Id] = c; } await _stateService.SetEncryptedCollectionsAsync(collections); _decryptedCollectionCache = null; } public async Task ReplaceAsync(Dictionary collections) { await _stateService.SetEncryptedCollectionsAsync(collections); _decryptedCollectionCache = null; } public async Task ClearAsync(string userId) { await _stateService.SetEncryptedCollectionsAsync(null, userId); _decryptedCollectionCache = null; } public async Task DeleteAsync(string id) { var collections = await _stateService.GetEncryptedCollectionsAsync(); if (collections == null || !collections.ContainsKey(id)) { return; } collections.Remove(id); await _stateService.SetEncryptedCollectionsAsync(collections); _decryptedCollectionCache = null; } private class CollectionLocaleComparer : IComparer { private readonly II18nService _i18nService; public CollectionLocaleComparer(II18nService i18nService) { _i18nService = i18nService; } public int Compare(CollectionView a, CollectionView 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); } } } }