diff --git a/arkindex/users/api.py b/arkindex/users/api.py index f64a62f200982490e17ffb4d7cc5168f98d68b36..56173b07968522350c13e6ba17d1712d83c8a634 100644 --- a/arkindex/users/api.py +++ b/arkindex/users/api.py @@ -163,6 +163,9 @@ class UserCreate(CreateAPIView): serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) + if User.objects.filter(email=serializer.validated_data['email']).exists(): + raise ValidationError({'email': ['An user with this email address already exists.']}) + user = serializer.save() login(self.request, user) diff --git a/arkindex/users/migrations/0016_alter_user_email_user_email_unique.py b/arkindex/users/migrations/0016_alter_user_email_user_email_unique.py new file mode 100644 index 0000000000000000000000000000000000000000..a92635ff8773b791136df9b55e88d9e4bbe22ff0 --- /dev/null +++ b/arkindex/users/migrations/0016_alter_user_email_user_email_unique.py @@ -0,0 +1,34 @@ +# Generated by Django 4.0.2 on 2022-02-28 11:36 + +from django.contrib.postgres.operations import CreateCollation +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0015_oauthcredentials_provider_choices'), + ] + + operations = [ + migrations.AlterField( + model_name='user', + name='email', + field=models.EmailField(max_length=255, verbose_name='email address'), + ), + migrations.AddConstraint( + model_name='user', + constraint=models.UniqueConstraint(fields=('email',), name='email_unique'), + ), + CreateCollation( + 'case_insensitive', + provider='icu', + locale='und-u-ks-level2', + deterministic=False, + ), + migrations.AlterField( + model_name='user', + name='email', + field=models.EmailField(db_collation='case_insensitive', max_length=255, verbose_name='email address'), + ), + ] diff --git a/arkindex/users/models.py b/arkindex/users/models.py index cd4707ebd1805dcf66573088cc277e30c92f3a1c..704a972de6ff3b68147aeef01127e3a35bb1859a 100644 --- a/arkindex/users/models.py +++ b/arkindex/users/models.py @@ -60,7 +60,7 @@ class User(AbstractBaseUser): email = models.EmailField( verbose_name='email address', max_length=255, - unique=True, + db_collation='case_insensitive', ) display_name = models.CharField(max_length=120) transkribus_email = models.EmailField( @@ -87,6 +87,11 @@ class User(AbstractBaseUser): USERNAME_FIELD = 'email' REQUIRED_FIELDS = ['display_name', ] + class Meta: + constraints = [ + models.UniqueConstraint(fields=['email'], name='email_unique'), + ] + def __str__(self): return self.email diff --git a/arkindex/users/tests/test_generic_memberships.py b/arkindex/users/tests/test_generic_memberships.py index 59eb38910bc06f0a3c03651c952c7b71e9146e05..36413d08978e6ab9974d416994aae93abf6f121a 100644 --- a/arkindex/users/tests/test_generic_memberships.py +++ b/arkindex/users/tests/test_generic_memberships.py @@ -578,7 +578,7 @@ class TestMembership(FixtureAPITestCase): self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertDictEqual(response.json(), {'detail': 'Authentication credentials were not provided.'}) - def test_add_member_requires_verfified(self): + def test_add_member_requires_verified(self): self.client.force_login(self.unverified) with self.assertNumQueries(2): response = self.client.post(reverse('api:membership-create'), { @@ -736,6 +736,32 @@ class TestMembership(FixtureAPITestCase): 'group_id': None, }) + def test_add_member_by_email_uppercase_letters(self): + """ + Adds a new member referenced by its email + Asserts the endpoint is case insensitive for the email + """ + user = User.objects.create_user('test@test.de', 'Pa$$w0rd') + self.client.force_login(self.user) + with self.assertNumQueries(8): + response = self.client.post(reverse('api:membership-create'), { + 'level': 10, + # Introducing uppercase letters in the user's email + 'user_email': 'tEsT@TeSt.DE', + 'content_type': 'group', + 'content_id': str(self.group.id) + }) + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + membership = user.rights.get(group_target=self.group) + self.assertDictEqual(response.json(), { + 'id': str(membership.id), + 'content_id': str(self.group.id), + 'content_type': 'group', + 'level': 10, + 'user_email': user.email, + 'group_id': None, + }) + def test_add_member_superuser(self): """ A superuser is able to add a member to a groups he has no right on diff --git a/arkindex/users/tests/test_registration.py b/arkindex/users/tests/test_registration.py index 66fe654480771aaa3adf27e8a20c392ddca920c3..a910d494389ab9ae1220172c9683b0c0fef2af14 100644 --- a/arkindex/users/tests/test_registration.py +++ b/arkindex/users/tests/test_registration.py @@ -94,6 +94,19 @@ class TestRegistration(FixtureAPITestCase): self.assertDictEqual(response.json(), {'detail': 'Incorrect authentication credentials.'}) self.assertFalse(auth.get_user(self.client).is_authenticated) + def test_login_email_uppercase_letters(self): + """ + Asserts the endpoint is case insensitive for the email + """ + response = self.client.post( + reverse('api:user-login'), + data={'email': 'eMaIL@adDreSs.cOm', 'password': 'P4$5w0Rd'}, + format='json', + ) + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertTrue(auth.get_user(self.client).is_authenticated) + self.assertEqual(auth.get_user(self.client).email, 'email@address.com') + def test_login(self): response = self.client.post( reverse('api:user-login'), @@ -263,3 +276,25 @@ class TestRegistration(FixtureAPITestCase): ) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertListEqual(response.json(), ['Registration has been disabled on this instance.']) + + def test_register_existing_user(self): + email = 'email@address.com' + self.assertTrue(User.objects.filter(email=email).exists()) + response = self.client.post( + reverse('api:user-new'), + data={'display_name': 'New user', 'email': email, 'password': 'myVerySecretPassword'}, + format='json', + ) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertDictEqual(response.json(), {'email': ['An user with this email address already exists.']}) + + # Asserts that the check is insensitive on the email field + email = 'eMaIL@adDreSs.cOm' + self.assertTrue(User.objects.filter(email=email).exists()) + response = self.client.post( + reverse('api:user-new'), + data={'display_name': 'New user', 'email': email, 'password': 'myVerySecretPassword'}, + format='json', + ) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertDictEqual(response.json(), {'email': ['An user with this email address already exists.']})