From a7326efe33e14b0993796df08e42c33e870cac22 Mon Sep 17 00:00:00 2001 From: Aksiznarf-Uar Date: Fri, 29 Nov 2024 13:55:33 +0100 Subject: [PATCH 1/8] Armis import is now hidden if not configured --- src/NAC_Service_Portal/settings.py | 1 + src/__init__.py | 0 src/nac/context_processors.py | 9 +++++++++ src/templates/base.html | 4 +++- src/tests/test_helper/test_armis.py | 26 +++++++++++++++++++++++++- 5 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 src/__init__.py create mode 100644 src/nac/context_processors.py diff --git a/src/NAC_Service_Portal/settings.py b/src/NAC_Service_Portal/settings.py index 283375a..ac026b6 100644 --- a/src/NAC_Service_Portal/settings.py +++ b/src/NAC_Service_Portal/settings.py @@ -68,6 +68,7 @@ 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', + 'nac.context_processors.armis_context', ], }, }, diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/nac/context_processors.py b/src/nac/context_processors.py new file mode 100644 index 0000000..bb3a321 --- /dev/null +++ b/src/nac/context_processors.py @@ -0,0 +1,9 @@ +from helper.armis import get_tenant_url + + +# is armis configured? We need this to render to nav-bar correctly +def armis_context(request): + # if no tenant url is given in the config, get_tenant_url() returns "https://" + armis_is_configured = get_tenant_url() != "https://" + print(get_tenant_url()) + return {"armis_is_configured": armis_is_configured} diff --git a/src/templates/base.html b/src/templates/base.html index f7df82c..8e98d94 100644 --- a/src/templates/base.html +++ b/src/templates/base.html @@ -66,6 +66,7 @@ Add Device + {% if armis_is_configured %} + + {% endif %} diff --git a/src/tests/test_helper/test_armis.py b/src/tests/test_helper/test_armis.py index a8d287f..21eb276 100644 --- a/src/tests/test_helper/test_armis.py +++ b/src/tests/test_helper/test_armis.py @@ -1,4 +1,5 @@ import pytest +from django.test import RequestFactory from unittest.mock import patch, MagicMock from helper.armis import ( armiscloud, @@ -7,6 +8,8 @@ _remove_existing_devices, get_devices, get_tenant_url, ) +from nac.context_processors import armis_context +from django.core.cache import cache @pytest.fixture @@ -104,6 +107,27 @@ def test_get_devices(mock_remove_existing_devices, mock_config): mock_remove_existing_devices.assert_called_once_with(mock_devices) -def test_get_tenent_url(mock_config): +def test_get_tenant_url(mock_config): with patch('helper.armis.armis_config', mock_config): assert get_tenant_url() == "https://test_host" + + +@pytest.mark.parametrize("tenant_hostname, validity", [ + ("", False), + ("test_host", True), +]) +def test_armis_context(tenant_hostname, validity): + + mock_config = { + 'armis-server': { + 'api_secret_key': 'test_key', + 'tenant_hostname': tenant_hostname, + 'sites_pattern': 'Site\\d+', # 'Site' + one or more numeric id's + 'vlan_blacklist': '100,200' + } + } + with patch('helper.armis.armis_config', mock_config): + cache.clear() + factory = RequestFactory() + request = factory.get("/") + assert armis_context(request)["armis_is_configured"] == validity From 6e530b161cea159bda2d86e6c08a4e07768925e0 Mon Sep 17 00:00:00 2001 From: Dan1elRoos Date: Tue, 26 Nov 2024 10:55:57 +0100 Subject: [PATCH 2/8] add boundary filter, add searchbar, enhancing usability --- src/helper/armis.py | 17 ++- src/nac/subviews/armis.py | 14 +- src/static/default.css | 22 +++ src/templates/armis_import.html | 181 ++++++++++++++++++++---- src/tests/test_helper/test_armis.py | 7 +- src/tests/test_views/test_armis_view.py | 15 +- 6 files changed, 206 insertions(+), 50 deletions(-) diff --git a/src/helper/armis.py b/src/helper/armis.py index 9a510da..d8c1732 100644 --- a/src/helper/armis.py +++ b/src/helper/armis.py @@ -41,16 +41,29 @@ def _remove_existing_devices(deviceList): # flake8: noqa: E231 @armiscloud -def get_devices(acloud, site): +def get_devices(acloud, sites): vlan_bl = "" vlan_blacklist = armis_config['armis-server'].get('vlan_blacklist', '') vlan_bl = f"!networkInterface:(vlans:{vlan_blacklist})" if vlan_blacklist else "" + sites = ','.join(f'"{site}"' for site in sites) deviceList = acloud.get_devices( - asq=f'in:devices site:"{site.get("name")}" timeFrame:"7 Days" {vlan_bl}', + asq=f'in:devices site:{sites} timeFrame:"7 Days" {vlan_bl}', fields_wanted=['id', 'ipAddress', 'macAddress', 'name', 'boundaries'] ) return _remove_existing_devices(deviceList) # flake8: qa + +def get_boundaries(deviceList): + unique_boundaries = set() + for device in deviceList: + boundaries = [b.strip() for b in device['boundaries'].split(',')] + unique_boundaries.update(boundaries) + + return sorted(list(unique_boundaries)) + def get_tenant_url(): return 'https://{}'.format(armis_config['armis-server']['tenant_hostname']) + +def map_ids_to_names(selectedSiteIds, armisServerSites): + return [armisServerSites[id]['name'] for id in selectedSiteIds if id in armisServerSites] diff --git a/src/nac/subviews/armis.py b/src/nac/subviews/armis.py index 39958b9..a3ff8a5 100644 --- a/src/nac/subviews/armis.py +++ b/src/nac/subviews/armis.py @@ -17,7 +17,7 @@ from django.core.cache import cache from django.shortcuts import render -from helper.armis import get_armis_sites, get_devices, get_tenant_url +from helper.armis import get_armis_sites, get_devices, get_tenant_url, get_boundaries, map_ids_to_names class ArmisView(View): @@ -35,14 +35,16 @@ def _get_context(self): # sets the site-context for armis_import.html, uses cac def get(self, request, *args, **kwargs): # rendering the html base with site-context context = self._get_context() + context['display'] = True return render(request, self.template_name, context) def post(self, request, *args, **kwargs): # gets site-id chosen in html-dropdown, gets Devices based on site-id, shows them via device-context context = self._get_context() - - selected_site = request.POST.get('site-id') - context['selected_site'] = selected_site if selected_site else '' - if selected_site: - context['devices'] = get_devices(context['armis_sites'][selected_site]) + selected_sites = request.POST.getlist('site-ids[]') + context['display'] = False if selected_sites else True + context['selected_sites'] = selected_sites if selected_sites else '' + if selected_sites: + context['devices'] = get_devices(map_ids_to_names(selected_sites, context['armis_sites'])) + context['boundaries'] = get_boundaries(context['devices']) return render(request, self.template_name, context) diff --git a/src/static/default.css b/src/static/default.css index bdf81bd..fa50750 100644 --- a/src/static/default.css +++ b/src/static/default.css @@ -80,4 +80,26 @@ td { margin-top: 5px; white-space: nowrap; z-index: 2000; + } + .checkbox-container { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 10px 20px; + padding: 10px; + } + + .checkbox-item { + display: flex; + align-items: center; + } + + .checkbox-label { + margin-left: 8px; + cursor: pointer; + } + + .custom-checkbox { + width: 20px; + height: 20px; + cursor: pointer; } \ No newline at end of file diff --git a/src/templates/armis_import.html b/src/templates/armis_import.html index c5ce752..113ffb5 100644 --- a/src/templates/armis_import.html +++ b/src/templates/armis_import.html @@ -3,19 +3,24 @@ {% load crispy_forms_tags %} {% block content %}
-

Armis Import

-
- {% csrf_token %} - - -
+

Armis Import

+ +
+
+ {% csrf_token %} +
+ {% for site_id, site_info in armis_sites.items %} +
+ + +
+ {% endfor %} +
+ +
+
{%if devices%} {% endblock content %} \ No newline at end of file diff --git a/src/templates/device_form.html b/src/templates/device_form.html index 3215db3..58cdcb7 100644 --- a/src/templates/device_form.html +++ b/src/templates/device_form.html @@ -3,7 +3,7 @@
{% csrf_token %} {{ form | crispy }} - +
diff --git a/src/templates/devices.html b/src/templates/devices.html index b9aea66..c764758 100644 --- a/src/templates/devices.html +++ b/src/templates/devices.html @@ -13,7 +13,7 @@

Devices

{% endfor %} - + From 8d246bca4cd2a8ad33412c50a7bdb5acaf4bc252 Mon Sep 17 00:00:00 2001 From: Dan1elRoos Date: Wed, 27 Nov 2024 11:15:20 +0100 Subject: [PATCH 5/8] change checkbox-container to auto-fit --- src/static/default.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/static/default.css b/src/static/default.css index fa50750..d609807 100644 --- a/src/static/default.css +++ b/src/static/default.css @@ -83,7 +83,7 @@ td { } .checkbox-container { display: grid; - grid-template-columns: repeat(4, 1fr); + grid-template-columns: repeat(auto-fit, minmax(100px, 1fr)); /* displays checkbox in 100pt width columns, as many as possible based on window size*/ gap: 10px 20px; padding: 10px; } From 066e71fc1b3c80265031b2549dc49573b93f1356 Mon Sep 17 00:00:00 2001 From: Aksiznarf-Uar Date: Fri, 29 Nov 2024 13:55:33 +0100 Subject: [PATCH 6/8] Armis import is now hidden if not configured --- src/NAC_Service_Portal/settings.py | 1 + src/__init__.py | 0 src/nac/context_processors.py | 9 +++++++++ src/templates/base.html | 4 +++- src/tests/test_helper/test_armis.py | 26 +++++++++++++++++++++++++- 5 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 src/__init__.py create mode 100644 src/nac/context_processors.py diff --git a/src/NAC_Service_Portal/settings.py b/src/NAC_Service_Portal/settings.py index 283375a..ac026b6 100644 --- a/src/NAC_Service_Portal/settings.py +++ b/src/NAC_Service_Portal/settings.py @@ -68,6 +68,7 @@ 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', + 'nac.context_processors.armis_context', ], }, }, diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/nac/context_processors.py b/src/nac/context_processors.py new file mode 100644 index 0000000..bb3a321 --- /dev/null +++ b/src/nac/context_processors.py @@ -0,0 +1,9 @@ +from helper.armis import get_tenant_url + + +# is armis configured? We need this to render to nav-bar correctly +def armis_context(request): + # if no tenant url is given in the config, get_tenant_url() returns "https://" + armis_is_configured = get_tenant_url() != "https://" + print(get_tenant_url()) + return {"armis_is_configured": armis_is_configured} diff --git a/src/templates/base.html b/src/templates/base.html index f7df82c..8e98d94 100644 --- a/src/templates/base.html +++ b/src/templates/base.html @@ -66,6 +66,7 @@ Add Device + {% if armis_is_configured %} + + {% endif %} diff --git a/src/tests/test_helper/test_armis.py b/src/tests/test_helper/test_armis.py index 1dc0f63..8792e25 100644 --- a/src/tests/test_helper/test_armis.py +++ b/src/tests/test_helper/test_armis.py @@ -1,4 +1,5 @@ import pytest +from django.test import RequestFactory from unittest.mock import patch, MagicMock from helper.armis import ( armiscloud, @@ -7,6 +8,8 @@ _remove_existing_devices, get_devices, get_tenant_url, ) +from nac.context_processors import armis_context +from django.core.cache import cache @pytest.fixture @@ -99,6 +102,27 @@ def test_get_devices(mock_remove_existing_devices, mock_config): mock_remove_existing_devices.assert_called_once_with(mock_devices) -def test_get_tenent_url(mock_config): +def test_get_tenant_url(mock_config): with patch('helper.armis.armis_config', mock_config): assert get_tenant_url() == "https://test_host" + + +@pytest.mark.parametrize("tenant_hostname, validity", [ + ("", False), + ("test_host", True), +]) +def test_armis_context(tenant_hostname, validity): + + mock_config = { + 'armis-server': { + 'api_secret_key': 'test_key', + 'tenant_hostname': tenant_hostname, + 'sites_pattern': 'Site\\d+', # 'Site' + one or more numeric id's + 'vlan_blacklist': '100,200' + } + } + with patch('helper.armis.armis_config', mock_config): + cache.clear() + factory = RequestFactory() + request = factory.get("/") + assert armis_context(request)["armis_is_configured"] == validity From f764e60a6f40bec1cc06649561cf847ee0b21713 Mon Sep 17 00:00:00 2001 From: Aksiznarf-Uar Date: Mon, 9 Dec 2024 19:34:03 +0100 Subject: [PATCH 7/8] Added caching --- src/nac/context_processors.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/nac/context_processors.py b/src/nac/context_processors.py index bb3a321..4687540 100644 --- a/src/nac/context_processors.py +++ b/src/nac/context_processors.py @@ -1,9 +1,11 @@ from helper.armis import get_tenant_url +from django.core.cache import cache # is armis configured? We need this to render to nav-bar correctly def armis_context(request): - # if no tenant url is given in the config, get_tenant_url() returns "https://" - armis_is_configured = get_tenant_url() != "https://" - print(get_tenant_url()) + armis_is_configured = cache.get("armis_is_configured") + if armis_is_configured is None: + armis_is_configured = get_tenant_url() != "https://" + cache.set("armis_is_configured", armis_is_configured) return {"armis_is_configured": armis_is_configured} From c41b0082777b030eadc72ae6f045c4317ff62787 Mon Sep 17 00:00:00 2001 From: Aksiznarf-Uar Date: Mon, 9 Dec 2024 19:38:25 +0100 Subject: [PATCH 8/8] removed wrong __init__.py --- src/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/__init__.py diff --git a/src/__init__.py b/src/__init__.py deleted file mode 100644 index e69de29..0000000