move event_retention from brands to tenants

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
This commit is contained in:
Marc 'risson' Schmitt 2023-12-12 07:54:12 +01:00
parent 4374bdedf6
commit 66ba607317
No known key found for this signature in database
GPG Key ID: 9C3FA22FABF1AA8D
14 changed files with 148 additions and 129 deletions

View File

@ -54,7 +54,6 @@ class BrandSerializer(ModelSerializer):
"flow_unenrollment", "flow_unenrollment",
"flow_user_settings", "flow_user_settings",
"flow_device_code", "flow_device_code",
"event_retention",
"web_certificate", "web_certificate",
"attributes", "attributes",
] ]
@ -125,7 +124,6 @@ class BrandViewSet(UsedByMixin, ModelViewSet):
"flow_unenrollment", "flow_unenrollment",
"flow_user_settings", "flow_user_settings",
"flow_device_code", "flow_device_code",
"event_retention",
"web_certificate", "web_certificate",
] ]
ordering = ["domain"] ordering = ["domain"]

View File

@ -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",
),
]

View File

@ -9,7 +9,6 @@ from structlog.stdlib import get_logger
from authentik.crypto.models import CertificateKeyPair from authentik.crypto.models import CertificateKeyPair
from authentik.flows.models import Flow from authentik.flows.models import Flow
from authentik.lib.models import SerializerModel from authentik.lib.models import SerializerModel
from authentik.lib.utils.time import timedelta_string_validator
LOGGER = get_logger() LOGGER = get_logger()
@ -51,14 +50,6 @@ class Brand(SerializerModel):
Flow, null=True, on_delete=models.SET_NULL, related_name="brand_device_code" 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( web_certificate = models.ForeignKey(
CertificateKeyPair, CertificateKeyPair,
null=True, null=True,

View File

@ -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): def test_create_default_multiple(self):
"""Test attempted creation of multiple default brands""" """Test attempted creation of multiple default brands"""
Brand.objects.create( Brand.objects.create(

View File

@ -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.lib.utils.time import timedelta_from_string
from authentik.policies.models import PolicyBindingModel from authentik.policies.models import PolicyBindingModel
from authentik.stages.email.utils import TemplateEmailMessage from authentik.stages.email.utils import TemplateEmailMessage
from authentik.tenants.models import Tenant
LOGGER = get_logger() LOGGER = get_logger()
if TYPE_CHECKING: if TYPE_CHECKING:
@ -224,12 +225,14 @@ class Event(SerializerModel, ExpiringModel):
if QS_QUERY in self.context["http_request"]["args"]: if QS_QUERY in self.context["http_request"]["args"]:
wrapped = self.context["http_request"]["args"][QS_QUERY] wrapped = self.context["http_request"]["args"][QS_QUERY]
self.context["http_request"]["args"] = cleanse_dict(QueryDict(wrapped)) self.context["http_request"]["args"] = cleanse_dict(QueryDict(wrapped))
if hasattr(request, "brand"): if hasattr(request, "tenant"):
brand: Brand = request.brand tenant: Tenant = request.tenant
# Because self.created only gets set on save, we can't use it's value here # 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 # hence we set self.created to now and then use it
self.created = now() 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)) self.brand = sanitize_dict(model_to_dict(brand))
if hasattr(request, "user"): if hasattr(request, "user"):
original_user = None original_user = None

View File

@ -105,9 +105,10 @@ class SettingsSerializer(ModelSerializer):
"default_user_change_name", "default_user_change_name",
"default_user_change_email", "default_user_change_email",
"default_user_change_username", "default_user_change_username",
"event_retention",
"footer_links",
"gdpr_compliance", "gdpr_compliance",
"impersonation", "impersonation",
"footer_links",
] ]

View File

@ -6,6 +6,7 @@ import django.db.models.deletion
import django_tenants.postgresql_backend.base import django_tenants.postgresql_backend.base
from django.db import migrations, models from django.db import migrations, models
import authentik.lib.utils.time
import authentik.tenants.models import authentik.tenants.models
from authentik.lib.config import CONFIG 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_name=CONFIG.get_bool("default_user_change_name", True),
default_user_change_email=CONFIG.get_bool("default_user_change_email", False), default_user_change_email=CONFIG.get_bool("default_user_change_email", False),
default_user_change_username=CONFIG.get_bool("default_user_change_username", 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), gdpr_compliance=CONFIG.get_bool("gdpr_compliance", True),
impersonation=CONFIG.get_bool("impersonation", 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.", 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", "gdpr_compliance",
models.BooleanField( models.BooleanField(
@ -94,14 +111,6 @@ class Migration(migrations.Migration):
default=True, help_text="Globally enable/disable impersonation." 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={ options={
"verbose_name": "Tenant", "verbose_name": "Tenant",

View File

@ -14,6 +14,7 @@ from structlog.stdlib import get_logger
from authentik.blueprints.apps import ManagedAppConfig from authentik.blueprints.apps import ManagedAppConfig
from authentik.lib.models import SerializerModel from authentik.lib.models import SerializerModel
from authentik.lib.utils.time import timedelta_string_validator
LOGGER = get_logger() LOGGER = get_logger()
@ -57,6 +58,18 @@ class Tenant(TenantMixin, SerializerModel):
default_user_change_username = models.BooleanField( default_user_change_username = models.BooleanField(
help_text=_("Enable the ability for users to change their username."), default=False 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( gdpr_compliance = models.BooleanField(
help_text=_( help_text=_(
"When enabled, all the events caused by a user " "When enabled, all the events caused by a user "
@ -67,11 +80,6 @@ class Tenant(TenantMixin, SerializerModel):
impersonation = models.BooleanField( impersonation = models.BooleanField(
help_text=_("Globally enable/disable impersonation."), default=True 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): def save(self, *args, **kwargs):
if self.schema_name == "template": if self.schema_name == "template":

View File

@ -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
)

View File

@ -8560,12 +8560,6 @@
"type": "integer", "type": "integer",
"title": "Flow device code" "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": { "web_certificate": {
"type": "integer", "type": "integer",
"title": "Web certificate", "title": "Web certificate",

View File

@ -3373,10 +3373,6 @@ paths:
name: domain name: domain
schema: schema:
type: string type: string
- in: query
name: event_retention
schema:
type: string
- in: query - in: query
name: flow_authentication name: flow_authentication
schema: schema:
@ -29844,9 +29840,6 @@ components:
type: string type: string
format: uuid format: uuid
nullable: true 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: web_certificate:
type: string type: string
format: uuid format: uuid
@ -29902,10 +29895,6 @@ components:
type: string type: string
format: uuid format: uuid
nullable: true 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: web_certificate:
type: string type: string
format: uuid format: uuid
@ -36804,10 +36793,6 @@ components:
type: string type: string
format: uuid format: uuid
nullable: true 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: web_certificate:
type: string type: string
format: uuid format: uuid
@ -38740,6 +38725,15 @@ components:
default_user_change_username: default_user_change_username:
type: boolean type: boolean
description: Enable the ability for users to change their username. 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: gdpr_compliance:
type: boolean type: boolean
description: When enabled, all the events caused by a user will be deleted description: When enabled, all the events caused by a user will be deleted
@ -38747,11 +38741,6 @@ components:
impersonation: impersonation:
type: boolean type: boolean
description: Globally enable/disable impersonation. description: Globally enable/disable impersonation.
footer_links:
type: object
additionalProperties: {}
description: The option configures the footer links on the flow executor
pages.
PatchedStaticDeviceRequest: PatchedStaticDeviceRequest:
type: object type: object
description: Serializer for static authenticator devices description: Serializer for static authenticator devices
@ -41638,6 +41627,14 @@ components:
default_user_change_username: default_user_change_username:
type: boolean type: boolean
description: Enable the ability for users to change their username. 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: gdpr_compliance:
type: boolean type: boolean
description: When enabled, all the events caused by a user will be deleted description: When enabled, all the events caused by a user will be deleted
@ -41645,11 +41642,6 @@ components:
impersonation: impersonation:
type: boolean type: boolean
description: Globally enable/disable impersonation. description: Globally enable/disable impersonation.
footer_links:
type: object
additionalProperties: {}
description: The option configures the footer links on the flow executor
pages.
SettingsRequest: SettingsRequest:
type: object type: object
description: Settings Serializer description: Settings Serializer
@ -41667,6 +41659,15 @@ components:
default_user_change_username: default_user_change_username:
type: boolean type: boolean
description: Enable the ability for users to change their username. 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: gdpr_compliance:
type: boolean type: boolean
description: When enabled, all the events caused by a user will be deleted description: When enabled, all the events caused by a user will be deleted
@ -41674,11 +41675,6 @@ components:
impersonation: impersonation:
type: boolean type: boolean
description: Globally enable/disable impersonation. description: Globally enable/disable impersonation.
footer_links:
type: object
additionalProperties: {}
description: The option configures the footer links on the flow executor
pages.
SeverityEnum: SeverityEnum:
enum: enum:
- notice - notice

View File

@ -7,6 +7,7 @@ import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement"; import "@goauthentik/elements/forms/HorizontalFormElement";
import "@goauthentik/elements/forms/Radio"; import "@goauthentik/elements/forms/Radio";
import "@goauthentik/elements/forms/SearchSelect"; import "@goauthentik/elements/forms/SearchSelect";
import "@goauthentik/elements/utils/TimeDeltaHelp";
import { msg } from "@lit/localize"; import { msg } from "@lit/localize";
import { TemplateResult, html } from "lit"; import { TemplateResult, html } from "lit";
@ -120,6 +121,41 @@ export class AdminSettingsForm extends Form<SettingsRequest> {
help=${msg("Enable the ability for users to change their username.")} help=${msg("Enable the ability for users to change their username.")}
> >
</ak-switch-input> </ak-switch-input>
<ak-text-input
name="eventRetention"
label=${msg("Event retention")}
required
value="${this._settings?.eventRetention}"
.bighelp=${html`<p class="pf-c-form__helper-text">
${msg("Duration after which events will be deleted from the database.")}
</p>
<p class="pf-c-form__helper-text">
${msg(
'When using an external logging solution for archiving, this can be set to "minutes=5".',
)}
</p>
<p class="pf-c-form__helper-text">
${msg(
"This setting only affects new Events, as the expiration is saved per-event.",
)}
</p>
<ak-utils-time-delta-help></ak-utils-time-delta-help>`}
>
</ak-text-input>
<ak-textarea-input
name="footerLinks"
label=${msg("Footer links")}
.value="${this._settings?.footerLinks}"
.bighelp=${html`
<p class="pf-c-form__helper-text">
${msg(
"This option configures the footer links on the flow executor pages. It must be a valid JSON list and can be used as follows:",
)}
<code>[{"name": "Link Name","href":"https://goauthentik.io"}]</code>
</p>
`}
>
</ak-textarea-input>
<ak-switch-input <ak-switch-input
name="gdprCompliance" name="gdprCompliance"
label=${msg("GDPR compliance")} label=${msg("GDPR compliance")}
@ -136,20 +172,6 @@ export class AdminSettingsForm extends Form<SettingsRequest> {
help=${msg("Globally enable/disable impersonation.")} help=${msg("Globally enable/disable impersonation.")}
> >
</ak-switch-input> </ak-switch-input>
<ak-textarea-input
name="footerLinks"
label=${msg("Footer links")}
.value="${this._settings?.footerLinks}"
.bighelp=${html`
<p class="pf-c-form__helper-text">
${msg(
"This option configures the footer links on the flow executor pages. It must be a valid JSON list and can be used as follows:",
)}
<code>[{"name": "Link Name","href":"https://goauthentik.io"}]</code>
</p>
`}
>
</ak-textarea-input>
`; `;
} }
} }

View File

@ -235,34 +235,6 @@ export class BrandForm extends ModelForm<Brand, string> {
certificate=${this.instance?.webCertificate} certificate=${this.instance?.webCertificate}
></ak-crypto-certificate-search> ></ak-crypto-certificate-search>
</ak-form-element-horizontal> </ak-form-element-horizontal>
<ak-form-element-horizontal
label=${msg("Event retention")}
?required=${true}
name="eventRetention"
>
<input
type="text"
value="${first(this.instance?.eventRetention, "days=365")}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">
${msg("Duration after which events will be deleted from the database.")}
</p>
<p class="pf-c-form__helper-text">
${msg(
'When using an external logging solution for archiving, this can be set to "minutes=5".',
)}
</p>
<p class="pf-c-form__helper-text">
${msg(
"This setting only affects new Events, as the expiration is saved per-event.",
)}
</p>
<p class="pf-c-form__helper-text">
${msg('Format: "weeks=3;days=2;hours=3,seconds=2".')}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${msg("Attributes")} name="attributes"> <ak-form-element-horizontal label=${msg("Attributes")} name="attributes">
<ak-codemirror <ak-codemirror
mode=${CodeMirrorMode.YAML} mode=${CodeMirrorMode.YAML}

View File

@ -8,7 +8,7 @@ Certain information is stripped from events, to ensure no passwords or other cre
## Event retention ## Event retention
The event retention is configured on a per-brand level, with the default being set to 365 days. For events where a related brand cannot be found, the retention is also set to 365 days. The event retention is configured in the system settings interface, with the default being set to 365 days.
If you want to forward these events to another application, forward the log output of all authentik containers. Every event creation is logged with the log level "info". For this configuration, it is also recommended to set the internal retention pretty low (for example, `days=1`). If you want to forward these events to another application, forward the log output of all authentik containers. Every event creation is logged with the log level "info". For this configuration, it is also recommended to set the internal retention pretty low (for example, `days=1`).