Merge branch 'kerl/cas_logout' into 'master'
Redirection vers cas.eleves.ens.fr/logout à la déconnexion. Closes #8 See merge request klub-dev-ens/authens!5
This commit is contained in:
commit
6fdde55b0f
6 changed files with 141 additions and 3 deletions
|
@ -60,6 +60,8 @@ class ENSCASBackend:
|
||||||
|
|
||||||
cas_login = self.clean_cas_login(cas_login)
|
cas_login = self.clean_cas_login(cas_login)
|
||||||
year = get_entrance_year(attributes)
|
year = get_entrance_year(attributes)
|
||||||
|
if request:
|
||||||
|
request.session["CASCONNECTED"] = True
|
||||||
return self._get_or_create(cas_login, year)
|
return self._get_or_create(cas_login, year)
|
||||||
|
|
||||||
def clean_cas_login(self, cas_login):
|
def clean_cas_login(self, cas_login):
|
||||||
|
|
69
authens/tests/test_views.py
Normal file
69
authens/tests/test_views.py
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
from unittest.mock import patch
|
||||||
|
from urllib.parse import quote as urlquote
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
from django.contrib.sessions.models import Session
|
||||||
|
from django.test import Client, TestCase
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
|
from authens.models import CASAccount
|
||||||
|
from authens.tests.cas_utils import FakeCASClient
|
||||||
|
|
||||||
|
UserModel = get_user_model()
|
||||||
|
|
||||||
|
|
||||||
|
class TestLogoutView(TestCase):
|
||||||
|
def test_regular_logout(self):
|
||||||
|
# Regular user (without a CAS account)
|
||||||
|
user = UserModel.objects.create_user(username="johndoe")
|
||||||
|
|
||||||
|
# Log the user in
|
||||||
|
client = Client()
|
||||||
|
client.force_login(user)
|
||||||
|
|
||||||
|
self.assertEqual(Session.objects.count(), 1)
|
||||||
|
response = client.get("/authens/logout")
|
||||||
|
self.assertEqual(Session.objects.count(), 0) # User is actually logged out.
|
||||||
|
self.assertRedirects(response, settings.LOGOUT_REDIRECT_URL)
|
||||||
|
|
||||||
|
@patch("authens.backends.get_cas_client")
|
||||||
|
def test_cas_logout(self, mock_cas_client):
|
||||||
|
# Make `get_cas_client` return a dummy CAS client that skips ticket verification
|
||||||
|
# and always log in a user with CAS login 'johndoe'.
|
||||||
|
# This is only used for login.
|
||||||
|
mock_cas_client.return_value = FakeCASClient("johndoe", 2019)
|
||||||
|
|
||||||
|
# CAS user
|
||||||
|
user = UserModel.objects.create_user(username="johndoe")
|
||||||
|
CASAccount.objects.create(user=user, cas_login="johndoe", entrance_year=2019)
|
||||||
|
|
||||||
|
# Log the user in via CAS
|
||||||
|
client = Client()
|
||||||
|
client.get("/authens/login/cas?ticket=dummy-ticket")
|
||||||
|
|
||||||
|
self.assertEqual(Session.objects.count(), 1)
|
||||||
|
response = client.get("/authens/logout")
|
||||||
|
self.assertEqual(Session.objects.count(), 0) # User is logged out…
|
||||||
|
self.assertRedirects( # … and redirected to the CAS logout page.
|
||||||
|
response,
|
||||||
|
"https://cas.eleves.ens.fr/logout?service={}".format(
|
||||||
|
urlquote("http://testserver" + reverse("authens:login"))
|
||||||
|
),
|
||||||
|
fetch_redirect_response=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_regular_logout_on_cas_account(self):
|
||||||
|
# CAS user
|
||||||
|
user = UserModel.objects.create_user(username="johndoe", password="p4ssw0rd")
|
||||||
|
CASAccount.objects.create(user=user, cas_login="johndoe", entrance_year=2019)
|
||||||
|
|
||||||
|
# Log the user in by password and *not* via CAS
|
||||||
|
client = Client()
|
||||||
|
client.login(username="johndoe", password="p4ssw0rd")
|
||||||
|
|
||||||
|
self.assertEqual(Session.objects.count(), 1)
|
||||||
|
response = client.get("/authens/logout")
|
||||||
|
self.assertEqual(Session.objects.count(), 0) # User is logged out…
|
||||||
|
# … and not redirected to the CAS logout page.
|
||||||
|
self.assertRedirects(response, settings.LOGOUT_REDIRECT_URL)
|
|
@ -1,4 +1,3 @@
|
||||||
from django.contrib.auth import views as auth_views
|
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from authens import views
|
from authens import views
|
||||||
|
@ -8,5 +7,5 @@ urlpatterns = [
|
||||||
path("login/choose", views.LoginSwitchView.as_view(), name="login"),
|
path("login/choose", views.LoginSwitchView.as_view(), name="login"),
|
||||||
path("login/cas", views.CASLoginView.as_view(), name="login.cas"),
|
path("login/cas", views.CASLoginView.as_view(), name="login.cas"),
|
||||||
path("login/pwd", views.PasswordLoginView.as_view(), name="login.pwd"),
|
path("login/pwd", views.PasswordLoginView.as_view(), name="login.pwd"),
|
||||||
path("logout", auth_views.LogoutView.as_view(), name="logout"),
|
path("logout", views.LogoutView.as_view(), name="logout"),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
|
from urllib.parse import urlparse, urlunparse
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib import auth
|
from django.contrib import auth
|
||||||
|
from django.contrib.auth import views as auth_views
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
from django.views.generic import TemplateView, View
|
from django.views.generic import TemplateView, View
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
|
@ -72,5 +75,37 @@ class CASLoginView(NextPageMixin, View):
|
||||||
return redirect(self.get_next_url())
|
return redirect(self.get_next_url())
|
||||||
|
|
||||||
|
|
||||||
class PasswordLoginView(auth.views.LoginView):
|
class PasswordLoginView(auth_views.LoginView):
|
||||||
template_name = "authens/pwd_login.html"
|
template_name = "authens/pwd_login.html"
|
||||||
|
|
||||||
|
|
||||||
|
class LogoutView(auth_views.LogoutView):
|
||||||
|
"""Logout view of AuthENS.
|
||||||
|
|
||||||
|
Tell Django to log the user out, then redirect to the CAS logout page if the user
|
||||||
|
logged in via CAS.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def setup(self, request):
|
||||||
|
super().setup(request)
|
||||||
|
if "CASCONNECTED" in request.session:
|
||||||
|
del request.session["CASCONNECTED"]
|
||||||
|
self.cas_connected = True
|
||||||
|
else:
|
||||||
|
self.cas_connected = False
|
||||||
|
|
||||||
|
def get_next_page(self):
|
||||||
|
next_page = super().get_next_page()
|
||||||
|
if self.cas_connected:
|
||||||
|
cas_client = get_cas_client(self.request)
|
||||||
|
|
||||||
|
# If the next_url is local (no hostname), make it absolute so that the user
|
||||||
|
# is correctly redirected from CAS.
|
||||||
|
if not urlparse(next_page).netloc:
|
||||||
|
request = self.request
|
||||||
|
next_page = urlunparse(
|
||||||
|
(request.scheme, request.get_host(), next_page, "", "", "")
|
||||||
|
)
|
||||||
|
|
||||||
|
next_page = cas_client.get_logout_url(redirect_url=next_page)
|
||||||
|
return next_page
|
||||||
|
|
|
@ -8,6 +8,7 @@ SECRET_KEY = "dummy"
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
"django.contrib.contenttypes",
|
"django.contrib.contenttypes",
|
||||||
"django.contrib.auth",
|
"django.contrib.auth",
|
||||||
|
"django.contrib.sessions",
|
||||||
"authens",
|
"authens",
|
||||||
"tests",
|
"tests",
|
||||||
]
|
]
|
||||||
|
@ -17,6 +18,34 @@ AUTHENTICATION_BACKENDS = [
|
||||||
"authens.backends.ENSCASBackend",
|
"authens.backends.ENSCASBackend",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
MIDDLEWARE = [
|
||||||
|
'django.middleware.security.SecurityMiddleware',
|
||||||
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
|
'django.middleware.common.CommonMiddleware',
|
||||||
|
'django.middleware.csrf.CsrfViewMiddleware',
|
||||||
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
|
]
|
||||||
|
|
||||||
|
TEMPLATES = [
|
||||||
|
{
|
||||||
|
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||||
|
"DIRS": [],
|
||||||
|
"APP_DIRS": True,
|
||||||
|
"OPTIONS": {
|
||||||
|
"context_processors": [
|
||||||
|
"django.template.context_processors.debug",
|
||||||
|
"django.template.context_processors.request",
|
||||||
|
"django.contrib.auth.context_processors.auth",
|
||||||
|
"django.contrib.messages.context_processors.messages",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
DATABASES = {"default": {"ENGINE": "django.db.backends.sqlite3"}}
|
DATABASES = {"default": {"ENGINE": "django.db.backends.sqlite3"}}
|
||||||
|
|
||||||
|
ROOT_URLCONF = "tests.urls"
|
||||||
LOGIN_URL = reverse_lazy("authens:login")
|
LOGIN_URL = reverse_lazy("authens:login")
|
||||||
|
LOGOUT_REDIRECT_URL = reverse_lazy("authens:login")
|
||||||
|
|
4
tests/urls.py
Normal file
4
tests/urls.py
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
from django.urls import include, path
|
||||||
|
|
||||||
|
|
||||||
|
urlpatterns = [path("authens/", include("authens.urls"))]
|
Loading…
Reference in a new issue