-
Notifications
You must be signed in to change notification settings - Fork 96
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
customicon support #96
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -39,13 +39,6 @@ Simple Example | |
# save database | ||
>>> kp.save() | ||
|
||
Context Manager Example | ||
-------------- | ||
.. code:: python | ||
>>> with PyKeePass('db.kdbx', password='somePassw0rd') as kp: | ||
>>> entry = kp.find_entries(title='facebook', first=True) | ||
>>> entry.password | ||
's3cure_p455w0rd' | ||
|
||
Finding Entries | ||
---------------------- | ||
|
@@ -173,13 +166,13 @@ For backwards compatibility, the following functions are also available: | |
|
||
Adding Entries | ||
-------------- | ||
**add_entry** (destination_group, title, username, password, url=None, notes=None, tags=None, expiry_time=None, icon=None, force_creation=False) | ||
**add_entry** (destination_group, title, username, password, url=None, notes=None, tags=None, expiry_time=None, icon=None, customicon=None, force_creation=False) | ||
|
||
**delete_entry** (entry) | ||
|
||
**move_entry** (entry, destination_group) | ||
|
||
where ``destination_group`` is a ``Group`` instance. ``entry`` is an ``Entry`` instance. ``title``, ``username``, ``password``, ``url``, ``notes``, ``tags``, ``icon`` are strings. ``expiry_time`` is a ``datetime`` instance. | ||
where ``destination_group`` is a ``Group`` instance. ``entry`` is an ``Entry`` instance. ``title``, ``username``, ``password``, ``url``, ``notes``, ``tags``, ``icon``, ``customicon`` are strings. ``expiry_time`` is a ``datetime`` instance. | ||
|
||
If ``expiry_time`` is a naive datetime object (i.e. ``expiry_time.tzinfo`` is not set), the timezone is retrieved from ``dateutil.tz.gettz()``. | ||
|
||
|
@@ -189,9 +182,9 @@ If ``expiry_time`` is a naive datetime object (i.e. ``expiry_time.tzinfo`` is no | |
>>> kp.add_entry(kp.root_group, 'testing', 'foo_user', 'passw0rd') | ||
Entry: "testing (foo_user)" | ||
|
||
# add a new entry to the social group | ||
# add a new entry to the social group with a custom icon | ||
>>> group = find_groups(name='social', first=True) | ||
>>> entry = kp.add_entry(group, 'testing', 'foo_user', 'passw0rd') | ||
>>> entry = kp.add_entry(group, 'testing', 'foo_user', 'passw0rd', customicon="2") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use single quotes |
||
Entry: "testing (foo_user)" | ||
|
||
# save the database | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,7 +2,7 @@ | |
from __future__ import absolute_import | ||
from pykeepass.baseelement import BaseElement | ||
from copy import deepcopy | ||
from lxml.etree import Element, _Element | ||
from lxml.etree import Element, _Element, iterwalk | ||
from lxml.objectify import ObjectifiedElement | ||
from lxml.builder import E | ||
import logging | ||
|
@@ -17,6 +17,7 @@ | |
'URL', | ||
'Tags', | ||
'IconID', | ||
'CustomIconUUID', | ||
'Times', | ||
'History' | ||
] | ||
|
@@ -26,20 +27,23 @@ class Entry(BaseElement): | |
|
||
def __init__(self, title=None, username=None, password=None, url=None, | ||
notes=None, tags=None, expires=False, expiry_time=None, | ||
icon=None, element=None, version=None): | ||
icon=None, customicon=None, element=None, version=None, meta=None): | ||
|
||
assert type(version) is tuple, 'The provided version is not a tuple, but a {}'.format( | ||
type(version) | ||
) | ||
self._version = version | ||
self._meta = meta | ||
|
||
if element is None: | ||
super(Entry, self).__init__( | ||
element=Element('Entry'), | ||
version=version, | ||
expires=expires, | ||
expiry_time=expiry_time, | ||
icon=icon | ||
icon=icon, | ||
customicon=customicon, | ||
meta=meta | ||
) | ||
self._element.append(E.String(E.Key('Title'), E.Value(title))) | ||
self._element.append(E.String(E.Key('UserName'), E.Value(username))) | ||
|
@@ -125,14 +129,6 @@ def notes(self): | |
def notes(self, value): | ||
return self._set_string_field('Notes', value) | ||
|
||
@property | ||
def icon(self): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why remove the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. because the exact same functions exist in the baseelement; didn't seem necessary |
||
return self._get_subelement_text('IconID') | ||
|
||
@icon.setter | ||
def icon(self, value): | ||
return self._set_subelement_text('IconID', value) | ||
|
||
@property | ||
def tags(self): | ||
val = self._get_subelement_text('Tags') | ||
|
@@ -147,7 +143,7 @@ def tags(self, value): | |
@property | ||
def history(self): | ||
if self._element.find('History') is not None: | ||
return [Entry(element=x, version=self._version) for x in self._element.find('History').findall('Entry')] | ||
return [Entry(element=x, version=self._version, meta=self._meta) for x in self._element.find('History').findall('Entry')] | ||
|
||
@history.setter | ||
def history(self, value): | ||
|
@@ -241,9 +237,9 @@ def __str__(self): | |
def __eq__(self, other): | ||
return ( | ||
(self.title, self.username, self.password, self.url, | ||
self.notes, self.icon, self.tags, self.atime, self.ctime, | ||
self.notes, self.icon, self.customicon, self.tags, self.atime, self.ctime, | ||
self.mtime, self.expires, self.uuid) == | ||
(other.title, other.username, other.password, other.url, | ||
other.notes, other.icon, other.tags, other.atime, other.ctime, | ||
other.notes, other.icon, other.customicon, other.tags, other.atime, other.ctime, | ||
other.mtime, other.expires, other.uuid) | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,22 +9,24 @@ | |
|
||
class Group(BaseElement): | ||
|
||
def __init__(self, name=None, element=None, icon=None, notes=None, | ||
version=None, expires=None, expiry_time=None): | ||
def __init__(self, name=None, element=None, icon=None, customicon=None, notes=None, | ||
version=None, expires=None, expiry_time=None, meta=None): | ||
|
||
assert type(version) is tuple, 'The provided version is not a tuple, but a {}'.format( | ||
type(version) | ||
) | ||
self._version = version | ||
|
||
self._meta = meta | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just curious: what's this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The KDBX XML contains a Meta tag which contains the customicons (the actual image files). Since it doesn't use a generic key index but instead a base64 index to link the image to the entry, I send the meta tag + children to the the entry, so the base64 index can be converted to a numerical one (as the keepass UI does as well) |
||
|
||
if element is None: | ||
super(Group, self).__init__( | ||
element=Element('Group'), | ||
version=version, | ||
expires=expires, | ||
expiry_time=expiry_time, | ||
icon=icon | ||
icon=icon, | ||
customicon=customicon, | ||
meta=meta | ||
) | ||
self._element.append(E.Name(name)) | ||
if notes: | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -314,6 +314,7 @@ def test_set_and_get_fields(self): | |
entry.expires = False | ||
entry.expiry_time = changed_time | ||
entry.icon = icons.GLOBE | ||
entry.customicon = "9" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this even be valid? I was expecting more something like an actual image file EDIT: And There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The way keepass works is that when you set a customicon, it sets the default icon to 0. If you do not want to use the customicon anymore, the CustomIconUUID tag should be removed from the entry. I can only create a separate test for customicons if I modify the test KDBX files; the code will not set customicon to a value which does not actually exist. So setting this to 9 will only result in a customicon with index 9 if at least 10 customicons exist. In all other cases (as it does here) customicon is set to None, which removes the tag from the entry XML. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. to elaborate, customicons are only stored in the database once and are referred to by entries by using a UUID which is base64 encoded. This PR does not add the possibility to add actual image files as custom icons, but merely allows you to set customicons which already exist in the DB by providing their numerical index. |
||
entry.set_custom_property('foo', 'bar') | ||
|
||
self.assertEqual(entry.title, changed_string + 'title') | ||
|
@@ -322,6 +323,7 @@ def test_set_and_get_fields(self): | |
self.assertEqual(entry.url, changed_string + 'url') | ||
self.assertEqual(entry.notes, changed_string + 'notes') | ||
self.assertEqual(entry.icon, icons.GLOBE) | ||
self.assertEqual(entry.customicon, None) | ||
self.assertEqual(entry.get_custom_property('foo'), 'bar') | ||
self.assertIn('foo', entry.custom_properties) | ||
# test time properties | ||
|
@@ -391,12 +393,6 @@ def test_db_info(self): | |
def tearDown(self): | ||
os.remove(os.path.join(base_dir, 'change_creds.kdbx')) | ||
|
||
class CtxManagerTests(unittest.TestCase): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why remove this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think this was intentional, but probably happened because you merged some pull requests after I rebased; I guess it can be ignored. |
||
def test_ctx_manager(self): | ||
with PyKeePass(os.path.join(base_dir, 'test.kdbx'), password='passw0rd', keyfile=base_dir + '/test.key') as kp: | ||
results = kp.find_entries_by_username('foobar_user', first=True) | ||
self.assertEqual('foobar_user', results.username) | ||
|
||
class KDBXTests(unittest.TestCase): | ||
|
||
def test_open_save(self): | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are you deleting this?