From 029e69495821728db76b028c835fce5a78efa4c8 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Wed, 18 Sep 2024 18:01:46 +0200 Subject: [PATCH 01/23] add institutions and get new worbench --- dashboard/mixins.py | 8 +-- dashboard/views.py | 2 +- device/models.py | 17 +++---- device/views.py | 16 +++--- evidence/forms.py | 2 +- .../migrations/0004_alter_annotation_owner.py | 22 +++++++++ evidence/models.py | 41 ++++++++++++++-- evidence/parse.py | 49 +++++++++++++++---- evidence/views.py | 4 +- ...wner_alter_lotannotation_owner_and_more.py | 36 ++++++++++++++ lot/models.py | 10 ++-- lot/views.py | 28 +++++------ user/management/commands/add_institution.py | 17 ++++++- user/management/commands/add_user.py | 32 ++++-------- user/views.py | 5 +- utils/constants.py | 18 +++++++ utils/device.py | 2 +- 17 files changed, 224 insertions(+), 85 deletions(-) create mode 100644 evidence/migrations/0004_alter_annotation_owner.py create mode 100644 lot/migrations/0003_alter_lot_owner_alter_lotannotation_owner_and_more.py diff --git a/dashboard/mixins.py b/dashboard/mixins.py index c28dcf8..7e2e236 100644 --- a/dashboard/mixins.py +++ b/dashboard/mixins.py @@ -39,16 +39,16 @@ class DashboardView(LoginRequiredMixin): 'section': self.section, 'path': resolve(self.request.path).url_name, 'user': self.request.user, - 'lot_tags': LotTag.objects.filter(owner=self.request.user) + 'lot_tags': LotTag.objects.filter(owner=self.request.user.institution) }) return context def get_session_devices(self): - # import pdb; pdb.set_trace() dev_ids = self.request.session.pop("devices", []) self._devices = [] - for x in Annotation.objects.filter(value__in=dev_ids).filter(owner=self.request.user).distinct(): + annotation = Annotation.objects.filter(value__in=dev_ids) + for x in annotation.filter(owner=self.request.user.institution).distinct(): self._devices.append(Device(id=x.value)) return self._devices @@ -57,7 +57,7 @@ class DetailsMixin(DashboardView, TemplateView): def get(self, request, *args, **kwargs): self.pk = kwargs['pk'] - self.object = get_object_or_404(self.model, pk=self.pk, owner=self.request.user) + self.object = get_object_or_404(self.model, pk=self.pk, owner=self.request.user.institution) return super().get(request, *args, **kwargs) def get_context_data(self, **kwargs): diff --git a/dashboard/views.py b/dashboard/views.py index 79b9346..bcc4eeb 100644 --- a/dashboard/views.py +++ b/dashboard/views.py @@ -12,7 +12,7 @@ class UnassignedDevicesView(InventaryMixin): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - devices = Device.get_unassigned(self.request.user) + devices = Device.get_unassigned(self.request.user.institution) context.update({ 'devices': devices, diff --git a/device/models.py b/device/models.py index f1b1ee7..6e0bc50 100644 --- a/device/models.py +++ b/device/models.py @@ -113,10 +113,10 @@ class Device: self.lots = [x.lot for x in DeviceLot.objects.filter(device_id=self.id)] @classmethod - def get_unassigned(cls, user): - chids = DeviceLot.objects.filter(lot__owner=user).values_list("device_id", flat=True).distinct() + def get_unassigned(cls, institution): + chids = DeviceLot.objects.filter(lot__owner=institution).values_list("device_id", flat=True).distinct() annotations = Annotation.objects.filter( - owner=user, + owner=institution, type=Annotation.Type.SYSTEM, ).exclude(value__in=chids).values_list("value", flat=True).distinct() return [cls(id=x) for x in annotations] @@ -141,22 +141,17 @@ class Device: def manufacturer(self): if not self.last_evidence: self.get_last_evidence() - return self.last_evidence.doc['device']['manufacturer'] + return self.last_evidence.get_manufacturer() @property def type(self): if not self.last_evidence: self.get_last_evidence() - return self.last_evidence.doc['device']['type'] + return self.last_evidence.get_chassis() @property def model(self): if not self.last_evidence: self.get_last_evidence() - return self.last_evidence.doc['device']['model'] + return self.last_evidence.get_model() - @property - def type(self): - if not self.last_evidence: - self.get_last_evidence() - return self.last_evidence.doc['device']['type'] diff --git a/device/views.py b/device/views.py index e89cc3c..0fd9365 100644 --- a/device/views.py +++ b/device/views.py @@ -92,7 +92,7 @@ class DetailsView(DashboardView, TemplateView): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) self.object.initial() - lot_tags = LotTag.objects.filter(owner=self.request.user) + lot_tags = LotTag.objects.filter(owner=self.request.user.institution) context.update({ 'object': self.object, 'snapshot': self.object.get_last_evidence(), @@ -110,7 +110,7 @@ class AddAnnotationView(DashboardView, CreateView): fields = ("key", "value") def form_valid(self, form): - form.instance.owner = self.request.user + form.instance.owner = self.request.user.institution form.instance.uuid = self.annotation.uuid form.instance.type = Annotation.Type.USER response = super().form_valid(form) @@ -118,11 +118,12 @@ class AddAnnotationView(DashboardView, CreateView): def get_form_kwargs(self): pk = self.kwargs.get('pk') + institution = self.request.user.institution self.annotation = Annotation.objects.filter( - owner=self.request.user, value=pk, type=Annotation.Type.SYSTEM + owner=institution, value=pk, type=Annotation.Type.SYSTEM ).first() if not self.annotation: - get_object_or_404(Annotation, pk=0, owner=self.request.user) + get_object_or_404(Annotation, pk=0, owner=institution) self.success_url = reverse_lazy('device:details', args=[pk]) kwargs = super().get_form_kwargs() return kwargs @@ -137,7 +138,7 @@ class AddDocumentView(DashboardView, CreateView): fields = ("key", "value") def form_valid(self, form): - form.instance.owner = self.request.user + form.instance.owner = self.request.user.institution form.instance.uuid = self.annotation.uuid form.instance.type = Annotation.Type.DOCUMENT response = super().form_valid(form) @@ -145,11 +146,12 @@ class AddDocumentView(DashboardView, CreateView): def get_form_kwargs(self): pk = self.kwargs.get('pk') + institution = self.request.user.institution self.annotation = Annotation.objects.filter( - owner=self.request.user, value=pk, type=Annotation.Type.SYSTEM + owner=institution, value=pk, type=Annotation.Type.SYSTEM ).first() if not self.annotation: - get_object_or_404(Annotation, pk=0, owner=self.request.user) + get_object_or_404(Annotation, pk=0, owner=institution) self.success_url = reverse_lazy('device:details', args=[pk]) kwargs = super().get_form_kwargs() return kwargs diff --git a/evidence/forms.py b/evidence/forms.py index ae2630d..be50ca8 100644 --- a/evidence/forms.py +++ b/evidence/forms.py @@ -71,7 +71,7 @@ class UserTagForm(forms.Form): Annotation.objects.create( uuid=self.uuid, - owner=user, + owner=user.institution, type=Annotation.Type.SYSTEM, key='CUSTOM_ID', value=self.tag diff --git a/evidence/migrations/0004_alter_annotation_owner.py b/evidence/migrations/0004_alter_annotation_owner.py new file mode 100644 index 0000000..26c9bda --- /dev/null +++ b/evidence/migrations/0004_alter_annotation_owner.py @@ -0,0 +1,22 @@ +# Generated by Django 5.0.6 on 2024-09-18 10:55 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("evidence", "0003_alter_annotation_type"), + ("user", "0001_initial"), + ] + + operations = [ + migrations.AlterField( + model_name="annotation", + name="owner", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="user.institution" + ), + ), + ] diff --git a/evidence/models.py b/evidence/models.py index dbb47a0..30625aa 100644 --- a/evidence/models.py +++ b/evidence/models.py @@ -1,10 +1,11 @@ import json +from dmidecode import DMIParse from django.db import models -from utils.constants import STR_SM_SIZE, STR_EXTEND_SIZE +from utils.constants import STR_SM_SIZE, STR_EXTEND_SIZE, CHASSIS_DH from evidence.xapian import search -from user.models import User +from user.models import Institution class Annotation(models.Model): @@ -15,7 +16,7 @@ class Annotation(models.Model): created = models.DateTimeField(auto_now_add=True) uuid = models.UUIDField() - owner = models.ForeignKey(User, on_delete=models.CASCADE) + owner = models.ForeignKey(Institution, on_delete=models.CASCADE) type = models.SmallIntegerField(choices=Type) key = models.CharField(max_length=STR_EXTEND_SIZE) value = models.CharField(max_length=STR_EXTEND_SIZE) @@ -32,6 +33,7 @@ class Evidence: self.owner = None self.doc = None self.created = None + self.dmi = None self.annotations = [] self.get_owner() @@ -58,6 +60,11 @@ class Evidence: for xa in matches: self.doc = json.loads(xa.document.get_data()) + + if self.doc.get("software") == "EreuseWorkbench": + dmidecode_raw = self.doc["data"]["dmidecode"] + self.dmi = DMIParse(dmidecode_raw) + def get_time(self): if not self.doc: @@ -70,9 +77,35 @@ class Evidence: def components(self): return self.doc.get('components', []) + def get_manufacturer(self): + if self.doc.get("software") != "EreuseWorkbench": + return self.doc['device']['manufacturer'] + + return self.dmi.manufacturer().strip() + + def get_model(self): + if self.doc.get("software") != "EreuseWorkbench": + return self.doc['device']['model'] + + return self.dmi.model().strip() + + def get_chassis(self): + if self.doc.get("software") != "EreuseWorkbench": + return self.doc['device']['model'] + + chassis = self.dmi.get("Chassis")[0].get("Type", '_virtual') + lower_type = chassis.lower() + + for k, v in CHASSIS_DH.items(): + if lower_type in v: + return k + return "" + + + @classmethod def get_all(cls, user): return Annotation.objects.filter( - owner=user, + owner=user.institution, type=Annotation.Type.SYSTEM, ).order_by("-created").values_list("uuid", flat=True).distinct() diff --git a/evidence/parse.py b/evidence/parse.py index 614c2ec..d9cfeda 100644 --- a/evidence/parse.py +++ b/evidence/parse.py @@ -4,9 +4,10 @@ import shutil import hashlib from datetime import datetime +from dmidecode import DMIParse from evidence.xapian import search, index from evidence.models import Evidence, Annotation -from utils.constants import ALGOS +from utils.constants import ALGOS, CHASSIS_DH class Build: @@ -33,13 +34,17 @@ class Build: } def get_hid_14(self): - device = self.json['device'] - manufacturer = device.get("manufacturer", '') - model = device.get("model", '') - chassis = device.get("chassis", '') - serial_number = device.get("serialNumber", '') - sku = device.get("sku", '') - hid = f"{manufacturer}{model}{chassis}{serial_number}{sku}" + if self.json.get("software") == "EreuseWorkbench": + hid = self.get_hid(self.json) + else: + device = self.json['device'] + manufacturer = device.get("manufacturer", '') + model = device.get("model", '') + chassis = device.get("chassis", '') + serial_number = device.get("serialNumber", '') + sku = device.get("sku", '') + hid = f"{manufacturer}{model}{chassis}{serial_number}{sku}" + return hashlib.sha3_256(hid.encode()).hexdigest() def create_annotations(self): @@ -47,8 +52,34 @@ class Build: for k, v in self.algorithms.items(): Annotation.objects.create( uuid=self.uuid, - owner=self.user, + owner=self.user.institution, type=Annotation.Type.SYSTEM, key=k, value=v ) + + def get_chassis_dh(self): + chassis = self.get_chassis() + lower_type = chassis.lower() + for k, v in CHASSIS_DH.items(): + if lower_type in v: + return k + return self.default + + def get_sku(self): + return self.dmi.get("System")[0].get("SKU Number", "n/a").strip() + + def get_chassis(self): + return self.dmi.get("Chassis")[0].get("Type", '_virtual') + + def get_hid(self, snapshot): + dmidecode_raw = snapshot["data"]["dmidecode"] + self.dmi = DMIParse(dmidecode_raw) + + manufacturer = self.dmi.manufacturer().strip() + model = self.dmi.model().strip() + chassis = self.get_chassis_dh() + serial_number = self.dmi.serial_number() + sku = self.get_sku() + + return f"{manufacturer}{model}{chassis}{serial_number}{sku}" diff --git a/evidence/views.py b/evidence/views.py index 1e02c7e..d98bddb 100644 --- a/evidence/views.py +++ b/evidence/views.py @@ -89,7 +89,7 @@ class EvidenceView(DashboardView, FormView): def get(self, request, *args, **kwargs): self.pk = kwargs['pk'] self.object = Evidence(self.pk) - if self.object.owner != self.request.user: + if self.object.owner != self.request.user.institution: raise Http403 self.object.get_annotations() @@ -127,7 +127,7 @@ class DownloadEvidenceView(DashboardView, TemplateView): def get(self, request, *args, **kwargs): pk = kwargs['pk'] evidence = Evidence(pk) - if evidence.owner != self.request.user: + if evidence.owner != self.request.user.institution: raise Http403() evidence.get_doc() diff --git a/lot/migrations/0003_alter_lot_owner_alter_lotannotation_owner_and_more.py b/lot/migrations/0003_alter_lot_owner_alter_lotannotation_owner_and_more.py new file mode 100644 index 0000000..806c6f1 --- /dev/null +++ b/lot/migrations/0003_alter_lot_owner_alter_lotannotation_owner_and_more.py @@ -0,0 +1,36 @@ +# Generated by Django 5.0.6 on 2024-09-18 10:55 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("lot", "0002_lotannotation"), + ("user", "0001_initial"), + ] + + operations = [ + migrations.AlterField( + model_name="lot", + name="owner", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="user.institution" + ), + ), + migrations.AlterField( + model_name="lotannotation", + name="owner", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="user.institution" + ), + ), + migrations.AlterField( + model_name="lottag", + name="owner", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="user.institution" + ), + ), + ] diff --git a/lot/models.py b/lot/models.py index 9078459..3926f29 100644 --- a/lot/models.py +++ b/lot/models.py @@ -6,14 +6,14 @@ from utils.constants import ( STR_EXTEND_SIZE, ) -from user.models import User +from user.models import Institution # from device.models import Device -from evidence.models import Annotation +# from evidence.models import Annotation class LotTag(models.Model): name = models.CharField(max_length=STR_SIZE, blank=False, null=False) - owner = models.ForeignKey(User, on_delete=models.CASCADE) + owner = models.ForeignKey(Institution, on_delete=models.CASCADE) def __str__(self): return self.name @@ -31,7 +31,7 @@ class Lot(models.Model): code = models.CharField(max_length=STR_SIZE, blank=True, null=True) description = models.CharField(max_length=STR_SIZE, blank=True, null=True) closed = models.BooleanField(default=True) - owner = models.ForeignKey(User, on_delete=models.CASCADE) + owner = models.ForeignKey(Institution, on_delete=models.CASCADE) type = models.ForeignKey(LotTag, on_delete=models.CASCADE) def add(self, v): @@ -52,7 +52,7 @@ class LotAnnotation(models.Model): created = models.DateTimeField(auto_now_add=True) lot = models.ForeignKey(Lot, on_delete=models.CASCADE) - owner = models.ForeignKey(User, on_delete=models.CASCADE) + owner = models.ForeignKey(Institution, on_delete=models.CASCADE) type = models.SmallIntegerField(choices=Type) key = models.CharField(max_length=STR_EXTEND_SIZE) value = models.CharField(max_length=STR_EXTEND_SIZE) diff --git a/lot/views.py b/lot/views.py index 280450e..46252c8 100644 --- a/lot/views.py +++ b/lot/views.py @@ -28,7 +28,7 @@ class NewLotView(DashboardView, CreateView): ) def form_valid(self, form): - form.instance.owner = self.request.user + form.instance.owner = self.request.user.institution response = super().form_valid(form) return response @@ -83,8 +83,8 @@ class AddToLotView(DashboardView, FormView): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - lots = Lot.objects.filter(owner=self.request.user) - lot_tags = LotTag.objects.filter(owner=self.request.user) + lots = Lot.objects.filter(owner=self.request.user.institution) + lot_tags = LotTag.objects.filter(owner=self.request.user.institution) context.update({ 'lots': lots, 'lot_tags':lot_tags, @@ -93,7 +93,7 @@ class AddToLotView(DashboardView, FormView): def get_form(self): form = super().get_form() - form.fields["lots"].queryset = Lot.objects.filter(owner=self.request.user) + form.fields["lots"].queryset = Lot.objects.filter(owner=self.request.user.institution) return form def form_valid(self, form): @@ -123,10 +123,10 @@ class LotsTagsView(DashboardView, TemplateView): def get_context_data(self, **kwargs): self.pk = kwargs.get('pk') context = super().get_context_data(**kwargs) - tag = get_object_or_404(LotTag, owner=self.request.user, id=self.pk) + tag = get_object_or_404(LotTag, owner=self.request.user.institution, id=self.pk) self.title += " {}".format(tag.name) self.breadcrumb += " {}".format(tag.name) - lots = Lot.objects.filter(owner=self.request.user).filter(type=tag) + lots = Lot.objects.filter(owner=self.request.user.institution).filter(type=tag) context.update({ 'lots': lots, 'title': self.title, @@ -144,7 +144,7 @@ class LotAddDocumentView(DashboardView, CreateView): fields = ("key", "value") def form_valid(self, form): - form.instance.owner = self.request.user + form.instance.owner = self.request.user.institution form.instance.lot = self.lot form.instance.type = LotAnnotation.Type.DOCUMENT response = super().form_valid(form) @@ -152,7 +152,7 @@ class LotAddDocumentView(DashboardView, CreateView): def get_form_kwargs(self): pk = self.kwargs.get('pk') - self.lot = get_object_or_404(Lot, pk=pk, owner=self.request.user) + self.lot = get_object_or_404(Lot, pk=pk, owner=self.request.user.institution) self.success_url = reverse_lazy('lot:documents', args=[pk]) kwargs = super().get_form_kwargs() return kwargs @@ -166,10 +166,10 @@ class LotDocumentsView(DashboardView, TemplateView): def get_context_data(self, **kwargs): self.pk = kwargs.get('pk') context = super().get_context_data(**kwargs) - lot = get_object_or_404(Lot, owner=self.request.user, id=self.pk) + lot = get_object_or_404(Lot, owner=self.request.user.institution, id=self.pk) documents = LotAnnotation.objects.filter( lot=lot, - owner=self.request.user, + owner=self.request.user.institution, type=LotAnnotation.Type.DOCUMENT, ) context.update({ @@ -189,10 +189,10 @@ class LotAnnotationsView(DashboardView, TemplateView): def get_context_data(self, **kwargs): self.pk = kwargs.get('pk') context = super().get_context_data(**kwargs) - lot = get_object_or_404(Lot, owner=self.request.user, id=self.pk) + lot = get_object_or_404(Lot, owner=self.request.user.institution, id=self.pk) annotations = LotAnnotation.objects.filter( lot=lot, - owner=self.request.user, + owner=self.request.user.institution, type=LotAnnotation.Type.USER, ) context.update({ @@ -213,7 +213,7 @@ class LotAddAnnotationView(DashboardView, CreateView): fields = ("key", "value") def form_valid(self, form): - form.instance.owner = self.request.user + form.instance.owner = self.request.user.institution form.instance.lot = self.lot form.instance.type = LotAnnotation.Type.USER response = super().form_valid(form) @@ -221,7 +221,7 @@ class LotAddAnnotationView(DashboardView, CreateView): def get_form_kwargs(self): pk = self.kwargs.get('pk') - self.lot = get_object_or_404(Lot, pk=pk, owner=self.request.user) + self.lot = get_object_or_404(Lot, pk=pk, owner=self.request.user.institution) self.success_url = reverse_lazy('lot:annotations', args=[pk]) kwargs = super().get_form_kwargs() return kwargs diff --git a/user/management/commands/add_institution.py b/user/management/commands/add_institution.py index 622dff8..b7a4a26 100644 --- a/user/management/commands/add_institution.py +++ b/user/management/commands/add_institution.py @@ -1,6 +1,6 @@ from django.core.management.base import BaseCommand from user.models import Institution - +from lot.models import LotTag class Command(BaseCommand): help = "Create a new Institution" @@ -9,4 +9,17 @@ class Command(BaseCommand): parser.add_argument('name', type=str, help='institution') def handle(self, *args, **kwargs): - Institution.objects.create(name=kwargs['name']) + self.institution = Institution.objects.create(name=kwargs['name']) + self.create_lot_tags() + + def create_lot_tags(self): + tags = [ + "Entrada", + "Salida", + "Temporal" + ] + for tag in tags: + LotTag.objects.create( + name=tag, + owner=self.institution + ) diff --git a/user/management/commands/add_user.py b/user/management/commands/add_user.py index 7d31ab7..499adfa 100644 --- a/user/management/commands/add_user.py +++ b/user/management/commands/add_user.py @@ -1,6 +1,5 @@ from django.core.management.base import BaseCommand from django.contrib.auth import get_user_model -from lot.models import LotTag from user.models import Institution @@ -16,29 +15,16 @@ class Command(BaseCommand): parser.add_argument('password', type=str, help='password') def handle(self, *args, **kwargs): - email = kwargs['email'] - password = kwargs['password'] - institution = Institution.objects.get(name=kwargs['institution']) - self.create_user(institution, email, password) - self.create_lot_tags() + self.email = kwargs['email'] + self.password = kwargs['password'] + self.institution = Institution.objects.get(name=kwargs['institution']) + self.create_user() - def create_user(self, institution, email, password): + def create_user(self): self.u = User.objects.create( - institution=institution, - email=email, - password=password + institution=self.institution, + email=self.email, + password=self.password ) - self.u.set_password(password) + self.u.set_password(self.password) self.u.save() - - def create_lot_tags(self): - tags = [ - "Entrada", - "Salida", - "Temporal" - ] - for tag in tags: - LotTag.objects.create( - name=tag, - owner=self.u - ) diff --git a/user/views.py b/user/views.py index 91ea44a..e7f3289 100644 --- a/user/views.py +++ b/user/views.py @@ -1,3 +1,6 @@ from django.shortcuts import render +from django.utils.translation import gettext_lazy as _ +from dashboard.mixins import InventaryMixin, DetailsMixin + + -# Create your views here. diff --git a/utils/constants.py b/utils/constants.py index 2fd9a13..e481d6c 100644 --- a/utils/constants.py +++ b/utils/constants.py @@ -20,3 +20,21 @@ HID_ALGO1 = [ ALGOS = { "hidalgo1": HID_ALGO1, } + + +CHASSIS_DH = { + 'Tower': {'desktop', 'low-profile', 'tower', 'server'}, + 'Docking': {'docking'}, + 'AllInOne': {'all-in-one'}, + 'Microtower': {'mini-tower', 'space-saving', 'mini'}, + 'PizzaBox': {'pizzabox'}, + 'Lunchbox': {'lunchbox'}, + 'Stick': {'stick'}, + 'Netbook': {'notebook', 'sub-notebook'}, + 'Handheld': {'handheld'}, + 'Laptop': {'portable', 'laptop'}, + 'Convertible': {'convertible'}, + 'Detachable': {'detachable'}, + 'Tablet': {'tablet'}, + 'Virtual': {'_virtual'}, +} diff --git a/utils/device.py b/utils/device.py index 2e41597..db9f0ce 100644 --- a/utils/device.py +++ b/utils/device.py @@ -69,7 +69,7 @@ def create_annotation(doc, user, commit=False): data = { 'uuid': doc['uuid'], - 'owner': user, + 'owner': user.institution, 'type': Annotation.Type.SYSTEM, 'key': 'CUSTOMER_ID', 'value': doc['CUSTOMER_ID'], -- 2.30.2 From 13c78fb3c1cf3001c01ec903d66882d1b228d8cf Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Thu, 19 Sep 2024 18:22:17 +0200 Subject: [PATCH 02/23] api --- dashboard/templates/base.html | 6 ++++++ dhub/settings.py | 2 ++ dhub/urls.py | 1 + evidence/forms.py | 9 +++++++++ requirements.txt | 3 ++- 5 files changed, 20 insertions(+), 1 deletion(-) diff --git a/dashboard/templates/base.html b/dashboard/templates/base.html index 3d046f1..f4a9307 100644 --- a/dashboard/templates/base.html +++ b/dashboard/templates/base.html @@ -143,6 +143,12 @@ + diff --git a/dhub/settings.py b/dhub/settings.py index 055828a..6e83032 100644 --- a/dhub/settings.py +++ b/dhub/settings.py @@ -43,6 +43,7 @@ INSTALLED_APPS = [ "django.contrib.staticfiles", 'django_extensions', 'django_bootstrap5', + 'django_tables2', "rest_framework", "login", "user", @@ -53,6 +54,7 @@ INSTALLED_APPS = [ "lot", "documents", "dashboard", + "api", ] diff --git a/dhub/urls.py b/dhub/urls.py index 90252d9..9bbd687 100644 --- a/dhub/urls.py +++ b/dhub/urls.py @@ -24,4 +24,5 @@ urlpatterns = [ path("evidence/", include("evidence.urls")), path("device/", include("device.urls")), path("lot/", include("lot.urls")), + path('api/', include('api.urls')), ] diff --git a/evidence/forms.py b/evidence/forms.py index be50ca8..d392f6e 100644 --- a/evidence/forms.py +++ b/evidence/forms.py @@ -56,6 +56,15 @@ class UserTagForm(forms.Form): def __init__(self, *args, **kwargs): self.uuid = kwargs.pop('uuid', None) + annotation = Annotation.objects.filter( + uuid=self.uuid, + type=Annotation.Type.SYSTEM, + key='CUSTOM_ID', + ).first() + + if annotation: + kwargs['initial'].update({'tag': annotation.value}) + super().__init__(*args, **kwargs) def clean(self): diff --git a/requirements.txt b/requirements.txt index 9d4492f..217d120 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,9 +3,10 @@ Django==5.0.6 django-bootstrap5==24.2 django-extensions==3.2.3 djangorestframework==3.15.1 +django-tables2==2.6.0 python-decouple==3.3 py-dmidecode==0.1.3 pandas==2.2.2 xlrd==2.0.1 odfpy==1.4.1 - +pytz==2024.2 -- 2.30.2 From ad59484b035d9ad5c0cb6ba96b534feff189d198 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Thu, 19 Sep 2024 18:35:26 +0200 Subject: [PATCH 03/23] api dir --- api/__init__.py | 0 api/admin.py | 3 + api/apps.py | 6 ++ api/forms.py | 1 + api/migrations/0001_initial.py | 39 +++++++++++ api/migrations/__init__.py | 0 api/models.py | 9 +++ api/tables.py | 67 ++++++++++++++++++ api/templates/custom_table.html | 100 +++++++++++++++++++++++++++ api/templates/token.html | 14 ++++ api/urls.py | 13 ++++ api/views.py | 116 ++++++++++++++++++++++++++++++++ 12 files changed, 368 insertions(+) create mode 100644 api/__init__.py create mode 100644 api/admin.py create mode 100644 api/apps.py create mode 100644 api/forms.py create mode 100644 api/migrations/0001_initial.py create mode 100644 api/migrations/__init__.py create mode 100644 api/models.py create mode 100644 api/tables.py create mode 100644 api/templates/custom_table.html create mode 100644 api/templates/token.html create mode 100644 api/urls.py create mode 100644 api/views.py diff --git a/api/__init__.py b/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/api/admin.py b/api/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/api/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/api/apps.py b/api/apps.py new file mode 100644 index 0000000..66656fd --- /dev/null +++ b/api/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class ApiConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'api' diff --git a/api/forms.py b/api/forms.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/api/forms.py @@ -0,0 +1 @@ + diff --git a/api/migrations/0001_initial.py b/api/migrations/0001_initial.py new file mode 100644 index 0000000..22c5a1f --- /dev/null +++ b/api/migrations/0001_initial.py @@ -0,0 +1,39 @@ +# Generated by Django 5.0.6 on 2024-09-19 15:09 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name="Token", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("token", models.UUIDField()), + ( + "owner", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), + ], + ), + ] diff --git a/api/migrations/__init__.py b/api/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/api/models.py b/api/models.py new file mode 100644 index 0000000..b8bbc24 --- /dev/null +++ b/api/models.py @@ -0,0 +1,9 @@ +from django.db import models +from user.models import User + +# Create your models here. + + +class Token(models.Model): + token = models.UUIDField() + owner = models.ForeignKey(User, on_delete=models.CASCADE) diff --git a/api/tables.py b/api/tables.py new file mode 100644 index 0000000..ac1cc7a --- /dev/null +++ b/api/tables.py @@ -0,0 +1,67 @@ +import django_tables2 as tables +from django.utils.html import format_html +from django.utils.translation import gettext_lazy as _ + +from api.models import Token + + +class ButtonColumn(tables.Column): + attrs = { + "a": { + "type": "button", + "class": "text-danger", + "title": "Remove", + } + } + # it makes no sense to order a column of buttons + orderable = False + # django_tables will only call the render function if it doesn't find + # any empty values in the data, so we stop it from matching the data + # to any value considered empty + empty_values = () + + def render(self): + return format_html('') + + +class TokensTable(tables.Table): + delete = ButtonColumn( + verbose_name=_("Delete"), + linkify={ + "viewname": "api:delete_token", + "args": [tables.A("pk")] + }, + orderable=False + ) + + token = tables.Column(verbose_name=_("Token"), empty_values=()) + + def render_view_user(self): + return format_html('') + + # def render_token(self, record): + # return record.get_memberships() + + # def order_membership(self, queryset, is_descending): + # # TODO: Test that this doesn't return more rows than it should + # queryset = queryset.order_by( + # ("-" if is_descending else "") + "memberships__type" + # ) + + # return (queryset, True) + + # def render_role(self, record): + # return record.get_roles() + + # def order_role(self, queryset, is_descending): + # queryset = queryset.order_by( + # ("-" if is_descending else "") + "roles" + # ) + + # return (queryset, True) + + class Meta: + model = Token + template_name = "custom_table.html" + fields = ("token", "view_user") + diff --git a/api/templates/custom_table.html b/api/templates/custom_table.html new file mode 100644 index 0000000..496ddec --- /dev/null +++ b/api/templates/custom_table.html @@ -0,0 +1,100 @@ +{% load django_tables2 %} +{% load i18n %} +{% block table-wrapper %} +
+ {% block table %} + + {% block table.thead %} + {% if table.show_header %} + + + {% for column in table.columns %} + + {% endfor %} + + + {% endif %} + {% endblock table.thead %} + {% block table.tbody %} + + {% for row in table.paginated_rows %} + {% block table.tbody.row %} + + {% for column, cell in row.items %} + + {% endfor %} + + {% endblock table.tbody.row %} + {% empty %} + {% if table.empty_text %} + {% block table.tbody.empty_text %} + + {% endblock table.tbody.empty_text %} + {% endif %} + {% endfor %} + + {% endblock table.tbody %} + {% block table.tfoot %} + {% if table.has_footer %} + + + {% for column in table.columns %} + + {% endfor %} + + + {% endif %} + {% endblock table.tfoot %} +
+ {% if column.orderable %} + {{ column.header }} + {% else %} + {{ column.header }} + {% endif %} +
{% if column.localize == None %}{{ cell }}{% else %}{% if column.localize %}{{ cell|localize }}{% else %}{{ cell|unlocalize }}{% endif %}{% endif %}
{{ table.empty_text }}
{{ column.footer }}
+ {% endblock table %} + + {% block pagination %} + {% if table.page and table.paginator.num_pages > 1 %} + + {% endif %} + {% endblock pagination %} +
+{% endblock table-wrapper %} diff --git a/api/templates/token.html b/api/templates/token.html new file mode 100644 index 0000000..5185090 --- /dev/null +++ b/api/templates/token.html @@ -0,0 +1,14 @@ +{% extends "base.html" %} +{% load i18n %} +{% load render_table from django_tables2 %} + +{% block content %} +

+ + {{ subtitle }} +

+{% render_table table %} + +{% endblock %} diff --git a/api/urls.py b/api/urls.py new file mode 100644 index 0000000..f3c8028 --- /dev/null +++ b/api/urls.py @@ -0,0 +1,13 @@ +from api import views + +from django.urls import path + + +app_name = 'api' + +urlpatterns = [ + path('snapshot/', views.NewSnapshot, name='new_snapshot'), + path('tokens/', views.TokenView.as_view(), name='tokens'), + path('tokens/new', views.TokenNewView.as_view(), name='new_token'), + path('tokens//del', views.TokenDeleteView.as_view(), name='delete_token'), +] diff --git a/api/views.py b/api/views.py new file mode 100644 index 0000000..5a119f9 --- /dev/null +++ b/api/views.py @@ -0,0 +1,116 @@ +import json + +from django.shortcuts import get_object_or_404, redirect +from django.utils.translation import gettext_lazy as _ +from django.views.decorators.csrf import csrf_exempt +from django.core.exceptions import ValidationError +from django.views.generic.edit import DeleteView +from django.views.generic.base import View +from django.http import JsonResponse +from django_tables2 import SingleTableView +from uuid import uuid4 + +from dashboard.mixins import DashboardView +from evidence.models import Annotation +from evidence.parse import Build +from user.models import User +from api.models import Token +from api.tables import TokensTable + + +def save_in_disk(data, user): + pass + + +@csrf_exempt +def NewSnapshot(request): + # Accept only posts + if request.method != 'POST': + return JsonResponse({'error': 'Invalid request method'}, status=400) + + # Authentication + # auth_header = request.headers.get('Authorization') + # if not auth_header or not auth_header.startswith('Bearer '): + # return JsonResponse({'error': 'Invalid or missing token'}, status=401) + + # token = auth_header.split(' ')[1] + # tk = Token.objects.filter(token=token).first() + # if not tk: + # return JsonResponse({'error': 'Invalid or missing token'}, status=401) + + # Validation snapshot + try: + data = json.loads(request.body) + except json.JSONDecodeError: + return JsonResponse({'error': 'Invalid JSON'}, status=400) + + # try: + # Build(data, None, check=True) + # except Exception: + # return JsonResponse({'error': 'Invalid Snapshot'}, status=400) + + exist_annotation = Annotation.objects.filter( + uuid=data['uuid'] + ).first() + + if exist_annotation: + raise ValidationError("error: the snapshot {} exist".format(data['uuid'])) + + # Process snapshot + # save_in_disk(data, tk.user) + + try: + # Build(data, tk.user) + user = User.objects.get(email="user@example.org") + Build(data, user) + except Exception: + return JsonResponse({'status': 'fail'}, status=200) + + return JsonResponse({'status': 'success'}, status=200) + + + + +class TokenView(DashboardView, SingleTableView): + template_name = "token.html" + title = _("Credential management") + section = "Credential" + subtitle = _('Managament Tokens') + icon = 'bi bi-key' + model = Token + table_class = TokensTable + + def get_queryset(self): + """ + Override the get_queryset method to filter events based on the user type. + """ + return Token.objects.filter().order_by("-id") + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context.update({ + 'tokens': Token.objects, + }) + return context + + +class TokenDeleteView(DashboardView, DeleteView): + model = Token + + def get(self, request, *args, **kwargs): + # self.check_valid_user() + self.pk = kwargs['pk'] + self.object = get_object_or_404(self.model, pk=self.pk) + self.object.delete() + + return redirect('api:tokens') + + +class TokenNewView(DashboardView, View): + + def get(self, request, *args, **kwargs): + # self.check_valid_user() + Token.objects.create(token=uuid4()) + + return redirect('api:tokens') + -- 2.30.2 From a1ffdb5ed694b534f8a604851845a69ec46f513b Mon Sep 17 00:00:00 2001 From: pedro Date: Thu, 19 Sep 2024 14:05:28 -0300 Subject: [PATCH 04/23] docker: add_institution command --- docker/devicehub-django.entrypoint.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docker/devicehub-django.entrypoint.sh b/docker/devicehub-django.entrypoint.sh index 158406e..68e3760 100644 --- a/docker/devicehub-django.entrypoint.sh +++ b/docker/devicehub-django.entrypoint.sh @@ -21,7 +21,9 @@ deploy() { # inspired by https://medium.com/analytics-vidhya/django-with-docker-and-docker-compose-python-part-2-8415976470cc echo "INFO detected NEW deployment" ./manage.py migrate - ./manage.py add_user user@example.org 1234 + ./manage.py add_institution example-org + # TODO: one error on add_user, and you don't add user anymore + ./manage.py add_user example-org user@example.org 1234 fi } -- 2.30.2 From 2f4a85f79bf0ac2cd74a80afcd79ed4139392de4 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Fri, 20 Sep 2024 14:23:14 +0200 Subject: [PATCH 05/23] add interface for create tokens by user for user --- api/views.py | 4 +--- dashboard/templates/base.html | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/api/views.py b/api/views.py index 5a119f9..2aeb4de 100644 --- a/api/views.py +++ b/api/views.py @@ -98,7 +98,6 @@ class TokenDeleteView(DashboardView, DeleteView): model = Token def get(self, request, *args, **kwargs): - # self.check_valid_user() self.pk = kwargs['pk'] self.object = get_object_or_404(self.model, pk=self.pk) self.object.delete() @@ -109,8 +108,7 @@ class TokenDeleteView(DashboardView, DeleteView): class TokenNewView(DashboardView, View): def get(self, request, *args, **kwargs): - # self.check_valid_user() - Token.objects.create(token=uuid4()) + Token.objects.create(token=uuid4(), owner=self.request.user) return redirect('api:tokens') diff --git a/dashboard/templates/base.html b/dashboard/templates/base.html index f4a9307..318c890 100644 --- a/dashboard/templates/base.html +++ b/dashboard/templates/base.html @@ -144,7 +144,7 @@ - diff --git a/user/templates/panel.html b/user/templates/panel.html index 99e22cb..ca27036 100644 --- a/user/templates/panel.html +++ b/user/templates/panel.html @@ -12,7 +12,9 @@ {% endblock %} -- 2.30.2 From e0929c03c7712086ba7aec8af069ca14223d8c8a Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Fri, 11 Oct 2024 13:04:37 +0200 Subject: [PATCH 22/23] add settings page for download settings file --- user/forms.py | 20 ++++++++++++++++++++ user/templates/panel.html | 13 ++++++++++--- user/templates/settings.html | 32 ++++++++++++++++++++++++++++++++ user/templates/settings.ini | 3 +++ user/urls.py | 1 + user/views.py | 29 +++++++++++++++++++++++++++++ 6 files changed, 95 insertions(+), 3 deletions(-) create mode 100644 user/forms.py create mode 100644 user/templates/settings.html create mode 100644 user/templates/settings.ini diff --git a/user/forms.py b/user/forms.py new file mode 100644 index 0000000..2dd7e6b --- /dev/null +++ b/user/forms.py @@ -0,0 +1,20 @@ +from django import forms + + +class SettingsForm(forms.Form): + token = forms.ChoiceField( + choices = [] + ) + erasure = forms.ChoiceField( + choices = [(0, 'Not erasure'), + ('erasure1', 'Erasure easy'), + ('erasure2', 'Erasure mediom'), + ('erasure3', 'Erasure hard'), + ], + ) + + def __init__(self, *args, **kwargs): + tokens = kwargs.pop('tokens') + super().__init__(*args, **kwargs) + tk = [(str(x.token), x.tag) for x in tokens] + self.fields['token'].choices = tk diff --git a/user/templates/panel.html b/user/templates/panel.html index ca27036..8686062 100644 --- a/user/templates/panel.html +++ b/user/templates/panel.html @@ -12,9 +12,16 @@ {% endblock %} diff --git a/user/templates/settings.html b/user/templates/settings.html new file mode 100644 index 0000000..cf93776 --- /dev/null +++ b/user/templates/settings.html @@ -0,0 +1,32 @@ +{% extends "base.html" %} +{% load i18n %} + +{% block content %} +
+
+

{{ subtitle }}

+
+
+ +{% load django_bootstrap5 %} +
+{% csrf_token %} +{% if form.errors %} + +{% endif %} +{% bootstrap_form form %} + + +
+{% endblock %} diff --git a/user/templates/settings.ini b/user/templates/settings.ini new file mode 100644 index 0000000..390b650 --- /dev/null +++ b/user/templates/settings.ini @@ -0,0 +1,3 @@ +token = {{ token }} +erasure = {{ erasure }} +legacy = False \ No newline at end of file diff --git a/user/urls.py b/user/urls.py index da4e507..f9e6246 100644 --- a/user/urls.py +++ b/user/urls.py @@ -5,4 +5,5 @@ app_name = 'user' urlpatterns = [ path("panel/", views.PanelView.as_view(), name="panel"), + path("settings/", views.SettingsView.as_view(), name="settings"), ] diff --git a/user/views.py b/user/views.py index e0a195a..36093b8 100644 --- a/user/views.py +++ b/user/views.py @@ -1,6 +1,14 @@ +from django.http import HttpResponse +from django.shortcuts import render from django.utils.translation import gettext_lazy as _ from django.views.generic.base import TemplateView from dashboard.mixins import DashboardView +from django.views.generic.edit import ( + FormView, +) + +from user.forms import SettingsForm +from api.models import Token class PanelView(DashboardView, TemplateView): @@ -8,3 +16,24 @@ class PanelView(DashboardView, TemplateView): title = _("User") breadcrumb = "User / Panel" subtitle = "User panel" + + +class SettingsView(DashboardView, FormView): + template_name = "settings.html" + title = _("Download Settings") + breadcrumb = "user / workbench / settings" + form_class = SettingsForm + + def form_valid(self, form): + form.devices = self.get_session_devices() + data = render(self.request, "settings.ini", form.cleaned_data) + response = HttpResponse(data.content, content_type="application/text") + response['Content-Disposition'] = 'attachment; filename={}'.format("settings.ini") + return response + + def get_form_kwargs(self): + tokens = Token.objects.filter(owner=self.request.user) + kwargs = super().get_form_kwargs() + kwargs['tokens'] = tokens + return kwargs + -- 2.30.2 From 017dc818da602462d55b367300e8e80b1df8297b Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Fri, 11 Oct 2024 13:46:21 +0200 Subject: [PATCH 23/23] add url in settings and legacy --- user/forms.py | 6 +++--- user/templates/settings.ini | 7 +++++-- user/templates/settings_legacy.ini | 6 ++++++ user/views.py | 15 +++++++++++++-- 4 files changed, 27 insertions(+), 7 deletions(-) create mode 100644 user/templates/settings_legacy.ini diff --git a/user/forms.py b/user/forms.py index 2dd7e6b..ec3f17f 100644 --- a/user/forms.py +++ b/user/forms.py @@ -7,9 +7,9 @@ class SettingsForm(forms.Form): ) erasure = forms.ChoiceField( choices = [(0, 'Not erasure'), - ('erasure1', 'Erasure easy'), - ('erasure2', 'Erasure mediom'), - ('erasure3', 'Erasure hard'), + ('basic', 'Erasure Basic'), + ('baseline', 'Erasure Baseline'), + ('enhanced', 'Erasure Enhanced'), ], ) diff --git a/user/templates/settings.ini b/user/templates/settings.ini index 390b650..217e993 100644 --- a/user/templates/settings.ini +++ b/user/templates/settings.ini @@ -1,3 +1,6 @@ +[settings] +url = {{ url }} token = {{ token }} -erasure = {{ erasure }} -legacy = False \ No newline at end of file +erase = {{ erasure }} +legacy = false +# path = /path/to/save \ No newline at end of file diff --git a/user/templates/settings_legacy.ini b/user/templates/settings_legacy.ini new file mode 100644 index 0000000..6407090 --- /dev/null +++ b/user/templates/settings_legacy.ini @@ -0,0 +1,6 @@ +[settings] +url = {{ url }} +token = {{ token }} +legacy = true +# erase = {{ erasure }} +# path = /path/to/save \ No newline at end of file diff --git a/user/views.py b/user/views.py index 36093b8..750b9a3 100644 --- a/user/views.py +++ b/user/views.py @@ -1,3 +1,5 @@ +from decouple import config +from django.urls import reverse from django.http import HttpResponse from django.shortcuts import render from django.utils.translation import gettext_lazy as _ @@ -25,8 +27,17 @@ class SettingsView(DashboardView, FormView): form_class = SettingsForm def form_valid(self, form): - form.devices = self.get_session_devices() - data = render(self.request, "settings.ini", form.cleaned_data) + cleaned_data = form.cleaned_data.copy() + settings_tmpl = "settings.ini" + path = reverse("api:new_snapshot") + cleaned_data['url'] = self.request.build_absolute_uri(path) + + if config("LEGACY", False): + cleaned_data['token'] = config.get('TOKEN_LEGACY', '') + cleaned_data['url'] = config.get('URL_LEGACY', '') + settings_tmpl = "settings_legacy.ini" + + data = render(self.request, settings_tmpl, cleaned_data) response = HttpResponse(data.content, content_type="application/text") response['Content-Disposition'] = 'attachment; filename={}'.format("settings.ini") return response -- 2.30.2