chiark / gitweb /
catacomb/pwsafe.py: New Git-friendly `DirectoryStorageBackend'.
authorMark Wooding <mdw@distorted.org.uk>
Thu, 28 May 2015 10:09:26 +0000 (11:09 +0100)
committerMark Wooding <mdw@distorted.org.uk>
Tue, 16 Jun 2015 10:25:18 +0000 (11:25 +0100)
catacomb/pwsafe.py

index 7b737b8d8fd3a5906392ae5bef0a0620e6fd6db3..08e946b7d97d9ac9aa2adb67aae3fbbe49e3e9d5 100644 (file)
@@ -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.