##// END OF EJS Templates
Moved exception texts to constants for sync
neko259 -
r1539:5b492628 decentral
parent child Browse files
Show More
@@ -1,266 +1,270 b''
1 import xml.etree.ElementTree as et
1 import xml.etree.ElementTree as et
2
2
3 from boards.models.attachment.downloaders import download
3 from boards.models.attachment.downloaders import download
4 from boards.utils import get_file_mimetype, get_file_hash
4 from boards.utils import get_file_mimetype, get_file_hash
5 from django.db import transaction
5 from django.db import transaction
6 from boards.models import KeyPair, GlobalId, Signature, Post, Tag
6 from boards.models import KeyPair, GlobalId, Signature, Post, Tag
7
7
8 EXCEPTION_NODE = 'Sync node returned an error: {}'
9 EXCEPTION_OP = 'Load the OP first'
10 EXCEPTION_DOWNLOAD = 'File was not downloaded'
11 EXCEPTION_HASH = 'File hash does not match attachment hash'
12 EXCEPTION_SIGNATURE = 'Invalid model signature for {}'
8 ENCODING_UNICODE = 'unicode'
13 ENCODING_UNICODE = 'unicode'
9
14
10 TAG_MODEL = 'model'
15 TAG_MODEL = 'model'
11 TAG_REQUEST = 'request'
16 TAG_REQUEST = 'request'
12 TAG_RESPONSE = 'response'
17 TAG_RESPONSE = 'response'
13 TAG_ID = 'id'
18 TAG_ID = 'id'
14 TAG_STATUS = 'status'
19 TAG_STATUS = 'status'
15 TAG_MODELS = 'models'
20 TAG_MODELS = 'models'
16 TAG_TITLE = 'title'
21 TAG_TITLE = 'title'
17 TAG_TEXT = 'text'
22 TAG_TEXT = 'text'
18 TAG_THREAD = 'thread'
23 TAG_THREAD = 'thread'
19 TAG_PUB_TIME = 'pub-time'
24 TAG_PUB_TIME = 'pub-time'
20 TAG_SIGNATURES = 'signatures'
25 TAG_SIGNATURES = 'signatures'
21 TAG_SIGNATURE = 'signature'
26 TAG_SIGNATURE = 'signature'
22 TAG_CONTENT = 'content'
27 TAG_CONTENT = 'content'
23 TAG_ATTACHMENTS = 'attachments'
28 TAG_ATTACHMENTS = 'attachments'
24 TAG_ATTACHMENT = 'attachment'
29 TAG_ATTACHMENT = 'attachment'
25 TAG_TAGS = 'tags'
30 TAG_TAGS = 'tags'
26 TAG_TAG = 'tag'
31 TAG_TAG = 'tag'
27 TAG_ATTACHMENT_REFS = 'attachment-refs'
32 TAG_ATTACHMENT_REFS = 'attachment-refs'
28 TAG_ATTACHMENT_REF = 'attachment-ref'
33 TAG_ATTACHMENT_REF = 'attachment-ref'
29
34
30 TYPE_GET = 'get'
35 TYPE_GET = 'get'
31
36
32 ATTR_VERSION = 'version'
37 ATTR_VERSION = 'version'
33 ATTR_TYPE = 'type'
38 ATTR_TYPE = 'type'
34 ATTR_NAME = 'name'
39 ATTR_NAME = 'name'
35 ATTR_VALUE = 'value'
40 ATTR_VALUE = 'value'
36 ATTR_MIMETYPE = 'mimetype'
41 ATTR_MIMETYPE = 'mimetype'
37 ATTR_KEY = 'key'
42 ATTR_KEY = 'key'
38 ATTR_REF = 'ref'
43 ATTR_REF = 'ref'
39 ATTR_URL = 'url'
44 ATTR_URL = 'url'
40
45
41 STATUS_SUCCESS = 'success'
46 STATUS_SUCCESS = 'success'
42
47
43
48
44 class SyncException(Exception):
49 class SyncException(Exception):
45 pass
50 pass
46
51
47
52
48 class SyncManager:
53 class SyncManager:
49 @staticmethod
54 @staticmethod
50 def generate_response_get(model_list: list):
55 def generate_response_get(model_list: list):
51 response = et.Element(TAG_RESPONSE)
56 response = et.Element(TAG_RESPONSE)
52
57
53 status = et.SubElement(response, TAG_STATUS)
58 status = et.SubElement(response, TAG_STATUS)
54 status.text = STATUS_SUCCESS
59 status.text = STATUS_SUCCESS
55
60
56 models = et.SubElement(response, TAG_MODELS)
61 models = et.SubElement(response, TAG_MODELS)
57
62
58 for post in model_list:
63 for post in model_list:
59 model = et.SubElement(models, TAG_MODEL)
64 model = et.SubElement(models, TAG_MODEL)
60 model.set(ATTR_NAME, 'post')
65 model.set(ATTR_NAME, 'post')
61
66
62 global_id = post.global_id
67 global_id = post.global_id
63
68
64 images = post.images.all()
69 images = post.images.all()
65 attachments = post.attachments.all()
70 attachments = post.attachments.all()
66 if global_id.content:
71 if global_id.content:
67 model.append(et.fromstring(global_id.content))
72 model.append(et.fromstring(global_id.content))
68 if len(images) > 0 or len(attachments) > 0:
73 if len(images) > 0 or len(attachments) > 0:
69 attachment_refs = et.SubElement(model, TAG_ATTACHMENT_REFS)
74 attachment_refs = et.SubElement(model, TAG_ATTACHMENT_REFS)
70 for image in images:
75 for image in images:
71 SyncManager._attachment_to_xml(
76 SyncManager._attachment_to_xml(
72 None, attachment_refs, image.image.file,
77 None, attachment_refs, image.image.file,
73 image.hash, image.image.url)
78 image.hash, image.image.url)
74 for file in attachments:
79 for file in attachments:
75 SyncManager._attachment_to_xml(
80 SyncManager._attachment_to_xml(
76 None, attachment_refs, file.file.file,
81 None, attachment_refs, file.file.file,
77 file.hash, file.file.url)
82 file.hash, file.file.url)
78 else:
83 else:
79 content_tag = et.SubElement(model, TAG_CONTENT)
84 content_tag = et.SubElement(model, TAG_CONTENT)
80
85
81 tag_id = et.SubElement(content_tag, TAG_ID)
86 tag_id = et.SubElement(content_tag, TAG_ID)
82 global_id.to_xml_element(tag_id)
87 global_id.to_xml_element(tag_id)
83
88
84 title = et.SubElement(content_tag, TAG_TITLE)
89 title = et.SubElement(content_tag, TAG_TITLE)
85 title.text = post.title
90 title.text = post.title
86
91
87 text = et.SubElement(content_tag, TAG_TEXT)
92 text = et.SubElement(content_tag, TAG_TEXT)
88 text.text = post.get_sync_text()
93 text.text = post.get_sync_text()
89
94
90 thread = post.get_thread()
95 thread = post.get_thread()
91 if post.is_opening():
96 if post.is_opening():
92 tag_tags = et.SubElement(content_tag, TAG_TAGS)
97 tag_tags = et.SubElement(content_tag, TAG_TAGS)
93 for tag in thread.get_tags():
98 for tag in thread.get_tags():
94 tag_tag = et.SubElement(tag_tags, TAG_TAG)
99 tag_tag = et.SubElement(tag_tags, TAG_TAG)
95 tag_tag.text = tag.name
100 tag_tag.text = tag.name
96 else:
101 else:
97 tag_thread = et.SubElement(content_tag, TAG_THREAD)
102 tag_thread = et.SubElement(content_tag, TAG_THREAD)
98 thread_id = et.SubElement(tag_thread, TAG_ID)
103 thread_id = et.SubElement(tag_thread, TAG_ID)
99 thread.get_opening_post().global_id.to_xml_element(thread_id)
104 thread.get_opening_post().global_id.to_xml_element(thread_id)
100
105
101 pub_time = et.SubElement(content_tag, TAG_PUB_TIME)
106 pub_time = et.SubElement(content_tag, TAG_PUB_TIME)
102 pub_time.text = str(post.get_pub_time_str())
107 pub_time.text = str(post.get_pub_time_str())
103
108
104 if len(images) > 0 or len(attachments) > 0:
109 if len(images) > 0 or len(attachments) > 0:
105 attachments_tag = et.SubElement(content_tag, TAG_ATTACHMENTS)
110 attachments_tag = et.SubElement(content_tag, TAG_ATTACHMENTS)
106 attachment_refs = et.SubElement(model, TAG_ATTACHMENT_REFS)
111 attachment_refs = et.SubElement(model, TAG_ATTACHMENT_REFS)
107
112
108 for image in images:
113 for image in images:
109 SyncManager._attachment_to_xml(
114 SyncManager._attachment_to_xml(
110 attachments_tag, attachment_refs, image.image.file,
115 attachments_tag, attachment_refs, image.image.file,
111 image.hash, image.image.url)
116 image.hash, image.image.url)
112 for file in attachments:
117 for file in attachments:
113 SyncManager._attachment_to_xml(
118 SyncManager._attachment_to_xml(
114 attachments_tag, attachment_refs, file.file.file,
119 attachments_tag, attachment_refs, file.file.file,
115 file.hash, file.file.url)
120 file.hash, file.file.url)
116
121
117 global_id.content = et.tostring(content_tag, ENCODING_UNICODE)
122 global_id.content = et.tostring(content_tag, ENCODING_UNICODE)
118 global_id.save()
123 global_id.save()
119
124
120 signatures_tag = et.SubElement(model, TAG_SIGNATURES)
125 signatures_tag = et.SubElement(model, TAG_SIGNATURES)
121 post_signatures = global_id.signature_set.all()
126 post_signatures = global_id.signature_set.all()
122 if post_signatures:
127 if post_signatures:
123 signatures = post_signatures
128 signatures = post_signatures
124 else:
129 else:
125 key = KeyPair.objects.get(public_key=global_id.key)
130 key = KeyPair.objects.get(public_key=global_id.key)
126 signature = Signature(
131 signature = Signature(
127 key_type=key.key_type,
132 key_type=key.key_type,
128 key=key.public_key,
133 key=key.public_key,
129 signature=key.sign(global_id.content),
134 signature=key.sign(global_id.content),
130 global_id=global_id,
135 global_id=global_id,
131 )
136 )
132 signature.save()
137 signature.save()
133 signatures = [signature]
138 signatures = [signature]
134 for signature in signatures:
139 for signature in signatures:
135 signature_tag = et.SubElement(signatures_tag, TAG_SIGNATURE)
140 signature_tag = et.SubElement(signatures_tag, TAG_SIGNATURE)
136 signature_tag.set(ATTR_TYPE, signature.key_type)
141 signature_tag.set(ATTR_TYPE, signature.key_type)
137 signature_tag.set(ATTR_VALUE, signature.signature)
142 signature_tag.set(ATTR_VALUE, signature.signature)
138 signature_tag.set(ATTR_KEY, signature.key)
143 signature_tag.set(ATTR_KEY, signature.key)
139
144
140 return et.tostring(response, ENCODING_UNICODE)
145 return et.tostring(response, ENCODING_UNICODE)
141
146
142 @staticmethod
147 @staticmethod
143 @transaction.atomic
148 @transaction.atomic
144 def parse_response_get(response_xml, hostname):
149 def parse_response_get(response_xml, hostname):
145 tag_root = et.fromstring(response_xml)
150 tag_root = et.fromstring(response_xml)
146 tag_status = tag_root.find(TAG_STATUS)
151 tag_status = tag_root.find(TAG_STATUS)
147 if STATUS_SUCCESS == tag_status.text:
152 if STATUS_SUCCESS == tag_status.text:
148 tag_models = tag_root.find(TAG_MODELS)
153 tag_models = tag_root.find(TAG_MODELS)
149 for tag_model in tag_models:
154 for tag_model in tag_models:
150 tag_content = tag_model.find(TAG_CONTENT)
155 tag_content = tag_model.find(TAG_CONTENT)
151
156
152 content_str = et.tostring(tag_content, ENCODING_UNICODE)
157 content_str = et.tostring(tag_content, ENCODING_UNICODE)
153 signatures = SyncManager._verify_model(content_str, tag_model)
158 signatures = SyncManager._verify_model(content_str, tag_model)
154
159
155 tag_id = tag_content.find(TAG_ID)
160 tag_id = tag_content.find(TAG_ID)
156 global_id, exists = GlobalId.from_xml_element(tag_id)
161 global_id, exists = GlobalId.from_xml_element(tag_id)
157
162
158 if exists:
163 if exists:
159 print('Post with same ID already exists')
164 print('Post with same ID already exists')
160 else:
165 else:
161 global_id.content = content_str
166 global_id.content = content_str
162 global_id.save()
167 global_id.save()
163 for signature in signatures:
168 for signature in signatures:
164 signature.global_id = global_id
169 signature.global_id = global_id
165 signature.save()
170 signature.save()
166
171
167 title = tag_content.find(TAG_TITLE).text or ''
172 title = tag_content.find(TAG_TITLE).text or ''
168 text = tag_content.find(TAG_TEXT).text or ''
173 text = tag_content.find(TAG_TEXT).text or ''
169 pub_time = tag_content.find(TAG_PUB_TIME).text
174 pub_time = tag_content.find(TAG_PUB_TIME).text
170
175
171 thread = tag_content.find(TAG_THREAD)
176 thread = tag_content.find(TAG_THREAD)
172 tags = []
177 tags = []
173 if thread:
178 if thread:
174 thread_id = thread.find(TAG_ID)
179 thread_id = thread.find(TAG_ID)
175 op_global_id, exists = GlobalId.from_xml_element(thread_id)
180 op_global_id, exists = GlobalId.from_xml_element(thread_id)
176 if exists:
181 if exists:
177 opening_post = Post.objects.get(global_id=op_global_id)
182 opening_post = Post.objects.get(global_id=op_global_id)
178 else:
183 else:
179 raise SyncException('Load the OP first')
184 raise SyncException(EXCEPTION_OP)
180 else:
185 else:
181 opening_post = None
186 opening_post = None
182 tag_tags = tag_content.find(TAG_TAGS)
187 tag_tags = tag_content.find(TAG_TAGS)
183 for tag_tag in tag_tags:
188 for tag_tag in tag_tags:
184 tag, created = Tag.objects.get_or_create(
189 tag, created = Tag.objects.get_or_create(
185 name=tag_tag.text)
190 name=tag_tag.text)
186 tags.append(tag)
191 tags.append(tag)
187
192
188 # TODO Check that the replied posts are already present
193 # TODO Check that the replied posts are already present
189 # before adding new ones
194 # before adding new ones
190
195
191 files = []
196 files = []
192 tag_attachments = tag_content.find(TAG_ATTACHMENTS) or list()
197 tag_attachments = tag_content.find(TAG_ATTACHMENTS) or list()
193 tag_refs = tag_model.find(TAG_ATTACHMENT_REFS)
198 tag_refs = tag_model.find(TAG_ATTACHMENT_REFS)
194 for attachment in tag_attachments:
199 for attachment in tag_attachments:
195 tag_ref = tag_refs.find("{}[@ref='{}']".format(
200 tag_ref = tag_refs.find("{}[@ref='{}']".format(
196 TAG_ATTACHMENT_REF, attachment.text))
201 TAG_ATTACHMENT_REF, attachment.text))
197 url = tag_ref.get(ATTR_URL)
202 url = tag_ref.get(ATTR_URL)
198 attached_file = download(hostname + url)
203 attached_file = download(hostname + url)
199 if attached_file is None:
204 if attached_file is None:
200 raise SyncException('File was not dowloaded')
205 raise SyncException(EXCEPTION_DOWNLOAD)
201
206
202 hash = get_file_hash(attached_file)
207 hash = get_file_hash(attached_file)
203 if hash != attachment.text:
208 if hash != attachment.text:
204 raise SyncException('File hash does not match attachment hash')
209 raise SyncException(EXCEPTION_HASH)
205
210
206 files.append(attached_file)
211 files.append(attached_file)
207
212
208 Post.objects.import_post(
213 Post.objects.import_post(
209 title=title, text=text, pub_time=pub_time,
214 title=title, text=text, pub_time=pub_time,
210 opening_post=opening_post, tags=tags,
215 opening_post=opening_post, tags=tags,
211 global_id=global_id, files=files)
216 global_id=global_id, files=files)
212 else:
217 else:
213 raise SyncException('Sync node returned an error: {}'.format(
218 raise SyncException(EXCEPTION_NODE.format(tag_status.text))
214 tag_status.text))
215
219
216 @staticmethod
220 @staticmethod
217 def generate_response_pull():
221 def generate_response_pull():
218 response = et.Element(TAG_RESPONSE)
222 response = et.Element(TAG_RESPONSE)
219
223
220 status = et.SubElement(response, TAG_STATUS)
224 status = et.SubElement(response, TAG_STATUS)
221 status.text = STATUS_SUCCESS
225 status.text = STATUS_SUCCESS
222
226
223 models = et.SubElement(response, TAG_MODELS)
227 models = et.SubElement(response, TAG_MODELS)
224
228
225 for post in Post.objects.all():
229 for post in Post.objects.all():
226 tag_id = et.SubElement(models, TAG_ID)
230 tag_id = et.SubElement(models, TAG_ID)
227 post.global_id.to_xml_element(tag_id)
231 post.global_id.to_xml_element(tag_id)
228
232
229 return et.tostring(response, ENCODING_UNICODE)
233 return et.tostring(response, ENCODING_UNICODE)
230
234
231 @staticmethod
235 @staticmethod
232 def _verify_model(content_str, tag_model):
236 def _verify_model(content_str, tag_model):
233 """
237 """
234 Verifies all signatures for a single model.
238 Verifies all signatures for a single model.
235 """
239 """
236
240
237 signatures = []
241 signatures = []
238
242
239 tag_signatures = tag_model.find(TAG_SIGNATURES)
243 tag_signatures = tag_model.find(TAG_SIGNATURES)
240 for tag_signature in tag_signatures:
244 for tag_signature in tag_signatures:
241 signature_type = tag_signature.get(ATTR_TYPE)
245 signature_type = tag_signature.get(ATTR_TYPE)
242 signature_value = tag_signature.get(ATTR_VALUE)
246 signature_value = tag_signature.get(ATTR_VALUE)
243 signature_key = tag_signature.get(ATTR_KEY)
247 signature_key = tag_signature.get(ATTR_KEY)
244
248
245 signature = Signature(key_type=signature_type,
249 signature = Signature(key_type=signature_type,
246 key=signature_key,
250 key=signature_key,
247 signature=signature_value)
251 signature=signature_value)
248
252
249 if not KeyPair.objects.verify(signature, content_str):
253 if not KeyPair.objects.verify(signature, content_str):
250 raise SyncException('Invalid model signature for {}'.format(content_str))
254 raise SyncException(EXCEPTION_SIGNATURE.format(content_str))
251
255
252 signatures.append(signature)
256 signatures.append(signature)
253
257
254 return signatures
258 return signatures
255
259
256 @staticmethod
260 @staticmethod
257 def _attachment_to_xml(tag_attachments, tag_refs, file, hash, url):
261 def _attachment_to_xml(tag_attachments, tag_refs, file, hash, url):
258 if tag_attachments is not None:
262 if tag_attachments is not None:
259 mimetype = get_file_mimetype(file)
263 mimetype = get_file_mimetype(file)
260 attachment = et.SubElement(tag_attachments, TAG_ATTACHMENT)
264 attachment = et.SubElement(tag_attachments, TAG_ATTACHMENT)
261 attachment.set(ATTR_MIMETYPE, mimetype)
265 attachment.set(ATTR_MIMETYPE, mimetype)
262 attachment.text = hash
266 attachment.text = hash
263
267
264 attachment_ref = et.SubElement(tag_refs, TAG_ATTACHMENT_REF)
268 attachment_ref = et.SubElement(tag_refs, TAG_ATTACHMENT_REF)
265 attachment_ref.set(ATTR_REF, hash)
269 attachment_ref.set(ATTR_REF, hash)
266 attachment_ref.set(ATTR_URL, url)
270 attachment_ref.set(ATTR_URL, url)
General Comments 0
You need to be logged in to leave comments. Login now