Beispiel #1
0
def remote_server_post_analytics(request: HttpRequest,
                                 entity: Union[UserProfile, RemoteZulipServer],
                                 realm_counts: List[Dict[str, Any]]=REQ(
                                     validator=check_list(check_dict_only([
                                         ('property', check_string),
                                         ('realm', check_int),
                                         ('id', check_int),
                                         ('end_time', check_float),
                                         ('subgroup', check_none_or(check_string)),
                                         ('value', check_int),
                                     ]))),
                                 installation_counts: List[Dict[str, Any]]=REQ(
                                     validator=check_list(check_dict_only([
                                         ('property', check_string),
                                         ('id', check_int),
                                         ('end_time', check_float),
                                         ('subgroup', check_none_or(check_string)),
                                         ('value', check_int),
                                     ])))) -> HttpResponse:
    validate_entity(entity)
    server = cast(RemoteZulipServer, entity)

    validate_count_stats(server, RemoteRealmCount, realm_counts)
    validate_count_stats(server, RemoteInstallationCount, installation_counts)

    BATCH_SIZE = 1000
    while len(realm_counts) > 0:
        batch = realm_counts[0:BATCH_SIZE]
        realm_counts = realm_counts[BATCH_SIZE:]

        objects_to_create = []
        for item in batch:
            objects_to_create.append(RemoteRealmCount(
                property=item['property'],
                realm_id=item['realm'],
                remote_id=item['id'],
                server=server,
                end_time=datetime.datetime.fromtimestamp(item['end_time'], tz=timezone_utc),
                subgroup=item['subgroup'],
                value=item['value']))
        RemoteRealmCount.objects.bulk_create(objects_to_create)

    while len(installation_counts) > 0:
        batch = installation_counts[0:BATCH_SIZE]
        installation_counts = installation_counts[BATCH_SIZE:]

        objects_to_create = []
        for item in batch:
            objects_to_create.append(RemoteInstallationCount(
                property=item['property'],
                remote_id=item['id'],
                server=server,
                end_time=datetime.datetime.fromtimestamp(item['end_time'], tz=timezone_utc),
                subgroup=item['subgroup'],
                value=item['value']))
        RemoteInstallationCount.objects.bulk_create(objects_to_create)
    return json_success()
Beispiel #2
0
def remote_server_post_analytics(request: HttpRequest,
                                 entity: Union[UserProfile, RemoteZulipServer],
                                 realm_counts: List[Dict[str, Any]]=REQ(
                                     validator=check_list(check_dict_only([
                                         ('property', check_string),
                                         ('realm', check_int),
                                         ('id', check_int),
                                         ('end_time', check_float),
                                         ('subgroup', check_none_or(check_string)),
                                         ('value', check_int),
                                     ]))),
                                 installation_counts: List[Dict[str, Any]]=REQ(
                                     validator=check_list(check_dict_only([
                                         ('property', check_string),
                                         ('id', check_int),
                                         ('end_time', check_float),
                                         ('subgroup', check_none_or(check_string)),
                                         ('value', check_int),
                                     ])))) -> HttpResponse:
    validate_entity(entity)
    server = cast(RemoteZulipServer, entity)

    validate_count_stats(server, RemoteRealmCount, realm_counts)
    validate_count_stats(server, RemoteInstallationCount, installation_counts)

    BATCH_SIZE = 1000
    while len(realm_counts) > 0:
        batch = realm_counts[0:BATCH_SIZE]
        realm_counts = realm_counts[BATCH_SIZE:]

        objects_to_create = []
        for item in batch:
            objects_to_create.append(RemoteRealmCount(
                property=item['property'],
                realm_id=item['realm'],
                remote_id=item['id'],
                server=server,
                end_time=datetime.datetime.fromtimestamp(item['end_time'], tz=timezone_utc),
                subgroup=item['subgroup'],
                value=item['value']))
        RemoteRealmCount.objects.bulk_create(objects_to_create)

    while len(installation_counts) > 0:
        batch = installation_counts[0:BATCH_SIZE]
        installation_counts = installation_counts[BATCH_SIZE:]

        objects_to_create = []
        for item in batch:
            objects_to_create.append(RemoteInstallationCount(
                property=item['property'],
                remote_id=item['id'],
                server=server,
                end_time=datetime.datetime.fromtimestamp(item['end_time'], tz=timezone_utc),
                subgroup=item['subgroup'],
                value=item['value']))
        RemoteInstallationCount.objects.bulk_create(objects_to_create)
    return json_success()
    def test_check_dict(self):
        # type: () -> None
        keys = [
            ('names', check_list(check_string)),
            ('city', check_string),
        ]  # type: List[Tuple[str, Validator]]

        x = {
            'names': ['alice', 'bob'],
            'city': 'Boston',
        }  # type: Any
        error = check_dict(keys)('x', x)
        self.assertEqual(error, None)

        x = 999
        error = check_dict(keys)('x', x)
        self.assertEqual(error, 'x is not a dict')

        x = {}
        error = check_dict(keys)('x', x)
        self.assertEqual(error, 'names key is missing from x')

        x = {
            'names': ['alice', 'bob', {}]
        }
        error = check_dict(keys)('x', x)
        self.assertEqual(error, 'x["names"][2] is not a string')

        x = {
            'names': ['alice', 'bob'],
            'city': 5
        }
        error = check_dict(keys)('x', x)
        self.assertEqual(error, 'x["city"] is not a string')

        # test dict_only
        x = {
            'names': ['alice', 'bob'],
            'city': 'Boston',
        }
        error = check_dict_only(keys)('x', x)
        self.assertEqual(error, None)

        x = {
            'names': ['alice', 'bob'],
            'city': 'Boston',
            'state': 'Massachusetts',
        }
        error = check_dict_only(keys)('x', x)
        self.assertEqual(error, 'Unexpected arguments: state')
Beispiel #4
0
    def test_check_dict(self):
        # type: () -> None
        keys = [
            ('names', check_list(check_string)),
            ('city', check_string),
        ]  # type: List[Tuple[str, Validator]]

        x = {
            'names': ['alice', 'bob'],
            'city': 'Boston',
        }  # type: Any
        error = check_dict(keys)('x', x)
        self.assertEqual(error, None)

        x = 999
        error = check_dict(keys)('x', x)
        self.assertEqual(error, 'x is not a dict')

        x = {}
        error = check_dict(keys)('x', x)
        self.assertEqual(error, 'names key is missing from x')

        x = {
            'names': ['alice', 'bob', {}]
        }
        error = check_dict(keys)('x', x)
        self.assertEqual(error, 'x["names"][2] is not a string')

        x = {
            'names': ['alice', 'bob'],
            'city': 5
        }
        error = check_dict(keys)('x', x)
        self.assertEqual(error, 'x["city"] is not a string')

        # test dict_only
        x = {
            'names': ['alice', 'bob'],
            'city': 'Boston',
        }
        error = check_dict_only(keys)('x', x)
        self.assertEqual(error, None)

        x = {
            'names': ['alice', 'bob'],
            'city': 'Boston',
            'state': 'Massachusetts',
        }
        error = check_dict_only(keys)('x', x)
        self.assertEqual(error, 'Unexpected arguments: state')
Beispiel #5
0
def check_events_dict(
    required_keys: Sequence[Tuple[str, Validator[object]]],
    optional_keys: Sequence[Tuple[str, Validator[object]]] = [],
) -> Validator[Dict[str, object]]:
    """
    This is just a tiny wrapper on check_dict, but it provides
    some minor benefits:

        - mark clearly that the schema is for a Zulip event
        - make sure there's a type field
        - add id field automatically
        - sanity check that we have no duplicate keys (we
          should just make check_dict do that, eventually)

    """
    rkeys = [key[0] for key in required_keys]
    okeys = [key[0] for key in optional_keys]
    keys = rkeys + okeys
    assert len(keys) == len(set(keys))
    assert "type" in rkeys
    assert "id" not in keys
    return check_dict_only(
        required_keys=list(required_keys) + [("id", check_int)],
        optional_keys=optional_keys,
    )
Beispiel #6
0
def validate_external_account_field_data(field_data: ProfileFieldData) -> ProfileFieldData:
    field_validator = check_dict_only(
        [("subtype", check_required_string)],
        [("url_pattern", check_external_account_url_pattern)],
    )
    field_validator("field_data", field_data)

    field_subtype = field_data.get("subtype")
    if field_subtype not in DEFAULT_EXTERNAL_ACCOUNTS.keys():
        if field_subtype == "custom":
            if "url_pattern" not in field_data.keys():
                raise ValidationError(_("Custom external account must define URL pattern"))
        else:
            raise ValidationError(_("Invalid external account type"))

    return field_data
Beispiel #7
0
def update_user_custom_profile_data(
    request: HttpRequest,
    user_profile: UserProfile,
    data: List[Dict[str, Union[int, ProfileDataElementValue]]] = REQ(
        json_validator=check_list(
            check_dict_only([
                ("id", check_int),
                ("value", check_union([check_string,
                                       check_list(check_int)])),
            ]), )),
) -> HttpResponse:

    validate_user_custom_profile_data(user_profile.realm.id, data)
    do_update_user_custom_profile_data_if_changed(user_profile, data)
    # We need to call this explicitly otherwise constraints are not check
    return json_success()
def validate_external_account_field_data(field_data: ProfileFieldData) -> Optional[str]:
    field_validator = check_dict_only(
        [('subtype', check_required_string)],
        [('url_pattern', check_external_account_url_pattern)],
    )
    error = field_validator('field_data', field_data)
    if error:
        return error

    field_subtype = field_data.get('subtype')
    if field_subtype not in DEFAULT_EXTERNAL_ACCOUNTS.keys():
        if field_subtype == "custom":
            if 'url_pattern' not in field_data.keys():
                return _("Custom external account must define url pattern")
        else:
            return _("Invalid external account type")

    return None
Beispiel #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),
        is_announcement_only: bool=REQ(validator=check_bool, default=False),
        history_public_to_subscribers: Optional[bool]=REQ(validator=check_bool, default=None),
        announce: bool=REQ(validator=check_bool, default=False),
        principals: List[str]=REQ(validator=check_list(check_string), 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 = {}  # type: 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["is_announcement_only"] = is_announcement_only
        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 (%s).") % 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():
            return json_error(_("Your account is too new to modify other users' subscriptions."))
        subscribers = set(principal_to_user_profile(user_profile, principal) for principal in principals)
    else:
        subscribers = set([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()  # type: Dict[str, UserProfile]

    result = dict(subscribed=defaultdict(list), already_subscribed=defaultdict(list))  # type: Dict[str, Any]
    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 = dict((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 and settings.NOTIFICATION_BOT is not None:
        notifications_stream = user_profile.realm.get_notifications_stream()
        if notifications_stream is not None:
            if len(created_streams) > 1:
                stream_strs = ", ".join('#**%s**' % s.name for s in created_streams)
                stream_msg = "the following streams: %s" % (stream_strs,)
            else:
                stream_msg = "a new stream #**%s**." % created_streams[0].name
            msg = ("@_**%s|%d** just created %s" % (user_profile.full_name, user_profile.id, stream_msg))

            sender = get_system_bot(settings.NOTIFICATION_BOT)
            topic = 'Streams'

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

    if not user_profile.realm.is_zephyr_mirror_realm:
        for stream in created_streams:
            notifications.append(prep_stream_welcome_message(stream))

    if len(notifications) > 0:
        do_send_messages(notifications)

    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)
Beispiel #10
0
    user_profile: UserProfile,
    include_subscribers: bool=REQ(validator=check_bool, default=False),
) -> HttpResponse:
    subscribed, _ = gather_subscriptions(
        user_profile, include_subscribers=include_subscribers,
    )
    return json_success({"subscriptions": subscribed})

FuncKwargPair = Tuple[Callable[..., HttpResponse], Dict[str, Union[int, Iterable[Any]]]]

add_subscriptions_schema = check_list(
    check_dict_only(
        required_keys=[
            ('name', check_string)
        ],
        optional_keys=[
            ('color', check_color),
            ('description', check_capped_string(Stream.MAX_DESCRIPTION_LENGTH)),
        ],
    ),
)

remove_subscriptions_schema = check_list(check_string)

@has_request_variables
def update_subscriptions_backend(
        request: HttpRequest, user_profile: UserProfile,
        delete: Iterable[str]=REQ(validator=remove_subscriptions_schema, default=[]),
        add: Iterable[Mapping[str, Any]]=REQ(validator=add_subscriptions_schema, default=[]),
) -> HttpResponse:
    if not add and not delete:
Beispiel #11
0
def remote_server_post_analytics(
    request: HttpRequest,
    entity: Union[UserProfile, RemoteZulipServer],
    realm_counts: List[Dict[str, Any]] = REQ(json_validator=check_list(
        check_dict_only([
            ("property", check_string),
            ("realm", check_int),
            ("id", check_int),
            ("end_time", check_float),
            ("subgroup", check_none_or(check_string)),
            ("value", check_int),
        ]))),
    installation_counts: List[Dict[str, Any]] = REQ(json_validator=check_list(
        check_dict_only([
            ("property", check_string),
            ("id", check_int),
            ("end_time", check_float),
            ("subgroup", check_none_or(check_string)),
            ("value", check_int),
        ]))),
    realmauditlog_rows: Optional[List[Dict[str, Any]]] = REQ(
        json_validator=check_list(
            check_dict_only([
                ("id", check_int),
                ("realm", check_int),
                ("event_time", check_float),
                ("backfilled", check_bool),
                ("extra_data", check_none_or(check_string)),
                ("event_type", check_int),
            ])),
        default=None,
    ),
) -> HttpResponse:
    server = validate_entity(entity)

    validate_incoming_table_data(server, RemoteRealmCount, realm_counts, True)
    validate_incoming_table_data(server, RemoteInstallationCount,
                                 installation_counts, True)
    if realmauditlog_rows is not None:
        validate_incoming_table_data(server, RemoteRealmAuditLog,
                                     realmauditlog_rows)

    row_objects = [
        RemoteRealmCount(
            property=row["property"],
            realm_id=row["realm"],
            remote_id=row["id"],
            server=server,
            end_time=datetime.datetime.fromtimestamp(row["end_time"],
                                                     tz=datetime.timezone.utc),
            subgroup=row["subgroup"],
            value=row["value"],
        ) for row in realm_counts
    ]
    batch_create_table_data(server, RemoteRealmCount, row_objects)

    row_objects = [
        RemoteInstallationCount(
            property=row["property"],
            remote_id=row["id"],
            server=server,
            end_time=datetime.datetime.fromtimestamp(row["end_time"],
                                                     tz=datetime.timezone.utc),
            subgroup=row["subgroup"],
            value=row["value"],
        ) for row in installation_counts
    ]
    batch_create_table_data(server, RemoteInstallationCount, row_objects)

    if realmauditlog_rows is not None:
        row_objects = [
            RemoteRealmAuditLog(
                realm_id=row["realm"],
                remote_id=row["id"],
                server=server,
                event_time=datetime.datetime.fromtimestamp(
                    row["event_time"], tz=datetime.timezone.utc),
                backfilled=row["backfilled"],
                extra_data=row["extra_data"],
                event_type=row["event_type"],
            ) for row in realmauditlog_rows
        ]
        batch_create_table_data(server, RemoteRealmAuditLog, row_objects)

    return json_success()
Beispiel #12
0
                               allow_bots=True,
                               for_admin=True)
    if target.is_bot:
        assert target.bot_type is not None
        check_bot_creation_policy(user_profile, target.bot_type)
    do_reactivate_user(target, acting_user=user_profile)
    return json_success()


check_profile_data: Validator[List[Dict[str, Optional[Union[
    int, str, List[int]]]]]] = check_list(
        check_dict_only([
            ("id", check_int),
            (
                "value",
                check_none_or(
                    check_union(
                        [check_int, check_string,
                         check_list(check_int)]), ),
            ),
        ]), )


@has_request_variables
def update_user_backend(
    request: HttpRequest,
    user_profile: UserProfile,
    user_id: int,
    full_name: Optional[str] = REQ(default=None, json_validator=check_string),
    role: Optional[int] = REQ(
        default=None,
        json_validator=check_int_in(UserProfile.ROLE_TYPES, ),
Beispiel #13
0
    check_union,
)
from zerver.models import Draft, UserProfile
from zerver.tornado.django_api import send_event

VALID_DRAFT_TYPES: Set[str] = {"", "private", "stream"}

# A validator to verify if the structure (syntax) of a dictionary
# meets the requirements to be a draft dictionary:
draft_dict_validator = check_dict_only(
    required_keys=[
        ("type", check_string_in(VALID_DRAFT_TYPES)),
        ("to", check_list(check_int)
         ),  # The ID of the stream to send to, or a list of user IDs.
        ("topic", check_string
         ),  # This string can simply be empty for private type messages.
        ("content", check_required_string),
    ],
    optional_keys=[
        ("timestamp", check_union([check_int,
                                   check_float])),  # A Unix timestamp.
    ],
)


def further_validated_draft_dict(draft_dict: Dict[str, Any],
                                 user_profile: UserProfile) -> Dict[str, Any]:
    """Take a draft_dict that was already validated by draft_dict_validator then
    further sanitize, validate, and transform it. Ultimately return this "further
    validated" draft dict. It will have a slightly different set of keys the values
    for which can be used to directly create a Draft object."""
Beispiel #14
0
                               user_id,
                               allow_deactivated=True,
                               allow_bots=True)
    if target.is_bot:
        assert target.bot_type is not None
        check_bot_creation_policy(user_profile, target.bot_type)
    do_reactivate_user(target, acting_user=user_profile)
    return json_success()


check_profile_data: Validator[List[Dict[str, Optional[Union[
    int, str, List[int]]]]]] = check_list(
        check_dict_only([
            ('id', check_int),
            ('value',
             check_none_or(
                 check_union([check_int, check_string,
                              check_list(check_int)]), )),
        ]), )


@has_request_variables
def update_user_backend(
    request: HttpRequest,
    user_profile: UserProfile,
    user_id: int,
    full_name: Optional[str] = REQ(default=None, validator=check_string),
    role: Optional[int] = REQ(default=None,
                              validator=check_int_in(
                                  UserProfile.ROLE_TYPES, )),
    profile_data: Optional[List[Dict[str, Optional[Union[
Beispiel #15
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),
    message_retention_days: Union[str,
                                  int] = REQ(validator=check_string_or_int,
                                             default="realm_default"),
    announce: bool = REQ(validator=check_bool, default=False),
    principals: Union[Sequence[str],
                      Sequence[int]] = REQ(validator=check_union(
                          [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_dict_copy[
            "message_retention_days"] = parse_message_retention_days(
                message_retention_days)
        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)
Beispiel #16
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),
        is_announcement_only: bool=REQ(validator=check_bool, default=False),
        history_public_to_subscribers: Optional[bool]=REQ(validator=check_bool, default=None),
        announce: bool=REQ(validator=check_bool, default=False),
        principals: List[str]=REQ(validator=check_list(check_string), 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 = {}  # type: 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["is_announcement_only"] = is_announcement_only
        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 (%s).") % 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():
            return json_error(_("Your account is too new to modify other users' subscriptions."))
        subscribers = set(principal_to_user_profile(user_profile, principal) for principal in principals)
    else:
        subscribers = set([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()  # type: Dict[str, UserProfile]

    result = dict(subscribed=defaultdict(list), already_subscribed=defaultdict(list))  # type: Dict[str, Any]
    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 = dict((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 and settings.NOTIFICATION_BOT is not None:
        notifications_stream = user_profile.realm.get_notifications_stream()
        if notifications_stream is not None:
            if len(created_streams) > 1:
                stream_strs = ", ".join('#**%s**' % s.name for s in created_streams)
                stream_msg = "the following streams: %s" % (stream_strs,)
            else:
                stream_msg = "a new stream #**%s**." % created_streams[0].name
            msg = ("@_**%s|%d** just created %s" % (user_profile.full_name, user_profile.id, stream_msg))

            sender = get_system_bot(settings.NOTIFICATION_BOT)
            topic = 'Streams'

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

    if not user_profile.realm.is_zephyr_mirror_realm:
        for stream in created_streams:
            notifications.append(prep_stream_welcome_message(stream))

    if len(notifications) > 0:
        do_send_messages(notifications)

    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)
Beispiel #17
0
def remote_server_post_analytics(
    request: HttpRequest,
    entity: Union[UserProfile, RemoteZulipServer],
    realm_counts: List[Dict[str, Any]] = REQ(validator=check_list(
        check_dict_only([
            ('property', check_string),
            ('realm', check_int),
            ('id', check_int),
            ('end_time', check_float),
            ('subgroup', check_none_or(check_string)),
            ('value', check_int),
        ]))),
    installation_counts: List[Dict[str, Any]] = REQ(validator=check_list(
        check_dict_only([
            ('property', check_string),
            ('id', check_int),
            ('end_time', check_float),
            ('subgroup', check_none_or(check_string)),
            ('value', check_int),
        ]))),
    realmauditlog_rows: Optional[List[Dict[str, Any]]] = REQ(
        validator=check_list(
            check_dict_only([
                ('id', check_int),
                ('realm', check_int),
                ('event_time', check_float),
                ('backfilled', check_bool),
                ('extra_data', check_none_or(check_string)),
                ('event_type', check_int),
            ])),
        default=None)
) -> HttpResponse:
    server = validate_entity(entity)

    validate_incoming_table_data(server, RemoteRealmCount, realm_counts, True)
    validate_incoming_table_data(server, RemoteInstallationCount,
                                 installation_counts, True)
    if realmauditlog_rows is not None:
        validate_incoming_table_data(server, RemoteRealmAuditLog,
                                     realmauditlog_rows)

    row_objects = [
        RemoteRealmCount(property=row['property'],
                         realm_id=row['realm'],
                         remote_id=row['id'],
                         server=server,
                         end_time=datetime.datetime.fromtimestamp(
                             row['end_time'], tz=timezone_utc),
                         subgroup=row['subgroup'],
                         value=row['value']) for row in realm_counts
    ]
    batch_create_table_data(server, RemoteRealmCount, row_objects)

    row_objects = [
        RemoteInstallationCount(
            property=row['property'],
            remote_id=row['id'],
            server=server,
            end_time=datetime.datetime.fromtimestamp(row['end_time'],
                                                     tz=timezone_utc),
            subgroup=row['subgroup'],
            value=row['value']) for row in installation_counts
    ]
    batch_create_table_data(server, RemoteInstallationCount, row_objects)

    if realmauditlog_rows is not None:
        row_objects = [
            RemoteRealmAuditLog(
                realm_id=row['realm'],
                remote_id=row['id'],
                server=server,
                event_time=datetime.datetime.fromtimestamp(row['event_time'],
                                                           tz=timezone_utc),
                backfilled=row['backfilled'],
                extra_data=row['extra_data'],
                event_type=row['event_type']) for row in realmauditlog_rows
        ]
        batch_create_table_data(server, RemoteRealmAuditLog, row_objects)

    return json_success()
Beispiel #18
0
    request: HttpRequest,
    user_profile: UserProfile,
    include_subscribers: bool = REQ(json_validator=check_bool, default=False),
) -> HttpResponse:
    subscribed, _ = gather_subscriptions(
        user_profile,
        include_subscribers=include_subscribers,
    )
    return json_success({"subscriptions": subscribed})


add_subscriptions_schema = check_list(
    check_dict_only(
        required_keys=[("name", check_string)],
        optional_keys=[
            ("color", check_color),
            ("description",
             check_capped_string(Stream.MAX_DESCRIPTION_LENGTH)),
        ],
    ), )

remove_subscriptions_schema = check_list(check_string)


@has_request_variables
def update_subscriptions_backend(
    request: HttpRequest,
    user_profile: UserProfile,
    delete: Sequence[str] = REQ(json_validator=remove_subscriptions_schema,
                                default=[]),
    add: Sequence[Mapping[str,
                          str]] = REQ(json_validator=add_subscriptions_schema,
Beispiel #19
0
    ]
)

check_alert_words = check_events_dict(
    required_keys=[
        # force vertical formatting
        ("type", equals("alert_words")),
        ("alert_words", check_list(check_string)),
    ]
)

_check_custom_profile_field = check_dict_only(
    required_keys=[
        ("id", check_int),
        ("type", check_int),
        ("name", check_string),
        ("hint", check_string),
        ("field_data", check_string),
        ("order", check_int),
    ]
)

check_custom_profile_fields = check_events_dict(
    required_keys=[
        ("type", equals("custom_profile_fields")),
        ("op", equals("add")),
        ("fields", check_list(_check_custom_profile_field)),
    ]
)

check_invites_changed = check_events_dict(
    required_keys=[