Maintain two APNS connections and send correct notifications to each

Now we have 2 different Zulip apps out there, and they are signed with
two certs: Zulip and Dropbox. The Dropbox-signed apps are going to need
to be sent APNS notifications from the appropriate APNS connection

(imported from commit 6db50c5811847db4f08e5c997c7bbb4b46cfc462)
This commit is contained in:
Leo Franchi
2015-02-09 23:08:47 -08:00
parent 1c22b48bb1
commit d865732e0d
4 changed files with 45 additions and 21 deletions

View File

@@ -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():

View File

@@ -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):

View File

@@ -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'

View File

@@ -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():