From aa08b8e9f9d5a459f67b010f7fe9e2b4d42889ac Mon Sep 17 00:00:00 2001
From: Paul Marillonnet <pmarillonnet@entrouvert.com>
Date: Wed, 8 Jun 2022 13:07:55 +0200
Subject: [PATCH 2/2] csv: set verification source for user attributes (#66053)

---
 src/authentic2/csv_import.py | 31 +++++++++++++++++++++++++------
 tests/test_csv_import.py     | 34 +++++++++++++++++++++++++++++++++-
 2 files changed, 58 insertions(+), 7 deletions(-)

diff --git a/src/authentic2/csv_import.py b/src/authentic2/csv_import.py
index c650ffdfc..3ed4b3d37 100644
--- a/src/authentic2/csv_import.py
+++ b/src/authentic2/csv_import.py
@@ -739,12 +739,31 @@ class UserCsvImporter:
                 row.action == 'update' and cell.header.update
             ):
                 attributes = user.attributes
-                if cell.header.verified:
-                    attributes = user.verified_attributes
-                if getattr(attributes, cell.header.name) != cell.value:
-                    setattr(attributes, cell.header.name, cell.value)
-                    cell.action = 'updated'
-                    continue
+                dummy = AttributeValue.objects.with_owner(user).select_for_update()
+                updated = False
+
+                with atomic():
+                    if cell.header.verified:
+                        attributes = user.verified_attributes
+                        is_known_unverified_value = False
+                        unverified_value = cell.header.attribute.get_value(user, verified=False)
+                        if unverified_value:
+                            is_known_unverified_value = (
+                                cell.value in unverified_value
+                                if cell.header.attribute.multiple
+                                else cell.value == unverified_value
+                            )
+                        if getattr(attributes, cell.header.name) != cell.value or is_known_unverified_value:
+                            attributes._set_sourced_attr(cell.header.name, cell.value, 'csv')
+                        updated = True
+                    else:
+                        # TODO remove only 'csv' among verification sources
+                        setattr(attributes, cell.header.name, cell.value)
+                        updated = True
+
+                    if updated:
+                        cell.action = 'updated'
+                        continue
             cell.action = 'nothing'
 
         for cell in row.cells:
diff --git a/tests/test_csv_import.py b/tests/test_csv_import.py
index bebd19df1..eba4e6f74 100644
--- a/tests/test_csv_import.py
+++ b/tests/test_csv_import.py
@@ -27,7 +27,7 @@ from authentic2.a2_rbac.models import Role
 from authentic2.a2_rbac.utils import get_default_ou
 from authentic2.csv_import import CsvHeader, CsvImporter, Error, LineError, UserCsvImporter
 from authentic2.custom_user.models import User
-from authentic2.models import Attribute, PasswordReset
+from authentic2.models import Attribute, AttributeValue, PasswordReset
 
 ENCODINGS = [
     'iso-8859-1',
@@ -701,3 +701,35 @@ john.doe@example.com,John,Doe2'''
     user2.refresh_from_db()
     assert user1.last_name == 'Doe'
     assert user2.last_name == 'Doe2'
+
+
+def test_user_attributes_verified(db, profile, user_csv_importer_factory):
+    content = '''email key,first_name,last_name,phone verified
+jdoe@nowhere.null,John,Doe,1234
+jsmith@nowhere.null,Jimmy,Smith,5678'''
+    importer = user_csv_importer_factory(content)
+    phone = Attribute.objects.get(name='phone')
+
+    assert importer.run(), importer.errors
+    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, verified=True),
+    ]
+
+    jdoe = User.objects.get(email='jdoe@nowhere.null')
+    assert jdoe.verified_attributes.phone == '1234'
+
+    jsmith = User.objects.get(email='jsmith@nowhere.null')
+    assert jsmith.verified_attributes.phone == '5678'
+
+    for user in (
+        jdoe,
+        jsmith,
+    ):
+        attribute = Attribute.objects.get(name='phone')
+        av = AttributeValue.objects.with_owner(user).get(attribute=attribute)
+        assert av.verified
+        assert av.verification_sources == ['csv']
+        assert av.last_verified_on
-- 
2.38.1

