feat(User): Switch to a custom model, simplifying the logic
This commit is contained in:
parent
c02b29a6f5
commit
f56961c7bb
6 changed files with 133 additions and 39 deletions
|
@ -129,6 +129,7 @@ SOCIALACCOUNT_PROVIDERS = {
|
||||||
}
|
}
|
||||||
|
|
||||||
AUTH_PASSWORD_VALIDATORS = []
|
AUTH_PASSWORD_VALIDATORS = []
|
||||||
|
AUTH_USER_MODEL = "dgsi.User"
|
||||||
|
|
||||||
|
|
||||||
###
|
###
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
import django.contrib.auth.models
|
||||||
from django.conf import settings
|
import django.contrib.auth.validators
|
||||||
|
import django.utils.timezone
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
@ -9,12 +10,12 @@ class Migration(migrations.Migration):
|
||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
("auth", "0012_alter_user_first_name_max_length"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name="Profile",
|
name="User",
|
||||||
fields=[
|
fields=[
|
||||||
(
|
(
|
||||||
"id",
|
"id",
|
||||||
|
@ -25,13 +26,106 @@ class Migration(migrations.Migration):
|
||||||
verbose_name="ID",
|
verbose_name="ID",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
("password", models.CharField(max_length=128, verbose_name="password")),
|
||||||
(
|
(
|
||||||
"user",
|
"last_login",
|
||||||
models.OneToOneField(
|
models.DateTimeField(
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
blank=True, null=True, verbose_name="last login"
|
||||||
to=settings.AUTH_USER_MODEL,
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"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()),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,31 +1,36 @@
|
||||||
# Create your models here.
|
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from functools import cached_property
|
from functools import cached_property
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from asgiref.sync import async_to_sync
|
from asgiref.sync import async_to_sync
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import AbstractUser
|
||||||
from django.db import models
|
|
||||||
from kanidm.models.person import Person
|
from kanidm.models.person import Person
|
||||||
|
|
||||||
from shared.kanidm import client
|
from shared.kanidm import client
|
||||||
|
|
||||||
|
ADMIN_GROUP = "idm_admins@sso.dgnum.eu"
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class KanidmProfile:
|
class KanidmProfile:
|
||||||
person: Person
|
person: Person
|
||||||
secret: Optional[str]
|
radius_secret: Optional[str]
|
||||||
|
|
||||||
|
|
||||||
class Profile(models.Model):
|
class User(AbstractUser):
|
||||||
user = models.OneToOneField(User, on_delete=models.CASCADE)
|
"""
|
||||||
|
Custom User class, to have a direct link to the Kanidm data.
|
||||||
|
"""
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def kanidm_profile(self):
|
def kanidm(self) -> KanidmProfile:
|
||||||
person = async_to_sync(client.person_account_get)(self.user.username)
|
radius_data = async_to_sync(client.get_radius_token)(self.username).data
|
||||||
data = async_to_sync(client.get_radius_token)(self.user.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
|
||||||
|
|
|
@ -1,18 +1,6 @@
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.contrib.auth.models import User
|
|
||||||
from django.views.generic import TemplateView
|
from django.views.generic import TemplateView
|
||||||
|
|
||||||
|
|
||||||
class ProfileView(LoginRequiredMixin, TemplateView):
|
class ProfileView(LoginRequiredMixin, TemplateView):
|
||||||
model = User
|
|
||||||
template_name = "account/profile.html"
|
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
|
|
||||||
)
|
|
||||||
|
|
|
@ -1,25 +1,26 @@
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h2 class="subtitle">
|
<h2 class="subtitle">
|
||||||
<span>Profil de {{ profile.person.displayname }}</span>
|
<span>Profil de {{ user.kanidm.person.displayname }}</span>
|
||||||
<span class="tag is-primary is-medium is-pulled-right">{{ profile.person.name }}</span>
|
<span class="tag is-primary is-medium is-pulled-right">{{ user.kanidm.person.name }}</span>
|
||||||
</h2>
|
</h2>
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<h3 class="has-text-weight-bold mb-3">Identifiant unique :</h3>
|
<h3 class="has-text-weight-bold mb-3">Identifiant unique :</h3>
|
||||||
|
|
||||||
<span class="button is-fullwidth">{{ profile.person.uuid }}</span>
|
<span class="button is-fullwidth">{{ user.kanidm.person.uuid }}</span>
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
<h3 class="has-text-weight-bold mb-3">Token RADIUS :</h3>
|
<h3 class="has-text-weight-bold mb-3">Token RADIUS :</h3>
|
||||||
|
|
||||||
<span class="button is-fullwidth">{{ profile.secret }}</span>
|
<span class="button is-fullwidth">{{ user.kanidm.radius_secret }}</span>
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
<h3 class="has-text-weight-bold mb-3">Membre des groupes suivants :</h3>
|
<h3 class="has-text-weight-bold mb-3">Membre des groupes suivants :</h3>
|
||||||
|
|
||||||
{% for group in profile.person.memberof %}
|
{% for group in user.kanidm.person.memberof %}
|
||||||
<span class="button is-fullwidth">{{ group }}</span><br>
|
<span class="button is-fullwidth">{{ group }}</span><br>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|
Loading…
Add table
Reference in a new issue