openwisp-controller 1.3__tar.gz → 1.3.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/CHANGES.rst +108 -0
- {openwisp_controller-1.3/openwisp_controller.egg-info → openwisp_controller-1.3.2}/PKG-INFO +1 -1
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/__init__.py +1 -1
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/admin.py +14 -2
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/api/download_views.py +6 -6
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/static/config/js/management_ip.js +9 -1
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/static/config/js/relevant_templates.js +19 -4
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/templates/admin/config/device/delete_selected_confirmation.html +1 -1
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/tests/test_admin.py +76 -1
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/tests/test_api.py +62 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/tests/test_selenium.py +27 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/connectors/openwrt/ssh.py +3 -1
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/static/connection/js/commands.js +25 -15
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/tests/test_models.py +44 -1
- openwisp_controller-1.3.2/openwisp_controller/connection/tests/test_selenium.py +126 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/api/serializers.py +85 -38
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/api/views.py +14 -5
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/tests/test_api.py +409 -14
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/pki/api/views.py +2 -3
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/pki/tests/test_api.py +21 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/subnet_division/admin.py +11 -7
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/subnet_division/tests/test_admin.py +10 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2/openwisp_controller.egg-info}/PKG-INFO +1 -1
- openwisp_controller-1.3/openwisp_controller/connection/tests/test_selenium.py +0 -65
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/LICENSE +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/MANIFEST.in +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/README.rst +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/admin.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/base.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/checks.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/__init__.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/api/__init__.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/api/filters.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/api/serializers.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/api/urls.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/api/views.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/api/zerotier_service.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/apps.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/base/__init__.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/base/base.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/base/cache.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/base/channels_consumer.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/base/config.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/base/device.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/base/device_group.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/base/multitenancy.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/base/tag.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/base/template.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/base/vpn.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/base/whois.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/controller/__init__.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/controller/urls.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/controller/views.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/converters.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/crypto.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/exceptions.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/exportable.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/filters.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/fixtures/test_templates.json +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/handlers.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/management/__init__.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/management/commands/__init__.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/management/commands/clear_last_ip.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/management/commands/print_cache_dependencies.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0001_squashed_0002_config_settings_uuid.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0003_template_tags.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0004_add_device_model.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0005_populate_device.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0006_config_device_not_null.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0007_simplify_config.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0008_update_indexes.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0009_device_system.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0010_auto_20180106_1814.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0011_update_device_mac_address.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0012_auto_20180219_1501.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0013_last_ip_management_ip_and_status_applied.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0014_device_hardware_id.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0015_default_groups_permissions.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0016_default_organization_config_settings.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0017_template_name_organization_unique_together.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0018_config_context.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0019_organization_mac_add_hardware_id_name_unique_together.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0020_remove_config_organization.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0021_vpn_key.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0022_vpn_format_dh.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0023_update_context.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0024_update_context_data.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0025_update_device_key.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0026_hardware_id_not_unique.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0027_add_indexes_on_ip_fields.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0028_template_default_values.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0029_merge_django_netjsonconfig.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0030_django_taggit_update.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0031_update_vpn_dh_param.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0032_update_legacy_vpn_backend.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0033_name_unique_per_organization.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0034_template_required.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0035_device_name_unique_optional.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0036_device_group.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0037_alter_taggedtemplate.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0038_vpn_subnet.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0039_wireguard_vxlan_ipam.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0040_vpnclient_ip_setnull.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0041_default_groups_organizationconfigsettings_permission.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0042_multiple_wireguard_tunnels.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0043_devicegroup_templates.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0044_config_error_reason.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0045_alter_vpn_webhook_endpoint.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0046_organizationlimits.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0047_add_organizationlimits.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0048_wifi_radio_band_migration.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0049_devicegroup_context.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0050_alter_vpnclient_unique_together.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0051_organizationconfigsettings_context.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0052_vpn_node_network_id.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0053_vpnclient_secret.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0054_device__is_deactivated.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0055_alter_config_status.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0056_vpnclient_template.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0057_populate_vpnclient_template.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0058_alter_vpnclient_template.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0059_zerotier_templates_ow_zt_to_global.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0060_cleanup_api_task_notification_types.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0061_config_checksum_db.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0062_whoisinfo.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0063_replace_jsonfield_with_django_builtin.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0064_template_notes.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0065_whoisinfo_unreferenced_since.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/__init__.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/models.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/settings.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/signals.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/sortedm2m/__init__.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/sortedm2m/fields.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/sortedm2m/forms.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/static/config/css/admin.css +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/static/config/css/device-delete-confirmation.css +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/static/config/css/devicegroup.css +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/static/config/css/lib/advanced-mode.css +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/static/config/css/lib/img/jsoneditor-icons.svg +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/static/config/css/lib/jsonschema-ui.css +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/static/config/js/device-delete-confirmation.js +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/static/config/js/lib/advanced-mode.js +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/static/config/js/lib/jsonschema-ui.js +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/static/config/js/lib/tomorrow_night_bright.js +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/static/config/js/preview.js +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/static/config/js/switcher.js +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/static/config/js/tabs.js +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/static/config/js/unsaved_changes.js +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/static/config/js/utils.js +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/static/config/js/vpn.js +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/static/config/js/widget.js +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/static/import_export/import-openwisp.css +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/static/sortedm2m/patch_sortedm2m.js +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/static/support.css +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/static/whois/css/whois.css +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/static/whois/images/whois_globe.svg +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/static/whois/js/whois.js +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/tasks.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/tasks_zerotier.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/templates/admin/config/change_device_group.html +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/templates/admin/config/change_form.html +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/templates/admin/config/change_list_device.html +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/templates/admin/config/clone_template_form.html +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/templates/admin/config/device/change_form.html +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/templates/admin/config/device/delete_confirmation.html +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/templates/admin/config/device_recover_form.html +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/templates/admin/config/jsonschema-widget.html +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/templates/admin/config/preview.html +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/templates/admin/config/system_context.html +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/templates/admin/device_group/change_form.html +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/templates/admin/import_export/import.html +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/templates/reversion/config/revision_form.html +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/tests/__init__.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/tests/pytest.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/tests/test_apps.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/tests/test_config.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/tests/test_controller.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/tests/test_converters.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/tests/test_device.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/tests/test_device_group.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/tests/test_handlers.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/tests/test_notifications.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/tests/test_tag.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/tests/test_template.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/tests/test_views.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/tests/test_vpn.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/tests/utils.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/urls.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/utils.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/validators.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/views.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/whois/__init__.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/whois/commands.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/whois/handlers.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/whois/mixins.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/whois/serializers.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/whois/service.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/whois/tasks.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/whois/tests/__init__.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/whois/tests/tests.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/whois/tests/utils.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/whois/utils.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/widgets.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/__init__.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/admin.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/api/serializers.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/api/urls.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/api/views.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/apps.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/base/__init__.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/base/models.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/channels/consumers.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/channels/routing.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/commands.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/connectors/__init__.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/connectors/airos/snmp.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/connectors/exceptions.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/connectors/openwrt/__init__.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/connectors/openwrt/snmp.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/connectors/snmp.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/connectors/ssh.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/exceptions.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/migrations/0001_initial.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/migrations/0002_credentials_auto_add.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/migrations/0003_default_group_permissions.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/migrations/0004_django3_1_upgrade.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/migrations/0005_device_connection_failure_reason.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/migrations/0006_name_unique_per_organization.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/migrations/0007_command.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/migrations/0008_remove_conflicting_deviceconnections.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/migrations/0009_alter_deviceconnection_unique_together.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/migrations/0010_replace_jsonfield_with_django_builtin.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/migrations/__init__.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/models.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/schema.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/settings.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/signals.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/static/connection/css/command-inline.css +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/static/connection/css/credentials.css +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/static/connection/js/credentials.js +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/static/connection/js/lib/reconnecting-websocket.min.js +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/tasks.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/tests/__init__.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/tests/base.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/tests/pytest.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/tests/test-key.ed25519 +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/tests/test-key.rsa +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/tests/test_admin.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/tests/test_api.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/tests/test_command_utilities.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/tests/test_notifications.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/tests/test_snmp.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/tests/test_ssh.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/tests/test_tasks.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/tests/utils.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/utils.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/widgets.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/context_processors.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/__init__.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/admin.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/api/__init__.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/api/filters.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/api/urls.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/apps.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/base/__init__.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/base/models.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/channels/__init__.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/channels/consumers.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/channels/routing.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/estimated_location/__init__.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/estimated_location/handlers.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/estimated_location/mixins.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/estimated_location/service.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/estimated_location/tasks.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/estimated_location/tests/__init__.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/estimated_location/tests/tests.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/estimated_location/tests/utils.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/estimated_location/utils.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/exportable.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/migrations/0001_initial.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/migrations/0002_default_groups_permissions.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/migrations/0003_alter_devicelocation_floorplan_location.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/migrations/0004_location_is_estimated.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/migrations/0005_organizationgeosettings.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/migrations/0006_create_geo_settings_for_existing_orgs.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/migrations/__init__.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/models.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/settings.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/tasks.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/templates/admin/geo/device_location_inline.html +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/templates/admin/geo/location/change_form.html +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/templates/admin/widgets/foreign_key_raw_id.html +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/tests/__init__.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/tests/pytest.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/tests/test_admin.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/tests/test_admin_inline.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/tests/test_apps.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/tests/test_models.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/tests/test_selenium.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/tests/utils.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/utils.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/migrations.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/mixins.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/pki/__init__.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/pki/admin.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/pki/api/serializers.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/pki/api/urls.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/pki/apps.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/pki/base/__init__.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/pki/base/models.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/pki/migrations/0001_initial.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/pki/migrations/0002_add_organization_name.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/pki/migrations/0003_fill_organization_name.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/pki/migrations/0004_auto_20180106_1814.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/pki/migrations/0005_organizational_unit_name.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/pki/migrations/0006_add_x509_passphrase_field.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/pki/migrations/0007_default_groups_permissions.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/pki/migrations/0008_serial_number_length.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/pki/migrations/0009_common_name_maxlength_64.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/pki/migrations/0010_common_name_organization_unique.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/pki/migrations/0011_disallowed_blank_key_length_or_digest.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/pki/migrations/0012_alter_ca_extensions_alter_ca_key_length_and_more.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/pki/migrations/__init__.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/pki/models.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/pki/static/admin/pki/js/show-org-field.js +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/pki/templates/admin/pki/change_form.html +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/pki/tests/__init__.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/pki/tests/test_admin.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/pki/tests/test_models.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/pki/tests/utils.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/pki/urls.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/pki/utils.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/routing.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/serializers.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/settings.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/subnet_division/__init__.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/subnet_division/apps.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/subnet_division/base/models.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/subnet_division/filters.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/subnet_division/migrations/0001_initial.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/subnet_division/migrations/0002_default_group_migration.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/subnet_division/migrations/0003_related_field_allow_blank.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/subnet_division/migrations/0004_index_rule_on_delete.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/subnet_division/migrations/0005_number_of_subnets_and_ips.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/subnet_division/migrations/__init__.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/subnet_division/models.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/subnet_division/rule_types/__init__.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/subnet_division/rule_types/base.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/subnet_division/rule_types/device.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/subnet_division/rule_types/vpn.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/subnet_division/settings.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/subnet_division/signals.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/subnet_division/static/subnet-division/css/subnet-division.css +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/subnet_division/static/subnet-division/js/subnet-division.js +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/subnet_division/tasks.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/subnet_division/tests/__init__.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/subnet_division/tests/helpers.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/subnet_division/tests/test_models.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/subnet_division/tests/test_rule.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/subnet_division/utils.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/tests/__init__.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/tests/mixins.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/tests/test_migrationgraph.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/tests/test_selenium.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/tests/test_users_integration.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/tests/test_utilities.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/tests/utils.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/urls.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/vpn_backends.py +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller.egg-info/SOURCES.txt +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller.egg-info/dependency_links.txt +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller.egg-info/not-zip-safe +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller.egg-info/requires.txt +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller.egg-info/top_level.txt +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/pyproject.toml +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/requirements.txt +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/setup.cfg +0 -0
- {openwisp_controller-1.3 → openwisp_controller-1.3.2}/setup.py +0 -0
|
@@ -1,6 +1,114 @@
|
|
|
1
1
|
Changelog
|
|
2
2
|
=========
|
|
3
3
|
|
|
4
|
+
Version 1.3.3 [2026-10-23]
|
|
5
|
+
--------------------------
|
|
6
|
+
|
|
7
|
+
Bugfixes
|
|
8
|
+
~~~~~~~~
|
|
9
|
+
|
|
10
|
+
- Prevented cross-organization configuration access in admin
|
|
11
|
+
|
|
12
|
+
Updated admin configuration download and context views to resolve
|
|
13
|
+
devices, VPNs, and templates through organization-scoped querysets and
|
|
14
|
+
object permissions.
|
|
15
|
+
|
|
16
|
+
This ensures users cannot access configuration data for resources
|
|
17
|
+
belonging to unauthorized organizations.
|
|
18
|
+
|
|
19
|
+
Advisory: `GHSA-2x7f-qxwh-m483
|
|
20
|
+
<https://github.com/openwisp/openwisp-controller/security/advisories/GHSA-2x7f-qxwh-m483>`_.
|
|
21
|
+
|
|
22
|
+
- Prevented cross-organization access to configuration downloads
|
|
23
|
+
|
|
24
|
+
Updated API configuration download views to resolve devices, VPNs, and
|
|
25
|
+
templates through organization-scoped querysets and object permissions.
|
|
26
|
+
|
|
27
|
+
This ensures users cannot access configuration downloads for resources
|
|
28
|
+
belonging to unauthorized organizations.
|
|
29
|
+
|
|
30
|
+
Advisory: `GHSA-5c5w-h9xc-3mq5
|
|
31
|
+
<https://github.com/openwisp/openwisp-controller/security/advisories/GHSA-5c5w-h9xc-3mq5>`_.
|
|
32
|
+
|
|
33
|
+
- Escaped OpenWrt change_password SSH command
|
|
34
|
+
|
|
35
|
+
The password and confirm_password values were interpolated into the
|
|
36
|
+
remote shell command without escaping, allowing shell command injection
|
|
37
|
+
on the device. They are now quoted with shlex.quote() and passed to
|
|
38
|
+
printf instead of echo -e, so backslashes are no longer interpreted.
|
|
39
|
+
|
|
40
|
+
Advisory: `GHSA-rcf7-q946-fvpg
|
|
41
|
+
<https://github.com/openwisp/openwisp-controller/security/advisories/GHSA-rcf7-q946-fvpg>`_.
|
|
42
|
+
|
|
43
|
+
- Prevented cross-organization access to CA CRLs
|
|
44
|
+
|
|
45
|
+
Organization managers with permission to view CAs could guess CA IDs and
|
|
46
|
+
download certificate revocation lists belonging to other organizations.
|
|
47
|
+
|
|
48
|
+
The issue did not expose CA private keys or allow changes. It primarily
|
|
49
|
+
affected deployments configured to protect CRLs, because CRLs are
|
|
50
|
+
publicly accessible by default.
|
|
51
|
+
|
|
52
|
+
Advisory: `GHSA-9gh4-xw24-9xh5
|
|
53
|
+
<https://github.com/openwisp/openwisp-controller/security/advisories/GHSA-9gh4-xw24-9xh5>`_.
|
|
54
|
+
|
|
55
|
+
Version 1.3.1 [2026-10-15]
|
|
56
|
+
--------------------------
|
|
57
|
+
|
|
58
|
+
Security
|
|
59
|
+
~~~~~~~~
|
|
60
|
+
|
|
61
|
+
- Fixed insecure multi-tenancy boundary in location REST API
|
|
62
|
+
|
|
63
|
+
Fixed a **security issue** that allowed organization managers to link
|
|
64
|
+
locations and floorplans belonging to other organizations to their
|
|
65
|
+
devices by submitting known object IDs.
|
|
66
|
+
|
|
67
|
+
The API now resolves related objects only from organizations managed by
|
|
68
|
+
the requesting user and validates the organization of all
|
|
69
|
+
device-location relationships. Device lists for a location are scoped to
|
|
70
|
+
managed organizations and no longer expose device keys.
|
|
71
|
+
|
|
72
|
+
Nested location and floorplan writes are validated before being saved
|
|
73
|
+
and run in a database transaction, preventing rejected requests from
|
|
74
|
+
leaving related database objects or files behind.
|
|
75
|
+
|
|
76
|
+
Advisory: `GHSA-39c4-xj33-j8j3
|
|
77
|
+
<https://github.com/openwisp/openwisp-controller/security/advisories/GHSA-39c4-xj33-j8j3>`_.
|
|
78
|
+
|
|
79
|
+
- Fixed XSS vulnerabilities in device and command admin UI
|
|
80
|
+
|
|
81
|
+
Fixed cross-site scripting (XSS) vulnerabilities caused by improper
|
|
82
|
+
escaping of user-controlled data in the following admin pages:
|
|
83
|
+
|
|
84
|
+
- Device and device group forms (template names).
|
|
85
|
+
- Device pages (command inputs, outputs, and management IP).
|
|
86
|
+
- Subnet administration (device names and links).
|
|
87
|
+
|
|
88
|
+
These vulnerabilities could allow attackers to execute malicious
|
|
89
|
+
JavaScript in an administrator's browser.
|
|
90
|
+
|
|
91
|
+
Advisory: `GHSA-4f2x-pxjx-h8m3
|
|
92
|
+
<https://github.com/openwisp/openwisp-controller/security/advisories/GHSA-4f2x-pxjx-h8m3>`_.
|
|
93
|
+
|
|
94
|
+
Bugfixes
|
|
95
|
+
~~~~~~~~
|
|
96
|
+
|
|
97
|
+
- Fixed loss of data in PATCH requests to device location endpoint `#1510
|
|
98
|
+
<https://github.com/openwisp/openwisp-controller/issues/1510>`_
|
|
99
|
+
|
|
100
|
+
Sending `PATCH` requests to the device location endpoint
|
|
101
|
+
(`/api/v1/controller/device/{id}/location/`) now doesn't set `location`
|
|
102
|
+
and `floorplan` to `NULL` when they are not included in the request
|
|
103
|
+
body.
|
|
104
|
+
|
|
105
|
+
Changes
|
|
106
|
+
~~~~~~~
|
|
107
|
+
|
|
108
|
+
- Added IP address search to DeviceAdmin
|
|
109
|
+
|
|
110
|
+
Enabled searches by last and management IP addresses.
|
|
111
|
+
|
|
4
112
|
Version 1.3.0 [2026-09-03]
|
|
5
113
|
--------------------------
|
|
6
114
|
|
|
@@ -13,6 +13,7 @@ from django.contrib.contenttypes.models import ContentType
|
|
|
13
13
|
from django.core.exceptions import (
|
|
14
14
|
FieldDoesNotExist,
|
|
15
15
|
ObjectDoesNotExist,
|
|
16
|
+
PermissionDenied,
|
|
16
17
|
ValidationError,
|
|
17
18
|
)
|
|
18
19
|
from django.db import models
|
|
@@ -318,7 +319,10 @@ class BaseConfigAdmin(BaseAdmin):
|
|
|
318
319
|
)
|
|
319
320
|
|
|
320
321
|
def download_view(self, request, pk):
|
|
321
|
-
instance =
|
|
322
|
+
instance = self._get_instance(request, pk)
|
|
323
|
+
return self.get_download_response(instance)
|
|
324
|
+
|
|
325
|
+
def get_download_response(self, instance):
|
|
322
326
|
if hasattr(instance, "generate"):
|
|
323
327
|
config = instance
|
|
324
328
|
elif hasattr(instance, "config"):
|
|
@@ -333,10 +337,16 @@ class BaseConfigAdmin(BaseAdmin):
|
|
|
333
337
|
)
|
|
334
338
|
|
|
335
339
|
def context_view(self, request, pk):
|
|
336
|
-
instance =
|
|
340
|
+
instance = self._get_instance(request, pk)
|
|
337
341
|
context = json.dumps(instance.get_context())
|
|
338
342
|
return HttpResponse(context, content_type="application/json")
|
|
339
343
|
|
|
344
|
+
def _get_instance(self, request, pk):
|
|
345
|
+
instance = get_object_or_404(self.get_queryset(request), pk=pk)
|
|
346
|
+
if not self.has_view_permission(request, instance):
|
|
347
|
+
raise PermissionDenied
|
|
348
|
+
return instance
|
|
349
|
+
|
|
340
350
|
|
|
341
351
|
class BaseForm(forms.ModelForm):
|
|
342
352
|
"""
|
|
@@ -542,6 +552,8 @@ class DeviceAdmin(MultitenantAdminMixin, BaseConfigAdmin, CopyableFieldsAdmin):
|
|
|
542
552
|
"model",
|
|
543
553
|
"os",
|
|
544
554
|
"system",
|
|
555
|
+
"last_ip",
|
|
556
|
+
"management_ip",
|
|
545
557
|
"devicelocation__location__address",
|
|
546
558
|
]
|
|
547
559
|
readonly_fields = ["last_ip", "uuid"]
|
|
@@ -17,29 +17,29 @@ Template = load_model("config", "Template")
|
|
|
17
17
|
|
|
18
18
|
class DownloadVpnView(ProtectedAPIMixin, RetrieveAPIView):
|
|
19
19
|
serializer_class = VpnSerializer
|
|
20
|
-
queryset = Vpn.objects.
|
|
20
|
+
queryset = Vpn.objects.all()
|
|
21
21
|
model = Vpn
|
|
22
22
|
|
|
23
23
|
def retrieve(self, request, *args, **kwargs):
|
|
24
|
-
return BaseConfigAdmin.
|
|
24
|
+
return BaseConfigAdmin.get_download_response(self, self.get_object())
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
class DownloadDeviceView(ProtectedAPIMixin, RetrieveAPIView):
|
|
28
28
|
serializer_class = DeviceListSerializer
|
|
29
|
-
queryset = Device.objects.
|
|
29
|
+
queryset = Device.objects.all()
|
|
30
30
|
model = Device
|
|
31
31
|
|
|
32
32
|
def retrieve(self, request, *args, **kwargs):
|
|
33
|
-
return BaseConfigAdmin.
|
|
33
|
+
return BaseConfigAdmin.get_download_response(self, self.get_object())
|
|
34
34
|
|
|
35
35
|
|
|
36
36
|
class DownloadTemplateconfiguration(ProtectedAPIMixin, RetrieveAPIView):
|
|
37
37
|
serializer_class = TemplateSerializer
|
|
38
|
-
queryset = Template.objects.
|
|
38
|
+
queryset = Template.objects.all()
|
|
39
39
|
model = Template
|
|
40
40
|
|
|
41
41
|
def retrieve(self, request, *args, **kwargs):
|
|
42
|
-
return BaseConfigAdmin.
|
|
42
|
+
return BaseConfigAdmin.get_download_response(self, self.get_object())
|
|
43
43
|
|
|
44
44
|
|
|
45
45
|
download_vpn_config = DownloadVpnView.as_view()
|
|
@@ -3,6 +3,14 @@ django.jQuery(function ($) {
|
|
|
3
3
|
if ($(".add-form").length || !$("#device_form").length) {
|
|
4
4
|
return;
|
|
5
5
|
}
|
|
6
|
+
// For XSS prevention
|
|
7
|
+
function escapeHtml(text) {
|
|
8
|
+
if (text === null || text === undefined) return "";
|
|
9
|
+
const div = document.createElement("div");
|
|
10
|
+
div.textContent = text;
|
|
11
|
+
return div.innerHTML;
|
|
12
|
+
}
|
|
13
|
+
|
|
6
14
|
var editAltText = gettext("Edit"),
|
|
7
15
|
cancelAltText = gettext("Cancel");
|
|
8
16
|
|
|
@@ -12,7 +20,7 @@ django.jQuery(function ($) {
|
|
|
12
20
|
ipInput.after(function () {
|
|
13
21
|
ipInput.hide();
|
|
14
22
|
return (
|
|
15
|
-
`<span class="readonly" id="management_ip_text">${initialIp}</span>` +
|
|
23
|
+
`<span class="readonly" id="management_ip_text">${escapeHtml(initialIp)}</span>` +
|
|
16
24
|
`<a class="related-widget-wrapper-link change-related" id="edit_management_ip"><img value="edit" src="${window.staticUrl}admin/img/icon-changelink.svg"` +
|
|
17
25
|
` alt="${editAltText}" title="${editAltText}"></a>`
|
|
18
26
|
);
|
|
@@ -30,10 +30,25 @@ django.jQuery(function ($) {
|
|
|
30
30
|
isDeviceGroup() && templateConfig.backend
|
|
31
31
|
? ` (backend: ${templateConfig.backend})`
|
|
32
32
|
: "",
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
inputId = `id_config-${prefix}templates_${index}`,
|
|
34
|
+
inputField = $("<input>")
|
|
35
|
+
.attr({
|
|
36
|
+
type: "checkbox",
|
|
37
|
+
value: templateId,
|
|
38
|
+
id: inputId,
|
|
39
|
+
"data-required": templateConfig.required,
|
|
40
|
+
})
|
|
41
|
+
.addClass("sortedm2m"),
|
|
42
|
+
element = $('<li class="sortedm2m-item">').append(
|
|
43
|
+
$("<label>")
|
|
44
|
+
.attr("for", inputId)
|
|
45
|
+
.append(inputField)
|
|
46
|
+
.append(
|
|
47
|
+
document.createTextNode(
|
|
48
|
+
` ${templateConfig.name}${requiredString}${backendString}`,
|
|
49
|
+
),
|
|
50
|
+
),
|
|
51
|
+
);
|
|
37
52
|
|
|
38
53
|
if (templateConfig.required) {
|
|
39
54
|
inputField.prop("disabled", true);
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
{% endblocktranslate %}
|
|
52
52
|
</p>
|
|
53
53
|
<span>
|
|
54
|
-
<button class="danger-btn" id="warning-ack">
|
|
54
|
+
<button class="danger-btn" id="warning-ack">{% blocktranslate count counter=active_devices|length %}I understand the risks, delete the device{% plural %}I understand the risks, delete the devices{% endblocktranslate %}</button>
|
|
55
55
|
<button class="button cancel-link">{% translate 'No, take me back' %}</button>
|
|
56
56
|
</span>
|
|
57
57
|
</li>
|
{openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/tests/test_admin.py
RENAMED
|
@@ -507,6 +507,69 @@ class TestAdmin(
|
|
|
507
507
|
],
|
|
508
508
|
)
|
|
509
509
|
|
|
510
|
+
def test_config_download_and_context_views_multitenancy(self):
|
|
511
|
+
data = self._create_multitenancy_test_env(vpn=True)
|
|
512
|
+
self.client.force_login(data["operator"])
|
|
513
|
+
instances = {
|
|
514
|
+
"device": (data["c1"].device, data["c2"].device),
|
|
515
|
+
"template": (data["t1"], data["t2"]),
|
|
516
|
+
"vpn": (data["vpn1"], data["vpn2"]),
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
for model, (managed, unmanaged) in instances.items():
|
|
520
|
+
with self.subTest(model=model, view="download", organization="managed"):
|
|
521
|
+
response = self.client.get(
|
|
522
|
+
reverse(
|
|
523
|
+
f"admin:{self.app_label}_{model}_download", args=[managed.pk]
|
|
524
|
+
)
|
|
525
|
+
)
|
|
526
|
+
self.assertEqual(response.status_code, 200)
|
|
527
|
+
|
|
528
|
+
with self.subTest(model=model, view="download", organization="unmanaged"):
|
|
529
|
+
response = self.client.get(
|
|
530
|
+
reverse(
|
|
531
|
+
f"admin:{self.app_label}_{model}_download", args=[unmanaged.pk]
|
|
532
|
+
)
|
|
533
|
+
)
|
|
534
|
+
self.assertEqual(response.status_code, 404)
|
|
535
|
+
|
|
536
|
+
with self.subTest(model=model, view="context", organization="managed"):
|
|
537
|
+
response = self.client.get(
|
|
538
|
+
reverse(
|
|
539
|
+
f"admin:{self.app_label}_{model}_context", args=[managed.pk]
|
|
540
|
+
)
|
|
541
|
+
)
|
|
542
|
+
self.assertEqual(response.status_code, 200)
|
|
543
|
+
|
|
544
|
+
with self.subTest(model=model, view="context", organization="unmanaged"):
|
|
545
|
+
response = self.client.get(
|
|
546
|
+
reverse(
|
|
547
|
+
f"admin:{self.app_label}_{model}_context", args=[unmanaged.pk]
|
|
548
|
+
)
|
|
549
|
+
)
|
|
550
|
+
self.assertEqual(response.status_code, 404)
|
|
551
|
+
|
|
552
|
+
with (
|
|
553
|
+
self.subTest(model=model, permission="denied"),
|
|
554
|
+
patch.object(
|
|
555
|
+
admin.site._registry[managed.__class__],
|
|
556
|
+
"has_view_permission",
|
|
557
|
+
return_value=False,
|
|
558
|
+
),
|
|
559
|
+
):
|
|
560
|
+
response = self.client.get(
|
|
561
|
+
reverse(
|
|
562
|
+
f"admin:{self.app_label}_{model}_download", args=[managed.pk]
|
|
563
|
+
)
|
|
564
|
+
)
|
|
565
|
+
self.assertEqual(response.status_code, 403)
|
|
566
|
+
response = self.client.get(
|
|
567
|
+
reverse(
|
|
568
|
+
f"admin:{self.app_label}_{model}_context", args=[managed.pk]
|
|
569
|
+
)
|
|
570
|
+
)
|
|
571
|
+
self.assertEqual(response.status_code, 403)
|
|
572
|
+
|
|
510
573
|
def test_vpn_organization_fk_autocomplete_view(self):
|
|
511
574
|
data = self._create_multitenancy_test_env()
|
|
512
575
|
self._test_multitenant_admin(
|
|
@@ -1476,13 +1539,25 @@ class TestAdmin(
|
|
|
1476
1539
|
self.assertContains(response, '<option value="netjsonconfig.OpenWisp" selected')
|
|
1477
1540
|
|
|
1478
1541
|
def test_device_search(self):
|
|
1479
|
-
d = self._create_device(
|
|
1542
|
+
d = self._create_device(
|
|
1543
|
+
name="admin-search-test",
|
|
1544
|
+
last_ip="192.0.2.1",
|
|
1545
|
+
management_ip="198.51.100.1",
|
|
1546
|
+
)
|
|
1480
1547
|
path = reverse(f"admin:{self.app_label}_device_changelist")
|
|
1481
1548
|
response = self.client.get(path, {"q": str(d.pk.hex)})
|
|
1482
1549
|
self.assertContains(response, "admin-search-test")
|
|
1483
1550
|
response = self.client.get(path, {"q": "ZERO-RESULTS-PLEASE"})
|
|
1484
1551
|
self.assertNotContains(response, "admin-search-test")
|
|
1485
1552
|
|
|
1553
|
+
for field, ip_address in {
|
|
1554
|
+
"last_ip": d.last_ip,
|
|
1555
|
+
"management_ip": d.management_ip,
|
|
1556
|
+
}.items():
|
|
1557
|
+
with self.subTest(field=field):
|
|
1558
|
+
response = self.client.get(path, {"q": ip_address})
|
|
1559
|
+
self.assertContains(response, "admin-search-test")
|
|
1560
|
+
|
|
1486
1561
|
with self.subTest("test device location search"):
|
|
1487
1562
|
response = self.client.get(path, {"q": "Estonia"})
|
|
1488
1563
|
self.assertNotContains(response, "admin-search-test")
|
{openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/tests/test_api.py
RENAMED
|
@@ -559,6 +559,68 @@ class TestConfigApi(
|
|
|
559
559
|
r = self.client.get(path)
|
|
560
560
|
self.assertEqual(r.status_code, 200)
|
|
561
561
|
|
|
562
|
+
def test_download_api_multitenancy(self):
|
|
563
|
+
org1 = self._create_org(name="org1")
|
|
564
|
+
org2 = self._create_org(name="org2")
|
|
565
|
+
device1 = self._create_config(organization=org1).device
|
|
566
|
+
device2 = self._create_config(organization=org2).device
|
|
567
|
+
template1 = self._create_template(name="template1", organization=org1)
|
|
568
|
+
template2 = self._create_template(name="template2", organization=org2)
|
|
569
|
+
vpn1 = self._create_vpn(name="vpn1", organization=org1)
|
|
570
|
+
vpn2 = self._create_vpn(name="vpn2", organization=org2)
|
|
571
|
+
shared_vpn = self._create_vpn(name="shared-vpn", organization=None)
|
|
572
|
+
operator = self._create_operator(organizations=[org1])
|
|
573
|
+
self.client.force_login(operator)
|
|
574
|
+
|
|
575
|
+
with self.subTest("Device of managed organization"):
|
|
576
|
+
r = self.client.get(
|
|
577
|
+
reverse("config_api:download_device_config", args=[device1.pk])
|
|
578
|
+
)
|
|
579
|
+
self.assertEqual(r.status_code, 200)
|
|
580
|
+
|
|
581
|
+
with self.subTest("Device of other organization"):
|
|
582
|
+
r = self.client.get(
|
|
583
|
+
reverse("config_api:download_device_config", args=[device2.pk])
|
|
584
|
+
)
|
|
585
|
+
self.assertEqual(r.status_code, 404)
|
|
586
|
+
|
|
587
|
+
with self.subTest("Template of managed organization"):
|
|
588
|
+
r = self.client.get(
|
|
589
|
+
reverse("config_api:download_template_config", args=[template1.pk])
|
|
590
|
+
)
|
|
591
|
+
self.assertEqual(r.status_code, 200)
|
|
592
|
+
|
|
593
|
+
with self.subTest("Template of other organization"):
|
|
594
|
+
r = self.client.get(
|
|
595
|
+
reverse("config_api:download_template_config", args=[template2.pk])
|
|
596
|
+
)
|
|
597
|
+
self.assertEqual(r.status_code, 404)
|
|
598
|
+
|
|
599
|
+
with self.subTest("VPN of managed organization"):
|
|
600
|
+
r = self.client.get(
|
|
601
|
+
reverse("config_api:download_vpn_config", args=[vpn1.pk])
|
|
602
|
+
)
|
|
603
|
+
self.assertEqual(r.status_code, 200)
|
|
604
|
+
|
|
605
|
+
with self.subTest("VPN of other organization"):
|
|
606
|
+
r = self.client.get(
|
|
607
|
+
reverse("config_api:download_vpn_config", args=[vpn2.pk])
|
|
608
|
+
)
|
|
609
|
+
self.assertEqual(r.status_code, 404)
|
|
610
|
+
|
|
611
|
+
vpn2.get_cached_configuration()
|
|
612
|
+
with self.subTest("Cached VPN of other organization"):
|
|
613
|
+
r = self.client.get(
|
|
614
|
+
reverse("config_api:download_vpn_config", args=[vpn2.pk])
|
|
615
|
+
)
|
|
616
|
+
self.assertEqual(r.status_code, 404)
|
|
617
|
+
|
|
618
|
+
with self.subTest("Shared VPN"):
|
|
619
|
+
r = self.client.get(
|
|
620
|
+
reverse("config_api:download_vpn_config", args=[shared_vpn.pk])
|
|
621
|
+
)
|
|
622
|
+
self.assertEqual(r.status_code, 200)
|
|
623
|
+
|
|
562
624
|
def test_update_deactivated_device(self):
|
|
563
625
|
device = self._create_device(_is_deactivated=True)
|
|
564
626
|
path = reverse("config_api:device_detail", args=[device.pk])
|
|
@@ -479,6 +479,33 @@ class TestDeviceAdmin(
|
|
|
479
479
|
self.find_element(by=By.NAME, value="_save").click()
|
|
480
480
|
self.wait_for_admin_success_message()
|
|
481
481
|
|
|
482
|
+
def test_device_form_shows_template_name_as_text(self):
|
|
483
|
+
name = '<b class="injected">template</b>'
|
|
484
|
+
template = self._create_template(name=name, organization=None)
|
|
485
|
+
device = self._create_config(organization=self._get_org()).device
|
|
486
|
+
self.login()
|
|
487
|
+
self.open(
|
|
488
|
+
reverse(f"admin:{self.config_app_label}_device_change", args=[device.id])
|
|
489
|
+
+ "#config-group"
|
|
490
|
+
)
|
|
491
|
+
self.hide_loading_overlay()
|
|
492
|
+
label = self.find_element(
|
|
493
|
+
by=By.XPATH,
|
|
494
|
+
value=(
|
|
495
|
+
f'//ul[contains(@class,"sortedm2m-items")]'
|
|
496
|
+
f'//input[@value="{template.id}" and @data-required]/parent::label'
|
|
497
|
+
),
|
|
498
|
+
)
|
|
499
|
+
self.assertEqual(
|
|
500
|
+
label.text.strip(), name, "the template name is not displayed as text"
|
|
501
|
+
)
|
|
502
|
+
self.assertFalse(
|
|
503
|
+
self.web_driver.find_elements(
|
|
504
|
+
By.CSS_SELECTOR, "ul.sortedm2m-items .injected"
|
|
505
|
+
),
|
|
506
|
+
"the template name was rendered as HTML",
|
|
507
|
+
)
|
|
508
|
+
|
|
482
509
|
|
|
483
510
|
@tag("selenium_tests")
|
|
484
511
|
class TestDeviceGroupAdmin(
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
from shlex import quote
|
|
2
3
|
|
|
3
4
|
from packaging import version
|
|
4
5
|
|
|
@@ -47,7 +48,8 @@ class OpenWrt(Ssh):
|
|
|
47
48
|
|
|
48
49
|
def change_password(self, password, confirm_password, user="root"):
|
|
49
50
|
return self.exec_command(
|
|
50
|
-
f'
|
|
51
|
+
f"printf '%s\\n%s\\n' {quote(password)} {quote(confirm_password)} "
|
|
52
|
+
f"| passwd {quote(user)}"
|
|
51
53
|
)
|
|
52
54
|
|
|
53
55
|
|
|
@@ -7,6 +7,14 @@ var gettext =
|
|
|
7
7
|
};
|
|
8
8
|
var interpolate = interpolate || function () {};
|
|
9
9
|
|
|
10
|
+
// For XSS prevention
|
|
11
|
+
function escapeHtml(text) {
|
|
12
|
+
if (text === null || text === undefined) return "";
|
|
13
|
+
const div = document.createElement("div");
|
|
14
|
+
div.textContent = text;
|
|
15
|
+
return div.innerHTML;
|
|
16
|
+
}
|
|
17
|
+
|
|
10
18
|
const deviceId = getObjectIdFromUrl();
|
|
11
19
|
|
|
12
20
|
django.jQuery(function ($) {
|
|
@@ -73,7 +81,9 @@ function initCommandDropdown($) {
|
|
|
73
81
|
}
|
|
74
82
|
|
|
75
83
|
Object.keys(schema).forEach(function (el) {
|
|
76
|
-
owCommandBtns += `<button class="ow-command-btn option" data-command="${el}">${
|
|
84
|
+
owCommandBtns += `<button class="ow-command-btn option" data-command="${escapeHtml(el)}">${escapeHtml(
|
|
85
|
+
schema[el].title,
|
|
86
|
+
)}</button>`;
|
|
77
87
|
});
|
|
78
88
|
widgetElement = `
|
|
79
89
|
<li>
|
|
@@ -468,24 +478,24 @@ function initCommandOverlay($) {
|
|
|
468
478
|
input = "";
|
|
469
479
|
}
|
|
470
480
|
return `<div class="inline-related has_original dynamic-command_set-2" id="command_set-2-${counter}">
|
|
471
|
-
<h3><b>Recent Commands:</b> <span class="inline_label">«${
|
|
472
|
-
response.type
|
|
473
|
-
}» ${sentOn} ${dateTimeStampToDateTimeLocaleString(
|
|
481
|
+
<h3><b>Recent Commands:</b> <span class="inline_label">«${escapeHtml(
|
|
482
|
+
response.type,
|
|
483
|
+
)}» ${sentOn} ${dateTimeStampToDateTimeLocaleString(
|
|
474
484
|
new Date(response.created),
|
|
475
485
|
)}</span></h3>
|
|
476
486
|
<fieldset class="module aligned ">
|
|
477
487
|
<div class="form-row field-status">
|
|
478
|
-
<div><label>Status:</label><div class="readonly">${
|
|
479
|
-
response.status
|
|
480
|
-
}</div></div>
|
|
488
|
+
<div><label>Status:</label><div class="readonly">${escapeHtml(
|
|
489
|
+
response.status,
|
|
490
|
+
)}</div></div>
|
|
481
491
|
</div>
|
|
482
492
|
<div class="form-row field-type">
|
|
483
|
-
<div><label>Type:</label><div class="readonly">${
|
|
484
|
-
response.type
|
|
485
|
-
}</div></div>
|
|
493
|
+
<div><label>Type:</label><div class="readonly">${escapeHtml(
|
|
494
|
+
response.type,
|
|
495
|
+
)}</div></div>
|
|
486
496
|
</div>
|
|
487
497
|
<div class="form-row field-input">
|
|
488
|
-
<div><label>Input:</label><div class="readonly">${input}</div></div>
|
|
498
|
+
<div><label>Input:</label><div class="readonly">${escapeHtml(input)}</div></div>
|
|
489
499
|
</div>
|
|
490
500
|
<div class="form-row field-output_data">
|
|
491
501
|
<div>
|
|
@@ -493,7 +503,7 @@ function initCommandOverlay($) {
|
|
|
493
503
|
<div class="readonly">${
|
|
494
504
|
response.status === "in-progress"
|
|
495
505
|
? '<div class="loader recent-commands-loader"></div>'
|
|
496
|
-
: response.output
|
|
506
|
+
: escapeHtml(response.output)
|
|
497
507
|
}
|
|
498
508
|
</div>
|
|
499
509
|
</div>
|
|
@@ -555,9 +565,9 @@ function initCommandWebSockets($, commandWebSocket) {
|
|
|
555
565
|
input = "";
|
|
556
566
|
}
|
|
557
567
|
|
|
558
|
-
commandObjectFieldset.find(".field-status .readonly").
|
|
559
|
-
commandObjectFieldset.find(".field-input .readonly").
|
|
560
|
-
commandObjectFieldset.find(".field-output_data .readonly").
|
|
568
|
+
commandObjectFieldset.find(".field-status .readonly").text(data.status);
|
|
569
|
+
commandObjectFieldset.find(".field-input .readonly").text(input);
|
|
570
|
+
commandObjectFieldset.find(".field-output_data .readonly").text(data.output);
|
|
561
571
|
|
|
562
572
|
// Is it required to update modified timestamp?
|
|
563
573
|
// We will require to to manipulation for rendering dates tp maintain consistency
|
|
@@ -782,7 +782,7 @@ HZAAAAgAhZz8ve4sK9Wbopq43Cu2kQDgX4NoA6W+FCmxCKf5AhYIzYQxIqyCazd7MrjCwS""",
|
|
|
782
782
|
connect_mocked.assert_called_once()
|
|
783
783
|
mocked_exec_command.assert_called_once()
|
|
784
784
|
mocked_exec_command.assert_called_with(
|
|
785
|
-
'
|
|
785
|
+
"printf '%s\\n%s\\n' Newpasswd@123 Newpasswd@123 | passwd root",
|
|
786
786
|
timeout=app_settings.SSH_COMMAND_TIMEOUT,
|
|
787
787
|
)
|
|
788
788
|
command.refresh_from_db()
|
|
@@ -790,6 +790,49 @@ HZAAAAgAhZz8ve4sK9Wbopq43Cu2kQDgX4NoA6W+FCmxCKf5AhYIzYQxIqyCazd7MrjCwS""",
|
|
|
790
790
|
self.assertEqual(command.output, "Changed password for user root.\n")
|
|
791
791
|
self.assertEqual(list(command.arguments), ["********"])
|
|
792
792
|
|
|
793
|
+
@mock.patch(_connect_path)
|
|
794
|
+
def test_execute_change_password_shell_metacharacters(self, connect_mocked):
|
|
795
|
+
dc = self._create_device_connection()
|
|
796
|
+
|
|
797
|
+
def _assert_quoted(password, expected):
|
|
798
|
+
command = Command(
|
|
799
|
+
device=dc.device,
|
|
800
|
+
connection=dc,
|
|
801
|
+
type="change_password",
|
|
802
|
+
input={"password": password, "confirm_password": password},
|
|
803
|
+
)
|
|
804
|
+
command.full_clean()
|
|
805
|
+
with mock.patch(_exec_command_path) as mocked_exec_command:
|
|
806
|
+
mocked_exec_command.return_value = self._exec_command_return_value(
|
|
807
|
+
stdout="Changed password for user root."
|
|
808
|
+
)
|
|
809
|
+
command.save()
|
|
810
|
+
command.execute()
|
|
811
|
+
mocked_exec_command.assert_called_once_with(
|
|
812
|
+
f"printf '%s\\n%s\\n' {expected} {expected} | passwd root",
|
|
813
|
+
timeout=app_settings.SSH_COMMAND_TIMEOUT,
|
|
814
|
+
)
|
|
815
|
+
command.refresh_from_db()
|
|
816
|
+
self.assertEqual(command.status, "success")
|
|
817
|
+
|
|
818
|
+
with self.subTest("safe password left unquoted"):
|
|
819
|
+
_assert_quoted("N3w-p@ss.word", "N3w-p@ss.word")
|
|
820
|
+
|
|
821
|
+
with self.subTest("double quote breakout"):
|
|
822
|
+
_assert_quoted(
|
|
823
|
+
'x"; touch /tmp/PWNED; echo "',
|
|
824
|
+
"'x\"; touch /tmp/PWNED; echo \"'",
|
|
825
|
+
)
|
|
826
|
+
|
|
827
|
+
with self.subTest("command substitution"):
|
|
828
|
+
_assert_quoted("$(touch /tmp/PWNED)", "'$(touch /tmp/PWNED)'")
|
|
829
|
+
|
|
830
|
+
with self.subTest("single quote breakout"):
|
|
831
|
+
_assert_quoted("x'; touch /tmp/PWNED", "'x'\"'\"'; touch /tmp/PWNED'")
|
|
832
|
+
|
|
833
|
+
with self.subTest("backslash kept literal"):
|
|
834
|
+
_assert_quoted("New\\pass123", "'New\\pass123'")
|
|
835
|
+
|
|
793
836
|
@mock.patch(_connect_path)
|
|
794
837
|
@mock.patch.dict(
|
|
795
838
|
ORGANIZATION_ENABLED_COMMANDS, {"__all__": ("callable_ping", "path_ping")}
|