diff --git a/zerver/lib/push_notifications.py b/zerver/lib/push_notifications.py index 21d5d8d6b6..e986fd01ff 100644 --- a/zerver/lib/push_notifications.py +++ b/zerver/lib/push_notifications.py @@ -18,6 +18,13 @@ connection = None if settings.APNS_CERT_FILE is not None and os.path.exists(settings.APNS_CERT_FILE): connection = session.get_connection(settings.APNS_SANDBOX, cert_file=settings.APNS_CERT_FILE) +# We maintain an additional APNS connection for pushing to Zulip apps that have been signed +# by the Dropbox certs (and have an app id of com.dropbox.zulip) +dbx_session = Session() +dbx_connection = None +if settings.DBX_APNS_CERT_FILE is not None and os.path.exists(settings.DBX_APNS_CERT_FILE): + dbx_connection = session.get_connection(settings.APNS_SANDBOX, cert_file=settings.DBX_APNS_CERT_FILE) + def num_push_devices_for_user(user_profile, kind = None): if kind is None: return PushDeviceToken.objects.filter(user=user_profile).count() @@ -31,25 +38,11 @@ def b64_to_hex(data): def hex_to_b64(data): return base64.b64encode(binascii.unhexlify(data)) -# Send a push notification to the desired clients -# extra_data is a dict that will be passed to the -# mobile app -@statsd_increment("apple_push_notification") -def send_apple_push_notification(user, alert, **extra_data): - if not connection: - logging.error("Attempting to send push notification, but no connection was found. This may be because we could not find the APNS Certificate file.") - return - - b64_tokens = [device.token for device in PushDeviceToken.objects.filter(user=user, kind=PushDeviceToken.APNS)] - tokens = [b64_to_hex(token) for token in b64_tokens] - - logging.info("APNS: Sending apple push notification to devices: %s" % (b64_tokens,)) - message = Message(tokens, alert=alert, **extra_data) - - apns_client = APNs(connection) +def _do_push_to_apns_service(user, message, apns_connection): + apns_client = APNs(apns_connection) ret = apns_client.send(message) if not ret: - logging.warning("APNS: Failed to send push notification for clients %s" % (b64_tokens,)) + logging.warning("APNS: Failed to send push notification for clients %s" % (message.tokens,)) return for token, reason in ret.failed.items(): @@ -73,6 +66,28 @@ def send_apple_push_notification(user, alert, **extra_data): logging.warning("APNS: Unknown error when delivering APNS: %s" % (errmsg,)) +# Send a push notification to the desired clients +# extra_data is a dict that will be passed to the +# mobile app +@statsd_increment("apple_push_notification") +def send_apple_push_notification(user, alert, **extra_data): + if not connection and not dbx_connection: + logging.error("Attempting to send push notification, but no connection was found. This may be because we could not find the APNS Certificate file.") + return + + devices = PushDeviceToken.objects.filter(user=user, kind=PushDeviceToken.APNS) + # Plain b64 token kept for debugging purposes + tokens = [(b64_to_hex(device.token), device.ios_app_id, device.token) for device in devices] + + logging.info("APNS: Sending apple push notification to devices: %s" % (tokens,)) + zulip_message = Message([token[0] for token in tokens if token[1] in (settings.ZULIP_IOS_APP_ID, None)], + alert=alert, **extra_data) + dbx_message = Message([token[0] for token in tokens if token[1] in (settings.DBX_IOS_APP_ID,)], + alert=alert, **extra_data) + + _do_push_to_apns_service(user, zulip_message, connection) + _do_push_to_apns_service(user, dbx_message, dbx_connection) + # NOTE: This is used by the check_apns_tokens manage.py command. Do not call it otherwise, as the # feedback() call can take up to 15s def check_apns_feedback(): diff --git a/zerver/views/__init__.py b/zerver/views/__init__.py index 8129446619..4af1055ca7 100644 --- a/zerver/views/__init__.py +++ b/zerver/views/__init__.py @@ -2281,7 +2281,7 @@ def json_set_muted_topics(request, user_profile, do_set_muted_topics(user_profile, muted_topics) return json_success() -def add_push_device_token(request, user_profile, token, kind): +def add_push_device_token(request, user_profile, token, kind, ios_app_id=None): if token == '' or len(token) > 4096: return json_error('Empty or invalid length token') @@ -2290,7 +2290,10 @@ def add_push_device_token(request, user_profile, token, kind): PushDeviceToken.objects.filter(token=token).delete() # Overwrite with the latest value - token, created = PushDeviceToken.objects.get_or_create(user=user_profile, token=token, kind=kind) + token, created = PushDeviceToken.objects.get_or_create(user=user_profile, + token=token, + kind=kind, + ios_app_id=ios_app_id) if not created: token.last_updated = now() token.save(update_fields=['last_updated']) @@ -2298,8 +2301,8 @@ def add_push_device_token(request, user_profile, token, kind): return json_success() @has_request_variables -def add_apns_device_token(request, user_profile, token=REQ): - return add_push_device_token(request, user_profile, token, PushDeviceToken.APNS) +def add_apns_device_token(request, user_profile, token=REQ, appid=REQ(default=settings.ZULIP_IOS_APP_ID)): + return add_push_device_token(request, user_profile, token, PushDeviceToken.APNS, ios_app_id=appid) @has_request_variables def add_android_reg_id(request, user_profile, token=REQ): diff --git a/zproject/local_settings.py b/zproject/local_settings.py index 2aafef084c..5755693587 100644 --- a/zproject/local_settings.py +++ b/zproject/local_settings.py @@ -126,10 +126,12 @@ if DEPLOYED or STAGING_DEPLOYED: APNS_SANDBOX = "push_production" APNS_FEEDBACK = "feedback_production" APNS_CERT_FILE = "/etc/ssl/django-private/apns-dist.pem" + DBX_APNS_CERT_FILE = "/etc/ssl/django-private/dbx-apns-dist.pem" else: APNS_SANDBOX = "push_sandbox" APNS_FEEDBACK = "feedback_sandbox" APNS_CERT_FILE = "/etc/ssl/django-private/apns-dev.pem" + DBX_APNS_CERT_FILE = "/etc/ssl/django-private/dbx-apns-dev.pem" # GCM tokens are IP-whitelisted; if we deploy to additional # servers you will need to explicitly add their IPs here: @@ -208,3 +210,6 @@ API_SUPER_USERS = set(["tabbott/extra@mit.edu", ADMINS = ( ('Zulip Error Reports', 'errors@zulip.com'), ) + +ZULIP_IOS_APP_ID = 'com.zulip.Zulip' +DBX_IOS_APP_ID = 'com.dropbox.Zulip' diff --git a/zproject/settings.py b/zproject/settings.py index 6886dfd755..7d5cb7a254 100644 --- a/zproject/settings.py +++ b/zproject/settings.py @@ -297,6 +297,7 @@ DEFAULT_SETTINGS = {'TWITTER_CONSUMER_KEY': '', 'EXTERNAL_URI_SCHEME': "https://", 'GOOGLE_CLIENT_ID': '', 'REDIS_PASSWORD': None, + 'DBX_APNS_CERT_FILE': None, } for setting_name, setting_val in DEFAULT_SETTINGS.iteritems():