We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
SI issuing a 500 error and then timing out on `develop`
## Description
The SI  is failing to start from the `develop` branch, first failing with a 500 error and the log messages below (with logs enabled, and then just timing out:
```
[Fri Feb 03 21:37:34.589966 2023] [wsgi:info] [pid 1330:tid 118957187065600] [remote 127.0.0.1:51894] mod_wsgi (pid=1330, process='source', application='localhost|'): Loading Python script file '/var/www/source.wsgi'.
[Fri Feb 03 21:37:34.591940 2023] [wsgi:error] [pid 1330:tid 118957187065600] [remote 127.0.0.1:51894] mod_wsgi (pid=1330): Failed to exec Python script file '/var/www/source.wsgi'.
[Fri Feb 03 21:37:34.592605 2023] [wsgi:error] [pid 1330:tid 118957187065600] [remote 127.0.0.1:51894] mod_wsgi (pid=1330): Exception occurred processing WSGI script '/var/www/source.wsgi'.
[Fri Feb 03 21:37:34.593310 2023] [wsgi:error] [pid 1330:tid 118957187065600] [remote 127.0.0.1:51894] Traceback (most recent call last):
[Fri Feb 03 21:37:34.593658 2023] [wsgi:error] [pid 1330:tid 118957187065600] [remote 127.0.0.1:51894]   File "/var/www/source.wsgi", line 11, in <module>
[Fri Feb 03 21:37:34.593894 2023] [wsgi:error] [pid 1330:tid 118957187065600] [remote 127.0.0.1:51894]     from source import app as application
[Fri Feb 03 21:37:34.594012 2023] [wsgi:error] [pid 1330:tid 118957187065600] [remote 127.0.0.1:51894] ImportError: cannot import name 'app' from 'source' (/var/www/securedrop/source.py)
```

## Steps to Reproduce

- set up a prod VM instance on `.2.5.1` 
- build packages from `develop` with `make build-debs`
- step through the upgrade scenario to apply said debs

## Expected Behavior
- system is upgraded cleanly and SI and JI functionality is available

## Actual Behavior
- JI is available, SI errors out with an initial 500 error as described.

## Comments

This looks to have been introduced in https://github.com/freedomofpress/securedrop/pull/6563, specifically in the change in https://github.com/freedomofpress/securedrop/blob/c9e2a7a61c486e38e4fcf06b6196b59a115ce152/securedrop/source.py#L6 . Since `app` is now defined only within the `__name__ == 'main'` block, it isn't defined when run via WSGI (as opposed to directly within the dev env).


--- 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.py`
Content:
```
1 from encryption import EncryptionManager, GpgKeyNotFoundError
2 from execution import asynchronous
3 from journalist_app import create_app
4 from models import Source
5 from sdconfig import SecureDropConfig
6 
7 config = SecureDropConfig.get_current()
8 app = create_app(config)
9 
10 
11 @asynchronous
12 def prime_keycache() -> None:
13     """Pre-load the source public keys into Redis."""
14     with app.app_context():
15         encryption_mgr = EncryptionManager.get_default()
16         for source in Source.query.filter_by(pending=False, deleted_at=None).all():
17             try:
18                 encryption_mgr.get_source_public_key(source.filesystem_id)
19             except GpgKeyNotFoundError:
20                 pass
21 
22 
23 prime_keycache()
24 
25 
26 if __name__ == "__main__":  # pragma: no cover
27     debug = getattr(config, "env", "prod") != "prod"
28     # nosemgrep: python.flask.security.audit.app-run-param-config.avoid_app_run_with_bad_host
29     app.run(debug=debug, host="0.0.0.0", port=8081)  # nosec
30
```

Path: `securedrop/source.py`
Content:
```
1 from sdconfig import SecureDropConfig
2 from source_app import create_app
3 
4 if __name__ == "__main__":  # pragma: no cover
5     config = SecureDropConfig.get_current()
6     app = create_app(config)
7     debug = getattr(config, "env", "prod") != "prod"
8     # nosemgrep: python.flask.security.audit.app-run-param-config.avoid_app_run_with_bad_host
9     app.run(debug=debug, host="0.0.0.0", port=8080)  # nosec
10
```

Path: `securedrop/source_app/__init__.py`
Content:
```
1 import os
2 import time
3 from pathlib import Path
4 from typing import Optional, Tuple
5 
6 import i18n
7 import template_filters
8 import version
9 import werkzeug
10 from db import db
11 from flask import Flask, g, redirect, render_template, request, session, url_for
12 from flask_babel import gettext
13 from flask_wtf.csrf import CSRFError, CSRFProtect
14 from models import InstanceConfig
15 from request_that_secures_file_uploads import RequestThatSecuresFileUploads
16 from sdconfig import SecureDropConfig
17 from source_app import api, info, main
18 from source_app.decorators import ignore_static
19 from source_app.utils import clear_session_and_redirect_to_logged_out_page
20 
21 
22 def get_logo_url(app: Flask) -> str:
23     if not app.static_folder:
24         raise FileNotFoundError
25 
26     custom_logo_filename = "i/custom_logo.png"
27     default_logo_filename = "i/logo.png"
28     custom_logo_path = Path(app.static_folder) / custom_logo_filename
29     default_logo_path = Path(app.static_folder) / default_logo_filename
30     if custom_logo_path.is_file():
31         return url_for("static", filename=custom_logo_filename)
32     elif default_logo_path.is_file():
33         return url_for("static", filename=default_logo_filename)
34 
35     raise FileNotFoundError
36 
37 
38 def create_app(config: SecureDropConfig) -> Flask:
39     app = Flask(
40         __name__,
41         template_folder=str(config.SOURCE_TEMPLATES_DIR.absolute()),
42         static_folder=config.STATIC_DIR.absolute(),
43     )
44     app.request_class = RequestThatSecuresFileUploads
45     app.config.from_object(config.SOURCE_APP_FLASK_CONFIG_CLS)
46 
47     i18n.configure(config, app)
48 
49     @app.before_request
50     @ignore_static
51     def setup_i18n() -> None:
52         """Store i18n-related values in Flask's special g object"""
53         i18n.set_locale(config)
54 
55     # The default CSRF token expiration is 1 hour. Since large uploads can
56     # take longer than an hour over Tor, we increase the valid window to 24h.
57     app.config["WTF_CSRF_TIME_LIMIT"] = 60 * 60 * 24
58     CSRFProtect(app)
59 
60     app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
61     app.config["SQLALCHEMY_DATABASE_URI"] = config.DATABASE_URI
62     db.init_app(app)
63 
64     @app.errorhandler(CSRFError)
65     def handle_csrf_error(e: CSRFError) -> werkzeug.Response:
66         return clear_session_and_redirect_to_logged_out_page(flask_session=session)
67 
68     app.jinja_env.trim_blocks = True
69     app.jinja_env.lstrip_blocks = True
70     app.jinja_env.globals["version"] = version.__version__
71     # Exported to source templates for being included in instructions
72     app.jinja_env.globals["submission_key_fpr"] = config.JOURNALIST_KEY
73     app.jinja_env.filters["rel_datetime_format"] = template_filters.rel_datetime_format
74     app.jinja_env.filters["nl2br"] = template_filters.nl2br
75     app.jinja_env.filters["filesizeformat"] = template_filters.filesizeformat
76     app.jinja_env.filters["html_datetime_format"] = template_filters.html_datetime_format
77     app.jinja_env.add_extension("jinja2.ext.do")
78 
79     for module in [main, info, api]:
80         app.register_blueprint(module.make_blueprint(config))  # type: ignore
81 
82     # before_request hooks are executed in order of declaration, so set up g object
83     # before the potential tor2web 403 response.
84     @app.before_request
85     @ignore_static
86     def setup_g() -> Optional[werkzeug.Response]:
87         if InstanceConfig.get_default(refresh=True).organization_name:
88             g.organization_name = (  # pylint: disable=assigning-non-slot
89                 InstanceConfig.get_default().organization_name
90             )
91         else:
92             g.organization_name = gettext("SecureDrop")  # pylint: disable=assigning-non-slot
93 
94         try:
95             g.logo = get_logo_url(app)  # pylint: disable=assigning-non-slot
96         except FileNotFoundError:
97             app.logger.error("Site logo not found.")
98 
99         return None
100 
101     @app.before_request
102     @ignore_static
103     def check_tor2web() -> Optional[werkzeug.Response]:
104         # TODO: expand header checking logic to catch modern tor2web proxies
105         if "X-tor2web" in request.headers:
106             if request.path != url_for("info.tor2web_warning"):
107                 return redirect(url_for("info.tor2web_warning"))
108         return None
109 
110     @app.errorhandler(404)
111     def page_not_found(error: werkzeug.exceptions.HTTPException) -> Tuple[str, int]:
112         return render_template("notfound.html"), 404
113 
114     @app.errorhandler(500)
115     def internal_error(error: werkzeug.exceptions.HTTPException) -> Tuple[str, int]:
116         return render_template("error.html"), 500
117 
118     # Obscure the creation time of source private keys by touching them all
119     # on startup.
120     private_keys = config.GPG_KEY_DIR / "private-keys-v1.d"
121     now = time.time()
122     for entry in os.scandir(private_keys):
123         if not entry.is_file() or not entry.name.endswith(".key"):
124             continue
125         os.utime(entry.path, times=(now, now))
126         # So the ctime is also updated
127         os.chmod(entry.path, entry.stat().st_mode)
128 
129     return app
130
```
--- 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.py b/securedrop/journalist.py
--- a/securedrop/journalist.py
+++ b/securedrop/journalist.py
@@ -5,6 +5,7 @@
 from sdconfig import SecureDropConfig
 
 config = SecureDropConfig.get_current()
+# app is imported by journalist.wsgi
 app = create_app(config)
 
 
diff --git a/securedrop/source.py b/securedrop/source.py
--- a/securedrop/source.py
+++ b/securedrop/source.py
@@ -1,9 +1,11 @@
 from sdconfig import SecureDropConfig
 from source_app import create_app
 
+config = SecureDropConfig.get_current()
+# app is imported by source.wsgi
+app = create_app(config)
+
 if __name__ == "__main__":  # pragma: no cover
-    config = SecureDropConfig.get_current()
-    app = create_app(config)
     debug = getattr(config, "env", "prod") != "prod"
     # nosemgrep: python.flask.security.audit.app-run-param-config.avoid_app_run_with_bad_host
     app.run(debug=debug, host="0.0.0.0", port=8080)  # nosec
diff --git a/securedrop/source_app/__init__.py b/securedrop/source_app/__init__.py
--- a/securedrop/source_app/__init__.py
+++ b/securedrop/source_app/__init__.py
@@ -118,12 +118,15 @@
     # Obscure the creation time of source private keys by touching them all
     # on startup.
     private_keys = config.GPG_KEY_DIR / "private-keys-v1.d"
-    now = time.time()
-    for entry in os.scandir(private_keys):
-        if not entry.is_file() or not entry.name.endswith(".key"):
-            continue
-        os.utime(entry.path, times=(now, now))
-        # So the ctime is also updated
-        os.chmod(entry.path, entry.stat().st_mode)
+    # The folder may not exist yet in some dev/testing setups,
+    # and if it doesn't exist there's no mtime to obscure.
+    if private_keys.is_dir():
+        now = time.time()
+        for entry in os.scandir(private_keys):
+            if not entry.is_file() or not entry.name.endswith(".key"):
+                continue
+            os.utime(entry.path, times=(now, now))
+            # So the ctime is also updated
+            os.chmod(entry.path, entry.stat().st_mode)
 
     return app

{"golden_diff": "diff --git a/securedrop/journalist.py b/securedrop/journalist.py\n--- a/securedrop/journalist.py\n+++ b/securedrop/journalist.py\n@@ -5,6 +5,7 @@\n from sdconfig import SecureDropConfig\n \n config = SecureDropConfig.get_current()\n+# app is imported by journalist.wsgi\n app = create_app(config)\n \n \ndiff --git a/securedrop/source.py b/securedrop/source.py\n--- a/securedrop/source.py\n+++ b/securedrop/source.py\n@@ -1,9 +1,11 @@\n from sdconfig import SecureDropConfig\n from source_app import create_app\n \n+config = SecureDropConfig.get_current()\n+# app is imported by source.wsgi\n+app = create_app(config)\n+\n if __name__ == \"__main__\":  # pragma: no cover\n-    config = SecureDropConfig.get_current()\n-    app = create_app(config)\n     debug = getattr(config, \"env\", \"prod\") != \"prod\"\n     # nosemgrep: python.flask.security.audit.app-run-param-config.avoid_app_run_with_bad_host\n     app.run(debug=debug, host=\"0.0.0.0\", port=8080)  # nosec\ndiff --git a/securedrop/source_app/__init__.py b/securedrop/source_app/__init__.py\n--- a/securedrop/source_app/__init__.py\n+++ b/securedrop/source_app/__init__.py\n@@ -118,12 +118,15 @@\n     # Obscure the creation time of source private keys by touching them all\n     # on startup.\n     private_keys = config.GPG_KEY_DIR / \"private-keys-v1.d\"\n-    now = time.time()\n-    for entry in os.scandir(private_keys):\n-        if not entry.is_file() or not entry.name.endswith(\".key\"):\n-            continue\n-        os.utime(entry.path, times=(now, now))\n-        # So the ctime is also updated\n-        os.chmod(entry.path, entry.stat().st_mode)\n+    # The folder may not exist yet in some dev/testing setups,\n+    # and if it doesn't exist there's no mtime to obscure.\n+    if private_keys.is_dir():\n+        now = time.time()\n+        for entry in os.scandir(private_keys):\n+            if not entry.is_file() or not entry.name.endswith(\".key\"):\n+                continue\n+            os.utime(entry.path, times=(now, now))\n+            # So the ctime is also updated\n+            os.chmod(entry.path, entry.stat().st_mode)\n \n     return app\n", "issue": "SI issuing a 500 error and then timing out on `develop`\n## Description\r\nThe SI  is failing to start from the `develop` branch, first failing with a 500 error and the log messages below (with logs enabled, and then just timing out:\r\n```\r\n[Fri Feb 03 21:37:34.589966 2023] [wsgi:info] [pid 1330:tid 118957187065600] [remote 127.0.0.1:51894] mod_wsgi (pid=1330, process='source', application='localhost|'): Loading Python script file '/var/www/source.wsgi'.\r\n[Fri Feb 03 21:37:34.591940 2023] [wsgi:error] [pid 1330:tid 118957187065600] [remote 127.0.0.1:51894] mod_wsgi (pid=1330): Failed to exec Python script file '/var/www/source.wsgi'.\r\n[Fri Feb 03 21:37:34.592605 2023] [wsgi:error] [pid 1330:tid 118957187065600] [remote 127.0.0.1:51894] mod_wsgi (pid=1330): Exception occurred processing WSGI script '/var/www/source.wsgi'.\r\n[Fri Feb 03 21:37:34.593310 2023] [wsgi:error] [pid 1330:tid 118957187065600] [remote 127.0.0.1:51894] Traceback (most recent call last):\r\n[Fri Feb 03 21:37:34.593658 2023] [wsgi:error] [pid 1330:tid 118957187065600] [remote 127.0.0.1:51894]   File \"/var/www/source.wsgi\", line 11, in <module>\r\n[Fri Feb 03 21:37:34.593894 2023] [wsgi:error] [pid 1330:tid 118957187065600] [remote 127.0.0.1:51894]     from source import app as application\r\n[Fri Feb 03 21:37:34.594012 2023] [wsgi:error] [pid 1330:tid 118957187065600] [remote 127.0.0.1:51894] ImportError: cannot import name 'app' from 'source' (/var/www/securedrop/source.py)\r\n```\r\n\r\n## Steps to Reproduce\r\n\r\n- set up a prod VM instance on `.2.5.1` \r\n- build packages from `develop` with `make build-debs`\r\n- step through the upgrade scenario to apply said debs\r\n\r\n## Expected Behavior\r\n- system is upgraded cleanly and SI and JI functionality is available\r\n\r\n## Actual Behavior\r\n- JI is available, SI errors out with an initial 500 error as described.\r\n\r\n## Comments\r\n\r\nThis looks to have been introduced in https://github.com/freedomofpress/securedrop/pull/6563, specifically in the change in https://github.com/freedomofpress/securedrop/blob/c9e2a7a61c486e38e4fcf06b6196b59a115ce152/securedrop/source.py#L6 . Since `app` is now defined only within the `__name__ == 'main'` block, it isn't defined when run via WSGI (as opposed to directly within the dev env).\r\n\n", "before_files": [{"content": "from encryption import EncryptionManager, GpgKeyNotFoundError\nfrom execution import asynchronous\nfrom journalist_app import create_app\nfrom models import Source\nfrom sdconfig import SecureDropConfig\n\nconfig = SecureDropConfig.get_current()\napp = create_app(config)\n\n\n@asynchronous\ndef prime_keycache() -> None:\n    \"\"\"Pre-load the source public keys into Redis.\"\"\"\n    with app.app_context():\n        encryption_mgr = EncryptionManager.get_default()\n        for source in Source.query.filter_by(pending=False, deleted_at=None).all():\n            try:\n                encryption_mgr.get_source_public_key(source.filesystem_id)\n            except GpgKeyNotFoundError:\n                pass\n\n\nprime_keycache()\n\n\nif __name__ == \"__main__\":  # pragma: no cover\n    debug = getattr(config, \"env\", \"prod\") != \"prod\"\n    # nosemgrep: python.flask.security.audit.app-run-param-config.avoid_app_run_with_bad_host\n    app.run(debug=debug, host=\"0.0.0.0\", port=8081)  # nosec\n", "path": "securedrop/journalist.py"}, {"content": "from sdconfig import SecureDropConfig\nfrom source_app import create_app\n\nif __name__ == \"__main__\":  # pragma: no cover\n    config = SecureDropConfig.get_current()\n    app = create_app(config)\n    debug = getattr(config, \"env\", \"prod\") != \"prod\"\n    # nosemgrep: python.flask.security.audit.app-run-param-config.avoid_app_run_with_bad_host\n    app.run(debug=debug, host=\"0.0.0.0\", port=8080)  # nosec\n", "path": "securedrop/source.py"}, {"content": "import os\nimport time\nfrom pathlib import Path\nfrom typing import Optional, Tuple\n\nimport i18n\nimport template_filters\nimport version\nimport werkzeug\nfrom db import db\nfrom flask import Flask, g, redirect, render_template, request, session, url_for\nfrom flask_babel import gettext\nfrom flask_wtf.csrf import CSRFError, CSRFProtect\nfrom models import InstanceConfig\nfrom request_that_secures_file_uploads import RequestThatSecuresFileUploads\nfrom sdconfig import SecureDropConfig\nfrom source_app import api, info, main\nfrom source_app.decorators import ignore_static\nfrom source_app.utils import clear_session_and_redirect_to_logged_out_page\n\n\ndef get_logo_url(app: Flask) -> str:\n    if not app.static_folder:\n        raise FileNotFoundError\n\n    custom_logo_filename = \"i/custom_logo.png\"\n    default_logo_filename = \"i/logo.png\"\n    custom_logo_path = Path(app.static_folder) / custom_logo_filename\n    default_logo_path = Path(app.static_folder) / default_logo_filename\n    if custom_logo_path.is_file():\n        return url_for(\"static\", filename=custom_logo_filename)\n    elif default_logo_path.is_file():\n        return url_for(\"static\", filename=default_logo_filename)\n\n    raise FileNotFoundError\n\n\ndef create_app(config: SecureDropConfig) -> Flask:\n    app = Flask(\n        __name__,\n        template_folder=str(config.SOURCE_TEMPLATES_DIR.absolute()),\n        static_folder=config.STATIC_DIR.absolute(),\n    )\n    app.request_class = RequestThatSecuresFileUploads\n    app.config.from_object(config.SOURCE_APP_FLASK_CONFIG_CLS)\n\n    i18n.configure(config, app)\n\n    @app.before_request\n    @ignore_static\n    def setup_i18n() -> None:\n        \"\"\"Store i18n-related values in Flask's special g object\"\"\"\n        i18n.set_locale(config)\n\n    # The default CSRF token expiration is 1 hour. Since large uploads can\n    # take longer than an hour over Tor, we increase the valid window to 24h.\n    app.config[\"WTF_CSRF_TIME_LIMIT\"] = 60 * 60 * 24\n    CSRFProtect(app)\n\n    app.config[\"SQLALCHEMY_TRACK_MODIFICATIONS\"] = False\n    app.config[\"SQLALCHEMY_DATABASE_URI\"] = config.DATABASE_URI\n    db.init_app(app)\n\n    @app.errorhandler(CSRFError)\n    def handle_csrf_error(e: CSRFError) -> werkzeug.Response:\n        return clear_session_and_redirect_to_logged_out_page(flask_session=session)\n\n    app.jinja_env.trim_blocks = True\n    app.jinja_env.lstrip_blocks = True\n    app.jinja_env.globals[\"version\"] = version.__version__\n    # Exported to source templates for being included in instructions\n    app.jinja_env.globals[\"submission_key_fpr\"] = config.JOURNALIST_KEY\n    app.jinja_env.filters[\"rel_datetime_format\"] = template_filters.rel_datetime_format\n    app.jinja_env.filters[\"nl2br\"] = template_filters.nl2br\n    app.jinja_env.filters[\"filesizeformat\"] = template_filters.filesizeformat\n    app.jinja_env.filters[\"html_datetime_format\"] = template_filters.html_datetime_format\n    app.jinja_env.add_extension(\"jinja2.ext.do\")\n\n    for module in [main, info, api]:\n        app.register_blueprint(module.make_blueprint(config))  # type: ignore\n\n    # before_request hooks are executed in order of declaration, so set up g object\n    # before the potential tor2web 403 response.\n    @app.before_request\n    @ignore_static\n    def setup_g() -> Optional[werkzeug.Response]:\n        if InstanceConfig.get_default(refresh=True).organization_name:\n            g.organization_name = (  # pylint: disable=assigning-non-slot\n                InstanceConfig.get_default().organization_name\n            )\n        else:\n            g.organization_name = gettext(\"SecureDrop\")  # pylint: disable=assigning-non-slot\n\n        try:\n            g.logo = get_logo_url(app)  # pylint: disable=assigning-non-slot\n        except FileNotFoundError:\n            app.logger.error(\"Site logo not found.\")\n\n        return None\n\n    @app.before_request\n    @ignore_static\n    def check_tor2web() -> Optional[werkzeug.Response]:\n        # TODO: expand header checking logic to catch modern tor2web proxies\n        if \"X-tor2web\" in request.headers:\n            if request.path != url_for(\"info.tor2web_warning\"):\n                return redirect(url_for(\"info.tor2web_warning\"))\n        return None\n\n    @app.errorhandler(404)\n    def page_not_found(error: werkzeug.exceptions.HTTPException) -> Tuple[str, int]:\n        return render_template(\"notfound.html\"), 404\n\n    @app.errorhandler(500)\n    def internal_error(error: werkzeug.exceptions.HTTPException) -> Tuple[str, int]:\n        return render_template(\"error.html\"), 500\n\n    # Obscure the creation time of source private keys by touching them all\n    # on startup.\n    private_keys = config.GPG_KEY_DIR / \"private-keys-v1.d\"\n    now = time.time()\n    for entry in os.scandir(private_keys):\n        if not entry.is_file() or not entry.name.endswith(\".key\"):\n            continue\n        os.utime(entry.path, times=(now, now))\n        # So the ctime is also updated\n        os.chmod(entry.path, entry.stat().st_mode)\n\n    return app\n", "path": "securedrop/source_app/__init__.py"}], "after_files": [{"content": "from encryption import EncryptionManager, GpgKeyNotFoundError\nfrom execution import asynchronous\nfrom journalist_app import create_app\nfrom models import Source\nfrom sdconfig import SecureDropConfig\n\nconfig = SecureDropConfig.get_current()\n# app is imported by journalist.wsgi\napp = create_app(config)\n\n\n@asynchronous\ndef prime_keycache() -> None:\n    \"\"\"Pre-load the source public keys into Redis.\"\"\"\n    with app.app_context():\n        encryption_mgr = EncryptionManager.get_default()\n        for source in Source.query.filter_by(pending=False, deleted_at=None).all():\n            try:\n                encryption_mgr.get_source_public_key(source.filesystem_id)\n            except GpgKeyNotFoundError:\n                pass\n\n\nprime_keycache()\n\n\nif __name__ == \"__main__\":  # pragma: no cover\n    debug = getattr(config, \"env\", \"prod\") != \"prod\"\n    # nosemgrep: python.flask.security.audit.app-run-param-config.avoid_app_run_with_bad_host\n    app.run(debug=debug, host=\"0.0.0.0\", port=8081)  # nosec\n", "path": "securedrop/journalist.py"}, {"content": "from sdconfig import SecureDropConfig\nfrom source_app import create_app\n\nconfig = SecureDropConfig.get_current()\n# app is imported by source.wsgi\napp = create_app(config)\n\nif __name__ == \"__main__\":  # pragma: no cover\n    debug = getattr(config, \"env\", \"prod\") != \"prod\"\n    # nosemgrep: python.flask.security.audit.app-run-param-config.avoid_app_run_with_bad_host\n    app.run(debug=debug, host=\"0.0.0.0\", port=8080)  # nosec\n", "path": "securedrop/source.py"}, {"content": "import os\nimport time\nfrom pathlib import Path\nfrom typing import Optional, Tuple\n\nimport i18n\nimport template_filters\nimport version\nimport werkzeug\nfrom db import db\nfrom flask import Flask, g, redirect, render_template, request, session, url_for\nfrom flask_babel import gettext\nfrom flask_wtf.csrf import CSRFError, CSRFProtect\nfrom models import InstanceConfig\nfrom request_that_secures_file_uploads import RequestThatSecuresFileUploads\nfrom sdconfig import SecureDropConfig\nfrom source_app import api, info, main\nfrom source_app.decorators import ignore_static\nfrom source_app.utils import clear_session_and_redirect_to_logged_out_page\n\n\ndef get_logo_url(app: Flask) -> str:\n    if not app.static_folder:\n        raise FileNotFoundError\n\n    custom_logo_filename = \"i/custom_logo.png\"\n    default_logo_filename = \"i/logo.png\"\n    custom_logo_path = Path(app.static_folder) / custom_logo_filename\n    default_logo_path = Path(app.static_folder) / default_logo_filename\n    if custom_logo_path.is_file():\n        return url_for(\"static\", filename=custom_logo_filename)\n    elif default_logo_path.is_file():\n        return url_for(\"static\", filename=default_logo_filename)\n\n    raise FileNotFoundError\n\n\ndef create_app(config: SecureDropConfig) -> Flask:\n    app = Flask(\n        __name__,\n        template_folder=str(config.SOURCE_TEMPLATES_DIR.absolute()),\n        static_folder=config.STATIC_DIR.absolute(),\n    )\n    app.request_class = RequestThatSecuresFileUploads\n    app.config.from_object(config.SOURCE_APP_FLASK_CONFIG_CLS)\n\n    i18n.configure(config, app)\n\n    @app.before_request\n    @ignore_static\n    def setup_i18n() -> None:\n        \"\"\"Store i18n-related values in Flask's special g object\"\"\"\n        i18n.set_locale(config)\n\n    # The default CSRF token expiration is 1 hour. Since large uploads can\n    # take longer than an hour over Tor, we increase the valid window to 24h.\n    app.config[\"WTF_CSRF_TIME_LIMIT\"] = 60 * 60 * 24\n    CSRFProtect(app)\n\n    app.config[\"SQLALCHEMY_TRACK_MODIFICATIONS\"] = False\n    app.config[\"SQLALCHEMY_DATABASE_URI\"] = config.DATABASE_URI\n    db.init_app(app)\n\n    @app.errorhandler(CSRFError)\n    def handle_csrf_error(e: CSRFError) -> werkzeug.Response:\n        return clear_session_and_redirect_to_logged_out_page(flask_session=session)\n\n    app.jinja_env.trim_blocks = True\n    app.jinja_env.lstrip_blocks = True\n    app.jinja_env.globals[\"version\"] = version.__version__\n    # Exported to source templates for being included in instructions\n    app.jinja_env.globals[\"submission_key_fpr\"] = config.JOURNALIST_KEY\n    app.jinja_env.filters[\"rel_datetime_format\"] = template_filters.rel_datetime_format\n    app.jinja_env.filters[\"nl2br\"] = template_filters.nl2br\n    app.jinja_env.filters[\"filesizeformat\"] = template_filters.filesizeformat\n    app.jinja_env.filters[\"html_datetime_format\"] = template_filters.html_datetime_format\n    app.jinja_env.add_extension(\"jinja2.ext.do\")\n\n    for module in [main, info, api]:\n        app.register_blueprint(module.make_blueprint(config))  # type: ignore\n\n    # before_request hooks are executed in order of declaration, so set up g object\n    # before the potential tor2web 403 response.\n    @app.before_request\n    @ignore_static\n    def setup_g() -> Optional[werkzeug.Response]:\n        if InstanceConfig.get_default(refresh=True).organization_name:\n            g.organization_name = (  # pylint: disable=assigning-non-slot\n                InstanceConfig.get_default().organization_name\n            )\n        else:\n            g.organization_name = gettext(\"SecureDrop\")  # pylint: disable=assigning-non-slot\n\n        try:\n            g.logo = get_logo_url(app)  # pylint: disable=assigning-non-slot\n        except FileNotFoundError:\n            app.logger.error(\"Site logo not found.\")\n\n        return None\n\n    @app.before_request\n    @ignore_static\n    def check_tor2web() -> Optional[werkzeug.Response]:\n        # TODO: expand header checking logic to catch modern tor2web proxies\n        if \"X-tor2web\" in request.headers:\n            if request.path != url_for(\"info.tor2web_warning\"):\n                return redirect(url_for(\"info.tor2web_warning\"))\n        return None\n\n    @app.errorhandler(404)\n    def page_not_found(error: werkzeug.exceptions.HTTPException) -> Tuple[str, int]:\n        return render_template(\"notfound.html\"), 404\n\n    @app.errorhandler(500)\n    def internal_error(error: werkzeug.exceptions.HTTPException) -> Tuple[str, int]:\n        return render_template(\"error.html\"), 500\n\n    # Obscure the creation time of source private keys by touching them all\n    # on startup.\n    private_keys = config.GPG_KEY_DIR / \"private-keys-v1.d\"\n    # The folder may not exist yet in some dev/testing setups,\n    # and if it doesn't exist there's no mtime to obscure.\n    if private_keys.is_dir():\n        now = time.time()\n        for entry in os.scandir(private_keys):\n            if not entry.is_file() or not entry.name.endswith(\".key\"):\n                continue\n            os.utime(entry.path, times=(now, now))\n            # So the ctime is also updated\n            os.chmod(entry.path, entry.stat().st_mode)\n\n    return app\n", "path": "securedrop/source_app/__init__.py"}]}