# HG changeset patch # User neko259 # Date 2014-08-23 21:21:50 # Node ID fbeaaa163684db4a01957588671fe818ec065f99 # Parent 9ee107b978c964d1668e97d6667ff71a4a0251d3 Added signatures to the GET response. Added a view to get a full post response for one post. Don't show post key as it is present in the XML post view. Changed key display format diff --git a/boards/models/post.py b/boards/models/post.py --- a/boards/models/post.py +++ b/boards/models/post.py @@ -12,7 +12,7 @@ from django.utils import timezone from markupfield.fields import MarkupField -from boards.models import PostImage, KeyPair, GlobalId +from boards.models import PostImage, KeyPair, GlobalId, Signature from boards.models.base import Viewable from boards.models.thread import Thread from boards import utils @@ -54,6 +54,8 @@ TAG_PUB_TIME = 'pub-time' TAG_EDIT_TIME = 'edit-time' TAG_PREVIOUS = 'previous' TAG_NEXT = 'next' +TAG_SIGNATURES = 'signatures' +TAG_SIGNATURE = 'signature' TYPE_GET = 'get' @@ -61,6 +63,8 @@ ATTR_VERSION = 'version' ATTR_TYPE = 'type' ATTR_NAME = 'name' ATTR_REF_ID = 'ref-id' +ATTR_MODEL_REF = 'model-ref' +ATTR_VALUE = 'value' STATUS_SUCCESS = 'success' @@ -190,7 +194,6 @@ class PostManager(models.Manager): cache.set(cache_key, ppd) return ppd - def generate_request_get(self, model_list: list): """ Form a get request from a list of ModelId objects. @@ -217,13 +220,13 @@ class PostManager(models.Manager): status.text = STATUS_SUCCESS models = et.SubElement(response, TAG_MODELS) + signatures = {} ref_id = 1 for post in model_list: model = et.SubElement(models, TAG_MODEL) model.set(ATTR_NAME, 'post') model.set(ATTR_REF_ID, str(ref_id)) - ref_id += 1 tag_id = et.SubElement(model, TAG_ID) post.global_id.to_xml_element(tag_id) @@ -252,7 +255,6 @@ class PostManager(models.Manager): replied_post = Post.objects.get(id=id) replied_post.global_id.to_xml_element(prev_id) - next_ids = post.referenced_posts.order_by('id').all() if len(next_ids) > 0: next_el = et.SubElement(model, TAG_NEXT) @@ -260,6 +262,31 @@ class PostManager(models.Manager): next_id = et.SubElement(next_el, TAG_ID) ref_post.global_id.to_xml_element(next_id) + post_signatures = post.signature.all() + if post_signatures: + signatures[ref_id] = post.signatures + else: + # TODO Maybe the signature can be computed only once after + # the post is added? Need to add some on_save signal queue + # and add this there. + key = KeyPair.objects.get(public_key=post.global_id.key) + signatures[ref_id] = [Signature( + key_type=key.key_type, + key=key.public_key, + signature=key.sign(et.tostring(model, 'unicode')), + )] + ref_id += 1 + + signatures_tag = et.SubElement(response, TAG_SIGNATURES) + for ref_id in signatures.keys(): + signatures = signatures[ref_id] + + for signature in signatures: + signature_tag = et.SubElement(signatures_tag, TAG_SIGNATURE) + signature_tag.set(ATTR_MODEL_REF, str(ref_id)) + signature_tag.set(ATTR_TYPE, signature.key_type) + signature_tag.set(ATTR_VALUE, signature.signature) + return et.tostring(response, 'unicode') diff --git a/boards/models/signature.py b/boards/models/signature.py --- a/boards/models/signature.py +++ b/boards/models/signature.py @@ -24,7 +24,7 @@ class GlobalId(models.Model): local_id = models.IntegerField() def __str__(self): - return '%s | %s | %d' % (self.key_type, self.key, self.local_id) + return '[%s][%s][%d]' % (self.key_type, self.key, self.local_id) def to_xml_element(self, element: et.Element): """ @@ -62,6 +62,14 @@ class Signature(models.Model): class Meta: app_label = 'boards' + def __init__(self, *args, **kwargs): + models.Model.__init__(self, *args, **kwargs) + + if 'key' in kwargs and 'key_type' in kwargs and 'signature' in kwargs: + self.key_type = kwargs['key_type'] + self.key = kwargs['key'] + self.signature = kwargs['signature'] + key_type = models.TextField() key = models.TextField() signature = models.TextField() diff --git a/boards/models/sync_key.py b/boards/models/sync_key.py --- a/boards/models/sync_key.py +++ b/boards/models/sync_key.py @@ -52,10 +52,10 @@ class KeyPair(models.Model): primary = models.BooleanField(default=False) def __str__(self): - return '%s | %s' % (self.key_type, self.public_key) + return '[%s][%s]' % (self.key_type, self.public_key) def sign(self, string): private = SigningKey.from_string(base64.b64decode( self.private_key.encode())) - signature_byte = private.sign(string.encode()) - return base64.b64encode(signature_byte) + signature_byte = private.sign_deterministic(string.encode()) + return base64.b64encode(signature_byte).decode() diff --git a/boards/templates/boards/post.html b/boards/templates/boards/post.html --- a/boards/templates/boards/post.html +++ b/boards/templates/boards/post.html @@ -35,7 +35,8 @@ {% endif %} {% if post.global_id %} - {{ post.global_id }} + [RAW] {% endif %} {% if moderator %} diff --git a/boards/tests/test_keys.py b/boards/tests/test_keys.py --- a/boards/tests/test_keys.py +++ b/boards/tests/test_keys.py @@ -1,3 +1,4 @@ +from base64 import b64encode import logging from django.test import TestCase @@ -38,11 +39,15 @@ class KeyTest(TestCase): request = Post.objects.generate_request_get([post]) logger.debug(request) + key = KeyPair.objects.get(primary=True) self.assertTrue('' '' - '' + '' '' - '' in request, + '' % ( + key.public_key, + key.key_type, + ) in request, 'Wrong XML generated for the GET request.') def test_response_get(self): @@ -58,38 +63,41 @@ class KeyTest(TestCase): response = Post.objects.generate_response_get([reply_post]) logger.debug(response) - self.assertTrue('' - 'success' + key = KeyPair.objects.get(primary=True) + self.assertTrue('success' '' '' - '' + '' 'test_title' '[post]%d[/post]' '%d' '%s' '%s' '' - '' + '' '' '' - '' + '' '' '' - '' - '' % ( + '' % ( + key.public_key, reply_post.id, + key.key_type, post.id, post.id, str(reply_post.get_edit_time_epoch()), str(reply_post.get_pub_time_epoch()), + key.public_key, post.id, + key.key_type, + key.public_key, reply_reply_post.id, + key.key_type, ) in response, 'Wrong XML generated for the GET response.') def _create_post_with_key(self): - key = KeyPair(public_key='pubkey', private_key='privkey', - key_type='test_key_type', primary=True) - key.save() + KeyPair.objects.generate_key(primary=True) return Post.objects.create_post(title='test_title', text='test_text') diff --git a/boards/tests/test_sync.py b/boards/tests/test_sync.py --- a/boards/tests/test_sync.py +++ b/boards/tests/test_sync.py @@ -14,10 +14,7 @@ class SyncTest(TestCase): Forms a GET request of a post and checks the response. """ - key = KeyPair(public_key='pubkey', private_key='privkey', - key_type='test_key_type', primary=True) - key.save() - + KeyPair.objects.generate_key(primary=True) post = Post.objects.create_post(title='test_title', text='test_text') request = MockRequest() @@ -32,7 +29,6 @@ class SyncTest(TestCase): ) self.assertTrue( - '' 'success' '' '' @@ -42,8 +38,7 @@ class SyncTest(TestCase): '%d' '%d' '' - '' - '' % ( + '' % ( post.global_id.key, post.id, post.global_id.key_type, diff --git a/boards/urls.py b/boards/urls.py --- a/boards/urls.py +++ b/boards/urls.py @@ -11,6 +11,7 @@ from boards.views.search import BoardSea from boards.views.static import StaticPageView from boards.views.post_admin import PostAdminView from boards.views.preview import PostPreviewView +from boards.views.sync import get_post_sync_data js_info_dict = { 'packages': ('boards',), @@ -78,6 +79,9 @@ urlpatterns = patterns('', url(r'^search/$', BoardSearchView.as_view(), name='search'), # Post preview - url(r'^preview/$', PostPreviewView.as_view(), name='preview') + url(r'^preview/$', PostPreviewView.as_view(), name='preview'), + + url(r'^post_xml/(?P\d+)$', get_post_sync_data, + name='post_sync_data'), ) diff --git a/boards/views/sync.py b/boards/views/sync.py --- a/boards/views/sync.py +++ b/boards/views/sync.py @@ -1,5 +1,5 @@ import xml.etree.ElementTree as et -from django.http import HttpResponse +from django.http import HttpResponse, Http404 from boards.models import GlobalId, Post @@ -30,4 +30,20 @@ def respond_get(request): response_xml = Post.objects.generate_response_get(posts) - return HttpResponse(content=response_xml) \ No newline at end of file + return HttpResponse(content=response_xml) + + +def get_post_sync_data(request, post_id): + try: + post = Post.objects.get(id=post_id) + except Post.DoesNotExist: + raise Http404() + + content = 'Global ID: %s\n\nXML: %s' \ + % (post.global_id, Post.objects.generate_response_get([post])) + + + return HttpResponse( + content_type='text/plain', + content=content, + ) \ No newline at end of file