diff --git a/src/Core/Models/Domain/Cipher.cs b/src/Core/Models/Domain/Cipher.cs index b40ad24cd..8c1c6ad25 100644 --- a/src/Core/Models/Domain/Cipher.cs +++ b/src/Core/Models/Domain/Cipher.cs @@ -74,28 +74,28 @@ namespace Bit.Core.Models.Domain public List PasswordHistory { get; set; } public List CollectionIds { get; set; } - public async Task DecryptAsync(string orgId) + public async Task DecryptAsync() { var model = new CipherView(this); await DecryptObjAsync(model, this, new HashSet { "Name", "Notes" - }, orgId); + }, OrganizationId); switch(Type) { case Enums.CipherType.Login: - model.Login = await Login.DecryptAsync(orgId); + model.Login = await Login.DecryptAsync(OrganizationId); break; case Enums.CipherType.SecureNote: - model.SecureNote = await SecureNote.DecryptAsync(orgId); + model.SecureNote = await SecureNote.DecryptAsync(OrganizationId); break; case Enums.CipherType.Card: - model.Card = await Card.DecryptAsync(orgId); + model.Card = await Card.DecryptAsync(OrganizationId); break; case Enums.CipherType.Identity: - model.Identity = await Identity.DecryptAsync(orgId); + model.Identity = await Identity.DecryptAsync(OrganizationId); break; default: break; @@ -107,7 +107,7 @@ namespace Bit.Core.Models.Domain var tasks = new List(); foreach(var attachment in Attachments) { - var t = attachment.DecryptAsync(orgId) + var t = attachment.DecryptAsync(OrganizationId) .ContinueWith(async decAttachment => model.Attachments.Add(await decAttachment)); tasks.Add(t); } @@ -119,7 +119,7 @@ namespace Bit.Core.Models.Domain var tasks = new List(); foreach(var field in Fields) { - var t = field.DecryptAsync(orgId) + var t = field.DecryptAsync(OrganizationId) .ContinueWith(async decField => model.Fields.Add(await decField)); tasks.Add(t); } @@ -131,7 +131,7 @@ namespace Bit.Core.Models.Domain var tasks = new List(); foreach(var ph in PasswordHistory) { - var t = ph.DecryptAsync(orgId) + var t = ph.DecryptAsync(OrganizationId) .ContinueWith(async decPh => model.PasswordHistory.Add(await decPh)); tasks.Add(t); } diff --git a/src/Core/Models/View/PasswordHistoryView.cs b/src/Core/Models/View/PasswordHistoryView.cs index cb3b52d6a..03d646730 100644 --- a/src/Core/Models/View/PasswordHistoryView.cs +++ b/src/Core/Models/View/PasswordHistoryView.cs @@ -13,6 +13,6 @@ namespace Bit.Core.Models.View } public string Password { get; set; } - public DateTime? LastUsedDate { get; set; } + public DateTime LastUsedDate { get; set; } } } diff --git a/src/Core/Services/CipherService.cs b/src/Core/Services/CipherService.cs index fa943b8bd..b4317cd32 100644 --- a/src/Core/Services/CipherService.cs +++ b/src/Core/Services/CipherService.cs @@ -1,11 +1,11 @@ using Bit.Core.Abstractions; +using Bit.Core.Enums; +using Bit.Core.Models.Data; using Bit.Core.Models.Domain; using Bit.Core.Models.View; using System; using System.Collections.Generic; -using System.Linq.Expressions; -using System.Reflection; -using System.Text; +using System.Linq; using System.Threading.Tasks; namespace Bit.Core.Services @@ -69,12 +69,78 @@ namespace Bit.Core.Services // Adjust password history if(model.Id != null) { - // TODO + if(originalCipher == null) + { + originalCipher = await GetAsync(model.Id); + } + if(originalCipher != null) + { + var existingCipher = await originalCipher.DecryptAsync(); + if(model.PasswordHistory == null) + { + model.PasswordHistory = new List(); + } + if(model.Type == CipherType.Login && existingCipher.Type == CipherType.Login) + { + if(!string.IsNullOrWhiteSpace(existingCipher.Login.Password) && + 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 + { + model.Login.PasswordRevisionDate = DateTime.UtcNow; + } + } + if(existingCipher.HasFields) + { + 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(); + foreach(var ef in existingHiddenFields) + { + var matchedField = hiddenFields.FirstOrDefault(f => f.Name == ef.Name); + if(matchedField == null || matchedField.Value != ef.Value) + { + var ph = new PasswordHistoryView + { + Password = string.Format("{0}: {1}", ef.Name, ef.Value), + LastUsedDate = DateTime.UtcNow + }; + model.PasswordHistory.Insert(0, ph); + } + } + } + } + if(!model.PasswordHistory?.Any() ?? false) + { + model.PasswordHistory = null; + } + else if(model.PasswordHistory != null && model.PasswordHistory.Count > 5) + { + model.PasswordHistory = model.PasswordHistory.Take(5).ToList(); + } } - var cipher = new Cipher(); - cipher.Id = model.Id; - // TODO others + var cipher = new Cipher + { + Id = model.Id, + FolderId = model.FolderId, + Favorite = model.Favorite, + OrganizationId = model.OrganizationId, + Type = model.Type, + CollectionIds = model.CollectionIds + }; if(key == null && cipher.OrganizationId != null) { @@ -85,16 +151,52 @@ namespace Bit.Core.Services } } - var tasks = new List(); - tasks.Add(EncryptObjPropertyAsync(model, cipher, new HashSet + var tasks = new List { - nameof(model.Name) - }, key)); - + EncryptObjPropertyAsync(model, cipher, new HashSet + { + "Name", + "Notes" + }, key), + EncryptCipherDataAsync(cipher, model, key), + EncryptFieldsAsync(model.Fields, key) + .ContinueWith(async fields => cipher.Fields = await fields), + EncryptPasswordHistoriesAsync(model.PasswordHistory, key) + .ContinueWith(async phs => cipher.PasswordHistory = await phs), + EncryptAttachmentsAsync(model.Attachments, key) + .ContinueWith(async attachments => cipher.Attachments = await attachments) + }; await Task.WhenAll(tasks); return cipher; } + public async Task GetAsync(string id) + { + var userId = await _userService.GetUserIdAsync(); + var localData = await _storageService.GetAsync>>( + Keys_LocalData); + var ciphers = await _storageService.GetAsync>( + string.Format(Keys_CiphersFormat, userId)); + if(!ciphers?.ContainsKey(id) ?? true) + { + return null; + } + return new Cipher(ciphers[id], false, + localData?.ContainsKey(id) ?? false ? localData[id] : null); + } + + public async Task> GetAllAsync() + { + var userId = await _userService.GetUserIdAsync(); + var localData = await _storageService.GetAsync>>( + Keys_LocalData); + var ciphers = await _storageService.GetAsync>( + string.Format(Keys_CiphersFormat, userId)); + var response = ciphers.Select(c => new Cipher(c.Value, false, + localData?.ContainsKey(c.Key) ?? false ? localData[c.Key] : null)); + return response.ToList(); + } + private Task EncryptObjPropertyAsync(V model, D obj, HashSet map, SymmetricCryptoKey key) where V : View where D : Domain @@ -125,5 +227,177 @@ namespace Bit.Core.Services } return Task.WhenAll(tasks); } + + private async Task> EncryptAttachmentsAsync( + List attachmentsModel, SymmetricCryptoKey key) + { + if(!attachmentsModel?.Any() ?? true) + { + return null; + } + var tasks = new List(); + var encAttachments = new List(); + foreach(var model in attachmentsModel) + { + var attachment = new Attachment + { + Id = model.Id, + Size = model.Size, + SizeName = model.SizeName, + Url = model.Url + }; + var task = EncryptObjPropertyAsync(model, attachment, new HashSet + { + "FileName" + }, key).ContinueWith(async (t) => + { + if(model.Key != null) + { + attachment.Key = await _cryptoService.EncryptAsync(model.Key.Key, key); + } + encAttachments.Add(attachment); + }); + tasks.Add(task); + } + await Task.WhenAll(tasks); + return encAttachments; + } + + private async Task EncryptCipherDataAsync(Cipher cipher, CipherView model, SymmetricCryptoKey key) + { + switch(cipher.Type) + { + case Enums.CipherType.Login: + cipher.Login = new Login + { + PasswordRevisionDate = model.Login.PasswordRevisionDate + }; + await EncryptObjPropertyAsync(model.Login, cipher.Login, new HashSet + { + "Username", + "Password", + "Totp" + }, key); + if(model.Login.Uris != null) + { + cipher.Login.Uris = new List(); + foreach(var uri in model.Login.Uris) + { + var loginUri = new LoginUri + { + Match = uri.Match + }; + await EncryptObjPropertyAsync(uri, loginUri, new HashSet { "Uri" }, key); + cipher.Login.Uris.Add(loginUri); + } + } + break; + case Enums.CipherType.SecureNote: + cipher.SecureNote = new SecureNote + { + Type = model.SecureNote.Type + }; + break; + case Enums.CipherType.Card: + cipher.Card = new Card(); + await EncryptObjPropertyAsync(model.Card, cipher.Card, new HashSet + { + "CardholderName", + "Brand", + "Number", + "ExpMonth", + "ExpYear", + "Code" + }, key); + break; + case Enums.CipherType.Identity: + cipher.Identity = new Identity(); + await EncryptObjPropertyAsync(model.Identity, cipher.Identity, new HashSet + { + "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."); + } + } + + private async Task> EncryptFieldsAsync(List fieldsModel, SymmetricCryptoKey key) + { + if(!fieldsModel?.Any() ?? true) + { + return null; + } + var tasks = new List(); + var encFields = new List(); + foreach(var model in fieldsModel) + { + var field = new Field + { + Type = model.Type + }; + // normalize boolean type field values + if(model.Type == FieldType.Boolean && model.Value != "true") + { + model.Value = "false"; + } + var task = EncryptObjPropertyAsync(model, field, new HashSet + { + "Name", + "Value" + }, key).ContinueWith((t) => + { + encFields.Add(field); + }); + tasks.Add(task); + } + await Task.WhenAll(tasks); + return encFields; + } + + private async Task> EncryptPasswordHistoriesAsync(List phModels, + SymmetricCryptoKey key) + { + if(!phModels?.Any() ?? true) + { + return null; + } + var tasks = new List(); + var encPhs = new List(); + foreach(var model in phModels) + { + var ph = new PasswordHistory + { + LastUsedDate = model.LastUsedDate + }; + var task = EncryptObjPropertyAsync(model, ph, new HashSet + { + "Password" + }, key).ContinueWith((t) => + { + encPhs.Add(ph); + }); + tasks.Add(task); + } + await Task.WhenAll(tasks); + return encPhs; + } } }