From d9bb6d0081170b2e15f69d3127a59b6920d30340 Mon Sep 17 00:00:00 2001 From: Tim Abbott Date: Tue, 24 Mar 2020 18:00:28 -0700 Subject: [PATCH] compatibility: Add more strict desktop app blocking. This allows us to block use of the desktop app with insecure versions (we simply fail to load the Zulip webapp at all, instead rendering an error page). For now we block only versions that are known to be both insecure and not auto-updating, but we can easily adjust these parameters in the future. --- static/styles/portico/portico.scss | 6 +++- templates/zerver/app/navbar_alerts.html | 5 +-- templates/zerver/insecure_desktop_app.html | 40 ++++++++++++++++++++++ zerver/tests/test_compatibility.py | 17 +++++---- zerver/tests/test_home.py | 9 +++++ zerver/views/compatibility.py | 27 ++++++++++----- zerver/views/home.py | 14 +++++++- 7 files changed, 96 insertions(+), 22 deletions(-) create mode 100644 templates/zerver/insecure_desktop_app.html diff --git a/static/styles/portico/portico.scss b/static/styles/portico/portico.scss index d3aa09a0e5..f8fbb930bc 100644 --- a/static/styles/portico/portico.scss +++ b/static/styles/portico/portico.scss @@ -1297,12 +1297,16 @@ input.new-organization-button { } .error_page { - padding: 20px 0px; min-height: calc(100vh - 290px); + height: 100%; background-color: hsl(163, 42%, 85%); font-family: 'Source Sans Pro', Helvetica, Arial, sans-serif; } +.error_page .container { + padding: 20px 0px; +} + .error_page .row-fluid { margin-top: 60px; } diff --git a/templates/zerver/app/navbar_alerts.html b/templates/zerver/app/navbar_alerts.html index a808ed318d..2e0ad38853 100644 --- a/templates/zerver/app/navbar_alerts.html +++ b/templates/zerver/app/navbar_alerts.html @@ -29,11 +29,12 @@
×
- You are using an old, insecure version of the Zulip - desktop app that cannot auto-update. + {% trans %} + You are using an old version of the Zulip desktop app with known security bugs. Download the latest version. + {% endtrans %}
diff --git a/templates/zerver/insecure_desktop_app.html b/templates/zerver/insecure_desktop_app.html new file mode 100644 index 0000000000..b29a9ffe80 --- /dev/null +++ b/templates/zerver/insecure_desktop_app.html @@ -0,0 +1,40 @@ +{% extends "zerver/portico.html" %} + +{% block content %} + +
+
+
+ +
+
+

{{ _('Update required') }}

+

+ {% trans %} + You are using old version of the Zulip desktop + app that is no longer supported. + {% endtrans %} +

+ + {% if auto_update_broken %} +

+ {% trans %} + The auto-update feature in this old version of + Zulip desktop app no longer works. + {% endtrans %} +

+ {% endif %} + +

+ + {{ _("Download the latest release.") }} + +

+
+ +
+
+
+
+ +{% endblock %} diff --git a/zerver/tests/test_compatibility.py b/zerver/tests/test_compatibility.py index e3cab24373..d91dcb06eb 100644 --- a/zerver/tests/test_compatibility.py +++ b/zerver/tests/test_compatibility.py @@ -1,5 +1,6 @@ from zerver.lib.test_classes import ZulipTestCase -from zerver.views.compatibility import find_mobile_os, version_lt +from zerver.views.compatibility import find_mobile_os, version_lt, is_outdated_desktop_app + class VersionTest(ZulipTestCase): data = [case.split() for case in ''' @@ -93,13 +94,11 @@ class CompatibilityTest(ZulipTestCase): assert False # nocoverage def test_insecure_desktop_app(self) -> None: - from zerver.views.compatibility import is_outdated_desktop_app + self.assertEqual(is_outdated_desktop_app('ZulipDesktop/0.5.2 (Mac)'), (True, True, True)) + self.assertEqual(is_outdated_desktop_app('ZulipElectron/2.3.82 Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Zulip/2.3.82 Chrome/61.0.3163.100 Electron/2.0.9 Safari/537.36'), (True, True, True)) + self.assertEqual(is_outdated_desktop_app('ZulipElectron/4.0.0 Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Zulip/4.0.3 Chrome/66.0.3359.181 Electron/3.1.10 Safari/537.36'), (True, False, False)) + self.assertEqual(is_outdated_desktop_app('ZulipElectron/4.0.3 Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Zulip/4.0.3 Chrome/66.0.3359.181 Electron/3.1.10 Safari/537.36'), (False, False, False)) - self.assertTrue(is_outdated_desktop_app('ZulipDesktop/0.5.2 (Mac)')) - self.assertTrue(is_outdated_desktop_app('ZulipElectron/2.3.82 Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Zulip/2.3.82 Chrome/61.0.3163.100 Electron/2.0.9 Safari/537.36')) - self.assertFalse(is_outdated_desktop_app('ZulipElectron/4.0.0 Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Zulip/4.0.3 Chrome/66.0.3359.181 Electron/3.1.10 Safari/537.36')) - self.assertFalse(is_outdated_desktop_app('ZulipElectron/4.0.3 Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Zulip/4.0.3 Chrome/66.0.3359.181 Electron/3.1.10 Safari/537.36')) + self.assertEqual(is_outdated_desktop_app('Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36'), (False, False, False)) - self.assertFalse(is_outdated_desktop_app('Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36')) - - self.assertFalse(is_outdated_desktop_app('')) + self.assertEqual(is_outdated_desktop_app(''), (False, False, False)) diff --git a/zerver/tests/test_home.py b/zerver/tests/test_home.py index 697a72c995..d61df80290 100644 --- a/zerver/tests/test_home.py +++ b/zerver/tests/test_home.py @@ -390,6 +390,15 @@ class HomeTest(ZulipTestCase): html = result.content.decode('utf-8') self.assertIn('Accept the new Terms of Service', html) + def test_banned_desktop_app_versions(self) -> None: + user = self.example_user('hamlet') + self.login_user(user) + + result = self.client_get('/', + HTTP_USER_AGENT="ZulipElectron/2.3.82") + html = result.content.decode('utf-8') + self.assertIn('You are using old version of the Zulip desktop', html) + def test_terms_of_service_first_time_template(self) -> None: user = self.example_user('hamlet') self.login_user(user) diff --git a/zerver/views/compatibility.py b/zerver/views/compatibility.py index 047dfc7a42..f54b60546c 100644 --- a/zerver/views/compatibility.py +++ b/zerver/views/compatibility.py @@ -88,16 +88,25 @@ def check_global_compatibility(request: HttpRequest) -> HttpResponse: return json_error(legacy_compatibility_error_message) return json_success() -def is_outdated_desktop_app(user_agent_str: str) -> bool: +def is_outdated_desktop_app(user_agent_str: str) -> Tuple[bool, bool, bool]: + # Returns (insecure, banned, auto_update_broken user_agent = parse_user_agent(user_agent_str) if user_agent['name'] == 'ZulipDesktop': # The deprecated QT/webkit based desktop app, last updated in ~2016. - return True + return (True, True, True) - if user_agent['name'] == 'ZulipElectron' and version_lt(user_agent['version'], '4.0.0'): - # Versions of the modern Electron-based Zulip desktop app with - # known security issues. Versions before 2.3.82 won't - # auto-update; we may want a special notice to distinguish - # those from modern releases. - return True - return False + if user_agent['name'] != 'ZulipElectron': + return (False, False, False) + + if version_lt(user_agent['version'], '4.0.0'): + # Version 2.3.82 and older (aka <4.0.0) of the modern + # Electron-based Zulip desktop app with known security issues. + # won't auto-update; we may want a special notice to + # distinguish those from modern releases. + return (True, True, True) + + if version_lt(user_agent['version'], '4.0.3'): + # Other insecure versions should just warn. + return (True, False, False) + + return (False, False, False) diff --git a/zerver/views/home.py b/zerver/views/home.py index ed394c6023..7d779e0020 100644 --- a/zerver/views/home.py +++ b/zerver/views/home.py @@ -146,6 +146,18 @@ def home(request: HttpRequest) -> HttpResponse: @zulip_login_required def home_real(request: HttpRequest) -> HttpResponse: + # Before we do any real work, check if the app is banned. + (insecure_desktop_app, banned_desktop_app, auto_update_broken) = is_outdated_desktop_app( + request.META.get("HTTP_USER_AGENT", "")) + if banned_desktop_app: + return render( + request, + 'zerver/insecure_desktop_app.html', + context={ + "auto_update_broken": auto_update_broken, + } + ) + # We need to modify the session object every two weeks or it will expire. # This line makes reloading the page a sufficient action to keep the # session alive. @@ -228,7 +240,7 @@ def home_real(request: HttpRequest) -> HttpResponse: debug_mode = settings.DEBUG, test_suite = settings.TEST_SUITE, poll_timeout = settings.POLL_TIMEOUT, - insecure_desktop_app = is_outdated_desktop_app(request.META.get("HTTP_USER_AGENT", "")), + insecure_desktop_app = insecure_desktop_app, login_page = settings.HOME_NOT_LOGGED_IN, root_domain_uri = settings.ROOT_DOMAIN_URI, max_file_upload_size = settings.MAX_FILE_UPLOAD_SIZE,