event_schema: Add schema check for realm/deactivated event.

This add the schema checker, openapi schema, and also a test for
realm/deactivated event.

With several block comments by tabbott explaining the logic behind our
behavior here.

Part of #17568.
This commit is contained in:
shanukun
2021-03-14 00:30:05 +05:30
committed by Tim Abbott
parent daa9bbba61
commit cfe0fa3788
7 changed files with 87 additions and 0 deletions

View File

@@ -216,6 +216,7 @@ exports.fixtures = {
realm__deactivated: {
type: "realm",
op: "deactivated",
realm_id: 2,
},
realm__update__bot_creation_policy: {

View File

@@ -249,6 +249,13 @@ export function dispatch_normal_event(event) {
page_params.realm_night_logo_source = event.data.night_logo_source;
realm_logo.rerender();
} else if (event.op === "deactivated") {
// This handler is likely unnecessary, in that if we
// did nothing here, we'd reload and end up at the
// same place when we attempt the next `GET /events`
// and get an error. Some clients will do that even
// with this code, if they didn't have an active
// longpoll waiting at the moment the realm was
// deactivated.
window.location.href = "/accounts/deactivated/";
}

View File

@@ -1010,6 +1010,12 @@ def do_deactivate_realm(realm: Realm, acting_user: Optional[UserProfile] = None)
# notice when they try to log in.
delete_user_sessions(user)
# This event will only ever be received by clients with an active
# longpoll connection, because by this point clients will be
# unable to authenticate again to their event queue (triggering an
# immediate reload into the page explaining the realm was
# deactivated). So the purpose of sending this is to flush all
# active longpoll connections for the realm.
event = dict(type="realm", op="deactivated", realm_id=realm.id)
send_event(realm, event, active_user_ids(realm.id))

View File

@@ -458,6 +458,14 @@ reaction_remove_event = event_dict_type(
)
check_reaction_remove = make_checker(reaction_remove_event)
realm_deactivated_event = event_dict_type(
required_keys=[
("type", Equals("realm")),
("op", Equals("deactivated")),
("realm_id", int),
]
)
check_realm_deactivated = make_checker(realm_deactivated_event)
bot_services_outgoing_type = DictType(
required_keys=[

View File

@@ -742,6 +742,15 @@ def apply_event(
if key == "authentication_methods":
state["realm_password_auth_enabled"] = value["Email"] or value["LDAP"]
state["realm_email_auth_enabled"] = value["Email"]
elif event["op"] == "deactivated":
# The realm has just been deactivated. If our request had
# arrived a moment later, we'd have rendered the
# deactivation UI; if it'd been a moment sooner, we've
# have rendered the app and then immediately got this
# event (or actually, more likely, an auth error on GET
# /events) and immediately reloaded into the same
# deactivation UI. Passing achieves the same result.
pass
else:
raise AssertionError("Unexpected event type {type}/{op}".format(**event))
elif event["type"] == "subscription":

View File

@@ -2773,6 +2773,47 @@ paths:
"value": false,
"id": 0,
}
- type: object
additionalProperties: false
description: |
Event sent to all users in a Zulip organization when the
organization (realm) is deactivated. Its main purpose is to
flush active longpolling connections so clients can immediately
show the organization as deactivated.
Clients cannot rely on receiving this event, because they will
no longer be able to authenticate to the Zulip API due to the
deactivation, and thus can miss it if they did not have an active
longpolling connection at the moment of deactivation.
Correct handling of realm deactivations requires that clients
parse authentication errors from GET /events; if that is done
correctly, the client can ignore this event type and rely on its
handling of the `GET /events` request it will do immediately
after processing this batch of events.
properties:
id:
$ref: "#/components/schemas/EventIdSchema"
type:
allOf:
- $ref: "#/components/schemas/EventTypeSchema"
- enum:
- realm
op:
type: string
enum:
- deactivated
realm_id:
type: integer
description: |
The ID of the deactivated realm.
example:
{
"type": "realm",
"op": "deactivated",
"realm_id": 2,
"id": 0,
}
- type: object
additionalProperties: false
description: |

View File

@@ -52,6 +52,7 @@ from zerver.lib.actions import (
do_create_default_stream_group,
do_create_multiuse_invite_link,
do_create_user,
do_deactivate_realm,
do_deactivate_stream,
do_deactivate_user,
do_delete_messages,
@@ -114,6 +115,7 @@ from zerver.lib.event_schema import (
check_realm_bot_delete,
check_realm_bot_remove,
check_realm_bot_update,
check_realm_deactivated,
check_realm_domains_add,
check_realm_domains_change,
check_realm_domains_remove,
@@ -1477,6 +1479,19 @@ class NormalActionsTest(BaseAction):
events = self.verify_action(action, num_events=2)
check_realm_bot_add("events[1]", events[1])
def test_do_deactivate_realm(self) -> None:
realm = self.user_profile.realm
action = lambda: do_deactivate_realm(realm)
# We delete sessions of all active users when a realm is
# deactivated, and redirect them to a deactivated page in
# order to inform that realm/organization has been
# deactivated. state_change_expected is False is kinda
# correct because were one to somehow compute page_params (as
# this test does), but that's not actually possible.
events = self.verify_action(action, state_change_expected=False)
check_realm_deactivated("events[0]", events[0])
def test_do_mark_hotspot_as_read(self) -> None:
self.user_profile.tutorial_status = UserProfile.TUTORIAL_WAITING
self.user_profile.save(update_fields=["tutorial_status"])