api_docs: Prep work to document zilencer endpoints.

Until now we were not documenting bouncer's REST API endpoints.

We plan to document the newly introduced "remotes/push/e2ee/register"
and "remotes/push/e2ee/notify" endpoints.

This commit does the prep work for documenting bouncer endpoints:
* mark the older endpoints related to sending non-E2EE push
  notifications as "intentionally_undocumented" - we'll remove
  them in future.
* the remaining endpoints are marked pending-to-document with
  helpful comments.

(cherry picked from commit 062a736097)
This commit is contained in:
Prakhar Pratyush
2025-08-27 13:46:01 +05:30
committed by Tim Abbott
parent 7065f91699
commit 5cd52a2a14
3 changed files with 51 additions and 15 deletions

View File

@@ -30,6 +30,7 @@ from zerver.openapi.openapi import (
validate_schema, validate_schema,
) )
from zerver.tornado.views import get_events, get_events_backend from zerver.tornado.views import get_events, get_events_backend
from zilencer.auth import remote_server_dispatch
TEST_ENDPOINT = "/messages/{message_id}" TEST_ENDPOINT = "/messages/{message_id}"
TEST_METHOD = "patch" TEST_METHOD = "patch"
@@ -260,6 +261,18 @@ class OpenAPIArgumentsTest(ZulipTestCase):
# Zulip outgoing webhook payload # Zulip outgoing webhook payload
"/zulip-outgoing-webhook", "/zulip-outgoing-webhook",
"/jwt/fetch_api_key", "/jwt/fetch_api_key",
#### Bouncer endpoints
# Higher priority to document
"/remotes/push/e2ee/register",
"/remotes/push/e2ee/notify",
# Lower priority to document
"/remotes/server/register",
"/remotes/server/register/transfer",
"/remotes/server/register/verify_challenge",
"/remotes/server/deactivate",
"/remotes/server/analytics",
"/remotes/server/analytics/status",
"/remotes/server/billing",
} }
# Endpoints in the API documentation that don't use rest_dispatch # Endpoints in the API documentation that don't use rest_dispatch
@@ -530,8 +543,8 @@ so maybe we shouldn't include it in pending_endpoints.
with the arguments declared in our API documentation with the arguments declared in our API documentation
for every API endpoint in Zulip. for every API endpoint in Zulip.
First, we import the fancy-Django version of zproject/urls.py First, we import the fancy-Django version of zproject/urls.py and
by doing this, each typed_endpoint wrapper around each zilencer/urls.py. By doing this, each typed_endpoint wrapper around each
imported view function gets called to generate the wrapped imported view function gets called to generate the wrapped
view function and thus filling the global arguments_map variable. view function and thus filling the global arguments_map variable.
Basically, we're exploiting code execution during import. Basically, we're exploiting code execution during import.
@@ -546,15 +559,21 @@ so maybe we shouldn't include it in pending_endpoints.
in code. in code.
""" """
from zilencer import urls as zilencer_urlconf
from zproject import urls as urlconf from zproject import urls as urlconf
# We loop through all the API patterns, looking in particular # We loop through all the API patterns, looking in particular
# for those using the rest_dispatch decorator; we then parse # for those using the rest_dispatch or remote_server_dispatch decorator;
# its mapping of (HTTP_METHOD -> FUNCTION). # we then parse its mapping of (HTTP_METHOD -> FUNCTION).
for p in urlconf.v1_api_and_json_patterns + urlconf.v1_api_mobile_patterns: for p in (
urlconf.v1_api_and_json_patterns
+ urlconf.v1_api_mobile_patterns
+ zilencer_urlconf.v1_api_bouncer_patterns
):
methods_endpoints: dict[str, Any] = {} methods_endpoints: dict[str, Any] = {}
if p.callback is not rest_dispatch: if p.callback not in [rest_dispatch, remote_server_dispatch]:
# Endpoints not using rest_dispatch don't have extra data. # Endpoints not using rest_dispatch or remote_server_dispatch
# don't have extra data.
if str(p.pattern) in self.documented_post_only_endpoints: if str(p.pattern) in self.documented_post_only_endpoints:
methods_endpoints = dict(POST=p.callback) methods_endpoints = dict(POST=p.callback)
else: else:

View File

@@ -172,6 +172,7 @@ def remote_server_dispatch(request: HttpRequest, /, **kwargs: Any) -> HttpRespon
def remote_server_path( def remote_server_path(
route: str, route: str,
**handlers: Callable[Concatenate[HttpRequest, RemoteZulipServer, ParamT], HttpResponse], **handlers: Callable[Concatenate[HttpRequest, RemoteZulipServer, ParamT], HttpResponse]
| tuple[Callable[Concatenate[HttpRequest, RemoteZulipServer, ParamT], HttpResponse], set[str]],
) -> URLPattern: ) -> URLPattern:
return path(route, remote_server_dispatch, handlers) return path(route, remote_server_dispatch, handlers)

View File

@@ -24,16 +24,31 @@ from zilencer.views import (
i18n_urlpatterns: Any = [] i18n_urlpatterns: Any = []
# Zilencer views following the REST API style # Zilencer views following the REST API style
# The endpoints marked "intentionally_undocumented" are part of the older system
# for sending non-E2EE push notifications, and will be removed in the future.
push_bouncer_patterns = [ push_bouncer_patterns = [
remote_server_path("remotes/push/register", POST=register_remote_push_device), remote_server_path(
"remotes/push/register", POST=(register_remote_push_device, {"intentionally_undocumented"})
),
remote_server_path( remote_server_path(
"remotes/push/e2ee/register", POST=register_remote_push_device_for_e2ee_push_notification "remotes/push/e2ee/register", POST=register_remote_push_device_for_e2ee_push_notification
), ),
remote_server_path("remotes/push/unregister", POST=unregister_remote_push_device), remote_server_path(
remote_server_path("remotes/push/unregister/all", POST=unregister_all_remote_push_devices), "remotes/push/unregister",
remote_server_path("remotes/push/notify", POST=remote_server_notify_push), POST=(unregister_remote_push_device, {"intentionally_undocumented"}),
),
remote_server_path(
"remotes/push/unregister/all",
POST=(unregister_all_remote_push_devices, {"intentionally_undocumented"}),
),
remote_server_path(
"remotes/push/notify", POST=(remote_server_notify_push, {"intentionally_undocumented"})
),
remote_server_path("remotes/push/e2ee/notify", POST=remote_server_send_e2ee_push_notification), remote_server_path("remotes/push/e2ee/notify", POST=remote_server_send_e2ee_push_notification),
remote_server_path("remotes/push/test_notification", POST=remote_server_send_test_notification), remote_server_path(
"remotes/push/test_notification",
POST=(remote_server_send_test_notification, {"intentionally_undocumented"}),
),
# Push signup doesn't use the REST API, since there's no auth. # Push signup doesn't use the REST API, since there's no auth.
path("remotes/server/register", register_remote_server), path("remotes/server/register", register_remote_server),
path("remotes/server/register/transfer", transfer_remote_server_registration), path("remotes/server/register/transfer", transfer_remote_server_registration),
@@ -49,7 +64,8 @@ push_bouncer_patterns = [
billing_patterns = [remote_server_path("remotes/server/billing", POST=remote_realm_billing_entry)] billing_patterns = [remote_server_path("remotes/server/billing", POST=remote_realm_billing_entry)]
v1_api_bouncer_patterns = push_bouncer_patterns + billing_patterns
urlpatterns = [ urlpatterns = [
path("api/v1/", include(push_bouncer_patterns)), path("api/v1/", include(v1_api_bouncer_patterns)),
path("api/v1/", include(billing_patterns)),
] ]