Show More
@@ -12,7 +12,7 b' from django.utils import timezone' | |||||
12 |
|
12 | |||
13 | from markupfield.fields import MarkupField |
|
13 | from markupfield.fields import MarkupField | |
14 |
|
14 | |||
15 | from boards.models import PostImage, KeyPair, GlobalId |
|
15 | from boards.models import PostImage, KeyPair, GlobalId, Signature | |
16 | from boards.models.base import Viewable |
|
16 | from boards.models.base import Viewable | |
17 | from boards.models.thread import Thread |
|
17 | from boards.models.thread import Thread | |
18 | from boards import utils |
|
18 | from boards import utils | |
@@ -54,6 +54,8 b" TAG_PUB_TIME = 'pub-time'" | |||||
54 | TAG_EDIT_TIME = 'edit-time' |
|
54 | TAG_EDIT_TIME = 'edit-time' | |
55 | TAG_PREVIOUS = 'previous' |
|
55 | TAG_PREVIOUS = 'previous' | |
56 | TAG_NEXT = 'next' |
|
56 | TAG_NEXT = 'next' | |
|
57 | TAG_SIGNATURES = 'signatures' | |||
|
58 | TAG_SIGNATURE = 'signature' | |||
57 |
|
59 | |||
58 | TYPE_GET = 'get' |
|
60 | TYPE_GET = 'get' | |
59 |
|
61 | |||
@@ -61,6 +63,8 b" ATTR_VERSION = 'version'" | |||||
61 | ATTR_TYPE = 'type' |
|
63 | ATTR_TYPE = 'type' | |
62 | ATTR_NAME = 'name' |
|
64 | ATTR_NAME = 'name' | |
63 | ATTR_REF_ID = 'ref-id' |
|
65 | ATTR_REF_ID = 'ref-id' | |
|
66 | ATTR_MODEL_REF = 'model-ref' | |||
|
67 | ATTR_VALUE = 'value' | |||
64 |
|
68 | |||
65 | STATUS_SUCCESS = 'success' |
|
69 | STATUS_SUCCESS = 'success' | |
66 |
|
70 | |||
@@ -190,7 +194,6 b' class PostManager(models.Manager):' | |||||
190 | cache.set(cache_key, ppd) |
|
194 | cache.set(cache_key, ppd) | |
191 | return ppd |
|
195 | return ppd | |
192 |
|
196 | |||
193 |
|
||||
194 | def generate_request_get(self, model_list: list): |
|
197 | def generate_request_get(self, model_list: list): | |
195 | """ |
|
198 | """ | |
196 | Form a get request from a list of ModelId objects. |
|
199 | Form a get request from a list of ModelId objects. | |
@@ -217,13 +220,13 b' class PostManager(models.Manager):' | |||||
217 | status.text = STATUS_SUCCESS |
|
220 | status.text = STATUS_SUCCESS | |
218 |
|
221 | |||
219 | models = et.SubElement(response, TAG_MODELS) |
|
222 | models = et.SubElement(response, TAG_MODELS) | |
|
223 | signatures = {} | |||
220 |
|
224 | |||
221 | ref_id = 1 |
|
225 | ref_id = 1 | |
222 | for post in model_list: |
|
226 | for post in model_list: | |
223 | model = et.SubElement(models, TAG_MODEL) |
|
227 | model = et.SubElement(models, TAG_MODEL) | |
224 | model.set(ATTR_NAME, 'post') |
|
228 | model.set(ATTR_NAME, 'post') | |
225 | model.set(ATTR_REF_ID, str(ref_id)) |
|
229 | model.set(ATTR_REF_ID, str(ref_id)) | |
226 | ref_id += 1 |
|
|||
227 |
|
230 | |||
228 | tag_id = et.SubElement(model, TAG_ID) |
|
231 | tag_id = et.SubElement(model, TAG_ID) | |
229 | post.global_id.to_xml_element(tag_id) |
|
232 | post.global_id.to_xml_element(tag_id) | |
@@ -252,7 +255,6 b' class PostManager(models.Manager):' | |||||
252 | replied_post = Post.objects.get(id=id) |
|
255 | replied_post = Post.objects.get(id=id) | |
253 | replied_post.global_id.to_xml_element(prev_id) |
|
256 | replied_post.global_id.to_xml_element(prev_id) | |
254 |
|
257 | |||
255 |
|
||||
256 | next_ids = post.referenced_posts.order_by('id').all() |
|
258 | next_ids = post.referenced_posts.order_by('id').all() | |
257 | if len(next_ids) > 0: |
|
259 | if len(next_ids) > 0: | |
258 | next_el = et.SubElement(model, TAG_NEXT) |
|
260 | next_el = et.SubElement(model, TAG_NEXT) | |
@@ -260,6 +262,31 b' class PostManager(models.Manager):' | |||||
260 | next_id = et.SubElement(next_el, TAG_ID) |
|
262 | next_id = et.SubElement(next_el, TAG_ID) | |
261 | ref_post.global_id.to_xml_element(next_id) |
|
263 | ref_post.global_id.to_xml_element(next_id) | |
262 |
|
264 | |||
|
265 | post_signatures = post.signature.all() | |||
|
266 | if post_signatures: | |||
|
267 | signatures[ref_id] = post.signatures | |||
|
268 | else: | |||
|
269 | # TODO Maybe the signature can be computed only once after | |||
|
270 | # the post is added? Need to add some on_save signal queue | |||
|
271 | # and add this there. | |||
|
272 | key = KeyPair.objects.get(public_key=post.global_id.key) | |||
|
273 | signatures[ref_id] = [Signature( | |||
|
274 | key_type=key.key_type, | |||
|
275 | key=key.public_key, | |||
|
276 | signature=key.sign(et.tostring(model, 'unicode')), | |||
|
277 | )] | |||
|
278 | ref_id += 1 | |||
|
279 | ||||
|
280 | signatures_tag = et.SubElement(response, TAG_SIGNATURES) | |||
|
281 | for ref_id in signatures.keys(): | |||
|
282 | signatures = signatures[ref_id] | |||
|
283 | ||||
|
284 | for signature in signatures: | |||
|
285 | signature_tag = et.SubElement(signatures_tag, TAG_SIGNATURE) | |||
|
286 | signature_tag.set(ATTR_MODEL_REF, str(ref_id)) | |||
|
287 | signature_tag.set(ATTR_TYPE, signature.key_type) | |||
|
288 | signature_tag.set(ATTR_VALUE, signature.signature) | |||
|
289 | ||||
263 | return et.tostring(response, 'unicode') |
|
290 | return et.tostring(response, 'unicode') | |
264 |
|
291 | |||
265 |
|
292 |
@@ -24,7 +24,7 b' class GlobalId(models.Model):' | |||||
24 | local_id = models.IntegerField() |
|
24 | local_id = models.IntegerField() | |
25 |
|
25 | |||
26 | def __str__(self): |
|
26 | def __str__(self): | |
27 |
return '%s |
|
27 | return '[%s][%s][%d]' % (self.key_type, self.key, self.local_id) | |
28 |
|
28 | |||
29 | def to_xml_element(self, element: et.Element): |
|
29 | def to_xml_element(self, element: et.Element): | |
30 | """ |
|
30 | """ | |
@@ -62,6 +62,14 b' class Signature(models.Model):' | |||||
62 | class Meta: |
|
62 | class Meta: | |
63 | app_label = 'boards' |
|
63 | app_label = 'boards' | |
64 |
|
64 | |||
|
65 | def __init__(self, *args, **kwargs): | |||
|
66 | models.Model.__init__(self, *args, **kwargs) | |||
|
67 | ||||
|
68 | if 'key' in kwargs and 'key_type' in kwargs and 'signature' in kwargs: | |||
|
69 | self.key_type = kwargs['key_type'] | |||
|
70 | self.key = kwargs['key'] | |||
|
71 | self.signature = kwargs['signature'] | |||
|
72 | ||||
65 | key_type = models.TextField() |
|
73 | key_type = models.TextField() | |
66 | key = models.TextField() |
|
74 | key = models.TextField() | |
67 | signature = models.TextField() |
|
75 | signature = models.TextField() |
@@ -52,10 +52,10 b' class KeyPair(models.Model):' | |||||
52 | primary = models.BooleanField(default=False) |
|
52 | primary = models.BooleanField(default=False) | |
53 |
|
53 | |||
54 | def __str__(self): |
|
54 | def __str__(self): | |
55 |
return '%s |
|
55 | return '[%s][%s]' % (self.key_type, self.public_key) | |
56 |
|
56 | |||
57 | def sign(self, string): |
|
57 | def sign(self, string): | |
58 | private = SigningKey.from_string(base64.b64decode( |
|
58 | private = SigningKey.from_string(base64.b64decode( | |
59 | self.private_key.encode())) |
|
59 | self.private_key.encode())) | |
60 | signature_byte = private.sign(string.encode()) |
|
60 | signature_byte = private.sign_deterministic(string.encode()) | |
61 | return base64.b64encode(signature_byte) |
|
61 | return base64.b64encode(signature_byte).decode() |
@@ -35,7 +35,8 b'' | |||||
35 | {% endif %} |
|
35 | {% endif %} | |
36 |
|
36 | |||
37 | {% if post.global_id %} |
|
37 | {% if post.global_id %} | |
38 |
< |
|
38 | <a class="global-id" href=" | |
|
39 | {% url 'post_sync_data' post.id %}"> [RAW] </a> | |||
39 | {% endif %} |
|
40 | {% endif %} | |
40 |
|
41 | |||
41 | {% if moderator %} |
|
42 | {% if moderator %} |
@@ -1,3 +1,4 b'' | |||||
|
1 | from base64 import b64encode | |||
1 | import logging |
|
2 | import logging | |
2 |
|
3 | |||
3 | from django.test import TestCase |
|
4 | from django.test import TestCase | |
@@ -38,11 +39,15 b' class KeyTest(TestCase):' | |||||
38 | request = Post.objects.generate_request_get([post]) |
|
39 | request = Post.objects.generate_request_get([post]) | |
39 | logger.debug(request) |
|
40 | logger.debug(request) | |
40 |
|
41 | |||
|
42 | key = KeyPair.objects.get(primary=True) | |||
41 | self.assertTrue('<request type="get" version="1.0">' |
|
43 | self.assertTrue('<request type="get" version="1.0">' | |
42 | '<model name="post" version="1.0">' |
|
44 | '<model name="post" version="1.0">' | |
43 |
'<id key=" |
|
45 | '<id key="%s" local-id="1" type="%s" />' | |
44 | '</model>' |
|
46 | '</model>' | |
45 |
'</request>' |
|
47 | '</request>' % ( | |
|
48 | key.public_key, | |||
|
49 | key.key_type, | |||
|
50 | ) in request, | |||
46 | 'Wrong XML generated for the GET request.') |
|
51 | 'Wrong XML generated for the GET request.') | |
47 |
|
52 | |||
48 | def test_response_get(self): |
|
53 | def test_response_get(self): | |
@@ -58,38 +63,41 b' class KeyTest(TestCase):' | |||||
58 | response = Post.objects.generate_response_get([reply_post]) |
|
63 | response = Post.objects.generate_response_get([reply_post]) | |
59 | logger.debug(response) |
|
64 | logger.debug(response) | |
60 |
|
65 | |||
61 | self.assertTrue('<response>' |
|
66 | key = KeyPair.objects.get(primary=True) | |
62 |
|
|
67 | self.assertTrue('<status>success</status>' | |
63 | '<models>' |
|
68 | '<models>' | |
64 | '<model name="post" ref-id="1">' |
|
69 | '<model name="post" ref-id="1">' | |
65 |
'<id key=" |
|
70 | '<id key="%s" local-id="%d" type="%s" />' | |
66 | '<title>test_title</title>' |
|
71 | '<title>test_title</title>' | |
67 | '<text>[post]%d[/post]</text>' |
|
72 | '<text>[post]%d[/post]</text>' | |
68 | '<thread>%d</thread>' |
|
73 | '<thread>%d</thread>' | |
69 | '<pub-time>%s</pub-time>' |
|
74 | '<pub-time>%s</pub-time>' | |
70 | '<edit-time>%s</edit-time>' |
|
75 | '<edit-time>%s</edit-time>' | |
71 | '<previous>' |
|
76 | '<previous>' | |
72 |
'<id key=" |
|
77 | '<id key="%s" local-id="%d" type="%s" />' | |
73 | '</previous>' |
|
78 | '</previous>' | |
74 | '<next>' |
|
79 | '<next>' | |
75 |
'<id key=" |
|
80 | '<id key="%s" local-id="%d" type="%s" />' | |
76 | '</next>' |
|
81 | '</next>' | |
77 | '</model>' |
|
82 | '</model>' | |
78 | '</models>' |
|
83 | '</models>' % ( | |
79 |
|
|
84 | key.public_key, | |
80 | reply_post.id, |
|
85 | reply_post.id, | |
|
86 | key.key_type, | |||
81 | post.id, |
|
87 | post.id, | |
82 | post.id, |
|
88 | post.id, | |
83 | str(reply_post.get_edit_time_epoch()), |
|
89 | str(reply_post.get_edit_time_epoch()), | |
84 | str(reply_post.get_pub_time_epoch()), |
|
90 | str(reply_post.get_pub_time_epoch()), | |
|
91 | key.public_key, | |||
85 | post.id, |
|
92 | post.id, | |
|
93 | key.key_type, | |||
|
94 | key.public_key, | |||
86 | reply_reply_post.id, |
|
95 | reply_reply_post.id, | |
|
96 | key.key_type, | |||
87 | ) in response, |
|
97 | ) in response, | |
88 | 'Wrong XML generated for the GET response.') |
|
98 | 'Wrong XML generated for the GET response.') | |
89 |
|
99 | |||
90 | def _create_post_with_key(self): |
|
100 | def _create_post_with_key(self): | |
91 | key = KeyPair(public_key='pubkey', private_key='privkey', |
|
101 | KeyPair.objects.generate_key(primary=True) | |
92 | key_type='test_key_type', primary=True) |
|
|||
93 | key.save() |
|
|||
94 |
|
102 | |||
95 | return Post.objects.create_post(title='test_title', text='test_text') |
|
103 | return Post.objects.create_post(title='test_title', text='test_text') |
@@ -14,10 +14,7 b' class SyncTest(TestCase):' | |||||
14 | Forms a GET request of a post and checks the response. |
|
14 | Forms a GET request of a post and checks the response. | |
15 | """ |
|
15 | """ | |
16 |
|
16 | |||
17 | key = KeyPair(public_key='pubkey', private_key='privkey', |
|
17 | KeyPair.objects.generate_key(primary=True) | |
18 | key_type='test_key_type', primary=True) |
|
|||
19 | key.save() |
|
|||
20 |
|
||||
21 | post = Post.objects.create_post(title='test_title', text='test_text') |
|
18 | post = Post.objects.create_post(title='test_title', text='test_text') | |
22 |
|
19 | |||
23 | request = MockRequest() |
|
20 | request = MockRequest() | |
@@ -32,7 +29,6 b' class SyncTest(TestCase):' | |||||
32 | ) |
|
29 | ) | |
33 |
|
30 | |||
34 | self.assertTrue( |
|
31 | self.assertTrue( | |
35 | '<response>' |
|
|||
36 | '<status>success</status>' |
|
32 | '<status>success</status>' | |
37 | '<models>' |
|
33 | '<models>' | |
38 | '<model name="post" ref-id="1">' |
|
34 | '<model name="post" ref-id="1">' | |
@@ -42,8 +38,7 b' class SyncTest(TestCase):' | |||||
42 | '<pub-time>%d</pub-time>' |
|
38 | '<pub-time>%d</pub-time>' | |
43 | '<edit-time>%d</edit-time>' |
|
39 | '<edit-time>%d</edit-time>' | |
44 | '</model>' |
|
40 | '</model>' | |
45 | '</models>' |
|
41 | '</models>' % ( | |
46 | '</response>' % ( |
|
|||
47 | post.global_id.key, |
|
42 | post.global_id.key, | |
48 | post.id, |
|
43 | post.id, | |
49 | post.global_id.key_type, |
|
44 | post.global_id.key_type, |
@@ -11,6 +11,7 b' from boards.views.search import BoardSea' | |||||
11 | from boards.views.static import StaticPageView |
|
11 | from boards.views.static import StaticPageView | |
12 | from boards.views.post_admin import PostAdminView |
|
12 | from boards.views.post_admin import PostAdminView | |
13 | from boards.views.preview import PostPreviewView |
|
13 | from boards.views.preview import PostPreviewView | |
|
14 | from boards.views.sync import get_post_sync_data | |||
14 |
|
15 | |||
15 | js_info_dict = { |
|
16 | js_info_dict = { | |
16 | 'packages': ('boards',), |
|
17 | 'packages': ('boards',), | |
@@ -78,6 +79,9 b" urlpatterns = patterns(''," | |||||
78 | url(r'^search/$', BoardSearchView.as_view(), name='search'), |
|
79 | url(r'^search/$', BoardSearchView.as_view(), name='search'), | |
79 |
|
80 | |||
80 | # Post preview |
|
81 | # Post preview | |
81 | url(r'^preview/$', PostPreviewView.as_view(), name='preview') |
|
82 | url(r'^preview/$', PostPreviewView.as_view(), name='preview'), | |
|
83 | ||||
|
84 | url(r'^post_xml/(?P<post_id>\d+)$', get_post_sync_data, | |||
|
85 | name='post_sync_data'), | |||
82 |
|
86 | |||
83 | ) |
|
87 | ) |
@@ -1,5 +1,5 b'' | |||||
1 | import xml.etree.ElementTree as et |
|
1 | import xml.etree.ElementTree as et | |
2 | from django.http import HttpResponse |
|
2 | from django.http import HttpResponse, Http404 | |
3 | from boards.models import GlobalId, Post |
|
3 | from boards.models import GlobalId, Post | |
4 |
|
4 | |||
5 |
|
5 | |||
@@ -30,4 +30,20 b' def respond_get(request):' | |||||
30 |
|
30 | |||
31 | response_xml = Post.objects.generate_response_get(posts) |
|
31 | response_xml = Post.objects.generate_response_get(posts) | |
32 |
|
32 | |||
33 | return HttpResponse(content=response_xml) No newline at end of file |
|
33 | return HttpResponse(content=response_xml) | |
|
34 | ||||
|
35 | ||||
|
36 | def get_post_sync_data(request, post_id): | |||
|
37 | try: | |||
|
38 | post = Post.objects.get(id=post_id) | |||
|
39 | except Post.DoesNotExist: | |||
|
40 | raise Http404() | |||
|
41 | ||||
|
42 | content = 'Global ID: %s\n\nXML: %s' \ | |||
|
43 | % (post.global_id, Post.objects.generate_response_get([post])) | |||
|
44 | ||||
|
45 | ||||
|
46 | return HttpResponse( | |||
|
47 | content_type='text/plain', | |||
|
48 | content=content, | |||
|
49 | ) No newline at end of file |
General Comments 0
You need to be logged in to leave comments.
Login now