diff --git a/templates/zerver/api/rest-error-handling.md b/templates/zerver/api/rest-error-handling.md index b2e93b30b4..7ea76c5250 100644 --- a/templates/zerver/api/rest-error-handling.md +++ b/templates/zerver/api/rest-error-handling.md @@ -42,3 +42,9 @@ for a query: A typical failed json response for when user's account is deactivated: {generate_code_example|/rest-error-handling:post|fixture(403_0)} + +## Realm deactivated + +A typical failed json response for when user's organization is deactivated: + +{generate_code_example|/rest-error-handling:post|fixture(403_1)} diff --git a/tools/test-api b/tools/test-api index 2b831e1eef..c2cb4d84f1 100755 --- a/tools/test-api +++ b/tools/test-api @@ -31,13 +31,20 @@ with test_server_running( ): # Zerver imports should happen after `django.setup()` is run # by the test_server_running decorator. - from zerver.lib.actions import change_user_is_active, do_create_user, do_reactivate_user + from zerver.lib.actions import ( + change_user_is_active, + do_create_user, + do_deactivate_realm, + do_reactivate_realm, + do_reactivate_user, + ) from zerver.lib.test_helpers import reset_emails_in_zulip_realm from zerver.lib.users import get_api_key from zerver.models import get_realm, get_user from zerver.openapi.javascript_examples import test_js_bindings from zerver.openapi.python_examples import ( test_invalid_api_key, + test_realm_deactivated, test_the_api, test_user_account_deactivated, ) @@ -122,5 +129,16 @@ with test_server_running( # reactivate user to avoid any side-effects in other tests. do_reactivate_user(guest_user, acting_user=None) + # Test realm deactivated error + do_deactivate_realm(guest_user.realm, acting_user=None) + + client = Client( + email=email, + api_key=api_key, + site=site, + ) + test_realm_deactivated(client) + do_reactivate_realm(guest_user.realm) + print("API tests passed!") diff --git a/zerver/decorator.py b/zerver/decorator.py index f208e48a40..6e4b139884 100644 --- a/zerver/decorator.py +++ b/zerver/decorator.py @@ -32,6 +32,7 @@ from zerver.lib.exceptions import ( OrganizationAdministratorRequired, OrganizationMemberRequired, OrganizationOwnerRequired, + RealmDeactivatedError, UnsupportedWebhookEventType, UserDeactivatedError, ) @@ -269,7 +270,7 @@ def validate_api_key( def validate_account_and_subdomain(request: HttpRequest, user_profile: UserProfile) -> None: if user_profile.realm.deactivated: - raise JsonableError(_("This organization has been deactivated")) + raise RealmDeactivatedError() if not user_profile.is_active: raise UserDeactivatedError() diff --git a/zerver/lib/exceptions.py b/zerver/lib/exceptions.py index 01a225dcea..d2149aa79b 100644 --- a/zerver/lib/exceptions.py +++ b/zerver/lib/exceptions.py @@ -51,6 +51,7 @@ class ErrorCode(AbstractEnum): NONEXISTENT_SUBDOMAIN = () RATE_LIMIT_HIT = () USER_DEACTIVATED = () + REALM_DEACTIVATED = () class JsonableError(Exception): @@ -284,6 +285,18 @@ class UserDeactivatedError(JsonableError): return _("Account is deactivated") +class RealmDeactivatedError(JsonableError): + code: ErrorCode = ErrorCode.REALM_DEACTIVATED + http_status_code = 403 + + def __init__(self) -> None: + pass + + @staticmethod + def msg_format() -> str: + return _("This organization has been deactivated") + + class MarkdownRenderingException(Exception): pass diff --git a/zerver/openapi/python_examples.py b/zerver/openapi/python_examples.py index 9f2734518e..36e3325860 100644 --- a/zerver/openapi/python_examples.py +++ b/zerver/openapi/python_examples.py @@ -1233,6 +1233,15 @@ def test_user_account_deactivated(client: Client) -> None: validate_against_openapi_schema(result, "/rest-error-handling", "post", "403_0") +def test_realm_deactivated(client: Client) -> None: + request = { + "content": "**foo**", + } + result = client.render_message(request) + + validate_against_openapi_schema(result, "/rest-error-handling", "post", "403_1") + + def test_invalid_stream_error(client: Client) -> None: result = client.get_stream_id("nonexistent") diff --git a/zerver/openapi/zulip.yaml b/zerver/openapi/zulip.yaml index 0ecad620e5..40470f72b4 100644 --- a/zerver/openapi/zulip.yaml +++ b/zerver/openapi/zulip.yaml @@ -9274,6 +9274,7 @@ paths: schema: oneOf: - $ref: "#/components/schemas/UserDeactivatedError" + - $ref: "#/components/schemas/RealmDeactivatedError" /zulip-outgoing-webhook: post: operationId: zulip_outgoing_webhooks @@ -10872,6 +10873,15 @@ components: "msg": "Account is deactivated", "result": "error", } + RealmDeactivatedError: + allOf: + - $ref: "#/components/schemas/CodedError" + - example: + { + "code": "REALM_DEACTIVATED", + "msg": "This organization is deactivated", + "result": "error", + } ################### # Shared responses diff --git a/zerver/tests/test_decorators.py b/zerver/tests/test_decorators.py index 4106a153d8..a12914bb78 100644 --- a/zerver/tests/test_decorators.py +++ b/zerver/tests/test_decorators.py @@ -1066,7 +1066,9 @@ class DeactivatedRealmTest(ZulipTestCase): "to": self.example_email("othello"), }, ) - self.assert_json_error_contains(result, "has been deactivated", status_code=400) + self.assert_json_error_contains( + result, "This organization has been deactivated", status_code=403 + ) result = self.api_post( self.example_user("hamlet"), @@ -1078,7 +1080,9 @@ class DeactivatedRealmTest(ZulipTestCase): "to": self.example_email("othello"), }, ) - self.assert_json_error_contains(result, "has been deactivated", status_code=401) + self.assert_json_error_contains( + result, "This organization has been deactivated", status_code=401 + ) def test_fetch_api_key_deactivated_realm(self) -> None: """ @@ -1094,7 +1098,9 @@ class DeactivatedRealmTest(ZulipTestCase): realm.deactivated = True realm.save() result = self.client_post("/json/fetch_api_key", {"password": test_password}) - self.assert_json_error_contains(result, "has been deactivated", status_code=400) + self.assert_json_error_contains( + result, "This organization has been deactivated", status_code=403 + ) def test_webhook_deactivated_realm(self) -> None: """ @@ -1107,7 +1113,9 @@ class DeactivatedRealmTest(ZulipTestCase): url = f"/api/v1/external/jira?api_key={api_key}&stream=jira_custom" data = self.webhook_fixture_data("jira", "created_v2") result = self.client_post(url, data, content_type="application/json") - self.assert_json_error_contains(result, "has been deactivated", status_code=400) + self.assert_json_error_contains( + result, "This organization has been deactivated", status_code=403 + ) class LoginRequiredTest(ZulipTestCase): @@ -1659,7 +1667,9 @@ class TestAuthenticatedJsonPostViewDecorator(ZulipTestCase): user_profile.realm.deactivated = True user_profile.realm.save() self.assert_json_error_contains( - self._do_test(user_profile), "This organization has been deactivated" + self._do_test(user_profile), + "This organization has been deactivated", + status_code=403, ) do_reactivate_realm(user_profile.realm)