From 930b1e1b6dbc9bf47af2f3e93fc2340d2bdc4c23 Mon Sep 17 00:00:00 2001 From: Julian Dehm Date: Thu, 16 Feb 2023 15:47:29 +0100 Subject: [PATCH] votes: increase token length to 16 --- meinberlin/apps/votes/forms.py | 11 +- .../0005_alter_votingtoken_token.py | 113 ++++++++++++++++++ meinberlin/apps/votes/models.py | 9 +- meinberlin/apps/votes/tasks.py | 4 +- meinberlin/test/factories/votes.py | 2 +- tests/votes/test_votes_models.py | 7 +- 6 files changed, 132 insertions(+), 14 deletions(-) create mode 100644 meinberlin/apps/votes/migrations/0005_alter_votingtoken_token.py diff --git a/meinberlin/apps/votes/forms.py b/meinberlin/apps/votes/forms.py index b33e38bc4e..47fa6295fb 100644 --- a/meinberlin/apps/votes/forms.py +++ b/meinberlin/apps/votes/forms.py @@ -10,8 +10,8 @@ def __init__(self, placeholder=None, *args, **kwargs): widget = TextInput( attrs={ "class": "form-control", - "minlength": 12, - "maxlength": 14, + "minlength": 16, + "maxlength": 19, "placeholder": placeholder, } ) @@ -23,21 +23,20 @@ def __init__(self, placeholder=None, *args, **kwargs): } def clean(self, value): - # ensure no spaces or dashes value = value.replace(" ", "").replace("-", "") # check the value is not too short or too long - if len(value) < 12: + if len(value) < 16: raise forms.ValidationError(self.error_messages["invalid_short"]) - elif len(value) > 12: + elif len(value) > 16: raise forms.ValidationError(self.error_messages["invalid_long"]) return value class TokenForm(forms.Form): - token = VotingTokenField(placeholder="0000-0000-0000") + token = VotingTokenField(placeholder="0000-0000-0000-0000") def __init__(self, *args, **kwargs): self.module_id = kwargs.pop("module_id") diff --git a/meinberlin/apps/votes/migrations/0005_alter_votingtoken_token.py b/meinberlin/apps/votes/migrations/0005_alter_votingtoken_token.py new file mode 100644 index 0000000000..483f505f32 --- /dev/null +++ b/meinberlin/apps/votes/migrations/0005_alter_votingtoken_token.py @@ -0,0 +1,113 @@ +# Generated by Django 3.2.17 on 2023-02-16 13:45 + +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone +import meinberlin.apps.votes.models + + +class Migration(migrations.Migration): + dependencies = [ + ("a4modules", "0006_module_blueprint_type"), + ("contenttypes", "0002_remove_content_type_name"), + ("meinberlin_votes", "0004_verbose_name_created_modified"), + ] + + operations = [ + migrations.DeleteModel( + name="VotingToken", + ), + migrations.DeleteModel( + name="TokenVote", + ), + migrations.CreateModel( + name="VotingToken", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "token", + models.CharField( + blank=True, + default=meinberlin.apps.votes.models.get_token_16, + editable=False, + max_length=40, + ), + ), + ( + "token_hash", + models.CharField(editable=False, max_length=128, unique=True), + ), + ("allowed_votes", models.PositiveSmallIntegerField(default=5)), + ("package_number", models.PositiveIntegerField()), + ( + "is_active", + models.BooleanField( + default=True, + help_text="Designates whether this token should be treated as active. Unselect this instead of deleting tokens.", + ), + ), + ( + "module", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="a4modules.module", + ), + ), + ], + ), + migrations.CreateModel( + name="TokenVote", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "created", + models.DateTimeField( + default=django.utils.timezone.now, + editable=False, + verbose_name="Created", + ), + ), + ( + "modified", + models.DateTimeField( + blank=True, editable=False, null=True, verbose_name="Modified" + ), + ), + ("object_pk", models.PositiveIntegerField()), + ( + "content_type", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="contenttypes.contenttype", + ), + ), + ( + "token", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="meinberlin_votes.votingtoken", + ), + ), + ], + options={ + "unique_together": {("content_type", "object_pk", "token")}, + "index_together": {("content_type", "object_pk")}, + }, + ), + ] diff --git a/meinberlin/apps/votes/models.py b/meinberlin/apps/votes/models.py index 79e10034ef..03a71f809c 100644 --- a/meinberlin/apps/votes/models.py +++ b/meinberlin/apps/votes/models.py @@ -18,6 +18,7 @@ def get_token(length): return "".join(secrets.choice(alphabet) for i in range(length)) +# only used by migration def get_token_12(): return get_token(12) @@ -57,7 +58,7 @@ def get_or_create_salt_for_module(module): class VotingToken(models.Model): token = models.CharField( - max_length=40, default=get_token_12, blank=True, editable=False + max_length=40, default=get_token_16, blank=True, editable=False ) token_hash = models.CharField(max_length=128, editable=False, unique=True) module = models.ForeignKey(Module, on_delete=models.CASCADE) @@ -82,7 +83,7 @@ def save(self, *args, **kwargs): if i == 3: raise else: - self.token = get_token_12() + self.token = get_token_16() self.token_hash = self.hash_token(self.token, self.module) else: raise @@ -146,7 +147,9 @@ def get_voting_token_by_hash(token_hash, module): return None def __str__(self): - return "{}-{}-{}".format(self.token[0:4], self.token[4:8], self.token[8:12]) + return "{}-{}-{}-{}".format( + self.token[0:4], self.token[4:8], self.token[8:12], self.token[12:16] + ) class TokenVote(base.TimeStampedModel): diff --git a/meinberlin/apps/votes/tasks.py b/meinberlin/apps/votes/tasks.py index b1f3073e42..dc1c20304e 100644 --- a/meinberlin/apps/votes/tasks.py +++ b/meinberlin/apps/votes/tasks.py @@ -2,7 +2,7 @@ from adhocracy4.modules.models import Module from meinberlin.apps.votes.models import VotingToken -from meinberlin.apps.votes.models import get_token_12 +from meinberlin.apps.votes.models import get_token_16 # Number of tokens to insert into database per bulk_create BATCH_SIZE = int(1e3) @@ -72,7 +72,7 @@ def generate_voting_tokens_batch( def get_token_and_hash(module, package_number): - token = get_token_12() + token = get_token_16() token_hash = VotingToken.hash_token(token, module) return VotingToken( token=token, token_hash=token_hash, module=module, package_number=package_number diff --git a/meinberlin/test/factories/votes.py b/meinberlin/test/factories/votes.py index e390b1bede..7f3eae8842 100644 --- a/meinberlin/test/factories/votes.py +++ b/meinberlin/test/factories/votes.py @@ -12,7 +12,7 @@ class VotingTokenFactory(factory.django.DjangoModelFactory): class Meta: model = voting_models.VotingToken - token = fuzzy.FuzzyText(length=12, chars=string.ascii_letters + string.digits) + token = fuzzy.FuzzyText(length=16, chars=string.ascii_letters + string.digits) module = factory.SubFactory(a4_factories.ModuleFactory) allowed_votes = 5 package_number = 0 diff --git a/tests/votes/test_votes_models.py b/tests/votes/test_votes_models.py index 1df2742dbb..cabc093e97 100644 --- a/tests/votes/test_votes_models.py +++ b/tests/votes/test_votes_models.py @@ -8,8 +8,11 @@ @pytest.mark.django_db def test_voting_token_str(voting_token): voting_token_string = voting_token.__str__() - assert voting_token_string == "{}-{}-{}".format( - voting_token.token[0:4], voting_token.token[4:8], voting_token.token[8:12] + assert voting_token_string == "{}-{}-{}-{}".format( + voting_token.token[0:4], + voting_token.token[4:8], + voting_token.token[8:12], + voting_token.token[12:16], )