Example #1
0
    def test_check_variable_type(self):
        # type: () -> None
        x = 5  # type: Any
        self.assertEqual(check_variable_type([check_string, check_int])('x', x), None)

        x = 'x'
        self.assertEqual(check_variable_type([check_string, check_int])('x', x), None)

        x = [{}]
        self.assertEqual(check_variable_type([check_string, check_int])('x', x), 'x is not an allowed_type')
Example #2
0
    def test_check_variable_type(self):
        # type: () -> None
        x = 5  # type: Any
        self.assertEqual(check_variable_type([check_string, check_int])('x', x), None)

        x = 'x'
        self.assertEqual(check_variable_type([check_string, check_int])('x', x), None)

        x = [{}]
        self.assertEqual(check_variable_type([check_string, check_int])('x', x), 'x is not an allowed_type')
Example #3
0
def update_subscription_properties_backend(
    request: HttpRequest,
    user_profile: UserProfile,
    subscription_data: List[Dict[str, Any]] = REQ(validator=check_list(
        check_dict([("stream_id", check_int), ("property", check_string),
                    ("value",
                     check_variable_type([check_string, check_bool]))]))),
) -> HttpResponse:
    """
    This is the entry point to changing subscription properties. This
    is a bulk endpoint: requestors always provide a subscription_data
    list containing dictionaries for each stream of interest.

    Requests are of the form:

    [{"stream_id": "1", "property": "is_muted", "value": False},
     {"stream_id": "1", "property": "color", "value": "#c2c2c2"}]
    """
    property_converters = {
        "color": check_color,
        "in_home_view": check_bool,
        "is_muted": check_bool,
        "desktop_notifications": check_bool,
        "audible_notifications": check_bool,
        "push_notifications": check_bool,
        "email_notifications": check_bool,
        "pin_to_top": check_bool,
        "wildcard_mentions_notify": check_bool
    }
    response_data = []

    for change in subscription_data:
        stream_id = change["stream_id"]
        property = change["property"]
        value = change["value"]

        if property not in property_converters:
            return json_error(
                _("Unknown subscription property: %s") % (property, ))

        (stream, recipient, sub) = access_stream_by_id(user_profile, stream_id)
        if sub is None:
            return json_error(
                _("Not subscribed to stream id %d") % (stream_id, ))

        property_conversion = property_converters[property](property, value)
        if property_conversion:
            return json_error(property_conversion)

        do_change_subscription_property(user_profile, sub, stream, property,
                                        value)

        response_data.append({
            'stream_id': stream_id,
            'property': property,
            'value': value
        })

    return json_success({"subscription_data": response_data})
Example #4
0
def json_subscription_property(
    request,
    user_profile,
    subscription_data=REQ(validator=check_list(
        check_dict([("stream", check_string), ("property", check_string),
                    ("value",
                     check_variable_type([check_string, check_bool]))])))):
    # type: (HttpRequest, UserProfile, List[Dict[str, Any]]) -> HttpResponse
    """
    This is the entry point to changing subscription properties. This
    is a bulk endpoint: requestors always provide a subscription_data
    list containing dictionaries for each stream of interest.

    Requests are of the form:

    [{"stream": "devel", "property": "in_home_view", "value": False},
     {"stream": "devel", "property": "color", "value": "#c2c2c2"}]
    """
    if request.method != "POST":
        return json_error(_("Invalid verb"))

    property_converters = {
        "color": check_string,
        "in_home_view": check_bool,
        "desktop_notifications": check_bool,
        "audible_notifications": check_bool,
        "pin_to_top": check_bool
    }
    response_data = []

    for change in subscription_data:
        stream_name = change["stream"]
        property = change["property"]
        value = change["value"]

        if property not in property_converters:
            return json_error(
                _("Unknown subscription property: %s") % (property, ))

        (stream, recipient,
         sub) = access_stream_by_name(user_profile, stream_name)
        if sub is None:
            return json_error(
                _("Not subscribed to stream %s") % (stream_name, ))

        property_conversion = property_converters[property](property, value)
        if property_conversion:
            return json_error(property_conversion)

        do_change_subscription_property(user_profile, sub, stream, property,
                                        value)

        response_data.append({
            'stream': stream_name,
            'property': property,
            'value': value
        })

    return json_success({"subscription_data": response_data})
Example #5
0
def json_subscription_property(
    request,
    user_profile,
    subscription_data=REQ(
        validator=check_list(
            check_dict(
                [
                    ("stream", check_string),
                    ("property", check_string),
                    ("value", check_variable_type([check_string, check_bool])),
                ]
            )
        )
    ),
):
    # type: (HttpRequest, UserProfile, List[Dict[str, Any]]) -> HttpResponse
    """
    This is the entry point to changing subscription properties. This
    is a bulk endpoint: requestors always provide a subscription_data
    list containing dictionaries for each stream of interest.

    Requests are of the form:

    [{"stream": "devel", "property": "in_home_view", "value": False},
     {"stream": "devel", "property": "color", "value": "#c2c2c2"}]
    """
    if request.method != "POST":
        return json_error(_("Invalid verb"))

    property_converters = {
        "color": check_string,
        "in_home_view": check_bool,
        "desktop_notifications": check_bool,
        "audible_notifications": check_bool,
        "pin_to_top": check_bool,
    }
    response_data = []

    for change in subscription_data:
        stream_name = change["stream"]
        property = change["property"]
        value = change["value"]

        if property not in property_converters:
            return json_error(_("Unknown subscription property: %s") % (property,))

        sub = get_subscription_or_die(stream_name, user_profile)[0]

        property_conversion = property_converters[property](property, value)
        if property_conversion:
            return json_error(property_conversion)

        do_change_subscription_property(user_profile, sub, stream_name, property, value)

        response_data.append({"stream": stream_name, "property": property, "value": value})

    return json_success({"subscription_data": response_data})
Example #6
0
def remove_subscriptions_backend(
    request: HttpRequest,
    user_profile: UserProfile,
    streams_raw: Iterable[str] = REQ("subscriptions",
                                     validator=check_list(check_string)),
    principals: Optional[Union[List[str], List[int]]] = REQ(
        validator=check_variable_type(
            [check_list(check_string),
             check_list(check_int)]),
        default=None),
) -> HttpResponse:

    # TODO: Extract this as a function to improve readability
    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:  # nocoverage # TODO: Fix me.
            removing_someone_else = True

    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
        # admin (whether the stream is public or private).
        return json_error(_("This action requires administrative rights"))

    streams_as_dict = []
    for stream_name in streams_raw:
        streams_as_dict.append({"name": stream_name.strip()})

    streams, __ = list_to_streams(streams_as_dict, user_profile)

    if principals:
        people_to_unsub = {
            principal_to_user_profile(user_profile, principal)
            for principal in principals
        }
    else:
        people_to_unsub = {user_profile}

    result: Dict[str, List[str]] = dict(removed=[], not_removed=[])
    (removed,
     not_subscribed) = bulk_remove_subscriptions(people_to_unsub,
                                                 streams,
                                                 request.client,
                                                 acting_user=user_profile)

    for (subscriber, removed_stream) in removed:
        result["removed"].append(removed_stream.name)
    for (subscriber, not_subscribed_stream) in not_subscribed:
        result["not_removed"].append(not_subscribed_stream.name)

    return json_success(result)
Example #7
0
def json_subscription_property(
    request,
    user_profile,
    subscription_data=REQ(validator=check_list(
        check_dict([["stream", check_string], ["property", check_string],
                    ["value",
                     check_variable_type([check_string, check_bool])]])))):
    """
    This is the entry point to changing subscription properties. This
    is a bulk endpoint: requestors always provide a subscription_data
    list containing dictionaries for each stream of interest.

    Requests are of the form:

    [{"stream": "devel", "property": "in_home_view", "value": False},
     {"stream": "devel", "property": "color", "value": "#c2c2c2"}]
    """
    if request.method != "POST":
        return json_error("Invalid verb")

    property_converters = {
        "color": check_string,
        "in_home_view": check_bool,
        "desktop_notifications": check_bool,
        "audible_notifications": check_bool
    }
    response_data = []

    for change in subscription_data:
        stream_name = change["stream"]
        property = change["property"]
        value = change["value"]

        if property not in property_converters:
            return json_error("Unknown subscription property: %s" %
                              (property, ))

        sub = get_subscription_or_die(stream_name, user_profile)[0]

        property_conversion = property_converters[property](property, value)
        if property_conversion:
            return json_error(property_conversion)

        do_change_subscription_property(user_profile, sub, stream_name,
                                        property, value)

        response_data.append({
            'stream': stream_name,
            'property': property,
            'value': value
        })

    return json_success({"subscription_data": response_data})
Example #8
0
def update_subscription_properties_backend(
        request: HttpRequest, user_profile: UserProfile,
        subscription_data: List[Dict[str, Any]]=REQ(
            validator=check_list(
                check_dict([("stream_id", check_int),
                            ("property", check_string),
                            ("value", check_variable_type([check_string, check_bool]))])
            )
        ),
) -> HttpResponse:
    """
    This is the entry point to changing subscription properties. This
    is a bulk endpoint: requestors always provide a subscription_data
    list containing dictionaries for each stream of interest.

    Requests are of the form:

    [{"stream_id": "1", "property": "in_home_view", "value": False},
     {"stream_id": "1", "property": "color", "value": "#c2c2c2"}]
    """
    property_converters = {"color": check_color, "in_home_view": check_bool,
                           "desktop_notifications": check_bool,
                           "audible_notifications": check_bool,
                           "push_notifications": check_bool,
                           "email_notifications": check_bool,
                           "pin_to_top": check_bool}
    response_data = []

    for change in subscription_data:
        stream_id = change["stream_id"]
        property = change["property"]
        value = change["value"]

        if property not in property_converters:
            return json_error(_("Unknown subscription property: %s") % (property,))

        (stream, recipient, sub) = access_stream_by_id(user_profile, stream_id)
        if sub is None:
            return json_error(_("Not subscribed to stream id %d") % (stream_id,))

        property_conversion = property_converters[property](property, value)
        if property_conversion:
            return json_error(property_conversion)

        do_change_subscription_property(user_profile, sub, stream,
                                        property, value)

        response_data.append({'stream_id': stream_id,
                              'property': property,
                              'value': value})

    return json_success({"subscription_data": response_data})
Example #9
0
def add_subscriptions_backend(
        request: HttpRequest, user_profile: UserProfile,
        streams_raw: Iterable[Dict[str, str]]=REQ(
            "subscriptions", validator=check_list(check_dict_only(
                [('name', check_string)], optional_keys=[
                    ('color', check_color),
                    ('description', check_capped_string(Stream.MAX_DESCRIPTION_LENGTH)),
                ]),
            )),
        invite_only: bool=REQ(validator=check_bool, default=False),
        stream_post_policy: int=REQ(validator=check_int_in(
            Stream.STREAM_POST_POLICY_TYPES), default=Stream.STREAM_POST_POLICY_EVERYONE),
        history_public_to_subscribers: Optional[bool]=REQ(validator=check_bool, default=None),
        announce: bool=REQ(validator=check_bool, default=False),
        principals: Sequence[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),
) -> HttpResponse:
    stream_dicts = []
    color_map = {}
    for stream_dict in streams_raw:
        # 'color' field is optional
        # check for its presence in the streams_raw first
        if 'color' in stream_dict:
            color_map[stream_dict['name']] = stream_dict['color']
        if 'description' in stream_dict:
            # We don't allow newline characters in stream descriptions.
            stream_dict['description'] = stream_dict['description'].replace("\n", " ")

        stream_dict_copy: Dict[str, Any] = {}
        for field in stream_dict:
            stream_dict_copy[field] = stream_dict[field]
        # Strip the stream name here.
        stream_dict_copy['name'] = stream_dict_copy['name'].strip()
        stream_dict_copy["invite_only"] = invite_only
        stream_dict_copy["stream_post_policy"] = stream_post_policy
        stream_dict_copy["history_public_to_subscribers"] = history_public_to_subscribers
        stream_dicts.append(stream_dict_copy)

    # Validation of the streams arguments, including enforcement of
    # can_create_streams policy and check_stream_name policy is inside
    # list_to_streams.
    existing_streams, created_streams = \
        list_to_streams(stream_dicts, user_profile, autocreate=True)
    authorized_streams, unauthorized_streams = \
        filter_stream_authorization(user_profile, existing_streams)
    if len(unauthorized_streams) > 0 and authorization_errors_fatal:
        return json_error(_("Unable to access stream ({stream_name}).").format(
            stream_name=unauthorized_streams[0].name,
        ))
    # Newly created streams are also authorized for the creator
    streams = authorized_streams + created_streams

    if len(principals) > 0:
        if user_profile.realm.is_zephyr_mirror_realm and not all(stream.invite_only for stream in streams):
            return json_error(_("You can only invite other Zephyr mirroring users to private streams."))
        if not user_profile.can_subscribe_other_users():
            if user_profile.realm.invite_to_stream_policy == Realm.POLICY_ADMINS_ONLY:
                return json_error(_("Only administrators can modify other users' subscriptions."))
            # Realm.POLICY_MEMBERS_ONLY only fails if the
            # user is a guest, which happens in the decorator above.
            assert user_profile.realm.invite_to_stream_policy == \
                Realm.POLICY_FULL_MEMBERS_ONLY
            return json_error(_("Your account is too new to modify other users' subscriptions."))
        subscribers = {principal_to_user_profile(user_profile, principal) for principal in principals}
    else:
        subscribers = {user_profile}

    (subscribed, already_subscribed) = bulk_add_subscriptions(streams, subscribers,
                                                              acting_user=user_profile, color_map=color_map)

    # We can assume unique emails here for now, but we should eventually
    # convert this function to be more id-centric.
    email_to_user_profile: Dict[str, UserProfile] = dict()

    result: Dict[str, Any] = dict(subscribed=defaultdict(list), already_subscribed=defaultdict(list))
    for (subscriber, stream) in subscribed:
        result["subscribed"][subscriber.email].append(stream.name)
        email_to_user_profile[subscriber.email] = subscriber
    for (subscriber, stream) in already_subscribed:
        result["already_subscribed"][subscriber.email].append(stream.name)

    bots = {subscriber.email: subscriber.is_bot for subscriber in subscribers}

    newly_created_stream_names = {s.name for s in created_streams}

    # Inform the user if someone else subscribed them to stuff,
    # or if a new stream was created with the "announce" option.
    notifications = []
    if len(principals) > 0 and result["subscribed"]:
        for email, subscribed_stream_names in result["subscribed"].items():
            if email == user_profile.email:
                # Don't send a Zulip if you invited yourself.
                continue
            if bots[email]:
                # Don't send invitation Zulips to bots
                continue

            # For each user, we notify them about newly subscribed streams, except for
            # streams that were newly created.
            notify_stream_names = set(subscribed_stream_names) - newly_created_stream_names

            if not notify_stream_names:
                continue

            msg = you_were_just_subscribed_message(
                acting_user=user_profile,
                stream_names=notify_stream_names,
            )

            sender = get_system_bot(settings.NOTIFICATION_BOT)
            notifications.append(
                internal_prep_private_message(
                    realm=user_profile.realm,
                    sender=sender,
                    recipient_user=email_to_user_profile[email],
                    content=msg))

    if announce and len(created_streams) > 0:
        notifications_stream = user_profile.realm.get_notifications_stream()
        if notifications_stream is not None:
            if len(created_streams) > 1:
                content = _("@_**%(user_name)s|%(user_id)d** created the following streams: %(stream_str)s.")
            else:
                content = _("@_**%(user_name)s|%(user_id)d** created a new stream %(stream_str)s.")
            content = content % {
                'user_name': user_profile.full_name,
                'user_id': user_profile.id,
                'stream_str': ", ".join(f'#**{s.name}**' for s in created_streams)}

            sender = get_system_bot(settings.NOTIFICATION_BOT)
            topic = _('new streams')

            notifications.append(
                internal_prep_stream_message(
                    realm=user_profile.realm,
                    sender=sender,
                    stream=notifications_stream,
                    topic=topic,
                    content=content,
                ),
            )

    if not user_profile.realm.is_zephyr_mirror_realm and len(created_streams) > 0:
        sender = get_system_bot(settings.NOTIFICATION_BOT)
        for stream in created_streams:
            notifications.append(
                internal_prep_stream_message(
                    realm=user_profile.realm,
                    sender=sender,
                    stream=stream,
                    topic=Realm.STREAM_EVENTS_NOTIFICATION_TOPIC,
                    content=_('Stream created by @_**{user_name}|{user_id}**.').format(
                        user_name=user_profile.full_name,
                        user_id=user_profile.id,
                    ),
                ),
            )

    if len(notifications) > 0:
        do_send_messages(notifications, mark_as_read=[user_profile.id])

    result["subscribed"] = dict(result["subscribed"])
    result["already_subscribed"] = dict(result["already_subscribed"])
    if not authorization_errors_fatal:
        result["unauthorized"] = [s.name for s in unauthorized_streams]
    return json_success(result)