mirror of
				https://github.com/zulip/zulip.git
				synced 2025-10-31 12:03:46 +00:00 
			
		
		
		
	stream: Users with permission to administer can unsubscribe others.
We've also converted the function to check for permission to unsubscribe others to accept a list of streams instead of checking each stream one by one.
This commit is contained in:
		
				
					committed by
					
						 Tim Abbott
						Tim Abbott
					
				
			
			
				
	
			
			
			
						parent
						
							0f51b029a0
						
					
				
				
					commit
					4d02a082a0
				
			| @@ -25,6 +25,10 @@ format used by the Zulip server that they are interacting with. | ||||
| * [`POST /users/me/subscriptions`](/api/subscribe): Users belonging to | ||||
|   `can_add_subscribers_group` should be able to add subscribers to a | ||||
|   private channel without being subscribed to it. | ||||
| * [`DELETE /users/me/subscriptions`](/api/get-subscriptions): Channel | ||||
|   administrators can now unsubscribe other users even if they are not | ||||
|   an organization administrator or part of | ||||
|   `can_remove_subscribers_group`. | ||||
|  | ||||
| **Feature level 348** | ||||
|  | ||||
|   | ||||
| @@ -333,6 +333,14 @@ def is_user_in_can_add_subscribers_group( | ||||
|     return group_allowed_to_add_subscribers_id in user_recursive_group_ids | ||||
|  | ||||
|  | ||||
| def is_user_in_can_remove_subscribers_group( | ||||
|     stream: Stream, user_recursive_group_ids: set[int] | ||||
| ) -> bool: | ||||
|     group_allowed_to_remove_subscribers_id = stream.can_remove_subscribers_group_id | ||||
|     assert group_allowed_to_remove_subscribers_id is not None | ||||
|     return group_allowed_to_remove_subscribers_id in user_recursive_group_ids | ||||
|  | ||||
|  | ||||
| def check_stream_access_based_on_can_send_message_group( | ||||
|     sender: UserProfile, stream: Stream | ||||
| ) -> None: | ||||
| @@ -775,29 +783,52 @@ def can_access_stream_history_by_id(user_profile: UserProfile, stream_id: int) - | ||||
|     return can_access_stream_history(user_profile, stream) | ||||
|  | ||||
|  | ||||
| def can_remove_subscribers_from_stream( | ||||
|     stream: Stream, user_profile: UserProfile, sub: Subscription | None | ||||
| def bulk_can_remove_subscribers_from_streams( | ||||
|     streams: list[Stream], user_profile: UserProfile | ||||
| ) -> bool: | ||||
|     if not check_basic_stream_access( | ||||
|         user_profile, stream, is_subscribed=sub is not None, allow_realm_admin=True | ||||
|     ): | ||||
|         return False | ||||
|  | ||||
|     # Optimization for the organization administrator code path. We | ||||
|     # don't explicitly grant realm admins this permission, but admins | ||||
|     # implicitly have the can_administer_channel_group permission for | ||||
|     # all accessible channels. | ||||
|     # all accessible channels. For channels that the administrator | ||||
|     # cannot access, they can do limited administration including | ||||
|     # removing subscribers. | ||||
|     if user_profile.is_realm_admin: | ||||
|         return True | ||||
|  | ||||
|     group_allowed_to_remove_subscribers = stream.can_remove_subscribers_group | ||||
|     assert group_allowed_to_remove_subscribers is not None | ||||
|     return user_has_permission_for_group_setting( | ||||
|         group_allowed_to_remove_subscribers, | ||||
|         user_profile, | ||||
|         Stream.stream_permission_group_settings["can_remove_subscribers_group"], | ||||
|     user_recursive_group_ids = set( | ||||
|         get_recursive_membership_groups(user_profile).values_list("id", flat=True) | ||||
|     ) | ||||
|  | ||||
|     # We check this before basic access since for the channels the user | ||||
|     # cannot access, they can unsubscribe other users if they have | ||||
|     # permission to administer that channel. | ||||
|     permission_failure_streams: set[int] = set() | ||||
|     for stream in streams: | ||||
|         if not is_user_in_can_administer_channel_group(stream, user_recursive_group_ids): | ||||
|             permission_failure_streams.add(stream.id) | ||||
|  | ||||
|     if not bool(permission_failure_streams): | ||||
|         return True | ||||
|  | ||||
|     existing_recipient_ids = [stream.recipient_id for stream in streams] | ||||
|     sub_recipient_ids = Subscription.objects.filter( | ||||
|         user_profile=user_profile, recipient_id__in=existing_recipient_ids, active=True | ||||
|     ).values_list("recipient_id", flat=True) | ||||
|  | ||||
|     for stream in streams: | ||||
|         assert stream.recipient_id is not None | ||||
|         is_subscribed = stream.recipient_id in sub_recipient_ids | ||||
|         if not check_basic_stream_access( | ||||
|             user_profile, stream, is_subscribed=is_subscribed, allow_realm_admin=True | ||||
|         ): | ||||
|             return False | ||||
|  | ||||
|     for stream in streams: | ||||
|         if not is_user_in_can_remove_subscribers_group(stream, user_recursive_group_ids): | ||||
|             return False | ||||
|  | ||||
|     return True | ||||
|  | ||||
|  | ||||
| def get_streams_to_which_user_cannot_add_subscribers( | ||||
|     streams: list[Stream], | ||||
| @@ -961,16 +992,10 @@ def list_to_streams( | ||||
|     missing_stream_dicts: list[StreamDict] = [] | ||||
|     existing_stream_map = bulk_get_streams(user_profile.realm, stream_set) | ||||
|  | ||||
|     if unsubscribing_others: | ||||
|         existing_recipient_ids = [stream.recipient_id for stream in existing_stream_map.values()] | ||||
|         subs = Subscription.objects.filter( | ||||
|             user_profile=user_profile, recipient_id__in=existing_recipient_ids, active=True | ||||
|         ) | ||||
|         sub_map = {sub.recipient_id: sub for sub in subs} | ||||
|         for stream in existing_stream_map.values(): | ||||
|             sub = sub_map.get(stream.recipient_id, None) | ||||
|             if not can_remove_subscribers_from_stream(stream, user_profile, sub): | ||||
|                 raise JsonableError(_("Insufficient permission")) | ||||
|     if unsubscribing_others and not bulk_can_remove_subscribers_from_streams( | ||||
|         list(existing_stream_map.values()), user_profile | ||||
|     ): | ||||
|         raise JsonableError(_("Insufficient permission")) | ||||
|  | ||||
|     message_retention_days_not_none = False | ||||
|     web_public_stream_requested = False | ||||
|   | ||||
| @@ -20453,10 +20453,17 @@ paths: | ||||
|                         Administrators can always unsubscribe others from a channel. | ||||
| 
 | ||||
|                         Note that a user must also [have access](/help/channel-permissions) to a | ||||
|                         channel in order to modify it. | ||||
|                         channel in order to modify it. Organization administrators and channel | ||||
|                         administrators are an exception to this. | ||||
| 
 | ||||
|                         **Changes**: Prior to Zulip 10.0 (feature level 320), this value was | ||||
|                         always the integer ID of a system group. | ||||
|                         **Changes**: Prior to Zulip 10.0 (feature level 349), channel administrators | ||||
|                         could not unsubscribe other users if they were not an orgnization | ||||
|                         administrator or part of `can_remove_subscribers_group`. Realm administrators | ||||
|                         were not allowed to unsubscribe other users from a private channel if they | ||||
|                         were not subscribed to that channel. | ||||
| 
 | ||||
|                         Prior to Zulip 10.0 (feature level 320), this value was always the integer | ||||
|                         ID of a system group. | ||||
| 
 | ||||
|                         Before Zulip 8.0 (feature level 197), the `can_remove_subscribers_group` | ||||
|                         setting was named `can_remove_subscribers_group_id`. | ||||
| @@ -25161,10 +25168,17 @@ components: | ||||
|             Administrators can always unsubscribe others from a channel. | ||||
| 
 | ||||
|             Note that a user must also [have access](/help/channel-permissions) to a | ||||
|             channel in order to modify it. | ||||
|             channel in order to modify it. Organization administrators and channel | ||||
|             administrators are an exception to this. | ||||
| 
 | ||||
|             **Changes**: Prior to Zulip 10.0 (feature level 320), this value was | ||||
|             always the integer ID of a system group. | ||||
|             **Changes**: Prior to Zulip 10.0 (feature level 349), channel administrators | ||||
|             could not unsubscribe other users if they were not an organization | ||||
|             administrator or part of `can_remove_subscribers_group`. Realm administrators | ||||
|             were not allowed to unsubscribe other users from a private channel if they | ||||
|             were not subscribed to that channel. | ||||
| 
 | ||||
|             Prior to Zulip 10.0 (feature level 320), this value was always the integer | ||||
|             ID of a system group. | ||||
| 
 | ||||
|             Before Zulip 8.0 (feature level 197), the `can_remove_subscribers_group` | ||||
|             setting was named `can_remove_subscribers_group_id`. | ||||
|   | ||||
| @@ -3078,7 +3078,7 @@ class StreamAdminTest(ZulipTestCase): | ||||
|         If you're not an admin, you can't remove other people from streams except your own bots. | ||||
|         """ | ||||
|         result = self.attempt_unsubscribe_of_principal( | ||||
|             query_count=8, | ||||
|             query_count=7, | ||||
|             target_users=[self.example_user("cordelia")], | ||||
|             is_realm_admin=False, | ||||
|             is_subbed=True, | ||||
| @@ -3093,7 +3093,7 @@ class StreamAdminTest(ZulipTestCase): | ||||
|         those you aren't on. | ||||
|         """ | ||||
|         result = self.attempt_unsubscribe_of_principal( | ||||
|             query_count=15, | ||||
|             query_count=14, | ||||
|             target_users=[self.example_user("cordelia")], | ||||
|             is_realm_admin=True, | ||||
|             is_subbed=True, | ||||
| @@ -3120,7 +3120,7 @@ class StreamAdminTest(ZulipTestCase): | ||||
|             for name in ["cordelia", "prospero", "iago", "hamlet", "outgoing_webhook_bot"] | ||||
|         ] | ||||
|         result = self.attempt_unsubscribe_of_principal( | ||||
|             query_count=22, | ||||
|             query_count=21, | ||||
|             cache_count=8, | ||||
|             target_users=target_users, | ||||
|             is_realm_admin=True, | ||||
| @@ -3138,7 +3138,7 @@ class StreamAdminTest(ZulipTestCase): | ||||
|         are on. | ||||
|         """ | ||||
|         result = self.attempt_unsubscribe_of_principal( | ||||
|             query_count=15, | ||||
|             query_count=14, | ||||
|             target_users=[self.example_user("cordelia")], | ||||
|             is_realm_admin=True, | ||||
|             is_subbed=True, | ||||
| @@ -3155,7 +3155,7 @@ class StreamAdminTest(ZulipTestCase): | ||||
|         streams you aren't on. | ||||
|         """ | ||||
|         result = self.attempt_unsubscribe_of_principal( | ||||
|             query_count=15, | ||||
|             query_count=14, | ||||
|             target_users=[self.example_user("cordelia")], | ||||
|             is_realm_admin=True, | ||||
|             is_subbed=False, | ||||
| @@ -3169,7 +3169,7 @@ class StreamAdminTest(ZulipTestCase): | ||||
|  | ||||
|     def test_cant_remove_others_from_stream_legacy_emails(self) -> None: | ||||
|         result = self.attempt_unsubscribe_of_principal( | ||||
|             query_count=8, | ||||
|             query_count=7, | ||||
|             is_realm_admin=False, | ||||
|             is_subbed=True, | ||||
|             invite_only=False, | ||||
| @@ -3181,7 +3181,7 @@ class StreamAdminTest(ZulipTestCase): | ||||
|  | ||||
|     def test_admin_remove_others_from_stream_legacy_emails(self) -> None: | ||||
|         result = self.attempt_unsubscribe_of_principal( | ||||
|             query_count=15, | ||||
|             query_count=14, | ||||
|             target_users=[self.example_user("cordelia")], | ||||
|             is_realm_admin=True, | ||||
|             is_subbed=True, | ||||
| @@ -3195,7 +3195,7 @@ class StreamAdminTest(ZulipTestCase): | ||||
|  | ||||
|     def test_admin_remove_multiple_users_from_stream_legacy_emails(self) -> None: | ||||
|         result = self.attempt_unsubscribe_of_principal( | ||||
|             query_count=17, | ||||
|             query_count=16, | ||||
|             target_users=[self.example_user("cordelia"), self.example_user("prospero")], | ||||
|             is_realm_admin=True, | ||||
|             is_subbed=True, | ||||
| @@ -3209,7 +3209,7 @@ class StreamAdminTest(ZulipTestCase): | ||||
|  | ||||
|     def test_remove_unsubbed_user_along_with_subbed(self) -> None: | ||||
|         result = self.attempt_unsubscribe_of_principal( | ||||
|             query_count=14, | ||||
|             query_count=13, | ||||
|             target_users=[self.example_user("cordelia"), self.example_user("iago")], | ||||
|             is_realm_admin=True, | ||||
|             is_subbed=True, | ||||
| @@ -3226,7 +3226,7 @@ class StreamAdminTest(ZulipTestCase): | ||||
|         fails gracefully. | ||||
|         """ | ||||
|         result = self.attempt_unsubscribe_of_principal( | ||||
|             query_count=7, | ||||
|             query_count=6, | ||||
|             target_users=[self.example_user("cordelia")], | ||||
|             is_realm_admin=True, | ||||
|             is_subbed=False, | ||||
| @@ -3256,7 +3256,7 @@ class StreamAdminTest(ZulipTestCase): | ||||
|         webhook_bot = self.example_user("webhook_bot") | ||||
|         do_change_bot_owner(webhook_bot, bot_owner=other_user, acting_user=other_user) | ||||
|         result = self.attempt_unsubscribe_of_principal( | ||||
|             query_count=8, | ||||
|             query_count=7, | ||||
|             target_users=[webhook_bot], | ||||
|             is_realm_admin=False, | ||||
|             is_subbed=True, | ||||
| @@ -3278,24 +3278,35 @@ class StreamAdminTest(ZulipTestCase): | ||||
|         managers_group = check_add_user_group(realm, "managers", [hamlet], acting_user=hamlet) | ||||
|         add_subgroups_to_user_group(managers_group, [leadership_group], acting_user=None) | ||||
|         cordelia = self.example_user("cordelia") | ||||
|         othello = self.example_user("othello") | ||||
|         shiva = self.example_user("shiva") | ||||
|  | ||||
|         stream = self.make_stream("public_stream") | ||||
|         public_stream = self.make_stream("public_stream") | ||||
|  | ||||
|         def check_unsubscribing_user( | ||||
|             user: UserProfile, can_remove_subscribers_group: UserGroup, expect_fail: bool = False | ||||
|             user: UserProfile, | ||||
|             can_remove_subscribers_group: UserGroup, | ||||
|             expect_fail: bool = False, | ||||
|             stream_list: list[Stream] | None = None, | ||||
|             skip_changing_group_setting: bool = False, | ||||
|         ) -> None: | ||||
|             self.login_user(user) | ||||
|             self.subscribe(cordelia, stream.name) | ||||
|             do_change_stream_group_based_setting( | ||||
|                 stream, | ||||
|                 "can_remove_subscribers_group", | ||||
|                 can_remove_subscribers_group, | ||||
|                 acting_user=None, | ||||
|             ) | ||||
|             if stream_list is None: | ||||
|                 stream_list = [public_stream] | ||||
|             for stream in stream_list: | ||||
|                 self.subscribe(cordelia, stream.name) | ||||
|                 if not skip_changing_group_setting: | ||||
|                     do_change_stream_group_based_setting( | ||||
|                         stream, | ||||
|                         "can_remove_subscribers_group", | ||||
|                         can_remove_subscribers_group, | ||||
|                         acting_user=None, | ||||
|                     ) | ||||
|             stream_name_list = [stream.name for stream in stream_list] | ||||
|             result = self.client_delete( | ||||
|                 "/json/users/me/subscriptions", | ||||
|                 { | ||||
|                     "subscriptions": orjson.dumps([stream.name]).decode(), | ||||
|                     "subscriptions": orjson.dumps(stream_name_list).decode(), | ||||
|                     "principals": orjson.dumps([cordelia.id]).decode(), | ||||
|                 }, | ||||
|             ) | ||||
| @@ -3304,49 +3315,152 @@ class StreamAdminTest(ZulipTestCase): | ||||
|                 return | ||||
|  | ||||
|             json = self.assert_json_success(result) | ||||
|             self.assert_length(json["removed"], 1) | ||||
|             self.assert_length(json["removed"], len(stream_name_list)) | ||||
|             self.assert_length(json["not_removed"], 0) | ||||
|  | ||||
|         check_unsubscribing_user(self.example_user("hamlet"), leadership_group, expect_fail=True) | ||||
|         check_unsubscribing_user(self.example_user("iago"), leadership_group) | ||||
|         # Owners can always unsubscribe others even when they are not a member | ||||
|         # allowed group. | ||||
|         check_unsubscribing_user(self.example_user("desdemona"), leadership_group) | ||||
|         check_unsubscribing_user( | ||||
|             self.example_user("hamlet"), | ||||
|             leadership_group, | ||||
|             expect_fail=True, | ||||
|             stream_list=[public_stream], | ||||
|         ) | ||||
|         check_unsubscribing_user(iago, leadership_group, stream_list=[public_stream]) | ||||
|         # Owners can unsubscribe others when they are not a member of | ||||
|         # the allowed group since owners have the permission to | ||||
|         # administer all channels. | ||||
|         check_unsubscribing_user( | ||||
|             self.example_user("desdemona"), leadership_group, stream_list=[public_stream] | ||||
|         ) | ||||
|  | ||||
|         check_unsubscribing_user(self.example_user("othello"), managers_group, expect_fail=True) | ||||
|         check_unsubscribing_user(self.example_user("shiva"), managers_group) | ||||
|         check_unsubscribing_user(self.example_user("hamlet"), managers_group) | ||||
|         check_unsubscribing_user( | ||||
|             othello, | ||||
|             managers_group, | ||||
|             expect_fail=True, | ||||
|             stream_list=[public_stream], | ||||
|         ) | ||||
|         check_unsubscribing_user(shiva, managers_group, stream_list=[public_stream]) | ||||
|         check_unsubscribing_user(hamlet, managers_group, stream_list=[public_stream]) | ||||
|  | ||||
|         stream = self.make_stream("private_stream", invite_only=True) | ||||
|         self.subscribe(self.example_user("hamlet"), stream.name) | ||||
|         # Non-admins are not allowed to unsubscribe others from private streams that they | ||||
|         # are not subscribed to even if they are member of the allowed group. | ||||
|         check_unsubscribing_user(self.example_user("shiva"), leadership_group, expect_fail=True) | ||||
|         check_unsubscribing_user(self.example_user("iago"), leadership_group) | ||||
|         # Owners can always unsubscribe others even when they are not a member | ||||
|         # allowed group. | ||||
|         check_unsubscribing_user(self.example_user("desdemona"), leadership_group) | ||||
|         self.subscribe(self.example_user("shiva"), stream.name) | ||||
|         check_unsubscribing_user(self.example_user("shiva"), leadership_group) | ||||
|         private_stream = self.make_stream("private_stream", invite_only=True) | ||||
|         self.subscribe(self.example_user("hamlet"), private_stream.name) | ||||
|         # Users are not allowed to unsubscribe others from streams they | ||||
|         # don't have metadata access to even if they are a member of the | ||||
|         # allowed group. In this case, a non-admin who is not subscribed | ||||
|         # to the channel does not have metadata access to the channel. | ||||
|         check_unsubscribing_user( | ||||
|             shiva, | ||||
|             leadership_group, | ||||
|             expect_fail=True, | ||||
|             stream_list=[private_stream], | ||||
|         ) | ||||
|         check_unsubscribing_user(iago, leadership_group, stream_list=[private_stream]) | ||||
|         # Users are allowed to unsubscribe others from private streams | ||||
|         # they have access to if they are a member of the allowed | ||||
|         # group. In this case, a user with the role `owner` is | ||||
|         # subscribed to the relevant channel. | ||||
|         check_unsubscribing_user( | ||||
|             self.example_user("desdemona"), leadership_group, stream_list=[private_stream] | ||||
|         ) | ||||
|         self.subscribe(shiva, private_stream.name) | ||||
|         check_unsubscribing_user(shiva, leadership_group, stream_list=[private_stream]) | ||||
|  | ||||
|         # Test changing setting to anonymous group. | ||||
|         setting_group = self.create_or_update_anonymous_group_for_setting( | ||||
|             [hamlet], | ||||
|             [leadership_group], | ||||
|         ) | ||||
|         check_unsubscribing_user(self.example_user("othello"), setting_group, expect_fail=True) | ||||
|         check_unsubscribing_user(self.example_user("hamlet"), setting_group) | ||||
|         check_unsubscribing_user(self.example_user("iago"), setting_group) | ||||
|         check_unsubscribing_user(self.example_user("shiva"), setting_group) | ||||
|         check_unsubscribing_user( | ||||
|             othello, | ||||
|             setting_group, | ||||
|             expect_fail=True, | ||||
|             stream_list=[private_stream], | ||||
|         ) | ||||
|         check_unsubscribing_user(hamlet, setting_group, stream_list=[private_stream]) | ||||
|         check_unsubscribing_user(iago, setting_group, stream_list=[private_stream]) | ||||
|         check_unsubscribing_user(shiva, setting_group, stream_list=[private_stream]) | ||||
|  | ||||
|         # Admins can unsubscribe others even when they are not a member of the | ||||
|         # allowed group. | ||||
|         # Owners can unsubscribe others when they are not a member of | ||||
|         # the allowed group since admins have the permission to | ||||
|         # administer all channels. | ||||
|         setting_group = self.create_or_update_anonymous_group_for_setting( | ||||
|             [hamlet], | ||||
|             [], | ||||
|         ) | ||||
|         check_unsubscribing_user(self.example_user("desdemona"), setting_group) | ||||
|         check_unsubscribing_user(self.example_user("iago"), setting_group) | ||||
|         check_unsubscribing_user( | ||||
|             self.example_user("desdemona"), setting_group, stream_list=[private_stream] | ||||
|         ) | ||||
|         check_unsubscribing_user(iago, setting_group, stream_list=[private_stream]) | ||||
|  | ||||
|         # A user who is part of can_administer_channel_group should be | ||||
|         # able to unsubscribe other users even if that user is not part | ||||
|         # of can_remove_subscribers_group. And even if that user is not | ||||
|         # subscribed to the channel in question. | ||||
|         with self.assertRaises(Subscription.DoesNotExist): | ||||
|             get_subscription(private_stream.name, othello) | ||||
|         check_unsubscribing_user(othello, setting_group, expect_fail=True) | ||||
|         othello_group = self.create_or_update_anonymous_group_for_setting( | ||||
|             [othello], | ||||
|             [], | ||||
|         ) | ||||
|         private_stream_2 = self.make_stream("private_stream_2") | ||||
|         do_change_stream_group_based_setting( | ||||
|             private_stream, | ||||
|             "can_administer_channel_group", | ||||
|             othello_group, | ||||
|             acting_user=None, | ||||
|         ) | ||||
|         # If the user can only administer one of the channels, the test | ||||
|         # should fail. | ||||
|         check_unsubscribing_user( | ||||
|             othello, | ||||
|             setting_group, | ||||
|             expect_fail=True, | ||||
|             stream_list=[private_stream, private_stream_2], | ||||
|         ) | ||||
|         # User can administer both channels now. | ||||
|         do_change_stream_group_based_setting( | ||||
|             private_stream_2, | ||||
|             "can_administer_channel_group", | ||||
|             othello_group, | ||||
|             acting_user=None, | ||||
|         ) | ||||
|         check_unsubscribing_user( | ||||
|             othello, setting_group, stream_list=[private_stream, private_stream_2] | ||||
|         ) | ||||
|  | ||||
|         shiva_group = self.create_or_update_anonymous_group_for_setting( | ||||
|             [shiva], | ||||
|             [], | ||||
|         ) | ||||
|         do_change_stream_group_based_setting( | ||||
|             private_stream, | ||||
|             "can_remove_subscribers_group", | ||||
|             shiva_group, | ||||
|             acting_user=None, | ||||
|         ) | ||||
|         self.subscribe(shiva, private_stream.name) | ||||
|         self.subscribe(shiva, private_stream_2.name) | ||||
|         # If the user can is present in the remove subscribers group of | ||||
|         # only one of the channels, the test should fail. | ||||
|         check_unsubscribing_user( | ||||
|             shiva, | ||||
|             setting_group, | ||||
|             expect_fail=True, | ||||
|             stream_list=[private_stream, private_stream_2], | ||||
|             skip_changing_group_setting=True, | ||||
|         ) | ||||
|         do_change_stream_group_based_setting( | ||||
|             private_stream_2, | ||||
|             "can_remove_subscribers_group", | ||||
|             shiva_group, | ||||
|             acting_user=None, | ||||
|         ) | ||||
|         check_unsubscribing_user( | ||||
|             shiva, | ||||
|             setting_group, | ||||
|             stream_list=[private_stream, private_stream_2], | ||||
|             skip_changing_group_setting=True, | ||||
|         ) | ||||
|  | ||||
|     def test_remove_invalid_user(self) -> None: | ||||
|         """ | ||||
|   | ||||
		Reference in New Issue
	
	Block a user