Skip to content

Commit

Permalink
Merge branch 'master' into a6seedregen
Browse files Browse the repository at this point in the history
  • Loading branch information
Evidlo committed Oct 6, 2024
2 parents 43b1f91 + 943d0fd commit 5105742
Show file tree
Hide file tree
Showing 30 changed files with 990 additions and 349 deletions.
27 changes: 21 additions & 6 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,40 @@ on: [push, pull_request, workflow_dispatch]

jobs:
tests:
runs-on: ubuntu-20.04
name: ${{ matrix.os }}-latest / ${{ matrix.python-version }}
runs-on: ${{ matrix.os }}-latest
strategy:
matrix:
python-version: [3.6, 3.7, 3.8, 3.9, "3.10", "3.11"]
fail-fast: false
matrix:
os:
- ubuntu
# - windows
# - macos
python-version:
# - "3.6" # not supported in ubuntu-latest (22.04)
- "3.7"
- "3.8"
- "3.9"
- "3.10"
- "3.11"
- "3.12"

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: pip
allow-prereleases: true

- name: Install dependencies
run: |
sudo apt update
sudo apt-get install -y libxml2-dev libxmlsec1-dev
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install .[test]
- name: Run tests
run: |
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ dist/
*.egg-info/
build/
*.xml
Pipfile
Pipfile.lock
*.kdbx
*.kdbx.out
.idea
.venv*
TMPNOTES
17 changes: 17 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
4.1.0 - 2024-06-26
------------------
- merged #389 - add PyKeePass.database_name and database_description
- merged #392, fixed #390 - fix pkg_resources dependency issue
- fixed #391 - Entry.tags returns empty list instead of None
- fixed #395 - set 'encoding' attribute when exporting as XML
- fixed #383 - parse datetimes using isoformat instead of strptime

4.0.7 - 2024-02-29
------------------
- fixed #359 - PyKeePass has `decrypt` kwarg for accessing header info
- merged #347 - added Entry.index and Entry.move for moving entries
- merged #367 - added Entry.autotype_window setter
- merged #364 - allow filename/keyfile to be file-like objects
- merged #371 - drop dateutil dependency
- merged #348 - switch to pyproject.toml

4.0.6 - 2023-08-22
------------------
- fixed #350 - fixed all Python 2 deprecation FIXMEs (e.g. future, )
Expand Down
46 changes: 41 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,12 +1,48 @@
version := $(shell python -c "exec(open('pykeepass/version.py').read());print(__version__)")
.ONESHELL:
.SHELLFLAGS = -ec
.SILENT:
version := $(shell python -c "import tomllib;print(tomllib.load(open('pyproject.toml', 'rb'))['project']['version'])")

.PHONY: dist
dist:
python setup.py sdist bdist_wheel
python -m build

.PHONY: pypi
pypi: dist
twine upload dist/pykeepass-$(version).tar.gz
.PHONY: release
release: lock dist
# check that changelog is updated. only look at first 3 parts of semver
version=$(version)
stripped=$$(echo $${version} | cut -d . -f -3 | cut -d '-' -f 1)
if ! grep $${stripped} CHANGELOG.rst
then
echo "Changelog doesn't seem to be updated! Quitting..."
exit 1
fi
# generate release notes from changelog
awk "BEGIN{p=0}; /^$${stripped}/{next}; /---/{p=1;next}; /^$$/{exit}; p {print}" CHANGELOG.rst > TMPNOTES
# make github and pypi release
gh release create --latest --verify-tag v$(version) dist/pykeepass-$(version)* -F TMPNOTES
twine upload -u __token__ dist/pykeepass-$(version)*

.PHONY: lock
lock:
# run tests then make a requirements.txt lockfile
rm -rf .venv_lock
virtualenv .venv_lock
. .venv_lock/bin/activate
pip install .[test]
python tests/tests.py
pip freeze > requirements.txt

.PHONY: tag
tag:
# tag git commit
git add requirements.txt
git add pyproject.toml
git add CHANGELOG.rst
git commit -m "bump version" --allow-empty
git tag -a v$(version) -m "version $(version)"
git push --tags
git push

.PHONY: docs
docs:
Expand Down
84 changes: 72 additions & 12 deletions README.rst
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
pykeepass
============

.. image:: https://github.com/libkeepass/pykeepass/workflows/CI/badge.svg
:target: https://github.com/libkeepass/pykeepass/actions?query=workflow%3ACI
.. image:: https://github.com/libkeepass/pykeepass/actions/workflows/ci.yaml/badge.svg
:target: https://github.com/libkeepass/pykeepass/actions/workflows/ci.yaml

.. image:: https://readthedocs.org/projects/pykeepass/badge/?version=latest
:target: https://pykeepass.readthedocs.io/en/latest/?badge=latest
Expand All @@ -16,10 +16,17 @@ pykeepass

This library allows you to write entries to a KeePass database.

Come chat at `#pykeepass`_ on Freenode or `#pykeepass:matrix.org`_ on Matrix.
Come chat at `#pykeepass:matrix.org`_ on Matrix.

.. _#pykeepass: irc://irc.freenode.net
.. _#pykeepass\:matrix.org: https://matrix.to/#/%23pykeepass:matrix.org
.. _#pykeepass\:matrix.org: https://matrix.to/#/%23pykeepass:matrix.org

Installation
------------

.. code::
sudo apt install python3-lxml
pip install pykeepass
Example
-------
Expand Down Expand Up @@ -66,7 +73,7 @@ Finding Entries

**find_entries** (title=None, username=None, password=None, url=None, notes=None, otp=None, path=None, uuid=None, tags=None, string=None, group=None, recursive=True, regex=False, flags=None, history=False, first=False)

Returns entries which match all provided parameters, where ``title``, ``username``, ``password``, ``url``, ``notes``, ``otp``, and ``autotype_sequence`` are strings, ``path`` is a list, ``string`` is a dict, ``autotype_enabled`` is a boolean, ``uuid`` is a ``uuid.UUID`` and ``tags`` is a list of strings. This function has optional ``regex`` boolean and ``flags`` string arguments, which means to interpret search strings as `XSLT style`_ regular expressions with `flags`_.
Returns entries which match all provided parameters, where ``title``, ``username``, ``password``, ``url``, ``notes``, ``otp``, ``autotype_window`` and ``autotype_sequence`` are strings, ``path`` is a list, ``string`` is a dict, ``autotype_enabled`` is a boolean, ``uuid`` is a ``uuid.UUID`` and ``tags`` is a list of strings. This function has optional ``regex`` boolean and ``flags`` string arguments, which means to interpret search strings as `XSLT style`_ regular expressions with `flags`_.

.. _XSLT style: https://www.xml.com/pub/a/2003/06/04/tr.html
.. _flags: https://www.w3.org/TR/xpath-functions/#flags
Expand Down Expand Up @@ -163,8 +170,8 @@ a flattened list of all groups in the database
Group: "/"
Entry Functions
---------------
Entry Functions and Properties
------------------------------
**add_entry** (destination_group, title, username, password, url=None, notes=None, tags=None, expiry_time=None, icon=None, force_creation=False)

**delete_entry** (entry)
Expand All @@ -175,6 +182,18 @@ move a group to the recycle bin. The recycle bin is created if it does not exit

**move_entry** (entry, destination_group)

**atime**

access time

**ctime**

creation time

**mtime**

modification time

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.

If ``expiry_time`` is a naive datetime object (i.e. ``expiry_time.tzinfo`` is not set), the timezone is retrieved from ``dateutil.tz.gettz()``.
Expand Down Expand Up @@ -202,8 +221,15 @@ If ``expiry_time`` is a naive datetime object (i.e. ``expiry_time.tzinfo`` is no
# save the database
>>> kp.save()
Group Functions
---------------
# change creation time
>>> from datetime import datetime, timezone
>>> entry.ctime = datetime(2023, 1, 1, tzinfo=timezone.utc)
# update modification or access time
>>> entry.touch(modify=True)
Group Functions and Properties
------------------------------
**add_group** (destination_group, group_name, icon=None, notes=None)

**delete_group** (group)
Expand All @@ -218,6 +244,18 @@ delete all entries and subgroups of a group. ``group`` is an instance of ``Grou

**move_group** (group, destination_group)

**atime**

access time

**ctime**

creation time

**mtime**

modification time

``destination_group`` and ``group`` are instances of ``Group``. ``group_name`` is a string

.. code:: python
Expand All @@ -241,6 +279,13 @@ delete all entries and subgroups of a group. ``group`` is an instance of ``Grou
# save the database
>>> kp.save()
# change creation time
>>> from datetime import datetime, timezone
>>> group.ctime = datetime(2023, 1, 1, tzinfo=timezone.utc)
# update modification or access time
>>> group.touch(modify=True)
Attachments
-----------

Expand Down Expand Up @@ -366,7 +411,7 @@ Miscellaneous
-------------
**read** (filename=None, password=None, keyfile=None, transformed_key=None, decrypt=False)
where ``filename``, ``password``, and ``keyfile`` are strings. ``filename`` is the path to the database, ``password`` is the master password string, and ``keyfile`` is the path to the database keyfile. At least one of ``password`` and ``keyfile`` is required. Alternatively, the derived key can be supplied directly through ``transformed_key``. ``decrypt`` tells whether the file should be decrypted or not.
where ``filename``, ``password``, and ``keyfile`` are strings ( ``filename`` and ``keyfile`` may also be file-like objects). ``filename`` is the path to the database, ``password`` is the master password string, and ``keyfile`` is the path to the database keyfile. At least one of ``password`` and ``keyfile`` is required. Alternatively, the derived key can be supplied directly through ``transformed_key``. ``decrypt`` tells whether the file should be decrypted or not.
Can raise ``CredentialsError``, ``HeaderChecksumError``, or ``PayloadChecksumError``.
Expand All @@ -376,7 +421,7 @@ reload database from disk using previous credentials
**save** (filename=None)
where ``filename`` is the path of the file to save to. If ``filename`` is not given, the path given in ``read`` will be used.
where ``filename`` is the path of the file to save to (``filename`` may also be file-like object). If ``filename`` is not given, the path given in ``read`` will be used.
**password**
Expand Down Expand Up @@ -414,6 +459,21 @@ get database XML data as string
pretty print database XML to file
TOTP
-------
**Entry.otp**
TOTP URI which can be passed to an OTP library to generate codes
.. code:: python
# find an entry which has otp attribute
>>> e = kp.find_entries(otp='.*', regex=True, first=True)
>>> import pyotp
>>> pyotp.parse_uri(e.otp).now()
799270
Tests and Debugging
-------------------
Expand Down
2 changes: 0 additions & 2 deletions pykeepass/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
from __future__ import absolute_import
from .pykeepass import PyKeePass, create_database

from .version import __version__

__all__ = ["PyKeePass", "create_database", "__version__"]
3 changes: 2 additions & 1 deletion pykeepass/attachment.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from . import entry
from .exceptions import BinaryError

class Attachment(object):

class Attachment:
def __init__(self, element=None, kp=None, id=None, filename=None):
self._element = element
self._kp = kp
Expand Down
19 changes: 12 additions & 7 deletions pykeepass/baseelement.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import base64
import uuid
from datetime import datetime, timezone


from lxml import etree
from lxml.builder import E
from datetime import datetime


class BaseElement():
class BaseElement:
"""Entry and Group inherit from this class"""

def __init__(self, element, kp=None, icon=None, expires=False,
Expand All @@ -17,9 +19,9 @@ def __init__(self, element, kp=None, icon=None, expires=False,
)
if icon:
self._element.append(E.IconID(icon))
current_time_str = self._kp._encode_time(datetime.now())
current_time_str = self._kp._encode_time(datetime.now(timezone.utc))
if expiry_time:
expiry_time_str = self._kp._encode_time(expiry_time)
expiry_time_str = self._kp._encode_time(expiry_time.astimezone(timezone.utc))
else:
expiry_time_str = current_time_str

Expand Down Expand Up @@ -116,8 +118,8 @@ def expires(self, value):
def expired(self):
if self.expires:
return (
self._kp._datetime_to_utc(datetime.utcnow()) >
self._kp._datetime_to_utc(self.expiry_time)
datetime.now(timezone.utc) >
self.expiry_time
)

return False
Expand All @@ -132,6 +134,7 @@ def expiry_time(self, value):

@property
def ctime(self):
"""(datetime.datetime): Creation time."""
return self._get_times_property('CreationTime')

@ctime.setter
Expand All @@ -140,6 +143,7 @@ def ctime(self, value):

@property
def atime(self):
"""(datetime.datetime): Access time. Update with touch()"""
return self._get_times_property('LastAccessTime')

@atime.setter
Expand All @@ -148,6 +152,7 @@ def atime(self, value):

@property
def mtime(self):
"""(datetime.datetime): Access time. Update with touch(modify=True)"""
return self._get_times_property('LastModificationTime')

@mtime.setter
Expand Down Expand Up @@ -178,7 +183,7 @@ def touch(self, modify=False):
Args:
modify (bool): update access time as well a modification time
"""
now = datetime.now()
now = datetime.now(timezone.utc)
self.atime = now
if modify:
self.mtime = now
3 changes: 0 additions & 3 deletions pykeepass/deprecated.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
#!/usr/bin/env python3


# ---------- Find functions ---------------
# Use find_entries()/find_groups() instead

Expand Down
Loading

0 comments on commit 5105742

Please sign in to comment.