diff --git a/cof/settings/bds_prod.py b/cof/settings/bds_prod.py
index 12f5a552..a99185e4 100644
--- a/cof/settings/bds_prod.py
+++ b/cof/settings/bds_prod.py
@@ -11,26 +11,12 @@ from .common import INSTALLED_APPS
ALLOWED_HOSTS = ["bds.ens.fr", "www.bds.ens.fr", "dev.cof.ens.fr"]
-INSTALLED_APPS += ["bds", "events", "clubs", "authens"]
+INSTALLED_APPS += ["bds", "events", "clubs"]
STATIC_ROOT = "/srv/bds.ens.fr/public/gestion2/static"
STATIC_URL = "/gestion2/static/"
MEDIA_ROOT = "/srv/bds.ens.fr/gestion2/media"
MEDIA_URL = "/gestion2/media/"
-
-# ---
-# Auth-related stuff
-# ---
-
-AUTHENTICATION_BACKENDS = [
- "django.contrib.auth.backends.ModelBackend",
- "authens.backends.ENSCASBackend",
- "authens.backends.OldCASBackend",
-]
-
-AUTHENS_USE_OLDCAS = False
-
-LOGIN_URL = "authens:login"
LOGIN_REDIRECT_URL = "bds:home"
LOGOUT_REDIRECT_URL = "bds:home"
diff --git a/cof/settings/cof_prod.py b/cof/settings/cof_prod.py
index 47fa3954..f0b56319 100644
--- a/cof/settings/cof_prod.py
+++ b/cof/settings/cof_prod.py
@@ -4,7 +4,6 @@ The settings that are not listed here are imported from .common
"""
import os
-from .common import * # NOQA
from .common import (
AUTHENTICATION_BACKENDS,
BASE_DIR,
@@ -14,6 +13,8 @@ from .common import (
import_secret,
)
+from .common import * # NOQA
+
# ---
# COF-specific secrets
# ---
@@ -108,13 +109,10 @@ CORS_ORIGIN_WHITELIST = ("bda.ens.fr", "www.bda.ens.fr" "cof.ens.fr", "www.cof.e
# Auth-related stuff
# ---
-AUTHENTICATION_BACKENDS += [
- "gestioncof.shared.COFCASBackend",
- "kfet.auth.backends.GenericBackend",
-]
+AUTHENTICATION_BACKENDS.append("kfet.auth.backends.GenericBackend")
-LOGIN_URL = "cof-login"
LOGIN_REDIRECT_URL = "home"
+LOGOUT_REDIRECT_URL = "home"
# ---
# Cache settings
diff --git a/cof/settings/common.py b/cof/settings/common.py
index 4636ace3..3d61c6f8 100644
--- a/cof/settings/common.py
+++ b/cof/settings/common.py
@@ -69,6 +69,7 @@ INSTALLED_APPS = [
"django_cas_ng",
"bootstrapform",
"widget_tweaks",
+ "authens",
]
MIDDLEWARE = [
@@ -133,11 +134,11 @@ FORMAT_MODULE_PATH = "cof.locale"
# Auth-related stuff
# ---
-AUTHENTICATION_BACKENDS = ["django.contrib.auth.backends.ModelBackend"]
+AUTHENTICATION_BACKENDS = [
+ "django.contrib.auth.backends.ModelBackend",
+ "authens.backends.ENSCASBackend",
+]
-CAS_SERVER_URL = "https://cas.eleves.ens.fr/"
-CAS_VERSION = "2"
-CAS_LOGIN_MSG = None
-CAS_IGNORE_REFERER = True
-CAS_REDIRECT_URL = "/"
-CAS_EMAIL_FORMAT = "%s@clipper.ens.fr"
+AUTHENS_USE_OLDCAS = False
+
+LOGIN_URL = "authens:login"
diff --git a/cof/urls.py b/cof/urls.py
index 1de437ed..b305967a 100644
--- a/cof/urls.py
+++ b/cof/urls.py
@@ -31,7 +31,7 @@ app_dict = {
"bda": "gestion/bda/",
"petitscours": "gestion/petitcours/",
"events": "gestion/event_v2/", # the events module is still experimental !
- "authens": "gestion/authens/",
+ "authens": "gestion/auth/",
}
for (app_name, url_prefix) in app_dict.items():
if app_name in settings.INSTALLED_APPS:
diff --git a/gestioncof/migrations/0018_django_cas_to_authens.py b/gestioncof/migrations/0018_django_cas_to_authens.py
new file mode 100644
index 00000000..9b689a7e
--- /dev/null
+++ b/gestioncof/migrations/0018_django_cas_to_authens.py
@@ -0,0 +1,40 @@
+import sys
+
+from authens.shortcuts import fetch_cas_account
+from django.db import migrations
+
+
+def to_authens(apps, schema_editor):
+ User = apps.get_model("auth", "User")
+ CASAccount = apps.get_model("authens", "CASAccount")
+
+ failures = []
+ for user in User.objects.select_related("profile").all():
+ login_clipper = user.profile.login_clipper
+ if login_clipper:
+ ldap_info = fetch_cas_account(login_clipper)
+ if ldap_info is None:
+ failures.append(user)
+ else:
+ entrance_year = ldap_info["entrance_year"]
+ CASAccount.objects.create(
+ user=user, cas_login=login_clipper, entrance_year=entrance_year
+ )
+
+ if failures:
+ sys.stderr.write(" ---------- \n")
+ sys.stderr.write("Some accounts could not be linked to a CAS account:\n")
+ for user in failures:
+ sys.stderr.write(f"- {user.username}\n")
+ sys.stderr.write(" ---------- \n")
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("gestioncof", "0017_petitscours_uniqueness"),
+ ]
+
+ operations = [
+ migrations.RunPython(to_authens, migrations.RunPython.noop),
+ ]
diff --git a/gestioncof/static/gestioncof/css/authens_extra.css b/gestioncof/static/gestioncof/css/authens_extra.css
new file mode 100644
index 00000000..c0204201
--- /dev/null
+++ b/gestioncof/static/gestioncof/css/authens_extra.css
@@ -0,0 +1,15 @@
+html, body {
+ background-color : #A7D4CD;
+ font-family: 'Roboto', sans-serif;
+ font-weight: 300;
+}
+
+#container-title, input[type="submit"] {
+ background: #4F504B;
+}
+
+.cas { background: #49A5E3; }
+.big-button.cas:hover { background: #71B5E3; }
+
+.exte { background: #E36748; }
+.big-button.exte:hover { background: #E38871; }
diff --git a/gestioncof/templates/authens/base.html b/gestioncof/templates/authens/base.html
new file mode 100644
index 00000000..4219fa21
--- /dev/null
+++ b/gestioncof/templates/authens/base.html
@@ -0,0 +1,8 @@
+{% extends "authens/base.html" %}
+
+{% load static %}
+
+{% block extra_css %}
+
+
+{% endblock %}
diff --git a/gestioncof/templates/gestioncof/base_header.html b/gestioncof/templates/gestioncof/base_header.html
index e5f757a7..2358a3a2 100644
--- a/gestioncof/templates/gestioncof/base_header.html
+++ b/gestioncof/templates/gestioncof/base_header.html
@@ -11,7 +11,7 @@
{% if user.first_name %}{{ user.first_name }}{% else %}{{ user.username }}{% endif %}, {% if user.profile.is_cof %}au COF{% else %}non-COF{% endif %}
diff --git a/gestioncof/urls.py b/gestioncof/urls.py
index 14fb101f..7df204e7 100644
--- a/gestioncof/urls.py
+++ b/gestioncof/urls.py
@@ -1,7 +1,7 @@
+from authens.views import LogoutView
from django.contrib.auth import views as django_auth_views
from django.urls import include, path
from django.views.generic.base import TemplateView
-from django_cas_ng import views as django_cas_views
from gestioncof import csv_views, views
@@ -96,21 +96,7 @@ urlpatterns = [
TemplateView.as_view(template_name="cof-denied.html"),
name="cof-denied",
),
- path("cas/login", django_cas_views.LoginView.as_view(), name="cas_login_view"),
- path("cas/logout", django_cas_views.LogoutView.as_view()),
- path(
- "outsider/login",
- views.LoginExtView.as_view(),
- name="ext_login_view",
- ),
- path(
- "outsider/logout",
- django_auth_views.LogoutView.as_view(),
- {"next_page": "home"},
- ),
- path("login", views.login, name="cof-login"),
- path("logout", views.logout, name="cof-logout"),
- path("admin/logout/", views.logout),
+ path("admin/logout/", LogoutView.as_view()),
# -----
# Infos persos
# -----
diff --git a/gestioncof/views.py b/gestioncof/views.py
index d4b6a5be..aa47b652 100644
--- a/gestioncof/views.py
+++ b/gestioncof/views.py
@@ -2,17 +2,12 @@ import csv
import uuid
from datetime import timedelta
from smtplib import SMTPRecipientsRefused
-from urllib.parse import parse_qs, urlencode, urlparse, urlunparse
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.models import User
-from django.contrib.auth.views import (
- LoginView as DjangoLoginView,
- LogoutView as DjangoLogoutView,
- redirect_to_login,
-)
+from django.contrib.auth.views import redirect_to_login
from django.contrib.sites.models import Site
from django.core.mail import send_mail
from django.http import Http404, HttpResponse, HttpResponseForbidden
@@ -22,7 +17,6 @@ from django.urls import reverse_lazy
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from django.views.generic import FormView, TemplateView
-from django_cas_ng.views import LogoutView as CasLogoutView
from icalendar import Calendar, Event as Vevent
from bda.models import Spectacle, Tirage
@@ -34,7 +28,6 @@ from gestioncof.forms import (
EventForm,
EventFormset,
EventStatusFilterForm,
- ExteAuthenticationForm,
GestioncofConfigForm,
PhoneForm,
ProfileForm,
@@ -79,75 +72,6 @@ class HomeView(LoginRequiredMixin, TemplateView):
return context
-def login(request):
- if request.user.is_authenticated:
- return redirect("home")
- context = {}
- if request.method == "GET" and "next" in request.GET:
- context["next"] = request.GET["next"]
- return render(request, "login_switch.html", context)
-
-
-class LoginExtView(DjangoLoginView):
- template_name = "login.html"
- form_class = ExteAuthenticationForm
-
- def form_invalid(self, form):
- for e in form.non_field_errors().as_data():
- if e.code in ["has_clipper", "no_password"]:
- return render(self.request, "login_error.html", {"error_code": e.code})
- return super().form_invalid(form)
-
-
-class CustomCasLogoutView(CasLogoutView):
- """
- Actuellement, le CAS de l'ENS est pété et n'a pas le bon paramètre GET
- pour rediriger après déconnexion. On change la redirection à la main
- dans la vue de logout.
- """
-
- def get(self, request):
- # CasLogoutView.get() retourne un HttpResponseRedirect
- response = super().get(request)
- parse_result = urlparse(response.url)
- qd = parse_qs(parse_result.query)
-
- if "url" in qd.keys():
- # Le 2e pop est nécessaire car CAS n'aime pas
- # les paramètres sous forme de liste
- qd["service"] = qd.pop("url").pop()
-
- # La méthode _replace est documentée !
- new_url = parse_result._replace(query=urlencode(qd))
-
- return redirect(urlunparse(new_url))
-
-
-@login_required
-def logout(request, next_page=None):
- if next_page is None:
- next_page = request.GET.get("next", None)
-
- profile = getattr(request.user, "profile", None)
-
- if profile and profile.login_clipper:
- if next_page is None:
- # On ne voit pas les messages quand on se déconnecte de CAS
- msg = None
- else:
- msg = _("Déconnexion de GestioCOF et CAS réussie. À bientôt {}.")
- logout_view = CustomCasLogoutView.as_view()
- else:
- msg = _("Déconnexion de GestioCOF réussie. À bientôt {}.")
- logout_view = DjangoLogoutView.as_view(
- next_page=next_page, template_name="logout.html"
- )
-
- if msg is not None:
- messages.success(request, msg.format(request.user.get_short_name()))
- return logout_view(request)
-
-
@login_required
def survey(request, survey_id):
survey = get_object_or_404(
diff --git a/kfet/auth/tests.py b/kfet/auth/tests.py
index 32e04812..01ceebe4 100644
--- a/kfet/auth/tests.py
+++ b/kfet/auth/tests.py
@@ -168,7 +168,9 @@ class GenericLoginViewTests(TestCase):
r = self.client.post(self.url)
self.assertRedirects(
- r, "/gestion/logout?next={}".format(self.url), fetch_redirect_response=False
+ r,
+ "/gestion/auth/logout?next={}".format(self.url),
+ fetch_redirect_response=False,
)
def test_notoken_not_team(self):
@@ -180,13 +182,17 @@ class GenericLoginViewTests(TestCase):
# With GET.
r = self.client.get(self.url)
self.assertRedirects(
- r, "/gestion/login?next={}".format(self.url), fetch_redirect_response=False
+ r,
+ "/gestion/auth/login/choose?next={}".format(self.url),
+ fetch_redirect_response=False,
)
# Also with POST.
r = self.client.post(self.url)
self.assertRedirects(
- r, "/gestion/login?next={}".format(self.url), fetch_redirect_response=False
+ r,
+ "/gestion/auth/login/choose?next={}".format(self.url),
+ fetch_redirect_response=False,
)
def _set_signed_cookie(self, client, key, value):
diff --git a/kfet/auth/views.py b/kfet/auth/views.py
index f57e8415..2ae79e83 100644
--- a/kfet/auth/views.py
+++ b/kfet/auth/views.py
@@ -73,7 +73,7 @@ class GenericLoginView(View):
here_qd["next"] = self.request.GET["next"]
here_url += "?{}".format(here_qd.urlencode())
- logout_url = reverse("cof-logout")
+ logout_url = reverse("authens:logout")
logout_qd = QueryDict(mutable=True)
logout_qd["next"] = here_url
logout_url += "?{}".format(logout_qd.urlencode(safe="/"))
diff --git a/kfet/decorators.py b/kfet/decorators.py
index 70848820..e7d383a5 100644
--- a/kfet/decorators.py
+++ b/kfet/decorators.py
@@ -1,8 +1,15 @@
from django.contrib.auth.decorators import user_passes_test
+from django.core.exceptions import PermissionDenied
def kfet_is_team(user):
- return user.has_perm("kfet.is_team")
+ if user.is_authenticated:
+ if user.has_perm("kfet.is_team"):
+ return True
+ else:
+ raise PermissionDenied
+ else:
+ return False
teamkfet_required = user_passes_test(kfet_is_team)
diff --git a/kfet/templates/kfet/base_nav.html b/kfet/templates/kfet/base_nav.html
index 1cded20b..6e6db51d 100644
--- a/kfet/templates/kfet/base_nav.html
+++ b/kfet/templates/kfet/base_nav.html
@@ -101,7 +101,7 @@
{% endif %}
-
+
Déconnexion
@@ -110,13 +110,13 @@
{% endif %}
{% if user.is_authenticated and not perms.kfet.is_team %}
-
+
{% elif not user.is_authenticated %}
-
+
Connexion
diff --git a/kfet/tests/test_statistic.py b/kfet/tests/test_statistic.py
index 6d8ecb47..1cd86743 100644
--- a/kfet/tests/test_statistic.py
+++ b/kfet/tests/test_statistic.py
@@ -61,7 +61,7 @@ class TestStats(TestCase):
self.assertEqual(404, resp2.status_code)
# 2. FOO is a member of the team and can get these pages but BAR
- # receives a Redirect response
+ # receives a 403 response
articles_urls = [
"/k-fet/articles/{}/stat/sales/list".format(article.pk),
"/k-fet/articles/{}/stat/sales?{}".format(
@@ -80,4 +80,4 @@ class TestStats(TestCase):
resp = client.get(url)
self.assertEqual(200, resp.status_code)
resp2 = client2.get(url, follow=True)
- self.assertRedirects(resp2, "/gestion/")
+ self.assertEqual(403, resp2.status_code)
diff --git a/kfet/tests/test_views.py b/kfet/tests/test_views.py
index 47382aa1..fcebef8c 100644
--- a/kfet/tests/test_views.py
+++ b/kfet/tests/test_views.py
@@ -232,7 +232,7 @@ class AccountReadViewTests(ViewTestCaseMixin, TestCase):
response = client.get(url)
self.assertRedirects(
response,
- "/gestion/login?next={}".format(url),
+ "/gestion/auth/login/choose?next={}".format(url),
fetch_redirect_response=False,
)
else:
@@ -344,7 +344,7 @@ class AccountUpdateViewTests(ViewTestCaseMixin, TestCase):
response = meth(url)
self.assertRedirects(
response,
- "/gestion/login?next={}".format(url),
+ "/gestion/auth/login/choose?next={}".format(url),
fetch_redirect_response=False,
)
else:
@@ -629,7 +629,7 @@ class AccountStatOperationListViewTests(ViewTestCaseMixin, TestCase):
response = client.get(url)
self.assertRedirects(
response,
- "/gestion/login?next={}".format(url),
+ "/gestion/auth/login/choose?next={}".format(url),
fetch_redirect_response=False,
)
else:
@@ -723,7 +723,7 @@ class AccountStatOperationViewTests(ViewTestCaseMixin, TestCase):
response = client.get(url)
self.assertRedirects(
response,
- "/gestion/login?next={}".format(url),
+ "/gestion/auth/login/choose?next={}".format(url),
fetch_redirect_response=False,
)
else:
@@ -764,7 +764,7 @@ class AccountStatBalanceListViewTests(ViewTestCaseMixin, TestCase):
response = client.get(url)
self.assertRedirects(
response,
- "/gestion/login?next={}".format(url),
+ "/gestion/auth/login/choose?next={}".format(url),
fetch_redirect_response=False,
)
else:
@@ -830,7 +830,7 @@ class AccountStatBalanceViewTests(ViewTestCaseMixin, TestCase):
response = client.get(url)
self.assertRedirects(
response,
- "/gestion/login?next={}".format(url),
+ "/gestion/auth/login/choose?next={}".format(url),
fetch_redirect_response=False,
)
else:
diff --git a/kfet/tests/testcases.py b/kfet/tests/testcases.py
index 4912023e..a6781387 100644
--- a/kfet/tests/testcases.py
+++ b/kfet/tests/testcases.py
@@ -39,7 +39,9 @@ class TestCaseMixin:
querystring = QueryDict(mutable=True)
querystring["next"] = full_path
- login_url = "/gestion/login?" + querystring.urlencode(safe="/")
+ login_url = "/gestion/auth/login/choose?{}".format(
+ querystring.urlencode(safe="/")
+ )
# We don't focus on what the login view does.
# So don't fetch the redirect.
diff --git a/shared/tests/mixins.py b/shared/tests/mixins.py
index ea83616a..6f056d20 100644
--- a/shared/tests/mixins.py
+++ b/shared/tests/mixins.py
@@ -173,7 +173,7 @@ class TestCaseMixin:
querystring["next"] = full_path
login_url = "{}?{}".format(
- reverse("cof-login"), querystring.urlencode(safe="/")
+ reverse("authens:login"), querystring.urlencode(safe="/")
)
# We don't focus on what the login view does.