From b62687295df6cf178ea9727eda33ec4e692c888c Mon Sep 17 00:00:00 2001 From: Matias Bordese Date: Fri, 5 May 2023 15:09:25 -0300 Subject: [PATCH] Fix teams filter related issue setting maintenance mode (#1885) Related to https://github.com/grafana/support-escalations/issues/5862 --- CHANGELOG.md | 1 + engine/apps/api/tests/test_maintenance.py | 98 +++++++++++++++++++++++ engine/apps/api/views/maintenance.py | 17 ++-- 3 files changed, 110 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b1c4278b..3e8c5d7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Fix issue with how OnCall determines if a cloud Grafana Instance supports RBAC by @joeyorlando ([#1880](https://github.com/grafana/oncall/pull/1880)) +- Fix issue trying to set maintenance mode for integrations belonging to non-current team ## v1.2.19 (2023-05-04) diff --git a/engine/apps/api/tests/test_maintenance.py b/engine/apps/api/tests/test_maintenance.py index 6441d063..1371608b 100644 --- a/engine/apps/api/tests/test_maintenance.py +++ b/engine/apps/api/tests/test_maintenance.py @@ -46,6 +46,64 @@ def test_start_maintenance_integration( assert alert_receive_channel.maintenance_author is not None +@pytest.mark.django_db +def test_start_maintenance_integration_user_team( + maintenance_internal_api_setup, mock_start_disable_maintenance_task, make_user_auth_headers, make_team +): + token, organization, user, alert_receive_channel = maintenance_internal_api_setup + another_team = make_team(organization) + user.current_team = another_team + user.save() + + client = APIClient() + + url = reverse("api-internal:start_maintenance") + data = { + "mode": AlertReceiveChannel.MAINTENANCE, + "duration": AlertReceiveChannel.DURATION_ONE_HOUR.total_seconds(), + "type": "alert_receive_channel", + "alert_receive_channel_id": alert_receive_channel.public_primary_key, + } + response = client.post(url, data=data, format="json", **make_user_auth_headers(user, token)) + + alert_receive_channel.refresh_from_db() + assert response.status_code == status.HTTP_200_OK + assert alert_receive_channel.maintenance_mode == AlertReceiveChannel.MAINTENANCE + assert alert_receive_channel.maintenance_duration == AlertReceiveChannel.DURATION_ONE_HOUR + assert alert_receive_channel.maintenance_uuid is not None + assert alert_receive_channel.maintenance_started_at is not None + assert alert_receive_channel.maintenance_author is not None + + +@pytest.mark.django_db +def test_start_maintenance_integration_different_team( + maintenance_internal_api_setup, mock_start_disable_maintenance_task, make_user_auth_headers, make_team +): + token, organization, user, alert_receive_channel = maintenance_internal_api_setup + another_team = make_team(organization) + other_team = make_team(organization) + user.current_team = another_team + user.save() + # integration belongs to non-general team, != user current team + alert_receive_channel.team = other_team + alert_receive_channel.save() + + client = APIClient() + + url = reverse("api-internal:start_maintenance") + data = { + "mode": AlertReceiveChannel.MAINTENANCE, + "duration": AlertReceiveChannel.DURATION_ONE_HOUR.total_seconds(), + "type": "alert_receive_channel", + "alert_receive_channel_id": alert_receive_channel.public_primary_key, + } + response = client.post(url, data=data, format="json", **make_user_auth_headers(user, token)) + assert response.status_code == status.HTTP_400_BAD_REQUEST + + alert_receive_channel.refresh_from_db() + assert alert_receive_channel.maintenance_mode is None + + @pytest.mark.django_db def test_stop_maintenance_integration( maintenance_internal_api_setup, @@ -159,6 +217,46 @@ def test_maintenances_list( assert response.json() == expected_payload +@pytest.mark.django_db +def test_maintenances_list_include_all_user_teams( + maintenance_internal_api_setup, + mock_start_disable_maintenance_task, + make_user_auth_headers, + make_team, +): + token, organization, user, alert_receive_channel = maintenance_internal_api_setup + another_team = make_team(organization) + other_team = make_team(organization) + # setup user teams + user.teams.add(another_team) + user.teams.add(other_team) + user.current_team = other_team + user.save() + # integration belongs to non-general team, != user current team + alert_receive_channel.team = another_team + alert_receive_channel.save() + + client = APIClient() + mode = AlertReceiveChannel.MAINTENANCE + duration = AlertReceiveChannel.DURATION_ONE_HOUR.seconds + alert_receive_channel.start_maintenance(mode, duration, user) + url = reverse("api-internal:maintenance") + response = client.get(url, format="json", **make_user_auth_headers(user, token)) + + expected_payload = [ + { + "alert_receive_channel_id": alert_receive_channel.public_primary_key, + "type": "alert_receive_channel", + "maintenance_mode": 1, + "maintenance_till_timestamp": alert_receive_channel.till_maintenance_timestamp, + "started_at_timestamp": alert_receive_channel.started_at_timestamp, + }, + ] + + assert response.status_code == status.HTTP_200_OK + assert response.json() == expected_payload + + @pytest.mark.django_db def test_empty_maintenances_list( maintenance_internal_api_setup, mock_start_disable_maintenance_task, make_user_auth_headers diff --git a/engine/apps/api/views/maintenance.py b/engine/apps/api/views/maintenance.py index c875d65c..1617b207 100644 --- a/engine/apps/api/views/maintenance.py +++ b/engine/apps/api/views/maintenance.py @@ -9,6 +9,7 @@ from apps.alerts.models.maintainable_object import MaintainableObject from apps.api.permissions import RBACPermission from apps.auth_token.auth import PluginAuthentication from common.api_helpers.exceptions import BadRequest +from common.api_helpers.mixins import TeamFilteringMixin from common.exceptions import MaintenanceCouldNotBeStartedError @@ -26,8 +27,9 @@ class GetObjectMixin: instance = AlertReceiveChannel.objects.get( public_primary_key=pk, organization=organization, - team=request.user.current_team, ) + if instance.team is not None and instance.team not in self.request.user.teams.all(): + raise BadRequest(detail={"alert_receive_channel_id": ["unknown id"]}) except AlertReceiveChannel.DoesNotExist: raise BadRequest(detail={"alert_receive_channel_id": ["unknown id"]}) else: @@ -38,7 +40,7 @@ class GetObjectMixin: return instance -class MaintenanceAPIView(APIView): +class MaintenanceAPIView(APIView, TeamFilteringMixin): """Deprecated. Maintenance management is now performed on integrations page (alert_receive_channel/ endpoint))""" authentication_classes = (PluginAuthentication,) @@ -51,12 +53,15 @@ class MaintenanceAPIView(APIView): def get(self, request): organization = self.request.auth.organization - team = self.request.user.current_team response = [] - integrations_under_maintenance = AlertReceiveChannel.objects.filter( - maintenance_mode__isnull=False, organization=organization, team=team - ).order_by("maintenance_started_at") + integrations_under_maintenance = ( + AlertReceiveChannel.objects.filter( + maintenance_mode__isnull=False, organization=organization, *self.available_teams_lookup_args + ) + .distinct() + .order_by("maintenance_started_at") + ) if organization.maintenance_mode is not None: response.append(