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