/// <summary> /// Determines whether this is in a currently valid save-able state. /// </summary> /// <param name="baseEntry">The entry we would like to save to.</param> /// <returns>Whether a save is possible.</returns> private bool CanSave(IKeePassEntry baseEntry) { ValidationError = String.Empty; if (String.IsNullOrEmpty(WorkingCopy.Key)) { ValidationError = this.LocalizedMissingKey; } if (FieldEditorViewModel.InvalidNames.Contains(WorkingCopy.Key)) { ValidationError = this.LocalizedReservedKey; } if (baseEntry != null) { // If this is a new string, or if we've changed the key from the original, // validate that it doesn't clash with other strings in the entry's set. if ((Original == null || Original.Key != WorkingCopy.Key) && baseEntry.Fields.Select(field => field.Key).Contains(WorkingCopy.Key)) { ValidationError = this.LocalizedDuplicateKey; } } return(String.IsNullOrEmpty(ValidationError)); }
public void HistoryStartsEmpty() { IKeePassEntry newEntry = GetNewEntry(); Assert.IsNotNull(newEntry.History, "New history should not be null"); Assert.AreEqual(0, newEntry.History.Entries.Count, "New history should be empty"); }
/// <summary> /// Handles clicks on the Child GridView. /// </summary> /// <param name="sender">The Child GridView.</param> /// <param name="e">ClickEventArgs for the action.</param> private void ChildGridView_ItemClick(object sender, ItemClickEventArgs e) { bool wasFiltered = !String.IsNullOrEmpty(this.searchBox.Text); if (wasFiltered) { this.searchBox.Text = String.Empty; } // First check to see if it's an entry if (e.ClickedItem is IDatabaseEntryViewModel clickedEntry) { IKeePassEntry entry = clickedEntry.Node as IKeePassEntry; DebugHelper.Assert(entry != null); if (wasFiltered) { ViewModel.NavigationViewModel.SetGroup(entry.Parent); } // For now, on item click, navigate to the EntryDetailsView. Frame.Navigate( typeof(EntryDetailsView), ViewModel.GetEntryDetailsViewModel(entry, /* editing */ false) ); } else { // We clicked a group, so drill into it... IDatabaseGroupViewModel clickedGroup = e.ClickedItem as IDatabaseGroupViewModel; DebugHelper.Assert(clickedGroup != null); clickedGroup.RequestOpenCommand.Execute(null); } }
public void PlaceholderTest() { IKeePassEntry entry = GetTestEntry(); Assert.AreEqual( $"this is a test string with scheme http and {entry.Title.ClearValue}", PlaceholderResolver.Resolve("this is a test string {C:comment here}with scheme {URL:SCM} and {TITLE}", entry) ); }
public void PlaceholderE2EUrlTest() { IKeePassEntry entry = GetTestEntry(); entry.OverrideUrl = "http://{C:test}example.com/{URL:HOST}"; Assert.AreEqual( "http://example.com/www.example.com", entry.ConstructUriString() ); }
/// <summary> /// Attempts to construct a URI string from either <paramref name="entry"/>'s URL or override URL. /// </summary> /// <param name="entry">The entry to construct a URI for.</param> /// <returns>The constructed URI string.</returns> public static string ConstructUriString(this IKeePassEntry entry) { string uriCandidate = entry.OverrideUrl; if (String.IsNullOrEmpty(uriCandidate)) { uriCandidate = entry.Url?.ClearValue ?? string.Empty; } return(PlaceholderResolver.Resolve(uriCandidate, entry)); }
public void SyncTo(IKeePassEntry newEntry, bool isUpdate = true) { DebugHelper.Assert(newEntry != null); if (newEntry == null) { throw new ArgumentNullException(nameof(newEntry)); } if (isUpdate) { DebugHelper.Assert(!this.isHistoryEntry); if (!this.isHistoryEntry) { if (History == null) { History = new KdbxHistory(this._metadata); } History.Add(this); } } IconID = newEntry.IconID; CustomIconUuid = newEntry.CustomIconUuid; ForegroundColor = newEntry.ForegroundColor; BackgroundColor = newEntry.BackgroundColor; OverrideUrl = newEntry.OverrideUrl; Tags = newEntry.Tags; Title = newEntry.Title?.Clone(); UserName = newEntry.UserName?.Clone(); Password = newEntry.Password?.Clone(); Url = newEntry.Url?.Clone(); Notes = newEntry.Notes?.Clone(); /*Fields.Clear(); * foreach(IProtectedString str in newEntry.Fields.Select(f => f.Clone())) * { * Fields.Add(str); * }*/ Fields = new ObservableCollection <IProtectedString>(newEntry.Fields.Select(f => f.Clone())); Binaries = newEntry.Binaries; AutoType = newEntry.AutoType; Times.SyncTo(newEntry.Times); if (isUpdate) { Times.LastModificationTime = DateTime.Now; } }
public void DatabaseNavigationViewModel_SetGroupPrunes() { IKeePassEntry activeEntry = this.document.Root.DatabaseGroup.GetChildEntry(0, 0, 0); this.viewModel.SetEntry(activeEntry); Assert.AreEqual(activeEntry, this.viewModel.ActiveLeaf, "ActiveLeaf should be the expected Entry after setting"); this.viewModel.SetGroup(activeEntry.Parent); Assert.AreEqual(activeEntry, this.viewModel.ActiveLeaf, "ActiveLeaf should not change when SetGroup is a no-op"); this.viewModel.SetGroup(this.document.Root.DatabaseGroup); Assert.IsNull(this.viewModel.ActiveLeaf, "ActiveLeaf should be null after setting a different active group"); }
/// <summary> /// Sets the last Breadcrumb to an Entry's parent, resets the leaves, and flags the Entry as active. /// </summary> /// <param name="entry">The entry to activate.</param> public void SetEntry(IKeePassEntry entry) { if (entry == null) { SetGroup(null); } else { SetGroup(entry.Parent); } ActiveLeaf = entry; }
/// <summary> /// Initializes the ViewModel. /// </summary> /// <param name="entry">The database entry to proxy.</param> /// <param name="isReadOnly">Whether the database can currently be edited.</param> /// <param name="clipboardService">Clipboard service used for requesting credential copies.</param> /// <param name="settingsService">Service used to check settings for launching URLs.</param> public DatabaseEntryViewModel( IKeePassEntry entry, bool isReadOnly, ISensitiveClipboardService clipboardService, IAppSettingsService settingsService ) : base(entry, isReadOnly) { if (entry == null) { throw new ArgumentNullException(nameof(entry)); } this.clipboardService = clipboardService ?? throw new ArgumentNullException(nameof(clipboardService)); this.settingsService = settingsService ?? throw new ArgumentNullException(nameof(settingsService)); this.entryUri = entry.GetLaunchableUri(); RequestCopyUsernameCommand = new ActionCommand( () => { this.clipboardService.CopyCredential(((IKeePassEntry)Node).UserName.ClearValue, ClipboardOperationType.UserName); } ); RequestCopyPasswordCommand = new ActionCommand( () => { this.clipboardService.CopyCredential(((IKeePassEntry)Node).Password.ClearValue, ClipboardOperationType.UserName); } ); RequestCopyUrlCommand = new ActionCommand( () => { this.clipboardService.CopyCredential(((IKeePassEntry)Node).ConstructUriString(), ClipboardOperationType.Other); } ); RequestLaunchUrlCommand = new ActionCommand( () => this.entryUri != null, async() => { if (this.settingsService.CopyPasswordOnUrlOpen) { RequestCopyPasswordCommand.Execute(null); } await Launcher.LaunchUriAsync(this.entryUri); } ); }
public DesignDatabaseViewModel() { NavigationViewModel = new DatabaseNavigationViewModel(); SortMode = new DatabaseSortMode(DatabaseSortMode.Mode.DatabaseOrder, "Database order"); AvailableSortModes = new List <DatabaseSortMode> { SortMode }; IKeePassGroup dbGroup = GetGroup("Database"); IKeePassGroup subGroup = GetGroup("Subdirectory", dbGroup); dbGroup.Children.Add(subGroup); IKeePassGroup rootGroup = GetGroup("Current Root", subGroup); subGroup.Children.Add(rootGroup); rootGroup.Children.Add(GetGroup("Foo Directory", rootGroup)); rootGroup.Children.Add(GetGroup("Bar Directory", rootGroup)); rootGroup.Children.Add(GetGroup("Baz Directory", rootGroup)); rootGroup.Children.Add(GetGroup("Some Directory", rootGroup)); rootGroup.Children.Add(GetGroup("Some other node", rootGroup)); rootGroup.Children.Add(GetGroup("Foo Directory", rootGroup)); rootGroup.Children.Add(GetGroup("Bar Directory", rootGroup)); rootGroup.Children.Add(GetGroup("Baz Directory", rootGroup)); rootGroup.Children.Add(GetGroup("Some Directory", rootGroup)); rootGroup.Children.Add(GetGroup("Some other node", rootGroup)); rootGroup.Children.Add(GetGroup("Foo Directory", rootGroup)); rootGroup.Children.Add(GetGroup("Bar Directory", rootGroup)); rootGroup.Children.Add(GetGroup("Baz Directory", rootGroup)); rootGroup.Children.Add(GetGroup("Some Directory", rootGroup)); rootGroup.Children.Add(GetGroup("Some other node", rootGroup)); rootGroup.Children.Add(GetEntry("Bank", "welcome", parent: rootGroup)); rootGroup.Children.Add(GetEntry("Airline", "flymeout", "123456", "myairline.org", parent: rootGroup)); rootGroup.Children.Add(GetEntry("Facebook", "aloha", parent: rootGroup)); IKeePassEntry active = GetEntry("FooHub", "secure89", "Jimbo", "http://test.com/", parent: rootGroup); rootGroup.Children.Add(active); NavigationViewModel.SetEntry(active); SortedChildren = new ReadOnlyObservableCollection <IDatabaseNodeViewModel>( new ObservableCollection <IDatabaseNodeViewModel>( NavigationViewModel.ActiveGroup.Children .Select(node => new DatabaseNodeViewModel(node, false))) ); }
public void HistoryUpdatesOnEntryUpdate() { IKeePassEntry newEntry = GetNewEntry(); newEntry.Title.ClearValue = "Foo"; IKeePassEntry editedEntry = newEntry.Clone(); editedEntry.Title.ClearValue = "Bar"; newEntry.SyncTo(editedEntry); Assert.AreEqual(1, newEntry.History.Entries.Count); Assert.AreEqual(0, editedEntry.History.Entries.Count); Assert.AreEqual("Bar", newEntry.Title.ClearValue); Assert.AreEqual("Foo", newEntry.History.Entries[0].Title.ClearValue); }
/// <summary> /// Attempts to construct a URI from either <paramref name="entry"/>'s URL or override URL. /// </summary> /// <param name="entry">The entry to construct a URI for.</param> /// <returns>A launchable URI, or null.</returns> public static Uri GetLaunchableUri(this IKeePassEntry entry) { string uriCandidate = entry.ConstructUriString(); if (uriCandidate == null) { return(null); } try { Uri uri = new Uri(uriCandidate); return(uri); } catch (FormatException) { return(null); } }
public async Task DatabaseViewModel_CopyPassword() { IKeePassEntry entry = this.viewModel.Document.Root.DatabaseGroup.GetChildEntry(1, 1, 1); TaskCompletionSource <object> tcs = new TaskCompletionSource <object>(); this.clipboardService.CredentialCopied += async(cs, typ) => { Assert.AreEqual(ClipboardOperationType.Password, typ, "CopyType should have been Password"); string clipboardContent = await this.clipboard.GetContentAsText(); Assert.AreEqual(entry.Password.ClearValue, clipboardContent, "Clipboard content should be the entry's password"); tcs.SetResult(null); }; this.viewModel.RequestCopyPasswordCommand.Execute(entry); await tcs.Task; }
public void BadPlaceholders() { IKeePassEntry entry = GetTestEntry(); Assert.AreEqual( "foo{username", PlaceholderResolver.Resolve("foo{username", entry) ); Assert.AreEqual( "foo{username{", PlaceholderResolver.Resolve("foo{username{", entry) ); Assert.AreEqual( "foo{BAR}", PlaceholderResolver.Resolve("foo{BAR}", entry) ); Assert.AreEqual( $"{{USERNAME{entry.UserName.ClearValue}}}", PlaceholderResolver.Resolve("{USERNAME{UsErnAME}}", entry) ); Assert.AreEqual( $"{entry.Password.ClearValue}}}{{{entry.Title.ClearValue}", PlaceholderResolver.Resolve("{pASsword}}{{TITLE}", entry) ); Assert.AreEqual( $"{{{{{{{{{entry.Password.ClearValue}{{}}{{}}}}}}}}}}}}}}}}{{{{}}{{", PlaceholderResolver.Resolve("{{{{{PASSWORD}{}{}}}}}}}}{{}{", entry) ); Assert.AreEqual( "{C}", PlaceholderResolver.Resolve("{C}", entry) ); Assert.AreEqual( string.Empty, PlaceholderResolver.Resolve("{C:}", entry) ); Assert.AreEqual( string.Empty, PlaceholderResolver.Resolve("{C::asdf:::}", entry) ); }
/// <summary> /// Attempts to save this field to the specified entry. /// </summary> /// <param name="baseEntry">The entry to save to.</param> private void DoSave(IKeePassEntry baseEntry) { if (baseEntry == null) { throw new ArgumentNullException(nameof(baseEntry)); } if (!CanSave(baseEntry)) { throw new InvalidOperationException("Cannot save in the current state."); } if (Original == null) { // New string... baseEntry.Fields.Add(WorkingCopy); Original = WorkingCopy; WorkingCopy = Original.Clone(); } else { // Existing string... if (Original.Key != WorkingCopy.Key) { Original.Key = WorkingCopy.Key; } if (Original.Protected != WorkingCopy.Protected) { Original.Protected = WorkingCopy.Protected; } if (Original.ClearValue != WorkingCopy.ClearValue) { Original.ClearValue = WorkingCopy.ClearValue; } } }
public IKeePassEntry AddEntry(IKeePassEntry entry) { var pwEntry = new PwEntry(true, true); if (!string.IsNullOrEmpty(entry.Title)) { pwEntry.Strings.Set(PwDefs.TitleField, new ProtectedString(true, entry.Title)); } if (!string.IsNullOrEmpty(entry.UserName)) { pwEntry.Strings.Set(PwDefs.UserNameField, new ProtectedString(true, entry.UserName)); } if (!string.IsNullOrEmpty(entry.Password)) { pwEntry.Strings.Set(PwDefs.PasswordField, new ProtectedString(true, entry.Password)); } if (!string.IsNullOrEmpty(entry.Notes)) { pwEntry.Strings.Set(PwDefs.NotesField, new ProtectedString(true, entry.Notes)); } if (!string.IsNullOrEmpty(entry.Url)) { pwEntry.Strings.Set(PwDefs.UrlField, new ProtectedString(true, entry.Url)); } _group.AddEntry(pwEntry, true); Database.Modified = true; var wrapped = new KbdxEntry(pwEntry, Database, this); Entries.Add(wrapped); return(wrapped); }
public void Initialize() { IRandomNumberGenerator rng = new Salsa20(new byte[32]); IResourceProvider resourceProvider = new MockResourceProvider(); this.parentEntry = new MockEntry(); this.parentEntry.Fields.Add( new KdbxString(ExistingFieldKey, ExistingFieldValue, rng, ExistingFieldProtected) ); MethodInfo testMethod = GetType().GetRuntimeMethod( TestContext.TestName, new Type[0] ); DetailsForAttribute specAttr = testMethod.GetCustomAttribute <DetailsForAttribute>(); if (specAttr == null) { return; } else { if (specAttr.IsNew) { this.viewModel = new FieldEditorViewModel(rng, resourceProvider); } else { this.originalString = new KdbxString(EditedFieldKey, EditedFieldValue, rng, EditedFieldProtected); this.parentEntry.Fields.Add(this.originalString); this.viewModel = new FieldEditorViewModel(this.originalString, resourceProvider); } } }
/// <summary> /// Creates an instance with the specified type of copy operation. /// </summary> /// <param name="type">The type of copy being performed (name vs password).</param> public CopyRequestedEventArgs(IKeePassEntry entry, ClipboardOperationType type) { Entry = entry; CopyType = type; }
private static string ResolvePlaceholder(string token, IKeePassEntry entry) { if (token == null) { throw new ArgumentNullException(nameof(token)); } string placeholder = token; string specifier = null; int firstColon = token.IndexOf(':'); if (firstColon >= 0) { placeholder = token.Substring(0, firstColon); specifier = token.Substring(firstColon + 1); } if (specifier == null) { if (placeholder.Equals("TITLE", StringComparison.OrdinalIgnoreCase)) { return(entry.Title.ClearValue); } else if (placeholder.Equals("USERNAME", StringComparison.OrdinalIgnoreCase)) { return(entry.UserName.ClearValue); } else if (placeholder.Equals("URL", StringComparison.OrdinalIgnoreCase)) { return(entry.Url.ClearValue); } else if (placeholder.Equals("PASSWORD", StringComparison.OrdinalIgnoreCase)) { return(entry.Password.ClearValue); } else if (placeholder.Equals("NOTES", StringComparison.OrdinalIgnoreCase)) { return(entry.Notes.ClearValue); } else if (placeholder.Equals("GROUP", StringComparison.OrdinalIgnoreCase)) { return(entry.Parent?.Title.ClearValue); } else if (placeholder.Equals("GROUP_NOTES", StringComparison.OrdinalIgnoreCase)) { return(entry.Parent?.Notes.ClearValue); } else if (placeholder.Equals("ENV_DIRSEP", StringComparison.OrdinalIgnoreCase)) { return(Path.DirectorySeparatorChar.ToString()); } else if (placeholder.Equals("DT_SIMPLE", StringComparison.OrdinalIgnoreCase)) { return(DateTime.Now.ToString("yyyyMMddHHmmss")); } else if (placeholder.Equals("DT_YEAR", StringComparison.OrdinalIgnoreCase)) { return(DateTime.Now.ToString("yyyy")); } else if (placeholder.Equals("DT_MONTH", StringComparison.OrdinalIgnoreCase)) { return(DateTime.Now.ToString("MM")); } else if (placeholder.Equals("DT_DAY", StringComparison.OrdinalIgnoreCase)) { return(DateTime.Now.ToString("dd")); } else if (placeholder.Equals("DT_HOUR", StringComparison.OrdinalIgnoreCase)) { return(DateTime.Now.ToString("HH")); } else if (placeholder.Equals("DT_MINUTE", StringComparison.OrdinalIgnoreCase)) { return(DateTime.Now.ToString("mm")); } else if (placeholder.Equals("DT_SECOND", StringComparison.OrdinalIgnoreCase)) { return(DateTime.Now.ToString("ss")); } else if (placeholder.Equals("DT_UTC_SIMPLE", StringComparison.OrdinalIgnoreCase)) { return(DateTime.UtcNow.ToString("yyyyMMddHHmmss")); } else if (placeholder.Equals("DT_UTC_YEAR", StringComparison.OrdinalIgnoreCase)) { return(DateTime.UtcNow.ToString("yyyy")); } else if (placeholder.Equals("DT_UTC_MONTH", StringComparison.OrdinalIgnoreCase)) { return(DateTime.UtcNow.ToString("MM")); } else if (placeholder.Equals("DT_UTC_DAY", StringComparison.OrdinalIgnoreCase)) { return(DateTime.UtcNow.ToString("dd")); } else if (placeholder.Equals("DT_UTC_HOUR", StringComparison.OrdinalIgnoreCase)) { return(DateTime.UtcNow.ToString("HH")); } else if (placeholder.Equals("DT_UTC_MINUTE", StringComparison.OrdinalIgnoreCase)) { return(DateTime.UtcNow.ToString("mm")); } else if (placeholder.Equals("DT_UTC_SECOND", StringComparison.OrdinalIgnoreCase)) { return(DateTime.UtcNow.ToString("ss")); } } else { if (placeholder.Equals("URL", StringComparison.OrdinalIgnoreCase)) { return(GetUrlComponent(entry.Url.ClearValue, specifier)); } else if (placeholder.Equals("C", StringComparison.OrdinalIgnoreCase)) { // C:foo is a comment return(string.Empty); } } // Not a valid placeholder return(null); }
public void SyncTo(IKeePassEntry template, bool updateModificationTime = true) { throw new NotImplementedException(); }
/// <summary> /// Given a string, potentially with KeePass placeholders, and the entry the string /// is associated with, resolves all placeholders and returns a new string. /// </summary> /// <param name="input">The string to resolve.</param> /// <param name="entry">The entry associated with <paramref name="input"/>.</param> /// <returns>A new string with relevant placeholders replaced.</returns> public static string Resolve(string input, IKeePassEntry entry) { if (entry == null) { throw new ArgumentNullException(nameof(entry)); } if (string.IsNullOrEmpty(input)) { return(input); } StringBuilder builder = new StringBuilder(input.Length); int len = input.Length; for (int i = 0; i < len; i++) { // If we are starting a new placeholder, look ahead in the string for // the end of it. // If we find it: // * Append the placeholder's value to 'builder' // * Move 'i' to the end of the token // If we don't: // * Append the "placeholder" token to 'builder' // * Move 'i' to the end of the string, we're done if (input[i] == PlaceholderOpen) { if (i == len - 1) { builder.Append(PlaceholderOpen); } else { StringBuilder tokenBuilder = new StringBuilder(); for (int j = i + 1; j < len; j++) { if (input[j] == PlaceholderOpen) { // We hit a "nested" token - we discard the work so far and start over. builder.Append(PlaceholderOpen); builder.Append(tokenBuilder.ToString()); tokenBuilder.Clear(); // If this is the last character we'll never finish a token, so go ahead // and get this over with because we're about to exit the loop. if (j == len - 1) { builder.Append(PlaceholderOpen); i = j; } } else if (input[j] == PlaceholderClose) { string token = tokenBuilder.ToString(); string replaced = ResolvePlaceholder(token, entry); if (replaced == null) { // If the token is not valid, we don't replace it. replaced = $"{PlaceholderOpen}{token}{PlaceholderClose}"; } builder.Append(replaced); // Move i forward now that we've evaluated it i = j; break; } else { tokenBuilder.Append(input[j]); if (j == len - 1) { // If we reached the end of the string, we don't have a // complete placeholder. Just append what we have and bail. builder.Append(PlaceholderOpen); builder.Append(tokenBuilder.ToString()); i = j; break; } } } } } else { // Otherwise we're not in a placeholder/token, so keep on trucking. builder.Append(input[i]); } } return(builder.ToString()); }
public IEntryDetailsViewModel GetEntryDetailsViewModel(IKeePassEntry entry, bool editing) { throw new NotImplementedException(); }