From fee31ab07b2d3ef5907e57c29111e34d92b6c025 Mon Sep 17 00:00:00 2001
From: sinavir
Date: Tue, 2 Jul 2024 22:50:21 +0200
Subject: [PATCH] feat: switch to authens
Be careful there is no User migration
---
accounts/admin.py | 38 ----------
accounts/backends.py | 7 --
accounts/forms.py | 8 +-
accounts/migrations/0002_delete_user.py | 16 ++++
accounts/models.py | 76 -------------------
.../registration/account_settings.html | 2 +-
.../registration/change_password.html | 12 ---
accounts/templates/registration/login.html | 13 ----
.../templates/registration/login_switch.html | 15 ----
accounts/urls.py | 18 +----
accounts/views.py | 53 -------------
comments/models.py | 2 +-
comments/templates/comments.html | 2 +-
default.nix | 8 +-
gestiojeux/settings.py | 23 +++---
gestiojeux/urls.py | 1 +
nix/authens/01-get-success_url.patch | 15 ++++
nix/authens/default.nix | 22 ++++++
nix/python-cas/default.nix | 23 ++++++
suggestions/models.py | 2 +-
.../templates/suggestions/suggestion.html | 2 +-
website/templates/403.html | 2 +-
website/templates/partials/header.html | 4 +-
23 files changed, 105 insertions(+), 259 deletions(-)
delete mode 100644 accounts/admin.py
delete mode 100644 accounts/backends.py
create mode 100644 accounts/migrations/0002_delete_user.py
delete mode 100644 accounts/models.py
delete mode 100644 accounts/templates/registration/change_password.html
delete mode 100644 accounts/templates/registration/login.html
delete mode 100644 accounts/templates/registration/login_switch.html
create mode 100644 nix/authens/01-get-success_url.patch
create mode 100644 nix/authens/default.nix
create mode 100644 nix/python-cas/default.nix
diff --git a/accounts/admin.py b/accounts/admin.py
deleted file mode 100644
index e363eb6..0000000
--- a/accounts/admin.py
+++ /dev/null
@@ -1,38 +0,0 @@
-from django.contrib import admin
-from django.contrib.auth.admin import UserAdmin as DjangoUserAdmin
-from django.utils.translation import gettext_lazy as _
-from .models import User
-
-
-class UserAdmin(DjangoUserAdmin):
- fieldsets = (
- (None, {"fields": ("email", "password")}),
- (_("Personal info"), {"fields": ("public_name",)}),
- (
- _("Permissions"),
- {
- "fields": (
- "is_active",
- "is_staff",
- "is_superuser",
- "groups",
- "user_permissions",
- ),
- },
- ),
- )
- add_fieldsets = (
- (
- None,
- {
- "classes": ("wide",),
- "fields": ("email", "public_name", "password1", "password2"),
- },
- ),
- )
- list_display = ("public_name", "email", "is_staff")
- search_fields = ("public_name", "email")
- ordering = ("email",)
-
-
-admin.site.register(User, UserAdmin)
diff --git a/accounts/backends.py b/accounts/backends.py
deleted file mode 100644
index 418eaa0..0000000
--- a/accounts/backends.py
+++ /dev/null
@@ -1,7 +0,0 @@
-from django_cas_ng.backends import CASBackend as DjangoCasNgBackend
-
-
-class CasBackend(DjangoCasNgBackend):
- def clean_username(self, username):
- # We need to build an email out of the CAS username
- return username.lower() + "@clipper.ens.fr"
diff --git a/accounts/forms.py b/accounts/forms.py
index e33b693..75401dc 100644
--- a/accounts/forms.py
+++ b/accounts/forms.py
@@ -1,5 +1,5 @@
from django.forms import ModelForm, ValidationError
-from .models import User
+from django.contrib.auth.models import User
class AccountSettingsForm(ModelForm):
@@ -7,13 +7,13 @@ class AccountSettingsForm(ModelForm):
class Meta:
model = User
- fields = ["public_name"]
+ fields = ["username"]
def clean_public_name(self):
- public_name = self.cleaned_data["public_name"]
+ public_name = self.cleaned_data["username"]
public_name = public_name.strip()
if (
- User.objects.filter(public_name=public_name)
+ User.objects.filter(username=public_name)
.exclude(pk=self.instance.pk)
.exists()
):
diff --git a/accounts/migrations/0002_delete_user.py b/accounts/migrations/0002_delete_user.py
new file mode 100644
index 0000000..c4f13a4
--- /dev/null
+++ b/accounts/migrations/0002_delete_user.py
@@ -0,0 +1,16 @@
+# Generated by Django 4.2.11 on 2024-07-02 20:26
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('accounts', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.DeleteModel(
+ name='User',
+ ),
+ ]
diff --git a/accounts/models.py b/accounts/models.py
deleted file mode 100644
index ce2ed5b..0000000
--- a/accounts/models.py
+++ /dev/null
@@ -1,76 +0,0 @@
-from django.db import models
-from django.contrib.auth.base_user import AbstractBaseUser, BaseUserManager
-from django.contrib.auth.models import PermissionsMixin
-from django.core.mail import send_mail
-
-
-class User(AbstractBaseUser, PermissionsMixin):
- USERNAME_FIELD = "email"
- EMAIL_FIELD = "email"
- REQUIRED_FIELDS = ["public_name"]
- MAX_NAME_LENGTH = 150
-
- email = models.EmailField(verbose_name="adresse email", unique=True)
- public_name = models.CharField(
- verbose_name="nom ou pseudo",
- max_length=MAX_NAME_LENGTH,
- unique=True,
- help_text="Ce nom est utilisé pour toutes les interactions publiques sur GestioJeux. Il doit être unique.",
- )
- is_staff = models.BooleanField(
- "statut équipe",
- default=False,
- help_text="Précise si l’utilisateur peut se connecter à ce site d'administration.",
- )
- is_active = models.BooleanField(
- "actif",
- default=True,
- help_text="Précise si l’utilisateur doit être considéré comme actif. Décochez ceci plutôt que de supprimer le compte.",
- )
-
- objects = BaseUserManager()
-
- class Meta:
- verbose_name = "utilisateur·ice"
- verbose_name_plural = "utilisateur·ice·s"
-
- @classmethod
- def generate_unique_public_name(cls, base_name):
- index = 0
- public_name = base_name
- while cls.objects.filter(public_name=public_name).exists():
- index += 1
-
- # ensure the resulting string is not too long
- tail_length = len(str(index))
- combined_length = len(base_name) + tail_length
- if cls.MAX_NAME_LENGTH < combined_length:
- base_name = base_name[: cls.MAX_NAME_LENGTH - tail_length]
-
- public_name = base_name + str(index)
-
- return public_name
-
- def save(self, *args, **kwargs):
- if not self.public_name:
- # Fill the public name with a generated one from email address
- base_name = self.email.split("@")[0]
- self.public_name = User.generate_unique_public_name(base_name)
- super().save(*args, **kwargs)
-
- def get_full_name(self):
- return self.public_name
-
- def get_short_name(self):
- return self.public_name
-
- def email_user(self, subject, message, from_email=None, **kwargs):
- """Send an email to this user."""
- send_mail(subject, message, from_email, [self.email], **kwargs)
-
- @classmethod
- def normalize_username(cls, username):
- return super().normalize_username(username.lower())
-
- def __str__(self):
- return self.public_name
diff --git a/accounts/templates/registration/account_settings.html b/accounts/templates/registration/account_settings.html
index 2bb23f5..5c13123 100644
--- a/accounts/templates/registration/account_settings.html
+++ b/accounts/templates/registration/account_settings.html
@@ -3,7 +3,7 @@
{% block "content" %}
Paramètres du compte
-Vous êtes connecté en tant que {{ request.user.email }}
+Vous êtes connecté en tant que {{ request.user }}
-{% endblock %}
-
diff --git a/accounts/templates/registration/login.html b/accounts/templates/registration/login.html
deleted file mode 100644
index b55ff9e..0000000
--- a/accounts/templates/registration/login.html
+++ /dev/null
@@ -1,13 +0,0 @@
-{% extends "small_page.html" %}
-
-{% block "content" %}
-Connexion par mot de passe
-
-
-{% endblock %}
-
diff --git a/accounts/templates/registration/login_switch.html b/accounts/templates/registration/login_switch.html
deleted file mode 100644
index 0fd7dac..0000000
--- a/accounts/templates/registration/login_switch.html
+++ /dev/null
@@ -1,15 +0,0 @@
-{% extends "small_page.html" %}
-
-{% block "content" %}
-Mode de connexion
-
-
- Compte clipper
-
-
- Mot de passe
-
-
-Si vous êtes exté·e et que vous n'avez pas encore de compte, demandez en un.
-{% endblock %}
-
diff --git a/accounts/urls.py b/accounts/urls.py
index 3e3fd1b..8577cf3 100644
--- a/accounts/urls.py
+++ b/accounts/urls.py
@@ -1,25 +1,9 @@
from django.urls import include, path
-import django.contrib.auth.views as dj_auth_views
-from .views import LoginView, LogoutView, PasswordChangeView, AccountSettingsView
-import django_cas_ng.views
+from .views import PasswordChangeView, AccountSettingsView
app_name = "accounts"
-cas_patterns = [
- path("login/", django_cas_ng.views.LoginView.as_view(), name="cas_ng_login"),
- path("logout/", django_cas_ng.views.LogoutView.as_view(), name="cas_ng_logout"),
- path(
- "callback/",
- django_cas_ng.views.CallbackView.as_view(),
- name="cas_ng_proxy_callback",
- ),
-]
-
accounts_patterns = [
- path("cas/", include(cas_patterns)),
- path("login/", LoginView.as_view(), name="login"),
- path("logout/", LogoutView.as_view(), name="logout"),
- path("password_login/", dj_auth_views.LoginView.as_view(), name="password_login"),
path("change_password/", PasswordChangeView.as_view(), name="change_password"),
path("settings/", AccountSettingsView.as_view(), name="account_settings"),
]
diff --git a/accounts/views.py b/accounts/views.py
index 277fd11..6bf66f4 100644
--- a/accounts/views.py
+++ b/accounts/views.py
@@ -14,59 +14,6 @@ from urllib.parse import quote as urlquote
from .forms import AccountSettingsForm
-class LoginView(TemplateView):
- template_name = "registration/login_switch.html"
-
- def dispatch(self, request, *args, **kwargs):
- if request.user.is_authenticated:
- return redirect(self.get_next_url() or "/")
-
- return super().dispatch(request, *args, **kwargs)
-
- def get_next_url(self):
- if self.request.method == "GET":
- req_dict = self.request.GET
- elif self.request.method == "POST":
- req_dict = self.request.POST
- return req_dict.get("next")
-
- def get_context_data(self, **kwargs):
- context = super().get_context_data(**kwargs)
-
- next_url = self.get_next_url()
- if next_url:
- context["pass_url"] = "{}?next={}".format(
- reverse("accounts:password_login"), urlquote(next_url, safe="")
- )
- context["cas_url"] = "{}?next={}".format(
- reverse("accounts:cas_ng_login"), urlquote(next_url, safe="")
- )
- else:
- context["pass_url"] = reverse("accounts:password_login")
- context["cas_url"] = reverse("accounts:cas_ng_login")
-
- return context
-
-
-class LogoutView(RedirectView):
- permanent = False
-
- def get_redirect_url(self, *args, **kwargs):
- CAS_BACKEND_NAME = "accounts.backends.CasBackend"
- if self.request.session["_auth_user_backend"] != CAS_BACKEND_NAME:
- auth_logout(self.request)
- if "next" in self.request.GET:
- return self.request.GET["next"]
- return reverse("website:home")
-
- if "next" in self.request.GET:
- return "{}?next={}".format(
- reverse("accounts:cas_ng_logout"),
- urlquote(self.request.GET["next"], safe=""),
- )
- return reverse("accounts:cas_ng_logout")
-
-
@receiver(user_logged_in)
def on_login(request, user, **kwargs):
messages.success(request, "Connexion réussie. Bienvenue, {}.".format(user))
diff --git a/comments/models.py b/comments/models.py
index 209084f..c8f9f9a 100644
--- a/comments/models.py
+++ b/comments/models.py
@@ -1,5 +1,5 @@
+from django.contrib.auth.models import User
from django.db import models
-from accounts.models import User
class AbstractComment(models.Model):
diff --git a/comments/templates/comments.html b/comments/templates/comments.html
index 33080e5..94222bc 100644
--- a/comments/templates/comments.html
+++ b/comments/templates/comments.html
@@ -34,6 +34,6 @@
{% else %}
-Connectez-vous pour ajouter un commentaire.
+Connectez-vous pour ajouter un commentaire.
{% endif %}
{% endif %}
diff --git a/default.nix b/default.nix
index a260880..a4ecad7 100644
--- a/default.nix
+++ b/default.nix
@@ -7,13 +7,14 @@ let
nix-pkgs = import sources.nix-pkgs { inherit pkgs; };
python3 = pkgs.python3.override {
- packageOverrides = _: _: {
+ packageOverrides = final: _: {
inherit (nix-pkgs)
django-autoslug
- django-cas-ng
loadcredential
markdown-icons
+ python-cas
;
+ authens = final.callPackage ./nix/authens {};
};
};
in
@@ -25,9 +26,9 @@ in
packages = [
(python3.withPackages (ps: [
ps.django
+ ps.django-types
ps.django-autoslug
ps.loadcredential
- ps.django-cas-ng
ps.django-cleanup
ps.django-haystack
ps.django-markdownx
@@ -35,6 +36,7 @@ in
ps.pillow
ps.whoosh
ps.markdown-icons
+ ps.authens
# Django haystack is drunk
ps.setuptools
diff --git a/gestiojeux/settings.py b/gestiojeux/settings.py
index 4db9268..b40d26b 100644
--- a/gestiojeux/settings.py
+++ b/gestiojeux/settings.py
@@ -12,6 +12,7 @@ https://docs.djangoproject.com/en/3.0/ref/settings/
import os
+from django.urls import reverse_lazy
from loadcredential import Credentials
# Secrets
@@ -80,7 +81,6 @@ INSTALLED_APPS = [
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
- "django_cas_ng",
"django_tables2",
"markdownx",
"haystack",
@@ -91,6 +91,7 @@ INSTALLED_APPS = [
"suggestions",
"loans",
"django_cleanup", # Keep last
+ "authens",
]
MIDDLEWARE = [
@@ -101,7 +102,6 @@ MIDDLEWARE = [
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
- "django_cas_ng.middleware.CASMiddleware",
]
ROOT_URLCONF = "gestiojeux.urls"
@@ -124,11 +124,12 @@ TEMPLATES = [
WSGI_APPLICATION = "gestiojeux.wsgi.application"
-AUTH_USER_MODEL = "accounts.User"
+# Authentication backends
AUTHENTICATION_BACKENDS = (
"django.contrib.auth.backends.ModelBackend",
- "accounts.backends.CasBackend",
+ "authens.backends.ENSCASBackend",
+ "authens.backends.OldCASBackend",
)
# Password validation
@@ -143,7 +144,11 @@ AUTH_PASSWORD_VALIDATORS = [
{"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"},
]
-LOGIN_URL = "accounts:login"
+LOGIN_URL = reverse_lazy("authens:login")
+
+LOGIN_REDIRECT_URL = reverse_lazy("website:home")
+
+LOGOUT_REDIRECT_URL = reverse_lazy("website:home")
# Use markdown extensions
MARKDOWNX_MARKDOWN_EXTENSIONS = [
@@ -185,12 +190,4 @@ MEDIA_URL = "/media/"
STATIC_ROOT = os.path.join(PUBLIC_DIR, "static")
MEDIA_ROOT = os.path.join(PUBLIC_DIR, "media")
-# CAS settings
-CAS_SERVER_URL = "https://cas.eleves.ens.fr/"
-CAS_VERIFY_URL = "https://cas.eleves.ens.fr/"
-CAS_VERSION = "CAS_2_SAML_1_0"
-CAS_IGNORE_REFERER = True
-CAS_LOGIN_MSG = None
-CAS_LOGIN_URL_NAME = "accounts:cas_ng_login"
-CAS_LOGOUT_URL_NAME = "accounts:cas_ng_logout"
diff --git a/gestiojeux/urls.py b/gestiojeux/urls.py
index 7651a39..6ebd097 100644
--- a/gestiojeux/urls.py
+++ b/gestiojeux/urls.py
@@ -24,6 +24,7 @@ urlpatterns = [
path("inventory/", include("inventory.urls")),
path("suggestions/", include("suggestions.urls")),
path("account/", include("accounts.urls")),
+ path("authens/", include("authens.urls")),
path("", include("website.urls")),
]
diff --git a/nix/authens/01-get-success_url.patch b/nix/authens/01-get-success_url.patch
new file mode 100644
index 0000000..c0d7650
--- /dev/null
+++ b/nix/authens/01-get-success_url.patch
@@ -0,0 +1,15 @@
+diff --git a/authens/views.py b/authens/views.py
+index 0478861..b1c93e9 100644
+--- a/authens/views.py
++++ b/authens/views.py
+@@ -138,8 +138,8 @@ class LogoutView(auth_views.LogoutView):
+ else:
+ self.cas_connected = False
+
+- def get_next_page(self):
+- next_page = super().get_next_page()
++ def get_success_url(self):
++ next_page = super().get_success_url()
+ if self.cas_connected:
+ cas_client = get_cas_client(self.request)
+
diff --git a/nix/authens/default.nix b/nix/authens/default.nix
new file mode 100644
index 0000000..2d27946
--- /dev/null
+++ b/nix/authens/default.nix
@@ -0,0 +1,22 @@
+{
+ python-cas,
+ django,
+ ldap,
+ buildPythonPackage,
+}:
+buildPythonPackage rec {
+ pname = "authens";
+ version = "v0.1b5";
+ doCheck = false;
+ patches = [ ./01-get-success_url.patch ];
+ src = builtins.fetchGit {
+ url = "https://git.eleves.ens.fr/klub-dev-ens/authens.git";
+ #rev = "master";
+ #sha256 = "sha256-R0Nw212/BOPHfpspT5wzxtji1vxZ/JOuwr00naklWE8=";
+ };
+ propagatedBuildInputs = [
+ django
+ ldap
+ python-cas
+ ];
+}
diff --git a/nix/python-cas/default.nix b/nix/python-cas/default.nix
new file mode 100644
index 0000000..42a51cf
--- /dev/null
+++ b/nix/python-cas/default.nix
@@ -0,0 +1,23 @@
+{
+ requests,
+ lxml,
+ six,
+ buildPythonPackage,
+ fetchFromGitHub,
+}:
+buildPythonPackage rec {
+ pname = "python-cas";
+ version = "1.6.0";
+ doCheck = false;
+ src = fetchFromGitHub {
+ owner = "python-cas";
+ repo = "python-cas";
+ rev = "v1.6.0";
+ sha512 = "sha512-qnYzgwELUij2EdqA6H17q8vnNUsfI7DkbZSI8CCIGfXOM+cZ7vsWe7CJxzsDUw73sBPB4+zzpLxvb7tpm/IDeg==";
+ };
+ propagatedBuildInputs = [
+ requests
+ lxml
+ six
+ ];
+}
diff --git a/suggestions/models.py b/suggestions/models.py
index dff5251..f3ecd4e 100644
--- a/suggestions/models.py
+++ b/suggestions/models.py
@@ -4,7 +4,7 @@ from django.core.validators import MinValueValidator
from django.core.exceptions import ValidationError
from autoslug import AutoSlugField
from website.validators import MaxFileSizeValidator
-from accounts.models import User
+from django.contrib.auth.models import User
from inventory.models import Category, Tag
from comments.models import AbstractComment
diff --git a/suggestions/templates/suggestions/suggestion.html b/suggestions/templates/suggestions/suggestion.html
index f1c0e27..5830e7e 100644
--- a/suggestions/templates/suggestions/suggestion.html
+++ b/suggestions/templates/suggestions/suggestion.html
@@ -44,7 +44,7 @@
Voter pour acheter ce jeu
{% endif %}
{% else %}
- Connectez-vous pour voter pour une suggestion.
+ Connectez-vous pour voter pour une suggestion.
{% endif %}
{% if suggestion.description %}
diff --git a/website/templates/403.html b/website/templates/403.html
index b6c5fe5..d2ebbd1 100644
--- a/website/templates/403.html
+++ b/website/templates/403.html
@@ -5,7 +5,7 @@
Vous n'avez pas la permission pour consulter cette page.
{% if not user.is_authenticated %}
Cet accès vous est probablement refusé car vous n'êtes actuellement pas connecté·e.
-Vous pouvez vous rendre à la page de connexion.
+Vous pouvez vous rendre à la page de connexion.
{% endif %}
Vous pouvez retourner sur la page d'accueil.
{% endblock %}
diff --git a/website/templates/partials/header.html b/website/templates/partials/header.html
index fd1d171..fb834c3 100644
--- a/website/templates/partials/header.html
+++ b/website/templates/partials/header.html
@@ -21,9 +21,9 @@
{% endwith %}