diff --git a/authens/backends.py b/authens/backends.py index 5481a0d..6033aad 100644 --- a/authens/backends.py +++ b/authens/backends.py @@ -85,7 +85,7 @@ class ENSCASBackend: i = len(cas_login) - 1 while i >= 0 and cas_login[i] in "0123456789": i -= 1 - radical = cas_login[:i + 1] + radical = cas_login[: i + 1] # Find an integer i such that radical + str(i) is not taken. taken = UserModel.objects.values_list("username", flat=True) diff --git a/authens/tests/cas_utils.py b/authens/tests/cas_utils.py new file mode 100644 index 0000000..089ffb7 --- /dev/null +++ b/authens/tests/cas_utils.py @@ -0,0 +1,19 @@ +class FakeCASClient: + """Fake CAS Client that mimics CAS interactions. + + By default, always log in the same user 'johndoe' without checking the ticket. + """ + + def __init__(self, cas_login="johndoe", entrance_year=2019): + self.cas_login = cas_login + self.entrance_year = entrance_year + + def verify_ticket(self, ticket): + """Dummy client that always log in the same user.""" + + attributes = { + "homeDirectory": "/users/{}/info/{}".format( + self.entrance_year % 100, self.cas_login + ) + } + return self.cas_login, attributes, None diff --git a/authens/tests/test_backend.py b/authens/tests/test_backend.py index a756f1f..2cf1f39 100644 --- a/authens/tests/test_backend.py +++ b/authens/tests/test_backend.py @@ -1,17 +1,19 @@ -from django.contrib.auth import get_user_model +from unittest import mock + +from django.contrib.auth import authenticate, get_user_model from django.test import TestCase from authens.backends import ENSCASBackend +from authens.models import CASAccount +from authens.tests.cas_utils import FakeCASClient UserModel = get_user_model() -class TestBackend(TestCase): - def setUp(self): +class TestCASBackend(TestCase): + def test_usernames_uniqueness(self): UserModel.objects.create(username="toto") UserModel.objects.create(username="toto2") - - def test_usernames_uniqueness(self): backend = ENSCASBackend() for _ in range(10): username = backend.get_free_username("toto") @@ -22,4 +24,69 @@ class TestBackend(TestCase): self.assertFalse(UserModel.objects.filter(username=username).exists()) UserModel.objects.create(username=username) - # TODO: https://git.eleves.ens.fr/klub-dev-ens/authens/issues/4 + @mock.patch("authens.backends.get_cas_client") + def test_cas_user_creation(self, mock_cas_client): + # Make `get_cas_client` return a dummy CAS client for testing purpose. + fake_cas_client = FakeCASClient() + mock_cas_client.return_value = fake_cas_client + + # First authentication + user1 = authenticate(None, ticket="dummy ticket") + # Should create a user and a Clipper instance. + self.assertEqual(CASAccount.objects.count(), 1) + self.assertEqual(UserModel.objects.count(), 1) + clipper1 = CASAccount.objects.get() + self.assertEqual(clipper1.user, user1) + self.assertEqual(clipper1.cas_login, fake_cas_client.cas_login) + self.assertEqual(clipper1.entrance_year, fake_cas_client.entrance_year) + + # Second time we authenticate the same user. + user2 = authenticate(None, ticket="dummy ticket") + # Should return the same user and should not create anything. + self.assertEqual(CASAccount.objects.count(), 1) + self.assertEqual(UserModel.objects.count(), 1) + clipper2 = CASAccount.objects.get() + self.assertEqual(clipper1, clipper2) + self.assertEqual(user1, user2) + self.assertEqual(user2, clipper2.user) + + @mock.patch("authens.backends.get_cas_client") + def test_name_conflict_handling(self, mock_cas_client): + # Make `get_cas_client` return a dummy CAS client for testing purpose. + fake_cas_client = FakeCASClient() + mock_cas_client.return_value = fake_cas_client + + # Create a regular user (without a CAS account) with the same username as the + # CAS user before her first login. + regular_user = UserModel.objects.create_user(username=fake_cas_client.cas_login) + + # First login of the CAS user. + cas_user = authenticate(None, ticket="dummy ticket") + + self.assertNotEqual(regular_user, cas_user) + + @mock.patch("authens.backends.get_cas_client") + def test_cas_conflict_handling(self, mock_cas_client): + # Make `get_cas_client` return a dummy CAS client for testing purpose. + fake_cas_client = FakeCASClient() + mock_cas_client.return_value = fake_cas_client + + # Create an old CAS user with the same cas_id as the new CAS user before her + # first login. This tests the behaviour of the library in the (unlikely) + # scenario where the SPI decides to give a clipper login that used to be someone + # else's to a new user. + old_user = UserModel.objects.create_user(username=fake_cas_client.cas_login) + old_clipper = CASAccount.objects.create( + user=old_user, + cas_login=fake_cas_client.cas_login, + entrance_year=fake_cas_client.entrance_year - 10, + ) + + # Log the new 'johndoe' in. + new_user = authenticate(None, ticket="dummy ticket") + new_clipper = new_user.cas_account + # Check that it gets a fresh user and a fresh clipper account. + self.assertNotEqual(old_user, new_user) + self.assertNotEqual(old_clipper, new_clipper) + self.assertEqual(new_clipper.cas_login, fake_cas_client.cas_login) + self.assertEqual(new_clipper.entrance_year, fake_cas_client.entrance_year)