mirror of https://github.com/zulip/zulip.git
stream: Add support for sending user_id to endpoint when subscribing users.
This commit modifies the backend to accept user ids when subscribing users to streams. It also migrates all existing tests to use this API, aside from a small set of tests for the legacy API.
This commit is contained in:
parent
da4f80caaa
commit
2187c84ed9
|
@ -10,6 +10,13 @@ below features are supported.
|
||||||
|
|
||||||
## Changes in Zulip 2.2
|
## Changes in Zulip 2.2
|
||||||
|
|
||||||
|
**Feature level 9**
|
||||||
|
* [`POST users/me/subscriptions`](/api/add-subscriptions), [`DELETE
|
||||||
|
/users/me/subscriptions`](/api/remove-subscriptions): Other users to
|
||||||
|
subscribe/unsubscribe, declared in the `principals` parameter, can
|
||||||
|
now be referenced by user_id, rather than Zulip display email
|
||||||
|
address.
|
||||||
|
|
||||||
**Feature level 8**
|
**Feature level 8**
|
||||||
* [`GET /users`](/api/get-all-users), [`GET /users/{user_id}`](/api/get-user)
|
* [`GET /users`](/api/get-all-users), [`GET /users/{user_id}`](/api/get-user)
|
||||||
and [`GET /users/me`](/api/get-profile): User objects now contain the
|
and [`GET /users/me`](/api/get-profile): User objects now contain the
|
||||||
|
|
|
@ -2240,6 +2240,12 @@ def get_active_user(email: str, realm: Realm) -> UserProfile:
|
||||||
def get_user_profile_by_id_in_realm(uid: int, realm: Realm) -> UserProfile:
|
def get_user_profile_by_id_in_realm(uid: int, realm: Realm) -> UserProfile:
|
||||||
return UserProfile.objects.select_related().get(id=uid, realm=realm)
|
return UserProfile.objects.select_related().get(id=uid, realm=realm)
|
||||||
|
|
||||||
|
def get_active_user_profile_by_id_in_realm(uid: int, realm: Realm) -> UserProfile:
|
||||||
|
user_profile = get_user_profile_by_id_in_realm(uid, realm)
|
||||||
|
if not user_profile.is_active:
|
||||||
|
raise UserProfile.DoesNotExist()
|
||||||
|
return user_profile
|
||||||
|
|
||||||
def get_user_including_cross_realm(email: str, realm: Optional[Realm]=None) -> UserProfile:
|
def get_user_including_cross_realm(email: str, realm: Optional[Realm]=None) -> UserProfile:
|
||||||
if is_cross_realm_bot_email(email):
|
if is_cross_realm_bot_email(email):
|
||||||
return get_system_bot(email)
|
return get_system_bot(email)
|
||||||
|
|
|
@ -4420,13 +4420,18 @@ components:
|
||||||
name: principals
|
name: principals
|
||||||
in: query
|
in: query
|
||||||
description: |
|
description: |
|
||||||
A list of email addresses of the users that will be subscribed/unsubscribed
|
A list of user ids (preferred) or Zulip display email
|
||||||
to the streams specified in the `subscriptions` argument. If not provided, then
|
addresses of the users to be subscribed to or unsubscribed
|
||||||
the requesting user/bot is subscribed.
|
from the streams specified in the `subscriptions` argument. If
|
||||||
|
not provided, then the requesting user/bot is subscribed.
|
||||||
|
|
||||||
|
**Changes**: The integer format is new in Zulip 2.2 (Feature level 9).
|
||||||
schema:
|
schema:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
type: string
|
oneOf:
|
||||||
|
- type: string
|
||||||
|
- type: integer
|
||||||
default: []
|
default: []
|
||||||
example: ['ZOE@zulip.com']
|
example: ['ZOE@zulip.com']
|
||||||
ReactionType:
|
ReactionType:
|
||||||
|
|
|
@ -466,6 +466,9 @@ do not match the types declared in the implementation of {}.\n""".format(functio
|
||||||
subtypes.append(VARMAP[st])
|
subtypes.append(VARMAP[st])
|
||||||
self.assertTrue(len(subtypes) > 1)
|
self.assertTrue(len(subtypes) > 1)
|
||||||
sub_type = self.get_type_by_priority(subtypes)
|
sub_type = self.get_type_by_priority(subtypes)
|
||||||
|
elif "oneOf" in items.keys():
|
||||||
|
sub_type = VARMAP[element["schema"]["items"]["oneOf"][0]["type"]]
|
||||||
|
self.assertIsNotNone(sub_type)
|
||||||
else:
|
else:
|
||||||
sub_type = VARMAP[schema["items"]["type"]]
|
sub_type = VARMAP[schema["items"]["type"]]
|
||||||
self.assertIsNotNone(sub_type)
|
self.assertIsNotNone(sub_type)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from typing import Any, Dict, List, Mapping, Optional, Set
|
from typing import Any, Dict, List, Mapping, Optional, Set, Union
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
|
@ -41,9 +41,9 @@ from zerver.lib.test_runner import (
|
||||||
|
|
||||||
from zerver.models import (
|
from zerver.models import (
|
||||||
Realm, Recipient, Stream, Subscription,
|
Realm, Recipient, Stream, Subscription,
|
||||||
DefaultStream, UserProfile, get_user_profile_by_id, active_non_guest_user_ids,
|
DefaultStream, UserProfile, active_non_guest_user_ids,
|
||||||
get_default_stream_groups, flush_per_request_caches, DefaultStreamGroup,
|
get_default_stream_groups, flush_per_request_caches, DefaultStreamGroup,
|
||||||
get_client, get_realm, get_user, Message, UserMessage
|
get_client, get_realm, get_user, get_user_profile_by_id_in_realm, Message, UserMessage
|
||||||
)
|
)
|
||||||
|
|
||||||
from zerver.lib.actions import (
|
from zerver.lib.actions import (
|
||||||
|
@ -249,6 +249,8 @@ class TestCreateStreams(ZulipTestCase):
|
||||||
realm = Realm.objects.get(name='Zulip Dev')
|
realm = Realm.objects.get(name='Zulip Dev')
|
||||||
iago = self.example_user('iago')
|
iago = self.example_user('iago')
|
||||||
hamlet = self.example_user('hamlet')
|
hamlet = self.example_user('hamlet')
|
||||||
|
cordelia = self.example_user('cordelia')
|
||||||
|
aaron = self.example_user('aaron')
|
||||||
|
|
||||||
# Establish a stream for notifications.
|
# Establish a stream for notifications.
|
||||||
announce_stream = ensure_stream(realm, "announce", False, "announcements here.")
|
announce_stream = ensure_stream(realm, "announce", False, "announcements here.")
|
||||||
|
@ -269,7 +271,7 @@ class TestCreateStreams(ZulipTestCase):
|
||||||
"history_public_to_subscribers": 'true',
|
"history_public_to_subscribers": 'true',
|
||||||
"invite_only": 'false',
|
"invite_only": 'false',
|
||||||
"announce": 'true',
|
"announce": 'true',
|
||||||
"principals": '["iago@zulip.com", "AARON@zulip.com", "cordelia@zulip.com", "hamlet@zulip.com"]',
|
"principals": ujson.dumps([iago.id, aaron.id, cordelia.id, hamlet.id]),
|
||||||
"stream_post_policy": '1'
|
"stream_post_policy": '1'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -714,7 +716,7 @@ class StreamAdminTest(ZulipTestCase):
|
||||||
|
|
||||||
self.login_user(iago)
|
self.login_user(iago)
|
||||||
result = self.common_subscribe_to_streams(iago, ["private_stream"],
|
result = self.common_subscribe_to_streams(iago, ["private_stream"],
|
||||||
dict(principals=ujson.dumps([hamlet.email])),
|
dict(principals=ujson.dumps([hamlet.id])),
|
||||||
invite_only=True)
|
invite_only=True)
|
||||||
self.assert_json_success(result)
|
self.assert_json_success(result)
|
||||||
|
|
||||||
|
@ -971,7 +973,7 @@ class StreamAdminTest(ZulipTestCase):
|
||||||
|
|
||||||
def attempt_unsubscribe_of_principal(self, query_count: int, is_admin: bool=False,
|
def attempt_unsubscribe_of_principal(self, query_count: int, is_admin: bool=False,
|
||||||
is_subbed: bool=True, invite_only: bool=False,
|
is_subbed: bool=True, invite_only: bool=False,
|
||||||
other_user_subbed: bool=True,
|
other_user_subbed: bool=True, using_legacy_emails: bool=False,
|
||||||
other_sub_users: Optional[List[UserProfile]]=None) -> HttpResponse:
|
other_sub_users: Optional[List[UserProfile]]=None) -> HttpResponse:
|
||||||
|
|
||||||
# Set up the main user, who is in most cases an admin.
|
# Set up the main user, who is in most cases an admin.
|
||||||
|
@ -988,7 +990,10 @@ class StreamAdminTest(ZulipTestCase):
|
||||||
|
|
||||||
# Set up the principal to be unsubscribed.
|
# Set up the principal to be unsubscribed.
|
||||||
other_user_profile = self.example_user('cordelia')
|
other_user_profile = self.example_user('cordelia')
|
||||||
other_email = other_user_profile.email
|
if using_legacy_emails:
|
||||||
|
principal = other_user_profile.email
|
||||||
|
else:
|
||||||
|
principal = other_user_profile.id
|
||||||
|
|
||||||
# Subscribe the admin and/or principal as specified in the flags.
|
# Subscribe the admin and/or principal as specified in the flags.
|
||||||
if is_subbed:
|
if is_subbed:
|
||||||
|
@ -1003,7 +1008,7 @@ class StreamAdminTest(ZulipTestCase):
|
||||||
result = self.client_delete(
|
result = self.client_delete(
|
||||||
"/json/users/me/subscriptions",
|
"/json/users/me/subscriptions",
|
||||||
{"subscriptions": ujson.dumps([stream_name]),
|
{"subscriptions": ujson.dumps([stream_name]),
|
||||||
"principals": ujson.dumps([other_email])})
|
"principals": ujson.dumps([principal])})
|
||||||
self.assert_length(queries, query_count)
|
self.assert_length(queries, query_count)
|
||||||
|
|
||||||
# If the removal succeeded, then assert that Cordelia is no longer subscribed.
|
# If the removal succeeded, then assert that Cordelia is no longer subscribed.
|
||||||
|
@ -1059,6 +1064,21 @@ class StreamAdminTest(ZulipTestCase):
|
||||||
self.assertEqual(len(json["removed"]), 1)
|
self.assertEqual(len(json["removed"]), 1)
|
||||||
self.assertEqual(len(json["not_removed"]), 0)
|
self.assertEqual(len(json["not_removed"]), 0)
|
||||||
|
|
||||||
|
def test_cant_remove_others_from_stream_legacy_emails(self) -> None:
|
||||||
|
result = self.attempt_unsubscribe_of_principal(
|
||||||
|
query_count=3, is_admin=False, is_subbed=True, invite_only=False,
|
||||||
|
other_user_subbed=True, using_legacy_emails=True)
|
||||||
|
self.assert_json_error(
|
||||||
|
result, "This action requires administrative rights")
|
||||||
|
|
||||||
|
def test_admin_remove_others_from_stream_legacy_emails(self) -> None:
|
||||||
|
result = self.attempt_unsubscribe_of_principal(
|
||||||
|
query_count=21, is_admin=True, is_subbed=True, invite_only=False,
|
||||||
|
other_user_subbed=True, using_legacy_emails=True)
|
||||||
|
json = self.assert_json_success(result)
|
||||||
|
self.assertEqual(len(json["removed"]), 1)
|
||||||
|
self.assertEqual(len(json["not_removed"]), 0)
|
||||||
|
|
||||||
def test_create_stream_policy_setting(self) -> None:
|
def test_create_stream_policy_setting(self) -> None:
|
||||||
"""
|
"""
|
||||||
When realm.create_stream_policy setting is Realm.POLICY_MEMBERS_ONLY then
|
When realm.create_stream_policy setting is Realm.POLICY_MEMBERS_ONLY then
|
||||||
|
@ -1147,7 +1167,7 @@ class StreamAdminTest(ZulipTestCase):
|
||||||
|
|
||||||
do_set_realm_property(hamlet_user.realm, 'invite_to_stream_policy',
|
do_set_realm_property(hamlet_user.realm, 'invite_to_stream_policy',
|
||||||
Realm.POLICY_FULL_MEMBERS_ONLY)
|
Realm.POLICY_FULL_MEMBERS_ONLY)
|
||||||
cordelia_email = cordelia_user.email
|
cordelia_user_id = cordelia_user.id
|
||||||
|
|
||||||
self.login_user(hamlet_user)
|
self.login_user(hamlet_user)
|
||||||
do_change_user_role(hamlet_user, UserProfile.ROLE_REALM_ADMINISTRATOR)
|
do_change_user_role(hamlet_user, UserProfile.ROLE_REALM_ADMINISTRATOR)
|
||||||
|
@ -1162,7 +1182,7 @@ class StreamAdminTest(ZulipTestCase):
|
||||||
do_set_realm_property(hamlet_user.realm, 'waiting_period_threshold', 10)
|
do_set_realm_property(hamlet_user.realm, 'waiting_period_threshold', 10)
|
||||||
|
|
||||||
# Attempt and fail to invite Cordelia to the stream..
|
# Attempt and fail to invite Cordelia to the stream..
|
||||||
result = self.common_subscribe_to_streams(hamlet_user, stream_name, {"principals": ujson.dumps([cordelia_email])})
|
result = self.common_subscribe_to_streams(hamlet_user, stream_name, {"principals": ujson.dumps([cordelia_user_id])})
|
||||||
self.assert_json_error(result,
|
self.assert_json_error(result,
|
||||||
"Your account is too new to modify other users' subscriptions.")
|
"Your account is too new to modify other users' subscriptions.")
|
||||||
|
|
||||||
|
@ -1170,7 +1190,7 @@ class StreamAdminTest(ZulipTestCase):
|
||||||
do_set_realm_property(hamlet_user.realm, 'waiting_period_threshold', 0)
|
do_set_realm_property(hamlet_user.realm, 'waiting_period_threshold', 0)
|
||||||
|
|
||||||
# Attempt and succeed to invite Cordelia to the stream..
|
# Attempt and succeed to invite Cordelia to the stream..
|
||||||
result = self.common_subscribe_to_streams(hamlet_user, stream_name, {"principals": ujson.dumps([cordelia_email])})
|
result = self.common_subscribe_to_streams(hamlet_user, stream_name, {"principals": ujson.dumps([cordelia_user_id])})
|
||||||
self.assert_json_success(result)
|
self.assert_json_success(result)
|
||||||
|
|
||||||
# Set threshold to 20 days..
|
# Set threshold to 20 days..
|
||||||
|
@ -1182,7 +1202,7 @@ class StreamAdminTest(ZulipTestCase):
|
||||||
self.unsubscribe(cordelia_user, stream_name[0])
|
self.unsubscribe(cordelia_user, stream_name[0])
|
||||||
|
|
||||||
# Attempt and succeed to invite Aaron to the stream..
|
# Attempt and succeed to invite Aaron to the stream..
|
||||||
result = self.common_subscribe_to_streams(hamlet_user, stream_name, {"principals": ujson.dumps([cordelia_email])})
|
result = self.common_subscribe_to_streams(hamlet_user, stream_name, {"principals": ujson.dumps([cordelia_user_id])})
|
||||||
self.assert_json_success(result)
|
self.assert_json_success(result)
|
||||||
|
|
||||||
def test_remove_already_not_subbed(self) -> None:
|
def test_remove_already_not_subbed(self) -> None:
|
||||||
|
@ -1210,10 +1230,10 @@ class StreamAdminTest(ZulipTestCase):
|
||||||
|
|
||||||
result = self.client_delete("/json/users/me/subscriptions",
|
result = self.client_delete("/json/users/me/subscriptions",
|
||||||
{"subscriptions": ujson.dumps([stream_name]),
|
{"subscriptions": ujson.dumps([stream_name]),
|
||||||
"principals": ujson.dumps(["baduser@zulip.com"])})
|
"principals": ujson.dumps([99])})
|
||||||
self.assert_json_error(
|
self.assert_json_error(
|
||||||
result,
|
result,
|
||||||
"User not authorized to execute queries on behalf of 'baduser@zulip.com'",
|
"User not authorized to execute queries on behalf of '99'",
|
||||||
status_code=403)
|
status_code=403)
|
||||||
|
|
||||||
class DefaultStreamTest(ZulipTestCase):
|
class DefaultStreamTest(ZulipTestCase):
|
||||||
|
@ -1948,7 +1968,7 @@ class SubscriptionRestApiTest(ZulipTestCase):
|
||||||
'principals': ujson.dumps([{}]),
|
'principals': ujson.dumps([{}]),
|
||||||
}
|
}
|
||||||
result = self.api_patch(user, "/api/v1/users/me/subscriptions", request)
|
result = self.api_patch(user, "/api/v1/users/me/subscriptions", request)
|
||||||
self.assert_json_error(result, 'principals[0] is not a string')
|
self.assert_json_error(result, 'principals is not an allowed_type')
|
||||||
|
|
||||||
def test_bad_delete_parameters(self) -> None:
|
def test_bad_delete_parameters(self) -> None:
|
||||||
user = self.example_user('hamlet')
|
user = self.example_user('hamlet')
|
||||||
|
@ -2172,7 +2192,7 @@ class SubscriptionAPITest(ZulipTestCase):
|
||||||
invite_streams,
|
invite_streams,
|
||||||
extra_post_data={
|
extra_post_data={
|
||||||
'announce': 'true',
|
'announce': 'true',
|
||||||
'principals': '["%s"]' % (self.user_profile.email,)
|
'principals': ujson.dumps([self.user_profile.id])
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
self.assert_json_success(result)
|
self.assert_json_success(result)
|
||||||
|
@ -2200,7 +2220,7 @@ class SubscriptionAPITest(ZulipTestCase):
|
||||||
invite_streams,
|
invite_streams,
|
||||||
extra_post_data=dict(
|
extra_post_data=dict(
|
||||||
announce='true',
|
announce='true',
|
||||||
principals='["%s"]' % (self.user_profile.email,)
|
principals= ujson.dumps([self.user_profile.id])
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
self.assert_json_success(result)
|
self.assert_json_success(result)
|
||||||
|
@ -2268,7 +2288,7 @@ class SubscriptionAPITest(ZulipTestCase):
|
||||||
invite_streams,
|
invite_streams,
|
||||||
extra_post_data={
|
extra_post_data={
|
||||||
'announce': 'true',
|
'announce': 'true',
|
||||||
'principals': '["%s"]' % (self.user_profile.email,)
|
'principals': ujson.dumps([self.user_profile.id])
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
self.assert_json_success(result)
|
self.assert_json_success(result)
|
||||||
|
@ -2349,14 +2369,14 @@ class SubscriptionAPITest(ZulipTestCase):
|
||||||
enough.
|
enough.
|
||||||
"""
|
"""
|
||||||
user_profile = self.example_user("cordelia")
|
user_profile = self.example_user("cordelia")
|
||||||
invitee_email = user_profile.email
|
invitee_user_id = user_profile.id
|
||||||
realm = user_profile.realm
|
realm = user_profile.realm
|
||||||
|
|
||||||
do_set_realm_property(realm, "create_stream_policy", Realm.POLICY_MEMBERS_ONLY)
|
do_set_realm_property(realm, "create_stream_policy", Realm.POLICY_MEMBERS_ONLY)
|
||||||
do_set_realm_property(realm, "invite_to_stream_policy",
|
do_set_realm_property(realm, "invite_to_stream_policy",
|
||||||
Realm.POLICY_ADMINS_ONLY)
|
Realm.POLICY_ADMINS_ONLY)
|
||||||
result = self.common_subscribe_to_streams(
|
result = self.common_subscribe_to_streams(
|
||||||
self.test_user, ['stream1'], {"principals": ujson.dumps([invitee_email])})
|
self.test_user, ['stream1'], {"principals": ujson.dumps([invitee_user_id])})
|
||||||
self.assert_json_error(
|
self.assert_json_error(
|
||||||
result, "Only administrators can modify other users' subscriptions.")
|
result, "Only administrators can modify other users' subscriptions.")
|
||||||
|
|
||||||
|
@ -2364,7 +2384,7 @@ class SubscriptionAPITest(ZulipTestCase):
|
||||||
Realm.POLICY_MEMBERS_ONLY)
|
Realm.POLICY_MEMBERS_ONLY)
|
||||||
result = self.common_subscribe_to_streams(
|
result = self.common_subscribe_to_streams(
|
||||||
self.test_user, ['stream2'], {"principals": ujson.dumps([
|
self.test_user, ['stream2'], {"principals": ujson.dumps([
|
||||||
self.test_email, invitee_email])})
|
self.test_user.id, invitee_user_id])})
|
||||||
self.assert_json_success(result)
|
self.assert_json_success(result)
|
||||||
self.unsubscribe(user_profile, "stream2")
|
self.unsubscribe(user_profile, "stream2")
|
||||||
|
|
||||||
|
@ -2372,13 +2392,13 @@ class SubscriptionAPITest(ZulipTestCase):
|
||||||
Realm.POLICY_FULL_MEMBERS_ONLY)
|
Realm.POLICY_FULL_MEMBERS_ONLY)
|
||||||
do_set_realm_property(realm, "waiting_period_threshold", 100000)
|
do_set_realm_property(realm, "waiting_period_threshold", 100000)
|
||||||
result = self.common_subscribe_to_streams(
|
result = self.common_subscribe_to_streams(
|
||||||
self.test_user, ['stream2'], {"principals": ujson.dumps([invitee_email])})
|
self.test_user, ['stream2'], {"principals": ujson.dumps([invitee_user_id])})
|
||||||
self.assert_json_error(
|
self.assert_json_error(
|
||||||
result, "Your account is too new to modify other users' subscriptions.")
|
result, "Your account is too new to modify other users' subscriptions.")
|
||||||
|
|
||||||
do_set_realm_property(realm, "waiting_period_threshold", 0)
|
do_set_realm_property(realm, "waiting_period_threshold", 0)
|
||||||
result = self.common_subscribe_to_streams(
|
result = self.common_subscribe_to_streams(
|
||||||
self.test_user, ['stream2'], {"principals": ujson.dumps([invitee_email])})
|
self.test_user, ['stream2'], {"principals": ujson.dumps([invitee_user_id])})
|
||||||
self.assert_json_success(result)
|
self.assert_json_success(result)
|
||||||
|
|
||||||
def test_can_subscribe_other_users(self) -> None:
|
def test_can_subscribe_other_users(self) -> None:
|
||||||
|
@ -2416,7 +2436,7 @@ class SubscriptionAPITest(ZulipTestCase):
|
||||||
self.assert_json_error(result,
|
self.assert_json_error(result,
|
||||||
"Invalid stream name '%s'" % (invalid_stream_name,))
|
"Invalid stream name '%s'" % (invalid_stream_name,))
|
||||||
|
|
||||||
def assert_adding_subscriptions_for_principal(self, invitee_email: str, invitee_realm: Realm,
|
def assert_adding_subscriptions_for_principal(self, invitee_data: Union[str, int], invitee_realm: Realm,
|
||||||
streams: List[str], invite_only: bool=False) -> None:
|
streams: List[str], invite_only: bool=False) -> None:
|
||||||
"""
|
"""
|
||||||
Calling POST /json/users/me/subscriptions on behalf of another principal (for
|
Calling POST /json/users/me/subscriptions on behalf of another principal (for
|
||||||
|
@ -2424,7 +2444,10 @@ class SubscriptionAPITest(ZulipTestCase):
|
||||||
those subscriptions and send a message to the subscribee notifying
|
those subscriptions and send a message to the subscribee notifying
|
||||||
them.
|
them.
|
||||||
"""
|
"""
|
||||||
other_profile = get_user(invitee_email, invitee_realm)
|
if isinstance(invitee_data, str):
|
||||||
|
other_profile = get_user(invitee_data, invitee_realm)
|
||||||
|
else:
|
||||||
|
other_profile = get_user_profile_by_id_in_realm(invitee_data, invitee_realm)
|
||||||
current_streams = self.get_streams(other_profile)
|
current_streams = self.get_streams(other_profile)
|
||||||
self.assertIsInstance(other_profile, UserProfile)
|
self.assertIsInstance(other_profile, UserProfile)
|
||||||
self.assertNotEqual(len(current_streams), 0) # necessary for full test coverage
|
self.assertNotEqual(len(current_streams), 0) # necessary for full test coverage
|
||||||
|
@ -2432,8 +2455,8 @@ class SubscriptionAPITest(ZulipTestCase):
|
||||||
streams_to_sub = streams[:1] # just add one, to make the message easier to check
|
streams_to_sub = streams[:1] # just add one, to make the message easier to check
|
||||||
streams_to_sub.extend(current_streams)
|
streams_to_sub.extend(current_streams)
|
||||||
self.helper_check_subs_before_and_after_add(streams_to_sub,
|
self.helper_check_subs_before_and_after_add(streams_to_sub,
|
||||||
{"principals": ujson.dumps([invitee_email])}, streams[:1],
|
{"principals": ujson.dumps([invitee_data])}, streams[:1],
|
||||||
current_streams, invitee_email, streams_to_sub,
|
current_streams, other_profile.email, streams_to_sub,
|
||||||
invitee_realm, invite_only=invite_only)
|
invitee_realm, invite_only=invite_only)
|
||||||
|
|
||||||
# verify that a welcome message was sent to the stream
|
# verify that a welcome message was sent to the stream
|
||||||
|
@ -2455,7 +2478,7 @@ class SubscriptionAPITest(ZulipTestCase):
|
||||||
self.common_subscribe_to_streams(
|
self.common_subscribe_to_streams(
|
||||||
self.test_user,
|
self.test_user,
|
||||||
streams_to_sub,
|
streams_to_sub,
|
||||||
dict(principals=ujson.dumps([user1.email, user2.email])),
|
dict(principals=ujson.dumps([user1.id, user2.id])),
|
||||||
)
|
)
|
||||||
self.assert_length(queries, 40)
|
self.assert_length(queries, 40)
|
||||||
|
|
||||||
|
@ -2483,7 +2506,7 @@ class SubscriptionAPITest(ZulipTestCase):
|
||||||
self.common_subscribe_to_streams(
|
self.common_subscribe_to_streams(
|
||||||
self.test_user,
|
self.test_user,
|
||||||
streams_to_sub,
|
streams_to_sub,
|
||||||
dict(principals=ujson.dumps([self.test_email])),
|
dict(principals=ujson.dumps([self.test_user.id])),
|
||||||
)
|
)
|
||||||
self.assert_length(queries, 14)
|
self.assert_length(queries, 14)
|
||||||
|
|
||||||
|
@ -2669,19 +2692,19 @@ class SubscriptionAPITest(ZulipTestCase):
|
||||||
othello = self.example_user('othello')
|
othello = self.example_user('othello')
|
||||||
cordelia = self.example_user('cordelia')
|
cordelia = self.example_user('cordelia')
|
||||||
iago = self.example_user('iago')
|
iago = self.example_user('iago')
|
||||||
orig_emails_to_subscribe = [self.test_email, othello.email]
|
orig_user_ids_to_subscribe = [self.test_user.id, othello.id]
|
||||||
self.common_subscribe_to_streams(
|
self.common_subscribe_to_streams(
|
||||||
self.test_user,
|
self.test_user,
|
||||||
streams_to_sub,
|
streams_to_sub,
|
||||||
dict(principals=ujson.dumps(orig_emails_to_subscribe)))
|
dict(principals=ujson.dumps(orig_user_ids_to_subscribe)))
|
||||||
|
|
||||||
new_emails_to_subscribe = [iago.email, cordelia.email]
|
new_user_ids_to_subscribe = [iago.id, cordelia.id]
|
||||||
events: List[Mapping[str, Any]] = []
|
events: List[Mapping[str, Any]] = []
|
||||||
with tornado_redirected_to_list(events):
|
with tornado_redirected_to_list(events):
|
||||||
self.common_subscribe_to_streams(
|
self.common_subscribe_to_streams(
|
||||||
self.test_user,
|
self.test_user,
|
||||||
streams_to_sub,
|
streams_to_sub,
|
||||||
dict(principals=ujson.dumps(new_emails_to_subscribe)),
|
dict(principals=ujson.dumps(new_user_ids_to_subscribe)),
|
||||||
)
|
)
|
||||||
|
|
||||||
add_peer_events = [events[2], events[3]]
|
add_peer_events = [events[2], events[3]]
|
||||||
|
@ -2689,16 +2712,13 @@ class SubscriptionAPITest(ZulipTestCase):
|
||||||
self.assertEqual(add_peer_event['event']['type'], 'subscription')
|
self.assertEqual(add_peer_event['event']['type'], 'subscription')
|
||||||
self.assertEqual(add_peer_event['event']['op'], 'peer_add')
|
self.assertEqual(add_peer_event['event']['op'], 'peer_add')
|
||||||
event_sent_to_ids = add_peer_event['users']
|
event_sent_to_ids = add_peer_event['users']
|
||||||
sent_emails = [
|
for user_id in new_user_ids_to_subscribe:
|
||||||
get_user_profile_by_id(user_id).email
|
|
||||||
for user_id in event_sent_to_ids]
|
|
||||||
for email in new_emails_to_subscribe:
|
|
||||||
# Make sure new users subscribed to stream is not in
|
# Make sure new users subscribed to stream is not in
|
||||||
# peer_add event recipient list
|
# peer_add event recipient list
|
||||||
self.assertNotIn(email, sent_emails)
|
self.assertNotIn(user_id, event_sent_to_ids)
|
||||||
for old_user in orig_emails_to_subscribe:
|
for old_user in orig_user_ids_to_subscribe:
|
||||||
# Check non new users are in peer_add event recipient list.
|
# Check non new users are in peer_add event recipient list.
|
||||||
self.assertIn(old_user, sent_emails)
|
self.assertIn(old_user, event_sent_to_ids)
|
||||||
|
|
||||||
def test_users_getting_remove_peer_event(self) -> None:
|
def test_users_getting_remove_peer_event(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -2793,7 +2813,7 @@ class SubscriptionAPITest(ZulipTestCase):
|
||||||
self.common_subscribe_to_streams(
|
self.common_subscribe_to_streams(
|
||||||
mit_user,
|
mit_user,
|
||||||
stream_names,
|
stream_names,
|
||||||
dict(principals=ujson.dumps([mit_user.email])),
|
dict(principals=ujson.dumps([mit_user.id])),
|
||||||
subdomain="zephyr",
|
subdomain="zephyr",
|
||||||
)
|
)
|
||||||
# Make sure Zephyr mirroring realms such as MIT do not get
|
# Make sure Zephyr mirroring realms such as MIT do not get
|
||||||
|
@ -2822,7 +2842,7 @@ class SubscriptionAPITest(ZulipTestCase):
|
||||||
self.common_subscribe_to_streams(
|
self.common_subscribe_to_streams(
|
||||||
self.test_user,
|
self.test_user,
|
||||||
streams,
|
streams,
|
||||||
dict(principals=ujson.dumps([self.test_email])),
|
dict(principals=ujson.dumps([self.test_user.id])),
|
||||||
)
|
)
|
||||||
# Make sure we don't make O(streams) queries
|
# Make sure we don't make O(streams) queries
|
||||||
self.assert_length(queries, 16)
|
self.assert_length(queries, 16)
|
||||||
|
@ -2831,6 +2851,12 @@ class SubscriptionAPITest(ZulipTestCase):
|
||||||
"""
|
"""
|
||||||
You can subscribe other people to streams.
|
You can subscribe other people to streams.
|
||||||
"""
|
"""
|
||||||
|
invitee = self.example_user("iago")
|
||||||
|
current_streams = self.get_streams(invitee)
|
||||||
|
invite_streams = self.make_random_stream_names(current_streams)
|
||||||
|
self.assert_adding_subscriptions_for_principal(invitee.id, invitee.realm, invite_streams)
|
||||||
|
|
||||||
|
def test_subscriptions_add_for_principal_legacy_emails(self) -> None:
|
||||||
invitee = self.example_user("iago")
|
invitee = self.example_user("iago")
|
||||||
current_streams = self.get_streams(invitee)
|
current_streams = self.get_streams(invitee)
|
||||||
invite_streams = self.make_random_stream_names(current_streams)
|
invite_streams = self.make_random_stream_names(current_streams)
|
||||||
|
@ -2842,7 +2868,7 @@ class SubscriptionAPITest(ZulipTestCase):
|
||||||
"""
|
"""
|
||||||
target_profile = self.example_user("cordelia")
|
target_profile = self.example_user("cordelia")
|
||||||
post_data = dict(
|
post_data = dict(
|
||||||
principals=ujson.dumps([target_profile.email])
|
principals=ujson.dumps([target_profile.id])
|
||||||
)
|
)
|
||||||
result = self.common_subscribe_to_streams(self.test_user, "Verona", post_data)
|
result = self.common_subscribe_to_streams(self.test_user, "Verona", post_data)
|
||||||
self.assert_json_success(result)
|
self.assert_json_success(result)
|
||||||
|
@ -2851,7 +2877,7 @@ class SubscriptionAPITest(ZulipTestCase):
|
||||||
result = self.common_subscribe_to_streams(self.test_user, "Denmark", post_data)
|
result = self.common_subscribe_to_streams(self.test_user, "Denmark", post_data)
|
||||||
self.assert_json_error(
|
self.assert_json_error(
|
||||||
result,
|
result,
|
||||||
"User not authorized to execute queries on behalf of '{}'".format(target_profile.email),
|
"User not authorized to execute queries on behalf of '{}'".format(target_profile.id),
|
||||||
status_code=403)
|
status_code=403)
|
||||||
|
|
||||||
def test_subscriptions_add_for_principal_invite_only(self) -> None:
|
def test_subscriptions_add_for_principal_invite_only(self) -> None:
|
||||||
|
@ -2861,7 +2887,7 @@ class SubscriptionAPITest(ZulipTestCase):
|
||||||
invitee = self.example_user("iago")
|
invitee = self.example_user("iago")
|
||||||
current_streams = self.get_streams(invitee)
|
current_streams = self.get_streams(invitee)
|
||||||
invite_streams = self.make_random_stream_names(current_streams)
|
invite_streams = self.make_random_stream_names(current_streams)
|
||||||
self.assert_adding_subscriptions_for_principal(invitee.email, invitee.realm, invite_streams,
|
self.assert_adding_subscriptions_for_principal(invitee.id, invitee.realm, invite_streams,
|
||||||
invite_only=True)
|
invite_only=True)
|
||||||
|
|
||||||
def test_non_ascii_subscription_for_principal(self) -> None:
|
def test_non_ascii_subscription_for_principal(self) -> None:
|
||||||
|
@ -2870,9 +2896,9 @@ class SubscriptionAPITest(ZulipTestCase):
|
||||||
non-ASCII characters.
|
non-ASCII characters.
|
||||||
"""
|
"""
|
||||||
iago = self.example_user('iago')
|
iago = self.example_user('iago')
|
||||||
self.assert_adding_subscriptions_for_principal(iago.email, get_realm('zulip'), ["hümbüǵ"])
|
self.assert_adding_subscriptions_for_principal(iago.id, get_realm('zulip'), ["hümbüǵ"])
|
||||||
|
|
||||||
def test_subscription_add_invalid_principal(self) -> None:
|
def test_subscription_add_invalid_principal_legacy_emails(self) -> None:
|
||||||
"""
|
"""
|
||||||
Calling subscribe on behalf of a principal that does not exist
|
Calling subscribe on behalf of a principal that does not exist
|
||||||
should return a JSON error.
|
should return a JSON error.
|
||||||
|
@ -2887,13 +2913,23 @@ class SubscriptionAPITest(ZulipTestCase):
|
||||||
self.assert_json_error(result, "User not authorized to execute queries on behalf of '%s'"
|
self.assert_json_error(result, "User not authorized to execute queries on behalf of '%s'"
|
||||||
% (invalid_principal,), status_code=403)
|
% (invalid_principal,), status_code=403)
|
||||||
|
|
||||||
|
def test_subscription_add_invalid_principal(self) -> None:
|
||||||
|
invalid_principal = 999
|
||||||
|
invalid_principal_realm = get_realm("zulip")
|
||||||
|
with self.assertRaises(UserProfile.DoesNotExist):
|
||||||
|
get_user_profile_by_id_in_realm(invalid_principal, invalid_principal_realm)
|
||||||
|
result = self.common_subscribe_to_streams(self.test_user, self.streams,
|
||||||
|
{"principals": ujson.dumps([invalid_principal])})
|
||||||
|
self.assert_json_error(result, "User not authorized to execute queries on behalf of '%s'"
|
||||||
|
% (invalid_principal,), status_code=403)
|
||||||
|
|
||||||
def test_subscription_add_principal_other_realm(self) -> None:
|
def test_subscription_add_principal_other_realm(self) -> None:
|
||||||
"""
|
"""
|
||||||
Calling subscribe on behalf of a principal in another realm
|
Calling subscribe on behalf of a principal in another realm
|
||||||
should return a JSON error.
|
should return a JSON error.
|
||||||
"""
|
"""
|
||||||
profile = self.mit_user('starnine')
|
profile = self.mit_user('starnine')
|
||||||
principal = profile.email
|
principal = profile.id
|
||||||
# verify that principal exists (thus, the reason for the error is the cross-realming)
|
# verify that principal exists (thus, the reason for the error is the cross-realming)
|
||||||
self.assertIsInstance(profile, UserProfile)
|
self.assertIsInstance(profile, UserProfile)
|
||||||
result = self.common_subscribe_to_streams(self.test_user, self.streams,
|
result = self.common_subscribe_to_streams(self.test_user, self.streams,
|
||||||
|
@ -3072,7 +3108,7 @@ class SubscriptionAPITest(ZulipTestCase):
|
||||||
inherited from the global notification settings.
|
inherited from the global notification settings.
|
||||||
"""
|
"""
|
||||||
user_profile = self.example_user('iago')
|
user_profile = self.example_user('iago')
|
||||||
invitee_email = user_profile.email
|
invitee_user_id = user_profile.id
|
||||||
invitee_realm = user_profile.realm
|
invitee_realm = user_profile.realm
|
||||||
user_profile.enable_stream_desktop_notifications = True
|
user_profile.enable_stream_desktop_notifications = True
|
||||||
user_profile.enable_stream_push_notifications = True
|
user_profile.enable_stream_push_notifications = True
|
||||||
|
@ -3081,7 +3117,7 @@ class SubscriptionAPITest(ZulipTestCase):
|
||||||
user_profile.save()
|
user_profile.save()
|
||||||
current_stream = self.get_streams(user_profile)[0]
|
current_stream = self.get_streams(user_profile)[0]
|
||||||
invite_streams = self.make_random_stream_names([current_stream])
|
invite_streams = self.make_random_stream_names([current_stream])
|
||||||
self.assert_adding_subscriptions_for_principal(invitee_email, invitee_realm, invite_streams)
|
self.assert_adding_subscriptions_for_principal(invitee_user_id, invitee_realm, invite_streams)
|
||||||
subscription = self.get_subscription(user_profile, invite_streams[0])
|
subscription = self.get_subscription(user_profile, invite_streams[0])
|
||||||
|
|
||||||
with mock.patch('zerver.models.Recipient.__str__', return_value='recip'):
|
with mock.patch('zerver.models.Recipient.__str__', return_value='recip'):
|
||||||
|
@ -3217,7 +3253,7 @@ class SubscriptionAPITest(ZulipTestCase):
|
||||||
self.common_subscribe_to_streams(
|
self.common_subscribe_to_streams(
|
||||||
self.test_user,
|
self.test_user,
|
||||||
[new_streams[0]],
|
[new_streams[0]],
|
||||||
dict(principals=ujson.dumps([user1.email, user2.email])),
|
dict(principals=ujson.dumps([user1.id, user2.id])),
|
||||||
)
|
)
|
||||||
self.assert_length(queries, 40)
|
self.assert_length(queries, 40)
|
||||||
|
|
||||||
|
@ -3226,7 +3262,7 @@ class SubscriptionAPITest(ZulipTestCase):
|
||||||
self.common_subscribe_to_streams(
|
self.common_subscribe_to_streams(
|
||||||
self.test_user,
|
self.test_user,
|
||||||
[new_streams[1]],
|
[new_streams[1]],
|
||||||
dict(principals=ujson.dumps([user1.email, user2.email])),
|
dict(principals=ujson.dumps([user1.id, user2.id])),
|
||||||
invite_only=True,
|
invite_only=True,
|
||||||
)
|
)
|
||||||
self.assert_length(queries, 40)
|
self.assert_length(queries, 40)
|
||||||
|
@ -3241,7 +3277,7 @@ class SubscriptionAPITest(ZulipTestCase):
|
||||||
[new_streams[2]],
|
[new_streams[2]],
|
||||||
dict(
|
dict(
|
||||||
announce='true',
|
announce='true',
|
||||||
principals=ujson.dumps([user1.email, user2.email])
|
principals=ujson.dumps([user1.id, user2.id])
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self.assert_length(queries, 52)
|
self.assert_length(queries, 52)
|
||||||
|
@ -3497,7 +3533,7 @@ class InviteOnlyStreamTest(ZulipTestCase):
|
||||||
self.login_user(hamlet)
|
self.login_user(hamlet)
|
||||||
result = self.common_subscribe_to_streams(
|
result = self.common_subscribe_to_streams(
|
||||||
hamlet, [stream_name],
|
hamlet, [stream_name],
|
||||||
extra_post_data={'principals': ujson.dumps([othello.email])})
|
extra_post_data={'principals': ujson.dumps([othello.id])})
|
||||||
self.assert_json_success(result)
|
self.assert_json_success(result)
|
||||||
json = result.json()
|
json = result.json()
|
||||||
self.assertEqual(json["subscribed"], {othello.email: [stream_name]})
|
self.assertEqual(json["subscribed"], {othello.email: [stream_name]})
|
||||||
|
@ -3577,9 +3613,9 @@ class GetSubscribersTest(ZulipTestCase):
|
||||||
self.make_stream(stream_name)
|
self.make_stream(stream_name)
|
||||||
|
|
||||||
users_to_subscribe = [
|
users_to_subscribe = [
|
||||||
self.user_profile.email,
|
self.user_profile.id,
|
||||||
self.example_user("othello").email,
|
self.example_user("othello").id,
|
||||||
self.example_user("cordelia").email,
|
self.example_user("cordelia").id,
|
||||||
]
|
]
|
||||||
ret = self.common_subscribe_to_streams(
|
ret = self.common_subscribe_to_streams(
|
||||||
self.user_profile,
|
self.user_profile,
|
||||||
|
@ -3609,7 +3645,7 @@ class GetSubscribersTest(ZulipTestCase):
|
||||||
ret = self.common_subscribe_to_streams(
|
ret = self.common_subscribe_to_streams(
|
||||||
self.user_profile,
|
self.user_profile,
|
||||||
["stream_invite_only_1"],
|
["stream_invite_only_1"],
|
||||||
dict(principals=ujson.dumps([self.user_profile.email])),
|
dict(principals=ujson.dumps([self.user_profile.id])),
|
||||||
invite_only=True)
|
invite_only=True)
|
||||||
self.assert_json_success(ret)
|
self.assert_json_success(ret)
|
||||||
|
|
||||||
|
@ -3644,8 +3680,8 @@ class GetSubscribersTest(ZulipTestCase):
|
||||||
"""
|
"""
|
||||||
realm = get_realm("zulip")
|
realm = get_realm("zulip")
|
||||||
users_to_subscribe = [
|
users_to_subscribe = [
|
||||||
self.example_user("othello").email,
|
self.example_user("othello").id,
|
||||||
self.example_user("cordelia").email,
|
self.example_user("cordelia").id,
|
||||||
]
|
]
|
||||||
|
|
||||||
public_streams = [
|
public_streams = [
|
||||||
|
@ -3794,8 +3830,8 @@ class GetSubscribersTest(ZulipTestCase):
|
||||||
"""
|
"""
|
||||||
# Subscribe only ourself because invites are disabled on mit.edu
|
# Subscribe only ourself because invites are disabled on mit.edu
|
||||||
mit_user_profile = self.mit_user('starnine')
|
mit_user_profile = self.mit_user('starnine')
|
||||||
email = mit_user_profile.email
|
user_id = mit_user_profile.id
|
||||||
users_to_subscribe = [email, self.mit_email("espuser")]
|
users_to_subscribe = [user_id, self.mit_user("espuser").id]
|
||||||
for email in users_to_subscribe:
|
for email in users_to_subscribe:
|
||||||
stream = self.subscribe(mit_user_profile, "mit_stream")
|
stream = self.subscribe(mit_user_profile, "mit_stream")
|
||||||
self.assertTrue(stream.is_in_zephyr_realm)
|
self.assertTrue(stream.is_in_zephyr_realm)
|
||||||
|
|
|
@ -30,7 +30,7 @@ from zerver.lib.validator import check_string, check_int, check_list, check_dict
|
||||||
check_bool, check_variable_type, check_capped_string, check_color, check_dict_only, \
|
check_bool, check_variable_type, check_capped_string, check_color, check_dict_only, \
|
||||||
check_int_in, to_non_negative_int
|
check_int_in, to_non_negative_int
|
||||||
from zerver.models import UserProfile, Stream, Realm, UserMessage, \
|
from zerver.models import UserProfile, Stream, Realm, UserMessage, \
|
||||||
get_system_bot, get_active_user
|
get_system_bot, get_active_user, get_active_user_profile_by_id_in_realm
|
||||||
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
import ujson
|
import ujson
|
||||||
|
@ -40,16 +40,19 @@ class PrincipalError(JsonableError):
|
||||||
data_fields = ['principal']
|
data_fields = ['principal']
|
||||||
http_status_code = 403
|
http_status_code = 403
|
||||||
|
|
||||||
def __init__(self, principal: str) -> None:
|
def __init__(self, principal: Union[int, str]) -> None:
|
||||||
self.principal: str = principal
|
self.principal: Union[int, str] = principal
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def msg_format() -> str:
|
def msg_format() -> str:
|
||||||
return _("User not authorized to execute queries on behalf of '{principal}'")
|
return _("User not authorized to execute queries on behalf of '{principal}'")
|
||||||
|
|
||||||
def principal_to_user_profile(agent: UserProfile, principal: str) -> UserProfile:
|
def principal_to_user_profile(agent: UserProfile, principal: Union[str, int]) -> UserProfile:
|
||||||
try:
|
try:
|
||||||
return get_active_user(principal, agent.realm)
|
if isinstance(principal, str):
|
||||||
|
return get_active_user(principal, agent.realm)
|
||||||
|
else:
|
||||||
|
return get_active_user_profile_by_id_in_realm(principal, agent.realm)
|
||||||
except UserProfile.DoesNotExist:
|
except UserProfile.DoesNotExist:
|
||||||
# We have to make sure we don't leak information about which users
|
# We have to make sure we don't leak information about which users
|
||||||
# are registered for Zulip in a different realm. We could do
|
# are registered for Zulip in a different realm. We could do
|
||||||
|
@ -249,11 +252,20 @@ def compose_views(
|
||||||
def remove_subscriptions_backend(
|
def remove_subscriptions_backend(
|
||||||
request: HttpRequest, user_profile: UserProfile,
|
request: HttpRequest, user_profile: UserProfile,
|
||||||
streams_raw: Iterable[str]=REQ("subscriptions", validator=check_list(check_string)),
|
streams_raw: Iterable[str]=REQ("subscriptions", validator=check_list(check_string)),
|
||||||
principals: Optional[Iterable[str]]=REQ(validator=check_list(check_string), default=None),
|
principals: Optional[Union[List[str], List[int]]]=REQ(validator=check_variable_type([
|
||||||
|
check_list(check_string), check_list(check_int)]), default=None),
|
||||||
) -> HttpResponse:
|
) -> HttpResponse:
|
||||||
|
|
||||||
removing_someone_else = principals and \
|
# TODO: Extract this as a function to improve readability
|
||||||
set(principals) != {user_profile.email}
|
removing_someone_else = False
|
||||||
|
if principals is not None and len(principals) > 0:
|
||||||
|
if len(principals) == 1:
|
||||||
|
if isinstance(principals[0], int):
|
||||||
|
removing_someone_else = principals[0] != user_profile.id
|
||||||
|
else:
|
||||||
|
removing_someone_else = principals[0] != user_profile.email
|
||||||
|
else:
|
||||||
|
removing_someone_else = True
|
||||||
|
|
||||||
if removing_someone_else and not user_profile.is_realm_admin:
|
if removing_someone_else and not user_profile.is_realm_admin:
|
||||||
# You can only unsubscribe other people from a stream if you are a realm
|
# You can only unsubscribe other people from a stream if you are a realm
|
||||||
|
@ -315,7 +327,8 @@ def add_subscriptions_backend(
|
||||||
Stream.STREAM_POST_POLICY_TYPES), default=Stream.STREAM_POST_POLICY_EVERYONE),
|
Stream.STREAM_POST_POLICY_TYPES), default=Stream.STREAM_POST_POLICY_EVERYONE),
|
||||||
history_public_to_subscribers: Optional[bool]=REQ(validator=check_bool, default=None),
|
history_public_to_subscribers: Optional[bool]=REQ(validator=check_bool, default=None),
|
||||||
announce: bool=REQ(validator=check_bool, default=False),
|
announce: bool=REQ(validator=check_bool, default=False),
|
||||||
principals: List[str]=REQ(validator=check_list(check_string), default=[]),
|
principals: List[Union[str, int]]=REQ(validator=check_variable_type([
|
||||||
|
check_list(check_string), check_list(check_int)]), default=[]),
|
||||||
authorization_errors_fatal: bool=REQ(validator=check_bool, default=True),
|
authorization_errors_fatal: bool=REQ(validator=check_bool, default=True),
|
||||||
) -> HttpResponse:
|
) -> HttpResponse:
|
||||||
stream_dicts = []
|
stream_dicts = []
|
||||||
|
|
Loading…
Reference in New Issue