Repository: zestedesavoir/zds-site

Issue:
S'inscrire avec Facebook crée un pseudo sans les lettres accentuées
Si je me m'inscrit à partir de mon compte Facebook dont le nom est « Clémentine Sanpépins », mon pseudo sur Zeste de Savoir sera « ClmentineSanppins ».

C'est le cas en local sur `upstream/dev` à la création de ce ticket. Je ne sais pas si c'est le cas en bêta et en production.


Discussion:

Resolving patch:
diff --git a/zds/member/validators.py b/zds/member/validators.py
--- a/zds/member/validators.py
+++ b/zds/member/validators.py
@@ -4,7 +4,7 @@
 from django.utils.encoding import force_str
 from django.utils.translation import gettext_lazy as _
 
-from zds.utils.misc import contains_utf8mb4
+from zds.utils.misc import contains_utf8mb4, remove_utf8mb4
 from zds.member.models import BannedEmailProvider, Profile
 
 
@@ -70,6 +70,15 @@ def __call__(self, value, check_username_available=True):
 validate_zds_email = ZdSEmailValidator()
 
 
+def clean_username_social_auth(username):
+    """
+    Clean username of accounts created using social auth.
+    """
+    # These three conditions are the same as the first three in the "validate_zds_username" function below.
+    # If you modify one of them here, make sure you do the same there!
+    return remove_utf8mb4(username).replace(",", "").replace("/", "")
+
+
 def validate_zds_username(value, check_username_available=True):
     """
     Check if username is used by another user
@@ -88,6 +97,9 @@ def validate_zds_username(value, check_username_available=True):
     msg = None
     user_count = User.objects.filter(username=value).count()
     skeleton_user_count = Profile.objects.filter(username_skeleton=Profile.find_username_skeleton(value)).count()
+
+    # These first three conditions are the same as those in the "clean_username_social_auth" function above.
+    # If you modify one of them here, make sure you do the same there!
     if "," in value:
         msg = _("Le nom d'utilisateur ne peut contenir de virgules")
     elif "/" in value:
diff --git a/zds/settings/abstract_base/requirements.py b/zds/settings/abstract_base/requirements.py
--- a/zds/settings/abstract_base/requirements.py
+++ b/zds/settings/abstract_base/requirements.py
@@ -10,9 +10,12 @@
 
 social_auth_config = config.get("social_auth", {})
 
+SOCIAL_AUTH_CLEAN_USERNAME_FUNCTION = "zds.member.validators.clean_username_social_auth"
+
 SOCIAL_AUTH_RAISE_EXCEPTIONS = False
 
 SOCIAL_AUTH_FACEBOOK_SCOPE = ["email"]
+SOCIAL_AUTH_FACEBOOK_PROFILE_EXTRA_PARAMS = {"fields": "name,email"}
 
 SOCIAL_AUTH_PIPELINE = (
     "social_core.pipeline.social_auth.social_details",
diff --git a/zds/utils/misc.py b/zds/utils/misc.py
--- a/zds/utils/misc.py
+++ b/zds/utils/misc.py
@@ -49,14 +49,21 @@ def convert_camel_to_underscore(camel_case):
     return re.sub("([a-z0-9])([A-Z])", r"\1_\2", s1).lower()
 
 
-def contains_utf8mb4(s):
+def remove_utf8mb4(s):
     """
-    This string contains at least one character of more than 3 bytes
+    Remove characters of more than 3 bytes.
     """
     if not isinstance(s, str):
         s = str(s, "utf-8")
     re_pattern = re.compile("[^\u0000-\uD7FF\uE000-\uFFFF]", re.UNICODE)
-    return s != re_pattern.sub("\uFFFD", s)
+    return re_pattern.sub("", s)
+
+
+def contains_utf8mb4(s):
+    """
+    Check if this string contains at least one character of more than 3 bytes.
+    """
+    return s != remove_utf8mb4(s)
 
 
 def check_essential_accounts():


Test patch:
diff --git a/zds/utils/tests/test_misc.py b/zds/utils/tests/test_misc.py
--- a/zds/utils/tests/test_misc.py
+++ b/zds/utils/tests/test_misc.py
@@ -3,13 +3,21 @@
 from django.test import TestCase
 from zds.member.tests.factories import ProfileFactory, StaffProfileFactory, UserFactory
 from zds.tutorialv2.tests.factories import PublishedContentFactory
-from zds.utils.misc import contains_utf8mb4, check_essential_accounts
+from zds.utils.misc import contains_utf8mb4, check_essential_accounts, remove_utf8mb4
 from zds.utils.models import Alert
 from zds.utils.context_processor import get_header_notifications
 
 
 class Misc(TestCase):
-    def test_utf8mb4(self):
+    def test_remove_utf8mb4(self):
+        self.assertEqual("abc", remove_utf8mb4("abc"))
+        self.assertEqual("abc", remove_utf8mb4("abc"))
+        self.assertEqual("abc€", remove_utf8mb4("abc€"))
+        self.assertEqual("abc€", remove_utf8mb4("abc€"))
+        self.assertEqual("atbc€", remove_utf8mb4("a🐙tbc€"))
+        self.assertEqual("atbc€", remove_utf8mb4("a🐙tbc€"))
+
+    def test_contains_utf8mb4(self):
         self.assertFalse(contains_utf8mb4("abc"))
         self.assertFalse(contains_utf8mb4("abc"))
         self.assertFalse(contains_utf8mb4("abc€"))