diff --git a/CHANGELOG.md b/CHANGELOG.md index 330b73d3..dafa545e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fix edit default team by admin @mderynck ([#3885](https://github.com/grafana/oncall/pull/3885)) - Unblock slack install by skipping check chatops gateway link in OSS deployment @mderynck ([#3893](https://github.com/grafana/oncall/pull/3893)) +### Changed + +- Check for permissions on Slack escalate command ([#3891](https://github.com/grafana/oncall/pull/3891)) + ## v1.3.105 (2024-02-13) ### Fixed diff --git a/engine/apps/slack/scenarios/paging.py b/engine/apps/slack/scenarios/paging.py index 7d80ebe1..6cacd73a 100644 --- a/engine/apps/slack/scenarios/paging.py +++ b/engine/apps/slack/scenarios/paging.py @@ -9,7 +9,7 @@ from rest_framework.response import Response from apps.alerts.models import AlertReceiveChannel from apps.alerts.paging import DirectPagingUserTeamValidationError, UserNotifications, direct_paging, user_is_oncall -from apps.api.permissions import RBACPermission +from apps.api.permissions import RBACPermission, user_is_authorized from apps.schedules.ical_utils import get_cached_oncall_users_for_multiple_schedules from apps.slack.constants import DIVIDER, PRIVATE_METADATA_MAX_LENGTH from apps.slack.errors import SlackAPIChannelNotFoundError @@ -114,7 +114,6 @@ def get_current_items( class StartDirectPaging(scenario_step.ScenarioStep): """Handle slash command invocation and show initial dialog.""" - REQUIRED_PERMISSIONS = [RBACPermission.Permissions.ALERT_GROUPS_DIRECT_PAGING] command_name = [settings.SLACK_DIRECT_PAGING_SLASH_COMMAND] def process_scenario( @@ -147,6 +146,8 @@ class StartDirectPaging(scenario_step.ScenarioStep): class FinishDirectPaging(scenario_step.ScenarioStep): """Handle page command dialog submit.""" + REQUIRED_PERMISSIONS = [RBACPermission.Permissions.ALERT_GROUPS_DIRECT_PAGING] + def process_scenario( self, slack_user_identity: "SlackUserIdentity", @@ -160,6 +161,21 @@ class FinishDirectPaging(scenario_step.ScenarioStep): selected_organization = _get_selected_org_from_payload( payload, input_id_prefix, slack_team_identity, slack_user_identity ) + + # get user in the context of the selected_organization + user = slack_user_identity.get_user(selected_organization) + if not user_is_authorized(user, self.REQUIRED_PERMISSIONS): + unauthorized_error = _get_unauthorized_warning(error=True) + return Response( + { + "response_action": "update", + "view": render_dialog( + slack_user_identity, slack_team_identity, payload, validation_errors=unauthorized_error + ), + }, + status=200, + ) + _, selected_team = _get_selected_team_from_payload(payload, input_id_prefix) user = slack_user_identity.get_user(selected_organization) @@ -416,6 +432,11 @@ def render_dialog( if validation_errors: blocks += validation_errors + # get user in the context of the selected_organization + user = slack_user_identity.get_user(selected_organization) + if not user_is_authorized(user, FinishDirectPaging.REQUIRED_PERMISSIONS): + blocks += _get_unauthorized_warning() + blocks.append(_get_message_input(payload)) # Add organization select if more than one organization available for user @@ -446,6 +467,19 @@ def render_dialog( return _get_form_view(submit_routing_uid, blocks, json.dumps(new_private_metadata)) +def _get_unauthorized_warning(error=False): + icon = ":warning:" if not error else ":no_entry:" + text = f"{icon} You do not have permission to perform this action." + if not error: + text += "\nAsk an admin to upgrade your permissions." + return [ + typing.cast( + Block.Section, + {"type": "section", "text": {"type": "mrkdwn", "text": text}}, + ) + ] + + def _get_form_view(routing_uid: str, blocks: Block.AnyBlocks, private_metadata: str) -> ModalView: view: ModalView = { "type": "modal", diff --git a/engine/apps/slack/tests/test_scenario_steps/test_paging.py b/engine/apps/slack/tests/test_scenario_steps/test_paging.py index 4a2498ca..0d4d114d 100644 --- a/engine/apps/slack/tests/test_scenario_steps/test_paging.py +++ b/engine/apps/slack/tests/test_scenario_steps/test_paging.py @@ -5,6 +5,7 @@ import pytest from django.utils import timezone from apps.alerts.models import AlertReceiveChannel +from apps.api.permissions import LegacyAccessControlRole from apps.schedules.models import CustomOnCallShift, OnCallScheduleWeb from apps.slack.scenarios.paging import ( DIRECT_PAGING_MESSAGE_INPUT_ID, @@ -75,6 +76,23 @@ def test_initial_state( assert metadata[DataKey.USERS] == {} +@pytest.mark.parametrize("role", (LegacyAccessControlRole.VIEWER, LegacyAccessControlRole.NONE)) +@pytest.mark.django_db +def test_initial_unauthorized(make_organization_and_user_with_slack_identities, role): + _, _, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities(role=role) + payload = {"channel_id": "123", "trigger_id": "111"} + + step = StartDirectPaging(slack_team_identity) + with patch.object(step._slack_client, "views_open") as mock_slack_api_call: + step.process_scenario(slack_user_identity, slack_team_identity, payload) + + view = mock_slack_api_call.call_args.kwargs["view"] + assert ( + view["blocks"][0]["text"]["text"] + == ":warning: You do not have permission to perform this action.\nAsk an admin to upgrade your permissions." + ) + + @pytest.mark.django_db def test_add_user_no_warning(make_organization_and_user_with_slack_identities, make_schedule, make_on_call_shift): organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() @@ -231,6 +249,25 @@ def test_trigger_paging_no_team_or_user_selected(make_organization_and_user_with ) +@pytest.mark.parametrize("role", (LegacyAccessControlRole.VIEWER, LegacyAccessControlRole.NONE)) +@pytest.mark.django_db +def test_trigger_paging_unauthorized(make_organization_and_user_with_slack_identities, role): + organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities( + role=role + ) + payload = make_slack_payload(organization=organization) + + step = FinishDirectPaging(slack_team_identity) + with patch.object(step._slack_client, "api_call"): + response = step.process_scenario(slack_user_identity, slack_team_identity, payload) + response = response.data + + assert response["response_action"] == "update" + assert ( + response["view"]["blocks"][0]["text"]["text"] == ":no_entry: You do not have permission to perform this action." + ) + + @pytest.mark.django_db def test_trigger_paging_additional_responders(make_organization_and_user_with_slack_identities, make_team): organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities()