diff --git a/device/models.py b/device/models.py
index a351a11..4e0778f 100644
--- a/device/models.py
+++ b/device/models.py
@@ -29,7 +29,7 @@ class Device:
self.shortid = self.pk[:6].upper()
self.algorithm = None
self.owner = None
- self.annotations = []
+ self.annotations = []
self.hids = []
self.uuids = []
self.evidences = []
@@ -108,7 +108,7 @@ class Device:
return
annotation = annotations.first()
self.last_evidence = Evidence(annotation.uuid)
-
+
def is_eraseserver(self):
if not self.uuids:
self.get_uuids()
@@ -120,7 +120,7 @@ class Device:
owner=self.owner,
type=Annotation.Type.ERASE_SERVER
).first()
-
+
if annotation:
return True
return False
@@ -129,7 +129,8 @@ class Device:
return self.uuids[0]
def get_lots(self):
- self.lots = [x.lot for x in DeviceLot.objects.filter(device_id=self.id)]
+ self.lots = [
+ x.lot for x in DeviceLot.objects.filter(device_id=self.id)]
@classmethod
def get_unassigned(cls, institution, offset=0, limit=None):
@@ -179,7 +180,6 @@ class Device:
count = cls.get_unassigned_count(institution)
return devices, count
-
@classmethod
def get_unassigned_count(cls, institution):
@@ -279,6 +279,12 @@ class Device:
self.get_last_evidence()
return self.last_evidence.get_manufacturer()
+ @property
+ def serial_number(self):
+ if not self.last_evidence:
+ self.get_last_evidence()
+ return self.last_evidence.get_serial_number()
+
@property
def type(self):
if self.last_evidence.doc['type'] == "WebSnapshot":
diff --git a/device/templates/details.html b/device/templates/details.html
index 8477948..331c857 100644
--- a/device/templates/details.html
+++ b/device/templates/details.html
@@ -84,7 +84,7 @@
{% trans 'Serial Number' %}
- {{ object.last_evidence.doc.device.serialNumber|default:'' }}
+ {{ object.serial_number|default:'' }}
{% endif %}
diff --git a/device/templates/device_web.html b/device/templates/device_web.html
index 21ca570..8c607d3 100644
--- a/device/templates/device_web.html
+++ b/device/templates/device_web.html
@@ -120,10 +120,12 @@
Model
{{ object.model|default:'' }}
-
-
Serial Number
-
{{ object.last_evidence.doc.device.serialNumber|default:'' }}
-
+ {% if user.is_authenticated %}
+
+
Serial Number
+
{{ object.serial_number|default:'' }}
+
+ {% endif %}
{% endif %}
@@ -136,7 +138,6 @@
{% endfor %}
-
Components
{% for component in object.components %}
@@ -147,7 +148,9 @@
{% for component_key, component_value in component.items %}
{% if component_key not in 'actions,type' %}
- {{ component_key }}: {{ component_value }}
+ {% if component_key != 'serialNumber' or user.is_authenticated %}
+ {{ component_key }}: {{ component_value }}
+ {% endif %}
{% endif %}
{% endfor %}
@@ -157,10 +160,9 @@
{% endfor %}
-
diff --git a/device/tests/test_mock_device.py b/device/tests/test_mock_device.py
index a3fe019..732c6ed 100644
--- a/device/tests/test_mock_device.py
+++ b/device/tests/test_mock_device.py
@@ -3,74 +3,47 @@ from unittest.mock import MagicMock
class TestDevice(Device):
- """A test subclass of Device that overrides the database-dependent methods"""
- # TODO Leaving commented bc not used, but might be useful at some point
- # def get_annotations(self):
- # """Return empty list instead of querying database"""
- # return []
+ def __init__(self, id):
+ super().__init__(id=id)
+ self.shortid = id[:6].upper()
+ self.uuids = []
+ self.hids = ['hid1', 'hid2']
+ self._setup_evidence()
- # def get_uuids(self):
- # """Set uuids directly instead of querying"""
- # self.uuids = ['uuid1', 'uuid2']
-
- # def get_hids(self):
- # """Set hids directly instead of querying"""
- # self.hids = ['hid1', 'hid2']
-
- # def get_evidences(self):
- # """Set evidences directly instead of querying"""
- # self.evidences = []
-
- # def get_lots(self):
- # """Set lots directly instead of querying"""
- # self.lots = []
-
- def get_last_evidence(self):
- if not hasattr(self, '_evidence'):
- self._evidence = MagicMock()
- self._evidence.doc = {
- 'type': 'Computer',
- 'manufacturer': 'Test Manufacturer',
- 'model': 'Test Model',
- 'device': {
- 'serialNumber': 'SN123456',
- 'type': 'Computer'
- }
+ def _setup_evidence(self):
+ self._evidence = MagicMock()
+ self._evidence.doc = {
+ 'type': 'Computer',
+ 'manufacturer': 'Test Manufacturer',
+ 'model': 'Test Model',
+ 'device': {
+ 'serialNumber': 'SN123456',
+ 'type': 'Computer'
}
- self._evidence.get_manufacturer = lambda: 'Test Manufacturer'
- self._evidence.get_model = lambda: 'Test Model'
- self._evidence.get_chassis = lambda: 'Computer'
- self._evidence.get_components = lambda: [
- {
- 'type': 'CPU',
- 'model': 'Intel i7',
- 'manufacturer': 'Intel'
- },
- {
- 'type': 'RAM',
- 'size': '8GB',
- 'manufacturer': 'Kingston'
- }
- ]
+ }
+ self._evidence.get_manufacturer = lambda: 'Test Manufacturer'
+ self._evidence.get_model = lambda: 'Test Model'
+ self._evidence.get_chassis = lambda: 'Computer'
+ self._evidence.get_components = lambda: [
+ {
+ 'type': 'CPU',
+ 'model': 'Intel i7',
+ 'manufacturer': 'Intel',
+ 'serialNumber': 'SN12345678'
+ },
+ {
+ 'type': 'RAM',
+ 'size': '8GB',
+ 'manufacturer': 'Kingston',
+ 'serialNumber': 'SN87654321'
+ }
+ ]
self.last_evidence = self._evidence
+ @property
+ def components(self):
+ return self.last_evidence.get_components()
-class TestWebSnapshotDevice(TestDevice):
- """A test subclass of Device that simulates a WebSnapshot device"""
-
- def get_last_evidence(self):
- if not hasattr(self, '_evidence'):
- self._evidence = MagicMock()
- self._evidence.doc = {
- 'type': 'WebSnapshot',
- 'kv': {
- 'URL': 'http://example.com',
- 'Title': 'Test Page',
- 'Timestamp': '2024-01-01'
- },
- 'device': {
- 'type': 'Laptop'
- }
- }
- self.last_evidence = self._evidence
- return self._evidence
+ @property
+ def serial_number(self):
+ return self.last_evidence.doc['device']['serialNumber']
diff --git a/device/tests/test_public_device_web.py b/device/tests/test_public_device_web.py
index 33efe05..84d8bf1 100644
--- a/device/tests/test_public_device_web.py
+++ b/device/tests/test_public_device_web.py
@@ -2,7 +2,8 @@ from django.test import TestCase, Client
from django.urls import reverse
from unittest.mock import patch
from device.views import PublicDeviceWebView
-from device.tests.test_mock_device import TestDevice, TestWebSnapshotDevice
+from device.tests.test_mock_device import TestDevice
+from user.models import User, Institution
class PublicDeviceWebViewTests(TestCase):
@@ -11,57 +12,99 @@ class PublicDeviceWebViewTests(TestCase):
self.test_id = "test123"
self.test_url = reverse('device:device_web',
kwargs={'pk': self.test_id})
+ self.institution = Institution.objects.create(
+ name="Test Institution"
+ )
+ self.user = User.objects.create_user(
+ email='test@example.com',
+ institution=self.institution,
+ password='testpass123'
+ )
def test_url_resolves_correctly(self):
- """Test that the URL is constructed correctly"""
url = reverse('device:device_web', kwargs={'pk': self.test_id})
self.assertEqual(url, f'/device/{self.test_id}/public/')
@patch('device.views.Device')
- def test_html_response(self, MockDevice):
+ def test_html_response_anonymous(self, MockDevice):
test_device = TestDevice(id=self.test_id)
MockDevice.return_value = test_device
response = self.client.get(self.test_url)
-
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, 'device_web.html')
-
self.assertContains(response, 'Test Manufacturer')
self.assertContains(response, 'Test Model')
self.assertContains(response, 'Computer')
self.assertContains(response, self.test_id)
+ self.assertNotContains(response, 'Serial Number')
+ self.assertNotContains(response, 'serialNumber')
+ @patch('device.views.Device')
+ def test_html_response_authenticated(self, MockDevice):
+ test_device = TestDevice(id=self.test_id)
+ MockDevice.return_value = test_device
+ self.client.login(username='test@example.com', password='testpass123')
+ response = self.client.get(self.test_url)
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'device_web.html')
+ self.assertContains(response, 'Test Manufacturer')
+ self.assertContains(response, 'Test Model')
+ self.assertContains(response, 'Computer')
+ self.assertContains(response, self.test_id)
+ self.assertContains(response, 'Serial Number')
+ self.assertContains(response, 'Components')
self.assertContains(response, 'CPU')
self.assertContains(response, 'Intel')
self.assertContains(response, 'RAM')
self.assertContains(response, 'Kingston')
@patch('device.views.Device')
- def test_json_response(self, MockDevice):
+ def test_json_response_anonymous(self, MockDevice):
test_device = TestDevice(id=self.test_id)
MockDevice.return_value = test_device
-
response = self.client.get(
self.test_url,
HTTP_ACCEPT='application/json'
)
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response['Content-Type'], 'application/json')
+ json_data = response.json()
+ self.assertEqual(json_data['id'], self.test_id)
+ self.assertEqual(json_data['shortid'], self.test_id[:6].upper())
+ self.assertEqual(json_data['uuids'], [])
+ self.assertEqual(json_data['hids'], ['hid1', 'hid2'])
+ self.assertNotIn('serial_number', json_data)
+ self.assertNotIn('serialNumber', json_data)
+ @patch('device.views.Device')
+ def test_json_response_authenticated(self, MockDevice):
+ test_device = TestDevice(id=self.test_id)
+ MockDevice.return_value = test_device
+ self.client.login(username='test@example.com', password='testpass123')
+ response = self.client.get(
+ self.test_url,
+ HTTP_ACCEPT='application/json'
+ )
self.assertEqual(response.status_code, 200)
self.assertEqual(response['Content-Type'], 'application/json')
json_data = response.json()
self.assertEqual(json_data['id'], self.test_id)
self.assertEqual(json_data['shortid'], self.test_id[:6].upper())
- self.assertEqual(json_data['components'], test_device.components)
-
- @patch('device.views.Device')
- def test_websnapshot_device(self, MockDevice):
- test_device = TestWebSnapshotDevice(id=self.test_id)
- MockDevice.return_value = test_device
- response = self.client.get(self.test_url)
-
- self.assertEqual(response.status_code, 200)
- self.assertTemplateUsed(response, 'device_web.html')
-
- self.assertContains(response, 'http://example.com')
- self.assertContains(response, 'Test Page')
+ self.assertEqual(json_data['components'], [
+ {
+ 'type': 'CPU',
+ 'model': 'Intel i7',
+ 'manufacturer': 'Intel',
+ 'serialNumber': 'SN12345678'
+ },
+ {
+ 'type': 'RAM',
+ 'size': '8GB',
+ 'manufacturer': 'Kingston',
+ 'serialNumber': 'SN87654321'
+ }
+ ])
+ self.assertEqual(json_data['serial_number'], 'SN123456')
+ self.assertEqual(json_data['uuids'], [])
+ self.assertEqual(json_data['hids'], ['hid1', 'hid2'])
diff --git a/device/views.py b/device/views.py
index 90b43e8..319f8cf 100644
--- a/device/views.py
+++ b/device/views.py
@@ -117,10 +117,10 @@ class PublicDeviceWebView(TemplateView):
def get(self, request, *args, **kwargs):
self.pk = kwargs['pk']
self.object = Device(id=self.pk)
-
+
if not self.object.last_evidence:
raise Http404
-
+
if self.request.headers.get('Accept') == 'application/json':
return self.get_json_response()
return super().get(request, *args, **kwargs)
@@ -133,15 +133,38 @@ class PublicDeviceWebView(TemplateView):
})
return context
- def get_json_response(self):
- data = {
+ @property
+ def public_fields(self):
+ return {
'id': self.object.id,
'shortid': self.object.shortid,
'uuids': self.object.uuids,
'hids': self.object.hids,
- 'components': self.object.components
+ 'components': self.remove_serial_number_from(self.object.components),
}
- return JsonResponse(data)
+
+ @property
+ def authenticated_fields(self):
+ return {
+ 'serial_number': self.object.serial_number,
+ 'components': self.object.components,
+ }
+
+ def remove_serial_number_from(self, components):
+ for component in components:
+ if 'serial_number' in component:
+ del component['SerialNumber']
+ return components
+
+ def get_device_data(self):
+ data = self.public_fields
+ if self.request.user.is_authenticated:
+ data.update(self.authenticated_fields)
+ return data
+
+ def get_json_response(self):
+ device_data = self.get_device_data()
+ return JsonResponse(device_data)
class AddAnnotationView(DashboardView, CreateView):
diff --git a/evidence/models.py b/evidence/models.py
index fef6a92..e9af092 100644
--- a/evidence/models.py
+++ b/evidence/models.py
@@ -11,7 +11,7 @@ from user.models import User, Institution
class Annotation(models.Model):
class Type(models.IntegerChoices):
- SYSTEM= 0, "System"
+ SYSTEM = 0, "System"
USER = 1, "User"
DOCUMENT = 2, "Document"
ERASE_SERVER = 3, "EraseServer"
@@ -19,14 +19,16 @@ class Annotation(models.Model):
created = models.DateTimeField(auto_now_add=True)
uuid = models.UUIDField()
owner = models.ForeignKey(Institution, on_delete=models.CASCADE)
- user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)
- type = models.SmallIntegerField(choices=Type)
+ user = models.ForeignKey(
+ User, on_delete=models.SET_NULL, null=True, blank=True)
+ type = models.SmallIntegerField(choices=Type)
key = models.CharField(max_length=STR_EXTEND_SIZE)
value = models.CharField(max_length=STR_EXTEND_SIZE)
class Meta:
constraints = [
- models.UniqueConstraint(fields=["type", "key", "uuid"], name="unique_type_key_uuid")
+ models.UniqueConstraint(
+ fields=["type", "key", "uuid"], name="unique_type_key_uuid")
]
@@ -37,8 +39,8 @@ class Evidence:
self.doc = None
self.created = None
self.dmi = None
- self.annotations = []
- self.components = []
+ self.annotations = []
+ self.components = []
self.default = "n/a"
self.get_owner()
@@ -87,7 +89,7 @@ class Evidence:
return self.components
def get_manufacturer(self):
- if self.doc.get("type") == "WebSnapshot":
+ if self.is_web_snapshot():
kv = self.doc.get('kv', {})
if len(kv) < 1:
return ""
@@ -99,7 +101,7 @@ class Evidence:
return self.dmi.manufacturer().strip()
def get_model(self):
- if self.doc.get("type") == "WebSnapshot":
+ if self.is_web_snapshot():
kv = self.doc.get('kv', {})
if len(kv) < 2:
return ""
@@ -122,6 +124,11 @@ class Evidence:
return k
return ""
+ def get_serial_number(self):
+ if self.is_legacy():
+ return self.doc['device']['serialNumber']
+ return self.dmi.serial_number().strip()
+
@classmethod
def get_all(cls, user):
return Annotation.objects.filter(
@@ -136,3 +143,6 @@ class Evidence:
def is_legacy(self):
return self.doc.get("software") != "workbench-script"
+
+ def is_web_snapshot(self):
+ return self.doc.get("type") == "WebSnapshot"