diff --git a/bunker/backends/__init__.py b/bunker/backends/__init__.py index e69de29..80ca188 100644 --- a/bunker/backends/__init__.py +++ b/bunker/backends/__init__.py @@ -0,0 +1,3 @@ +from .kvs import KeyValueStore + +components = {KeyValueStore.component_type: KeyValueStore} diff --git a/bunker/backends/component.py b/bunker/backends/component.py new file mode 100644 index 0000000..8fad65b --- /dev/null +++ b/bunker/backends/component.py @@ -0,0 +1,38 @@ +from abc import abstractmethod, abstractclassmethod, ABCMeta +class AbstractComponent(metaclass=ABCMeta): + component_type = "DO NOT INSTANTIATE abstract component" + def __init__(self, bunker, name, password): + self._bunker = bunker + self._name = name + self._password = password + + @abstractclassmethod + def new(cls, bunker, name, password): + """ + Create a new component in the given bunker + using the given name and password. + """ + pass + + @abstractmethod + def close(self, notify_bunker=True): + """ + This method should "close" the given component, + meaning it should write back the changes and bring + it in a state where it cannot be used anymore. + + Such a state needs two important protections: + + - It is protected against accidental usage, such that + a regular user that forgets that the component is already + closed cannot damage the data + - All sensitive data (i.e. the password) is destroyed. + + If notify_bunker is True the parenting bunker will be + notified that the current component is already closed. + + """ + pass + @abstractmethod + def write_back(self): + pass diff --git a/bunker/backends/kvs.py b/bunker/backends/kvs.py new file mode 100644 index 0000000..ca4115e --- /dev/null +++ b/bunker/backends/kvs.py @@ -0,0 +1,54 @@ +from ljson.slapdash.mem import Table +from ljson.slapdash import SlapdashHeader + +from .component import AbstractComponent + +class KeyValueStore(AbstractComponent): + component_type = "kvs" + def __init__(self, bunker, name, password): + AbstractComponent.__init__(self, bunker, name, password) + + file_, type_ = bunker._load_component(name, password) + self._content = Table.from_file(file_) + self._open = True + + @classmethod + def new(cls, bunker, name, password): + bunker._add_component(name, password, KeyValueStore.component_type) + + file_, type_ = bunker._load_component(name, password) + table = Table(SlapdashHeader({}), []) + table.save(file_) + bunker._save_component(name, password, file_) + + return cls(bunker, name, password) + + def additem(self, key, value): + if(not self._open): + raise IOError("KeyValueStore is closed") + self._content.additem({"key": key, "value": value}) + def getitem(self, key): + if(not self._open): + raise IOError("KeyValueStore is closed") + return self._content[{"key": key}]["value"][0] + + def write_back(self): + if(not self._open): + raise IOError("KeyValueStore is closed") + file_, type_ = self._bunker._load_component(self._name, self._password) + self._content.save(file_) + self._bunker._save_component(self._name, self._password, file_) + + @property + def closed(self): + return not self._open + + def close(self, notify_bunker=True): + self.write_back() + self._open = False + self._name = "" + self._password = b"" + del(self._content) + if(notify_bunker): + self._bunker.notify_component_is_closed(self) + diff --git a/bunker/bunker.py b/bunker/bunker.py index 57bd530..1f167ab 100644 --- a/bunker/bunker.py +++ b/bunker/bunker.py @@ -1,15 +1,18 @@ import uuid +from collections import deque import ljson from Crypto.Cipher import AES from .files.tarfile import RewriteableTarFile from .files.bunkeredfile import BunkeredFile +from .backends import components class Bunker(object): def __init__(self, path): self._tarfile = RewriteableTarFile.open(path) self._components = ljson.Table.from_file(self._tarfile.get_file("__bunker_main__")) - + self._stack_of_open_components = deque() + self._stack_of_open_components.appendleft(deque()) @classmethod def open(cls, path): @@ -52,6 +55,8 @@ class Bunker(object): while(True): chunk = file_.read(chunk_size) length_read += len(chunk) + if(not chunk): + break if(length_read == length): decrypted.write(key.decrypt(chunk)) break @@ -87,7 +92,7 @@ class Bunker(object): self._components.save(self._tarfile.get_file("__bunker_main__")) self._tarfile.writeback_file("__bunker_main__") - def _save_compontent(self, name, password, file_): + def _save_component(self, name, password, file_): chunk_size = 16 if(not {"component": name} in self._components): raise ValueError("unknown component: {}".format(name)) @@ -102,6 +107,7 @@ class Bunker(object): file_out = self._tarfile.get_file(file_name) file_out.truncate(0) + file_out.seek(0, 0) file_.seek(0, 0) chunk = file_.read(chunk_size) @@ -117,4 +123,33 @@ class Bunker(object): file_out.close() + def __enter__(self): + self._stack_of_open_components.appendleft(deque()) + return self + def __exit__(self): + open_components = self._stack_of_open_components.popleft() + for component in open_components: + component.close(notify_bunker=False) + return False + + def add_component(self, type_, name, password): + if(not type_ in components): + raise ValueError("unknown component type: '{}'".format(type_)) + component = components[type_].new(self, name, password) + self._stack_of_open_components[0].append(component) + return component + + def get_component(self, name, password): + type_ = self._components[{"component": name}]["type"][0] + component = components[type_](self, name, password) + self._stack_of_open_components[0].append(component) + + return component + + def notify_component_is_closed(self, component): + for context in self._stack_of_open_components: + for open_component in context: + if(open_component == component): + context.remove(open_component) + break