oncall-engine/engine/apps/api/views/live_setting.py
Vadim Stepanov 3d7c044193
Fix Telegram ratelimit on live setting change (#2100)
# What this PR does

Fixes https://github.com/grafana/oncall/issues/1103, inspired by
https://github.com/grafana/oncall/pull/1934.

Makes sure that:
1. `LiveSettings.validate_settings` is only called once per update
request and not called for any individual LiveSetting instance save.
2. `telegram.Bot.set_webhook` is only called once per request when
changing `TELEGRAM_WEBHOOK_HOST`.


## Which issue(s) this PR fixes

https://github.com/grafana/oncall/issues/1103

## Checklist

- [x] Unit, integration, and e2e (if applicable) tests updated
- [x] Documentation added (or `pr:no public docs` PR label added if not
required)
- [x] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not
required)
2023-06-06 15:18:12 +00:00

104 lines
4.1 KiB
Python

from contextlib import suppress
from django.conf import settings
from django.http import HttpResponse
from rest_framework import status, viewsets
from rest_framework.permissions import IsAuthenticated
from telegram import error
from apps.api.permissions import RBACPermission
from apps.api.serializers.live_setting import LiveSettingSerializer
from apps.auth_token.auth import PluginAuthentication
from apps.base.models import LiveSetting
from apps.oss_installation.tasks import sync_users_with_cloud
from apps.slack.tasks import unpopulate_slack_user_identities
from apps.telegram.client import TelegramClient
from apps.telegram.tasks import register_telegram_webhook
from apps.user_management.models import User
from common.api_helpers.mixins import PublicPrimaryKeyMixin
class LiveSettingViewSet(PublicPrimaryKeyMixin, viewsets.ModelViewSet):
serializer_class = LiveSettingSerializer
authentication_classes = (PluginAuthentication,)
permission_classes = (IsAuthenticated, RBACPermission)
rbac_permissions = {
"list": [RBACPermission.Permissions.OTHER_SETTINGS_READ],
"retrieve": [RBACPermission.Permissions.OTHER_SETTINGS_READ],
"create": [RBACPermission.Permissions.OTHER_SETTINGS_WRITE],
"update": [RBACPermission.Permissions.OTHER_SETTINGS_WRITE],
"destroy": [RBACPermission.Permissions.OTHER_SETTINGS_WRITE],
}
def dispatch(self, request, *args, **kwargs):
if not settings.FEATURE_LIVE_SETTINGS_ENABLED:
return HttpResponse(status=status.HTTP_404_NOT_FOUND)
return super().dispatch(request, *args, **kwargs)
def get_queryset(self):
LiveSetting.populate_settings_if_needed()
queryset = LiveSetting.objects.filter(name__in=LiveSetting.AVAILABLE_NAMES).order_by("name")
search = self.request.query_params.get("search", None)
if search:
queryset = queryset.filter(name=search)
return queryset
def perform_update(self, serializer):
name = serializer.instance.name
old_value = serializer.instance.value
new_value = serializer.validated_data["value"]
super().perform_update(serializer)
if new_value != old_value:
self._post_update_hook(name, old_value)
LiveSetting.validate_settings()
def perform_destroy(self, instance):
name = instance.name
old_value = instance.value
new_value = instance.default_value
super().perform_destroy(instance)
if new_value != old_value:
self._post_update_hook(name, old_value)
def _post_update_hook(self, name, old_value):
if name == "TELEGRAM_TOKEN":
self._reset_telegram_integration(old_token=old_value)
register_telegram_webhook.delay()
if name in ["SLACK_CLIENT_OAUTH_ID", "SLACK_CLIENT_OAUTH_SECRET"]:
organization = self.request.auth.organization
slack_team_identity = organization.slack_team_identity
if slack_team_identity is not None:
unpopulate_slack_user_identities.delay(organization_pk=organization.pk, force=True)
if name == "GRAFANA_CLOUD_ONCALL_TOKEN":
from apps.oss_installation.models import CloudConnector
CloudConnector.remove_sync()
sync_users = self.request.query_params.get("sync_users", "true") == "true"
if sync_users:
sync_users_with_cloud.apply_async()
def _reset_telegram_integration(self, old_token):
# tell Telegram to cancel sending events from old bot
with suppress(error.InvalidToken, error.Unauthorized):
old_client = TelegramClient(token=old_token)
old_client.api_client.delete_webhook()
# delete telegram channels for current team
organization = self.request.auth.organization
organization.telegram_channel.all().delete()
# delete telegram connectors for users in team
users_with_telegram_connector = User.objects.filter(
organization=organization, telegram_connection__isnull=False
).distinct()
for user in users_with_telegram_connector:
user.telegram_connection.delete()