Пример #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),
        ]))),
    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=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()
Пример #2
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_union([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: {}").format(property))

        (stream, recipient, sub) = access_stream_by_id(user_profile, stream_id)
        if sub is None:
            return json_error(
                _("Not subscribed to stream id {}").format(stream_id))

        try:
            value = property_converters[property](property, value)
        except ValidationError as error:
            return json_error(error.message)

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

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

    return json_success({"subscription_data": response_data})
Пример #3
0
def update_realm(request, user_profile, name=REQ(validator=check_string, default=None),
                 restricted_to_domain=REQ(validator=check_bool, default=None),
                 invite_required=REQ(validator=check_bool, default=None),
                 invite_by_admins_only=REQ(validator=check_bool, default=None),
                 email_changes_disabled=REQ(validator=check_bool, default=None),
                 create_stream_by_admins_only=REQ(validator=check_bool, default=None),
                 add_emoji_by_admins_only=REQ(validator=check_bool, default=None),
                 allow_message_editing=REQ(validator=check_bool, default=None),
                 message_content_edit_limit_seconds=REQ(converter=to_non_negative_int, default=None),
                 default_language=REQ(validator=check_string, default=None),
                 waiting_period_threshold=REQ(converter=to_non_negative_int, default=None),
                 authentication_methods=REQ(validator=check_dict([]), default=None)):
    # type: (HttpRequest, UserProfile, Optional[str], Optional[bool], Optional[bool], Optional[bool], Optional[bool], Optional[bool], Optional[bool], Optional[bool], Optional[int], Optional[str], Optional[int], Optional[dict]) -> HttpResponse
    # Validation for default_language
    if default_language is not None and default_language not in get_available_language_codes():
        raise JsonableError(_("Invalid language '%s'" % (default_language,)))
    realm = user_profile.realm
    data = {} # type: Dict[str, Any]
    if name is not None and realm.name != name:
        do_set_realm_name(realm, name)
        data['name'] = 'updated'
    if restricted_to_domain is not None and realm.restricted_to_domain != restricted_to_domain:
        do_set_realm_restricted_to_domain(realm, restricted_to_domain)
        data['restricted_to_domain'] = restricted_to_domain
    if invite_required is not None and realm.invite_required != invite_required:
        do_set_realm_invite_required(realm, invite_required)
        data['invite_required'] = invite_required
    if invite_by_admins_only is not None and realm.invite_by_admins_only != invite_by_admins_only:
        do_set_realm_invite_by_admins_only(realm, invite_by_admins_only)
        data['invite_by_admins_only'] = invite_by_admins_only
    if email_changes_disabled is not None and realm.email_changes_disabled != email_changes_disabled:
        do_set_email_changes_disabled(realm, email_changes_disabled)
        data['email_changes_disabled'] = email_changes_disabled
    if authentication_methods is not None and realm.authentication_methods_dict() != authentication_methods:
        if True not in list(authentication_methods.values()):
            return json_error(_("At least one authentication method must be enabled."),
                              data={"reason": "no authentication"}, status=403)
        else:
            do_set_realm_authentication_methods(realm, authentication_methods)
        data['authentication_methods'] = authentication_methods
    if create_stream_by_admins_only is not None and realm.create_stream_by_admins_only != create_stream_by_admins_only:
        do_set_realm_create_stream_by_admins_only(realm, create_stream_by_admins_only)
        data['create_stream_by_admins_only'] = create_stream_by_admins_only
    if add_emoji_by_admins_only is not None and realm.add_emoji_by_admins_only != add_emoji_by_admins_only:
        do_set_realm_add_emoji_by_admins_only(realm, add_emoji_by_admins_only)
        data['add_emoji_by_admins_only'] = add_emoji_by_admins_only
    if (allow_message_editing is not None and realm.allow_message_editing != allow_message_editing) or \
       (message_content_edit_limit_seconds is not None and
            realm.message_content_edit_limit_seconds != message_content_edit_limit_seconds):
        if allow_message_editing is None:
            allow_message_editing = realm.allow_message_editing
        if message_content_edit_limit_seconds is None:
            message_content_edit_limit_seconds = realm.message_content_edit_limit_seconds
        do_set_realm_message_editing(realm, allow_message_editing, message_content_edit_limit_seconds)
        data['allow_message_editing'] = allow_message_editing
        data['message_content_edit_limit_seconds'] = message_content_edit_limit_seconds
    if default_language is not None and realm.default_language != default_language:
        do_set_realm_default_language(realm, default_language)
        data['default_language'] = default_language
    if waiting_period_threshold is not None and realm.waiting_period_threshold != waiting_period_threshold:
        do_set_realm_waiting_period_threshold(realm, waiting_period_threshold)
        data['waiting_period_threshold'] = waiting_period_threshold
    return json_success(data)
Пример #4
0
def get_chart_data(request: HttpRequest,
                   user_profile: UserProfile,
                   chart_name: str = REQ(),
                   min_length: Optional[int] = REQ(
                       converter=to_non_negative_int, default=None),
                   start: Optional[datetime] = REQ(converter=to_utc_datetime,
                                                   default=None),
                   end: Optional[datetime] = REQ(converter=to_utc_datetime,
                                                 default=None),
                   realm: Optional[Realm] = None,
                   for_installation: bool = False) -> HttpResponse:
    aggregate_table = RealmCount
    if for_installation:
        aggregate_table = InstallationCount

    if chart_name == 'number_of_humans':
        stats = [
            COUNT_STATS['1day_actives::day'],
            COUNT_STATS['realm_active_humans::day'],
            COUNT_STATS['active_users_audit:is_bot:day']
        ]
        tables = [aggregate_table]
        subgroup_to_label = {
            stats[0]: {
                None: '_1day'
            },
            stats[1]: {
                None: '_15day'
            },
            stats[2]: {
                'false': 'all_time'
            }
        }  # type: Dict[CountStat, Dict[Optional[str], str]]
        labels_sort_function = None
        include_empty_subgroups = True
    elif chart_name == 'messages_sent_over_time':
        stats = [COUNT_STATS['messages_sent:is_bot:hour']]
        tables = [aggregate_table, UserCount]
        subgroup_to_label = {stats[0]: {'false': 'human', 'true': 'bot'}}
        labels_sort_function = None
        include_empty_subgroups = True
    elif chart_name == 'messages_sent_by_message_type':
        stats = [COUNT_STATS['messages_sent:message_type:day']]
        tables = [aggregate_table, UserCount]
        subgroup_to_label = {
            stats[0]: {
                'public_stream': _('Public streams'),
                'private_stream': _('Private streams'),
                'private_message': _('Private messages'),
                'huddle_message': _('Group private messages')
            }
        }
        labels_sort_function = lambda data: sort_by_totals(data['everyone'])
        include_empty_subgroups = True
    elif chart_name == 'messages_sent_by_client':
        stats = [COUNT_STATS['messages_sent:client:day']]
        tables = [aggregate_table, UserCount]
        # Note that the labels are further re-written by client_label_map
        subgroup_to_label = {
            stats[0]: {
                str(id): name
                for id, name in Client.objects.values_list('id', 'name')
            }
        }
        labels_sort_function = sort_client_labels
        include_empty_subgroups = False
    else:
        raise JsonableError(_("Unknown chart name: %s") % (chart_name, ))

    # Most likely someone using our API endpoint. The /stats page does not
    # pass a start or end in its requests.
    if start is not None:
        start = convert_to_UTC(start)
    if end is not None:
        end = convert_to_UTC(end)
    if start is not None and end is not None and start > end:
        raise JsonableError(
            _("Start time is later than end time. Start: %(start)s, End: %(end)s"
              ) % {
                  'start': start,
                  'end': end
              })

    if realm is None:
        realm = user_profile.realm
    if start is None:
        if for_installation:
            start = installation_epoch()
        else:
            start = realm.date_created
    if end is None:
        end = max(
            last_successful_fill(stat.property) or datetime.min.replace(
                tzinfo=timezone_utc) for stat in stats)
    if end is None or start > end:
        logging.warning(
            "User from realm %s attempted to access /stats, but the computed "
            "start time: %s (creation of realm or installation) is later than the computed "
            "end time: %s (last successful analytics update). Is the "
            "analytics cron job running?" % (realm.string_id, start, end))
        raise JsonableError(
            _("No analytics data available. Please contact your server administrator."
              ))

    assert len(set([stat.frequency for stat in stats])) == 1
    end_times = time_range(start, end, stats[0].frequency, min_length)
    data = {
        'end_times': end_times,
        'frequency': stats[0].frequency
    }  # type: Dict[str, Any]

    aggregation_level = {
        InstallationCount: 'everyone',
        RealmCount: 'everyone',
        UserCount: 'user'
    }
    # -1 is a placeholder value, since there is no relevant filtering on InstallationCount
    id_value = {
        InstallationCount: -1,
        RealmCount: realm.id,
        UserCount: user_profile.id
    }
    for table in tables:
        data[aggregation_level[table]] = {}
        for stat in stats:
            data[aggregation_level[table]].update(
                get_time_series_by_subgroup(stat, table, id_value[table],
                                            end_times, subgroup_to_label[stat],
                                            include_empty_subgroups))

    if labels_sort_function is not None:
        data['display_order'] = labels_sort_function(data)
    else:
        data['display_order'] = None
    return json_success(data=data)
Пример #5
0
def add_subscriptions_backend(
    request: HttpRequest,
    user_profile: UserProfile,
    streams_raw: Iterable[Dict[str,
                               str]] = REQ("subscriptions",
                                           validator=add_subscriptions_schema),
    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=RETENTION_DEFAULT),
    announce: bool = REQ(validator=check_bool, default=False),
    principals: Union[Sequence[str], Sequence[int]] = REQ(
        validator=check_principals,
        default=EMPTY_PRINCIPALS,
    ),
    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.MESSAGE_RETENTION_SPECIAL_VALUES_MAP)
        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

            sender = get_system_bot(settings.NOTIFICATION_BOT)
            recipient_user = email_to_user_profile[email]

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

            notifications.append(
                internal_prep_private_message(realm=user_profile.realm,
                                              sender=sender,
                                              recipient_user=recipient_user,
                                              content=msg))

    if announce and len(created_streams) > 0:
        notifications_stream = user_profile.realm.get_notifications_stream()
        if notifications_stream is not None:
            with override_language(
                    notifications_stream.realm.default_language):
                if len(created_streams) > 1:
                    content = _(
                        "{user_name} created the following streams: {stream_str}."
                    )
                else:
                    content = _(
                        "{user_name} created a new stream {stream_str}.")
                topic = _('new streams')

            content = content.format(
                user_name=f"@_**{user_profile.full_name}|{user_profile.id}**",
                stream_str=", ".join(f'#**{s.name}**'
                                     for s in created_streams))

            sender = get_system_bot(settings.NOTIFICATION_BOT)

            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:
            with override_language(stream.realm.default_language):
                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}.').format(
                            user_name=
                            f"@_**{user_profile.full_name}|{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)
Пример #6
0
def json_rename_stream(request, user_profile, old_name=REQ(), new_name=REQ()):
    # type: (HttpRequest, UserProfile, text_type, text_type) -> HttpResponse
    do_rename_stream(user_profile.realm, old_name, new_name)
    return json_success()
Пример #7
0
def add_subscriptions_backend(
    request,
    user_profile,
    streams_raw=REQ("subscriptions",
                    validator=check_list(check_dict([('name', check_string)
                                                     ]))),
    invite_only=REQ(validator=check_bool, default=False),
    announce=REQ(validator=check_bool, default=False),
    principals=REQ(validator=check_list(check_string), default=None),
    authorization_errors_fatal=REQ(validator=check_bool, default=True)):
    # type: (HttpRequest, UserProfile, Iterable[Mapping[str, text_type]], bool, bool, Optional[List[text_type]], bool) -> HttpResponse
    stream_names = []
    for stream_dict in streams_raw:
        stream_name = stream_dict["name"].strip()
        if len(stream_name) > Stream.MAX_NAME_LENGTH:
            return json_error(
                _("Stream name (%s) too long.") % (stream_name, ))
        if not valid_stream_name(stream_name):
            return json_error(_("Invalid stream name (%s).") % (stream_name, ))
        stream_names.append(stream_name)

    # Enforcement of can_create_streams policy is inside list_to_streams.
    existing_streams, created_streams = \
        list_to_streams(stream_names, user_profile, autocreate=True, invite_only=invite_only)
    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 principals is not None:
        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 invite-only streams."
                  ))
        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)

    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)
    for (subscriber, stream) in already_subscribed:
        result["already_subscribed"][subscriber.email].append(stream.name)

    private_streams = dict(
        (stream.name, stream.invite_only) for stream in streams)
    bots = dict(
        (subscriber.email, subscriber.is_bot) for subscriber in subscribers)

    # Inform the user if someone else subscribed them to stuff,
    # or if a new stream was created with the "announce" option.
    notifications = []
    if principals and result["subscribed"]:
        for email, subscriptions in six.iteritems(result["subscribed"]):
            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

            if len(subscriptions) == 1:
                msg = ("Hi there!  We thought you'd like to know that %s just "
                       "subscribed you to the%s stream [%s](%s)." % (
                           user_profile.full_name,
                           " **invite-only**"
                           if private_streams[subscriptions[0]] else "",
                           subscriptions[0],
                           stream_link(subscriptions[0]),
                       ))
            else:
                msg = ("Hi there!  We thought you'd like to know that %s just "
                       "subscribed you to the following streams: \n\n" %
                       (user_profile.full_name, ))
                for stream in subscriptions:
                    msg += "* [%s](%s)%s\n" % (stream, stream_link(stream),
                                               " (**invite-only**)" if
                                               private_streams[stream] else "")

            if len([s for s in subscriptions if not private_streams[s]]) > 0:
                msg += "\nYou can see historical content on a non-invite-only stream by narrowing to it."
            notifications.append(
                internal_prep_message(settings.NOTIFICATION_BOT, "private",
                                      email, "", msg))

    if announce and len(created_streams) > 0:
        notifications_stream = user_profile.realm.notifications_stream
        if notifications_stream is not None:
            if len(created_streams) > 1:
                stream_msg = "the following streams: %s" % \
                              (", ".join('`%s`' % (s.name,) for s in created_streams),)
            else:
                stream_msg = "a new stream `%s`" % (created_streams[0].name)

            stream_buttons = ' '.join(
                stream_button(s.name) for s in created_streams)
            msg = ("%s just created %s. %s" %
                   (user_profile.full_name, stream_msg, stream_buttons))
            notifications.append(
                internal_prep_message(settings.NOTIFICATION_BOT,
                                      "stream",
                                      notifications_stream.name,
                                      "Streams",
                                      msg,
                                      realm=notifications_stream.realm))
        else:
            msg = ("Hi there!  %s just created a new stream '%s'. %s" %
                   (user_profile.full_name, created_streams[0].name,
                    stream_button(created_streams[0].name)))
            for realm_user_dict in get_active_user_dicts_in_realm(
                    user_profile.realm):
                # Don't announce to yourself or to people you explicitly added
                # (who will get the notification above instead).
                if realm_user_dict['email'] in principals or realm_user_dict[
                        'email'] == user_profile.email:
                    continue
                notifications.append(
                    internal_prep_message(settings.NOTIFICATION_BOT, "private",
                                          realm_user_dict['email'], "", msg))

    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"] = [
            stream.name for stream in unauthorized_streams
        ]
    return json_success(result)
Пример #8
0
def zcommand_backend(
    request: HttpRequest, user_profile: UserProfile, command: str = REQ("command")
) -> HttpResponse:
    return json_success(process_zcommands(command, user_profile))
Пример #9
0
def report_error(
    request: HttpRequest,
    user_profile: UserProfile,
    message: str = REQ(),
    stacktrace: str = REQ(),
    ui_message: bool = REQ(validator=check_bool),
    user_agent: str = REQ(),
    href: str = REQ(),
    log: str = REQ(),
    more_info: Mapping[str, Any] = REQ(validator=check_dict([]), default={}),
) -> HttpResponse:
    """Accepts an error report and stores in a queue for processing.  The
    actual error reports are later handled by do_report_error"""
    if not settings.BROWSER_ERROR_REPORTING:
        return json_success()
    more_info = dict(more_info)

    js_source_map = get_js_source_map()
    if js_source_map:
        stacktrace = js_source_map.annotate_stacktrace(stacktrace)

    try:
        version: Optional[str] = subprocess.check_output(
            ["git", "show", "-s", "--oneline"],
            universal_newlines=True,
        )
    except (FileNotFoundError, subprocess.CalledProcessError):
        version = None

    # Get the IP address of the request
    remote_ip = request.META["REMOTE_ADDR"]

    # For the privacy of our users, we remove any actual text content
    # in draft_content (from drafts rendering exceptions).  See the
    # comment on privacy_clean_markdown for more details.
    if more_info.get("draft_content"):
        more_info["draft_content"] = privacy_clean_markdown(
            more_info["draft_content"])

    if user_profile.is_authenticated:
        email = user_profile.delivery_email
        full_name = user_profile.full_name
    else:
        email = "*****@*****.**"
        full_name = "Anonymous User"

    queue_json_publish(
        "error_reports",
        dict(
            type="browser",
            report=dict(
                host=SplitResult("", request.get_host(), "", "", "").hostname,
                ip_address=remote_ip,
                user_email=email,
                user_full_name=full_name,
                user_visible=ui_message,
                server_path=settings.DEPLOY_ROOT,
                version=version,
                user_agent=user_agent,
                href=href,
                message=message,
                stacktrace=stacktrace,
                log=log,
                more_info=more_info,
            ),
        ),
    )

    return json_success()
Пример #10
0
def login_page(
        request: HttpRequest,
        next: str = REQ(default="/"),
        **kwargs: Any,
) -> HttpResponse:
    # To support previewing the Zulip login pages, we have a special option
    # that disables the default behavior of redirecting logged-in users to the
    # logged-in app.
    is_preview = 'preview' in request.GET
    if settings.TWO_FACTOR_AUTHENTICATION_ENABLED:
        if request.user and request.user.is_verified():
            return HttpResponseRedirect(request.user.realm.uri)
    elif request.user.is_authenticated and not is_preview:
        return HttpResponseRedirect(request.user.realm.uri)
    if is_subdomain_root_or_alias(
            request) and settings.ROOT_DOMAIN_LANDING_PAGE:
        redirect_url = reverse('zerver.views.registration.realm_redirect')
        if request.GET:
            redirect_url = add_query_to_redirect_url(redirect_url,
                                                     request.GET.urlencode())
        return HttpResponseRedirect(redirect_url)

    realm = get_realm_from_request(request)
    if realm and realm.deactivated:
        return redirect_to_deactivation_notice()

    extra_context = kwargs.pop('extra_context', {})
    extra_context["next"] = next
    if dev_auth_enabled() and kwargs.get(
            "template_name") == "zerver/dev_login.html":
        if 'new_realm' in request.POST:
            try:
                realm = get_realm(request.POST['new_realm'])
            except Realm.DoesNotExist:
                realm = None

        add_dev_login_context(realm, extra_context)
        if realm and 'new_realm' in request.POST:
            # If we're switching realms, redirect to that realm, but
            # only if it actually exists.
            return HttpResponseRedirect(realm.uri)

    if 'username' in request.POST:
        extra_context['email'] = request.POST['username']
    extra_context.update(login_context(request))

    if settings.TWO_FACTOR_AUTHENTICATION_ENABLED:
        return start_two_factor_auth(request,
                                     extra_context=extra_context,
                                     **kwargs)

    try:
        template_response = DjangoLoginView.as_view(
            authentication_form=OurAuthenticationForm,
            extra_context=extra_context,
            **kwargs)(request)
    except ZulipLDAPConfigurationError as e:
        assert len(e.args) > 1
        return redirect_to_misconfigured_ldap_notice(e.args[1])

    if isinstance(template_response, SimpleTemplateResponse):
        # Only those responses that are rendered using a template have
        # context_data attribute. This attribute doesn't exist otherwise. It is
        # added in SimpleTemplateResponse class, which is a derived class of
        # HttpResponse. See django.template.response.SimpleTemplateResponse,
        # https://github.com/django/django/blob/master/django/template/response.py#L19.
        update_login_page_context(request, template_response.context_data)

    return template_response
Пример #11
0
def send_message_backend(
    request: HttpRequest,
    user_profile: UserProfile,
    message_type_name: str = REQ("type"),
    req_to: Optional[str] = REQ("to", default=None),
    forged_str: Optional[str] = REQ("forged", default=None, documentation_pending=True),
    topic_name: Optional[str] = REQ_topic(),
    message_content: str = REQ("content"),
    widget_content: Optional[str] = REQ(default=None, documentation_pending=True),
    realm_str: Optional[str] = REQ("realm_str", default=None, documentation_pending=True),
    local_id: Optional[str] = REQ(default=None),
    queue_id: Optional[str] = REQ(default=None),
    delivery_type: str = REQ("delivery_type", default="send_now", documentation_pending=True),
    defer_until: Optional[str] = REQ("deliver_at", default=None, documentation_pending=True),
    tz_guess: Optional[str] = REQ("tz_guess", default=None, documentation_pending=True),
    time: Optional[float] = REQ(default=None, converter=to_float, documentation_pending=True),
) -> HttpResponse:

    # If req_to is None, then we default to an
    # empty list of recipients.
    message_to: Union[Sequence[int], Sequence[str]] = []

    if req_to is not None:
        if message_type_name == "stream":
            stream_indicator = extract_stream_indicator(req_to)

            # For legacy reasons check_send_message expects
            # a list of streams, instead of a single stream.
            #
            # Also, mypy can't detect that a single-item
            # list populated from a Union[int, str] is actually
            # a Union[Sequence[int], Sequence[str]].
            if isinstance(stream_indicator, int):
                message_to = [stream_indicator]
            else:
                message_to = [stream_indicator]
        else:
            message_to = extract_private_recipients(req_to)

    # Temporary hack: We're transitioning `forged` from accepting
    # `yes` to accepting `true` like all of our normal booleans.
    forged = forged_str is not None and forged_str in ["yes", "true"]

    client = get_request_notes(request).client
    assert client is not None
    can_forge_sender = user_profile.can_forge_sender
    if forged and not can_forge_sender:
        raise JsonableError(_("User not authorized for this query"))

    realm = None
    if realm_str and realm_str != user_profile.realm.string_id:
        # The realm_str parameter does nothing, because it has to match
        # the user's realm - but we keep it around for backward compatibility.
        raise JsonableError(_("User not authorized for this query"))

    if client.name in ["zephyr_mirror", "irc_mirror", "jabber_mirror", "JabberMirror"]:
        # Here's how security works for mirroring:
        #
        # For private messages, the message must be (1) both sent and
        # received exclusively by users in your realm, and (2)
        # received by the forwarding user.
        #
        # For stream messages, the message must be (1) being forwarded
        # by an API superuser for your realm and (2) being sent to a
        # mirrored stream.
        #
        # The most important security checks are in
        # `create_mirrored_message_users` below, which checks the
        # same-realm constraint.
        if "sender" not in request.POST:
            raise JsonableError(_("Missing sender"))
        if message_type_name != "private" and not can_forge_sender:
            raise JsonableError(_("User not authorized for this query"))

        # For now, mirroring only works with recipient emails, not for
        # recipient user IDs.
        if not all(isinstance(to_item, str) for to_item in message_to):
            raise JsonableError(_("Mirroring not allowed with recipient user IDs"))

        # We need this manual cast so that mypy doesn't complain about
        # create_mirrored_message_users not being able to accept a Sequence[int]
        # type parameter.
        message_to = cast(Sequence[str], message_to)

        try:
            mirror_sender = create_mirrored_message_users(request, user_profile, message_to)
        except InvalidMirrorInput:
            raise JsonableError(_("Invalid mirrored message"))

        if client.name == "zephyr_mirror" and not user_profile.realm.is_zephyr_mirror_realm:
            raise JsonableError(_("Zephyr mirroring is not allowed in this organization"))
        sender = mirror_sender
    else:
        if "sender" in request.POST:
            raise JsonableError(_("Invalid mirrored message"))
        sender = user_profile

    if (delivery_type == "send_later" or delivery_type == "remind") and defer_until is None:
        raise JsonableError(_("Missing deliver_at in a request for delayed message delivery"))

    if (delivery_type == "send_later" or delivery_type == "remind") and defer_until is not None:
        return handle_deferred_message(
            sender,
            client,
            message_type_name,
            message_to,
            topic_name,
            message_content,
            delivery_type,
            defer_until,
            tz_guess,
            forwarder_user_profile=user_profile,
            realm=realm,
        )

    ret = check_send_message(
        sender,
        client,
        message_type_name,
        message_to,
        topic_name,
        message_content,
        forged=forged,
        forged_timestamp=time,
        forwarder_user_profile=user_profile,
        realm=realm,
        local_id=local_id,
        sender_queue_id=queue_id,
        widget_content=widget_content,
    )
    return json_success({"id": ret})
Пример #12
0
def delete_user_group(request: HttpRequest, user_profile: UserProfile,
                      user_group_id: int=REQ(validator=check_int)) -> HttpResponse:

    check_delete_user_group(user_group_id, user_profile)
    return json_success()
Пример #13
0
def update_realm(
    request: HttpRequest,
    user_profile: UserProfile,
    name: Optional[str] = REQ(validator=check_string, default=None),
    description: Optional[str] = REQ(validator=check_string, default=None),
    emails_restricted_to_domains: Optional[bool] = REQ(validator=check_bool,
                                                       default=None),
    disallow_disposable_email_addresses: Optional[bool] = REQ(
        validator=check_bool, default=None),
    invite_required: Optional[bool] = REQ(validator=check_bool, default=None),
    invite_by_admins_only: Optional[bool] = REQ(validator=check_bool,
                                                default=None),
    name_changes_disabled: Optional[bool] = REQ(validator=check_bool,
                                                default=None),
    email_changes_disabled: Optional[bool] = REQ(validator=check_bool,
                                                 default=None),
    avatar_changes_disabled: Optional[bool] = REQ(validator=check_bool,
                                                  default=None),
    inline_image_preview: Optional[bool] = REQ(validator=check_bool,
                                               default=None),
    inline_url_embed_preview: Optional[bool] = REQ(validator=check_bool,
                                                   default=None),
    add_emoji_by_admins_only: Optional[bool] = REQ(validator=check_bool,
                                                   default=None),
    allow_message_deleting: Optional[bool] = REQ(validator=check_bool,
                                                 default=None),
    message_content_delete_limit_seconds: Optional[int] = REQ(
        converter=to_non_negative_int, default=None),
    allow_message_editing: Optional[bool] = REQ(validator=check_bool,
                                                default=None),
    allow_community_topic_editing: Optional[bool] = REQ(validator=check_bool,
                                                        default=None),
    mandatory_topics: Optional[bool] = REQ(validator=check_bool, default=None),
    message_content_edit_limit_seconds: Optional[int] = REQ(
        converter=to_non_negative_int, default=None),
    allow_edit_history: Optional[bool] = REQ(validator=check_bool,
                                             default=None),
    default_language: Optional[str] = REQ(validator=check_string,
                                          default=None),
    waiting_period_threshold: Optional[int] = REQ(
        converter=to_non_negative_int, default=None),
    authentication_methods: Optional[Dict[str,
                                          Any]] = REQ(validator=check_dict([]),
                                                      default=None),
    notifications_stream_id: Optional[int] = REQ(validator=check_int,
                                                 default=None),
    signup_notifications_stream_id: Optional[int] = REQ(validator=check_int,
                                                        default=None),
    message_retention_days_raw: Optional[Union[int, str]] = REQ(
        "message_retention_days", validator=check_string_or_int, default=None),
    send_welcome_emails: Optional[bool] = REQ(validator=check_bool,
                                              default=None),
    digest_emails_enabled: Optional[bool] = REQ(validator=check_bool,
                                                default=None),
    message_content_allowed_in_email_notifications: Optional[bool] = REQ(
        validator=check_bool, default=None),
    bot_creation_policy: Optional[int] = REQ(validator=check_int_in(
        Realm.BOT_CREATION_POLICY_TYPES),
                                             default=None),
    create_stream_policy: Optional[int] = REQ(validator=check_int_in(
        Realm.COMMON_POLICY_TYPES),
                                              default=None),
    invite_to_stream_policy: Optional[int] = REQ(validator=check_int_in(
        Realm.COMMON_POLICY_TYPES),
                                                 default=None),
    user_group_edit_policy: Optional[int] = REQ(validator=check_int_in(
        Realm.USER_GROUP_EDIT_POLICY_TYPES),
                                                default=None),
    private_message_policy: Optional[int] = REQ(validator=check_int_in(
        Realm.PRIVATE_MESSAGE_POLICY_TYPES),
                                                default=None),
    email_address_visibility: Optional[int] = REQ(validator=check_int_in(
        Realm.EMAIL_ADDRESS_VISIBILITY_TYPES),
                                                  default=None),
    default_twenty_four_hour_time: Optional[bool] = REQ(validator=check_bool,
                                                        default=None),
    video_chat_provider: Optional[int] = REQ(validator=check_int,
                                             default=None),
    default_code_block_language: Optional[str] = REQ(validator=check_string,
                                                     default=None),
    digest_weekday: Optional[int] = REQ(validator=check_int_in(
        Realm.DIGEST_WEEKDAY_VALUES),
                                        default=None),
) -> HttpResponse:
    realm = user_profile.realm

    # Additional validation/error checking beyond types go here, so
    # the entire request can succeed or fail atomically.
    if default_language is not None and default_language not in get_available_language_codes(
    ):
        raise JsonableError(
            _("Invalid language '{}'").format(default_language))
    if description is not None and len(description) > 1000:
        return json_error(_("Organization description is too long."))
    if name is not None and len(name) > Realm.MAX_REALM_NAME_LENGTH:
        return json_error(_("Organization name is too long."))
    if authentication_methods is not None:
        if not user_profile.is_realm_owner:
            raise OrganizationOwnerRequired()
        if True not in list(authentication_methods.values()):
            return json_error(
                _("At least one authentication method must be enabled."))
    if (video_chat_provider is not None and video_chat_provider
            not in {p['id']
                    for p in Realm.VIDEO_CHAT_PROVIDERS.values()}):
        return json_error(
            _("Invalid video_chat_provider {}").format(video_chat_provider))

    message_retention_days: Optional[int] = None
    if message_retention_days_raw is not None:
        if not user_profile.is_realm_owner:
            raise OrganizationOwnerRequired()
        realm.ensure_not_on_limited_plan()
        message_retention_days = parse_message_retention_days(
            message_retention_days_raw,
            Realm.MESSAGE_RETENTION_SPECIAL_VALUES_MAP)

    # The user of `locals()` here is a bit of a code smell, but it's
    # restricted to the elements present in realm.property_types.
    #
    # TODO: It should be possible to deduplicate this function up
    # further by some more advanced usage of the
    # `REQ/has_request_variables` extraction.
    req_vars = {
        k: v
        for k, v in list(locals().items()) if k in realm.property_types
    }
    data: Dict[str, Any] = {}

    for k, v in list(req_vars.items()):
        if v is not None and getattr(realm, k) != v:
            do_set_realm_property(realm, k, v, acting_user=user_profile)
            if isinstance(v, str):
                data[k] = 'updated'
            else:
                data[k] = v

    # The following realm properties do not fit the pattern above
    # authentication_methods is not supported by the do_set_realm_property
    # framework because of its bitfield.
    if authentication_methods is not None and (
            realm.authentication_methods_dict() != authentication_methods):
        do_set_realm_authentication_methods(realm,
                                            authentication_methods,
                                            acting_user=user_profile)
        data['authentication_methods'] = authentication_methods
    # The message_editing settings are coupled to each other, and thus don't fit
    # into the do_set_realm_property framework.
    if ((allow_message_editing is not None
         and realm.allow_message_editing != allow_message_editing)
            or (message_content_edit_limit_seconds is not None
                and realm.message_content_edit_limit_seconds !=
                message_content_edit_limit_seconds)
            or (allow_community_topic_editing is not None
                and realm.allow_community_topic_editing !=
                allow_community_topic_editing)):
        if allow_message_editing is None:
            allow_message_editing = realm.allow_message_editing
        if message_content_edit_limit_seconds is None:
            message_content_edit_limit_seconds = realm.message_content_edit_limit_seconds
        if allow_community_topic_editing is None:
            allow_community_topic_editing = realm.allow_community_topic_editing
        do_set_realm_message_editing(realm, allow_message_editing,
                                     message_content_edit_limit_seconds,
                                     allow_community_topic_editing)
        data['allow_message_editing'] = allow_message_editing
        data[
            'message_content_edit_limit_seconds'] = message_content_edit_limit_seconds
        data['allow_community_topic_editing'] = allow_community_topic_editing

    if (message_content_delete_limit_seconds is not None
            and realm.message_content_delete_limit_seconds !=
            message_content_delete_limit_seconds):
        do_set_realm_message_deleting(realm,
                                      message_content_delete_limit_seconds)
        data[
            'message_content_delete_limit_seconds'] = message_content_delete_limit_seconds
    # Realm.notifications_stream and Realm.signup_notifications_stream are not boolean,
    # str or integer field, and thus doesn't fit into the do_set_realm_property framework.
    if notifications_stream_id is not None:
        if realm.notifications_stream is None or (realm.notifications_stream.id
                                                  != notifications_stream_id):
            new_notifications_stream = None
            if notifications_stream_id >= 0:
                (new_notifications_stream, recipient,
                 sub) = access_stream_by_id(user_profile,
                                            notifications_stream_id)
            do_set_realm_notifications_stream(realm, new_notifications_stream,
                                              notifications_stream_id)
            data['notifications_stream_id'] = notifications_stream_id

    if signup_notifications_stream_id is not None:
        if realm.signup_notifications_stream is None or (
                realm.signup_notifications_stream.id !=
                signup_notifications_stream_id):
            new_signup_notifications_stream = None
            if signup_notifications_stream_id >= 0:
                (new_signup_notifications_stream, recipient,
                 sub) = access_stream_by_id(user_profile,
                                            signup_notifications_stream_id)
            do_set_realm_signup_notifications_stream(
                realm, new_signup_notifications_stream,
                signup_notifications_stream_id)
            data[
                'signup_notifications_stream_id'] = signup_notifications_stream_id

    if default_code_block_language is not None:
        # Migrate '', used in the API to encode the default/None behavior of this feature.
        if default_code_block_language == '':
            data['default_code_block_language'] = None
        else:
            data['default_code_block_language'] = default_code_block_language

    return json_success(data)
Пример #14
0
def add_default_stream(request, user_profile, stream_name=REQ()):
    # type: (HttpRequest, UserProfile, Text) -> HttpResponse
    do_add_default_stream(user_profile.realm, stream_name)
    return json_success()
Пример #15
0
def get_members_backend(
    request: HttpRequest,
    user_profile: UserProfile,
    client_gravatar: bool = REQ(validator=check_bool, default=False)
) -> HttpResponse:
    '''
    The client_gravatar field here is set to True if clients can compute
    their own gravatars, which saves us bandwidth.  We want to eventually
    make this the default behavior, but we have old clients that expect
    the server to compute this for us.
    '''

    realm = user_profile.realm

    query = UserProfile.objects.filter(realm_id=realm.id).values(
        'id',
        'email',
        'realm_id',
        'full_name',
        'is_bot',
        'is_realm_admin',
        'is_active',
        'is_guest',
        'bot_type',
        'avatar_source',
        'avatar_version',
        'bot_owner__email',
    )

    def get_member(row: Dict[str, Any]) -> Dict[str, Any]:
        email = row['email']
        user_id = row['id']

        result = dict(
            user_id=user_id,
            email=email,
            full_name=row['full_name'],
            is_bot=row['is_bot'],
            is_active=row['is_active'],
            is_admin=row['is_realm_admin'],
            bot_type=row['bot_type'],
            is_guest=row['is_guest'],
        )

        result['avatar_url'] = get_avatar_field(
            user_id=user_id,
            email=email,
            avatar_source=row['avatar_source'],
            avatar_version=row['avatar_version'],
            realm_id=row['realm_id'],
            medium=False,
            client_gravatar=client_gravatar,
        )

        if row['bot_owner__email']:
            result['bot_owner'] = row['bot_owner__email']

        return result

    members = [get_member(row) for row in query]

    return json_success({'members': members})
Пример #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():
            if user_profile.realm.invite_to_stream_policy == Realm.INVITE_TO_STREAM_POLICY_ADMINS:
                return json_error(
                    _("Only administrators can modify other users' subscriptions."
                      ))
            # Realm.INVITE_TO_STREAM_POLICY_MEMBERS only fails if the
            # user is a guest, which happens in the decorator above.
            assert user_profile.realm.invite_to_stream_policy == \
                Realm.INVITE_TO_STREAM_POLICY_WAITING_PERIOD
            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:
        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('#**%s**' % (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)s|%(user_id)d**.') %
                    {
                        '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)
Пример #17
0
def remove_default_stream(request, user_profile, stream_name=REQ()):
    # type: (HttpRequest, UserProfile, text_type) -> HttpResponse
    do_remove_default_stream(user_profile.realm, stream_name)
    return json_success()
Пример #18
0
def add_default_stream(
    request: HttpRequest, user_profile: UserProfile,
    stream_name: str = REQ()) -> HttpResponse:
    (stream, recipient, sub) = access_stream_by_name(user_profile, stream_name)
    do_add_default_stream(stream)
    return json_success()
Пример #19
0
def json_make_stream_private(request, user_profile, stream_name=REQ()):
    # type: (HttpRequest, UserProfile, text_type) -> HttpResponse
    do_make_stream_private(user_profile.realm, stream_name)
    return json_success()
Пример #20
0
def json_change_settings(
    request: HttpRequest,
    user_profile: UserProfile,
    full_name: str = REQ(default=""),
    email: str = REQ(default=""),
    old_password: str = REQ(default=""),
    new_password: str = REQ(default=""),
    twenty_four_hour_time: Optional[bool] = REQ(json_validator=check_bool,
                                                default=None),
    dense_mode: Optional[bool] = REQ(json_validator=check_bool, default=None),
    starred_message_counts: Optional[bool] = REQ(json_validator=check_bool,
                                                 default=None),
    fluid_layout_width: Optional[bool] = REQ(json_validator=check_bool,
                                             default=None),
    high_contrast_mode: Optional[bool] = REQ(json_validator=check_bool,
                                             default=None),
    color_scheme: Optional[int] = REQ(json_validator=check_int_in(
        UserProfile.COLOR_SCHEME_CHOICES),
                                      default=None),
    translate_emoticons: Optional[bool] = REQ(json_validator=check_bool,
                                              default=None),
    default_language: Optional[str] = REQ(default=None),
    default_view: Optional[str] = REQ(
        str_validator=check_string_in(default_view_options), default=None),
    escape_navigates_to_default_view: Optional[bool] = REQ(
        json_validator=check_bool, default=None),
    left_side_userlist: Optional[bool] = REQ(json_validator=check_bool,
                                             default=None),
    emojiset: Optional[str] = REQ(
        str_validator=check_string_in(emojiset_choices), default=None),
    demote_inactive_streams: Optional[int] = REQ(json_validator=check_int_in(
        UserProfile.DEMOTE_STREAMS_CHOICES),
                                                 default=None),
    timezone: Optional[str] = REQ(str_validator=check_string_in(
        pytz.all_timezones_set),
                                  default=None),
    email_notifications_batching_period_seconds: Optional[int] = REQ(
        json_validator=check_int, default=None),
    enable_drafts_synchronization: Optional[bool] = REQ(
        json_validator=check_bool, default=None),
    enable_stream_desktop_notifications: Optional[bool] = REQ(
        json_validator=check_bool, default=None),
    enable_stream_email_notifications: Optional[bool] = REQ(
        json_validator=check_bool, default=None),
    enable_stream_push_notifications: Optional[bool] = REQ(
        json_validator=check_bool, default=None),
    enable_stream_audible_notifications: Optional[bool] = REQ(
        json_validator=check_bool, default=None),
    wildcard_mentions_notify: Optional[bool] = REQ(json_validator=check_bool,
                                                   default=None),
    notification_sound: Optional[str] = REQ(default=None),
    enable_desktop_notifications: Optional[bool] = REQ(
        json_validator=check_bool, default=None),
    enable_sounds: Optional[bool] = REQ(json_validator=check_bool,
                                        default=None),
    enable_offline_email_notifications: Optional[bool] = REQ(
        json_validator=check_bool, default=None),
    enable_offline_push_notifications: Optional[bool] = REQ(
        json_validator=check_bool, default=None),
    enable_online_push_notifications: Optional[bool] = REQ(
        json_validator=check_bool, default=None),
    enable_digest_emails: Optional[bool] = REQ(json_validator=check_bool,
                                               default=None),
    enable_login_emails: Optional[bool] = REQ(json_validator=check_bool,
                                              default=None),
    enable_marketing_emails: Optional[bool] = REQ(json_validator=check_bool,
                                                  default=None),
    message_content_in_email_notifications: Optional[bool] = REQ(
        json_validator=check_bool, default=None),
    pm_content_in_desktop_notifications: Optional[bool] = REQ(
        json_validator=check_bool, default=None),
    desktop_icon_count_display: Optional[int] = REQ(
        json_validator=check_int_in(
            UserProfile.DESKTOP_ICON_COUNT_DISPLAY_CHOICES),
        default=None),
    realm_name_in_notifications: Optional[bool] = REQ(
        json_validator=check_bool, default=None),
    presence_enabled: Optional[bool] = REQ(json_validator=check_bool,
                                           default=None),
    enter_sends: Optional[bool] = REQ(json_validator=check_bool, default=None),
    send_private_typing_notifications: Optional[bool] = REQ(
        json_validator=check_bool, default=None),
    send_stream_typing_notifications: Optional[bool] = REQ(
        json_validator=check_bool, default=None),
    send_read_receipts: Optional[bool] = REQ(json_validator=check_bool,
                                             default=None),
) -> HttpResponse:
    if (default_language is not None or notification_sound is not None
            or email_notifications_batching_period_seconds is not None):
        check_settings_values(notification_sound,
                              email_notifications_batching_period_seconds,
                              default_language)

    if new_password != "":
        return_data: Dict[str, Any] = {}
        if email_belongs_to_ldap(user_profile.realm,
                                 user_profile.delivery_email):
            raise JsonableError(_("Your Zulip password is managed in LDAP"))

        try:
            if not authenticate(
                    request,
                    username=user_profile.delivery_email,
                    password=old_password,
                    realm=user_profile.realm,
                    return_data=return_data,
            ):
                raise JsonableError(_("Wrong password!"))
        except RateLimited as e:
            assert e.secs_to_freedom is not None
            secs_to_freedom = int(e.secs_to_freedom)
            raise JsonableError(
                _("You're making too many attempts! Try again in {} seconds.").
                format(secs_to_freedom), )

        if not check_password_strength(new_password):
            raise JsonableError(_("New password is too weak!"))

        do_change_password(user_profile, new_password)
        # Password changes invalidates sessions, see
        # https://docs.djangoproject.com/en/3.2/topics/auth/default/#session-invalidation-on-password-change
        # for details. To avoid this logging the user out of their own
        # session (which would provide a confusing UX at best), we
        # update the session hash here.
        update_session_auth_hash(request, user_profile)
        # We also save the session to the DB immediately to mitigate
        # race conditions. In theory, there is still a race condition
        # and to completely avoid it we will have to use some kind of
        # mutex lock in `django.contrib.auth.get_user` where session
        # is verified. To make that lock work we will have to control
        # the AuthenticationMiddleware which is currently controlled
        # by Django,
        request.session.save()

    result: Dict[str, Any] = {}
    new_email = email.strip()
    if user_profile.delivery_email != new_email and new_email != "":
        if user_profile.realm.email_changes_disabled and not user_profile.is_realm_admin:
            raise JsonableError(
                _("Email address changes are disabled in this organization."))

        error = validate_email_is_valid(
            new_email,
            get_realm_email_validator(user_profile.realm),
        )
        if error:
            raise JsonableError(error)

        try:
            validate_email_not_already_in_realm(
                user_profile.realm,
                new_email,
                verbose=False,
            )
        except ValidationError as e:
            raise JsonableError(e.message)

        ratelimited, time_until_free = RateLimitedUser(
            user_profile, domain="email_change_by_user").rate_limit()
        if ratelimited:
            raise RateLimited(time_until_free)

        do_start_email_change_process(user_profile, new_email)

    if user_profile.full_name != full_name and full_name.strip() != "":
        if name_changes_disabled(
                user_profile.realm) and not user_profile.is_realm_admin:
            # Failingly silently is fine -- they can't do it through the UI, so
            # they'd have to be trying to break the rules.
            pass
        else:
            # Note that check_change_full_name strips the passed name automatically
            check_change_full_name(user_profile, full_name, user_profile)

    # Loop over user_profile.property_types
    request_settings = {
        k: v
        for k, v in list(locals().items()) if k in user_profile.property_types
    }
    for k, v in list(request_settings.items()):
        if v is not None and getattr(user_profile, k) != v:
            do_change_user_setting(user_profile,
                                   k,
                                   v,
                                   acting_user=user_profile)

    if timezone is not None and user_profile.timezone != timezone:
        do_change_user_setting(user_profile,
                               "timezone",
                               timezone,
                               acting_user=user_profile)

    # TODO: Do this more generally.
    from zerver.lib.request import RequestNotes

    request_notes = RequestNotes.get_notes(request)
    for req_var in request.POST:
        if req_var not in request_notes.processed_parameters:
            request_notes.ignored_parameters.add(req_var)

    if len(request_notes.ignored_parameters) > 0:
        result["ignored_parameters_unsupported"] = list(
            request_notes.ignored_parameters)

    return json_success(result)
Пример #21
0
def json_stream_exists(request,
                       user_profile,
                       stream=REQ(),
                       autosubscribe=REQ(default=False)):
    # type: (HttpRequest, UserProfile, text_type, bool) -> HttpResponse
    return stream_exists_backend(request, user_profile, stream, autosubscribe)
Пример #22
0
def accounts_register(
    request: HttpRequest,
    key: str = REQ(default=""),
    timezone: str = REQ(default="", converter=to_timezone_or_empty),
    from_confirmation: Optional[str] = REQ(default=None),
    form_full_name: Optional[str] = REQ("full_name", default=None),
    source_realm_id: Optional[int] = REQ(default=None,
                                         converter=to_converted_or_fallback(
                                             to_non_negative_int, None)),
) -> HttpResponse:
    key_check_result = check_prereg_key(request, key)
    if isinstance(key_check_result, HttpResponse):
        return key_check_result

    prereg_user = key_check_result.content_object
    assert prereg_user is not None
    email = prereg_user.email
    realm_creation = prereg_user.realm_creation
    password_required = prereg_user.password_required

    role = prereg_user.invited_as
    if realm_creation:
        role = UserProfile.ROLE_REALM_OWNER

    try:
        validators.validate_email(email)
    except ValidationError:
        return render(request,
                      "zerver/invalid_email.html",
                      context={"invalid_email": True})

    if realm_creation:
        # For creating a new realm, there is no existing realm or domain
        realm = None
    else:
        if get_subdomain(request) != prereg_user.realm.string_id:
            return render_confirmation_key_error(
                request,
                ConfirmationKeyException(
                    ConfirmationKeyException.DOES_NOT_EXIST))
        realm = prereg_user.realm
        try:
            email_allowed_for_realm(email, realm)
        except DomainNotAllowedForRealmError:
            return render(
                request,
                "zerver/invalid_email.html",
                context={
                    "realm_name": realm.name,
                    "closed_domain": True
                },
            )
        except DisposableEmailError:
            return render(
                request,
                "zerver/invalid_email.html",
                context={
                    "realm_name": realm.name,
                    "disposable_emails_not_allowed": True
                },
            )
        except EmailContainsPlusError:
            return render(
                request,
                "zerver/invalid_email.html",
                context={
                    "realm_name": realm.name,
                    "email_contains_plus": True
                },
            )

        if realm.deactivated:
            # The user is trying to register for a deactivated realm. Advise them to
            # contact support.
            return redirect_to_deactivation_notice()

        try:
            validate_email_not_already_in_realm(realm, email)
        except ValidationError:
            return redirect_to_email_login_url(email)

        if settings.BILLING_ENABLED:
            try:
                check_spare_licenses_available_for_registering_new_user(
                    realm, email)
            except LicenseLimitError:
                return render(request, "zerver/no_spare_licenses.html")

    name_validated = False
    require_ldap_password = False

    if from_confirmation:
        try:
            del request.session["authenticated_full_name"]
        except KeyError:
            pass

        ldap_full_name = None
        if settings.POPULATE_PROFILE_VIA_LDAP:
            # If the user can be found in LDAP, we'll take the full name from the directory,
            # and further down create a form pre-filled with it.
            for backend in get_backends():
                if isinstance(backend, LDAPBackend):
                    try:
                        ldap_username = backend.django_to_ldap_username(email)
                    except ZulipLDAPExceptionNoMatchingLDAPUser:
                        logging.warning(
                            "New account email %s could not be found in LDAP",
                            email)
                        break

                    # Note that this `ldap_user` object is not a
                    # `ZulipLDAPUser` with a `Realm` attached, so
                    # calling `.populate_user()` on it will crash.
                    # This is OK, since we're just accessing this user
                    # to extract its name.
                    #
                    # TODO: We should potentially be accessing this
                    # user to sync its initial avatar and custom
                    # profile fields as well, if we indeed end up
                    # creating a user account through this flow,
                    # rather than waiting until `manage.py
                    # sync_ldap_user_data` runs to populate it.
                    ldap_user = _LDAPUser(backend, ldap_username)

                    try:
                        ldap_full_name = backend.get_mapped_name(ldap_user)
                    except TypeError:
                        break

                    # Check whether this is ZulipLDAPAuthBackend,
                    # which is responsible for authentication and
                    # requires that LDAP accounts enter their LDAP
                    # password to register, or ZulipLDAPUserPopulator,
                    # which just populates UserProfile fields (no auth).
                    require_ldap_password = isinstance(backend,
                                                       ZulipLDAPAuthBackend)
                    break

        if ldap_full_name:
            # We don't use initial= here, because if the form is
            # complete (that is, no additional fields need to be
            # filled out by the user) we want the form to validate,
            # so they can be directly registered without having to
            # go through this interstitial.
            form = RegistrationForm({"full_name": ldap_full_name},
                                    realm_creation=realm_creation)
            request.session["authenticated_full_name"] = ldap_full_name
            name_validated = True
        elif realm is not None and realm.is_zephyr_mirror_realm:
            # For MIT users, we can get an authoritative name from Hesiod.
            # Technically we should check that this is actually an MIT
            # realm, but we can cross that bridge if we ever get a non-MIT
            # zephyr mirroring realm.
            hesiod_name = compute_mit_user_fullname(email)
            form = RegistrationForm(
                initial={
                    "full_name": hesiod_name if "@" not in hesiod_name else ""
                },
                realm_creation=realm_creation,
            )
            name_validated = True
        elif prereg_user.full_name:
            if prereg_user.full_name_validated:
                request.session[
                    "authenticated_full_name"] = prereg_user.full_name
                name_validated = True
                form = RegistrationForm({"full_name": prereg_user.full_name},
                                        realm_creation=realm_creation)
            else:
                form = RegistrationForm(
                    initial={"full_name": prereg_user.full_name},
                    realm_creation=realm_creation)
        elif form_full_name is not None:
            form = RegistrationForm(
                initial={"full_name": form_full_name},
                realm_creation=realm_creation,
            )
        else:
            form = RegistrationForm(realm_creation=realm_creation)
    else:
        postdata = request.POST.copy()
        if name_changes_disabled(realm):
            # If we populate profile information via LDAP and we have a
            # verified name from you on file, use that. Otherwise, fall
            # back to the full name in the request.
            try:
                postdata.update(
                    full_name=request.session["authenticated_full_name"])
                name_validated = True
            except KeyError:
                pass
        form = RegistrationForm(postdata, realm_creation=realm_creation)

    if not (password_auth_enabled(realm) and password_required):
        form["password"].field.required = False

    if form.is_valid():
        if password_auth_enabled(realm) and form["password"].field.required:
            password = form.cleaned_data["password"]
        else:
            # If the user wasn't prompted for a password when
            # completing the authentication form (because they're
            # signing up with SSO and no password is required), set
            # the password field to `None` (Which causes Django to
            # create an unusable password).
            password = None

        if realm_creation:
            string_id = form.cleaned_data["realm_subdomain"]
            realm_name = form.cleaned_data["realm_name"]
            realm_type = form.cleaned_data["realm_type"]
            is_demo_org = form.cleaned_data["is_demo_organization"]
            realm = do_create_realm(string_id,
                                    realm_name,
                                    org_type=realm_type,
                                    is_demo_organization=is_demo_org)
            setup_realm_internal_bots(realm)
        assert realm is not None

        full_name = form.cleaned_data["full_name"]
        enable_marketing_emails = form.cleaned_data["enable_marketing_emails"]
        default_stream_group_names = request.POST.getlist(
            "default_stream_group")
        default_stream_groups = lookup_default_stream_groups(
            default_stream_group_names, realm)

        if source_realm_id is not None:
            # Non-integer realm_id values like "string" are treated
            # like the "Do not import" value of "".
            source_profile: Optional[UserProfile] = get_source_profile(
                email, source_realm_id)
        else:
            source_profile = None

        if not realm_creation:
            try:
                existing_user_profile: Optional[
                    UserProfile] = get_user_by_delivery_email(email, realm)
            except UserProfile.DoesNotExist:
                existing_user_profile = None
        else:
            existing_user_profile = None

        user_profile: Optional[UserProfile] = None
        return_data: Dict[str, bool] = {}
        if ldap_auth_enabled(realm):
            # If the user was authenticated using an external SSO
            # mechanism like Google or GitHub auth, then authentication
            # will have already been done before creating the
            # PreregistrationUser object with password_required=False, and
            # so we don't need to worry about passwords.
            #
            # If instead the realm is using EmailAuthBackend, we will
            # set their password above.
            #
            # But if the realm is using LDAPAuthBackend, we need to verify
            # their LDAP password (which will, as a side effect, create
            # the user account) here using authenticate.
            # pregeg_user.realm_creation carries the information about whether
            # we're in realm creation mode, and the ldap flow will handle
            # that and create the user with the appropriate parameters.
            user_profile = authenticate(
                request=request,
                username=email,
                password=password,
                realm=realm,
                prereg_user=prereg_user,
                return_data=return_data,
            )
            if user_profile is None:
                can_use_different_backend = email_auth_enabled(realm) or (len(
                    get_external_method_dicts(realm)) > 0)
                if settings.LDAP_APPEND_DOMAIN:
                    # In LDAP_APPEND_DOMAIN configurations, we don't allow making a non-LDAP account
                    # if the email matches the ldap domain.
                    can_use_different_backend = can_use_different_backend and (
                        not email_belongs_to_ldap(realm, email))
                if return_data.get(
                        "no_matching_ldap_user") and can_use_different_backend:
                    # If both the LDAP and Email or Social auth backends are
                    # enabled, and there's no matching user in the LDAP
                    # directory then the intent is to create a user in the
                    # realm with their email outside the LDAP organization
                    # (with e.g. a password stored in the Zulip database,
                    # not LDAP).  So we fall through and create the new
                    # account.
                    pass
                else:
                    # TODO: This probably isn't going to give a
                    # user-friendly error message, but it doesn't
                    # particularly matter, because the registration form
                    # is hidden for most users.
                    view_url = reverse("login")
                    query = urlencode({"email": email})
                    redirect_url = append_url_query_string(view_url, query)
                    return HttpResponseRedirect(redirect_url)
            elif not realm_creation:
                # Since we'll have created a user, we now just log them in.
                return login_and_go_to_home(request, user_profile)
            else:
                # With realm_creation=True, we're going to return further down,
                # after finishing up the creation process.
                pass

        if existing_user_profile is not None and existing_user_profile.is_mirror_dummy:
            user_profile = existing_user_profile
            do_activate_mirror_dummy_user(user_profile,
                                          acting_user=user_profile)
            do_change_password(user_profile, password)
            do_change_full_name(user_profile, full_name, user_profile)
            do_change_user_setting(user_profile,
                                   "timezone",
                                   timezone,
                                   acting_user=user_profile)
            # TODO: When we clean up the `do_activate_mirror_dummy_user` code path,
            # make it respect invited_as_admin / is_realm_admin.

        if user_profile is None:
            user_profile = do_create_user(
                email,
                password,
                realm,
                full_name,
                prereg_user=prereg_user,
                role=role,
                tos_version=settings.TOS_VERSION,
                timezone=timezone,
                default_stream_groups=default_stream_groups,
                source_profile=source_profile,
                realm_creation=realm_creation,
                acting_user=None,
                enable_marketing_emails=enable_marketing_emails,
            )

        if realm_creation:
            bulk_add_subscriptions(realm, [realm.signup_notifications_stream],
                                   [user_profile],
                                   acting_user=None)
            send_initial_realm_messages(realm)

            # Because for realm creation, registration happens on the
            # root domain, we need to log them into the subdomain for
            # their new realm.
            return redirect_and_log_into_subdomain(
                ExternalAuthResult(user_profile=user_profile,
                                   data_dict={"is_realm_creation": True}))

        # This dummy_backend check below confirms the user is
        # authenticating to the correct subdomain.
        auth_result = authenticate(
            username=user_profile.delivery_email,
            realm=realm,
            return_data=return_data,
            use_dummy_backend=True,
        )
        if return_data.get("invalid_subdomain"):
            # By construction, this should never happen.
            logging.error(
                "Subdomain mismatch in registration %s: %s",
                realm.subdomain,
                user_profile.delivery_email,
            )
            return redirect("/")

        return login_and_go_to_home(request, auth_result)

    return render(
        request,
        "zerver/register.html",
        context={
            "form":
            form,
            "email":
            email,
            "key":
            key,
            "full_name":
            request.session.get("authenticated_full_name", None),
            "lock_name":
            name_validated and name_changes_disabled(realm),
            # password_auth_enabled is normally set via our context processor,
            # but for the registration form, there is no logged in user yet, so
            # we have to set it here.
            "creating_new_team":
            realm_creation,
            "password_required":
            password_auth_enabled(realm) and password_required,
            "require_ldap_password":
            require_ldap_password,
            "password_auth_enabled":
            password_auth_enabled(realm),
            "root_domain_available":
            is_root_domain_available(),
            "default_stream_groups":
            [] if realm is None else get_default_stream_groups(realm),
            "accounts":
            get_accounts_for_email(email),
            "MAX_REALM_NAME_LENGTH":
            str(Realm.MAX_REALM_NAME_LENGTH),
            "MAX_NAME_LENGTH":
            str(UserProfile.MAX_NAME_LENGTH),
            "MAX_PASSWORD_LENGTH":
            str(form.MAX_PASSWORD_LENGTH),
            "MAX_REALM_SUBDOMAIN_LENGTH":
            str(Realm.MAX_REALM_SUBDOMAIN_LENGTH),
            "sorted_realm_types":
            sorted(Realm.ORG_TYPES.values(), key=lambda d: d["display_order"]),
        },
    )
Пример #23
0
def update_stream_backend(
    request: HttpRequest,
    user_profile: UserProfile,
    stream_id: int,
    description: Optional[str] = REQ(validator=check_capped_string(
        Stream.MAX_DESCRIPTION_LENGTH),
                                     default=None),
    is_private: Optional[bool] = REQ(validator=check_bool, default=None),
    is_announcement_only: Optional[bool] = REQ(validator=check_bool,
                                               default=None),
    stream_post_policy: Optional[int] = REQ(validator=check_int_in(
        Stream.STREAM_POST_POLICY_TYPES),
                                            default=None),
    history_public_to_subscribers: Optional[bool] = REQ(validator=check_bool,
                                                        default=None),
    new_name: Optional[str] = REQ(validator=check_string, default=None),
    message_retention_days: Optional[Union[int, str]] = REQ(
        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 = 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()
        message_retention_days_value = parse_message_retention_days(
            message_retention_days,
            Stream.MESSAGE_RETENTION_SPECIAL_VALUES_MAP)
        do_change_stream_message_retention_days(stream,
                                                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)
    if new_name is not None:
        new_name = new_name.strip()
        if stream.name == new_name:
            return json_error(_("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)

    # But we require even realm administrators to be actually
    # subscribed to make a private stream public.
    if is_private is not None:
        (stream, recipient, sub) = access_stream_by_id(user_profile, stream_id)
        do_change_stream_invite_only(stream, is_private,
                                     history_public_to_subscribers)
    return json_success()
Пример #24
0
def find_account(
    request: HttpRequest,
    raw_emails: Optional[str] = REQ("emails", default=None)
) -> HttpResponse:
    url = reverse("find_account")

    emails: List[str] = []
    if request.method == "POST":
        form = FindMyTeamForm(request.POST)
        if form.is_valid():
            emails = form.cleaned_data["emails"]
            for i in range(len(emails)):
                try:
                    rate_limit_request_by_ip(request,
                                             domain="sends_email_by_ip")
                except RateLimited as e:
                    assert e.secs_to_freedom is not None
                    return render(
                        request,
                        "zerver/rate_limit_exceeded.html",
                        context={"retry_after": int(e.secs_to_freedom)},
                        status=429,
                    )

            # Django doesn't support __iexact__in lookup with EmailField, so we have
            # to use Qs to get around that without needing to do multiple queries.
            emails_q = Q()
            for email in emails:
                emails_q |= Q(delivery_email__iexact=email)

            user_profiles = UserProfile.objects.filter(
                emails_q,
                is_active=True,
                is_bot=False,
                realm__deactivated=False)

            # We organize the data in preparation for sending exactly
            # one outgoing email per provided email address, with each
            # email listing all of the accounts that email address has
            # with the current Zulip server.
            context: Dict[str, Dict[str, Any]] = {}
            for user in user_profiles:
                key = user.delivery_email.lower()
                context.setdefault(key, {})
                context[key].setdefault("realms", [])
                context[key]["realms"].append(user.realm)
                context[key]["external_host"] = settings.EXTERNAL_HOST
                # This value will end up being the last user ID among
                # matching accounts; since it's only used for minor
                # details like language, that arbitrary choice is OK.
                context[key]["to_user_id"] = user.id

            for delivery_email, realm_context in context.items():
                realm_context["email"] = delivery_email
                send_email(
                    "zerver/emails/find_team",
                    to_user_ids=[realm_context["to_user_id"]],
                    context=realm_context,
                    from_address=FromAddress.SUPPORT,
                    request=request,
                )

            # Note: Show all the emails in the result otherwise this
            # feature can be used to ascertain which email addresses
            # are associated with Zulip.
            data = urllib.parse.urlencode({"emails": ",".join(emails)})
            return redirect(append_url_query_string(url, data))
    else:
        form = FindMyTeamForm()
        # The below validation is perhaps unnecessary, in that we
        # shouldn't get able to get here with an invalid email unless
        # the user hand-edits the URLs.
        if raw_emails:
            for email in raw_emails.split(","):
                try:
                    validators.validate_email(email)
                    emails.append(email)
                except ValidationError:
                    pass

    return render(
        request,
        "zerver/find_account.html",
        context={
            "form": form,
            "current_url": lambda: url,
            "emails": emails
        },
    )
Пример #25
0
def json_get_stream_id(
    request: HttpRequest,
    user_profile: UserProfile,
    stream_name: str = REQ('stream')) -> HttpResponse:
    (stream, recipient, sub) = access_stream_by_name(user_profile, stream_name)
    return json_success({'stream_id': stream.id})
Пример #26
0
def patch_bot_backend(
    request: HttpRequest,
    user_profile: UserProfile,
    bot_id: int,
    full_name: Optional[str] = REQ(default=None),
    bot_owner_id: Optional[int] = REQ(default=None),
    config_data: Optional[Dict[str, str]] = REQ(
        default=None, validator=check_dict(value_validator=check_string)),
    service_payload_url: Optional[str] = REQ(validator=check_url,
                                             default=None),
    service_interface: Optional[int] = REQ(validator=check_int, default=1),
    default_sending_stream: Optional[str] = REQ(default=None),
    default_events_register_stream: Optional[str] = REQ(default=None),
    default_all_public_streams: Optional[bool] = REQ(default=None,
                                                     validator=check_bool)
) -> HttpResponse:
    bot = access_bot_by_id(user_profile, bot_id)

    if full_name is not None:
        check_change_bot_full_name(bot, full_name, user_profile)
    if bot_owner_id is not None:
        try:
            owner = get_user_profile_by_id_in_realm(bot_owner_id,
                                                    user_profile.realm)
        except UserProfile.DoesNotExist:
            return json_error(_('Failed to change owner, no such user'))
        if not owner.is_active:
            return json_error(_('Failed to change owner, user is deactivated'))
        if owner.is_bot:
            return json_error(
                _("Failed to change owner, bots can't own other bots"))

        previous_owner = bot.bot_owner
        if previous_owner != owner:
            do_change_bot_owner(bot, owner, user_profile)

    if default_sending_stream is not None:
        if default_sending_stream == "":
            stream = None  # type: Optional[Stream]
        else:
            (stream, recipient,
             sub) = access_stream_by_name(user_profile, default_sending_stream)
        do_change_default_sending_stream(bot, stream)
    if default_events_register_stream is not None:
        if default_events_register_stream == "":
            stream = None
        else:
            (stream, recipient,
             sub) = access_stream_by_name(user_profile,
                                          default_events_register_stream)
        do_change_default_events_register_stream(bot, stream)
    if default_all_public_streams is not None:
        do_change_default_all_public_streams(bot, default_all_public_streams)

    if service_payload_url is not None:
        check_valid_interface_type(service_interface)
        assert service_interface is not None
        do_update_outgoing_webhook_service(bot, service_interface,
                                           service_payload_url)

    if config_data is not None:
        do_update_bot_config_data(bot, config_data)

    if len(request.FILES) == 0:
        pass
    elif len(request.FILES) == 1:
        user_file = list(request.FILES.values())[0]
        upload_avatar_image(user_file, user_profile, bot)
        avatar_source = UserProfile.AVATAR_FROM_USER
        do_change_avatar_fields(bot, avatar_source)
    else:
        return json_error(_("You may only upload one file at a time"))

    json_result = dict(
        full_name=bot.full_name,
        avatar_url=avatar_url(bot),
        service_interface=service_interface,
        service_payload_url=service_payload_url,
        config_data=config_data,
        default_sending_stream=get_stream_name(bot.default_sending_stream),
        default_events_register_stream=get_stream_name(
            bot.default_events_register_stream),
        default_all_public_streams=bot.default_all_public_streams,
    )

    # Don't include the bot owner in case it is not set.
    # Default bots have no owner.
    if bot.bot_owner is not None:
        json_result['bot_owner'] = bot.bot_owner.email

    return json_success(json_result)
Пример #27
0
def remove_android_reg_id(request, user_profile, token=REQ()):
    # type: (HttpRequest, UserProfile, str) -> HttpResponse
    validate_token(token)
    remove_push_device_token(user_profile, token, PushDeviceToken.GCM)
    return json_success()
Пример #28
0
def add_bot_backend(
    request: HttpRequest,
    user_profile: UserProfile,
    full_name_raw: str = REQ("full_name"),
    short_name_raw: str = REQ("short_name"),
    bot_type: int = REQ(validator=check_int, default=UserProfile.DEFAULT_BOT),
    payload_url: Optional[str] = REQ(validator=check_url, default=""),
    service_name: Optional[str] = REQ(default=None),
    config_data: Dict[str, str] = REQ(
        default={}, validator=check_dict(value_validator=check_string)),
    interface_type: int = REQ(validator=check_int, default=Service.GENERIC),
    default_sending_stream_name: Optional[str] = REQ('default_sending_stream',
                                                     default=None),
    default_events_register_stream_name: Optional[str] = REQ(
        'default_events_register_stream', default=None),
    default_all_public_streams: Optional[bool] = REQ(validator=check_bool,
                                                     default=None)
) -> HttpResponse:
    short_name = check_short_name(short_name_raw)
    service_name = service_name or short_name
    short_name += "-bot"
    full_name = check_full_name(full_name_raw)
    email = '%s@%s' % (short_name, user_profile.realm.get_bot_domain())
    form = CreateUserForm({'full_name': full_name, 'email': email})

    if bot_type == UserProfile.EMBEDDED_BOT:
        if not settings.EMBEDDED_BOTS_ENABLED:
            return json_error(_("Embedded bots are not enabled."))
        if service_name not in [bot.name for bot in EMBEDDED_BOTS]:
            return json_error(_("Invalid embedded bot name."))

    if not form.is_valid():
        # We validate client-side as well
        return json_error(_('Bad name or username'))
    try:
        get_user(email, user_profile.realm)
        return json_error(_("Username already in use"))
    except UserProfile.DoesNotExist:
        pass

    check_bot_name_available(
        realm_id=user_profile.realm_id,
        full_name=full_name,
    )

    check_bot_creation_policy(user_profile, bot_type)
    check_valid_bot_type(user_profile, bot_type)
    check_valid_interface_type(interface_type)

    if len(request.FILES) == 0:
        avatar_source = UserProfile.AVATAR_FROM_GRAVATAR
    elif len(request.FILES) != 1:
        return json_error(_("You may only upload one file at a time"))
    else:
        avatar_source = UserProfile.AVATAR_FROM_USER

    default_sending_stream = None
    if default_sending_stream_name is not None:
        (default_sending_stream, ignored_rec,
         ignored_sub) = access_stream_by_name(user_profile,
                                              default_sending_stream_name)

    default_events_register_stream = None
    if default_events_register_stream_name is not None:
        (default_events_register_stream, ignored_rec,
         ignored_sub) = access_stream_by_name(
             user_profile, default_events_register_stream_name)

    if bot_type == UserProfile.EMBEDDED_BOT:
        check_valid_bot_config(service_name, config_data)

    bot_profile = do_create_user(
        email=email,
        password='',
        realm=user_profile.realm,
        full_name=full_name,
        short_name=short_name,
        bot_type=bot_type,
        bot_owner=user_profile,
        avatar_source=avatar_source,
        default_sending_stream=default_sending_stream,
        default_events_register_stream=default_events_register_stream,
        default_all_public_streams=default_all_public_streams)
    if len(request.FILES) == 1:
        user_file = list(request.FILES.values())[0]
        upload_avatar_image(user_file, user_profile, bot_profile)

    if bot_type in (UserProfile.OUTGOING_WEBHOOK_BOT,
                    UserProfile.EMBEDDED_BOT):
        add_service(name=service_name,
                    user_profile=bot_profile,
                    base_url=payload_url,
                    interface=interface_type,
                    token=generate_api_key())

    if bot_type == UserProfile.EMBEDDED_BOT:
        for key, value in config_data.items():
            set_bot_config(bot_profile, key, value)

    notify_created_bot(bot_profile)

    api_key = get_api_key(bot_profile)

    json_result = dict(
        api_key=api_key,
        avatar_url=avatar_url(bot_profile),
        default_sending_stream=get_stream_name(
            bot_profile.default_sending_stream),
        default_events_register_stream=get_stream_name(
            bot_profile.default_events_register_stream),
        default_all_public_streams=bot_profile.default_all_public_streams,
    )
    return json_success(json_result)
Пример #29
0
def api_fetch_api_key(
    request: HttpRequest, username: str = REQ(), password: str = REQ()
) -> HttpResponse:
    return_data = {}  # type: Dict[str, bool]
    subdomain = get_subdomain(request)
    realm = get_realm(subdomain)
    if username == "google-oauth2-token":
        # This code path is auth for the legacy Android app
        user_profile = authenticate(google_oauth2_token=password,
                                    realm=realm,
                                    return_data=return_data)
    else:
        if not ldap_auth_enabled(realm=get_realm_from_request(request)):
            # In case we don't authenticate against LDAP, check for a valid
            # email. LDAP backend can authenticate against a non-email.
            validate_login_email(username)

        user_profile = authenticate(username=username,
                                    password=password,
                                    realm=realm,
                                    return_data=return_data)
    if return_data.get("inactive_user"):
        return json_error(_("Your account has been disabled."),
                          data={"reason": "user disable"},
                          status=403)
    if return_data.get("inactive_realm"):
        return json_error(_("This organization has been deactivated."),
                          data={"reason": "realm deactivated"},
                          status=403)
    if return_data.get("password_auth_disabled"):
        return json_error(_("Password auth is disabled in your team."),
                          data={"reason": "password auth disabled"},
                          status=403)
    if user_profile is None:
        if return_data.get("valid_attestation"):
            # We can leak that the user is unregistered iff
            # they present a valid authentication string for the user.
            return json_error(
                _("This user is not registered; do so from a browser."),
                data={"reason": "unregistered"},
                status=403)
        return json_error(_("Your username or password is incorrect."),
                          data={"reason": "incorrect_creds"},
                          status=403)

    # Maybe sending 'user_logged_in' signal is the better approach:
    #   user_logged_in.send(sender=user_profile.__class__, request=request, user=user_profile)
    # Not doing this only because over here we don't add the user information
    # in the session. If the signal receiver assumes that we do then that
    # would cause problems.
    email_on_new_login(sender=user_profile.__class__,
                       request=request,
                       user=user_profile)

    # Mark this request as having a logged-in user for our server logs.
    process_client(request, user_profile)
    request._email = user_profile.email

    return json_success({
        "api_key": user_profile.api_key,
        "email": user_profile.email
    })
Пример #30
0
def add_default_stream(request: HttpRequest,
                       user_profile: UserProfile,
                       stream_id: int=REQ(validator=check_int)) -> HttpResponse:
    (stream, recipient, sub) = access_stream_by_id(user_profile, stream_id)
    do_add_default_stream(stream)
    return json_success()