summaryrefslogtreecommitdiff
path: root/client/src/leap/soledad/client/__init__.py
diff options
context:
space:
mode:
Diffstat (limited to 'client/src/leap/soledad/client/__init__.py')
-rw-r--r--client/src/leap/soledad/client/__init__.py290
1 files changed, 163 insertions, 127 deletions
diff --git a/client/src/leap/soledad/client/__init__.py b/client/src/leap/soledad/client/__init__.py
index 4c6a41fc..13a3b68f 100644
--- a/client/src/leap/soledad/client/__init__.py
+++ b/client/src/leap/soledad/client/__init__.py
@@ -32,6 +32,8 @@ import socket
import ssl
import urlparse
+import cchardet
+
from hashlib import sha256
from u1db.remote import http_client
@@ -226,25 +228,26 @@ class Soledad(object):
"""
Initialize configuration, cryptographic keys and dbs.
- @param uuid: User's uuid.
- @type uuid: str
- @param passphrase: The passphrase for locking and unlocking encryption
- secrets for local and remote storage.
- @type passphrase: str
- @param secrets_path: Path for storing encrypted key used for
- symmetric encryption.
- @type secrets_path: str
- @param local_db_path: Path for local encrypted storage db.
- @type local_db_path: str
- @param server_url: URL for Soledad server. This is used either to sync
+ :param uuid: User's uuid.
+ :type uuid: str
+ :param passphrase: The passphrase for locking and unlocking encryption
+ secrets for local and remote storage.
+ :type passphrase: str
+ :param secrets_path: Path for storing encrypted key used for
+ symmetric encryption.
+ :type secrets_path: str
+ :param local_db_path: Path for local encrypted storage db.
+ :type local_db_path: str
+ :param server_url: URL for Soledad server. This is used either to sync
with the user's remote db and to interact with the shared recovery
database.
- @type server_url: str
- @param cert_file: Path to the SSL certificate to use in the
- connection to the server_url.
- @type cert_file: str
- @param auth_token: Authorization token for accessing remote databases.
- @type auth_token: str
+ :type server_url: str
+ :param cert_file: Path to the certificate of the ca used
+ to validate the SSL certificate used by the remote
+ soledad server.
+ :type cert_file: str
+ :param auth_token: Authorization token for accessing remote databases.
+ :type auth_token: str
"""
# get config params
self._uuid = uuid
@@ -339,7 +342,7 @@ class Soledad(object):
"""
Create work directories.
- @raise OSError: in case file exists and is not a dir.
+ :raise OSError: in case file exists and is not a dir.
"""
paths = map(
lambda x: os.path.dirname(x),
@@ -418,8 +421,8 @@ class Soledad(object):
Storage secret is encrypted before being stored. This method decrypts
and returns the stored secret.
- @return: The storage secret.
- @rtype: str
+ :return: The storage secret.
+ :rtype: str
"""
# calculate the encryption key
key = scrypt.hash(
@@ -478,8 +481,8 @@ class Soledad(object):
"""
Return whether there is a storage secret available for use or not.
- @return: Whether there's a storage secret for symmetric encryption.
- @rtype: bool
+ :return: Whether there's a storage secret for symmetric encryption.
+ :rtype: bool
"""
if self._secret_id is None or self._secret_id not in self._secrets:
try:
@@ -515,8 +518,8 @@ class Soledad(object):
}
}
- @return: The id of the generated secret.
- @rtype: str
+ :return: The id of the generated secret.
+ :rtype: str
"""
signal(SOLEDAD_CREATING_KEYS, self._uuid)
# generate random secret
@@ -571,10 +574,10 @@ class Soledad(object):
"""
Change the passphrase that encrypts the storage secret.
- @param new_passphrase: The new passphrase.
- @type new_passphrase: str
+ :param new_passphrase: The new passphrase.
+ :type new_passphrase: str
- @raise NoStorageSecret: Raised if there's no storage secret available.
+ :raise NoStorageSecret: Raised if there's no storage secret available.
"""
# maybe we want to add more checks to guarantee passphrase is
# reasonable?
@@ -616,8 +619,8 @@ class Soledad(object):
Calculate a hash for storing/retrieving key material on shared
database, based on user's uuid.
- @return: the hash
- @rtype: str
+ :return: the hash
+ :rtype: str
"""
return sha256(
'%s%s' % (
@@ -639,8 +642,8 @@ class Soledad(object):
Retrieve the document with encrypted key material from the shared
database.
- @return: a document with encrypted key material in its contents
- @rtype: SoledadDocument
+ :return: a document with encrypted key material in its contents
+ :rtype: SoledadDocument
"""
signal(SOLEDAD_DOWNLOADING_KEYS, self._uuid)
db = self._shared_db()
@@ -687,11 +690,11 @@ class Soledad(object):
"""
Update a document in the local encrypted database.
- @param doc: the document to update
- @type doc: SoledadDocument
+ :param doc: the document to update
+ :type doc: SoledadDocument
- @return: the new revision identifier for the document
- @rtype: str
+ :return: the new revision identifier for the document
+ :rtype: str
"""
return self._db.put_doc(doc)
@@ -699,11 +702,11 @@ class Soledad(object):
"""
Delete a document from the local encrypted database.
- @param doc: the document to delete
- @type doc: SoledadDocument
+ :param doc: the document to delete
+ :type doc: SoledadDocument
- @return: the new revision identifier for the document
- @rtype: str
+ :return: the new revision identifier for the document
+ :rtype: str
"""
return self._db.delete_doc(doc)
@@ -711,15 +714,15 @@ class Soledad(object):
"""
Retrieve a document from the local encrypted database.
- @param doc_id: the unique document identifier
- @type doc_id: str
- @param include_deleted: if True, deleted documents will be
- returned with empty content; otherwise asking for a deleted
- document will return None
- @type include_deleted: bool
+ :param doc_id: the unique document identifier
+ :type doc_id: str
+ :param include_deleted: if True, deleted documents will be
+ returned with empty content; otherwise asking
+ for a deleted document will return None
+ :type include_deleted: bool
- @return: the document object or None
- @rtype: SoledadDocument
+ :return: the document object or None
+ :rtype: SoledadDocument
"""
return self._db.get_doc(doc_id, include_deleted=include_deleted)
@@ -728,15 +731,15 @@ class Soledad(object):
"""
Get the content for many documents.
- @param doc_ids: a list of document identifiers
- @type doc_ids: list
- @param check_for_conflicts: if set False, then the conflict check will
+ :param doc_ids: a list of document identifiers
+ :type doc_ids: list
+ :param check_for_conflicts: if set False, then the conflict check will
be skipped, and 'None' will be returned instead of True/False
- @type check_for_conflicts: bool
+ :type check_for_conflicts: bool
- @return: iterable giving the Document object for each document id
+ :return: iterable giving the Document object for each document id
in matching doc_ids order.
- @rtype: generator
+ :rtype: generator
"""
return self._db.get_docs(doc_ids,
check_for_conflicts=check_for_conflicts,
@@ -745,28 +748,59 @@ class Soledad(object):
def get_all_docs(self, include_deleted=False):
"""Get the JSON content for all documents in the database.
- @param include_deleted: If set to True, deleted documents will be
- returned with empty content. Otherwise deleted documents will not
- be included in the results.
- @return: (generation, [Document])
- The current generation of the database, followed by a list of all
- the documents in the database.
+ :param include_deleted: If set to True, deleted documents will be
+ returned with empty content. Otherwise deleted
+ documents will not be included in the results.
+ :return: (generation, [Document])
+ The current generation of the database, followed by a list of
+ all the documents in the database.
"""
return self._db.get_all_docs(include_deleted)
+ def _convert_to_utf8(self, content):
+ """
+ Converts content to utf8 (or all the strings in content)
+
+ NOTE: Even though this method supports any type, it will
+ currently ignore contents of lists, tuple or any other
+ iterable than dict. We don't need support for these at the
+ moment
+
+ :param content: content to convert
+ :type content: object
+
+ :rtype: object
+ """
+
+ if isinstance(content, unicode):
+ return content
+ elif isinstance(content, str):
+ try:
+ result = cchardet.detect(content)
+ content = content.decode(result["encoding"]).encode("utf-8")\
+ .decode("utf-8")
+ except UnicodeError:
+ pass
+ return content
+ else:
+ if isinstance(content, dict):
+ for key in content.keys():
+ content[key] = self._convert_to_utf8(content[key])
+ return content
+
def create_doc(self, content, doc_id=None):
"""
Create a new document in the local encrypted database.
- @param content: the contents of the new document
- @type content: dict
- @param doc_id: an optional identifier specifying the document id
- @type doc_id: str
+ :param content: the contents of the new document
+ :type content: dict
+ :param doc_id: an optional identifier specifying the document id
+ :type doc_id: str
- @return: the new document
- @rtype: SoledadDocument
+ :return: the new document
+ :rtype: SoledadDocument
"""
- return self._db.create_doc(content, doc_id=doc_id)
+ return self._db.create_doc(self._convert_to_utf8(content), doc_id=doc_id)
def create_doc_from_json(self, json, doc_id=None):
"""
@@ -778,12 +812,12 @@ class Soledad(object):
If the database specifies a maximum document size and the document
exceeds it, create will fail and raise a DocumentTooBig exception.
- @param json: The JSON document string
- @type json: str
- @param doc_id: An optional identifier specifying the document id.
- @type doc_id:
- @return: The new cocument
- @rtype: SoledadDocument
+ :param json: The JSON document string
+ :type json: str
+ :param doc_id: An optional identifier specifying the document id.
+ :type doc_id:
+ :return: The new cocument
+ :rtype: SoledadDocument
"""
return self._db.create_doc_from_json(json, doc_id=doc_id)
@@ -796,11 +830,11 @@ class Soledad(object):
Creating an index will block until the expressions have been evaluated
and the index generated.
- @param index_name: A unique name which can be used as a key prefix
- @type index_name: str
- @param index_expressions: index expressions defining the index
- information.
- @type index_expressions: dict
+ :param index_name: A unique name which can be used as a key prefix
+ :type index_name: str
+ :param index_expressions: index expressions defining the index
+ information.
+ :type index_expressions: dict
Examples:
@@ -816,8 +850,8 @@ class Soledad(object):
"""
Remove a named index.
- @param index_name: The name of the index we are removing
- @type index_name: str
+ :param index_name: The name of the index we are removing
+ :type index_name: str
"""
if self._db:
return self._db.delete_index(index_name)
@@ -826,8 +860,8 @@ class Soledad(object):
"""
List the definitions of all known indexes.
- @return: A list of [('index-name', ['field', 'field2'])] definitions.
- @rtype: list
+ :return: A list of [('index-name', ['field', 'field2'])] definitions.
+ :rtype: list
"""
if self._db:
return self._db.list_indexes()
@@ -843,14 +877,14 @@ class Soledad(object):
It is also possible to append a '*' to the last supplied value (eg
'val*', '*', '*' or 'val', 'val*', '*', but not 'val*', 'val', '*')
- @param index_name: The index to query
- @type index_name: str
- @param key_values: values to match. eg, if you have
- an index with 3 fields then you would have:
- get_from_index(index_name, val1, val2, val3)
- @type key_values: tuple
- @return: List of [Document]
- @rtype: list
+ :param index_name: The index to query
+ :type index_name: str
+ :param key_values: values to match. eg, if you have
+ an index with 3 fields then you would have:
+ get_from_index(index_name, val1, val2, val3)
+ :type key_values: tuple
+ :return: List of [Document]
+ :rtype: list
"""
if self._db:
return self._db.get_from_index(index_name, *key_values)
@@ -869,18 +903,18 @@ class Soledad(object):
possible to append a '*' to the last supplied value (eg 'val*', '*',
'*' or 'val', 'val*', '*', but not 'val*', 'val', '*')
- @param index_name: The index to query
- @type index_name: str
- @param start_values: tuples of values that define the lower bound of
+ :param index_name: The index to query
+ :type index_name: str
+ :param start_values: tuples of values that define the lower bound of
the range. eg, if you have an index with 3 fields then you would
have: (val1, val2, val3)
- @type start_values: tuple
- @param end_values: tuples of values that define the upper bound of the
+ :type start_values: tuple
+ :param end_values: tuples of values that define the upper bound of the
range. eg, if you have an index with 3 fields then you would have:
(val1, val2, val3)
- @type end_values: tuple
- @return: List of [Document]
- @rtype: list
+ :type end_values: tuple
+ :return: List of [Document]
+ :rtype: list
"""
if self._db:
return self._db.get_range_from_index(
@@ -890,10 +924,10 @@ class Soledad(object):
"""
Return all keys under which documents are indexed in this index.
- @param index_name: The index to query
- @type index_name: str
- @return: [] A list of tuples of indexed keys.
- @rtype: list
+ :param index_name: The index to query
+ :type index_name: str
+ :return: [] A list of tuples of indexed keys.
+ :rtype: list
"""
if self._db:
return self._db.get_index_keys(index_name)
@@ -902,11 +936,11 @@ class Soledad(object):
"""
Get the list of conflicts for the given document.
- @param doc_id: the document id
- @type doc_id: str
+ :param doc_id: the document id
+ :type doc_id: str
- @return: a list of the document entries that are conflicted
- @rtype: list
+ :return: a list of the document entries that are conflicted
+ :rtype: list
"""
if self._db:
return self._db.get_doc_conflicts(doc_id)
@@ -915,11 +949,11 @@ class Soledad(object):
"""
Mark a document as no longer conflicted.
- @param doc: a document with the new content to be inserted.
- @type doc: SoledadDocument
- @param conflicted_doc_revs: a list of revisions that the new content
- supersedes.
- @type conflicted_doc_revs: list
+ :param doc: a document with the new content to be inserted.
+ :type doc: SoledadDocument
+ :param conflicted_doc_revs: a list of revisions that the new content
+ supersedes.
+ :type conflicted_doc_revs: list
"""
if self._db:
return self._db.resolve_doc(doc, conflicted_doc_revs)
@@ -928,12 +962,12 @@ class Soledad(object):
"""
Synchronize the local encrypted replica with a remote replica.
- @param url: the url of the target replica to sync with
- @type url: str
+ :param url: the url of the target replica to sync with
+ :type url: str
- @return: the local generation before the synchronisation was
+ :return: the local generation before the synchronisation was
performed.
- @rtype: str
+ :rtype: str
"""
if self._db:
local_gen = self._db.sync(
@@ -946,11 +980,11 @@ class Soledad(object):
"""
Return if local db replica differs from remote url's replica.
- @param url: The remote replica to compare with local replica.
- @type url: str
+ :param url: The remote replica to compare with local replica.
+ :type url: str
- @return: Whether remote replica and local replica differ.
- @rtype: bool
+ :return: Whether remote replica and local replica differ.
+ :rtype: bool
"""
target = SoledadSyncTarget(url, creds=self._creds, crypto=self._crypto)
info = target.get_sync_info(self._db._get_replica_uid())
@@ -972,8 +1006,8 @@ class Soledad(object):
'token': '<token>'
}
- @param token: The authentication token.
- @type token: str
+ :param token: The authentication token.
+ :type token: str
"""
self._creds = {
'token': {
@@ -1004,11 +1038,11 @@ class Soledad(object):
self.UUID_KEY: '<uuid>', # (optional)
}
- @param include_uuid: Should the uuid be included?
- @type include_uuid: bool
+ :param include_uuid: Should the uuid be included?
+ :type include_uuid: bool
- @return: The recovery document.
- @rtype: dict
+ :return: The recovery document.
+ :rtype: dict
"""
data = {self.STORAGE_SECRETS_KEY: self._secrets}
if include_uuid:
@@ -1027,8 +1061,8 @@ class Soledad(object):
self.UUID_KEY: '<uuid>', # (optional)
}
- @param data: The recovery document.
- @type data: dict
+ :param data: The recovery document.
+ :type data: dict
"""
# include new secrets in our secret pool.
for secret_id, secret_data in data[self.STORAGE_SECRETS_KEY].items():
@@ -1101,7 +1135,9 @@ SOLEDAD_TIMEOUT = 10
class VerifiedHTTPSConnection(httplib.HTTPSConnection):
- """HTTPSConnection verifying server side certificates."""
+ """
+ HTTPSConnection verifying server side certificates.
+ """
# derived from httplib.py
def connect(self):