diff --git a/authentik/brands/api.py b/authentik/brands/api.py
index c5960e4db..2b22a3508 100644
--- a/authentik/brands/api.py
+++ b/authentik/brands/api.py
@@ -54,7 +54,6 @@ class BrandSerializer(ModelSerializer):
"flow_unenrollment",
"flow_user_settings",
"flow_device_code",
- "event_retention",
"web_certificate",
"attributes",
]
@@ -125,7 +124,6 @@ class BrandViewSet(UsedByMixin, ModelViewSet):
"flow_unenrollment",
"flow_user_settings",
"flow_device_code",
- "event_retention",
"web_certificate",
]
ordering = ["domain"]
diff --git a/authentik/brands/migrations/0005_remove_brand_event_retention.py b/authentik/brands/migrations/0005_remove_brand_event_retention.py
new file mode 100644
index 000000000..5fdbc8d1b
--- /dev/null
+++ b/authentik/brands/migrations/0005_remove_brand_event_retention.py
@@ -0,0 +1,16 @@
+# Generated by Django 4.2.7 on 2023-12-12 06:41
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ("authentik_brands", "0004_tenant_flow_device_code"),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name="brand",
+ name="event_retention",
+ ),
+ ]
diff --git a/authentik/brands/models.py b/authentik/brands/models.py
index f86eddabf..268aa5c26 100644
--- a/authentik/brands/models.py
+++ b/authentik/brands/models.py
@@ -9,7 +9,6 @@ from structlog.stdlib import get_logger
from authentik.crypto.models import CertificateKeyPair
from authentik.flows.models import Flow
from authentik.lib.models import SerializerModel
-from authentik.lib.utils.time import timedelta_string_validator
LOGGER = get_logger()
@@ -51,14 +50,6 @@ class Brand(SerializerModel):
Flow, null=True, on_delete=models.SET_NULL, related_name="brand_device_code"
)
- 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)."
- ),
- )
-
web_certificate = models.ForeignKey(
CertificateKeyPair,
null=True,
diff --git a/authentik/brands/tests.py b/authentik/brands/tests.py
index b7d7d62ee..4b242b068 100644
--- a/authentik/brands/tests.py
+++ b/authentik/brands/tests.py
@@ -64,27 +64,6 @@ class TestBrands(APITestCase):
},
)
- def test_event_retention(self):
- """Test brand's event retention"""
- brand = Brand.objects.create(
- domain="foo",
- default=True,
- branding_title="custom",
- event_retention="weeks=3",
- )
- factory = RequestFactory()
- request = factory.get("/")
- request.brand = brand
- event = Event.new(action=EventAction.SYSTEM_EXCEPTION, message="test").from_http(request)
- self.assertEqual(event.expires.day, (event.created + timedelta_from_string("weeks=3")).day)
- self.assertEqual(
- event.expires.month,
- (event.created + timedelta_from_string("weeks=3")).month,
- )
- self.assertEqual(
- event.expires.year, (event.created + timedelta_from_string("weeks=3")).year
- )
-
def test_create_default_multiple(self):
"""Test attempted creation of multiple default brands"""
Brand.objects.create(
diff --git a/authentik/events/models.py b/authentik/events/models.py
index 35d2c1cbc..2e210b9d1 100644
--- a/authentik/events/models.py
+++ b/authentik/events/models.py
@@ -42,6 +42,7 @@ from authentik.lib.utils.http import get_client_ip, get_http_session
from authentik.lib.utils.time import timedelta_from_string
from authentik.policies.models import PolicyBindingModel
from authentik.stages.email.utils import TemplateEmailMessage
+from authentik.tenants.models import Tenant
LOGGER = get_logger()
if TYPE_CHECKING:
@@ -224,12 +225,14 @@ class Event(SerializerModel, ExpiringModel):
if QS_QUERY in self.context["http_request"]["args"]:
wrapped = self.context["http_request"]["args"][QS_QUERY]
self.context["http_request"]["args"] = cleanse_dict(QueryDict(wrapped))
- if hasattr(request, "brand"):
- brand: Brand = request.brand
+ if hasattr(request, "tenant"):
+ tenant: Tenant = request.tenant
# Because self.created only gets set on save, we can't use it's value here
# hence we set self.created to now and then use it
self.created = now()
- self.expires = self.created + timedelta_from_string(brand.event_retention)
+ self.expires = self.created + timedelta_from_string(tenant.event_retention)
+ if hasattr(request, "brand"):
+ brand: Brand = request.brand
self.brand = sanitize_dict(model_to_dict(brand))
if hasattr(request, "user"):
original_user = None
diff --git a/authentik/tenants/api.py b/authentik/tenants/api.py
index 890d46e27..1fc423c77 100644
--- a/authentik/tenants/api.py
+++ b/authentik/tenants/api.py
@@ -105,9 +105,10 @@ class SettingsSerializer(ModelSerializer):
"default_user_change_name",
"default_user_change_email",
"default_user_change_username",
+ "event_retention",
+ "footer_links",
"gdpr_compliance",
"impersonation",
- "footer_links",
]
diff --git a/authentik/tenants/migrations/0001_initial.py b/authentik/tenants/migrations/0001_initial.py
index d0feb55bd..eda3e7c91 100644
--- a/authentik/tenants/migrations/0001_initial.py
+++ b/authentik/tenants/migrations/0001_initial.py
@@ -6,6 +6,7 @@ import django.db.models.deletion
import django_tenants.postgresql_backend.base
from django.db import migrations, models
+import authentik.lib.utils.time
import authentik.tenants.models
from authentik.lib.config import CONFIG
@@ -22,9 +23,9 @@ def create_default_tenant(apps, schema_editor):
default_user_change_name=CONFIG.get_bool("default_user_change_name", True),
default_user_change_email=CONFIG.get_bool("default_user_change_email", False),
default_user_change_username=CONFIG.get_bool("default_user_change_username", False),
+ footer_links=CONFIG.get("footer_links", default=[]),
gdpr_compliance=CONFIG.get_bool("gdpr_compliance", True),
impersonation=CONFIG.get_bool("impersonation", True),
- footer_links=CONFIG.get("footer_links", default=[]),
)
@@ -81,6 +82,22 @@ class Migration(migrations.Migration):
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],
+ ),
+ ),
+ (
+ "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(
@@ -94,14 +111,6 @@ class Migration(migrations.Migration):
default=True, help_text="Globally enable/disable impersonation."
),
),
- (
- "footer_links",
- models.JSONField(
- blank=True,
- default=list,
- help_text="The option configures the footer links on the flow executor pages.",
- ),
- ),
],
options={
"verbose_name": "Tenant",
diff --git a/authentik/tenants/models.py b/authentik/tenants/models.py
index b1c3f3ac6..603cd22f3 100644
--- a/authentik/tenants/models.py
+++ b/authentik/tenants/models.py
@@ -14,6 +14,7 @@ from structlog.stdlib import get_logger
from authentik.blueprints.apps import ManagedAppConfig
from authentik.lib.models import SerializerModel
+from authentik.lib.utils.time import timedelta_string_validator
LOGGER = get_logger()
@@ -57,6 +58,18 @@ class Tenant(TenantMixin, SerializerModel):
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)."
+ ),
+ )
+ 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 "
@@ -67,11 +80,6 @@ class Tenant(TenantMixin, SerializerModel):
impersonation = models.BooleanField(
help_text=_("Globally enable/disable impersonation."), default=True
)
- footer_links = models.JSONField(
- help_text=_("The option configures the footer links on the flow executor pages."),
- default=list,
- blank=True,
- )
def save(self, *args, **kwargs):
if self.schema_name == "template":
diff --git a/authentik/tenants/tests/test_event_retention.py b/authentik/tenants/tests/test_event_retention.py
new file mode 100644
index 000000000..daaa908f1
--- /dev/null
+++ b/authentik/tenants/tests/test_event_retention.py
@@ -0,0 +1,30 @@
+"""Test event retention"""
+from django.test.client import RequestFactory
+from django_tenants.utils import get_public_schema_name
+from rest_framework.test import APITestCase
+
+from authentik.events.models import Event, EventAction
+from authentik.lib.utils.time import timedelta_from_string
+from authentik.tenants.models import Tenant
+
+
+class TestEventRetention(APITestCase):
+ """Test event retention"""
+
+ def test_event_retention(self):
+ """Test brand's event retention"""
+ default_tenant = Tenant.objects.get(schema_name=get_public_schema_name())
+ default_tenant.event_retention = "weeks=3"
+ default_tenant.save()
+ factory = RequestFactory()
+ request = factory.get("/")
+ request.tenant = default_tenant
+ event = Event.new(action=EventAction.SYSTEM_EXCEPTION, message="test").from_http(request)
+ self.assertEqual(event.expires.day, (event.created + timedelta_from_string("weeks=3")).day)
+ self.assertEqual(
+ event.expires.month,
+ (event.created + timedelta_from_string("weeks=3")).month,
+ )
+ self.assertEqual(
+ event.expires.year, (event.created + timedelta_from_string("weeks=3")).year
+ )
diff --git a/blueprints/schema.json b/blueprints/schema.json
index 7947e0153..aaf0d82e6 100644
--- a/blueprints/schema.json
+++ b/blueprints/schema.json
@@ -8560,12 +8560,6 @@
"type": "integer",
"title": "Flow device code"
},
- "event_retention": {
- "type": "string",
- "minLength": 1,
- "title": "Event retention",
- "description": "Events will be deleted after this duration.(Format: weeks=3;days=2;hours=3,seconds=2)."
- },
"web_certificate": {
"type": "integer",
"title": "Web certificate",
diff --git a/schema.yml b/schema.yml
index d0a3e7570..b75e5556d 100644
--- a/schema.yml
+++ b/schema.yml
@@ -3373,10 +3373,6 @@ paths:
name: domain
schema:
type: string
- - in: query
- name: event_retention
- schema:
- type: string
- in: query
name: flow_authentication
schema:
@@ -29844,9 +29840,6 @@ components:
type: string
format: uuid
nullable: true
- event_retention:
- type: string
- description: 'Events will be deleted after this duration.(Format: weeks=3;days=2;hours=3,seconds=2).'
web_certificate:
type: string
format: uuid
@@ -29902,10 +29895,6 @@ components:
type: string
format: uuid
nullable: true
- event_retention:
- type: string
- minLength: 1
- description: 'Events will be deleted after this duration.(Format: weeks=3;days=2;hours=3,seconds=2).'
web_certificate:
type: string
format: uuid
@@ -36804,10 +36793,6 @@ components:
type: string
format: uuid
nullable: true
- event_retention:
- type: string
- minLength: 1
- description: 'Events will be deleted after this duration.(Format: weeks=3;days=2;hours=3,seconds=2).'
web_certificate:
type: string
format: uuid
@@ -38740,6 +38725,15 @@ components:
default_user_change_username:
type: boolean
description: Enable the ability for users to change their username.
+ event_retention:
+ type: string
+ minLength: 1
+ description: 'Events will be deleted after this duration.(Format: weeks=3;days=2;hours=3,seconds=2).'
+ footer_links:
+ type: object
+ additionalProperties: {}
+ description: The option configures the footer links on the flow executor
+ pages.
gdpr_compliance:
type: boolean
description: When enabled, all the events caused by a user will be deleted
@@ -38747,11 +38741,6 @@ components:
impersonation:
type: boolean
description: Globally enable/disable impersonation.
- footer_links:
- type: object
- additionalProperties: {}
- description: The option configures the footer links on the flow executor
- pages.
PatchedStaticDeviceRequest:
type: object
description: Serializer for static authenticator devices
@@ -41638,6 +41627,14 @@ components:
default_user_change_username:
type: boolean
description: Enable the ability for users to change their username.
+ event_retention:
+ type: string
+ description: 'Events will be deleted after this duration.(Format: weeks=3;days=2;hours=3,seconds=2).'
+ footer_links:
+ type: object
+ additionalProperties: {}
+ description: The option configures the footer links on the flow executor
+ pages.
gdpr_compliance:
type: boolean
description: When enabled, all the events caused by a user will be deleted
@@ -41645,11 +41642,6 @@ components:
impersonation:
type: boolean
description: Globally enable/disable impersonation.
- footer_links:
- type: object
- additionalProperties: {}
- description: The option configures the footer links on the flow executor
- pages.
SettingsRequest:
type: object
description: Settings Serializer
@@ -41667,6 +41659,15 @@ components:
default_user_change_username:
type: boolean
description: Enable the ability for users to change their username.
+ event_retention:
+ type: string
+ minLength: 1
+ description: 'Events will be deleted after this duration.(Format: weeks=3;days=2;hours=3,seconds=2).'
+ footer_links:
+ type: object
+ additionalProperties: {}
+ description: The option configures the footer links on the flow executor
+ pages.
gdpr_compliance:
type: boolean
description: When enabled, all the events caused by a user will be deleted
@@ -41674,11 +41675,6 @@ components:
impersonation:
type: boolean
description: Globally enable/disable impersonation.
- footer_links:
- type: object
- additionalProperties: {}
- description: The option configures the footer links on the flow executor
- pages.
SeverityEnum:
enum:
- notice
diff --git a/web/src/admin/admin-settings/AdminSettingsForm.ts b/web/src/admin/admin-settings/AdminSettingsForm.ts
index 9bedf636a..803e64183 100644
--- a/web/src/admin/admin-settings/AdminSettingsForm.ts
+++ b/web/src/admin/admin-settings/AdminSettingsForm.ts
@@ -7,6 +7,7 @@ import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement";
import "@goauthentik/elements/forms/Radio";
import "@goauthentik/elements/forms/SearchSelect";
+import "@goauthentik/elements/utils/TimeDeltaHelp";
import { msg } from "@lit/localize";
import { TemplateResult, html } from "lit";
@@ -120,6 +121,41 @@ export class AdminSettingsForm extends Form
+ ${msg( + 'When using an external logging solution for archiving, this can be set to "minutes=5".', + )} +
++ ${msg( + "This setting only affects new Events, as the expiration is saved per-event.", + )} +
+[{"name": "Link Name","href":"https://goauthentik.io"}]
+
+ `}
+ >
+ [{"name": "Link Name","href":"https://goauthentik.io"}]
-
- `}
- >
- - ${msg("Duration after which events will be deleted from the database.")} -
-- ${msg( - 'When using an external logging solution for archiving, this can be set to "minutes=5".', - )} -
-- ${msg( - "This setting only affects new Events, as the expiration is saved per-event.", - )} -
-- ${msg('Format: "weeks=3;days=2;hours=3,seconds=2".')} -
-