Skip to content
Snippets Groups Projects
test_registration.py 14.73 KiB
import urllib.parse
from unittest.mock import call, patch

from django.contrib import auth
from django.contrib.auth.tokens import default_token_generator
from django.core import mail
from django.test import override_settings
from django.urls import reverse
from rest_framework import status

from arkindex.documents.models import Corpus
from arkindex.project.default_corpus import DEFAULT_CORPUS_TYPES
from arkindex.project.tests import FixtureAPITestCase
from arkindex.users.models import Group, Right, Role, Scope, User


class TestRegistration(FixtureAPITestCase):

    @classmethod
    def setUpTestData(cls):
        cls.user = User.objects.create_user("email@address.com", "P4$5w0Rd")

    def test_retrieve_requires_login(self):
        response = self.client.get(reverse("api:user-retrieve"))
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
        # Feature flags should be included
        self.assertIsInstance(response.json().get("features"), dict)

    def test_update_requires_login(self):
        response = self.client.put(
            reverse("api:user-retrieve"),
            data={"password": "N€wP4$5w0Rd"},
            format="json",
        )
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)

    def test_logout_requires_login(self):
        response = self.client.delete(reverse("api:user-retrieve"))
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)

    def test_retrieve(self):
        self.client.force_login(self.user)
        response = self.client.get(reverse("api:user-retrieve"))
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        data = response.json()
        self.assertEqual(data["email"], "email@address.com")
        self.assertEqual(data["is_admin"], False)
        self.assertNotIn("password", data)

    def test_update_password(self):
        self.client.force_login(self.user)
        response = self.client.patch(
            reverse("api:user-retrieve"),
            data={"password": "N€wP4$5w0Rd"},
            format="json",
        )
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.user.refresh_from_db()
        self.assertTrue(self.user.check_password("N€wP4$5w0Rd"))

    def test_update_display_name(self):
        self.client.force_login(self.user)
        response = self.client.patch(
            reverse("api:user-retrieve"),
            data={"display_name": "There was a typo in my name"},
            format="json",
        )
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.user.refresh_from_db()
        self.assertEqual(self.user.display_name, "There was a typo in my name")

    def test_logout(self):
        self.client.force_login(self.user)
        response = self.client.delete(reverse("api:user-retrieve"))
        self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
        self.assertFalse(auth.get_user(self.client).is_authenticated)

    def test_login_wrong_email(self):
        response = self.client.post(
            reverse("api:user-login"),
            data={"email": "lol@lol.lol", "password": "password"},
            format="json",
        )
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
        self.assertDictEqual(response.json(), {"detail": "Incorrect authentication credentials."})
        self.assertFalse(auth.get_user(self.client).is_authenticated)

    def test_login_wrong_password(self):
        response = self.client.post(
            reverse("api:user-login"),
            data={"email": "email@address.com", "password": "wrongpassword"},
            format="json",
        )
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
        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"),
            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")

    @patch("arkindex.users.managers.BaseACLManager.filter_rights", return_value=Corpus.objects.none())
    def test_email_verification(self, filter_rights_mock):
        newuser = User.objects.create_user("newuser@example.com", password="hunter2")
        self.assertFalse(newuser.verified_email)
        self.assertTrue(newuser.has_usable_password())
        corpora = list(Corpus.objects.all().values_list("id", flat=True))

        response = self.client.get("{}?{}".format(
            reverse("api:user-token"),
            urllib.parse.urlencode({
                "email": "newuser@example.com",
                "token": default_token_generator.make_token(newuser),
            })),
        )
        self.assertEqual(response.status_code, status.HTTP_302_FOUND)

        newuser.refresh_from_db()
        self.assertTrue(newuser.verified_email)

        self.assertEqual(filter_rights_mock.call_count, 1)
        self.assertEqual(filter_rights_mock.call_args, call(newuser, Corpus, Role.Admin.value))

        # Assert a trial corpus is created for the new user
        new_corpus = Corpus.objects.exclude(id__in=corpora).get()
        self.assertEqual(new_corpus.name, "My Project")
        self.assertEqual(new_corpus.description, "Project for newuser@example.com")
        membership = new_corpus.memberships.get()
        self.assertEqual(membership.user, newuser)
        self.assertEqual(membership.level, Role.Admin.value)
        # Assert defaults types are set on the new corpus
        self.assertCountEqual(
            list(new_corpus.types.values(
                "slug",
                "display_name",
                "folder",
                "color",
            )),
            [{
                "folder": False,
                **values
            } for values in DEFAULT_CORPUS_TYPES]
        )

        self.assertCountEqual(
            list(newuser.user_scopes.values_list("scope", flat=True)),
            [Scope.CreateIIIFImage, Scope.UploadS3Image],
        )

    def test_email_verification_default_group(self):
        """
        If settings.SIGNUP_DEFAULT_GROUP is defined, then upon email verification the user
        is added to this group
        """
        test_group = Group.objects.first()
        newuser = User.objects.create_user("newuser@example.com", password="hunter2")
        self.assertFalse(newuser.verified_email)

        with (self.settings(SIGNUP_DEFAULT_GROUP=str(test_group.id)), self.assertNumQueries(15)):
            response = self.client.get("{}?{}".format(
                reverse("api:user-token"),
                urllib.parse.urlencode({
                    "email": "newuser@example.com",
                    "token": default_token_generator.make_token(newuser),
                })),
            )
            self.assertEqual(response.status_code, status.HTTP_302_FOUND)

        newuser.refresh_from_db()
        self.assertTrue(newuser.verified_email)
        self.assertTrue(Right.objects.filter(user=newuser, content_id=test_group.id, level=Role.Guest.value).exists())

    def test_email_verification_default_group_doesnt_exist(self):
        newuser = User.objects.create_user("newuser@example.com", password="hunter2")
        self.assertFalse(newuser.verified_email)

        with (
            self.settings(SIGNUP_DEFAULT_GROUP="aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"),
            self.assertNumQueries(10),
            self.assertRaises(Group.DoesNotExist)
        ):
            self.client.get("{}?{}".format(
                reverse("api:user-token"),
                urllib.parse.urlencode({
                    "email": "newuser@example.com",
                    "token": default_token_generator.make_token(newuser),
                })),
            )

    def test_email_verification_no_password(self):
        # A user with a verified email but no usable password may not be granted a trial corpus
        newuser = User.objects.create_user("newuser@example.com")
        self.assertFalse(newuser.verified_email)
        self.assertFalse(newuser.has_usable_password())
        corpora = Corpus.objects.all().values_list("id", flat=True)

        response = self.client.get("{}?{}".format(
            reverse("api:user-token"),
            urllib.parse.urlencode({
                "email": "newuser@example.com",
                "token": default_token_generator.make_token(newuser),
            })),
        )
        self.assertEqual(response.status_code, status.HTTP_302_FOUND)
        newuser.refresh_from_db()
        self.assertTrue(newuser.verified_email)
        self.assertFalse(Corpus.objects.exclude(id__in=corpora).exists())

    def test_email_verification_already_admin(self):
        user = User.objects.get(email="user@user.fr")
        self.assertTrue(user.verified_email)
        self.assertEqual(user.rights.filter(level__gte=Role.Admin.value).exists(), True)
        corpora = list(Corpus.objects.all().values_list("id", flat=True))

        response = self.client.get("{}?{}".format(
            reverse("api:user-token"),
            urllib.parse.urlencode({
                "email": "user@user.fr",
                "token": default_token_generator.make_token(user),
            })),
        )
        self.assertEqual(response.status_code, status.HTTP_302_FOUND)
        # Assert no trial corpus is created
        new_corpus = Corpus.objects.exclude(id__in=corpora)
        self.assertFalse(new_corpus.exists())

    def test_email_verification_no_args(self):
        response = self.client.get(reverse("api:user-token"))
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)

    def test_create_no_password(self):
        response = self.client.post(
            reverse("api:user-new"),
            data={"display_name": "New user", "email": "newuser@example.com"},
            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, "newuser@example.com")
        user = User.objects.get_by_natural_key("newuser@example.com")
        self.assertFalse(user.verified_email)
        self.assertFalse(user.user_scopes.exists())
        self.assertEqual(len(mail.outbox), 1)
        self.assertEqual(mail.outbox[0].to, ["newuser@example.com"])

    def test_create(self):
        response = self.client.post(
            reverse("api:user-new"),
            data={"display_name": "New user", "email": "newuser@example.com", "password": "myVerySecretPassword"},
            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, "newuser@example.com")
        user = User.objects.get_by_natural_key("newuser@example.com")
        self.assertFalse(user.verified_email)
        self.assertFalse(user.user_scopes.exists())
        self.assertEqual(len(mail.outbox), 1)
        self.assertEqual(mail.outbox[0].to, ["newuser@example.com"])
        self.assertTrue(user.check_password("myVerySecretPassword"))

    def test_register_wrong_password(self):
        wrong = {
            "P@SsWrD": ["This password is too short. It must contain at least 8 characters."],
            "42133742": ["This password is entirely numeric."],
            "example.com": ["The password is too similar to the email address."],
            "pikachu1": ["This password is too common."]
        }
        for pwd, messages in wrong.items():
            response = self.client.post(
                reverse("api:user-new"),
                data={"display_name": "New user", "email": "newuser@example.com", "password": pwd},
                format="json",
            )
            self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
            self.assertEqual(response.json(), {"password": messages})

    def test_register_wrong_fields(self):
        response = self.client.post(
            reverse("api:user-new"),
            data={
                "display_name": "A" * 1024,
                "email": "",
                "password": ""
            },
            format="json",
        )
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertDictEqual(response.json(), {
            "display_name": ["Ensure this field has no more than 120 characters."],
            "email": ["This field may not be blank."],
            "password": ["This field may not be blank."]
        })

    @override_settings(ARKINDEX_FEATURES={"signup": False})
    def test_create_no_signup(self):
        response = self.client.post(
            reverse("api:user-new"),
            data={"email": "newuser@example.com", "password": "myVerySecretPassword"},
            format="json",
        )
        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."]})