Compare commits

...

2 Commits

Author SHA1 Message Date
1fcef476c3 fix
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-06-22 00:48:46 +02:00
e8b6b3366b events: improve error formatting in events
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-06-21 23:40:36 +02:00
18 changed files with 47 additions and 46 deletions

View File

@ -11,7 +11,6 @@ from authentik.core.expression.exceptions import SkipObjectException
from authentik.core.models import User
from authentik.events.models import Event, EventAction
from authentik.lib.expression.evaluator import BaseEvaluator
from authentik.lib.utils.errors import exception_to_string
from authentik.policies.types import PolicyRequest
PROPERTY_MAPPING_TIME = Histogram(
@ -69,12 +68,11 @@ class PropertyMappingEvaluator(BaseEvaluator):
# For dry-run requests we don't save exceptions
if self.dry_run:
return
error_string = exception_to_string(exc)
event = Event.new(
EventAction.PROPERTY_MAPPING_EXCEPTION,
expression=expression_source,
message=error_string,
)
message="Failed to execute property mapping",
).with_exception(exc)
if "request" in self._context:
req: PolicyRequest = self._context["request"]
if req.http_request:

View File

@ -20,7 +20,7 @@ from authentik.core.models import Group, User
from authentik.events.models import Event, EventAction, Notification
from authentik.events.utils import model_to_dict
from authentik.lib.sentry import before_send
from authentik.lib.utils.errors import exception_to_string
from authentik.lib.utils.errors import exception_to_dict
from authentik.stages.authenticator_static.models import StaticToken
IGNORED_MODELS = tuple(
@ -170,14 +170,16 @@ class AuditMiddleware:
thread = EventNewThread(
EventAction.SUSPICIOUS_REQUEST,
request,
message=exception_to_string(exception),
message=str(exception),
exception=exception_to_dict(exception),
)
thread.run()
elif before_send({}, {"exc_info": (None, exception, None)}) is not None:
thread = EventNewThread(
EventAction.SYSTEM_EXCEPTION,
request,
message=exception_to_string(exception),
message=str(exception),
exception=exception_to_dict(exception),
)
thread.run()

View File

@ -38,6 +38,7 @@ from authentik.events.utils import (
)
from authentik.lib.models import DomainlessURLValidator, SerializerModel
from authentik.lib.sentry import SentryIgnoredException
from authentik.lib.utils.errors import exception_to_dict
from authentik.lib.utils.http import get_http_session
from authentik.lib.utils.time import timedelta_from_string
from authentik.policies.models import PolicyBindingModel
@ -163,6 +164,12 @@ class Event(SerializerModel, ExpiringModel):
event = Event(action=action, app=app, context=cleaned_kwargs)
return event
def with_exception(self, exc: Exception) -> "Event":
"""Add data from 'exc' to the event in a database-saveable format"""
self.context.setdefault("message", str(exc))
self.context["exception"] = exception_to_dict(exc)
return self
def set_user(self, user: User) -> "Event":
"""Set `.user` based on user, ensuring the correct attributes are copied.
This should only be used when self.from_http is *not* used."""

View File

@ -127,8 +127,8 @@ class SystemTask(TenantTask):
)
Event.new(
EventAction.SYSTEM_TASK_EXCEPTION,
message=f"Task {self.__name__} encountered an error: {exception_to_string(exc)}",
).save()
message=f"Task {self.__name__} encountered an error",
).with_exception(exc).save()
def run(self, *args, **kwargs):
raise NotImplementedError

View File

@ -56,7 +56,6 @@ from authentik.flows.planner import (
)
from authentik.flows.stage import AccessDeniedStage, StageView
from authentik.lib.sentry import SentryIgnoredException
from authentik.lib.utils.errors import exception_to_string
from authentik.lib.utils.reflection import all_subclasses, class_to_path
from authentik.lib.utils.urls import is_url_absolute, redirect_with_qs
from authentik.policies.engine import PolicyEngine
@ -238,8 +237,8 @@ class FlowExecutorView(APIView):
self._logger.warning(exc)
Event.new(
action=EventAction.SYSTEM_EXCEPTION,
message=exception_to_string(exc),
).from_http(self.request)
message="System exception during flow execution.",
).with_exception(exc).from_http(self.request)
challenge = FlowErrorChallenge(self.request, exc)
challenge.is_valid(raise_exception=True)
return to_stage_response(self.request, HttpChallengeResponse(challenge))

View File

@ -14,7 +14,6 @@ from authentik.events.models import Event, EventAction
from authentik.lib.expression.exceptions import ControlFlowException
from authentik.lib.sync.mapper import PropertyMappingManager
from authentik.lib.sync.outgoing.exceptions import NotFoundSyncException, StopSync
from authentik.lib.utils.errors import exception_to_string
if TYPE_CHECKING:
from django.db.models import Model
@ -106,9 +105,9 @@ class BaseOutgoingSyncClient[
# Value error can be raised when assigning invalid data to an attribute
Event.new(
EventAction.CONFIGURATION_ERROR,
message=f"Failed to evaluate property-mapping {exception_to_string(exc)}",
message="Failed to evaluate property-mapping",
mapping=exc.mapping,
).save()
).with_exception(exc).save()
raise StopSync(exc, obj, exc.mapping) from exc
if not raw_final_object:
raise StopSync(ValueError("No mappings configured"), obj)

View File

@ -2,6 +2,8 @@
from traceback import extract_tb
from structlog.tracebacks import ExceptionDictTransformer
from authentik.lib.utils.reflection import class_to_path
TRACEBACK_HEADER = "Traceback (most recent call last):"
@ -17,3 +19,8 @@ def exception_to_string(exc: Exception) -> str:
f"{class_to_path(exc.__class__)}: {str(exc)}",
]
)
def exception_to_dict(exc: Exception) -> dict:
"""Format exception as a dictionary"""
return ExceptionDictTransformer()((type(exc), exc, exc.__traceback__))

View File

@ -35,7 +35,6 @@ from authentik.events.models import Event, EventAction
from authentik.lib.config import CONFIG
from authentik.lib.models import InheritanceForeignKey, SerializerModel
from authentik.lib.sentry import SentryIgnoredException
from authentik.lib.utils.errors import exception_to_string
from authentik.outposts.controllers.k8s.utils import get_namespace
OUR_VERSION = parse(__version__)
@ -326,9 +325,8 @@ class Outpost(SerializerModel, ManagedModel):
"While setting the permissions for the service-account, a "
"permission was not found: Check "
"https://goauthentik.io/docs/troubleshooting/missing_permission"
)
+ exception_to_string(exc),
).set_user(user).save()
),
).with_exception(exc).set_user(user).save()
else:
app_label, perm = model_or_perm.split(".")
permission = Permission.objects.filter(

View File

@ -10,7 +10,7 @@ from structlog.stdlib import get_logger
from authentik.events.models import Event, EventAction
from authentik.lib.config import CONFIG
from authentik.lib.utils.errors import exception_to_string
from authentik.lib.utils.errors import exception_to_dict
from authentik.lib.utils.reflection import class_to_path
from authentik.policies.apps import HIST_POLICIES_EXECUTION_TIME
from authentik.policies.exceptions import PolicyException
@ -95,10 +95,13 @@ class PolicyProcess(PROCESS_CLASS):
except PolicyException as exc:
# Either use passed original exception or whatever we have
src_exc = exc.src_exc if exc.src_exc else exc
error_string = exception_to_string(src_exc)
# Create policy exception event, only when we're not debugging
if not self.request.debug:
self.create_event(EventAction.POLICY_EXCEPTION, message=error_string)
self.create_event(
EventAction.POLICY_EXCEPTION,
message="Policy failed to execute",
exception=exception_to_dict(src_exc),
)
LOGGER.debug("P_ENG(proc): error, using failure result", exc=src_exc)
policy_result = PolicyResult(self.binding.failure_result, str(src_exc))
policy_result.source_binding = self.binding
@ -143,5 +146,5 @@ class PolicyProcess(PROCESS_CLASS):
try:
self.connection.send(self.profiling_wrapper())
except Exception as exc:
LOGGER.warning("Policy failed to run", exc=exception_to_string(exc))
LOGGER.warning("Policy failed to run", exc=exc)
self.connection.send(PolicyResult(False, str(exc)))

View File

@ -237,4 +237,4 @@ class TestPolicyProcess(TestCase):
self.assertEqual(len(events), 1)
event = events.first()
self.assertEqual(event.user["username"], self.user.username)
self.assertIn("division by zero", event.context["message"])
self.assertIn("Policy failed to execute", event.context["message"])

View File

@ -23,7 +23,6 @@ from authentik.core.models import Application
from authentik.events.models import Event, EventAction
from authentik.lib.expression.exceptions import ControlFlowException
from authentik.lib.sync.mapper import PropertyMappingManager
from authentik.lib.utils.errors import exception_to_string
from authentik.policies.api.exec import PolicyTestResultSerializer
from authentik.policies.engine import PolicyEngine
from authentik.policies.types import PolicyResult
@ -142,9 +141,9 @@ class RadiusOutpostConfigViewSet(ListModelMixin, GenericViewSet):
# Value error can be raised when assigning invalid data to an attribute
Event.new(
EventAction.CONFIGURATION_ERROR,
message=f"Failed to evaluate property-mapping {exception_to_string(exc)}",
message="Failed to evaluate property-mapping",
mapping=exc.mapping,
).save()
).with_exception(exc).save()
return None
return b64encode(packet.RequestPacket()).decode()

View File

@ -28,7 +28,6 @@ from tenant_schemas_celery.app import CeleryApp as TenantAwareCeleryApp
from authentik import get_full_version
from authentik.lib.sentry import before_send
from authentik.lib.utils.errors import exception_to_string
# set the default Django settings module for the 'celery' program.
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "authentik.root.settings")
@ -83,8 +82,8 @@ def task_error_hook(task_id: str, exception: Exception, traceback, *args, **kwar
CTX_TASK_ID.set(...)
if before_send({}, {"exc_info": (None, exception, None)}) is not None:
Event.new(
EventAction.SYSTEM_EXCEPTION, message=exception_to_string(exception), task_id=task_id
).save()
EventAction.SYSTEM_EXCEPTION, message="Failed to execute task", task_id=task_id
).with_exception(exception).save()
def _get_startup_tasks_default_tenant() -> list[Callable]:

View File

@ -8,7 +8,6 @@ from authentik.events.models import TaskStatus
from authentik.events.system_tasks import SystemTask
from authentik.lib.config import CONFIG
from authentik.lib.sync.outgoing.exceptions import StopSync
from authentik.lib.utils.errors import exception_to_string
from authentik.root.celery import CELERY_APP
from authentik.sources.kerberos.models import KerberosSource
from authentik.sources.kerberos.sync import KerberosSync
@ -64,5 +63,5 @@ def kerberos_sync_single(self, source_pk: str):
syncer.sync()
self.set_status(TaskStatus.SUCCESSFUL, *syncer.messages)
except StopSync as exc:
LOGGER.warning(exception_to_string(exc))
LOGGER.warning("Error syncing kerberos", exc=exc, source=source)
self.set_error(exc)

View File

@ -12,7 +12,6 @@ from authentik.events.models import TaskStatus
from authentik.events.system_tasks import SystemTask
from authentik.lib.config import CONFIG
from authentik.lib.sync.outgoing.exceptions import StopSync
from authentik.lib.utils.errors import exception_to_string
from authentik.lib.utils.reflection import class_to_path, path_to_class
from authentik.root.celery import CELERY_APP
from authentik.sources.ldap.models import LDAPSource
@ -149,5 +148,5 @@ def ldap_sync(self: SystemTask, source_pk: str, sync_class: str, page_cache_key:
cache.delete(page_cache_key)
except (LDAPException, StopSync) as exc:
# No explicit event is created here as .set_status with an error will do that
LOGGER.warning(exception_to_string(exc))
LOGGER.warning("Failed to sync LDAP", exc=exc, source=source)
self.set_error(exc)

View File

@ -13,7 +13,6 @@ from authentik.flows.exceptions import StageInvalidException
from authentik.flows.models import ConfigurableStage, FriendlyNamedStage, Stage
from authentik.lib.config import CONFIG
from authentik.lib.models import SerializerModel
from authentik.lib.utils.errors import exception_to_string
from authentik.lib.utils.time import timedelta_string_validator
from authentik.stages.authenticator.models import SideChannelDevice
from authentik.stages.email.utils import TemplateEmailMessage
@ -160,9 +159,8 @@ class EmailDevice(SerializerModel, SideChannelDevice):
Event.new(
EventAction.CONFIGURATION_ERROR,
message=_("Exception occurred while rendering E-mail template"),
error=exception_to_string(exc),
template=stage.template,
).from_http(self.request)
).with_exception(exc).from_http(self.request)
raise StageInvalidException from exc
def __str__(self):

View File

@ -17,7 +17,6 @@ from authentik.flows.challenge import (
from authentik.flows.exceptions import StageInvalidException
from authentik.flows.stage import ChallengeStageView
from authentik.lib.utils.email import mask_email
from authentik.lib.utils.errors import exception_to_string
from authentik.lib.utils.time import timedelta_from_string
from authentik.stages.authenticator_email.models import (
AuthenticatorEmailStage,
@ -100,9 +99,8 @@ class AuthenticatorEmailStageView(ChallengeStageView):
Event.new(
EventAction.CONFIGURATION_ERROR,
message=_("Exception occurred while rendering E-mail template"),
error=exception_to_string(exc),
template=stage.template,
).from_http(self.request)
).with_exception(exc).from_http(self.request)
raise StageInvalidException from exc
def _has_email(self) -> str | None:

View File

@ -19,7 +19,6 @@ from authentik.events.models import Event, EventAction, NotificationWebhookMappi
from authentik.events.utils import sanitize_item
from authentik.flows.models import ConfigurableStage, FriendlyNamedStage, Stage
from authentik.lib.models import SerializerModel
from authentik.lib.utils.errors import exception_to_string
from authentik.lib.utils.http import get_http_session
from authentik.stages.authenticator.models import SideChannelDevice
@ -142,10 +141,9 @@ class AuthenticatorSMSStage(ConfigurableStage, FriendlyNamedStage, Stage):
Event.new(
EventAction.CONFIGURATION_ERROR,
message="Error sending SMS",
exc=exception_to_string(exc),
status_code=response.status_code,
body=response.text,
).set_user(device.user).save()
).with_exception(exc).set_user(device.user).save()
if response.status_code >= HttpResponseBadRequest.status_code:
raise ValidationError(response.text) from None
raise

View File

@ -21,7 +21,6 @@ from authentik.flows.models import FlowDesignation, FlowToken
from authentik.flows.planner import PLAN_CONTEXT_IS_RESTORED, PLAN_CONTEXT_PENDING_USER
from authentik.flows.stage import ChallengeStageView
from authentik.flows.views.executor import QS_KEY_TOKEN, QS_QUERY
from authentik.lib.utils.errors import exception_to_string
from authentik.lib.utils.time import timedelta_from_string
from authentik.stages.email.flow import pickle_flow_token_for_email
from authentik.stages.email.models import EmailStage
@ -129,9 +128,8 @@ class EmailStageView(ChallengeStageView):
Event.new(
EventAction.CONFIGURATION_ERROR,
message=_("Exception occurred while rendering E-mail template"),
error=exception_to_string(exc),
template=current_stage.template,
).from_http(self.request)
).with_exception(exc).from_http(self.request)
raise StageInvalidException from exc
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: