fix(api): inventory upload requires sanitization

!33
This commit is contained in:
2024-06-19 15:00:30 +09:30
parent 58051f297f
commit 4e42856027
8 changed files with 264 additions and 48 deletions

View File

View File

@ -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)

View File

@ -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,16 +78,22 @@ this setting populated, no device will be created and the endpoint will return H
)
def post(self, request, *args, **kwargs):
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():
if Device.objects.filter(slug=str(data.details.name).lower()).exists():
self.obj = Device.objects.get(slug=str(data['details']['name']).lower())
self.obj = Device.objects.get(slug=str(data.details.name).lower())
device = self.obj
@ -94,50 +102,45 @@ this setting populated, no device will be created and the endpoint will return H
raise Http404
status = Http.Status.BAD_REQUEST
device_operating_system = None
operating_system = None
operating_system_version = None
try:
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<semver>\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)

View File

@ -10,8 +10,11 @@ class Http():
"""HTTP server status codes."""
OK = 200
CREATED = 201
BAD_REQUEST = 400
FORBIDDEN = 403
SERVER_ERROR = 500

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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