invite emails: Ensure user-controlled input is always in links.

Popular email clients like Gmail will automatically linkify link-like
content present in an HTML email they receive, even if it doesn't have
links in it.  This made it possible to include what in Gmail will be a
user-controlled link in invitation emails that Zulip sends, which a
spammer/phisher could try to take advantage of to send really bad spam
(the limitation of having the rest of the invitation email HTML there
makes it hard to do something compelling here).

We close this opportunity by structuring our emails to always show the
user's name inside an existing link, so that Gmail won't do new
linkification, and add a test to help ensure we don't remove this
structure in a future design change.

Co-authored-by: Anders Kaseorg <andersk@mit.edu>
This commit is contained in:
Tim Abbott 2020-05-08 14:02:41 -07:00 committed by GitHub
parent 6364d27ed5
commit a920544bc3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 38 additions and 4 deletions

View File

@ -8,7 +8,7 @@
<p>{{ _("Hi there,") }}</p>
<p>
{% trans %}{{ referrer_full_name }} (<a href="mailto:{{ referrer_email }}">{{ referrer_email }}</a>) wants you to join them on Zulip &mdash; the team communication tool designed for productivity.{% endtrans %}
{% trans %}<a href="mailto:{{ referrer_email }}">{{ referrer_full_name }} ({{ referrer_email }})</a> wants you to join them on Zulip &mdash; the team communication tool designed for productivity.{% endtrans %}
</p>
<p>
{{ _("To get started, click the button below.") }}

View File

@ -7,7 +7,7 @@
{% block content %}
<p>{{ _("Hi again,") }}</p>
<p>{% trans %}This is a friendly reminder that {{ referrer_name }} (<a href="mailto:{{ referrer_email }}">{{ referrer_email }}</a>) wants you to join them on Zulip &mdash; the team communication tool designed for productivity.{% endtrans %}</p>
<p>{% trans %}This is a friendly reminder that <a href="mailto:{{ referrer_email }}">{{ referrer_name }} ({{ referrer_email }})</a> wants you to join them on Zulip &mdash; the team communication tool designed for productivity.{% endtrans %}</p>
<p>
{{ _("To get started, click the button below.") }}
@ -19,6 +19,10 @@
</p>
<p>
{% trans %}This invitation expires in two days. If the invitation expires, you'll need to ask {{ referrer_name }} for another one.{% endtrans %}
{% trans %}
This invitation expires in two days. If the invitation expires,
you'll need to ask <a href="mailto:{{ referrer_email }}">{{ referrer_name }}</a>
for another one.
{% endtrans %}
</p>
{% endblock %}

View File

@ -44,4 +44,4 @@ API_FEATURE_LEVEL = 3
# historical commits sharing the same major version, in which case a
# minor version bump suffices.
PROVISION_VERSION = '82.0'
PROVISION_VERSION = '82.1'

View File

@ -47,6 +47,7 @@ from zerver.lib.send_email import send_future_email, FromAddress, \
from zerver.lib.initial_password import initial_password
from zerver.lib.actions import (
do_get_user_invites,
do_change_full_name,
do_deactivate_realm,
do_deactivate_user,
do_set_realm_property,
@ -1192,6 +1193,35 @@ earl-test@zulip.com""", ["Denmark"]))
)
self.check_sent_emails([])
def normalize_string(self, s: str) -> str:
s = s.strip()
return re.sub(r'\s+', ' ', s)
def test_invite_links_in_name(self) -> None:
"""
If you invite an address already using Zulip, no invitation is sent.
"""
hamlet = self.example_user("hamlet")
self.login_user(hamlet)
# Test we properly handle links in user full names
do_change_full_name(hamlet, "</a> https://www.google.com", hamlet)
result = self.invite('newuser@zulip.com', ["Denmark"])
self.assert_json_success(result)
self.check_sent_emails(['newuser@zulip.com'])
from django.core.mail import outbox
body = self.normalize_string(outbox[0].alternatives[0][0])
# Verify that one can't get Zulip to send invitation emails
# that third-party products will linkify using the full_name
# field, because we've included that field inside the mailto:
# link for the sender.
self.assertIn('<a href="mailto:hamlet@zulip.com" style="color:#46aa8f; text-decoration:underline">&lt;/a&gt; https://www.google.com (hamlet@zulip.com</a>) wants', body)
# TODO: Ideally, this test would also test the Invitation
# Reminder email generated, but the test setup for that is
# annoying.
def test_invite_some_existing_some_new(self) -> None:
"""
If you invite a mix of already existing and new users, invitations are