oncall-engine/engine/apps/mobile_app/fcm_relay.py

107 lines
3.4 KiB
Python
Raw Permalink Normal View History

import logging
from celery.utils.log import get_task_logger
from django.conf import settings
from firebase_admin.messaging import AndroidConfig, APNSConfig, APNSPayload, Aps, ApsAlert, CriticalSound, Message
from rest_framework import status
Enforce cloud connection to send push notifications on OSS (#1132) This PR modifies how OSS instances send mobile app push notifications. It also adds frontend warnings when user is trying to use the mobile app without connecting to cloud. - [x] Add public API authentication to `FCMRelayView` and throttle the view to 300 push notifications per instance per minute. This is similar to how SMS and phone call notifications work on OSS instances. - [x] Add frontend warnings based on cloud connectivity - [x] Fix/add frontend tests - [x] Add tests for FCMRelayView and mobile app backend ## Screenshots When a user tries to connect the mobile app in his settings and cloud is not connected (clicking "Connect Cloud OnCall" redirects to the "Cloud" tab): <img width="1088" alt="Screenshot 2023-01-12 at 18 48 58" src="https://user-images.githubusercontent.com/20116910/212156591-86906020-eddf-43f1-9402-7ebb7547c7e6.png"> When a user tries to use mobile push notifications as a personal notification step and cloud is not connected: <img width="764" alt="Screenshot 2023-01-12 at 19 01 10" src="https://user-images.githubusercontent.com/20116910/212157580-9abb0758-79ad-4316-b8cd-15b4fff01502.png"> Now on the "Cloud" tab there's some info about the mobile app (the last section at the bottom of the page): <img width="1245" alt="Screenshot 2023-01-12 at 18 49 10" src="https://user-images.githubusercontent.com/20116910/212156997-c8b70dd5-bf15-4bc7-8eb8-9decdb8ecc80.png"> After connecting to the cloud instance, everything goes back to active and it's now possible to connect the mobile app: <img width="1091" alt="Screenshot 2023-01-12 at 19 08 27" src="https://user-images.githubusercontent.com/20116910/212158811-60d49888-4714-4c0e-850f-3ff6a11a117a.png"> After connecting the app the warning is gone: <img width="764" alt="Screenshot 2023-01-12 at 19 07 00" src="https://user-images.githubusercontent.com/20116910/212158614-677ab889-127f-4d64-bacc-0c26887f3097.png">
2023-01-19 11:15:56 +00:00
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
Enforce cloud connection to send push notifications on OSS (#1132) This PR modifies how OSS instances send mobile app push notifications. It also adds frontend warnings when user is trying to use the mobile app without connecting to cloud. - [x] Add public API authentication to `FCMRelayView` and throttle the view to 300 push notifications per instance per minute. This is similar to how SMS and phone call notifications work on OSS instances. - [x] Add frontend warnings based on cloud connectivity - [x] Fix/add frontend tests - [x] Add tests for FCMRelayView and mobile app backend ## Screenshots When a user tries to connect the mobile app in his settings and cloud is not connected (clicking "Connect Cloud OnCall" redirects to the "Cloud" tab): <img width="1088" alt="Screenshot 2023-01-12 at 18 48 58" src="https://user-images.githubusercontent.com/20116910/212156591-86906020-eddf-43f1-9402-7ebb7547c7e6.png"> When a user tries to use mobile push notifications as a personal notification step and cloud is not connected: <img width="764" alt="Screenshot 2023-01-12 at 19 01 10" src="https://user-images.githubusercontent.com/20116910/212157580-9abb0758-79ad-4316-b8cd-15b4fff01502.png"> Now on the "Cloud" tab there's some info about the mobile app (the last section at the bottom of the page): <img width="1245" alt="Screenshot 2023-01-12 at 18 49 10" src="https://user-images.githubusercontent.com/20116910/212156997-c8b70dd5-bf15-4bc7-8eb8-9decdb8ecc80.png"> After connecting to the cloud instance, everything goes back to active and it's now possible to connect the mobile app: <img width="1091" alt="Screenshot 2023-01-12 at 19 08 27" src="https://user-images.githubusercontent.com/20116910/212158811-60d49888-4714-4c0e-850f-3ff6a11a117a.png"> After connecting the app the warning is gone: <img width="764" alt="Screenshot 2023-01-12 at 19 07 00" src="https://user-images.githubusercontent.com/20116910/212158614-677ab889-127f-4d64-bacc-0c26887f3097.png">
2023-01-19 11:15:56 +00:00
from apps.auth_token.auth import ApiTokenAuthentication
from apps.mobile_app.models import FCMDevice
from apps.mobile_app.utils import send_message_to_fcm_device
from common.api_helpers.custom_rate_scoped_throttler import CustomRateUserThrottler
from common.custom_celery_tasks import shared_dedicated_queue_retry_task
task_logger = get_task_logger(__name__)
task_logger.setLevel(logging.DEBUG)
class FCMRelayThrottler(CustomRateUserThrottler):
Enforce cloud connection to send push notifications on OSS (#1132) This PR modifies how OSS instances send mobile app push notifications. It also adds frontend warnings when user is trying to use the mobile app without connecting to cloud. - [x] Add public API authentication to `FCMRelayView` and throttle the view to 300 push notifications per instance per minute. This is similar to how SMS and phone call notifications work on OSS instances. - [x] Add frontend warnings based on cloud connectivity - [x] Fix/add frontend tests - [x] Add tests for FCMRelayView and mobile app backend ## Screenshots When a user tries to connect the mobile app in his settings and cloud is not connected (clicking "Connect Cloud OnCall" redirects to the "Cloud" tab): <img width="1088" alt="Screenshot 2023-01-12 at 18 48 58" src="https://user-images.githubusercontent.com/20116910/212156591-86906020-eddf-43f1-9402-7ebb7547c7e6.png"> When a user tries to use mobile push notifications as a personal notification step and cloud is not connected: <img width="764" alt="Screenshot 2023-01-12 at 19 01 10" src="https://user-images.githubusercontent.com/20116910/212157580-9abb0758-79ad-4316-b8cd-15b4fff01502.png"> Now on the "Cloud" tab there's some info about the mobile app (the last section at the bottom of the page): <img width="1245" alt="Screenshot 2023-01-12 at 18 49 10" src="https://user-images.githubusercontent.com/20116910/212156997-c8b70dd5-bf15-4bc7-8eb8-9decdb8ecc80.png"> After connecting to the cloud instance, everything goes back to active and it's now possible to connect the mobile app: <img width="1091" alt="Screenshot 2023-01-12 at 19 08 27" src="https://user-images.githubusercontent.com/20116910/212158811-60d49888-4714-4c0e-850f-3ff6a11a117a.png"> After connecting the app the warning is gone: <img width="764" alt="Screenshot 2023-01-12 at 19 07 00" src="https://user-images.githubusercontent.com/20116910/212158614-677ab889-127f-4d64-bacc-0c26887f3097.png">
2023-01-19 11:15:56 +00:00
scope = "fcm_relay"
rate = "300/m"
class FCMRelayView(APIView):
Enforce cloud connection to send push notifications on OSS (#1132) This PR modifies how OSS instances send mobile app push notifications. It also adds frontend warnings when user is trying to use the mobile app without connecting to cloud. - [x] Add public API authentication to `FCMRelayView` and throttle the view to 300 push notifications per instance per minute. This is similar to how SMS and phone call notifications work on OSS instances. - [x] Add frontend warnings based on cloud connectivity - [x] Fix/add frontend tests - [x] Add tests for FCMRelayView and mobile app backend ## Screenshots When a user tries to connect the mobile app in his settings and cloud is not connected (clicking "Connect Cloud OnCall" redirects to the "Cloud" tab): <img width="1088" alt="Screenshot 2023-01-12 at 18 48 58" src="https://user-images.githubusercontent.com/20116910/212156591-86906020-eddf-43f1-9402-7ebb7547c7e6.png"> When a user tries to use mobile push notifications as a personal notification step and cloud is not connected: <img width="764" alt="Screenshot 2023-01-12 at 19 01 10" src="https://user-images.githubusercontent.com/20116910/212157580-9abb0758-79ad-4316-b8cd-15b4fff01502.png"> Now on the "Cloud" tab there's some info about the mobile app (the last section at the bottom of the page): <img width="1245" alt="Screenshot 2023-01-12 at 18 49 10" src="https://user-images.githubusercontent.com/20116910/212156997-c8b70dd5-bf15-4bc7-8eb8-9decdb8ecc80.png"> After connecting to the cloud instance, everything goes back to active and it's now possible to connect the mobile app: <img width="1091" alt="Screenshot 2023-01-12 at 19 08 27" src="https://user-images.githubusercontent.com/20116910/212158811-60d49888-4714-4c0e-850f-3ff6a11a117a.png"> After connecting the app the warning is gone: <img width="764" alt="Screenshot 2023-01-12 at 19 07 00" src="https://user-images.githubusercontent.com/20116910/212158614-677ab889-127f-4d64-bacc-0c26887f3097.png">
2023-01-19 11:15:56 +00:00
"""
This view accepts push notifications from OSS instances and forwards these requests to FCM.
Requests to this endpoint come from OSS instances: apps.mobile_app.tasks.send_push_notification_to_fcm_relay.
The view uses public API authentication, so an OSS instance must be connected to cloud to use FCM relay.
"""
authentication_classes = [ApiTokenAuthentication]
permission_classes = [IsAuthenticated]
throttle_classes = [FCMRelayThrottler]
def post(self, request):
Enforce cloud connection to send push notifications on OSS (#1132) This PR modifies how OSS instances send mobile app push notifications. It also adds frontend warnings when user is trying to use the mobile app without connecting to cloud. - [x] Add public API authentication to `FCMRelayView` and throttle the view to 300 push notifications per instance per minute. This is similar to how SMS and phone call notifications work on OSS instances. - [x] Add frontend warnings based on cloud connectivity - [x] Fix/add frontend tests - [x] Add tests for FCMRelayView and mobile app backend ## Screenshots When a user tries to connect the mobile app in his settings and cloud is not connected (clicking "Connect Cloud OnCall" redirects to the "Cloud" tab): <img width="1088" alt="Screenshot 2023-01-12 at 18 48 58" src="https://user-images.githubusercontent.com/20116910/212156591-86906020-eddf-43f1-9402-7ebb7547c7e6.png"> When a user tries to use mobile push notifications as a personal notification step and cloud is not connected: <img width="764" alt="Screenshot 2023-01-12 at 19 01 10" src="https://user-images.githubusercontent.com/20116910/212157580-9abb0758-79ad-4316-b8cd-15b4fff01502.png"> Now on the "Cloud" tab there's some info about the mobile app (the last section at the bottom of the page): <img width="1245" alt="Screenshot 2023-01-12 at 18 49 10" src="https://user-images.githubusercontent.com/20116910/212156997-c8b70dd5-bf15-4bc7-8eb8-9decdb8ecc80.png"> After connecting to the cloud instance, everything goes back to active and it's now possible to connect the mobile app: <img width="1091" alt="Screenshot 2023-01-12 at 19 08 27" src="https://user-images.githubusercontent.com/20116910/212158811-60d49888-4714-4c0e-850f-3ff6a11a117a.png"> After connecting the app the warning is gone: <img width="764" alt="Screenshot 2023-01-12 at 19 07 00" src="https://user-images.githubusercontent.com/20116910/212158614-677ab889-127f-4d64-bacc-0c26887f3097.png">
2023-01-19 11:15:56 +00:00
if not settings.FCM_RELAY_ENABLED:
return Response(status=status.HTTP_404_NOT_FOUND)
try:
token = request.data["token"]
data = request.data["data"]
apns = request.data["apns"]
android = request.data.get("android") # optional
except KeyError:
return Response(status=status.HTTP_400_BAD_REQUEST)
fcm_relay_async.delay(token=token, data=data, apns=apns, android=android)
return Response(status=status.HTTP_200_OK)
@shared_dedicated_queue_retry_task(
autoretry_for=(Exception,), retry_backoff=True, max_retries=1 if settings.DEBUG else 5
)
def fcm_relay_async(token, data, apns, android=None):
send_message_to_fcm_device(
FCMDevice(registration_id=token), _get_message_from_request_data(token, data, apns, android)
)
def _get_message_from_request_data(token, data, apns, android):
"""
Create Message object from JSON payload from OSS instance.
"""
return Message(
token=token, data=data, apns=_deserialize_apns(apns), android=AndroidConfig(**android) if android else None
)
def _deserialize_apns(apns):
"""
Create APNSConfig object from JSON payload from OSS instance.
"""
aps = apns.get("payload", {}).get("aps", {})
if not aps:
return None
thread_id = aps.get("thread-id")
badge = aps.get("badge")
alert = aps.get("alert")
if isinstance(alert, dict):
alert = ApsAlert(**alert)
sound = aps.get("sound")
if isinstance(sound, dict):
sound = CriticalSound(**sound)
# remove all keys from "aps" so it can be used for custom_data
for key in ["thread-id", "badge", "alert", "sound"]:
aps.pop(key, None)
return APNSConfig(
payload=APNSPayload(
aps=Aps(
thread_id=thread_id,
badge=badge,
alert=alert,
sound=sound,
custom_data=aps,
)
),
headers=apns.get("headers"),
)