Repository: freedomofpress/securedrop

Issue:
Codename check triggers KeyError after subsequent login
# Steps to reproduce

1. Enable the new "Prevent sources from submitting their codename as an initial message" setting
2. Create a new source and submit message, note down the codename
3. Log out and log back in as that source
4. Submit another message

# Expected behavior

Message submitted

# Actual behavior

KeyError: 'new_user_codename'



Discussion:
Makes sense in retrospect, as that value is no longer in the session on the return visit. Fix inbound

Resolving patch:
diff --git a/securedrop/source_app/main.py b/securedrop/source_app/main.py
--- a/securedrop/source_app/main.py
+++ b/securedrop/source_app/main.py
@@ -208,14 +208,19 @@ def submit(logged_in_source: SourceUser) -> werkzeug.Response:
                     "error")
                 return redirect(url_for('main.lookup'))
 
+            # if the new_user_codename key is not present in the session, this is
+            # not a first session
+            new_codename = session.get('new_user_codename', None)
+
             codenames_rejected = InstanceConfig.get_default().reject_message_with_codename
-            if codenames_rejected and codename_detected(msg, session['new_user_codename']):
-                flash(Markup('{}<br>{}'.format(
-                    escape(gettext("Please do not submit your codename!")),
-                    escape(gettext("Keep your codename secret, and use it to log in later"
-                                   " to check for replies."))
-                    )), "error")
-                return redirect(url_for('main.lookup'))
+            if new_codename is not None:
+                if codenames_rejected and codename_detected(msg, new_codename):
+                    flash(Markup('{}<br>{}'.format(
+                        escape(gettext("Please do not submit your codename!")),
+                        escape(gettext("Keep your codename secret, and use it to log in later"
+                                       " to check for replies."))
+                        )), "error")
+                    return redirect(url_for('main.lookup'))
 
         if not os.path.exists(Storage.get_default().path(logged_in_source.filesystem_id)):
             current_app.logger.debug("Store directory not found for source '{}', creating one."


Test patch:
diff --git a/securedrop/tests/test_source.py b/securedrop/tests/test_source.py
--- a/securedrop/tests/test_source.py
+++ b/securedrop/tests/test_source.py
@@ -495,6 +495,43 @@ def test_submit_antispam(source_app):
         assert resp.status_code == 403
 
 
+def test_submit_codename_second_login(source_app):
+    """
+    Test codename submissions *not* prevented on second session
+    """
+    with source_app.test_client() as app:
+        InstanceConfig.get_default().update_submission_prefs(
+            allow_uploads=True, min_length=0, reject_codenames=True)
+        codename = new_codename(app, session)
+        resp = app.post(
+            url_for('main.submit'),
+            data=dict(msg=codename, fh=(StringIO(''), '')),
+            follow_redirects=True)
+        assert resp.status_code == 200
+        text = resp.data.decode('utf-8')
+        assert "Please do not submit your codename!" in text
+
+        resp = app.get(url_for('main.logout'),
+                       follow_redirects=True)
+        assert not SessionManager.is_user_logged_in(db_session=db.session)
+        text = resp.data.decode('utf-8')
+        assert 'This will clear your Tor Browser activity data' in text
+
+        resp = app.post(url_for('main.login'),
+                        data=dict(codename=codename),
+                        follow_redirects=True)
+        assert resp.status_code == 200
+        assert SessionManager.is_user_logged_in(db_session=db.session)
+
+        resp = app.post(
+            url_for('main.submit'),
+            data=dict(msg=codename, fh=(StringIO(''), '')),
+            follow_redirects=True)
+        assert resp.status_code == 200
+        text = resp.data.decode('utf-8')
+        assert "Thank you for sending this information" in text
+
+
 def test_submit_codename(source_app):
     """
     Test preventions against people submitting their codename.