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" %}
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" %} -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 @@