mirror of https://github.com/zulip/zulip.git
user_groups: Add can_join_group setting for user group.
This field will be used to control permission for who can join a user group. Fixes part of #25938.
This commit is contained in:
parent
a073eaa534
commit
e1d7f57da7
|
@ -20,6 +20,15 @@ format used by the Zulip server that they are interacting with.
|
|||
|
||||
## Changes in Zulip 10.0
|
||||
|
||||
**Feature level 301**
|
||||
|
||||
* [`POST /user_groups/create`](/api/create-user-group): Added `can_join_group`
|
||||
parameter to support setting the user group whose members can join the user
|
||||
group.
|
||||
* [`PATCH /user_groups/{user_group_id}`](/api/update-user-group): Added
|
||||
`can_join_group` parameter to support changing the user group whose
|
||||
members can join the specified user group.
|
||||
|
||||
**Feature level 300**
|
||||
|
||||
* [`GET /messages`](/api/get-message): Added a new message_ids parameter,
|
||||
|
|
|
@ -757,6 +757,7 @@ def bulk_import_named_user_groups(data: TableData) -> None:
|
|||
group["name"],
|
||||
group["description"],
|
||||
group["is_system_group"],
|
||||
group["can_join_group_id"],
|
||||
group["can_manage_group_id"],
|
||||
group["can_mention_group_id"],
|
||||
group["deactivated"],
|
||||
|
@ -767,7 +768,7 @@ def bulk_import_named_user_groups(data: TableData) -> None:
|
|||
|
||||
query = SQL(
|
||||
"""
|
||||
INSERT INTO zerver_namedusergroup (usergroup_ptr_id, realm_id, name, description, is_system_group, can_manage_group_id, can_mention_group_id, deactivated, date_created)
|
||||
INSERT INTO zerver_namedusergroup (usergroup_ptr_id, realm_id, name, description, is_system_group, can_join_group_id, can_manage_group_id, can_mention_group_id, deactivated, date_created)
|
||||
VALUES %s
|
||||
"""
|
||||
)
|
||||
|
|
|
@ -819,7 +819,7 @@ def bulk_create_system_user_groups(groups: list[dict[str, str]], realm: Realm) -
|
|||
user_group_ids = [id for (id,) in cursor.fetchall()]
|
||||
|
||||
rows = [
|
||||
SQL("({},{},{},{},{},{},{},{})").format(
|
||||
SQL("({},{},{},{},{},{},{},{},{})").format(
|
||||
Literal(user_group_ids[idx]),
|
||||
Literal(realm.id),
|
||||
Literal(group["name"]),
|
||||
|
@ -827,13 +827,14 @@ def bulk_create_system_user_groups(groups: list[dict[str, str]], realm: Realm) -
|
|||
Literal(True),
|
||||
Literal(initial_group_setting_value),
|
||||
Literal(initial_group_setting_value),
|
||||
Literal(initial_group_setting_value),
|
||||
Literal(False),
|
||||
)
|
||||
for idx, group in enumerate(groups)
|
||||
]
|
||||
query = SQL(
|
||||
"""
|
||||
INSERT INTO zerver_namedusergroup (usergroup_ptr_id, realm_id, name, description, is_system_group, can_manage_group_id, can_mention_group_id, deactivated)
|
||||
INSERT INTO zerver_namedusergroup (usergroup_ptr_id, realm_id, name, description, is_system_group, can_join_group_id, can_manage_group_id, can_mention_group_id, deactivated)
|
||||
VALUES {rows}
|
||||
"""
|
||||
).format(rows=SQL(", ").join(rows))
|
||||
|
@ -915,7 +916,7 @@ def create_system_user_groups_for_realm(realm: Realm) -> dict[int, NamedUserGrou
|
|||
user_group = set_defaults_for_group_settings(group, {}, system_groups_name_dict)
|
||||
groups_with_updated_settings.append(user_group)
|
||||
NamedUserGroup.objects.bulk_update(
|
||||
groups_with_updated_settings, ["can_manage_group", "can_mention_group"]
|
||||
groups_with_updated_settings, ["can_join_group", "can_manage_group", "can_mention_group"]
|
||||
)
|
||||
|
||||
subgroup_objects: list[GroupGroupMembership] = []
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
# Generated by Django 5.0.8 on 2024-09-19 10:34
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("zerver", "0595_add_realmexport_table_and_backfill"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="namedusergroup",
|
||||
name="can_join_group",
|
||||
field=models.ForeignKey(
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.RESTRICT,
|
||||
related_name="+",
|
||||
to="zerver.usergroup",
|
||||
),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,56 @@
|
|||
# Generated by Django 5.0.8 on 2024-09-19 10:34
|
||||
|
||||
from django.db import migrations, transaction
|
||||
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||
from django.db.migrations.state import StateApps
|
||||
from django.db.models import Max, Min, OuterRef
|
||||
|
||||
|
||||
def set_default_value_for_can_join_group(
|
||||
apps: StateApps, schema_editor: BaseDatabaseSchemaEditor
|
||||
) -> None:
|
||||
NamedUserGroup = apps.get_model("zerver", "NamedUserGroup")
|
||||
BATCH_SIZE = 1000
|
||||
|
||||
max_id = NamedUserGroup.objects.filter(can_join_group=None).aggregate(Max("id"))["id__max"]
|
||||
if max_id is None:
|
||||
# Do nothing if there are no user groups on the server.
|
||||
return
|
||||
|
||||
lower_bound = NamedUserGroup.objects.filter(can_join_group=None).aggregate(Min("id"))["id__min"]
|
||||
while lower_bound <= max_id + BATCH_SIZE / 2:
|
||||
upper_bound = lower_bound + BATCH_SIZE - 1
|
||||
print(f"Processing batch {lower_bound} to {upper_bound} for NamedUserGroup")
|
||||
|
||||
with transaction.atomic():
|
||||
# Owners will naturally have the permission to join the
|
||||
# group via their permission to manage all groups or add
|
||||
# anyone to this group.
|
||||
NamedUserGroup.objects.filter(
|
||||
id__range=(lower_bound, upper_bound),
|
||||
can_join_group=None,
|
||||
).update(
|
||||
can_join_group=NamedUserGroup.objects.filter(
|
||||
name="role:nobody",
|
||||
realm_for_sharding=OuterRef("realm_for_sharding"),
|
||||
is_system_group=True,
|
||||
).values("pk")
|
||||
)
|
||||
|
||||
lower_bound += BATCH_SIZE
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
atomic = False
|
||||
|
||||
dependencies = [
|
||||
("zerver", "0596_namedusergroup_can_join_group"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(
|
||||
set_default_value_for_can_join_group,
|
||||
elidable=True,
|
||||
reverse_code=migrations.RunPython.noop,
|
||||
)
|
||||
]
|
|
@ -0,0 +1,22 @@
|
|||
# Generated by Django 5.0.8 on 2024-09-19 10:40
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("zerver", "0597_set_default_value_for_can_join_group"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="namedusergroup",
|
||||
name="can_join_group",
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.RESTRICT,
|
||||
related_name="+",
|
||||
to="zerver.usergroup",
|
||||
),
|
||||
),
|
||||
]
|
|
@ -59,6 +59,7 @@ class NamedUserGroup(UserGroup): # type: ignore[django-manager-missing] # djang
|
|||
)
|
||||
is_system_group = models.BooleanField(default=False, db_column="is_system_group")
|
||||
|
||||
can_join_group = models.ForeignKey(UserGroup, on_delete=models.RESTRICT, related_name="+")
|
||||
can_manage_group = models.ForeignKey(UserGroup, on_delete=models.RESTRICT, related_name="+")
|
||||
can_mention_group = models.ForeignKey(
|
||||
UserGroup, on_delete=models.RESTRICT, db_column="can_mention_group_id"
|
||||
|
@ -94,6 +95,16 @@ class NamedUserGroup(UserGroup): # type: ignore[django-manager-missing] # djang
|
|||
}
|
||||
|
||||
GROUP_PERMISSION_SETTINGS = {
|
||||
"can_join_group": GroupPermissionSetting(
|
||||
require_system_group=False,
|
||||
allow_internet_group=False,
|
||||
allow_owners_group=True,
|
||||
allow_nobody_group=True,
|
||||
allow_everyone_group=False,
|
||||
default_group_name=SystemGroups.NOBODY,
|
||||
default_for_system_groups=SystemGroups.NOBODY,
|
||||
id_field_name="can_join_group_id",
|
||||
),
|
||||
"can_manage_group": GroupPermissionSetting(
|
||||
require_system_group=False,
|
||||
allow_internet_group=False,
|
||||
|
|
|
@ -264,6 +264,7 @@ def get_temp_user_group_id() -> dict[str, object]:
|
|||
user_group, _ = NamedUserGroup.objects.get_or_create(
|
||||
name="temp",
|
||||
realm=get_realm("zulip"),
|
||||
can_join_group_id=11,
|
||||
can_manage_group_id=11,
|
||||
can_mention_group_id=11,
|
||||
realm_for_sharding=get_realm("zulip"),
|
||||
|
@ -278,6 +279,7 @@ def get_temp_user_group_id_for_deactivation() -> dict[str, object]:
|
|||
user_group, _ = NamedUserGroup.objects.get_or_create(
|
||||
name="temp-deactivation",
|
||||
realm=get_realm("zulip"),
|
||||
can_join_group_id=11,
|
||||
can_manage_group_id=11,
|
||||
can_mention_group_id=11,
|
||||
realm_for_sharding=get_realm("zulip"),
|
||||
|
|
|
@ -20081,6 +20081,18 @@ paths:
|
|||
items:
|
||||
type: integer
|
||||
example: [1, 2, 3, 4]
|
||||
can_join_group:
|
||||
allOf:
|
||||
- description: |
|
||||
A [group-setting value][setting-values] defining the set of users who
|
||||
have permission to join this user group.
|
||||
|
||||
**Changes**: New in Zulip 10.0 (feature level 301).
|
||||
|
||||
[setting-values]: /api/group-setting-values
|
||||
[system-groups]: /api/group-setting-values#system-groups
|
||||
- $ref: "#/components/schemas/GroupSettingValue"
|
||||
example: 11
|
||||
can_manage_group:
|
||||
allOf:
|
||||
- description: |
|
||||
|
@ -20127,6 +20139,8 @@ paths:
|
|||
encoding:
|
||||
members:
|
||||
contentType: application/json
|
||||
can_join_group:
|
||||
contentType: application/json
|
||||
can_manage_group:
|
||||
contentType: application/json
|
||||
can_mention_group:
|
||||
|
@ -20257,6 +20271,37 @@ paths:
|
|||
a required field.
|
||||
type: string
|
||||
example: The marketing team.
|
||||
can_join_group:
|
||||
description: |
|
||||
The set of users who have permission to join this user group
|
||||
expressed as an [update to a group-setting value][update-group-setting].
|
||||
|
||||
**Changes**: New in Zulip 10.0 (feature level 301).
|
||||
|
||||
[update-group-setting]: /api/group-setting-values#updating-group-setting-values
|
||||
[system-groups]: /api/group-setting-values#system-groups
|
||||
type: object
|
||||
additionalProperties: false
|
||||
properties:
|
||||
new:
|
||||
allOf:
|
||||
- description: |
|
||||
The new [group-setting value](/api/group-setting-values) for who would
|
||||
have the permission to join the group.
|
||||
- $ref: "#/components/schemas/GroupSettingValue"
|
||||
old:
|
||||
allOf:
|
||||
- description: |
|
||||
The expected current [group-setting value](/api/group-setting-values)
|
||||
for who has the permission to join the group.
|
||||
- $ref: "#/components/schemas/GroupSettingValue"
|
||||
required:
|
||||
- new
|
||||
example:
|
||||
{
|
||||
"new": {"direct_members": [10], "direct_subgroups": [11]},
|
||||
"old": 11,
|
||||
}
|
||||
can_manage_group:
|
||||
description: |
|
||||
The set of users who have permission to [manage this user group][manage-user-groups]
|
||||
|
@ -20341,6 +20386,8 @@ paths:
|
|||
"old": 11,
|
||||
}
|
||||
encoding:
|
||||
can_join_group:
|
||||
contentType: application/json
|
||||
can_manage_group:
|
||||
contentType: application/json
|
||||
can_mention_group:
|
||||
|
|
|
@ -57,6 +57,7 @@ def add_user_group(
|
|||
name: str,
|
||||
members: Json[list[int]],
|
||||
description: str,
|
||||
can_join_group: Json[int | AnonymousSettingGroupDict] | None = None,
|
||||
can_manage_group: Json[int | AnonymousSettingGroupDict] | None = None,
|
||||
can_mention_group: Json[int | AnonymousSettingGroupDict] | None = None,
|
||||
) -> HttpResponse:
|
||||
|
@ -116,12 +117,14 @@ def edit_user_group(
|
|||
user_group_id: PathOnly[int],
|
||||
name: str | None = None,
|
||||
description: str | None = None,
|
||||
can_join_group: Json[GroupSettingChangeRequest] | None = None,
|
||||
can_manage_group: Json[GroupSettingChangeRequest] | None = None,
|
||||
can_mention_group: Json[GroupSettingChangeRequest] | None = None,
|
||||
) -> HttpResponse:
|
||||
if (
|
||||
name is None
|
||||
and description is None
|
||||
and can_join_group is None
|
||||
and can_manage_group is None
|
||||
and can_mention_group is None
|
||||
):
|
||||
|
@ -132,7 +135,10 @@ def edit_user_group(
|
|||
)
|
||||
|
||||
if user_group.deactivated and (
|
||||
description is not None or can_mention_group is not None or can_manage_group is not None
|
||||
description is not None
|
||||
or can_join_group is not None
|
||||
or can_mention_group is not None
|
||||
or can_manage_group is not None
|
||||
):
|
||||
raise JsonableError(_("You can only change name of deactivated user groups"))
|
||||
|
||||
|
|
Loading…
Reference in New Issue