From: Mark Wooding Date: Thu, 28 May 2015 10:09:26 +0000 (+0100) Subject: catacomb/pwsafe.py: New Git-friendly `DirectoryStorageBackend'. X-Git-Tag: 1.1.0~6 X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~mdw/git/catacomb-python/commitdiff_plain/b61e9efeda64d9736f767fd9a4b205716f7cc324 catacomb/pwsafe.py: New Git-friendly `DirectoryStorageBackend'. --- diff --git a/catacomb/pwsafe.py b/catacomb/pwsafe.py index 7b737b8..08e946b 100644 --- a/catacomb/pwsafe.py +++ b/catacomb/pwsafe.py @@ -753,6 +753,77 @@ class FlatFileStorageBackend (PlainTextBackend): me._write_meta(f) me._write_passwd(f, '$') +class DirectoryStorageBackend (PlainTextBackend): + """ + I maintain a password database in a directory, with one file per password. + + This makes password databases easy to maintain in a revision-control system + such as Git. + + The directory is structured as follows. + + dir/meta Contains metadata, similar to the `FlatFileBackend'. + + dir/pw/LABEL Contains the (raw binary) payload for the given password + LABEL (base64-encoded, without the useless `=' padding, and + with `/' replaced by `.'). + + dir/tmp/ Contains temporary files used by the implementation. + """ + + NAME = 'dir' + METAMAGIC = '### pwsafe password directory metadata' + + def _open(me, file, writep): + if not _OS.path.isdir(file) or \ + not _OS.path.isdir(_OS.path.join(file, 'pw')) or \ + not _OS.path.isdir(_OS.path.join(file, 'tmp')) or \ + not _OS.path.isfile(_OS.path.join(file, 'meta')): + raise StorageBackendRefusal + me._dir = file + me._parse_file(_OS.path.join(file, 'meta'), magic = me.METAMAGIC) + def _parse_line(me, line): + me._parse_meta(line) + + def _create(me, file): + _OS.mkdir(file, 0700) + _OS.mkdir(_OS.path.join(file, 'pw'), 0700) + _OS.mkdir(_OS.path.join(file, 'tmp'), 0700) + me._mark_dirty() + me._dir = file + + def _close(me, abruptp): + if not abruptp and me._dirtyp: + me._write_file(_OS.path.join(me._dir, 'meta'), + me._write_meta, magic = me.METAMAGIC) + + def _pwfile(me, label, dir = 'pw'): + return _OS.path.join(me._dir, dir, _b64(label).replace('/', '.')) + def _get_passwd(me, label): + try: + f = open(me._pwfile(label), 'rb') + except (OSError, IOError), e: + if e.errno == _E.ENOENT: raise KeyError, label + else: raise + with f: return f.read() + def _put_passwd(me, label, payload): + new = me._pwfile(label, 'tmp') + fd = _OS.open(new, _OS.O_WRONLY | _OS.O_CREAT | _OS.O_TRUNC, 0600) + _OS.close(fd) + with open(new, 'wb') as f: f.write(payload) + _OS.rename(new, me._pwfile(label)) + def _del_passwd(me, label): + try: + _OS.remove(me._pwfile(label)) + except (OSError, IOError), e: + if e == _E.ENOENT: raise KeyError, label + else: raise + def _iter_passwds(me): + pw = _OS.path.join(me._dir, 'pw') + for i in _OS.listdir(pw): + with open(_OS.path.join(pw, i), 'rb') as f: pld = f.read() + yield _unb64(i.replace('.', '/')), pld + ###-------------------------------------------------------------------------- ### Password storage.