diff --git a/CHANGELOG.md b/CHANGELOG.md index ca254640..34c4efd2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Fixed too frequent retry of `perform_notification` task on Telegram ratelimit error by @Ferril ([#3744](https://github.com/grafana/oncall/pull/3744)) +- Add check whether organization has Slack connection on update Slack related field using public api endpoints + by @Ferril ([#3751](https://github.com/grafana/oncall/pull/3751)) ## v1.3.92 (2024-01-23) diff --git a/engine/apps/public_api/serializers/routes.py b/engine/apps/public_api/serializers/routes.py index 878722be..0b6468db 100644 --- a/engine/apps/public_api/serializers/routes.py +++ b/engine/apps/public_api/serializers/routes.py @@ -86,6 +86,8 @@ class BaseChannelFilterSerializer(OrderedModelSerializer): slack_channel_id = slack_channel_id.upper() organization = self.context["request"].auth.organization slack_team_identity = organization.slack_team_identity + if not slack_team_identity: + raise BadRequest(detail="Slack isn't connected to this workspace") try: slack_team_identity.get_cached_channels().get(slack_id=slack_channel_id) except SlackChannel.DoesNotExist: diff --git a/engine/apps/public_api/serializers/schedules_base.py b/engine/apps/public_api/serializers/schedules_base.py index 03e4ae60..5ea13cb4 100644 --- a/engine/apps/public_api/serializers/schedules_base.py +++ b/engine/apps/public_api/serializers/schedules_base.py @@ -45,6 +45,9 @@ class ScheduleBaseSerializer(serializers.ModelSerializer): organization = self.context["request"].auth.organization slack_team_identity = organization.slack_team_identity + if (slack_channel_id or user_group_id) and not slack_team_identity: + raise BadRequest(detail="Slack isn't connected to this workspace") + if slack_channel_id is not None: slack_channel_id = slack_channel_id.upper() try: diff --git a/engine/apps/public_api/tests/test_integrations.py b/engine/apps/public_api/tests/test_integrations.py index 0d7a3045..8f9e2473 100644 --- a/engine/apps/public_api/tests/test_integrations.py +++ b/engine/apps/public_api/tests/test_integrations.py @@ -819,6 +819,50 @@ def test_update_integration_default_route( assert response.data["default_route"]["escalation_chain_id"] == escalation_chain.public_primary_key +@pytest.mark.django_db +def test_create_integration_default_route_with_slack_field( + make_organization_and_user_with_token, + make_escalation_chain, +): + organization, _, token = make_organization_and_user_with_token() + escalation_chain = make_escalation_chain(organization) + + client = APIClient() + data_for_create = { + "type": "grafana", + "name": "grafana_created", + "team_id": None, + "default_route": { + "escalation_chain_id": escalation_chain.public_primary_key, + "slack": {"channel_id": "TEST_SLACK_ID"}, + }, + } + url = reverse("api-public:integrations-list") + response = client.post(url, data=data_for_create, format="json", HTTP_AUTHORIZATION=f"{token}") + assert response.status_code == status.HTTP_400_BAD_REQUEST + assert response.data["detail"] == "Slack isn't connected to this workspace" + + +@pytest.mark.django_db +def test_update_integration_default_route_with_slack_field( + make_organization_and_user_with_token, make_alert_receive_channel, make_channel_filter +): + organization, _, token = make_organization_and_user_with_token() + integration = make_alert_receive_channel(organization) + make_channel_filter(integration, is_default=True) + + client = APIClient() + data_for_update = { + "default_route": {"slack": {"channel_id": "TEST_SLACK_ID"}}, + } + + url = reverse("api-public:integrations-detail", args=[integration.public_primary_key]) + response = client.put(url, data=data_for_update, format="json", HTTP_AUTHORIZATION=f"{token}") + + assert response.status_code == status.HTTP_400_BAD_REQUEST + assert response.data["detail"] == "Slack isn't connected to this workspace" + + @pytest.mark.django_db def test_cant_create_integrations_direct_paging( make_organization_and_user_with_token, make_team, make_alert_receive_channel, make_user_auth_headers diff --git a/engine/apps/public_api/tests/test_routes.py b/engine/apps/public_api/tests/test_routes.py index a03a238b..126408bc 100644 --- a/engine/apps/public_api/tests/test_routes.py +++ b/engine/apps/public_api/tests/test_routes.py @@ -282,6 +282,52 @@ def test_delete_route( new_channel_filter.refresh_from_db() +@pytest.mark.django_db +def test_create_route_slack_error( + route_public_api_setup, +): + _, _, token, alert_receive_channel, escalation_chain, _ = route_public_api_setup + + client = APIClient() + + url = reverse("api-public:routes-list") + data_for_create = { + "integration_id": alert_receive_channel.public_primary_key, + "routing_regex": "testreg", + "escalation_chain_id": escalation_chain.public_primary_key, + "slack": {"channel_id": "TEST_SLACK_ID"}, + } + response = client.post(url, format="json", HTTP_AUTHORIZATION=token, data=data_for_create) + + assert response.status_code == status.HTTP_400_BAD_REQUEST + assert response.data["detail"] == "Slack isn't connected to this workspace" + + +@pytest.mark.django_db +def test_update_route_slack_error( + route_public_api_setup, + make_channel_filter, +): + _, _, token, alert_receive_channel, escalation_chain, _ = route_public_api_setup + new_channel_filter = make_channel_filter( + alert_receive_channel, + is_default=False, + filtering_term="testreg", + ) + + client = APIClient() + + url = reverse("api-public:routes-detail", kwargs={"pk": new_channel_filter.public_primary_key}) + data_to_update = { + "slack": {"channel_id": "TEST_SLACK_ID"}, + } + + response = client.put(url, format="json", HTTP_AUTHORIZATION=token, data=data_to_update) + + assert response.status_code == status.HTTP_400_BAD_REQUEST + assert response.data["detail"] == "Slack isn't connected to this workspace" + + @pytest.mark.django_db def test_create_route_with_messaging_backend( route_public_api_setup, diff --git a/engine/apps/public_api/tests/test_schedules.py b/engine/apps/public_api/tests/test_schedules.py index 956f59c2..6d041bf9 100644 --- a/engine/apps/public_api/tests/test_schedules.py +++ b/engine/apps/public_api/tests/test_schedules.py @@ -844,6 +844,65 @@ def test_create_schedule_invalid_timezone(make_organization_and_user_with_token, assert response.json() == {"time_zone": ["Invalid timezone"]} +@pytest.mark.django_db +def test_create_calendar_schedule_slack_error(make_organization_and_user_with_token): + organization, user, token = make_organization_and_user_with_token() + client = APIClient() + + url = reverse("api-public:schedules-list") + # with slack channel id + data = { + "team_id": None, + "name": "schedule test name", + "time_zone": "Europe/Moscow", + "type": "calendar", + "slack": { + "channel_id": "TEST_SLACK_ID", + }, + } + + response = client.post(url, data=data, format="json", HTTP_AUTHORIZATION=f"{token}") + assert response.status_code == status.HTTP_400_BAD_REQUEST + assert response.data["detail"] == "Slack isn't connected to this workspace" + # with slack user group id + data = { + "team_id": None, + "name": "schedule test name", + "time_zone": "Europe/Moscow", + "type": "calendar", + "slack": { + "user_group_id": "TEST_SLACK_ID", + }, + } + + response = client.post(url, data=data, format="json", HTTP_AUTHORIZATION=f"{token}") + assert response.status_code == status.HTTP_400_BAD_REQUEST + assert response.data["detail"] == "Slack isn't connected to this workspace" + + +@pytest.mark.django_db +def test_update_calendar_schedule_slack_error( + make_organization_and_user_with_token, + make_schedule, +): + organization, user, token = make_organization_and_user_with_token() + client = APIClient() + schedule = make_schedule(organization, schedule_class=OnCallScheduleCalendar) + url = reverse("api-public:schedules-detail", kwargs={"pk": schedule.public_primary_key}) + + data = {"slack": {"channel_id": "TEST_SLACK_ID"}} + + response = client.put(url, data=data, format="json", HTTP_AUTHORIZATION=f"{token}") + assert response.status_code == status.HTTP_400_BAD_REQUEST + assert response.data["detail"] == "Slack isn't connected to this workspace" + + data = {"slack": {"user_group_id": "TEST_SLACK_ID"}} + + response = client.put(url, data=data, format="json", HTTP_AUTHORIZATION=f"{token}") + assert response.status_code == status.HTTP_400_BAD_REQUEST + assert response.data["detail"] == "Slack isn't connected to this workspace" + + @pytest.mark.django_db def test_create_ical_schedule_without_ical_url(make_organization_and_user_with_token): _, _, token = make_organization_and_user_with_token()