From fb6d942bb717e74464cd1ceec9b1e96f82e24594 Mon Sep 17 00:00:00 2001 From: Davide Arcuri Date: Wed, 23 Oct 2024 16:14:54 +0200 Subject: [PATCH] #1073 --- compose/local/dask/Dockerfile | 2 +- compose/local/django/Dockerfile | 2 +- examples/local_api.ipynb | 76 +++++++----- orochi/api/models.py | 24 +++- orochi/api/routers/dumps.py | 102 +++++++++++++++- orochi/static/js/handlebars/dump.js | 46 ++++++++ orochi/static/js/handlebars/dump_folder.js | 63 ++++++++++ orochi/templates/base.html | 2 + orochi/templates/handlebars/dump.handlebars | 43 +++++++ .../handlebars/dump_folder.handlebars | 49 ++++++++ orochi/templates/website/index.html | 74 +++++++++--- .../website/partial_index_create.html | 2 +- orochi/templates/website/partial_indices.html | 2 +- orochi/website/admin.py | 2 + orochi/website/forms.py | 2 - orochi/website/views.py | 110 ++---------------- requirements/base.txt | 16 +-- requirements/local.txt | 4 +- 18 files changed, 453 insertions(+), 168 deletions(-) create mode 100644 orochi/static/js/handlebars/dump.js create mode 100644 orochi/static/js/handlebars/dump_folder.js create mode 100644 orochi/templates/handlebars/dump.handlebars create mode 100644 orochi/templates/handlebars/dump_folder.handlebars diff --git a/compose/local/dask/Dockerfile b/compose/local/dask/Dockerfile index 57aacfec..aa92f6f1 100644 --- a/compose/local/dask/Dockerfile +++ b/compose/local/dask/Dockerfile @@ -27,7 +27,7 @@ RUN freshclam # Workers should have similar reqs as django WORKDIR / COPY ./requirements /requirements -RUN pip install uv==0.4.21 -e git+https://github.com/dadokkio/volatility3.git@7b0cb4facd1e1714a36793a27c0570461a3f02a1#egg=volatility3 \ +RUN pip install uv==0.4.25 -e git+https://github.com/dadokkio/volatility3.git@7b0cb4facd1e1714a36793a27c0570461a3f02a1#egg=volatility3 \ && uv pip install --no-cache --system -r /requirements/base.txt COPY ./compose/local/dask/prepare.sh /usr/bin/prepare.sh diff --git a/compose/local/django/Dockerfile b/compose/local/django/Dockerfile index ffc063bc..e888f429 100644 --- a/compose/local/django/Dockerfile +++ b/compose/local/django/Dockerfile @@ -44,7 +44,7 @@ RUN /usr/local/go/bin/go build FROM common-base WORKDIR / COPY ./requirements /requirements -RUN pip install uv==0.4.21 -e git+https://github.com/dadokkio/volatility3.git@7b0cb4facd1e1714a36793a27c0570461a3f02a1#egg=volatility3 \ +RUN pip install uv==0.4.25 -e git+https://github.com/dadokkio/volatility3.git@7b0cb4facd1e1714a36793a27c0570461a3f02a1#egg=volatility3 \ && uv pip install --no-cache --system -r /requirements/base.txt COPY ./compose/local/__init__.py /src/volatility3/volatility3/framework/constants/__init__.py diff --git a/examples/local_api.ipynb b/examples/local_api.ipynb index ffa29574..1a66aade 100644 --- a/examples/local_api.ipynb +++ b/examples/local_api.ipynb @@ -47,7 +47,7 @@ "}\n", "\n", "req = session.post(\n", - " f\"{url}/api/auth/\", data=data, cookies=first.cookies, verify=False, headers=headers\n", + " f\"{url}/api/auth/\", data=data, cookies=first.cookies, headers=headers, verify=False\n", ")\n", "if req.status_code != 200:\n", " print(req.text)\n", @@ -67,9 +67,10 @@ "metadata": {}, "outputs": [], "source": [ - "dumps = session.get(f\"{url}/api/dumps/\").json()\n", + "dumps = session.get(f\"{url}/api/dumps/\", verify=False).json()\n", "print(f\"{len(dumps)} dumps found\")\n", - "pprint(dumps[0])" + "if dumps: \n", + " pprint(dumps[0])" ] }, { @@ -85,16 +86,17 @@ "metadata": {}, "outputs": [], "source": [ - "\"\"\" TODO\n", - "files = {'upload': open('/home/DATA/AMF_MemorySamples/linux/sorpresa.zip','rb')}\n", - "values = {'operating_system': 'Linux', 'name': 'sorpresa'}\n", - "res = session.post(f\"{url}/api/dumps/\", files=files, data=values)\n", + "\n", + "files = {'upload': ('sorpresa.zip', open('/home/dadokkio/Insync/dadokkio@gmail.com/Google Drive/Lavoro/Agusta/DATA/AMF_MemorySamples/linux/sorpresa.zip','rb'))}\n", + "data = {\n", + " 'payload': '{\"operating_system\": \"Linux\", \"name\": \"sorpresa\", \"folder\": {\"name\": \"linux-samples\"}}'\n", + "}\n", + "res = session.post(f\"{url}/api/dumps/\", files=files, data=data, cookies=first.cookies, headers=headers, verify=False)\n", "if res.status_code == 200:\n", " pprint(res.json())\n", - " dump_pk = res.json()[\"pk\"]\n", + " dump_pk = res.json()[\"index\"]\n", "else:\n", - " print(res.status_code)\n", - "\"\"\"" + " print(res.status_code, res.text)" ] }, { @@ -110,20 +112,19 @@ "metadata": {}, "outputs": [], "source": [ - "\"\"\" TODO\n", "# This code requires a file on the server in the folder specified in the LOCAL_UPLOAD_PATH\n", "# settings folder\n", "\n", - "res = session.post(f\"{url}/api/dumps/import_local/\", files=(\n", - " ('operating_system', (None, 'Linux')),\n", - " ('name', (None, 'sasf3sfas33')),\n", - " ('filepath', (None, '/uploads/linux/linux-sample-4.bin')),\n", - " ))\n", + "files = {'upload': None}\n", + "data = {\n", + " 'payload': '{\"operating_system\": \"Linux\", \"name\": \"remote-test\", \"folder\": {\"name\": \"linux-samples\"}, \"local_folder\": \"/uploads/sorpresa.vmem\"}'\n", + "}\n", + "res = session.post(f\"{url}/api/dumps/\", files=files, data=data, cookies=first.cookies, headers=headers, verify=False)\n", "if res.status_code == 200:\n", " pprint(res.json())\n", + " dump_pk = res.json()[\"index\"]\n", "else:\n", - " print(res.status_code)\n", - "\"\"\"" + " print(res.status_code, res.text)" ] }, { @@ -139,16 +140,41 @@ "metadata": {}, "outputs": [], "source": [ - "res = session.get(f\"{url}/api/plugins/\")\n", + "res = session.get(f\"{url}/api/plugins/\", verify=False)\n", "if res.status_code == 200:\n", " plugins = res.json()\n", " print(f\"{len(plugins)} plugins found\")\n", " pprint(plugins[0])\n", - "res = session.get(f\"{url}/api/plugins/?operating_system=Other\")\n", + "else:\n", + " print(res.status_code, res.text) \n", + "res = session.get(f\"{url}/api/plugins/?operating_system=Other\", verify=False)\n", "if res.status_code == 200:\n", " plugins = res.json()\n", " print(f\"{len(plugins)} plugins found\")\n", - " pprint(plugins[0])" + " pprint(plugins[0])\n", + "else:\n", + " print(res.status_code, res.text) " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# GET PLUGINS FOR A SPECIFIC DUMP" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "res = session.get(f\"{url}/api/dumps/{dump_pk}/plugins\", verify=False)\n", + "if res.status_code == 200:\n", + " if example := res.json():\n", + " print(example[0])\n", + "else:\n", + " print(res.status_code, res.text) " ] }, { @@ -164,13 +190,11 @@ "metadata": {}, "outputs": [], "source": [ - "\"\"\" TODO\n", - "res = session.get(f\"{url}/api/dumps/{dump_pk}/results/\")\n", + "res = session.get(f\"{url}/api/dumps/{dump_pk}/plugins/{plugin_pk}/\", verify=False)\n", "if res.status_code == 200:\n", " pprint(res.json())\n", " result_pk = [x['pk'] for x in res.json() if x['plugin'] == 'linux.pslist.PsList'][0]\n", - " print(res.status_code)\n", - "\"\"\"" + " print(res.status_code)\n" ] }, { @@ -267,7 +291,7 @@ ], "metadata": { "kernelspec": { - "display_name": "orochi", + "display_name": "base", "language": "python", "name": "python3" }, diff --git a/orochi/api/models.py b/orochi/api/models.py index 622e0acf..f986d98a 100644 --- a/orochi/api/models.py +++ b/orochi/api/models.py @@ -7,7 +7,7 @@ from ninja.orm import create_schema from orochi.website.defaults import OSEnum -from orochi.website.models import Bookmark, CustomRule, Dump, Folder, Plugin +from orochi.website.models import Bookmark, CustomRule, Dump, Folder, Plugin, Result from orochi.ya.models import Rule @@ -158,18 +158,35 @@ class Meta: ################################################### # Dump ################################################### -class DumpSchema(ModelSchema): +class DumpIn(ModelSchema): + folder: Optional[FolderSchema] = None + local_folder: Optional[str] = None + password: Optional[str] = None + original_name: Optional[str] = None + class Meta: + model = Dump + fields = [ + "operating_system", + "description", + "comment", + "name", + "color", + ] + + +class DumpSchema(ModelSchema): folder: Optional[FolderSchema] = None + author: UserOutSchema = None class Meta: model = Dump fields = [ + "id", "index", "name", "color", "operating_system", - "author", "upload", "status", "description", @@ -211,6 +228,7 @@ class Meta: class ResultSmallOutSchema(Schema): name: str = Field(..., alias="plugin__name") comment: Optional[str] = Field(..., alias="plugin__comment") + id: int = Field(..., alias="plugin__id") ################################################### diff --git a/orochi/api/routers/dumps.py b/orochi/api/routers/dumps.py index 9f51bb8b..07436f2b 100644 --- a/orochi/api/routers/dumps.py +++ b/orochi/api/routers/dumps.py @@ -1,15 +1,21 @@ -from typing import List -from uuid import UUID +import shutil +from pathlib import Path +from typing import List, Optional +from uuid import UUID, uuid1 +from django.conf import settings +from django.db import transaction from django.http import HttpResponse from django.shortcuts import get_object_or_404 from guardian.shortcuts import get_objects_for_user -from ninja import Query, Router +from ninja import File, Query, Router, UploadedFile from ninja.security import django_auth from orochi.api.filters import DumpFilters, OperatingSytemFilters -from orochi.api.models import DumpInfoSchema, DumpSchema, ResultSmallOutSchema -from orochi.website.models import Dump, Result +from orochi.api.models import DumpIn, DumpInfoSchema, DumpSchema, ResultSmallOutSchema +from orochi.website.defaults import RESULT_STATUS_NOT_STARTED, RESULT_STATUS_RUNNING +from orochi.website.models import Dump, Folder, Result, UserPlugin +from orochi.website.views import index_f_and_f router = Router() @@ -62,6 +68,90 @@ def get_dump_info(request, pk: UUID): return dump +@router.post("/", url_name="create_index", response=DumpSchema, auth=django_auth) +def create_dump(request, payload: DumpIn, upload: Optional[UploadedFile] = File(None)): + """ + Creates a new dump index and handles the associated file uploads. This function processes the provided payload to create a dump entry in the database and manages file storage based on the input parameters. + + Args: + request: The HTTP request object. + payload (DumpIn): The data containing information about the dump to be created. + upload (Optional[UploadedFile]): An optional file to be uploaded. + + Returns: + DumpSchema: The created dump object. + + Raises: + HttpResponse: Returns a 400 Bad Request response if an error occurs during the process. + """ + + try: + if payload.folder: + folder, _ = Folder.objects.get_or_create( + name=payload.folder.name, user=request.user + ) + else: + folder = None + dump_index = str(uuid1()) + Path(f"{settings.MEDIA_ROOT}/{dump_index}").mkdir() + dump = Dump.objects.create( + name=payload.name, + color=payload.color, + comment=payload.comment, + operating_system=payload.operating_system, + folder=folder, + author=request.user, + index=dump_index, + ) + if payload.local_folder: + start = payload.local_folder + start = start.replace("/upload/upload", "/media/uploads") + filename = payload.original_name or Path(start).name + shutil.move(start, f"{settings.MEDIA_ROOT}/{dump_index}/{filename}") + dump.upload.name = f"{settings.MEDIA_URL}{dump_index}/{filename}" + move = False + elif upload: + dump.upload.save(Path(upload.name).name, upload) + move = True + else: + return HttpResponse("Bad Request", status=400) + dump.save() + Result.objects.bulk_create( + [ + Result( + plugin=up.plugin, + dump=dump, + result=( + RESULT_STATUS_RUNNING + if up.automatic + else RESULT_STATUS_NOT_STARTED + ), + ) + for up in UserPlugin.objects.filter( + plugin__operating_system__in=[ + dump.operating_system, + "Other", + ], + user=request.user, + plugin__disabled=False, + ) + ] + ) + + transaction.on_commit( + lambda: index_f_and_f( + dump.pk, + request.user.pk, + password=payload.password, + restart=None, + move=move, + ) + ) + return dump + except Exception as excp: + return HttpResponse(f"Bad Request ({excp})", status=400) + + @router.get( "/{idxs:pks}/plugins", url_name="dumps_plugins", @@ -93,7 +183,7 @@ def get_dump_plugins(request, pks: List[UUID], filters: Query[DumpFilters] = Non .filter(dump__index__in=dumps) .order_by("plugin__name") .distinct() - .values("plugin__name", "plugin__comment") + .values("plugin__name", "plugin__comment", "plugin__id") ) if filters and filters.result: res = res.filter(result=filters.result) diff --git a/orochi/static/js/handlebars/dump.js b/orochi/static/js/handlebars/dump.js new file mode 100644 index 00000000..115aafaf --- /dev/null +++ b/orochi/static/js/handlebars/dump.js @@ -0,0 +1,46 @@ +(function() { + var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {}; +templates['dump'] = template({"1":function(container,depth0,helpers,partials,data) { + return " \n"; +},"3":function(container,depth0,helpers,partials,data) { + return " \n"; +},"5":function(container,depth0,helpers,partials,data) { + return " \n"; +},"7":function(container,depth0,helpers,partials,data) { + return " \n"; +},"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) { + var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=container.hooks.helperMissing, alias3="function", alias4=container.escapeExpression, lookupProperty = container.lookupProperty || function(parent, propertyName) { + if (Object.prototype.hasOwnProperty.call(parent, propertyName)) { + return parent[propertyName]; + } + return undefined + }; + + return "\n
  • \n \n
  • \n\n"; +},"useData":true}); +})(); diff --git a/orochi/static/js/handlebars/dump_folder.js b/orochi/static/js/handlebars/dump_folder.js new file mode 100644 index 00000000..568f9560 --- /dev/null +++ b/orochi/static/js/handlebars/dump_folder.js @@ -0,0 +1,63 @@ +(function() { + var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {}; +templates['dump_folder'] = template({"1":function(container,depth0,helpers,partials,data) { + var stack1, lookupProperty = container.lookupProperty || function(parent, propertyName) { + if (Object.prototype.hasOwnProperty.call(parent, propertyName)) { + return parent[propertyName]; + } + return undefined + }; + + return " " + + container.escapeExpression(container.lambda(((stack1 = (depth0 != null ? lookupProperty(depth0,"foler") : depth0)) != null ? lookupProperty(stack1,"name") : stack1), depth0)) + + " "; +},"3":function(container,depth0,helpers,partials,data) { + return " - "; +},"5":function(container,depth0,helpers,partials,data) { + return " \n"; +},"7":function(container,depth0,helpers,partials,data) { + return " \n"; +},"9":function(container,depth0,helpers,partials,data) { + return " \n"; +},"11":function(container,depth0,helpers,partials,data) { + return " \n"; +},"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) { + var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=container.escapeExpression, alias3=container.hooks.helperMissing, alias4="function", lookupProperty = container.lookupProperty || function(parent, propertyName) { + if (Object.prototype.hasOwnProperty.call(parent, propertyName)) { + return parent[propertyName]; + } + return undefined + }; + + return "\n"; +},"useData":true}); +})(); diff --git a/orochi/templates/base.html b/orochi/templates/base.html index e07de37b..2d91e7d9 100644 --- a/orochi/templates/base.html +++ b/orochi/templates/base.html @@ -208,6 +208,8 @@ + +