diff --git a/engine/apps/alerts/incident_appearance/renderers/telegram_renderer.py b/engine/apps/alerts/incident_appearance/renderers/telegram_renderer.py index b3364810..9b3ad515 100644 --- a/engine/apps/alerts/incident_appearance/renderers/telegram_renderer.py +++ b/engine/apps/alerts/incident_appearance/renderers/telegram_renderer.py @@ -51,7 +51,7 @@ class AlertGroupTelegramRenderer(AlertGroupBaseRenderer): status_verbose = self.alert_group.get_acknowledge_text() # First line in the invisible link with id of organization. # It is needed to add info about organization to the telegram message for the oncall-gateway. - text = f"" + text = f"" text += f"{status_emoji} #{self.alert_group.inside_organization_number}, {title}\n" text += f"{status_verbose}, alerts: {alerts_count_str}\n" text += f"Source: {self.alert_group.channel.short_name}\n" diff --git a/engine/apps/api/views/organization.py b/engine/apps/api/views/organization.py index 86bae0bc..bce742b1 100644 --- a/engine/apps/api/views/organization.py +++ b/engine/apps/api/views/organization.py @@ -67,7 +67,7 @@ class GetTelegramVerificationCode(APIView): bot_username = telegram_client.api_client.username bot_link = f"https://t.me/{bot_username}" return Response( - {"telegram_code": str(new_code.uuid_with_org_id), "bot_link": bot_link}, status=status.HTTP_200_OK + {"telegram_code": str(new_code.uuid_with_org_uuid), "bot_link": bot_link}, status=status.HTTP_200_OK ) diff --git a/engine/apps/api/views/user.py b/engine/apps/api/views/user.py index be7ff475..f54d58be 100644 --- a/engine/apps/api/views/user.py +++ b/engine/apps/api/views/user.py @@ -374,7 +374,7 @@ class UserView( bot_link = f"https://t.me/{bot_username}" return Response( - {"telegram_code": str(new_code.uuid_with_org_id), "bot_link": bot_link}, status=status.HTTP_200_OK + {"telegram_code": str(new_code.uuid_with_org_uuid), "bot_link": bot_link}, status=status.HTTP_200_OK ) @action(detail=True, methods=["post"]) diff --git a/engine/apps/telegram/models/verification/channel.py b/engine/apps/telegram/models/verification/channel.py index 8d80f03a..1b24bb6e 100644 --- a/engine/apps/telegram/models/verification/channel.py +++ b/engine/apps/telegram/models/verification/channel.py @@ -24,8 +24,8 @@ class TelegramChannelVerificationCode(models.Model): return self.datetime + timezone.timedelta(days=1) < timezone.now() @property - def uuid_with_org_id(self) -> str: - return f"{self.organization.public_primary_key}_{self.uuid}" + def uuid_with_org_uuid(self) -> str: + return f"{self.organization.uuid}_{self.uuid}" @classmethod def uuid_without_org_id(cls, verification_code: str) -> str: diff --git a/engine/apps/telegram/models/verification/personal.py b/engine/apps/telegram/models/verification/personal.py index 299c9993..323b990a 100644 --- a/engine/apps/telegram/models/verification/personal.py +++ b/engine/apps/telegram/models/verification/personal.py @@ -22,8 +22,8 @@ class TelegramVerificationCode(models.Model): return self.datetime + timezone.timedelta(days=1) < timezone.now() @property - def uuid_with_org_id(self) -> str: - return f"{self.user.organization.public_primary_key}_{self.uuid}" + def uuid_with_org_uuid(self) -> str: + return f"{self.user.organization.uuid}_{self.uuid}" @classmethod def uuid_without_org_id(cls, verification_code: str) -> str: diff --git a/engine/apps/telegram/renderers/keyboard.py b/engine/apps/telegram/renderers/keyboard.py index 997f5473..d580dc26 100644 --- a/engine/apps/telegram/renderers/keyboard.py +++ b/engine/apps/telegram/renderers/keyboard.py @@ -83,10 +83,10 @@ class TelegramKeyboardRenderer: callback_data_args = [self.alert_group.pk, action.value] if action_data is not None: callback_data_args.append(action_data) - # Add org id with 'x-oncall-org-id' prefix to callback data. + # Add org id with 'oncall-uuid' prefix to callback data. # It's a workaroung to pass org_id to the oncall-gateway while proxying requests. # TODO: switch to json str instead of ':' separated string. - callback_data_args.append(f"x-oncall-org-id{self.alert_group.channel.organization.public_primary_key}") + callback_data_args.append(f"oncall-uuid{self.alert_group.channel.organization.uuid}") button = InlineKeyboardButton(text=text, callback_data=CallbackQueryFactory.encode_data(*callback_data_args)) return button diff --git a/engine/apps/telegram/tests/test_keyboard_renderer.py b/engine/apps/telegram/tests/test_keyboard_renderer.py index 50d769d8..89b1182d 100644 --- a/engine/apps/telegram/tests/test_keyboard_renderer.py +++ b/engine/apps/telegram/tests/test_keyboard_renderer.py @@ -49,27 +49,27 @@ def test_actions_keyboard_alerting(make_organization, make_alert_receive_channel [ InlineKeyboardButton( text="Acknowledge", - callback_data=f"{alert_group.pk}:acknowledge:x-oncall-org-id{organization.public_primary_key}", + callback_data=f"{alert_group.pk}:acknowledge:oncall-uuid{organization.uuid}", ) ], [ InlineKeyboardButton( text="Resolve", - callback_data=f"{alert_group.pk}:resolve:x-oncall-org-id{organization.public_primary_key}", + callback_data=f"{alert_group.pk}:resolve:oncall-uuid{organization.uuid}", ) ], [ InlineKeyboardButton( text="🔕 forever", - callback_data=f"{alert_group.pk}:silence:x-oncall-org-id{organization.public_primary_key}", + callback_data=f"{alert_group.pk}:silence:oncall-uuid{organization.uuid}", ), InlineKeyboardButton( text="... for 1h", - callback_data=f"{alert_group.pk}:silence:3600:x-oncall-org-id{organization.public_primary_key}", + callback_data=f"{alert_group.pk}:silence:3600:oncall-uuid{organization.uuid}", ), InlineKeyboardButton( text="... for 4h", - callback_data=f"{alert_group.pk}:silence:14400:x-oncall-org-id{organization.public_primary_key}", + callback_data=f"{alert_group.pk}:silence:14400:oncall-uuid{organization.uuid}", ), ], ] @@ -97,13 +97,13 @@ def test_actions_keyboard_acknowledged( [ InlineKeyboardButton( text="Unacknowledge", - callback_data=f"{alert_group.pk}:unacknowledge:x-oncall-org-id{organization.public_primary_key}", + callback_data=f"{alert_group.pk}:unacknowledge:oncall-uuid{organization.uuid}", ) ], [ InlineKeyboardButton( text="Resolve", - callback_data=f"{alert_group.pk}:resolve:x-oncall-org-id{organization.public_primary_key}", + callback_data=f"{alert_group.pk}:resolve:oncall-uuid{organization.uuid}", ) ], ] @@ -131,7 +131,7 @@ def test_actions_keyboard_resolved( [ InlineKeyboardButton( text="Unresolve", - callback_data=f"{alert_group.pk}:unresolve:x-oncall-org-id{organization.public_primary_key}", + callback_data=f"{alert_group.pk}:unresolve:oncall-uuid{organization.uuid}", ) ], ] @@ -159,19 +159,19 @@ def test_actions_keyboard_silenced( [ InlineKeyboardButton( text="Acknowledge", - callback_data=f"{alert_group.pk}:acknowledge:x-oncall-org-id{organization.public_primary_key}", + callback_data=f"{alert_group.pk}:acknowledge:oncall-uuid{organization.uuid}", ) ], [ InlineKeyboardButton( text="Resolve", - callback_data=f"{alert_group.pk}:resolve:x-oncall-org-id{organization.public_primary_key}", + callback_data=f"{alert_group.pk}:resolve:oncall-uuid{organization.uuid}", ) ], [ InlineKeyboardButton( text="Unsilence", - callback_data=f"{alert_group.pk}:unsilence:x-oncall-org-id{organization.public_primary_key}", + callback_data=f"{alert_group.pk}:unsilence:oncall-uuid{organization.uuid}", ) ], ] diff --git a/engine/apps/telegram/tests/test_message_renderer.py b/engine/apps/telegram/tests/test_message_renderer.py index 7d800975..862d1c50 100644 --- a/engine/apps/telegram/tests/test_message_renderer.py +++ b/engine/apps/telegram/tests/test_message_renderer.py @@ -72,7 +72,7 @@ def test_alert_group_message(make_organization, make_alert_receive_channel, make renderer = TelegramMessageRenderer(alert_group=alert_group) text = renderer.render_alert_group_message() assert text == ( - f"🔴 #{alert_group.inside_organization_number}, {alert_receive_channel.config.tests['telegram']['title']}\n" + f"🔴 #{alert_group.inside_organization_number}, {alert_receive_channel.config.tests['telegram']['title']}\n" "Alerting, alerts: 1\n" "Source: Test integration - Grafana\n" f"{alert_group.web_link}\n\n" @@ -156,7 +156,7 @@ def test_personal_message( text = renderer.render_personal_message() assert text == ( - f"🟠 #{alert_group.inside_organization_number}, {alert_receive_channel.config.tests['telegram']['title']}\n" + f"🟠 #{alert_group.inside_organization_number}, {alert_receive_channel.config.tests['telegram']['title']}\n" f"Acknowledged by {user_name}, alerts: 1\n" "Source: Test integration - Grafana\n" f"{alert_group.web_link}\n\n" diff --git a/engine/apps/telegram/tests/test_models.py b/engine/apps/telegram/tests/test_models.py index 0a9497e4..16a22b93 100644 --- a/engine/apps/telegram/tests/test_models.py +++ b/engine/apps/telegram/tests/test_models.py @@ -17,7 +17,7 @@ def test_user_verification_handler_process_update_another_account_already_linked user_2 = make_user_for_organization(organization) code = make_telegram_verification_code(user_2) - connector, created = TelegramVerificationCode.verify_user(code.uuid_with_org_id, chat_id, "nickname") + connector, created = TelegramVerificationCode.verify_user(code.uuid_with_org_uuid, chat_id, "nickname") assert created assert connector.telegram_chat_id == chat_id @@ -38,7 +38,7 @@ def test_user_verification_handler_process_update_user_already_linked( other_chat_id = 321 code = make_telegram_verification_code(user_1) - connector, created = TelegramVerificationCode.verify_user(code.uuid_with_org_id, other_chat_id, "nickname") + connector, created = TelegramVerificationCode.verify_user(code.uuid_with_org_uuid, other_chat_id, "nickname") assert created is False assert connector.user == user_1 diff --git a/engine/apps/telegram/updates/update_handlers/button_press.py b/engine/apps/telegram/updates/update_handlers/button_press.py index 005460fe..91d2002e 100644 --- a/engine/apps/telegram/updates/update_handlers/button_press.py +++ b/engine/apps/telegram/updates/update_handlers/button_press.py @@ -61,8 +61,8 @@ class ButtonPressHandler(UpdateHandler): has_permission = user_is_authorized(user, [RBACPermission.Permissions.CHATOPS_WRITE]) return user.organization == alert_group.channel.organization and has_permission - @staticmethod - def _get_action_context(data: str) -> ActionContext: + @classmethod + def _get_action_context(cls, data: str) -> ActionContext: args = CallbackQueryFactory.decode_data(data) alert_group_pk = args[0] @@ -71,10 +71,16 @@ class ButtonPressHandler(UpdateHandler): action_name = args[1] action = Action(action_name) - action_data = args[2] if len(args) >= 3 and not args[2].startswith("x-oncall-org-id") else None + action_data = args[2] if len(args) >= 3 and not cls._is_oncall_identifier(args[2]) else None return ActionContext(alert_group=alert_group, action=action, action_data=action_data) + @staticmethod + def _is_oncall_identifier(string: str) -> bool: + # determines if piece of data passed via callback_data is oncall_identifier + # x-oncall-org-id is kept here for backward compatibility. + return string.startswith("x-oncall-org-id") or string.startswith("oncall-uuid") + @staticmethod def _map_action_context_to_fn(action_context: ActionContext) -> Tuple[Callable, dict]: action_to_fn = { diff --git a/engine/apps/telegram/utils.py b/engine/apps/telegram/utils.py index 18640c58..be878e7a 100644 --- a/engine/apps/telegram/utils.py +++ b/engine/apps/telegram/utils.py @@ -1,7 +1,8 @@ import re from typing import List, Union -TELEGRAM_VERIFICATION_CODE_REGEX = "^[A-Z0-9]*_[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" +uuid_regex = "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}" +TELEGRAM_VERIFICATION_CODE_REGEX = f"^{uuid_regex}_{uuid_regex}$" def is_verification_message(text: str) -> bool: diff --git a/engine/apps/user_management/migrations/0006_organization_uuid.py b/engine/apps/user_management/migrations/0006_organization_uuid.py new file mode 100644 index 00000000..63d16ded --- /dev/null +++ b/engine/apps/user_management/migrations/0006_organization_uuid.py @@ -0,0 +1,19 @@ +# Generated by Django 3.2.16 on 2022-12-05 07:00 + +from django.db import migrations, models +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + ('user_management', '0005_rbac_permissions'), + ] + + operations = [ + migrations.AddField( + model_name='organization', + name='uuid', + field=models.UUIDField(default=uuid.uuid4, editable=False), + ), + ] diff --git a/engine/apps/user_management/models/organization.py b/engine/apps/user_management/models/organization.py index f428b32d..1a571b1f 100644 --- a/engine/apps/user_management/models/organization.py +++ b/engine/apps/user_management/models/organization.py @@ -1,5 +1,6 @@ import logging import typing +import uuid from urllib.parse import urljoin from django.apps import apps @@ -129,6 +130,9 @@ class Organization(MaintainableObject): # Slack specific field with general log channel id general_log_channel_id = models.CharField(max_length=100, null=True, default=None) + # uuid used to unuqie identify organization in different clusters + uuid = models.UUIDField(default=uuid.uuid4, editable=False) + # Organization Settings configured from slack ( ACKNOWLEDGE_REMIND_NEVER, @@ -282,9 +286,9 @@ class Organization(MaintainableObject): return urljoin(self.grafana_url, "a/grafana-oncall-app/") @property - def web_link_with_id(self): - # It's a workaround to pass org id to the oncall gateway while proxying telegram requests - return urljoin(self.grafana_url, f"a/grafana-oncall-app/?x-oncall-org-id={self.public_primary_key}") + def web_link_with_uuid(self): + # It's a workaround to pass some unique identifier to the oncall gateway while proxying telegram requests + return urljoin(self.grafana_url, f"a/grafana-oncall-app/?oncall-uuid={self.uuid}") def __str__(self): return f"{self.pk}: {self.org_title}" diff --git a/engine/common/oncall_gateway/oncall_gateway_client.py b/engine/common/oncall_gateway/oncall_gateway_client.py index 63d48613..77af439d 100644 --- a/engine/common/oncall_gateway/oncall_gateway_client.py +++ b/engine/common/oncall_gateway/oncall_gateway_client.py @@ -71,8 +71,8 @@ class OnCallGatewayAPIClient: response = self._post(url=self._slack_connectors_url, json=d) response_data = response.json() return ( - OnCallConnector( - response_data["oncall_org_id"], + SlackConnector( + response_data["slack_team_id"], response_data["backend"], ), response, diff --git a/grafana-plugin/src/containers/UserSettings/parts/tabs/TelegramInfo/TelegramInfo.tsx b/grafana-plugin/src/containers/UserSettings/parts/tabs/TelegramInfo/TelegramInfo.tsx index 3c186429..efc94642 100644 --- a/grafana-plugin/src/containers/UserSettings/parts/tabs/TelegramInfo/TelegramInfo.tsx +++ b/grafana-plugin/src/containers/UserSettings/parts/tabs/TelegramInfo/TelegramInfo.tsx @@ -39,13 +39,13 @@ const TelegramInfo = observer((_props: TelegramInfoProps) => { <> {telegramConfigured || !store.hasFeature(AppFeature.LiveSettings) ? ( - Connect personal Telegram + {/* Connect personal Telegram Connect Telegram automatically - + */} Manual connection