Files
zulip/zerver/management/commands/register_server.py
Mateusz Mandera 4a93149435 settings: Rework how push notifications service is configured.
Instead of the PUSH_NOTIFICATIONS_BOUNCER_URL and
SUBMIT_USAGE_STATISTICS settings, we want servers to configure
individual ZULIP_SERVICE_* settings, while maintaining backward
compatibility with the old settings. Thus, if all the new
ZULIP_SERVICE_* are at their default False value, but the legacy
settings are activated, they need to be translated in computed_settings
to the modern way.
2024-07-17 17:14:06 -07:00

162 lines
6.3 KiB
Python

import os
import subprocess
from argparse import ArgumentParser
from typing import Any
import requests
from django.conf import settings
from django.core.management.base import CommandError
from django.utils.crypto import get_random_string
from requests.models import Response
from typing_extensions import override
from zerver.lib.management import ZulipBaseCommand, check_config
from zerver.lib.remote_server import (
PushBouncerSession,
send_json_to_push_bouncer,
send_server_data_to_push_bouncer,
)
if settings.DEVELOPMENT:
SECRETS_FILENAME = "zproject/dev-secrets.conf"
else:
SECRETS_FILENAME = "/etc/zulip/zulip-secrets.conf"
class Command(ZulipBaseCommand):
help = """Register a remote Zulip server for push notifications."""
@override
def add_arguments(self, parser: ArgumentParser) -> None:
parser.add_argument(
"--agree_to_terms_of_service",
action="store_true",
help="Agree to the Zulipchat Terms of Service: https://zulip.com/policies/terms.",
)
action = parser.add_mutually_exclusive_group()
action.add_argument(
"--rotate-key",
action="store_true",
help="Automatically rotate your server's zulip_org_key",
)
action.add_argument(
"--deactivate",
action="store_true",
help="Deregister the server; this will stop mobile push notifications",
)
@override
def handle(self, *args: Any, **options: Any) -> None:
if not settings.DEVELOPMENT:
check_config()
if not settings.ZULIP_ORG_ID:
raise CommandError(
"Missing zulip_org_id; run scripts/setup/generate_secrets.py to generate."
)
if not settings.ZULIP_ORG_KEY:
raise CommandError(
"Missing zulip_org_key; run scripts/setup/generate_secrets.py to generate."
)
if not settings.ZULIP_SERVICES_URL:
raise CommandError(
"ZULIP_SERVICES_URL is not set; was the default incorrectly overridden in /etc/zulip/settings.py?"
)
if not settings.ZULIP_SERVICE_PUSH_NOTIFICATIONS:
raise CommandError(
"Please set ZULIP_SERVICE_PUSH_NOTIFICATIONS to True in /etc/zulip/settings.py"
)
if options["deactivate"]:
send_json_to_push_bouncer("POST", "server/deactivate", {})
print("Mobile Push Notification Service registration successfully deactivated!")
return
request = {
"zulip_org_id": settings.ZULIP_ORG_ID,
"zulip_org_key": settings.ZULIP_ORG_KEY,
"hostname": settings.EXTERNAL_HOST,
"contact_email": settings.ZULIP_ADMINISTRATOR,
}
if options["rotate_key"]:
if not os.access(SECRETS_FILENAME, os.W_OK):
raise CommandError(f"{SECRETS_FILENAME} is not writable by the current user.")
request["new_org_key"] = get_random_string(64)
print(
"This command registers your server for the Mobile Push Notifications Service.\n"
"Doing so will share basic metadata with the service's maintainers:\n\n"
f"* This server's configured hostname: {request['hostname']}\n"
f"* This server's configured contact email address: {request['contact_email']}\n"
"* Metadata about each organization hosted by the server; see:\n\n"
" <https://zulip.readthedocs.io/en/latest/production/mobile-push-notifications.html#uploading-basic-metadata>\n\n"
"Use of this service is governed by the Zulip Terms of Service:\n\n"
" <https://zulip.com/policies/terms>\n"
)
if not options["agree_to_terms_of_service"] and not options["rotate_key"]:
tos_prompt = input(
"Do you want to agree to the Zulip Terms of Service and proceed? [Y/n] "
)
print()
if not (
tos_prompt.lower() == "y" or tos_prompt.lower() == "" or tos_prompt.lower() == "yes"
):
# Exit without registering; no need to print anything
# special, as the "n" reply to the query is clear
# enough about what happened.
return
response = self._request_push_notification_bouncer_url(
"/api/v1/remotes/server/register", request
)
# Makes sure that we have a current state of user count when first
# logging in after the RemoteRealm flow.
send_server_data_to_push_bouncer(consider_usage_statistics=False)
if response.json()["created"]:
print(
"Your server is now registered for the Mobile Push Notification Service!\n"
"Return to the documentation for next steps."
)
else:
if options["rotate_key"]:
print(f"Success! Updating {SECRETS_FILENAME} with the new key...")
subprocess.check_call(
[
"crudini",
"--inplace",
"--set",
SECRETS_FILENAME,
"secrets",
"zulip_org_key",
request["new_org_key"],
]
)
print("Mobile Push Notification Service registration successfully updated!")
def _request_push_notification_bouncer_url(self, url: str, params: dict[str, Any]) -> Response:
assert settings.ZULIP_SERVICES_URL is not None
registration_url = settings.ZULIP_SERVICES_URL + url
session = PushBouncerSession()
try:
response = session.post(registration_url, data=params)
except requests.RequestException:
raise CommandError(
"Network error connecting to push notifications service "
f"({settings.ZULIP_SERVICES_URL})",
)
try:
response.raise_for_status()
except requests.HTTPError as e:
# Report nice errors from the Zulip API if possible.
try:
content_dict = response.json()
except Exception:
raise e
raise CommandError("Error: " + content_dict["msg"])
return response