forked from DGNum/gestioCOF
355 lines
11 KiB
Python
355 lines
11 KiB
Python
|
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")
|