Repository: freedomofpress/securedrop

Issue:
Add confirmation message after updating submission preferences
#4879 made it possible to prevent sources from uploading documents by changing a setting in the *Admin Interface*:

![Screen Shot 2019-11-19 at 16 41 15-fullpage](https://user-images.githubusercontent.com/213636/69203093-51470f00-0af8-11ea-9394-2f6349bd646e.png)

However, there is no confirmation message after changing this setting. At minimum, we should add a generic flashed message, which we could use when future preferences added to this page are changed, e.g., "Settings have been updated successfully."

# User Story

As an admin, I want to know at a glance that my actions on the preferences page have had the desired effect, so that I don't have to re-examine the state of specific checkboxes to be sure.


Discussion:
Hi, first contributor to securedrop here.
I'd like to have a go at implementing this if possible!

To me it seems like the most sensible approach would be to implement this entirely with client-side Javascript, changing the behaviour of the form dynamically and including a popup or flash on successful submission.
Any suggestions are appreciated.
Hello @Petvdp, welcome!! :)

I don't believe that any Javascript is used in the Admin Interface, today; most of SecureDrop avoids Javascript dependencies, for security reasons. Technical contributors can speak more to implementation details. 

I'm a UX contributor, and am in the process of finalizing the updated styling for all flash messaging. I'll be happy to share those updated styles, here, sometime next week (or sooner—but am trying to be conservative, for my own sanity). We'd all love to see this page get pushed past the finish line with this Issue being addressed, and I'd love to see the updated messaging style get implemented. 

Jen or Erik or others can speak more to timing and technical particulars. Anywhoo, glad to see you here! More later...
Hi @ninavizz, thanks for the intro! I figured this would be a good opportunity to learn a few things about security concious programming while building something that's useful.

Yeah I can definitely see why you would want to avoid Javascript wherever possible, and definitely only ever Vanilla. It would also be bad to mess up the http request somehow. 

There are potential serverside approaches to this feature, but I can't think of any that don't involve modifying application state in the page's GET request, which would violate REST, since the server has to now know about whether or not the client is reloading the page after making the request.
Hi @petevdp, to resolve this issue, it should be sufficient to use the existing message flashing code and notification styling, and to add a flash upon a POST request to the `/update-submission-preferences` route. Check out example flashed messages in the `admin` routes (`securedrop/journalist_app/admin.py`).
Okay, I'll give that a shot, thanks!

Resolving patch:
diff --git a/securedrop/journalist_app/admin.py b/securedrop/journalist_app/admin.py
--- a/securedrop/journalist_app/admin.py
+++ b/securedrop/journalist_app/admin.py
@@ -59,6 +59,7 @@ def update_submission_preferences():
         form = SubmissionPreferencesForm()
         if form.validate_on_submit():
             # The UI prompt ("prevent") is the opposite of the setting ("allow"):
+            flash(gettext("Preferences saved."), "submission-preferences-success")
             value = not bool(request.form.get('prevent_document_uploads'))
             InstanceConfig.set('allow_document_uploads', value)
             return redirect(url_for('admin.manage_config'))


Test patch:
diff --git a/securedrop/tests/functional/journalist_navigation_steps.py b/securedrop/tests/functional/journalist_navigation_steps.py
--- a/securedrop/tests/functional/journalist_navigation_steps.py
+++ b/securedrop/tests/functional/journalist_navigation_steps.py
@@ -287,11 +287,21 @@ def _admin_disallows_document_uploads(self):
             self.safe_click_by_id("prevent_document_uploads")
             self.safe_click_by_id("submit-submission-preferences")
 
+        def preferences_saved():
+            flash_msg = self.driver.find_element_by_css_selector(".flash")
+            assert "Preferences saved." in flash_msg.text
+        self.wait_for(preferences_saved, timeout=self.timeout * 6)
+
     def _admin_allows_document_uploads(self):
         if self.driver.find_element_by_id("prevent_document_uploads").is_selected():
             self.safe_click_by_id("prevent_document_uploads")
             self.safe_click_by_id("submit-submission-preferences")
 
+        def preferences_saved():
+            flash_msg = self.driver.find_element_by_css_selector(".flash")
+            assert "Preferences saved." in flash_msg.text
+        self.wait_for(preferences_saved, timeout=self.timeout * 6)
+
     def _add_user(self, username, first_name="", last_name="", is_admin=False, hotp=None):
         self.safe_send_keys_by_css_selector('input[name="username"]', username)
 
diff --git a/securedrop/tests/test_journalist.py b/securedrop/tests/test_journalist.py
--- a/securedrop/tests/test_journalist.py
+++ b/securedrop/tests/test_journalist.py
@@ -1297,6 +1297,11 @@ def test_prevent_document_uploads(journalist_app, test_admin):
                  data=form.data,
                  follow_redirects=True)
         assert InstanceConfig.get_current().allow_document_uploads is False
+        with InstrumentedApp(journalist_app) as ins:
+            app.post(url_for('admin.update_submission_preferences'),
+                     data=form.data,
+                     follow_redirects=True)
+            ins.assert_message_flashed('Preferences saved.', 'submission-preferences-success')
 
 
 def test_no_prevent_document_uploads(journalist_app, test_admin):
@@ -1306,6 +1311,10 @@ def test_no_prevent_document_uploads(journalist_app, test_admin):
         app.post(url_for('admin.update_submission_preferences'),
                  follow_redirects=True)
         assert InstanceConfig.get_current().allow_document_uploads is True
+        with InstrumentedApp(journalist_app) as ins:
+            app.post(url_for('admin.update_submission_preferences'),
+                     follow_redirects=True)
+            ins.assert_message_flashed('Preferences saved.', 'submission-preferences-success')
 
 
 def test_logo_upload_with_valid_image_succeeds(journalist_app, test_admin):