/// <summary> /// Creates a ViewModel wrapping a brand new KdbxGroup as a child of the specified parent group. /// </summary> /// <param name="resourceProvider">IResourceProvider for localizing strings.</param> /// <param name="navigationViewModel">A ViewModel used for tracking navigation history.</param> /// <param name="persistenceService">A service used for persisting the document.</param> /// <param name="clipboardService">A service used for accessing the clipboard.</param> /// <param name="settingsService">A service used for accessing app settings.</param> /// <param name="document">A KdbxDocument representing the database we are working on.</param> /// <param name="parentGroup">The IKeePassGroup to use as a parent for the new group.</param> /// <param name="rng">A random number generator used to protect strings in memory.</param> public EntryDetailsViewModel( IResourceProvider resourceProvider, IDatabaseNavigationViewModel navigationViewModel, IDatabasePersistenceService persistenceService, ISensitiveClipboardService clipboardService, IAppSettingsService settingsService, KdbxDocument document, IKeePassGroup parentGroup, IRandomNumberGenerator rng ) : this( resourceProvider, navigationViewModel, persistenceService, clipboardService, settingsService, document, new KdbxEntry(parentGroup, rng, document.Metadata), true, false, rng ) { if (parentGroup == null) { throw new ArgumentNullException(nameof(parentGroup)); } if (rng == null) { throw new ArgumentNullException(nameof(rng)); } }
/// <summary> /// Handles PropertyChanged events from the persistence service. /// </summary> /// <param name="sender">The persistence service.</param> /// <param name="e">EventArgs for the property change.</param> private void PersistenceServicePropertyChangedHandler(object sender, PropertyChangedEventArgs e) { IDatabasePersistenceService service = sender as IDatabasePersistenceService; DebugHelper.Assert(service != null); if (e.PropertyName == nameof(service.IsSaving)) { MessageBus.Publish(new SavingStateChangeMessage(service.IsSaving)); } }
public async Task Init() { // Get database from test attributes Utils.DatabaseInfo dbInfo = await Utils.GetDatabaseInfoForTest(TestContext); this.dbPassword = dbInfo.Password; this.dbKeyFile = dbInfo.Keyfile; // Assert that databases named *ReadOnly* are actually readonly after a clone if (dbInfo.Database.Name.IndexOf("ReadOnly", StringComparison.OrdinalIgnoreCase) >= 0) { Assert.IsFalse( await dbInfo.Database.CheckWritableAsync(), $"This file is expected to be read-only; please verify this before testing: {dbInfo.Database.Name}" ); } this.saveFile = (await dbInfo.Database.AsIStorageFile.CopyAsync( ApplicationData.Current.TemporaryFolder, $"PersistenceTestDb-{Guid.NewGuid()}.kdbx", NameCollisionOption.ReplaceExisting )).AsWrapper(); // Use a KdbxReader to parse the database and get a corresponding writer KdbxReader reader = new KdbxReader(); using (IRandomAccessStream stream = await this.saveFile.AsIStorageFile.OpenReadAsync()) { await reader.ReadHeaderAsync(stream, CancellationToken.None); KdbxDecryptionResult decryption = await reader.DecryptFileAsync(stream, dbInfo.Password, dbInfo.Keyfile, CancellationToken.None); Assert.AreEqual(KdbxParserCode.Success, decryption.Result.Code); this.document = decryption.GetDocument(); } // Construct services we can use for the test this.writer = reader.GetWriter(); this.persistenceService = new DefaultFilePersistenceService( this.writer, this.writer, new StorageFileDatabaseCandidate(this.saveFile, true), new MockSyncContext(), true ); this.credentialStorage = new MockCredentialProvider(); this.masterKeyVm = new MasterKeyChangeViewModel( this.document, this.saveFile, new DatabaseCredentialProvider(this.persistenceService, this.credentialStorage), new MockFileService() ); this.settingsVm = new DatabaseSettingsViewModel(this.writer); }
/// <summary> /// Creates a ViewModel wrapping a brand new KdbxGroup as a child of the specified parent group. /// </summary> /// <param name="navigationViewModel">A ViewModel used for tracking navigation history.</param> /// <param name="persistenceService">A service used for persisting the document.</param> /// <param name="document">A KdbxDocument representing the database we are working on.</param> /// <param name="parentGroup">The IKeePassGroup to use as a parent for the new group.</param> public GroupDetailsViewModel( IDatabaseNavigationViewModel navigationViewModel, IDatabasePersistenceService persistenceService, KdbxDocument document, IKeePassGroup parentGroup ) : base(navigationViewModel, persistenceService, document, new KdbxGroup(parentGroup), true, false) { if (parentGroup == null) { throw new ArgumentNullException("parentGroup"); } }
/// <summary> /// Creates a ViewModel wrapping an existing KdbxGroup. /// <param name="navigationViewModel">A ViewModel used for tracking navigation history.</param> /// <param name="persistenceService">A service used for persisting the document.</param> /// <param name="document">A KdbxDocument representing the database we are working on.</param> /// <param name="groupToEdit">The group being viewed.</param> /// <param name="isReadOnly">Whether to open the group in read-only mode.</param> public GroupDetailsViewModel( IDatabaseNavigationViewModel navigationViewModel, IDatabasePersistenceService persistenceService, KdbxDocument document, IKeePassGroup groupToEdit, bool isReadOnly ) : base(navigationViewModel, persistenceService, document, groupToEdit, false, isReadOnly) { if (groupToEdit == null) { throw new ArgumentNullException("groupToEdit"); } }
/// <summary> /// Initializes the EventArgs with the provided parameters. /// </summary> /// <param name="document">A model representing the decrypted XML document.</param> /// <param name="candidate">The file corresponding to the opened document.</param> /// <param name="persistenceService">A service that can persist the document.</param> /// <param name="rng">A random number generator that can encrypt protected strings for the document.</param> /// <param name="keyChangeVmFactory">Factory used to generate the value of <see cref="KeyChangeViewModel"/>.</param> public DocumentReadyEventArgs( KdbxDocument document, IDatabaseCandidate candidate, IDatabasePersistenceService persistenceService, IRandomNumberGenerator rng, IMasterKeyChangeViewModelFactory keyChangeVmFactory) { Document = document; Candidate = candidate; PersistenceService = persistenceService; Rng = rng; KeyChangeViewModel = keyChangeVmFactory?.Assemble(document, PersistenceService, candidate.File) ?? throw new ArgumentNullException(nameof(keyChangeVmFactory)); }
/// <summary> /// Creates a ViewModel wrapping an existing KdbxGroup. /// </summary> /// <param name="resourceProvider">IResourceProvider for localizing strings.</param> /// <param name="navigationViewModel">A ViewModel used for tracking navigation history.</param> /// <param name="persistenceService">A service used for persisting the document.</param> /// <param name="clipboardService">A service used for accessing the clipboard.</param> /// <param name="settingsService">A service used to access app settings.</param> /// <param name="document">A KdbxDocument representing the database we are working on.</param> /// <param name="entryToEdit">The entry being viewed.</param> /// <param name="isReadOnly">Whether to open the group in read-only mode.</param> /// <param name="rng">A random number generator used to protect strings in memory.</param> public EntryDetailsViewModel( IResourceProvider resourceProvider, IDatabaseNavigationViewModel navigationViewModel, IDatabasePersistenceService persistenceService, ISensitiveClipboardService clipboardService, IAppSettingsService settingsService, KdbxDocument document, IKeePassEntry entryToEdit, bool isReadOnly, IRandomNumberGenerator rng ) : this(resourceProvider, navigationViewModel, persistenceService, clipboardService, settingsService, document, entryToEdit, false, isReadOnly, rng) { if (entryToEdit == null) { throw new ArgumentNullException(nameof(entryToEdit)); } }
/// <summary> /// Initializes the instance. /// </summary> /// <param name="syncContext">Context to use for marshalling to the UI thread.</param> /// <param name="timerFactory">Used to create a timer.</param> /// <param name="file">The file on disk represented by this database.</param> /// <param name="fileIsSample">Whether this file is a sample file.</param> /// <param name="document">The decrypted database.</param> /// <param name="resourceProvider">A IResourceProvider for the View.</param> /// <param name="rng">A random number generator used to protect strings.</param> /// <param name="navigationViewModel">A ViewModel representing the navigation of the database.</param> /// <param name="masterKeyViewModel">A ViewModel that allows configuring the database's master key.</param> /// <param name="persistenceService">A service used to save the database.</param> /// <param name="identityService">A service used to authenticate the user.</param> /// <param name="credentialStorage">A service used to update saved credentials.</param> /// <param name="settingsService">A service used to access app settings.</param> /// <param name="clipboardService">A service used to access the clipboard for credentials.</param> public DatabaseParentViewModel( ISyncContext syncContext, ITimerFactory timerFactory, ITestableFile file, bool fileIsSample, KdbxDocument document, IResourceProvider resourceProvider, IRandomNumberGenerator rng, IDatabaseNavigationViewModel navigationViewModel, IMasterKeyViewModel masterKeyViewModel, IDatabasePersistenceService persistenceService, IIdentityVerificationService identityService, ICredentialStorageProvider credentialStorage, IAppSettingsService settingsService, ISensitiveClipboardService clipboardService ) : base(document, persistenceService) { if (timerFactory == null) { throw new ArgumentNullException(nameof(timerFactory)); } this.syncContext = syncContext ?? throw new ArgumentNullException(nameof(syncContext)); this.idleTimer = timerFactory.Assemble(TimeSpan.FromSeconds(1)); this.file = file ?? throw new ArgumentNullException(nameof(file)); this.fileIsSample = fileIsSample; this.document = document ?? throw new ArgumentNullException(nameof(document)); this.resourceProvider = resourceProvider ?? throw new ArgumentNullException(nameof(resourceProvider)); this.rng = rng ?? throw new ArgumentNullException(nameof(rng)); this.navigationViewModel = navigationViewModel ?? throw new ArgumentNullException(nameof(navigationViewModel)); this.settingsViewModel = new DatabaseSettingsViewModel(PersistenceService.SettingsProvider); this.masterKeyViewModel = masterKeyViewModel ?? throw new ArgumentNullException(nameof(masterKeyViewModel)); this.identityService = identityService ?? throw new ArgumentNullException(nameof(identityService)); this.credentialProvider = credentialStorage ?? throw new ArgumentNullException(nameof(credentialStorage)); this.settingsService = settingsService ?? throw new ArgumentNullException(nameof(settingsService)); this.clipboardService = clipboardService ?? throw new ArgumentNullException(nameof(clipboardService)); }
/// <summary> /// Initializes the base class from a subclass. /// </summary> /// <param name="navigationViewModel">A ViewModel used for tracking navigation history.</param> /// <param name="persistenceService">A service used for persisting the document.</param> /// <param name="document">A KdbxDocument representing the database we are working on.</param> /// <param name="item">The item this ViewModel wraps.</param> /// <param name="isNew">Whether the child is being created for the first time.</param> /// <param name="isReadOnly">Whether the child is being accessed as read-only.</param> protected NodeDetailsViewModel( IDatabaseNavigationViewModel navigationViewModel, IDatabasePersistenceService persistenceService, KdbxDocument document, T item, bool isNew, bool isReadOnly ) : base(document, persistenceService) { if (isNew && item.Parent == null) { throw new ArgumentException("Cannot create a new node with no parent!"); } if (navigationViewModel.ActiveGroup != item.Parent) { throw new ArgumentException("The database's active group must be the node's parent!"); } NavigationViewModel = navigationViewModel ?? throw new ArgumentNullException(nameof(navigationViewModel)); Document = document ?? throw new ArgumentNullException(nameof(document)); IsNew = isNew; if (!isNew) { this.masterCopy = GetClone(item); WorkingCopy = GetClone(this.masterCopy); } else { this.masterCopy = null; WorkingCopy = GetClone(item); } IsReadOnly = isReadOnly; }
/// <summary> /// Passes provided parameters to the base constructor and initializes commands. /// </summary> /// <param name="resourceProvider">IResourceProvider for localizing strings.</param> /// <param name="navigationViewModel"></param> /// <param name="persistenceService"></param> /// <param name="clipboardService"></param> /// <param name="settingsService"></param> /// <param name="document"></param> /// <param name="entry"></param> /// <param name="isNew"></param> /// <param name="isReadOnly"></param> /// <param name="rng"></param> private EntryDetailsViewModel( IResourceProvider resourceProvider, IDatabaseNavigationViewModel navigationViewModel, IDatabasePersistenceService persistenceService, ISensitiveClipboardService clipboardService, IAppSettingsService settingsService, KdbxDocument document, IKeePassEntry entry, bool isNew, bool isReadOnly, IRandomNumberGenerator rng ) : base(navigationViewModel, persistenceService, document, entry, isNew, isReadOnly) { this.resourceProvider = resourceProvider; this.clipboardService = clipboardService; this.settingsService = settingsService; this.rng = rng; this.copyFieldValueCommand = new TypedCommand <IProtectedString>( str => { clipboardService.CopyCredential(str.ClearValue, ClipboardOperationType.Other); } ); this.deleteFieldCommand = new TypedCommand <IProtectedString>( str => !IsReadOnly && PersistenceService.CanSave, str => { DebugHelper.Assert(!IsReadOnly); WorkingCopy.Fields.Remove(str); } ); this.editFieldCommand = new AsyncTypedCommand <IProtectedString>( str => PersistenceService.CanSave, async str => { IsReadOnly = false; await UpdateFieldEditorViewModel(new FieldEditorViewModel(str, this.resourceProvider)); } ); this.newFieldCommand = new AsyncActionCommand( () => PersistenceService.CanSave, async() => { IsReadOnly = false; await UpdateFieldEditorViewModel(new FieldEditorViewModel(this.rng, this.resourceProvider)); } ); this.commitFieldCommand = new AsyncActionCommand( () => FieldEditorViewModel?.CommitCommand.CanExecute(WorkingCopy) ?? false, async() => { FieldEditorViewModel.CommitCommand.Execute(WorkingCopy); await UpdateFieldEditorViewModel(null); } ); PropertyChanged += (s, e) => { if (e.PropertyName == nameof(IsReadOnly)) { ((TypedCommand <IProtectedString>)DeleteFieldCommand).RaiseCanExecuteChanged(); } else if (e.PropertyName == nameof(WorkingCopy)) { OnPropertyChanged(nameof(WorkingCopyViewModel)); } }; }
/// <summary> /// Initializes the ViewModel base. /// </summary> /// <param name="document">The document that will be saved.</param> /// <param name="persistenceService">The service to use for document writing.</param> protected DatabasePersistenceViewModel(KdbxDocument document, IDatabasePersistenceService persistenceService) { this.document = document ?? throw new ArgumentNullException(nameof(document)); PersistenceService = persistenceService ?? throw new ArgumentNullException(nameof(persistenceService)); }
public IDatabaseCredentialProvider Assemble(IDatabasePersistenceService persistenceService) { return(new DatabaseCredentialProvider(persistenceService, this.credentialStorage)); }
/// <summary> /// Generats a ViewModel representing an existing child in the DOM. /// </summary> /// <param name="navigationViewModel"></param> /// <param name="persistenceService"></param> /// <param name="document"></param> /// <param name="openForReadOnly"></param> /// <returns>A ViewModel representing an existing child in the DOM.</returns> protected abstract TViewModel GetExistingViewModel( IDatabaseNavigationViewModel navigationViewModel, IDatabasePersistenceService persistenceService, KdbxDocument document, bool openForReadOnly );
/// <summary> /// Generates a ViewModel representing a new child. /// </summary> /// <param name="navigationViewModel"></param> /// <param name="persistenceService"></param> /// <param name="document"></param> /// <param name="parent"></param> /// <returns>A ViewModel representing a new child not already in the tree.</returns> protected abstract TViewModel GetNewViewModel( IDatabaseNavigationViewModel navigationViewModel, IDatabasePersistenceService persistenceService, KdbxDocument document, IKeePassGroup parent );
/// <summary> /// Initializes the provider with the provided dependencies. /// </summary> /// <param name="persistenceService">Used to save the database after updating credentials.</param> /// <param name="credentialStorage">Used to manage previously saved credentials.</param> public DatabaseCredentialProvider(IDatabasePersistenceService persistenceService, ICredentialStorageProvider credentialStorage) { this.persistenceService = persistenceService ?? throw new ArgumentNullException(nameof(persistenceService)); this.credentialStorage = credentialStorage ?? throw new ArgumentNullException(nameof(credentialStorage)); }