From f8a94eeb04bec54e92c753aeff11dbed51ae7e6d Mon Sep 17 00:00:00 2001 From: Tom Hacohen Date: Thu, 12 Mar 2020 15:52:36 +0200 Subject: [PATCH] Revision: add a proper uid for revisions (which we also use for sync tag). --- .../0024_collectionitemrevision_uid.py | 19 +++++++++++++++++++ .../migrations/0025_auto_20200312_1350.py | 19 +++++++++++++++++++ django_etesync/models.py | 4 +++- django_etesync/serializers.py | 15 ++++++++++----- 4 files changed, 51 insertions(+), 6 deletions(-) create mode 100644 django_etesync/migrations/0024_collectionitemrevision_uid.py create mode 100644 django_etesync/migrations/0025_auto_20200312_1350.py diff --git a/django_etesync/migrations/0024_collectionitemrevision_uid.py b/django_etesync/migrations/0024_collectionitemrevision_uid.py new file mode 100644 index 0000000..6134c89 --- /dev/null +++ b/django_etesync/migrations/0024_collectionitemrevision_uid.py @@ -0,0 +1,19 @@ +# Generated by Django 3.0.3 on 2020-03-12 13:41 + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('django_etesync', '0023_auto_20200310_1556'), + ] + + operations = [ + migrations.AddField( + model_name='collectionitemrevision', + name='uid', + field=models.CharField(db_index=True, max_length=44, validators=[django.core.validators.RegexValidator(message='Not a valid UID', regex='[a-zA-Z0-9]')]), + ), + ] diff --git a/django_etesync/migrations/0025_auto_20200312_1350.py b/django_etesync/migrations/0025_auto_20200312_1350.py new file mode 100644 index 0000000..b54aeff --- /dev/null +++ b/django_etesync/migrations/0025_auto_20200312_1350.py @@ -0,0 +1,19 @@ +# Generated by Django 3.0.3 on 2020-03-12 13:50 + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('django_etesync', '0024_collectionitemrevision_uid'), + ] + + operations = [ + migrations.AlterField( + model_name='collectionitemrevision', + name='uid', + field=models.CharField(db_index=True, max_length=44, unique=True, validators=[django.core.validators.RegexValidator(message='Not a valid UID', regex='[a-zA-Z0-9]')]), + ), + ] diff --git a/django_etesync/models.py b/django_etesync/models.py index df25867..8079c28 100644 --- a/django_etesync/models.py +++ b/django_etesync/models.py @@ -78,6 +78,8 @@ class CollectionItemChunk(models.Model): class CollectionItemRevision(models.Model): + uid = models.CharField(db_index=True, unique=True, blank=False, null=False, + max_length=44, validators=[UidValidator]) item = models.ForeignKey(CollectionItem, related_name='revisions', on_delete=models.CASCADE) chunks = models.ManyToManyField(CollectionItemChunk, related_name='items') hmac = models.CharField(max_length=50, blank=False, null=False) @@ -88,7 +90,7 @@ class CollectionItemRevision(models.Model): unique_together = ('item', 'current') def __str__(self): - return '{} {} current={}'.format(self.item.uid, self.id, self.current) + return '{} {} current={}'.format(self.uid, self.item.uid, self.current) class CollectionMember(models.Model): diff --git a/django_etesync/serializers.py b/django_etesync/serializers.py index c24239b..c6d3a86 100644 --- a/django_etesync/serializers.py +++ b/django_etesync/serializers.py @@ -16,12 +16,17 @@ import base64 from django.contrib.auth import get_user_model from django.db import transaction +from django.utils.crypto import get_random_string from rest_framework import serializers from . import models User = get_user_model() +def generate_rev_uid(length=32): + return get_random_string(length) + + class BinaryBase64Field(serializers.Field): def to_representation(self, value): return base64.b64encode(value).decode('ascii') @@ -54,14 +59,12 @@ class CollectionSerializer(serializers.ModelSerializer): return None def get_ctag(self, obj): - # FIXME: we need to have something that's more privacy friendly. Can probably just generate a uid per revision - # on revision creation (on the server) and just use that. last_revision = models.CollectionItemRevision.objects.filter(item__collection=obj).last() if last_revision is None: # FIXME: what is the etag for None? Though if we use the revision for collection it should be shared anyway. return None - return str(last_revision.id) + return last_revision.uid def create(self, validated_data): """Function that's called when this serializer creates an item""" @@ -148,7 +151,8 @@ class CollectionItemSerializer(serializers.ModelSerializer): instance.save() chunks = revision_data.pop('chunks') - revision = models.CollectionItemRevision.objects.create(**revision_data, item=instance) + revision = models.CollectionItemRevision.objects.create(**revision_data, uid=generate_rev_uid(), + item=instance) revision.chunks.set(chunks) return instance @@ -165,7 +169,8 @@ class CollectionItemSerializer(serializers.ModelSerializer): current_revision.save() chunks = revision_data.pop('chunks') - revision = models.CollectionItemRevision.objects.create(**revision_data, item=instance) + revision = models.CollectionItemRevision.objects.create(**revision_data, uid=generate_rev_uid(), + item=instance) revision.chunks.set(chunks) return instance