import re from allauth.socialaccount.models import SocialAccount from allauth.socialaccount.providers import registry as providers_registry from allauth_cas.test.testcases import CASViewTestCase from django.contrib.auth import HASH_SESSION_KEY, get_user_model from django.core import mail from django.core.urlresolvers import reverse from django.test import Client, TestCase from .testcases import ViewTestCaseMixin User = get_user_model() def prevent_logout_pwd_change(client, user): """ Updating a user's password logs out all sessions for the user. By calling this function this behavior will be prevented. See this link, and the source code of `update_session_auth_hash`: https://docs.djangoproject.com/en/dev/topics/auth/default/#session-invalidation-on-password-change """ if hasattr(user, "get_session_auth_hash"): session = client.session session[HASH_SESSION_KEY] = user.get_session_auth_hash() session.save() def get_reset_password_link(email_msg): m = re.search(r"http://testserver(/profil/password/reset/key/.*/)", email_msg.body) return m.group(1) class LoginViewTests(ViewTestCaseMixin, TestCase): url_name = "account_login" url_expected = "/profil/login/" http_methods = ["GET", "POST"] def test_get(self): """ Unauthenticated users can access the login form. """ r = self.client.get(self.url) self.assertEqual(r.status_code, 200) def test_get_already_auth(self): """ Even already authenticated users can access the login form. They may have been redirected due to a lack of authorizations. """ self.client.login(username="user", password="user") r = self.client.get(self.url) self.assertEqual(r.status_code, 200) def test_post(self): """ Users can log in. """ r = self.client.post(self.url, {"login": "user", "password": "user"}) self.assertRedirects(r, reverse("home")) self.assertEqual(r.wsgi_request.user, self.users["user"]) def test_post_redirect(self): """ On login, user is redirected to the value of the `next` GET parameter. """ redirect_url = reverse("account_logout") url = self.url + "?next=" + redirect_url r = self.client.post(url, {"login": "user", "password": "user"}) self.assertRedirects(r, redirect_url) self.assertEqual(r.wsgi_request.user, self.users["user"]) def test_post_invalid_password(self): """ If credentials are incorrect, the page is displayed again. """ r = self.client.post(self.url, {"username": "user", "password": "bad password"}) self.assertEqual(r.status_code, 200) self.assertTrue(r.wsgi_request.user.is_anonymous()) class LogoutViewTests(ViewTestCaseMixin, TestCase): url_name = "account_logout" url_expected = "/profil/logout/" http_methods = ["GET", "POST"] auth_user = "user" def test_get(self): """ Using the HTTP method GET, only a confirmation is prompted to the user. """ r = self.client.get(self.url) self.assertEqual(r.status_code, 200) def test_post(self): """ With a POST request, user is logged out. """ r = self.client.post(self.url) self.assertRedirects(r, reverse("home"), fetch_redirect_response=False) self.assertTrue(r.wsgi_request.user.is_anonymous()) def test_post_redirect(self): """ On logout, user is redirected to the value of the `next` GET parameter. """ redirect_url = reverse("account_set_password") url = self.url + "?next=" + redirect_url r = self.client.post(url) self.assertRedirects(r, redirect_url, fetch_redirect_response=False) self.assertTrue(r.wsgi_request.user.is_anonymous()) class ChangePasswordViewTests(ViewTestCaseMixin, TestCase): url_name = "account_change_password" url_expected = "/profil/password/change/" http_methods = ["GET", "POST"] auth_user = "user" auth_forbidden = [None] def test_get(self): """ Authenticated users can access the page to change their password. """ r = self.client.get(self.url) self.assertEqual(r.status_code, 200) def test_get_no_password(self): """ Authenticated users who do not have a password are redirected to the `account_set_password` view. """ user = self.users["user"] user.set_unusable_password() user.save() prevent_logout_pwd_change(self.client, user) r = self.client.get(self.url) self.assertRedirects(r, reverse("account_set_password")) def test_post(self): """ Authenticated users can change their password. """ user = self.users["user"] r = self.client.post( self.url, {"oldpassword": "user", "password1": "usertruc", "password2": "usertruc"}, ) self.assertRedirects(r, reverse("account_change_password")) user.refresh_from_db() self.assertTrue(user.check_password("usertruc")) class SetPasswordViewTests(ViewTestCaseMixin, TestCase): url_name = "account_set_password" url_expected = "/profil/password/set/" http_methods = ["GET", "POST"] auth_user = "user_nopwd" auth_forbidden = [None] def setUp(self): super().setUp() user_nopwd = self.users["user_nopwd"] user_nopwd.set_unusable_password() user_nopwd.save() prevent_logout_pwd_change(self.client, user_nopwd) def get_users_extra(self): # `user_nopwd` is created with a password to use the `login` method of # the test client. The password is then removed. return {"user_nopwd": User.objects.create_user("user_nopwd", "", "user_nopwd")} def test_get(self): """ Authenticated users who do not have a password can access the page. """ r = self.client.get(self.url) self.assertEqual(r.status_code, 200) def test_get_has_password(self): """ Authenticated users who already have a password are redirected to the `account_change_password` view. """ client = Client() client.login(username="user", password="user") r = client.get(self.url) self.assertRedirects(r, reverse("account_change_password")) def test_post(self): """ Authenticated users can set their password. """ user = self.users["user_nopwd"] r = self.client.post( self.url, {"password1": "plop2fois", "password2": "plop2fois"} ) self.assertRedirects( r, reverse("account_set_password"), fetch_redirect_response=False ) user.refresh_from_db() self.assertTrue(user.check_password("plop2fois")) class ResetPasswordViewTests(ViewTestCaseMixin, TestCase): url_name = "account_reset_password" url_expected = "/profil/password/reset/" http_methods = ["GET", "POST"] def setUp(self): super().setUp() user = self.users["user"] user.email = "user@mail.net" user.save() def test_get(self): """ Users can access the page. """ r = self.client.get(self.url) self.assertEqual(r.status_code, 200) def test_post(self): """ Users can ask for a link to be sent to reset their password. """ r = self.client.post(self.url, {"email": "user@mail.net"}) self.assertRedirects(r, reverse("account_reset_password_done")) get_reset_password_link(mail.outbox[0]) class ResetPasswordKeyViewTests(TestCase): def setUp(self): self.user = User.objects.create_user("user", "user@mail.net", "user") self.client.post(reverse("account_reset_password"), {"email": "user@mail.net"}) self.reset_link = get_reset_password_link(mail.outbox[0]) def test_get(self): """ With valid link, users can access the form to reset their password. """ # Redirection happens in order to "hide" the reset key. r = self.client.get(self.reset_link, follow=True) self.assertEqual(r.status_code, 200) self.assertIn("form", r.context) def test_get_bad_token(self): """ """ # Edit the key (remove the last slash, add some keys) bad_link = self.reset_link[:-1] + "reallybad/" r = self.client.get(bad_link, follow=True) self.assertEqual(r.status_code, 200) self.assertNotIn("form", r.context) def test_post(self): """ If the form is valid, user password is changed. """ r = self.client.get(self.reset_link, follow=True) r = self.client.post( r.redirect_chain[-1][0], {"password1": "thepassword", "password2": "thepassword"}, ) self.assertRedirects(r, reverse("home")) self.user.refresh_from_db() self.assertTrue(self.user.check_password("thepassword")) class ClipperAuthTests(CASViewTestCase): def setUp(self): self.user = User.objects.create_user("user", "", "user") self.provider = providers_registry.by_id("clipper") # When the Clipper callback view verifies ticket with the CAS server, # this ensures it will approve the ticket for the identifier # 'theclipper'. self.patch_cas_response(username="theclipper", valid_ticket="__all__") self.callback_url = reverse("clipper_callback") + "?ticket=any" def test_login(self): SocialAccount.objects.create( provider="clipper", user=self.user, uid="theclipper" ) self.client.get(reverse("clipper_login")) r = self.client.get(self.callback_url) self.assertRedirects(r, reverse("home")) self.assertEqual(r.wsgi_request.user, self.user) def test_autosignup(self): """ Connecting via Clipper automatically creates a User instance, if none is already linked to the used clipper login. This identifier is used as username (trimmed, lowercased). """ self.assertFalse(User.objects.filter(username="theclipper").exists()) self.client.get(reverse("clipper_login")) r = self.client.get(self.callback_url) self.assertRedirects(r, reverse("home")) user = User.objects.get(username="theclipper") SocialAccount.objects.get(provider="clipper", user=user, uid="theclipper") self.assertEqual(r.wsgi_request.user, user) self.assertEqual(user.email, "theclipper@clipper.ens.fr") def test_autosignup_conflict_username(self): """ When creating User via Clipper auto-signup, if the username is not available, a similar one is chosen. """ User.objects.create_user("theclipper", "", "") previous_user_pks = list(User.objects.values_list("pk", flat=True)) self.client.get(reverse("clipper_login")) r = self.client.get(self.callback_url) self.assertRedirects(r, reverse("home")) user = User.objects.exclude(pk__in=previous_user_pks).get() SocialAccount.objects.get(provider="clipper", user=user, uid="theclipper") self.assertEqual(r.wsgi_request.user, user) self.assertTrue(user.username.startswith("theclipper")) self.assertEqual(user.email, "theclipper@clipper.ens.fr")