diff --git a/engine/apps/email/inbound.py b/engine/apps/email/inbound.py index 6c86e194..950728f2 100644 --- a/engine/apps/email/inbound.py +++ b/engine/apps/email/inbound.py @@ -77,6 +77,7 @@ class InboundEmailWebhookView(AlertChannelDefiningMixin, APIView): integration_token = self.get_integration_token_from_request(request) if integration_token is None: return HttpResponse(status=status.HTTP_400_BAD_REQUEST) + request.inbound_email_integration_token = integration_token # used in RequestTimeLoggingMiddleware return super().dispatch(request, alert_channel_key=integration_token) def post(self, request): @@ -138,14 +139,15 @@ class InboundEmailWebhookView(AlertChannelDefiningMixin, APIView): try: view.run_validators(self.request) events = view.parse_events(self.request) - except (AnymailWebhookValidationFailure, AnymailAPIError) as e: - logger.info(f"inbound email webhook validation failed for ESP {esp}: {e}") + except (AnymailWebhookValidationFailure, AnymailAPIError): continue messages = [event.message for event in events if isinstance(event, AnymailInboundEvent)] if messages: + logger.info(f"Received inbound email message from ESP: {esp}") return messages[0] + logger.error("Failed to parse inbound email message") return None def check_inbound_email_settings_set(self): diff --git a/engine/apps/public_api/serializers/resolution_notes.py b/engine/apps/public_api/serializers/resolution_notes.py index b48f3fa6..6a7749f1 100644 --- a/engine/apps/public_api/serializers/resolution_notes.py +++ b/engine/apps/public_api/serializers/resolution_notes.py @@ -1,6 +1,7 @@ from rest_framework import serializers from apps.alerts.models import AlertGroup, ResolutionNote +from apps.user_management.models import ServiceAccountUser from common.api_helpers.custom_fields import OrganizationFilteredPrimaryKeyRelatedField, UserIdField from common.api_helpers.exceptions import BadRequest from common.api_helpers.mixins import EagerLoadingMixin @@ -34,8 +35,9 @@ class ResolutionNoteSerializer(EagerLoadingMixin, serializers.ModelSerializer): SELECT_RELATED = ["alert_group", "resolution_note_slack_message", "author"] def create(self, validated_data): - if self.context["request"].user.pk: - validated_data["author"] = self.context["request"].user + user = self.context["request"].user + if not isinstance(user, ServiceAccountUser) and user.pk: + validated_data["author"] = user validated_data["source"] = ResolutionNote.Source.WEB return super().create(validated_data) diff --git a/engine/apps/public_api/tests/test_resolution_notes.py b/engine/apps/public_api/tests/test_resolution_notes.py index 7a730e18..a98abac5 100644 --- a/engine/apps/public_api/tests/test_resolution_notes.py +++ b/engine/apps/public_api/tests/test_resolution_notes.py @@ -1,13 +1,16 @@ from unittest.mock import patch +import httpretty import pytest from django.urls import reverse from rest_framework import status from rest_framework.test import APIClient from apps.alerts.models import ResolutionNote +from apps.api import permissions from apps.auth_token.auth import ApiTokenAuthentication, GrafanaServiceAccountAuthentication from apps.auth_token.models import ApiAuthToken, ServiceAccountToken +from apps.auth_token.tests.helpers import setup_service_account_api_mocks @pytest.mark.django_db @@ -146,6 +149,53 @@ def test_create_resolution_note( mock_send_update_resolution_note_signal.assert_called_once() +@pytest.mark.django_db +@httpretty.activate(verbose=True, allow_net_connect=False) +@patch("apps.alerts.tasks.send_update_resolution_note_signal.send_update_resolution_note_signal.apply_async") +def test_create_resolution_note_via_service_account( + mock_send_update_resolution_note_signal, + make_organization, + make_service_account_for_organization, + make_token_for_service_account, + make_alert_receive_channel, + make_alert_group, +): + organization = make_organization(grafana_url="http://grafana.test") + service_account = make_service_account_for_organization(organization) + token_string = "glsa_token" + make_token_for_service_account(service_account, token_string) + + perms = { + permissions.RBACPermission.Permissions.ALERT_GROUPS_WRITE.value: ["*"], + } + setup_service_account_api_mocks(organization, perms) + + alert_receive_channel = make_alert_receive_channel(organization) + alert_group = make_alert_group(alert_receive_channel) + data = { + "alert_group_id": alert_group.public_primary_key, + "text": "Test Resolution Note Message", + } + url = reverse("api-public:resolution_notes-list") + client = APIClient() + response = client.post( + url, + data=data, + format="json", + HTTP_AUTHORIZATION=f"{token_string}", + HTTP_X_GRAFANA_URL=organization.grafana_url, + ) + if not organization.is_rbac_permissions_enabled: + assert response.status_code == status.HTTP_403_FORBIDDEN + else: + assert response.status_code == status.HTTP_201_CREATED + mock_send_update_resolution_note_signal.assert_called_once() + resolution_note = ResolutionNote.objects.get(public_primary_key=response.data["id"]) + assert resolution_note.author is None + assert resolution_note.text == data["text"] + assert resolution_note.alert_group == alert_group + + @pytest.mark.django_db def test_create_resolution_note_invalid_text( make_organization_and_user_with_token, diff --git a/engine/engine/middlewares.py b/engine/engine/middlewares.py index 0173323b..6842d274 100644 --- a/engine/engine/middlewares.py +++ b/engine/engine/middlewares.py @@ -44,7 +44,7 @@ class RequestTimeLoggingMiddleware(MiddlewareMixin): if len(split_path) >= 5: integration_token = split_path[4] else: - integration_token = None + integration_token = getattr(request, "inbound_email_integration_token", None) message += f"integration_type={integration_type} integration_token={integration_token} " logging.info(message)