diff options
Diffstat (limited to 'backends/leap_backend.py')
-rw-r--r-- | backends/leap_backend.py | 224 |
1 files changed, 0 insertions, 224 deletions
diff --git a/backends/leap_backend.py b/backends/leap_backend.py deleted file mode 100644 index a37f9d25..00000000 --- a/backends/leap_backend.py +++ /dev/null @@ -1,224 +0,0 @@ -""" -A U1DB backend that encrypts data before sending to server and decrypts after -receiving. -""" - -try: - import simplejson as json -except ImportError: - import json # noqa - -from u1db import Document -from u1db.remote import utils -from u1db.remote.http_target import HTTPSyncTarget -from u1db.remote.http_database import HTTPDatabase -from u1db.errors import BrokenSyncStream - -import uuid - - -class NoDefaultKey(Exception): - """ - Exception to signal that there's no default OpenPGP key configured. - """ - pass - - -class NoSoledadInstance(Exception): - """ - Exception to signal that no Soledad instance was found. - """ - pass - - -class DocumentNotEncrypted(Exception): - """ - Exception to signal failures in document encryption. - """ - pass - - -class LeapDocument(Document): - """ - Encryptable and syncable document. - - LEAP Documents are standard u1db documents with cabability of returning an - encrypted version of the document json string as well as setting document - content based on an encrypted version of json string. - """ - - def __init__(self, doc_id=None, rev=None, json='{}', has_conflicts=False, - encrypted_json=None, soledad=None, syncable=True): - super(LeapDocument, self).__init__(doc_id, rev, json, has_conflicts) - self._soledad = soledad - self._syncable = syncable - if encrypted_json: - self.set_encrypted_json(encrypted_json) - - def get_encrypted_content(self): - """ - Return an encrypted JSON serialization of document's contents. - """ - if not self._soledad: - raise NoSoledadInstance() - return self._soledad.encrypt_symmetric(self.doc_id, - self.get_json()) - - def set_encrypted_content(self, cyphertext): - """ - Set document's content based on an encrypted JSON serialization of - contents. - """ - plaintext = self._soledad.decrypt_symmetric(self.doc_id, cyphertext) - return self.set_json(plaintext) - - def get_encrypted_json(self): - """ - Return a valid JSON string containing document's content encrypted to - the user's public key. - """ - return json.dumps({'_encrypted_json': self.get_encrypted_content()}) - - def set_encrypted_json(self, encrypted_json): - """ - Set document's content based on a valid JSON string containing the - encrypted document's contents. - """ - if not self._soledad: - raise NoSoledadInstance() - cyphertext = json.loads(encrypted_json)['_encrypted_json'] - self.set_encrypted_content(cyphertext) - - def _get_syncable(self): - return self._syncable - - def _set_syncable(self, syncable=True): - self._syncable = syncable - - syncable = property( - _get_syncable, - _set_syncable, - doc="Determine if document should be synced with server." - ) - - # Returning the revision as string solves the following exception in - # Twisted web: - # exceptions.TypeError: Can only pass-through bytes on Python 2 - def _get_rev(self): - if self._rev is None: - return None - return str(self._rev) - - def _set_rev(self, rev): - self._rev = rev - - rev = property( - _get_rev, - _set_rev, - doc="Wrapper to ensure `doc.rev` is always returned as bytes.") - - -class LeapSyncTarget(HTTPSyncTarget): - """ - A SyncTarget that encrypts data before sending and decrypts data after - receiving. - """ - - def __init__(self, url, creds=None, soledad=None): - super(LeapSyncTarget, self).__init__(url, creds) - self._soledad = soledad - - def _parse_sync_stream(self, data, return_doc_cb, ensure_callback=None): - """ - Does the same as parent's method but ensures incoming content will be - decrypted. - """ - parts = data.splitlines() # one at a time - if not parts or parts[0] != '[': - raise BrokenSyncStream - data = parts[1:-1] - comma = False - if data: - line, comma = utils.check_and_strip_comma(data[0]) - res = json.loads(line) - if ensure_callback and 'replica_uid' in res: - ensure_callback(res['replica_uid']) - for entry in data[1:]: - if not comma: # missing in between comma - raise BrokenSyncStream - line, comma = utils.check_and_strip_comma(entry) - entry = json.loads(line) - # decrypt after receiving from server. - if not self._soledad: - raise NoSoledadInstance() - enc_json = json.loads(entry['content'])['_encrypted_json'] - if not self._soledad.is_encrypted_sym(enc_json): - raise DocumentNotEncrypted( - "Incoming document from sync is not encrypted.") - doc = LeapDocument(entry['id'], entry['rev'], - encrypted_json=entry['content'], - soledad=self._soledad) - return_doc_cb(doc, entry['gen'], entry['trans_id']) - if parts[-1] != ']': - try: - partdic = json.loads(parts[-1]) - except ValueError: - pass - else: - if isinstance(partdic, dict): - self._error(partdic) - raise BrokenSyncStream - if not data or comma: # no entries or bad extra comma - raise BrokenSyncStream - return res - - def sync_exchange(self, docs_by_generations, source_replica_uid, - last_known_generation, last_known_trans_id, - return_doc_cb, ensure_callback=None): - """ - Does the same as parent's method but encrypts content before syncing. - """ - self._ensure_connection() - if self._trace_hook: # for tests - self._trace_hook('sync_exchange') - url = '%s/sync-from/%s' % (self._url.path, source_replica_uid) - self._conn.putrequest('POST', url) - self._conn.putheader('content-type', 'application/x-u1db-sync-stream') - for header_name, header_value in self._sign_request('POST', url, {}): - self._conn.putheader(header_name, header_value) - entries = ['['] - size = 1 - - def prepare(**dic): - entry = comma + '\r\n' + json.dumps(dic) - entries.append(entry) - return len(entry) - - comma = '' - size += prepare( - last_known_generation=last_known_generation, - last_known_trans_id=last_known_trans_id, - ensure=ensure_callback is not None) - comma = ',' - for doc, gen, trans_id in docs_by_generations: - if doc.syncable: - # encrypt and verify before sending to server. - enc_json = json.loads( - doc.get_encrypted_json())['_encrypted_json'] - if not self._soledad.is_encrypted_sym(enc_json): - raise DocumentNotEncrypted( - "Could not encrypt document before sync.") - size += prepare(id=doc.doc_id, rev=doc.rev, - content=doc.get_encrypted_json(), - gen=gen, trans_id=trans_id) - entries.append('\r\n]') - size += len(entries[-1]) - self._conn.putheader('content-length', str(size)) - self._conn.endheaders() - for entry in entries: - self._conn.send(entry) - entries = None - data, _ = self._response() - res = self._parse_sync_stream(data, return_doc_cb, ensure_callback) - data = None - return res['new_generation'], res['new_transaction_id'] |