Repository: freedomofpress/securedrop

Issue:
"Journalist key" in source interface is not (typically) a journalist key
The *Source Interface* refers to "the journalist's public key", both through the `/journalist-key` and `/why-journalist-key` route names, and in the page heading on the `/why-journalist-key` page, "Why download the journalist's public key?"

This is very misleading wording, as most SecureDrops are operated by news organizations employing more than one journalist. This key is in fact the SecureDrop Submission Key for this installation, the _exact same_ key that messages/files will be encrypted to upon submission. (See #4413 for terminology standardization.)

Offering technical users the option to pre-encrypt is essentially a workaround until we establish a reasonable method for automatic client-side encryption (see #92).

To avoid messing up people's mental model of how SecureDrop works, we should rename this route, especially given that we do refer to managing individual journalist keys in parts of the documentation (for completely different reasons: to securely export files from the *Secure Viewing Station*).


Discussion:
@DrGFreeman Are you potentially interested in helping with this, as well?
@eloquence, sure, I can get on it shortly.

So my understanding is that only the routes need renaming as the Source Interface now refers to the key as "our public key" (on `/lookup` and "the team's public key" on `/why-journalist-key`.

Resolving patch:
diff --git a/securedrop/source_app/info.py b/securedrop/source_app/info.py
--- a/securedrop/source_app/info.py
+++ b/securedrop/source_app/info.py
@@ -1,6 +1,7 @@
 # -*- coding: utf-8 -*-
 import flask
-from flask import Blueprint, render_template, send_file, current_app
+from flask import Blueprint, render_template, send_file, current_app, redirect, url_for
+import werkzeug
 
 from io import BytesIO  # noqa
 
@@ -18,8 +19,8 @@ def tor2web_warning() -> str:
     def recommend_tor_browser() -> str:
         return render_template("use-tor-browser.html")
 
-    @view.route('/journalist-key')
-    def download_journalist_pubkey() -> flask.Response:
+    @view.route('/public-key')
+    def download_public_key() -> flask.Response:
         journalist_pubkey = current_app.crypto_util.gpg.export_keys(
             config.JOURNALIST_KEY)
         data = BytesIO(journalist_pubkey.encode('utf-8'))
@@ -28,8 +29,12 @@ def download_journalist_pubkey() -> flask.Response:
                          attachment_filename=config.JOURNALIST_KEY + ".asc",
                          as_attachment=True)
 
-    @view.route('/why-journalist-key')
-    def why_download_journalist_pubkey() -> str:
-        return render_template("why-journalist-key.html")
+    @view.route('/journalist-key')
+    def download_journalist_key() -> werkzeug.wrappers.Response:
+        return redirect(url_for('.download_public_key'), code=301)
+
+    @view.route('/why-public-key')
+    def why_download_public_key() -> str:
+        return render_template("why-public-key.html")
 
     return view


Test patch:
diff --git a/securedrop/tests/functional/source_navigation_steps.py b/securedrop/tests/functional/source_navigation_steps.py
--- a/securedrop/tests/functional/source_navigation_steps.py
+++ b/securedrop/tests/functional/source_navigation_steps.py
@@ -223,7 +223,7 @@ def _source_tor2web_warning(self):
         self.driver.get(self.source_location + "/tor2web-warning")
 
     def _source_why_journalist_key(self):
-        self.driver.get(self.source_location + "/why-journalist-key")
+        self.driver.get(self.source_location + "/why-public-key")
 
     def _source_waits_for_session_to_timeout(self):
         time.sleep(self.session_expiration + 2)
diff --git a/securedrop/tests/functional/test_source.py b/securedrop/tests/functional/test_source.py
--- a/securedrop/tests/functional/test_source.py
+++ b/securedrop/tests/functional/test_source.py
@@ -48,7 +48,7 @@ class TestDownloadKey(
 
     def test_journalist_key_from_source_interface(self):
         data = self.return_downloaded_content(self.source_location +
-                                              "/journalist-key", None)
+                                              "/public-key", None)
 
         data = data.decode('utf-8')
         assert "BEGIN PGP PUBLIC KEY BLOCK" in data
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
@@ -5,7 +5,7 @@
 import time
 
 from io import BytesIO, StringIO
-from flask import session, escape, current_app, url_for, g
+from flask import session, escape, current_app, url_for, g, request
 from mock import patch, ANY
 
 import crypto_util
@@ -204,11 +204,21 @@ def test_lookup(source_app):
         text = resp.data.decode('utf-8')
         assert "public key" in text
         # download the public key
-        resp = app.get(url_for('info.download_journalist_pubkey'))
+        resp = app.get(url_for('info.download_public_key'))
         text = resp.data.decode('utf-8')
         assert "BEGIN PGP PUBLIC KEY BLOCK" in text
 
 
+def test_journalist_key_redirects_to_public_key(source_app):
+    """Test that the /journalist-key route redirects to /public-key."""
+    with source_app.test_client() as app:
+        resp = app.get(url_for('info.download_journalist_key'))
+        assert resp.status_code == 301
+        resp = app.get(url_for('info.download_journalist_key'), follow_redirects=True)
+        assert request.path == url_for('info.download_public_key')
+        assert "BEGIN PGP PUBLIC KEY BLOCK" in resp.data.decode('utf-8')
+
+
 def test_login_and_logout(source_app):
     with source_app.test_client() as app:
         resp = app.get(url_for('main.login'))
@@ -576,7 +586,7 @@ def test_why_use_tor_browser(source_app):
 
 def test_why_journalist_key(source_app):
     with source_app.test_client() as app:
-        resp = app.get(url_for('info.why_download_journalist_pubkey'))
+        resp = app.get(url_for('info.why_download_public_key'))
         assert resp.status_code == 200
         text = resp.data.decode('utf-8')
         assert "Why download the team's public key?" in text