core: rework base for SkipObject exception to better support control flow exceptions (#10186)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
Jens L
2024-06-20 16:16:24 +09:00
committed by GitHub
parent 54743e8187
commit 942019d31f
7 changed files with 33 additions and 15 deletions

View File

@ -1,5 +1,6 @@
"""authentik core exceptions""" """authentik core exceptions"""
from authentik.lib.expression.exceptions import ControlFlowException
from authentik.lib.sentry import SentryIgnoredException from authentik.lib.sentry import SentryIgnoredException
@ -12,11 +13,7 @@ class PropertyMappingExpressionException(SentryIgnoredException):
self.mapping = mapping self.mapping = mapping
class SkipObjectException(PropertyMappingExpressionException): class SkipObjectException(ControlFlowException):
"""Exception which can be raised in a property mapping to skip syncing an object. """Exception which can be raised in a property mapping to skip syncing an object.
Only applies to Property mappings which sync objects, and not on mappings which transitively Only applies to Property mappings which sync objects, and not on mappings which transitively
apply to a single user""" apply to a single user"""
def __init__(self) -> None:
# For this class only, both of these are set by the function evaluating the property mapping
super().__init__(exc=None, mapping=None)

View File

@ -26,6 +26,7 @@ from authentik.blueprints.models import ManagedModel
from authentik.core.expression.exceptions import PropertyMappingExpressionException from authentik.core.expression.exceptions import PropertyMappingExpressionException
from authentik.core.types import UILoginButton, UserSettingSerializer from authentik.core.types import UILoginButton, UserSettingSerializer
from authentik.lib.avatars import get_avatar from authentik.lib.avatars import get_avatar
from authentik.lib.expression.exceptions import ControlFlowException
from authentik.lib.generators import generate_id from authentik.lib.generators import generate_id
from authentik.lib.models import ( from authentik.lib.models import (
CreatedUpdatedModel, CreatedUpdatedModel,
@ -783,6 +784,8 @@ class PropertyMapping(SerializerModel, ManagedModel):
evaluator = PropertyMappingEvaluator(self, user, request, **kwargs) evaluator = PropertyMappingEvaluator(self, user, request, **kwargs)
try: try:
return evaluator.evaluate(self.expression) return evaluator.evaluate(self.expression)
except ControlFlowException as exc:
raise exc
except Exception as exc: except Exception as exc:
raise PropertyMappingExpressionException(self, exc) from exc raise PropertyMappingExpressionException(self, exc) from exc

View File

@ -3,7 +3,10 @@
from django.test import RequestFactory, TestCase from django.test import RequestFactory, TestCase
from guardian.shortcuts import get_anonymous_user from guardian.shortcuts import get_anonymous_user
from authentik.core.expression.exceptions import PropertyMappingExpressionException from authentik.core.expression.exceptions import (
PropertyMappingExpressionException,
SkipObjectException,
)
from authentik.core.models import PropertyMapping from authentik.core.models import PropertyMapping
from authentik.core.tests.utils import create_test_admin_user from authentik.core.tests.utils import create_test_admin_user
from authentik.events.models import Event, EventAction from authentik.events.models import Event, EventAction
@ -42,6 +45,17 @@ class TestPropertyMappings(TestCase):
self.assertTrue(events.exists()) self.assertTrue(events.exists())
self.assertEqual(len(events), 1) self.assertEqual(len(events), 1)
def test_expression_skip(self):
"""Test expression error"""
expr = "raise SkipObject"
mapping = PropertyMapping.objects.create(name=generate_id(), expression=expr)
with self.assertRaises(SkipObjectException):
mapping.evaluate(None, None)
events = Event.objects.filter(
action=EventAction.PROPERTY_MAPPING_EXCEPTION, context__expression=expr
)
self.assertFalse(events.exists())
def test_expression_error_extended(self): def test_expression_error_extended(self):
"""Test expression error (with user and http request""" """Test expression error (with user and http request"""
expr = "return aaa" expr = "return aaa"

View File

@ -19,6 +19,7 @@ from structlog.stdlib import get_logger
from authentik.core.models import User from authentik.core.models import User
from authentik.events.models import Event from authentik.events.models import Event
from authentik.lib.expression.exceptions import ControlFlowException
from authentik.lib.utils.http import get_http_session from authentik.lib.utils.http import get_http_session
from authentik.policies.models import Policy, PolicyBinding from authentik.policies.models import Policy, PolicyBinding
from authentik.policies.process import PolicyProcess from authentik.policies.process import PolicyProcess
@ -216,7 +217,8 @@ class BaseEvaluator:
# so the user only sees information relevant to them # so the user only sees information relevant to them
# and none of our surrounding error handling # and none of our surrounding error handling
exc.__traceback__ = exc.__traceback__.tb_next exc.__traceback__ = exc.__traceback__.tb_next
self.handle_error(exc, expression_source) if not isinstance(exc, ControlFlowException):
self.handle_error(exc, expression_source)
raise exc raise exc
return result return result

View File

@ -0,0 +1,6 @@
from authentik.lib.sentry import SentryIgnoredException
class ControlFlowException(SentryIgnoredException):
"""Exceptions used to control the flow from exceptions, not reported as a warning/
error in logs"""

View File

@ -6,9 +6,9 @@ from django.http import HttpRequest
from authentik.core.expression.evaluator import PropertyMappingEvaluator from authentik.core.expression.evaluator import PropertyMappingEvaluator
from authentik.core.expression.exceptions import ( from authentik.core.expression.exceptions import (
PropertyMappingExpressionException, PropertyMappingExpressionException,
SkipObjectException,
) )
from authentik.core.models import PropertyMapping, User from authentik.core.models import PropertyMapping, User
from authentik.lib.expression.exceptions import ControlFlowException
class PropertyMappingManager: class PropertyMappingManager:
@ -60,11 +60,7 @@ class PropertyMappingManager:
mapping.set_context(user, request, **kwargs) mapping.set_context(user, request, **kwargs)
try: try:
value = mapping.evaluate(mapping.model.expression) value = mapping.evaluate(mapping.model.expression)
except SkipObjectException as exc: except (PropertyMappingExpressionException, ControlFlowException) as exc:
exc.exc = exc
exc.mapping = mapping
raise exc from exc
except PropertyMappingExpressionException as exc:
raise exc from exc raise exc from exc
except Exception as exc: except Exception as exc:
raise PropertyMappingExpressionException(exc, mapping.model) from exc raise PropertyMappingExpressionException(exc, mapping.model) from exc

View File

@ -9,9 +9,9 @@ from structlog.stdlib import get_logger
from authentik.core.expression.exceptions import ( from authentik.core.expression.exceptions import (
PropertyMappingExpressionException, PropertyMappingExpressionException,
SkipObjectException,
) )
from authentik.events.models import Event, EventAction 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.mapper import PropertyMappingManager
from authentik.lib.sync.outgoing.exceptions import NotFoundSyncException, StopSync from authentik.lib.sync.outgoing.exceptions import NotFoundSyncException, StopSync
from authentik.lib.utils.errors import exception_to_string from authentik.lib.utils.errors import exception_to_string
@ -92,7 +92,7 @@ class BaseOutgoingSyncClient[
eval_kwargs.setdefault("user", None) eval_kwargs.setdefault("user", None)
for value in self.mapper.iter_eval(**eval_kwargs): for value in self.mapper.iter_eval(**eval_kwargs):
always_merger.merge(raw_final_object, value) always_merger.merge(raw_final_object, value)
except SkipObjectException as exc: except ControlFlowException as exc:
raise exc from exc raise exc from exc
except PropertyMappingExpressionException as exc: except PropertyMappingExpressionException as exc:
# Value error can be raised when assigning invalid data to an attribute # Value error can be raised when assigning invalid data to an attribute