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,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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										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