[WIP] phone attribution verification support (#66053)
gitea/authentic/pipeline/head Something is wrong with the build of this commit
Details
gitea/authentic/pipeline/head Something is wrong with the build of this commit
Details
This commit is contained in:
parent
e9de5a8e29
commit
1d89b679a1
|
@ -28,7 +28,6 @@ from django.core.validators import RegexValidator
|
|||
from django.db import IntegrityError, models
|
||||
from django.db.transaction import atomic
|
||||
from django.utils.encoding import force_bytes, force_str
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from authentic2 import app_settings
|
||||
|
@ -500,6 +499,7 @@ class UserCsvImporter:
|
|||
header.globally_unique = True
|
||||
except FieldDoesNotExist:
|
||||
pass
|
||||
|
||||
if not header.field:
|
||||
try:
|
||||
header.attribute = Attribute.objects.get(name=header.name)
|
||||
|
@ -737,6 +737,8 @@ class UserCsvImporter:
|
|||
if not user:
|
||||
user = User(ou=self.ou)
|
||||
user.set_random_password()
|
||||
# we need user to have an id in order for its potential verified attributes to be set
|
||||
user.save()
|
||||
|
||||
for cell in row.cells:
|
||||
if not cell.header.field:
|
||||
|
@ -749,7 +751,8 @@ class UserCsvImporter:
|
|||
if cell.header.name == 'email' and cell.header.verified:
|
||||
user.set_email_verified(True, source='csv')
|
||||
if cell.header.name == 'phone' and cell.header.verified:
|
||||
user.phone_verified_on = now()
|
||||
user.verified_attributes.phone = cell.value
|
||||
user.set_phone_verified(True, source='csv')
|
||||
cell.action = 'updated'
|
||||
continue
|
||||
cell.action = 'nothing'
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
# Generated by Django 2.2.26 on 2023-01-25 08:08
|
||||
|
||||
import django.contrib.postgres.fields
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('custom_user', '0034_user_email_verified_sources'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='phone_verified_sources',
|
||||
field=django.contrib.postgres.fields.ArrayField(
|
||||
base_field=models.CharField(max_length=63),
|
||||
blank=True,
|
||||
null=True,
|
||||
size=None,
|
||||
verbose_name='phone verification sources',
|
||||
),
|
||||
),
|
||||
]
|
|
@ -205,6 +205,13 @@ class User(AbstractBaseUser):
|
|||
default=None,
|
||||
verbose_name=_('phone verification date'),
|
||||
)
|
||||
phone_verified_sources = ArrayField(
|
||||
verbose_name=_('phone verification sources'),
|
||||
base_field=models.CharField(max_length=63),
|
||||
null=True,
|
||||
blank=True,
|
||||
)
|
||||
|
||||
is_staff = models.BooleanField(
|
||||
_('staff status'),
|
||||
default=False,
|
||||
|
@ -625,12 +632,34 @@ class User(AbstractBaseUser):
|
|||
self.email_verified_date = timezone.now()
|
||||
if source and source not in self.email_verified_sources:
|
||||
self.email_verified_sources.append(source)
|
||||
Attribute.add_verification_source(self, 'email', self.email, source)
|
||||
else:
|
||||
if source and source in self.email_verified_sources:
|
||||
self.email_verified_sources.remove(source)
|
||||
if not source or not self.email_verified_sources:
|
||||
self.email_verified = False
|
||||
self.email_verified_date = None
|
||||
Attribute.remove_verification_source(self, 'email', self.email, source)
|
||||
self.save()
|
||||
|
||||
def set_phone_verified(self, value, source=None):
|
||||
if self.phone_verified_sources is None:
|
||||
self.phone_verified_sources = []
|
||||
if bool(value):
|
||||
if isinstance(value, datetime.datetime):
|
||||
self.phone_verified_on = value
|
||||
else:
|
||||
self.phone_verified_on = timezone.now()
|
||||
if source and source not in self.phone_verified_sources:
|
||||
self.phone_verified_sources.append(source)
|
||||
Attribute.add_verification_source(self, 'phone', self.phone, source)
|
||||
else:
|
||||
if source and source in self.phone_verified_sources:
|
||||
self.phone_verified_sources.remove(source)
|
||||
if not source or not self.phone_verified_sources:
|
||||
self.phone_verified_on = None
|
||||
Attribute.remove_verification_source(self, 'phone', self.phone, source)
|
||||
self.save()
|
||||
|
||||
|
||||
class DeletedUser(models.Model):
|
||||
|
|
|
@ -281,6 +281,38 @@ class Attribute(models.Model):
|
|||
av.last_verified_on = timezone.now()
|
||||
av.save()
|
||||
|
||||
@classmethod
|
||||
def remove_verification_source(cls, owner, attribute_name, value, source):
|
||||
'''
|
||||
Remove verification source for an existing unitary attribute value,
|
||||
typically the user's first_name and last_name extended attributes.
|
||||
'''
|
||||
attribute = None
|
||||
try:
|
||||
attribute = Attribute.objects.get(name=attribute_name)
|
||||
except Attribute.DoesNotExist:
|
||||
pass
|
||||
if not attribute:
|
||||
# caller should have checked that the attribute object has been created, silently failing.
|
||||
return
|
||||
|
||||
avs = AttributeValue.objects.with_owner(owner).select_for_update()
|
||||
with transaction.atomic():
|
||||
try:
|
||||
av = avs.get(attribute=attribute, content=value)
|
||||
except AttributeValue.DoesNotExist:
|
||||
# caller should have checked that the attribute value is defined, silently failing
|
||||
# to add source.
|
||||
pass
|
||||
else:
|
||||
sources = av.verification_sources or []
|
||||
if source in sources:
|
||||
sources.remove(source)
|
||||
av.verification_sources = sources
|
||||
if not sources:
|
||||
av.last_verified_on = None
|
||||
av.save()
|
||||
|
||||
def set_value(self, owner, value, verified=False, attribute_value=None, verification_source=None):
|
||||
serialize = self.get_kind()['serialize']
|
||||
# setting to None is to delete
|
||||
|
|
|
@ -66,6 +66,7 @@ USER_ATTRIBUTES_SET = {
|
|||
'email_verified_sources',
|
||||
'phone',
|
||||
'phone_verified_on',
|
||||
'phone_verified_sources',
|
||||
'last_account_deletion_alert',
|
||||
'deactivation',
|
||||
'deactivation_reason',
|
||||
|
|
|
@ -76,6 +76,7 @@ class SerializerTests(TestCase):
|
|||
'email': '',
|
||||
'phone': None,
|
||||
'phone_verified_on': None,
|
||||
'phone_verified_sources': None,
|
||||
'first_name': '',
|
||||
'last_name': '',
|
||||
'is_active': True,
|
||||
|
|
|
@ -181,14 +181,13 @@ tnoel@entrouvert.com,Thomas,Noël,0123456789
|
|||
fpeters@entrouvert.com,Frédéric,Péters,+3281005678
|
||||
x,x,x,x'''
|
||||
importer = user_csv_importer_factory(content)
|
||||
phone = Attribute.objects.get(name='phone')
|
||||
|
||||
importer.run()
|
||||
assert importer.headers == [
|
||||
CsvHeader(1, 'email', field=True, key=True, verified=True),
|
||||
CsvHeader(2, 'first_name', field=True),
|
||||
CsvHeader(3, 'last_name', field=True),
|
||||
CsvHeader(4, 'phone', attribute=phone),
|
||||
CsvHeader(4, 'phone', field=True),
|
||||
]
|
||||
assert importer.has_errors
|
||||
assert len(importer.rows) == 3
|
||||
|
@ -234,14 +233,13 @@ tnoel@entrouvert.com,Thomas,Noël,0123456789
|
|||
fpeters@entrouvert.com,Frédéric,Péters,+3281005678
|
||||
x,x,x,x'''
|
||||
importer = user_csv_importer_factory(content)
|
||||
phone = Attribute.objects.get(name='phone')
|
||||
|
||||
assert not importer.run(simulate=True)
|
||||
assert importer.headers == [
|
||||
CsvHeader(1, 'email', field=True, key=True, verified=True),
|
||||
CsvHeader(2, 'first_name', field=True),
|
||||
CsvHeader(3, 'last_name', field=True),
|
||||
CsvHeader(4, 'phone', attribute=phone),
|
||||
CsvHeader(4, 'phone', field=True),
|
||||
]
|
||||
assert importer.has_errors
|
||||
assert len(importer.rows) == 3
|
||||
|
@ -270,7 +268,7 @@ tnoel@entrouvert.com,Thomas,Noël,0123456789'''
|
|||
user.attributes.phone = '+33123456789'
|
||||
|
||||
assert not importer.run()
|
||||
assert importer.has_error
|
||||
assert importer.all_errors
|
||||
assert importer.created == 0
|
||||
assert importer.updated == 0
|
||||
assert len(importer.rows) == 1
|
||||
|
@ -418,7 +416,6 @@ app1,1,tnoel@entrouvert.com,Thomas,Noël,0606060606
|
|||
app1,2,tnoel@entrouvert.com,Thomas,Noël,0606060606
|
||||
'''
|
||||
importer = user_csv_importer_factory(content)
|
||||
phone = Attribute.objects.get(name='phone')
|
||||
|
||||
assert importer.run(), importer.all_errors
|
||||
assert importer.headers == [
|
||||
|
@ -427,7 +424,7 @@ app1,2,tnoel@entrouvert.com,Thomas,Noël,0606060606
|
|||
CsvHeader(3, 'email', field=True, verified=True),
|
||||
CsvHeader(4, 'first_name', field=True),
|
||||
CsvHeader(5, 'last_name', field=True),
|
||||
CsvHeader(6, 'phone', attribute=phone),
|
||||
CsvHeader(6, 'phone', field=True),
|
||||
]
|
||||
assert not importer.has_errors
|
||||
assert len(importer.rows) == 2
|
||||
|
@ -745,10 +742,10 @@ jsmith@nowhere.null,Jimmy,Smith,0202020202'''
|
|||
]
|
||||
|
||||
jdoe = User.objects.get(email='jdoe@nowhere.null')
|
||||
assert jdoe.verified_attributes.phone == '0101010101'
|
||||
assert jdoe.verified_attributes.phone == '+33101010101'
|
||||
|
||||
jsmith = User.objects.get(email='jsmith@nowhere.null')
|
||||
assert jsmith.verified_attributes.phone == '0202020202'
|
||||
assert jsmith.verified_attributes.phone == '+33202020202'
|
||||
|
||||
for user in (
|
||||
jdoe,
|
||||
|
|
Loading…
Reference in New Issue