# HG changeset patch # User neko259 # Date 2014-08-14 13:39:39 # Node ID 8a6f4ef57ab6405dc66dbf3e50eb0dfe3255b1d9 # Parent 507fd1cf8c8b98aa7421da5fd996297ce08e0b87 Added KeyPair model for signing and verifying data, that will be user for syncing diff --git a/boards/migrations/0025_add_refmap_cache.py b/boards/migrations/0025_add_refmap_cache.py --- a/boards/migrations/0025_add_refmap_cache.py +++ b/boards/migrations/0025_add_refmap_cache.py @@ -14,7 +14,7 @@ class Migration(DataMigration): # Note: Don't use "from appname.models import ModelName". # Use orm.ModelName to refer to models in this application, # and orm['appname.ModelName'] for models in other applications. - for post in Post.objects.all(): + for post in orm['boards.Post'].objects.all(): post.build_refmap() post.save() diff --git a/boards/migrations/0031_auto__add_field_post_public_key.py b/boards/migrations/0031_auto__add_field_post_public_key.py new file mode 100644 --- /dev/null +++ b/boards/migrations/0031_auto__add_field_post_public_key.py @@ -0,0 +1,74 @@ +# -*- coding: utf-8 -*- +from south.utils import datetime_utils as datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding field 'Post.public_key' + db.add_column('boards_post', 'public_key', + self.gf('django.db.models.fields.TextField')(blank=True, null=True), + keep_default=False) + + + def backwards(self, orm): + # Deleting field 'Post.public_key' + db.delete_column('boards_post', 'public_key') + + + models = { + 'boards.ban': { + 'Meta': {'object_name': 'Ban'}, + 'can_read': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}), + 'reason': ('django.db.models.fields.CharField', [], {'max_length': '200', 'default': "'Auto'"}) + }, + 'boards.post': { + 'Meta': {'ordering': "('id',)", 'object_name': 'Post'}, + '_text_rendered': ('django.db.models.fields.TextField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'images': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'blank': 'True', 'null': 'True', 'related_name': "'ip+'", 'to': "orm['boards.PostImage']", 'db_index': 'True'}), + 'last_edit_time': ('django.db.models.fields.DateTimeField', [], {}), + 'poster_ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}), + 'poster_user_agent': ('django.db.models.fields.TextField', [], {}), + 'pub_time': ('django.db.models.fields.DateTimeField', [], {}), + 'public_key': ('django.db.models.fields.TextField', [], {'blank': 'True', 'null': 'True'}), + 'referenced_posts': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'blank': 'True', 'null': 'True', 'related_name': "'rfp+'", 'to': "orm['boards.Post']", 'db_index': 'True'}), + 'refmap': ('django.db.models.fields.TextField', [], {'blank': 'True', 'null': 'True'}), + 'text': ('markupfield.fields.MarkupField', [], {'rendered_field': 'True'}), + 'text_markup_type': ('django.db.models.fields.CharField', [], {'max_length': '30', 'default': "'bbcode'"}), + 'thread_new': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['boards.Thread']", 'null': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '200'}) + }, + 'boards.postimage': { + 'Meta': {'ordering': "('id',)", 'object_name': 'PostImage'}, + 'hash': ('django.db.models.fields.CharField', [], {'max_length': '36'}), + 'height': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'image': ('boards.thumbs.ImageWithThumbsField', [], {'max_length': '100', 'blank': 'True'}), + 'pre_height': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'pre_width': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'width': ('django.db.models.fields.IntegerField', [], {'default': '0'}) + }, + 'boards.tag': { + 'Meta': {'ordering': "('name',)", 'object_name': 'Tag'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'db_index': 'True'}), + 'threads': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['boards.Thread']", 'null': 'True', 'blank': 'True', 'related_name': "'tag+'"}) + }, + 'boards.thread': { + 'Meta': {'object_name': 'Thread'}, + 'archived': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'bump_time': ('django.db.models.fields.DateTimeField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_edit_time': ('django.db.models.fields.DateTimeField', [], {}), + 'replies': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['boards.Post']", 'null': 'True', 'blank': 'True', 'related_name': "'tre+'"}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['boards.Tag']"}) + } + } + + complete_apps = ['boards'] \ No newline at end of file diff --git a/boards/migrations/0032_auto__add_keypair.py b/boards/migrations/0032_auto__add_keypair.py new file mode 100644 --- /dev/null +++ b/boards/migrations/0032_auto__add_keypair.py @@ -0,0 +1,85 @@ +# -*- coding: utf-8 -*- +from south.utils import datetime_utils as datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding model 'KeyPair' + db.create_table('boards_keypair', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('public_key', self.gf('django.db.models.fields.TextField')()), + ('private_key', self.gf('django.db.models.fields.TextField')()), + ('key_type', self.gf('django.db.models.fields.TextField')()), + )) + db.send_create_signal('boards', ['KeyPair']) + + + def backwards(self, orm): + # Deleting model 'KeyPair' + db.delete_table('boards_keypair') + + + models = { + 'boards.ban': { + 'Meta': {'object_name': 'Ban'}, + 'can_read': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}), + 'reason': ('django.db.models.fields.CharField', [], {'max_length': '200', 'default': "'Auto'"}) + }, + 'boards.keypair': { + 'Meta': {'object_name': 'KeyPair'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'key_type': ('django.db.models.fields.TextField', [], {}), + 'private_key': ('django.db.models.fields.TextField', [], {}), + 'public_key': ('django.db.models.fields.TextField', [], {}) + }, + 'boards.post': { + 'Meta': {'ordering': "('id',)", 'object_name': 'Post'}, + '_text_rendered': ('django.db.models.fields.TextField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'images': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['boards.PostImage']", 'db_index': 'True', 'related_name': "'ip+'", 'symmetrical': 'False', 'blank': 'True', 'null': 'True'}), + 'last_edit_time': ('django.db.models.fields.DateTimeField', [], {}), + 'poster_ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}), + 'poster_user_agent': ('django.db.models.fields.TextField', [], {}), + 'pub_time': ('django.db.models.fields.DateTimeField', [], {}), + 'public_key': ('django.db.models.fields.TextField', [], {'blank': 'True', 'null': 'True'}), + 'referenced_posts': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['boards.Post']", 'db_index': 'True', 'related_name': "'rfp+'", 'symmetrical': 'False', 'blank': 'True', 'null': 'True'}), + 'refmap': ('django.db.models.fields.TextField', [], {'blank': 'True', 'null': 'True'}), + 'text': ('markupfield.fields.MarkupField', [], {'rendered_field': 'True'}), + 'text_markup_type': ('django.db.models.fields.CharField', [], {'max_length': '30', 'default': "'bbcode'"}), + 'thread_new': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['boards.Thread']", 'null': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '200'}) + }, + 'boards.postimage': { + 'Meta': {'ordering': "('id',)", 'object_name': 'PostImage'}, + 'hash': ('django.db.models.fields.CharField', [], {'max_length': '36'}), + 'height': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'image': ('boards.thumbs.ImageWithThumbsField', [], {'max_length': '100', 'blank': 'True'}), + 'pre_height': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'pre_width': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'width': ('django.db.models.fields.IntegerField', [], {'default': '0'}) + }, + 'boards.tag': { + 'Meta': {'ordering': "('name',)", 'object_name': 'Tag'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'db_index': 'True'}), + 'threads': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['boards.Thread']", 'null': 'True', 'blank': 'True', 'related_name': "'tag+'"}) + }, + 'boards.thread': { + 'Meta': {'object_name': 'Thread'}, + 'archived': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'bump_time': ('django.db.models.fields.DateTimeField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_edit_time': ('django.db.models.fields.DateTimeField', [], {}), + 'replies': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['boards.Post']", 'null': 'True', 'blank': 'True', 'related_name': "'tre+'"}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['boards.Tag']"}) + } + } + + complete_apps = ['boards'] \ No newline at end of file diff --git a/boards/models/__init__.py b/boards/models/__init__.py --- a/boards/models/__init__.py +++ b/boards/models/__init__.py @@ -5,3 +5,4 @@ from boards.models.thread import Thread from boards.models.post import Post from boards.models.tag import Tag from boards.models.user import Ban +from boards.models.sync_key import KeyPair diff --git a/boards/models/post.py b/boards/models/post.py --- a/boards/models/post.py +++ b/boards/models/post.py @@ -188,12 +188,20 @@ class Post(models.Model, Viewable): db_index=True) last_edit_time = models.DateTimeField() + # Replies to the post referenced_posts = models.ManyToManyField('Post', symmetrical=False, null=True, blank=True, related_name='rfp+', db_index=True) + + # Replies map. This is built from the referenced posts list to speed up + # page loading (no need to get all the referenced posts from the database). refmap = models.TextField(null=True, blank=True) + # Sender key. If the message was downloaded from another server, this + # indicates the server. + public_key = models.TextField(null=True, blank=True) + def __unicode__(self): return '#' + str(self.id) + ' ' + self.title + ' (' + \ self.text.raw[:50] + ')' diff --git a/boards/settings.py b/boards/settings.py --- a/boards/settings.py +++ b/boards/settings.py @@ -1,5 +1,5 @@ VERSION = '1.8.1 Kara' -SITE_NAME = 'Neboard' +SITE_NAME = 'n3b0a2d' CACHE_TIMEOUT = 600 # Timeout for caching, if cache is used LOGIN_TIMEOUT = 3600 # Timeout between login tries @@ -20,4 +20,4 @@ ARCHIVE_THREADS = True LIMIT_POSTING_SPEED = False # This password is used to add admin permissions to the user -MASTER_PASSWORD = u'password' \ No newline at end of file +MASTER_PASSWORD = u'password' diff --git a/boards/static/css/md/base_page.css b/boards/static/css/md/base_page.css --- a/boards/static/css/md/base_page.css +++ b/boards/static/css/md/base_page.css @@ -437,6 +437,11 @@ ul { font-size: 1.2em; } +/* Post */ +.post > .message, .post > .image { + padding-left: 1em; +} + /* Reflink preview */ .post_preview { border-left: 1px solid #777; diff --git a/boards/tests.py b/boards/tests.py --- a/boards/tests.py +++ b/boards/tests.py @@ -8,7 +8,7 @@ from django.test.client import Client from django.core.urlresolvers import reverse, NoReverseMatch from boards.abstracts.settingsmanager import get_settings_manager -from boards.models import Post, Tag, Thread +from boards.models import Post, Tag, Thread, KeyPair from boards import urls from boards import settings import neboard @@ -261,3 +261,19 @@ class AbstractTest(TestCase): class MockRequest: def __init__(self): self.session = dict() + + +class KeyTest(TestCase): + def test_create_key(self): + key = KeyPair.objects.generate_key('ecdsa') + + self.assertIsNotNone(key, 'The key was not created.') + + def test_validation(self): + key = KeyPair.objects.generate_key(key_type='ecdsa') + message = 'msg' + signature = key.sign(message) + valid = KeyPair.objects.verify(key.public_key, message, signature, + key_type='ecdsa') + + self.assertTrue(valid, 'Message verification failed.') diff --git a/requirements.txt b/requirements.txt --- a/requirements.txt +++ b/requirements.txt @@ -5,3 +5,4 @@ django>=1.6 django_cleanup django-markupfield bbcode +ecdsa