diff --git a/app/api/serializers/__init__.py b/app/api/serializers/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/app/api/serializers/inventory.py b/app/api/serializers/inventory.py new file mode 100644 index 00000000..b12370e9 --- /dev/null +++ b/app/api/serializers/inventory.py @@ -0,0 +1,168 @@ +from django.core.exceptions import ValidationError +from django.utils.html import escape + +class Inventory: + """ Inventory Object + + Pass in an Inventory dict that a device has provided and sanitize ready for use. + + Raises: + ValidationError: Malformed inventory data. + """ + + + class Details: + + _name: str + + _serial_number: str + + _uuid: str + + + def __init__(self, details: dict): + + self._name = escape(details['name']) + + self._serial_number = escape(details['serial_number']) + + self._uuid = escape(details['uuid']) + + + @property + def name(self) -> str: + + return str(self._name) + + + @property + def serial_number(self) -> str: + + return str(self._serial_number) + + + @property + def uuid(self) -> str: + + return str(self._uuid) + + + + class OperatingSystem: + + _name: str + + _version_major: str + + _version: str + + + def __init__(self, operating_system: dict): + + self._name = escape(operating_system['name']) + + self._version_major = escape(operating_system['version_major']) + + self._version = escape(operating_system['version']) + + + @property + def name(self) -> str: + + return str(self._name) + + + @property + def version_major(self) -> str: + + return str(self._version_major) + + + @property + def version(self) -> str: + + return str(self._version) + + + + class Software: + + _name: str + + _category: str + + _version: str + + + def __init__(self, software: dict): + + self._name = escape(software['name']) + + self._category = escape(software['category']) + + self._version = escape(software['version']) + + + @property + def name(self) -> str: + + return str(self._name) + + + @property + def category(self) -> str: + + return str(self._category) + + + @property + def version(self) -> str: + + return str(self._version) + + + + _details: Details = None + + _operating_system: OperatingSystem = None + + _software: list[Software] = [] + + + def __init__(self, inventory: dict): + + if ( + type(inventory['details']) is dict and + type(inventory['os']) is dict and + type(inventory['software']) is list + ): + + self._details = self.Details(inventory['details']) + + self._operating_system = self.OperatingSystem(inventory['os']) + + for software in inventory['software']: + + self._software += [ self.Software(software) ] + + else: + + raise ValidationError('Inventory File is invalid') + + + @property + def details(self) -> Details: + + return self._details + + + @property + def operating_system(self) -> OperatingSystem: + + return self._operating_system + + + @property + def software(self) -> list[Software]: + + return list(self._software) diff --git a/app/api/views/itam/inventory.py b/app/api/views/itam/inventory.py index c3281005..d72fedba 100644 --- a/app/api/views/itam/inventory.py +++ b/app/api/views/itam/inventory.py @@ -2,6 +2,7 @@ import json import re +from django.core.exceptions import ValidationError, PermissionDenied from django.http import Http404, JsonResponse from django.utils import timezone @@ -15,6 +16,7 @@ from access.models import Organization from api.views.mixin import OrganizationPermissionAPI from api.serializers.itam.inventory import InventorySerializer +from api.serializers.inventory import Inventory from core.http.common import Http @@ -33,7 +35,7 @@ class InventoryPermissions(OrganizationPermissionAPI): data = view.request.data - self.obj = Device.objects.get(slug=str(data['details']['name']).lower()) + self.obj = Device.objects.get(slug=str(data.details.name).lower()) return super().permission_check(request, view, obj=None) @@ -76,68 +78,69 @@ this setting populated, no device will be created and the endpoint will return H ) def post(self, request, *args, **kwargs): - - data = json.loads(request.body) - - device = None - - self.default_organization = UserSettings.objects.get(user=request.user).default_organization - - if Device.objects.filter(slug=str(data['details']['name']).lower()).exists(): - - self.obj = Device.objects.get(slug=str(data['details']['name']).lower()) - - device = self.obj - - - if not self.permission_check(request=request, view=self, obj=device): - - raise Http404 - - - - status = Http.Status.BAD_REQUEST - - device_operating_system = None - operating_system = None - operating_system_version = None + status = Http.Status.OK + response_data = 'OK' try: + data = json.loads(request.body) + data = Inventory(data) + + device = None + + + self.default_organization = UserSettings.objects.get(user=request.user).default_organization + + if Device.objects.filter(slug=str(data.details.name).lower()).exists(): + + self.obj = Device.objects.get(slug=str(data.details.name).lower()) + + device = self.obj + + + if not self.permission_check(request=request, view=self, obj=device): + + raise Http404 + + device_operating_system = None + operating_system = None + operating_system_version = None + + app_settings = AppSettings.objects.get(owner_organization = None) if not device: # Create the device device = Device.objects.create( - name = data['details']['name'], + name = data.details.name, device_type = None, - serial_number = data['details']['serial_number'], - uuid = data['details']['uuid'], + serial_number = data.details.serial_number, + uuid = data.details.uuid, organization = self.default_organization, ) status = Http.Status.CREATED - if OperatingSystem.objects.filter( slug=data['os']['name'] ).exists(): + if OperatingSystem.objects.filter( slug=data.operating_system.name ).exists(): - operating_system = OperatingSystem.objects.get( slug=data['os']['name'] ) + operating_system = OperatingSystem.objects.get( slug=data.operating_system.name ) else: # Create Operating System operating_system = OperatingSystem.objects.create( - name = data['os']['name'], + name = data.operating_system.name, organization = self.default_organization, is_global = True ) - if OperatingSystemVersion.objects.filter( name=data['os']['version_major'], operating_system=operating_system ).exists(): + if OperatingSystemVersion.objects.filter( name=data.operating_system.version_major, operating_system=operating_system ).exists(): operating_system_version = OperatingSystemVersion.objects.get( organization = self.default_organization, is_global = True, - name = data['os']['version_major'], + name = data.operating_system.version_major, operating_system = operating_system ) @@ -146,16 +149,16 @@ this setting populated, no device will be created and the endpoint will return H operating_system_version = OperatingSystemVersion.objects.create( organization = self.default_organization, is_global = True, - name = data['os']['version_major'], + name = data.operating_system.version_major, operating_system = operating_system, ) - if DeviceOperatingSystem.objects.filter( version=data['os']['version'], device=device, operating_system_version=operating_system_version ).exists(): + if DeviceOperatingSystem.objects.filter( version=data.operating_system.version, device=device, operating_system_version=operating_system_version ).exists(): device_operating_system = DeviceOperatingSystem.objects.get( device=device, - version = data['os']['version'], + version = data.operating_system.version, operating_system_version = operating_system_version, ) @@ -170,7 +173,7 @@ this setting populated, no device will be created and the endpoint will return H device_operating_system = DeviceOperatingSystem.objects.create( organization = self.default_organization, device=device, - version = data['os']['version'], + version = data.operating_system.version, operating_system_version = operating_system_version, installdate = timezone.now() ) @@ -195,7 +198,7 @@ this setting populated, no device will be created and the endpoint will return H - for inventory in list(data['software']): + for inventory in list(data.software): software = None software_category = None @@ -204,10 +207,10 @@ this setting populated, no device will be created and the endpoint will return H device_software = None - if SoftwareCategory.objects.filter( name = inventory['category'] ).exists(): + if SoftwareCategory.objects.filter( name = inventory.category ).exists(): software_category = SoftwareCategory.objects.get( - name = inventory['category'] + name = inventory.category ) else: # Create Software Category @@ -215,14 +218,14 @@ this setting populated, no device will be created and the endpoint will return H software_category = SoftwareCategory.objects.create( organization = software_category_organization, is_global = True, - name = inventory['category'], + name = inventory.category, ) - if Software.objects.filter( name = inventory['name'] ).exists(): + if Software.objects.filter( name = inventory.name ).exists(): software = Software.objects.get( - name = inventory['name'] + name = inventory.name ) if not software.category: @@ -235,14 +238,14 @@ this setting populated, no device will be created and the endpoint will return H software = Software.objects.create( organization = software_organization, is_global = True, - name = inventory['name'], + name = inventory.name, category = software_category, ) pattern = r"^(\d+:)?(?P\d+\.\d+(\.\d+)?)" - semver = re.search(pattern, str(inventory['version']), re.DOTALL) + semver = re.search(pattern, str(inventory.version), re.DOTALL) if semver: @@ -250,7 +253,7 @@ this setting populated, no device will be created and the endpoint will return H semver = semver['semver'] else: - semver = inventory['version'] + semver = inventory.version if SoftwareVersion.objects.filter( name = semver, software = software ).exists(): @@ -328,15 +331,25 @@ this setting populated, no device will be created and the endpoint will return H status = Http.Status.OK + except PermissionDenied as e: + + status = Http.Status.FORBIDDEN + response_data = '' + + except ValidationError as e: + + status = Http.Status.BAD_REQUEST + response_data = e.message except Exception as e: print(f'An error occured{e}') status = Http.Status.SERVER_ERROR + response_data = 'Unknown Server Error occured' - return Response(data='OK',status=status) + return Response(data=response_data,status=status) diff --git a/app/core/http/common.py b/app/core/http/common.py index 1f5e4c8a..b4acfabe 100644 --- a/app/core/http/common.py +++ b/app/core/http/common.py @@ -10,8 +10,11 @@ class Http(): """HTTP server status codes.""" OK = 200 + CREATED = 201 BAD_REQUEST = 400 + FORBIDDEN = 403 + SERVER_ERROR = 500 diff --git a/docs/projects/django-template/development/api/index.md b/docs/projects/django-template/development/api/index.md index 437c9507..2ef68892 100644 --- a/docs/projects/django-template/development/api/index.md +++ b/docs/projects/django-template/development/api/index.md @@ -8,6 +8,9 @@ about: https://gitlab.com/nofusscomputing/infrastructure/configuration-managemen This section contains the application API documentation to assist in application development. The target audience is anyone whom would be developing the application. + - [Models](./models/index.md) +- [Serializers](./serializer/index.md) + - [Unit Testing](./tests/index.md) diff --git a/docs/projects/django-template/development/api/serializer/index.md b/docs/projects/django-template/development/api/serializer/index.md new file mode 100644 index 00000000..e849f35c --- /dev/null +++ b/docs/projects/django-template/development/api/serializer/index.md @@ -0,0 +1,12 @@ +--- +title: Serializers +description: No Fuss Computings Django ITSM API Documentation for Serializers +date: 2024-06-19 +template: project.html +about: https://gitlab.com/nofusscomputing/infrastructure/configuration-management/django_app +--- + +This section contains the application API documentation for Serializers to assist in application development. The target audience is anyone whom would be developing the application. + + +- [Inventory](./inventory.md) diff --git a/docs/projects/django-template/development/api/serializer/inventory.md b/docs/projects/django-template/development/api/serializer/inventory.md new file mode 100644 index 00000000..59d74925 --- /dev/null +++ b/docs/projects/django-template/development/api/serializer/inventory.md @@ -0,0 +1,11 @@ +--- +title: Inventory +description: No Fuss Computings django ITSM Inventory serializer +date: 2024-06-19 +template: project.html +about: https://gitlab.com/nofusscomputing/infrastructure/configuration-management/django_app +--- + +::: app.api.serializers.inventory.Inventory + options: + inherited_members: true diff --git a/mkdocs.yml b/mkdocs.yml index cb40e975..da39971e 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -76,6 +76,12 @@ nav: - projects/django-template/development/api/models/access_organization_permission_checking.md + - Serializers: + + - projects/django-template/development/api/serializer/index.md + + - projects/django-template/development/api/serializer/inventory.md + - Unit Testing: - projects/django-template/development/api/tests/index.md