We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Be nicer to admins when an uploaded logo doesn't work.
## Description

If an uploaded logo can't be processed, the [error message shown to admins](https://github.com/freedomofpress/securedrop/blob/427675858c49ef67573bb002b37fce06c029a84d/securedrop/journalist_app/admin.py#L56) is curt: "Unable to process the image file. Try another one."

Let's say "Please try another one." 

Thanks to AO on Weblate for [pointing it out](https://weblate.securedrop.org/translate/securedrop/securedrop/en/?checksum=c2a5a60aaa227c25#comments).


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `securedrop/journalist_app/admin.py`
Content:
```
1 # -*- coding: utf-8 -*-
2 
3 import os
4 from typing import Optional
5 from typing import Union
6 
7 import werkzeug
8 from flask import (Blueprint, render_template, request, url_for, redirect, g,
9                    current_app, flash, abort)
10 from flask_babel import gettext
11 from sqlalchemy.exc import IntegrityError
12 from sqlalchemy.orm.exc import NoResultFound
13 
14 from db import db
15 from html import escape
16 from models import (InstanceConfig, Journalist, InvalidUsernameException,
17                     FirstOrLastNameError, PasswordError)
18 from journalist_app.decorators import admin_required
19 from journalist_app.utils import (commit_account_changes, set_diceware_password,
20                                   validate_hotp_secret, revoke_token)
21 from journalist_app.forms import LogoForm, NewUserForm, SubmissionPreferencesForm, OrgNameForm
22 from sdconfig import SDConfig
23 from passphrases import PassphraseGenerator
24 
25 
26 def make_blueprint(config: SDConfig) -> Blueprint:
27     view = Blueprint('admin', __name__)
28 
29     @view.route('/', methods=('GET', 'POST'))
30     @admin_required
31     def index() -> str:
32         users = Journalist.query.all()
33         return render_template("admin.html", users=users)
34 
35     @view.route('/config', methods=('GET', 'POST'))
36     @admin_required
37     def manage_config() -> Union[str, werkzeug.Response]:
38         # The UI prompt ("prevent") is the opposite of the setting ("allow"):
39         submission_preferences_form = SubmissionPreferencesForm(
40             prevent_document_uploads=not current_app.instance_config.allow_document_uploads)
41         organization_name_form = OrgNameForm(
42             organization_name=current_app.instance_config.organization_name)
43         logo_form = LogoForm()
44         if logo_form.validate_on_submit():
45             f = logo_form.logo.data
46 
47             if current_app.static_folder is None:
48                 abort(500)
49             custom_logo_filepath = os.path.join(current_app.static_folder, 'i',
50                                                 'custom_logo.png')
51             try:
52                 f.save(custom_logo_filepath)
53                 flash(gettext("Image updated."), "logo-success")
54             except Exception:
55                 # Translators: This error is shown when an uploaded image cannot be used.
56                 flash(gettext("Unable to process the image file. Try another one."), "logo-error")
57             finally:
58                 return redirect(url_for("admin.manage_config") + "#config-logoimage")
59         else:
60             for field, errors in list(logo_form.errors.items()):
61                 for error in errors:
62                     flash(error, "logo-error")
63             return render_template("config.html",
64                                    submission_preferences_form=submission_preferences_form,
65                                    organization_name_form=organization_name_form,
66                                    logo_form=logo_form)
67 
68     @view.route('/update-submission-preferences', methods=['POST'])
69     @admin_required
70     def update_submission_preferences() -> Optional[werkzeug.Response]:
71         form = SubmissionPreferencesForm()
72         if form.validate_on_submit():
73             # The UI prompt ("prevent") is the opposite of the setting ("allow"):
74             flash(gettext("Preferences saved."), "submission-preferences-success")
75             value = not bool(request.form.get('prevent_document_uploads'))
76             InstanceConfig.set_allow_document_uploads(value)
77             return redirect(url_for('admin.manage_config') + "#config-preventuploads")
78         else:
79             for field, errors in list(form.errors.items()):
80                 for error in errors:
81                     flash(gettext("Preferences not updated.") + " " + error,
82                           "submission-preferences-error")
83         return redirect(url_for('admin.manage_config') + "#config-preventuploads")
84 
85     @view.route('/update-org-name', methods=['POST'])
86     @admin_required
87     def update_org_name() -> Union[str, werkzeug.Response]:
88         form = OrgNameForm()
89         if form.validate_on_submit():
90             try:
91                 value = request.form['organization_name']
92                 InstanceConfig.set_organization_name(escape(value, quote=True))
93                 flash(gettext("Preferences saved."), "org-name-success")
94             except Exception:
95                 flash(gettext('Failed to update organization name.'), 'org-name-error')
96             return redirect(url_for('admin.manage_config') + "#config-orgname")
97         else:
98             for field, errors in list(form.errors.items()):
99                 for error in errors:
100                     flash(error, "org-name-error")
101         return redirect(url_for('admin.manage_config') + "#config-orgname")
102 
103     @view.route('/add', methods=('GET', 'POST'))
104     @admin_required
105     def add_user() -> Union[str, werkzeug.Response]:
106         form = NewUserForm()
107         if form.validate_on_submit():
108             form_valid = True
109             username = request.form['username']
110             first_name = request.form['first_name']
111             last_name = request.form['last_name']
112             password = request.form['password']
113             is_admin = bool(request.form.get('is_admin'))
114 
115             try:
116                 otp_secret = None
117                 if request.form.get('is_hotp', False):
118                     otp_secret = request.form.get('otp_secret', '')
119                 new_user = Journalist(username=username,
120                                       password=password,
121                                       first_name=first_name,
122                                       last_name=last_name,
123                                       is_admin=is_admin,
124                                       otp_secret=otp_secret)
125                 db.session.add(new_user)
126                 db.session.commit()
127             except PasswordError:
128                 flash(gettext(
129                       'There was an error with the autogenerated password. '
130                       'User not created. Please try again.'), 'error')
131                 form_valid = False
132             except InvalidUsernameException as e:
133                 form_valid = False
134                 # Translators: Here, "{message}" explains the problem with the username.
135                 flash(gettext('Invalid username: {message}').format(message=e), "error")
136             except IntegrityError as e:
137                 db.session.rollback()
138                 form_valid = False
139                 if "UNIQUE constraint failed: journalists.username" in str(e):
140                     flash(
141                         gettext('Username "{username}" already taken.').format(username=username),
142                         "error"
143                     )
144                 else:
145                     flash(gettext("An error occurred saving this user"
146                                   " to the database."
147                                   " Please inform your admin."),
148                           "error")
149                     current_app.logger.error("Adding user "
150                                              "'{}' failed: {}".format(
151                                                  username, e))
152 
153             if form_valid:
154                 return redirect(url_for('admin.new_user_two_factor',
155                                         uid=new_user.id))
156 
157         password = PassphraseGenerator.get_default().generate_passphrase(
158             preferred_language=g.localeinfo.language
159         )
160         return render_template("admin_add_user.html",
161                                password=password,
162                                form=form)
163 
164     @view.route('/2fa', methods=('GET', 'POST'))
165     @admin_required
166     def new_user_two_factor() -> Union[str, werkzeug.Response]:
167         user = Journalist.query.get(request.args['uid'])
168 
169         if request.method == 'POST':
170             token = request.form['token']
171             if user.verify_token(token):
172                 flash(gettext(
173                     "The two-factor code for user \"{user}\" was verified "
174                     "successfully.").format(user=user.username),
175                     "notification")
176                 return redirect(url_for("admin.index"))
177             else:
178                 flash(gettext(
179                     "There was a problem verifying the two-factor code. Please try again."),
180                       "error")
181 
182         return render_template("admin_new_user_two_factor.html", user=user)
183 
184     @view.route('/reset-2fa-totp', methods=['POST'])
185     @admin_required
186     def reset_two_factor_totp() -> werkzeug.Response:
187         uid = request.form['uid']
188         user = Journalist.query.get(uid)
189         user.is_totp = True
190         user.regenerate_totp_shared_secret()
191         db.session.commit()
192         return redirect(url_for('admin.new_user_two_factor', uid=uid))
193 
194     @view.route('/reset-2fa-hotp', methods=['POST'])
195     @admin_required
196     def reset_two_factor_hotp() -> Union[str, werkzeug.Response]:
197         uid = request.form['uid']
198         otp_secret = request.form.get('otp_secret', None)
199         if otp_secret:
200             user = Journalist.query.get(uid)
201             if not validate_hotp_secret(user, otp_secret):
202                 return render_template('admin_edit_hotp_secret.html', uid=uid)
203             db.session.commit()
204             return redirect(url_for('admin.new_user_two_factor', uid=uid))
205         else:
206             return render_template('admin_edit_hotp_secret.html', uid=uid)
207 
208     @view.route('/edit/<int:user_id>', methods=('GET', 'POST'))
209     @admin_required
210     def edit_user(user_id: int) -> Union[str, werkzeug.Response]:
211         user = Journalist.query.get(user_id)
212 
213         if request.method == 'POST':
214             if request.form.get('username', None):
215                 new_username = request.form['username']
216 
217                 try:
218                     Journalist.check_username_acceptable(new_username)
219                 except InvalidUsernameException as e:
220                     flash(gettext('Invalid username: {message}').format(message=e), "error")
221                     return redirect(url_for("admin.edit_user",
222                                             user_id=user_id))
223 
224                 if new_username == user.username:
225                     pass
226                 elif Journalist.query.filter_by(
227                         username=new_username).one_or_none():
228                     flash(
229                         gettext('Username "{username}" already taken.').format(
230                             username=new_username
231                         ),
232                         "error"
233                     )
234                     return redirect(url_for("admin.edit_user",
235                                             user_id=user_id))
236                 else:
237                     user.username = new_username
238 
239             try:
240                 first_name = request.form['first_name']
241                 Journalist.check_name_acceptable(first_name)
242                 user.first_name = first_name
243             except FirstOrLastNameError as e:
244                 # Translators: Here, "{message}" explains the problem with the name.
245                 flash(gettext('Name not updated: {message}').format(message=e), "error")
246                 return redirect(url_for("admin.edit_user", user_id=user_id))
247 
248             try:
249                 last_name = request.form['last_name']
250                 Journalist.check_name_acceptable(last_name)
251                 user.last_name = last_name
252             except FirstOrLastNameError as e:
253                 flash(gettext('Name not updated: {message}').format(message=e), "error")
254                 return redirect(url_for("admin.edit_user", user_id=user_id))
255 
256             user.is_admin = bool(request.form.get('is_admin'))
257 
258             commit_account_changes(user)
259 
260         password = PassphraseGenerator.get_default().generate_passphrase(
261             preferred_language=g.localeinfo.language
262         )
263         return render_template("edit_account.html", user=user,
264                                password=password)
265 
266     @view.route('/delete/<int:user_id>', methods=('POST',))
267     @admin_required
268     def delete_user(user_id: int) -> werkzeug.Response:
269         user = Journalist.query.get(user_id)
270         if user_id == g.user.id:
271             # Do not flash because the interface already has safe guards.
272             # It can only happen by manually crafting a POST request
273             current_app.logger.error(
274                 "Admin {} tried to delete itself".format(g.user.username))
275             abort(403)
276         elif user:
277             db.session.delete(user)
278             db.session.commit()
279             flash(gettext("Deleted user '{user}'.").format(
280                 user=user.username), "notification")
281         else:
282             current_app.logger.error(
283                 "Admin {} tried to delete nonexistent user with pk={}".format(
284                     g.user.username, user_id))
285             abort(404)
286 
287         return redirect(url_for('admin.index'))
288 
289     @view.route('/edit/<int:user_id>/new-password', methods=('POST',))
290     @admin_required
291     def new_password(user_id: int) -> werkzeug.Response:
292         try:
293             user = Journalist.query.get(user_id)
294         except NoResultFound:
295             abort(404)
296 
297         password = request.form.get('password')
298         if set_diceware_password(user, password) is not False:
299             if user.last_token is not None:
300                 revoke_token(user, user.last_token)
301             user.session_nonce += 1
302             db.session.commit()
303         return redirect(url_for('admin.edit_user', user_id=user_id))
304 
305     @view.route('/ossec-test')
306     @admin_required
307     def ossec_test() -> werkzeug.Response:
308         current_app.logger.error('This is a test OSSEC alert')
309         flash(gettext('Test alert sent. Please check your email.'),
310               'testalert-notification')
311         return redirect(url_for('admin.manage_config') + "#config-testalert")
312 
313     return view
314
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

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
@@ -53,7 +53,10 @@
                 flash(gettext("Image updated."), "logo-success")
             except Exception:
                 # Translators: This error is shown when an uploaded image cannot be used.
-                flash(gettext("Unable to process the image file. Try another one."), "logo-error")
+                flash(
+                    gettext("Unable to process the image file. Please try another one."),
+                    "logo-error"
+                )
             finally:
                 return redirect(url_for("admin.manage_config") + "#config-logoimage")
         else:

{"golden_diff": "diff --git a/securedrop/journalist_app/admin.py b/securedrop/journalist_app/admin.py\n--- a/securedrop/journalist_app/admin.py\n+++ b/securedrop/journalist_app/admin.py\n@@ -53,7 +53,10 @@\n                 flash(gettext(\"Image updated.\"), \"logo-success\")\n             except Exception:\n                 # Translators: This error is shown when an uploaded image cannot be used.\n-                flash(gettext(\"Unable to process the image file. Try another one.\"), \"logo-error\")\n+                flash(\n+                    gettext(\"Unable to process the image file. Please try another one.\"),\n+                    \"logo-error\"\n+                )\n             finally:\n                 return redirect(url_for(\"admin.manage_config\") + \"#config-logoimage\")\n         else:\n", "issue": "Be nicer to admins when an uploaded logo doesn't work.\n## Description\r\n\r\nIf an uploaded logo can't be processed, the [error message shown to admins](https://github.com/freedomofpress/securedrop/blob/427675858c49ef67573bb002b37fce06c029a84d/securedrop/journalist_app/admin.py#L56) is curt: \"Unable to process the image file. Try another one.\"\r\n\r\nLet's say \"Please try another one.\" \r\n\r\nThanks to AO on Weblate for [pointing it out](https://weblate.securedrop.org/translate/securedrop/securedrop/en/?checksum=c2a5a60aaa227c25#comments).\r\n\n", "before_files": [{"content": "# -*- coding: utf-8 -*-\n\nimport os\nfrom typing import Optional\nfrom typing import Union\n\nimport werkzeug\nfrom flask import (Blueprint, render_template, request, url_for, redirect, g,\n                   current_app, flash, abort)\nfrom flask_babel import gettext\nfrom sqlalchemy.exc import IntegrityError\nfrom sqlalchemy.orm.exc import NoResultFound\n\nfrom db import db\nfrom html import escape\nfrom models import (InstanceConfig, Journalist, InvalidUsernameException,\n                    FirstOrLastNameError, PasswordError)\nfrom journalist_app.decorators import admin_required\nfrom journalist_app.utils import (commit_account_changes, set_diceware_password,\n                                  validate_hotp_secret, revoke_token)\nfrom journalist_app.forms import LogoForm, NewUserForm, SubmissionPreferencesForm, OrgNameForm\nfrom sdconfig import SDConfig\nfrom passphrases import PassphraseGenerator\n\n\ndef make_blueprint(config: SDConfig) -> Blueprint:\n    view = Blueprint('admin', __name__)\n\n    @view.route('/', methods=('GET', 'POST'))\n    @admin_required\n    def index() -> str:\n        users = Journalist.query.all()\n        return render_template(\"admin.html\", users=users)\n\n    @view.route('/config', methods=('GET', 'POST'))\n    @admin_required\n    def manage_config() -> Union[str, werkzeug.Response]:\n        # The UI prompt (\"prevent\") is the opposite of the setting (\"allow\"):\n        submission_preferences_form = SubmissionPreferencesForm(\n            prevent_document_uploads=not current_app.instance_config.allow_document_uploads)\n        organization_name_form = OrgNameForm(\n            organization_name=current_app.instance_config.organization_name)\n        logo_form = LogoForm()\n        if logo_form.validate_on_submit():\n            f = logo_form.logo.data\n\n            if current_app.static_folder is None:\n                abort(500)\n            custom_logo_filepath = os.path.join(current_app.static_folder, 'i',\n                                                'custom_logo.png')\n            try:\n                f.save(custom_logo_filepath)\n                flash(gettext(\"Image updated.\"), \"logo-success\")\n            except Exception:\n                # Translators: This error is shown when an uploaded image cannot be used.\n                flash(gettext(\"Unable to process the image file. Try another one.\"), \"logo-error\")\n            finally:\n                return redirect(url_for(\"admin.manage_config\") + \"#config-logoimage\")\n        else:\n            for field, errors in list(logo_form.errors.items()):\n                for error in errors:\n                    flash(error, \"logo-error\")\n            return render_template(\"config.html\",\n                                   submission_preferences_form=submission_preferences_form,\n                                   organization_name_form=organization_name_form,\n                                   logo_form=logo_form)\n\n    @view.route('/update-submission-preferences', methods=['POST'])\n    @admin_required\n    def update_submission_preferences() -> Optional[werkzeug.Response]:\n        form = SubmissionPreferencesForm()\n        if form.validate_on_submit():\n            # The UI prompt (\"prevent\") is the opposite of the setting (\"allow\"):\n            flash(gettext(\"Preferences saved.\"), \"submission-preferences-success\")\n            value = not bool(request.form.get('prevent_document_uploads'))\n            InstanceConfig.set_allow_document_uploads(value)\n            return redirect(url_for('admin.manage_config') + \"#config-preventuploads\")\n        else:\n            for field, errors in list(form.errors.items()):\n                for error in errors:\n                    flash(gettext(\"Preferences not updated.\") + \" \" + error,\n                          \"submission-preferences-error\")\n        return redirect(url_for('admin.manage_config') + \"#config-preventuploads\")\n\n    @view.route('/update-org-name', methods=['POST'])\n    @admin_required\n    def update_org_name() -> Union[str, werkzeug.Response]:\n        form = OrgNameForm()\n        if form.validate_on_submit():\n            try:\n                value = request.form['organization_name']\n                InstanceConfig.set_organization_name(escape(value, quote=True))\n                flash(gettext(\"Preferences saved.\"), \"org-name-success\")\n            except Exception:\n                flash(gettext('Failed to update organization name.'), 'org-name-error')\n            return redirect(url_for('admin.manage_config') + \"#config-orgname\")\n        else:\n            for field, errors in list(form.errors.items()):\n                for error in errors:\n                    flash(error, \"org-name-error\")\n        return redirect(url_for('admin.manage_config') + \"#config-orgname\")\n\n    @view.route('/add', methods=('GET', 'POST'))\n    @admin_required\n    def add_user() -> Union[str, werkzeug.Response]:\n        form = NewUserForm()\n        if form.validate_on_submit():\n            form_valid = True\n            username = request.form['username']\n            first_name = request.form['first_name']\n            last_name = request.form['last_name']\n            password = request.form['password']\n            is_admin = bool(request.form.get('is_admin'))\n\n            try:\n                otp_secret = None\n                if request.form.get('is_hotp', False):\n                    otp_secret = request.form.get('otp_secret', '')\n                new_user = Journalist(username=username,\n                                      password=password,\n                                      first_name=first_name,\n                                      last_name=last_name,\n                                      is_admin=is_admin,\n                                      otp_secret=otp_secret)\n                db.session.add(new_user)\n                db.session.commit()\n            except PasswordError:\n                flash(gettext(\n                      'There was an error with the autogenerated password. '\n                      'User not created. Please try again.'), 'error')\n                form_valid = False\n            except InvalidUsernameException as e:\n                form_valid = False\n                # Translators: Here, \"{message}\" explains the problem with the username.\n                flash(gettext('Invalid username: {message}').format(message=e), \"error\")\n            except IntegrityError as e:\n                db.session.rollback()\n                form_valid = False\n                if \"UNIQUE constraint failed: journalists.username\" in str(e):\n                    flash(\n                        gettext('Username \"{username}\" already taken.').format(username=username),\n                        \"error\"\n                    )\n                else:\n                    flash(gettext(\"An error occurred saving this user\"\n                                  \" to the database.\"\n                                  \" Please inform your admin.\"),\n                          \"error\")\n                    current_app.logger.error(\"Adding user \"\n                                             \"'{}' failed: {}\".format(\n                                                 username, e))\n\n            if form_valid:\n                return redirect(url_for('admin.new_user_two_factor',\n                                        uid=new_user.id))\n\n        password = PassphraseGenerator.get_default().generate_passphrase(\n            preferred_language=g.localeinfo.language\n        )\n        return render_template(\"admin_add_user.html\",\n                               password=password,\n                               form=form)\n\n    @view.route('/2fa', methods=('GET', 'POST'))\n    @admin_required\n    def new_user_two_factor() -> Union[str, werkzeug.Response]:\n        user = Journalist.query.get(request.args['uid'])\n\n        if request.method == 'POST':\n            token = request.form['token']\n            if user.verify_token(token):\n                flash(gettext(\n                    \"The two-factor code for user \\\"{user}\\\" was verified \"\n                    \"successfully.\").format(user=user.username),\n                    \"notification\")\n                return redirect(url_for(\"admin.index\"))\n            else:\n                flash(gettext(\n                    \"There was a problem verifying the two-factor code. Please try again.\"),\n                      \"error\")\n\n        return render_template(\"admin_new_user_two_factor.html\", user=user)\n\n    @view.route('/reset-2fa-totp', methods=['POST'])\n    @admin_required\n    def reset_two_factor_totp() -> werkzeug.Response:\n        uid = request.form['uid']\n        user = Journalist.query.get(uid)\n        user.is_totp = True\n        user.regenerate_totp_shared_secret()\n        db.session.commit()\n        return redirect(url_for('admin.new_user_two_factor', uid=uid))\n\n    @view.route('/reset-2fa-hotp', methods=['POST'])\n    @admin_required\n    def reset_two_factor_hotp() -> Union[str, werkzeug.Response]:\n        uid = request.form['uid']\n        otp_secret = request.form.get('otp_secret', None)\n        if otp_secret:\n            user = Journalist.query.get(uid)\n            if not validate_hotp_secret(user, otp_secret):\n                return render_template('admin_edit_hotp_secret.html', uid=uid)\n            db.session.commit()\n            return redirect(url_for('admin.new_user_two_factor', uid=uid))\n        else:\n            return render_template('admin_edit_hotp_secret.html', uid=uid)\n\n    @view.route('/edit/<int:user_id>', methods=('GET', 'POST'))\n    @admin_required\n    def edit_user(user_id: int) -> Union[str, werkzeug.Response]:\n        user = Journalist.query.get(user_id)\n\n        if request.method == 'POST':\n            if request.form.get('username', None):\n                new_username = request.form['username']\n\n                try:\n                    Journalist.check_username_acceptable(new_username)\n                except InvalidUsernameException as e:\n                    flash(gettext('Invalid username: {message}').format(message=e), \"error\")\n                    return redirect(url_for(\"admin.edit_user\",\n                                            user_id=user_id))\n\n                if new_username == user.username:\n                    pass\n                elif Journalist.query.filter_by(\n                        username=new_username).one_or_none():\n                    flash(\n                        gettext('Username \"{username}\" already taken.').format(\n                            username=new_username\n                        ),\n                        \"error\"\n                    )\n                    return redirect(url_for(\"admin.edit_user\",\n                                            user_id=user_id))\n                else:\n                    user.username = new_username\n\n            try:\n                first_name = request.form['first_name']\n                Journalist.check_name_acceptable(first_name)\n                user.first_name = first_name\n            except FirstOrLastNameError as e:\n                # Translators: Here, \"{message}\" explains the problem with the name.\n                flash(gettext('Name not updated: {message}').format(message=e), \"error\")\n                return redirect(url_for(\"admin.edit_user\", user_id=user_id))\n\n            try:\n                last_name = request.form['last_name']\n                Journalist.check_name_acceptable(last_name)\n                user.last_name = last_name\n            except FirstOrLastNameError as e:\n                flash(gettext('Name not updated: {message}').format(message=e), \"error\")\n                return redirect(url_for(\"admin.edit_user\", user_id=user_id))\n\n            user.is_admin = bool(request.form.get('is_admin'))\n\n            commit_account_changes(user)\n\n        password = PassphraseGenerator.get_default().generate_passphrase(\n            preferred_language=g.localeinfo.language\n        )\n        return render_template(\"edit_account.html\", user=user,\n                               password=password)\n\n    @view.route('/delete/<int:user_id>', methods=('POST',))\n    @admin_required\n    def delete_user(user_id: int) -> werkzeug.Response:\n        user = Journalist.query.get(user_id)\n        if user_id == g.user.id:\n            # Do not flash because the interface already has safe guards.\n            # It can only happen by manually crafting a POST request\n            current_app.logger.error(\n                \"Admin {} tried to delete itself\".format(g.user.username))\n            abort(403)\n        elif user:\n            db.session.delete(user)\n            db.session.commit()\n            flash(gettext(\"Deleted user '{user}'.\").format(\n                user=user.username), \"notification\")\n        else:\n            current_app.logger.error(\n                \"Admin {} tried to delete nonexistent user with pk={}\".format(\n                    g.user.username, user_id))\n            abort(404)\n\n        return redirect(url_for('admin.index'))\n\n    @view.route('/edit/<int:user_id>/new-password', methods=('POST',))\n    @admin_required\n    def new_password(user_id: int) -> werkzeug.Response:\n        try:\n            user = Journalist.query.get(user_id)\n        except NoResultFound:\n            abort(404)\n\n        password = request.form.get('password')\n        if set_diceware_password(user, password) is not False:\n            if user.last_token is not None:\n                revoke_token(user, user.last_token)\n            user.session_nonce += 1\n            db.session.commit()\n        return redirect(url_for('admin.edit_user', user_id=user_id))\n\n    @view.route('/ossec-test')\n    @admin_required\n    def ossec_test() -> werkzeug.Response:\n        current_app.logger.error('This is a test OSSEC alert')\n        flash(gettext('Test alert sent. Please check your email.'),\n              'testalert-notification')\n        return redirect(url_for('admin.manage_config') + \"#config-testalert\")\n\n    return view\n", "path": "securedrop/journalist_app/admin.py"}], "after_files": [{"content": "# -*- coding: utf-8 -*-\n\nimport os\nfrom typing import Optional\nfrom typing import Union\n\nimport werkzeug\nfrom flask import (Blueprint, render_template, request, url_for, redirect, g,\n                   current_app, flash, abort)\nfrom flask_babel import gettext\nfrom sqlalchemy.exc import IntegrityError\nfrom sqlalchemy.orm.exc import NoResultFound\n\nfrom db import db\nfrom html import escape\nfrom models import (InstanceConfig, Journalist, InvalidUsernameException,\n                    FirstOrLastNameError, PasswordError)\nfrom journalist_app.decorators import admin_required\nfrom journalist_app.utils import (commit_account_changes, set_diceware_password,\n                                  validate_hotp_secret, revoke_token)\nfrom journalist_app.forms import LogoForm, NewUserForm, SubmissionPreferencesForm, OrgNameForm\nfrom sdconfig import SDConfig\nfrom passphrases import PassphraseGenerator\n\n\ndef make_blueprint(config: SDConfig) -> Blueprint:\n    view = Blueprint('admin', __name__)\n\n    @view.route('/', methods=('GET', 'POST'))\n    @admin_required\n    def index() -> str:\n        users = Journalist.query.all()\n        return render_template(\"admin.html\", users=users)\n\n    @view.route('/config', methods=('GET', 'POST'))\n    @admin_required\n    def manage_config() -> Union[str, werkzeug.Response]:\n        # The UI prompt (\"prevent\") is the opposite of the setting (\"allow\"):\n        submission_preferences_form = SubmissionPreferencesForm(\n            prevent_document_uploads=not current_app.instance_config.allow_document_uploads)\n        organization_name_form = OrgNameForm(\n            organization_name=current_app.instance_config.organization_name)\n        logo_form = LogoForm()\n        if logo_form.validate_on_submit():\n            f = logo_form.logo.data\n\n            if current_app.static_folder is None:\n                abort(500)\n            custom_logo_filepath = os.path.join(current_app.static_folder, 'i',\n                                                'custom_logo.png')\n            try:\n                f.save(custom_logo_filepath)\n                flash(gettext(\"Image updated.\"), \"logo-success\")\n            except Exception:\n                # Translators: This error is shown when an uploaded image cannot be used.\n                flash(\n                    gettext(\"Unable to process the image file. Please try another one.\"),\n                    \"logo-error\"\n                )\n            finally:\n                return redirect(url_for(\"admin.manage_config\") + \"#config-logoimage\")\n        else:\n            for field, errors in list(logo_form.errors.items()):\n                for error in errors:\n                    flash(error, \"logo-error\")\n            return render_template(\"config.html\",\n                                   submission_preferences_form=submission_preferences_form,\n                                   organization_name_form=organization_name_form,\n                                   logo_form=logo_form)\n\n    @view.route('/update-submission-preferences', methods=['POST'])\n    @admin_required\n    def update_submission_preferences() -> Optional[werkzeug.Response]:\n        form = SubmissionPreferencesForm()\n        if form.validate_on_submit():\n            # The UI prompt (\"prevent\") is the opposite of the setting (\"allow\"):\n            flash(gettext(\"Preferences saved.\"), \"submission-preferences-success\")\n            value = not bool(request.form.get('prevent_document_uploads'))\n            InstanceConfig.set_allow_document_uploads(value)\n            return redirect(url_for('admin.manage_config') + \"#config-preventuploads\")\n        else:\n            for field, errors in list(form.errors.items()):\n                for error in errors:\n                    flash(gettext(\"Preferences not updated.\") + \" \" + error,\n                          \"submission-preferences-error\")\n        return redirect(url_for('admin.manage_config') + \"#config-preventuploads\")\n\n    @view.route('/update-org-name', methods=['POST'])\n    @admin_required\n    def update_org_name() -> Union[str, werkzeug.Response]:\n        form = OrgNameForm()\n        if form.validate_on_submit():\n            try:\n                value = request.form['organization_name']\n                InstanceConfig.set_organization_name(escape(value, quote=True))\n                flash(gettext(\"Preferences saved.\"), \"org-name-success\")\n            except Exception:\n                flash(gettext('Failed to update organization name.'), 'org-name-error')\n            return redirect(url_for('admin.manage_config') + \"#config-orgname\")\n        else:\n            for field, errors in list(form.errors.items()):\n                for error in errors:\n                    flash(error, \"org-name-error\")\n        return redirect(url_for('admin.manage_config') + \"#config-orgname\")\n\n    @view.route('/add', methods=('GET', 'POST'))\n    @admin_required\n    def add_user() -> Union[str, werkzeug.Response]:\n        form = NewUserForm()\n        if form.validate_on_submit():\n            form_valid = True\n            username = request.form['username']\n            first_name = request.form['first_name']\n            last_name = request.form['last_name']\n            password = request.form['password']\n            is_admin = bool(request.form.get('is_admin'))\n\n            try:\n                otp_secret = None\n                if request.form.get('is_hotp', False):\n                    otp_secret = request.form.get('otp_secret', '')\n                new_user = Journalist(username=username,\n                                      password=password,\n                                      first_name=first_name,\n                                      last_name=last_name,\n                                      is_admin=is_admin,\n                                      otp_secret=otp_secret)\n                db.session.add(new_user)\n                db.session.commit()\n            except PasswordError:\n                flash(gettext(\n                      'There was an error with the autogenerated password. '\n                      'User not created. Please try again.'), 'error')\n                form_valid = False\n            except InvalidUsernameException as e:\n                form_valid = False\n                # Translators: Here, \"{message}\" explains the problem with the username.\n                flash(gettext('Invalid username: {message}').format(message=e), \"error\")\n            except IntegrityError as e:\n                db.session.rollback()\n                form_valid = False\n                if \"UNIQUE constraint failed: journalists.username\" in str(e):\n                    flash(\n                        gettext('Username \"{username}\" already taken.').format(username=username),\n                        \"error\"\n                    )\n                else:\n                    flash(gettext(\"An error occurred saving this user\"\n                                  \" to the database.\"\n                                  \" Please inform your admin.\"),\n                          \"error\")\n                    current_app.logger.error(\"Adding user \"\n                                             \"'{}' failed: {}\".format(\n                                                 username, e))\n\n            if form_valid:\n                return redirect(url_for('admin.new_user_two_factor',\n                                        uid=new_user.id))\n\n        password = PassphraseGenerator.get_default().generate_passphrase(\n            preferred_language=g.localeinfo.language\n        )\n        return render_template(\"admin_add_user.html\",\n                               password=password,\n                               form=form)\n\n    @view.route('/2fa', methods=('GET', 'POST'))\n    @admin_required\n    def new_user_two_factor() -> Union[str, werkzeug.Response]:\n        user = Journalist.query.get(request.args['uid'])\n\n        if request.method == 'POST':\n            token = request.form['token']\n            if user.verify_token(token):\n                flash(gettext(\n                    \"The two-factor code for user \\\"{user}\\\" was verified \"\n                    \"successfully.\").format(user=user.username),\n                    \"notification\")\n                return redirect(url_for(\"admin.index\"))\n            else:\n                flash(gettext(\n                    \"There was a problem verifying the two-factor code. Please try again.\"),\n                      \"error\")\n\n        return render_template(\"admin_new_user_two_factor.html\", user=user)\n\n    @view.route('/reset-2fa-totp', methods=['POST'])\n    @admin_required\n    def reset_two_factor_totp() -> werkzeug.Response:\n        uid = request.form['uid']\n        user = Journalist.query.get(uid)\n        user.is_totp = True\n        user.regenerate_totp_shared_secret()\n        db.session.commit()\n        return redirect(url_for('admin.new_user_two_factor', uid=uid))\n\n    @view.route('/reset-2fa-hotp', methods=['POST'])\n    @admin_required\n    def reset_two_factor_hotp() -> Union[str, werkzeug.Response]:\n        uid = request.form['uid']\n        otp_secret = request.form.get('otp_secret', None)\n        if otp_secret:\n            user = Journalist.query.get(uid)\n            if not validate_hotp_secret(user, otp_secret):\n                return render_template('admin_edit_hotp_secret.html', uid=uid)\n            db.session.commit()\n            return redirect(url_for('admin.new_user_two_factor', uid=uid))\n        else:\n            return render_template('admin_edit_hotp_secret.html', uid=uid)\n\n    @view.route('/edit/<int:user_id>', methods=('GET', 'POST'))\n    @admin_required\n    def edit_user(user_id: int) -> Union[str, werkzeug.Response]:\n        user = Journalist.query.get(user_id)\n\n        if request.method == 'POST':\n            if request.form.get('username', None):\n                new_username = request.form['username']\n\n                try:\n                    Journalist.check_username_acceptable(new_username)\n                except InvalidUsernameException as e:\n                    flash(gettext('Invalid username: {message}').format(message=e), \"error\")\n                    return redirect(url_for(\"admin.edit_user\",\n                                            user_id=user_id))\n\n                if new_username == user.username:\n                    pass\n                elif Journalist.query.filter_by(\n                        username=new_username).one_or_none():\n                    flash(\n                        gettext('Username \"{username}\" already taken.').format(\n                            username=new_username\n                        ),\n                        \"error\"\n                    )\n                    return redirect(url_for(\"admin.edit_user\",\n                                            user_id=user_id))\n                else:\n                    user.username = new_username\n\n            try:\n                first_name = request.form['first_name']\n                Journalist.check_name_acceptable(first_name)\n                user.first_name = first_name\n            except FirstOrLastNameError as e:\n                # Translators: Here, \"{message}\" explains the problem with the name.\n                flash(gettext('Name not updated: {message}').format(message=e), \"error\")\n                return redirect(url_for(\"admin.edit_user\", user_id=user_id))\n\n            try:\n                last_name = request.form['last_name']\n                Journalist.check_name_acceptable(last_name)\n                user.last_name = last_name\n            except FirstOrLastNameError as e:\n                flash(gettext('Name not updated: {message}').format(message=e), \"error\")\n                return redirect(url_for(\"admin.edit_user\", user_id=user_id))\n\n            user.is_admin = bool(request.form.get('is_admin'))\n\n            commit_account_changes(user)\n\n        password = PassphraseGenerator.get_default().generate_passphrase(\n            preferred_language=g.localeinfo.language\n        )\n        return render_template(\"edit_account.html\", user=user,\n                               password=password)\n\n    @view.route('/delete/<int:user_id>', methods=('POST',))\n    @admin_required\n    def delete_user(user_id: int) -> werkzeug.Response:\n        user = Journalist.query.get(user_id)\n        if user_id == g.user.id:\n            # Do not flash because the interface already has safe guards.\n            # It can only happen by manually crafting a POST request\n            current_app.logger.error(\n                \"Admin {} tried to delete itself\".format(g.user.username))\n            abort(403)\n        elif user:\n            db.session.delete(user)\n            db.session.commit()\n            flash(gettext(\"Deleted user '{user}'.\").format(\n                user=user.username), \"notification\")\n        else:\n            current_app.logger.error(\n                \"Admin {} tried to delete nonexistent user with pk={}\".format(\n                    g.user.username, user_id))\n            abort(404)\n\n        return redirect(url_for('admin.index'))\n\n    @view.route('/edit/<int:user_id>/new-password', methods=('POST',))\n    @admin_required\n    def new_password(user_id: int) -> werkzeug.Response:\n        try:\n            user = Journalist.query.get(user_id)\n        except NoResultFound:\n            abort(404)\n\n        password = request.form.get('password')\n        if set_diceware_password(user, password) is not False:\n            if user.last_token is not None:\n                revoke_token(user, user.last_token)\n            user.session_nonce += 1\n            db.session.commit()\n        return redirect(url_for('admin.edit_user', user_id=user_id))\n\n    @view.route('/ossec-test')\n    @admin_required\n    def ossec_test() -> werkzeug.Response:\n        current_app.logger.error('This is a test OSSEC alert')\n        flash(gettext('Test alert sent. Please check your email.'),\n              'testalert-notification')\n        return redirect(url_for('admin.manage_config') + \"#config-testalert\")\n\n    return view\n", "path": "securedrop/journalist_app/admin.py"}]}