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 | 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 |
def to_xml_element(self, element: et. |
|
|
29 | def to_xml_element(self, element: et.Element): | |
|
30 | 30 | """ |
|
31 | 31 | Exports global id to an XML element. |
|
32 | 32 | """ |
@@ -35,6 +35,28 b' class GlobalId(models.Model):' | |||
|
35 | 35 | element.set(ATTR_KEY_TYPE, self.key_type) |
|
36 | 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 | 61 | class Signature(models.Model): |
|
40 | 62 | class Meta: |
@@ -16,8 +16,8 b' class KeyPairManager(models.Manager):' | |||
|
16 | 16 | private = SigningKey.generate() |
|
17 | 17 | public = private.get_verifying_key() |
|
18 | 18 | |
|
19 |
private_key_str = private.to_ |
|
|
20 |
public_key_str = public.to_ |
|
|
19 | private_key_str = base64.b64encode(private.to_string()).decode() | |
|
20 | public_key_str = base64.b64encode(public.to_string()).decode() | |
|
21 | 21 | |
|
22 | 22 | return self.create(public_key=public_key_str, |
|
23 | 23 | private_key=private_key_str, |
@@ -27,7 +27,7 b' class KeyPairManager(models.Manager):' | |||
|
27 | 27 | |
|
28 | 28 | def verify(self, public_key_str, string, signature, key_type=TYPE_ECDSA): |
|
29 | 29 | if key_type == TYPE_ECDSA: |
|
30 |
public = VerifyingKey.from_ |
|
|
30 | public = VerifyingKey.from_string(base64.b64decode(public_key_str)) | |
|
31 | 31 | signature_byte = base64.b64decode(signature) |
|
32 | 32 | try: |
|
33 | 33 | return public.verify(signature_byte, string.encode()) |
@@ -36,6 +36,9 b' class KeyPairManager(models.Manager):' | |||
|
36 | 36 | else: |
|
37 | 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 | 43 | class KeyPair(models.Model): |
|
41 | 44 | class Meta: |
@@ -49,9 +52,10 b' class KeyPair(models.Model):' | |||
|
49 | 52 | primary = models.BooleanField(default=False) |
|
50 | 53 | |
|
51 | 54 | def __str__(self): |
|
52 |
return '%s |
|
|
55 | return '%s | %s' % (self.key_type, self.public_key) | |
|
53 | 56 | |
|
54 | 57 | def sign(self, string): |
|
55 |
private = SigningKey.from_ |
|
|
58 | private = SigningKey.from_string(base64.b64decode( | |
|
59 | self.private_key.encode())) | |
|
56 | 60 | signature_byte = private.sign(string.encode()) |
|
57 | 61 | return base64.b64encode(signature_byte) |
@@ -35,7 +35,7 b'' | |||
|
35 | 35 | {% endif %} |
|
36 | 36 | |
|
37 | 37 | {% if post.global_id %} |
|
38 |
<span class="global-id"> |
|
|
38 | <span class="global-id"> {{ post.global_id }} </span> | |
|
39 | 39 | {% endif %} |
|
40 | 40 | |
|
41 | 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 | 6 | def respond_pull(request): |
|
2 | 7 | pass |
|
3 | 8 | |
|
4 | 9 | |
|
5 | 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 | 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