Show More
@@ -12,7 +12,7 b' from django.utils import timezone' | |||
|
12 | 12 | |
|
13 | 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 | 16 | from boards.models.base import Viewable |
|
17 | 17 | from boards.models.thread import Thread |
|
18 | 18 | from boards import utils |
@@ -54,6 +54,8 b" TAG_PUB_TIME = 'pub-time'" | |||
|
54 | 54 | TAG_EDIT_TIME = 'edit-time' |
|
55 | 55 | TAG_PREVIOUS = 'previous' |
|
56 | 56 | TAG_NEXT = 'next' |
|
57 | TAG_SIGNATURES = 'signatures' | |
|
58 | TAG_SIGNATURE = 'signature' | |
|
57 | 59 | |
|
58 | 60 | TYPE_GET = 'get' |
|
59 | 61 | |
@@ -61,6 +63,8 b" ATTR_VERSION = 'version'" | |||
|
61 | 63 | ATTR_TYPE = 'type' |
|
62 | 64 | ATTR_NAME = 'name' |
|
63 | 65 | ATTR_REF_ID = 'ref-id' |
|
66 | ATTR_MODEL_REF = 'model-ref' | |
|
67 | ATTR_VALUE = 'value' | |
|
64 | 68 | |
|
65 | 69 | STATUS_SUCCESS = 'success' |
|
66 | 70 | |
@@ -190,7 +194,6 b' class PostManager(models.Manager):' | |||
|
190 | 194 | cache.set(cache_key, ppd) |
|
191 | 195 | return ppd |
|
192 | 196 | |
|
193 | ||
|
194 | 197 | def generate_request_get(self, model_list: list): |
|
195 | 198 | """ |
|
196 | 199 | Form a get request from a list of ModelId objects. |
@@ -217,13 +220,13 b' class PostManager(models.Manager):' | |||
|
217 | 220 | status.text = STATUS_SUCCESS |
|
218 | 221 | |
|
219 | 222 | models = et.SubElement(response, TAG_MODELS) |
|
223 | signatures = {} | |
|
220 | 224 | |
|
221 | 225 | ref_id = 1 |
|
222 | 226 | for post in model_list: |
|
223 | 227 | model = et.SubElement(models, TAG_MODEL) |
|
224 | 228 | model.set(ATTR_NAME, 'post') |
|
225 | 229 | model.set(ATTR_REF_ID, str(ref_id)) |
|
226 | ref_id += 1 | |
|
227 | 230 | |
|
228 | 231 | tag_id = et.SubElement(model, TAG_ID) |
|
229 | 232 | post.global_id.to_xml_element(tag_id) |
@@ -252,7 +255,6 b' class PostManager(models.Manager):' | |||
|
252 | 255 | replied_post = Post.objects.get(id=id) |
|
253 | 256 | replied_post.global_id.to_xml_element(prev_id) |
|
254 | 257 | |
|
255 | ||
|
256 | 258 | next_ids = post.referenced_posts.order_by('id').all() |
|
257 | 259 | if len(next_ids) > 0: |
|
258 | 260 | next_el = et.SubElement(model, TAG_NEXT) |
@@ -260,6 +262,31 b' class PostManager(models.Manager):' | |||
|
260 | 262 | next_id = et.SubElement(next_el, TAG_ID) |
|
261 | 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 | 290 | return et.tostring(response, 'unicode') |
|
264 | 291 | |
|
265 | 292 |
@@ -24,7 +24,7 b' class GlobalId(models.Model):' | |||
|
24 | 24 | local_id = models.IntegerField() |
|
25 | 25 | |
|
26 | 26 | def __str__(self): |
|
27 |
return '%s |
|
|
27 | return '[%s][%s][%d]' % (self.key_type, self.key, self.local_id) | |
|
28 | 28 | |
|
29 | 29 | def to_xml_element(self, element: et.Element): |
|
30 | 30 | """ |
@@ -62,6 +62,14 b' class Signature(models.Model):' | |||
|
62 | 62 | class Meta: |
|
63 | 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 | 73 | key_type = models.TextField() |
|
66 | 74 | key = models.TextField() |
|
67 | 75 | signature = models.TextField() |
@@ -52,10 +52,10 b' class KeyPair(models.Model):' | |||
|
52 | 52 | primary = models.BooleanField(default=False) |
|
53 | 53 | |
|
54 | 54 | def __str__(self): |
|
55 |
return '%s |
|
|
55 | return '[%s][%s]' % (self.key_type, self.public_key) | |
|
56 | 56 | |
|
57 | 57 | def sign(self, string): |
|
58 | 58 | private = SigningKey.from_string(base64.b64decode( |
|
59 | 59 | self.private_key.encode())) |
|
60 | signature_byte = private.sign(string.encode()) | |
|
61 | return base64.b64encode(signature_byte) | |
|
60 | signature_byte = private.sign_deterministic(string.encode()) | |
|
61 | return base64.b64encode(signature_byte).decode() |
@@ -35,7 +35,8 b'' | |||
|
35 | 35 | {% endif %} |
|
36 | 36 | |
|
37 | 37 | {% if post.global_id %} |
|
38 |
< |
|
|
38 | <a class="global-id" href=" | |
|
39 | {% url 'post_sync_data' post.id %}"> [RAW] </a> | |
|
39 | 40 | {% endif %} |
|
40 | 41 | |
|
41 | 42 | {% if moderator %} |
@@ -1,3 +1,4 b'' | |||
|
1 | from base64 import b64encode | |
|
1 | 2 | import logging |
|
2 | 3 | |
|
3 | 4 | from django.test import TestCase |
@@ -38,11 +39,15 b' class KeyTest(TestCase):' | |||
|
38 | 39 | request = Post.objects.generate_request_get([post]) |
|
39 | 40 | logger.debug(request) |
|
40 | 41 | |
|
42 | key = KeyPair.objects.get(primary=True) | |
|
41 | 43 | self.assertTrue('<request type="get" version="1.0">' |
|
42 | 44 | '<model name="post" version="1.0">' |
|
43 |
'<id key=" |
|
|
45 | '<id key="%s" local-id="1" type="%s" />' | |
|
44 | 46 | '</model>' |
|
45 |
'</request>' |
|
|
47 | '</request>' % ( | |
|
48 | key.public_key, | |
|
49 | key.key_type, | |
|
50 | ) in request, | |
|
46 | 51 | 'Wrong XML generated for the GET request.') |
|
47 | 52 | |
|
48 | 53 | def test_response_get(self): |
@@ -58,38 +63,41 b' class KeyTest(TestCase):' | |||
|
58 | 63 | response = Post.objects.generate_response_get([reply_post]) |
|
59 | 64 | logger.debug(response) |
|
60 | 65 | |
|
61 | self.assertTrue('<response>' | |
|
62 |
|
|
|
66 | key = KeyPair.objects.get(primary=True) | |
|
67 | self.assertTrue('<status>success</status>' | |
|
63 | 68 | '<models>' |
|
64 | 69 | '<model name="post" ref-id="1">' |
|
65 |
'<id key=" |
|
|
70 | '<id key="%s" local-id="%d" type="%s" />' | |
|
66 | 71 | '<title>test_title</title>' |
|
67 | 72 | '<text>[post]%d[/post]</text>' |
|
68 | 73 | '<thread>%d</thread>' |
|
69 | 74 | '<pub-time>%s</pub-time>' |
|
70 | 75 | '<edit-time>%s</edit-time>' |
|
71 | 76 | '<previous>' |
|
72 |
'<id key=" |
|
|
77 | '<id key="%s" local-id="%d" type="%s" />' | |
|
73 | 78 | '</previous>' |
|
74 | 79 | '<next>' |
|
75 |
'<id key=" |
|
|
80 | '<id key="%s" local-id="%d" type="%s" />' | |
|
76 | 81 | '</next>' |
|
77 | 82 | '</model>' |
|
78 | '</models>' | |
|
79 |
|
|
|
83 | '</models>' % ( | |
|
84 | key.public_key, | |
|
80 | 85 | reply_post.id, |
|
86 | key.key_type, | |
|
81 | 87 | post.id, |
|
82 | 88 | post.id, |
|
83 | 89 | str(reply_post.get_edit_time_epoch()), |
|
84 | 90 | str(reply_post.get_pub_time_epoch()), |
|
91 | key.public_key, | |
|
85 | 92 | post.id, |
|
93 | key.key_type, | |
|
94 | key.public_key, | |
|
86 | 95 | reply_reply_post.id, |
|
96 | key.key_type, | |
|
87 | 97 | ) in response, |
|
88 | 98 | 'Wrong XML generated for the GET response.') |
|
89 | 99 | |
|
90 | 100 | def _create_post_with_key(self): |
|
91 | key = KeyPair(public_key='pubkey', private_key='privkey', | |
|
92 | key_type='test_key_type', primary=True) | |
|
93 | key.save() | |
|
101 | KeyPair.objects.generate_key(primary=True) | |
|
94 | 102 | |
|
95 | 103 | return Post.objects.create_post(title='test_title', text='test_text') |
@@ -14,10 +14,7 b' class SyncTest(TestCase):' | |||
|
14 | 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', | |
|
18 | key_type='test_key_type', primary=True) | |
|
19 | key.save() | |
|
20 | ||
|
17 | KeyPair.objects.generate_key(primary=True) | |
|
21 | 18 | post = Post.objects.create_post(title='test_title', text='test_text') |
|
22 | 19 | |
|
23 | 20 | request = MockRequest() |
@@ -32,7 +29,6 b' class SyncTest(TestCase):' | |||
|
32 | 29 | ) |
|
33 | 30 | |
|
34 | 31 | self.assertTrue( |
|
35 | '<response>' | |
|
36 | 32 | '<status>success</status>' |
|
37 | 33 | '<models>' |
|
38 | 34 | '<model name="post" ref-id="1">' |
@@ -42,8 +38,7 b' class SyncTest(TestCase):' | |||
|
42 | 38 | '<pub-time>%d</pub-time>' |
|
43 | 39 | '<edit-time>%d</edit-time>' |
|
44 | 40 | '</model>' |
|
45 | '</models>' | |
|
46 | '</response>' % ( | |
|
41 | '</models>' % ( | |
|
47 | 42 | post.global_id.key, |
|
48 | 43 | post.id, |
|
49 | 44 | post.global_id.key_type, |
@@ -11,6 +11,7 b' from boards.views.search import BoardSea' | |||
|
11 | 11 | from boards.views.static import StaticPageView |
|
12 | 12 | from boards.views.post_admin import PostAdminView |
|
13 | 13 | from boards.views.preview import PostPreviewView |
|
14 | from boards.views.sync import get_post_sync_data | |
|
14 | 15 | |
|
15 | 16 | js_info_dict = { |
|
16 | 17 | 'packages': ('boards',), |
@@ -78,6 +79,9 b" urlpatterns = patterns(''," | |||
|
78 | 79 | url(r'^search/$', BoardSearchView.as_view(), name='search'), |
|
79 | 80 | |
|
80 | 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 | 1 | import xml.etree.ElementTree as et |
|
2 | from django.http import HttpResponse | |
|
2 | from django.http import HttpResponse, Http404 | |
|
3 | 3 | from boards.models import GlobalId, Post |
|
4 | 4 | |
|
5 | 5 | |
@@ -30,4 +30,20 b' def respond_get(request):' | |||
|
30 | 30 | |
|
31 | 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