Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Display a banner in the JI regarding the noble migration #7348

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ fix: ruff ## Apply automatic fixes.
.PHONY: html-lint
html-lint: ## Validate HTML in web application template files.
@echo "███ Linting application templates..."
@html_lint.py --printfilename --disable=optional_tag,extra_whitespace,indentation,names,quotation \
@html_lint.py --printfilename --disable=optional_tag,extra_whitespace,indentation,names,quotation,protocol \
securedrop/source_templates/*.html securedrop/journalist_templates/*.html
@echo

Expand Down
8 changes: 8 additions & 0 deletions securedrop/journalist_app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from typing import Any, Optional, Tuple, Union

import i18n
import server_os
import template_filters
import version
from db import db
Expand Down Expand Up @@ -54,6 +55,11 @@ def create_app(config: SecureDropConfig) -> Flask:

app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
app.config["SQLALCHEMY_DATABASE_URI"] = config.DATABASE_URI

# Check if the server OS is past EOL; if so, we'll display banners
app.config["OS_PAST_EOL"] = server_os.is_os_past_eol()
app.config["OS_NEEDS_MIGRATION_FIXES"] = server_os.needs_migration_fixes()

db.init_app(app)

class JSONEncoder(json.JSONEncoder):
Expand Down Expand Up @@ -109,6 +115,8 @@ def setup_g() -> Optional[Response]:
"""Store commonly used values in Flask's special g object"""

i18n.set_locale(config)
g.show_os_past_eol_warning = app.config["OS_PAST_EOL"]
g.show_os_needs_migration_fixes = app.config["OS_NEEDS_MIGRATION_FIXES"]

if InstanceConfig.get_default().organization_name:
g.organization_name = ( # pylint: disable=assigning-non-slot
Expand Down
9 changes: 9 additions & 0 deletions securedrop/journalist_templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,15 @@
<body>

{% if session.logged_in() %}
{% if g.show_os_past_eol_warning %}
<div id="os-past-eol" class="alert-banner">
{{ gettext('<strong>Critical:</strong>&nbsp;&nbsp;The operating system used by your SecureDrop servers has reached its end-of-life. A manual update is required to re-enable the Source Interface and remain safe. Please contact your administrator. <a href="https://securedrop.org/focal-eol" rel="noreferrer">Learn More</a>') }}
</div>
{% elif g.show_os_needs_migration_fixes %}
<div id="os-near-eol" class="alert-banner">
{{ gettext('<strong>Important:</strong>&nbsp;&nbsp;Your SecureDrop server needs manual attention to resolve issues blocking automatic upgrade to the next operating system. Please contact your adminstrator. <a href="https://securedrop.org/focal-eol" rel="noreferrer">Learn More</a>') }}
</div>
{% endif %}
<nav aria-label="{{ gettext('Navigation') }}">
<a href="#main" class="visually-hidden until-focus">{{ gettext('Skip to main content') }}</a>
{{ gettext('Logged on as') }}
Expand Down
36 changes: 36 additions & 0 deletions securedrop/server_os.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import functools
import json
from datetime import date
from pathlib import Path

FOCAL_VERSION = "20.04"
NOBLE_VERSION = "24.04"

FOCAL_ENDOFLIFE = date(2025, 4, 2)


@functools.lru_cache
Expand All @@ -12,3 +18,33 @@ def get_os_release() -> str:
version_id = line.split("=")[1].strip().strip('"')
break
return version_id


def is_os_past_eol() -> bool:
"""
Check if it's focal and if today is past the official EOL date
"""
return get_os_release() == FOCAL_VERSION and date.today() >= FOCAL_ENDOFLIFE


def needs_migration_fixes() -> bool:
"""
See if the check script has flagged any issues
"""
if get_os_release() != FOCAL_VERSION:
return False
state_path = Path("/etc/securedrop-noble-migration.json")
if not state_path.exists():
# Script hasn't run yet
return False
try:
contents = json.loads(state_path.read_text())
except json.JSONDecodeError:
# Invalid output from the script is an error
return True
if "error" in contents:
# Something went wrong with the script itself,
# it needs manual fixes.
return True
# True if any of the checks failed
return not all(contents.values())
7 changes: 5 additions & 2 deletions securedrop/source_app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from typing import Optional, Tuple

import i18n
import server_os
import template_filters
import version
import werkzeug
Expand Down Expand Up @@ -65,6 +66,8 @@ def setup_i18n() -> None:

# Check if the Submission Key is valid; if not, we'll disable the UI
app.config["SUBMISSION_KEY_VALID"] = validate_journalist_key()
# Check if the server OS is past EOL; if so, we'll disable the UI
app.config["OS_PAST_EOL"] = server_os.is_os_past_eol()

@app.errorhandler(CSRFError)
def handle_csrf_error(e: CSRFError) -> werkzeug.Response:
Expand Down Expand Up @@ -113,8 +116,8 @@ def check_tor2web() -> Optional[werkzeug.Response]:

@app.before_request
@ignore_static
def check_submission_key() -> Optional[werkzeug.Response]:
if not app.config["SUBMISSION_KEY_VALID"]:
def check_offline() -> Optional[werkzeug.Response]:
if not app.config["SUBMISSION_KEY_VALID"] or app.config["OS_PAST_EOL"]:
session.clear()
g.show_offline_message = True
return make_response(render_template("offline.html"), 503)
Expand Down
42 changes: 42 additions & 0 deletions securedrop/tests/test_journalist.py
Original file line number Diff line number Diff line change
Expand Up @@ -4026,3 +4026,45 @@ def test_journalist_deletion(journalist_app, app_storage):
assert len(SeenReply.query.filter_by(journalist_id=deleted.id).all()) == 2
# And there are no login attempts
assert JournalistLoginAttempt.query.all() == []


def test_user_sees_os_warning_if_server_past_eol(config, journalist_app, test_journo):
journalist_app.config.update(OS_PAST_EOL=True, OS_NEAR_EOL=False)
with journalist_app.test_client() as app:
login_journalist(
app, test_journo["username"], test_journo["password"], test_journo["otp_secret"]
)

resp = app.get(url_for("main.index"))

text = resp.data.decode("utf-8")
assert 'id="os-past-eol"' in text, text
assert 'id="os-near-eol"' not in text, text


def test_user_sees_os_warning_if_migration_fixes(config, journalist_app, test_journo):
journalist_app.config.update(OS_PAST_EOL=False, OS_NEEDS_MIGRATION_FIXES=True)
with journalist_app.test_client() as app:
login_journalist(
app, test_journo["username"], test_journo["password"], test_journo["otp_secret"]
)

resp = app.get(url_for("main.index"))

text = resp.data.decode("utf-8")
assert 'id="os-past-eol"' not in text, text
assert 'id="os-near-eol"' in text, text


def test_user_does_not_see_os_warning_if_server_is_current(config, journalist_app, test_journo):
journalist_app.config.update(OS_PAST_EOL=False, OS_NEEDS_MIGRATION_FIXES=False)
with journalist_app.test_client() as app:
login_journalist(
app, test_journo["username"], test_journo["password"], test_journo["otp_secret"]
)

resp = app.get(url_for("main.index"))

text = resp.data.decode("utf-8")
assert 'id="os-past-eol"' not in text, text
assert 'id="os-near-eol"' not in text, text
6 changes: 6 additions & 0 deletions securedrop/translations/messages.pot
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,12 @@ msgstr ""
msgid "Can't scan the barcode? You can manually pair FreeOTP with this account by entering the following two-factor secret into the app:"
msgstr ""

msgid "<strong>Critical:</strong>&nbsp;&nbsp;The operating system used by your SecureDrop servers has reached its end-of-life. A manual update is required to re-enable the Source Interface and remain safe. Please contact your administrator. <a href=\"https://securedrop.org/focal-eol\" rel=\"noreferrer\">Learn More</a>"
msgstr ""

msgid "<strong>Important:</strong>&nbsp;&nbsp;Your SecureDrop server needs manual attention to resolve issues blocking automatic upgrade to the next operating system. Please contact your adminstrator. <a href=\"https://securedrop.org/focal-eol\" rel=\"noreferrer\">Learn More</a>"
msgstr ""

msgid "Navigation"
msgstr ""

Expand Down