mirror of https://github.com/zulip/zulip.git
invite: Add new setting for "Who can create multiuse invite links".
This commit does the backend changes required for adding a realm setting based on groups permission model and does the API changes required for the new setting `Who can create multiuse invite link`.
This commit is contained in:
parent
54c7cbaf1c
commit
f67cef8885
|
@ -20,6 +20,19 @@ format used by the Zulip server that they are interacting with.
|
|||
|
||||
## Changes in Zulip 8.0
|
||||
|
||||
**Feature level 209**
|
||||
|
||||
* `PATCH /realm`, [`POST /register`](/api/register-queue),
|
||||
[`GET /events`](/api/get-events): Added `create_multiuse_invite_group`
|
||||
realm setting, which is the ID of the user group whose members can
|
||||
create [reusable invitation links](/help/invite-new-users#create-a-reusable-invitation-link)
|
||||
to an organization. Previously, only admin users could create these
|
||||
links.
|
||||
|
||||
* `POST /invites/multiuse`: Non-admin users can now use this endpoint
|
||||
to create reusable invitation links. Previously, this endpoint was
|
||||
restricted to admin users only.
|
||||
|
||||
**Feature level 208**
|
||||
|
||||
* [`POST /users/me/subscriptions`](/api/subscribe),
|
||||
|
|
|
@ -16,7 +16,10 @@ from zerver.actions.realm_settings import (
|
|||
from zerver.lib.bulk_create import create_users
|
||||
from zerver.lib.server_initialization import create_internal_realm, server_initialized
|
||||
from zerver.lib.streams import ensure_stream, get_signups_stream
|
||||
from zerver.lib.user_groups import create_system_user_groups_for_realm
|
||||
from zerver.lib.user_groups import (
|
||||
create_system_user_groups_for_realm,
|
||||
get_role_based_system_groups_dict,
|
||||
)
|
||||
from zerver.models import (
|
||||
DefaultStream,
|
||||
PreregistrationRealm,
|
||||
|
@ -115,6 +118,17 @@ def set_realm_permissions_based_on_org_type(realm: Realm) -> None:
|
|||
realm.move_messages_between_streams_policy = Realm.POLICY_MODERATORS_ONLY
|
||||
|
||||
|
||||
@transaction.atomic(savepoint=False)
|
||||
def set_default_for_realm_permission_group_settings(realm: Realm) -> None:
|
||||
system_groups_dict = get_role_based_system_groups_dict(realm)
|
||||
|
||||
for setting_name, permissions_configuration in Realm.REALM_PERMISSION_GROUP_SETTINGS.items():
|
||||
group_name = permissions_configuration.default_group_name
|
||||
setattr(realm, setting_name, system_groups_dict[group_name])
|
||||
|
||||
realm.save(update_fields=list(Realm.REALM_PERMISSION_GROUP_SETTINGS.keys()))
|
||||
|
||||
|
||||
def setup_realm_internal_bots(realm: Realm) -> None:
|
||||
"""Create this realm's internal bots.
|
||||
|
||||
|
@ -204,6 +218,12 @@ def do_create_realm(
|
|||
)
|
||||
|
||||
set_realm_permissions_based_on_org_type(realm)
|
||||
|
||||
# For now a dummy value of -1 is given to groups fields which
|
||||
# is changed later before the transaction is committed.
|
||||
for permissions_configuration in Realm.REALM_PERMISSION_GROUP_SETTINGS.values():
|
||||
setattr(realm, permissions_configuration.id_field_name, -1)
|
||||
|
||||
realm.save()
|
||||
|
||||
RealmAuditLog.objects.create(
|
||||
|
@ -230,6 +250,7 @@ def do_create_realm(
|
|||
)
|
||||
|
||||
create_system_user_groups_for_realm(realm)
|
||||
set_default_for_realm_permission_group_settings(realm)
|
||||
|
||||
# We create realms with all authentications methods enabled by default.
|
||||
RealmAuthenticationMethod.objects.bulk_create(
|
||||
|
|
|
@ -31,6 +31,7 @@ from zerver.models import (
|
|||
ScheduledEmail,
|
||||
Stream,
|
||||
Subscription,
|
||||
UserGroup,
|
||||
UserProfile,
|
||||
active_user_ids,
|
||||
get_realm,
|
||||
|
@ -101,6 +102,42 @@ def do_set_realm_property(
|
|||
update_users_in_full_members_system_group(realm, acting_user=acting_user)
|
||||
|
||||
|
||||
@transaction.atomic(durable=True)
|
||||
def do_change_realm_permission_group_setting(
|
||||
realm: Realm, setting_name: str, user_group: UserGroup, *, acting_user: Optional[UserProfile]
|
||||
) -> None:
|
||||
"""Takes in a realm object, the name of an attribute to update, the
|
||||
user_group to update and and the user who initiated the update.
|
||||
"""
|
||||
assert setting_name in Realm.REALM_PERMISSION_GROUP_SETTINGS
|
||||
old_user_group_id = getattr(realm, setting_name).id
|
||||
|
||||
setattr(realm, setting_name, user_group)
|
||||
realm.save(update_fields=[setting_name])
|
||||
|
||||
event = dict(
|
||||
type="realm",
|
||||
op="update_dict",
|
||||
property="default",
|
||||
data={setting_name: user_group.id},
|
||||
)
|
||||
|
||||
send_event_on_commit(realm, event, active_user_ids(realm.id))
|
||||
|
||||
event_time = timezone_now()
|
||||
RealmAuditLog.objects.create(
|
||||
realm=realm,
|
||||
event_type=RealmAuditLog.REALM_PROPERTY_CHANGED,
|
||||
event_time=event_time,
|
||||
acting_user=acting_user,
|
||||
extra_data={
|
||||
RealmAuditLog.OLD_VALUE: old_user_group_id,
|
||||
RealmAuditLog.NEW_VALUE: user_group.id,
|
||||
"property": setting_name,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def parse_and_set_setting_value_if_required(
|
||||
realm: Realm, setting_name: str, value: Union[int, str], *, acting_user: Optional[UserProfile]
|
||||
) -> Tuple[Optional[int], bool]:
|
||||
|
|
|
@ -955,6 +955,10 @@ night_logo_data = DictType(
|
|||
]
|
||||
)
|
||||
|
||||
group_setting_update_data_type = DictType(
|
||||
required_keys=[], optional_keys=[("create_multiuse_invite_group", int)]
|
||||
)
|
||||
|
||||
update_dict_data = UnionType(
|
||||
[
|
||||
allow_message_editing_data,
|
||||
|
@ -964,6 +968,7 @@ update_dict_data = UnionType(
|
|||
logo_data,
|
||||
message_content_edit_limit_seconds_data,
|
||||
night_logo_data,
|
||||
group_setting_update_data_type,
|
||||
]
|
||||
)
|
||||
|
||||
|
@ -996,6 +1001,10 @@ def check_realm_update_dict(
|
|||
sub_type = edit_topic_policy_data
|
||||
elif "authentication_methods" in event["data"]:
|
||||
sub_type = authentication_data
|
||||
elif any(
|
||||
setting_name in event["data"] for setting_name in Realm.REALM_PERMISSION_GROUP_SETTINGS
|
||||
):
|
||||
sub_type = group_setting_update_data_type
|
||||
else:
|
||||
raise AssertionError("unhandled fields in data")
|
||||
|
||||
|
|
|
@ -259,6 +259,12 @@ def fetch_initial_state_data(
|
|||
for property_name in Realm.property_types:
|
||||
state["realm_" + property_name] = getattr(realm, property_name)
|
||||
|
||||
for (
|
||||
setting_name,
|
||||
permissions_configuration,
|
||||
) in Realm.REALM_PERMISSION_GROUP_SETTINGS.items():
|
||||
state["realm_" + setting_name] = getattr(realm, permissions_configuration.id_field_name)
|
||||
|
||||
# Most state is handled via the property_types framework;
|
||||
# these manual entries are for those realm settings that don't
|
||||
# fit into that framework.
|
||||
|
|
|
@ -18,6 +18,7 @@ from psycopg2.extras import execute_values
|
|||
from psycopg2.sql import SQL, Identifier
|
||||
|
||||
from analytics.models import RealmCount, StreamCount, UserCount
|
||||
from zerver.actions.create_realm import set_default_for_realm_permission_group_settings
|
||||
from zerver.actions.realm_settings import do_change_realm_plan_type
|
||||
from zerver.actions.user_settings import do_change_avatar_fields
|
||||
from zerver.lib.avatar_hash import user_avatar_path_from_ids
|
||||
|
@ -906,10 +907,10 @@ def import_uploads(
|
|||
# have to import the dependencies first.)
|
||||
#
|
||||
# * Client [no deps]
|
||||
# * Realm [-notifications_stream]
|
||||
# * Realm [-notifications_stream,-group_permissions]
|
||||
# * UserGroup
|
||||
# * Stream [only depends on realm]
|
||||
# * Realm's notifications_stream
|
||||
# * Realm's notifications_stream and group_permissions
|
||||
# * UserProfile, in order by ID to avoid bot loop issues
|
||||
# * Now can do all realm_tables
|
||||
# * Huddle
|
||||
|
@ -948,12 +949,17 @@ def do_import_realm(import_dir: Path, subdomain: str, processes: int = 1) -> Rea
|
|||
|
||||
bulk_import_client(data, Client, "zerver_client")
|
||||
|
||||
# We don't import the Stream model yet, since it depends on Realm,
|
||||
# which isn't imported yet. But we need the Stream model IDs for
|
||||
# notifications_stream.
|
||||
# We don't import the Stream and UserGroup models yet, since
|
||||
# they depend on Realm, which isn't imported yet.
|
||||
# But we need the Stream and UserGroup model IDs for
|
||||
# notifications_stream and group permissions, respectively
|
||||
update_model_ids(Stream, data, "stream")
|
||||
re_map_foreign_keys(data, "zerver_realm", "notifications_stream", related_table="stream")
|
||||
re_map_foreign_keys(data, "zerver_realm", "signup_notifications_stream", related_table="stream")
|
||||
if "zerver_usergroup" in data:
|
||||
update_model_ids(UserGroup, data, "usergroup")
|
||||
for setting_name in Realm.REALM_PERMISSION_GROUP_SETTINGS:
|
||||
re_map_foreign_keys(data, "zerver_realm", setting_name, related_table="usergroup")
|
||||
|
||||
fix_datetime_fields(data, "zerver_realm")
|
||||
# Fix realm subdomain information
|
||||
|
@ -968,10 +974,15 @@ def do_import_realm(import_dir: Path, subdomain: str, processes: int = 1) -> Rea
|
|||
|
||||
with transaction.atomic(durable=True):
|
||||
realm = Realm(**realm_properties)
|
||||
if "zerver_usergroup" not in data:
|
||||
# For now a dummy value of -1 is given to groups fields which
|
||||
# is changed later before the transaction is committed.
|
||||
for permissions_configuration in Realm.REALM_PERMISSION_GROUP_SETTINGS.values():
|
||||
setattr(realm, permissions_configuration.id_field_name, -1)
|
||||
|
||||
realm.save()
|
||||
|
||||
if "zerver_usergroup" in data:
|
||||
update_model_ids(UserGroup, data, "usergroup")
|
||||
re_map_foreign_keys(data, "zerver_usergroup", "realm", related_table="realm")
|
||||
for setting_name in UserGroup.GROUP_PERMISSION_SETTINGS:
|
||||
re_map_foreign_keys(
|
||||
|
@ -1003,6 +1014,9 @@ def do_import_realm(import_dir: Path, subdomain: str, processes: int = 1) -> Rea
|
|||
stream["rendered_description"] = render_stream_description(stream["description"], realm)
|
||||
bulk_import_model(data, Stream)
|
||||
|
||||
if "zerver_usergroup" not in data:
|
||||
set_default_for_realm_permission_group_settings(realm)
|
||||
|
||||
# Remap the user IDs for notification_bot and friends to their
|
||||
# appropriate IDs on this server
|
||||
internal_realm = get_realm(settings.SYSTEM_BOT_REALM)
|
||||
|
|
|
@ -23,14 +23,23 @@ def server_initialized() -> bool:
|
|||
|
||||
@transaction.atomic(durable=True)
|
||||
def create_internal_realm() -> None:
|
||||
from zerver.actions.create_realm import set_default_for_realm_permission_group_settings
|
||||
from zerver.actions.users import do_change_can_forge_sender
|
||||
|
||||
realm = Realm.objects.create(string_id=settings.SYSTEM_BOT_REALM, name="System bot realm")
|
||||
realm = Realm(string_id=settings.SYSTEM_BOT_REALM, name="System bot realm")
|
||||
|
||||
# For now a dummy value of -1 is given to groups fields which
|
||||
# is changed later before the transaction is committed.
|
||||
for permissions_configuration in Realm.REALM_PERMISSION_GROUP_SETTINGS.values():
|
||||
setattr(realm, permissions_configuration.id_field_name, -1)
|
||||
realm.save()
|
||||
|
||||
RealmAuditLog.objects.create(
|
||||
realm=realm, event_type=RealmAuditLog.REALM_CREATED, event_time=realm.date_created
|
||||
)
|
||||
RealmUserDefault.objects.create(realm=realm)
|
||||
create_system_user_groups_for_realm(realm)
|
||||
set_default_for_realm_permission_group_settings(realm)
|
||||
|
||||
# We create realms with all authentications methods enabled by default.
|
||||
RealmAuthenticationMethod.objects.bulk_create(
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
# Generated by Django 4.2.1 on 2023-05-31 07:28
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("zerver", "0468_rename_followup_day_email_templates"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="realm",
|
||||
name="create_multiuse_invite_group",
|
||||
field=models.ForeignKey(
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.RESTRICT,
|
||||
related_name="+",
|
||||
to="zerver.usergroup",
|
||||
),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,46 @@
|
|||
# Generated by Django 4.2.1 on 2023-06-03 10:53
|
||||
|
||||
from django.db import migrations
|
||||
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||
from django.db.migrations.state import StateApps
|
||||
|
||||
|
||||
def set_default_value_for_create_multiuse_invite_group(
|
||||
apps: StateApps, schema_editor: BaseDatabaseSchemaEditor
|
||||
) -> None:
|
||||
Realm = apps.get_model("zerver", "Realm")
|
||||
UserGroup = apps.get_model("zerver", "UserGroup")
|
||||
|
||||
UserGroup.ADMINISTRATORS_GROUP_NAME = "role:administrators"
|
||||
|
||||
for realm in Realm.objects.all():
|
||||
if realm.create_multiuse_invite_group is not None:
|
||||
continue
|
||||
|
||||
# Prior to the new create_multiuse_invite_group field being
|
||||
# created, multi-use invitation links could only be created
|
||||
# and managed by administrators, regardless of
|
||||
# invite_to_realm_policy. We replicate that policy for the
|
||||
# initial value of the new setting.
|
||||
admins_group = UserGroup.objects.get(
|
||||
name=UserGroup.ADMINISTRATORS_GROUP_NAME, realm=realm, is_system_group=True
|
||||
)
|
||||
realm.create_multiuse_invite_group = admins_group
|
||||
|
||||
realm.save(update_fields=["create_multiuse_invite_group"])
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
atomic = False
|
||||
|
||||
dependencies = [
|
||||
("zerver", "0469_realm_create_multiuse_invite_group"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(
|
||||
set_default_value_for_create_multiuse_invite_group,
|
||||
elidable=True,
|
||||
reverse_code=migrations.RunPython.noop,
|
||||
),
|
||||
]
|
|
@ -0,0 +1,22 @@
|
|||
# Generated by Django 4.2.1 on 2023-06-06 08:26
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("zerver", "0470_set_default_value_for_create_multiuse_invite_group"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="realm",
|
||||
name="create_multiuse_invite_group",
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.RESTRICT,
|
||||
related_name="+",
|
||||
to="zerver.usergroup",
|
||||
),
|
||||
),
|
||||
]
|
|
@ -409,6 +409,11 @@ class Realm(models.Model): # type: ignore[django-manager-missing] # django-stub
|
|||
# Who in the organization is allowed to invite other users to organization.
|
||||
invite_to_realm_policy = models.PositiveSmallIntegerField(default=POLICY_MEMBERS_ONLY)
|
||||
|
||||
# UserGroup whose members are allowed to create invite link.
|
||||
create_multiuse_invite_group = models.ForeignKey(
|
||||
"UserGroup", on_delete=models.RESTRICT, related_name="+"
|
||||
)
|
||||
|
||||
# Who in the organization is allowed to invite other users to streams.
|
||||
invite_to_stream_policy = models.PositiveSmallIntegerField(default=POLICY_MEMBERS_ONLY)
|
||||
|
||||
|
@ -704,6 +709,9 @@ class Realm(models.Model): # type: ignore[django-manager-missing] # django-stub
|
|||
# they will not be available regardless of users' personal settings.
|
||||
enable_read_receipts = models.BooleanField(default=False)
|
||||
|
||||
# Duplicates of names for system group; TODO: Clean this up.
|
||||
ADMINISTRATORS_GROUP_NAME = "role:administrators"
|
||||
|
||||
# Define the types of the various automatically managed properties
|
||||
property_types: Dict[str, Union[type, Tuple[type, ...]]] = dict(
|
||||
add_custom_emoji_policy=int,
|
||||
|
@ -751,6 +759,17 @@ class Realm(models.Model): # type: ignore[django-manager-missing] # django-stub
|
|||
wildcard_mention_policy=int,
|
||||
)
|
||||
|
||||
REALM_PERMISSION_GROUP_SETTINGS: Dict[str, GroupPermissionSetting] = dict(
|
||||
create_multiuse_invite_group=GroupPermissionSetting(
|
||||
require_system_group=True,
|
||||
allow_internet_group=False,
|
||||
allow_owners_group=False,
|
||||
allow_nobody_group=True,
|
||||
default_group_name=ADMINISTRATORS_GROUP_NAME,
|
||||
id_field_name="create_multiuse_invite_group_id",
|
||||
),
|
||||
)
|
||||
|
||||
DIGEST_WEEKDAY_VALUES = [0, 1, 2, 3, 4, 5, 6]
|
||||
|
||||
# Icon is the square mobile icon.
|
||||
|
@ -2089,8 +2108,11 @@ class UserProfile(AbstractBaseUser, PermissionsMixin, UserBaseSettings): # type
|
|||
return False
|
||||
|
||||
def has_permission(self, policy_name: str) -> bool:
|
||||
from zerver.lib.user_groups import is_user_in_group
|
||||
|
||||
if policy_name not in [
|
||||
"add_custom_emoji_policy",
|
||||
"create_multiuse_invite_group",
|
||||
"create_private_stream_policy",
|
||||
"create_public_stream_policy",
|
||||
"create_web_public_stream_policy",
|
||||
|
@ -2103,6 +2125,10 @@ class UserProfile(AbstractBaseUser, PermissionsMixin, UserBaseSettings): # type
|
|||
]:
|
||||
raise AssertionError("Invalid policy")
|
||||
|
||||
if policy_name in Realm.REALM_PERMISSION_GROUP_SETTINGS:
|
||||
allowed_user_group = getattr(self.realm, policy_name)
|
||||
return is_user_in_group(allowed_user_group, self)
|
||||
|
||||
policy_value = getattr(self.realm, policy_name)
|
||||
if policy_value == Realm.POLICY_NOBODY:
|
||||
return False
|
||||
|
@ -2154,6 +2180,9 @@ class UserProfile(AbstractBaseUser, PermissionsMixin, UserBaseSettings): # type
|
|||
def can_invite_others_to_realm(self) -> bool:
|
||||
return self.has_permission("invite_to_realm_policy")
|
||||
|
||||
def can_create_multiuse_invite_to_realm(self) -> bool:
|
||||
return self.has_permission("create_multiuse_invite_group")
|
||||
|
||||
def can_move_messages_between_streams(self) -> bool:
|
||||
return self.has_permission("move_messages_between_streams_policy")
|
||||
|
||||
|
|
|
@ -3961,6 +3961,19 @@ paths:
|
|||
description: |
|
||||
The [policy](/api/roles-and-permissions#permission-levels)
|
||||
for which users can create bot users in this organization.
|
||||
create_multiuse_invite_group:
|
||||
type: integer
|
||||
description: |
|
||||
The ID of the [user group](/api/get-user-groups) whose members are
|
||||
allowed to create [reusable invitation
|
||||
links](/help/invite-new-users#create-a-reusable-invitation-link)
|
||||
to the organization.
|
||||
|
||||
This setting can currently only be set to user groups that are
|
||||
system groups, except for the system groups named
|
||||
`"role:internet"` and `"role:owners"`.
|
||||
|
||||
**Changes**: New in Zulip 8.0 (feature level 209).
|
||||
create_public_stream_policy:
|
||||
type: integer
|
||||
description: |
|
||||
|
@ -13258,6 +13271,19 @@ paths:
|
|||
|
||||
[permission-level]: /api/roles-and-permissions#permission-levels
|
||||
[calc-full-member]: /api/roles-and-permissions#determining-if-a-user-is-a-full-member
|
||||
realm_create_multiuse_invite_group:
|
||||
type: integer
|
||||
description: |
|
||||
The ID of the [user group](/api/get-user-groups) whose members are
|
||||
allowed to create [reusable invitation
|
||||
links](/help/invite-new-users#create-a-reusable-invitation-link)
|
||||
to the organization.
|
||||
|
||||
This setting can currently only be set to user groups that are
|
||||
system groups, except for the system groups named
|
||||
`"role:internet"` and `"role:owners"`.
|
||||
|
||||
**Changes**: New in Zulip 8.0 (feature level 209).
|
||||
realm_move_messages_between_streams_policy:
|
||||
type: integer
|
||||
description: |
|
||||
|
|
|
@ -71,6 +71,7 @@ from zerver.actions.realm_logo import do_change_logo_source
|
|||
from zerver.actions.realm_playgrounds import check_add_realm_playground, do_remove_realm_playground
|
||||
from zerver.actions.realm_settings import (
|
||||
do_change_realm_org_type,
|
||||
do_change_realm_permission_group_setting,
|
||||
do_change_realm_plan_type,
|
||||
do_deactivate_realm,
|
||||
do_set_realm_authentication_methods,
|
||||
|
@ -2968,11 +2969,83 @@ class RealmPropertyActionTest(BaseAction):
|
|||
else:
|
||||
check_realm_update("events[0]", events[0], name)
|
||||
|
||||
def do_set_realm_permission_group_setting_test(self, setting_name: str) -> None:
|
||||
all_system_user_groups = UserGroup.objects.filter(
|
||||
realm=self.user_profile.realm,
|
||||
is_system_group=True,
|
||||
)
|
||||
|
||||
setting_permission_configuration = Realm.REALM_PERMISSION_GROUP_SETTINGS[setting_name]
|
||||
|
||||
default_group_name = setting_permission_configuration.default_group_name
|
||||
default_group = all_system_user_groups.get(name=default_group_name)
|
||||
old_group_id = default_group.id
|
||||
|
||||
now = timezone_now()
|
||||
|
||||
do_change_realm_permission_group_setting(
|
||||
self.user_profile.realm,
|
||||
setting_name,
|
||||
default_group,
|
||||
acting_user=self.user_profile,
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
RealmAuditLog.objects.filter(
|
||||
realm=self.user_profile.realm,
|
||||
event_type=RealmAuditLog.REALM_PROPERTY_CHANGED,
|
||||
event_time__gte=now,
|
||||
acting_user=self.user_profile,
|
||||
).count(),
|
||||
1,
|
||||
)
|
||||
for user_group in all_system_user_groups:
|
||||
if user_group.name == default_group_name:
|
||||
continue
|
||||
|
||||
now = timezone_now()
|
||||
state_change_expected = True
|
||||
num_events = 1
|
||||
new_group_id = user_group.id
|
||||
|
||||
events = self.verify_action(
|
||||
lambda: do_change_realm_permission_group_setting(
|
||||
self.user_profile.realm,
|
||||
setting_name,
|
||||
user_group,
|
||||
acting_user=self.user_profile,
|
||||
),
|
||||
state_change_expected=state_change_expected,
|
||||
num_events=num_events,
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
RealmAuditLog.objects.filter(
|
||||
realm=self.user_profile.realm,
|
||||
event_type=RealmAuditLog.REALM_PROPERTY_CHANGED,
|
||||
event_time__gte=now,
|
||||
acting_user=self.user_profile,
|
||||
extra_data={
|
||||
RealmAuditLog.OLD_VALUE: old_group_id,
|
||||
RealmAuditLog.NEW_VALUE: new_group_id,
|
||||
"property": setting_name,
|
||||
},
|
||||
).count(),
|
||||
1,
|
||||
)
|
||||
check_realm_update_dict("events[0]", events[0])
|
||||
|
||||
old_group_id = new_group_id
|
||||
|
||||
def test_change_realm_property(self) -> None:
|
||||
for prop in Realm.property_types:
|
||||
with self.settings(SEND_DIGEST_EMAILS=True):
|
||||
self.do_set_realm_property_test(prop)
|
||||
|
||||
for prop in Realm.REALM_PERMISSION_GROUP_SETTINGS:
|
||||
with self.settings(SEND_DIGEST_EMAILS=True):
|
||||
self.do_set_realm_permission_group_setting_test(prop)
|
||||
|
||||
def do_set_realm_user_default_setting_test(self, name: str) -> None:
|
||||
bool_tests: List[bool] = [True, False, True]
|
||||
test_values: Dict[str, Any] = dict(
|
||||
|
|
|
@ -115,6 +115,7 @@ class HomeTest(ZulipTestCase):
|
|||
"realm_bot_creation_policy",
|
||||
"realm_bot_domain",
|
||||
"realm_bots",
|
||||
"realm_create_multiuse_invite_group",
|
||||
"realm_create_private_stream_policy",
|
||||
"realm_create_public_stream_policy",
|
||||
"realm_create_web_public_stream_policy",
|
||||
|
|
|
@ -1045,6 +1045,13 @@ class RealmImportExportTest(ExportFile):
|
|||
def get_active_stream_names(r: Realm) -> Set[str]:
|
||||
return {stream.name for stream in get_active_streams(r)}
|
||||
|
||||
@getter
|
||||
def get_group_names_for_group_settings(r: Realm) -> Set[str]:
|
||||
return {
|
||||
getattr(r, permmission_name).name
|
||||
for permmission_name in Realm.REALM_PERMISSION_GROUP_SETTINGS
|
||||
}
|
||||
|
||||
# test recipients
|
||||
def get_recipient_stream(r: Realm) -> Recipient:
|
||||
recipient = Stream.objects.get(name="Verona", realm=r).recipient
|
||||
|
@ -1559,6 +1566,12 @@ class RealmImportExportTest(ExportFile):
|
|||
data = read_json("realm.json")
|
||||
data.pop("zerver_usergroup")
|
||||
data.pop("zerver_realmauditlog")
|
||||
|
||||
# User groups data is missing. So, all the realm group based settings
|
||||
# should be None.
|
||||
for setting_name in Realm.REALM_PERMISSION_GROUP_SETTINGS:
|
||||
data["zerver_realm"][0][setting_name] = None
|
||||
|
||||
with open(export_fn("realm.json"), "wb") as f:
|
||||
f.write(orjson.dumps(data))
|
||||
|
||||
|
|
|
@ -37,7 +37,11 @@ from zerver.actions.invites import (
|
|||
do_revoke_multi_use_invite,
|
||||
too_many_recent_realm_invites,
|
||||
)
|
||||
from zerver.actions.realm_settings import do_change_realm_plan_type, do_set_realm_property
|
||||
from zerver.actions.realm_settings import (
|
||||
do_change_realm_permission_group_setting,
|
||||
do_change_realm_plan_type,
|
||||
do_set_realm_property,
|
||||
)
|
||||
from zerver.actions.user_settings import do_change_full_name
|
||||
from zerver.actions.users import change_user_is_active
|
||||
from zerver.context_processors import common_context
|
||||
|
@ -55,6 +59,7 @@ from zerver.models import (
|
|||
Realm,
|
||||
ScheduledEmail,
|
||||
Stream,
|
||||
UserGroup,
|
||||
UserMessage,
|
||||
UserProfile,
|
||||
get_realm,
|
||||
|
@ -2444,22 +2449,62 @@ class MultiuseInviteTest(ZulipTestCase):
|
|||
self.assert_length(get_default_streams_for_realm_as_dicts(self.realm.id), 1)
|
||||
self.check_user_subscribed_only_to_streams("alice", [])
|
||||
|
||||
def test_only_admin_can_create_multiuse_link_api_call(self) -> None:
|
||||
self.login("iago")
|
||||
# Only admins should be able to create multiuse invites even if
|
||||
# invite_to_realm_policy is set to Realm.POLICY_MEMBERS_ONLY.
|
||||
self.realm.invite_to_realm_policy = Realm.POLICY_MEMBERS_ONLY
|
||||
self.realm.save()
|
||||
|
||||
result = self.client_post(
|
||||
"/json/invites/multiuse", {"invite_expires_in_minutes": 2 * 24 * 60}
|
||||
def test_create_multiuse_invite_group_setting(self) -> None:
|
||||
realm = get_realm("zulip")
|
||||
full_members_system_group = UserGroup.objects.get(
|
||||
name=UserGroup.FULL_MEMBERS_GROUP_NAME, realm=realm, is_system_group=True
|
||||
)
|
||||
nobody_system_group = UserGroup.objects.get(
|
||||
name=UserGroup.NOBODY_GROUP_NAME, realm=realm, is_system_group=True
|
||||
)
|
||||
|
||||
# Default value of create_multiuse_invite_group is administrators
|
||||
self.login("shiva")
|
||||
result = self.client_post("/json/invites/multiuse")
|
||||
self.assert_json_error(result, "Insufficient permission")
|
||||
|
||||
self.login("iago")
|
||||
result = self.client_post("/json/invites/multiuse")
|
||||
invite_link = self.assert_json_success(result)["invite_link"]
|
||||
self.check_user_able_to_register(self.nonreg_email("test"), invite_link)
|
||||
|
||||
do_change_realm_permission_group_setting(
|
||||
realm, "create_multiuse_invite_group", full_members_system_group, acting_user=None
|
||||
)
|
||||
|
||||
self.login("hamlet")
|
||||
result = self.client_post("/json/invites/multiuse")
|
||||
self.assert_json_error(result, "Must be an organization administrator")
|
||||
invite_link = self.assert_json_success(result)["invite_link"]
|
||||
self.check_user_able_to_register(self.nonreg_email("test1"), invite_link)
|
||||
|
||||
self.login("desdemona")
|
||||
do_change_realm_permission_group_setting(
|
||||
realm, "create_multiuse_invite_group", nobody_system_group, acting_user=None
|
||||
)
|
||||
result = self.client_post("/json/invites/multiuse")
|
||||
self.assert_json_error(result, "Insufficient permission")
|
||||
|
||||
def test_only_owner_can_change_create_multiuse_invite_group(self) -> None:
|
||||
realm = get_realm("zulip")
|
||||
full_members_system_group = UserGroup.objects.get(
|
||||
name=UserGroup.FULL_MEMBERS_GROUP_NAME, realm=realm, is_system_group=True
|
||||
)
|
||||
|
||||
self.login("iago")
|
||||
result = self.client_patch(
|
||||
"/json/realm",
|
||||
{"create_multiuse_invite_group": orjson.dumps(full_members_system_group.id).decode()},
|
||||
)
|
||||
self.assert_json_error(result, "Must be an organization owner")
|
||||
|
||||
self.login("desdemona")
|
||||
result = self.client_patch(
|
||||
"/json/realm",
|
||||
{"create_multiuse_invite_group": orjson.dumps(full_members_system_group.id).decode()},
|
||||
)
|
||||
self.assert_json_success(result)
|
||||
realm = get_realm("zulip")
|
||||
self.assertEqual(realm.create_multiuse_invite_group_id, full_members_system_group.id)
|
||||
|
||||
def test_multiuse_link_for_inviting_as_owner(self) -> None:
|
||||
self.login("iago")
|
||||
|
@ -2483,6 +2528,78 @@ class MultiuseInviteTest(ZulipTestCase):
|
|||
invite_link = self.assert_json_success(result)["invite_link"]
|
||||
self.check_user_able_to_register(self.nonreg_email("test"), invite_link)
|
||||
|
||||
def test_multiuse_link_for_inviting_as_admin(self) -> None:
|
||||
realm = get_realm("zulip")
|
||||
full_members_system_group = UserGroup.objects.get(
|
||||
name=UserGroup.FULL_MEMBERS_GROUP_NAME, realm=realm, is_system_group=True
|
||||
)
|
||||
|
||||
do_change_realm_permission_group_setting(
|
||||
realm, "create_multiuse_invite_group", full_members_system_group, acting_user=None
|
||||
)
|
||||
|
||||
self.login("hamlet")
|
||||
result = self.client_post(
|
||||
"/json/invites/multiuse",
|
||||
{
|
||||
"invite_as": orjson.dumps(PreregistrationUser.INVITE_AS["REALM_ADMIN"]).decode(),
|
||||
"invite_expires_in_minutes": 2 * 24 * 60,
|
||||
},
|
||||
)
|
||||
self.assert_json_error(result, "Must be an organization administrator")
|
||||
|
||||
self.login("iago")
|
||||
result = self.client_post(
|
||||
"/json/invites/multiuse",
|
||||
{
|
||||
"invite_as": orjson.dumps(PreregistrationUser.INVITE_AS["REALM_ADMIN"]).decode(),
|
||||
"invite_expires_in_minutes": 2 * 24 * 60,
|
||||
},
|
||||
)
|
||||
invite_link = self.assert_json_success(result)["invite_link"]
|
||||
self.check_user_able_to_register(self.nonreg_email("test"), invite_link)
|
||||
|
||||
def test_multiuse_link_for_inviting_as_moderator(self) -> None:
|
||||
realm = get_realm("zulip")
|
||||
full_members_system_group = UserGroup.objects.get(
|
||||
name=UserGroup.FULL_MEMBERS_GROUP_NAME, realm=realm, is_system_group=True
|
||||
)
|
||||
|
||||
do_change_realm_permission_group_setting(
|
||||
realm, "create_multiuse_invite_group", full_members_system_group, acting_user=None
|
||||
)
|
||||
|
||||
self.login("hamlet")
|
||||
result = self.client_post(
|
||||
"/json/invites/multiuse",
|
||||
{
|
||||
"invite_as": orjson.dumps(PreregistrationUser.INVITE_AS["MODERATOR"]).decode(),
|
||||
"invite_expires_in_minutes": 2 * 24 * 60,
|
||||
},
|
||||
)
|
||||
self.assert_json_error(result, "Must be an organization administrator")
|
||||
|
||||
self.login("shiva")
|
||||
result = self.client_post(
|
||||
"/json/invites/multiuse",
|
||||
{
|
||||
"invite_as": orjson.dumps(PreregistrationUser.INVITE_AS["MODERATOR"]).decode(),
|
||||
"invite_expires_in_minutes": 2 * 24 * 60,
|
||||
},
|
||||
)
|
||||
self.assert_json_error(result, "Must be an organization administrator")
|
||||
|
||||
self.login("iago")
|
||||
result = self.client_post(
|
||||
"/json/invites/multiuse",
|
||||
{
|
||||
"invite_as": orjson.dumps(PreregistrationUser.INVITE_AS["REALM_ADMIN"]).decode(),
|
||||
"invite_expires_in_minutes": 2 * 24 * 60,
|
||||
},
|
||||
)
|
||||
invite_link = self.assert_json_success(result)["invite_link"]
|
||||
self.check_user_able_to_register(self.nonreg_email("test"), invite_link)
|
||||
|
||||
def test_create_multiuse_link_invalid_stream_api_call(self) -> None:
|
||||
self.login("iago")
|
||||
result = self.client_post(
|
||||
|
|
|
@ -892,6 +892,14 @@ class RealmTest(ZulipTestCase):
|
|||
|
||||
self.assertEqual(realm.plan_type, Realm.PLAN_TYPE_LIMITED)
|
||||
|
||||
for (
|
||||
setting_name,
|
||||
permissions_configuration,
|
||||
) in Realm.REALM_PERMISSION_GROUP_SETTINGS.items():
|
||||
self.assertEqual(
|
||||
getattr(realm, setting_name).name, permissions_configuration.default_group_name
|
||||
)
|
||||
|
||||
def test_do_create_realm_with_keyword_arguments(self) -> None:
|
||||
date_created = timezone_now() - datetime.timedelta(days=100)
|
||||
realm = do_create_realm(
|
||||
|
@ -1207,11 +1215,56 @@ class RealmAPITest(ZulipTestCase):
|
|||
realm = self.update_with_api(name, vals[0])
|
||||
self.assertEqual(getattr(realm, name), vals[0])
|
||||
|
||||
def do_test_realm_permission_group_setting_update_api(self, setting_name: str) -> None:
|
||||
realm = get_realm("zulip")
|
||||
|
||||
all_system_user_groups = UserGroup.objects.filter(
|
||||
realm=realm,
|
||||
is_system_group=True,
|
||||
)
|
||||
|
||||
setting_permission_configuration = Realm.REALM_PERMISSION_GROUP_SETTINGS[setting_name]
|
||||
|
||||
default_group_name = setting_permission_configuration.default_group_name
|
||||
default_group = all_system_user_groups.get(name=default_group_name)
|
||||
|
||||
self.set_up_db(setting_name, default_group)
|
||||
|
||||
for user_group in all_system_user_groups:
|
||||
if (
|
||||
(
|
||||
user_group.name == UserGroup.EVERYONE_ON_INTERNET_GROUP_NAME
|
||||
and not setting_permission_configuration.allow_internet_group
|
||||
)
|
||||
or (
|
||||
user_group.name == UserGroup.NOBODY_GROUP_NAME
|
||||
and not setting_permission_configuration.allow_nobody_group
|
||||
)
|
||||
or (
|
||||
user_group.name == UserGroup.OWNERS_GROUP_NAME
|
||||
and not setting_permission_configuration.allow_owners_group
|
||||
)
|
||||
):
|
||||
value = orjson.dumps(user_group.id).decode()
|
||||
|
||||
result = self.client_patch("/json/realm", {setting_name: value})
|
||||
self.assert_json_error(
|
||||
result, f"'{setting_name}' setting cannot be set to '{user_group.name}' group."
|
||||
)
|
||||
continue
|
||||
|
||||
realm = self.update_with_api(setting_name, user_group.id)
|
||||
self.assertEqual(getattr(realm, setting_name), user_group)
|
||||
|
||||
def test_update_realm_properties(self) -> None:
|
||||
for prop in Realm.property_types:
|
||||
with self.subTest(property=prop):
|
||||
self.do_test_realm_update_api(prop)
|
||||
|
||||
for prop in Realm.REALM_PERMISSION_GROUP_SETTINGS:
|
||||
with self.subTest(property=prop):
|
||||
self.do_test_realm_permission_group_setting_update_api(prop)
|
||||
|
||||
# Not in Realm.property_types because org_type has
|
||||
# a unique RealmAuditLog event_type.
|
||||
def test_update_realm_org_type(self) -> None:
|
||||
|
|
|
@ -184,7 +184,7 @@ def resend_user_invite_email(
|
|||
return json_success(request, data={"timestamp": timestamp})
|
||||
|
||||
|
||||
@require_realm_admin
|
||||
@require_member_or_admin
|
||||
@has_request_variables
|
||||
def generate_multiuse_invite_backend(
|
||||
request: HttpRequest,
|
||||
|
@ -200,7 +200,19 @@ def generate_multiuse_invite_backend(
|
|||
),
|
||||
stream_ids: Sequence[int] = REQ(json_validator=check_list(check_int), default=[]),
|
||||
) -> HttpResponse:
|
||||
check_role_based_permissions(invite_as, user_profile, require_admin=True)
|
||||
if not user_profile.can_create_multiuse_invite_to_realm():
|
||||
# Guest users case will not be handled here as it will
|
||||
# be handled by the decorator above.
|
||||
raise JsonableError(_("Insufficient permission"))
|
||||
|
||||
require_admin = invite_as in [
|
||||
# Owners can only be invited by owners, checked by separate
|
||||
# logic in check_role_based_permissions.
|
||||
PreregistrationUser.INVITE_AS["REALM_OWNER"],
|
||||
PreregistrationUser.INVITE_AS["REALM_ADMIN"],
|
||||
PreregistrationUser.INVITE_AS["MODERATOR"],
|
||||
]
|
||||
check_role_based_permissions(invite_as, user_profile, require_admin=require_admin)
|
||||
|
||||
streams = []
|
||||
for stream_id in stream_ids:
|
||||
|
|
|
@ -10,6 +10,7 @@ from confirmation.models import Confirmation, ConfirmationKeyError, get_object_f
|
|||
from zerver.actions.create_realm import do_change_realm_subdomain
|
||||
from zerver.actions.realm_settings import (
|
||||
do_change_realm_org_type,
|
||||
do_change_realm_permission_group_setting,
|
||||
do_deactivate_realm,
|
||||
do_reactivate_realm,
|
||||
do_set_realm_authentication_methods,
|
||||
|
@ -27,6 +28,7 @@ from zerver.lib.request import REQ, has_request_variables
|
|||
from zerver.lib.response import json_success
|
||||
from zerver.lib.retention import parse_message_retention_days
|
||||
from zerver.lib.streams import access_stream_by_id
|
||||
from zerver.lib.user_groups import access_user_group_for_setting
|
||||
from zerver.lib.validator import (
|
||||
check_bool,
|
||||
check_capped_string,
|
||||
|
@ -60,6 +62,9 @@ def update_realm(
|
|||
invite_to_realm_policy: Optional[int] = REQ(
|
||||
json_validator=check_int_in(Realm.INVITE_TO_REALM_POLICY_TYPES), default=None
|
||||
),
|
||||
create_multiuse_invite_group_id: Optional[int] = REQ(
|
||||
"create_multiuse_invite_group", json_validator=check_int, default=None
|
||||
),
|
||||
name_changes_disabled: Optional[bool] = REQ(json_validator=check_bool, default=None),
|
||||
email_changes_disabled: Optional[bool] = REQ(json_validator=check_bool, default=None),
|
||||
avatar_changes_disabled: Optional[bool] = REQ(json_validator=check_bool, default=None),
|
||||
|
@ -188,7 +193,9 @@ def update_realm(
|
|||
)
|
||||
|
||||
if (
|
||||
invite_to_realm_policy is not None or invite_required is not None
|
||||
invite_to_realm_policy is not None
|
||||
or invite_required is not None
|
||||
or create_multiuse_invite_group_id is not None
|
||||
) and not user_profile.is_realm_owner:
|
||||
raise OrganizationOwnerRequiredError
|
||||
|
||||
|
@ -275,7 +282,16 @@ def update_realm(
|
|||
# TODO: It should be possible to deduplicate this function up
|
||||
# further by some more advanced usage of the
|
||||
# `REQ/has_request_variables` extraction.
|
||||
req_vars = {k: v for k, v in list(locals().items()) if k in realm.property_types}
|
||||
req_vars = {}
|
||||
req_group_setting_vars = {}
|
||||
|
||||
for k, v in list(locals().items()):
|
||||
if k in realm.property_types:
|
||||
req_vars[k] = v
|
||||
|
||||
for permissions_configuration in Realm.REALM_PERMISSION_GROUP_SETTINGS.values():
|
||||
if k == permissions_configuration.id_field_name:
|
||||
req_group_setting_vars[k] = v
|
||||
|
||||
for k, v in list(req_vars.items()):
|
||||
if v is not None and getattr(realm, k) != v:
|
||||
|
@ -285,6 +301,29 @@ def update_realm(
|
|||
else:
|
||||
data[k] = v
|
||||
|
||||
for setting_name, permissions_configuration in Realm.REALM_PERMISSION_GROUP_SETTINGS.items():
|
||||
setting_group_id_name = permissions_configuration.id_field_name
|
||||
|
||||
assert setting_group_id_name in req_group_setting_vars
|
||||
|
||||
if req_group_setting_vars[setting_group_id_name] is not None and req_group_setting_vars[
|
||||
setting_group_id_name
|
||||
] != getattr(realm, setting_group_id_name):
|
||||
user_group_id = req_group_setting_vars[setting_group_id_name]
|
||||
user_group = access_user_group_for_setting(
|
||||
user_group_id,
|
||||
user_profile,
|
||||
setting_name=setting_name,
|
||||
require_system_group=permissions_configuration.require_system_group,
|
||||
allow_internet_group=permissions_configuration.allow_internet_group,
|
||||
allow_owners_group=permissions_configuration.allow_owners_group,
|
||||
allow_nobody_group=permissions_configuration.allow_nobody_group,
|
||||
)
|
||||
do_change_realm_permission_group_setting(
|
||||
realm, setting_name, user_group, acting_user=user_profile
|
||||
)
|
||||
data[setting_name] = user_group_id
|
||||
|
||||
# The following realm properties do not fit the pattern above
|
||||
# authentication_methods is not supported by the do_set_realm_property
|
||||
# framework because it's tracked through the RealmAuthenticationMethod table.
|
||||
|
|
Loading…
Reference in New Issue