Skip to content

Commit

Permalink
Merge branch 'master' into python3_syntax
Browse files Browse the repository at this point in the history
  • Loading branch information
ekuler authored Feb 12, 2024
2 parents 2d1194d + d8ec188 commit 0868301
Show file tree
Hide file tree
Showing 11 changed files with 164 additions and 103 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
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 .
- name: Run tests
run: |
Expand Down
19 changes: 14 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
version := $(shell python -c "exec(open('pykeepass/version.py').read());print(__version__)")
.ONESHELL:
.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: dist
# check that changelog is updated
if ! grep ${version} CHANGELOG.rst
then
echo "Changelog doesn't seem to be updated! Quitting..."
exit 1
fi
twine upload dist/pykeepass-$(version)*
gh release create pykeepass-$(version) dist/pykeepass-$(version)*

.PHONY: docs
docs:
Expand Down
10 changes: 9 additions & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,15 @@ This library allows you to write entries to a KeePass database.
Come chat at `#pykeepass`_ on Freenode or `#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
13 changes: 7 additions & 6 deletions pykeepass/baseelement.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import base64
import uuid
from datetime import datetime
from datetime import datetime, timezone


from lxml import etree
from lxml.builder import E
Expand All @@ -18,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 @@ -117,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 Down Expand Up @@ -179,7 +180,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
61 changes: 29 additions & 32 deletions pykeepass/pykeepass.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@
import uuid
import zlib
from binascii import Error as BinasciiError
from datetime import datetime, timedelta
from datetime import datetime, timedelta, timezone
from pathlib import Path

from construct import CheckError, ChecksumError, Container
from construct import Container, ChecksumError, CheckError

from dateutil import parser, tz
from lxml import etree
from lxml.builder import E
Expand All @@ -34,7 +35,7 @@
BLANK_DATABASE_FILENAME = "blank_database.kdbx"
BLANK_DATABASE_LOCATION = os.path.join(os.path.dirname(os.path.realpath(__file__)), BLANK_DATABASE_FILENAME)
BLANK_DATABASE_PASSWORD = "password"

DT_ISOFORMAT = "%Y-%m-%dT%H:%M:%S%fZ"

class PyKeePass:
"""Open a KeePass database
Expand Down Expand Up @@ -231,10 +232,19 @@ def database_salt(self):
kdf_parameters = self.kdbx.header.value.dynamic_header.kdf_parameters.data.dict
return kdf_parameters['S'].value

@property
def payload(self):
"""Encrypted payload of keepass database"""
# check if payload is decrypted
if self.kdbx.body.payload is None:
raise ValueError("Database is not decrypted")
else:
return self.kdbx.body.payload

@property
def tree(self):
"""lxml.etree._ElementTree: database XML payload"""
return self.kdbx.body.payload.xml
return self.payload.xml

@property
def root_group(self):
Expand Down Expand Up @@ -593,7 +603,7 @@ def attachments(self):
def binaries(self):
if self.version >= (4, 0):
# first byte is a prepended flag
binaries = [a.data[1:] for a in self.kdbx.body.payload.inner_header.binary]
binaries = [a.data[1:] for a in self.payload.inner_header.binary]
else:
binaries = []
for elem in self._xpath('/KeePassFile/Meta/Binaries/Binary'):
Expand All @@ -617,7 +627,7 @@ def add_binary(self, data, compressed=True, protected=True):
data = b'\x01' + data if protected else b'\x00' + data
# add binary element to inner header
c = Container(type='binary', data=data)
self.kdbx.body.payload.inner_header.binary.append(c)
self.payload.inner_header.binary.append(c)
else:
binaries = self._xpath(
'/KeePassFile/Meta/Binaries',
Expand Down Expand Up @@ -649,7 +659,7 @@ def delete_binary(self, id):
try:
if self.version >= (4, 0):
# remove binary element from inner header
self.kdbx.body.payload.inner_header.binary.pop(id)
self.payload.inner_header.binary.pop(id)
else:
# remove binary element from XML
binaries = self._xpath('/KeePassFile/Meta/Binaries', first=True)
Expand Down Expand Up @@ -716,7 +726,7 @@ def password(self):
@password.setter
def password(self, password):
self._password = password
self.credchange_date = datetime.now()
self.credchange_date = datetime.now(timezone.utc)

@property
def keyfile(self):
Expand All @@ -726,7 +736,7 @@ def keyfile(self):
@keyfile.setter
def keyfile(self, keyfile):
self._keyfile = keyfile
self.credchange_date = datetime.now()
self.credchange_date = datetime.now(timezone.utc)

@property
def credchange_required_days(self):
Expand Down Expand Up @@ -763,16 +773,16 @@ def credchange_date(self):

@credchange_date.setter
def credchange_date(self, date):
time = self._xpath('/KeePassFile/Meta/MasterKeyChanged', first=True)
time.text = self._encode_time(date)
mk_time = self._xpath('/KeePassFile/Meta/MasterKeyChanged', first=True)
mk_time.text = self._encode_time(date)

@property
def credchange_required(self):
"""bool: Check if credential change is required"""
change_date = self.credchange_date
if change_date is None or self.credchange_required_days == -1:
return False
now_date = self._datetime_to_utc(datetime.now())
now_date = datetime.now(timezone.utc)
return (now_date - change_date).days > self.credchange_required_days

@property
Expand All @@ -781,38 +791,31 @@ def credchange_recommended(self):
change_date = self.credchange_date
if change_date is None or self.credchange_recommended_days == -1:
return False
now_date = self._datetime_to_utc(datetime.now())
now_date = datetime.now(timezone.utc)
return (now_date - change_date).days > self.credchange_recommended_days

# ---------- Datetime Functions ----------

def _datetime_to_utc(self, dt):
"""Convert naive datetimes to UTC"""

if not dt.tzinfo:
dt = dt.replace(tzinfo=tz.gettz())
return dt.astimezone(tz.gettz('UTC'))

def _encode_time(self, value):
"""bytestring or plaintext string: Convert datetime to base64 or plaintext string"""

if self.version >= (4, 0):
diff_seconds = int(
(
self._datetime_to_utc(value) -
value -
datetime(
year=1,
month=1,
day=1,
tzinfo=tz.gettz('UTC')
tzinfo=timezone.utc
)
).total_seconds()
)
return base64.b64encode(
struct.pack('<Q', diff_seconds)
).decode('utf-8')
else:
return self._datetime_to_utc(value).isoformat()
return value.strftime(DT_ISOFORMAT)

def _decode_time(self, text):
"""datetime.datetime: Convert base64 time or plaintext time to datetime"""
Expand All @@ -821,21 +824,15 @@ def _decode_time(self, text):
# decode KDBX4 date from b64 format
try:
return (
datetime(year=1, month=1, day=1, tzinfo=tz.gettz('UTC')) +
datetime(year=1, month=1, day=1, tzinfo=timezone.utc) +
timedelta(
seconds=struct.unpack('<Q', base64.b64decode(text))[0]
)
)
except BinasciiError:
return parser.parse(
text,
tzinfos={'UTC': tz.gettz('UTC')}
)
return datetime.strptime(text, DT_ISOFORMAT).replace(tzinfo=timezone.utc)
else:
return parser.parse(
text,
tzinfos={'UTC': tz.gettz('UTC')}
)
return datetime.strptime(text, DT_ISOFORMAT).replace(tzinfo=timezone.utc)

def create_database(
filename, password=None, keyfile=None, transformed_key=None
Expand Down
6 changes: 4 additions & 2 deletions pykeepass/version.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
__version__ = "4.0.6"

__all__= ["__version__"]

# FIXME: switch to using importlib.metadata when dropping Python<=3.7
import pkg_resources
__version__ = pkg_resources.get_distribution('pykeepass').version
42 changes: 42 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
[project]
name = "pykeepass"
version = "4.0.7"
readme = "README.rst"
description = "Python library to interact with keepass databases (supports KDBX3 and KDBX4)"
authors = [
{ name = "Philipp Schmitt", email = "[email protected]" },
{ name = "Evan Widloski", email = "[email protected]" }
]
license = {text = "GPL-3.0"}
keywords = ["vault", "keepass"]
dependencies = [
"construct>=2.10.53",
"argon2_cffi>=18.1.0",
"pycryptodomex>=3.6.2",
"lxml",
]
classifiers = [
"Topic :: Security",
"Topic :: Software Development :: Libraries",
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
]

[project.urls]
Homepage = "https://github.com/libkeepass/pykeepass"
Repository = "https://github.com/libkeepass/pykeepass"
Issues = "https://github.com/libkeepass/pykeepass/issues"
Changelog = "https://github.com/libkeepass/pykeepass/blob/master/CHANGELOG.rst"

[tool.setuptools]
packages = ["pykeepass"]
include-package-data = true

[build-system]
requires = ["setuptools>=59.0.0"]
build-backend = 'setuptools.build_meta'
6 changes: 0 additions & 6 deletions requirements-rtd.txt

This file was deleted.

5 changes: 0 additions & 5 deletions requirements.txt

This file was deleted.

30 changes: 0 additions & 30 deletions setup.py

This file was deleted.

Loading

0 comments on commit 0868301

Please sign in to comment.