summaryrefslogtreecommitdiff
path: root/soledad/backends/leap_backend.py
diff options
context:
space:
mode:
authordrebs <drebs@leap.se>2013-03-07 17:23:05 -0300
committerdrebs <drebs@leap.se>2013-03-07 17:23:05 -0300
commitd89c1849d551de68d26a1f56798ee5084dca6556 (patch)
tree25a54c87b22fca5ac494621a0ccbcca9e181cf76 /soledad/backends/leap_backend.py
parent1b1def113e6ed9b8af6897e16f0d9b4c96bbfa6b (diff)
Move source files to proper subdirectory.
Diffstat (limited to 'soledad/backends/leap_backend.py')
-rw-r--r--soledad/backends/leap_backend.py224
1 files changed, 0 insertions, 224 deletions
diff --git a/soledad/backends/leap_backend.py b/soledad/backends/leap_backend.py
deleted file mode 100644
index a37f9d25..00000000
--- a/soledad/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']