#37162 closed Cleanup/optimization (fixed)
Docs ContactForm example demonstrates poor practices
| Reported by: | Mike Edmunds | Owned by: | Mike Edmunds |
|---|---|---|---|
| Component: | Documentation | Version: | 6.0 |
| Severity: | Normal | Keywords: | |
| Cc: | Triage Stage: | Ready for checkin | |
| Has patch: | yes | Needs documentation: | no |
| Needs tests: | no | Patch needs improvement: | no |
| Easy pickings: | no | UI/UX: | no |
Description
The ContactForm used as a running example starting in the More on fields forms topic demonstrates poor practices for implementing a contact form that sends email:
- It uses the contact's email address (
sender) as the message'sfrom_email. At best, this won't work (the message will be rejected by the outgoing server or silently ignored as spam at the receiving end). At worst, it creates vulnerabilities. - It passes through
subjectandmessagewithout any indication they originated in a web contact form. This can allow high-fidelity phishing attacks against the organization deploying the form (particularly when combined with an arbitrary sender address).
I realize the example needs to be kept simple, but Django's docs probably shouldn't be illustrating insecure approaches.
I'd suggest changing the current:
from django.core.mail import send_mail if form.is_valid(): subject = form.cleaned_data["subject"] message = form.cleaned_data["message"] sender = form.cleaned_data["sender"] cc_myself = form.cleaned_data["cc_myself"] recipients = ["info@example.com"] if cc_myself: recipients.append(sender) send_mail(subject, message, sender, recipients) return HttpResponseRedirect("/thanks/")
to something like (also renaming the sender form field to email throughout the page):
from django.core.mail import EmailMessage if form.is_valid(): subject = form.cleaned_data["subject"] message = form.cleaned_data["message"] email = form.cleaned_data["email"] cc_myself = form.cleaned_data["cc_myself"] # Send the message, directing replies to the contact's email. EmailMessage( subject=f"[via contact form] {subject}", body=f"Contact email: {email}\n\n{message}", to=["info@example.com"], cc=[email] if cc_myself else None, reply_to=[email], ).send() return HttpResponseRedirect("/thanks/")
Note that the cc handling there (and in the original) is also not ideal. Maybe we could come up with some other reason to include a boolean field in the form? ("Urgent"? "No reply necessary"?)
Or if something like that seems too complicated, maybe we should consider rewriting the page to use something other than a contact form.
[Noticed this while I was working on #34753. I was considering removing the contact-form-like example from the Email header injection section and replacing it with a ref to this actual-contact-form example.]
Change History (10)
comment:1 by , 12 days ago
| Triage Stage: | Unreviewed → Accepted |
|---|
comment:2 by , 12 days ago
| Has patch: | set |
|---|
comment:3 by , 12 days ago
A version of that same ContactForm also appears in djangoproject.com/start/ under Intro to Django > Forms (but without the email sending function).
We may want to update it in djangoproject.com:djangoproject/templates/start.html.
comment:4 by , 3 days ago
| Patch needs improvement: | set |
|---|
Setting as patch needs improvement to ensure other related references of the ContactForm are updated to match. On the djangoproject.com start page from comment:3, my recommendation is to remove the Python code for the form altogether and just leave the actual form definition to be found once the user follows the "read more" link.
comment:5 by , 2 days ago
| Patch needs improvement: | unset |
|---|
comment:6 by , 2 days ago
Opened separate issue https://github.com/django/djangoproject.com/issues/2684 for djangoproject.com example.
comment:7 by , 27 hours ago
| Triage Stage: | Accepted → Ready for checkin |
|---|
https://github.com/django/django/pull/21522
I ended up replacing the
cc_myselffield with anurgentoption.