diff --git a/.githooks/pre-commit b/.githooks/pre-commit
index c34cdcfd94..108c6319e7 100755
--- a/.githooks/pre-commit
+++ b/.githooks/pre-commit
@@ -10,12 +10,9 @@ if [[ -n "$PY_FILES" ]]; then
if [[ ! -v VIRTUAL_ENV ]]; then
source .venv/bin/activate
fi
- echo "Checking:"
- echo "$PY_FILES"
- # Run black against changed python files for this commit
- black --check --diff ${PY_FILES//$'\n'/ }
# Run ruff (against all files, it's fast enough)
- ruff check . --diff && echo "ruff passed!"
+ ruff format . && ruff check . --diff \
+ && echo "ruff passed!"
else
exit 0
fi
diff --git a/Makefile b/Makefile
index c22c3adf3d..9d091fa15c 100644
--- a/Makefile
+++ b/Makefile
@@ -61,16 +61,6 @@ update-pip-requirements: update-admin-pip-requirements update-python3-requiremen
#
#################
-.PHONY: check-black
-check-black: ## Check Python source code formatting with black
- @echo "███ Running black check..."
- @black --check --diff .
- @echo
-
-.PHONY: black
-black: ## Update Python source code formatting with black
- @black securedrop .
-
.PHONY: ansible-config-lint
ansible-config-lint: ## Run custom Ansible linting tasks.
@echo "███ Linting Ansible configuration..."
@@ -94,15 +84,19 @@ app-lint-full: ## Test pylint compliance, with no checks disabled.
@echo
.PHONY: check-ruff
-check-ruff: ## Lint Python source files.
+check-ruff: ## Check linting and formatting of Python source files.
@echo "███ Running ruff..."
- @ruff check . --show-source
+ @ruff format . --diff
+ @ruff check .
@echo
.PHONY: ruff
ruff: ## Update Python source file formatting.
+ @ruff format .
@ruff check . --fix
+fix: ruff ## Apply automatic fixes.
+
# The --disable=names is required to use the BEM syntax
# # https://csswizardry.com/2013/01/mindbemding-getting-your-head-round-bem-syntax/
.PHONY: html-lint
@@ -139,7 +133,7 @@ yamllint: ## Lint YAML files (does not validate syntax!).
# While the order mostly doesn't matter here, keep "check-ruff" first, since it
# gives the broadest coverage and runs (and therefore fails) fastest.
.PHONY: lint
-lint: check-ruff ansible-config-lint app-lint check-black html-lint shellcheck typelint yamllint check-strings check-supported-locales check-desktop-files ## Runs all lint checks
+lint: check-ruff ansible-config-lint app-lint html-lint shellcheck typelint yamllint check-strings check-supported-locales check-desktop-files ## Runs all lint checks
.PHONY: safety
safety: ## Run `safety check` to check python dependencies for vulnerabilities.
diff --git a/admin/bootstrap.py b/admin/bootstrap.py
index a85f2be7a5..838a57b408 100755
--- a/admin/bootstrap.py
+++ b/admin/bootstrap.py
@@ -117,10 +117,7 @@ def is_missing_dependency() -> bool:
return True
# If any packages couldn't be found, it may point to an apt cache issue
- if "Unable to locate package" in apt_query_result.stderr:
- return True
-
- return False
+ return "Unable to locate package" in apt_query_result.stderr
except subprocess.CalledProcessError as e:
sdlog.error("Error checking apt dependencies")
@@ -185,7 +182,6 @@ def envsetup(args: argparse.Namespace, virtualenv_dir: str = VENV_DIR) -> None:
# virtualenv doesnt exist? Install dependencies and create
if not os.path.exists(virtualenv_dir):
-
# Technically you can create a virtualenv from within python
# but pip can only be run over Tor on Tails, and debugging that
# along with instaling a third-party dependency is not worth
@@ -260,12 +256,12 @@ def install_pip_dependencies(
]
ansible_ver = subprocess.run(
- maybe_torify() + ansible_vercheck_cmd, text=True, capture_output=True
+ maybe_torify() + ansible_vercheck_cmd, text=True, capture_output=True, check=False
)
if ansible_ver.stdout.startswith("2.9"):
sdlog.info("Ansible is out-of-date, removing it.")
delete_result = subprocess.run(
- maybe_torify() + ansible_uninstall_cmd, capture_output=True, text=True
+ maybe_torify() + ansible_uninstall_cmd, capture_output=True, text=True, check=False
)
if delete_result.returncode != 0:
sdlog.error(
diff --git a/admin/securedrop_admin/__init__.py b/admin/securedrop_admin/__init__.py
index be35b16a66..ae69c6ba99 100755
--- a/admin/securedrop_admin/__init__.py
+++ b/admin/securedrop_admin/__init__.py
@@ -66,7 +66,7 @@
# Check OpenSSH version - ansible requires an extra argument for scp on OpenSSH 9
def openssh_version() -> int:
try:
- result = subprocess.run(["ssh", "-V"], capture_output=True, text=True)
+ result = subprocess.run(["ssh", "-V"], capture_output=True, text=True, check=False)
if result.stderr.startswith("OpenSSH_9"):
return 9
elif result.stderr.startswith("OpenSSH_8"):
@@ -75,7 +75,6 @@ def openssh_version() -> int:
return 0
except subprocess.CalledProcessError:
return 0
- pass
return 0
@@ -130,14 +129,14 @@ def validate(self, document: Document) -> bool:
class ValidateTime(Validator):
def validate(self, document: Document) -> bool:
- if document.text.isdigit() and int(document.text) in range(0, 24):
+ if document.text.isdigit() and int(document.text) in range(24):
return True
raise ValidationError(message="Must be an integer between 0 and 23")
class ValidateUser(Validator):
def validate(self, document: Document) -> bool:
text = document.text
- if text != "" and text != "root" and text != "amnesia":
+ if text not in ("", "root", "amnesia"):
return True
raise ValidationError(message="Must not be root, amnesia or an empty string")
@@ -160,8 +159,8 @@ def validate(self, document: Document) -> bool:
raise ValidationError(
message=(
"DNS server(s) should be a space/comma-separated list "
- "of up to {} IP addresses"
- ).format(MAX_NAMESERVERS)
+ f"of up to {MAX_NAMESERVERS} IP addresses"
+ )
)
return True
@@ -194,7 +193,7 @@ def validate(self, document: Document) -> bool:
class ValidateYesNo(Validator):
def validate(self, document: Document) -> bool:
text = document.text.lower()
- if text == "yes" or text == "no":
+ if text in ("yes", "no"):
return True
raise ValidationError(message="Must be either yes or no")
@@ -769,7 +768,6 @@ def wrapper(*args: Any, **kwargs: Any) -> Any:
update_status, latest_tag = check_for_updates(cli_args)
if update_status is True:
-
# Useful for troubleshooting
branch_status = get_git_branch(cli_args)
@@ -793,7 +791,7 @@ def wrapper(*args: Any, **kwargs: Any) -> Any:
)
sdlog.error(
"If you are certain you want to proceed, run:\n\n\t"
- "./securedrop-admin --force {}\n".format(cmd_name)
+ f"./securedrop-admin --force {cmd_name}\n"
)
sdlog.error("To apply the latest updates, run:\n\n\t" "./securedrop-admin update\n")
sdlog.error(
diff --git a/admin/tests/test_integration.py b/admin/tests/test_integration.py
index 618b547659..9ccff54b44 100644
--- a/admin/tests/test_integration.py
+++ b/admin/tests/test_integration.py
@@ -531,7 +531,7 @@ def test_sdconfig_enable_https_disable_pow_on_source_interface():
"""
-@pytest.fixture()
+@pytest.fixture
def securedrop_git_repo(tmpdir):
cwd = os.getcwd()
os.chdir(str(tmpdir))
diff --git a/admin/tests/test_securedrop-admin.py b/admin/tests/test_securedrop-admin.py
index 5b55807c40..acd65999f2 100644
--- a/admin/tests/test_securedrop-admin.py
+++ b/admin/tests/test_securedrop-admin.py
@@ -76,9 +76,7 @@ def test_update_check_decorator_when_no_update_needed(self, caplog):
"securedrop_admin.check_for_updates", side_effect=[[False, "1.5.0"]]
) as mocked_check, mock.patch(
"securedrop_admin.get_git_branch", side_effect=["develop"]
- ), mock.patch(
- "sys.exit"
- ) as mocked_exit:
+ ), mock.patch("sys.exit") as mocked_exit:
# The decorator itself interprets --force
args = argparse.Namespace(force=False)
rv = securedrop_admin.update_check_required("update_check_test")(lambda _: 100)(args)
@@ -101,9 +99,7 @@ def test_update_check_decorator_when_update_needed(self, caplog):
"securedrop_admin.check_for_updates", side_effect=[[True, "1.5.0"]]
) as mocked_check, mock.patch(
"securedrop_admin.get_git_branch", side_effect=["bad_branch"]
- ), mock.patch(
- "sys.exit"
- ) as mocked_exit:
+ ), mock.patch("sys.exit") as mocked_exit:
# The decorator itself interprets --force
args = argparse.Namespace(force=False)
securedrop_admin.update_check_required("update_check_test")(lambda _: _)(args)
@@ -125,9 +121,7 @@ def test_update_check_decorator_when_skipped(self, caplog):
"securedrop_admin.check_for_updates", side_effect=[[True, "1.5.0"]]
) as mocked_check, mock.patch(
"securedrop_admin.get_git_branch", side_effect=["develop"]
- ), mock.patch(
- "sys.exit"
- ) as mocked_exit:
+ ), mock.patch("sys.exit") as mocked_exit:
# The decorator itself interprets --force
args = argparse.Namespace(force=True)
rv = securedrop_admin.update_check_required("update_check_test")(lambda _: 100)(args)
@@ -847,7 +841,7 @@ def verify_desc_consistency_optional(self, site_config, desc):
if callable(default):
default = default()
assert site_config.user_prompt_config_one(desc, None) == default
- assert type(default) == etype
+ assert type(default) is etype
def verify_desc_consistency(self, site_config, desc):
self.verify_desc_consistency_optional(site_config, desc)
@@ -935,7 +929,7 @@ def verify_desc_consistency_allow_empty(self, site_config, desc):
(var, default, etype, prompt, validator, transform, condition) = desc
# verify the default passes validation
assert site_config.user_prompt_config_one(desc, None) == default
- assert type(default) == etype
+ assert type(default) is etype
def verify_prompt_fingerprint(self, site_config, desc):
self.verify_prompt_not_empty(site_config, desc)
@@ -962,7 +956,7 @@ def verify_prompt_securedrop_supported_locales(self, site_config, desc):
(var, default, etype, prompt, validator, transform, condition) = desc
# verify the default passes validation
assert site_config.user_prompt_config_one(desc, None) == default
- assert type(default) == etype
+ assert type(default) is etype
assert site_config.user_prompt_config_one(desc, "fr_FR en_US") == ["fr_FR", "en_US"]
assert site_config.user_prompt_config_one(desc, ["fr_FR", "en_US"]) == ["fr_FR", "en_US"]
assert site_config.user_prompt_config_one(desc, "") == []
diff --git a/devops/scripts/verify-mo.py b/devops/scripts/verify-mo.py
index 8309e8f5c8..f7cf2ff77e 100755
--- a/devops/scripts/verify-mo.py
+++ b/devops/scripts/verify-mo.py
@@ -21,7 +21,7 @@
import shlex
import subprocess
from pathlib import Path
-from typing import Any, Iterator, Optional, Set
+from typing import Iterator, Set
import polib
from translate.tools.pocompile import convertmo
@@ -59,9 +59,9 @@ def __enter__(self) -> "CatalogVerifier":
def __exit__(
self,
- exc_type: Optional[Any],
- exc_value: Optional[Any],
- traceback: Optional[Any],
+ exc_type: object,
+ exc_value: object,
+ traceback: object,
) -> None:
"""Clean up."""
@@ -113,11 +113,12 @@ def diffoscope_call(
# because we want to inherit the Python virtual environment
# in which we're invoked.
# nosemgrep: python.lang.security.audit.subprocess-shell-true.subprocess-shell-true
- return subprocess.run(
+ return subprocess.run( # noqa: S602
cmd,
capture_output=True,
env=os.environ,
- shell=True, # noqa: S602
+ shell=True,
+ check=False,
)
def reproduce(self) -> None:
diff --git a/install_files/ansible-base/roles/restore/files/compare_torrc.py b/install_files/ansible-base/roles/restore/files/compare_torrc.py
index 53c34aae6c..c46091e60b 100644
--- a/install_files/ansible-base/roles/restore/files/compare_torrc.py
+++ b/install_files/ansible-base/roles/restore/files/compare_torrc.py
@@ -53,15 +53,12 @@ def strset(s):
sys.exit(0)
print(
- "The Tor configuration on the app server offers version {} services.".format(
- strset(server_versions)
- )
+ f"The Tor configuration on the app server offers version {strset(server_versions)} "
+ "services."
)
print(
- "The Tor configuration in this backup offers version {} services.".format(
- strset(backup_versions)
- )
+ f"The Tor configuration in this backup offers version {strset(backup_versions)} services."
)
print("\nIncompatible configuration: Restoring a backup including a different ")
diff --git a/install_files/ansible-base/roles/tails-config/files/securedrop_init.py b/install_files/ansible-base/roles/tails-config/files/securedrop_init.py
index a7d5a68338..813f76e3f9 100644
--- a/install_files/ansible-base/roles/tails-config/files/securedrop_init.py
+++ b/install_files/ansible-base/roles/tails-config/files/securedrop_init.py
@@ -207,7 +207,6 @@
)
if "OK" in verify_proc:
-
# Updating the cert chain requires sudo privileges
os.setresgid(0, 0, -1)
os.setresuid(0, 0, -1)
diff --git a/journalist_gui/journalist_gui/SecureDropUpdater.py b/journalist_gui/journalist_gui/SecureDropUpdater.py
index a243cf0e71..75e8018035 100644
--- a/journalist_gui/journalist_gui/SecureDropUpdater.py
+++ b/journalist_gui/journalist_gui/SecureDropUpdater.py
@@ -17,16 +17,12 @@
def password_is_set():
-
pwd_flag = subprocess.check_output(["passwd", "--status"]).decode("utf-8").split()[1]
- if pwd_flag == "NP":
- return False
- return True
+ return pwd_flag != "NP"
def prevent_second_instance(app: QtWidgets.QApplication, name: str) -> None:
-
# Null byte triggers abstract namespace
IDENTIFIER = "\0" + name
ALREADY_BOUND_ERRNO = 98
diff --git a/journalist_gui/test_gui.py b/journalist_gui/test_gui.py
index 4d57b8b931..fbec32c4f8 100644
--- a/journalist_gui/test_gui.py
+++ b/journalist_gui/test_gui.py
@@ -101,7 +101,6 @@ def test_default_tab(self):
assert self.window.tabWidget.currentIndex() == 0
def test_output_tab(self):
-
tab = self.window.tabWidget.tabBar()
QTest.mouseClick(tab, Qt.LeftButton)
assert self.window.tabWidget.currentIndex() == self.window.tabWidget.indexOf(
diff --git a/molecule/testinfra/app-code/test_securedrop_app_code.py b/molecule/testinfra/app-code/test_securedrop_app_code.py
index 245ef7683b..d795cce67b 100644
--- a/molecule/testinfra/app-code/test_securedrop_app_code.py
+++ b/molecule/testinfra/app-code/test_securedrop_app_code.py
@@ -52,7 +52,7 @@ def test_unwanted_packages_absent(host, package):
assert not host.package(package).is_installed
-@pytest.mark.skip_in_prod()
+@pytest.mark.skip_in_prod
def test_securedrop_application_test_locale(host):
"""
Ensure both SecureDrop DEFAULT_LOCALE and SUPPORTED_LOCALES are present.
@@ -66,7 +66,7 @@ def test_securedrop_application_test_locale(host):
assert "\nSUPPORTED_LOCALES = ['el', 'ar', 'en_US']\n" in securedrop_config.content_string
-@pytest.mark.skip_in_prod()
+@pytest.mark.skip_in_prod
def test_securedrop_application_test_journalist_key(host):
"""
Ensure the SecureDrop Application GPG public key file is present.
diff --git a/molecule/testinfra/app-code/test_securedrop_rqrequeue.py b/molecule/testinfra/app-code/test_securedrop_rqrequeue.py
index 16b414cdfa..d5f49ecc6d 100644
--- a/molecule/testinfra/app-code/test_securedrop_rqrequeue.py
+++ b/molecule/testinfra/app-code/test_securedrop_rqrequeue.py
@@ -17,13 +17,9 @@ def test_securedrop_rqrequeue_service(host):
"Wants=redis-server.service",
"",
"[Service]",
- 'Environment=PYTHONPATH="{}:{}"'.format(
- securedrop_test_vars.securedrop_code,
- securedrop_test_vars.securedrop_venv_site_packages,
- ),
- "ExecStart={}/python /var/www/securedrop/scripts/rqrequeue --interval 60".format(
- securedrop_test_vars.securedrop_venv_bin
- ),
+ f'Environment=PYTHONPATH="{securedrop_test_vars.securedrop_code}:{securedrop_test_vars.securedrop_venv_site_packages}"',
+ f"ExecStart={securedrop_test_vars.securedrop_venv_bin}/python /var/www/securedrop/"
+ "scripts/rqrequeue --interval 60",
"PrivateDevices=yes",
"PrivateTmp=yes",
"ProtectSystem=full",
diff --git a/molecule/testinfra/app-code/test_securedrop_rqworker.py b/molecule/testinfra/app-code/test_securedrop_rqworker.py
index 1afdb20c47..88347d10cb 100644
--- a/molecule/testinfra/app-code/test_securedrop_rqworker.py
+++ b/molecule/testinfra/app-code/test_securedrop_rqworker.py
@@ -19,10 +19,7 @@ def test_securedrop_rqworker_service(host):
"Wants=redis-server.service",
"",
"[Service]",
- 'Environment=PYTHONPATH="{}:{}"'.format(
- securedrop_test_vars.securedrop_code,
- securedrop_test_vars.securedrop_venv_site_packages,
- ),
+ f'Environment=PYTHONPATH="{securedrop_test_vars.securedrop_code}:{securedrop_test_vars.securedrop_venv_site_packages}"',
f"ExecStart={securedrop_test_vars.securedrop_venv_bin}/rqworker -c rq_config",
"PrivateDevices=yes",
"PrivateTmp=yes",
diff --git a/molecule/testinfra/app-code/test_securedrop_shredder_configuration.py b/molecule/testinfra/app-code/test_securedrop_shredder_configuration.py
index a39164197f..688d2b30c3 100644
--- a/molecule/testinfra/app-code/test_securedrop_shredder_configuration.py
+++ b/molecule/testinfra/app-code/test_securedrop_shredder_configuration.py
@@ -16,13 +16,9 @@ def test_securedrop_shredder_service(host):
"Description=SecureDrop shredder",
"",
"[Service]",
- 'Environment=PYTHONPATH="{}:{}"'.format(
- securedrop_test_vars.securedrop_code,
- securedrop_test_vars.securedrop_venv_site_packages,
- ),
- "ExecStart={}/python /var/www/securedrop/scripts/shredder --interval 60".format(
- securedrop_test_vars.securedrop_venv_bin
- ),
+ f'Environment=PYTHONPATH="{securedrop_test_vars.securedrop_code}:{securedrop_test_vars.securedrop_venv_site_packages}"',
+ f"ExecStart={securedrop_test_vars.securedrop_venv_bin}/python /var/www/securedrop/"
+ "scripts/shredder --interval 60",
"PrivateDevices=yes",
"PrivateTmp=yes",
"ProtectSystem=full",
diff --git a/molecule/testinfra/app-code/test_securedrop_source_deleter_configuration.py b/molecule/testinfra/app-code/test_securedrop_source_deleter_configuration.py
index 9de809a288..a30077a386 100644
--- a/molecule/testinfra/app-code/test_securedrop_source_deleter_configuration.py
+++ b/molecule/testinfra/app-code/test_securedrop_source_deleter_configuration.py
@@ -15,13 +15,9 @@ def test_securedrop_source_deleter_service(host):
"Description=SecureDrop Source deleter",
"",
"[Service]",
- 'Environment=PYTHONPATH="{}:{}"'.format(
- securedrop_test_vars.securedrop_code,
- securedrop_test_vars.securedrop_venv_site_packages,
- ),
- "ExecStart={}/python /var/www/securedrop/scripts/source_deleter --interval 10".format(
- securedrop_test_vars.securedrop_venv_bin
- ),
+ f'Environment=PYTHONPATH="{securedrop_test_vars.securedrop_code}:{securedrop_test_vars.securedrop_venv_site_packages}"',
+ f"ExecStart={securedrop_test_vars.securedrop_venv_bin}/python /var/www/securedrop/"
+ "scripts/source_deleter --interval 10",
"PrivateDevices=yes",
"PrivateTmp=yes",
"ProtectSystem=full",
diff --git a/molecule/testinfra/app/apache/test_apache_journalist_interface.py b/molecule/testinfra/app/apache/test_apache_journalist_interface.py
index f3977ad46f..7d71a3a216 100644
--- a/molecule/testinfra/app/apache/test_apache_journalist_interface.py
+++ b/molecule/testinfra/app/apache/test_apache_journalist_interface.py
@@ -111,20 +111,14 @@ def test_apache_logging_journalist_interface(host):
AllowOverride None
Require all denied
-""".strip(
- "\n"
- ),
+""".strip("\n"),
"""
Require all granted
# Cache static resources for 1 hour
Header set Cache-Control "max-age=3600"
-""".strip(
- "\n"
- ).format(
- securedrop_test_vars.securedrop_code
- ),
+""".strip("\n").format(securedrop_test_vars.securedrop_code),
"""
Options None
@@ -136,11 +130,7 @@ def test_apache_logging_journalist_interface(host):
Require all denied
-""".strip(
- "\n"
- ).format(
- securedrop_test_vars.securedrop_code
- ),
+""".strip("\n").format(securedrop_test_vars.securedrop_code),
],
)
def test_apache_config_journalist_interface_access_control(host, apache_opt):
diff --git a/molecule/testinfra/app/apache/test_apache_source_interface.py b/molecule/testinfra/app/apache/test_apache_source_interface.py
index 68830dc991..f348422bd4 100644
--- a/molecule/testinfra/app/apache/test_apache_source_interface.py
+++ b/molecule/testinfra/app/apache/test_apache_source_interface.py
@@ -79,20 +79,14 @@ def test_apache_config_source_interface_headers_per_distro(host):
AllowOverride None
Require all denied
-""".strip(
- "\n"
- ),
+""".strip("\n"),
"""
Require all granted
# Cache static resources for 1 hour
Header set Cache-Control "max-age=3600"
-""".strip(
- "\n"
- ).format(
- securedrop_test_vars.securedrop_code
- ),
+""".strip("\n").format(securedrop_test_vars.securedrop_code),
"""
Options None
@@ -104,11 +98,7 @@ def test_apache_config_source_interface_headers_per_distro(host):
Require all denied
-""".strip(
- "\n"
- ).format(
- securedrop_test_vars.securedrop_code
- ),
+""".strip("\n").format(securedrop_test_vars.securedrop_code),
],
)
def test_apache_config_source_interface_access_control(host, apache_opt):
diff --git a/molecule/testinfra/app/apache/test_apache_system_config.py b/molecule/testinfra/app/apache/test_apache_system_config.py
index b40a0e9cc4..1d5456ab0f 100644
--- a/molecule/testinfra/app/apache/test_apache_system_config.py
+++ b/molecule/testinfra/app/apache/test_apache_system_config.py
@@ -90,9 +90,7 @@ def test_apache_ports_config(host, port):
assert f.group == "root"
assert f.mode == 0o644
- listening_regex = "^Listen {}:{}$".format(
- re.escape(securedrop_test_vars.apache_listening_address), port
- )
+ listening_regex = f"^Listen {re.escape(securedrop_test_vars.apache_listening_address)}:{port}$"
assert f.contains(listening_regex)
diff --git a/molecule/testinfra/app/test_app_network.py b/molecule/testinfra/app/test_app_network.py
index cf20efeac1..fb5474ce40 100644
--- a/molecule/testinfra/app/test_app_network.py
+++ b/molecule/testinfra/app/test_app_network.py
@@ -9,9 +9,8 @@
testinfra_hosts = [securedrop_test_vars.app_hostname]
-@pytest.mark.skip_in_prod()
+@pytest.mark.skip_in_prod
def test_app_iptables_rules(host):
-
local = host.get_host("local://")
# Build a dict of variables to pass to jinja for iptables comparison
@@ -32,9 +31,7 @@ def test_app_iptables_rules(host):
# Build iptables scrape cmd, purge comments + counters
iptables = r"iptables-save | sed 's/ \[[0-9]*\:[0-9]*\]//g' | egrep -v '^#'"
environment = os.environ.get("SECUREDROP_TESTINFRA_TARGET_HOST", "staging")
- iptables_file = "{}/iptables-app-{}.j2".format(
- os.path.dirname(os.path.abspath(__file__)), environment
- )
+ iptables_file = f"{os.path.dirname(os.path.abspath(__file__))}/iptables-app-{environment}.j2"
# template out a local iptables jinja file
jinja_iptables = Template(open(iptables_file).read())
diff --git a/molecule/testinfra/app/test_apparmor.py b/molecule/testinfra/app/test_apparmor.py
index 2977bf6469..5ce70ffaa3 100644
--- a/molecule/testinfra/app/test_apparmor.py
+++ b/molecule/testinfra/app/test_apparmor.py
@@ -103,7 +103,7 @@ def test_aastatus_unconfined(host):
expected_unconfined = 0
unconfined_chk = str(
- "{} processes are unconfined but have" " a profile defined".format(expected_unconfined)
+ f"{expected_unconfined} processes are unconfined but have" " a profile defined"
)
with host.sudo():
aa_status_output = host.check_output("aa-status")
diff --git a/molecule/testinfra/app/test_appenv.py b/molecule/testinfra/app/test_appenv.py
index e5d4f7b904..ead90c1a0a 100644
--- a/molecule/testinfra/app/test_appenv.py
+++ b/molecule/testinfra/app/test_appenv.py
@@ -8,14 +8,16 @@
@pytest.mark.parametrize("exp_pip_pkg", sdvars.pip_deps)
def test_app_pip_deps(host, exp_pip_pkg):
"""Ensure expected package versions are installed"""
- cmd = "{}/bin/python3 -c \"from importlib.metadata import version; print(version('{}'))\"".format( # noqa
- sdvars.securedrop_venv, exp_pip_pkg["name"]
+ cmd = (
+ "{}/bin/python3 -c \"from importlib.metadata import version; print(version('{}'))\"".format(
+ sdvars.securedrop_venv, exp_pip_pkg["name"]
+ )
)
result = host.run(cmd)
assert result.stdout.strip() == exp_pip_pkg["version"]
-@pytest.mark.skip_in_prod()
+@pytest.mark.skip_in_prod
def test_app_wsgi(host):
"""ensure logging is enabled for source interface in staging"""
f = host.file("/var/www/source.wsgi")
@@ -74,9 +76,8 @@ def test_app_code_venv(host):
"""
Ensure the securedrop-app-code virtualenv is correct.
"""
- cmd = """test -z $VIRTUAL_ENV && . {}/bin/activate && test "$VIRTUAL_ENV" = "{}" """.format(
- sdvars.securedrop_venv, sdvars.securedrop_venv
- )
+ cmd = f"""test -z $VIRTUAL_ENV && . {sdvars.securedrop_venv}/bin/activate && "
+ "test "$VIRTUAL_ENV" = "{sdvars.securedrop_venv}" """
result = host.run(cmd)
assert result.rc == 0
@@ -87,7 +88,7 @@ def test_supervisor_not_installed(host):
assert host.package("supervisor").is_installed is False
-@pytest.mark.skip_in_prod()
+@pytest.mark.skip_in_prod
def test_gpg_key_in_keyring(host):
"""ensure test gpg key is present in app keyring"""
with host.sudo(sdvars.securedrop_user):
diff --git a/molecule/testinfra/app/test_ossec_agent.py b/molecule/testinfra/app/test_ossec_agent.py
index e972e8ab83..50d7071a72 100644
--- a/molecule/testinfra/app/test_ossec_agent.py
+++ b/molecule/testinfra/app/test_ossec_agent.py
@@ -39,7 +39,7 @@ def test_ossec_agent_installed(host):
# Permissions don't match between Ansible and OSSEC deb packages postinst.
-@pytest.mark.xfail()
+@pytest.mark.xfail
def test_ossec_keyfile_present(host):
"""ensure client keyfile for ossec-agent is present"""
pattern = "^1024 {} {} [0-9a-f]{{64}}$".format(
diff --git a/molecule/testinfra/app/test_smoke.py b/molecule/testinfra/app/test_smoke.py
index 1f4c949cea..b28361f1e2 100644
--- a/molecule/testinfra/app/test_smoke.py
+++ b/molecule/testinfra/app/test_smoke.py
@@ -60,7 +60,7 @@ def test_redwood(host):
assert len(parsed[2]) == 40
-@pytest.mark.skip_in_prod()
+@pytest.mark.skip_in_prod
def test_weak_submission_key(host):
"""
If the Submission Key is weak (e.g. has a SHA-1 signature),
diff --git a/molecule/testinfra/app/test_tor_config.py b/molecule/testinfra/app/test_tor_config.py
index c9422c87cf..ba6ce93fec 100644
--- a/molecule/testinfra/app/test_tor_config.py
+++ b/molecule/testinfra/app/test_tor_config.py
@@ -68,7 +68,7 @@ def test_tor_torrc_sandbox(host):
assert not f.contains("^.*Sandbox.*$")
-@pytest.mark.skip_in_prod()
+@pytest.mark.skip_in_prod
def test_tor_v2_onion_url_file_absent(host):
v2_url_filepath = "/var/lib/securedrop/source_v2_url"
with host.sudo():
@@ -76,7 +76,7 @@ def test_tor_v2_onion_url_file_absent(host):
assert not f.exists
-@pytest.mark.skip_in_prod()
+@pytest.mark.skip_in_prod
def test_tor_v3_onion_url_readable_by_app(host):
v3_url_filepath = "/var/lib/securedrop/source_v3_url"
with host.sudo():
diff --git a/molecule/testinfra/app/test_tor_hidden_services.py b/molecule/testinfra/app/test_tor_hidden_services.py
index ffe1431d6a..38d22a1643 100644
--- a/molecule/testinfra/app/test_tor_hidden_services.py
+++ b/molecule/testinfra/app/test_tor_hidden_services.py
@@ -9,7 +9,7 @@
# Prod Tor services may have unexpected configs
# TODO: read from admin workstation site-specific file if available
-@pytest.mark.skip_in_prod()
+@pytest.mark.skip_in_prod
@pytest.mark.parametrize("tor_service", sdvars.tor_services)
def test_tor_service_directories(host, tor_service):
"""
@@ -23,7 +23,7 @@ def test_tor_service_directories(host, tor_service):
assert f.group == "debian-tor"
-@pytest.mark.skip_in_prod()
+@pytest.mark.skip_in_prod
@pytest.mark.parametrize("tor_service", sdvars.tor_services)
def test_tor_service_hostnames(host, tor_service):
"""
@@ -58,7 +58,7 @@ def test_tor_service_hostnames(host, tor_service):
assert re.search(f"^{ths_hostname_regex_v3}$", f.content_string)
-@pytest.mark.skip_in_prod()
+@pytest.mark.skip_in_prod
@pytest.mark.parametrize("tor_service", sdvars.tor_services)
def test_tor_services_config(host, tor_service):
"""
diff --git a/molecule/testinfra/common/test_fpf_apt_repo.py b/molecule/testinfra/common/test_fpf_apt_repo.py
index cfcde90a2c..91b945ee43 100644
--- a/molecule/testinfra/common/test_fpf_apt_repo.py
+++ b/molecule/testinfra/common/test_fpf_apt_repo.py
@@ -28,9 +28,8 @@ def test_fpf_apt_repo_present(host):
f = host.file("/etc/apt/sources.list.d/apt_test_freedom_press.list")
else:
f = host.file("/etc/apt/sources.list.d/apt_freedom_press.list")
- repo_regex = r"^deb \[arch=amd64\] {} {} main$".format(
- re.escape(test_vars.fpf_apt_repo_url), re.escape(host.system_info.codename)
- )
+ repo_regex = rf"^deb \[arch=amd64\] {re.escape(test_vars.fpf_apt_repo_url)} "
+ rf"{re.escape(host.system_info.codename)} main$"
assert f.contains(repo_regex)
@@ -61,7 +60,6 @@ def test_fpf_apt_repo_fingerprint(host):
[
"pub 4096R/FC9F6818 2014-10-26 [expired: 2016-10-27]",
"pub 4096R/00F4AD77 2016-10-20 [expired: 2017-10-20]",
- "pub 4096R/00F4AD77 2016-10-20 [expired: 2017-10-20]",
"pub 4096R/7B22E6A3 2021-05-10 [expired: 2022-07-04]",
"pub 4096R/7B22E6A3 2021-05-10 [expired: 2023-07-04]",
"pub 4096R/7B22E6A3 2021-05-10 [expired: 2024-07-08]",
diff --git a/molecule/testinfra/common/test_grsecurity.py b/molecule/testinfra/common/test_grsecurity.py
index 49364e7b45..83d17f58eb 100644
--- a/molecule/testinfra/common/test_grsecurity.py
+++ b/molecule/testinfra/common/test_grsecurity.py
@@ -113,9 +113,7 @@ def test_grsecurity_paxtest(host):
paxtest_cmd += " | grep -P '^(Executable|Return)'"
paxtest_results = host.check_output(paxtest_cmd)
- paxtest_template_path = "{}/paxtest_results.j2".format(
- os.path.dirname(os.path.abspath(__file__))
- )
+ paxtest_template_path = f"{os.path.dirname(os.path.abspath(__file__))}/paxtest_results.j2"
memcpy_result = "Killed"
# Versions of paxtest newer than 0.9.12 or so will report
diff --git a/molecule/testinfra/common/test_user_config.py b/molecule/testinfra/common/test_user_config.py
index 3d703cd442..339aaaa2d8 100644
--- a/molecule/testinfra/common/test_user_config.py
+++ b/molecule/testinfra/common/test_user_config.py
@@ -88,7 +88,7 @@ def test_tmux_installed(host):
assert host.package("tmux").is_installed
-@pytest.mark.skip_in_prod()
+@pytest.mark.skip_in_prod
def test_sudoers_tmux_env_deprecated(host):
"""
Previous version of the Ansible config set the tmux config
diff --git a/molecule/testinfra/mon/test_mon_network.py b/molecule/testinfra/mon/test_mon_network.py
index 69a80601ce..92efb7e296 100644
--- a/molecule/testinfra/mon/test_mon_network.py
+++ b/molecule/testinfra/mon/test_mon_network.py
@@ -9,9 +9,8 @@
testinfra_hosts = [securedrop_test_vars.monitor_hostname]
-@pytest.mark.skip_in_prod()
+@pytest.mark.skip_in_prod
def test_mon_iptables_rules(host):
-
local = host.get_host("local://")
# Build a dict of variables to pass to jinja for iptables comparison
@@ -32,9 +31,7 @@ def test_mon_iptables_rules(host):
# Build iptables scrape cmd, purge comments + counters
iptables = r"iptables-save | sed 's/ \[[0-9]*\:[0-9]*\]//g' | egrep -v '^#'"
environment = os.environ.get("SECUREDROP_TESTINFRA_TARGET_HOST", "staging")
- iptables_file = "{}/iptables-mon-{}.j2".format(
- os.path.dirname(os.path.abspath(__file__)), environment
- )
+ iptables_file = f"{os.path.dirname(os.path.abspath(__file__))}/iptables-mon-{environment}.j2"
# template out a local iptables jinja file
jinja_iptables = Template(open(iptables_file).read())
@@ -54,7 +51,7 @@ def test_mon_iptables_rules(host):
assert iptables_expected == iptables
-@pytest.mark.skip_in_prod()
+@pytest.mark.skip_in_prod
@pytest.mark.parametrize(
"ossec_service",
[
diff --git a/molecule/testinfra/mon/test_ossec_server.py b/molecule/testinfra/mon/test_ossec_server.py
index 32534885a6..a515b836ef 100644
--- a/molecule/testinfra/mon/test_ossec_server.py
+++ b/molecule/testinfra/mon/test_ossec_server.py
@@ -32,7 +32,7 @@ def test_ossec_service_start_style(host):
# Permissions don't match between Ansible and OSSEC deb packages postinst.
-@pytest.mark.xfail()
+@pytest.mark.xfail
@pytest.mark.parametrize(
"keyfile",
[
@@ -59,7 +59,7 @@ def test_ossec_keyfiles(host, keyfile):
# Permissions don't match between Ansible and OSSEC deb packages postinst.
-@pytest.mark.xfail()
+@pytest.mark.xfail
def test_procmail_log(host):
"""
Ensure procmail log file exist with proper ownership.
diff --git a/molecule/testinfra/mon/test_postfix.py b/molecule/testinfra/mon/test_postfix.py
index 3801bb9eaf..19dadd0946 100644
--- a/molecule/testinfra/mon/test_postfix.py
+++ b/molecule/testinfra/mon/test_postfix.py
@@ -37,11 +37,8 @@ def test_postfix_generic_maps(host):
"""
assert host.file("/etc/postfix/generic").exists
assert host.file("/etc/postfix/generic").contains(
- "^ossec@{} {}@{}".format(
- securedrop_test_vars.monitor_hostname,
- securedrop_test_vars.sasl_username,
- securedrop_test_vars.sasl_domain,
- )
+ f"^ossec@{securedrop_test_vars.monitor_hostname} {securedrop_test_vars.sasl_username}@"
+ f"{securedrop_test_vars.sasl_domain}"
)
assert host.file("/etc/postfix/main.cf").contains("^smtp_generic_maps")
assert host.file("/etc/postfix/main.cf").contains(
diff --git a/molecule/testinfra/ossec/test_journalist_mail.py b/molecule/testinfra/ossec/test_journalist_mail.py
index 1e0fc1938f..f483e35000 100644
--- a/molecule/testinfra/ossec/test_journalist_mail.py
+++ b/molecule/testinfra/ossec/test_journalist_mail.py
@@ -70,7 +70,7 @@ def test_procmail(self, host):
):
# Look up CWD, in case tests move in the future
current_dir = os.path.dirname(os.path.abspath(__file__))
- self.ansible(host, "copy", "dest=/tmp/{f} src={d}/{f}".format(f=f, d=current_dir))
+ self.ansible(host, "copy", f"dest=/tmp/{f} src={current_dir}/{f}")
assert self.run(host, "/var/ossec/process_submissions_today.sh forget")
assert self.run(host, "postsuper -d ALL")
assert self.run(host, f"cat /tmp/{f} | mail -s 'abc' root@localhost")
@@ -96,12 +96,10 @@ def trigger(who, payload):
assert self.run(host, f"! mailq | grep -q {who}@ossec.test")
assert self.run(
host,
- """
+ f"""
( echo 'Subject: TEST' ; echo ; echo -e '{payload}' ) | \
/var/ossec/send_encrypted_alarm.sh {who}
- """.format(
- who=who, payload=payload
- ),
+ """,
)
assert self.wait_for_command(host, f"mailq | grep -q {who}@ossec.test")
@@ -116,14 +114,12 @@ def trigger(who, payload):
trigger(who, payload)
assert self.run(
host,
- """
+ f"""
job=$(mailq | sed -n -e '2p' | cut -f1 -d ' ')
postcat -q $job | tee /dev/stderr | \
gpg --homedir /var/ossec/.gnupg --decrypt 2>&1 | \
grep -q {expected}
- """.format(
- expected=expected
- ),
+ """,
)
#
# failure to encrypt must trigger an emergency mail to ossec contact
diff --git a/pyproject.toml b/pyproject.toml
index 799c8aa95a..8db51d4599 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -2,13 +2,10 @@
name = "securedrop"
requires-python = ">=3.8"
-[tool.black]
-line-length = 100
-target-version = ["py38"]
-# Don't use `extend`, we want the default behavior that honors .gitignore rules
-
[tool.ruff]
line-length = 100
+extend-include = ["securedrop/scripts/*"]
+[tool.ruff.lint]
select = [
# pycodestyle errors
"E",
@@ -79,14 +76,13 @@ ignore = [
# nested if and with statements, too many violations for now
"SIM102", "SIM117",
]
-extend-include = ["securedrop/scripts/*"]
-[tool.ruff.isort]
+[tool.ruff.lint.isort]
# ruff's isort is smart enough to know these are first party, but let's treat
# them as third-party to match O.G. isort until we fix our package layout
known-third-party = ["journalist_app", "management", "source_app", "tests"]
-[tool.ruff.per-file-ignores]
+[tool.ruff.lint.per-file-ignores]
"**/test**.py" = [
# use of `assert`
"S101",
@@ -105,6 +101,8 @@ known-third-party = ["journalist_app", "management", "source_app", "tests"]
"securedrop/pretty_bad_protocol/*.py" = [
# legacy code that still uses `assert`
"S101",
+ # too much % formatting, not worth fixing for now
+ "UP031",
]
[tool.mypy]
diff --git a/redwood/build-wheel.py b/redwood/build-wheel.py
index 666747dfad..1134bef63c 100644
--- a/redwood/build-wheel.py
+++ b/redwood/build-wheel.py
@@ -12,6 +12,7 @@
* Copy and rename that into the Python package structure
* Build a Python wheel using setuptools
"""
+
import argparse
import os
import shutil
diff --git a/securedrop/alembic/versions/3da3fcab826a_delete_orphaned_submissions.py b/securedrop/alembic/versions/3da3fcab826a_delete_orphaned_submissions.py
index 19887fa014..f81cc38be7 100644
--- a/securedrop/alembic/versions/3da3fcab826a_delete_orphaned_submissions.py
+++ b/securedrop/alembic/versions/3da3fcab826a_delete_orphaned_submissions.py
@@ -24,12 +24,12 @@
def raw_sql_grab_orphaned_objects(table_name: str) -> str:
"""Objects that have a source ID that doesn't exist in the
sources table OR a NULL source ID should be deleted."""
- return ( # noqa: S608
- "SELECT id, filename, source_id FROM {table} "
+ return (
+ f"SELECT id, filename, source_id FROM {table_name} " # noqa: S608
"WHERE source_id NOT IN (SELECT id FROM sources) "
- "UNION SELECT id, filename, source_id FROM {table} "
+ f"UNION SELECT id, filename, source_id FROM {table_name} "
"WHERE source_id IS NULL"
- ).format(table=table_name)
+ )
def upgrade() -> None:
diff --git a/securedrop/debian/ossec-common/var/ossec/checksdconfig.py b/securedrop/debian/ossec-common/var/ossec/checksdconfig.py
index ecfb085907..fa0b9c06ed 100755
--- a/securedrop/debian/ossec-common/var/ossec/checksdconfig.py
+++ b/securedrop/debian/ossec-common/var/ossec/checksdconfig.py
@@ -32,7 +32,7 @@
def list_iptables_rules() -> dict:
- result = subprocess.run(["iptables", "-S"], capture_output=True)
+ result = subprocess.run(["iptables", "-S"], capture_output=True, check=False)
rules = result.stdout.decode("utf-8").splitlines()
policies = [r for r in rules if r.startswith("-P")]
input_rules = [r for r in rules if r.startswith("-A INPUT")]
diff --git a/securedrop/i18n.py b/securedrop/i18n.py
index 596ca74076..0b281b6e92 100644
--- a/securedrop/i18n.py
+++ b/securedrop/i18n.py
@@ -114,9 +114,8 @@ def configure_babel(config: SecureDropConfig, app: Flask) -> Babel:
# verify that Babel is only using the translations we told it about
if list(babel.translation_directories) != [translations_directory]:
raise ValueError(
- "Babel translation directories ({}) do not match SecureDrop configuration ({})".format(
- babel.translation_directories, [translations_directory]
- )
+ f"Babel translation directories ({babel.translation_directories}) do not match "
+ f"SecureDrop configuration ({[translations_directory]})"
)
# register the function used to determine the locale of a request
diff --git a/securedrop/journalist_app/admin.py b/securedrop/journalist_app/admin.py
index b823fb0cda..606ed43d58 100644
--- a/securedrop/journalist_app/admin.py
+++ b/securedrop/journalist_app/admin.py
@@ -216,7 +216,7 @@ def add_user() -> Union[str, werkzeug.Response]:
),
"error",
)
- current_app.logger.error("Adding user " "'{}' failed: {}".format(username, e))
+ current_app.logger.error("Adding user " f"'{username}' failed: {e}")
if form_valid:
if new_user.is_totp:
@@ -423,9 +423,8 @@ def delete_user(user_id: int) -> werkzeug.Response:
abort(403)
elif not user:
current_app.logger.error(
- "Admin {} tried to delete nonexistent user with pk={}".format(
- session.get_user().username, user_id
- )
+ f"Admin {session.get_user().username} tried to delete nonexistent user with "
+ f"pk={user_id}"
)
abort(404)
elif user.is_deleted_user():
@@ -456,9 +455,8 @@ def new_password(user_id: int) -> werkzeug.Response:
if user.id == session.get_uid():
current_app.logger.error(
- "Admin {} tried to change their password without validation.".format(
- session.get_user().username
- )
+ f"Admin {session.get_user().username} tried to change their password without "
+ "validation."
)
abort(403)
diff --git a/securedrop/journalist_app/forms.py b/securedrop/journalist_app/forms.py
index 7543166f00..e52974e012 100644
--- a/securedrop/journalist_app/forms.py
+++ b/securedrop/journalist_app/forms.py
@@ -27,7 +27,6 @@ def __init__(
*args: Any,
**kwargs: Any,
) -> None:
-
self.other_field_name = other_field_name
if custom_message is not None:
self.custom_message = custom_message
diff --git a/securedrop/journalist_app/main.py b/securedrop/journalist_app/main.py
index 5a95feb241..b6a319b0d1 100644
--- a/securedrop/journalist_app/main.py
+++ b/securedrop/journalist_app/main.py
@@ -138,9 +138,7 @@ def reply() -> werkzeug.Response:
return redirect(url_for("col.col", filesystem_id=g.filesystem_id))
g.source.interaction_count += 1
- filename = "{}-{}-reply.gpg".format(
- g.source.interaction_count, g.source.journalist_filename
- )
+ filename = f"{g.source.interaction_count}-{g.source.journalist_filename}-reply.gpg"
EncryptionManager.get_default().encrypt_journalist_reply(
for_source=g.source,
reply_in=form.message.data,
@@ -163,12 +161,10 @@ def reply() -> werkzeug.Response:
# with responses to sources. It's possible the exception message
# could contain information we don't want to write to disk.
current_app.logger.error(
- "Reply from '{}' (ID {}) failed: {}!".format(
- session.get_user().username, session.get_uid(), exc.__class__
- )
+ f"Reply from '{session.get_user().username}' (ID {session.get_uid()}) "
+ f"failed: {exc.__class__}!"
)
else:
-
flash(
Markup(
"{} {}".format(
diff --git a/securedrop/loaddata.py b/securedrop/loaddata.py
index 241e00f435..ffd18091db 100755
--- a/securedrop/loaddata.py
+++ b/securedrop/loaddata.py
@@ -381,16 +381,10 @@ def add_sources(args: argparse.Namespace, journalists: Tuple[Journalist, ...]) -
add_reply(source, journalist_who_replied, journalist_who_saw)
print(
- "Created source {}/{} (codename: '{}', journalist designation '{}', "
- "files: {}, messages: {}, replies: {})".format(
- i,
- args.source_count,
- codename,
- source.journalist_designation,
- args.files_per_source,
- args.messages_per_source,
- args.replies_per_source if i <= replied_sources_count else 0,
- )
+ f"Created source {i}/{args.source_count} (codename: '{codename}', "
+ f"journalist designation '{source.journalist_designation}', "
+ f"files: {args.files_per_source}, messages: {args.messages_per_source}, "
+ f"replies: {args.replies_per_source if i <= replied_sources_count else 0})"
)
diff --git a/securedrop/manage.py b/securedrop/manage.py
index fc69c9e37d..169e0921ac 100755
--- a/securedrop/manage.py
+++ b/securedrop/manage.py
@@ -19,18 +19,18 @@
sys.path.insert(0, "/var/www/securedrop")
-import qrcode # noqa: E402
-from sqlalchemy.orm.exc import NoResultFound # noqa: E402
+import qrcode
+from sqlalchemy.orm.exc import NoResultFound
if not os.environ.get("SECUREDROP_ENV"):
os.environ["SECUREDROP_ENV"] = "dev"
-from db import db # noqa: E402
-from management import SecureDropConfig, app_context # noqa: E402
-from management.run import run # noqa: E402
-from management.sources import remove_pending_sources # noqa: E402
-from management.submissions import ( # noqa: E402
+from db import db
+from management import SecureDropConfig, app_context
+from management.run import run
+from management.sources import remove_pending_sources
+from management.submissions import (
add_check_db_disconnect_parser,
add_check_fs_disconnect_parser,
add_delete_db_disconnect_parser,
@@ -39,7 +39,7 @@
add_list_fs_disconnect_parser,
add_were_there_submissions_today,
)
-from models import FirstOrLastNameError, InvalidUsernameException, Journalist # noqa: E402
+from models import FirstOrLastNameError, InvalidUsernameException, Journalist
logging.basicConfig(format="%(asctime)s %(levelname)s %(message)s")
log = logging.getLogger(__name__)
@@ -197,8 +197,8 @@ def _add_user(is_admin: bool = False, context: Optional[AppContext] = None) -> i
if len(tmp_str) != 40:
print(
"The length of the secret is not correct. "
- "Expected 40 characters, but received {}. "
- "Try again.".format(len(tmp_str))
+ f"Expected 40 characters, but received {len(tmp_str)}. "
+ "Try again."
)
continue
if otp_secret:
@@ -239,7 +239,7 @@ def _add_user(is_admin: bool = False, context: Optional[AppContext] = None) -> i
'you will need to change the "Non-ASCII Font", which '
"is your profile's Text settings.\n\nCan't scan the "
"barcode? Enter following shared secret manually:"
- "\n{}\n".format(user.formatted_otp_secret)
+ f"\n{user.formatted_otp_secret}\n"
)
return 0
@@ -249,11 +249,9 @@ def _get_username_to_delete() -> str:
def _get_delete_confirmation(username: str) -> bool:
- confirmation = obtain_input(
- "Are you sure you want to delete user " '"{}" (y/n)?'.format(username)
- )
+ confirmation = obtain_input("Are you sure you want to delete user " f'"{username}" (y/n)?')
if confirmation.lower() != "y":
- print('Confirmation not received: user "{}" was NOT ' "deleted".format(username))
+ print(f'Confirmation not received: user "{username}" was NOT ' "deleted")
return False
return True
@@ -414,14 +412,13 @@ def set_clean_tmp_parser(subps: _SubParsersAction, name: str) -> None:
default=default_days,
type=int,
help=(
- "remove files not modified in a given number of DAYS "
- "(default {} days)".format(default_days)
+ "remove files not modified in a given number of DAYS " f"(default {default_days} days)"
),
)
parser.add_argument(
"--directory",
default=config.TEMP_DIR,
- help=("remove old files from DIRECTORY " "(default {})".format(config.TEMP_DIR)),
+ help=("remove old files from DIRECTORY " f"(default {config.TEMP_DIR})"),
)
parser.set_defaults(func=clean_tmp)
diff --git a/securedrop/management/submissions.py b/securedrop/management/submissions.py
index 5eee374d29..5217b6c435 100644
--- a/securedrop/management/submissions.py
+++ b/securedrop/management/submissions.py
@@ -160,9 +160,8 @@ def delete_disconnected_fs_submissions(args: argparse.Namespace) -> None:
time_elapsed += file_elapsed
rate = bytes_deleted / time_elapsed
print(
- "elapsed: {:.2f}s rate: {:.1f} MB/s overall rate: {:.1f} MB/s".format(
- file_elapsed, filesize / 1048576 / file_elapsed, rate / 1048576
- )
+ f"elapsed: {file_elapsed:.2f}s rate: {filesize / 1048576 / file_elapsed:.1f} "
+ f"MB/s overall rate: {rate / 1048576:.1f} MB/s"
)
else:
print(f"Not removing {f}.")
diff --git a/securedrop/models.py b/securedrop/models.py
index a68f9bed1a..badd5b7e59 100644
--- a/securedrop/models.py
+++ b/securedrop/models.py
@@ -35,12 +35,7 @@ def get_one_or_else(
try:
return query.one()
except MultipleResultsFound as e:
- logger.error(
- "Found multiple while executing {} when one was expected: {}".format(
- query,
- e,
- )
- )
+ logger.error(f"Found multiple while executing {query} when one was expected: {e}")
failure_method(500)
except NoResultFound as e:
logger.error(f"Found none when one was expected: {e}")
@@ -87,7 +82,7 @@ def __init__(
self.uuid = str(uuid.uuid4())
def __repr__(self) -> str:
- return "