start adding system settings model

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
This commit is contained in:
Marc 'risson' Schmitt
2026-06-17 16:57:29 +02:00
parent 13f938cb64
commit 364b1e4540
4 changed files with 370 additions and 1 deletions
+113
View File
@@ -0,0 +1,113 @@
"""Serializer for settings"""
from typing import get_args
from django.utils.translation import gettext_lazy as _
from drf_spectacular.extensions import OpenApiSerializerFieldExtension
from drf_spectacular.plumbing import build_basic_type, build_object_type
from rest_framework.exceptions import ValidationError
from rest_framework.fields import JSONField
from rest_framework.generics import RetrieveUpdateAPIView
from rest_framework.permissions import SAFE_METHODS
from authentik.admin.models import SystemSettings
from authentik.core.api.utils import JSONDictField, ModelSerializer
from authentik.rbac.permissions import HasPermission
from authentik.tenants.flags import Flag
class FlagJSONField(JSONDictField):
def to_internal_value(self, data: str):
flags = super().to_internal_value(data)
for flag in Flag.available(visibility="system", exclude_system=False):
flags[flag().key] = flag.get()
return flags
def to_representation(self, value: dict) -> dict:
new_value = value.copy()
for flag in Flag.available(exclude_system=False):
_flag = flag()
# Exclude any system flags that aren't modifiable
if _flag.visibility == "system":
new_value.pop(_flag.key, None)
# Explicitly present unset flags as if they were set to default
if _flag.key not in value:
value[_flag.key] = _flag.default
return super().to_representation(new_value)
def run_validators(self, value: dict):
super().run_validators(value)
for flag in Flag.available():
_flag = flag()
if _flag.key not in value:
continue
flag_value = value.get(_flag.key)
flag_type = get_args(_flag.__orig_bases__[0])[0]
if flag_value and not isinstance(flag_value, flag_type):
raise ValidationError(
_("Value for flag {flag_key} needs to be of type {type}.").format(
flag_key=_flag.key, type=flag_type.__name__
)
)
class FlagsJSONExtension(OpenApiSerializerFieldExtension):
"""Generate API Schema for JSON fields as"""
target_class = "authentik.tenants.api.settings.FlagJSONField"
def map_serializer_field(self, auto_schema, direction):
props = {}
for flag in Flag.available():
_flag = flag()
props[_flag.key] = build_basic_type(get_args(_flag.__orig_bases__[0])[0])
if _flag.description:
props[_flag.key]["description"] = _flag.description
if _flag.deprecated:
props[_flag.key]["deprecated"] = _flag.deprecated
return build_object_type(props, required=props.keys())
class SettingsSerializer(ModelSerializer):
"""Settings Serializer"""
footer_links = JSONField(required=False)
flags = FlagJSONField()
class Meta:
model = SystemSettings
fields = [
"avatars",
"default_user_change_name",
"default_user_change_email",
"default_user_change_username",
"event_retention",
"reputation_lower_limit",
"reputation_upper_limit",
"footer_links",
"gdpr_compliance",
"impersonation",
"impersonation_require_reason",
"default_token_duration",
"default_token_length",
"pagination_default_page_size",
"pagination_max_page_size",
"flags",
]
class SettingsView(RetrieveUpdateAPIView):
"""Settings view"""
queryset = SystemSettings.objects.filter(pk=True)
serializer_class = SettingsSerializer
filter_backends = []
def get_permissions(self):
return [
HasPermission(
"authentik_rbac.view_system_settings"
if self.request.method in SAFE_METHODS
else "authentik_rbac.edit_system_settings"
)()
]
+152
View File
@@ -0,0 +1,152 @@
# Generated by Django 5.2.15 on 2026-06-17 14:56
import authentik.lib.models
import authentik.lib.utils.time
import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = []
operations = [
migrations.CreateModel(
name="VersionHistory",
fields=[
("id", models.BigAutoField(primary_key=True, serialize=False)),
("timestamp", models.DateTimeField()),
("version", models.TextField()),
("build", models.TextField()),
],
options={
"verbose_name": "Version history",
"verbose_name_plural": "Version history",
"db_table": "authentik_version_history",
"ordering": ("-timestamp",),
"managed": False,
"default_permissions": [],
},
),
migrations.CreateModel(
name="SystemSettings",
fields=[
("id", models.BooleanField(default=True, primary_key=True, serialize=False)),
(
"avatars",
models.TextField(
default="gravatar,initials",
help_text="Configure how authentik should show avatars for users.",
),
),
(
"default_user_change_name",
models.BooleanField(
default=True, help_text="Enable the ability for users to change their name."
),
),
(
"default_user_change_email",
models.BooleanField(
default=False,
help_text="Enable the ability for users to change their email address.",
),
),
(
"default_user_change_username",
models.BooleanField(
default=False,
help_text="Enable the ability for users to change their username.",
),
),
(
"event_retention",
models.TextField(
default="days=365",
help_text="Events will be deleted after this duration.(Format: weeks=3;days=2;hours=3,seconds=2).",
validators=[authentik.lib.utils.time.timedelta_string_validator],
),
),
(
"reputation_lower_limit",
models.IntegerField(
default=-5,
help_text="Reputation cannot decrease lower than this value. Zero or negative.",
validators=[django.core.validators.MaxValueValidator(0)],
),
),
(
"reputation_upper_limit",
models.IntegerField(
default=5,
help_text="Reputation cannot increase higher than this value. Zero or positive.",
validators=[django.core.validators.MinValueValidator(0)],
),
),
(
"footer_links",
models.JSONField(
blank=True,
default=list,
help_text="The option configures the footer links on the flow executor pages.",
),
),
(
"gdpr_compliance",
models.BooleanField(
default=True,
help_text="When enabled, all the events caused by a user will be deleted upon the user's deletion.",
),
),
(
"impersonation",
models.BooleanField(
default=True, help_text="Globally enable/disable impersonation."
),
),
(
"impersonation_require_reason",
models.BooleanField(
default=True,
help_text="Require administrators to provide a reason for impersonating a user.",
),
),
(
"default_token_duration",
models.TextField(
default="days=1",
help_text="Default token duration",
validators=[authentik.lib.utils.time.timedelta_string_validator],
),
),
(
"default_token_length",
models.PositiveIntegerField(
default=60,
help_text="Default token length",
validators=[django.core.validators.MinValueValidator(1)],
),
),
(
"pagination_default_page_size",
models.PositiveIntegerField(
default=20,
help_text="Default page size for API responses, if no size was requested.",
),
),
(
"pagination_max_page_size",
models.PositiveIntegerField(default=100, help_text="Maximum page size"),
),
("flags", models.JSONField(default=dict)),
],
options={
"verbose_name": "System settings",
"verbose_name_plural": "System settings",
"default_permissions": [],
},
bases=(authentik.lib.models.InternallyManagedMixin, models.Model),
),
]
+105 -1
View File
@@ -1,7 +1,111 @@
"""authentik admin models"""
from django.db import models
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import IntegrityError, models
from django.utils.translation import gettext_lazy as _
from rest_framework.serializers import Serializer
from authentik.lib.models import InternallyManagedMixin, SerializerModel
from authentik.lib.utils.time import timedelta_string_validator
DEFAULT_TOKEN_DURATION = "days=1" # nosec
DEFAULT_TOKEN_LENGTH = 60
DEFAULT_REPUTATION_LOWER_LIMIT = -5
DEFAULT_REPUTATION_UPPER_LIMIT = 5
class SystemSettings(InternallyManagedMixin, SerializerModel):
id = models.BooleanField(primary_key=True, default=True)
avatars = models.TextField(
help_text=_("Configure how authentik should show avatars for users."),
default="gravatar,initials",
)
default_user_change_name = models.BooleanField(
help_text=_("Enable the ability for users to change their name."), default=True
)
default_user_change_email = models.BooleanField(
help_text=_("Enable the ability for users to change their email address."), default=False
)
default_user_change_username = models.BooleanField(
help_text=_("Enable the ability for users to change their username."), default=False
)
event_retention = models.TextField(
default="days=365",
validators=[timedelta_string_validator],
help_text=_(
"Events will be deleted after this duration.(Format: weeks=3;days=2;hours=3,seconds=2)."
),
)
reputation_lower_limit = models.IntegerField(
help_text=_("Reputation cannot decrease lower than this value. Zero or negative."),
default=DEFAULT_REPUTATION_LOWER_LIMIT,
validators=[MaxValueValidator(0)],
)
reputation_upper_limit = models.IntegerField(
help_text=_("Reputation cannot increase higher than this value. Zero or positive."),
default=DEFAULT_REPUTATION_UPPER_LIMIT,
validators=[MinValueValidator(0)],
)
footer_links = models.JSONField(
help_text=_("The option configures the footer links on the flow executor pages."),
default=list,
blank=True,
)
gdpr_compliance = models.BooleanField(
help_text=_(
"When enabled, all the events caused by a user "
"will be deleted upon the user's deletion."
),
default=True,
)
impersonation = models.BooleanField(
help_text=_("Globally enable/disable impersonation."), default=True
)
impersonation_require_reason = models.BooleanField(
help_text=_("Require administrators to provide a reason for impersonating a user."),
default=True,
)
default_token_duration = models.TextField(
help_text=_("Default token duration"),
default=DEFAULT_TOKEN_DURATION,
validators=[timedelta_string_validator],
)
default_token_length = models.PositiveIntegerField(
help_text=_("Default token length"),
default=DEFAULT_TOKEN_LENGTH,
validators=[MinValueValidator(1)],
)
pagination_default_page_size = models.PositiveIntegerField(
help_text=_("Default page size for API responses, if no size was requested."),
default=20,
)
pagination_max_page_size = models.PositiveIntegerField(
help_text=_("Maximum page size"),
default=100,
)
flags = models.JSONField(default=dict)
class Meta:
verbose_name = _("System settings")
verbose_name_plural = _("System settings")
default_permissions = []
def __str__(self):
return "System settings"
def save(self, *args, **kwargs):
if not self.pk:
raise IntegrityError("Only one instance of system settings is allowed")
super().save(*args, **kwargs)
@property
def serializer(self) -> Serializer:
from authentik.admin.api.settings import SettingsSerializer
return SettingsSerializer
class VersionHistory(models.Model):