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:
@ -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)
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
@ -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,6 +217,7 @@ 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
|
||||||
|
if not isinstance(exc, ControlFlowException):
|
||||||
self.handle_error(exc, expression_source)
|
self.handle_error(exc, expression_source)
|
||||||
raise exc
|
raise exc
|
||||||
return result
|
return result
|
||||||
|
|||||||
6
authentik/lib/expression/exceptions.py
Normal file
6
authentik/lib/expression/exceptions.py
Normal 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"""
|
||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user