From 8c4fd0d3180f1fc8230b75da2b828c9734db1ff0 Mon Sep 17 00:00:00 2001
From: Paul Marillonnet <pmarillonnet@entrouvert.com>
Date: Wed, 5 Oct 2022 09:16:40 +0200
Subject: [PATCH 04/10] add sms code model (#69223)

---
 src/authentic2/migrations/0045_smscode.py | 51 +++++++++++++++++++++++
 src/authentic2/models.py                  | 35 +++++++++++++++-
 2 files changed, 84 insertions(+), 2 deletions(-)
 create mode 100644 src/authentic2/migrations/0045_smscode.py

diff --git a/src/authentic2/migrations/0045_smscode.py b/src/authentic2/migrations/0045_smscode.py
new file mode 100644
index 000000000..0e8f294e4
--- /dev/null
+++ b/src/authentic2/migrations/0045_smscode.py
@@ -0,0 +1,51 @@
+# Generated by Django 2.2.26 on 2022-10-12 14:53
+
+import uuid
+
+from django.db import migrations, models
+
+import authentic2.utils.misc
+import authentic2.validators
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('authentic2', '0044_migrate_phone_field'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='SMSCode',
+            fields=[
+                (
+                    'id',
+                    models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
+                ),
+                (
+                    'value',
+                    models.CharField(
+                        default=authentic2.utils.misc.create_sms_code,
+                        editable=False,
+                        max_length=32,
+                        verbose_name='Identifier',
+                    ),
+                ),
+                ('kind', models.CharField(max_length=32, verbose_name='Kind')),
+                (
+                    'phone',
+                    models.CharField(
+                        blank=True,
+                        max_length=64,
+                        null=True,
+                        validators=[authentic2.validators.PhoneNumberValidator],
+                        verbose_name='phone number',
+                    ),
+                ),
+                ('url_token', models.UUIDField(default=uuid.uuid4, verbose_name='URL token')),
+                ('created', models.DateTimeField(auto_now_add=True, verbose_name='Creation date')),
+                ('expires', models.DateTimeField(verbose_name='Expires')),
+                ('sent', models.BooleanField(default=False, verbose_name='SMS code sent')),
+            ],
+        ),
+    ]
diff --git a/src/authentic2/models.py b/src/authentic2/models.py
index ce9ba9e01..9d761058e 100644
--- a/src/authentic2/models.py
+++ b/src/authentic2/models.py
@@ -42,13 +42,13 @@ from model_utils.managers import QueryManager
 from authentic2.a2_rbac.models import Role
 from authentic2.a2_rbac.utils import get_default_ou_pk
 from authentic2.utils.crypto import base64url_decode, base64url_encode
-from authentic2.validators import HexaColourValidator
+from authentic2.validators import HexaColourValidator, PhoneNumberValidator
 from django_rbac.backends import DjangoRBACBackend
 
 # install our natural_key implementation
 from . import managers
 from . import natural_key as unused_natural_key  # pylint: disable=unused-import
-from .utils.misc import ServiceAccessDenied
+from .utils.misc import ServiceAccessDenied, create_sms_code
 
 
 class UserExternalId(models.Model):
@@ -732,3 +732,34 @@ class APIClient(models.Model):
 
     def get_absolute_url(self):
         return reverse('a2-manager-api-client-detail', kwargs={'pk': self.pk})
+
+
+class SMSCode(models.Model):
+    KIND_REGISTRATION = 'registration'
+    KIND_PASSWORD_LOST = 'password-lost'
+    value = models.CharField(
+        verbose_name=_('Identifier'), default=create_sms_code, editable=False, max_length=32
+    )
+    kind = models.CharField(verbose_name=_('Kind'), max_length=32)
+    phone = models.CharField(
+        _('phone number'), null=True, blank=True, max_length=64, validators=[PhoneNumberValidator]
+    )
+    url_token = models.UUIDField(
+        verbose_name=_('URL token'),
+        default=uuid.uuid4,
+    )
+    created = models.DateTimeField(verbose_name=_('Creation date'), auto_now_add=True)
+    expires = models.DateTimeField(verbose_name=_('Expires'))
+    sent = models.BooleanField(default=False, verbose_name=_('SMS code sent'))
+
+    @classmethod
+    def cleanup(cls, now=None):
+        now = now or timezone.now()
+        cls.objects.filter(expires__lte=now).delete()
+
+    @classmethod
+    def create(cls, phone, kind=None, expires=None, duration=120):
+        if not kind:
+            kind = cls.KIND_REGISTRATION
+        expires = expires or (timezone.now() + datetime.timedelta(seconds=duration))
+        return cls.objects.create(kind=kind, phone=phone, expires=expires)
-- 
2.37.2

