diff --git a/HISTORY.rst b/HISTORY.rst index e8d98c4..eecf30e 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -9,6 +9,7 @@ History * [`#114 `_] Add "bare" status view (@jamesmallen) * [`#115 `_] Adds ``WATCHMAN_DISABLE_APM`` option (@xfxf) +* [`#63 `_] Disable watchman version output by default, add ``EXPOSE_WATCHMAN_VERSION`` setting (@mwarkentin) 0.14.0 (2018-01-09) ------------------- diff --git a/README.rst b/README.rst index 4510a59..8e533c1 100644 --- a/README.rst +++ b/README.rst @@ -76,6 +76,12 @@ Pycon Canada Presentation (10 minutes) Features -------- +Human-friendly dashboard +************************ + +Visit ``http://127.0.0.1:8000/watchman/dashboard/`` to get a human-friendly HTML +representation of all of your watchman checks. + Token based authentication ************************** @@ -221,6 +227,17 @@ Use ``-h`` to see a full list of options:: python manage.py watchman -h +X-Watchman-Version response header +********************************** + +Watchman can return the version of watchman which is running to help you keep +track of whether or not your sites are using an up-to-date version. This is +disabled by default to prevent any unintended information leakage for websites +without authentication. To enable, update the ``EXPOSE_WATCHMAN_VERSION`` +setting:: + + EXPOSE_WATCHMAN_VERSION = True + Custom response code ******************** diff --git a/sample_project/sample_project/settings.py b/sample_project/sample_project/settings.py index 7ff40d9..a26a9dd 100644 --- a/sample_project/sample_project/settings.py +++ b/sample_project/sample_project/settings.py @@ -27,6 +27,10 @@ ALLOWED_HOSTS = [] +# Watchman configuration + +EXPOSE_WATCHMAN_VERSION = True + # Application definition diff --git a/tests/test_views.py b/tests/test_views.py index 2e8bbf7..41c33b0 100644 --- a/tests/test_views.py +++ b/tests/test_views.py @@ -176,6 +176,17 @@ def test_login_not_required_with_get_param(self): self.assertEqual(response.status_code, 200) + @override_settings(WATCHMAN_TOKEN='ABCDE') + def test_version_header_not_included_when_token_auth_fails(self): + # Have to manually reload settings here because override_settings + # happens after self.setUp(), but before self.tearDown() + reload_settings() + request = RequestFactory().get('/') + + response = views.status(request) + self.assertEqual(response.status_code, 403) + self.assertFalse(response.has_header('X-Watchman-Version')) + @override_settings(WATCHMAN_TOKEN='ABCDE') @override_settings(WATCHMAN_AUTH_DECORATOR='watchman.decorators.token_required') def test_login_not_required_with_authorization_header(self): @@ -243,7 +254,16 @@ def test_response_when_login_required(self): response = views.status(request) self.assertEqual(response.status_code, 200) + def test_response_version_header_missing_by_default(self): + request = RequestFactory().get('/') + response = views.status(request) + self.assertFalse(response.has_header('X-Watchman-Version')) + + @override_settings(EXPOSE_WATCHMAN_VERSION=True) def test_response_version_header(self): + # Have to manually reload settings here because override_settings + # happens after self.setUp() + reload_settings() request = RequestFactory().get('/') response = views.status(request) self.assertTrue(response.has_header('X-Watchman-Version')) @@ -336,10 +356,21 @@ def test_dashboard_response_code(self): response = views.dashboard(request) self.assertEqual(response.status_code, 200) - def test_response_version_header(self): + def test_response_version_header_and_html_missing_by_default(self): + request = RequestFactory().get('/') + response = views.dashboard(request) + self.assertFalse(response.has_header('X-Watchman-Version')) + self.assertNotIn('Watchman version:', response.content.decode()) + + @override_settings(EXPOSE_WATCHMAN_VERSION=True) + def test_response_has_version_header_and_html(self): + # Have to manually reload settings here because override_settings + # happens after self.setUp() + reload_settings() request = RequestFactory().get('/') response = views.dashboard(request) self.assertTrue(response.has_header('X-Watchman-Version')) + self.assertIn('Watchman version:', response.content.decode()) class TestPing(unittest.TestCase): diff --git a/watchman/settings.py b/watchman/settings.py index 21abb14..576e7df 100644 --- a/watchman/settings.py +++ b/watchman/settings.py @@ -30,3 +30,5 @@ DEFAULT_CHECKS = DEFAULT_CHECKS + PAID_CHECKS WATCHMAN_CHECKS = getattr(settings, 'WATCHMAN_CHECKS', DEFAULT_CHECKS) + +EXPOSE_WATCHMAN_VERSION = getattr(settings, 'EXPOSE_WATCHMAN_VERSION', False) diff --git a/watchman/templates/watchman/dashboard.html b/watchman/templates/watchman/dashboard.html index 8b87362..881a215 100644 --- a/watchman/templates/watchman/dashboard.html +++ b/watchman/templates/watchman/dashboard.html @@ -33,12 +33,6 @@

-
-
-
-
-
-
@@ -82,7 +76,6 @@

{% endblock %}{# status_content #} - {% block error_content %} {% for type_name, type in checks.items %} {% for status in type.statuses %} @@ -108,4 +101,13 @@

{{ status.error }}

{% endfor %}{# for status in type.statuses #} {% endfor %}{# for type_name, type in checks.items #} {% endblock %}{# error_content #} + +{% if expose_watchman_version %} +
+
+

Watchman version: {{ watchman_version }}

+
+
+{% endif %} + {% endblock %}{# content #} diff --git a/watchman/views.py b/watchman/views.py index 92592f2..717e891 100644 --- a/watchman/views.py +++ b/watchman/views.py @@ -8,8 +8,7 @@ from django.shortcuts import render from django.utils.translation import ugettext as _ from jsonview.decorators import json_view -from watchman import settings -from watchman import __version__ +from watchman import __version__, settings from watchman.decorators import auth from watchman.utils import get_checks @@ -83,9 +82,14 @@ def status(request): if not checks: raise Http404(_('No checks found')) + http_code = 200 if ok else settings.WATCHMAN_ERROR_CODE - return checks, http_code, {WATCHMAN_VERSION_HEADER: __version__} + response_headers = {} + if settings.EXPOSE_WATCHMAN_VERSION: + response_headers[WATCHMAN_VERSION_HEADER] = __version__ + + return checks, http_code, response_headers @non_atomic_requests def bare_status(request): @@ -160,8 +164,12 @@ def dashboard(request): response = render(request, 'watchman/dashboard.html', { 'checks': expanded_checks, - 'overall_status': overall_status + 'overall_status': overall_status, + 'watchman_version': __version__, + 'expose_watchman_version': settings.EXPOSE_WATCHMAN_VERSION, }) - response[WATCHMAN_VERSION_HEADER] = __version__ + if settings.EXPOSE_WATCHMAN_VERSION: + response[WATCHMAN_VERSION_HEADER] = __version__ + return response