Show More
@@ -0,0 +1,17 b'' | |||||
|
1 | __author__ = 'neko259' | |||
|
2 | ||||
|
3 | ||||
|
4 | from django.core.management import BaseCommand | |||
|
5 | from django.db import transaction | |||
|
6 | ||||
|
7 | from boards.models import KeyPair | |||
|
8 | ||||
|
9 | ||||
|
10 | class Command(BaseCommand): | |||
|
11 | help = 'Generates the new keypair. The first one will be primary.' | |||
|
12 | ||||
|
13 | @transaction.atomic | |||
|
14 | def handle(self, *args, **options): | |||
|
15 | key = KeyPair.objects.generate_key( | |||
|
16 | primary=not KeyPair.objects.has_primary()) | |||
|
17 | print(key) No newline at end of file |
@@ -0,0 +1,55 b'' | |||||
|
1 | from boards.models import KeyPair, Post | |||
|
2 | from boards.tests.mocks import MockRequest | |||
|
3 | from boards.views.sync import respond_get | |||
|
4 | ||||
|
5 | __author__ = 'neko259' | |||
|
6 | ||||
|
7 | ||||
|
8 | from django.test import TestCase | |||
|
9 | ||||
|
10 | ||||
|
11 | class SyncTest(TestCase): | |||
|
12 | def test_get(self): | |||
|
13 | """ | |||
|
14 | Forms a GET request of a post and checks the response. | |||
|
15 | """ | |||
|
16 | ||||
|
17 | key = KeyPair(public_key='pubkey', private_key='privkey', | |||
|
18 | key_type='test_key_type', primary=True) | |||
|
19 | key.save() | |||
|
20 | ||||
|
21 | post = Post.objects.create_post(title='test_title', text='test_text') | |||
|
22 | ||||
|
23 | request = MockRequest() | |||
|
24 | request.POST['xml'] = ( | |||
|
25 | '<request type="get" version="1.0">' | |||
|
26 | '<model name="post" version="1.0">' | |||
|
27 | '<id key="%s" local-id="%d" type="%s" />' | |||
|
28 | '</model>' | |||
|
29 | '</request>' % (post.global_id.key, | |||
|
30 | post.id, | |||
|
31 | post.global_id.key_type) | |||
|
32 | ) | |||
|
33 | ||||
|
34 | self.assertTrue( | |||
|
35 | '<response>' | |||
|
36 | '<status>success</status>' | |||
|
37 | '<models>' | |||
|
38 | '<model name="post" ref-id="1">' | |||
|
39 | '<id key="%s" local-id="%d" type="%s" />' | |||
|
40 | '<title>%s</title>' | |||
|
41 | '<text>%s</text>' | |||
|
42 | '<pub-time>%d</pub-time>' | |||
|
43 | '<edit-time>%d</edit-time>' | |||
|
44 | '</model>' | |||
|
45 | '</models>' | |||
|
46 | '</response>' % ( | |||
|
47 | post.global_id.key, | |||
|
48 | post.id, | |||
|
49 | post.global_id.key_type, | |||
|
50 | post.title, | |||
|
51 | post.text.raw, | |||
|
52 | post.get_pub_time_epoch(), | |||
|
53 | post.get_edit_time_epoch(), | |||
|
54 | ) in respond_get(request).content.decode(), | |||
|
55 | 'Wrong response generated for the GET request.') No newline at end of file |
@@ -24,9 +24,9 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. |
|
29 | def to_xml_element(self, element: et.Element): | |
30 | """ |
|
30 | """ | |
31 | Exports global id to an XML element. |
|
31 | Exports global id to an XML element. | |
32 | """ |
|
32 | """ | |
@@ -35,6 +35,28 b' class GlobalId(models.Model):' | |||||
35 | element.set(ATTR_KEY_TYPE, self.key_type) |
|
35 | element.set(ATTR_KEY_TYPE, self.key_type) | |
36 | element.set(ATTR_LOCAL_ID, str(self.local_id)) |
|
36 | element.set(ATTR_LOCAL_ID, str(self.local_id)) | |
37 |
|
37 | |||
|
38 | @staticmethod | |||
|
39 | def from_xml_element(element: et.Element, existing=False): | |||
|
40 | """ | |||
|
41 | Parses XML id tag and gets global id from it. | |||
|
42 | ||||
|
43 | Arguments: | |||
|
44 | element -- the XML 'id' element | |||
|
45 | existing -- if this is False, a new instance of GlobalId will be | |||
|
46 | created. Otherwise, we will search for an existing GlobalId instance | |||
|
47 | and throw DoesNotExist if there isn't one. | |||
|
48 | """ | |||
|
49 | ||||
|
50 | if existing: | |||
|
51 | return GlobalId.objects.get(key=element.get(ATTR_KEY), | |||
|
52 | key_type=element.get(ATTR_KEY_TYPE), | |||
|
53 | local_id=int(element.get( | |||
|
54 | ATTR_LOCAL_ID))) | |||
|
55 | else: | |||
|
56 | return GlobalId(key=element.get(ATTR_KEY), | |||
|
57 | key_type=element.get(ATTR_KEY_TYPE), | |||
|
58 | local_id=int(element.get(ATTR_LOCAL_ID))) | |||
|
59 | ||||
38 |
|
60 | |||
39 | class Signature(models.Model): |
|
61 | class Signature(models.Model): | |
40 | class Meta: |
|
62 | class Meta: |
@@ -16,8 +16,8 b' class KeyPairManager(models.Manager):' | |||||
16 | private = SigningKey.generate() |
|
16 | private = SigningKey.generate() | |
17 | public = private.get_verifying_key() |
|
17 | public = private.get_verifying_key() | |
18 |
|
18 | |||
19 |
private_key_str = private.to_ |
|
19 | private_key_str = base64.b64encode(private.to_string()).decode() | |
20 |
public_key_str = public.to_ |
|
20 | public_key_str = base64.b64encode(public.to_string()).decode() | |
21 |
|
21 | |||
22 | return self.create(public_key=public_key_str, |
|
22 | return self.create(public_key=public_key_str, | |
23 | private_key=private_key_str, |
|
23 | private_key=private_key_str, | |
@@ -27,7 +27,7 b' class KeyPairManager(models.Manager):' | |||||
27 |
|
27 | |||
28 | def verify(self, public_key_str, string, signature, key_type=TYPE_ECDSA): |
|
28 | def verify(self, public_key_str, string, signature, key_type=TYPE_ECDSA): | |
29 | if key_type == TYPE_ECDSA: |
|
29 | if key_type == TYPE_ECDSA: | |
30 |
public = VerifyingKey.from_ |
|
30 | public = VerifyingKey.from_string(base64.b64decode(public_key_str)) | |
31 | signature_byte = base64.b64decode(signature) |
|
31 | signature_byte = base64.b64decode(signature) | |
32 | try: |
|
32 | try: | |
33 | return public.verify(signature_byte, string.encode()) |
|
33 | return public.verify(signature_byte, string.encode()) | |
@@ -36,6 +36,9 b' class KeyPairManager(models.Manager):' | |||||
36 | else: |
|
36 | else: | |
37 | raise Exception('Key type not supported') |
|
37 | raise Exception('Key type not supported') | |
38 |
|
38 | |||
|
39 | def has_primary(self): | |||
|
40 | return self.filter(primary=True).exists() | |||
|
41 | ||||
39 |
|
42 | |||
40 | class KeyPair(models.Model): |
|
43 | class KeyPair(models.Model): | |
41 | class Meta: |
|
44 | class Meta: | |
@@ -49,9 +52,10 b' class KeyPair(models.Model):' | |||||
49 | primary = models.BooleanField(default=False) |
|
52 | primary = models.BooleanField(default=False) | |
50 |
|
53 | |||
51 | def __str__(self): |
|
54 | def __str__(self): | |
52 |
return '%s |
|
55 | return '%s | %s' % (self.key_type, self.public_key) | |
53 |
|
56 | |||
54 | def sign(self, string): |
|
57 | def sign(self, string): | |
55 |
private = SigningKey.from_ |
|
58 | private = SigningKey.from_string(base64.b64decode( | |
|
59 | self.private_key.encode())) | |||
56 | signature_byte = private.sign(string.encode()) |
|
60 | signature_byte = private.sign(string.encode()) | |
57 | return base64.b64encode(signature_byte) |
|
61 | return base64.b64encode(signature_byte) |
@@ -35,7 +35,7 b'' | |||||
35 | {% endif %} |
|
35 | {% endif %} | |
36 |
|
36 | |||
37 | {% if post.global_id %} |
|
37 | {% if post.global_id %} | |
38 |
<span class="global-id"> |
|
38 | <span class="global-id"> {{ post.global_id }} </span> | |
39 | {% endif %} |
|
39 | {% endif %} | |
40 |
|
40 | |||
41 | {% if moderator %} |
|
41 | {% if moderator %} |
@@ -1,6 +1,33 b'' | |||||
|
1 | import xml.etree.ElementTree as et | |||
|
2 | from django.http import HttpResponse | |||
|
3 | from boards.models import GlobalId, Post | |||
|
4 | ||||
|
5 | ||||
1 | def respond_pull(request): |
|
6 | def respond_pull(request): | |
2 | pass |
|
7 | pass | |
3 |
|
8 | |||
4 |
|
9 | |||
5 | def respond_get(request): |
|
10 | def respond_get(request): | |
|
11 | """ | |||
|
12 | Processes a GET request with post ID list and returns the posts XML list. | |||
|
13 | Request should contain an 'xml' post attribute with the actual request XML. | |||
|
14 | """ | |||
|
15 | ||||
|
16 | request_xml = request.POST['xml'] | |||
|
17 | ||||
|
18 | posts = [] | |||
|
19 | ||||
|
20 | root_tag = et.fromstring(request_xml) | |||
|
21 | model_tag = root_tag[0] | |||
|
22 | for id_tag in model_tag: | |||
|
23 | try: | |||
|
24 | global_id = GlobalId.from_xml_element(id_tag, existing=True) | |||
|
25 | posts += Post.objects.filter(global_id=global_id) | |||
|
26 | except GlobalId.DoesNotExist: | |||
|
27 | # This is normal. If we don't have such GlobalId in the system, | |||
|
28 | # just ignore this ID and proceed to the next one. | |||
6 | pass |
|
29 | pass | |
|
30 | ||||
|
31 | response_xml = Post.objects.generate_response_get(posts) | |||
|
32 | ||||
|
33 | return HttpResponse(content=response_xml) No newline at end of file |
General Comments 0
You need to be logged in to leave comments.
Login now