diff --git a/openwisp_controller/config/admin.py b/openwisp_controller/config/admin.py index f51194af5..cde7abdd9 100644 --- a/openwisp_controller/config/admin.py +++ b/openwisp_controller/config/admin.py @@ -1,5 +1,6 @@ import json import logging +import re import reversion from django import forms @@ -108,6 +109,11 @@ def get_extra_context(self, pk=None): if not issubclass(self.model, AbstractVpn): ctx['CONFIG_BACKEND_FIELD_SHOWN'] = app_settings.CONFIG_BACKEND_FIELD_SHOWN if pk: + UUID_PATTERN = re.compile( + '^[a-fA-F0-9]{8}-?[a-fA-F0-9]{4}-?[a-fA-F0-9]{4}-?[a-fA-F0-9]{4}-?[a-fA-F0-9]{12}$' + ) + if not UUID_PATTERN.match(str(pk)): + raise Http404() ctx['download_url'] = reverse('{0}_download'.format(prefix), args=[pk]) try: has_config = True @@ -137,9 +143,10 @@ def change_view(self, request, object_id, form_url='', extra_context=None): def get_urls(self): options = getattr(self.model, '_meta') url_prefix = '{0}_{1}'.format(options.app_label, options.model_name) + UUID_PATTERN = r'([a-fA-F0-9]{8}-?[a-fA-F0-9]{4}-?[a-fA-F0-9]{4}-?[a-fA-F0-9]{4}-?[a-fA-F0-9]{12})' return [ re_path( - r'^download/(?P[^/]+)/$', + r'^download/{0}/$'.format(UUID_PATTERN), self.admin_site.admin_view(self.download_view), name='{0}_download'.format(url_prefix), ), @@ -149,7 +156,7 @@ def get_urls(self): name='{0}_preview'.format(url_prefix), ), re_path( - r'^(?P[^/]+)/context\.json$', + r'^{0}/context\.json$'.format(UUID_PATTERN), self.admin_site.admin_view(self.context_view), name='{0}_context'.format(url_prefix), ), diff --git a/openwisp_controller/config/tests/pytest.py b/openwisp_controller/config/tests/pytest.py index 4d0987e7d..516787405 100644 --- a/openwisp_controller/config/tests/pytest.py +++ b/openwisp_controller/config/tests/pytest.py @@ -18,6 +18,7 @@ @pytest.mark.django_db(transaction=True) class TestDeviceConsumer(CreateDeviceMixin): model = Device + UUID_PATTERN = '[a-fA-F0-9]{8}-?[a-fA-F0-9]{4}-?[a-fA-F0-9]{4}-?[a-fA-F0-9]{4}-?[a-fA-F0-9]{12}' application = ProtocolTypeRouter( { 'websocket': AllowedHostsOriginValidator( @@ -25,7 +26,7 @@ class TestDeviceConsumer(CreateDeviceMixin): URLRouter( [ re_path( - r'^ws/controller/device/(?P[^/]+)/$', + f'^ws/controller/device/(?P{UUID_PATTERN})/$', BaseDeviceConsumer.as_asgi(), ) ] diff --git a/openwisp_controller/config/utils.py b/openwisp_controller/config/utils.py index 78989fb1b..20c13a5dd 100644 --- a/openwisp_controller/config/utils.py +++ b/openwisp_controller/config/utils.py @@ -114,24 +114,26 @@ def get_controller_urls(views_module): """ used by third party apps to reduce boilerplate """ + UUID_PATTERN = '[a-fA-F0-9]{8}-?[a-fA-F0-9]{4}-?[a-fA-F0-9]{4}-?[a-fA-F0-9]{4}-?[a-fA-F0-9]{12}' + urls = [ re_path( - 'controller/device/checksum/(?P[^/]+)/$', + f'controller/device/checksum/(?P{UUID_PATTERN})/$', views_module.device_checksum, name='device_checksum', ), re_path( - 'controller/device/download-config/(?P[^/]+)/$', + f'controller/device/download-config/(?P{UUID_PATTERN})/$', views_module.device_download_config, name='device_download_config', ), re_path( - 'controller/device/update-info/(?P[^/]+)/$', + f'controller/device/update-info/(?P{UUID_PATTERN})/$', views_module.device_update_info, name='device_update_info', ), re_path( - 'controller/device/report-status/(?P[^/]+)/$', + f'controller/device/report-status/(?P{UUID_PATTERN})/$', views_module.device_report_status, name='device_report_status', ), @@ -141,33 +143,33 @@ def get_controller_urls(views_module): name='device_register', ), re_path( - 'controller/vpn/checksum/(?P[^/]+)/$', + f'controller/vpn/checksum/(?P{UUID_PATTERN})/$', views_module.vpn_checksum, name='vpn_checksum', ), re_path( - 'controller/vpn/download-config/(?P[^/]+)/$', + f'controller/vpn/download-config/(?P{UUID_PATTERN})/$', views_module.vpn_download_config, name='vpn_download_config', ), # legacy URLs re_path( - 'controller/checksum/(?P[^/]+)/$', + f'controller/checksum/(?P{UUID_PATTERN})/$', views_module.device_checksum, name='checksum_legacy', ), re_path( - 'controller/download-config/(?P[^/]+)/$', + f'controller/download-config/(?P{UUID_PATTERN})/$', views_module.device_download_config, name='download_config_legacy', ), re_path( - 'controller/update-info/(?P[^/]+)/$', + f'controller/update-info/(?P{UUID_PATTERN})/$', views_module.device_update_info, name='update_info_legacy', ), re_path( - 'controller/report-status/(?P[^/]+)/$', + f'controller/report-status/(?P{UUID_PATTERN})/$', views_module.device_report_status, name='report_status_legacy', ), diff --git a/openwisp_controller/connection/channels/routing.py b/openwisp_controller/connection/channels/routing.py index 70c426332..4c38e0154 100644 --- a/openwisp_controller/connection/channels/routing.py +++ b/openwisp_controller/connection/channels/routing.py @@ -4,9 +4,11 @@ def get_routes(consumer=ow_consumer): + UUID_PATTERN = '[a-fA-F0-9]{8}-?[a-fA-F0-9]{4}-?[a-fA-F0-9]{4}-?[a-fA-F0-9]{4}-?[a-fA-F0-9]{12}' + return [ re_path( - r'^ws/controller/device/(?P[^/]+)/command$', + f'^ws/controller/device/(?P{UUID_PATTERN})/command$', consumer.CommandConsumer.as_asgi(), ) ]