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.
Files changed (379) hide show
  1. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/CHANGES.rst +108 -0
  2. {openwisp_controller-1.3/openwisp_controller.egg-info → openwisp_controller-1.3.2}/PKG-INFO +1 -1
  3. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/__init__.py +1 -1
  4. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/admin.py +14 -2
  5. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/api/download_views.py +6 -6
  6. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/static/config/js/management_ip.js +9 -1
  7. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/static/config/js/relevant_templates.js +19 -4
  8. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/templates/admin/config/device/delete_selected_confirmation.html +1 -1
  9. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/tests/test_admin.py +76 -1
  10. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/tests/test_api.py +62 -0
  11. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/tests/test_selenium.py +27 -0
  12. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/connectors/openwrt/ssh.py +3 -1
  13. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/static/connection/js/commands.js +25 -15
  14. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/tests/test_models.py +44 -1
  15. openwisp_controller-1.3.2/openwisp_controller/connection/tests/test_selenium.py +126 -0
  16. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/api/serializers.py +85 -38
  17. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/api/views.py +14 -5
  18. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/tests/test_api.py +409 -14
  19. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/pki/api/views.py +2 -3
  20. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/pki/tests/test_api.py +21 -0
  21. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/subnet_division/admin.py +11 -7
  22. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/subnet_division/tests/test_admin.py +10 -0
  23. {openwisp_controller-1.3 → openwisp_controller-1.3.2/openwisp_controller.egg-info}/PKG-INFO +1 -1
  24. openwisp_controller-1.3/openwisp_controller/connection/tests/test_selenium.py +0 -65
  25. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/LICENSE +0 -0
  26. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/MANIFEST.in +0 -0
  27. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/README.rst +0 -0
  28. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/admin.py +0 -0
  29. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/base.py +0 -0
  30. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/checks.py +0 -0
  31. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/__init__.py +0 -0
  32. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/api/__init__.py +0 -0
  33. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/api/filters.py +0 -0
  34. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/api/serializers.py +0 -0
  35. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/api/urls.py +0 -0
  36. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/api/views.py +0 -0
  37. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/api/zerotier_service.py +0 -0
  38. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/apps.py +0 -0
  39. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/base/__init__.py +0 -0
  40. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/base/base.py +0 -0
  41. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/base/cache.py +0 -0
  42. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/base/channels_consumer.py +0 -0
  43. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/base/config.py +0 -0
  44. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/base/device.py +0 -0
  45. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/base/device_group.py +0 -0
  46. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/base/multitenancy.py +0 -0
  47. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/base/tag.py +0 -0
  48. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/base/template.py +0 -0
  49. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/base/vpn.py +0 -0
  50. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/base/whois.py +0 -0
  51. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/controller/__init__.py +0 -0
  52. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/controller/urls.py +0 -0
  53. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/controller/views.py +0 -0
  54. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/converters.py +0 -0
  55. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/crypto.py +0 -0
  56. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/exceptions.py +0 -0
  57. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/exportable.py +0 -0
  58. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/filters.py +0 -0
  59. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/fixtures/test_templates.json +0 -0
  60. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/handlers.py +0 -0
  61. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/management/__init__.py +0 -0
  62. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/management/commands/__init__.py +0 -0
  63. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/management/commands/clear_last_ip.py +0 -0
  64. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/management/commands/print_cache_dependencies.py +0 -0
  65. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0001_squashed_0002_config_settings_uuid.py +0 -0
  66. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0003_template_tags.py +0 -0
  67. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0004_add_device_model.py +0 -0
  68. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0005_populate_device.py +0 -0
  69. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0006_config_device_not_null.py +0 -0
  70. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0007_simplify_config.py +0 -0
  71. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0008_update_indexes.py +0 -0
  72. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0009_device_system.py +0 -0
  73. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0010_auto_20180106_1814.py +0 -0
  74. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0011_update_device_mac_address.py +0 -0
  75. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0012_auto_20180219_1501.py +0 -0
  76. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0013_last_ip_management_ip_and_status_applied.py +0 -0
  77. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0014_device_hardware_id.py +0 -0
  78. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0015_default_groups_permissions.py +0 -0
  79. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0016_default_organization_config_settings.py +0 -0
  80. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0017_template_name_organization_unique_together.py +0 -0
  81. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0018_config_context.py +0 -0
  82. {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
  83. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0020_remove_config_organization.py +0 -0
  84. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0021_vpn_key.py +0 -0
  85. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0022_vpn_format_dh.py +0 -0
  86. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0023_update_context.py +0 -0
  87. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0024_update_context_data.py +0 -0
  88. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0025_update_device_key.py +0 -0
  89. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0026_hardware_id_not_unique.py +0 -0
  90. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0027_add_indexes_on_ip_fields.py +0 -0
  91. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0028_template_default_values.py +0 -0
  92. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0029_merge_django_netjsonconfig.py +0 -0
  93. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0030_django_taggit_update.py +0 -0
  94. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0031_update_vpn_dh_param.py +0 -0
  95. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0032_update_legacy_vpn_backend.py +0 -0
  96. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0033_name_unique_per_organization.py +0 -0
  97. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0034_template_required.py +0 -0
  98. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0035_device_name_unique_optional.py +0 -0
  99. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0036_device_group.py +0 -0
  100. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0037_alter_taggedtemplate.py +0 -0
  101. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0038_vpn_subnet.py +0 -0
  102. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0039_wireguard_vxlan_ipam.py +0 -0
  103. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0040_vpnclient_ip_setnull.py +0 -0
  104. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0041_default_groups_organizationconfigsettings_permission.py +0 -0
  105. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0042_multiple_wireguard_tunnels.py +0 -0
  106. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0043_devicegroup_templates.py +0 -0
  107. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0044_config_error_reason.py +0 -0
  108. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0045_alter_vpn_webhook_endpoint.py +0 -0
  109. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0046_organizationlimits.py +0 -0
  110. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0047_add_organizationlimits.py +0 -0
  111. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0048_wifi_radio_band_migration.py +0 -0
  112. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0049_devicegroup_context.py +0 -0
  113. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0050_alter_vpnclient_unique_together.py +0 -0
  114. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0051_organizationconfigsettings_context.py +0 -0
  115. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0052_vpn_node_network_id.py +0 -0
  116. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0053_vpnclient_secret.py +0 -0
  117. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0054_device__is_deactivated.py +0 -0
  118. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0055_alter_config_status.py +0 -0
  119. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0056_vpnclient_template.py +0 -0
  120. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0057_populate_vpnclient_template.py +0 -0
  121. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0058_alter_vpnclient_template.py +0 -0
  122. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0059_zerotier_templates_ow_zt_to_global.py +0 -0
  123. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0060_cleanup_api_task_notification_types.py +0 -0
  124. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0061_config_checksum_db.py +0 -0
  125. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0062_whoisinfo.py +0 -0
  126. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0063_replace_jsonfield_with_django_builtin.py +0 -0
  127. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0064_template_notes.py +0 -0
  128. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/0065_whoisinfo_unreferenced_since.py +0 -0
  129. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/migrations/__init__.py +0 -0
  130. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/models.py +0 -0
  131. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/settings.py +0 -0
  132. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/signals.py +0 -0
  133. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/sortedm2m/__init__.py +0 -0
  134. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/sortedm2m/fields.py +0 -0
  135. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/sortedm2m/forms.py +0 -0
  136. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/static/config/css/admin.css +0 -0
  137. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/static/config/css/device-delete-confirmation.css +0 -0
  138. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/static/config/css/devicegroup.css +0 -0
  139. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/static/config/css/lib/advanced-mode.css +0 -0
  140. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/static/config/css/lib/img/jsoneditor-icons.svg +0 -0
  141. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/static/config/css/lib/jsonschema-ui.css +0 -0
  142. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/static/config/js/device-delete-confirmation.js +0 -0
  143. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/static/config/js/lib/advanced-mode.js +0 -0
  144. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/static/config/js/lib/jsonschema-ui.js +0 -0
  145. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/static/config/js/lib/tomorrow_night_bright.js +0 -0
  146. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/static/config/js/preview.js +0 -0
  147. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/static/config/js/switcher.js +0 -0
  148. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/static/config/js/tabs.js +0 -0
  149. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/static/config/js/unsaved_changes.js +0 -0
  150. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/static/config/js/utils.js +0 -0
  151. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/static/config/js/vpn.js +0 -0
  152. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/static/config/js/widget.js +0 -0
  153. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/static/import_export/import-openwisp.css +0 -0
  154. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/static/sortedm2m/patch_sortedm2m.js +0 -0
  155. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/static/support.css +0 -0
  156. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/static/whois/css/whois.css +0 -0
  157. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/static/whois/images/whois_globe.svg +0 -0
  158. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/static/whois/js/whois.js +0 -0
  159. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/tasks.py +0 -0
  160. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/tasks_zerotier.py +0 -0
  161. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/templates/admin/config/change_device_group.html +0 -0
  162. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/templates/admin/config/change_form.html +0 -0
  163. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/templates/admin/config/change_list_device.html +0 -0
  164. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/templates/admin/config/clone_template_form.html +0 -0
  165. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/templates/admin/config/device/change_form.html +0 -0
  166. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/templates/admin/config/device/delete_confirmation.html +0 -0
  167. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/templates/admin/config/device_recover_form.html +0 -0
  168. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/templates/admin/config/jsonschema-widget.html +0 -0
  169. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/templates/admin/config/preview.html +0 -0
  170. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/templates/admin/config/system_context.html +0 -0
  171. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/templates/admin/device_group/change_form.html +0 -0
  172. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/templates/admin/import_export/import.html +0 -0
  173. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/templates/reversion/config/revision_form.html +0 -0
  174. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/tests/__init__.py +0 -0
  175. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/tests/pytest.py +0 -0
  176. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/tests/test_apps.py +0 -0
  177. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/tests/test_config.py +0 -0
  178. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/tests/test_controller.py +0 -0
  179. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/tests/test_converters.py +0 -0
  180. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/tests/test_device.py +0 -0
  181. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/tests/test_device_group.py +0 -0
  182. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/tests/test_handlers.py +0 -0
  183. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/tests/test_notifications.py +0 -0
  184. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/tests/test_tag.py +0 -0
  185. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/tests/test_template.py +0 -0
  186. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/tests/test_views.py +0 -0
  187. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/tests/test_vpn.py +0 -0
  188. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/tests/utils.py +0 -0
  189. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/urls.py +0 -0
  190. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/utils.py +0 -0
  191. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/validators.py +0 -0
  192. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/views.py +0 -0
  193. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/whois/__init__.py +0 -0
  194. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/whois/commands.py +0 -0
  195. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/whois/handlers.py +0 -0
  196. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/whois/mixins.py +0 -0
  197. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/whois/serializers.py +0 -0
  198. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/whois/service.py +0 -0
  199. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/whois/tasks.py +0 -0
  200. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/whois/tests/__init__.py +0 -0
  201. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/whois/tests/tests.py +0 -0
  202. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/whois/tests/utils.py +0 -0
  203. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/whois/utils.py +0 -0
  204. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/config/widgets.py +0 -0
  205. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/__init__.py +0 -0
  206. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/admin.py +0 -0
  207. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/api/serializers.py +0 -0
  208. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/api/urls.py +0 -0
  209. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/api/views.py +0 -0
  210. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/apps.py +0 -0
  211. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/base/__init__.py +0 -0
  212. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/base/models.py +0 -0
  213. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/channels/consumers.py +0 -0
  214. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/channels/routing.py +0 -0
  215. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/commands.py +0 -0
  216. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/connectors/__init__.py +0 -0
  217. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/connectors/airos/snmp.py +0 -0
  218. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/connectors/exceptions.py +0 -0
  219. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/connectors/openwrt/__init__.py +0 -0
  220. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/connectors/openwrt/snmp.py +0 -0
  221. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/connectors/snmp.py +0 -0
  222. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/connectors/ssh.py +0 -0
  223. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/exceptions.py +0 -0
  224. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/migrations/0001_initial.py +0 -0
  225. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/migrations/0002_credentials_auto_add.py +0 -0
  226. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/migrations/0003_default_group_permissions.py +0 -0
  227. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/migrations/0004_django3_1_upgrade.py +0 -0
  228. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/migrations/0005_device_connection_failure_reason.py +0 -0
  229. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/migrations/0006_name_unique_per_organization.py +0 -0
  230. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/migrations/0007_command.py +0 -0
  231. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/migrations/0008_remove_conflicting_deviceconnections.py +0 -0
  232. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/migrations/0009_alter_deviceconnection_unique_together.py +0 -0
  233. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/migrations/0010_replace_jsonfield_with_django_builtin.py +0 -0
  234. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/migrations/__init__.py +0 -0
  235. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/models.py +0 -0
  236. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/schema.py +0 -0
  237. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/settings.py +0 -0
  238. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/signals.py +0 -0
  239. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/static/connection/css/command-inline.css +0 -0
  240. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/static/connection/css/credentials.css +0 -0
  241. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/static/connection/js/credentials.js +0 -0
  242. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/static/connection/js/lib/reconnecting-websocket.min.js +0 -0
  243. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/tasks.py +0 -0
  244. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/tests/__init__.py +0 -0
  245. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/tests/base.py +0 -0
  246. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/tests/pytest.py +0 -0
  247. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/tests/test-key.ed25519 +0 -0
  248. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/tests/test-key.rsa +0 -0
  249. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/tests/test_admin.py +0 -0
  250. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/tests/test_api.py +0 -0
  251. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/tests/test_command_utilities.py +0 -0
  252. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/tests/test_notifications.py +0 -0
  253. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/tests/test_snmp.py +0 -0
  254. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/tests/test_ssh.py +0 -0
  255. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/tests/test_tasks.py +0 -0
  256. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/tests/utils.py +0 -0
  257. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/utils.py +0 -0
  258. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/connection/widgets.py +0 -0
  259. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/context_processors.py +0 -0
  260. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/__init__.py +0 -0
  261. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/admin.py +0 -0
  262. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/api/__init__.py +0 -0
  263. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/api/filters.py +0 -0
  264. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/api/urls.py +0 -0
  265. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/apps.py +0 -0
  266. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/base/__init__.py +0 -0
  267. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/base/models.py +0 -0
  268. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/channels/__init__.py +0 -0
  269. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/channels/consumers.py +0 -0
  270. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/channels/routing.py +0 -0
  271. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/estimated_location/__init__.py +0 -0
  272. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/estimated_location/handlers.py +0 -0
  273. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/estimated_location/mixins.py +0 -0
  274. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/estimated_location/service.py +0 -0
  275. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/estimated_location/tasks.py +0 -0
  276. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/estimated_location/tests/__init__.py +0 -0
  277. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/estimated_location/tests/tests.py +0 -0
  278. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/estimated_location/tests/utils.py +0 -0
  279. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/estimated_location/utils.py +0 -0
  280. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/exportable.py +0 -0
  281. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/migrations/0001_initial.py +0 -0
  282. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/migrations/0002_default_groups_permissions.py +0 -0
  283. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/migrations/0003_alter_devicelocation_floorplan_location.py +0 -0
  284. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/migrations/0004_location_is_estimated.py +0 -0
  285. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/migrations/0005_organizationgeosettings.py +0 -0
  286. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/migrations/0006_create_geo_settings_for_existing_orgs.py +0 -0
  287. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/migrations/__init__.py +0 -0
  288. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/models.py +0 -0
  289. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/settings.py +0 -0
  290. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/tasks.py +0 -0
  291. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/templates/admin/geo/device_location_inline.html +0 -0
  292. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/templates/admin/geo/location/change_form.html +0 -0
  293. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/templates/admin/widgets/foreign_key_raw_id.html +0 -0
  294. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/tests/__init__.py +0 -0
  295. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/tests/pytest.py +0 -0
  296. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/tests/test_admin.py +0 -0
  297. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/tests/test_admin_inline.py +0 -0
  298. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/tests/test_apps.py +0 -0
  299. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/tests/test_models.py +0 -0
  300. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/tests/test_selenium.py +0 -0
  301. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/tests/utils.py +0 -0
  302. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/geo/utils.py +0 -0
  303. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/migrations.py +0 -0
  304. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/mixins.py +0 -0
  305. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/pki/__init__.py +0 -0
  306. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/pki/admin.py +0 -0
  307. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/pki/api/serializers.py +0 -0
  308. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/pki/api/urls.py +0 -0
  309. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/pki/apps.py +0 -0
  310. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/pki/base/__init__.py +0 -0
  311. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/pki/base/models.py +0 -0
  312. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/pki/migrations/0001_initial.py +0 -0
  313. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/pki/migrations/0002_add_organization_name.py +0 -0
  314. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/pki/migrations/0003_fill_organization_name.py +0 -0
  315. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/pki/migrations/0004_auto_20180106_1814.py +0 -0
  316. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/pki/migrations/0005_organizational_unit_name.py +0 -0
  317. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/pki/migrations/0006_add_x509_passphrase_field.py +0 -0
  318. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/pki/migrations/0007_default_groups_permissions.py +0 -0
  319. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/pki/migrations/0008_serial_number_length.py +0 -0
  320. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/pki/migrations/0009_common_name_maxlength_64.py +0 -0
  321. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/pki/migrations/0010_common_name_organization_unique.py +0 -0
  322. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/pki/migrations/0011_disallowed_blank_key_length_or_digest.py +0 -0
  323. {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
  324. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/pki/migrations/__init__.py +0 -0
  325. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/pki/models.py +0 -0
  326. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/pki/static/admin/pki/js/show-org-field.js +0 -0
  327. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/pki/templates/admin/pki/change_form.html +0 -0
  328. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/pki/tests/__init__.py +0 -0
  329. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/pki/tests/test_admin.py +0 -0
  330. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/pki/tests/test_models.py +0 -0
  331. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/pki/tests/utils.py +0 -0
  332. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/pki/urls.py +0 -0
  333. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/pki/utils.py +0 -0
  334. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/routing.py +0 -0
  335. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/serializers.py +0 -0
  336. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/settings.py +0 -0
  337. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/subnet_division/__init__.py +0 -0
  338. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/subnet_division/apps.py +0 -0
  339. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/subnet_division/base/models.py +0 -0
  340. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/subnet_division/filters.py +0 -0
  341. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/subnet_division/migrations/0001_initial.py +0 -0
  342. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/subnet_division/migrations/0002_default_group_migration.py +0 -0
  343. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/subnet_division/migrations/0003_related_field_allow_blank.py +0 -0
  344. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/subnet_division/migrations/0004_index_rule_on_delete.py +0 -0
  345. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/subnet_division/migrations/0005_number_of_subnets_and_ips.py +0 -0
  346. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/subnet_division/migrations/__init__.py +0 -0
  347. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/subnet_division/models.py +0 -0
  348. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/subnet_division/rule_types/__init__.py +0 -0
  349. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/subnet_division/rule_types/base.py +0 -0
  350. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/subnet_division/rule_types/device.py +0 -0
  351. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/subnet_division/rule_types/vpn.py +0 -0
  352. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/subnet_division/settings.py +0 -0
  353. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/subnet_division/signals.py +0 -0
  354. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/subnet_division/static/subnet-division/css/subnet-division.css +0 -0
  355. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/subnet_division/static/subnet-division/js/subnet-division.js +0 -0
  356. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/subnet_division/tasks.py +0 -0
  357. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/subnet_division/tests/__init__.py +0 -0
  358. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/subnet_division/tests/helpers.py +0 -0
  359. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/subnet_division/tests/test_models.py +0 -0
  360. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/subnet_division/tests/test_rule.py +0 -0
  361. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/subnet_division/utils.py +0 -0
  362. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/tests/__init__.py +0 -0
  363. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/tests/mixins.py +0 -0
  364. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/tests/test_migrationgraph.py +0 -0
  365. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/tests/test_selenium.py +0 -0
  366. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/tests/test_users_integration.py +0 -0
  367. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/tests/test_utilities.py +0 -0
  368. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/tests/utils.py +0 -0
  369. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/urls.py +0 -0
  370. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller/vpn_backends.py +0 -0
  371. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller.egg-info/SOURCES.txt +0 -0
  372. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller.egg-info/dependency_links.txt +0 -0
  373. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller.egg-info/not-zip-safe +0 -0
  374. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller.egg-info/requires.txt +0 -0
  375. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/openwisp_controller.egg-info/top_level.txt +0 -0
  376. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/pyproject.toml +0 -0
  377. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/requirements.txt +0 -0
  378. {openwisp_controller-1.3 → openwisp_controller-1.3.2}/setup.cfg +0 -0
  379. {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
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openwisp-controller
3
- Version: 1.3
3
+ Version: 1.3.2
4
4
  Summary: OpenWISP Controller
5
5
  Home-page: http://openwisp.org
6
6
  Download-URL: https://github.com/openwisp/openwisp-controller/releases
@@ -1,4 +1,4 @@
1
- VERSION = (1, 3, 0, "final")
1
+ VERSION = (1, 3, 2, "final")
2
2
  __version__ = VERSION # alias
3
3
 
4
4
 
@@ -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 = get_object_or_404(self.model, pk=pk)
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 = get_object_or_404(self.model, pk=pk)
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.none()
20
+ queryset = Vpn.objects.all()
21
21
  model = Vpn
22
22
 
23
23
  def retrieve(self, request, *args, **kwargs):
24
- return BaseConfigAdmin.download_view(self, request, pk=kwargs["pk"])
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.none()
29
+ queryset = Device.objects.all()
30
30
  model = Device
31
31
 
32
32
  def retrieve(self, request, *args, **kwargs):
33
- return BaseConfigAdmin.download_view(self, request, pk=kwargs["pk"])
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.none()
38
+ queryset = Template.objects.all()
39
39
  model = Template
40
40
 
41
41
  def retrieve(self, request, *args, **kwargs):
42
- return BaseConfigAdmin.download_view(self, request, pk=kwargs["pk"])
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
- element = $(
34
- `<li class="sortedm2m-item"><label for="id_config-${prefix}templates_${index}"><input type="checkbox" value="${templateId}" id="id_config-${prefix}templates_${index}" class="sortedm2m" data-required=${templateConfig.required}> ${templateConfig.name}${requiredString}${backendString}</label></li>`,
35
- ),
36
- inputField = element.children().children("input");
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">"{% blocktranslate count counter=active_devices|length %}I understand the risks, delete the device{% plural %}I understand the risks, delete the devices{% endblocktranslate %}</button>
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>
@@ -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(name="admin-search-test")
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")
@@ -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'echo -e "{password}\n{confirm_password}" | passwd {user}'
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}">${schema[el].title}</button>`;
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>&nbsp;<span class="inline_label">«${
472
- response.type
473
- }» ${sentOn} ${dateTimeStampToDateTimeLocaleString(
481
+ <h3><b>Recent Commands:</b>&nbsp;<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").html(data.status);
559
- commandObjectFieldset.find(".field-input .readonly").html(input);
560
- commandObjectFieldset.find(".field-output_data .readonly").html(data.output);
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
- 'echo -e "Newpasswd@123\nNewpasswd@123" | passwd root',
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")}