From 3bc6ab2d5ac65c1919e8e6ff627894924e4f98a9 Mon Sep 17 00:00:00 2001 From: drebs Date: Thu, 27 Dec 2012 11:09:39 -0200 Subject: Fix imports and namespaces --- __init__.py | 27 +++++--- backends/__init__.py | 5 ++ backends/couch.py | 4 +- backends/leap.py | 175 ----------------------------------------------- backends/leap_backend.py | 174 ++++++++++++++++++++++++++++++++++++++++++++++ backends/objectstore.py | 6 +- tests/test_couch.py | 4 +- tests/test_encrypted.py | 4 +- tests/test_logs.py | 3 +- tests/test_sqlcipher.py | 6 +- 10 files changed, 208 insertions(+), 200 deletions(-) delete mode 100644 backends/leap.py create mode 100644 backends/leap_backend.py diff --git a/__init__.py b/__init__.py index 3ed82ca7..6c313f77 100644 --- a/__init__.py +++ b/__init__.py @@ -5,10 +5,10 @@ import os import string import random -import cStringIO import hmac -from backends import sqlcipher -from soledad.util import GPGWrapper +from leap.soledad.backends import sqlcipher +from leap.soledad.util import GPGWrapper +import util class Soledad(object): @@ -27,12 +27,12 @@ class Soledad(object): os.makedirs(self.PREFIX) if not gpghome: gpghome = self.GNUPG_HOME - self._gpg = GPGWrapper(gpghome=gpghome) - # load OpenPGP keypair + self._gpg = util.GPGWrapper(gpghome=gpghome) + # loaa/generate OpenPGP keypair if not self._has_openpgp_keypair(): self._gen_openpgp_keypair() self._load_openpgp_keypair() - # load secret + # load/generate secret if not self._has_secret(): self._gen_secret() self._load_secret() @@ -42,7 +42,7 @@ class Soledad(object): self._db = sqlcipher.open(self.U1DB_PATH, True, self._secret) #------------------------------------------------------------------------- - # Symmetric secret management + # Management of secret for symmetric encryption #------------------------------------------------------------------------- def _has_secret(self): @@ -76,7 +76,7 @@ class Soledad(object): f.close() #------------------------------------------------------------------------- - # OpenPGP keypair management + # Management of OpenPGP keypair #------------------------------------------------------------------------- def _has_openpgp_keypair(self): @@ -92,7 +92,7 @@ class Soledad(object): def _gen_openpgp_keypair(self): """ - Generate and OpenPGP keypair for this user. + Generate an OpenPGP keypair for this user. """ params = self._gpg.gen_key_input( key_type='RSA', @@ -109,10 +109,13 @@ class Soledad(object): self._fingerprint = self._gpg.find_key(self._user_email)['fingerprint'] def publish_pubkey(self, keyserver): + """ + Publish OpenPGP public key to a keyserver. + """ pass #------------------------------------------------------------------------- - # Data encryption/decryption + # Data encryption and decryption #------------------------------------------------------------------------- def encrypt(self, data, sign=None, passphrase=None, symmetric=False): @@ -143,7 +146,7 @@ class Soledad(object): return self.decrypt(data, passphrase=h) #------------------------------------------------------------------------- - # Document storage/retrieval and sync + # Document storage, retrieval and sync #------------------------------------------------------------------------- def put_doc(self, doc): @@ -197,3 +200,5 @@ class Soledad(object): """ # TODO: create authentication scheme for sync with server. return self._db.sync(url, creds=None, autocreate=True) + +__all__ = ['util'] diff --git a/backends/__init__.py b/backends/__init__.py index e69de29b..72907f37 100644 --- a/backends/__init__.py +++ b/backends/__init__.py @@ -0,0 +1,5 @@ +import objectstore + + +__all__ = [ + 'objectstore'] diff --git a/backends/couch.py b/backends/couch.py index ed356fdd..101dd4ea 100644 --- a/backends/couch.py +++ b/backends/couch.py @@ -2,8 +2,8 @@ from u1db import errors from u1db.remote.http_target import HTTPSyncTarget from couchdb.client import Server, Document from couchdb.http import ResourceNotFound -from soledad.backends.objectstore import ObjectStore -from soledad.backends.leap import LeapDocument +from leap.soledad.backends.objectstore import ObjectStore +from leap.soledad.backends.leap_backend import LeapDocument try: import simplejson as json diff --git a/backends/leap.py b/backends/leap.py deleted file mode 100644 index 9fbd49fe..00000000 --- a/backends/leap.py +++ /dev/null @@ -1,175 +0,0 @@ -try: - import simplejson as json -except ImportError: - import json # noqa - -from u1db import Document -from u1db.remote.http_target import HTTPSyncTarget -from u1db.remote.http_database import HTTPDatabase -import base64 -from soledad.util import GPGWrapper - - -class NoDefaultKey(Exception): - pass - -class NoSoledadInstance(Exception): - pass - - -class LeapDocument(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): - super(LeapDocument, self).__init__(doc_id, rev, json, has_conflicts) - self._soledad = soledad - if encrypted_json: - self.set_encrypted_json(encrypted_json) - - def get_encrypted_json(self): - """ - Returns document's json serialization encrypted with user's public key. - """ - if not self._soledad: - raise NoSoledadInstance() - ciphertext = self._soledad.encrypt_symmetric(self.doc_id, self.get_json()) - return json.dumps({'_encrypted_json' : ciphertext}) - - def set_encrypted_json(self, encrypted_json): - """ - Set document's content based on encrypted version of json string. - """ - if not self._soledad: - raise NoSoledadInstance() - ciphertext = json.loads(encrypted_json)['_encrypted_json'] - plaintext = self._soledad.decrypt_symmetric(self.doc_id, ciphertext) - return self.set_json(plaintext) - - -class LeapDatabase(HTTPDatabase): - """Implement the HTTP remote database API to a Leap server.""" - - def __init__(self, url, document_factory=None, creds=None, soledad=None): - super(LeapDatabase, self).__init__(url, creds=creds) - self._soledad = soledad - self._factory = LeapDocument - - @staticmethod - def open_database(url, create): - db = LeapDatabase(url) - db.open(create) - return db - - @staticmethod - def delete_database(url): - db = LeapDatabase(url) - db._delete() - db.close() - - def get_sync_target(self): - st = LeapSyncTarget(self._url.geturl()) - st._creds = self._creds - return st - - def create_doc_from_json(self, content, doc_id=None): - if doc_id is None: - doc_id = self._allocate_doc_id() - res, headers = self._request_json('PUT', ['doc', doc_id], {}, - content, 'application/json') - new_doc = self._factory(doc_id, res['rev'], content, soledad=self._soledad) - return new_doc - - -class LeapSyncTarget(HTTPSyncTarget): - - 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. - 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: - # encrypt before sending to server. - 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'] diff --git a/backends/leap_backend.py b/backends/leap_backend.py new file mode 100644 index 00000000..ced8734c --- /dev/null +++ b/backends/leap_backend.py @@ -0,0 +1,174 @@ +try: + import simplejson as json +except ImportError: + import json # noqa + +from u1db import Document +from u1db.remote.http_target import HTTPSyncTarget +from u1db.remote.http_database import HTTPDatabase +from leap.soledad.util import GPGWrapper + + +class NoDefaultKey(Exception): + pass + +class NoSoledadInstance(Exception): + pass + + +class LeapDocument(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): + super(LeapDocument, self).__init__(doc_id, rev, json, has_conflicts) + self._soledad = soledad + if encrypted_json: + self.set_encrypted_json(encrypted_json) + + def get_encrypted_json(self): + """ + Returns document's json serialization encrypted with user's public key. + """ + if not self._soledad: + raise NoSoledadInstance() + ciphertext = self._soledad.encrypt_symmetric(self.doc_id, self.get_json()) + return json.dumps({'_encrypted_json' : ciphertext}) + + def set_encrypted_json(self, encrypted_json): + """ + Set document's content based on encrypted version of json string. + """ + if not self._soledad: + raise NoSoledadInstance() + ciphertext = json.loads(encrypted_json)['_encrypted_json'] + plaintext = self._soledad.decrypt_symmetric(self.doc_id, ciphertext) + return self.set_json(plaintext) + + +class LeapDatabase(HTTPDatabase): + """Implement the HTTP remote database API to a Leap server.""" + + def __init__(self, url, document_factory=None, creds=None, soledad=None): + super(LeapDatabase, self).__init__(url, creds=creds) + self._soledad = soledad + self._factory = LeapDocument + + @staticmethod + def open_database(url, create): + db = LeapDatabase(url) + db.open(create) + return db + + @staticmethod + def delete_database(url): + db = LeapDatabase(url) + db._delete() + db.close() + + def get_sync_target(self): + st = LeapSyncTarget(self._url.geturl()) + st._creds = self._creds + return st + + def create_doc_from_json(self, content, doc_id=None): + if doc_id is None: + doc_id = self._allocate_doc_id() + res, headers = self._request_json('PUT', ['doc', doc_id], {}, + content, 'application/json') + new_doc = self._factory(doc_id, res['rev'], content, soledad=self._soledad) + return new_doc + + +class LeapSyncTarget(HTTPSyncTarget): + + 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. + 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: + # encrypt before sending to server. + 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'] diff --git a/backends/objectstore.py b/backends/objectstore.py index 61445a1f..9fce88f6 100644 --- a/backends/objectstore.py +++ b/backends/objectstore.py @@ -1,7 +1,7 @@ import uuid from u1db.backends import CommonBackend from u1db import errors, Document -from soledad.util import SyncLog, TransactionLog +from leap.soledad import util as soledadutil class ObjectStore(CommonBackend): @@ -14,8 +14,8 @@ class ObjectStore(CommonBackend): # with the database is established, so it can ensure that u1db data is # configured and up-to-date. self.set_document_factory(Document) - self._sync_log = SyncLog() - self._transaction_log = TransactionLog() + self._sync_log = soledadutil.SyncLog() + self._transaction_log = soledadutil.TransactionLog() self._ensure_u1db_data() #------------------------------------------------------------------------- diff --git a/tests/test_couch.py b/tests/test_couch.py index 4468ae04..b5bf4e9b 100644 --- a/tests/test_couch.py +++ b/tests/test_couch.py @@ -1,6 +1,6 @@ import unittest2 -from soledad.backends.couch import CouchDatabase -from soledad.backends.leap import LeapDocument +from leap.soledad.backends.couch import CouchDatabase +from leap.soledad.backends.leap_backend import LeapDocument from u1db import errors, vectorclock try: diff --git a/tests/test_encrypted.py b/tests/test_encrypted.py index eafd258e..4ee03a3c 100644 --- a/tests/test_encrypted.py +++ b/tests/test_encrypted.py @@ -7,8 +7,8 @@ import unittest2 as unittest import os import u1db -from soledad import Soledad -from soledad.backends.leap import LeapDocument +from leap.soledad import Soledad +from leap.soledad.backends.leap_backend import LeapDocument class EncryptedSyncTestCase(unittest.TestCase): diff --git a/tests/test_logs.py b/tests/test_logs.py index d61700f2..072ac1a5 100644 --- a/tests/test_logs.py +++ b/tests/test_logs.py @@ -1,10 +1,9 @@ import unittest2 as unittest -from soledad.util import TransactionLog, SyncLog +from leap.soledad.util import TransactionLog, SyncLog class LogTestCase(unittest.TestCase): - def test_transaction_log(self): data = [ (2, "doc_3", "tran_3"), diff --git a/tests/test_sqlcipher.py b/tests/test_sqlcipher.py index f9e9f681..3bb495ec 100644 --- a/tests/test_sqlcipher.py +++ b/tests/test_sqlcipher.py @@ -27,9 +27,9 @@ from u1db import ( errors, query_parser, ) -from soledad.backends import sqlcipher -from soledad.backends.leap import LeapDocument -from soledad import tests +from leap.soledad.backends import sqlcipher +from leap.soledad.backends.leap_backend import LeapDocument +from leap.soledad import tests simple_doc = '{"key": "value"}' -- cgit v1.2.3