email-mirror: Remove HTTP interface.

This commit is contained in:
Alex Vandiver
2025-01-22 15:54:41 +00:00
committed by Tim Abbott
parent a6a5fc246a
commit c6e0f0b436
15 changed files with 18 additions and 656 deletions

View File

@@ -18,7 +18,7 @@ that address will be delivered into the channel.
There are two ways to configure Zulip's email gateway:
1. Local delivery (recommended): A postfix server runs on the Zulip
1. Local delivery (recommended): A server runs on the Zulip
server and passes the emails directly to Zulip.
1. Polling: A cron job running on the Zulip server checks an IMAP
inbox (`username@example.com`) every minute for new emails.
@@ -68,27 +68,13 @@ using an [HTTP reverse proxy][reverse-proxy]).
1. Log in to your Zulip server; the remaining steps all happen there.
1. Add `, zulip::postfix_localmail` to `puppet_classes` in
1. Add `, zulip::local_mailserver` to `puppet_classes` in
`/etc/zulip/zulip.conf`. A typical value after this change is:
```ini
puppet_classes = zulip::profile::standalone, zulip::postfix_localmail
puppet_classes = zulip::profile::standalone, zulip::local_mailserver
```
1. If `hostname.example.com` is different from
`emaildomain.example.com`, add a section to `/etc/zulip/zulip.conf`
on your Zulip server like this:
```ini
[postfix]
mailname = emaildomain.example.com
```
This tells postfix to expect to receive emails at addresses ending with
`@emaildomain.example.com`, overriding the default of
`@hostname.example.com`. It will also identify itself as
`emaildomain.example.com` on any outgoing emails it sends.
1. Run `/home/zulip/deployments/current/scripts/zulip-puppet-apply`
(and answer `y`) to apply your new `/etc/zulip/zulip.conf`
configuration to your Zulip server.

View File

@@ -221,14 +221,6 @@ the message to fail to send.
Set to the port number for the KaTeX server; defaults to port 9700.
### `[postfix]`
#### `mailname`
The hostname that [Postfix should be configured to receive mail
at](email-gateway.md#local-delivery-setup), as well as identify itself as for
outgoing email.
### `[postgresql]`
#### `effective_io_concurrency`

View File

@@ -1,9 +0,0 @@
# This is the list of email addresses that are accepted via SMTP;
# these consist of only the addresses in `virtual`, as well as the
# RFC822-specified postmaster.
/\+.*@/ OK
/\..*@/ OK
/^mm.{32}@/ OK
/^postmaster@/ OK

View File

@@ -1,114 +0,0 @@
#
# Postfix master process configuration file. For details on the format
# of the file, see the master(5) manual page (command: "man 5 master").
#
# Do not forget to execute "postfix reload" after editing this file.
#
# ==========================================================================
# service type private unpriv chroot wakeup maxproc command + args
# (yes) (yes) (yes) (never) (100)
# ==========================================================================
smtp inet n - - - - smtpd
#smtp inet n - - - 1 postscreen
#smtpd pass - - - - - smtpd
#dnsblog unix - - - - 0 dnsblog
#tlsproxy unix - - - - 0 tlsproxy
#submission inet n - - - - smtpd
# -o syslog_name=postfix/submission
# -o smtpd_tls_security_level=encrypt
# -o smtpd_sasl_auth_enable=yes
# -o smtpd_client_restrictions=permit_sasl_authenticated,reject
# -o milter_macro_daemon_name=ORIGINATING
#smtps inet n - - - - smtpd
# -o syslog_name=postfix/smtps
# -o smtpd_tls_wrappermode=yes
# -o smtpd_sasl_auth_enable=yes
# -o smtpd_client_restrictions=permit_sasl_authenticated,reject
# -o milter_macro_daemon_name=ORIGINATING
#628 inet n - - - - qmqpd
pickup fifo n - - 60 1 pickup
cleanup unix n - - - 0 cleanup
qmgr fifo n - n 300 1 qmgr
#qmgr fifo n - n 300 1 oqmgr
tlsmgr unix - - - 1000? 1 tlsmgr
rewrite unix - - - - - trivial-rewrite
bounce unix - - - - 0 bounce
defer unix - - - - 0 bounce
trace unix - - - - 0 bounce
verify unix - - - - 1 verify
flush unix n - - 1000? 0 flush
proxymap unix - - n - - proxymap
proxywrite unix - - n - 1 proxymap
smtp unix - - - - - smtp
relay unix - - - - - smtp
# -o smtp_helo_timeout=5 -o smtp_connect_timeout=5
showq unix n - - - - showq
error unix - - - - - error
retry unix - - - - - error
discard unix - - - - - discard
local unix - n n - - local
virtual unix - n n - - virtual
lmtp unix - - - - - lmtp
anvil unix - - - - 1 anvil
scache unix - - - - 1 scache
#
# ====================================================================
# Interfaces to non-Postfix software. Be sure to examine the manual
# pages of the non-Postfix software to find out what options it wants.
#
# Many of the following services use the Postfix pipe(8) delivery
# agent. See the pipe(8) man page for information about ${recipient}
# and other message envelope options.
# ====================================================================
#
# maildrop. See the Postfix MAILDROP_README file for details.
# Also specify in main.cf: maildrop_destination_recipient_limit=1
#
maildrop unix - n n - - pipe
flags=DRhu user=vmail argv=/usr/bin/maildrop -d ${recipient}
#
# ====================================================================
#
# Recent Cyrus versions can use the existing "lmtp" master.cf entry.
#
# Specify in cyrus.conf:
# lmtp cmd="lmtpd -a" listen="localhost:lmtp" proto=tcp4
#
# Specify in main.cf one or more of the following:
# mailbox_transport = lmtp:inet:localhost
# virtual_transport = lmtp:inet:localhost
#
# ====================================================================
#
# Cyrus 2.1.5 (Amos Gouaux)
# Also specify in main.cf: cyrus_destination_recipient_limit=1
#
#cyrus unix - n n - - pipe
# user=cyrus argv=/cyrus/bin/deliver -e -r ${sender} -m ${extension} ${user}
#
# ====================================================================
# Old example of delivery via Cyrus.
#
#old-cyrus unix - n n - - pipe
# flags=R user=cyrus argv=/cyrus/bin/deliver -e -m ${extension} ${user}
#
# ====================================================================
#
# See the Postfix UUCP_README file for configuration details.
#
uucp unix - n n - - pipe
flags=Fqhu user=uucp argv=uux -r -n -z -a$sender - $nexthop!rmail ($recipient)
#
# Other external delivery methods.
#
ifmail unix - n n - - pipe
flags=F user=ftn argv=/usr/lib/ifmail/ifmail -r $nexthop ($recipient)
bsmtp unix - n n - - pipe
flags=Fq. user=bsmtp argv=/usr/lib/bsmtp/bsmtp -t$nexthop -f$sender $recipient
scalemail-backend unix - n n - 2 pipe
flags=R user=scalemail argv=/usr/lib/scalemail/bin/scalemail-store ${nexthop} ${user} ${extension}
mailman unix - n n - - pipe
flags=FR user=list argv=/usr/lib/mailman/bin/postfix-to-mailman.py
${nexthop} ${user}
zulip unix - n n - - pipe
flags= user=zulip argv=/home/zulip/deployments/current/scripts/lib/email-mirror-postfix -r ${original_recipient}

View File

@@ -1 +0,0 @@
zulip@localhost zulip:

View File

@@ -1,81 +0,0 @@
class zulip::postfix_localmail {
include zulip::snakeoil
$postfix_packages = [ 'postfix', ]
$fqdn = $facts['networking']['fqdn']
if $fqdn == '' {
fail('Your system does not have a fully-qualified domain name defined. See hostname(1).')
}
$postfix_mailname = zulipconf('postfix', 'mailname', $fqdn)
package { $postfix_packages:
ensure => installed,
require => File['/etc/mailname'],
}
service { 'postfix':
require => Exec['generate-default-snakeoil'],
}
file {'/etc/mailname':
ensure => file,
mode => '0644',
owner => root,
group => root,
content => $postfix_mailname,
}
file {'/etc/postfix/main.cf':
ensure => file,
mode => '0644',
owner => root,
group => root,
content => template('zulip/postfix/main.cf.erb'),
require => Package[postfix],
notify => Service['postfix'],
}
file {'/etc/postfix/master.cf':
ensure => file,
mode => '0644',
owner => root,
group => root,
source => 'puppet:///modules/zulip/postfix/master.cf',
require => Package[postfix],
notify => Service['postfix'],
}
file {'/etc/postfix/virtual':
ensure => file,
mode => '0644',
owner => root,
group => root,
content => template('zulip/postfix/virtual.erb'),
require => Package[postfix],
notify => Service['postfix'],
}
file {'/etc/postfix/transport':
ensure => file,
mode => '0644',
owner => root,
group => root,
source => 'puppet:///modules/zulip/postfix/transport',
require => Package[postfix],
}
exec {'postmap /etc/postfix/transport':
subscribe => File['/etc/postfix/transport'],
refreshonly => true,
require => [
File['/etc/postfix/main.cf'],
Package[postfix],
],
}
file {'/etc/postfix/access':
ensure => file,
mode => '0644',
owner => root,
group => root,
source => 'puppet:///modules/zulip/postfix/access',
require => Package[postfix],
}
}

View File

@@ -1,9 +1,9 @@
class zulip::snakeoil {
zulip::safepackage { 'ssl-cert': ensure => installed }
# We use the snakeoil certificate for PostgreSQL and Postfix; some VMs
# install the `ssl-cert` package but (reasonably) don't build the
# snakeoil certs into the image; build them as needed.
# We use the snakeoil certificate for PostgreSQL; some VMs install
# the `ssl-cert` package but (reasonably) don't build the snakeoil
# certs into the image; build them as needed.
exec { 'generate-default-snakeoil':
require => Package['ssl-cert'],
creates => '/etc/ssl/certs/ssl-cert-snakeoil.pem',

View File

@@ -1,43 +0,0 @@
# This file is managed by Puppet; local changes will be overridden.
smtpd_banner = $myhostname ESMTP $mail_name (Zulip)
biff = no
# appending .domain is the MUA's job.
append_dot_mydomain = no
readme_directory = no
# TLS parameters
smtpd_tls_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem
smtpd_tls_key_file=/etc/ssl/private/ssl-cert-snakeoil.key
smtpd_use_tls=yes
smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache
smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated reject_unauth_destination
smtpd_recipient_restrictions = permit_mynetworks, check_recipient_access regexp:/etc/postfix/access, reject
myhostname = <%= @fqdn %>
alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases
transport_maps = hash:/etc/postfix/transport
myorigin = /etc/mailname
mydestination = localhost, <%= @postfix_mailname %>
relayhost =
mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128
mailbox_size_limit = 0
recipient_delimiter = +
inet_interfaces = all
inet_protocols = all
# This enables us to do a catchall
virtual_alias_maps = regexp:/etc/postfix/virtual
# Needed to accept mail with percents without trying to interpret as
# percent-style routing.
allow_percent_hack = no
allow_untrusted_routing = yes
strict_rfc821_envelopes = no
# Ingest messages up to 25MB
message_size_limit = 26214400

View File

@@ -1,7 +0,0 @@
if /@<%= Regexp.escape(@postfix_mailname) %>\.?$/
# Changes to this list require a corresponding change to `access` as
# well.
/\+.*@/ zulip@localhost
/\..*@/ zulip@localhost
/^mm.{32}@/ zulip@localhost
endif

View File

@@ -1,169 +0,0 @@
#!/usr/bin/env python3
"""Postfix implementation of the incoming email gateway's helper for
forwarding emails into Zulip.
https://zulip.readthedocs.io/en/latest/production/email-gateway.html
The email gateway supports two major modes of operation: An email
server (using Postfix) where the email address configured in
EMAIL_GATEWAY_PATTERN delivers emails directly to Zulip (this) or a
cron job that connects to an IMAP inbox (which receives the emails)
periodically.
Zulip's Puppet configuration takes care of configuring Postfix to
execute this script when emails are received by Postfix, piping the
email content via standard input (and the destination email address in
the ORIGINAL_RECIPIENT environment variable).
In Postfix, you can express that via an /etc/aliases entry like this:
|/home/zulip/deployments/current/scripts/lib/email-mirror-postfix -r ${original_recipient}
To manage DoS issues, this script does very little work (just sending
an HTTP request to queue the message for processing) to avoid
importing expensive libraries.
Also you can use optional keys to configure the script and change default values:
-s SHARED_SECRET For adding shared secret key if it is not contained in
"/etc/zulip/zulip-secrets.conf". This key is used to authenticate
the HTTP requests made by this tool.
-d HOST Destination Zulip host for email uploading. Address must contain type of
HTTP protocol, e.g. "https://example.com". Default value: "https://127.0.0.1".
-u URL Destination relative for email uploading. Default value: "/api/internal/email_mirror_message".
-n Disable checking ssl certificate. This option is used for
self-signed certificates. Default value: False.
-t Disable sending request to the Zulip server. Default value: False.
"""
import argparse
import base64
import json
import os
import posix
import ssl
import sys
from configparser import RawConfigParser
from typing import NoReturn
from urllib.error import HTTPError
from urllib.parse import urlencode, urljoin, urlsplit
from urllib.request import Request, urlopen
sys.path.append(os.path.join(os.path.dirname(__file__), "..", ".."))
from scripts.lib.zulip_tools import get_config, get_config_file
parser = argparse.ArgumentParser()
parser.add_argument("-r", "--recipient", default="", help="Original recipient.")
parser.add_argument("-s", "--shared-secret", default="", help="Secret access key.")
parser.add_argument(
"-d",
"--dst-host",
dest="host",
default="127.0.0.1",
help="Destination server address for uploading email from email mirror. "
"Address must contain an HTTP protocol. Otherwise, default value is assumed "
"based on the http_only setting.",
)
parser.add_argument(
"-u",
"--dst-url",
dest="url",
default="/api/internal/email_mirror_message",
help="Destination relative URL for uploading email from email mirror.",
)
parser.add_argument(
"-n",
"--not-verify-ssl",
dest="verify_ssl",
action="store_false",
help="Disable ssl certificate verifying for self-signed certificates",
)
parser.add_argument("-t", "--test", action="store_true", help="Test mode.")
options = parser.parse_args()
MAX_ALLOWED_PAYLOAD = 25 * 1024 * 1024
def process_response_error(e: HTTPError) -> NoReturn:
if e.code == 400:
response_content = e.read()
response_data = json.loads(response_content)
print(response_data["msg"])
sys.exit(posix.EX_NOUSER)
else:
print("4.4.2 Connection dropped: Internal server error.")
sys.exit(1)
def send_email_mirror(
rcpt_to: str,
shared_secret: str,
host: str,
url: str,
test: bool,
verify_ssl: bool,
) -> None:
if not rcpt_to:
print("5.1.1 Bad destination mailbox address: No missed message email address.")
sys.exit(posix.EX_NOUSER)
msg_bytes = sys.stdin.buffer.read(MAX_ALLOWED_PAYLOAD + 1)
if len(msg_bytes) > MAX_ALLOWED_PAYLOAD:
# We're not at EOF, reject large mail.
print("5.3.4 Message too big for system: Max size is 25MiB")
sys.exit(posix.EX_DATAERR)
secrets_file = RawConfigParser()
secrets_file.read("/etc/zulip/zulip-secrets.conf")
if not shared_secret:
shared_secret = secrets_file.get("secrets", "shared_secret")
if test:
return
if not urlsplit(host).scheme:
config_file = get_config_file()
http_only = get_config(config_file, "application_server", "http_only", False)
scheme = "http://" if http_only else "https://"
host = scheme + host
if host == "https://127.0.0.1":
# Don't try to verify SSL when posting to 127.0.0.1; it won't
# work, and connections to 127.0.0.1 are secure without SSL.
verify_ssl = False
# Because this script is run from postfix, it does not have any
# http proxy environment variables set which might interfere with
# access to localhost.
context = None
if not verify_ssl:
context = ssl.create_default_context()
context.check_hostname = False
context.verify_mode = ssl.CERT_NONE
data = {
"rcpt_to": rcpt_to,
"msg_base64": base64.b64encode(msg_bytes).decode(),
"secret": shared_secret,
}
req = Request(url=urljoin(host, url), data=urlencode(data).encode())
try:
urlopen(req, context=context)
except HTTPError as err:
process_response_error(err)
recipient = str(os.environ.get("ORIGINAL_RECIPIENT", options.recipient))
send_email_mirror(
recipient, options.shared_secret, options.host, options.url, options.test, options.verify_ssl
)

View File

@@ -25,7 +25,6 @@ from zerver.lib.email_mirror_helpers import (
from zerver.lib.email_notifications import convert_html_to_markdown
from zerver.lib.exceptions import JsonableError, RateLimitedError
from zerver.lib.message import normalize_body, truncate_content, truncate_topic
from zerver.lib.queue import queue_json_publish_rollback_unsafe
from zerver.lib.rate_limiter import RateLimitedObject
from zerver.lib.send_email import FromAddress
from zerver.lib.streams import access_stream_for_send_message
@@ -531,28 +530,7 @@ def validate_to_address(rcpt_to: str) -> None:
decode_stream_email_address(rcpt_to)
def mirror_email_message(rcpt_to: str, msg_base64: str) -> dict[str, str]:
try:
validate_to_address(rcpt_to)
except ZulipEmailForwardError as e:
return {
"status": "error",
"msg": f"5.1.1 Bad destination mailbox address: {e}",
}
queue_json_publish_rollback_unsafe(
"email_mirror",
{
"rcpt_to": rcpt_to,
"msg_base64": msg_base64,
},
)
return {"status": "success"}
# Email mirror rate limiter code:
class RateLimitedRealmMirror(RateLimitedObject):
def __init__(self, realm: Realm) -> None:
self.realm = realm

View File

@@ -10,9 +10,10 @@ from django.conf import settings
from django.core.management.base import CommandError, CommandParser
from typing_extensions import override
from zerver.lib.email_mirror import mirror_email_message
from zerver.lib.email_mirror import validate_to_address
from zerver.lib.email_mirror_helpers import encode_email_address, get_channel_email_token
from zerver.lib.management import ZulipBaseCommand
from zerver.lib.queue import queue_json_publish_rollback_unsafe
from zerver.models import Realm, UserProfile
from zerver.models.realms import get_realm
from zerver.models.streams import get_stream
@@ -99,9 +100,15 @@ Example:
)
self._prepare_message(message, realm, stream, creator, sender)
mirror_email_message(
message["To"].addresses[0].addr_spec,
base64.b64encode(message.as_bytes()).decode(),
rcpt_to = message["To"].addresses[0].addr_spec
validate_to_address(rcpt_to)
queue_json_publish_rollback_unsafe(
"email_mirror",
{
"rcpt_to": rcpt_to,
"msg_base64": base64.b64encode(message.as_bytes()).decode(),
},
)
def _does_fixture_path_exist(self, fixture_path: str) -> bool:

View File

@@ -3,14 +3,11 @@ import base64
import email.parser
import email.policy
import os
import subprocess
from collections.abc import Callable, Mapping
from contextlib import suppress
from datetime import timedelta
from email.headerregistry import Address
from email.message import EmailMessage, MIMEPart
from smtplib import SMTPException, SMTPSenderRefused
from typing import TYPE_CHECKING, Any
from unittest import mock
import orjson
@@ -52,17 +49,13 @@ from zerver.lib.email_notifications import convert_html_to_markdown
from zerver.lib.send_email import FromAddress
from zerver.lib.streams import ensure_stream
from zerver.lib.test_classes import ZulipTestCase
from zerver.lib.test_helpers import mock_queue_publish, most_recent_message, most_recent_usermessage
from zerver.lib.test_helpers import most_recent_message, most_recent_usermessage
from zerver.models import Attachment, Recipient, Stream, UserProfile
from zerver.models.groups import NamedUserGroup, SystemGroups
from zerver.models.messages import Message
from zerver.models.realms import get_realm
from zerver.models.streams import get_stream
from zerver.models.users import get_system_bot
from zerver.worker.email_mirror import MirrorWorker
if TYPE_CHECKING:
from django.test.client import _MonkeyPatchedWSGIResponse as TestHttpResponse
logger_name = "zerver.lib.email_mirror"
@@ -1725,153 +1718,6 @@ class TestReplyExtraction(ZulipTestCase):
self.assertEqual(message.content, convert_html_to_markdown(html))
class TestScriptMTA(ZulipTestCase):
def test_success(self) -> None:
script = os.path.join(os.path.dirname(__file__), "../../scripts/lib/email-mirror-postfix")
user_profile = self.example_user("hamlet")
sender = self.example_email("hamlet")
stream = get_stream("Denmark", get_realm("zulip"))
email_token = get_channel_email_token(stream, creator=user_profile, sender=user_profile)
stream_to_address = encode_email_address(stream.name, email_token)
mail_template = self.fixture_data("simple.txt", type="email")
mail = mail_template.format(stream_to_address=stream_to_address, sender=sender)
subprocess.run(
[script, "-r", stream_to_address, "-s", settings.SHARED_SECRET, "-t"],
input=mail,
check=True,
text=True,
)
def test_error_no_recipient(self) -> None:
script = os.path.join(os.path.dirname(__file__), "../../scripts/lib/email-mirror-postfix")
user_profile = self.example_user("hamlet")
sender = self.example_email("hamlet")
stream = get_stream("Denmark", get_realm("zulip"))
email_token = get_channel_email_token(stream, creator=user_profile, sender=user_profile)
stream_to_address = encode_email_address(stream.name, email_token)
mail_template = self.fixture_data("simple.txt", type="email")
mail = mail_template.format(stream_to_address=stream_to_address, sender=sender)
p = subprocess.run(
[script, "-s", settings.SHARED_SECRET, "-t"],
input=mail,
stdout=subprocess.PIPE,
text=True,
check=False,
)
self.assertEqual(
p.stdout,
"5.1.1 Bad destination mailbox address: No missed message email address.\n",
)
self.assertEqual(p.returncode, 67)
class TestEmailMirrorTornadoView(ZulipTestCase):
def send_private_message(self) -> str:
self.login("othello")
cordelia = self.example_user("cordelia")
iago = self.example_user("iago")
result = self.client_post(
"/json/messages",
{
"type": "private",
"content": "test_receive_missed_message_email_messages",
"to": orjson.dumps([cordelia.id, iago.id]).decode(),
},
)
self.assert_json_success(result)
user_profile = self.example_user("cordelia")
user_message = most_recent_usermessage(user_profile)
return create_missed_message_address(user_profile, user_message.message)
def send_offline_message(self, to_address: str, sender: UserProfile) -> "TestHttpResponse":
mail_template = self.fixture_data("simple.txt", type="email")
mail = mail_template.format(stream_to_address=to_address, sender=sender.delivery_email)
msg_base64 = base64.b64encode(mail.encode()).decode()
def check_queue_json_publish(
queue_name: str,
event: Mapping[str, Any],
processor: Callable[[Any], None] | None = None,
) -> None:
self.assertEqual(queue_name, "email_mirror")
self.assertEqual(event, {"rcpt_to": to_address, "msg_base64": msg_base64})
MirrorWorker().consume(event)
self.assertEqual(
self.get_last_message().content,
"This is a plain-text message for testing Zulip.",
)
post_data = {
"rcpt_to": to_address,
"msg_base64": msg_base64,
"secret": settings.SHARED_SECRET,
}
with mock_queue_publish("zerver.lib.email_mirror.queue_json_publish_rollback_unsafe") as m:
m.side_effect = check_queue_json_publish
return self.client_post("/api/internal/email_mirror_message", post_data)
def test_success_stream(self) -> None:
stream = get_stream("Denmark", get_realm("zulip"))
user_profile = self.example_user("hamlet")
email_token = get_channel_email_token(stream, creator=user_profile, sender=user_profile)
stream_to_address = encode_email_address(stream.name, email_token)
result = self.send_offline_message(stream_to_address, self.example_user("hamlet"))
self.assert_json_success(result)
def test_error_to_stream_with_wrong_address(self) -> None:
stream = get_stream("Denmark", get_realm("zulip"))
user_profile = self.example_user("hamlet")
email_token = get_channel_email_token(stream, creator=user_profile, sender=user_profile)
stream_to_address = encode_email_address(stream.name, email_token)
# get the email_token:
token = decode_email_address(stream_to_address)[0]
stream_to_address = stream_to_address.replace(token, "Wrong_token")
result = self.send_offline_message(stream_to_address, self.example_user("hamlet"))
self.assert_json_error(
result,
"5.1.1 Bad destination mailbox address: "
"Bad stream token from email recipient " + stream_to_address,
)
def test_success_to_stream_with_good_token_wrong_stream_name(self) -> None:
stream = get_stream("Denmark", get_realm("zulip"))
user_profile = self.example_user("hamlet")
email_token = get_channel_email_token(stream, creator=user_profile, sender=user_profile)
stream_to_address = encode_email_address(stream.name, email_token)
stream_to_address = stream_to_address.replace("denmark", "Wrong_name")
result = self.send_offline_message(stream_to_address, self.example_user("hamlet"))
self.assert_json_success(result)
def test_success_to_private(self) -> None:
mm_address = self.send_private_message()
result = self.send_offline_message(mm_address, self.example_user("cordelia"))
self.assert_json_success(result)
def test_using_mm_address_multiple_times(self) -> None:
mm_address = self.send_private_message()
# there is no longer a usage limit. Ensure we can send multiple times.
for i in range(5):
result = self.send_offline_message(mm_address, self.example_user("cordelia"))
self.assert_json_success(result)
def test_wrong_missed_email_private_message(self) -> None:
self.send_private_message()
mm_address = "mm" + ("x" * 32) + "@testserver"
result = self.send_offline_message(mm_address, self.example_user("cordelia"))
self.assert_json_error(
result,
"5.1.1 Bad destination mailbox address: Zulip notification reply address is invalid.",
)
class TestStreamEmailMessagesSubjectStripping(ZulipTestCase):
def test_process_message_strips_subject(self) -> None:
user_profile = self.example_user("hamlet")

View File

@@ -1,21 +0,0 @@
from django.http import HttpRequest, HttpResponse
from zerver.decorator import internal_api_view
from zerver.lib.email_mirror import mirror_email_message
from zerver.lib.exceptions import JsonableError
from zerver.lib.response import json_success
from zerver.lib.typed_endpoint import typed_endpoint
@internal_api_view(False)
@typed_endpoint
def email_mirror_message(
request: HttpRequest,
*,
rcpt_to: str,
msg_base64: str,
) -> HttpResponse:
result = mirror_email_message(rcpt_to, msg_base64)
if result["status"] == "error":
raise JsonableError(result["msg"])
return json_success(request)

View File

@@ -58,7 +58,6 @@ from zerver.views.custom_profile_fields import (
from zerver.views.digest import digest_page
from zerver.views.documentation import IntegrationView, MarkdownDirectoryView, integration_doc
from zerver.views.drafts import create_drafts, delete_draft, edit_draft, fetch_drafts
from zerver.views.email_mirror import email_mirror_message
from zerver.views.events_register import events_register_backend
from zerver.views.health import health
from zerver.views.home import accounts_accept_terms, desktop_home, doc_permalinks_view, home
@@ -808,7 +807,6 @@ for app_name in settings.EXTRA_INSTALLED_APPS:
# Used internally for communication between command-line, tusd, Django,
# and Tornado processes
urls += [
path("api/internal/email_mirror_message", email_mirror_message),
path("api/internal/notify_tornado", notify),
path("api/internal/tusd", handle_tusd_hook),
path("api/internal/web_reload_clients", web_reload_clients),