diff --git a/zerver/actions/user_groups.py b/zerver/actions/user_groups.py index d5bb8f86c8..aed2e4ba8d 100644 --- a/zerver/actions/user_groups.py +++ b/zerver/actions/user_groups.py @@ -15,6 +15,7 @@ from zerver.lib.user_groups import ( from zerver.models import ( GroupGroupMembership, Realm, + RealmAuditLog, UserGroup, UserGroupMembership, UserProfile, @@ -56,6 +57,28 @@ def create_user_group_in_database( UserGroupMembership.objects.bulk_create( UserGroupMembership(user_profile=member, user_group=user_group) for member in members ) + + creation_time = timezone_now() + audit_log_entries = [ + RealmAuditLog( + realm=realm, + acting_user=acting_user, + event_type=RealmAuditLog.USER_GROUP_CREATED, + event_time=creation_time, + modified_user_group=user_group, + ) + ] + [ + RealmAuditLog( + realm=realm, + acting_user=acting_user, + event_type=RealmAuditLog.USER_GROUP_DIRECT_USER_MEMBERSHIP_ADDED, + event_time=creation_time, + modified_user=member, + modified_user_group=user_group, + ) + for member in members + ] + RealmAuditLog.objects.bulk_create(audit_log_entries) return user_group diff --git a/zerver/lib/user_groups.py b/zerver/lib/user_groups.py index 386e234d5f..af34064f17 100644 --- a/zerver/lib/user_groups.py +++ b/zerver/lib/user_groups.py @@ -2,12 +2,20 @@ from typing import Dict, Iterable, List, Mapping, Sequence, TypedDict from django.db import transaction from django.db.models import F, QuerySet +from django.utils.timezone import now as timezone_now from django.utils.translation import gettext as _ from django_cte import With from django_stubs_ext import ValuesQuerySet from zerver.lib.exceptions import JsonableError -from zerver.models import GroupGroupMembership, Realm, UserGroup, UserGroupMembership, UserProfile +from zerver.models import ( + GroupGroupMembership, + Realm, + RealmAuditLog, + UserGroup, + UserGroupMembership, + UserProfile, +) class UserGroupDict(TypedDict): @@ -316,7 +324,18 @@ def create_system_user_groups_for_realm(realm: Realm) -> Dict[int, UserGroup]: everyone_on_internet_system_group, ] + creation_time = timezone_now() UserGroup.objects.bulk_create(system_user_groups_list) + RealmAuditLog.objects.bulk_create( + RealmAuditLog( + realm=realm, + acting_user=None, + event_type=RealmAuditLog.USER_GROUP_CREATED, + event_time=creation_time, + modified_user_group=user_group, + ) + for user_group in system_user_groups_list + ) groups_with_updated_settings = [] system_groups_name_dict = get_role_based_system_groups_dict(realm) diff --git a/zerver/tests/test_audit_log.py b/zerver/tests/test_audit_log.py index d4003a18e1..acd5122b01 100644 --- a/zerver/tests/test_audit_log.py +++ b/zerver/tests/test_audit_log.py @@ -45,6 +45,7 @@ from zerver.actions.streams import ( do_deactivate_stream, do_rename_stream, ) +from zerver.actions.user_groups import check_add_user_group from zerver.actions.user_settings import ( do_change_avatar_fields, do_change_password, @@ -61,6 +62,7 @@ from zerver.lib.streams import create_stream_if_needed from zerver.lib.test_classes import ZulipTestCase from zerver.lib.test_helpers import get_test_image_file from zerver.lib.types import LinkifierDict, RealmPlaygroundDict +from zerver.lib.user_groups import create_system_user_groups_for_realm from zerver.lib.utils import assert_is_not_none from zerver.models import ( EmojiInfo, @@ -71,6 +73,7 @@ from zerver.models import ( RealmPlayground, Recipient, Subscription, + UserGroup, UserProfile, get_realm, get_realm_domains, @@ -985,3 +988,60 @@ class TestRealmAuditLog(ZulipTestCase): ).count(), 1, ) + + def test_system_user_groups_creation(self) -> None: + realm = Realm.objects.create(string_id="test", name="foo") + now = timezone_now() + create_system_user_groups_for_realm(realm) + + # The expected number of system user group is the total number of roles + # from UserGroup.SYSTEM_USER_GROUP_ROLE_MAP in addition to + # full_members_system_group, everyone_on_internet_system_group and + # nobody_system_group. + expected_system_user_group_count = len(UserGroup.SYSTEM_USER_GROUP_ROLE_MAP) + 3 + + system_user_group_ids = sorted( + UserGroup.objects.filter( + realm=realm, + is_system_group=True, + ).values_list("id", flat=True) + ) + self.assert_length(system_user_group_ids, expected_system_user_group_count) + + logged_system_group_ids = sorted( + RealmAuditLog.objects.filter( + realm=realm, + event_type=RealmAuditLog.USER_GROUP_CREATED, + event_time__gte=now, + acting_user=None, + ).values_list("modified_user_group_id", flat=True) + ) + self.assertListEqual(logged_system_group_ids, system_user_group_ids) + + def test_user_group_creation(self) -> None: + hamlet = self.example_user("hamlet") + cordelia = self.example_user("cordelia") + now = timezone_now() + user_group = check_add_user_group( + hamlet.realm, "empty", [hamlet, cordelia], acting_user=hamlet, description="lorem" + ) + + audit_log_entries = RealmAuditLog.objects.filter( + acting_user=hamlet, + realm=hamlet.realm, + event_time__gte=now, + event_type=RealmAuditLog.USER_GROUP_CREATED, + ) + self.assert_length(audit_log_entries, 1) + self.assertIsNone(audit_log_entries[0].modified_user) + self.assertEqual(audit_log_entries[0].modified_user_group, user_group) + + audit_log_entries = RealmAuditLog.objects.filter( + acting_user=hamlet, + realm=hamlet.realm, + event_time__gte=now, + event_type=RealmAuditLog.USER_GROUP_DIRECT_USER_MEMBERSHIP_ADDED, + ) + self.assert_length(audit_log_entries, 2) + self.assertEqual(audit_log_entries[0].modified_user, hamlet) + self.assertEqual(audit_log_entries[1].modified_user, cordelia) diff --git a/zerver/tests/test_slack_importer.py b/zerver/tests/test_slack_importer.py index 2c416be2e6..c28da40cc5 100644 --- a/zerver/tests/test_slack_importer.py +++ b/zerver/tests/test_slack_importer.py @@ -1334,6 +1334,7 @@ class SlackImporter(ZulipTestCase): RealmAuditLog.SUBSCRIPTION_CREATED, RealmAuditLog.REALM_PLAN_TYPE_CHANGED, RealmAuditLog.REALM_CREATED, + RealmAuditLog.USER_GROUP_CREATED, }, )