From f4296b2573d58c16224dc43d4236c992cbcc7af3 Mon Sep 17 00:00:00 2001 From: Davide Arcuri Date: Tue, 16 Apr 2024 17:31:37 +0200 Subject: [PATCH] #1073 wip --- orochi/api/api.py | 2 + orochi/api/models.py | 14 +- orochi/api/routers/bookmarks.py | 83 ++++++++++++ orochi/api/routers/plugins.py | 2 +- orochi/api/routers/utils.py | 7 + orochi/static/css/style.css | 9 ++ orochi/templates/base.html | 4 +- orochi/templates/users/user_bookmarks.html | 28 ++-- orochi/templates/users/user_plugins.html | 2 +- orochi/templates/website/index.html | 1 - orochi/templates/website/partial_indices.html | 2 +- orochi/users/views.py | 16 +-- orochi/website/urls.py | 2 - orochi/website/views.py | 125 +++++++----------- 14 files changed, 191 insertions(+), 106 deletions(-) create mode 100644 orochi/api/routers/bookmarks.py diff --git a/orochi/api/api.py b/orochi/api/api.py index c64dd814..8dea779d 100644 --- a/orochi/api/api.py +++ b/orochi/api/api.py @@ -1,6 +1,7 @@ from ninja import NinjaAPI from orochi.api.routers.auth import router as auth_router +from orochi.api.routers.bookmarks import router as bookmarks_router from orochi.api.routers.dumps import router as dumps_router from orochi.api.routers.folders import router as folders_router from orochi.api.routers.plugins import router as plugins_router @@ -14,3 +15,4 @@ api.add_router("/dumps/", dumps_router, tags=["Dumps"]) api.add_router("/plugins/", plugins_router, tags=["Plugins"]) api.add_router("/utils/", utils_router, tags=["Utils"]) +api.add_router("/bookmars/", bookmarks_router, tags=["Bookmars"]) diff --git a/orochi/api/models.py b/orochi/api/models.py index 433206a8..ad766799 100644 --- a/orochi/api/models.py +++ b/orochi/api/models.py @@ -6,7 +6,7 @@ from ninja.orm import create_schema from orochi.website.defaults import OSEnum -from orochi.website.models import Dump, Folder, Plugin, Result +from orochi.website.models import Bookmark, Dump, Folder, Plugin, Result ################################################### # Auth @@ -205,3 +205,15 @@ class Meta: class ResultSmallOutSchema(Schema): name: str = Field(..., alias="plugin__name") comment: str = Field(..., alias="plugin__comment") + + +################################################### +# Bookmarks +################################################### +class BookmarksSchema(ModelSchema): + user: UserOutSchema = None + indexes: List[DumpSchema] = [] + + class Meta: + model = Bookmark + fields = ["id", "name", "icon", "star", "query"] diff --git a/orochi/api/routers/bookmarks.py b/orochi/api/routers/bookmarks.py new file mode 100644 index 00000000..dbf0f0c3 --- /dev/null +++ b/orochi/api/routers/bookmarks.py @@ -0,0 +1,83 @@ +from typing import List + +from django.shortcuts import get_object_or_404 +from ninja import Router +from ninja.security import django_auth + +from orochi.api.models import BookmarksSchema, ErrorsOut, SuccessResponse +from orochi.website.models import Bookmark + +router = Router() + + +@router.get("/", auth=django_auth, response=List[BookmarksSchema]) +def list_bookmarks(request): + """ + Retrieves a list of bookmarks for the current user. + + Returns: + QuerySet: A queryset of bookmarks belonging to the current user. + """ + return Bookmark.objects.filter(user=request.user) + + +@router.delete( + "/{int:id}", + auth=django_auth, + url_name="delete_bookmark", + response={200: SuccessResponse, 400: ErrorsOut}, +) +def delete_bookmarks(request, id: int): + """ + Deletes a bookmark by its ID. + + Args: + id (int): The ID of the bookmark to delete. + + Returns: + tuple: A tuple containing the status code and a message dictionary. + + Raises: + Exception: If an error occurs during the deletion process. + """ + bookmark = get_object_or_404(Bookmark, pk=id, user=request.user) + name = bookmark.name + try: + bookmark.delete() + return 200, {"message": f"Bookmark {name} deleted"} + except Exception as excp: + return 400, {"errors": str(excp)} + + +@router.post( + "/{int:id}/star/{star}", + auth=django_auth, + url_name="star_bookmark", + response={200: SuccessResponse, 400: ErrorsOut}, +) +def star_bookmark(request, id: int, star: bool): + """ + Stars or unstars a bookmark. + + Args: + id (int): The ID of the bookmark to star/unstar. + star (bool): True to star the bookmark, False to unstar it. + + Returns: + tuple: A tuple containing the HTTP status code and a message dict. + + Raises: + Exception: If an error occurs during the process. + """ + try: + bookmark = get_object_or_404(Bookmark, pk=id, user=request.user) + name = bookmark.name + bookmark.star = star + bookmark.save() + return 200, { + "message": ( + f"Bookmark {name} starred" if star else f"Bookmark {name} unstarred" + ) + } + except Exception as excp: + return 400, {"errors": str(excp)} diff --git a/orochi/api/routers/plugins.py b/orochi/api/routers/plugins.py index 5b443633..72b843da 100644 --- a/orochi/api/routers/plugins.py +++ b/orochi/api/routers/plugins.py @@ -156,7 +156,7 @@ def update_plugin(request, name: str, data: PluginInSchema): @router.post( "/{str:name}/enable/{enable}", auth=django_auth, - url_name="enable", + url_name="enable_plugin", response={200: SuccessResponse, 400: ErrorsOut}, ) def enable_plugin(request, name: str, enable: bool): diff --git a/orochi/api/routers/utils.py b/orochi/api/routers/utils.py index ac4a892b..3067b3f5 100644 --- a/orochi/api/routers/utils.py +++ b/orochi/api/routers/utils.py @@ -5,11 +5,14 @@ import geoip2.database from dask.distributed import Client from django.conf import settings +from django.shortcuts import get_object_or_404 from geoip2.errors import GeoIP2Error +from guardian.shortcuts import get_objects_for_user from ninja import Router from ninja.security import django_auth from orochi.api.models import DaskStatusOut, ErrorsOut +from orochi.website.models import Dump router = Router() @@ -112,6 +115,10 @@ def maxmind(request, ip: str): @router.get("/vt", url_name="vt", response={200: Any, 400: ErrorsOut}, auth=django_auth) def get_extracted_dump_vt_report(request, path: str): path = Path(path) + index = path.parts[2] + dump = get_object_or_404(Dump, index=index) + if dump not in get_objects_for_user(request.user, "website.can_see"): + return 403, ErrorsOut(errors="You do not have permission to access this dump.") if path.exists(): return 200, json.loads(open(path, "r").read()) return 400, ErrorsOut(errors="File not found.") diff --git a/orochi/static/css/style.css b/orochi/static/css/style.css index 55482b61..65567987 100644 --- a/orochi/static/css/style.css +++ b/orochi/static/css/style.css @@ -282,3 +282,12 @@ main.view { div.wunderbaum { height: 95%; } + +/******************************************************** +DATATABLE MIN HEIGHT +********************************************************/ + +.dataTables_scrollBody { + max-height: unset !important; + height: unset !important; +} diff --git a/orochi/templates/base.html b/orochi/templates/base.html index b670c7c6..d3650073 100644 --- a/orochi/templates/base.html +++ b/orochi/templates/base.html @@ -144,10 +144,10 @@ {% block fullpage %}
-
+
{% block sidebar %}{% endblock sidebar %}
-
+
{% block content %}{% endblock content %}
{% block offcanvas %} {% endblock offcanvas %} diff --git a/orochi/templates/users/user_bookmarks.html b/orochi/templates/users/user_bookmarks.html index 2d156827..70e54d6d 100644 --- a/orochi/templates/users/user_bookmarks.html +++ b/orochi/templates/users/user_bookmarks.html @@ -140,21 +140,25 @@ // DELETE BOOKMARK $(document).on("click", ".remove-index", function (e) { var bookmark = this; - var bookmark_name = $(this).data('name'); var bookmark_pk = $(this).data('up'); bootbox.confirm("Are you sure??", function (result) { if (result === true) { + + $.ajaxSetup({ + headers: { 'X-CSRFToken': $('input[name="csrfmiddlewaretoken"]').val() } + }); + + var url = "{% url 'api:delete_bookmark' id=123 %}".replace(/123/, bookmark_pk); $.ajax({ - url: "{% url 'website:delete_bookmark' %}", - data: { 'bookmark': bookmark_pk, 'csrfmiddlewaretoken': $("input[name=csrfmiddlewaretoken").val() }, - method: 'post', + url: url, + method: 'delete', dataType: 'json', success: function (data) { $(bookmark).parent().parent().remove(); $.toast({ title: 'Bookmark status!', - content: 'Bookmark ' + bookmark_name + ' deleted.', + content: data.message, type: 'success', delay: 5000 }); @@ -162,7 +166,7 @@ error: function () { $.toast({ title: 'Bookmark status!', - content: 'Error during submission.', + content: data.message, type: 'error', delay: 5000 }); @@ -179,9 +183,13 @@ var bookmark_pk = $(this).data('up'); var bookmark_star = $(this).data('star'); + $.ajaxSetup({ + headers: { 'X-CSRFToken': $('input[name="csrfmiddlewaretoken"]').val() } + }); + + var url = "{% url 'api:star_bookmark' id=123 star=456 %}".replace(/123/, bookmark_pk).replace(/456/, !bookmark_star); $.ajax({ - url: "{% url 'website:star_bookmark' %}", - data: { 'bookmark': bookmark_pk, 'enable': !bookmark_star, 'csrfmiddlewaretoken': $("input[name=csrfmiddlewaretoken").val() }, + url: url, method: 'post', dataType: 'json', success: function (data) { @@ -193,7 +201,7 @@ } $.toast({ title: 'Bookmark status!', - content: 'Bookmark ' + bookmark_name + ' updated.', + content: data.message, type: 'success', delay: 5000 }); @@ -201,7 +209,7 @@ error: function () { $.toast({ title: 'Bookmark status!', - content: 'Error during submission.', + content: data.message, type: 'error', delay: 5000 }); diff --git a/orochi/templates/users/user_plugins.html b/orochi/templates/users/user_plugins.html index c553909b..463baf1f 100644 --- a/orochi/templates/users/user_plugins.html +++ b/orochi/templates/users/user_plugins.html @@ -253,7 +253,7 @@ headers: { 'X-CSRFToken': $('input[name="csrfmiddlewaretoken"]').val() } }); - var url = "{% url 'api:enable' name=123 enable=456 %}".replace(/123/, plg_name).replace(/456/, plg.checked); + var url = "{% url 'api:enable_plugin' name=123 enable=456 %}".replace(/123/, plg_name).replace(/456/, plg.checked); $.ajax({ url: url, method: 'post', diff --git a/orochi/templates/website/index.html b/orochi/templates/website/index.html index a903359c..c2d5cd1e 100644 --- a/orochi/templates/website/index.html +++ b/orochi/templates/website/index.html @@ -215,7 +215,6 @@
History Log
$(document).on("click", ".maxmind-info", function(){ var btn = $(this); var ip = btn.data('ip'); - $.ajax({ url: "{% url 'api:maxmind' %}", data: { 'ip': ip }, diff --git a/orochi/templates/website/partial_indices.html b/orochi/templates/website/partial_indices.html index c2ddd9b5..4c759c98 100644 --- a/orochi/templates/website/partial_indices.html +++ b/orochi/templates/website/partial_indices.html @@ -20,7 +20,7 @@ {% else %} {% endif %} - {{name}} + {{name|truncatechars:35}} {% if status != 2 and status != 5 and status != 6 %} diff --git a/orochi/users/views.py b/orochi/users/views.py index fb468a22..28511f44 100644 --- a/orochi/users/views.py +++ b/orochi/users/views.py @@ -23,9 +23,7 @@ class UserYaraView(LoginRequiredMixin, DetailView): def get_queryset(self) -> QuerySet[Any]: mine = self.request.user == User.objects.get(username=self.kwargs["username"]) qs = super().get_queryset() - if mine: - return qs - return qs.none() + return qs if mine else qs.none() user_yara_view = UserYaraView.as_view() @@ -42,23 +40,21 @@ def post(self, request, *args, **kwargs): plugin_ids = request.POST.getlist("id[]") for plugin in plugin_ids: up = get_object_or_404(UserPlugin, pk=plugin, user=request.user) - up.automatic = bool(action == "enable") + up.automatic = action == "enable" up.save() self.object = self.get_object() context = self.get_context_data(object=self.object) messages.add_message( request, messages.SUCCESS if action == "enable" else messages.ERROR, - "{} plugins {}d".format(len(plugin_ids), action), + f"{len(plugin_ids)} plugins {action}d", ) return self.render_to_response(context) def get_queryset(self) -> QuerySet[Any]: mine = self.request.user == User.objects.get(username=self.kwargs["username"]) qs = super().get_queryset() - if mine: - return qs - return qs.none() + return qs if mine else qs.none() user_plugins_view = UserPluginView.as_view() @@ -73,9 +69,7 @@ class UserBookmarksView(LoginRequiredMixin, DetailView): def get_queryset(self) -> QuerySet[Any]: mine = self.request.user == User.objects.get(username=self.kwargs["username"]) qs = super().get_queryset() - if mine: - return qs - return qs.none() + return qs if mine else qs.none() user_bookmarks_view = UserBookmarksView.as_view() diff --git a/orochi/website/urls.py b/orochi/website/urls.py index f39dac92..fe98fc87 100644 --- a/orochi/website/urls.py +++ b/orochi/website/urls.py @@ -75,8 +75,6 @@ def to_url(self, value): name="diff_view", ), # USER PAGE - path("star_bookmark", views.star_bookmark, name="star_bookmark"), - path("delete_bookmark", views.delete_bookmark, name="delete_bookmark"), path("edit_bookmark", views.edit_bookmark, name="edit_bookmark"), path("add_bookmark", views.add_bookmark, name="add_bookmark"), # ADMIN diff --git a/orochi/website/views.py b/orochi/website/views.py index 37215158..71aa82b1 100644 --- a/orochi/website/views.py +++ b/orochi/website/views.py @@ -30,6 +30,7 @@ from django.template.loader import render_to_string from django.template.response import TemplateResponse from django.utils.text import slugify +from django.views.decorators.http import require_http_methods from elasticsearch import Elasticsearch from elasticsearch_dsl import Search from guardian.shortcuts import assign_perm, get_objects_for_user, get_perms, remove_perm @@ -146,10 +147,9 @@ def handle_uploaded_file(index, plugin, f): @login_required @user_passes_test(is_not_readonly) +@require_http_methods(["POST"]) def plugin(request): """Prepares for plugin resubmission on selected index with/without parameters""" - if request.method != "POST": - return JsonResponse({"status_code": 405, "error": "Method Not Allowed"}) indexes = request.POST.get("selected_indexes").split(",") plugin = get_object_or_404(Plugin, name=request.POST.get("selected_plugin")) get_object_or_404(UserPlugin, plugin=plugin, user=request.user) @@ -763,61 +763,59 @@ def restart(request): # EXPORT ############################## @login_required +@require_http_methods(["GET"]) def export(request): """Export extracted dump to misp""" - if request.method == "GET": - filepath = request.GET.get("path") - _, _, index, plugin, _ = filepath.split("/") - misp_info = get_object_or_404(Service, name=SERVICE_MISP) - dump = get_object_or_404(Dump, index=index) - _ = get_object_or_404(Plugin, name=plugin) + filepath = request.GET.get("path") + _, _, index, plugin, _ = filepath.split("/") + misp_info = get_object_or_404(Service, name=SERVICE_MISP) + dump = get_object_or_404(Dump, index=index) + _ = get_object_or_404(Plugin, name=plugin) - plugin = plugin.lower() + plugin = plugin.lower() - # CREATE GENERIC EVENT - misp = PyMISP(misp_info.url, misp_info.key, False, proxies=misp_info.proxy) - event = MISPEvent() - event.info = f"From orochi: {plugin}@{dump.name}" + # CREATE GENERIC EVENT + misp = PyMISP(misp_info.url, misp_info.key, False, proxies=misp_info.proxy) + event = MISPEvent() + event.info = f"From orochi: {plugin}@{dump.name}" - # CREATE FILE OBJ - file_obj = FileObject(filepath) - event.add_object(file_obj) + # CREATE FILE OBJ + file_obj = FileObject(filepath) + event.add_object(file_obj) - es_client = Elasticsearch([settings.ELASTICSEARCH_URL]) - if s := ( - Search(using=es_client, index=f"{index}_{plugin}") - .query({"match": {"down_path": filepath}}) - .execute() - ): - s = s[0].to_dict() - - # ADD CLAMAV SIGNATURE - if s.get("clamav"): - clamav_obj = MISPObject("av-signature") - clamav_obj.add_attribute("signature", value=s["clamav"]) - clamav_obj.add_attribute("software", value="clamav") - file_obj.add_reference(clamav_obj.uuid, "attributed-to") - event.add_object(clamav_obj) - - # ADD VT SIGNATURE - if Path(f"{filepath}.vt.json").exists(): - with open(f"{filepath}.vt.json", "r") as f: - vt = json.load(f) - vt_obj = MISPObject("virustotal-report") - vt_obj.add_attribute( - "last-submission", value=vt.get("scan_date", "") - ) - vt_obj.add_attribute( - "detection-ratio", - value=f'{vt.get("positives", 0)}/{vt.get("total", 0)}', - ) - vt_obj.add_attribute("permalink", value=vt.get("permalink", "")) - file_obj.add_reference(vt.uuid, "attributed-to") - event.add_object(vt_obj) + es_client = Elasticsearch([settings.ELASTICSEARCH_URL]) + if s := ( + Search(using=es_client, index=f"{index}_{plugin}") + .query({"match": {"down_path": filepath}}) + .execute() + ): + s = s[0].to_dict() + + # ADD CLAMAV SIGNATURE + if s.get("clamav"): + clamav_obj = MISPObject("av-signature") + clamav_obj.add_attribute("signature", value=s["clamav"]) + clamav_obj.add_attribute("software", value="clamav") + file_obj.add_reference(clamav_obj.uuid, "attributed-to") + event.add_object(clamav_obj) + + # ADD VT SIGNATURE + if Path(f"{filepath}.vt.json").exists(): + with open(f"{filepath}.vt.json", "r") as f: + vt = json.load(f) + vt_obj = MISPObject("virustotal-report") + vt_obj.add_attribute("last-submission", value=vt.get("scan_date", "")) + vt_obj.add_attribute( + "detection-ratio", + value=f'{vt.get("positives", 0)}/{vt.get("total", 0)}', + ) + vt_obj.add_attribute("permalink", value=vt.get("permalink", "")) + file_obj.add_reference(vt.uuid, "attributed-to") + event.add_object(vt_obj) - misp.add_event(event) - return JsonResponse({"success": True}) - return JsonResponse({"status_code": 405, "error": "Method Not Allowed"}) + misp.add_event(event) + return JsonResponse({"success": True}) + return JsonResponse({"status_code": 404, "error": "No data found"}) ############################## @@ -913,30 +911,6 @@ def edit_bookmark(request): return JsonResponse(data) -@login_required -def delete_bookmark(request): - """Delete bookmark in user settings""" - if request.method == "POST": - bookmark = request.POST.get("bookmark") - up = get_object_or_404(Bookmark, pk=bookmark, user=request.user) - up.delete() - return JsonResponse({"ok": True}) - return JsonResponse({"status_code": 405, "error": "Method Not Allowed"}) - - -@login_required -def star_bookmark(request): - """Star/unstar bookmark in user settings""" - if request.method == "POST": - bookmark = request.POST.get("bookmark") - enable = request.POST.get("enable") - up = get_object_or_404(Bookmark, pk=bookmark, user=request.user) - up.star = enable == "true" - up.save() - return JsonResponse({"ok": True}) - return JsonResponse({"status_code": 405, "error": "Method Not Allowed"}) - - @login_required def bookmarks(request, indexes, plugin, query=None): """Open index but from a stored configuration of indexes and plugin""" @@ -956,9 +930,8 @@ def bookmarks(request, indexes, plugin, query=None): ############################## @login_required @user_passes_test(is_not_readonly) +@require_http_methods(["GET"]) def folder_create(request): - if request.method != "GET": - raise Http404("404") return JsonResponse( { "html_form": render_to_string(