Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use WTForm for source interface submission form #5226

Merged
merged 1 commit into from
May 4, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions securedrop/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,8 @@ def to_json(self):


class Submission(db.Model):
MAX_MESSAGE_LEN = 100000

__tablename__ = 'submissions'
id = Column(Integer, primary_key=True)
uuid = Column(String(36), unique=True, nullable=False)
Expand Down
24 changes: 21 additions & 3 deletions securedrop/source_app/forms.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from flask import current_app
from flask_babel import lazy_gettext as gettext
from flask_wtf import FlaskForm
from wtforms import PasswordField
from wtforms.validators import InputRequired, Regexp, Length
from wtforms import FileField, PasswordField, TextAreaField
from wtforms.validators import InputRequired, Regexp, Length, ValidationError

from models import Source
from models import Source, Submission


class LoginForm(FlaskForm):
Expand All @@ -17,3 +18,20 @@ class LoginForm(FlaskForm):
# Make sure to allow dashes since some words in the wordlist have them
Regexp(r'[\sA-Za-z0-9-]+$', message=gettext('Invalid input.'))
])


class SubmissionForm(FlaskForm):
msg = TextAreaField("msg", render_kw={"placeholder": gettext("Write a message.")})
fh = FileField("fh")

def validate_msg(self, field):
if len(field.data) > Submission.MAX_MESSAGE_LEN:
message = gettext("Message text too long.")
if current_app.instance_config.allow_document_uploads:
message = "{} {}".format(
message,
gettext(
"Large blocks of text must be uploaded as a file, not copied and pasted."
)
)
raise ValidationError(message)
28 changes: 19 additions & 9 deletions securedrop/source_app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from source_app.utils import (logged_in, generate_unique_codename,
async_genkey, normalize_timestamps,
valid_codename, get_entropy_estimate)
from source_app.forms import LoginForm
from source_app.forms import LoginForm, SubmissionForm


def make_blueprint(config):
Expand Down Expand Up @@ -145,13 +145,21 @@ def lookup():
replies=replies,
flagged=g.source.flagged,
new_user=session.get('new_user', None),
haskey=current_app.crypto_util.get_fingerprint(
g.filesystem_id))
haskey=current_app.crypto_util.get_fingerprint(g.filesystem_id),
form=SubmissionForm(),
)

@view.route('/submit', methods=('POST',))
@login_required
def submit():
allow_document_uploads = current_app.instance_config.allow_document_uploads
form = SubmissionForm()
if not form.validate():
for field, errors in form.errors.items():
for error in errors:
flash(error, "error")
return redirect(url_for('main.lookup'))

msg = request.form['msg']
fh = None
if allow_document_uploads and 'fh' in request.files:
Expand Down Expand Up @@ -190,21 +198,23 @@ def submit():
fh.stream))

if first_submission:
msg = render_template('first_submission_flashed_message.html')
flash(Markup(msg), "success")
flash_message = render_template('first_submission_flashed_message.html')
flash(Markup(flash_message), "success")

else:
if msg and not fh:
html_contents = gettext('Thanks! We received your message.')
elif not msg and fh:
elif fh and not msg:
html_contents = gettext('Thanks! We received your document.')
else:
html_contents = gettext('Thanks! We received your message and '
'document.')

msg = render_template('next_submission_flashed_message.html',
html_contents=html_contents)
flash(Markup(msg), "success")
flash_message = render_template(
'next_submission_flashed_message.html',
html_contents=html_contents
)
flash(Markup(flash_message), "success")

new_submissions = []
for fname in fnames:
Expand Down
4 changes: 2 additions & 2 deletions securedrop/source_templates/lookup.html
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,12 @@ <h2 class="headline">{{ gettext('Submit Messages') }}</h2>
{% if allow_document_uploads %}
<div class="attachment grid-item center">
<img class="center" id="upload-icon" src="{{ url_for('static', filename='i/arrow-upload-large.png') }}" width="56" height="56">
<input type="file" name="fh" autocomplete="off">
{{ form.fh() }}
<p class="center" id="max-file-size">{{ gettext('Maximum upload size: 500 MB') }}</p>
</div>
{% endif %}
<div class="message grid-item{% if not allow_document_uploads %} wide{% endif %}">
<textarea name="msg" class="fill-parent" placeholder="{{ gettext('Write a message.') }}"></textarea>
{{ form.msg(class="fill-parent") }}
</div>
</div>

Expand Down
10 changes: 4 additions & 6 deletions securedrop/tests/test_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -352,11 +352,9 @@ def test_submit_empty_message(source_app):


def test_submit_big_message(source_app):
'''
When the message is larger than 512KB it's written to disk instead of
just residing in memory. Make sure the different return type of
SecureTemporaryFile is handled as well as BytesIO.
'''
"""
Test the message size limit.
"""
with source_app.test_client() as app:
new_codename(app, session)
_dummy_submission(app)
Expand All @@ -366,7 +364,7 @@ def test_submit_big_message(source_app):
follow_redirects=True)
assert resp.status_code == 200
text = resp.data.decode('utf-8')
assert "Thanks! We received your message" in text
assert "Message text too long." in text


def test_submit_file(source_app):
Expand Down