diff --git a/ModernKeePass.Application/Application.csproj b/ModernKeePass.Application/Application.csproj index 813b769..2415ebe 100644 --- a/ModernKeePass.Application/Application.csproj +++ b/ModernKeePass.Application/Application.csproj @@ -54,6 +54,9 @@ + + + @@ -71,6 +74,7 @@ + @@ -78,6 +82,7 @@ + @@ -102,9 +107,6 @@ - - - diff --git a/ModernKeePass.Application/Common/Interfaces/IDatabaseProxy.cs b/ModernKeePass.Application/Common/Interfaces/IDatabaseProxy.cs index 61cd1f0..748f0d1 100644 --- a/ModernKeePass.Application/Common/Interfaces/IDatabaseProxy.cs +++ b/ModernKeePass.Application/Common/Interfaces/IDatabaseProxy.cs @@ -25,8 +25,9 @@ namespace ModernKeePass.Application.Common.Interfaces void CloseDatabase(); Task AddEntry(string parentGroupId, string entryId); + Task InsertEntry(string parentGroupId, string entryId, int messageIndex); Task AddGroup(string parentGroupId, string groupId); - void UpdateEntry(string entryId, string fieldName, string fieldValue); + void UpdateEntry(string entryId, string fieldName, object fieldValue); void UpdateGroup(string groupId); Task RemoveEntry(string parentGroupId, string entryId); Task RemoveGroup(string parentGroupId, string groupId); diff --git a/ModernKeePass.Application/Cryptography/Commands/SetCipher/SetCipherCommand.cs b/ModernKeePass.Application/Cryptography/Commands/SetCipher/SetCipherCommand.cs new file mode 100644 index 0000000..1a5a8fd --- /dev/null +++ b/ModernKeePass.Application/Cryptography/Commands/SetCipher/SetCipherCommand.cs @@ -0,0 +1,27 @@ +using MediatR; +using ModernKeePass.Application.Common.Interfaces; +using ModernKeePass.Domain.Exceptions; + +namespace ModernKeePass.Application.Cryptography.Commands.SetCipher +{ + public class SetCipherCommand : IRequest + { + public string CipherId { get; set; } + + public class SetCipherCommandHandler : IRequestHandler + { + private readonly IDatabaseProxy _database; + + public SetCipherCommandHandler(IDatabaseProxy database) + { + _database = database; + } + + public void Handle(SetCipherCommand message) + { + if (_database.IsOpen) _database.CipherId = message.CipherId; + else throw new DatabaseClosedException(); + } + } + } +} \ No newline at end of file diff --git a/ModernKeePass.Application/Cryptography/Commands/SetCompression/SetCompressionCommand.cs b/ModernKeePass.Application/Cryptography/Commands/SetCompression/SetCompressionCommand.cs new file mode 100644 index 0000000..7a95564 --- /dev/null +++ b/ModernKeePass.Application/Cryptography/Commands/SetCompression/SetCompressionCommand.cs @@ -0,0 +1,27 @@ +using MediatR; +using ModernKeePass.Application.Common.Interfaces; +using ModernKeePass.Domain.Exceptions; + +namespace ModernKeePass.Application.Cryptography.Commands.SetCompression +{ + public class SetCompressionCommand : IRequest + { + public string Compression { get; set; } + + public class SetCompressionCommandHandler : IRequestHandler + { + private readonly IDatabaseProxy _database; + + public SetCompressionCommandHandler(IDatabaseProxy database) + { + _database = database; + } + + public void Handle(SetCompressionCommand message) + { + if (_database.IsOpen) _database.Compression = message.Compression; + else throw new DatabaseClosedException(); + } + } + } +} \ No newline at end of file diff --git a/ModernKeePass.Application/Cryptography/Commands/SetKeyDerivation/SetKeyDerivationCommand.cs b/ModernKeePass.Application/Cryptography/Commands/SetKeyDerivation/SetKeyDerivationCommand.cs new file mode 100644 index 0000000..cbeb949 --- /dev/null +++ b/ModernKeePass.Application/Cryptography/Commands/SetKeyDerivation/SetKeyDerivationCommand.cs @@ -0,0 +1,27 @@ +using MediatR; +using ModernKeePass.Application.Common.Interfaces; +using ModernKeePass.Domain.Exceptions; + +namespace ModernKeePass.Application.Cryptography.Commands.SetKeyDerivation +{ + public class SetKeyDerivationCommand : IRequest + { + public string KeyDerivationId { get; set; } + + public class SetKeyDerivationCommandHandler : IRequestHandler + { + private readonly IDatabaseProxy _database; + + public SetKeyDerivationCommandHandler(IDatabaseProxy database) + { + _database = database; + } + + public void Handle(SetKeyDerivationCommand message) + { + if (_database.IsOpen) _database.KeyDerivationId = message.KeyDerivationId; + else throw new DatabaseClosedException(); + } + } + } +} \ No newline at end of file diff --git a/ModernKeePass.Application/Database/Commands/UpdateCredentials/UpdateCredentialsCommand.cs b/ModernKeePass.Application/Database/Commands/UpdateCredentials/UpdateCredentialsCommand.cs index b565c0c..beb1314 100644 --- a/ModernKeePass.Application/Database/Commands/UpdateCredentials/UpdateCredentialsCommand.cs +++ b/ModernKeePass.Application/Database/Commands/UpdateCredentials/UpdateCredentialsCommand.cs @@ -14,7 +14,7 @@ namespace ModernKeePass.Application.Database.Commands.UpdateCredentials { private readonly IDatabaseProxy _database; - public UpdateCredentialsCommandHandler(IDatabaseProxy database, IMediator mediator) + public UpdateCredentialsCommandHandler(IDatabaseProxy database) { _database = database; } diff --git a/ModernKeePass.Application/DependencyInjection.cs b/ModernKeePass.Application/DependencyInjection.cs index 0b9a065..4e0ebd1 100644 --- a/ModernKeePass.Application/DependencyInjection.cs +++ b/ModernKeePass.Application/DependencyInjection.cs @@ -13,7 +13,7 @@ namespace ModernKeePass.Application var assembly = typeof(DependencyInjection).GetTypeInfo().Assembly; services.AddAutoMapper(assembly); services.AddMediatR(assembly); - //services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly()); + //services.AddValidatorsFromAssembly(assembly); return services; } diff --git a/ModernKeePass.Application/Entry/Commands/SetFieldValue/SetFieldValueCommand.cs b/ModernKeePass.Application/Entry/Commands/SetFieldValue/SetFieldValueCommand.cs index c9379c5..32176ec 100644 --- a/ModernKeePass.Application/Entry/Commands/SetFieldValue/SetFieldValueCommand.cs +++ b/ModernKeePass.Application/Entry/Commands/SetFieldValue/SetFieldValueCommand.cs @@ -8,7 +8,7 @@ namespace ModernKeePass.Application.Entry.Commands.SetFieldValue { public string EntryId { get; set; } public string FieldName { get; set; } - public string FieldValue { get; set; } + public object FieldValue { get; set; } public class SetFieldValueCommandHandler : IRequestHandler { diff --git a/ModernKeePass.Application/Entry/Commands/SetFieldValue/SetFieldValueCommandValidator.cs b/ModernKeePass.Application/Entry/Commands/SetFieldValue/SetFieldValueCommandValidator.cs new file mode 100644 index 0000000..c2f20d1 --- /dev/null +++ b/ModernKeePass.Application/Entry/Commands/SetFieldValue/SetFieldValueCommandValidator.cs @@ -0,0 +1,17 @@ +using FluentValidation; + +namespace ModernKeePass.Application.Entry.Commands.SetFieldValue +{ + public class SetFieldValueCommandValidator: AbstractValidator + { + public SetFieldValueCommandValidator() + { + RuleFor(v => v.EntryId) + .NotNull() + .NotEmpty(); + RuleFor(v => v.FieldName) + .NotNull() + .NotEmpty(); + } + } +} \ No newline at end of file diff --git a/ModernKeePass.Application/Entry/Models/EntryVm.cs b/ModernKeePass.Application/Entry/Models/EntryVm.cs index ea13b79..f218bd0 100644 --- a/ModernKeePass.Application/Entry/Models/EntryVm.cs +++ b/ModernKeePass.Application/Entry/Models/EntryVm.cs @@ -17,7 +17,7 @@ namespace ModernKeePass.Application.Entry.Models public string Notes { get; set; } public Uri Url { get; set; } public Dictionary AdditionalFields { get; set; } - public IEnumerable History { get; set; } + public IEnumerable History { get; set; } public Icon Icon { get; set; } public Color ForegroundColor { get; set; } public Color BackgroundColor { get; set; } diff --git a/ModernKeePass.Application/Group/Commands/InsertEntry/InsertEntryCommand.cs b/ModernKeePass.Application/Group/Commands/InsertEntry/InsertEntryCommand.cs new file mode 100644 index 0000000..db3e417 --- /dev/null +++ b/ModernKeePass.Application/Group/Commands/InsertEntry/InsertEntryCommand.cs @@ -0,0 +1,34 @@ +using System.Threading.Tasks; +using MediatR; +using ModernKeePass.Application.Common.Interfaces; +using ModernKeePass.Application.Entry.Models; +using ModernKeePass.Application.Group.Models; +using ModernKeePass.Domain.Exceptions; + +namespace ModernKeePass.Application.Group.Commands.InsertEntry +{ + public class InsertEntryCommand : IRequest + { + public GroupVm ParentGroup { get; set; } + public EntryVm Entry { get; set; } + public int Index { get; set; } + + public class InsertEntryCommandHandler : IAsyncRequestHandler + { + private readonly IDatabaseProxy _database; + + public InsertEntryCommandHandler(IDatabaseProxy database) + { + _database = database; + } + + public async Task Handle(InsertEntryCommand message) + { + if (!_database.IsOpen) throw new DatabaseClosedException(); + + await _database.InsertEntry(message.ParentGroup.Id, message.Entry.Id, message.Index); + message.ParentGroup.Entries.Insert(message.Index, message.Entry); + } + } + } +} \ No newline at end of file diff --git a/ModernKeePass.Infrastructure/KeePass/KeePassDatabaseClient.cs b/ModernKeePass.Infrastructure/KeePass/KeePassDatabaseClient.cs index daf6d2a..bcdb38f 100644 --- a/ModernKeePass.Infrastructure/KeePass/KeePassDatabaseClient.cs +++ b/ModernKeePass.Infrastructure/KeePass/KeePassDatabaseClient.cs @@ -5,6 +5,7 @@ using AutoMapper; using ModernKeePass.Application.Common.Interfaces; using ModernKeePass.Domain.Dtos; using ModernKeePass.Domain.Entities; +using ModernKeePass.Domain.Enums; using ModernKeePass.Domain.Exceptions; using ModernKeePassLib; using ModernKeePassLib.Cryptography.KeyDerivation; @@ -45,6 +46,7 @@ namespace ModernKeePass.Infrastructure.KeePass } set { _pwDatabase.RecycleBinUuid = BuildIdFromString(value); } } + public string CipherId { get { return _pwDatabase.DataCipherUuid.ToHexString(); } @@ -168,6 +170,17 @@ namespace ModernKeePass.Infrastructure.KeePass parentPwGroup.AddEntry(pwEntry, true); }); } + + public async Task InsertEntry(string parentGroupId, string entryId, int index) + { + await Task.Run(() => + { + var parentPwGroup = _pwDatabase.RootGroup.FindGroup(BuildIdFromString(parentGroupId), true); + var pwEntry = _pwDatabase.RootGroup.FindEntry(BuildIdFromString(entryId), true); + parentPwGroup.Entries.Insert((uint)index, pwEntry); + }); + } + public async Task AddGroup(string parentGroupId, string groupId) { await Task.Run(() => @@ -197,15 +210,34 @@ namespace ModernKeePass.Infrastructure.KeePass }); } - public void UpdateEntry(string entryId, string fieldName, string fieldValue) + public void UpdateEntry(string entryId, string fieldName, object fieldValue) { var pwEntry = _pwDatabase.RootGroup.FindEntry(BuildIdFromString(entryId), true); pwEntry.Touch(true); pwEntry.CreateBackup(null); - pwEntry.Strings.Set(EntryFieldMapper.MapFieldToPwDef(fieldName), new ProtectedString(true, fieldValue)); + + switch (fieldName) + { + case EntryFieldName.Title: + case EntryFieldName.UserName: + case EntryFieldName.Password: + case EntryFieldName.Notes: + case EntryFieldName.Url: + pwEntry.Strings.Set(EntryFieldMapper.MapFieldToPwDef(fieldName), new ProtectedString(true, fieldValue.ToString())); + break; + case EntryFieldName.HasExpirationDate: + pwEntry.Expires = (bool)fieldValue; + break; + case EntryFieldName.ExpirationDate: + pwEntry.ExpiryTime = (DateTime)fieldValue; + break; + case EntryFieldName.Icon: + pwEntry.IconId = IconMapper.MapIconToPwIcon((Icon)fieldValue); + break; + } } - public void UpdateGroup(string group) + public void UpdateGroup(string groupId) { throw new NotImplementedException(); } @@ -237,7 +269,7 @@ namespace ModernKeePass.Infrastructure.KeePass var id = pwEntry.Uuid; pwEntry.ParentGroup.Entries.Remove(pwEntry); - if (_pwDatabase.RecycleBinEnabled) + if (!_pwDatabase.RecycleBinEnabled || pwEntry.ParentGroup.Uuid.Equals(_pwDatabase.RecycleBinUuid)) { _pwDatabase.DeletedObjects.Add(new PwDeletedObject(id, DateTime.UtcNow)); } @@ -252,7 +284,7 @@ namespace ModernKeePass.Infrastructure.KeePass var id = pwGroup.Uuid; pwGroup.ParentGroup.Groups.Remove(pwGroup); - if (_pwDatabase.RecycleBinEnabled) + if (!_pwDatabase.RecycleBinEnabled || pwGroup.ParentGroup.Uuid.Equals(_pwDatabase.RecycleBinUuid)) { _pwDatabase.DeletedObjects.Add(new PwDeletedObject(id, DateTime.UtcNow)); } diff --git a/ModernKeePass/Actions/DeleteEntityAction.cs b/ModernKeePass/Actions/DeleteEntityAction.cs index cc08709..ba23276 100644 --- a/ModernKeePass/Actions/DeleteEntityAction.cs +++ b/ModernKeePass/Actions/DeleteEntityAction.cs @@ -1,9 +1,9 @@ using System.Windows.Input; using Windows.UI.Xaml; using Microsoft.Xaml.Interactivity; +using ModernKeePass.Application.Resources.Queries; using ModernKeePass.Common; using ModernKeePass.Interfaces; -using ModernKeePass.Services; using ModernKeePass.ViewModels; namespace ModernKeePass.Actions @@ -32,19 +32,23 @@ namespace ModernKeePass.Actions public object Execute(object sender, object parameter) { - var resource = new ResourcesService(); + var mediator = App.Mediator; var type = Entity is GroupVm ? "Group" : "Entry"; var message = Entity.IsRecycleOnDelete - ? resource.GetResourceValue($"{type}RecyclingConfirmation") - : resource.GetResourceValue($"{type}DeletingConfirmation"); - var text = Entity.IsRecycleOnDelete ? resource.GetResourceValue($"{type}Recycled") : resource.GetResourceValue($"{type}Deleted"); - MessageDialogHelper.ShowActionDialog(resource.GetResourceValue("EntityDeleteTitle"), message, - resource.GetResourceValue("EntityDeleteActionButton"), - resource.GetResourceValue("EntityDeleteCancelButton"), a => + ? mediator.Send(new GetResourceQuery { Key = $"{type}RecyclingConfirmation" }) + : mediator.Send(new GetResourceQuery { Key = $"{type}DeletingConfirmation" }); + var text = Entity.IsRecycleOnDelete ? + mediator.Send(new GetResourceQuery { Key = $"{type}Recycled" }) : + mediator.Send(new GetResourceQuery { Key = $"{type}Deleted" }); + MessageDialogHelper.ShowActionDialog( + mediator.Send(new GetResourceQuery { Key = "EntityDeleteTitle" }).GetAwaiter().GetResult(), + message.GetAwaiter().GetResult(), + mediator.Send(new GetResourceQuery { Key = "EntityDeleteActionButton" }).GetAwaiter().GetResult(), + mediator.Send(new GetResourceQuery { Key = "EntityDeleteCancelButton" }).GetAwaiter().GetResult(), async a => { - ToastNotificationHelper.ShowMovedToast(Entity, resource.GetResourceValue("EntityDeleting"), text); - Entity.MarkForDelete(resource.GetResourceValue("RecycleBinTitle")); + ToastNotificationHelper.ShowMovedToast(Entity, await mediator.Send(new GetResourceQuery { Key = "EntityDeleting" }), await text); + await Entity.MarkForDelete(await mediator.Send(new GetResourceQuery { Key = "RecycleBinTitle"})); Command.Execute(null); }, null).GetAwaiter(); diff --git a/ModernKeePass/Common/ToastNotificationHelper.cs b/ModernKeePass/Common/ToastNotificationHelper.cs index 1dd88c8..9b2592e 100644 --- a/ModernKeePass/Common/ToastNotificationHelper.cs +++ b/ModernKeePass/Common/ToastNotificationHelper.cs @@ -13,7 +13,7 @@ namespace ModernKeePass.Common { var notificationXml = ToastNotificationManager.GetTemplateContent(ToastTemplateType.ToastText02); var toastElements = notificationXml.GetElementsByTagName("text"); - toastElements[0].AppendChild(notificationXml.CreateTextNode($"{action} {entity.Name}")); + toastElements[0].AppendChild(notificationXml.CreateTextNode($"{action} {entity.Title}")); toastElements[1].AppendChild(notificationXml.CreateTextNode(text)); var toastNode = notificationXml.SelectSingleNode("/toast"); diff --git a/ModernKeePass/Interfaces/IVmEntity.cs b/ModernKeePass/Interfaces/IVmEntity.cs index 093ae64..7bd1203 100644 --- a/ModernKeePass/Interfaces/IVmEntity.cs +++ b/ModernKeePass/Interfaces/IVmEntity.cs @@ -9,9 +9,9 @@ namespace ModernKeePass.Interfaces { GroupVm ParentGroup { get; } GroupVm PreviousGroup { get; } - int IconId { get; } + int Icon { get; } string Id { get; } - string Name { get; set; } + string Title { get; set; } IEnumerable BreadCrumb { get; } bool IsEditMode { get; } bool IsRecycleOnDelete { get; } diff --git a/ModernKeePass/Services/ImportService.cs b/ModernKeePass/Services/ImportService.cs index e339abc..baa718f 100644 --- a/ModernKeePass/Services/ImportService.cs +++ b/ModernKeePass/Services/ImportService.cs @@ -14,7 +14,7 @@ namespace ModernKeePass.Services foreach (var entity in data) { var entry = group.AddNewEntry(); - entry.Name = entity["0"]; + entry.Title = entity["0"]; entry.UserName = entity["1"]; entry.Password = entity["2"]; if (entity.Count > 3) entry.Url = entity["3"]; diff --git a/ModernKeePass/ViewModels/EntryVm.cs b/ModernKeePass/ViewModels/EntryVm.cs index 02994af..1adb5eb 100644 --- a/ModernKeePass/ViewModels/EntryVm.cs +++ b/ModernKeePass/ViewModels/EntryVm.cs @@ -1,25 +1,27 @@ using System; using System.Collections.Generic; -using System.ComponentModel; using System.Drawing; +using System.Threading.Tasks; using System.Windows.Input; +using MediatR; +using ModernKeePass.Application.Database.Commands.SaveDatabase; +using ModernKeePass.Application.Entry.Commands.SetFieldValue; +using ModernKeePass.Application.Group.Commands.DeleteEntry; +using ModernKeePass.Application.Resources.Queries; +using ModernKeePass.Application.Security.Commands.GeneratePassword; +using ModernKeePass.Application.Security.Queries.EstimatePasswordComplexity; using ModernKeePass.Common; using ModernKeePass.Interfaces; -using ModernKeePass.Services; -using ModernKeePassLib; -using ModernKeePassLib.Cryptography.PasswordGenerator; -using ModernKeePassLib.Security; -using ModernKeePassLib.Cryptography; namespace ModernKeePass.ViewModels { - public class EntryVm : INotifyPropertyChanged, IVmEntity, ISelectableModel + public class EntryVm : NotifyPropertyChangedBase, IVmEntity, ISelectableModel { public GroupVm ParentGroup { get; private set; } public GroupVm PreviousGroup { get; private set; } public bool IsRevealPasswordEnabled => !string.IsNullOrEmpty(Password); - public bool HasExpired => HasExpirationDate && _pwEntry.ExpiryTime < DateTime.Now; - public double PasswordComplexityIndicator => QualityEstimation.EstimatePasswordBits(Password?.ToCharArray()); + public bool HasExpired => HasExpirationDate && ExpiryDate < DateTime.Now; + public double PasswordComplexityIndicator => _mediator.Send(new EstimatePasswordComplexityQuery {Password = Password}).GetAwaiter().GetResult(); public bool UpperCasePatternSelected { get; set; } = true; public bool LowerCasePatternSelected { get; set; } = true; public bool DigitsPatternSelected { get; set; } = true; @@ -29,8 +31,7 @@ namespace ModernKeePass.ViewModels public bool SpecialPatternSelected { get; set; } public bool BracketsPatternSelected { get; set; } public string CustomChars { get; set; } = string.Empty; - public PwUuid IdUuid => _pwEntry?.Uuid; - public string Id => _pwEntry?.Uuid.ToHexString(); + public string Id => _entry.Id; public bool IsRecycleOnDelete => _database.RecycleBinEnabled && !ParentGroup.IsSelected; public IEnumerable BreadCrumb => new List(ParentGroup.BreadCrumb) {ParentGroup}; /// @@ -44,80 +45,73 @@ namespace ModernKeePass.ViewModels set { _passwordLength = value; - NotifyPropertyChanged("PasswordLength"); + OnPropertyChanged(); } } - public string Name + public string Title { - get { return GetEntryValue(PwDefs.TitleField); } - set { SetEntryValue(PwDefs.TitleField, new ProtectedString(true, value)); } + get { return _entry.Title; } + set { _mediator.Send(new SetFieldValueCommand { EntryId = Id, FieldName = nameof(Title), FieldValue = value}); } } public string UserName { - get { return GetEntryValue(PwDefs.UserNameField); } - set { SetEntryValue(PwDefs.UserNameField, new ProtectedString(true, value)); } + get { return _entry.Username; } + set { _mediator.Send(new SetFieldValueCommand { EntryId = Id, FieldName = nameof(UserName), FieldValue = value }); } } public string Password { - get { return GetEntryValue(PwDefs.PasswordField); } + get { return _entry.Password; } set { - SetEntryValue(PwDefs.PasswordField, new ProtectedString(true, value)); - NotifyPropertyChanged("Password"); - NotifyPropertyChanged("PasswordComplexityIndicator"); + _mediator.Send(new SetFieldValueCommand { EntryId = Id, FieldName = nameof(Password), FieldValue = value }); + OnPropertyChanged(); + OnPropertyChanged(nameof(PasswordComplexityIndicator)); } } public string Url { - get { return GetEntryValue(PwDefs.UrlField); } - set { SetEntryValue(PwDefs.UrlField, new ProtectedString(true, value)); } + get { return _entry.Url.ToString();} + set { _mediator.Send(new SetFieldValueCommand { EntryId = Id, FieldName = nameof(Url), FieldValue = value }); } } public string Notes { - get { return GetEntryValue(PwDefs.NotesField); } - set { SetEntryValue(PwDefs.NotesField, new ProtectedString(true, value)); } + get { return _entry.Notes; } + set { _mediator.Send(new SetFieldValueCommand { EntryId = Id, FieldName = nameof(Notes), FieldValue = value }); } } - public int IconId + public int Icon { get { - if (HasExpired) return (int) PwIcon.Expired; - if (_pwEntry?.IconId != null) return (int) _pwEntry?.IconId; - return -1; - } - set - { - HandleBackup(); - _pwEntry.IconId = (PwIcon)value; + if (HasExpired) return (int)Domain.Enums.Icon.ReportHacked; + return (int) _entry.Icon; } + set { _mediator.Send(new SetFieldValueCommand { EntryId = Id, FieldName = nameof(Icon), FieldValue = value }); } } public DateTimeOffset ExpiryDate { - get { return new DateTimeOffset(_pwEntry.ExpiryTime.Date); } + get { return _entry.ExpirationDate; } set { if (!HasExpirationDate) return; - HandleBackup(); - _pwEntry.ExpiryTime = value.DateTime; + _mediator.Send(new SetFieldValueCommand { EntryId = Id, FieldName = "ExpirationDate", FieldValue = value.Date }); } } public TimeSpan ExpiryTime { - get { return _pwEntry.ExpiryTime.TimeOfDay; } + get { return _entry.ExpirationDate.TimeOfDay; } set { if (!HasExpirationDate) return; - HandleBackup(); - _pwEntry.ExpiryTime = _pwEntry.ExpiryTime.Date.Add(value); + _mediator.Send(new SetFieldValueCommand { EntryId = Id, FieldName = "ExpirationDate", FieldValue = ExpiryDate.Date.Add(value) }); } } @@ -127,7 +121,7 @@ namespace ModernKeePass.ViewModels set { _isEditMode = value; - NotifyPropertyChanged("IsEditMode"); + OnPropertyChanged(); } } @@ -137,7 +131,7 @@ namespace ModernKeePass.ViewModels set { _isVisible = value; - NotifyPropertyChanged("IsVisible"); + OnPropertyChanged(); } } @@ -147,16 +141,16 @@ namespace ModernKeePass.ViewModels set { _isRevealPassword = value; - NotifyPropertyChanged("IsRevealPassword"); + OnPropertyChanged(); } } public bool HasExpirationDate { - get { return _pwEntry.Expires; } + get { return _entry.HasExpirationDate; } set { - _pwEntry.Expires = value; - NotifyPropertyChanged("HasExpirationDate"); + _mediator.Send(new SetFieldValueCommand {EntryId = Id, FieldName = nameof(HasExpirationDate), FieldValue = value}); + OnPropertyChanged(); } } @@ -165,7 +159,7 @@ namespace ModernKeePass.ViewModels get { var history = new Stack(); - foreach (var historyEntry in _pwEntry.History) + foreach (var historyEntry in _entry.History) { history.Push(new EntryVm(historyEntry, ParentGroup) {IsSelected = false}); } @@ -178,89 +172,68 @@ namespace ModernKeePass.ViewModels public Color? BackgroundColor { - get { return _pwEntry?.BackgroundColor; } + get { return _entry?.BackgroundColor; } set { - if (value != null) _pwEntry.BackgroundColor = (Color) value; + if (value != null) _entry.BackgroundColor = (Color) value; } } public Color? ForegroundColor { - get { return _pwEntry?.ForegroundColor; } + get { return _entry?.ForegroundColor; } set { - if (value != null) _pwEntry.ForegroundColor = (Color)value; + if (value != null) _entry.ForegroundColor = (Color)value; } } public ICommand SaveCommand { get; } public ICommand GeneratePasswordCommand { get; } public ICommand UndoDeleteCommand { get; } - - public event PropertyChangedEventHandler PropertyChanged; - - private readonly PwEntry _pwEntry; - private readonly IDatabaseService _database; - private readonly IResourceService _resource; + + private readonly Application.Entry.Models.EntryVm _entry; + private readonly IMediator _mediator; private bool _isEditMode; - private bool _isDirty = true; private bool _isRevealPassword; private double _passwordLength = 25; private bool _isVisible = true; - - private void NotifyPropertyChanged(string propertyName) - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - } - + public EntryVm() { } - internal EntryVm(PwEntry entry, GroupVm parent) : this(entry, parent, DatabaseService.Instance, new ResourcesService()) { } + internal EntryVm(Application.Entry.Models.EntryVm entry, GroupVm parent) : this(entry, parent, App.Mediator) { } - public EntryVm(PwEntry entry, GroupVm parent, IDatabaseService database, IResourceService resource) + public EntryVm(Application.Entry.Models.EntryVm entry, GroupVm parent, IMediator mediator) { - _database = database; - _resource = resource; - _pwEntry = entry; + _entry = entry; + _mediator = mediator; ParentGroup = parent; - SaveCommand = new RelayCommand(() => _database.Save()); - GeneratePasswordCommand = new RelayCommand(GeneratePassword); + SaveCommand = new RelayCommand(() => _mediator.Send(new SaveDatabaseCommand())); + GeneratePasswordCommand = new RelayCommand(async () => await GeneratePassword()); UndoDeleteCommand = new RelayCommand(() => Move(PreviousGroup), () => PreviousGroup != null); } - public void GeneratePassword() + public async Task GeneratePassword() { - var pwProfile = new PwProfile + Password = await _mediator.Send(new GeneratePasswordCommand { - GeneratorType = PasswordGeneratorType.CharSet, - Length = (uint)PasswordLength, - CharSet = new PwCharSet() - }; - - if (UpperCasePatternSelected) pwProfile.CharSet.Add(PwCharSet.UpperCase); - if (LowerCasePatternSelected) pwProfile.CharSet.Add(PwCharSet.LowerCase); - if (DigitsPatternSelected) pwProfile.CharSet.Add(PwCharSet.Digits); - if (SpecialPatternSelected) pwProfile.CharSet.Add(PwCharSet.Special); - if (MinusPatternSelected) pwProfile.CharSet.Add('-'); - if (UnderscorePatternSelected) pwProfile.CharSet.Add('_'); - if (SpacePatternSelected) pwProfile.CharSet.Add(' '); - if (BracketsPatternSelected) pwProfile.CharSet.Add(PwCharSet.Brackets); - - pwProfile.CharSet.Add(CustomChars); - - ProtectedString password; - PwGenerator.Generate(out password, pwProfile, null, new CustomPwGeneratorPool()); - - SetEntryValue(PwDefs.PasswordField, password); - NotifyPropertyChanged("Password"); - NotifyPropertyChanged("IsRevealPasswordEnabled"); - NotifyPropertyChanged("PasswordComplexityIndicator"); + BracketsPatternSelected = BracketsPatternSelected, + CustomChars = CustomChars, + DigitsPatternSelected = DigitsPatternSelected, + LowerCasePatternSelected = LowerCasePatternSelected, + MinusPatternSelected = MinusPatternSelected, + PasswordLength = (int)PasswordLength, + SpacePatternSelected = SpacePatternSelected, + SpecialPatternSelected = SpecialPatternSelected, + UnderscorePatternSelected = UnderscorePatternSelected, + UpperCasePatternSelected = UpperCasePatternSelected + }); + OnPropertyChanged(nameof(IsRevealPasswordEnabled)); } - public void MarkForDelete(string recycleBinTitle) + public Task MarkForDelete(string recycleBinTitle) { if (_database.RecycleBinEnabled && _database.RecycleBin?.IdUuid == null) _database.CreateRecycleBin(recycleBinTitle); @@ -280,43 +253,21 @@ namespace ModernKeePass.ViewModels ParentGroup.Entries.Add(this); } - public void CommitDelete() + public async Task CommitDelete() { - _pwEntry.ParentGroup.Entries.Remove(_pwEntry); - if (!_database.RecycleBinEnabled || PreviousGroup.IsSelected) _database.AddDeletedItem(IdUuid); + await _mediator.Send(new DeleteEntryCommand {Entry = _entry}); } - public PwEntry GetPwEntry() + public Application.Entry.Models.EntryVm GetEntry() { - return _pwEntry; - } - public void Reset() - { - _isDirty = false; + return _entry; } public override string ToString() { - return IsSelected ? _resource.GetResourceValue("EntryCurrent") : _pwEntry.LastModificationTime.ToString("g"); - } - - private void HandleBackup() - { - if (_isDirty) return; - _pwEntry?.Touch(true); - _pwEntry?.CreateBackup(null); - _isDirty = true; - } - - private string GetEntryValue(string key) - { - return _pwEntry?.Strings.GetSafe(key).ReadString(); - } - - private void SetEntryValue(string key, ProtectedString newValue) - { - HandleBackup(); - _pwEntry?.Strings.Set(key, newValue); + return IsSelected ? + _mediator.Send(new GetResourceQuery{Key = "EntryCurrent"}).GetAwaiter().GetResult() : + _entry.ModificationDate.ToString("g"); } } } diff --git a/ModernKeePass/ViewModels/GroupVm.cs b/ModernKeePass/ViewModels/GroupVm.cs index 264ef85..35bafcb 100644 --- a/ModernKeePass/ViewModels/GroupVm.cs +++ b/ModernKeePass/ViewModels/GroupVm.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Linq; @@ -8,10 +7,17 @@ using System.Windows.Input; using MediatR; using ModernKeePass.Application.Database.Commands.SaveDatabase; using ModernKeePass.Application.Database.Queries.GetDatabase; +using ModernKeePass.Application.Group.Commands.AddEntry; +using ModernKeePass.Application.Group.Commands.CreateEntry; +using ModernKeePass.Application.Group.Commands.CreateGroup; +using ModernKeePass.Application.Group.Commands.DeleteGroup; +using ModernKeePass.Application.Group.Commands.InsertEntry; +using ModernKeePass.Application.Group.Commands.RemoveEntry; +using ModernKeePass.Application.Group.Commands.SortEntries; +using ModernKeePass.Application.Group.Commands.SortGroups; using ModernKeePass.Common; using ModernKeePass.Domain.Enums; using ModernKeePass.Interfaces; -using ModernKeePassLib; namespace ModernKeePass.ViewModels { @@ -63,17 +69,17 @@ namespace ModernKeePass.ViewModels } public IOrderedEnumerable> EntriesZoomedOut => from e in Entries - group e by e.Name.ToUpper().FirstOrDefault() into grp + group e by e.Title.ToUpper().FirstOrDefault() into grp orderby grp.Key select grp; - public string Name + public string Title { get { return _group == null ? string.Empty : _group.Title; } set { _group.Title = value; } } - public int IconId + public int Icon { get { @@ -152,38 +158,44 @@ namespace ModernKeePass.ViewModels Groups = new ObservableCollection(group.SubGroups.Select(g => new GroupVm(g, this, recycleBinId))); } - private void Entries_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + private async void Entries_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { switch (e.Action) { case NotifyCollectionChangedAction.Remove: - var oldIndex = (uint) e.OldStartingIndex; - _reorderedEntry = _group.Entries.GetAt(oldIndex); - _group.Entries.RemoveAt(oldIndex); + var oldIndex = e.OldStartingIndex; + _reorderedEntry = _group.Entries[oldIndex]; + await _mediator.Send(new RemoveEntryCommand {Entry = _reorderedEntry, ParentGroup = _group}); break; case NotifyCollectionChangedAction.Add: - if (_reorderedEntry == null) _group.AddEntry(((EntryVm) e.NewItems[0]).GetPwEntry(), true); - else _group.Entries.Insert((uint)e.NewStartingIndex, _reorderedEntry); + if (_reorderedEntry == null) + { + var entry = ((EntryVm) e.NewItems[0]).GetEntry(); + await _mediator.Send(new AddEntryCommand {Entry = entry, ParentGroup = _group}); + } + else + { + await _mediator.Send(new InsertEntryCommand {Entry = _reorderedEntry, ParentGroup = _group, Index = e.NewStartingIndex}); + } break; } } - public GroupVm AddNewGroup(string name = "") + public async Task AddNewGroup(string name = "") { - var pwGroup = new PwGroup(true, true, name, PwIcon.Folder); - _group.AddGroup(pwGroup, true); - var newGroup = new GroupVm(pwGroup, this) {Name = name, IsEditMode = string.IsNullOrEmpty(name)}; - Groups.Add(newGroup); - return newGroup; + var newGroup = await _mediator.Send(new CreateGroupCommand {Name = name, ParentGroup = _group}); + var newGroupVm = new GroupVm(newGroup, this) {Title = name, IsEditMode = string.IsNullOrEmpty(name)}; + Groups.Add(newGroupVm); + return newGroupVm; } - public EntryVm AddNewEntry() + public async Task AddNewEntry() { - var pwEntry = new PwEntry(true, true); - var newEntry = new EntryVm(pwEntry, this) {IsEditMode = true}; - newEntry.GeneratePassword(); - Entries.Add(newEntry); - return newEntry; + var newEntry = await _mediator.Send(new CreateEntryCommand { ParentGroup = _group }); + var newEntryVm = new EntryVm(newEntry, this) {IsEditMode = true}; + await newEntryVm.GeneratePassword(); + Entries.Add(newEntryVm); + return newEntryVm; } public async Task MarkForDelete(string recycleBinTitle) @@ -217,42 +229,26 @@ namespace ModernKeePass.ViewModels public async Task CommitDelete() { - _group.ParentGroup.Groups.Remove(_group); - if (await IsRecycleBinEnabled() && !PreviousGroup.IsSelected) _database.RecycleBin._group.AddGroup(_group, true); - else _database.AddDeletedItem(IdUuid); + await _mediator.Send(new DeleteGroupCommand { Group = _group }); } public override string ToString() { - return Name; + return Title; } private async Task SortEntriesAsync() { - var comparer = new PwEntryComparer(PwDefs.TitleField, true, false); - try - { - _group.Entries.Sort(comparer); - Entries = new ObservableCollection(Entries.OrderBy(e => e.Name)); - } - catch (Exception e) - { - await MessageDialogHelper.ShowErrorDialog(e); - } + await _mediator.Send(new SortEntriesCommand {Group = _group}); + Entries = new ObservableCollection(Entries.OrderBy(e => e.Title)); } private async Task SortGroupsAsync() { - try - { - _group.SortSubGroups(false); - Groups = new ObservableCollection(Groups.OrderBy(g => g.Name).ThenBy(g => g._group == null)); - OnPropertyChanged("Groups"); - } - catch (Exception e) - { - await MessageDialogHelper.ShowErrorDialog(e); - } + await _mediator.Send(new SortGroupsCommand {Group = _group}); + Groups = new ObservableCollection(Groups.OrderBy(g => g.Title).ThenBy(g => g._group == null)); + // TODO: should not be needed + OnPropertyChanged(nameof(Groups)); } private async Task IsRecycleBinEnabled() diff --git a/ModernKeePass/ViewModels/NewVm.cs b/ModernKeePass/ViewModels/NewVm.cs index 01ff1d5..9d92a4d 100644 --- a/ModernKeePass/ViewModels/NewVm.cs +++ b/ModernKeePass/ViewModels/NewVm.cs @@ -41,23 +41,23 @@ namespace ModernKeePass.ViewModels var converter = new IntToSymbolConverter(); var bankingGroup = group.AddNewGroup("Banking"); - bankingGroup.IconId = (int)converter.ConvertBack(Symbol.Calculator, null, null, string.Empty); + bankingGroup.Icon = (int)converter.ConvertBack(Symbol.Calculator, null, null, string.Empty); var emailGroup = group.AddNewGroup("Email"); - emailGroup.IconId = (int)converter.ConvertBack(Symbol.Mail, null, null, string.Empty); + emailGroup.Icon = (int)converter.ConvertBack(Symbol.Mail, null, null, string.Empty); var internetGroup = group.AddNewGroup("Internet"); - internetGroup.IconId = (int)converter.ConvertBack(Symbol.World, null, null, string.Empty); + internetGroup.Icon = (int)converter.ConvertBack(Symbol.World, null, null, string.Empty); var sample1 = group.AddNewEntry(); - sample1.Name = "Sample Entry"; + sample1.Title = "Sample Entry"; sample1.UserName = "Username"; sample1.Url = PwDefs.HomepageUrl; sample1.Password = "Password"; sample1.Notes = "You may safely delete this sample"; var sample2 = group.AddNewEntry(); - sample2.Name = "Sample Entry #2"; + sample2.Title = "Sample Entry #2"; sample2.UserName = "Michael321"; sample2.Url = PwDefs.HelpUrl + "kb/testform.html"; sample2.Password = "12345"; diff --git a/ModernKeePass/Views/EntryDetailPage.xaml b/ModernKeePass/Views/EntryDetailPage.xaml index 50cfe11..cbbed2a 100644 --- a/ModernKeePass/Views/EntryDetailPage.xaml +++ b/ModernKeePass/Views/EntryDetailPage.xaml @@ -415,7 +415,7 @@ - + @@ -425,7 +425,7 @@ - + @@ -498,13 +498,13 @@ - + - + - + diff --git a/ModernKeePass/Views/EntryDetailPage.xaml.cs b/ModernKeePass/Views/EntryDetailPage.xaml.cs index 65abfff..e3e85c5 100644 --- a/ModernKeePass/Views/EntryDetailPage.xaml.cs +++ b/ModernKeePass/Views/EntryDetailPage.xaml.cs @@ -49,7 +49,6 @@ namespace ModernKeePass.Views protected override void OnNavigatedFrom(NavigationEventArgs e) { NavigationHelper.OnNavigatedFrom(e); - Model.Reset(); } #endregion diff --git a/ModernKeePass/Views/GroupDetailPage.xaml b/ModernKeePass/Views/GroupDetailPage.xaml index 68266bb..ce6f446 100644 --- a/ModernKeePass/Views/GroupDetailPage.xaml +++ b/ModernKeePass/Views/GroupDetailPage.xaml @@ -105,11 +105,11 @@ - + - + @@ -122,7 +122,7 @@ - + @@ -130,7 +130,7 @@ - + @@ -138,7 +138,7 @@ - + @@ -159,7 +159,7 @@ - + @@ -207,14 +207,14 @@ - + - + - + diff --git a/ModernKeePass/Views/GroupDetailPage.xaml.cs b/ModernKeePass/Views/GroupDetailPage.xaml.cs index cd8d3a2..f10e2d3 100644 --- a/ModernKeePass/Views/GroupDetailPage.xaml.cs +++ b/ModernKeePass/Views/GroupDetailPage.xaml.cs @@ -122,10 +122,10 @@ namespace ModernKeePass.Views private void SearchBox_OnSuggestionsRequested(SearchBox sender, SearchBoxSuggestionsRequestedEventArgs args) { var imageUri = RandomAccessStreamReference.CreateFromUri(new Uri("ms-appdata://Assets/ModernKeePass-SmallLogo.scale-80.png")); - var results = Model.SubEntries.Where(e => e.Name.IndexOf(args.QueryText, StringComparison.OrdinalIgnoreCase) >= 0).Take(5); + var results = Model.SubEntries.Where(e => e.Title.IndexOf(args.QueryText, StringComparison.OrdinalIgnoreCase) >= 0).Take(5); foreach (var result in results) { - args.Request.SearchSuggestionCollection.AppendResultSuggestion(result.Name, result.ParentGroup.Name, result.Id, imageUri, string.Empty); + args.Request.SearchSuggestionCollection.AppendResultSuggestion(result.Title, result.ParentGroup.Title, result.Id, imageUri, string.Empty); } } diff --git a/ModernKeePassApp.Test/ViewModelsTests.cs b/ModernKeePassApp.Test/ViewModelsTests.cs index d01d31d..ce5635a 100644 --- a/ModernKeePassApp.Test/ViewModelsTests.cs +++ b/ModernKeePassApp.Test/ViewModelsTests.cs @@ -118,7 +118,7 @@ namespace ModernKeePassApp.Test var database = new DatabaseServiceMock(); var entryVm = new EntryVm(new PwEntry(true, true), new GroupVm(), database, _resource) { - Name = "Test", + Title = "Test", UserName = "login", Password = "password" }; @@ -130,7 +130,7 @@ namespace ModernKeePassApp.Test var database = new DatabaseServiceMock(); var entryVm = new GroupVm(new PwGroup(true, true), new GroupVm(), database) { - Name = "Test" + Title = "Test" }; } }