Skip to content

Commit

Permalink
Rework support for keepass field references
Browse files Browse the repository at this point in the history
  • Loading branch information
firecat53 committed Mar 8, 2021
1 parent cde95c4 commit d3b901a
Show file tree
Hide file tree
Showing 5 changed files with 27 additions and 56 deletions.
4 changes: 2 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,7 @@ Features
generation in the config file (see config.ini.example for instructions).
Multiple character sets can be selected on the fly when using Rofi.
- Optional Pinentry support for secure passphrase entry.
- Keepass field references are supported on auto-type. Adding and editing
references is not (yet) supported.
- `Keepass field references`_ are supported.

License
-------
Expand Down Expand Up @@ -153,6 +152,7 @@ Tests

.. _Rofi: https://davedavenport.github.io/rofi/
.. _Passhole: https://github.com/purduelug/passhole
.. _Keepass field references: https://keepass.info/help/base/fieldrefs.html
.. _Pykeepass: https://github.com/pschmitt/pykeepass
.. _pynput: https://github.com/moses-palmer/pynput
.. _Archlinux AUR: https://aur.archlinux.org/packages/python-keepmenu-git
Expand Down
48 changes: 17 additions & 31 deletions keepmenu
Original file line number Diff line number Diff line change
Expand Up @@ -502,7 +502,6 @@ def type_entry(entry):
if hasattr(entry, 'autotype_enabled') and entry.autotype_enabled is False:
dmenu_err("Autotype disabled for this entry")
return
entry = resolve_references_to_values(entry)
if hasattr(entry, 'autotype_sequence') and \
entry.autotype_sequence is not None and \
entry.autotype_sequence != 'None':
Expand All @@ -520,26 +519,12 @@ def type_entry(entry):
type_entry_pynput(entry, tokens)


def resolve_references_to_values(entry):
if entry.password is not None and "REF:P" in entry.password:
entry.password = entry.deref("password")
if entry.username is not None and "REF:U" in entry.username:
entry.username = entry.deref("username")
if entry.title is not None and "REF:T" in entry.title:
entry.title = entry.deref("title")
if entry.url is not None and "REF:A" in entry.url:
entry.url = entry.deref("url")
if entry.notes is not None and "REF:N" in entry.notes:
entry.notes = entry.deref("notes")
return entry


PLACEHOLDER_AUTOTYPE_TOKENS = {
"{TITLE}" : lambda e: e.title,
"{USERNAME}": lambda e: e.username,
"{URL}" : lambda e: e.url,
"{PASSWORD}": lambda e: e.password,
"{NOTES}" : lambda e: e.notes,
"{TITLE}" : lambda e: e.deref('title'),
"{USERNAME}": lambda e: e.deref('username'),
"{URL}" : lambda e: e.deref('url'),
"{PASSWORD}": lambda e: e.deref('password'),
"{NOTES}" : lambda e: e.deref('notes'),
}

STRING_AUTOTYPE_TOKENS = {
Expand Down Expand Up @@ -912,10 +897,10 @@ def view_all_entries(options, kp_entries):
kp_entry_pattern = str("{:>{na}} - {} - {} - {}") # Path,username,url
# Have to number each entry to capture duplicates correctly
kp_entries_b = str("\n").join([kp_entry_pattern.format(j,
"/".join(i.path),
i.username,
i.url,
na=num_align)
os.path.join("/".join(i.path[:-1]), i.deref('title') or ""),
i.deref('username'),
i.deref('url'),
na=num_align)
for j, i in enumerate(kp_entries)]).encode(ENC)
if options:
options_b = ("\n".join(options) + "\n").encode(ENC)
Expand Down Expand Up @@ -1096,19 +1081,20 @@ def view_entry(kp_entry):
Returns: dmenu selection
"""
fields = ["/".join(kp_entry.path) or "Title: None",
kp_entry.username or "Username: None",
'**********' if kp_entry.password else "Password: None",
kp_entry.url or "URL: None",
"Notes: <Enter to view>" if kp_entry.notes else "Notes: None"]
fields = [os.path.join("/".join(kp_entry.path[:-1]), kp_entry.deref('title') or "")
or "Title: None",
kp_entry.deref('username') or "Username: None",
'**********' if kp_entry.deref('password') else "Password: None",
kp_entry.deref('url') or "URL: None",
"Notes: <Enter to view>" if kp_entry.deref('notes') else "Notes: None"]
kp_entries_b = "\n".join(fields).encode(ENC)
sel = dmenu_select(len(fields), inp=kp_entries_b)
if sel == "Notes: <Enter to view>":
sel = view_notes(kp_entry.notes)
sel = view_notes(kp_entry.deref('notes'))
elif sel == "Notes: None":
sel = ""
elif sel == '**********':
sel = kp_entry.password
sel = kp_entry.deref('password')
elif sel == fields[3]:
if sel != "URL: None":
webbrowser.open(sel)
Expand Down
3 changes: 1 addition & 2 deletions keepmenu.1
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,7 @@ Rofi.

\fB15.\fR Optional Pinentry support for secure passphrase entry.

\fB16.\fR Keepass field references are supported on auto-type. Adding and
editing references is not (yet) supported.
\fB16.\fR Keepass field references are supported. https://keepass.info/help/base/fieldrefs.html

.SH LICENSE
Copyright © 2021 Scott Hansen <[email protected]>. Keepmenu is released under the terms of the GPLv3 license.
Expand Down
Binary file modified tests/test.kdbx
Binary file not shown.
28 changes: 7 additions & 21 deletions tests/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,27 +200,13 @@ def test_resolve_references(self):
KM.CONF.write(conf_file)
database = KM.get_database()
kpo = KM.get_entries(database)
""" Check that reference entry exists in DB
"""
ref_exists = False
for entry in kpo.entries:
if entry.password is not None and "REF:P" in entry.password:
ref_exists = True
self.assertTrue(ref_exists)
""" Check that refernce entries have been resolved to their values
"""
for entry in kpo.entries:
entry = KM.resolve_references_to_values(entry)
if entry.password is not None:
self.assertNotIn("REF", entry.password)
if entry.username is not None:
self.assertNotIn("REF", entry.username)
if entry.title is not None:
self.assertNotIn("REF", entry.title)
if entry.url is not None:
self.assertNotIn("REF", entry.url)
if entry.notes is not None:
self.assertNotIn("REF", entry.notes)
ref_entry = kpo.find_entries_by_title(title='.*REF.*', regex=True)[0]
base_entry = kpo.find_entries_by_title(title='Test Title 1')[0]
self.assertEqual(ref_entry.deref("title"), "Reference Entry Test - " + base_entry.title)
self.assertEqual(ref_entry.deref("username"), base_entry.username)
self.assertEqual(ref_entry.deref("password"), base_entry.password)
self.assertEqual(ref_entry.deref("url"), base_entry.url)
self.assertEqual(ref_entry.deref("notes"), base_entry.notes)

def test_tokenize_autotype(self):
"""Test tokenizing autotype strings
Expand Down

0 comments on commit d3b901a

Please sign in to comment.