summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--backends/leap_backend.py25
-rw-r--r--backends/sqlcipher.py26
-rw-r--r--tests/test_sqlcipher.py42
3 files changed, 76 insertions, 17 deletions
diff --git a/backends/leap_backend.py b/backends/leap_backend.py
index 7e98dd45..ec26dca4 100644
--- a/backends/leap_backend.py
+++ b/backends/leap_backend.py
@@ -8,7 +8,6 @@ from u1db.remote import utils
from u1db.remote.http_target import HTTPSyncTarget
from u1db.remote.http_database import HTTPDatabase
from u1db.errors import BrokenSyncStream
-from leap.soledad.util import GPGWrapper
import uuid
@@ -29,9 +28,10 @@ class LeapDocument(Document):
"""
def __init__(self, doc_id=None, rev=None, json='{}', has_conflicts=False,
- encrypted_json=None, soledad=None):
+ 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)
@@ -55,6 +55,18 @@ class LeapDocument(Document):
plaintext = self._soledad.decrypt_symmetric(self.doc_id, ciphertext)
return self.set_json(plaintext)
+ 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."
+ )
+
class LeapDatabase(HTTPDatabase):
"""Implement the HTTP remote database API to a Leap server."""
@@ -168,10 +180,11 @@ class LeapSyncTarget(HTTPSyncTarget):
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)
+ if doc.syncable:
+ # 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))
diff --git a/backends/sqlcipher.py b/backends/sqlcipher.py
index 08b4df43..6cebcf7d 100644
--- a/backends/sqlcipher.py
+++ b/backends/sqlcipher.py
@@ -25,10 +25,11 @@ from u1db.backends.sqlite_backend import (
SQLitePartialExpandDatabase,
)
from u1db import (
- Document,
errors,
)
+from leap.soledad.backends.leap_backend import LeapDocument
+
def open(path, password, create=True, document_factory=None):
"""Open a database at the given location.
@@ -70,7 +71,7 @@ class SQLCipherDatabase(SQLitePartialExpandDatabase):
SQLCipherDatabase.set_pragma_key(self._db_handle, password)
self._real_replica_uid = None
self._ensure_schema()
- self._factory = document_factory or Document
+ self._factory = document_factory or LeapDocument
def _check_if_db_is_encrypted(self, sqlite_file):
if not os.path.exists(sqlite_file):
@@ -133,5 +134,26 @@ class SQLCipherDatabase(SQLitePartialExpandDatabase):
return Synchronizer(self, LeapSyncTarget(url, creds=creds),
soledad=self._soledad).sync(autocreate=autocreate)
+ def _extra_schema_init(self, c):
+ c.execute(
+ 'ALTER TABLE document '
+ 'ADD COLUMN syncable BOOL NOT NULL DEFAULT TRUE')
+
+ def _put_and_update_indexes(self, old_doc, doc):
+ super(SQLCipherDatabase, self)._put_and_update_indexes(old_doc, doc)
+ c = self._db_handle.cursor()
+ c.execute('UPDATE document SET syncable=? WHERE doc_id=?',
+ (doc.syncable, doc.doc_id))
+
+ def _get_doc(self, doc_id, check_for_conflicts=False):
+ doc = super(SQLCipherDatabase, self)._get_doc(doc_id,
+ check_for_conflicts)
+ if doc:
+ c = self._db_handle.cursor()
+ c.execute('SELECT syncable FROM document WHERE doc_id=?',
+ (doc.doc_id,))
+ doc.syncable = bool(c.fetchone()[0])
+ return doc
+
SQLiteDatabase.register_implementation(SQLCipherDatabase)
diff --git a/tests/test_sqlcipher.py b/tests/test_sqlcipher.py
index d2fe0b11..a3ab35b6 100644
--- a/tests/test_sqlcipher.py
+++ b/tests/test_sqlcipher.py
@@ -20,6 +20,7 @@ from leap.soledad.backends.sqlcipher import (
DatabaseIsNotEncrypted,
)
from leap.soledad.backends.sqlcipher import open as u1db_open
+from leap.soledad.backends.leap_backend import LeapDocument
# u1db tests stuff.
from leap.soledad.tests import u1db_tests as tests
@@ -75,10 +76,14 @@ def copy_sqlcipher_database_for_test(test, db):
return new_db
+def make_document_for_test(test, doc_id, rev, content, has_conflicts=False):
+ return LeapDocument(doc_id, rev, content, has_conflicts=has_conflicts)
+
+
SQLCIPHER_SCENARIOS = [
('sqlcipher', {'make_database_for_test': make_sqlcipher_database_for_test,
'copy_database_for_test': copy_sqlcipher_database_for_test,
- 'make_document_for_test': tests.make_document_for_test, }),
+ 'make_document_for_test': make_document_for_test, }),
]
@@ -161,6 +166,10 @@ class TestSQLCipherDatabase(test_sqlite_backend.TestSQLiteDatabase):
self.assertTrue(db2._is_initialized(db1._get_sqlite_handle().cursor()))
+class TestAlternativeDocument(LeapDocument):
+ """A (not very) alternative implementation of Document."""
+
+
class TestSQLCipherPartialExpandDatabase(
test_sqlite_backend.TestSQLitePartialExpandDatabase):
@@ -223,8 +232,8 @@ class TestSQLCipherPartialExpandDatabase(
SQLCipherDatabase(path, PASSWORD)
db2 = SQLCipherDatabase._open_database(
path, PASSWORD,
- document_factory=test_backends.TestAlternativeDocument)
- self.assertEqual(test_backends.TestAlternativeDocument, db2._factory)
+ document_factory=TestAlternativeDocument)
+ self.assertEqual(TestAlternativeDocument, db2._factory)
def test_open_database_existing(self):
temp_dir = self.createTempDir(prefix='u1db-test-')
@@ -239,8 +248,8 @@ class TestSQLCipherPartialExpandDatabase(
SQLCipherDatabase(path, PASSWORD)
db2 = SQLCipherDatabase.open_database(
path, PASSWORD, create=False,
- document_factory=test_backends.TestAlternativeDocument)
- self.assertEqual(test_backends.TestAlternativeDocument, db2._factory)
+ document_factory=TestAlternativeDocument)
+ self.assertEqual(TestAlternativeDocument, db2._factory)
def test_create_database_initializes_schema(self):
# This test had to be cloned because our implementation of SQLCipher
@@ -255,6 +264,19 @@ class TestSQLCipherPartialExpandDatabase(
'index_storage': 'expand referenced encrypted'},
config)
+ def test_store_syncable(self):
+ doc = self.db.create_doc_from_json(tests.simple_doc)
+ # assert that docs are syncable by default
+ self.assertEqual(True, doc.syncable)
+ # assert that we can store syncable = False
+ doc.syncable = False
+ self.db.put_doc(doc)
+ self.assertEqual(False, self.db.get_doc(doc.doc_id).syncable)
+ # assert that we can store syncable = True
+ doc.syncable = True
+ self.db.put_doc(doc)
+ self.assertEqual(True, self.db.get_doc(doc.doc_id).syncable)
+
#-----------------------------------------------------------------------------
# The following tests come from `u1db.tests.test_open`.
@@ -277,9 +299,9 @@ class SQLCipherOpen(test_open.TestU1DBOpen):
def test_open_with_factory(self):
db = u1db_open(self.db_path, password=PASSWORD, create=True,
- document_factory=test_backends.TestAlternativeDocument)
+ document_factory=TestAlternativeDocument)
self.addCleanup(db.close)
- self.assertEqual(test_backends.TestAlternativeDocument, db._factory)
+ self.assertEqual(TestAlternativeDocument, db._factory)
def test_open_existing(self):
db = SQLCipherDatabase(self.db_path, PASSWORD)
@@ -325,7 +347,8 @@ class SQLCipherEncryptionTest(unittest.TestCase):
try:
# trying to open an encrypted database with the regular u1db
# backend should raise a DatabaseError exception.
- SQLitePartialExpandDatabase(self.DB_FILE)
+ SQLitePartialExpandDatabase(self.DB_FILE,
+ document_factory=LeapDocument)
raise DatabaseIsNotEncrypted()
except DatabaseError:
# at this point we know that the regular U1DB sqlcipher backend
@@ -337,7 +360,8 @@ class SQLCipherEncryptionTest(unittest.TestCase):
'decrypted content mismatch')
def test_try_to_open_raw_db_with_sqlcipher_backend(self):
- db = SQLitePartialExpandDatabase(self.DB_FILE)
+ db = SQLitePartialExpandDatabase(self.DB_FILE,
+ document_factory=LeapDocument)
db.create_doc_from_json(tests.simple_doc)
db.close()
try: