api: Add REALM_DEACTIVATED error code.

In `validate_account_and_subdomain` we check
if user's realm is not deactivated. In case
of failure of this check, we raise our standard
JsonableError. While this works well in most
cases but it creates difficulties in handling
of users with deactivated realms for non-browser
clients.

So we register a new REALM_DEACTIVATED error
code so that clients can distinguish if error
is because of deactivated account. Following
these changes `validate_account_and_subdomain`
raises RealmDeactivatedError if user's realm
is deactivated.

This error is also documented in
`/api/rest-error-handling`.

Testing: I have mostly relied on automated
backend tests to test this.

Fixes #17763.
This commit is contained in:
m-e-l-u-h-a-n 2021-03-31 16:44:08 +05:30 committed by Tim Abbott
parent 2eeb82edba
commit aea31eb31f
7 changed files with 74 additions and 7 deletions

View File

@ -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)}

View File

@ -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!")

View File

@ -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()

View File

@ -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

View File

@ -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")

View File

@ -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

View File

@ -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)