Skip to content
Merged

Engine #2685

Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
9b3ea29
engine
0ssigeno Jan 15, 2025
47dbca7
More stuff
0ssigeno Jan 15, 2025
0248f08
More engine
0ssigeno Jan 15, 2025
ebdda39
More engine
0ssigeno Jan 15, 2025
9926348
Removed old code
0ssigeno Jan 15, 2025
bc2351b
Blake
0ssigeno Jan 15, 2025
92e2507
Fixes
0ssigeno Jan 15, 2025
a170e55
Fixes
0ssigeno Jan 15, 2025
4987106
Fixes
0ssigeno Jan 15, 2025
4fe8326
Fixes
0ssigeno Jan 15, 2025
5d8c47a
Fix
0ssigeno Jan 15, 2025
66c3b96
Fix merge with dict
0ssigeno Jan 15, 2025
88966c6
Fix
0ssigeno Jan 15, 2025
295dc06
More tests
0ssigeno Jan 15, 2025
96ec0a4
Added another engine
0ssigeno Jan 17, 2025
7a0e260
Analyzable
0ssigeno Jan 27, 2025
b0080ea
Merge branch 'develop' into engine
0ssigeno Jan 27, 2025
bfdb7be
Blake
0ssigeno Jan 27, 2025
a9de79f
Fixes
0ssigeno Jan 27, 2025
57a033c
Fixes
0ssigeno Jan 27, 2025
140ca3b
Fixes
0ssigeno Jan 27, 2025
c135206
Added error
0ssigeno Jan 27, 2025
caad40e
Added delete
0ssigeno Jan 27, 2025
a504028
Fixes
0ssigeno Jan 28, 2025
888fb1b
Fixes
0ssigeno Jan 28, 2025
327aba5
Blake
0ssigeno Jan 28, 2025
96b9ef1
Fix
0ssigeno Jan 28, 2025
91d998e
More fixes
0ssigeno Jan 28, 2025
35f6e31
Fixes
0ssigeno Jan 28, 2025
caecc17
Fixes
0ssigeno Jan 28, 2025
0e9258c
update
0ssigeno Jan 28, 2025
f7d6d2a
Fixes
0ssigeno Jan 28, 2025
650f0ab
Fixes
0ssigeno Jan 29, 2025
a447168
Fix typo
0ssigeno Jan 29, 2025
4ec6970
More fixes
0ssigeno Jan 29, 2025
1f6a274
Merge remote-tracking branch 'origin/develop' into engine
0ssigeno Jan 29, 2025
3a67780
More fixes
0ssigeno Jan 29, 2025
e1b838f
Fixed files
0ssigeno Jan 29, 2025
e967ddf
Update deepsource
0ssigeno Jan 29, 2025
30af478
Fixes
0ssigeno Jan 29, 2025
2f1ea6b
Typo
0ssigeno Jan 29, 2025
5ada941
Fixes
0ssigeno Jan 30, 2025
1e7834f
Merge branch 'develop' into engine
0ssigeno Jan 30, 2025
3cc15c8
Fixes
0ssigeno Jan 30, 2025
0aa3b39
Fixes
0ssigeno Feb 5, 2025
d442a2d
Fixes
0ssigeno Feb 10, 2025
0069872
Merge branch 'develop' into engine
0ssigeno Feb 11, 2025
0b9e202
Blake
0ssigeno Feb 11, 2025
eed5fbb
Fix
0ssigeno Feb 11, 2025
149e606
Fix
0ssigeno Feb 11, 2025
14c791e
Typo
0ssigeno Feb 11, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 10 additions & 9 deletions api_app/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,8 @@ class JobAdminView(CustomAdminView):
"id",
"status",
"user",
"observable_name",
"observable_classification",
"file_name",
"file_mimetype",
"get_analyzable_name",
"get_analyzable_classification",
"received_request_time",
"analyzers_executed",
"connectors_executed",
Expand All @@ -64,13 +62,16 @@ class JobAdminView(CustomAdminView):
"user",
"status",
)
search_fields = (
"md5",
"observable_name",
"file_name",
)
list_filter = ("status", "user", "tags")

@admin.display(description="Name")
def get_analyzable_name(self, instance):
return instance.analyzable.name

@admin.display(description="Classification")
def get_analyzable_classification(self, instance):
return instance.analyzable.classification

@staticmethod
def has_add_permission(request: HttpRequest) -> bool:
return False
Expand Down
Empty file.
11 changes: 11 additions & 0 deletions api_app/analyzables_manager/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from django.contrib import admin

from api_app.analyzables_manager.models import Analyzable


@admin.register(Analyzable)
class AnalyzableAdmin(admin.ModelAdmin):
list_display = ["pk", "name", "sha1", "sha256", "md5"]
search_fields = ["name", "sha1", "sha256", "md5"]
ordering = ["name"]
list_filter = ["discovery_date"]
Empty file.
70 changes: 70 additions & 0 deletions api_app/analyzables_manager/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Generated by Django 4.2.17 on 2025-01-22 08:59

from django.db import migrations, models
from django.utils.timezone import now

import api_app.defaults


class Migration(migrations.Migration):

initial = True

dependencies = []

operations = [
migrations.CreateModel(
name="Analyzable",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("md5", models.CharField(max_length=255, unique=True, editable=False)),
(
"sha256",
models.CharField(max_length=255, unique=True, editable=False),
),
("sha1", models.CharField(max_length=255, unique=True, editable=False)),
("name", models.CharField(max_length=255)),
(
"mimetype",
models.CharField(
blank=True, max_length=80, null=True, default=None
),
),
(
"file",
models.FileField(
null=True,
default=None,
blank=True,
upload_to=api_app.defaults.file_directory_path,
),
),
(
"classification",
models.CharField(
max_length=100,
choices=[
("ip", "Ip"),
("url", "Url"),
("domain", "Domain"),
("hash", "Hash"),
("generic", "Generic"),
("file", "File"),
],
),
),
("discovery_date", models.DateTimeField(default=now)),
],
options={
"abstract": False,
},
),
]
55 changes: 55 additions & 0 deletions api_app/analyzables_manager/migrations/0002_migrate_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
from django.db import migrations


def migrate(apps, schema_editor):
Job = apps.get_model("api_app", "Job")
Analyzable = apps.get_model("analyzables_manager", "Analyzable")
for job in Job.objects.all().order_by("received_request_time"):
if job.is_sample:
obj, created = Analyzable.objects.get_or_create(
md5=job.md5,
defaults={
"file": job.file,
"mimetype": job.file_mimetype,
"name": job.file_name,
"md5": job.md5,
"classification": "sample",
"discovery_date": job.received_request_time,
},
)
if created:
p = job.file.path
try:
p.rename(p.parent / job.md5)
except Exception:
...
else:
job.file.name = job.md5
else:
obj, created = Analyzable.objects.get_or_create(
md5=job.md5,
defaults={
"name": job.observable_name,
"md5": job.md5,
"classification": job.observable_classification,
"discovery_date": job.received_request_time,
},
)
if created:
obj.full_clean()
obj.save()
job.analyzable = obj
job.save()


class Migration(migrations.Migration):

dependencies = [
("contenttypes", "0002_remove_content_type_name"),
("api_app", "0067_add_analyzable"),
("analyzables_manager", "0001_initial"),
]

operations = [
migrations.RunPython(migrate, migrations.RunPython.noop),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Generated by Django 4.2.17 on 2025-01-23 14:38

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("analyzables_manager", "0002_migrate_data"),
]

operations = [
migrations.AddIndex(
model_name="analyzable",
index=models.Index(
fields=["classification"], name="analyzables_classif_adf7ca_idx"
),
),
migrations.AddIndex(
model_name="analyzable",
index=models.Index(
fields=["mimetype"], name="analyzables_mimetyp_321d7d_idx"
),
),
]
Empty file.
92 changes: 92 additions & 0 deletions api_app/analyzables_manager/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
from typing import Type, Union

from django.core.exceptions import ValidationError
from django.db import models
from django.utils.timezone import now

from api_app.analyzables_manager.queryset import AnalyzableQuerySet
from api_app.choices import Classification
from api_app.data_model_manager.models import (
BaseDataModel,
DomainDataModel,
FileDataModel,
IPDataModel,
)
from api_app.defaults import file_directory_path
from api_app.helpers import calculate_md5, calculate_sha1, calculate_sha256


class Analyzable(models.Model):
name = models.CharField(max_length=255)
discovery_date = models.DateTimeField(default=now)
md5 = models.CharField(max_length=255, unique=True, editable=False)
sha256 = models.CharField(max_length=255, unique=True, editable=False)
sha1 = models.CharField(max_length=255, unique=True, editable=False)
classification = models.CharField(max_length=100, choices=Classification.choices)
mimetype = models.CharField(max_length=80, blank=True, null=True, default=None)
file = models.FileField(
upload_to=file_directory_path, null=True, blank=True, default=None
)

objects = AnalyzableQuerySet.as_manager()

class Meta:
indexes = [
models.Index(fields=["classification"]),
models.Index(fields=["mimetype"]),
]

def __str__(self):
return self.name

@property
def analyzed_object(self):
return self.file if self.is_sample else self.name

@property
def is_sample(self) -> bool:
return self.classification == Classification.FILE.value

def get_data_model_class(self) -> Type[BaseDataModel]:
if self.classification == Classification.IP.value:
return IPDataModel
elif self.classification in [
Classification.URL.value,
Classification.DOMAIN.value,
]:
return DomainDataModel
elif self.classification in [
Classification.HASH.value,
Classification.FILE.value,
]:
return FileDataModel
else:
raise NotImplementedError()

def _set_hashes(self, value: Union[str, bytes]):
if isinstance(value, str):
value = value.encode("utf-8")
if not self.md5:
self.md5 = calculate_md5(value)
if not self.sha256:
self.sha256 = calculate_sha256(value)
if not self.sha1:
self.sha1 = calculate_sha1(value)

def clean(self):
if self.classification == Classification.FILE.value:
if not self.mimetype or not self.file:
raise ValidationError("Mimetype and file must be set for samples")
content = self.read()
else:
if self.mimetype or self.file:
raise ValidationError(
"Mimetype and file must not be set for observables"
)
content = self.name
self._set_hashes(content)

def read(self) -> bytes:
if self.classification == Classification.FILE.value:
self.file.seek(0)
return self.file.read()
22 changes: 22 additions & 0 deletions api_app/analyzables_manager/queryset.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from django.db.models import QuerySet


class AnalyzableQuerySet(QuerySet):

def visible_for_user(self, user):
from api_app.models import Job

jobs = (
Job.objects.visible_for_user(user)
.values("analyzable")
.distinct()
.values_list("pk", flat=True)
)
return self.filter(pk__in=jobs)

def create(self, *args, **kwargs):
obj = self.model(**kwargs)
self._for_write = True
obj.full_clean()
obj.save(force_insert=True, using=self.db)
return obj
12 changes: 12 additions & 0 deletions api_app/analyzables_manager/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from rest_framework.serializers import ModelSerializer

from api_app.analyzables_manager.models import Analyzable
from api_app.serializers.job import JobRelatedField


class AnalyzableSerializer(ModelSerializer):
jobs = JobRelatedField(many=True, read_only=True)

class Meta:
model = Analyzable
fields = "__all__"
19 changes: 19 additions & 0 deletions api_app/analyzables_manager/signals.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from django.db import models
from django.dispatch import receiver

from api_app.analyzables_manager.models import Analyzable


@receiver(models.signals.pre_delete, sender=Analyzable)
def pre_delete_analyzable(sender, instance: Analyzable, **kwargs):
"""
Signal receiver for the pre_delete signal of the Analyzable model.
Deletes the associated file if it exists.

Args:
sender (Model): The model class sending the signal.
instance (Analyzable): The instance of the model being deleted.
**kwargs: Additional keyword arguments.
"""
if instance.file:
instance.file.delete()
18 changes: 18 additions & 0 deletions api_app/analyzables_manager/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# This file is a part of IntelOwl https://github.com/intelowlproject/IntelOwl
# See the file 'LICENSE' for copying permission.

from django.urls import include, path
from rest_framework import routers

from api_app.analyzables_manager.views import AnalyzableViewSet

# Routers provide an easy way of automatically determining the URL conf.


router = routers.DefaultRouter(trailing_slash=False)
router.register(r"analyzable", AnalyzableViewSet, basename="analyzable")

urlpatterns = [
# Viewsets
path(r"", include(router.urls)),
]
14 changes: 14 additions & 0 deletions api_app/analyzables_manager/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from rest_framework import viewsets
from rest_framework.permissions import IsAuthenticated

from api_app.analyzables_manager.serializers import AnalyzableSerializer


class AnalyzableViewSet(viewsets.ReadOnlyModelViewSet):

serializer_class = AnalyzableSerializer
permission_classes = [IsAuthenticated]

def get_queryset(self):
user = self.request.user
return super().get_queryset().visible_for_user(user)
Loading
Loading