예제 #1
0
def update_stream_backend(
    request: HttpRequest,
    user_profile: UserProfile,
    stream_id: int,
    description: Optional[str] = REQ(str_validator=check_capped_string(
        Stream.MAX_DESCRIPTION_LENGTH),
                                     default=None),
    is_private: Optional[bool] = REQ(json_validator=check_bool, default=None),
    is_announcement_only: Optional[bool] = REQ(json_validator=check_bool,
                                               default=None),
    stream_post_policy: Optional[int] = REQ(json_validator=check_int_in(
        Stream.STREAM_POST_POLICY_TYPES),
                                            default=None),
    history_public_to_subscribers: Optional[bool] = REQ(
        json_validator=check_bool, default=None),
    is_web_public: Optional[bool] = REQ(json_validator=check_bool,
                                        default=None),
    new_name: Optional[str] = REQ(default=None),
    message_retention_days: Optional[Union[int, str]] = REQ(
        json_validator=check_string_or_int, default=None),
) -> HttpResponse:
    # We allow realm administrators to to update the stream name and
    # description even for private streams.
    (stream, sub) = access_stream_for_delete_or_update(user_profile, stream_id)

    if message_retention_days is not None:
        if not user_profile.is_realm_owner:
            raise OrganizationOwnerRequired()
        user_profile.realm.ensure_not_on_limited_plan()
        new_message_retention_days_value = parse_message_retention_days(
            message_retention_days,
            Stream.MESSAGE_RETENTION_SPECIAL_VALUES_MAP)
        do_change_stream_message_retention_days(
            stream, user_profile, new_message_retention_days_value)

    if description is not None:
        if "\n" in description:
            # We don't allow newline characters in stream descriptions.
            description = description.replace("\n", " ")
        do_change_stream_description(stream,
                                     description,
                                     acting_user=user_profile)
    if new_name is not None:
        new_name = new_name.strip()
        if stream.name == new_name:
            raise JsonableError(_("Stream already has that name!"))
        if stream.name.lower() != new_name.lower():
            # Check that the stream name is available (unless we are
            # are only changing the casing of the stream name).
            check_stream_name_available(user_profile.realm, new_name)
        do_rename_stream(stream, new_name, user_profile)
    if is_announcement_only is not None:
        # is_announcement_only is a legacy way to specify
        # stream_post_policy.  We can probably just delete this code,
        # since we're not aware of clients that used it, but we're
        # keeping it for backwards-compatibility for now.
        stream_post_policy = Stream.STREAM_POST_POLICY_EVERYONE
        if is_announcement_only:
            stream_post_policy = Stream.STREAM_POST_POLICY_ADMINS
    if stream_post_policy is not None:
        do_change_stream_post_policy(stream,
                                     stream_post_policy,
                                     acting_user=user_profile)

    # But we require even realm administrators to be actually
    # subscribed to make a private stream public.
    if is_private is not None:
        default_stream_ids = {
            s.id
            for s in get_default_streams_for_realm(stream.realm_id)
        }
        (stream, sub) = access_stream_by_id(user_profile, stream_id)
        if is_private and stream.id in default_stream_ids:
            raise JsonableError(_("Default streams cannot be made private."))

    if is_web_public:
        # Enforce restrictions on creating web-public streams.
        if not user_profile.realm.web_public_streams_enabled():
            raise JsonableError(_("Web-public streams are not enabled."))
        if not user_profile.can_create_web_public_streams():
            raise JsonableError(_("Insufficient permission"))
        # Forbid parameter combinations that are inconsistent
        if is_private or history_public_to_subscribers is False:
            raise JsonableError(_("Invalid parameters"))

    if is_private is not None or is_web_public is not None:
        do_change_stream_permission(
            stream,
            invite_only=is_private,
            history_public_to_subscribers=history_public_to_subscribers,
            is_web_public=is_web_public,
            acting_user=user_profile,
        )
    return json_success(request)
예제 #2
0
    def test_topic_delete(self) -> None:
        initial_last_msg_id = self.get_last_message().id
        stream_name = "new_stream"
        topic_name = "new topic 2"

        # NON-ADMIN USER
        user_profile = self.example_user("hamlet")
        self.subscribe(user_profile, stream_name)

        # Send message
        stream = get_stream(stream_name, user_profile.realm)
        self.send_stream_message(user_profile,
                                 stream_name,
                                 topic_name=topic_name)
        last_msg_id = self.send_stream_message(user_profile,
                                               stream_name,
                                               topic_name=topic_name)

        # Deleting the topic
        self.login_user(user_profile)
        endpoint = "/json/streams/" + str(stream.id) + "/delete_topic"
        result = self.client_post(
            endpoint,
            {
                "topic_name": topic_name,
            },
        )
        self.assert_json_error(result, "Must be an organization administrator")
        self.assertTrue(Message.objects.filter(id=last_msg_id).exists())

        # Make stream private with limited history
        do_change_stream_permission(stream,
                                    invite_only=True,
                                    history_public_to_subscribers=False,
                                    acting_user=user_profile)

        # ADMIN USER subscribed now
        user_profile = self.example_user("iago")
        self.subscribe(user_profile, stream_name)
        self.login_user(user_profile)
        new_last_msg_id = self.send_stream_message(user_profile,
                                                   stream_name,
                                                   topic_name=topic_name)

        # Now admin deletes all messages in topic -- which should only
        # delete new_last_msg_id, i.e. the one sent since they joined.
        self.assertEqual(self.get_last_message().id, new_last_msg_id)
        result = self.client_post(
            endpoint,
            {
                "topic_name": topic_name,
            },
        )
        self.assert_json_success(result)
        self.assertTrue(Message.objects.filter(id=last_msg_id).exists())

        # Try to delete all messages in the topic again. There are no messages accessible
        # to the administrator, so this should do nothing.
        result = self.client_post(
            endpoint,
            {
                "topic_name": topic_name,
            },
        )
        self.assert_json_success(result)
        self.assertTrue(Message.objects.filter(id=last_msg_id).exists())

        # Make the stream's history public to subscribers
        do_change_stream_permission(stream,
                                    invite_only=True,
                                    history_public_to_subscribers=True,
                                    acting_user=user_profile)
        # Delete the topic should now remove all messages
        result = self.client_post(
            endpoint,
            {
                "topic_name": topic_name,
            },
        )
        self.assert_json_success(result)
        self.assertFalse(Message.objects.filter(id=last_msg_id).exists())
        self.assertTrue(
            Message.objects.filter(id=initial_last_msg_id).exists())

        # Delete again, to test the edge case of deleting an empty topic.
        result = self.client_post(
            endpoint,
            {
                "topic_name": topic_name,
            },
        )
        self.assert_json_success(result)
        self.assertFalse(Message.objects.filter(id=last_msg_id).exists())
        self.assertTrue(
            Message.objects.filter(id=initial_last_msg_id).exists())
예제 #3
0
    def test_topics_history(self) -> None:
        # verified: int(UserMessage.flags.read) == 1
        user_profile = self.example_user("iago")
        self.login_user(user_profile)
        stream_name = "Verona"

        stream = get_stream(stream_name, user_profile.realm)
        recipient = stream.recipient

        def create_test_message(topic: str) -> int:
            # TODO: Clean this up to send messages the normal way.

            hamlet = self.example_user("hamlet")
            message = Message(
                sender=hamlet,
                recipient=recipient,
                content="whatever",
                date_sent=timezone_now(),
                sending_client=get_client("whatever"),
            )
            message.set_topic_name(topic)
            message.save()

            UserMessage.objects.create(
                user_profile=user_profile,
                message=message,
                flags=0,
            )

            return message.id

        # our most recent topics are topic0, topic1, topic2

        # Create old messages with strange spellings.
        create_test_message("topic2")
        create_test_message("toPIc1")
        create_test_message("toPIc0")
        create_test_message("topic2")
        create_test_message("topic2")
        create_test_message("Topic2")

        # Create new messages
        topic2_msg_id = create_test_message("topic2")
        create_test_message("topic1")
        create_test_message("topic1")
        topic1_msg_id = create_test_message("topic1")
        topic0_msg_id = create_test_message("topic0")

        endpoint = f"/json/users/me/{stream.id}/topics"
        result = self.client_get(endpoint, {})
        self.assert_json_success(result)
        history = result.json()["topics"]

        # We only look at the most recent three topics, because
        # the prior fixture data may be unreliable.
        history = history[:3]

        self.assertEqual(
            [topic["name"] for topic in history],
            [
                "topic0",
                "topic1",
                "topic2",
            ],
        )

        self.assertEqual(
            [topic["max_id"] for topic in history],
            [
                topic0_msg_id,
                topic1_msg_id,
                topic2_msg_id,
            ],
        )

        # Now try as cordelia, who we imagine as a totally new user in
        # that she doesn't have UserMessage rows.  We should see the
        # same results for a public stream.
        self.login("cordelia")
        result = self.client_get(endpoint, {})
        self.assert_json_success(result)
        history = result.json()["topics"]

        # We only look at the most recent three topics, because
        # the prior fixture data may be unreliable.
        history = history[:3]

        self.assertEqual(
            [topic["name"] for topic in history],
            [
                "topic0",
                "topic1",
                "topic2",
            ],
        )
        self.assertIn("topic0", [topic["name"] for topic in history])

        self.assertEqual(
            [topic["max_id"] for topic in history],
            [
                topic0_msg_id,
                topic1_msg_id,
                topic2_msg_id,
            ],
        )

        # Now make stream private, but subscribe cordelia
        do_change_stream_permission(stream,
                                    invite_only=True,
                                    acting_user=self.example_user("cordelia"))
        self.subscribe(self.example_user("cordelia"), stream.name)

        result = self.client_get(endpoint, {})
        self.assert_json_success(result)
        history = result.json()["topics"]
        history = history[:3]

        # Cordelia doesn't have these recent history items when we
        # wasn't subscribed in her results.
        self.assertNotIn("topic0", [topic["name"] for topic in history])
        self.assertNotIn("topic1", [topic["name"] for topic in history])
        self.assertNotIn("topic2", [topic["name"] for topic in history])
예제 #4
0
    def test_reaction_event_scope(self) -> None:
        iago = self.example_user("iago")
        hamlet = self.example_user("hamlet")
        polonius = self.example_user("polonius")
        reaction_info = {
            "emoji_name": "smile",
        }

        # Test `invite_only` streams with `!history_public_to_subscribers` and `!is_web_public`
        stream = self.make_stream("test_reactions_stream",
                                  invite_only=True,
                                  history_public_to_subscribers=False)
        self.subscribe(iago, stream.name)
        message_before_id = self.send_stream_message(
            iago, "test_reactions_stream",
            "before subscription history private")
        self.subscribe(hamlet, stream.name)
        self.subscribe(polonius, stream.name)

        # Hamlet and Polonius joined after the message was sent, and
        # so only Iago should receive the event.
        events: List[Mapping[str, Any]] = []
        with self.tornado_redirected_to_list(events, expected_num_events=1):
            result = self.api_post(
                iago, f"/api/v1/messages/{message_before_id}/reactions",
                reaction_info)
        self.assert_json_success(result)
        event = events[0]["event"]
        self.assertEqual(event["type"], "reaction")
        event_user_ids = set(events[0]["users"])
        self.assertEqual(event_user_ids, {iago.id})
        remove = self.api_delete(
            iago, f"/api/v1/messages/{message_before_id}/reactions",
            reaction_info)
        self.assert_json_success(remove)

        # Reaction to a Message sent after subscription, should
        # trigger events for all subscribers (Iago, Hamlet and Polonius).
        message_after_id = self.send_stream_message(
            iago, "test_reactions_stream",
            "after subscription history private")
        with self.tornado_redirected_to_list(events, expected_num_events=1):
            result = self.api_post(
                iago, f"/api/v1/messages/{message_after_id}/reactions",
                reaction_info)
        self.assert_json_success(result)
        event = events[0]["event"]
        self.assertEqual(event["type"], "reaction")
        event_user_ids = set(events[0]["users"])
        self.assertEqual(event_user_ids, {iago.id, hamlet.id, polonius.id})
        remove = self.api_delete(
            iago, f"/api/v1/messages/{message_after_id}/reactions",
            reaction_info)
        self.assert_json_success(remove)

        # Make stream history public to subscribers
        do_change_stream_permission(stream,
                                    invite_only=False,
                                    history_public_to_subscribers=True,
                                    acting_user=iago)
        # Since stream history is public to subscribers, reacting to
        # message_before_id should notify all subscribers:
        # Iago and Hamlet.
        with self.tornado_redirected_to_list(events, expected_num_events=1):
            result = self.api_post(
                iago, f"/api/v1/messages/{message_before_id}/reactions",
                reaction_info)
        self.assert_json_success(result)
        event = events[0]["event"]
        self.assertEqual(event["type"], "reaction")
        event_user_ids = set(events[0]["users"])
        self.assertEqual(event_user_ids, {iago.id, hamlet.id, polonius.id})
        remove = self.api_delete(
            iago, f"/api/v1/messages/{message_before_id}/reactions",
            reaction_info)
        self.assert_json_success(remove)

        # Make stream web_public as well.
        do_change_stream_permission(stream,
                                    is_web_public=True,
                                    acting_user=iago)
        # For is_web_public streams, events even on old messages
        # should go to all subscribers, including guests like polonius.
        with self.tornado_redirected_to_list(events, expected_num_events=1):
            result = self.api_post(
                iago, f"/api/v1/messages/{message_before_id}/reactions",
                reaction_info)
        self.assert_json_success(result)
        event = events[0]["event"]
        self.assertEqual(event["type"], "reaction")
        event_user_ids = set(events[0]["users"])
        self.assertEqual(event_user_ids, {iago.id, hamlet.id, polonius.id})
        remove = self.api_delete(
            iago, f"/api/v1/messages/{message_before_id}/reactions",
            reaction_info)
        self.assert_json_success(remove)

        # Private message, event should go to both participants.
        private_message_id = self.send_personal_message(
            iago,
            hamlet,
            "hello to single receiver",
        )
        with self.tornado_redirected_to_list(events, expected_num_events=1):
            result = self.api_post(
                hamlet, f"/api/v1/messages/{private_message_id}/reactions",
                reaction_info)
        self.assert_json_success(result)
        event = events[0]["event"]
        self.assertEqual(event["type"], "reaction")
        event_user_ids = set(events[0]["users"])
        self.assertEqual(event_user_ids, {iago.id, hamlet.id})

        # Group private message; event should go to all participants.
        huddle_message_id = self.send_huddle_message(
            hamlet,
            [polonius, iago],
            "hello message to muliple receiver",
        )
        with self.tornado_redirected_to_list(events, expected_num_events=1):
            result = self.api_post(
                polonius, f"/api/v1/messages/{huddle_message_id}/reactions",
                reaction_info)
        self.assert_json_success(result)
        event = events[0]["event"]
        self.assertEqual(event["type"], "reaction")
        event_user_ids = set(events[0]["users"])
        self.assertEqual(event_user_ids, {iago.id, hamlet.id, polonius.id})