summaryrefslogtreecommitdiff
path: root/shared_db.py
blob: 9694db2bebe1e09096d5b5855888810459955fac (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# -*- coding: utf-8 -*-
"""
Created on Tue Mar  5 18:46:38 2013

@author: drebs
"""

try:
    import simplejson as json
except ImportError:
    import json  # noqa

from u1db import errors
from u1db.remote import http_database


#-----------------------------------------------------------------------------
# Soledad shared database
#-----------------------------------------------------------------------------

class NoTokenForAuth(Exception):
    """
    No token was found for token-based authentication.
    """


class Unauthorized(Exception):
    """
    User does not have authorization to perform task.
    """


class SoledadSharedDatabase(http_database.HTTPDatabase):
    """
    This is a shared HTTP database that holds users' encrypted keys.

    An authorization token is attached to every request other than
    get_doc_unauth, which has the purpose of retrieving encrypted content from
    the shared database without the need to associate user information with
    the request.
    """
    # TODO: prevent client from messing with the shared DB.
    # TODO: define and document API.

    @staticmethod
    def open_database(url, create, token=None):
        """
        Open a Soledad shared database.
        """
        db = SoledadSharedDatabase(url, token=token)
        db.open(create)
        return db

    @staticmethod
    def delete_database(url):
        """
        Dummy method that prevents from deleting shared database.
        """
        raise Unauthorized("Can't delete shared database.")

    def __init__(self, url, document_factory=None, creds=None, token=None):
        """
        Initialize database with auth token and encryption powers.
        """
        self._token = token
        super(SoledadSharedDatabase, self).__init__(url, document_factory,
                                                    creds)

    def _request(self, method, url_parts, params=None, body=None,
                 content_type=None, auth=True):
        """
        Perform token-based http request.
        """
        # add the auth-token as a request parameter
        if auth:
            if not self._token:
                raise NoTokenForAuth()
            if not params:
                params = {}
            params['auth_token'] = self._token
        return super(SoledadSharedDatabase, self)._request(
            method, url_parts,
            params,
            body,
            content_type)

    def _request_json(self, method, url_parts, params=None, body=None,
                      content_type=None, auth=True):
        """
        Perform token-based http request.
        """
        # allow for token-authenticated requests.
        res, headers = self._request(method, url_parts,
                                     params=params, body=body,
                                     content_type=content_type, auth=auth)
        return json.loads(res), headers

    def get_doc_unauth(self, doc_id):
        """
        Modified method to allow for unauth request.
        """
        try:
            res, headers = self._request(
                'GET', ['doc', doc_id], {"include_deleted": False},
                auth=False)
        except errors.DocumentDoesNotExist:
            return None
        except errors.HTTPError, e:
            if (e.status == http_database.DOCUMENT_DELETED_STATUS and
                    'x-u1db-rev' in e.headers):
                res = None
                headers = e.headers
            else:
                raise
        doc_rev = headers['x-u1db-rev']
        has_conflicts = json.loads(headers['x-u1db-has-conflicts'])
        doc = self._factory(doc_id, doc_rev, res)
        doc.has_conflicts = has_conflicts
        return doc