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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
|
# -*- coding: utf-8 -*-
# _incoming.py
# Copyright (C) 2017 LEAP
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
A twisted resource that saves externally delivered documents into user's db.
"""
import base64
from io import BytesIO
from twisted.web.server import NOT_DONE_YET
from twisted.web.resource import Resource
from leap.soledad.common.blobs import Flags
from leap.soledad.common.blobs import preamble
from leap.soledad.common.couch.state import CouchServerState
from leap.soledad.common.crypto import ENC_JSON_KEY
from leap.soledad.common.crypto import ENC_SCHEME_KEY
from leap.soledad.common.crypto import EncryptionSchemes
from leap.soledad.common.document import ServerDocument
from leap.soledad.common.log import getLogger
from leap.soledad.server._config import get_config
from leap.soledad.server._blobs import BlobsServerState
from leap.soledad.server._blobs import BlobExists
from leap.soledad.server._blobs import QuotaExceeded
__all__ = ['IncomingResource']
logger = getLogger(__name__)
def _get_backend_from_config():
conf = get_config()
if conf['blobs']:
return BlobsServerState("filesystem", blobs_path=conf['blobs_path'])
return CouchServerState(conf['couch_url'])
def uses_legacy(db):
return hasattr(db, 'put_doc')
class IncomingResource(Resource):
isLeaf = True
def __init__(self, backend_factory=None):
self.factory = backend_factory or _get_backend_from_config()
self.formatter = IncomingFormatter()
def render_PUT(self, request):
uuid, doc_id = request.postpath
scheme = EncryptionSchemes.PUBKEY
db = self.factory.open_database(uuid)
if uses_legacy(db):
self._put_legacy(doc_id, scheme, db, request)
else:
self._put_incoming(uuid, doc_id, scheme, db, request)
return NOT_DONE_YET
def _put_legacy(self, doc_id, scheme, db, request):
try:
doc = ServerDocument(doc_id)
content = request.content.read()
doc.content = self.formatter.format(content, scheme)
db.put_doc(doc)
self._finish(request)
except Exception as e:
self._error(e, request)
def _put_incoming(self, user, blob_id, scheme, db, request):
raw_content = request.content.read()
preamble = self.formatter.preamble(raw_content, blob_id)
request.content = BytesIO(preamble + ' ' + raw_content)
def catchBlobExists(failure):
failure.trap(BlobExists)
request.setResponseCode(409)
request.write("Blob already exists: %s" % blob_id)
request.finish()
def catchQuotaExceeded(failure):
failure.trap(QuotaExceeded)
logger.error("Error 507: Quota exceeded for user: %s" % user)
request.setResponseCode(507)
request.write('Quota Exceeded!')
request.finish()
fd = request.content
d = db.write_blob(user, blob_id, fd, namespace='MX')
flags = [Flags.PENDING]
d.addCallback(lambda _: db.set_flags(user, blob_id, flags,
namespace='MX'))
d.addCallback(lambda _: request.finish())
d.addErrback(catchBlobExists)
d.addErrback(catchQuotaExceeded)
d.addErrback(self._error, request)
def _finish(self, request):
request.write('{"success": true}')
request.finish()
def _error(self, e, request):
logger.error('Error processing request: %s' % e.getErrorMessage())
request.write('{"success": false}')
request.setResponseCode(500)
request.finish()
class IncomingFormatter(object):
"""
Formats an incoming document. Today as it was by leap_mx and as expected by
leap_mail, but the general usage should evolve towards a generic way for
the user to receive external documents.
"""
INCOMING_KEY = 'incoming'
ERROR_DECRYPTING_KEY = 'errdecr' # TODO: Always false on MX, remove it
def format(self, raw_content, enc_scheme):
return {self.INCOMING_KEY: True,
self.ERROR_DECRYPTING_KEY: False,
ENC_SCHEME_KEY: enc_scheme,
ENC_JSON_KEY: raw_content}
def preamble(self, raw_content, doc_id):
rev = '0'
scheme = preamble.ENC_SCHEME.external
method = preamble.ENC_METHOD.pgp
size = len(raw_content)
raw_preamble = preamble.Preamble(doc_id, rev, scheme, method,
content_size=size).encode()
return base64.urlsafe_b64encode(raw_preamble)
|