mirror of
https://github.com/zulip/zulip.git
synced 2025-11-04 22:13:26 +00:00
security: Add tooling to nag users if a Zulip server is very old.
This will help ensure that users upgrade their Zulip server. Essentially rewritten by tabbott. Fixes part of #17826.
This commit is contained in:
@@ -80,6 +80,8 @@ export function initialize() {
|
|||||||
const ls = localstorage();
|
const ls = localstorage();
|
||||||
if (page_params.insecure_desktop_app) {
|
if (page_params.insecure_desktop_app) {
|
||||||
open($("[data-process='insecure-desktop-app']"));
|
open($("[data-process='insecure-desktop-app']"));
|
||||||
|
} else if (page_params.server_needs_upgrade) {
|
||||||
|
open($("[data-process='server-needs-upgrade']"));
|
||||||
} else if (page_params.warn_no_email === true && page_params.is_admin) {
|
} else if (page_params.warn_no_email === true && page_params.is_admin) {
|
||||||
// if email has not been set up and the user is the admin,
|
// if email has not been set up and the user is the admin,
|
||||||
// display a warning to tell them to set up an email server.
|
// display a warning to tell them to set up an email server.
|
||||||
|
|||||||
@@ -45,6 +45,15 @@
|
|||||||
</div>
|
</div>
|
||||||
<span class="close" data-dismiss="alert" aria-label="{{ _('Close') }}" role="button" tabindex=0>×</span>
|
<span class="close" data-dismiss="alert" aria-label="{{ _('Close') }}" role="button" tabindex=0>×</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div data-process="server-needs-upgrade" class="alert alert-info red">
|
||||||
|
<div data-step="1">
|
||||||
|
{{ _("This Zulip server is running an old version and should be upgraded.") }}
|
||||||
|
<a class="alert-link" href="https://zulip.readthedocs.io/en/latest/production/upgrade-or-modify.html" target="_blank" rel="noopener noreferrer">
|
||||||
|
{{ _("Learn more.") }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<span class="close" data-dismiss="alert" aria-label="{{ _('Close') }}" role="button" tabindex=0>×</span>
|
||||||
|
</div>
|
||||||
<div data-process="bankruptcy" class="alert alert-info brankruptcy">
|
<div data-process="bankruptcy" class="alert alert-info brankruptcy">
|
||||||
<div data-step="1">
|
<div data-step="1">
|
||||||
{% trans count=page_params.unread_msgs.count %}
|
{% trans count=page_params.unread_msgs.count %}
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
import calendar
|
import calendar
|
||||||
|
import datetime
|
||||||
|
import os
|
||||||
import time
|
import time
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Any, Dict, List, Optional, Tuple
|
from typing import Any, Dict, List, Optional, Tuple
|
||||||
|
|
||||||
|
import pytz
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.http import HttpRequest
|
from django.http import HttpRequest
|
||||||
from django.utils import translation
|
from django.utils import translation
|
||||||
|
from django.utils.timezone import now as timezone_now
|
||||||
from two_factor.utils import default_device
|
from two_factor.utils import default_device
|
||||||
|
|
||||||
from zerver.lib.events import do_events_register
|
from zerver.lib.events import do_events_register
|
||||||
@@ -35,6 +39,33 @@ class UserPermissionInfo:
|
|||||||
show_webathena: bool
|
show_webathena: bool
|
||||||
|
|
||||||
|
|
||||||
|
# LAST_SERVER_UPGRADE_TIME is the last time the server had a version deployed.
|
||||||
|
if settings.PRODUCTION: # nocoverage
|
||||||
|
timestamp = os.path.basename(os.path.abspath(settings.DEPLOY_ROOT))
|
||||||
|
LAST_SERVER_UPGRADE_TIME = datetime.datetime.strptime(timestamp, "%Y-%m-%d-%H-%M-%S").replace(
|
||||||
|
tzinfo=pytz.utc
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
LAST_SERVER_UPGRADE_TIME = timezone_now()
|
||||||
|
|
||||||
|
|
||||||
|
def is_outdated_server(user_profile: Optional[UserProfile]) -> bool:
|
||||||
|
# TODO: We should ideally be using the minimum of this calculation
|
||||||
|
# and the date the release tarball was generated.
|
||||||
|
tzaware_last_upgrade_time = LAST_SERVER_UPGRADE_TIME
|
||||||
|
deadline = tzaware_last_upgrade_time + datetime.timedelta(
|
||||||
|
days=settings.SERVER_UPGRADE_NAG_DEADLINE
|
||||||
|
)
|
||||||
|
|
||||||
|
if user_profile is None or not user_profile.is_realm_admin:
|
||||||
|
# Administrators get warned at the deadline; all users 30 days later.
|
||||||
|
deadline = deadline + datetime.timedelta(days=30)
|
||||||
|
|
||||||
|
if timezone_now() > deadline:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def get_furthest_read_time(user_profile: Optional[UserProfile]) -> Optional[float]:
|
def get_furthest_read_time(user_profile: Optional[UserProfile]) -> Optional[float]:
|
||||||
if user_profile is None:
|
if user_profile is None:
|
||||||
return time.time()
|
return time.time()
|
||||||
@@ -174,6 +205,7 @@ def build_page_params_for_home_page_load(
|
|||||||
test_suite=settings.TEST_SUITE,
|
test_suite=settings.TEST_SUITE,
|
||||||
poll_timeout=settings.POLL_TIMEOUT,
|
poll_timeout=settings.POLL_TIMEOUT,
|
||||||
insecure_desktop_app=insecure_desktop_app,
|
insecure_desktop_app=insecure_desktop_app,
|
||||||
|
server_needs_upgrade=is_outdated_server(user_profile),
|
||||||
login_page=settings.HOME_NOT_LOGGED_IN,
|
login_page=settings.HOME_NOT_LOGGED_IN,
|
||||||
root_domain_uri=settings.ROOT_DOMAIN_URI,
|
root_domain_uri=settings.ROOT_DOMAIN_URI,
|
||||||
save_stacktraces=settings.SAVE_FRONTEND_STACKTRACES,
|
save_stacktraces=settings.SAVE_FRONTEND_STACKTRACES,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from typing import Any
|
|||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
import orjson
|
import orjson
|
||||||
|
import pytz
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from django.utils.timezone import now as timezone_now
|
from django.utils.timezone import now as timezone_now
|
||||||
@@ -17,10 +18,10 @@ from zerver.lib.actions import (
|
|||||||
do_create_user,
|
do_create_user,
|
||||||
)
|
)
|
||||||
from zerver.lib.events import add_realm_logo_fields
|
from zerver.lib.events import add_realm_logo_fields
|
||||||
from zerver.lib.home import get_furthest_read_time
|
from zerver.lib.home import LAST_SERVER_UPGRADE_TIME, get_furthest_read_time, is_outdated_server
|
||||||
from zerver.lib.soft_deactivation import do_soft_deactivate_users
|
from zerver.lib.soft_deactivation import do_soft_deactivate_users
|
||||||
from zerver.lib.test_classes import ZulipTestCase
|
from zerver.lib.test_classes import ZulipTestCase
|
||||||
from zerver.lib.test_helpers import get_user_messages, queries_captured
|
from zerver.lib.test_helpers import get_user_messages, override_settings, queries_captured
|
||||||
from zerver.lib.users import compute_show_invites_and_add_streams
|
from zerver.lib.users import compute_show_invites_and_add_streams
|
||||||
from zerver.models import (
|
from zerver.models import (
|
||||||
DefaultStream,
|
DefaultStream,
|
||||||
@@ -211,6 +212,7 @@ class HomeTest(ZulipTestCase):
|
|||||||
"server_inline_image_preview",
|
"server_inline_image_preview",
|
||||||
"server_inline_url_embed_preview",
|
"server_inline_url_embed_preview",
|
||||||
"server_name_changes_disabled",
|
"server_name_changes_disabled",
|
||||||
|
"server_needs_upgrade",
|
||||||
"settings_send_digest_emails",
|
"settings_send_digest_emails",
|
||||||
"starred_message_counts",
|
"starred_message_counts",
|
||||||
"starred_messages",
|
"starred_messages",
|
||||||
@@ -874,6 +876,27 @@ class HomeTest(ZulipTestCase):
|
|||||||
compute_navbar_logo_url(page_params), "/static/images/logo/zulip-org-logo.svg?version=0"
|
compute_navbar_logo_url(page_params), "/static/images/logo/zulip-org-logo.svg?version=0"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@override_settings(SERVER_UPGRADE_NAG_DEADLINE=365)
|
||||||
|
def test_is_outdated_server(self) -> None:
|
||||||
|
# Check when server_upgrade_nag_deadline > last_server_upgrade_time
|
||||||
|
hamlet = self.example_user("hamlet")
|
||||||
|
iago = self.example_user("iago")
|
||||||
|
now = LAST_SERVER_UPGRADE_TIME.replace(tzinfo=pytz.utc)
|
||||||
|
with patch("zerver.lib.home.timezone_now", return_value=now + timedelta(days=10)):
|
||||||
|
self.assertEqual(is_outdated_server(iago), False)
|
||||||
|
self.assertEqual(is_outdated_server(hamlet), False)
|
||||||
|
self.assertEqual(is_outdated_server(None), False)
|
||||||
|
|
||||||
|
with patch("zerver.lib.home.timezone_now", return_value=now + timedelta(days=397)):
|
||||||
|
self.assertEqual(is_outdated_server(iago), True)
|
||||||
|
self.assertEqual(is_outdated_server(hamlet), True)
|
||||||
|
self.assertEqual(is_outdated_server(None), True)
|
||||||
|
|
||||||
|
with patch("zerver.lib.home.timezone_now", return_value=now + timedelta(days=380)):
|
||||||
|
self.assertEqual(is_outdated_server(iago), True)
|
||||||
|
self.assertEqual(is_outdated_server(hamlet), False)
|
||||||
|
self.assertEqual(is_outdated_server(None), False)
|
||||||
|
|
||||||
def test_furthest_read_time(self) -> None:
|
def test_furthest_read_time(self) -> None:
|
||||||
msg_id = self.send_test_message("hello!", sender_name="iago")
|
msg_id = self.send_test_message("hello!", sender_name="iago")
|
||||||
|
|
||||||
|
|||||||
@@ -441,3 +441,6 @@ NAGIOS_BOT_HOST = EXTERNAL_HOST
|
|||||||
|
|
||||||
# Use half of the available CPUs for data import purposes.
|
# Use half of the available CPUs for data import purposes.
|
||||||
DEFAULT_DATA_EXPORT_IMPORT_PARALLELISM = (len(os.sched_getaffinity(0)) // 2) or 1
|
DEFAULT_DATA_EXPORT_IMPORT_PARALLELISM = (len(os.sched_getaffinity(0)) // 2) or 1
|
||||||
|
|
||||||
|
# How long after the last upgrade to nag users that the server is insecure
|
||||||
|
SERVER_UPGRADE_NAG_DEADLINE = 365
|
||||||
|
|||||||
Reference in New Issue
Block a user