diff --git a/.gitignore b/.gitignore index 871fdd6b53..4fef9ac094 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,7 @@ ## Config files for the dev environment /zproject/apns-dev.pem +/zproject/apns-dev-key.p8 /zproject/dev-secrets.conf /tools/conf.ini /tools/custom_provision diff --git a/docs/production/mobile-push-notifications.md b/docs/production/mobile-push-notifications.md index 504181b500..12514d6de7 100644 --- a/docs/production/mobile-push-notifications.md +++ b/docs/production/mobile-push-notifications.md @@ -213,13 +213,23 @@ push notifications through the new app is quite straightforward: [FCM push notifications](https://firebase.google.com/docs/cloud-messaging) key in the Google Developer console and set `android_gcm_api_key` in `/etc/zulip/zulip-secrets.conf` to that key. -- Register for a - [mobile push notification certificate][apple-docs] - from Apple's developer console. Set `APNS_SANDBOX=False` and - `APNS_CERT_FILE` to be the path of your APNS certificate file in - `/etc/zulip/settings.py`. + +- In Apple's developer console, register a [token][apple-doc-token] or + [certificate][apple-doc-cert] for sending push notifications. + Then in `/etc/zulip/settings.py`, set `APNS_SANDBOX=False`, and: + + - If using APNs [certificate-based authentication][apple-doc-cert], + set `APNS_CERT_FILE` to the path of your APNs certificate file. + + - If using APNs [token-based authentication][apple-doc-token], + set `APNS_TOKEN_KEY_FILE` to the path of your APNs token key file, + `APNS_TOKEN_KEY_ID` to the corresponding 10-character key ID, and + `APNS_TEAM_ID` to your 10-character Apple team ID. + - Set the `APNS_TOPIC` setting to the ID for your app (for the official Zulip apps, it's `org.zulip.Zulip`). + - Restart the Zulip server. -[apple-docs]: https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/APNSOverview.html +[apple-doc-cert]: https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/establishing_a_certificate-based_connection_to_apns +[apple-doc-token]: https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/establishing_a_token-based_connection_to_apns diff --git a/zerver/lib/push_notifications.py b/zerver/lib/push_notifications.py index 51cb43f652..9f7acee4b4 100644 --- a/zerver/lib/push_notifications.py +++ b/zerver/lib/push_notifications.py @@ -145,13 +145,17 @@ class APNsContext: loop: asyncio.AbstractEventLoop +def apns_enabled() -> bool: + return settings.APNS_TOKEN_KEY_FILE is not None or settings.APNS_CERT_FILE is not None + + @lru_cache(maxsize=None) def get_apns_context() -> Optional[APNsContext]: # We lazily do this import as part of optimizing Zulip's base # import time. import aioapns - if settings.APNS_CERT_FILE is None: # nocoverage + if not apns_enabled(): # nocoverage return None # NB if called concurrently, this will make excess connections. @@ -171,6 +175,9 @@ def get_apns_context() -> Optional[APNsContext]: async def make_apns() -> aioapns.APNs: return aioapns.APNs( client_cert=settings.APNS_CERT_FILE, + key=settings.APNS_TOKEN_KEY_FILE, + key_id=settings.APNS_TOKEN_KEY_ID, + team_id=settings.APNS_TEAM_ID, topic=settings.APNS_TOPIC, max_connection_attempts=APNS_MAX_RETRIES, use_sandbox=settings.APNS_SANDBOX, @@ -181,10 +188,6 @@ def get_apns_context() -> Optional[APNsContext]: return APNsContext(apns=apns, loop=loop) -def apns_enabled() -> bool: - return settings.APNS_CERT_FILE is not None - - def modernize_apns_payload(data: Mapping[str, Any]) -> Mapping[str, Any]: """Take a payload in an unknown Zulip version's format, and return in current format.""" # TODO this isn't super robust as is -- if a buggy remote server diff --git a/zproject/default_settings.py b/zproject/default_settings.py index aaad3c05eb..818e313a16 100644 --- a/zproject/default_settings.py +++ b/zproject/default_settings.py @@ -401,6 +401,9 @@ POST_MIGRATION_CACHE_FLUSHING = False # rebuilding the mobile app with a different push notifications # server. APNS_CERT_FILE: Optional[str] = None +APNS_TOKEN_KEY_FILE: Optional[str] = None +APNS_TOKEN_KEY_ID = get_secret("apns_token_key_id", development_only=True) +APNS_TEAM_ID = get_secret("apns_team_id", development_only=True) APNS_SANDBOX = True APNS_TOPIC = "org.zulip.Zulip" # ZULIP_IOS_APP_ID is obsolete diff --git a/zproject/dev_settings.py b/zproject/dev_settings.py index b039f032b2..6e1cbd2c10 100644 --- a/zproject/dev_settings.py +++ b/zproject/dev_settings.py @@ -100,11 +100,14 @@ USING_PGROONGA = True # Flush cache after migration. POST_MIGRATION_CACHE_FLUSHING = True -# If a sandbox APNs cert is provided, use it. -# To create such a cert, see instructions at: +# If a sandbox APNs key or cert is provided, use it. +# To create such a key or cert, see instructions at: # https://github.com/zulip/zulip-mobile/blob/main/docs/howto/push-notifications.md#ios +_candidate_apns_token_key_file = "zproject/apns-dev-key.p8" _candidate_apns_cert_file = "zproject/apns-dev.pem" -if os.path.isfile(_candidate_apns_cert_file): +if os.path.isfile(_candidate_apns_token_key_file): + APNS_TOKEN_KEY_FILE = _candidate_apns_token_key_file +elif os.path.isfile(_candidate_apns_cert_file): APNS_CERT_FILE = _candidate_apns_cert_file # Don't require anything about password strength in development diff --git a/zproject/test_extra_settings.py b/zproject/test_extra_settings.py index 877541584e..0b4924d820 100644 --- a/zproject/test_extra_settings.py +++ b/zproject/test_extra_settings.py @@ -156,7 +156,8 @@ INLINE_URL_EMBED_PREVIEW = False HOME_NOT_LOGGED_IN = "/login/" LOGIN_URL = "/accounts/login/" -# If dev_settings.py found a cert file to use here, ignore it. +# If dev_settings.py found a key or cert file to use here, ignore it. +APNS_TOKEN_KEY_FILE: Optional[str] = None APNS_CERT_FILE: Optional[str] = None # By default will not send emails when login occurs.