diff --git a/boards/management/commands/generate_keypair.py b/boards/management/commands/generate_keypair.py
new file mode 100644
--- /dev/null
+++ b/boards/management/commands/generate_keypair.py
@@ -0,0 +1,17 @@
+__author__ = 'neko259'
+
+
+from django.core.management import BaseCommand
+from django.db import transaction
+
+from boards.models import KeyPair
+
+
+class Command(BaseCommand):
+ help = 'Generates the new keypair. The first one will be primary.'
+
+ @transaction.atomic
+ def handle(self, *args, **options):
+ key = KeyPair.objects.generate_key(
+ primary=not KeyPair.objects.has_primary())
+ print(key)
\ No newline at end of file
diff --git a/boards/models/signature.py b/boards/models/signature.py
--- a/boards/models/signature.py
+++ b/boards/models/signature.py
@@ -24,9 +24,9 @@ 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.SubElement):
+ def to_xml_element(self, element: et.Element):
"""
Exports global id to an XML element.
"""
@@ -35,6 +35,28 @@ class GlobalId(models.Model):
element.set(ATTR_KEY_TYPE, self.key_type)
element.set(ATTR_LOCAL_ID, str(self.local_id))
+ @staticmethod
+ def from_xml_element(element: et.Element, existing=False):
+ """
+ Parses XML id tag and gets global id from it.
+
+ Arguments:
+ element -- the XML 'id' element
+ existing -- if this is False, a new instance of GlobalId will be
+ created. Otherwise, we will search for an existing GlobalId instance
+ and throw DoesNotExist if there isn't one.
+ """
+
+ if existing:
+ return GlobalId.objects.get(key=element.get(ATTR_KEY),
+ key_type=element.get(ATTR_KEY_TYPE),
+ local_id=int(element.get(
+ ATTR_LOCAL_ID)))
+ else:
+ return GlobalId(key=element.get(ATTR_KEY),
+ key_type=element.get(ATTR_KEY_TYPE),
+ local_id=int(element.get(ATTR_LOCAL_ID)))
+
class Signature(models.Model):
class Meta:
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
@@ -16,8 +16,8 @@ class KeyPairManager(models.Manager):
private = SigningKey.generate()
public = private.get_verifying_key()
- private_key_str = private.to_pem().decode()
- public_key_str = public.to_pem().decode()
+ private_key_str = base64.b64encode(private.to_string()).decode()
+ public_key_str = base64.b64encode(public.to_string()).decode()
return self.create(public_key=public_key_str,
private_key=private_key_str,
@@ -27,7 +27,7 @@ class KeyPairManager(models.Manager):
def verify(self, public_key_str, string, signature, key_type=TYPE_ECDSA):
if key_type == TYPE_ECDSA:
- public = VerifyingKey.from_pem(public_key_str)
+ public = VerifyingKey.from_string(base64.b64decode(public_key_str))
signature_byte = base64.b64decode(signature)
try:
return public.verify(signature_byte, string.encode())
@@ -36,6 +36,9 @@ class KeyPairManager(models.Manager):
else:
raise Exception('Key type not supported')
+ def has_primary(self):
+ return self.filter(primary=True).exists()
+
class KeyPair(models.Model):
class Meta:
@@ -49,9 +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_pem(self.private_key)
+ private = SigningKey.from_string(base64.b64decode(
+ self.private_key.encode()))
signature_byte = private.sign(string.encode())
return base64.b64encode(signature_byte)
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,7 @@
{% endif %}
{% if post.global_id %}
- {{ post.global_id.key_type }} / {{ post.global_id.key }} / {{ post.global_id.local_id }}
+ {{ post.global_id }}
{% endif %}
{% if moderator %}
diff --git a/boards/tests/test_sync.py b/boards/tests/test_sync.py
new file mode 100644
--- /dev/null
+++ b/boards/tests/test_sync.py
@@ -0,0 +1,55 @@
+from boards.models import KeyPair, Post
+from boards.tests.mocks import MockRequest
+from boards.views.sync import respond_get
+
+__author__ = 'neko259'
+
+
+from django.test import TestCase
+
+
+class SyncTest(TestCase):
+ def test_get(self):
+ """
+ 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()
+
+ post = Post.objects.create_post(title='test_title', text='test_text')
+
+ request = MockRequest()
+ request.POST['xml'] = (
+ ''
+ ''
+ ''
+ ''
+ '' % (post.global_id.key,
+ post.id,
+ post.global_id.key_type)
+ )
+
+ self.assertTrue(
+ ''
+ 'success'
+ ''
+ ''
+ ''
+ '%s'
+ '%s'
+ '%d'
+ '%d'
+ ''
+ ''
+ '' % (
+ post.global_id.key,
+ post.id,
+ post.global_id.key_type,
+ post.title,
+ post.text.raw,
+ post.get_pub_time_epoch(),
+ post.get_edit_time_epoch(),
+ ) in respond_get(request).content.decode(),
+ 'Wrong response generated for the GET request.')
\ No newline at end of file
diff --git a/boards/views/sync.py b/boards/views/sync.py
--- a/boards/views/sync.py
+++ b/boards/views/sync.py
@@ -1,6 +1,33 @@
+import xml.etree.ElementTree as et
+from django.http import HttpResponse
+from boards.models import GlobalId, Post
+
+
def respond_pull(request):
pass
def respond_get(request):
- pass
+ """
+ Processes a GET request with post ID list and returns the posts XML list.
+ Request should contain an 'xml' post attribute with the actual request XML.
+ """
+
+ request_xml = request.POST['xml']
+
+ posts = []
+
+ root_tag = et.fromstring(request_xml)
+ model_tag = root_tag[0]
+ for id_tag in model_tag:
+ try:
+ global_id = GlobalId.from_xml_element(id_tag, existing=True)
+ posts += Post.objects.filter(global_id=global_id)
+ except GlobalId.DoesNotExist:
+ # This is normal. If we don't have such GlobalId in the system,
+ # just ignore this ID and proceed to the next one.
+ pass
+
+ response_xml = Post.objects.generate_response_get(posts)
+
+ return HttpResponse(content=response_xml)
\ No newline at end of file