Ejemplo n.º 1
0
def fetch_user_profile_cross_realm(response: TableData, config: Config, context: Context) -> None:
    realm = context['realm']

    if realm.string_id == settings.SYSTEM_BOT_REALM:
        response['zerver_userprofile_crossrealm'] = []
    else:
        response['zerver_userprofile_crossrealm'] = [dict(email=x.email, id=x.id) for x in [
            get_system_bot(settings.NOTIFICATION_BOT),
            get_system_bot(settings.EMAIL_GATEWAY_BOT),
            get_system_bot(settings.WELCOME_BOT),
        ]]
Ejemplo n.º 2
0
def zulip_server_error(report: Dict[str, Any]) -> None:
    email_subject = '%(node)s: %(message)s' % (report)

    logger_str = logger_repr(report)
    user_info = user_info_str(report)
    deployment = deployment_repr(report)

    if report['has_request']:
        request_repr = (
            "Request info:\n~~~~\n"
            "- path: %(path)s\n"
            "- %(method)s: %(data)s\n") % (report)
        for field in ["REMOTE_ADDR", "QUERY_STRING", "SERVER_NAME"]:
            val = report.get(field.lower())
            if field == "QUERY_STRING":
                val = clean_data_from_query_parameters(str(val))
            request_repr += "- %s: \"%s\"\n" % (field, val)
        request_repr += "~~~~"
    else:
        request_repr = "Request info: none"

    message = ("%s\nError generated by %s\n\n~~~~ pytb\n%s\n\n~~~~\n%s\n%s"
               % (logger_str, user_info, report['stack_trace'], deployment, request_repr))

    realm = get_system_bot(settings.ERROR_BOT).realm
    internal_send_message(realm, settings.ERROR_BOT, "stream", "errors",
                          format_email_subject(email_subject), message)
Ejemplo n.º 3
0
    def handle(self, *args: Any, **options: Any) -> None:
        if Realm.objects.count() > 0:
            print("Database already initialized; doing nothing.")
            return
        realm = Realm.objects.create(string_id=settings.INTERNAL_BOT_DOMAIN.split('.')[0])

        names = [(settings.FEEDBACK_BOT_NAME, settings.FEEDBACK_BOT)]
        create_users(realm, names, bot_type=UserProfile.DEFAULT_BOT)

        get_client("website")
        get_client("API")

        internal_bots = [(bot['name'], bot['email_template'] % (settings.INTERNAL_BOT_DOMAIN,))
                         for bot in settings.INTERNAL_BOTS]
        create_users(realm, internal_bots, bot_type=UserProfile.DEFAULT_BOT)
        # Set the owners for these bots to the bots themselves
        bots = UserProfile.objects.filter(email__in=[bot_info[1] for bot_info in internal_bots])
        for bot in bots:
            bot.bot_owner = bot
            bot.save()

        # Initialize the email gateway bot as an API Super User
        email_gateway_bot = get_system_bot(settings.EMAIL_GATEWAY_BOT)
        email_gateway_bot.is_api_super_user = True
        email_gateway_bot.save()

        self.stdout.write("Successfully populated database with initial data.\n")
        self.stdout.write("Please run ./manage.py generate_realm_creation_link "
                          "to generate link for creating organization")
Ejemplo n.º 4
0
def extract_and_upload_attachments(message: message.Message, realm: Realm) -> str:
    user_profile = get_system_bot(settings.EMAIL_GATEWAY_BOT)
    attachment_links = []

    payload = message.get_payload()
    if not isinstance(payload, list):
        # This is not a multipart message, so it can't contain attachments.
        return ""

    for part in payload:
        content_type = part.get_content_type()
        filename = part.get_filename()
        if filename:
            attachment = part.get_payload(decode=True)
            if isinstance(attachment, bytes):
                s3_url = upload_message_file(filename, len(attachment), content_type,
                                             attachment,
                                             user_profile,
                                             target_realm=realm)
                formatted_link = "[%s](%s)" % (filename, s3_url)
                attachment_links.append(formatted_link)
            else:
                logger.warning("Payload is not bytes (invalid attachment %s in message from %s)." %
                               (filename, message.get("From")))

    return "\n".join(attachment_links)
Ejemplo n.º 5
0
def report_to_zulip(error_message: str) -> None:
    if settings.ERROR_BOT is None:
        return
    error_bot = get_system_bot(settings.ERROR_BOT)
    error_stream = Stream.objects.get(name="errors", realm=error_bot.realm)
    send_zulip(settings.ERROR_BOT, error_stream, "email mirror error",
               """~~~\n%s\n~~~""" % (error_message,))
Ejemplo n.º 6
0
def deliver_feedback_by_zulip(message: Mapping[str, Any]) -> None:
    subject = "%s" % (message["sender_email"],)

    if len(subject) > 60:
        subject = subject[:57].rstrip() + "..."

    content = ''
    sender_email = message['sender_email']

    # We generate ticket numbers if it's been more than a few minutes
    # since their last message.  This avoids some noise when people use
    # enter-send.
    need_ticket = has_enough_time_expired_since_last_message(sender_email, 180)

    if need_ticket:
        ticket_number = get_ticket_number()
        content += '\n~~~'
        content += '\nticket Z%03d (@support please ack)' % (ticket_number,)
        content += '\nsender: %s' % (message['sender_full_name'],)
        content += '\nemail: %s' % (sender_email,)
        if 'sender_realm_str' in message:
            content += '\nrealm: %s' % (message['sender_realm_str'],)
        content += '\n~~~'
        content += '\n\n'

    content += message['content']

    user_profile = get_system_bot(settings.FEEDBACK_BOT)
    internal_send_message(user_profile.realm, settings.FEEDBACK_BOT,
                          "stream", settings.FEEDBACK_STREAM, subject, content)
Ejemplo n.º 7
0
def send_initial_pms(user):
    # type: (UserProfile) -> None
    content = """Welcome to Zulip!

This is a great place to test formatting, sending, and editing messages.
Click anywhere on this message to reply. A compose box will open at the bottom of the screen."""

    internal_send_private_message(user.realm, get_system_bot(settings.WELCOME_BOT),
                                  user.email, content)
Ejemplo n.º 8
0
 def test_stream_error_pm_to_bot_owner(self) -> None:
     # Note taht this is really just a test for check_send_webhook_message
     self.STREAM_NAME = 'nonexistent'
     self.url = self.build_webhook_url()
     notification_bot = get_system_bot(settings.NOTIFICATION_BOT)
     expected_message = "Hi there! We thought you'd like to know that your bot **Zulip Webhook Bot** just tried to send a message to stream `nonexistent`, but that stream does not yet exist. To create it, click the gear in the left-side stream list."
     self.send_and_test_private_message('goodbye', expected_message=expected_message,
                                        content_type='application/x-www-form-urlencoded',
                                        sender=notification_bot)
Ejemplo n.º 9
0
def export_avatars_from_local(realm: Realm, local_dir: Path, output_dir: Path) -> None:

    count = 0
    records = []

    users = list(UserProfile.objects.filter(realm=realm))
    users += [
        get_system_bot(settings.NOTIFICATION_BOT),
        get_system_bot(settings.EMAIL_GATEWAY_BOT),
        get_system_bot(settings.WELCOME_BOT),
    ]
    for user in users:
        if user.avatar_source == UserProfile.AVATAR_FROM_GRAVATAR:
            continue

        avatar_path = user_avatar_path_from_ids(user.id, realm.id)
        wildcard = os.path.join(local_dir, avatar_path + '.*')

        for local_path in glob.glob(wildcard):
            logging.info('Copying avatar file for user %s from %s' % (
                user.email, local_path))
            fn = os.path.relpath(local_path, local_dir)
            output_path = os.path.join(output_dir, fn)
            os.makedirs(str(os.path.dirname(output_path)), exist_ok=True)
            subprocess.check_call(["cp", "-a", str(local_path), str(output_path)])
            stat = os.stat(local_path)
            record = dict(realm_id=realm.id,
                          user_profile_id=user.id,
                          user_profile_email=user.email,
                          s3_path=fn,
                          path=fn,
                          size=stat.st_size,
                          last_modified=stat.st_mtime,
                          content_type=None)
            records.append(record)

            count += 1

            if (count % 100 == 0):
                logging.info("Finished %s" % (count,))

    with open(os.path.join(output_dir, "records.json"), "w") as records_file:
        ujson.dump(records, records_file, indent=4)
Ejemplo n.º 10
0
def zulip_browser_error(report: Dict[str, Any]) -> None:
    subject = "JS error: %s" % (report['user_email'],)

    user_info = user_info_str(report)

    body = "User: %s\n" % (user_info,)
    body += ("Message: %(message)s\n"
             % (report))

    realm = get_system_bot(settings.ERROR_BOT).realm
    internal_send_message(realm, settings.ERROR_BOT,
                          "stream", "errors", format_subject(subject), body)
Ejemplo n.º 11
0
def send_initial_pms(user):
    # type: (UserProfile) -> None
    content = (
        "Hello, and welcome to Zulip!\n\nThis is a private message from me, Welcome Bot. "
        "Here are some tips to get you started:\n"
        "* Download our [Desktop and mobile apps](/apps)\n"
        "* Customize your account and notifications on your [Settings page](#settings).\n"
        "* Check out our !modal_link(#keyboard-shortcuts, Keyboard shortcuts)\n\n"
        "The most important shortcut is `r` or `Enter` to reply.\n\n"
        "Practice sending a few messages by replying to this conversation. If you're not into "
        "keyboards, that's okay too; clicking anywhere on this message will also do the trick!")

    internal_send_private_message(user.realm, get_system_bot(settings.WELCOME_BOT),
                                  user.email, content)
Ejemplo n.º 12
0
def highlight_html_differences(s1, s2):
    # type: (Text, Text) -> Text
    differ = diff_match_patch()
    ops = differ.diff_main(s1, s2)
    differ.diff_cleanupSemantic(ops)
    retval = u''
    in_tag = False

    idx = 0
    while idx < len(ops):
        op, text = ops[idx]
        next_op = None
        if idx != len(ops) - 1:
            next_op, next_text = ops[idx + 1]
        if op == diff_match_patch.DIFF_DELETE and next_op == diff_match_patch.DIFF_INSERT:
            # Replace operation
            chunks, in_tag = chunkize(next_text, in_tag)
            retval += highlight_chunks(chunks, highlight_replaced)
            idx += 1
        elif op == diff_match_patch.DIFF_INSERT and next_op == diff_match_patch.DIFF_DELETE:
            # Replace operation
            # I have no idea whether diff_match_patch generates inserts followed
            # by deletes, but it doesn't hurt to handle them
            chunks, in_tag = chunkize(text, in_tag)
            retval += highlight_chunks(chunks, highlight_replaced)
            idx += 1
        elif op == diff_match_patch.DIFF_DELETE:
            retval += highlight_deleted('&nbsp;')
        elif op == diff_match_patch.DIFF_INSERT:
            chunks, in_tag = chunkize(text, in_tag)
            retval += highlight_chunks(chunks, highlight_inserted)
        elif op == diff_match_patch.DIFF_EQUAL:
            chunks, in_tag = chunkize(text, in_tag)
            retval += text
        idx += 1

    if not verify_html(retval):
        from zerver.lib.actions import internal_send_message
        from zerver.models import get_system_bot
        # We probably want more information here
        logging.getLogger('').error('HTML diff produced mal-formed HTML')

        if settings.ERROR_BOT is not None:
            subject = "HTML diff failure on %s" % (platform.node(),)
            realm = get_system_bot(settings.ERROR_BOT).realm
            internal_send_message(realm, settings.ERROR_BOT, "stream",
                                  "errors", subject, "HTML diff produced malformed HTML")
        return s2

    return retval
Ejemplo n.º 13
0
def send_initial_realm_messages(realm):
    # type: (Realm) -> None
    welcome_bot = get_system_bot(settings.WELCOME_BOT)
    # Make sure each stream created in the realm creation process has at least one message below
    # Order corresponds to the ordering of the streams on the left sidebar, to make the initial Home
    # view slightly less overwhelming
    welcome_messages = [
        {'stream': Realm.DEFAULT_NOTIFICATION_STREAM_NAME,
         'topic': "welcome",
         'content': "This is a message on stream `%s` with the topic `welcome`. We'll use this stream "
         "for system-generated notifications." % (Realm.DEFAULT_NOTIFICATION_STREAM_NAME,)},
        {'stream': "core team",
         'topic': "private streams",
         'content': "This is a private stream. Only admins and people you invite "
         "to the stream will be able to see that this stream exists."},
        {'stream': "general",
         'topic': "welcome",
         'content': "Welcome to #**general**."},
        {'stream': "new members",
         'topic': "onboarding",
         'content': "A #**new members** stream is great for onboarding new members.\n\nIf you're "
         "reading this and aren't the first person here, introduce yourself in a new thread "
         "using your name as the topic! Type `c` or click on `New Topic` at the bottom of the "
         "screen to start a new topic."},
        {'stream': "zulip",
         'topic': "topic demonstration",
         'content': "Here is a message in one topic. Replies to this message will go to this topic."},
        {'stream': "zulip",
         'topic': "topic demonstration",
         'content': "A second message in this topic. With [turtles](/static/images/cute/turtle.png)!"},
        {'stream': "zulip",
         'topic': "second topic",
         'content': "This is a message in a second topic.\n\nTopics are similar to email subjects, "
         "in that each conversation should get its own topic. Keep them short, though; one "
         "or two words will do it!"},
    ]  # type: List[Dict[str, Text]]
    messages = [internal_prep_stream_message(
        realm, welcome_bot,
        message['stream'], message['topic'], message['content']) for message in welcome_messages]
    message_ids = do_send_messages(messages)

    # We find the one of our just-sent messages with turtle.png in it,
    # and react to it.  This is a bit hacky, but works and is kinda a
    # 1-off thing.
    turtle_message = Message.objects.get(
        id__in=message_ids,
        subject='topic demonstration',
        content__icontains='cute/turtle.png')
    do_add_reaction_legacy(welcome_bot, turtle_message, 'turtle')
Ejemplo n.º 14
0
    def test_cross_realm_file_access(self):
        # type: () -> None

        def create_user(email, realm_id):
            # type: (Text, Text) -> UserProfile
            self.register(email, 'test', subdomain=realm_id)
            return get_user(email, get_realm(realm_id))

        test_subdomain = "uploadtest.example.com"
        user1_email = '*****@*****.**'
        user2_email = '*****@*****.**'
        user3_email = '*****@*****.**'

        r1 = Realm.objects.create(string_id=test_subdomain, invite_required=False)
        RealmDomain.objects.create(realm=r1, domain=test_subdomain)

        create_user(user1_email, test_subdomain)
        create_user(user2_email, 'zulip')
        create_user(user3_email, test_subdomain)

        # Send a message from @zulip.com -> @uploadtest.example.com
        self.login(user2_email, 'test')
        fp = StringIO("zulip!")
        fp.name = "zulip.txt"
        result = self.client_post("/json/user_uploads", {'file': fp})
        uri = result.json()['uri']
        fp_path_id = re.sub('/user_uploads/', '', uri)
        body = "First message ...[zulip.txt](http://localhost:9991/user_uploads/" + fp_path_id + ")"
        with self.settings(CROSS_REALM_BOT_EMAILS = set((user2_email, user3_email))):
            internal_send_private_message(
                realm=r1,
                sender=get_system_bot(user2_email),
                recipient_user=get_user(user1_email, r1),
                content=body,
            )

        self.login(user1_email, 'test')
        response = self.client_get(uri, subdomain=test_subdomain)
        self.assertEqual(response.status_code, 200)
        data = b"".join(response.streaming_content)
        self.assertEqual(b"zulip!", data)
        self.logout()

        # Confirm other cross-realm users can't read it.
        self.login(user3_email, 'test')
        response = self.client_get(uri, subdomain=test_subdomain)
        self.assertEqual(response.status_code, 403)
        self.assert_in_response("You are not authorized to view this file.", response)
Ejemplo n.º 15
0
def send_welcome_bot_response(send_request: SendMessageRequest) -> None:
    welcome_bot = get_system_bot(settings.WELCOME_BOT,
                                 send_request.message.sender.realm_id)
    human_recipient_id = send_request.message.sender.recipient_id
    assert human_recipient_id is not None
    if Message.objects.filter(sender=welcome_bot,
                              recipient_id=human_recipient_id).count() < 2:
        content = (
            _("Congratulations on your first reply!") + " "
            ":tada:"
            "\n"
            "\n" +
            _("Feel free to continue using this space to practice your new messaging "
              "skills. Or, try clicking on some of the stream names to your left!"
              ))
        internal_send_private_message(welcome_bot, send_request.message.sender,
                                      content)
Ejemplo n.º 16
0
    def test_cross_realm_file_access(self) -> None:

        def create_user(email: Text, realm_id: Text) -> UserProfile:
            self.register(email, 'test', subdomain=realm_id)
            return get_user(email, get_realm(realm_id))

        test_subdomain = "uploadtest.example.com"
        user1_email = '*****@*****.**'
        user2_email = '*****@*****.**'
        user3_email = '*****@*****.**'

        r1 = Realm.objects.create(string_id=test_subdomain, invite_required=False)
        RealmDomain.objects.create(realm=r1, domain=test_subdomain)

        create_user(user1_email, test_subdomain)
        create_user(user2_email, 'zulip')
        create_user(user3_email, test_subdomain)

        # Send a message from @zulip.com -> @uploadtest.example.com
        self.login(user2_email, 'test')
        fp = StringIO("zulip!")
        fp.name = "zulip.txt"
        result = self.client_post("/json/user_uploads", {'file': fp})
        uri = result.json()['uri']
        fp_path_id = re.sub('/user_uploads/', '', uri)
        body = "First message ...[zulip.txt](http://localhost:9991/user_uploads/" + fp_path_id + ")"
        with self.settings(CROSS_REALM_BOT_EMAILS = set((user2_email, user3_email))):
            internal_send_private_message(
                realm=r1,
                sender=get_system_bot(user2_email),
                recipient_user=get_user(user1_email, r1),
                content=body,
            )

        self.login(user1_email, 'test', realm=r1)
        response = self.client_get(uri, subdomain=test_subdomain)
        self.assertEqual(response.status_code, 200)
        data = b"".join(response.streaming_content)
        self.assertEqual(b"zulip!", data)
        self.logout()

        # Confirm other cross-realm users can't read it.
        self.login(user3_email, 'test', realm=r1)
        response = self.client_get(uri, subdomain=test_subdomain)
        self.assertEqual(response.status_code, 403)
        self.assert_in_response("You are not authorized to view this file.", response)
Ejemplo n.º 17
0
def highlight_html_differences(s1, s2):
    # type: (Text, Text) -> Text
    differ = diff_match_patch()
    ops = differ.diff_main(s1, s2)
    differ.diff_cleanupSemantic(ops)
    retval = u''
    in_tag = False

    idx = 0
    while idx < len(ops):
        op, text = ops[idx]
        text = check_tags(text)
        if idx != 0:
            prev_op, prev_text = ops[idx - 1]
            prev_text = check_tags(prev_text)
            # Remove visual offset from editing newlines
            if '<p><br>' in text:
                text = text.replace('<p><br>', '<p>')
            elif prev_text.endswith('<p>') and text.startswith('<br>'):
                text = text[4:]
        if op == diff_match_patch.DIFF_DELETE:
            chunks, in_tag = chunkize(text, in_tag)
            retval += highlight_chunks(chunks, highlight_deleted)
        elif op == diff_match_patch.DIFF_INSERT:
            chunks, in_tag = chunkize(text, in_tag)
            retval += highlight_chunks(chunks, highlight_inserted)
        elif op == diff_match_patch.DIFF_EQUAL:
            chunks, in_tag = chunkize(text, in_tag)
            retval += text
        idx += 1

    if not verify_html(retval):
        from zerver.lib.actions import internal_send_message
        from zerver.models import get_system_bot
        # We probably want more information here
        logging.getLogger('').error('HTML diff produced mal-formed HTML')

        if settings.ERROR_BOT is not None:
            subject = "HTML diff failure on %s" % (platform.node(), )
            realm = get_system_bot(settings.ERROR_BOT).realm
            internal_send_message(realm, settings.ERROR_BOT, "stream",
                                  "errors", subject,
                                  "HTML diff produced malformed HTML")
        return s2

    return retval
Ejemplo n.º 18
0
def create_internal_realm() -> None:
    internal_realm = Realm.objects.create(string_id=settings.SYSTEM_BOT_REALM)

    internal_realm_bots = [
        (bot['name'], bot['email_template'] % (settings.INTERNAL_BOT_DOMAIN, ))
        for bot in settings.INTERNAL_BOTS
    ]
    internal_realm_bots += [
        ("Zulip Feedback Bot", "*****@*****.**"),
    ]
    create_users(internal_realm,
                 internal_realm_bots,
                 bot_type=UserProfile.DEFAULT_BOT)

    # Initialize the email gateway bot as an API Super User
    email_gateway_bot = get_system_bot(settings.EMAIL_GATEWAY_BOT)
    do_change_is_admin(email_gateway_bot, True, permission="api_super_user")
Ejemplo n.º 19
0
    def consume_batch(self, slow_queries: List[Dict[str, Any]]) -> None:
        for query in slow_queries:
            logging.info("Slow query: %s" % (query))

        if settings.ERROR_BOT is None:
            return

        if len(slow_queries) > 0:
            topic = "%s: slow queries" % (settings.EXTERNAL_HOST,)

            content = ""
            for query in slow_queries:
                content += "    %s\n" % (query,)

            error_bot_realm = get_system_bot(settings.ERROR_BOT).realm
            internal_send_message(error_bot_realm, settings.ERROR_BOT,
                                  "stream", "logs", topic, content)
Ejemplo n.º 20
0
 def test_single_response_to_pm(self) -> None:
     user_email = '*****@*****.**'
     user = self.example_user('hamlet')
     bot = get_system_bot(settings.WELCOME_BOT)
     content = 'whatever'
     self.login(user_email)
     self.send_personal_message(user, bot, content)
     user_messages = message_stream_count(user)
     expected_response = (
         "Congratulations on your first reply! :tada:\n\n"
         "Feel free to continue using this space to practice your new messaging "
         "skills. Or, try clicking on some of the stream names to your left!"
     )
     self.assertEqual(most_recent_message(user).content, expected_response)
     # Welcome bot shouldn't respond to further PMs.
     self.send_personal_message(user, bot, content)
     self.assertEqual(message_stream_count(user), user_messages + 1)
Ejemplo n.º 21
0
def process_stream_message(to: str, message: EmailMessage) -> None:
    subject_header = message.get("Subject", "")
    subject = strip_from_subject(subject_header) or "(no topic)"

    stream, options = decode_stream_email_address(to)
    # Don't remove quotations if message is forwarded, unless otherwise specified:
    if "include_quotes" not in options:
        options["include_quotes"] = is_forwarded(subject_header)

    body = construct_zulip_body(message, stream.realm, **options)
    send_zulip(get_system_bot(settings.EMAIL_GATEWAY_BOT), stream, subject,
               body)
    logger.info(
        "Successfully processed email to %s (%s)",
        stream.name,
        stream.realm.string_id,
    )
Ejemplo n.º 22
0
def send_mm_reply_to_stream(user_profile: UserProfile, stream: Stream,
                            topic: str, body: str) -> None:
    try:
        check_send_message(
            sender=user_profile,
            client=get_client("Internal"),
            message_type_name="stream",
            message_to=[stream.id],
            topic_name=topic,
            message_content=body,
        )
    except JsonableError as error:
        error_message = "Error sending message to stream {stream} via message notification email reply:\n{error}".format(
            stream=stream.name, error=error.msg)
        internal_send_private_message(
            get_system_bot(settings.NOTIFICATION_BOT), user_profile,
            error_message)
Ejemplo n.º 23
0
    def consume(self, event: Mapping[str, Any]) -> None:
        if event['type'] == 'mark_stream_messages_as_read':
            user_profile = get_user_profile_by_id(event['user_profile_id'])
            client = Client.objects.get(id=event['client_id'])

            for stream_id in event['stream_ids']:
                # Since the user just unsubscribed, we don't require
                # an active Subscription object (otherwise, private
                # streams would never be accessible)
                (stream, recipient,
                 sub) = access_stream_by_id(user_profile,
                                            stream_id,
                                            require_active=False)
                do_mark_stream_messages_as_read(user_profile, client, stream)
        elif event['type'] == 'realm_exported':
            realm = Realm.objects.get(id=event['realm_id'])
            output_dir = tempfile.mkdtemp(prefix="zulip-export-")

            public_url = export_realm_wrapper(realm=realm,
                                              output_dir=output_dir,
                                              threads=6,
                                              upload=True,
                                              public_only=True,
                                              delete_after_upload=True)
            assert public_url is not None

            # TODO: This enables support for delete after access, and needs to be tested.
            export_event = RealmAuditLog.objects.get(id=event['id'])
            export_event.extra_data = public_url
            export_event.save(update_fields=['extra_data'])

            # Send a private message notification letting the user who
            # triggered the export know the export finished.
            user_profile = get_user_profile_by_id(event['user_profile_id'])
            content = "Your data export is complete and has been uploaded here:\n\n%s" % (
                public_url, )
            internal_send_private_message(realm=user_profile.realm,
                                          sender=get_system_bot(
                                              settings.NOTIFICATION_BOT),
                                          recipient_user=user_profile,
                                          content=content)

            # For future frontend use, also notify administrator
            # clients that the export happened, including sending the
            # url.
            notify_export_completed(user_profile, public_url)
Ejemplo n.º 24
0
def approve_sponsorship(realm: Realm) -> None:
    from zerver.lib.actions import do_change_plan_type, internal_send_private_message
    do_change_plan_type(realm, Realm.STANDARD_FREE)
    customer = get_customer_by_realm(realm)
    if customer is not None and customer.sponsorship_pending:
        customer.sponsorship_pending = False
        customer.save(update_fields=["sponsorship_pending"])
    notification_bot = get_system_bot(settings.NOTIFICATION_BOT)
    for billing_admin in realm.get_human_billing_admin_users():
        with override_language(billing_admin.default_language):
            # Using variable to make life easier for translators if these details change.
            plan_name = "Zulip Cloud Standard"
            emoji = ":tada:"
            message = _(
                f"Your organization's request for sponsored hosting has been approved! {emoji}.\n"
                f"You have been upgraded to {plan_name}, free of charge.")
            internal_send_private_message(billing_admin.realm, notification_bot, billing_admin, message)
Ejemplo n.º 25
0
def highlight_html_differences(s1, s2):
    # type: (Text, Text) -> Text
    differ = diff_match_patch()
    ops = differ.diff_main(s1, s2)
    differ.diff_cleanupSemantic(ops)
    retval = u''
    in_tag = False

    idx = 0
    while idx < len(ops):
        op, text = ops[idx]
        text = check_tags(text)
        if idx != 0:
            prev_op, prev_text = ops[idx - 1]
            prev_text = check_tags(prev_text)
            # Remove visual offset from editing newlines
            if '<p><br>' in text:
                text = text.replace('<p><br>', '<p>')
            elif prev_text.endswith('<p>') and text.startswith('<br>'):
                text = text[4:]
        if op == diff_match_patch.DIFF_DELETE:
            chunks, in_tag = chunkize(text, in_tag)
            retval += highlight_chunks(chunks, highlight_deleted)
        elif op == diff_match_patch.DIFF_INSERT:
            chunks, in_tag = chunkize(text, in_tag)
            retval += highlight_chunks(chunks, highlight_inserted)
        elif op == diff_match_patch.DIFF_EQUAL:
            chunks, in_tag = chunkize(text, in_tag)
            retval += text
        idx += 1

    if not verify_html(retval):
        from zerver.lib.actions import internal_send_message
        from zerver.models import get_system_bot
        # We probably want more information here
        logging.getLogger('').error('HTML diff produced mal-formed HTML')

        if settings.ERROR_BOT is not None:
            subject = "HTML diff failure on %s" % (platform.node(),)
            realm = get_system_bot(settings.ERROR_BOT).realm
            internal_send_message(realm, settings.ERROR_BOT, "stream",
                                  "errors", subject, "HTML diff produced malformed HTML")
        return s2

    return retval
Ejemplo n.º 26
0
    def test_slow_queries_worker(self) -> None:
        error_bot = get_system_bot(settings.ERROR_BOT)
        fake_client = self.FakeClient()
        # TODO: Rewrite this set part of the test by just mocking
        # `is_slow_query` to generate the events.
        events = [
            {
                'query': 'test query (data)'
            },
            {
                'query': 'second test query (data)'
            },
        ]
        for event in events:
            fake_client.queue.append(('slow_queries', event))

        worker = SlowQueryWorker()

        time_mock = patch(
            'zerver.worker.queue_processors.time.sleep',
            side_effect=AbortLoop,
        )

        send_mock = patch(
            'zerver.worker.queue_processors.internal_send_message')

        with send_mock as sm, time_mock as tm:
            with simulated_queue_client(lambda: fake_client):
                try:
                    worker.setup()
                    worker.start()
                except AbortLoop:
                    pass

        self.assertEqual(tm.call_args[0][0], 60)  # should sleep 60 seconds

        sm.assert_called_once()
        args = [c[0] for c in sm.call_args_list][0]
        self.assertEqual(args[0], error_bot.realm)
        self.assertEqual(args[1], error_bot.email)
        self.assertEqual(args[2], "stream")
        self.assertEqual(args[3], "errors")
        self.assertEqual(args[4], "testserver: slow queries")
        self.assertEqual(
            args[5], "    test query (data)\n    second test query (data)\n")
Ejemplo n.º 27
0
def send_initial_realm_messages(realm: Realm) -> None:
    welcome_bot = get_system_bot(settings.WELCOME_BOT)
    # Make sure each stream created in the realm creation process has at least one message below
    # Order corresponds to the ordering of the streams on the left sidebar, to make the initial Home
    # view slightly less overwhelming
    welcome_messages = [
        {'stream': Realm.DEFAULT_NOTIFICATION_STREAM_NAME,
         'topic': "welcome",
         'content': "This is a message on stream `%s` with the topic `welcome`. We'll use this stream "
         "for system-generated notifications." % (Realm.DEFAULT_NOTIFICATION_STREAM_NAME,)},
        {'stream': Realm.INITIAL_PRIVATE_STREAM_NAME,
         'topic': "private streams",
         'content': "This is a private stream. Only admins and people you invite "
         "to the stream will be able to see that this stream exists."},
        {'stream': "general",
         'topic': "welcome",
         'content': "Welcome to #**general**."},
        {'stream': "new members",
         'topic': "onboarding",
         'content': "A #**new members** stream is great for onboarding new members.\n\nIf you're "
         "reading this and aren't the first person here, introduce yourself in a new thread "
         "using your name as the topic! Type `c` or click on `New Topic` at the bottom of the "
         "screen to start a new topic."},
        {'stream': "zulip",
         'topic': "topic demonstration",
         'content': "Here is a message in one topic. Replies to this message will go to this topic."},
        {'stream': "zulip",
         'topic': "topic demonstration",
         'content': "A second message in this topic. With [turtles](/static/images/cute/turtle.png)!"},
        {'stream': "zulip",
         'topic': "second topic",
         'content': "This is a message in a second topic.\n\nTopics are similar to email subjects, "
         "in that each conversation should get its own topic. Keep them short, though; one "
         "or two words will do it!"},
    ]  # type: List[Dict[str, str]]
    messages = [internal_prep_stream_message(
        realm, welcome_bot,
        message['stream'], message['topic'], message['content']) for message in welcome_messages]
    message_ids = do_send_messages(messages)

    # We find the one of our just-sent messages with turtle.png in it,
    # and react to it.  This is a bit hacky, but works and is kinda a
    # 1-off thing.
    turtle_message = get_turtle_message(message_ids=message_ids)
    do_add_reaction_legacy(welcome_bot, turtle_message, 'turtle')
Ejemplo n.º 28
0
def send_initial_pms(user: UserProfile) -> None:
    organization_setup_text = ""
    if user.is_realm_admin:
        help_url = user.realm.uri + "/help/getting-your-organization-started-with-zulip"
        organization_setup_text = (
            " " + _("We also have a guide for [Setting up your organization]({help_url}).")
        ).format(help_url=help_url)

    welcome_msg = _("Hello, and welcome to Zulip!")
    if user.realm.demo_organization_scheduled_deletion_date is not None:
        welcome_msg += " " + _(
            "Note that this is a [demo organization]({demo_org_help_url}) and will be automatically deleted in 30 days."
        )

    content = "".join(
        [
            welcome_msg + " ",
            _("This is a private message from me, Welcome Bot.") + "\n\n",
            "* "
            + _(
                "If you are new to Zulip, check out our [Getting started guide]({getting_started_url})!"
            )
            + "{organization_setup_text}\n",
            "* " + _("[Add a profile picture]({profile_url}).") + "\n",
            "* " + _("[Browse and subscribe to streams]({streams_url}).") + "\n",
            "* " + _("Download our [mobile and desktop apps]({apps_url}).") + " ",
            _("Zulip also works great in a browser.") + "\n",
            "* " + _("You can type `?` to learn more about Zulip shortcuts.") + "\n\n",
            _("Practice sending a few messages by replying to this conversation.") + " ",
            _("Click anywhere on this message or press `r` to reply."),
        ]
    )

    content = content.format(
        getting_started_url="/help/getting-started-with-zulip",
        apps_url="/apps",
        profile_url="#settings/profile",
        streams_url="#streams/all",
        organization_setup_text=organization_setup_text,
        demo_org_help_url="/help/demo-organizations",
    )

    internal_send_private_message(
        get_system_bot(settings.WELCOME_BOT, user.realm_id), user, content
    )
Ejemplo n.º 29
0
def zulip_server_error(report: Dict[str, Any]) -> None:
    email_subject = "{node}: {message}".format(**report)

    logger_str = logger_repr(report)
    user_info = user_info_str(report)
    deployment = deployment_repr(report)

    if report["has_request"]:
        request_repr = """\
Request info:
~~~~
- path: {path}
- {method}: {data}
""".format(
            **report
        )
        for field in ["REMOTE_ADDR", "QUERY_STRING", "SERVER_NAME"]:
            val = report.get(field.lower())
            if field == "QUERY_STRING":
                val = clean_data_from_query_parameters(str(val))
            request_repr += f'- {field}: "{val}"\n'
        request_repr += "~~~~"
    else:
        request_repr = "Request info: none"

    message = f"""{logger_str}
Error generated by {user_info}

~~~~ pytb
{report['stack_trace']}

~~~~
{deployment}
{request_repr}"""

    error_bot_realm = get_realm(settings.STAFF_SUBDOMAIN)
    error_bot = get_system_bot(settings.ERROR_BOT, error_bot_realm.id)
    errors_stream = get_stream("errors", error_bot_realm)

    internal_send_stream_message(
        error_bot,
        errors_stream,
        format_email_subject(email_subject),
        message,
    )
Ejemplo n.º 30
0
 def __init__(self, host_url: str, sockjs_url: str, sender_email: str,
              run_on_start: Callable[..., None], validate_ssl: bool=True,
              **run_kwargs: Any) -> None:
     # NOTE: Callable should take a WebsocketClient & kwargs, but this is not standardised
     self.validate_ssl = validate_ssl
     self.auth_email = sender_email
     self.user_profile = get_system_bot(sender_email)
     self.request_id_number = 0
     self.parsed_host_url = urlparse(host_url)
     self.sockjs_url = sockjs_url
     self.cookie_dict = self._login()
     self.cookie_str = self._get_cookie_header(self.cookie_dict)
     self.events_data = self._get_queue_events(self.cookie_str)
     self.ioloop_instance = IOLoop.instance()
     self.run_on_start = run_on_start
     self.run_kwargs = run_kwargs
     self.scheme_dict = {'http': 'ws', 'https': 'wss'}
     self.ws = None  # type: Optional[WebSocketClientConnection]
Ejemplo n.º 31
0
def zulip_browser_error(report: Dict[str, Any]) -> None:
    email_subject = "JS error: {user_email}".format(**report)

    user_info = user_info_str(report)

    body = f"User: {user_info}\n"
    body += "Message: {message}\n".format(**report)

    error_bot_realm = get_realm(settings.STAFF_SUBDOMAIN)
    error_bot = get_system_bot(settings.ERROR_BOT, error_bot_realm.id)
    errors_stream = get_stream("errors", error_bot_realm)

    internal_send_stream_message(
        error_bot,
        errors_stream,
        format_email_subject(email_subject),
        body,
    )
Ejemplo n.º 32
0
    def consume_batch(self, slow_queries):
        # type: (List[Dict[str, Any]]) -> None
        for query in slow_queries:
            logging.info("Slow query: %s" % (query))

        if settings.ERROR_BOT is None:
            return

        if len(slow_queries) > 0:
            topic = "%s: slow queries" % (settings.EXTERNAL_HOST,)

            content = ""
            for query in slow_queries:
                content += "    %s\n" % (query,)

            error_bot_realm = get_system_bot(settings.ERROR_BOT).realm
            internal_send_message(error_bot_realm, settings.ERROR_BOT,
                                  "stream", "logs", topic, content)
Ejemplo n.º 33
0
    def test_slow_query_log(self, mock_logging_info: Mock) -> None:
        error_bot = get_system_bot(settings.ERROR_BOT)
        create_stream_if_needed(error_bot.realm,
                                settings.SLOW_QUERY_LOGS_STREAM)

        self.log_data['time_started'] = time.time() - self.SLOW_QUERY_TIME
        write_log_line(self.log_data,
                       path='/socket/open',
                       method='SOCKET',
                       remote_ip='123.456.789.012',
                       requestor_for_logs='unknown',
                       client_name='?')
        last_message = self.get_last_message()
        self.assertEqual(last_message.sender.email, "*****@*****.**")
        self.assertIn("logs", str(last_message.recipient))
        self.assertEqual(last_message.topic_name(), "testserver: slow queries")
        self.assertRegexpMatches(last_message.content,
                                 r"123\.456\.789\.012 SOCKET  200 10\.\ds .*")
Ejemplo n.º 34
0
    def test_slow_queries_worker(self) -> None:
        error_bot = get_system_bot(settings.ERROR_BOT)
        fake_client = self.FakeClient()
        worker = SlowQueryWorker()

        time_mock = patch(
            'zerver.worker.queue_processors.time.sleep',
            side_effect=AbortLoop,
        )

        send_mock = patch(
            'zerver.worker.queue_processors.internal_send_message')

        with send_mock as sm, time_mock as tm:
            with simulated_queue_client(lambda: fake_client):
                try:
                    worker.setup()
                    # `write_log_line` is where we publish slow queries to the queue.
                    with patch('zerver.middleware.is_slow_query',
                               return_value=True):
                        write_log_line(log_data=dict(test='data'),
                                       email='*****@*****.**',
                                       remote_ip='127.0.0.1',
                                       client_name='website',
                                       path='/test/',
                                       method='GET')
                    worker.start()
                except AbortLoop:
                    pass

        self.assertEqual(tm.call_args[0][0], 60)  # should sleep 60 seconds

        sm.assert_called_once()
        args = [c[0] for c in sm.call_args_list][0]
        self.assertEqual(args[0], error_bot.realm)
        self.assertEqual(args[1], error_bot.email)
        self.assertEqual(args[2], "stream")
        self.assertEqual(args[3], "errors")
        self.assertEqual(args[4], "testserver: slow queries")
        # Testing for specific query times can lead to test discrepancies.
        logging_info = re.sub(r'\(db: [0-9]+ms/13q\)', '', args[5])
        self.assertEqual(
            logging_info, '    127.0.0.1       GET     200 -1000ms '
            ' /test/ ([email protected] via website) ([email protected])\n')
Ejemplo n.º 35
0
def send_initial_realm_messages(realm: Realm) -> None:
    welcome_bot = get_system_bot(settings.WELCOME_BOT)
    # Make sure each stream created in the realm creation process has at least one message below
    # Order corresponds to the ordering of the streams on the left sidebar, to make the initial Home
    # view slightly less overwhelming
    welcome_messages = [
        {'stream': Realm.INITIAL_PRIVATE_STREAM_NAME,
         'topic': "private streams",
         'content': "This is a private stream, as indicated by the "
         "lock icon next to the stream name. Private streams are only visible to stream members. "
         "\n\nTo manage this stream, go to [Stream settings](#streams/subscribed) and click on "
         "`%(initial_private_stream_name)s`."},
        {'stream': Realm.DEFAULT_NOTIFICATION_STREAM_NAME,
         'topic': "topic demonstration",
         'content': "This is a message on stream #**%(default_notification_stream_name)s** with the "
         "topic `topic demonstration`."},
        {'stream': Realm.DEFAULT_NOTIFICATION_STREAM_NAME,
         'topic': "topic demonstration",
         'content': "Topics are a lightweight tool to keep conversations organized. "
         "You can learn more about topics at [Streams and topics](/help/about-streams-and-topics). "},
        {'stream': realm.DEFAULT_NOTIFICATION_STREAM_NAME,
         'topic': "swimming turtles",
         'content': "This is a message on stream #**%(default_notification_stream_name)s** with the "
         "topic `swimming turtles`. "
         "\n\n[](/static/images/cute/turtle.png)"
         "\n\n[Start a new topic](/help/start-a-new-topic) any time you're not replying to a "
         "previous message."},
    ]  # type: List[Dict[str, str]]
    messages = [internal_prep_stream_message_by_name(
        realm, welcome_bot, message['stream'], message['topic'],
        message['content'] % {
            'initial_private_stream_name': Realm.INITIAL_PRIVATE_STREAM_NAME,
            'default_notification_stream_name': Realm.DEFAULT_NOTIFICATION_STREAM_NAME,
        }
    ) for message in welcome_messages]
    message_ids = do_send_messages(messages)

    # We find the one of our just-sent messages with turtle.png in it,
    # and react to it.  This is a bit hacky, but works and is kinda a
    # 1-off thing.
    turtle_message = Message.objects.get(
        id__in=message_ids,
        content__icontains='cute/turtle.png')
    do_add_reaction_legacy(welcome_bot, turtle_message, 'turtle')
Ejemplo n.º 36
0
    def process_one_batch(self):
        # type: () -> None
        slow_queries = self.q.drain_queue("slow_queries", json=True)

        if settings.ERROR_BOT is None:
            return

        if len(slow_queries) > 0:
            topic = "%s: slow queries" % (settings.EXTERNAL_HOST,)

            content = ""
            for query in slow_queries:
                content += "    %s\n" % (query,)

            error_bot_realm = get_system_bot(settings.ERROR_BOT).realm
            internal_send_message(error_bot_realm, settings.ERROR_BOT,
                                  "stream", "logs", topic, content)

        reset_queries()
Ejemplo n.º 37
0
def zulip_browser_error(report: Dict[str, Any]) -> None:
    email_subject = "JS error: {user_email}".format(**report)

    user_info = user_info_str(report)

    body = f"User: {user_info}\n"
    body += "Message: {message}\n".format(**report)

    error_bot = get_system_bot(settings.ERROR_BOT)
    realm = error_bot.realm
    errors_stream = get_stream('errors', realm)

    internal_send_stream_message(
        realm,
        error_bot,
        errors_stream,
        format_email_subject(email_subject),
        body,
    )
Ejemplo n.º 38
0
def send_change_stream_message_retention_days_notification(
        user_profile: UserProfile, stream: Stream, old_value: Optional[int],
        new_value: Optional[int]) -> None:
    sender = get_system_bot(settings.NOTIFICATION_BOT, user_profile.realm_id)
    user_mention = silent_mention_syntax_for_user(user_profile)

    # If switching from or to the organization's default retention policy,
    # we want to take the realm's default into account.
    if old_value is None:
        old_value = stream.realm.message_retention_days
    if new_value is None:
        new_value = stream.realm.message_retention_days

    with override_language(stream.realm.default_language):
        if old_value == Stream.MESSAGE_RETENTION_SPECIAL_VALUES_MAP[
                "unlimited"]:
            old_retention_period = _("Forever")
            new_retention_period = f"{new_value} days"
            summary_line = f"Messages in this stream will now be automatically deleted {new_value} days after they are sent."
        elif new_value == Stream.MESSAGE_RETENTION_SPECIAL_VALUES_MAP[
                "unlimited"]:
            old_retention_period = f"{old_value} days"
            new_retention_period = _("Forever")
            summary_line = _(
                "Messages in this stream will now be retained forever.")
        else:
            old_retention_period = f"{old_value} days"
            new_retention_period = f"{new_value} days"
            summary_line = f"Messages in this stream will now be automatically deleted {new_value} days after they are sent."
        notification_string = _(
            "{user} has changed the [message retention period](/help/message-retention-policy) for this stream:\n"
            "* **Old retention period**: {old_retention_period}\n"
            "* **New retention period**: {new_retention_period}\n\n"
            "{summary_line}")
        notification_string = notification_string.format(
            user=user_mention,
            old_retention_period=old_retention_period,
            new_retention_period=new_retention_period,
            summary_line=summary_line,
        )
        internal_send_stream_message(sender, stream,
                                     Realm.STREAM_EVENTS_NOTIFICATION_TOPIC,
                                     notification_string)
Ejemplo n.º 39
0
    def process_one_batch(self):
        # type: () -> None
        slow_queries = self.q.drain_queue("slow_queries", json=True)

        if settings.ERROR_BOT is None:
            return

        if len(slow_queries) > 0:
            topic = "%s: slow queries" % (settings.EXTERNAL_HOST, )

            content = ""
            for query in slow_queries:
                content += "    %s\n" % (query, )

            error_bot_realm = get_system_bot(settings.ERROR_BOT).realm
            internal_send_message(error_bot_realm, settings.ERROR_BOT,
                                  "stream", "logs", topic, content)

        reset_queries()
Ejemplo n.º 40
0
def send_initial_pms(user: UserProfile) -> None:
    organization_setup_text = ""

    # We need to override the language in this code path, because it's
    # called from account registration, which is a pre-account API
    # request and thus may not have the user's language context yet.
    with override_language(user.default_language):
        if user.is_realm_admin:
            help_url = user.realm.uri + "/help/getting-your-organization-started-with-zulip"
            organization_setup_text = (" " + _(
                "We also have a guide for [Setting up your organization]({help_url})."
            )).format(help_url=help_url)

        welcome_msg = _("Hello, and welcome to Zulip!") + "👋"
        demo_org_warning = ""
        if user.realm.demo_organization_scheduled_deletion_date is not None:
            demo_org_warning = (_(
                "Note that this is a [demo organization]({demo_org_help_url}) and will be "
                "**automatically deleted** in 30 days.") + "\n\n")

        content = "".join([
            welcome_msg + " ",
            _("This is a private message from me, Welcome Bot.") + "\n\n",
            _("If you are new to Zulip, check out our [Getting started guide]({getting_started_url})!"
              ),
            "{organization_setup_text}" + "\n\n",
            "{demo_org_warning}",
            _("I can also help you get set up! Just click anywhere on this message or press `r` to reply."
              ) + "\n\n",
            _("Here are a few messages I understand:") + " ",
            bot_commands(),
        ])

    content = content.format(
        organization_setup_text=organization_setup_text,
        demo_org_warning=demo_org_warning,
        demo_org_help_url="/help/demo-organizations",
        getting_started_url="/help/getting-started-with-zulip",
    )

    internal_send_private_message(
        get_system_bot(settings.WELCOME_BOT, user.realm_id), user, content)
Ejemplo n.º 41
0
    def consume_batch(self, slow_query_events: List[Dict[str, Any]]) -> None:
        for event in slow_query_events:
            logging.info("Slow query: %s" % (event["query"],))

        if settings.SLOW_QUERY_LOGS_STREAM is None:
            return

        if settings.ERROR_BOT is None:
            return

        if len(slow_query_events) > 0:
            topic = "%s: slow queries" % (settings.EXTERNAL_HOST,)

            content = ""
            for event in slow_query_events:
                content += "    %s\n" % (event["query"],)

            error_bot_realm = get_system_bot(settings.ERROR_BOT).realm
            internal_send_message(error_bot_realm, settings.ERROR_BOT,
                                  "stream", settings.SLOW_QUERY_LOGS_STREAM, topic, content)
Ejemplo n.º 42
0
def send_change_stream_post_policy_notification(
        stream: Stream, *, old_post_policy: int, new_post_policy: int,
        acting_user: UserProfile) -> None:
    sender = get_system_bot(settings.NOTIFICATION_BOT, acting_user.realm_id)
    user_mention = silent_mention_syntax_for_user(acting_user)

    with override_language(stream.realm.default_language):
        notification_string = _(
            "{user} changed the [posting permissions](/help/stream-sending-policy) "
            "for this stream:\n\n"
            "* **Old permissions**: {old_policy}.\n"
            "* **New permissions**: {new_policy}.\n")
        notification_string = notification_string.format(
            user=user_mention,
            old_policy=Stream.POST_POLICIES[old_post_policy],
            new_policy=Stream.POST_POLICIES[new_post_policy],
        )
        internal_send_stream_message(sender, stream,
                                     Realm.STREAM_EVENTS_NOTIFICATION_TOPIC,
                                     notification_string)
Ejemplo n.º 43
0
def zulip_server_error(report):
    # type: (Dict[str, Any]) -> None
    subject = '%(node)s: %(message)s' % (report)
    stack_trace = report['stack_trace'] or "No stack trace available"

    user_info = user_info_str(report)

    request_repr = ("Request info:\n~~~~\n"
                    "- path: %(path)s\n"
                    "- %(method)s: %(data)s\n") % (report)

    for field in ["REMOTE_ADDR", "QUERY_STRING", "SERVER_NAME"]:
        request_repr += "- %s: \"%s\"\n" % (field, report.get(field.lower()))
    request_repr += "~~~~"

    realm = get_system_bot(settings.ERROR_BOT).realm
    internal_send_message(
        realm, settings.ERROR_BOT, "stream", "errors", format_subject(subject),
        "Error generated by %s\n\n~~~~ pytb\n%s\n\n~~~~\n%s" %
        (user_info, stack_trace, request_repr))
Ejemplo n.º 44
0
    def test_recipient_for_user_ids(self) -> None:
        hamlet = self.example_user('hamlet')
        othello = self.example_user('othello')
        cross_realm_bot = get_system_bot(settings.WELCOME_BOT)
        sender = self.example_user('iago')
        recipient_user_ids = [hamlet.id, othello.id, cross_realm_bot.id]

        result = recipient_for_user_ids(recipient_user_ids, sender)
        recipient = get_display_recipient(result)
        recipient_ids = [
            recipient[0]['id'],
            recipient[1]['id'],  # type: ignore
            recipient[2]['id'],
            recipient[3]['id']
        ]  # type: ignore

        expected_recipient_ids = [
            hamlet.id, othello.id, sender.id, cross_realm_bot.id
        ]
        self.assertEqual(set(recipient_ids), set(expected_recipient_ids))
Ejemplo n.º 45
0
    def consume_batch(self, slow_queries: List[Any]) -> None:
        for query in slow_queries:
            logging.info("Slow query: %s" % (query))

        if settings.SLOW_QUERY_LOGS_STREAM is None:
            return

        if settings.ERROR_BOT is None:
            return

        if len(slow_queries) > 0:
            topic = "%s: slow queries" % (settings.EXTERNAL_HOST,)

            content = ""
            for query in slow_queries:
                content += "    %s\n" % (query,)

            error_bot_realm = get_system_bot(settings.ERROR_BOT).realm
            internal_send_message(error_bot_realm, settings.ERROR_BOT,
                                  "stream", settings.SLOW_QUERY_LOGS_STREAM, topic, content)
Ejemplo n.º 46
0
def extract_and_upload_attachments(message: message.Message, realm: Realm) -> str:
    user_profile = get_system_bot(settings.EMAIL_GATEWAY_BOT)

    attachment_links = []
    for part in message.walk():
        content_type = part.get_content_type()
        filename = part.get_filename()
        if filename:
            attachment = part.get_payload(decode=True)
            if isinstance(attachment, bytes):
                s3_url = upload_message_file(filename, len(attachment), content_type,
                                             attachment,
                                             user_profile,
                                             target_realm=realm)
                formatted_link = "[%s](%s)" % (filename, s3_url)
                attachment_links.append(formatted_link)
            else:
                logger.warning("Payload is not bytes (invalid attachment %s in message from %s)." %
                               (filename, message.get("From")))

    return '\n'.join(attachment_links)
Ejemplo n.º 47
0
def zulip_server_error(report):
    # type: (Dict[str, Any]) -> None
    subject = '%(node)s: %(message)s' % (report)
    stack_trace = report['stack_trace'] or "No stack trace available"

    user_info = user_info_str(report)

    request_repr = (
        "Request info:\n~~~~\n"
        "- path: %(path)s\n"
        "- %(method)s: %(data)s\n") % (report)

    for field in ["REMOTE_ADDR", "QUERY_STRING", "SERVER_NAME"]:
        request_repr += "- %s: \"%s\"\n" % (field, report.get(field.lower()))
    request_repr += "~~~~"

    realm = get_system_bot(settings.ERROR_BOT).realm
    internal_send_message(realm, settings.ERROR_BOT,
                          "stream", "errors", format_subject(subject),
                          "Error generated by %s\n\n~~~~ pytb\n%s\n\n~~~~\n%s" % (
                              user_info, stack_trace, request_repr))
Ejemplo n.º 48
0
def create_internal_realm() -> None:
    realm = Realm.objects.create(string_id=settings.SYSTEM_BOT_REALM)

    # Create the "website" and "API" clients:
    get_client("website")
    get_client("API")

    internal_bots = [(bot['name'],
                      bot['email_template'] % (settings.INTERNAL_BOT_DOMAIN, ))
                     for bot in settings.INTERNAL_BOTS]
    create_users(realm, internal_bots, bot_type=UserProfile.DEFAULT_BOT)
    # Set the owners for these bots to the bots themselves
    bots = UserProfile.objects.filter(
        email__in=[bot_info[1] for bot_info in internal_bots])
    for bot in bots:
        bot.bot_owner = bot
        bot.save()

    # Initialize the email gateway bot as an API Super User
    email_gateway_bot = get_system_bot(settings.EMAIL_GATEWAY_BOT)
    do_change_is_admin(email_gateway_bot, True, permission="api_super_user")
Ejemplo n.º 49
0
def send_initial_pms(user: UserProfile) -> None:
    organization_setup_text = ""
    if user.is_realm_admin:
        help_url = user.realm.uri + "/help/getting-your-organization-started-with-zulip"
        organization_setup_text = ("* " + _(
            "[Read the guide]({help_url}) for getting your organization started with Zulip"
        ) + "\n").format(help_url=help_url)

    welcome_msg = _("Hello, and welcome to Zulip!")
    if user.realm.demo_organization_scheduled_deletion_date is not None:
        welcome_msg += " " + _(
            "Note that this is a [demo organization]({demo_org_help_url}) and will be automatically deleted in 30 days."
        )

    content = "".join([
        welcome_msg + "\n\n",
        _("This is a private message from me, Welcome Bot.") + " ",
        _("Here are some tips to get you started:") + "\n",
        "* " + _("Download our [Desktop and mobile apps]({apps_url})") + "\n",
        "* " +
        _("Customize your account and notifications on your [Settings page]({settings_url})"
          ) + "\n",
        "* " + _("Type `?` to check out Zulip's keyboard shortcuts") +
        "\n {organization_setup_text}\n",
        _("The most important shortcut is `r` to reply.") + "\n\n",
        _("Practice sending a few messages by replying to this conversation.")
        + " ",
        _("If you're not into keyboards, that's okay too; "
          "clicking anywhere on this message will also do the trick!"),
    ])

    content = content.format(
        apps_url="/apps",
        settings_url="#settings",
        organization_setup_text=organization_setup_text,
        demo_org_help_url="/help/demo-organizations",
    )

    internal_send_private_message(
        get_system_bot(settings.WELCOME_BOT, user.realm_id), user, content)
Ejemplo n.º 50
0
def highlight_html_differences(s1, s2):
    # type: (Text, Text) -> Text
    differ = diff_match_patch()
    ops = differ.diff_main(s1, s2)
    differ.diff_cleanupSemantic(ops)
    retval = u''
    in_tag = False

    idx = 0
    while idx < len(ops):
        op, text = ops[idx]
        next_op = None
        if idx != len(ops) - 1:
            next_op, next_text = ops[idx + 1]
        if op == diff_match_patch.DIFF_DELETE:
            chunks, in_tag = chunkize(text, in_tag)
            retval += highlight_chunks(chunks, highlight_deleted)
        elif op == diff_match_patch.DIFF_INSERT:
            chunks, in_tag = chunkize(text, in_tag)
            retval += highlight_chunks(chunks, highlight_inserted)
        elif op == diff_match_patch.DIFF_EQUAL:
            chunks, in_tag = chunkize(text, in_tag)
            retval += text
        idx += 1

    if not verify_html(retval):
        from zerver.lib.actions import internal_send_message
        from zerver.models import get_system_bot
        # We probably want more information here
        logging.getLogger('').error('HTML diff produced mal-formed HTML')

        if settings.ERROR_BOT is not None:
            subject = "HTML diff failure on %s" % (platform.node(), )
            realm = get_system_bot(settings.ERROR_BOT).realm
            internal_send_message(realm, settings.ERROR_BOT, "stream",
                                  "errors", subject,
                                  "HTML diff produced malformed HTML")
        return s2

    return retval
Ejemplo n.º 51
0
    def test_slow_queries_worker(self) -> None:
        error_bot = get_system_bot(settings.ERROR_BOT)
        fake_client = self.FakeClient()
        events = [
            'test query (data)',
            'second test query (data)',
        ]
        for event in events:
            fake_client.queue.append(('slow_queries', event))

        worker = SlowQueryWorker()

        time_mock = patch(
            'zerver.worker.queue_processors.time.sleep',
            side_effect=AbortLoop,
        )

        send_mock = patch(
            'zerver.worker.queue_processors.internal_send_message'
        )

        with send_mock as sm, time_mock as tm:
            with simulated_queue_client(lambda: fake_client):
                try:
                    worker.setup()
                    worker.start()
                except AbortLoop:
                    pass

        self.assertEqual(tm.call_args[0][0], 60)  # should sleep 60 seconds

        sm.assert_called_once()
        args = [c[0] for c in sm.call_args_list][0]
        self.assertEqual(args[0], error_bot.realm)
        self.assertEqual(args[1], error_bot.email)
        self.assertEqual(args[2], "stream")
        self.assertEqual(args[3], "errors")
        self.assertEqual(args[4], "testserver: slow queries")
        self.assertEqual(args[5], "    test query (data)\n    second test query (data)\n")
Ejemplo n.º 52
0
def send_initial_pms(user: UserProfile) -> None:
    organization_setup_text = ""
    if user.is_realm_admin:
        help_url = user.realm.uri + "/help/getting-your-organization-started-with-zulip"
        organization_setup_text = ("* [Read the guide](%s) for getting your organization "
                                   "started with Zulip\n" % (help_url,))

    content = (
        "Hello, and welcome to Zulip!\n\nThis is a private message from me, Welcome Bot. "
        "Here are some tips to get you started:\n"
        "* Download our [Desktop and mobile apps](/apps)\n"
        "* Customize your account and notifications on your [Settings page](#settings)\n"
        "* Type `?` to check out Zulip's keyboard shortcuts\n"
        "%s"
        "\n"
        "The most important shortcut is `r` to reply.\n\n"
        "Practice sending a few messages by replying to this conversation. If you're not into "
        "keyboards, that's okay too; clicking anywhere on this message will also do the trick!") \
        % (organization_setup_text,)

    internal_send_private_message(user.realm, get_system_bot(settings.WELCOME_BOT),
                                  user, content)
Ejemplo n.º 53
0
def do_import_realm(import_dir: Path, subdomain: str) -> Realm:
    logging.info("Importing realm dump %s" % (import_dir,))
    if not os.path.exists(import_dir):
        raise Exception("Missing import directory!")

    realm_data_filename = os.path.join(import_dir, "realm.json")
    if not os.path.exists(realm_data_filename):
        raise Exception("Missing realm.json file!")

    logging.info("Importing realm data from %s" % (realm_data_filename,))
    with open(realm_data_filename) as f:
        data = ujson.load(f)

    bulk_import_client(data, Client, 'zerver_client')

    # We don't import the Stream model yet, since it depends on Realm,
    # which isn't imported yet.  But we need the Stream model IDs for
    # notifications_stream.
    update_model_ids(Stream, data, 'stream')
    re_map_foreign_keys(data, 'zerver_realm', 'notifications_stream', related_table="stream")
    re_map_foreign_keys(data, 'zerver_realm', 'signup_notifications_stream', related_table="stream")

    fix_datetime_fields(data, 'zerver_realm')
    # Fix realm subdomain information
    data['zerver_realm'][0]['string_id'] = subdomain
    data['zerver_realm'][0]['name'] = subdomain
    fix_realm_authentication_bitfield(data, 'zerver_realm', 'authentication_methods')
    update_model_ids(Realm, data, 'realm')

    realm = Realm(**data['zerver_realm'][0])
    if settings.BILLING_ENABLED:
        realm.plan_type = Realm.LIMITED
    else:
        realm.plan_type = Realm.SELF_HOSTED

    if realm.notifications_stream_id is not None:
        notifications_stream_id = int(realm.notifications_stream_id)  # type: Optional[int]
    else:
        notifications_stream_id = None
    realm.notifications_stream_id = None
    if realm.signup_notifications_stream_id is not None:
        signup_notifications_stream_id = int(realm.signup_notifications_stream_id)  # type: Optional[int]
    else:
        signup_notifications_stream_id = None
    realm.signup_notifications_stream_id = None
    realm.save()

    # Email tokens will automatically be randomly generated when the
    # Stream objects are created by Django.
    fix_datetime_fields(data, 'zerver_stream')
    re_map_foreign_keys(data, 'zerver_stream', 'realm', related_table="realm")
    bulk_import_model(data, Stream)

    realm.notifications_stream_id = notifications_stream_id
    realm.signup_notifications_stream_id = signup_notifications_stream_id
    realm.save()

    # Remap the user IDs for notification_bot and friends to their
    # appropriate IDs on this server
    for item in data['zerver_userprofile_crossrealm']:
        logging.info("Adding to ID map: %s %s" % (item['id'], get_system_bot(item['email']).id))
        new_user_id = get_system_bot(item['email']).id
        update_id_map(table='user_profile', old_id=item['id'], new_id=new_user_id)
        new_recipient_id = Recipient.objects.get(type=Recipient.PERSONAL, type_id=new_user_id).id
        update_id_map(table='recipient', old_id=item['recipient_id'], new_id=new_recipient_id)

    # Merge in zerver_userprofile_mirrordummy
    data['zerver_userprofile'] = data['zerver_userprofile'] + data['zerver_userprofile_mirrordummy']
    del data['zerver_userprofile_mirrordummy']
    data['zerver_userprofile'].sort(key=lambda r: r['id'])

    # To remap foreign key for UserProfile.last_active_message_id
    update_message_foreign_keys(import_dir)

    fix_datetime_fields(data, 'zerver_userprofile')
    update_model_ids(UserProfile, data, 'user_profile')
    re_map_foreign_keys(data, 'zerver_userprofile', 'realm', related_table="realm")
    re_map_foreign_keys(data, 'zerver_userprofile', 'bot_owner', related_table="user_profile")
    re_map_foreign_keys(data, 'zerver_userprofile', 'default_sending_stream',
                        related_table="stream")
    re_map_foreign_keys(data, 'zerver_userprofile', 'default_events_register_stream',
                        related_table="stream")
    re_map_foreign_keys(data, 'zerver_userprofile', 'last_active_message_id',
                        related_table="message", id_field=True)
    for user_profile_dict in data['zerver_userprofile']:
        user_profile_dict['password'] = None
        user_profile_dict['api_key'] = generate_api_key()
        # Since Zulip doesn't use these permissions, drop them
        del user_profile_dict['user_permissions']
        del user_profile_dict['groups']

    user_profiles = [UserProfile(**item) for item in data['zerver_userprofile']]
    for user_profile in user_profiles:
        user_profile.set_unusable_password()
    UserProfile.objects.bulk_create(user_profiles)

    re_map_foreign_keys(data, 'zerver_defaultstream', 'stream', related_table="stream")
    re_map_foreign_keys(data, 'zerver_realmemoji', 'author', related_table="user_profile")
    for (table, model, related_table) in realm_tables:
        re_map_foreign_keys(data, table, 'realm', related_table="realm")
        update_model_ids(model, data, related_table)
        bulk_import_model(data, model)

    if 'zerver_huddle' in data:
        update_model_ids(Huddle, data, 'huddle')
        # We don't import Huddle yet, since we don't have the data to
        # compute huddle hashes until we've imported some of the
        # tables below.
        # TODO: double-check this.

    re_map_foreign_keys(data, 'zerver_recipient', 'type_id', related_table="stream",
                        recipient_field=True, id_field=True)
    re_map_foreign_keys(data, 'zerver_recipient', 'type_id', related_table="user_profile",
                        recipient_field=True, id_field=True)
    re_map_foreign_keys(data, 'zerver_recipient', 'type_id', related_table="huddle",
                        recipient_field=True, id_field=True)
    update_model_ids(Recipient, data, 'recipient')
    bulk_import_model(data, Recipient)

    re_map_foreign_keys(data, 'zerver_subscription', 'user_profile', related_table="user_profile")
    get_huddles_from_subscription(data, 'zerver_subscription')
    re_map_foreign_keys(data, 'zerver_subscription', 'recipient', related_table="recipient")
    update_model_ids(Subscription, data, 'subscription')
    bulk_import_model(data, Subscription)

    if 'zerver_realmauditlog' in data:
        fix_datetime_fields(data, 'zerver_realmauditlog')
        re_map_foreign_keys(data, 'zerver_realmauditlog', 'realm', related_table="realm")
        re_map_foreign_keys(data, 'zerver_realmauditlog', 'modified_user',
                            related_table='user_profile')
        re_map_foreign_keys(data, 'zerver_realmauditlog', 'acting_user',
                            related_table='user_profile')
        re_map_foreign_keys(data, 'zerver_realmauditlog', 'modified_stream',
                            related_table="stream")
        update_model_ids(RealmAuditLog, data, related_table="realmauditlog")
        bulk_import_model(data, RealmAuditLog)
    else:
        logging.info('about to call create_subscription_events')
        create_subscription_events(
            data=data,
            realm_id=realm.id,
        )
        logging.info('done with create_subscription_events')

    if 'zerver_huddle' in data:
        process_huddle_hash(data, 'zerver_huddle')
        bulk_import_model(data, Huddle)

    if 'zerver_userhotspot' in data:
        fix_datetime_fields(data, 'zerver_userhotspot')
        re_map_foreign_keys(data, 'zerver_userhotspot', 'user', related_table='user_profile')
        update_model_ids(UserHotspot, data, 'userhotspot')
        bulk_import_model(data, UserHotspot)

    if 'zerver_mutedtopic' in data:
        re_map_foreign_keys(data, 'zerver_mutedtopic', 'user_profile', related_table='user_profile')
        re_map_foreign_keys(data, 'zerver_mutedtopic', 'stream', related_table='stream')
        re_map_foreign_keys(data, 'zerver_mutedtopic', 'recipient', related_table='recipient')
        update_model_ids(MutedTopic, data, 'mutedtopic')
        bulk_import_model(data, MutedTopic)

    if 'zerver_service' in data:
        re_map_foreign_keys(data, 'zerver_service', 'user_profile', related_table='user_profile')
        fix_service_tokens(data, 'zerver_service')
        update_model_ids(Service, data, 'service')
        bulk_import_model(data, Service)

    if 'zerver_usergroup' in data:
        re_map_foreign_keys(data, 'zerver_usergroup', 'realm', related_table='realm')
        re_map_foreign_keys_many_to_many(data, 'zerver_usergroup',
                                         'members', related_table='user_profile')
        update_model_ids(UserGroup, data, 'usergroup')
        bulk_import_model(data, UserGroup)

        re_map_foreign_keys(data, 'zerver_usergroupmembership',
                            'user_group', related_table='usergroup')
        re_map_foreign_keys(data, 'zerver_usergroupmembership',
                            'user_profile', related_table='user_profile')
        update_model_ids(UserGroupMembership, data, 'usergroupmembership')
        bulk_import_model(data, UserGroupMembership)

    if 'zerver_botstoragedata' in data:
        re_map_foreign_keys(data, 'zerver_botstoragedata', 'bot_profile', related_table='user_profile')
        update_model_ids(BotStorageData, data, 'botstoragedata')
        bulk_import_model(data, BotStorageData)

    if 'zerver_botconfigdata' in data:
        re_map_foreign_keys(data, 'zerver_botconfigdata', 'bot_profile', related_table='user_profile')
        update_model_ids(BotConfigData, data, 'botconfigdata')
        bulk_import_model(data, BotConfigData)

    fix_datetime_fields(data, 'zerver_userpresence')
    re_map_foreign_keys(data, 'zerver_userpresence', 'user_profile', related_table="user_profile")
    re_map_foreign_keys(data, 'zerver_userpresence', 'client', related_table='client')
    update_model_ids(UserPresence, data, 'user_presence')
    bulk_import_model(data, UserPresence)

    fix_datetime_fields(data, 'zerver_useractivity')
    re_map_foreign_keys(data, 'zerver_useractivity', 'user_profile', related_table="user_profile")
    re_map_foreign_keys(data, 'zerver_useractivity', 'client', related_table='client')
    update_model_ids(UserActivity, data, 'useractivity')
    bulk_import_model(data, UserActivity)

    fix_datetime_fields(data, 'zerver_useractivityinterval')
    re_map_foreign_keys(data, 'zerver_useractivityinterval', 'user_profile', related_table="user_profile")
    update_model_ids(UserActivityInterval, data, 'useractivityinterval')
    bulk_import_model(data, UserActivityInterval)

    re_map_foreign_keys(data, 'zerver_customprofilefield', 'realm', related_table="realm")
    update_model_ids(CustomProfileField, data, related_table="customprofilefield")
    bulk_import_model(data, CustomProfileField)

    re_map_foreign_keys(data, 'zerver_customprofilefieldvalue', 'user_profile',
                        related_table="user_profile")
    re_map_foreign_keys(data, 'zerver_customprofilefieldvalue', 'field',
                        related_table="customprofilefield")
    fix_customprofilefield(data)
    update_model_ids(CustomProfileFieldValue, data, related_table="customprofilefieldvalue")
    bulk_import_model(data, CustomProfileFieldValue)

    # Import uploaded files and avatars
    import_uploads(os.path.join(import_dir, "avatars"), processing_avatars=True)
    import_uploads(os.path.join(import_dir, "uploads"))

    # We need to have this check as the emoji files are only present in the data
    # importer from slack
    # For Zulip export, this doesn't exist
    if os.path.exists(os.path.join(import_dir, "emoji")):
        import_uploads(os.path.join(import_dir, "emoji"), processing_emojis=True)

    # Import zerver_message and zerver_usermessage
    import_message_data(import_dir)

    re_map_foreign_keys(data, 'zerver_reaction', 'message', related_table="message")
    re_map_foreign_keys(data, 'zerver_reaction', 'user_profile', related_table="user_profile")
    re_map_foreign_keys(data, 'zerver_reaction', 'emoji_code', related_table="realmemoji", id_field=True,
                        reaction_field=True)
    update_model_ids(Reaction, data, 'reaction')
    bulk_import_model(data, Reaction)

    # Do attachments AFTER message data is loaded.
    # TODO: de-dup how we read these json files.
    fn = os.path.join(import_dir, "attachment.json")
    if not os.path.exists(fn):
        raise Exception("Missing attachment.json file!")

    logging.info("Importing attachment data from %s" % (fn,))
    with open(fn) as f:
        data = ujson.load(f)

    import_attachments(data)
    return realm
Ejemplo n.º 54
0
def do_import_realm(import_dir: Path, subdomain: str) -> Realm:
    logging.info("Importing realm dump %s" % (import_dir,))
    if not os.path.exists(import_dir):
        raise Exception("Missing import directory!")

    realm_data_filename = os.path.join(import_dir, "realm.json")
    if not os.path.exists(realm_data_filename):
        raise Exception("Missing realm.json file!")

    logging.info("Importing realm data from %s" % (realm_data_filename,))
    with open(realm_data_filename) as f:
        data = ujson.load(f)

    update_model_ids(Stream, data, 'zerver_stream', 'stream')
    re_map_foreign_keys(data, 'zerver_realm', 'notifications_stream', related_table="stream")

    fix_datetime_fields(data, 'zerver_realm')
    # Fix realm subdomain information
    data['zerver_realm'][0]['string_id'] = subdomain
    data['zerver_realm'][0]['name'] = subdomain
    fix_realm_authentication_bitfield(data, 'zerver_realm', 'authentication_methods')
    update_model_ids(Realm, data, 'zerver_realm', 'realm')

    realm = Realm(**data['zerver_realm'][0])
    if realm.notifications_stream_id is not None:
        notifications_stream_id = int(realm.notifications_stream_id)  # type: Optional[int]
    else:
        notifications_stream_id = None
    realm.notifications_stream_id = None
    realm.save()
    bulk_import_client(data, Client, 'zerver_client')

    # Email tokens will automatically be randomly generated when the
    # Stream objects are created by Django.
    fix_datetime_fields(data, 'zerver_stream')
    re_map_foreign_keys(data, 'zerver_stream', 'realm', related_table="realm")
    bulk_import_model(data, Stream, 'zerver_stream')

    realm.notifications_stream_id = notifications_stream_id
    realm.save()

    re_map_foreign_keys(data, 'zerver_defaultstream', 'stream', related_table="stream")
    re_map_foreign_keys(data, 'zerver_realmemoji', 'author', related_table="user_profile")
    for (table, model, related_table) in realm_tables:
        re_map_foreign_keys(data, table, 'realm', related_table="realm")
        update_model_ids(model, data, table, related_table)
        bulk_import_model(data, model, table)

    # Remap the user IDs for notification_bot and friends to their
    # appropriate IDs on this server
    for item in data['zerver_userprofile_crossrealm']:
        logging.info("Adding to ID map: %s %s" % (item['id'], get_system_bot(item['email']).id))
        new_user_id = get_system_bot(item['email']).id
        update_id_map(table='user_profile', old_id=item['id'], new_id=new_user_id)

    # Merge in zerver_userprofile_mirrordummy
    data['zerver_userprofile'] = data['zerver_userprofile'] + data['zerver_userprofile_mirrordummy']
    del data['zerver_userprofile_mirrordummy']
    data['zerver_userprofile'].sort(key=lambda r: r['id'])

    # To remap foreign key for UserProfile.last_active_message_id
    update_message_foreign_keys(import_dir)

    fix_datetime_fields(data, 'zerver_userprofile')
    update_model_ids(UserProfile, data, 'zerver_userprofile', 'user_profile')
    re_map_foreign_keys(data, 'zerver_userprofile', 'realm', related_table="realm")
    re_map_foreign_keys(data, 'zerver_userprofile', 'bot_owner', related_table="user_profile")
    re_map_foreign_keys(data, 'zerver_userprofile', 'default_sending_stream',
                        related_table="stream")
    re_map_foreign_keys(data, 'zerver_userprofile', 'default_events_register_stream',
                        related_table="stream")
    re_map_foreign_keys(data, 'zerver_userprofile', 'last_active_message_id',
                        related_table="message", id_field=True)
    for user_profile_dict in data['zerver_userprofile']:
        user_profile_dict['password'] = None
        user_profile_dict['api_key'] = random_api_key()
        # Since Zulip doesn't use these permissions, drop them
        del user_profile_dict['user_permissions']
        del user_profile_dict['groups']

    user_profiles = [UserProfile(**item) for item in data['zerver_userprofile']]
    for user_profile in user_profiles:
        user_profile.set_unusable_password()
    UserProfile.objects.bulk_create(user_profiles)

    if 'zerver_huddle' in data:
        bulk_import_model(data, Huddle, 'zerver_huddle')

    re_map_foreign_keys(data, 'zerver_recipient', 'type_id', related_table="stream",
                        recipient_field=True, id_field=True)
    re_map_foreign_keys(data, 'zerver_recipient', 'type_id', related_table="user_profile",
                        recipient_field=True, id_field=True)
    update_model_ids(Recipient, data, 'zerver_recipient', 'recipient')
    bulk_import_model(data, Recipient, 'zerver_recipient')

    re_map_foreign_keys(data, 'zerver_subscription', 'user_profile', related_table="user_profile")
    re_map_foreign_keys(data, 'zerver_subscription', 'recipient', related_table="recipient")
    update_model_ids(Subscription, data, 'zerver_subscription', 'subscription')
    bulk_import_model(data, Subscription, 'zerver_subscription')

    fix_datetime_fields(data, 'zerver_userpresence')
    re_map_foreign_keys(data, 'zerver_userpresence', 'user_profile', related_table="user_profile")
    re_map_foreign_keys(data, 'zerver_userpresence', 'client', related_table='client')
    update_model_ids(UserPresence, data, 'zerver_userpresence', 'user_presence')
    bulk_import_model(data, UserPresence, 'zerver_userpresence')

    fix_datetime_fields(data, 'zerver_useractivity')
    re_map_foreign_keys(data, 'zerver_useractivity', 'user_profile', related_table="user_profile")
    re_map_foreign_keys(data, 'zerver_useractivity', 'client', related_table='client')
    update_model_ids(UserActivity, data, 'zerver_useractivity', 'useractivity')
    bulk_import_model(data, UserActivity, 'zerver_useractivity')

    fix_datetime_fields(data, 'zerver_useractivityinterval')
    re_map_foreign_keys(data, 'zerver_useractivityinterval', 'user_profile', related_table="user_profile")
    update_model_ids(UserActivityInterval, data, 'zerver_useractivityinterval',
                     'useractivityinterval')
    bulk_import_model(data, UserActivityInterval, 'zerver_useractivityinterval')

    if 'zerver_customprofilefield' in data:
        # As the export of Custom Profile fields is not supported, Zulip exported
        # data would not contain this field.
        # However this is supported in slack importer script
        re_map_foreign_keys(data, 'zerver_customprofilefield', 'realm', related_table="realm")
        update_model_ids(CustomProfileField, data, 'zerver_customprofilefield',
                         related_table="customprofilefield")
        bulk_import_model(data, CustomProfileField, 'zerver_customprofilefield')

        re_map_foreign_keys(data, 'zerver_customprofilefield_value', 'user_profile',
                            related_table="user_profile")
        re_map_foreign_keys(data, 'zerver_customprofilefield_value', 'field',
                            related_table="customprofilefield")
        update_model_ids(CustomProfileFieldValue, data, 'zerver_customprofilefield_value',
                         related_table="customprofilefield_value")
        bulk_import_model(data, CustomProfileFieldValue, 'zerver_customprofilefield_value')

    # Import uploaded files and avatars
    import_uploads(os.path.join(import_dir, "avatars"), processing_avatars=True)
    import_uploads(os.path.join(import_dir, "uploads"))

    # We need to have this check as the emoji files are only present in the data
    # importer from slack
    # For Zulip export, this doesn't exist
    if os.path.exists(os.path.join(import_dir, "emoji")):
        import_uploads(os.path.join(import_dir, "emoji"), processing_emojis=True)

    # Import zerver_message and zerver_usermessage
    import_message_data(import_dir)

    # Do attachments AFTER message data is loaded.
    # TODO: de-dup how we read these json files.
    fn = os.path.join(import_dir, "attachment.json")
    if not os.path.exists(fn):
        raise Exception("Missing attachment.json file!")

    logging.info("Importing attachment data from %s" % (fn,))
    with open(fn) as f:
        data = ujson.load(f)

    import_attachments(data)
    return realm
Ejemplo n.º 55
0
def add_subscriptions_backend(
        request: HttpRequest, user_profile: UserProfile,
        streams_raw: Iterable[Mapping[str, Text]]=REQ(
            "subscriptions", validator=check_list(check_dict([('name', check_string)]))),
        invite_only: bool=REQ(validator=check_bool, default=False),
        announce: bool=REQ(validator=check_bool, default=False),
        principals: List[Text]=REQ(validator=check_list(check_string), default=[]),
        authorization_errors_fatal: bool=REQ(validator=check_bool, default=True),
) -> HttpResponse:
    stream_dicts = []
    for stream_dict in streams_raw:
        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_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 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,
                                                              acting_user=user_profile)

    # 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[Text, 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}
    private_stream_names = {s.name for s in streams if s.invite_only}

    # 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,
                private_stream_names=private_stream_names
            )

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

    if announce and len(created_streams) > 0 and settings.NOTIFICATION_BOT is not None:
        notifications_stream = user_profile.realm.get_notifications_stream()
        if notifications_stream is not None:
            if len(created_streams) > 1:
                stream_strs = ", ".join('#**%s**' % s.name for s in created_streams)
                stream_msg = "the following streams: %s" % (stream_strs,)
            else:
                stream_msg = "a new stream #**%s**." % created_streams[0].name
            msg = ("%s just created %s" % (user_profile.full_name, stream_msg))

            sender = get_system_bot(settings.NOTIFICATION_BOT)
            stream_name = notifications_stream.name
            topic = 'Streams'

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

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

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

    result["subscribed"] = dict(result["subscribed"])
    result["already_subscribed"] = dict(result["already_subscribed"])
    if not authorization_errors_fatal:
        result["unauthorized"] = [s.name for s in unauthorized_streams]
    return json_success(result)
Ejemplo n.º 56
0
    def handle(self, **options: Any) -> None:
        if options["percent_huddles"] + options["percent_personals"] > 100:
            self.stderr.write("Error!  More than 100% of messages allocated.\n")
            return

        # Get consistent data for backend tests.
        if options["test_suite"]:
            random.seed(0)

        if options["delete"]:
            # Start by clearing all the data in our database
            clear_database()

            # Create our two default realms
            # Could in theory be done via zerver.lib.actions.do_create_realm, but
            # welcome-bot (needed for do_create_realm) hasn't been created yet
            zulip_realm = Realm.objects.create(
                string_id="zulip", name="Zulip Dev", emails_restricted_to_domains=True,
                description="The Zulip development environment default organization."
                            "  It's great for testing!",
                invite_required=False, org_type=Realm.CORPORATE)
            RealmDomain.objects.create(realm=zulip_realm, domain="zulip.com")
            if options["test_suite"]:
                mit_realm = Realm.objects.create(
                    string_id="zephyr", name="MIT", emails_restricted_to_domains=True,
                    invite_required=False, org_type=Realm.CORPORATE)
                RealmDomain.objects.create(realm=mit_realm, domain="mit.edu")

                lear_realm = Realm.objects.create(
                    string_id="lear", name="Lear & Co.", emails_restricted_to_domains=False,
                    invite_required=False, org_type=Realm.CORPORATE)

            # Create test Users (UserProfiles are automatically created,
            # as are subscriptions to the ability to receive personals).
            names = [
                ("Zoe", "*****@*****.**"),
                ("Othello, the Moor of Venice", "*****@*****.**"),
                ("Iago", "*****@*****.**"),
                ("Prospero from The Tempest", "*****@*****.**"),
                ("Cordelia Lear", "*****@*****.**"),
                ("King Hamlet", "*****@*****.**"),
                ("aaron", "*****@*****.**"),
                ("Polonius", "*****@*****.**"),
            ]
            for i in range(options["extra_users"]):
                names.append(('Extra User %d' % (i,), '*****@*****.**' % (i,)))
            create_users(zulip_realm, names)

            iago = get_user("*****@*****.**", zulip_realm)
            do_change_is_admin(iago, True)
            iago.is_staff = True
            iago.save(update_fields=['is_staff'])

            guest_user = get_user("*****@*****.**", zulip_realm)
            guest_user.is_guest = True
            guest_user.save(update_fields=['is_guest'])

            # These bots are directly referenced from code and thus
            # are needed for the test suite.
            all_realm_bots = [(bot['name'], bot['email_template'] % (settings.INTERNAL_BOT_DOMAIN,))
                              for bot in settings.INTERNAL_BOTS]
            zulip_realm_bots = [
                ("Zulip New User Bot", "*****@*****.**"),
                ("Zulip Error Bot", "*****@*****.**"),
                ("Zulip Default Bot", "*****@*****.**"),
                ("Welcome Bot", "*****@*****.**"),
            ]

            for i in range(options["extra_bots"]):
                zulip_realm_bots.append(('Extra Bot %d' % (i,), '*****@*****.**' % (i,)))
            zulip_realm_bots.extend(all_realm_bots)
            create_users(zulip_realm, zulip_realm_bots, bot_type=UserProfile.DEFAULT_BOT)

            # Initialize the email gateway bot as an API Super User
            email_gateway_bot = get_system_bot(settings.EMAIL_GATEWAY_BOT)
            email_gateway_bot.is_api_super_user = True
            email_gateway_bot.save()

            zoe = get_user("*****@*****.**", zulip_realm)
            zulip_webhook_bots = [
                ("Zulip Webhook Bot", "*****@*****.**"),
            ]
            # If a stream is not supplied in the webhook URL, the webhook
            # will (in some cases) send the notification as a PM to the
            # owner of the webhook bot, so bot_owner can't be None
            create_users(zulip_realm, zulip_webhook_bots,
                         bot_type=UserProfile.INCOMING_WEBHOOK_BOT, bot_owner=zoe)
            aaron = get_user("*****@*****.**", zulip_realm)

            zulip_outgoing_bots = [
                ("Outgoing Webhook", "*****@*****.**")
            ]
            create_users(zulip_realm, zulip_outgoing_bots,
                         bot_type=UserProfile.OUTGOING_WEBHOOK_BOT, bot_owner=aaron)
            outgoing_webhook = get_user("*****@*****.**", zulip_realm)
            add_service("outgoing-webhook", user_profile=outgoing_webhook, interface=Service.GENERIC,
                        base_url="http://127.0.0.1:5002", token=generate_api_key())

            # Add the realm internl bots to each realm.
            create_if_missing_realm_internal_bots()

            # Create public streams.
            stream_list = ["Verona", "Denmark", "Scotland", "Venice", "Rome"]
            stream_dict = {
                "Verona": {"description": "A city in Italy"},
                "Denmark": {"description": "A Scandinavian country"},
                "Scotland": {"description": "Located in the United Kingdom"},
                "Venice": {"description": "A northeastern Italian city"},
                "Rome": {"description": "Yet another Italian city", "is_web_public": True}
            }  # type: Dict[str, Dict[str, Any]]

            bulk_create_streams(zulip_realm, stream_dict)
            recipient_streams = [Stream.objects.get(name=name, realm=zulip_realm).id
                                 for name in stream_list]  # type: List[int]

            # Create subscriptions to streams.  The following
            # algorithm will give each of the users a different but
            # deterministic subset of the streams (given a fixed list
            # of users). For the test suite, we have a fixed list of
            # subscriptions to make sure test data is consistent
            # across platforms.

            subscriptions_list = []  # type: List[Tuple[UserProfile, Recipient]]
            profiles = UserProfile.objects.select_related().filter(
                is_bot=False).order_by("email")  # type: Sequence[UserProfile]

            if options["test_suite"]:
                subscriptions_map = {
                    '*****@*****.**': ['Verona'],
                    '*****@*****.**': ['Verona'],
                    '*****@*****.**': ['Verona', 'Denmark'],
                    '*****@*****.**': ['Verona', 'Denmark', 'Scotland'],
                    '*****@*****.**': ['Verona', 'Denmark', 'Scotland'],
                    '*****@*****.**': ['Verona', 'Denmark', 'Scotland', 'Venice'],
                    '*****@*****.**': ['Verona', 'Denmark', 'Scotland', 'Venice', 'Rome'],
                    '*****@*****.**': ['Verona'],
                }

                for profile in profiles:
                    if profile.email not in subscriptions_map:
                        raise Exception('Subscriptions not listed for user %s' % (profile.email,))

                    for stream_name in subscriptions_map[profile.email]:
                        stream = Stream.objects.get(name=stream_name)
                        r = Recipient.objects.get(type=Recipient.STREAM, type_id=stream.id)
                        subscriptions_list.append((profile, r))
            else:
                for i, profile in enumerate(profiles):
                    # Subscribe to some streams.
                    for type_id in recipient_streams[:int(len(recipient_streams) *
                                                          float(i)/len(profiles)) + 1]:
                        r = Recipient.objects.get(type=Recipient.STREAM, type_id=type_id)
                        subscriptions_list.append((profile, r))

            subscriptions_to_add = []  # type: List[Subscription]
            event_time = timezone_now()
            all_subscription_logs = []  # type: (List[RealmAuditLog])

            i = 0
            for profile, recipient in subscriptions_list:
                i += 1
                color = STREAM_ASSIGNMENT_COLORS[i % len(STREAM_ASSIGNMENT_COLORS)]
                s = Subscription(
                    recipient=recipient,
                    user_profile=profile,
                    color=color)

                subscriptions_to_add.append(s)

                log = RealmAuditLog(realm=profile.realm,
                                    modified_user=profile,
                                    modified_stream_id=recipient.type_id,
                                    event_last_message_id=0,
                                    event_type=RealmAuditLog.SUBSCRIPTION_CREATED,
                                    event_time=event_time)
                all_subscription_logs.append(log)

            Subscription.objects.bulk_create(subscriptions_to_add)
            RealmAuditLog.objects.bulk_create(all_subscription_logs)

            # Create custom profile field data
            phone_number = try_add_realm_custom_profile_field(zulip_realm, "Phone number",
                                                              CustomProfileField.SHORT_TEXT,
                                                              hint='')
            biography = try_add_realm_custom_profile_field(zulip_realm, "Biography",
                                                           CustomProfileField.LONG_TEXT,
                                                           hint='What are you known for?')
            favorite_food = try_add_realm_custom_profile_field(zulip_realm, "Favorite food",
                                                               CustomProfileField.SHORT_TEXT,
                                                               hint="Or drink, if you'd prefer")
            field_data = {
                'vim': {'text': 'Vim', 'order': '1'},
                'emacs': {'text': 'Emacs', 'order': '2'},
            }
            favorite_editor = try_add_realm_custom_profile_field(zulip_realm,
                                                                 "Favorite editor",
                                                                 CustomProfileField.CHOICE,
                                                                 field_data=field_data)
            birthday = try_add_realm_custom_profile_field(zulip_realm, "Birthday",
                                                          CustomProfileField.DATE)
            favorite_website = try_add_realm_custom_profile_field(zulip_realm, "GitHub profile",
                                                                  CustomProfileField.URL,
                                                                  hint="Or your personal blog's URL")
            mentor = try_add_realm_custom_profile_field(zulip_realm, "Mentor",
                                                        CustomProfileField.USER)

            # Fill in values for Iago and Hamlet
            hamlet = get_user("*****@*****.**", zulip_realm)
            do_update_user_custom_profile_data(iago, [
                {"id": phone_number.id, "value": "+1-234-567-8901"},
                {"id": biography.id, "value": "Betrayer of Othello."},
                {"id": favorite_food.id, "value": "Apples"},
                {"id": favorite_editor.id, "value": "emacs"},
                {"id": birthday.id, "value": "2000-1-1"},
                {"id": favorite_website.id, "value": "https://github.com/zulip/zulip"},
                {"id": mentor.id, "value": [hamlet.id]},
            ])
            do_update_user_custom_profile_data(hamlet, [
                {"id": phone_number.id, "value": "+0-11-23-456-7890"},
                {"id": biography.id, "value": "Prince of Denmark, and other things!"},
                {"id": favorite_food.id, "value": "Dark chocolate"},
                {"id": favorite_editor.id, "value": "vim"},
                {"id": birthday.id, "value": "1900-1-1"},
                {"id": favorite_website.id, "value": "https://blog.zulig.org"},
                {"id": mentor.id, "value": [iago.id]},
            ])
        else:
            zulip_realm = get_realm("zulip")
            recipient_streams = [klass.type_id for klass in
                                 Recipient.objects.filter(type=Recipient.STREAM)]

        # Extract a list of all users
        user_profiles = list(UserProfile.objects.filter(is_bot=False))  # type: List[UserProfile]

        # Create a test realm emoji.
        IMAGE_FILE_PATH = os.path.join(settings.STATIC_ROOT, 'images', 'test-images', 'checkbox.png')
        with open(IMAGE_FILE_PATH, 'rb') as fp:
            check_add_realm_emoji(zulip_realm, 'green_tick', iago, fp)

        if not options["test_suite"]:
            # Populate users with some bar data
            for user in user_profiles:
                status = UserPresence.ACTIVE  # type: int
                date = timezone_now()
                client = get_client("website")
                if user.full_name[0] <= 'H':
                    client = get_client("ZulipAndroid")
                UserPresence.objects.get_or_create(user_profile=user,
                                                   client=client,
                                                   timestamp=date,
                                                   status=status)

        user_profiles_ids = [user_profile.id for user_profile in user_profiles]

        # Create several initial huddles
        for i in range(options["num_huddles"]):
            get_huddle(random.sample(user_profiles_ids, random.randint(3, 4)))

        # Create several initial pairs for personals
        personals_pairs = [random.sample(user_profiles_ids, 2)
                           for i in range(options["num_personals"])]

        # Generate a new set of test data.
        create_test_data()

        # prepopulate the URL preview/embed data for the links present
        # in the config.generate_data.json data set.  This makes it
        # possible for populate_db to run happily without Internet
        # access.
        with open("zerver/tests/fixtures/docs_url_preview_data.json", "r") as f:
            urls_with_preview_data = ujson.load(f)
            for url in urls_with_preview_data:
                cache_set(url, urls_with_preview_data[url], PREVIEW_CACHE_NAME)

        threads = options["threads"]
        jobs = []  # type: List[Tuple[int, List[List[int]], Dict[str, Any], Callable[[str], int], int]]
        for i in range(threads):
            count = options["num_messages"] // threads
            if i < options["num_messages"] % threads:
                count += 1
            jobs.append((count, personals_pairs, options, self.stdout.write, random.randint(0, 10**10)))

        for job in jobs:
            send_messages(job)

        if options["delete"]:
            # Create the "website" and "API" clients; if we don't, the
            # default values in zerver/decorators.py will not work
            # with the Django test suite.
            get_client("website")
            get_client("API")

            if options["test_suite"]:
                # Create test users; the MIT ones are needed to test
                # the Zephyr mirroring codepaths.
                testsuite_mit_users = [
                    ("Fred Sipb (MIT)", "*****@*****.**"),
                    ("Athena Consulting Exchange User (MIT)", "*****@*****.**"),
                    ("Esp Classroom (MIT)", "*****@*****.**"),
                ]
                create_users(mit_realm, testsuite_mit_users)

                testsuite_lear_users = [
                    ("King Lear", "*****@*****.**"),
                    ("Cordelia Lear", "*****@*****.**"),
                ]
                create_users(lear_realm, testsuite_lear_users)

            if not options["test_suite"]:
                # To keep the messages.json fixtures file for the test
                # suite fast, don't add these users and subscriptions
                # when running populate_db for the test suite

                zulip_stream_dict = {
                    "devel": {"description": "For developing"},
                    "all": {"description": "For everything"},
                    "announce": {"description": "For announcements", 'is_announcement_only': True},
                    "design": {"description": "For design"},
                    "support": {"description": "For support"},
                    "social": {"description": "For socializing"},
                    "test": {"description": "For testing"},
                    "errors": {"description": "For errors"},
                    "sales": {"description": "For sales discussion"}
                }  # type: Dict[str, Dict[str, Any]]

                # Calculate the maximum number of digits in any extra stream's
                # number, since a stream with name "Extra Stream 3" could show
                # up after "Extra Stream 29". (Used later to pad numbers with
                # 0s).
                maximum_digits = len(str(options['extra_streams'] - 1))

                for i in range(options['extra_streams']):
                    # Pad the number with 0s based on `maximum_digits`.
                    number_str = str(i).zfill(maximum_digits)

                    extra_stream_name = 'Extra Stream ' + number_str

                    zulip_stream_dict[extra_stream_name] = {
                        "description": "Auto-generated extra stream.",
                    }

                bulk_create_streams(zulip_realm, zulip_stream_dict)
                # Now that we've created the notifications stream, configure it properly.
                zulip_realm.notifications_stream = get_stream("announce", zulip_realm)
                zulip_realm.save(update_fields=['notifications_stream'])

                # Add a few default streams
                for default_stream_name in ["design", "devel", "social", "support"]:
                    DefaultStream.objects.create(realm=zulip_realm,
                                                 stream=get_stream(default_stream_name, zulip_realm))

                # Now subscribe everyone to these streams
                subscribe_users_to_streams(zulip_realm, zulip_stream_dict)

                # These bots are not needed by the test suite
                internal_zulip_users_nosubs = [
                    ("Zulip Commit Bot", "*****@*****.**"),
                    ("Zulip Trac Bot", "*****@*****.**"),
                    ("Zulip Nagios Bot", "*****@*****.**"),
                ]
                create_users(zulip_realm, internal_zulip_users_nosubs, bot_type=UserProfile.DEFAULT_BOT)

            zulip_cross_realm_bots = [
                ("Zulip Feedback Bot", "*****@*****.**"),
            ]
            create_users(zulip_realm, zulip_cross_realm_bots, bot_type=UserProfile.DEFAULT_BOT)

            # Mark all messages as read
            UserMessage.objects.all().update(flags=UserMessage.flags.read)

            if not options["test_suite"]:
                # Update pointer of each user to point to the last message in their
                # UserMessage rows with sender_id=user_profile_id.
                users = list(UserMessage.objects.filter(
                    message__sender_id=F('user_profile_id')).values(
                    'user_profile_id').annotate(pointer=Max('message_id')))
                for user in users:
                    UserProfile.objects.filter(id=user['user_profile_id']).update(
                        pointer=user['pointer'])

            create_user_groups()

            if not options["test_suite"]:
                # We populate the analytics database here for
                # development purpose only
                call_command('populate_analytics_db')
            self.stdout.write("Successfully populated test database.\n")
Ejemplo n.º 57
0
def export_files_from_s3(realm: Realm, bucket_name: str, output_dir: Path,
                         processing_avatars: bool=False) -> None:
    conn = S3Connection(settings.S3_KEY, settings.S3_SECRET_KEY)
    bucket = conn.get_bucket(bucket_name, validate=True)
    records = []

    logging.info("Downloading uploaded files from %s" % (bucket_name))

    avatar_hash_values = set()
    user_ids = set()
    if processing_avatars:
        bucket_list = bucket.list()
        for user_profile in UserProfile.objects.filter(realm=realm):
            avatar_path = user_avatar_path_from_ids(user_profile.id, realm.id)
            avatar_hash_values.add(avatar_path)
            avatar_hash_values.add(avatar_path + ".original")
            user_ids.add(user_profile.id)
    else:
        bucket_list = bucket.list(prefix="%s/" % (realm.id,))

    if settings.EMAIL_GATEWAY_BOT is not None:
        email_gateway_bot = get_system_bot(settings.EMAIL_GATEWAY_BOT)  # type: Optional[UserProfile]
    else:
        email_gateway_bot = None

    count = 0
    for bkey in bucket_list:
        if processing_avatars and bkey.name not in avatar_hash_values:
            continue
        key = bucket.get_key(bkey.name)

        # This can happen if an email address has moved realms
        if 'realm_id' in key.metadata and key.metadata['realm_id'] != str(realm.id):
            if email_gateway_bot is None or key.metadata['user_profile_id'] != str(email_gateway_bot.id):
                raise AssertionError("Key metadata problem: %s %s / %s" % (key.name, key.metadata, realm.id))
            # Email gateway bot sends messages, potentially including attachments, cross-realm.
            print("File uploaded by email gateway bot: %s / %s" % (key.name, key.metadata))
        elif processing_avatars:
            if 'user_profile_id' not in key.metadata:
                raise AssertionError("Missing user_profile_id in key metadata: %s" % (key.metadata,))
            if int(key.metadata['user_profile_id']) not in user_ids:
                raise AssertionError("Wrong user_profile_id in key metadata: %s" % (key.metadata,))
        elif 'realm_id' not in key.metadata:
            raise AssertionError("Missing realm_id in key metadata: %s" % (key.metadata,))

        record = dict(s3_path=key.name, bucket=bucket_name,
                      size=key.size, last_modified=key.last_modified,
                      content_type=key.content_type, md5=key.md5)
        record.update(key.metadata)

        # A few early avatars don't have 'realm_id' on the object; fix their metadata
        user_profile = get_user_profile_by_id(record['user_profile_id'])
        if 'realm_id' not in record:
            record['realm_id'] = user_profile.realm_id
        record['user_profile_email'] = user_profile.email

        if processing_avatars:
            dirname = output_dir
            filename = os.path.join(dirname, key.name)
            record['path'] = key.name
        else:
            fields = key.name.split('/')
            if len(fields) != 3:
                raise AssertionError("Suspicious key with invalid format %s" % (key.name))
            dirname = os.path.join(output_dir, fields[1])
            filename = os.path.join(dirname, fields[2])
            record['path'] = os.path.join(fields[1], fields[2])

        if not os.path.exists(dirname):
            os.makedirs(dirname)
        key.get_contents_to_filename(filename)

        records.append(record)
        count += 1

        if (count % 100 == 0):
            logging.info("Finished %s" % (count,))

    with open(os.path.join(output_dir, "records.json"), "w") as records_file:
        ujson.dump(records, records_file, indent=4)
Ejemplo n.º 58
0
    def handle(self, **options: Any) -> None:
        if options["percent_huddles"] + options["percent_personals"] > 100:
            self.stderr.write("Error!  More than 100% of messages allocated.\n")
            return

        if options["delete"]:
            # Start by clearing all the data in our database
            clear_database()

            # Create our two default realms
            # Could in theory be done via zerver.lib.actions.do_create_realm, but
            # welcome-bot (needed for do_create_realm) hasn't been created yet
            zulip_realm = Realm.objects.create(
                string_id="zulip", name="Zulip Dev", restricted_to_domain=True,
                description="The Zulip development environment default organization."
                            "  It's great for testing!",
                invite_required=False, org_type=Realm.CORPORATE)
            RealmDomain.objects.create(realm=zulip_realm, domain="zulip.com")
            if options["test_suite"]:
                mit_realm = Realm.objects.create(
                    string_id="zephyr", name="MIT", restricted_to_domain=True,
                    invite_required=False, org_type=Realm.CORPORATE)
                RealmDomain.objects.create(realm=mit_realm, domain="mit.edu")

                lear_realm = Realm.objects.create(
                    string_id="lear", name="Lear & Co.", restricted_to_domain=False,
                    invite_required=False, org_type=Realm.CORPORATE)

            # Create test Users (UserProfiles are automatically created,
            # as are subscriptions to the ability to receive personals).
            names = [
                ("Zoe", "*****@*****.**"),
                ("Othello, the Moor of Venice", "*****@*****.**"),
                ("Iago", "*****@*****.**"),
                ("Prospero from The Tempest", "*****@*****.**"),
                ("Cordelia Lear", "*****@*****.**"),
                ("King Hamlet", "*****@*****.**"),
                ("aaron", "*****@*****.**"),
            ]
            for i in range(options["extra_users"]):
                names.append(('Extra User %d' % (i,), '*****@*****.**' % (i,)))
            create_users(zulip_realm, names)

            iago = get_user("*****@*****.**", zulip_realm)
            do_change_is_admin(iago, True)
            iago.is_staff = True
            iago.save(update_fields=['is_staff'])

            # These bots are directly referenced from code and thus
            # are needed for the test suite.
            all_realm_bots = [(bot['name'], bot['email_template'] % (settings.INTERNAL_BOT_DOMAIN,))
                              for bot in settings.INTERNAL_BOTS]
            zulip_realm_bots = [
                ("Zulip New User Bot", "*****@*****.**"),
                ("Zulip Error Bot", "*****@*****.**"),
                ("Zulip Default Bot", "*****@*****.**"),
                ("Welcome Bot", "*****@*****.**"),
            ]

            for i in range(options["extra_bots"]):
                zulip_realm_bots.append(('Extra Bot %d' % (i,), '*****@*****.**' % (i,)))
            zulip_realm_bots.extend(all_realm_bots)
            create_users(zulip_realm, zulip_realm_bots, bot_type=UserProfile.DEFAULT_BOT)

            zoe = get_user("*****@*****.**", zulip_realm)
            zulip_webhook_bots = [
                ("Zulip Webhook Bot", "*****@*****.**"),
            ]
            # If a stream is not supplied in the webhook URL, the webhook
            # will (in some cases) send the notification as a PM to the
            # owner of the webhook bot, so bot_owner can't be None
            create_users(zulip_realm, zulip_webhook_bots,
                         bot_type=UserProfile.INCOMING_WEBHOOK_BOT, bot_owner=zoe)
            aaron = get_user("*****@*****.**", zulip_realm)
            zulip_outgoing_bots = [
                ("Outgoing Webhook", "*****@*****.**")
            ]
            create_users(zulip_realm, zulip_outgoing_bots,
                         bot_type=UserProfile.OUTGOING_WEBHOOK_BOT, bot_owner=aaron)
            # TODO: Clean up this initial bot creation code
            Service.objects.create(
                name="test",
                user_profile=get_user("*****@*****.**", zulip_realm),
                base_url="http://127.0.0.1:5002/bots/followup",
                token="abcd1234",
                interface=1)

            # Create public streams.
            stream_list = ["Verona", "Denmark", "Scotland", "Venice", "Rome"]
            stream_dict = {
                "Verona": {"description": "A city in Italy", "invite_only": False},
                "Denmark": {"description": "A Scandinavian country", "invite_only": False},
                "Scotland": {"description": "Located in the United Kingdom", "invite_only": False},
                "Venice": {"description": "A northeastern Italian city", "invite_only": False},
                "Rome": {"description": "Yet another Italian city", "invite_only": False}
            }  # type: Dict[Text, Dict[Text, Any]]

            bulk_create_streams(zulip_realm, stream_dict)
            recipient_streams = [Stream.objects.get(name=name, realm=zulip_realm).id
                                 for name in stream_list]  # type: List[int]
            # Create subscriptions to streams.  The following
            # algorithm will give each of the users a different but
            # deterministic subset of the streams (given a fixed list
            # of users).
            subscriptions_to_add = []  # type: List[Subscription]
            event_time = timezone_now()
            all_subscription_logs = []  # type: (List[RealmAuditLog])
            profiles = UserProfile.objects.select_related().filter(
                is_bot=False).order_by("email")  # type: Sequence[UserProfile]
            for i, profile in enumerate(profiles):
                # Subscribe to some streams.
                for type_id in recipient_streams[:int(len(recipient_streams) *
                                                      float(i)/len(profiles)) + 1]:
                    r = Recipient.objects.get(type=Recipient.STREAM, type_id=type_id)
                    s = Subscription(
                        recipient=r,
                        user_profile=profile,
                        color=STREAM_ASSIGNMENT_COLORS[i % len(STREAM_ASSIGNMENT_COLORS)])

                    subscriptions_to_add.append(s)

                    log = RealmAuditLog(realm=profile.realm,
                                        modified_user=profile,
                                        modified_stream_id=type_id,
                                        event_last_message_id=0,
                                        event_type='subscription_created',
                                        event_time=event_time)
                    all_subscription_logs.append(log)

            Subscription.objects.bulk_create(subscriptions_to_add)
            RealmAuditLog.objects.bulk_create(all_subscription_logs)

            if not options['test_suite']:
                # Create custom profile field data
                phone_number = try_add_realm_custom_profile_field(zulip_realm, "Phone number",
                                                                  CustomProfileField.SHORT_TEXT)
                biography = try_add_realm_custom_profile_field(zulip_realm, "Biography",
                                                               CustomProfileField.LONG_TEXT)
                favorite_integer = try_add_realm_custom_profile_field(zulip_realm, "Favorite integer",
                                                                      CustomProfileField.INTEGER)

                # Fill in values for Iago and Hamlet
                hamlet = get_user("*****@*****.**", zulip_realm)
                do_update_user_custom_profile_data(iago, [
                    {"id": phone_number.id, "value": "+1-234-567-8901"},
                    {"id": biography.id, "value": "Betrayer of Othello."},
                    {"id": favorite_integer.id, "value": 17},
                ])
                do_update_user_custom_profile_data(hamlet, [
                    {"id": phone_number.id, "value": "+0-11-23-456-7890"},
                    {"id": biography.id, "value": "Prince of Denmark, and other things!"},
                    {"id": favorite_integer.id, "value": 12},
                ])
        else:
            zulip_realm = get_realm("zulip")
            recipient_streams = [klass.type_id for klass in
                                 Recipient.objects.filter(type=Recipient.STREAM)]

        # Extract a list of all users
        user_profiles = list(UserProfile.objects.filter(is_bot=False))  # type: List[UserProfile]

        # Create a test realm emoji.
        IMAGE_FILE_PATH = os.path.join(settings.STATIC_ROOT, 'images', 'test-images', 'checkbox.png')
        UPLOADED_EMOJI_FILE_NAME = 'green_tick.png'
        with open(IMAGE_FILE_PATH, 'rb') as fp:
            upload_backend.upload_emoji_image(fp, UPLOADED_EMOJI_FILE_NAME, iago)
            RealmEmoji.objects.create(
                name='green_tick',
                author=iago,
                realm=zulip_realm,
                deactivated=False,
                file_name=UPLOADED_EMOJI_FILE_NAME,
            )

        if not options["test_suite"]:
            # Populate users with some bar data
            for user in user_profiles:
                status = UserPresence.ACTIVE  # type: int
                date = timezone_now()
                client = get_client("website")
                if user.full_name[0] <= 'H':
                    client = get_client("ZulipAndroid")
                UserPresence.objects.get_or_create(user_profile=user,
                                                   client=client,
                                                   timestamp=date,
                                                   status=status)

        user_profiles_ids = [user_profile.id for user_profile in user_profiles]

        # Create several initial huddles
        for i in range(options["num_huddles"]):
            get_huddle(random.sample(user_profiles_ids, random.randint(3, 4)))

        # Create several initial pairs for personals
        personals_pairs = [random.sample(user_profiles_ids, 2)
                           for i in range(options["num_personals"])]

        # Generate a new set of test data.
        create_test_data()

        threads = options["threads"]
        jobs = []  # type: List[Tuple[int, List[List[int]], Dict[str, Any], Callable[[str], int], int]]
        for i in range(threads):
            count = options["num_messages"] // threads
            if i < options["num_messages"] % threads:
                count += 1
            jobs.append((count, personals_pairs, options, self.stdout.write, random.randint(0, 10**10)))

        for job in jobs:
            send_messages(job)

        if options["delete"]:
            # Create the "website" and "API" clients; if we don't, the
            # default values in zerver/decorators.py will not work
            # with the Django test suite.
            get_client("website")
            get_client("API")

            if options["test_suite"]:
                # Create test users; the MIT ones are needed to test
                # the Zephyr mirroring codepaths.
                testsuite_mit_users = [
                    ("Fred Sipb (MIT)", "*****@*****.**"),
                    ("Athena Consulting Exchange User (MIT)", "*****@*****.**"),
                    ("Esp Classroom (MIT)", "*****@*****.**"),
                ]
                create_users(mit_realm, testsuite_mit_users)

                testsuite_lear_users = [
                    ("King Lear", "*****@*****.**"),
                    ("Cordelia Lear", "*****@*****.**"),
                ]
                create_users(lear_realm, testsuite_lear_users)

            if not options["test_suite"]:
                # Initialize the email gateway bot as an API Super User
                email_gateway_bot = get_system_bot(settings.EMAIL_GATEWAY_BOT)
                email_gateway_bot.is_api_super_user = True
                email_gateway_bot.save()

                # To keep the messages.json fixtures file for the test
                # suite fast, don't add these users and subscriptions
                # when running populate_db for the test suite

                zulip_stream_dict = {
                    "devel": {"description": "For developing", "invite_only": False},
                    "all": {"description": "For everything", "invite_only": False},
                    "announce": {"description": "For announcements", "invite_only": False},
                    "design": {"description": "For design", "invite_only": False},
                    "support": {"description": "For support", "invite_only": False},
                    "social": {"description": "For socializing", "invite_only": False},
                    "test": {"description": "For testing", "invite_only": False},
                    "errors": {"description": "For errors", "invite_only": False},
                    "sales": {"description": "For sales discussion", "invite_only": False}
                }  # type: Dict[Text, Dict[Text, Any]]

                # Calculate the maximum number of digits in any extra stream's
                # number, since a stream with name "Extra Stream 3" could show
                # up after "Extra Stream 29". (Used later to pad numbers with
                # 0s).
                maximum_digits = len(str(options['extra_streams'] - 1))

                for i in range(options['extra_streams']):
                    # Pad the number with 0s based on `maximum_digits`.
                    number_str = str(i).zfill(maximum_digits)

                    extra_stream_name = 'Extra Stream ' + number_str

                    zulip_stream_dict[extra_stream_name] = {
                        "description": "Auto-generated extra stream.",
                        "invite_only": False,
                    }

                bulk_create_streams(zulip_realm, zulip_stream_dict)
                # Now that we've created the notifications stream, configure it properly.
                zulip_realm.notifications_stream = get_stream("announce", zulip_realm)
                zulip_realm.save(update_fields=['notifications_stream'])

                # Add a few default streams
                for default_stream_name in ["design", "devel", "social", "support"]:
                    DefaultStream.objects.create(realm=zulip_realm,
                                                 stream=get_stream(default_stream_name, zulip_realm))

                # Now subscribe everyone to these streams
                subscriptions_to_add = []
                event_time = timezone_now()
                all_subscription_logs = []
                profiles = UserProfile.objects.select_related().filter(realm=zulip_realm)
                for i, stream_name in enumerate(zulip_stream_dict):
                    stream = Stream.objects.get(name=stream_name, realm=zulip_realm)
                    recipient = Recipient.objects.get(type=Recipient.STREAM, type_id=stream.id)
                    for profile in profiles:
                        # Subscribe to some streams.
                        s = Subscription(
                            recipient=recipient,
                            user_profile=profile,
                            color=STREAM_ASSIGNMENT_COLORS[i % len(STREAM_ASSIGNMENT_COLORS)])
                        subscriptions_to_add.append(s)

                        log = RealmAuditLog(realm=profile.realm,
                                            modified_user=profile,
                                            modified_stream=stream,
                                            event_last_message_id=0,
                                            event_type='subscription_created',
                                            event_time=event_time)
                        all_subscription_logs.append(log)
                Subscription.objects.bulk_create(subscriptions_to_add)
                RealmAuditLog.objects.bulk_create(all_subscription_logs)

                # These bots are not needed by the test suite
                internal_zulip_users_nosubs = [
                    ("Zulip Commit Bot", "*****@*****.**"),
                    ("Zulip Trac Bot", "*****@*****.**"),
                    ("Zulip Nagios Bot", "*****@*****.**"),
                ]
                create_users(zulip_realm, internal_zulip_users_nosubs, bot_type=UserProfile.DEFAULT_BOT)

            zulip_cross_realm_bots = [
                ("Zulip Feedback Bot", "*****@*****.**"),
            ]
            create_users(zulip_realm, zulip_cross_realm_bots, bot_type=UserProfile.DEFAULT_BOT)

            # Mark all messages as read
            UserMessage.objects.all().update(flags=UserMessage.flags.read)

            if not options["test_suite"]:
                # Update pointer of each user to point to the last message in their
                # UserMessage rows with sender_id=user_profile_id.
                users = list(UserMessage.objects.filter(
                    message__sender_id=F('user_profile_id')).values(
                    'user_profile_id').annotate(pointer=Max('message_id')))
                for user in users:
                    UserProfile.objects.filter(id=user['user_profile_id']).update(
                        pointer=user['pointer'])

            create_user_groups()
            self.stdout.write("Successfully populated test database.\n")