flows: add additional sentry spans
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
		@ -6,6 +6,7 @@ from django.http.response import HttpResponse
 | 
				
			|||||||
from django.urls import reverse
 | 
					from django.urls import reverse
 | 
				
			||||||
from django.views.generic.base import View
 | 
					from django.views.generic.base import View
 | 
				
			||||||
from rest_framework.request import Request
 | 
					from rest_framework.request import Request
 | 
				
			||||||
 | 
					from sentry_sdk.hub import Hub
 | 
				
			||||||
from structlog.stdlib import get_logger
 | 
					from structlog.stdlib import get_logger
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from authentik.core.models import DEFAULT_AVATAR, User
 | 
					from authentik.core.models import DEFAULT_AVATAR, User
 | 
				
			||||||
@ -94,8 +95,16 @@ class ChallengeStageView(StageView):
 | 
				
			|||||||
                    keep_context=keep_context,
 | 
					                    keep_context=keep_context,
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
                return self.executor.restart_flow(keep_context)
 | 
					                return self.executor.restart_flow(keep_context)
 | 
				
			||||||
            return self.challenge_invalid(challenge)
 | 
					            with Hub.current.start_span(
 | 
				
			||||||
        return self.challenge_valid(challenge)
 | 
					                op="authentik.flow.stage.challenge_invalid",
 | 
				
			||||||
 | 
					                description=self.__class__.__name__,
 | 
				
			||||||
 | 
					            ):
 | 
				
			||||||
 | 
					                return self.challenge_invalid(challenge)
 | 
				
			||||||
 | 
					        with Hub.current.start_span(
 | 
				
			||||||
 | 
					            op="authentik.flow.stage.challenge_valid",
 | 
				
			||||||
 | 
					            description=self.__class__.__name__,
 | 
				
			||||||
 | 
					        ):
 | 
				
			||||||
 | 
					            return self.challenge_valid(challenge)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def format_title(self) -> str:
 | 
					    def format_title(self) -> str:
 | 
				
			||||||
        """Allow usage of placeholder in flow title."""
 | 
					        """Allow usage of placeholder in flow title."""
 | 
				
			||||||
@ -104,7 +113,11 @@ class ChallengeStageView(StageView):
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _get_challenge(self, *args, **kwargs) -> Challenge:
 | 
					    def _get_challenge(self, *args, **kwargs) -> Challenge:
 | 
				
			||||||
        challenge = self.get_challenge(*args, **kwargs)
 | 
					        with Hub.current.start_span(
 | 
				
			||||||
 | 
					            op="authentik.flow.stage.get_challenge",
 | 
				
			||||||
 | 
					            description=self.__class__.__name__,
 | 
				
			||||||
 | 
					        ):
 | 
				
			||||||
 | 
					            challenge = self.get_challenge(*args, **kwargs)
 | 
				
			||||||
        if "flow_info" not in challenge.initial_data:
 | 
					        if "flow_info" not in challenge.initial_data:
 | 
				
			||||||
            flow_info = ContextualFlowInfo(
 | 
					            flow_info = ContextualFlowInfo(
 | 
				
			||||||
                data={
 | 
					                data={
 | 
				
			||||||
 | 
				
			|||||||
@ -12,6 +12,7 @@ from django.utils.translation import gettext as _
 | 
				
			|||||||
from drf_spectacular.utils import PolymorphicProxySerializer, extend_schema_field
 | 
					from drf_spectacular.utils import PolymorphicProxySerializer, extend_schema_field
 | 
				
			||||||
from rest_framework.fields import BooleanField, CharField, DictField, ListField
 | 
					from rest_framework.fields import BooleanField, CharField, DictField, ListField
 | 
				
			||||||
from rest_framework.serializers import ValidationError
 | 
					from rest_framework.serializers import ValidationError
 | 
				
			||||||
 | 
					from sentry_sdk.hub import Hub
 | 
				
			||||||
from structlog.stdlib import get_logger
 | 
					from structlog.stdlib import get_logger
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from authentik.core.api.utils import PassiveSerializer
 | 
					from authentik.core.api.utils import PassiveSerializer
 | 
				
			||||||
@ -90,8 +91,12 @@ class IdentificationChallengeResponse(ChallengeResponse):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        pre_user = self.stage.get_user(uid_field)
 | 
					        pre_user = self.stage.get_user(uid_field)
 | 
				
			||||||
        if not pre_user:
 | 
					        if not pre_user:
 | 
				
			||||||
            # Sleep a random time (between 90 and 210ms) to "prevent" user enumeration attacks
 | 
					            with Hub.current.start_span(
 | 
				
			||||||
            sleep(0.30 * SystemRandom().randint(3, 7))
 | 
					                op="authentik.stages.identification.validate_invalid_wait",
 | 
				
			||||||
 | 
					                description="Sleep random time on invalid user identifier",
 | 
				
			||||||
 | 
					            ):
 | 
				
			||||||
 | 
					                # Sleep a random time (between 90 and 210ms) to "prevent" user enumeration attacks
 | 
				
			||||||
 | 
					                sleep(0.30 * SystemRandom().randint(3, 7))
 | 
				
			||||||
            LOGGER.debug("invalid_login", identifier=uid_field)
 | 
					            LOGGER.debug("invalid_login", identifier=uid_field)
 | 
				
			||||||
            identification_failed.send(sender=self, request=self.stage.request, uid_field=uid_field)
 | 
					            identification_failed.send(sender=self, request=self.stage.request, uid_field=uid_field)
 | 
				
			||||||
            # We set the pending_user even on failure so it's part of the context, even
 | 
					            # We set the pending_user even on failure so it's part of the context, even
 | 
				
			||||||
@ -114,12 +119,16 @@ class IdentificationChallengeResponse(ChallengeResponse):
 | 
				
			|||||||
        if not password:
 | 
					        if not password:
 | 
				
			||||||
            LOGGER.warning("Password not set for ident+auth attempt")
 | 
					            LOGGER.warning("Password not set for ident+auth attempt")
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            user = authenticate(
 | 
					            with Hub.current.start_span(
 | 
				
			||||||
                self.stage.request,
 | 
					                op="authentik.stages.identification.authenticate",
 | 
				
			||||||
                current_stage.password_stage.backends,
 | 
					                description="User authenticate call (combo stage)",
 | 
				
			||||||
                username=self.pre_user.username,
 | 
					            ):
 | 
				
			||||||
                password=password,
 | 
					                user = authenticate(
 | 
				
			||||||
            )
 | 
					                    self.stage.request,
 | 
				
			||||||
 | 
					                    current_stage.password_stage.backends,
 | 
				
			||||||
 | 
					                    username=self.pre_user.username,
 | 
				
			||||||
 | 
					                    password=password,
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
            if not user:
 | 
					            if not user:
 | 
				
			||||||
                raise ValidationError("Failed to authenticate.")
 | 
					                raise ValidationError("Failed to authenticate.")
 | 
				
			||||||
            self.pre_user = user
 | 
					            self.pre_user = user
 | 
				
			||||||
 | 
				
			|||||||
@ -10,6 +10,7 @@ from django.urls import reverse
 | 
				
			|||||||
from django.utils.translation import gettext as _
 | 
					from django.utils.translation import gettext as _
 | 
				
			||||||
from rest_framework.exceptions import ErrorDetail, ValidationError
 | 
					from rest_framework.exceptions import ErrorDetail, ValidationError
 | 
				
			||||||
from rest_framework.fields import CharField
 | 
					from rest_framework.fields import CharField
 | 
				
			||||||
 | 
					from sentry_sdk.hub import Hub
 | 
				
			||||||
from structlog.stdlib import get_logger
 | 
					from structlog.stdlib import get_logger
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from authentik.core.models import User
 | 
					from authentik.core.models import User
 | 
				
			||||||
@ -43,7 +44,11 @@ def authenticate(request: HttpRequest, backends: list[str], **credentials: Any)
 | 
				
			|||||||
            LOGGER.warning("Failed to import backend", path=backend_path)
 | 
					            LOGGER.warning("Failed to import backend", path=backend_path)
 | 
				
			||||||
            continue
 | 
					            continue
 | 
				
			||||||
        LOGGER.debug("Attempting authentication...", backend=backend_path)
 | 
					        LOGGER.debug("Attempting authentication...", backend=backend_path)
 | 
				
			||||||
        user = backend.authenticate(request, **credentials)
 | 
					        with Hub.current.start_span(
 | 
				
			||||||
 | 
					            op="authentik.stages.password.authenticate",
 | 
				
			||||||
 | 
					            description=backend_path,
 | 
				
			||||||
 | 
					        ):
 | 
				
			||||||
 | 
					            user = backend.authenticate(request, **credentials)
 | 
				
			||||||
        if user is None:
 | 
					        if user is None:
 | 
				
			||||||
            LOGGER.debug("Backend returned nothing, continuing", backend=backend_path)
 | 
					            LOGGER.debug("Backend returned nothing, continuing", backend=backend_path)
 | 
				
			||||||
            continue
 | 
					            continue
 | 
				
			||||||
@ -120,7 +125,13 @@ class PasswordStageView(ChallengeStageView):
 | 
				
			|||||||
            "username": pending_user.username,
 | 
					            "username": pending_user.username,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            user = authenticate(self.request, self.executor.current_stage.backends, **auth_kwargs)
 | 
					            with Hub.current.start_span(
 | 
				
			||||||
 | 
					                op="authentik.stages.password.authenticate",
 | 
				
			||||||
 | 
					                description="User authenticate call",
 | 
				
			||||||
 | 
					            ):
 | 
				
			||||||
 | 
					                user = authenticate(
 | 
				
			||||||
 | 
					                    self.request, self.executor.current_stage.backends, **auth_kwargs
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
        except PermissionDenied:
 | 
					        except PermissionDenied:
 | 
				
			||||||
            del auth_kwargs["password"]
 | 
					            del auth_kwargs["password"]
 | 
				
			||||||
            # User was found, but permission was denied (i.e. user is not active)
 | 
					            # User was found, but permission was denied (i.e. user is not active)
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user