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
	 Jens Langhammer
					Jens Langhammer