Можно ли установить время "истечения" на основе времени "создания"?
После использования Keepass в течение нескольких лет мне пришла идея, которую было бы неплохо реализовать. В моей базе более 300 записей, и было бы неплохо узнать, какие из этих записей все еще хороши. Установка флага истечения срока действия в записях поможет напомнить мне проверить, являются ли записи действительными, и потенциально побудит меня сменить пароль. Есть ли способ просмотреть всю базу данных и сделать следующее для каждой записи?
- Проверьте, установлен ли флаг истечения.
- Если он не установлен, добавьте X количество лет ко времени создания (или изменения).
- Установите флаг истечения.
- Установите время истечения до значения, рассчитанного на шаге 2.
- Перейдите к следующей записи в базе данных.
Пол получил ответ: "Не с KeePass, но вы можете использовать KPScript и некоторые PowerShell". Он включил ссылку на #1318 Изменить дату истечения срока из списка, но моя неопытность с инструментами, которые он рекомендовал, оставила мне только больше вопросов. Может ли кто-нибудь оказать дальнейшую помощь? (1)
добавление
После некоторых дальнейших исследований была обнаружена возможность экспорта базы данных в формате XML. Будет ли проще проанализировать файл XML, изменить данные в этой форме, а затем создать новую базу данных, импортировав результаты? Если использование уже внесенных предложений будет слишком сложным, обработка XML может оказаться более простым путем.
2 ответа
Да, можно установить время "истечения" в зависимости от времени "создания". С помощью программы процесс может быть автоматизирован. Измените глобальные константы (KPSCRIPT, DATABASE и PASSWORD) на значения, соответствующие вашей системе перед запуском. В этом конкретном примере срок действия устанавливается через пять лет после даты последнего изменения. Программа ниже была адаптирована из ответа Пола.
#! /usr/bin/env python3
import datetime
import subprocess
import uuid
KPSCRIPT = r'C:\Program Files (x86)\KeePass Password Safe 2\KPScript.exe'
DATABASE = r'C:\Users\Stephen Paul Chappel\Documents\Database.kdbx'
PASSWORD = r'password'
def main():
"""Get KeePass entries, check each one, and change if required."""
for line in ps(KPSCRIPT, '-c:ListEntries', DATABASE, '-pw:' + PASSWORD):
key, value, *pair = line.split(': ', 1) + [None]
if pair:
if key == 'UUID':
reference = uuid.UUID(value)
elif key == 'TLM':
tlm = datetime.datetime.strptime(value, '%Y-%m-%dT%H:%M:%S')
elif key == 'EXP':
if not {'False': False, 'True': True}[value]:
# Add 5 years to the last modification
# time for the expiry date and format.
te = tlm.replace(tlm.year + 5)
te = te.strftime('%Y-%m-%dT%H:%M:%S')
ps(
KPSCRIPT,
'-c:EditEntry',
DATABASE,
'-pw:' + PASSWORD,
'-refx-UUID:' + reference.hex,
'-setx-Expires:True',
'-setx-ExpiryTime:' + te
)
del reference, tlm
def ps(*args):
"""Provide an interface for writing commands like those in PowerShell."""
return subprocess.run(
args,
timeout=1,
check=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
universal_newlines=True
).stdout.splitlines()
if __name__ == '__main__':
main()
Если вам нужен подход ООП, вы можете попробовать использовать следующий прототип:
#! /usr/bin/env python3
from datetime import datetime
from pathlib import Path
from subprocess import run, CalledProcessError
from sys import intern
from uuid import UUID
DATABASE = Path(r'C:\Users\Stephen Chappell\Documents\Accounts Live.kdbx')
PASSWORD = r'password'
def main():
"""Get KeePass entries, check each one, and change if required."""
db = KPScript(DATABASE, PASSWORD)
for entry in filter(needs_expiration, db.list_entries()):
print('Updating:', {key: entry['S'][key] for key in
'Title UserName Password URL'.split()})
# Add 5 years to the last modification
# time for the expiry date and format.
last_modified = entry['TLM']
expiry_time = last_modified.replace(last_modified.year + 5)
db.edit_entry(entry['UUID'], True, expiry_time)
def needs_expiration(entry):
"""Check if an entry needs its expiration set."""
return not entry['EXP'] and \
entry['GRPN'] not in {'Recycle Bin', 'Templates'}
def _field_to_datetime(value):
"""Convert a timestamp into a datetime object."""
return datetime.strptime(value, KPScript.DATETIME_FORMAT)
class KPScript:
"""Reference: https://keepass.info/help/v2_dev/scr_sc_index.html"""
EXE = Path(r'C:\Program Files\KeePass Password Safe 2\KPScript.exe')
TIMEOUT = 2
DATETIME_FORMAT = '%Y-%m-%dT%H:%M:%SZ'
FIELD_TYPES = {
'UUID': UUID,
'GRPU': UUID,
'GRPN': intern,
'S': lambda s: s,
'TC': _field_to_datetime,
'TLM': _field_to_datetime,
'TE': _field_to_datetime,
'EXP': {'True': True, 'False': False}.__getitem__
}
def __init__(self, db_path, password=None, keyfile=None):
"""Initialize the attributes of a KPScript instance."""
self.__db_path = db_path
self.__password = password
self.__keyfile = keyfile
def __do_command(self, name, kwargs=None):
"""Help run whatever command is specified by name."""
args = [self.EXE, f'-c:{name}', self.__db_path]
if self.__password is not None:
args.append(f'-pw:{self.__password}')
if self.__keyfile is not None:
args.append(f'-keyfile:{self.__keyfile}')
if kwargs is not None:
for key, value in kwargs.items():
args.append(f'-{key}:{value}')
try:
completed_process = run(
args,
capture_output=True,
timeout=self.TIMEOUT,
check=True,
text=True
)
except CalledProcessError as error:
print('Error:', error.stdout)
raise error
else:
return completed_process.stdout.splitlines()
def list_groups(self):
"""This command lists all groups in a format that easily
machine-readable."""
...
def list_entries(self):
"""This command lists all entries in a format that easily
machine-readable."""
def parse_line():
nonlocal line_key, line_value
try:
new_line_key, new_line_value = line.split(': ', 1)
if new_line_key not in self.FIELD_TYPES.keys() | {'OK'}:
raise ValueError()
except ValueError:
append_value()
else:
line_key, line_value = new_line_key, new_line_value
if line_key == 'S':
parse_s()
else:
entry[line_key] = line_value
def parse_s():
nonlocal s_key, s_value
try:
s_key, s_value = line_value.split(' = ', 1)
except ValueError:
raise SyntaxError(line_value)
else:
s_field = entry.setdefault('S', {})
s_field[s_key] = s_value
def append_value():
if line_key == 'S':
entry['S'][s_key] += '\n' + line
else:
entry[line_key] += '\n' + line
def complete_entry():
return entry.keys() == self.FIELD_TYPES.keys()
def transform():
for key, value in self.FIELD_TYPES.items():
entry[key] = value(entry[key])
return entry
entry = {}
line_key = line_value = ''
s_key = s_value = ''
for line in self.__do_command('ListEntries'):
if line:
parse_line()
elif complete_entry():
yield transform()
entry = {}
else:
append_value()
if len(entry) != 1 or 'OK' not in entry:
raise ValueError('last entry is invalid')
def get_entry_string(self):
"""Retrieves the value of an entry string field."""
...
def add_entry(self):
"""This command adds an entry to the database."""
...
def edit_entry(self, uuid, expires=None, expiry_time=None):
"""This command edits existing entries."""
kwargs = {'refx-UUID': uuid.hex}
if expires is not None:
kwargs['setx-Expires'] = str(bool(expires)).lower()
if expiry_time is not None:
kwargs['setx-ExpiryTime'] = expiry_time.strftime(
self.DATETIME_FORMAT)
self.__do_command('EditEntry', kwargs)
def move_entry(self):
"""This command moves one or more existing entries."""
...
def delete_entry(self):
"""This command deletes one or more existing entries."""
...
def delete_all_entries(self):
"""This command deletes all entries (in all subgroups)."""
...
def import_(self):
"""This command imports a file into the database."""
...
def export(self):
"""This command exports (parts of) the database."""
...
def sync(self):
"""This command synchronizes the database with another one."""
...
def change_master_key(self):
"""This command changes the master key of the database."""
...
def detach_bins(self):
"""This command saves all entry attachments (into the directory of the
database) and removes them from the database."""
...
def gen_pw(self):
"""Generates passwords."""
...
def estimate_quality(self):
"""Estimates the quality (in bits) of the password specified via the
-text: parameter."""
...
if __name__ == '__main__':
main()