users: Return true for admins for is_moderator.

This change is for consistency with how is_admin works.

API design discussion at https://chat.zulip.org/#narrow/channel/378-api-design/topic/Should.20is_moderator.20have.20admins
This commit is contained in:
Shubham Padia
2025-04-17 12:43:07 +00:00
committed by Tim Abbott
parent 50fbaa4da6
commit b8ba174455
18 changed files with 58 additions and 40 deletions

View File

@@ -1438,7 +1438,6 @@ def check_update_message(
# and the time limit for editing topics is passed, raise an error.
if (
user_profile.realm.move_messages_within_stream_limit_seconds is not None
and not user_profile.is_realm_admin
and not user_profile.is_moderator
):
deadline_seconds = (
@@ -1504,7 +1503,6 @@ def check_update_message(
if (
user_profile.realm.move_messages_between_streams_limit_seconds is not None
and not user_profile.is_realm_admin
and not user_profile.is_moderator
):
deadline_seconds = (
@@ -1518,7 +1516,6 @@ def check_update_message(
if (
propagate_mode == "change_all"
and not user_profile.is_realm_admin
and not user_profile.is_moderator
and message_edit_request.is_message_moved
and not message_edit_request.topic_resolved

View File

@@ -75,6 +75,7 @@ from zerver.lib.users import (
get_data_for_inaccessible_user,
get_users_for_api,
is_administrator_role,
is_moderator_role,
max_message_id_for_user,
)
from zerver.lib.utils import optional_bytes_to_mib
@@ -1096,7 +1097,7 @@ def apply_event(
if "role" in person:
state["is_admin"] = is_administrator_role(person["role"])
state["is_owner"] = person["role"] == UserProfile.ROLE_REALM_OWNER
state["is_moderator"] = person["role"] == UserProfile.ROLE_MODERATOR
state["is_moderator"] = is_moderator_role(person["role"])
state["is_guest"] = person["role"] == UserProfile.ROLE_GUEST
# Recompute properties based on is_admin/is_guest
state["can_create_private_streams"] = user_profile.can_create_private_streams()

View File

@@ -1234,10 +1234,8 @@ def check_user_has_permission_by_role(
if system_group_name == SystemGroups.ADMINISTRATORS:
return user.is_realm_admin
# is_moderator returns False for realm admins and
# owners.
if system_group_name == SystemGroups.MODERATORS:
return user.is_realm_admin or user.is_moderator
return user.is_moderator
# Handle full members case.
return user.role != UserProfile.ROLE_MEMBER or not user.is_provisional_member

View File

@@ -216,6 +216,10 @@ def is_administrator_role(role: int) -> bool:
return role in {UserProfile.ROLE_REALM_ADMINISTRATOR, UserProfile.ROLE_REALM_OWNER}
def is_moderator_role(role: int) -> bool:
return is_administrator_role(role) or role == UserProfile.ROLE_MODERATOR
def bulk_get_cross_realm_bots() -> dict[str, UserProfile]:
emails = list(settings.CROSS_REALM_BOT_EMAILS)

View File

@@ -776,7 +776,7 @@ class UserProfile(AbstractBaseUser, PermissionsMixin, UserBaseSettings):
@property
def is_moderator(self) -> bool:
return self.role == UserProfile.ROLE_MODERATOR
return self.is_realm_admin or self.role == UserProfile.ROLE_MODERATOR
@is_moderator.setter
def is_moderator(self, value: bool) -> None:

View File

@@ -19208,7 +19208,7 @@ paths:
description: |
Present if `realm_user` is present in `fetch_event_types`.
Whether the current user is an [organization administrator](/api/roles-and-permissions).
Whether the current user is at least an [organization administrator](/api/roles-and-permissions).
is_owner:
type: boolean
description: |
@@ -19222,9 +19222,12 @@ paths:
description: |
Present if `realm_user` is present in `fetch_event_types`.
Whether the current user is an [organization moderator](/api/roles-and-permissions).
Whether the current user is at least an [organization moderator](/api/roles-and-permissions).
**Changes**: New in Zulip 4.0 (feature level 60).
**Changes**: Prior to Zulip 11.0 (feature level 380), this was only true
for users whose role was exactly the moderator role.
New in Zulip 4.0 (feature level 60).
is_guest:
type: boolean
description: |

View File

@@ -5628,10 +5628,6 @@ class SubscriptionAPITest(ZulipTestCase):
realm, "can_add_subscribers_group", moderators_group, acting_user=None
)
# Moderators, Admins and owners are always full members.
do_change_user_role(self.test_user, UserProfile.ROLE_MODERATOR, acting_user=None)
self.assertFalse(self.test_user.is_provisional_member)
do_change_user_role(self.test_user, UserProfile.ROLE_MEMBER, acting_user=None)
# Make sure that we are checking the permission with a full member,
# as full member is the user just below moderator in the role hierarchy.
@@ -5694,6 +5690,15 @@ class SubscriptionAPITest(ZulipTestCase):
)
self.assert_json_error(result, "Insufficient permission")
# Moderators, Admins and owners are always full members.
self.assertTrue(user_profile.is_provisional_member)
do_change_user_role(self.test_user, UserProfile.ROLE_MODERATOR, acting_user=None)
self.assertFalse(self.test_user.is_provisional_member)
do_change_user_role(self.test_user, UserProfile.ROLE_REALM_ADMINISTRATOR, acting_user=None)
self.assertFalse(self.test_user.is_provisional_member)
do_change_user_role(self.test_user, UserProfile.ROLE_REALM_OWNER, acting_user=None)
self.assertFalse(self.test_user.is_provisional_member)
do_set_realm_property(realm, "waiting_period_threshold", 0, acting_user=None)
self.subscribe_via_post(
self.test_user, ["stream2"], {"principals": orjson.dumps([invitee_user_id]).decode()}

View File

@@ -116,7 +116,7 @@ class PermissionTest(ZulipTestCase):
self.assertEqual(user_profile.role, UserProfile.ROLE_REALM_ADMINISTRATOR)
user_profile.is_moderator = False
self.assertEqual(user_profile.is_moderator, False)
self.assertEqual(user_profile.is_moderator, True)
self.assertEqual(user_profile.role, UserProfile.ROLE_REALM_ADMINISTRATOR)
user_profile.is_realm_admin = False
@@ -593,21 +593,16 @@ class PermissionTest(ZulipTestCase):
user_profile.is_realm_admin
and not user_profile.is_guest
and not user_profile.is_realm_owner
and not user_profile.is_moderator
)
elif role == UserProfile.ROLE_REALM_OWNER:
return (
user_profile.is_realm_owner
and user_profile.is_realm_admin
and not user_profile.is_moderator
and not user_profile.is_guest
)
elif role == UserProfile.ROLE_MODERATOR:
return (
user_profile.is_moderator
and not user_profile.is_realm_owner
and not user_profile.is_realm_admin
and not user_profile.is_guest
return user_profile.is_moderator or (
user_profile.is_realm_admin and not user_profile.is_guest
)
if role == UserProfile.ROLE_MEMBER:

View File

@@ -58,10 +58,8 @@ def add_dev_login_context(realm: Realm | None, context: dict[str, Any]) -> None:
context["direct_owners"] = sort([u for u in users if u.is_realm_owner])
context["direct_admins"] = sort([u for u in users if u.is_realm_admin and not u.is_realm_owner])
context["guest_users"] = sort([u for u in users if u.is_guest])
context["direct_moderators"] = sort([u for u in users if u.is_moderator])
context["direct_users"] = sort(
[u for u in users if not (u.is_realm_admin or u.is_guest or u.is_moderator)]
)
context["direct_moderators"] = sort([u for u in users if u.role == UserProfile.ROLE_MODERATOR])
context["direct_users"] = sort([u for u in users if not (u.is_guest or u.is_moderator)])
@csrf_exempt