From 1878b7e596f728fc84cef192af46f60168bceabd Mon Sep 17 00:00:00 2001 From: Vadim Stepanov Date: Thu, 1 Dec 2022 15:17:01 +0000 Subject: [PATCH] Mobile app FCM support (#923) * Add ability to configure FCM_API_KEY and FCM_POST_URL * Delete APNSDevice and GCMDevice instances when unlinking the mobile app backend * Add a simple FCM relay endpoint * GCM -> FCM * comment --- engine/apps/mobile_app/backend.py | 5 +++++ engine/apps/mobile_app/fcm_relay.py | 26 ++++++++++++++++++++++++++ engine/apps/mobile_app/urls.py | 12 ++++++++++-- engine/apps/mobile_app/views.py | 14 ++++++++++++-- engine/settings/base.py | 10 ++++++---- 5 files changed, 59 insertions(+), 8 deletions(-) create mode 100644 engine/apps/mobile_app/fcm_relay.py diff --git a/engine/apps/mobile_app/backend.py b/engine/apps/mobile_app/backend.py index 1acf67b9..40663216 100644 --- a/engine/apps/mobile_app/backend.py +++ b/engine/apps/mobile_app/backend.py @@ -1,6 +1,7 @@ import json from django.conf import settings +from push_notifications.models import APNSDevice, GCMDevice from apps.base.messaging import BaseMessagingBackend from apps.mobile_app.tasks import notify_user_async @@ -33,6 +34,10 @@ class MobileAppBackend(BaseMessagingBackend): token = MobileAppAuthToken.objects.get(user=user) token.delete() + # delete push notification related info for user + APNSDevice.objects.filter(user=user).delete() + GCMDevice.objects.filter(user=user).delete() + def serialize_user(self, user): from apps.mobile_app.models import MobileAppAuthToken diff --git a/engine/apps/mobile_app/fcm_relay.py b/engine/apps/mobile_app/fcm_relay.py new file mode 100644 index 00000000..33bebda4 --- /dev/null +++ b/engine/apps/mobile_app/fcm_relay.py @@ -0,0 +1,26 @@ +from push_notifications.gcm import send_message +from rest_framework import status +from rest_framework.response import Response +from rest_framework.views import APIView + +REQUIRED_FIELDS = {"registration_ids", "notification", "data"} + + +class FCMRelayView(APIView): + def post(self, request): + """ + This view accepts requests from OSS instances of Grafana OnCall and forwards these requests to FCM. + Requests will be sent with the FCM_API_KEY configured in server settings + (see PUSH_NOTIFICATIONS_SETTINGS in settings/base.py) + """ + + if not REQUIRED_FIELDS.issubset(request.data.keys()): + return Response(status=status.HTTP_400_BAD_REQUEST) + + registration_ids = request.data["registration_ids"] + data = { + **request.data["data"], + **request.data["notification"], + } + + return send_message(registration_ids=registration_ids, data=data, cloud_type="FCM") diff --git a/engine/apps/mobile_app/urls.py b/engine/apps/mobile_app/urls.py index ddb9700b..5af825a9 100644 --- a/engine/apps/mobile_app/urls.py +++ b/engine/apps/mobile_app/urls.py @@ -1,13 +1,21 @@ -from apps.mobile_app.views import APNSDeviceAuthorizedViewSet, GCMDeviceAuthorizedViewSet, MobileAppAuthTokenAPIView +from django.conf import settings + +from apps.mobile_app.fcm_relay import FCMRelayView +from apps.mobile_app.views import APNSDeviceAuthorizedViewSet, FCMDeviceAuthorizedViewSet, MobileAppAuthTokenAPIView from common.api_helpers.optional_slash_router import OptionalSlashRouter, optional_slash_path app_name = "mobile_app" router = OptionalSlashRouter() router.register("apns", APNSDeviceAuthorizedViewSet, basename="apns") -router.register("gcm", GCMDeviceAuthorizedViewSet, basename="gcm") +router.register("fcm", FCMDeviceAuthorizedViewSet, basename="fcm") urlpatterns = [ *router.urls, optional_slash_path("auth_token", MobileAppAuthTokenAPIView.as_view(), name="auth_token"), ] + +if settings.FCM_RELAY_ENABLED: + urlpatterns += [ + optional_slash_path("fcm_relay", FCMRelayView.as_view(), name="fcm_relay"), + ] diff --git a/engine/apps/mobile_app/views.py b/engine/apps/mobile_app/views.py index 694eeff3..a9290f85 100644 --- a/engine/apps/mobile_app/views.py +++ b/engine/apps/mobile_app/views.py @@ -1,8 +1,9 @@ from push_notifications.api.rest_framework import APNSDeviceAuthorizedViewSet as BaseAPNSDeviceAuthorizedViewSet -from push_notifications.api.rest_framework import GCMDeviceAuthorizedViewSet as BaseGCMDeviceAuthorizedViewSet +from push_notifications.api.rest_framework import GCMDeviceAuthorizedViewSet, GCMDeviceSerializer from rest_framework import status from rest_framework.exceptions import NotFound from rest_framework.response import Response +from rest_framework.serializers import HiddenField from rest_framework.views import APIView from apps.mobile_app.auth import MobileAppAuthTokenAuthentication, MobileAppVerificationTokenAuthentication @@ -13,8 +14,17 @@ class APNSDeviceAuthorizedViewSet(BaseAPNSDeviceAuthorizedViewSet): authentication_classes = (MobileAppAuthTokenAuthentication,) -class GCMDeviceAuthorizedViewSet(BaseGCMDeviceAuthorizedViewSet): +class FCMDeviceAuthorizedViewSet(GCMDeviceAuthorizedViewSet): + class FCMDeviceSerializer(GCMDeviceSerializer): + """ + GCMDevice has cloud_message_type equal to "GCM" by default, in this serializer cloud_message_type is always set + to "FCM" no matter what was provided in the request. + """ + + cloud_message_type = HiddenField(default="FCM") + authentication_classes = (MobileAppAuthTokenAuthentication,) + serializer_class = FCMDeviceSerializer class MobileAppAuthTokenAPIView(APIView): diff --git a/engine/settings/base.py b/engine/settings/base.py index bbd17936..680b8249 100644 --- a/engine/settings/base.py +++ b/engine/settings/base.py @@ -552,16 +552,18 @@ if FEATURE_MOBILE_APP_INTEGRATION_ENABLED: ] PUSH_NOTIFICATIONS_SETTINGS = { - "FCM_API_KEY": os.environ.get("FCM_API_KEY", None), - "GCM_API_KEY": os.environ.get("GCM_API_KEY", None), + "FCM_API_KEY": os.getenv("FCM_API_KEY"), + "FCM_POST_URL": os.getenv("FCM_POST_URL", default="https://fcm.googleapis.com/fcm/send"), + "USER_MODEL": "user_management.User", + "UPDATE_ON_DUPLICATE_REG_ID": True, + # TODO: remove APNS related endpoints after the hackathon app is deprecated "APNS_AUTH_KEY_PATH": os.environ.get("APNS_AUTH_KEY_PATH", None), "APNS_TOPIC": os.environ.get("APNS_TOPIC", None), "APNS_AUTH_KEY_ID": os.environ.get("APNS_AUTH_KEY_ID", None), "APNS_TEAM_ID": os.environ.get("APNS_TEAM_ID", None), "APNS_USE_SANDBOX": getenv_boolean("APNS_USE_SANDBOX", True), - "USER_MODEL": "user_management.User", - "UPDATE_ON_DUPLICATE_REG_ID": True, } +FCM_RELAY_ENABLED = getenv_boolean("FCM_RELAY_ENABLED", default=False) SELF_HOSTED_SETTINGS = { "STACK_ID": 5,