Browse Source

feat(api): introduce Domain renewal fields and corresponding actions

Peter Thomassen 5 năm trước cách đây
mục cha
commit
469568bf1a

+ 62 - 0
api/desecapi/migrations/0016_domain_renewal.py

@@ -0,0 +1,62 @@
+# Generated by Django 3.0.6 on 2020-06-29 19:15
+
+import desecapi.models
+import django.core.validators
+from django.db import migrations, models
+import django.db.models.deletion
+import re
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('desecapi', '0015_rrset_touched_auto_now'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='AuthenticatedBasicUserAction',
+            fields=[
+                ('authenticatedaction_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='desecapi.AuthenticatedAction')),
+            ],
+            options={
+                'managed': False,
+            },
+            bases=('desecapi.authenticatedaction',),
+        ),
+        migrations.AddField(
+            model_name='domain',
+            name='renewal_changed',
+            field=models.DateTimeField(auto_now_add=True),
+        ),
+        migrations.AddField(
+            model_name='domain',
+            name='renewal_state',
+            field=models.IntegerField(choices=[(1, 'Fresh'), (2, 'Notified'), (3, 'Warned')], default=1),
+        ),
+        migrations.AlterField(
+            model_name='domain',
+            name='name',
+            field=models.CharField(max_length=191, unique=True, validators=[desecapi.models.validate_lower, django.core.validators.RegexValidator(code='invalid_domain_name', flags=re.RegexFlag['IGNORECASE'], message='Domain names must be labels separated by dots. Labels may consist of up to 63 letters, digits, hyphens, and underscores. The last label may only contain letters.', regex='^(([a-z0-9_-]{1,63})\\.)*[a-z]{1,63}$')]),
+        ),
+        migrations.CreateModel(
+            name='AuthenticatedDomainBasicUserAction',
+            fields=[
+                ('authenticatedbasicuseraction_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='desecapi.AuthenticatedBasicUserAction')),
+            ],
+            options={
+                'managed': False,
+            },
+            bases=('desecapi.authenticatedbasicuseraction',),
+        ),
+        migrations.CreateModel(
+            name='AuthenticatedRenewDomainBasicUserAction',
+            fields=[
+                ('authenticateddomainbasicuseraction_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='desecapi.AuthenticatedDomainBasicUserAction')),
+            ],
+            options={
+                'managed': False,
+            },
+            bases=('desecapi.authenticateddomainbasicuseraction',),
+        ),
+    ]

+ 42 - 0
api/desecapi/models.py

@@ -210,6 +210,12 @@ def get_minimum_ttl_default():
 
 
 class Domain(ExportModelOperationsMixin('Domain'), models.Model):
+
+    class RenewalState(models.IntegerChoices):
+        FRESH = 1
+        NOTIFIED = 2
+        WARNED = 3
+
     created = models.DateTimeField(auto_now_add=True)
     name = models.CharField(max_length=191,
                             unique=True,
@@ -217,6 +223,8 @@ class Domain(ExportModelOperationsMixin('Domain'), models.Model):
     owner = models.ForeignKey(User, on_delete=models.PROTECT, related_name='domains')
     published = models.DateTimeField(null=True, blank=True)
     minimum_ttl = models.PositiveIntegerField(default=get_minimum_ttl_default)
+    renewal_state = models.IntegerField(choices=RenewalState.choices, default=RenewalState.FRESH)
+    renewal_changed = models.DateTimeField(auto_now_add=True)
     _keys = None
 
     @cached_property
@@ -654,6 +662,40 @@ class AuthenticatedDeleteUserAction(AuthenticatedUserAction):
         self.user.delete()
 
 
+class AuthenticatedDomainBasicUserAction(AuthenticatedBasicUserAction):
+    """
+    Abstract AuthenticatedUserAction involving an domain instance, incorporating the domain's id, name as well as the
+    owner ID into the Message Authentication Code state.
+    """
+    domain = models.ForeignKey(Domain, on_delete=models.DO_NOTHING)
+
+    class Meta:
+        managed = False
+
+    @property
+    def _state_fields(self):
+        return super()._state_fields + [
+            str(self.domain.id),  # ensures the domain object is identical
+            self.domain.name,  # exclude renamed domains
+            str(self.domain.owner.id),  # exclude transferred domains
+        ]
+
+
+class AuthenticatedRenewDomainBasicUserAction(AuthenticatedDomainBasicUserAction):
+
+    class Meta:
+        managed = False
+
+    @property
+    def _state_fields(self):
+        return super()._state_fields + [str(self.domain.renewal_changed)]
+
+    def _act(self):
+        self.domain.renewal_state = Domain.RenewalState.FRESH
+        self.domain.renewal_changed = timezone.now()
+        self.domain.save(update_fields=['renewal_state', 'renewal_changed'])
+
+
 def captcha_default_content():
     alphabet = (string.ascii_uppercase + string.digits).translate({ord(c): None for c in 'IO0'})
     content = ''.join([secrets.choice(alphabet) for _ in range(5)])

+ 17 - 0
api/desecapi/serializers.py

@@ -758,3 +758,20 @@ class AuthenticatedDeleteUserActionSerializer(AuthenticatedBasicUserActionSerial
 
     class Meta(AuthenticatedBasicUserActionSerializer.Meta):
         model = models.AuthenticatedDeleteUserAction
+
+
+class AuthenticatedDomainBasicUserActionSerializer(AuthenticatedBasicUserActionSerializer):
+    domain = serializers.PrimaryKeyRelatedField(
+        queryset=models.Domain.objects.all(),
+        error_messages={'does_not_exist': 'This domain does not exist.'},
+    )
+
+    class Meta:
+        model = models.AuthenticatedDomainBasicUserAction
+        fields = AuthenticatedBasicUserActionSerializer.Meta.fields + ('domain',)
+
+
+class AuthenticatedRenewDomainBasicUserActionSerializer(AuthenticatedDomainBasicUserActionSerializer):
+
+    class Meta(AuthenticatedDomainBasicUserActionSerializer.Meta):
+        model = models.AuthenticatedRenewDomainBasicUserAction