diff --git a/src/app/settings.py b/src/app/settings.py index af2393f..14c4af8 100644 --- a/src/app/settings.py +++ b/src/app/settings.py @@ -129,6 +129,7 @@ SOCIALACCOUNT_PROVIDERS = { } AUTH_PASSWORD_VALIDATORS = [] +AUTH_USER_MODEL = "dgsi.User" ### diff --git a/src/dgsi/admin.py b/src/dgsi/admin.py index 846f6b4..5279d05 100644 --- a/src/dgsi/admin.py +++ b/src/dgsi/admin.py @@ -1 +1,6 @@ -# Register your models here. +from django.contrib import admin +from django.contrib.auth.admin import UserAdmin + +from dgsi.models import User + +admin.site.register(User, UserAdmin) diff --git a/src/dgsi/migrations/0001_initial.py b/src/dgsi/migrations/0001_initial.py index 1a56d02..a9dfd9c 100644 --- a/src/dgsi/migrations/0001_initial.py +++ b/src/dgsi/migrations/0001_initial.py @@ -1,7 +1,8 @@ -# Generated by Django 4.2.12 on 2024-09-13 14:30 +# Generated by Django 4.2.12 on 2024-09-14 13:35 -import django.db.models.deletion -from django.conf import settings +import django.contrib.auth.models +import django.contrib.auth.validators +import django.utils.timezone from django.db import migrations, models @@ -9,12 +10,12 @@ class Migration(migrations.Migration): initial = True dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ("auth", "0012_alter_user_first_name_max_length"), ] operations = [ migrations.CreateModel( - name="Profile", + name="User", fields=[ ( "id", @@ -25,13 +26,106 @@ class Migration(migrations.Migration): verbose_name="ID", ), ), + ("password", models.CharField(max_length=128, verbose_name="password")), ( - "user", - models.OneToOneField( - on_delete=django.db.models.deletion.CASCADE, - to=settings.AUTH_USER_MODEL, + "last_login", + models.DateTimeField( + blank=True, null=True, verbose_name="last login" + ), + ), + ( + "is_superuser", + models.BooleanField( + default=False, + help_text="Designates that this user has all permissions without explicitly assigning them.", + verbose_name="superuser status", + ), + ), + ( + "username", + models.CharField( + error_messages={ + "unique": "A user with that username already exists." + }, + help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.", + max_length=150, + unique=True, + validators=[ + django.contrib.auth.validators.UnicodeUsernameValidator() + ], + verbose_name="username", + ), + ), + ( + "first_name", + models.CharField( + blank=True, max_length=150, verbose_name="first name" + ), + ), + ( + "last_name", + models.CharField( + blank=True, max_length=150, verbose_name="last name" + ), + ), + ( + "email", + models.EmailField( + blank=True, max_length=254, verbose_name="email address" + ), + ), + ( + "is_staff", + models.BooleanField( + default=False, + help_text="Designates whether the user can log into this admin site.", + verbose_name="staff status", + ), + ), + ( + "is_active", + models.BooleanField( + default=True, + help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.", + verbose_name="active", + ), + ), + ( + "date_joined", + models.DateTimeField( + default=django.utils.timezone.now, verbose_name="date joined" + ), + ), + ( + "groups", + models.ManyToManyField( + blank=True, + help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.", + related_name="user_set", + related_query_name="user", + to="auth.group", + verbose_name="groups", + ), + ), + ( + "user_permissions", + models.ManyToManyField( + blank=True, + help_text="Specific permissions for this user.", + related_name="user_set", + related_query_name="user", + to="auth.permission", + verbose_name="user permissions", ), ), ], + options={ + "verbose_name": "user", + "verbose_name_plural": "users", + "abstract": False, + }, + managers=[ + ("objects", django.contrib.auth.models.UserManager()), + ], ), ] diff --git a/src/dgsi/models.py b/src/dgsi/models.py index 6d3491f..b60cd1d 100644 --- a/src/dgsi/models.py +++ b/src/dgsi/models.py @@ -1,31 +1,36 @@ -# Create your models here. - from dataclasses import dataclass from functools import cached_property from typing import Optional from asgiref.sync import async_to_sync -from django.contrib.auth.models import User -from django.db import models +from django.contrib.auth.models import AbstractUser from kanidm.models.person import Person from shared.kanidm import client +ADMIN_GROUP = "idm_admins@sso.dgnum.eu" + @dataclass class KanidmProfile: person: Person - secret: Optional[str] + radius_secret: Optional[str] -class Profile(models.Model): - user = models.OneToOneField(User, on_delete=models.CASCADE) +class User(AbstractUser): + """ + Custom User class, to have a direct link to the Kanidm data. + """ @cached_property - def kanidm_profile(self): - person = async_to_sync(client.person_account_get)(self.user.username) - data = async_to_sync(client.get_radius_token)(self.user.username).data + def kanidm(self) -> KanidmProfile: + radius_data = async_to_sync(client.get_radius_token)(self.username).data - secret = data.get("secret") if data is not None else None + return KanidmProfile( + person=async_to_sync(client.person_account_get)(self.username), + radius_secret=radius_data and radius_data.get("secret"), + ) - return KanidmProfile(person, secret) + @property + def is_admin(self) -> bool: + return ADMIN_GROUP in self.kanidm.person.memberof diff --git a/src/dgsi/views.py b/src/dgsi/views.py index 786a36b..75ebf1f 100644 --- a/src/dgsi/views.py +++ b/src/dgsi/views.py @@ -1,18 +1,6 @@ from django.contrib.auth.mixins import LoginRequiredMixin -from django.contrib.auth.models import User from django.views.generic import TemplateView class ProfileView(LoginRequiredMixin, TemplateView): - model = User template_name = "account/profile.html" - - def get_context_data(self, **kwargs): - assert isinstance(self.request.user, User) - - return super().get_context_data( - # Pyright throws a fit as it doesn't detect the reverse relation - # giving a user its profile - profile=self.request.user.profile.kanidm_profile, # pyright: ignore - **kwargs - ) diff --git a/src/shared/templates/account/profile.html b/src/shared/templates/account/profile.html index cb3bf13..90e20ae 100644 --- a/src/shared/templates/account/profile.html +++ b/src/shared/templates/account/profile.html @@ -1,25 +1,26 @@ {% extends "base.html" %} +{% load i18n %} {% block content %}

- Profil de {{ profile.person.displayname }} - {{ profile.person.name }} + Profil de {{ user.kanidm.person.displayname }} + {{ user.kanidm.person.name }}


Identifiant unique :

- {{ profile.person.uuid }} + {{ user.kanidm.person.uuid }}

Token RADIUS :

- {{ profile.secret }} + {{ user.kanidm.radius_secret }}

Membre des groupes suivants :

- {% for group in profile.person.memberof %} + {% for group in user.kanidm.person.memberof %} {{ group }}
{% endfor %} {% endblock content %}