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
|
## 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**
|
**Feature level 208**
|
||||||
|
|
||||||
* [`POST /users/me/subscriptions`](/api/subscribe),
|
* [`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.bulk_create import create_users
|
||||||
from zerver.lib.server_initialization import create_internal_realm, server_initialized
|
from zerver.lib.server_initialization import create_internal_realm, server_initialized
|
||||||
from zerver.lib.streams import ensure_stream, get_signups_stream
|
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 (
|
from zerver.models import (
|
||||||
DefaultStream,
|
DefaultStream,
|
||||||
PreregistrationRealm,
|
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
|
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:
|
def setup_realm_internal_bots(realm: Realm) -> None:
|
||||||
"""Create this realm's internal bots.
|
"""Create this realm's internal bots.
|
||||||
|
|
||||||
|
@ -204,6 +218,12 @@ def do_create_realm(
|
||||||
)
|
)
|
||||||
|
|
||||||
set_realm_permissions_based_on_org_type(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()
|
realm.save()
|
||||||
|
|
||||||
RealmAuditLog.objects.create(
|
RealmAuditLog.objects.create(
|
||||||
|
@ -230,6 +250,7 @@ def do_create_realm(
|
||||||
)
|
)
|
||||||
|
|
||||||
create_system_user_groups_for_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.
|
# We create realms with all authentications methods enabled by default.
|
||||||
RealmAuthenticationMethod.objects.bulk_create(
|
RealmAuthenticationMethod.objects.bulk_create(
|
||||||
|
|
|
@ -31,6 +31,7 @@ from zerver.models import (
|
||||||
ScheduledEmail,
|
ScheduledEmail,
|
||||||
Stream,
|
Stream,
|
||||||
Subscription,
|
Subscription,
|
||||||
|
UserGroup,
|
||||||
UserProfile,
|
UserProfile,
|
||||||
active_user_ids,
|
active_user_ids,
|
||||||
get_realm,
|
get_realm,
|
||||||
|
@ -101,6 +102,42 @@ def do_set_realm_property(
|
||||||
update_users_in_full_members_system_group(realm, acting_user=acting_user)
|
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(
|
def parse_and_set_setting_value_if_required(
|
||||||
realm: Realm, setting_name: str, value: Union[int, str], *, acting_user: Optional[UserProfile]
|
realm: Realm, setting_name: str, value: Union[int, str], *, acting_user: Optional[UserProfile]
|
||||||
) -> Tuple[Optional[int], bool]:
|
) -> 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(
|
update_dict_data = UnionType(
|
||||||
[
|
[
|
||||||
allow_message_editing_data,
|
allow_message_editing_data,
|
||||||
|
@ -964,6 +968,7 @@ update_dict_data = UnionType(
|
||||||
logo_data,
|
logo_data,
|
||||||
message_content_edit_limit_seconds_data,
|
message_content_edit_limit_seconds_data,
|
||||||
night_logo_data,
|
night_logo_data,
|
||||||
|
group_setting_update_data_type,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -996,6 +1001,10 @@ def check_realm_update_dict(
|
||||||
sub_type = edit_topic_policy_data
|
sub_type = edit_topic_policy_data
|
||||||
elif "authentication_methods" in event["data"]:
|
elif "authentication_methods" in event["data"]:
|
||||||
sub_type = authentication_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:
|
else:
|
||||||
raise AssertionError("unhandled fields in data")
|
raise AssertionError("unhandled fields in data")
|
||||||
|
|
||||||
|
|
|
@ -259,6 +259,12 @@ def fetch_initial_state_data(
|
||||||
for property_name in Realm.property_types:
|
for property_name in Realm.property_types:
|
||||||
state["realm_" + property_name] = getattr(realm, property_name)
|
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;
|
# Most state is handled via the property_types framework;
|
||||||
# these manual entries are for those realm settings that don't
|
# these manual entries are for those realm settings that don't
|
||||||
# fit into that framework.
|
# fit into that framework.
|
||||||
|
|
|
@ -18,6 +18,7 @@ from psycopg2.extras import execute_values
|
||||||
from psycopg2.sql import SQL, Identifier
|
from psycopg2.sql import SQL, Identifier
|
||||||
|
|
||||||
from analytics.models import RealmCount, StreamCount, UserCount
|
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.realm_settings import do_change_realm_plan_type
|
||||||
from zerver.actions.user_settings import do_change_avatar_fields
|
from zerver.actions.user_settings import do_change_avatar_fields
|
||||||
from zerver.lib.avatar_hash import user_avatar_path_from_ids
|
from zerver.lib.avatar_hash import user_avatar_path_from_ids
|
||||||
|
@ -906,10 +907,10 @@ def import_uploads(
|
||||||
# have to import the dependencies first.)
|
# have to import the dependencies first.)
|
||||||
#
|
#
|
||||||
# * Client [no deps]
|
# * Client [no deps]
|
||||||
# * Realm [-notifications_stream]
|
# * Realm [-notifications_stream,-group_permissions]
|
||||||
# * UserGroup
|
# * UserGroup
|
||||||
# * Stream [only depends on realm]
|
# * 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
|
# * UserProfile, in order by ID to avoid bot loop issues
|
||||||
# * Now can do all realm_tables
|
# * Now can do all realm_tables
|
||||||
# * Huddle
|
# * 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")
|
bulk_import_client(data, Client, "zerver_client")
|
||||||
|
|
||||||
# We don't import the Stream model yet, since it depends on Realm,
|
# We don't import the Stream and UserGroup models yet, since
|
||||||
# which isn't imported yet. But we need the Stream model IDs for
|
# they depend on Realm, which isn't imported yet.
|
||||||
# notifications_stream.
|
# But we need the Stream and UserGroup model IDs for
|
||||||
|
# notifications_stream and group permissions, respectively
|
||||||
update_model_ids(Stream, data, "stream")
|
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", "notifications_stream", related_table="stream")
|
||||||
re_map_foreign_keys(data, "zerver_realm", "signup_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_datetime_fields(data, "zerver_realm")
|
||||||
# Fix realm subdomain information
|
# 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):
|
with transaction.atomic(durable=True):
|
||||||
realm = Realm(**realm_properties)
|
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()
|
realm.save()
|
||||||
|
|
||||||
if "zerver_usergroup" in data:
|
if "zerver_usergroup" in data:
|
||||||
update_model_ids(UserGroup, data, "usergroup")
|
|
||||||
re_map_foreign_keys(data, "zerver_usergroup", "realm", related_table="realm")
|
re_map_foreign_keys(data, "zerver_usergroup", "realm", related_table="realm")
|
||||||
for setting_name in UserGroup.GROUP_PERMISSION_SETTINGS:
|
for setting_name in UserGroup.GROUP_PERMISSION_SETTINGS:
|
||||||
re_map_foreign_keys(
|
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)
|
stream["rendered_description"] = render_stream_description(stream["description"], realm)
|
||||||
bulk_import_model(data, Stream)
|
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
|
# Remap the user IDs for notification_bot and friends to their
|
||||||
# appropriate IDs on this server
|
# appropriate IDs on this server
|
||||||
internal_realm = get_realm(settings.SYSTEM_BOT_REALM)
|
internal_realm = get_realm(settings.SYSTEM_BOT_REALM)
|
||||||
|
|
|
@ -23,14 +23,23 @@ def server_initialized() -> bool:
|
||||||
|
|
||||||
@transaction.atomic(durable=True)
|
@transaction.atomic(durable=True)
|
||||||
def create_internal_realm() -> None:
|
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
|
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(
|
RealmAuditLog.objects.create(
|
||||||
realm=realm, event_type=RealmAuditLog.REALM_CREATED, event_time=realm.date_created
|
realm=realm, event_type=RealmAuditLog.REALM_CREATED, event_time=realm.date_created
|
||||||
)
|
)
|
||||||
RealmUserDefault.objects.create(realm=realm)
|
RealmUserDefault.objects.create(realm=realm)
|
||||||
create_system_user_groups_for_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.
|
# We create realms with all authentications methods enabled by default.
|
||||||
RealmAuthenticationMethod.objects.bulk_create(
|
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.
|
# Who in the organization is allowed to invite other users to organization.
|
||||||
invite_to_realm_policy = models.PositiveSmallIntegerField(default=POLICY_MEMBERS_ONLY)
|
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.
|
# Who in the organization is allowed to invite other users to streams.
|
||||||
invite_to_stream_policy = models.PositiveSmallIntegerField(default=POLICY_MEMBERS_ONLY)
|
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.
|
# they will not be available regardless of users' personal settings.
|
||||||
enable_read_receipts = models.BooleanField(default=False)
|
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
|
# Define the types of the various automatically managed properties
|
||||||
property_types: Dict[str, Union[type, Tuple[type, ...]]] = dict(
|
property_types: Dict[str, Union[type, Tuple[type, ...]]] = dict(
|
||||||
add_custom_emoji_policy=int,
|
add_custom_emoji_policy=int,
|
||||||
|
@ -751,6 +759,17 @@ class Realm(models.Model): # type: ignore[django-manager-missing] # django-stub
|
||||||
wildcard_mention_policy=int,
|
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]
|
DIGEST_WEEKDAY_VALUES = [0, 1, 2, 3, 4, 5, 6]
|
||||||
|
|
||||||
# Icon is the square mobile icon.
|
# Icon is the square mobile icon.
|
||||||
|
@ -2089,8 +2108,11 @@ class UserProfile(AbstractBaseUser, PermissionsMixin, UserBaseSettings): # type
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def has_permission(self, policy_name: str) -> bool:
|
def has_permission(self, policy_name: str) -> bool:
|
||||||
|
from zerver.lib.user_groups import is_user_in_group
|
||||||
|
|
||||||
if policy_name not in [
|
if policy_name not in [
|
||||||
"add_custom_emoji_policy",
|
"add_custom_emoji_policy",
|
||||||
|
"create_multiuse_invite_group",
|
||||||
"create_private_stream_policy",
|
"create_private_stream_policy",
|
||||||
"create_public_stream_policy",
|
"create_public_stream_policy",
|
||||||
"create_web_public_stream_policy",
|
"create_web_public_stream_policy",
|
||||||
|
@ -2103,6 +2125,10 @@ class UserProfile(AbstractBaseUser, PermissionsMixin, UserBaseSettings): # type
|
||||||
]:
|
]:
|
||||||
raise AssertionError("Invalid policy")
|
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)
|
policy_value = getattr(self.realm, policy_name)
|
||||||
if policy_value == Realm.POLICY_NOBODY:
|
if policy_value == Realm.POLICY_NOBODY:
|
||||||
return False
|
return False
|
||||||
|
@ -2154,6 +2180,9 @@ class UserProfile(AbstractBaseUser, PermissionsMixin, UserBaseSettings): # type
|
||||||
def can_invite_others_to_realm(self) -> bool:
|
def can_invite_others_to_realm(self) -> bool:
|
||||||
return self.has_permission("invite_to_realm_policy")
|
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:
|
def can_move_messages_between_streams(self) -> bool:
|
||||||
return self.has_permission("move_messages_between_streams_policy")
|
return self.has_permission("move_messages_between_streams_policy")
|
||||||
|
|
||||||
|
|
|
@ -3961,6 +3961,19 @@ paths:
|
||||||
description: |
|
description: |
|
||||||
The [policy](/api/roles-and-permissions#permission-levels)
|
The [policy](/api/roles-and-permissions#permission-levels)
|
||||||
for which users can create bot users in this organization.
|
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:
|
create_public_stream_policy:
|
||||||
type: integer
|
type: integer
|
||||||
description: |
|
description: |
|
||||||
|
@ -13258,6 +13271,19 @@ paths:
|
||||||
|
|
||||||
[permission-level]: /api/roles-and-permissions#permission-levels
|
[permission-level]: /api/roles-and-permissions#permission-levels
|
||||||
[calc-full-member]: /api/roles-and-permissions#determining-if-a-user-is-a-full-member
|
[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:
|
realm_move_messages_between_streams_policy:
|
||||||
type: integer
|
type: integer
|
||||||
description: |
|
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_playgrounds import check_add_realm_playground, do_remove_realm_playground
|
||||||
from zerver.actions.realm_settings import (
|
from zerver.actions.realm_settings import (
|
||||||
do_change_realm_org_type,
|
do_change_realm_org_type,
|
||||||
|
do_change_realm_permission_group_setting,
|
||||||
do_change_realm_plan_type,
|
do_change_realm_plan_type,
|
||||||
do_deactivate_realm,
|
do_deactivate_realm,
|
||||||
do_set_realm_authentication_methods,
|
do_set_realm_authentication_methods,
|
||||||
|
@ -2968,11 +2969,83 @@ class RealmPropertyActionTest(BaseAction):
|
||||||
else:
|
else:
|
||||||
check_realm_update("events[0]", events[0], name)
|
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:
|
def test_change_realm_property(self) -> None:
|
||||||
for prop in Realm.property_types:
|
for prop in Realm.property_types:
|
||||||
with self.settings(SEND_DIGEST_EMAILS=True):
|
with self.settings(SEND_DIGEST_EMAILS=True):
|
||||||
self.do_set_realm_property_test(prop)
|
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:
|
def do_set_realm_user_default_setting_test(self, name: str) -> None:
|
||||||
bool_tests: List[bool] = [True, False, True]
|
bool_tests: List[bool] = [True, False, True]
|
||||||
test_values: Dict[str, Any] = dict(
|
test_values: Dict[str, Any] = dict(
|
||||||
|
|
|
@ -115,6 +115,7 @@ class HomeTest(ZulipTestCase):
|
||||||
"realm_bot_creation_policy",
|
"realm_bot_creation_policy",
|
||||||
"realm_bot_domain",
|
"realm_bot_domain",
|
||||||
"realm_bots",
|
"realm_bots",
|
||||||
|
"realm_create_multiuse_invite_group",
|
||||||
"realm_create_private_stream_policy",
|
"realm_create_private_stream_policy",
|
||||||
"realm_create_public_stream_policy",
|
"realm_create_public_stream_policy",
|
||||||
"realm_create_web_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]:
|
def get_active_stream_names(r: Realm) -> Set[str]:
|
||||||
return {stream.name for stream in get_active_streams(r)}
|
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
|
# test recipients
|
||||||
def get_recipient_stream(r: Realm) -> Recipient:
|
def get_recipient_stream(r: Realm) -> Recipient:
|
||||||
recipient = Stream.objects.get(name="Verona", realm=r).recipient
|
recipient = Stream.objects.get(name="Verona", realm=r).recipient
|
||||||
|
@ -1559,6 +1566,12 @@ class RealmImportExportTest(ExportFile):
|
||||||
data = read_json("realm.json")
|
data = read_json("realm.json")
|
||||||
data.pop("zerver_usergroup")
|
data.pop("zerver_usergroup")
|
||||||
data.pop("zerver_realmauditlog")
|
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:
|
with open(export_fn("realm.json"), "wb") as f:
|
||||||
f.write(orjson.dumps(data))
|
f.write(orjson.dumps(data))
|
||||||
|
|
||||||
|
|
|
@ -37,7 +37,11 @@ from zerver.actions.invites import (
|
||||||
do_revoke_multi_use_invite,
|
do_revoke_multi_use_invite,
|
||||||
too_many_recent_realm_invites,
|
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.user_settings import do_change_full_name
|
||||||
from zerver.actions.users import change_user_is_active
|
from zerver.actions.users import change_user_is_active
|
||||||
from zerver.context_processors import common_context
|
from zerver.context_processors import common_context
|
||||||
|
@ -55,6 +59,7 @@ from zerver.models import (
|
||||||
Realm,
|
Realm,
|
||||||
ScheduledEmail,
|
ScheduledEmail,
|
||||||
Stream,
|
Stream,
|
||||||
|
UserGroup,
|
||||||
UserMessage,
|
UserMessage,
|
||||||
UserProfile,
|
UserProfile,
|
||||||
get_realm,
|
get_realm,
|
||||||
|
@ -2444,22 +2449,62 @@ class MultiuseInviteTest(ZulipTestCase):
|
||||||
self.assert_length(get_default_streams_for_realm_as_dicts(self.realm.id), 1)
|
self.assert_length(get_default_streams_for_realm_as_dicts(self.realm.id), 1)
|
||||||
self.check_user_subscribed_only_to_streams("alice", [])
|
self.check_user_subscribed_only_to_streams("alice", [])
|
||||||
|
|
||||||
def test_only_admin_can_create_multiuse_link_api_call(self) -> None:
|
def test_create_multiuse_invite_group_setting(self) -> None:
|
||||||
self.login("iago")
|
realm = get_realm("zulip")
|
||||||
# Only admins should be able to create multiuse invites even if
|
full_members_system_group = UserGroup.objects.get(
|
||||||
# invite_to_realm_policy is set to Realm.POLICY_MEMBERS_ONLY.
|
name=UserGroup.FULL_MEMBERS_GROUP_NAME, realm=realm, is_system_group=True
|
||||||
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}
|
|
||||||
)
|
)
|
||||||
|
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"]
|
invite_link = self.assert_json_success(result)["invite_link"]
|
||||||
self.check_user_able_to_register(self.nonreg_email("test"), 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")
|
self.login("hamlet")
|
||||||
result = self.client_post("/json/invites/multiuse")
|
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:
|
def test_multiuse_link_for_inviting_as_owner(self) -> None:
|
||||||
self.login("iago")
|
self.login("iago")
|
||||||
|
@ -2483,6 +2528,78 @@ class MultiuseInviteTest(ZulipTestCase):
|
||||||
invite_link = self.assert_json_success(result)["invite_link"]
|
invite_link = self.assert_json_success(result)["invite_link"]
|
||||||
self.check_user_able_to_register(self.nonreg_email("test"), 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:
|
def test_create_multiuse_link_invalid_stream_api_call(self) -> None:
|
||||||
self.login("iago")
|
self.login("iago")
|
||||||
result = self.client_post(
|
result = self.client_post(
|
||||||
|
|
|
@ -892,6 +892,14 @@ class RealmTest(ZulipTestCase):
|
||||||
|
|
||||||
self.assertEqual(realm.plan_type, Realm.PLAN_TYPE_LIMITED)
|
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:
|
def test_do_create_realm_with_keyword_arguments(self) -> None:
|
||||||
date_created = timezone_now() - datetime.timedelta(days=100)
|
date_created = timezone_now() - datetime.timedelta(days=100)
|
||||||
realm = do_create_realm(
|
realm = do_create_realm(
|
||||||
|
@ -1207,11 +1215,56 @@ class RealmAPITest(ZulipTestCase):
|
||||||
realm = self.update_with_api(name, vals[0])
|
realm = self.update_with_api(name, vals[0])
|
||||||
self.assertEqual(getattr(realm, 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:
|
def test_update_realm_properties(self) -> None:
|
||||||
for prop in Realm.property_types:
|
for prop in Realm.property_types:
|
||||||
with self.subTest(property=prop):
|
with self.subTest(property=prop):
|
||||||
self.do_test_realm_update_api(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
|
# Not in Realm.property_types because org_type has
|
||||||
# a unique RealmAuditLog event_type.
|
# a unique RealmAuditLog event_type.
|
||||||
def test_update_realm_org_type(self) -> None:
|
def test_update_realm_org_type(self) -> None:
|
||||||
|
|
|
@ -184,7 +184,7 @@ def resend_user_invite_email(
|
||||||
return json_success(request, data={"timestamp": timestamp})
|
return json_success(request, data={"timestamp": timestamp})
|
||||||
|
|
||||||
|
|
||||||
@require_realm_admin
|
@require_member_or_admin
|
||||||
@has_request_variables
|
@has_request_variables
|
||||||
def generate_multiuse_invite_backend(
|
def generate_multiuse_invite_backend(
|
||||||
request: HttpRequest,
|
request: HttpRequest,
|
||||||
|
@ -200,7 +200,19 @@ def generate_multiuse_invite_backend(
|
||||||
),
|
),
|
||||||
stream_ids: Sequence[int] = REQ(json_validator=check_list(check_int), default=[]),
|
stream_ids: Sequence[int] = REQ(json_validator=check_list(check_int), default=[]),
|
||||||
) -> HttpResponse:
|
) -> 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 = []
|
streams = []
|
||||||
for stream_id in stream_ids:
|
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.create_realm import do_change_realm_subdomain
|
||||||
from zerver.actions.realm_settings import (
|
from zerver.actions.realm_settings import (
|
||||||
do_change_realm_org_type,
|
do_change_realm_org_type,
|
||||||
|
do_change_realm_permission_group_setting,
|
||||||
do_deactivate_realm,
|
do_deactivate_realm,
|
||||||
do_reactivate_realm,
|
do_reactivate_realm,
|
||||||
do_set_realm_authentication_methods,
|
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.response import json_success
|
||||||
from zerver.lib.retention import parse_message_retention_days
|
from zerver.lib.retention import parse_message_retention_days
|
||||||
from zerver.lib.streams import access_stream_by_id
|
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 (
|
from zerver.lib.validator import (
|
||||||
check_bool,
|
check_bool,
|
||||||
check_capped_string,
|
check_capped_string,
|
||||||
|
@ -60,6 +62,9 @@ def update_realm(
|
||||||
invite_to_realm_policy: Optional[int] = REQ(
|
invite_to_realm_policy: Optional[int] = REQ(
|
||||||
json_validator=check_int_in(Realm.INVITE_TO_REALM_POLICY_TYPES), default=None
|
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),
|
name_changes_disabled: Optional[bool] = REQ(json_validator=check_bool, default=None),
|
||||||
email_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),
|
avatar_changes_disabled: Optional[bool] = REQ(json_validator=check_bool, default=None),
|
||||||
|
@ -188,7 +193,9 @@ def update_realm(
|
||||||
)
|
)
|
||||||
|
|
||||||
if (
|
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:
|
) and not user_profile.is_realm_owner:
|
||||||
raise OrganizationOwnerRequiredError
|
raise OrganizationOwnerRequiredError
|
||||||
|
|
||||||
|
@ -275,7 +282,16 @@ def update_realm(
|
||||||
# TODO: It should be possible to deduplicate this function up
|
# TODO: It should be possible to deduplicate this function up
|
||||||
# further by some more advanced usage of the
|
# further by some more advanced usage of the
|
||||||
# `REQ/has_request_variables` extraction.
|
# `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()):
|
for k, v in list(req_vars.items()):
|
||||||
if v is not None and getattr(realm, k) != v:
|
if v is not None and getattr(realm, k) != v:
|
||||||
|
@ -285,6 +301,29 @@ def update_realm(
|
||||||
else:
|
else:
|
||||||
data[k] = v
|
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
|
# The following realm properties do not fit the pattern above
|
||||||
# authentication_methods is not supported by the do_set_realm_property
|
# authentication_methods is not supported by the do_set_realm_property
|
||||||
# framework because it's tracked through the RealmAuthenticationMethod table.
|
# framework because it's tracked through the RealmAuthenticationMethod table.
|
||||||
|
|
Loading…
Reference in New Issue