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.
This commit is contained in:
Tim Abbott 2020-03-24 18:00:28 -07:00
parent 6fb438277e
commit d9bb6d0081
7 changed files with 96 additions and 22 deletions

View File

@ -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;
}

View File

@ -29,11 +29,12 @@
<div data-process="insecure-desktop-app" class="alert alert-info red">
<span class="close" data-dismiss="alert" aria-label="{{ _('Close') }}">&times;</span>
<div data-step="1">
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.
<a class="alert-link" href="https://zulipchat.com/apps" target="_blank">
Download the latest version.
</a>
{% endtrans %}
</div>
</div>
</div>

View File

@ -0,0 +1,40 @@
{% extends "zerver/portico.html" %}
{% block content %}
<div class="error_page">
<div class="container">
<div class="row-fluid">
<img src="/static/images/400art.svg" alt=""/>
<div class="errorbox config-error">
<div class="errorcontent">
<h1 class="lead">{{ _('Update required') }}</h1>
<p>
{% trans %}
You are using old version of the Zulip desktop
app that is no longer supported.
{% endtrans %}
</p>
{% if auto_update_broken %}
<p>
{% trans %}
The auto-update feature in this old version of
Zulip desktop app no longer works.
{% endtrans %}
</p>
{% endif %}
<p>
<a href="https://zulipchat.com/apps" target="_blank">
{{ _("Download the latest release.") }}
</a>
</p>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -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))

View File

@ -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)

View File

@ -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)

View File

@ -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,